Skip to content

Commit e5833be

Browse files
authored
chore: prepare src for parallel TypeScript transcription (#4538)
* chore: prepare `src` for parallel TypeScript transcription * chore: update Biome schema * ci: reflect same engines to ts workflow
1 parent 5f6482f commit e5833be

15 files changed

Lines changed: 353 additions & 2 deletions

.github/workflows/ci-src.yml

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
name: CI - src (TypeScript)
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- '.github/workflows/ci-src.yml'
7+
- 'src/**'
8+
- 'tools/src/**'
9+
push:
10+
branches: [master, js-to-ts]
11+
12+
workflow_dispatch:
13+
14+
jobs:
15+
src:
16+
runs-on: ubuntu-latest
17+
name: Typecheck and build
18+
19+
steps:
20+
- uses: actions/checkout@v6
21+
22+
- name: Set up Node.js
23+
uses: actions/setup-node@v6
24+
with:
25+
node-version: 22
26+
package-manager-cache: false
27+
28+
- name: Cache dependencies
29+
uses: actions/cache@v5
30+
with:
31+
path: ~/.npm
32+
key: npm-linux-${{ hashFiles('package-lock.json') }}
33+
restore-keys: npm-linux-
34+
35+
- name: Install npm dependencies
36+
run: npm ci
37+
38+
- name: Typecheck
39+
run: npm run src:typecheck
40+
41+
- name: Build
42+
run: npm run src:build
43+
44+
smoke:
45+
needs: src
46+
runs-on: ubuntu-latest
47+
strategy:
48+
fail-fast: false
49+
matrix:
50+
node-version: [18, 20, 22, 24]
51+
52+
name: Load dist on Node.js ${{ matrix.node-version }}
53+
54+
steps:
55+
- uses: actions/checkout@v6
56+
57+
- name: Set up Node.js 22 to install and build
58+
uses: actions/setup-node@v6
59+
with:
60+
node-version: 22
61+
package-manager-cache: false
62+
63+
- name: Cache dependencies
64+
uses: actions/cache@v5
65+
with:
66+
path: ~/.npm
67+
key: npm-linux-${{ hashFiles('package-lock.json') }}
68+
restore-keys: npm-linux-
69+
70+
- name: Install npm dependencies
71+
run: npm ci
72+
73+
- name: Build
74+
run: npm run src:build
75+
76+
- name: Set up Node.js ${{ matrix.node-version }}
77+
uses: actions/setup-node@v6
78+
with:
79+
node-version: ${{ matrix.node-version }}
80+
package-manager-cache: false
81+
82+
- name: Load the built entry points
83+
run: |
84+
if [ ! -f dist/index.js ]; then
85+
echo "dist/index.js is not transcribed yet: nothing to load"
86+
exit 0
87+
fi
88+
node -e "require('./dist/index.js'); require('./dist/promise.js'); console.log('loaded on', process.version)"

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,6 @@ test/circular/dist.mjs
3030
/.lagune/skills/*
3131
/**/lagune.*
3232
/**/lagune/
33+
34+
# Temporary TypeScript transcription output (src/)
35+
/dist/

.prettierrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
}
2323
},
2424
{
25-
"files": "test/**/*.mts",
25+
"files": ["test/**/*.mts", "src/**/*.ts", "tools/src/**/*.mts"],
2626
"options": {
2727
"plugins": ["@ianvs/prettier-plugin-sort-imports"],
2828
"importOrder": [

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ You are an expert Node.js developer contributing to MySQL2, a high-performance M
77
- Minimum compatibility: **Node 14**, whatever the `engines` field in `package.json` declares.
88
- Core: `/lib`, exposed through `index.js` (callback) and `promise.js` (promise-based).
99
- Types: `/typings` (`.d.ts` files), exposed through `index.d.ts` (callback) and `promise.d.ts` (promise-based).
10+
- Off limits: `/src` is a separate effort (the TypeScript transcription) and out of scope for any task that does not explicitly ask for it. Never touch it, and never replicate a change made to `/lib`, `index.js` or `promise.js` there.
1011
- Documentation: `website/docs/` (Docusaurus).
1112
- Tests:
1213
- Unit: `test/unit`, parallel.

biome.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"$schema": "https://biomejs.dev/schemas/2.5.7/schema.json",
2+
"$schema": "https://biomejs.dev/schemas/2.5.10/schema.json",
33
"files": {
44
"includes": [
55
"**/*.js",
@@ -251,6 +251,16 @@
251251
"javascript": {
252252
"globals": ["arguments"]
253253
}
254+
},
255+
{
256+
"includes": ["src/**/*.ts"],
257+
"linter": {
258+
"rules": {
259+
"suspicious": {
260+
"noExplicitAny": "error"
261+
}
262+
}
263+
}
254264
}
255265
]
256266
}

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
"test:coverage": "c8 npm test",
2121
"test:build": "rollup -c",
2222
"typecheck": "cd \"test/tsc-build\" && tsc -p \"tsconfig.json\" && cd .. && tsc -p \"tsconfig.json\" --noEmit",
23+
"src:typecheck": "tsc -p src/tsconfig.json --noEmit && tsc -p tools/src/tsconfig.json",
24+
"src:build": "tsx tools/src/clean.mts && tsc -p src/tsconfig.json",
25+
"src:test": "npm run src:build && tsx tools/src/prepare-tests.mts && poku --config=tools/src/poku.config.mts",
26+
"src:test:bun": "npm run src:build && tsx tools/src/prepare-tests.mts && bun poku --config=tools/src/poku.config.mts",
27+
"src:test:deno": "npm run src:build && tsx tools/src/prepare-tests.mts && deno run -A npm:poku --config=tools/src/poku.config.mts",
2328
"benchmark": "node ./benchmarks/benchmark.js",
2429
"wait-port": "wait-on"
2530
},

src/README.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# `src`: the TypeScript transcription of MySQL2
2+
3+
`src` is the TypeScript transcription of the shipped JavaScript (the two entry points and `lib`), developed in parallel while that JavaScript remains what users install. Nothing in `src` is published, covered by the regular CI, or loaded at runtime until the community declares the transcription complete.
4+
5+
Context: [#2803](https://github.com/sidorares/node-mysql2/issues/2803) and [#3695](https://github.com/sidorares/node-mysql2/issues/3695).
6+
7+
## Rules
8+
9+
1. **Runtime behavior never changes.** The built output must do exactly what the JavaScript does today: same exports, same lazy loading, same errors, same event order, same performance. Types describe the code, they do not redesign it.
10+
2. **Mirror the layout by default.** Each shipped file gets a TypeScript twin at the same path under `src`. Deviate only when TypeScript or the build requires it, and say why in the pull request.
11+
3. **Transcribe, do not refactor.** Keep names, structure and statement order. A bug found while transcribing is fixed in the shipped JavaScript first, through a regular pull request to `master`, then ported.
12+
4. **No `any`, no double casts.** Use `unknown` and narrow it. Prefer `type` over `interface`, and named exports over default exports, so the emitted CommonJS keeps the flat `exports` shape users rely on.
13+
5. **Imports carry the `.js` extension**, the way the built output resolves them. Biome enforces it.
14+
6. **Ambient declarations for dependencies without typings live in `src/types`.** Keep them faithful to how the shipped code uses each package, nothing more.
15+
7. **`typings` remains the public type contract until the switch.** When it disagrees with `src`, the mismatch is a finding to discuss in the pull request, not something to paper over.
16+
17+
## Build
18+
19+
The build emits to `dist`, git-ignored, mirroring the repository root: the two entry points, `lib`, and a declaration file next to each module. Class fields compile to constructor assignments, matching the JavaScript.
20+
21+
| Setting | Value | Why |
22+
| -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
23+
| `target` | `es2020` | Node.js 14 is the real runtime floor and ES2020 is the newest syntax it runs natively, so nothing the JavaScript already uses gets down-leveled. |
24+
| `module` | `node16` | With the package declared as CommonJS, every file compiles to CommonJS, and Node.js, Bun and Deno keep loading the same module format. |
25+
| `declaration` | `true` | The emitted declarations become the type contract at the switch. |
26+
| `sourceMap` | `false` | Open decision: maps help debugging but add package size. Revisit before the switch. |
27+
| `skipLibCheck` | `true` | `aws-ssl-profiles` ships a declaration file the compiler rejects. Fixing it upstream lets this go back to `false`. |
28+
29+
## Commands
30+
31+
```sh
32+
npm run src:typecheck
33+
npm run src:build
34+
npm run src:test # Node.js
35+
npm run src:test:bun
36+
npm run src:test:deno
37+
```
38+
39+
`npm run lint` already covers `src`.
40+
41+
## Transcribing a file
42+
43+
1. Pick a file, starting from the leaves (constants, packets, parsers) and moving up to the connection, the promise wrapper and the entry points.
44+
2. Create its twin in `src` and transcribe it statement by statement.
45+
3. Run the typecheck and the linter.
46+
4. Open a pull request against `js-to-ts`, one file or one small directory each.
47+
48+
## Keeping up with `master`
49+
50+
`master` keeps changing the shipped JavaScript while the transcription happens here. Merge it into `js-to-ts` regularly. Since `src` and the shipped files never overlap, the merge never conflicts, it only brings changes the transcribed files may not reflect yet. Before merging, list what changed:
51+
52+
```sh
53+
git fetch origin
54+
git diff --stat HEAD...origin/master -- index.js promise.js lib
55+
```
56+
57+
Merge, then port each change to its twin in `src`. Files not transcribed yet need nothing, since they will be transcribed from the current JavaScript later.
58+
59+
## Testing
60+
61+
The existing test suite, unchanged, is the acceptance test. The `src:test` scripts build `dist`, copy the tests next to it and run them there, so every relative import in a test resolves to the built output instead of the shipped JavaScript. `FILTER` works as usual, a MySQL server is needed as for `npm test`, and the run only makes sense once the entry points and everything they import exist in `src`.
62+
63+
The dedicated workflow typechecks and builds `src` on every pull request that touches it, then loads the built entry points on the Node.js versions the regular CI already tests, once they exist. The regular workflows never look at `src`.
64+
65+
## Known questions
66+
67+
- The callback entry point exposes part of its API through lazy getters, so the promise API is not loaded eagerly and the module graph stays free of cycles, which the circular-import check enforces. ES module syntax has no lazy export, so the transcribed entry points need an explicit CommonJS getter and a lint exception for it.
68+
- Named placeholders are loaded lazily on first use. The transcription must keep that.
69+
- The row parsers generate code from strings. The generated source stays untyped by nature: type the generator inputs and the returned function, nothing inside the string.
70+
71+
## The switch
72+
73+
When `src` is complete, one pull request makes the built output the shipped code:
74+
75+
1. Point the build output at the repository root. It then writes the entry points and `lib` exactly where they are today, so the package manifest, coverage, CodeQL and every test import keep working unchanged.
76+
2. Delete the JavaScript sources from git, ignore the generated paths, and build before packing.
77+
3. The emitted declarations become the type contract. Remove the hand-written typings and the manual compile checks: compiling `src` in its approved state is the type test.
78+
4. Fold the `src` scripts and workflow into the regular ones and delete the helper tools.
79+
5. Release as a major version, with a changelog entry stating that runtime behavior is unchanged.

src/tsconfig.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"include": ["**/*.ts"],
3+
"compilerOptions": {
4+
"rootDir": ".",
5+
"outDir": "../dist",
6+
"target": "es2020",
7+
"lib": ["es2020"],
8+
"module": "node16",
9+
"moduleResolution": "node16",
10+
"types": ["node"],
11+
"strict": true,
12+
"noImplicitAny": true,
13+
"noImplicitOverride": true,
14+
"noImplicitReturns": true,
15+
"noFallthroughCasesInSwitch": true,
16+
"noUnusedLocals": true,
17+
"noUnusedParameters": true,
18+
"isolatedModules": true,
19+
"esModuleInterop": true,
20+
"skipLibCheck": true,
21+
"declaration": true,
22+
"declarationMap": false,
23+
"sourceMap": false,
24+
"removeComments": false,
25+
"newLine": "lf",
26+
"noEmitOnError": true
27+
}
28+
}

src/types/generate-function.d.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
declare module 'generate-function' {
2+
type Formatters = {
3+
s(value: unknown): string;
4+
d(value: unknown): string;
5+
o(value: unknown): string;
6+
};
7+
8+
type GeneratedFunction = {
9+
(format?: string, ...args: unknown[]): GeneratedFunction;
10+
scope: Record<string, unknown>;
11+
formats: Formatters;
12+
sym(name?: string): string;
13+
property(name: string | number): string;
14+
property(object: string, name: string | number): string;
15+
toString(): string;
16+
toFunction<T = unknown>(scope?: Record<string, unknown>): T;
17+
};
18+
19+
type GenerateFunction = {
20+
(format?: string, ...args: unknown[]): GeneratedFunction;
21+
formats: Formatters;
22+
};
23+
24+
const generateFunction: GenerateFunction;
25+
26+
export = generateFunction;
27+
}

src/types/named-placeholders.d.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
declare module 'named-placeholders' {
2+
type CompiledQuery = [sql: string, values: unknown[]];
3+
4+
type QueryCache = {
5+
get(query: string): unknown;
6+
set(query: string, tree: unknown): unknown;
7+
};
8+
9+
type CompilerOptions = {
10+
placeholder?: string;
11+
cache?: number | QueryCache | false;
12+
};
13+
14+
type Compile = (
15+
query: string,
16+
params?: Record<string, unknown>
17+
) => CompiledQuery;
18+
19+
type CreateCompiler = {
20+
(options?: CompilerOptions): Compile;
21+
toNumbered(query: string, params: Record<string, unknown>): CompiledQuery;
22+
};
23+
24+
const createCompiler: CreateCompiler;
25+
26+
export = createCompiler;
27+
}

0 commit comments

Comments
 (0)