From b38965dc4b7567586eec2837a00761d7c717e327 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Fri, 14 Aug 2026 13:49:52 +0700 Subject: [PATCH] refactor(web): drop TypeScript for plain JavaScript MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every .ts becomes .js, every lang="ts" becomes lang-less, and the type declarations go with them: types.ts held nothing but types, so it is deleted outright. Tooling follows. typescript, svelte-check, typescript-eslint and @types/sql.js are uninstalled; tsconfig.json becomes jsconfig.json, which still extends the generated SvelteKit config so $lib and $app resolve in an editor; `npm run lint` is now ESLint alone, and CI's comment about it covering the type check goes too. What this gives up, stated plainly: a mistyped column name like row.nguvan used to fail the build and now renders blank, and the datasets.json-to-CONTENT cross-check is back to throwing at module load rather than at compile time. The runtime guard for the latter is still there and still throws loudly. Two mechanical notes. The svelte/no-navigation-without-resolve rule started flagging the footer's source link, which points at an off-site article — without type information the rule can no longer tell an external URL from a route, so that one line carries a disable comment. And Vitest's include pattern had to follow the tests to .js. Lint, 25 tests and the build all pass. --- .github/workflows/deploy-pages.yml | 4 +- CLAUDE.md | 7 +- README.md | 4 +- docs/data-pipeline.md | 2 +- docs/system-architecture.md | 8 +- parser/README.md | 2 +- web/eslint.config.js | 17 +- web/{tsconfig.json => jsconfig.json} | 9 +- web/package-lock.json | 473 ------------------ web/package.json | 6 +- ...dmission-blocks.ts => admission-blocks.js} | 25 +- ...locks.test.ts => admission-blocks.test.js} | 3 +- web/src/lib/components/custom-query.svelte | 25 +- web/src/lib/components/score-table.svelte | 7 +- web/src/lib/components/search-form.svelte | 22 +- web/src/lib/components/student-detail.svelte | 15 +- web/src/lib/{datasets.ts => datasets.js} | 13 +- web/src/lib/{query-mode.ts => query-mode.js} | 14 +- ...{query-mode.test.ts => query-mode.test.js} | 0 web/src/lib/{search.ts => search.js} | 20 +- .../lib/{sql-presets.ts => sql-presets.js} | 6 +- .../{sqlite.svelte.ts => sqlite.svelte.js} | 46 +- web/src/lib/{subjects.ts => subjects.js} | 8 +- web/src/lib/{to-ascii.ts => to-ascii.js} | 4 +- .../{to-ascii.test.ts => to-ascii.test.js} | 2 +- web/src/lib/types.ts | 56 --- web/src/routes/{+layout.ts => +layout.js} | 0 web/src/routes/+layout.svelte | 2 +- web/src/routes/+page.svelte | 5 +- .../routes/[dataset]/{+page.ts => +page.js} | 5 +- web/src/routes/[dataset]/+page.svelte | 21 +- web/{vite.config.ts => vite.config.js} | 2 +- 32 files changed, 121 insertions(+), 712 deletions(-) rename web/{tsconfig.json => jsconfig.json} (63%) rename web/src/lib/{admission-blocks.ts => admission-blocks.js} (88%) rename web/src/lib/{admission-blocks.test.ts => admission-blocks.test.js} (96%) rename web/src/lib/{datasets.ts => datasets.js} (87%) rename web/src/lib/{query-mode.ts => query-mode.js} (81%) rename web/src/lib/{query-mode.test.ts => query-mode.test.js} (100%) rename web/src/lib/{search.ts => search.js} (82%) rename web/src/lib/{sql-presets.ts => sql-presets.js} (98%) rename web/src/lib/{sqlite.svelte.ts => sqlite.svelte.js} (79%) rename web/src/lib/{subjects.ts => subjects.js} (84%) rename web/src/lib/{to-ascii.ts => to-ascii.js} (89%) rename web/src/lib/{to-ascii.test.ts => to-ascii.test.js} (97%) delete mode 100644 web/src/lib/types.ts rename web/src/routes/{+layout.ts => +layout.js} (100%) rename web/src/routes/[dataset]/{+page.ts => +page.js} (71%) rename web/{vite.config.ts => vite.config.js} (90%) diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index 3b8263d..502a273 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -66,8 +66,8 @@ jobs: - name: Test assembler run: go -C assembler test ./... - # Lint covers ESLint and svelte-check; the tests cover the framework-free - # modules, including the ASCII fold that has to match the Go parser. + # The tests cover the framework-free modules, including the ASCII fold + # that has to match the Go parser. - name: Test web working-directory: web run: npm test diff --git a/CLAUDE.md b/CLAUDE.md index a6b5962..e640750 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,7 +24,8 @@ Add the web checks when frontend files changed: (cd web && npm test && npm run lint && npm run build) ``` -`npm run lint` is ESLint plus `svelte-check`, so it is the type check too. +`npm run lint` is ESLint. The web app is plain JavaScript — no TypeScript, no +type-check step. **Do not run the crawler suite as part of routine verification.** The crawler is not part of the build — `data//` is committed and a crawl only refreshes it @@ -44,8 +45,8 @@ hashes every real input file. That is the point of it; do not skip it. regenerated.** A mismatch is a reader bug until proven otherwise, never a cue to refresh the file. `parser/cmd/dumpcells` narrows a failure to the cell. - **`ToAscii` filters the literal range U+0300..U+036F, not `unicode.Mn`.** It - must match `toAscii` in `web/src/lib/to-ascii.ts`, or accent-insensitive search - silently misses rows. `to-ascii.test.ts` pins the pairs both sides must agree + must match `toAscii` in `web/src/lib/to-ascii.js`, or accent-insensitive search + silently misses rows. `to-ascii.test.js` pins the pairs both sides must agree on. - **The 2016 files use four different layouts, and detection is per sheet.** Two of them publish scores in one column per subject instead of a `DIEM_THI` diff --git a/README.md b/README.md index f578951..1fcbda7 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Each stage runs on its own and hands its output to the next through the stores. `datasets.json` is the contract between them. It is JSON because Go and the web app both read it and neither needs a dependency to do so; presentation stays in -`web/src/lib/datasets.ts`, keyed by id, which fails loudly if the two disagree. +`web/src/lib/datasets.js`, keyed by id, which fails loudly if the two disagree. The dataset id is one identifier end to end: @@ -86,7 +86,7 @@ Pushing to `main` runs the same steps in 2. Add `parser/configs/.yml` — sheet mode, column indices, validation guards. No SQL; the schema is canonical. 3. Add an entry to `datasets.json` with its expected row count and size -4. Add the matching presentation to `CONTENT` in `web/src/lib/datasets.ts` +4. Add the matching presentation to `CONTENT` in `web/src/lib/datasets.js` Everything else follows: the assembler, the router and the hub all read the registry, and the UI adapts to whichever columns the dataset fills. Steps 3 and 4 diff --git a/docs/data-pipeline.md b/docs/data-pipeline.md index 1bb9567..518df28 100644 --- a/docs/data-pipeline.md +++ b/docs/data-pipeline.md @@ -183,7 +183,7 @@ silently drops 13,720 students** (Hanoi +7,275, HCM +6,445). That is what The databases are written with 1 KiB pages (`PRAGMA page_size` in `parser/internal/writer/writer.go`) because the browser fetches them a page per -HTTP request. `CHUNK_BYTES` in `web/src/lib/sqlite.svelte.ts` must match. +HTTP request. `CHUNK_BYTES` in `web/src/lib/sqlite.svelte.js` must match. | id | Source rows | Skipped | DB rows | | --- | --- | --- | --- | diff --git a/docs/system-architecture.md b/docs/system-architecture.md index 850804a..fd6d5e3 100644 --- a/docs/system-architecture.md +++ b/docs/system-architecture.md @@ -46,7 +46,7 @@ because the assembler is a Go program and the web app is not, and JSON is the only format both parse without a dependency. Presentation — titles, labels, search examples, SQL presets — stays in -`web/src/lib/datasets.ts`, keyed by id. That file cross-checks the two: a registry +`web/src/lib/datasets.js`, keyed by id. That file cross-checks the two: a registry entry with no content, or content for a dataset that was never built, throws at module load rather than rendering a page with no title or a link to a database that does not exist. @@ -125,13 +125,13 @@ No component contains a per-dataset conditional. Two mechanisms do the work: self-exclude wherever those languages were not sat. Anything genuinely per-dataset — title, source, database size, search examples, -SQL presets — lives in `web/src/lib/datasets.ts`. +SQL presets — lives in `web/src/lib/datasets.js`. ## Exam ID formats -`web/src/lib/query-mode.ts` decides whether a query is an exam ID or a name, and +`web/src/lib/query-mode.js` decides whether a query is an exam ID or a name, and is shared by the dataset page and `search-form.svelte` (they previously held -separate copies and had drifted apart on exactly this rule). `query-mode.test.ts` +separate copies and had drifted apart on exactly this rule). `query-mode.test.js` covers every form in the table below. | Form | Example | Where | diff --git a/parser/README.md b/parser/README.md index 23f57e1..3fea335 100644 --- a/parser/README.md +++ b/parser/README.md @@ -41,7 +41,7 @@ reader independently verifiable against a hash oracle. - `ToAscii` strips combining marks in the literal range U+0300–U+036F rather than by Unicode category. That covers every Vietnamese diacritic and must - stay identical to `toAscii` in `web/src/lib/to-ascii.ts`, or accent-insensitive + stay identical to `toAscii` in `web/src/lib/to-ascii.js`, or accent-insensitive search misses rows. - Gender is normalised to `Nam`/`Nữ`; the Cần Thơ files write `0`/`1` instead and are translated. Anything else becomes NULL. diff --git a/web/eslint.config.js b/web/eslint.config.js index 2175062..6378a41 100644 --- a/web/eslint.config.js +++ b/web/eslint.config.js @@ -1,12 +1,10 @@ import js from "@eslint/js"; import svelte from "eslint-plugin-svelte"; import globals from "globals"; -import ts from "typescript-eslint"; import svelteConfig from "./svelte.config.js"; -export default ts.config( +export default [ js.configs.recommended, - ...ts.configs.recommended, ...svelte.configs.recommended, { languageOptions: { @@ -15,16 +13,11 @@ export default ts.config( }, { // Svelte files and rune modules are parsed by svelte-eslint-parser, which - // needs the TypeScript parser handed to it for `lang="ts"` blocks. - files: ["**/*.svelte", "**/*.svelte.ts", "**/*.svelte.js"], + // needs the project's svelte.config.js to resolve aliases and runes. + files: ["**/*.svelte", "**/*.svelte.js"], languageOptions: { - parserOptions: { - parser: ts.parser, - projectService: true, - extraFileExtensions: [".svelte"], - svelteConfig, - }, + parserOptions: { svelteConfig }, }, }, { ignores: ["dist/", ".svelte-kit/", "node_modules/"] }, -); +]; diff --git a/web/tsconfig.json b/web/jsconfig.json similarity index 63% rename from web/tsconfig.json rename to web/jsconfig.json index 4344710..21df54b 100644 --- a/web/tsconfig.json +++ b/web/jsconfig.json @@ -1,14 +1,11 @@ { "extends": "./.svelte-kit/tsconfig.json", "compilerOptions": { - "allowJs": true, - "checkJs": true, + "moduleResolution": "bundler", + "resolveJsonModule": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, "skipLibCheck": true, - "sourceMap": true, - "strict": true, - "moduleResolution": "bundler" + "sourceMap": true } } diff --git a/web/package-lock.json b/web/package-lock.json index 5ed552d..6f55121 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -16,15 +16,11 @@ "@sveltejs/kit": "^2.70.2", "@sveltejs/vite-plugin-svelte": "^6.2.1", "@tailwindcss/vite": "^4.3.3", - "@types/sql.js": "^1.4.11", "eslint": "^9.39.4", "eslint-plugin-svelte": "^3.14.0", "globals": "^17.4.0", "svelte": "^5.56.9", - "svelte-check": "^4.4.3", "tailwindcss": "^4.3.3", - "typescript": "^5.9.3", - "typescript-eslint": "^8.48.2", "vite": "^7.1.14", "vitest": "^4.1.10" } @@ -1187,16 +1183,6 @@ } } }, - "node_modules/@sveltejs/load-config": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@sveltejs/load-config/-/load-config-0.2.3.tgz", - "integrity": "sha512-VT3qmUb8pRV2QrZjd8iAmtg8lf4W0TIjZbvXtz5MKei/q96teWZgGJyyidJzOjzZzvdq616eSRVeMYIQChUTAQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 18.0.0" - } - }, "node_modules/@sveltejs/vite-plugin-svelte": { "version": "6.2.4", "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz", @@ -1818,13 +1804,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/emscripten": { - "version": "1.41.5", - "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.41.5.tgz", - "integrity": "sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/estree": { "version": "1.0.9", "dev": true, @@ -1835,27 +1814,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/node": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", - "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~8.3.0" - } - }, - "node_modules/@types/sql.js": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@types/sql.js/-/sql.js-1.4.11.tgz", - "integrity": "sha512-QXIx38p2ZThJaK9vP5ZdqdlRe1FG9I8SmCZOS7FHfB/2qPAjZwkL7/vlfPg6N/oWHuuOaGg/P/IRwfP2W0kWVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/emscripten": "*", - "@types/node": "*" - } - }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -1863,301 +1821,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.67.0.tgz", - "integrity": "sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.67.0", - "@typescript-eslint/type-utils": "8.67.0", - "@typescript-eslint/utils": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.67.0", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", - "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.67.0.tgz", - "integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.67.0", - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/typescript-estree": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz", - "integrity": "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.67.0", - "@typescript-eslint/types": "^8.67.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.67.0.tgz", - "integrity": "sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", - "integrity": "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.67.0.tgz", - "integrity": "sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/typescript-estree": "8.67.0", - "@typescript-eslint/utils": "8.67.0", - "debug": "^4.4.3", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.67.0.tgz", - "integrity": "sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz", - "integrity": "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.67.0", - "@typescript-eslint/tsconfig-utils": "8.67.0", - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", - "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.8" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.67.0.tgz", - "integrity": "sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.67.0", - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/typescript-estree": "8.67.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.67.0.tgz", - "integrity": "sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.67.0", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@vitest/expect": { "version": "4.1.10", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", @@ -2401,22 +2064,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -3189,16 +2836,6 @@ "node": "*" } }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -3495,20 +3132,6 @@ "node": ">=6" } }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "dev": true, @@ -3563,19 +3186,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/sade": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mri": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/set-cookie-parser": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.2.tgz", @@ -3705,31 +3315,6 @@ "node": ">=18" } }, - "node_modules/svelte-check": { - "version": "4.7.6", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.7.6.tgz", - "integrity": "sha512-t2scM//ZuVbSY/T2w6FSBw1v9s2NEmh/g+sy1lqtosW5ylBV5AF4wFb1Ts9Kf3MbfPDUDJDZ9L436YT0SPTdvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "@sveltejs/load-config": "^0.2.3", - "chokidar": "^4.0.1", - "fdir": "^6.2.0", - "picocolors": "^1.0.0", - "sade": "^1.7.4" - }, - "bin": { - "svelte-check": "bin/svelte-check" - }, - "engines": { - "node": ">= 18.0.0" - }, - "peerDependencies": { - "svelte": "^4.0.0 || ^5.0.0-next.0", - "typescript": "^5.0.0 || ^6.0.0" - } - }, "node_modules/svelte-eslint-parser": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.8.0.tgz", @@ -3847,19 +3432,6 @@ "node": ">=6" } }, - "node_modules/ts-api-utils": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", - "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, "node_modules/type-check": { "version": "0.4.0", "dev": true, @@ -3871,51 +3443,6 @@ "node": ">= 0.8.0" } }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.67.0.tgz", - "integrity": "sha512-S2udFs8tCKEKffuJ4TB1idGUZiXdCPGi3IPBGWXarbLQ5UPXORV8QEVzJ4gCRduURMb5EkpNCdjbk0eDIuI8Yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.67.0", - "@typescript-eslint/parser": "8.67.0", - "@typescript-eslint/typescript-estree": "8.67.0", - "@typescript-eslint/utils": "8.67.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", - "dev": true, - "license": "MIT" - }, "node_modules/uri-js": { "version": "4.4.1", "dev": true, diff --git a/web/package.json b/web/package.json index fbf0a85..646acc1 100644 --- a/web/package.json +++ b/web/package.json @@ -9,7 +9,7 @@ "build": "vite build", "preview": "vite preview", "test": "vitest run", - "lint": "eslint . && svelte-check --tsconfig ./tsconfig.json" + "lint": "eslint ." }, "dependencies": { "sql.js-httpvfs": "^0.8.12" @@ -20,15 +20,11 @@ "@sveltejs/kit": "^2.70.2", "@sveltejs/vite-plugin-svelte": "^6.2.1", "@tailwindcss/vite": "^4.3.3", - "@types/sql.js": "^1.4.11", "eslint": "^9.39.4", "eslint-plugin-svelte": "^3.14.0", "globals": "^17.4.0", "svelte": "^5.56.9", - "svelte-check": "^4.4.3", "tailwindcss": "^4.3.3", - "typescript": "^5.9.3", - "typescript-eslint": "^8.48.2", "vite": "^7.1.14", "vitest": "^4.1.10" } diff --git a/web/src/lib/admission-blocks.ts b/web/src/lib/admission-blocks.js similarity index 88% rename from web/src/lib/admission-blocks.ts rename to web/src/lib/admission-blocks.js index 98f5142..b99c600 100644 --- a/web/src/lib/admission-blocks.ts +++ b/web/src/lib/admission-blocks.js @@ -1,15 +1,3 @@ -import type { Student, SubjectKey } from "./types"; - -export type AdmissionBlock = { code: string; subjects: SubjectKey[]; label: string }; -export type ComputedBlock = { - code: string; - label: string; - total: number; - parts: { key: SubjectKey; score: number }[]; -}; -export type TierKey = "common" | "uncommon" | "rare" | "epic" | "legendary" | "prismatic"; -export type Tier = { key: TierKey; symbol: string; label: string }; - // Vietnamese university admission-block ("khối thi") definitions, per Circular // 03/2017/TT-BGDĐT. Each block is the sum of exactly 3 subject scores. // @@ -17,7 +5,7 @@ export type Tier = { key: TierKey; symbol: string; label: string }; // skips any block with a missing subject score, so blocks needing GDCD // self-exclude on 2016 rows, and blocks needing a language the candidate did // not sit self-exclude row by row. -export const ADMISSION_BLOCKS: AdmissionBlock[] = [ +export const ADMISSION_BLOCKS = [ { code: "A00", subjects: ["toan", "vat_ly", "hoa_hoc"], label: "Toán + Lý + Hóa" }, { code: "A01", subjects: ["toan", "vat_ly", "tieng_anh"], label: "Toán + Lý + Anh" }, { code: "A02", subjects: ["toan", "vat_ly", "sinh_hoc"], label: "Toán + Lý + Sinh" }, @@ -76,14 +64,13 @@ export const ADMISSION_BLOCKS: AdmissionBlock[] = [ // Returns { code, label, total, parts:[{key,score}] } for every block where // the student has scores for all three subjects, sorted by total desc. -export function computeBlocks(student: Student): ComputedBlock[] { - const out: ComputedBlock[] = []; +export function computeBlocks(student) { + const out = []; for (const b of ADMISSION_BLOCKS) { const parts = b.subjects.map((k) => ({ key: k, score: student[k] })); if (parts.some((p) => p.score === null || p.score === undefined)) continue; - const whole = parts as { key: SubjectKey; score: number }[]; - const total = whole.reduce((s, p) => s + p.score, 0); - out.push({ code: b.code, label: b.label, total, parts: whole }); + const total = parts.reduce((s, p) => s + p.score, 0); + out.push({ code: b.code, label: b.label, total, parts }); } return out.sort((a, b) => b.total - a.total); } @@ -93,7 +80,7 @@ export function computeBlocks(student: Student): ComputedBlock[] { // whatever the other scores are, so that boundary is inclusive while the rest // are exclusive upper bounds. Every tier carries a unicode symbol as well as a // color, so the meaning is never conveyed by color alone. -export function scoreTier(score: number | null | undefined): Tier | null { +export function scoreTier(score) { if (score === null || score === undefined) return null; if (score <= 1) return { key: "common", symbol: "·", label: "Điểm liệt" }; if (score < 5) return { key: "uncommon", symbol: "○", label: "Chưa đạt" }; diff --git a/web/src/lib/admission-blocks.test.ts b/web/src/lib/admission-blocks.test.js similarity index 96% rename from web/src/lib/admission-blocks.test.ts rename to web/src/lib/admission-blocks.test.js index d78f189..8239e6e 100644 --- a/web/src/lib/admission-blocks.test.ts +++ b/web/src/lib/admission-blocks.test.js @@ -1,9 +1,8 @@ import { describe, expect, it } from "vitest"; import { ADMISSION_BLOCKS, computeBlocks, scoreTier } from "./admission-blocks"; -import type { Student } from "./types"; /** A row with every column NULL, so a test only states the scores it needs. */ -function student(scores: Partial): Student { +function student(scores) { return { so_bao_danh: "49008235", ho_ten: "Nguyễn Văn A", diff --git a/web/src/lib/components/custom-query.svelte b/web/src/lib/components/custom-query.svelte index d18df0b..dd0734c 100644 --- a/web/src/lib/components/custom-query.svelte +++ b/web/src/lib/components/custom-query.svelte @@ -1,29 +1,24 @@ - diff --git a/web/src/routes/[dataset]/+page.ts b/web/src/routes/[dataset]/+page.js similarity index 71% rename from web/src/routes/[dataset]/+page.ts rename to web/src/routes/[dataset]/+page.js index 055bd77..b9d5a57 100644 --- a/web/src/routes/[dataset]/+page.ts +++ b/web/src/routes/[dataset]/+page.js @@ -1,15 +1,14 @@ import { error } from "@sveltejs/kit"; import { DATASETS, datasetById } from "$lib/datasets"; -import type { EntryGenerator, PageLoad } from "./$types"; /** * One prerendered page per registered dataset. The registry is the repository * root's datasets.json, so a dataset the assembler builds is a page that exists, * and one it does not is a 404 at build time rather than a blank page. */ -export const entries: EntryGenerator = () => DATASETS.map((d) => ({ dataset: d.id })); +export const entries = () => DATASETS.map((d) => ({ dataset: d.id })); -export const load: PageLoad = ({ params }) => { +export const load = ({ params }) => { const dataset = datasetById(params.dataset); if (!dataset) throw error(404, `Unknown dataset: ${params.dataset}`); return { dataset }; diff --git a/web/src/routes/[dataset]/+page.svelte b/web/src/routes/[dataset]/+page.svelte index ac18283..e77d48e 100644 --- a/web/src/routes/[dataset]/+page.svelte +++ b/web/src/routes/[dataset]/+page.svelte @@ -1,4 +1,4 @@ -