@c9up/atlas 0.3.3 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/db.win32-x64-msvc.node +0 -0
- package/dist/adapters/NapiDbAdapter.d.ts.map +1 -1
- package/dist/adapters/NapiDbAdapter.js +34 -4
- package/dist/adapters/NapiDbAdapter.js.map +1 -1
- package/dist/native/generated.d.ts +80 -0
- package/dist/native/generated.d.ts.map +1 -0
- package/dist/native/generated.js +7 -0
- package/dist/native/generated.js.map +1 -0
- package/dist/query/native.d.ts.map +1 -1
- package/dist/query/native.js.map +1 -1
- package/dist/testing/TestUtils.d.ts +1 -1
- package/dist/testing/TestUtils.js +1 -1
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +3 -2
- package/scripts/build-napi-types.mjs +68 -0
- package/scripts/generate-napi-types.mjs +153 -0
- package/src/adapters/NapiDbAdapter.ts +50 -46
- package/src/native/generated.ts +111 -0
- package/src/query/native.ts +10 -2
- package/src/testing/DbAssertions.ts +1 -1
- package/src/testing/TestUtils.ts +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ AdonisJS Lucid–style ORM for Node.js — Active Record **models** *and* a Data
|
|
|
6
6
|
> tracked closely against Lucid. The capabilities Lucid bundles via Adonis libs
|
|
7
7
|
> are covered by **Ream's own equivalent packages** — validation `@c9up/rune`,
|
|
8
8
|
> testing `@c9up/atlas/testing` + `@c9up/ream/testing`, health `@c9up/ream`
|
|
9
|
-
> `HealthCheck` — not
|
|
9
|
+
> `HealthCheck` — not bindings to Adonis's own libraries. See
|
|
10
10
|
> [`LUCID-PARITY.md`](./LUCID-PARITY.md) for the authoritative contract, named
|
|
11
11
|
> deviations, and known limitations.
|
|
12
12
|
|
package/db.win32-x64-msvc.node
CHANGED
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NapiDbAdapter.d.ts","sourceRoot":"","sources":["../../src/adapters/NapiDbAdapter.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"NapiDbAdapter.d.ts","sourceRoot":"","sources":["../../src/adapters/NapiDbAdapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -20,6 +20,36 @@ function napiReplacer(_key, value) {
|
|
|
20
20
|
}
|
|
21
21
|
return value;
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Refuse an array bound as ONE parameter.
|
|
25
|
+
*
|
|
26
|
+
* The binder has no array arm, so an array reached its catch-all and was bound
|
|
27
|
+
* as the JSON text `["a","b"]`. On SQLite that is merely wrong; on Postgres,
|
|
28
|
+
* `WHERE id = ANY($1)` makes the server read those text bytes as a binary array
|
|
29
|
+
* header, and it reports a dimension count read out of whatever happened to be
|
|
30
|
+
* there — a different number on every run. Silently sending bytes the server
|
|
31
|
+
* misreads is the failure this replaces.
|
|
32
|
+
*
|
|
33
|
+
* Expanding the list is the answer, and it is what Lucid does: `whereIn` emits
|
|
34
|
+
* `IN ($1, $2, …)` with one placeholder per value. So this names `whereIn`
|
|
35
|
+
* rather than teaching the binder a shape the query builder never produces.
|
|
36
|
+
*
|
|
37
|
+
* Only the TOP level is checked: a nested array is a JSON value inside an
|
|
38
|
+
* object being bound to a `json`/`jsonb` column, which is legitimate.
|
|
39
|
+
*/
|
|
40
|
+
function assertNoArrayParams(params) {
|
|
41
|
+
if (!params)
|
|
42
|
+
return;
|
|
43
|
+
for (const [index, value] of params.entries()) {
|
|
44
|
+
if (!Array.isArray(value))
|
|
45
|
+
continue;
|
|
46
|
+
throw new Error(`[E_ARRAY_PARAM] Parameter $${index + 1} is an array, which cannot be bound as a single value. ` +
|
|
47
|
+
"Expand it into one placeholder per element — `whereIn(column, values)` does this, " +
|
|
48
|
+
"and so does building the placeholders yourself: `IN (${values.map((_, i) => '$' + (i + 1)).join(', ')})`. " +
|
|
49
|
+
"Binding the array itself sends Postgres text where it expects an array, and it reports " +
|
|
50
|
+
"a nonsensical dimension count rather than a type error.");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
23
53
|
/**
|
|
24
54
|
* JSON reviver for napi result sets. Rebuilds `{"$bytes": …}` envelopes (emitted
|
|
25
55
|
* by the Rust decoder for BLOB/BYTEA columns) into a `Uint8Array`. Integers
|
|
@@ -91,7 +121,7 @@ export async function createNapiConnection(url, poolMin = 1, poolMax = 10, pragm
|
|
|
91
121
|
const evt = makeTrxEvents();
|
|
92
122
|
const base = {
|
|
93
123
|
async execute(sql, params = []) {
|
|
94
|
-
const json = await native.execute(sql, JSON.stringify(params, napiReplacer));
|
|
124
|
+
const json = await native.execute(sql, (assertNoArrayParams(params), JSON.stringify(params, napiReplacer)));
|
|
95
125
|
const r = JSON.parse(json);
|
|
96
126
|
return {
|
|
97
127
|
rowsAffected: Number(r.rows_affected),
|
|
@@ -99,7 +129,7 @@ export async function createNapiConnection(url, poolMin = 1, poolMax = 10, pragm
|
|
|
99
129
|
};
|
|
100
130
|
},
|
|
101
131
|
async query(sql, params = []) {
|
|
102
|
-
const json = await native.query(sql, JSON.stringify(params, napiReplacer));
|
|
132
|
+
const json = await native.query(sql, (assertNoArrayParams(params), JSON.stringify(params, napiReplacer)));
|
|
103
133
|
return JSON.parse(json, napiReviver);
|
|
104
134
|
},
|
|
105
135
|
get isCompleted() {
|
|
@@ -201,7 +231,7 @@ export async function createNapiConnection(url, poolMin = 1, poolMax = 10, pragm
|
|
|
201
231
|
transaction,
|
|
202
232
|
async query(sql, params = [], meta) {
|
|
203
233
|
return observed(sql, params, meta, async () => {
|
|
204
|
-
const paramsJson = JSON.stringify(params, napiReplacer);
|
|
234
|
+
const paramsJson = (assertNoArrayParams(params), JSON.stringify(params, napiReplacer));
|
|
205
235
|
const json = meta?.serverTimeoutMs != null
|
|
206
236
|
? await db.queryTimed(sql, paramsJson, meta.serverTimeoutMs)
|
|
207
237
|
: await db.query(sql, paramsJson);
|
|
@@ -210,7 +240,7 @@ export async function createNapiConnection(url, poolMin = 1, poolMax = 10, pragm
|
|
|
210
240
|
},
|
|
211
241
|
async execute(sql, params = [], meta) {
|
|
212
242
|
return observed(sql, params, meta, async () => {
|
|
213
|
-
const paramsJson = JSON.stringify(params, napiReplacer);
|
|
243
|
+
const paramsJson = (assertNoArrayParams(params), JSON.stringify(params, napiReplacer));
|
|
214
244
|
const json = meta?.serverTimeoutMs != null
|
|
215
245
|
? await db.executeTimed(sql, paramsJson, meta.serverTimeoutMs)
|
|
216
246
|
: await db.execute(sql, paramsJson);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NapiDbAdapter.js","sourceRoot":"","sources":["../../src/adapters/NapiDbAdapter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"NapiDbAdapter.js","sourceRoot":"","sources":["../../src/adapters/NapiDbAdapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/** NAPI-exposed database connection. */
|
|
2
|
+
export declare class ReamDatabase {
|
|
3
|
+
/**
|
|
4
|
+
* Connect to a database. URL determines the driver:
|
|
5
|
+
* - "sqlite:path/to/db.sqlite" or "sqlite::memory:"
|
|
6
|
+
* - "postgres://user:pass@host/dbname"
|
|
7
|
+
* - "mysql://user:pass@host/dbname"
|
|
8
|
+
*/
|
|
9
|
+
static connect(url: string, poolMin?: number | undefined | null, poolMax?: number | undefined | null, pragmas?: Array<Array<string>> | undefined | null, connectRetries?: number | undefined | null, connectBackoffMs?: number | undefined | null, connectTimeoutMs?: number | undefined | null): Promise<ReamDatabase>;
|
|
10
|
+
/** Execute a SELECT query. Returns JSON array of row objects. */
|
|
11
|
+
query(sql: string, paramsJson: string): Promise<string>;
|
|
12
|
+
/**
|
|
13
|
+
* Like {@link query} with a server-side statement timeout (Lucid
|
|
14
|
+
* `timeout(ms, { cancel: true })`) — Postgres `statement_timeout`, MySQL
|
|
15
|
+
* `MAX_EXECUTION_TIME` (SELECT). Returns JSON array of row objects.
|
|
16
|
+
*/
|
|
17
|
+
queryTimed(sql: string, paramsJson: string, timeoutMs: number): Promise<string>;
|
|
18
|
+
/**
|
|
19
|
+
* Like {@link execute} with a server-side statement timeout (Postgres
|
|
20
|
+
* `statement_timeout`). Returns the JSON-serialized `ExecResult`.
|
|
21
|
+
*/
|
|
22
|
+
executeTimed(sql: string, paramsJson: string, timeoutMs: number): Promise<string>;
|
|
23
|
+
/**
|
|
24
|
+
* Execute an INSERT/UPDATE/DELETE. Returns the JSON-serialized `ExecResult`
|
|
25
|
+
* (`{ rows_affected, last_insert_id }`) so the JS side can read the MySQL/
|
|
26
|
+
* SQLite auto-increment id (Lucid's insert-without-returning shape).
|
|
27
|
+
*/
|
|
28
|
+
execute(sql: string, paramsJson: string): Promise<string>;
|
|
29
|
+
/**
|
|
30
|
+
* Run a batch of `[sql, params_json]` pairs atomically in a single transaction.
|
|
31
|
+
* Accepts JSON `[[sql, params], ...]` and returns the total affected rows.
|
|
32
|
+
*/
|
|
33
|
+
runInTransaction(batchJson: string): Promise<number>;
|
|
34
|
+
/**
|
|
35
|
+
* Open an interactive transaction pinned to a single pooled connection.
|
|
36
|
+
* Returns a handle whose `query`/`execute` run on that one connection;
|
|
37
|
+
* `commit`/`rollback` release it. This is what makes a TS
|
|
38
|
+
* read-then-decide-then-write atomic — `BEGIN`/`COMMIT` pulled through the
|
|
39
|
+
* pool would land on different connections and guarantee nothing.
|
|
40
|
+
*/
|
|
41
|
+
begin(isolationLevel?: string | undefined | null): Promise<ReamTransaction>;
|
|
42
|
+
/** Health check. */
|
|
43
|
+
ping(): Promise<void>;
|
|
44
|
+
/** Get pool size. */
|
|
45
|
+
poolSize(): number;
|
|
46
|
+
/** Close the connection pool. */
|
|
47
|
+
close(): Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* An interactive transaction handle (see [`ReamDatabase::begin`]). The pinned
|
|
51
|
+
* `DbTransaction` lives in an async mutex so the separate NAPI calls
|
|
52
|
+
* (execute… → commit) all hit the SAME connection; `commit`/`rollback` take it
|
|
53
|
+
* out (the connection returns to the pool) and any later call sees a clear
|
|
54
|
+
* "transaction already finished" error instead of a silent no-op.
|
|
55
|
+
*/
|
|
56
|
+
export declare class ReamTransaction {
|
|
57
|
+
/** SELECT on the pinned connection. Returns a JSON array of row objects. */
|
|
58
|
+
query(sql: string, paramsJson: string): Promise<string>;
|
|
59
|
+
/**
|
|
60
|
+
* INSERT/UPDATE/DELETE on the pinned connection. Returns the JSON-serialized
|
|
61
|
+
* `ExecResult` (`{ rows_affected, last_insert_id }`).
|
|
62
|
+
*/
|
|
63
|
+
execute(sql: string, paramsJson: string): Promise<string>;
|
|
64
|
+
/**
|
|
65
|
+
* Commit and release the connection back to the pool. Idempotent-safe: a
|
|
66
|
+
* second commit/rollback errors with "transaction already finished".
|
|
67
|
+
*/
|
|
68
|
+
commit(): Promise<void>;
|
|
69
|
+
/** Roll back and release the connection back to the pool. */
|
|
70
|
+
rollback(): Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Compile a full statement (SELECT / INSERT / UPDATE / DELETE / DDL) via a tagged JSON spec.
|
|
74
|
+
* `dialect` is one of "sqlite" | "postgres" | "mysql".
|
|
75
|
+
* Returns a JSON-encoded `CompiledStatement` ({ statements: [...], params: [...] }).
|
|
76
|
+
*/
|
|
77
|
+
export declare function compileStatement(specJson: string, dialect: string): string;
|
|
78
|
+
/** Quote a SQL identifier (validates and wraps in double quotes). */
|
|
79
|
+
export declare function quoteIdent(name: string): string;
|
|
80
|
+
//# sourceMappingURL=generated.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generated.d.ts","sourceRoot":"","sources":["../../src/native/generated.ts"],"names":[],"mappings":"AAMA,wCAAwC;AAExC,MAAM,CAAC,OAAO,OAAO,YAAY;IAChC;;;;;OAKG;IACH,MAAM,CAAC,OAAO,CACb,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EACnC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EACnC,OAAO,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,SAAS,GAAG,IAAI,EACjD,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EAC1C,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EAC5C,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAC1C,OAAO,CAAC,YAAY,CAAC;IACxB,iEAAiE;IACjE,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IACvD;;;;OAIG;IACH,UAAU,CACT,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC;IAClB;;;OAGG;IACH,YAAY,CACX,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC;IAClB;;;;OAIG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IACzD;;;OAGG;IACH,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IACpD;;;;;;OAMG;IACH,KAAK,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAAC,eAAe,CAAC;IAC3E,oBAAoB;IACpB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IACrB,qBAAqB;IACrB,QAAQ,IAAI,MAAM;IAClB,iCAAiC;IACjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CACtB;AAED;;;;;;GAMG;AAEH,MAAM,CAAC,OAAO,OAAO,eAAe;IACnC,4EAA4E;IAC5E,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IACvD;;;OAGG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IACzD;;;OAGG;IACH,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IACvB,6DAA6D;IAC7D,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CACzB;AAED;;;;GAIG;AAEH,MAAM,CAAC,OAAO,UAAU,gBAAgB,CACvC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACb,MAAM,CAAC;AAEV,qEAAqE;AAErE,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// GENERATED FROM THE RUST — do not edit.
|
|
2
|
+
//
|
|
3
|
+
// Produced by scripts/generate-napi-types.mjs from napi-derive's type-def
|
|
4
|
+
// output. Editing this file by hand puts it back where it started: a
|
|
5
|
+
// description that can disagree with the code it describes.
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=generated.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generated.js","sourceRoot":"","sources":["../../src/native/generated.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,EAAE;AACF,0EAA0E;AAC1E,qEAAqE;AACrE,4DAA4D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../../src/query/native.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../../src/query/native.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA4CH,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;AAE3D;;;;GAIG;AACH,MAAM,MAAM,WAAW,GACpB,YAAY,GACZ,SAAS,GACT,gBAAgB,GAChB,YAAY,GACZ,IAAI,GACJ,QAAQ,CAAC;AAaZ,qFAAqF;AACrF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAEvE;AAKD,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAE3D;AAED,wBAAgB,eAAe,IAAI,YAAY,CAE9C;AAED,MAAM,WAAW,iBAAiB;IACjC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE,OAAO,EAAE,CAAC;CAClB;AAaD;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CACjC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,IAAI,CAIN;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CACjC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACV,IAAI,CAIN;AAuCD;;;GAGG;AACH,wBAAgB,sBAAsB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,YAA6B,GACpC,iBAAiB,CAYnB"}
|
package/dist/query/native.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native.js","sourceRoot":"","sources":["../../src/query/native.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"native.js","sourceRoot":"","sources":["../../src/query/native.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* group.each.setup(() => t.db().truncate()); // teardown truncates
|
|
8
8
|
* });
|
|
9
9
|
*
|
|
10
|
-
* Each method returns the TEARDOWN function a
|
|
10
|
+
* Each method returns the TEARDOWN function a helix/helix hook uses (Adonis
|
|
11
11
|
* parity): `group.each.setup(() => testUtils.db().truncate())`.
|
|
12
12
|
*
|
|
13
13
|
* NOTE (named atlas deviation): Lucid's `withGlobalTransaction()` transparently
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* group.each.setup(() => t.db().truncate()); // teardown truncates
|
|
8
8
|
* });
|
|
9
9
|
*
|
|
10
|
-
* Each method returns the TEARDOWN function a
|
|
10
|
+
* Each method returns the TEARDOWN function a helix/helix hook uses (Adonis
|
|
11
11
|
* parity): `group.each.setup(() => testUtils.db().truncate())`.
|
|
12
12
|
*
|
|
13
13
|
* NOTE (named atlas deviation): Lucid's `withGlobalTransaction()` transparently
|
package/index.darwin-arm64.node
CHANGED
|
Binary file
|
package/index.darwin-x64.node
CHANGED
|
Binary file
|
|
Binary file
|
package/index.linux-x64-gnu.node
CHANGED
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c9up/atlas",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "Atlas — Data Mapper ORM for the Ream framework",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -90,7 +90,8 @@
|
|
|
90
90
|
"scripts": {
|
|
91
91
|
"build": "tsc -p tsconfig.build.json",
|
|
92
92
|
"build:rust": "cargo build --release -p atlas-query-napi && cargo build --release -p atlas-db-napi",
|
|
93
|
-
"build:napi": "pnpm build:rust && node scripts/copy-napi.mjs && node scripts/copy-napi.mjs --basename db",
|
|
93
|
+
"build:napi": "pnpm build:rust && pnpm build:napi-types && node scripts/copy-napi.mjs && node scripts/copy-napi.mjs --basename db",
|
|
94
|
+
"build:napi-types": "node scripts/build-napi-types.mjs",
|
|
94
95
|
"test": "vitest run",
|
|
95
96
|
"test:rust": "cargo test -p atlas-query",
|
|
96
97
|
"lint": "biome check src/",
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Regenerate `src/native/generated.ts` from the Rust.
|
|
4
|
+
*
|
|
5
|
+
* Node rather than a shell script because this runs inside `build:napi`, and
|
|
6
|
+
* that runs on the Windows prebuild runner too: a bash `mktemp` there hands
|
|
7
|
+
* cargo a `/tmp/...` path the native proc-macro cannot write to, so the
|
|
8
|
+
* type-def file comes back empty and the build fails for no visible reason.
|
|
9
|
+
*
|
|
10
|
+
* One crate at a time on purpose: napi-derive APPENDS to `TYPE_DEF_TMP_PATH`
|
|
11
|
+
* while cargo compiles, and a parallel build interleaves the writes —
|
|
12
|
+
* definitions go missing, silently, and the generated file comes out short.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { execFileSync } from 'node:child_process'
|
|
16
|
+
import { createRequire } from 'node:module'
|
|
17
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
18
|
+
import { tmpdir } from 'node:os'
|
|
19
|
+
import { dirname, join } from 'node:path'
|
|
20
|
+
import { fileURLToPath } from 'node:url'
|
|
21
|
+
|
|
22
|
+
const CRATES = ['atlas-query-napi', 'atlas-db-napi']
|
|
23
|
+
|
|
24
|
+
const packageRoot = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
25
|
+
const output = join('src', 'native', 'generated.ts')
|
|
26
|
+
const scratch = mkdtempSync(join(tmpdir(), 'napi-types-'))
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const lines = []
|
|
30
|
+
for (const crate of CRATES) {
|
|
31
|
+
const perCrate = join(scratch, `${crate}.jsonl`)
|
|
32
|
+
writeFileSync(perCrate, '')
|
|
33
|
+
execFileSync('cargo', ['build', '-p', crate], {
|
|
34
|
+
cwd: packageRoot,
|
|
35
|
+
env: { ...process.env, TYPE_DEF_TMP_PATH: perCrate },
|
|
36
|
+
stdio: ['ignore', 'ignore', 'inherit'],
|
|
37
|
+
})
|
|
38
|
+
const emitted = readFileSync(perCrate, 'utf8').split('\n').filter(Boolean)
|
|
39
|
+
if (emitted.length === 0) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`[napi-types] ${crate} emitted nothing — is napi-derive's "type-def" feature on?`,
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
lines.push(...emitted)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const combined = join(scratch, 'combined.jsonl')
|
|
48
|
+
writeFileSync(combined, `${lines.join('\n')}\n`)
|
|
49
|
+
|
|
50
|
+
execFileSync(
|
|
51
|
+
process.execPath,
|
|
52
|
+
[join('scripts', 'generate-napi-types.mjs'), combined, output],
|
|
53
|
+
{ cwd: packageRoot, stdio: 'inherit' },
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
// Formatted here rather than excluded from the linter: the file is checked
|
|
57
|
+
// in, so it should read like the rest of the tree — and formatting it at
|
|
58
|
+
// generation means it never shows up as a diff someone has to fix by hand.
|
|
59
|
+
// Resolved through the package entry rather than `node_modules/.bin`, whose
|
|
60
|
+
// shim is a shell script the Windows runner cannot execute directly.
|
|
61
|
+
const biome = createRequire(import.meta.url).resolve('@biomejs/biome/bin/biome')
|
|
62
|
+
execFileSync(process.execPath, [biome, 'format', '--write', output], {
|
|
63
|
+
cwd: packageRoot,
|
|
64
|
+
stdio: ['ignore', 'ignore', 'inherit'],
|
|
65
|
+
})
|
|
66
|
+
} finally {
|
|
67
|
+
rmSync(scratch, { recursive: true, force: true })
|
|
68
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generate the TypeScript surface of the native module FROM the Rust.
|
|
4
|
+
*
|
|
5
|
+
* `napi-derive`'s `type-def` feature emits one JSON line per exported item
|
|
6
|
+
* while cargo compiles. Those lines are the source of truth: they are derived
|
|
7
|
+
* from the `#[napi]` items themselves, so a signature cannot drift from the
|
|
8
|
+
* Rust without this file changing.
|
|
9
|
+
*
|
|
10
|
+
* Hand-written interfaces are what this replaces, and they do drift — nothing
|
|
11
|
+
* on the TypeScript side notices when a `pub fn` gains a parameter or stops
|
|
12
|
+
* being `async`.
|
|
13
|
+
*
|
|
14
|
+
* One thing napi-rs cannot infer: the shape of a `JsFunction` callback. It
|
|
15
|
+
* emits `(...args: any[]) => any`. Any such parameter is refined below, by
|
|
16
|
+
* name, and the script fails if a refinement no longer matches anything — so
|
|
17
|
+
* the table cannot rot either.
|
|
18
|
+
*
|
|
19
|
+
* TYPE_DEF_TMP_PATH=<file> cargo build -p <crate>
|
|
20
|
+
* node scripts/generate-napi-types.mjs <file> <out.d.ts>
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { readFileSync, writeFileSync } from 'node:fs'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Callback signatures napi-rs erases to `any`, restored here.
|
|
27
|
+
*
|
|
28
|
+
* Keyed by `<owner>.<method>`; the value replaces the whole parameter. Every
|
|
29
|
+
* entry must match, or the script fails: a stale refinement is how a hand
|
|
30
|
+
* annotation quietly stops describing the Rust.
|
|
31
|
+
*
|
|
32
|
+
* Empty while this crate exposes no `JsFunction` parameter — add an entry the
|
|
33
|
+
* day one appears, rather than letting an `any` through.
|
|
34
|
+
*/
|
|
35
|
+
const CALLBACK_REFINEMENTS = {}
|
|
36
|
+
|
|
37
|
+
const [input, output] = process.argv.slice(2)
|
|
38
|
+
if (!input || !output) {
|
|
39
|
+
console.error('usage: generate-napi-types.mjs <type-def file> <out.d.ts>')
|
|
40
|
+
process.exit(2)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const entries = readFileSync(input, 'utf8')
|
|
44
|
+
.split('\n')
|
|
45
|
+
.map((l) => l.trim())
|
|
46
|
+
.filter(Boolean)
|
|
47
|
+
.map((l) => JSON.parse(l))
|
|
48
|
+
|
|
49
|
+
const used = new Set()
|
|
50
|
+
|
|
51
|
+
/** Apply the refinements that belong to `owner`, tracking which ones matched. */
|
|
52
|
+
function refine(owner, body) {
|
|
53
|
+
let out = body
|
|
54
|
+
for (const [key, { from, to }] of Object.entries(CALLBACK_REFINEMENTS)) {
|
|
55
|
+
const [entryOwner] = key.split('.')
|
|
56
|
+
if (entryOwner !== owner) continue
|
|
57
|
+
if (out.includes(from)) {
|
|
58
|
+
out = out.replace(from, to)
|
|
59
|
+
used.add(key)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return out
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* JSDoc as emitted by napi-derive, indented to sit above its member.
|
|
67
|
+
*
|
|
68
|
+
* `*` followed by `/` inside the text closes the comment early — a Rust doc
|
|
69
|
+
* example holding a cron expression (`0 */5 * * *`) is enough to do it, and
|
|
70
|
+
* the generated file then fails to parse. Escaped rather than stripped, so the
|
|
71
|
+
* example still reads correctly.
|
|
72
|
+
*/
|
|
73
|
+
function docBlock(doc, indent = '') {
|
|
74
|
+
if (!doc) return ''
|
|
75
|
+
// Escape every `*/` EXCEPT the one that closes the block. A Rust doc example
|
|
76
|
+
// holding a cron expression (`0 */5 * * *`) closes the comment early
|
|
77
|
+
// otherwise, and the generated file stops parsing — but escaping the closer
|
|
78
|
+
// too breaks it just as thoroughly, whether the block spans one line or many.
|
|
79
|
+
const closer = doc.lastIndexOf('*/')
|
|
80
|
+
const escaped =
|
|
81
|
+
closer === -1
|
|
82
|
+
? doc
|
|
83
|
+
: doc.slice(0, closer).replaceAll('*/', '*\\/') + doc.slice(closer)
|
|
84
|
+
return (
|
|
85
|
+
escaped
|
|
86
|
+
.split('\n')
|
|
87
|
+
.filter((l) => l.length > 0)
|
|
88
|
+
.map((l) => `${indent}${l}`)
|
|
89
|
+
.join('\n') + '\n'
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const interfaces = entries.filter((e) => e.kind === 'interface')
|
|
94
|
+
const structs = entries.filter((e) => e.kind === 'struct')
|
|
95
|
+
const impls = new Map(entries.filter((e) => e.kind === 'impl').map((e) => [e.name, e]))
|
|
96
|
+
const fns = entries.filter((e) => e.kind === 'fn')
|
|
97
|
+
|
|
98
|
+
const out = [
|
|
99
|
+
'// GENERATED FROM THE RUST — do not edit.',
|
|
100
|
+
'//',
|
|
101
|
+
"// Produced by scripts/generate-napi-types.mjs from napi-derive's type-def",
|
|
102
|
+
'// output. Editing this file by hand puts it back where it started: a',
|
|
103
|
+
'// description that can disagree with the code it describes.',
|
|
104
|
+
'',
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
for (const iface of interfaces) {
|
|
108
|
+
out.push(docBlock(iface.js_doc))
|
|
109
|
+
out.push(`export interface ${iface.name} {`)
|
|
110
|
+
for (const line of iface.def.split('\n')) {
|
|
111
|
+
out.push(line ? ` ${line.trim()}` : '')
|
|
112
|
+
}
|
|
113
|
+
out.push('}', '')
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
for (const struct of structs) {
|
|
117
|
+
const impl = impls.get(struct.name)
|
|
118
|
+
out.push(docBlock(struct.js_doc))
|
|
119
|
+
out.push(`export declare class ${struct.name} {`)
|
|
120
|
+
if (impl) {
|
|
121
|
+
for (const line of refine(struct.name, impl.def).split('\n')) {
|
|
122
|
+
out.push(line ? ` ${line.trim()}` : '')
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
out.push('}', '')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for (const fn of fns) {
|
|
129
|
+
out.push(docBlock(fn.js_doc))
|
|
130
|
+
// napi-derive emits the whole declaration for a function, unlike a struct
|
|
131
|
+
// where `def` holds only the members.
|
|
132
|
+
const declaration = fn.def.trim()
|
|
133
|
+
out.push(
|
|
134
|
+
declaration.startsWith('export declare function')
|
|
135
|
+
? `${declaration};`
|
|
136
|
+
: `export declare function ${fn.name}${declaration};`,
|
|
137
|
+
'',
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const stale = Object.keys(CALLBACK_REFINEMENTS).filter((k) => !used.has(k))
|
|
142
|
+
if (stale.length > 0) {
|
|
143
|
+
console.error(
|
|
144
|
+
`[napi-types] refinement(s) that matched nothing: ${stale.join(', ')}\n` +
|
|
145
|
+
'[napi-types] the Rust changed under them — update or remove the entry.',
|
|
146
|
+
)
|
|
147
|
+
process.exit(1)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
writeFileSync(output, out.join('\n'))
|
|
151
|
+
console.log(
|
|
152
|
+
`[napi-types] ${output} — ${interfaces.length} interface(s), ${structs.length} class(es), ${fns.length} function(s)`,
|
|
153
|
+
)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { emitDbQuery, hasDbQueryListeners } from "../events.js";
|
|
6
|
+
import type { ReamDatabase as NapiReamDatabase } from "../native/generated.js";
|
|
6
7
|
import { makeTransactionQueryBuilders } from "../query/DatabaseQueryBuilder.js";
|
|
7
8
|
import {
|
|
8
9
|
type AfterHook,
|
|
@@ -28,6 +29,37 @@ function napiReplacer(_key: string, value: unknown): unknown {
|
|
|
28
29
|
return value;
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Refuse an array bound as ONE parameter.
|
|
34
|
+
*
|
|
35
|
+
* The binder has no array arm, so an array reached its catch-all and was bound
|
|
36
|
+
* as the JSON text `["a","b"]`. On SQLite that is merely wrong; on Postgres,
|
|
37
|
+
* `WHERE id = ANY($1)` makes the server read those text bytes as a binary array
|
|
38
|
+
* header, and it reports a dimension count read out of whatever happened to be
|
|
39
|
+
* there — a different number on every run. Silently sending bytes the server
|
|
40
|
+
* misreads is the failure this replaces.
|
|
41
|
+
*
|
|
42
|
+
* Expanding the list is the answer, and it is what Lucid does: `whereIn` emits
|
|
43
|
+
* `IN ($1, $2, …)` with one placeholder per value. So this names `whereIn`
|
|
44
|
+
* rather than teaching the binder a shape the query builder never produces.
|
|
45
|
+
*
|
|
46
|
+
* Only the TOP level is checked: a nested array is a JSON value inside an
|
|
47
|
+
* object being bound to a `json`/`jsonb` column, which is legitimate.
|
|
48
|
+
*/
|
|
49
|
+
function assertNoArrayParams(params: readonly unknown[] | undefined): void {
|
|
50
|
+
if (!params) return;
|
|
51
|
+
for (const [index, value] of params.entries()) {
|
|
52
|
+
if (!Array.isArray(value)) continue;
|
|
53
|
+
throw new Error(
|
|
54
|
+
`[E_ARRAY_PARAM] Parameter $${index + 1} is an array, which cannot be bound as a single value. ` +
|
|
55
|
+
"Expand it into one placeholder per element — `whereIn(column, values)` does this, " +
|
|
56
|
+
"and so does building the placeholders yourself: `IN (${values.map((_, i) => '$' + (i + 1)).join(', ')})`. " +
|
|
57
|
+
"Binding the array itself sends Postgres text where it expects an array, and it reports " +
|
|
58
|
+
"a nonsensical dimension count rather than a type error.",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
31
63
|
/**
|
|
32
64
|
* JSON reviver for napi result sets. Rebuilds `{"$bytes": …}` envelopes (emitted
|
|
33
65
|
* by the Rust decoder for BLOB/BYTEA columns) into a `Uint8Array`. Integers
|
|
@@ -160,49 +192,19 @@ export interface AsyncDatabaseConnection {
|
|
|
160
192
|
ping(): Promise<void>;
|
|
161
193
|
}
|
|
162
194
|
|
|
163
|
-
/**
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
execute(sql: string, paramsJson: string): Promise<string>;
|
|
175
|
-
/** query with a server-side statement timeout (Lucid `timeout(ms,{cancel:true})`). */
|
|
176
|
-
queryTimed(
|
|
177
|
-
sql: string,
|
|
178
|
-
paramsJson: string,
|
|
179
|
-
timeoutMs: number,
|
|
180
|
-
): Promise<string>;
|
|
181
|
-
/** execute with a server-side statement timeout (Postgres). */
|
|
182
|
-
executeTimed(
|
|
183
|
-
sql: string,
|
|
184
|
-
paramsJson: string,
|
|
185
|
-
timeoutMs: number,
|
|
186
|
-
): Promise<string>;
|
|
187
|
-
runInTransaction(batchJson: string): Promise<number>;
|
|
188
|
-
begin(isolationLevel?: string): Promise<NapiReamTransaction>;
|
|
189
|
-
close(): Promise<void>;
|
|
190
|
-
ping(): Promise<void>;
|
|
191
|
-
poolSize(): number;
|
|
192
|
-
}
|
|
193
|
-
|
|
195
|
+
/**
|
|
196
|
+
* What the `db.<platform>.node` binary exports.
|
|
197
|
+
*
|
|
198
|
+
* `ReamDatabase` and `ReamTransaction` come from `../native/generated.js`,
|
|
199
|
+
* derived from the `#[napi]` items themselves, so neither can drift from the
|
|
200
|
+
* Rust the way the restatement that used to sit here silently did. Run
|
|
201
|
+
* `pnpm build:napi-types` after touching one of those signatures.
|
|
202
|
+
*
|
|
203
|
+
* Only `connect` is named: the class is a `#[napi(factory)]`, so there is no
|
|
204
|
+
* usable constructor to reach for even though the binary exports the class.
|
|
205
|
+
*/
|
|
194
206
|
interface NapiModule {
|
|
195
|
-
ReamDatabase: {
|
|
196
|
-
connect(
|
|
197
|
-
url: string,
|
|
198
|
-
min: number,
|
|
199
|
-
max: number,
|
|
200
|
-
pragmas?: Array<[string, string]>,
|
|
201
|
-
connectRetries?: number,
|
|
202
|
-
connectBackoffMs?: number,
|
|
203
|
-
connectTimeoutMs?: number,
|
|
204
|
-
): Promise<NapiReamDatabase>;
|
|
205
|
-
};
|
|
207
|
+
ReamDatabase: { connect: typeof NapiReamDatabase.connect };
|
|
206
208
|
}
|
|
207
209
|
|
|
208
210
|
/** Connection retry / timeout knobs (see {@link createNapiConnection}). */
|
|
@@ -303,7 +305,7 @@ export async function createNapiConnection(
|
|
|
303
305
|
): Promise<{ rowsAffected: number; lastInsertId?: number }> {
|
|
304
306
|
const json = await native.execute(
|
|
305
307
|
sql,
|
|
306
|
-
JSON.stringify(params, napiReplacer),
|
|
308
|
+
(assertNoArrayParams(params), JSON.stringify(params, napiReplacer)),
|
|
307
309
|
);
|
|
308
310
|
const r = JSON.parse(json);
|
|
309
311
|
return {
|
|
@@ -318,7 +320,7 @@ export async function createNapiConnection(
|
|
|
318
320
|
): Promise<T[]> {
|
|
319
321
|
const json = await native.query(
|
|
320
322
|
sql,
|
|
321
|
-
JSON.stringify(params, napiReplacer),
|
|
323
|
+
(assertNoArrayParams(params), JSON.stringify(params, napiReplacer)),
|
|
322
324
|
);
|
|
323
325
|
return JSON.parse(json, napiReviver) as T[];
|
|
324
326
|
},
|
|
@@ -444,7 +446,8 @@ export async function createNapiConnection(
|
|
|
444
446
|
meta?: QueryMeta,
|
|
445
447
|
): Promise<T[]> {
|
|
446
448
|
return observed(sql, params, meta, async () => {
|
|
447
|
-
const paramsJson =
|
|
449
|
+
const paramsJson =
|
|
450
|
+
(assertNoArrayParams(params), JSON.stringify(params, napiReplacer));
|
|
448
451
|
const json =
|
|
449
452
|
meta?.serverTimeoutMs != null
|
|
450
453
|
? await db.queryTimed(sql, paramsJson, meta.serverTimeoutMs)
|
|
@@ -459,7 +462,8 @@ export async function createNapiConnection(
|
|
|
459
462
|
meta?: QueryMeta,
|
|
460
463
|
): Promise<{ rowsAffected: number; lastInsertId?: number }> {
|
|
461
464
|
return observed(sql, params, meta, async () => {
|
|
462
|
-
const paramsJson =
|
|
465
|
+
const paramsJson =
|
|
466
|
+
(assertNoArrayParams(params), JSON.stringify(params, napiReplacer));
|
|
463
467
|
const json =
|
|
464
468
|
meta?.serverTimeoutMs != null
|
|
465
469
|
? await db.executeTimed(sql, paramsJson, meta.serverTimeoutMs)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// GENERATED FROM THE RUST — do not edit.
|
|
2
|
+
//
|
|
3
|
+
// Produced by scripts/generate-napi-types.mjs from napi-derive's type-def
|
|
4
|
+
// output. Editing this file by hand puts it back where it started: a
|
|
5
|
+
// description that can disagree with the code it describes.
|
|
6
|
+
|
|
7
|
+
/** NAPI-exposed database connection. */
|
|
8
|
+
|
|
9
|
+
export declare class ReamDatabase {
|
|
10
|
+
/**
|
|
11
|
+
* Connect to a database. URL determines the driver:
|
|
12
|
+
* - "sqlite:path/to/db.sqlite" or "sqlite::memory:"
|
|
13
|
+
* - "postgres://user:pass@host/dbname"
|
|
14
|
+
* - "mysql://user:pass@host/dbname"
|
|
15
|
+
*/
|
|
16
|
+
static connect(
|
|
17
|
+
url: string,
|
|
18
|
+
poolMin?: number | undefined | null,
|
|
19
|
+
poolMax?: number | undefined | null,
|
|
20
|
+
pragmas?: Array<Array<string>> | undefined | null,
|
|
21
|
+
connectRetries?: number | undefined | null,
|
|
22
|
+
connectBackoffMs?: number | undefined | null,
|
|
23
|
+
connectTimeoutMs?: number | undefined | null,
|
|
24
|
+
): Promise<ReamDatabase>;
|
|
25
|
+
/** Execute a SELECT query. Returns JSON array of row objects. */
|
|
26
|
+
query(sql: string, paramsJson: string): Promise<string>;
|
|
27
|
+
/**
|
|
28
|
+
* Like {@link query} with a server-side statement timeout (Lucid
|
|
29
|
+
* `timeout(ms, { cancel: true })`) — Postgres `statement_timeout`, MySQL
|
|
30
|
+
* `MAX_EXECUTION_TIME` (SELECT). Returns JSON array of row objects.
|
|
31
|
+
*/
|
|
32
|
+
queryTimed(
|
|
33
|
+
sql: string,
|
|
34
|
+
paramsJson: string,
|
|
35
|
+
timeoutMs: number,
|
|
36
|
+
): Promise<string>;
|
|
37
|
+
/**
|
|
38
|
+
* Like {@link execute} with a server-side statement timeout (Postgres
|
|
39
|
+
* `statement_timeout`). Returns the JSON-serialized `ExecResult`.
|
|
40
|
+
*/
|
|
41
|
+
executeTimed(
|
|
42
|
+
sql: string,
|
|
43
|
+
paramsJson: string,
|
|
44
|
+
timeoutMs: number,
|
|
45
|
+
): Promise<string>;
|
|
46
|
+
/**
|
|
47
|
+
* Execute an INSERT/UPDATE/DELETE. Returns the JSON-serialized `ExecResult`
|
|
48
|
+
* (`{ rows_affected, last_insert_id }`) so the JS side can read the MySQL/
|
|
49
|
+
* SQLite auto-increment id (Lucid's insert-without-returning shape).
|
|
50
|
+
*/
|
|
51
|
+
execute(sql: string, paramsJson: string): Promise<string>;
|
|
52
|
+
/**
|
|
53
|
+
* Run a batch of `[sql, params_json]` pairs atomically in a single transaction.
|
|
54
|
+
* Accepts JSON `[[sql, params], ...]` and returns the total affected rows.
|
|
55
|
+
*/
|
|
56
|
+
runInTransaction(batchJson: string): Promise<number>;
|
|
57
|
+
/**
|
|
58
|
+
* Open an interactive transaction pinned to a single pooled connection.
|
|
59
|
+
* Returns a handle whose `query`/`execute` run on that one connection;
|
|
60
|
+
* `commit`/`rollback` release it. This is what makes a TS
|
|
61
|
+
* read-then-decide-then-write atomic — `BEGIN`/`COMMIT` pulled through the
|
|
62
|
+
* pool would land on different connections and guarantee nothing.
|
|
63
|
+
*/
|
|
64
|
+
begin(isolationLevel?: string | undefined | null): Promise<ReamTransaction>;
|
|
65
|
+
/** Health check. */
|
|
66
|
+
ping(): Promise<void>;
|
|
67
|
+
/** Get pool size. */
|
|
68
|
+
poolSize(): number;
|
|
69
|
+
/** Close the connection pool. */
|
|
70
|
+
close(): Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* An interactive transaction handle (see [`ReamDatabase::begin`]). The pinned
|
|
75
|
+
* `DbTransaction` lives in an async mutex so the separate NAPI calls
|
|
76
|
+
* (execute… → commit) all hit the SAME connection; `commit`/`rollback` take it
|
|
77
|
+
* out (the connection returns to the pool) and any later call sees a clear
|
|
78
|
+
* "transaction already finished" error instead of a silent no-op.
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
export declare class ReamTransaction {
|
|
82
|
+
/** SELECT on the pinned connection. Returns a JSON array of row objects. */
|
|
83
|
+
query(sql: string, paramsJson: string): Promise<string>;
|
|
84
|
+
/**
|
|
85
|
+
* INSERT/UPDATE/DELETE on the pinned connection. Returns the JSON-serialized
|
|
86
|
+
* `ExecResult` (`{ rows_affected, last_insert_id }`).
|
|
87
|
+
*/
|
|
88
|
+
execute(sql: string, paramsJson: string): Promise<string>;
|
|
89
|
+
/**
|
|
90
|
+
* Commit and release the connection back to the pool. Idempotent-safe: a
|
|
91
|
+
* second commit/rollback errors with "transaction already finished".
|
|
92
|
+
*/
|
|
93
|
+
commit(): Promise<void>;
|
|
94
|
+
/** Roll back and release the connection back to the pool. */
|
|
95
|
+
rollback(): Promise<void>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Compile a full statement (SELECT / INSERT / UPDATE / DELETE / DDL) via a tagged JSON spec.
|
|
100
|
+
* `dialect` is one of "sqlite" | "postgres" | "mysql".
|
|
101
|
+
* Returns a JSON-encoded `CompiledStatement` ({ statements: [...], params: [...] }).
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
export declare function compileStatement(
|
|
105
|
+
specJson: string,
|
|
106
|
+
dialect: string,
|
|
107
|
+
): string;
|
|
108
|
+
|
|
109
|
+
/** Quote a SQL identifier (validates and wraps in double quotes). */
|
|
110
|
+
|
|
111
|
+
export declare function quoteIdent(name: string): string;
|
package/src/query/native.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { createRequire } from "node:module";
|
|
|
8
8
|
import { dirname, join } from "node:path";
|
|
9
9
|
import { arch, platform } from "node:process";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
|
+
import type { compileStatement, quoteIdent } from "../native/generated.js";
|
|
11
12
|
|
|
12
13
|
const require2 = createRequire(import.meta.url);
|
|
13
14
|
const __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
@@ -20,9 +21,16 @@ const platformMap: Record<string, string> = {
|
|
|
20
21
|
"linux-arm64": "linux-arm64-gnu",
|
|
21
22
|
};
|
|
22
23
|
|
|
24
|
+
/**
|
|
25
|
+
* What the `index.<platform>.node` binary exports.
|
|
26
|
+
*
|
|
27
|
+
* The signatures come from `../native/generated.js`, derived from the `#[napi]`
|
|
28
|
+
* items themselves, so they cannot drift from the Rust the way a restatement
|
|
29
|
+
* here silently would. Run `pnpm build:napi-types` after touching one.
|
|
30
|
+
*/
|
|
23
31
|
interface NativeBinding {
|
|
24
|
-
compileStatement:
|
|
25
|
-
quoteIdent:
|
|
32
|
+
compileStatement: typeof compileStatement;
|
|
33
|
+
quoteIdent: typeof quoteIdent;
|
|
26
34
|
}
|
|
27
35
|
|
|
28
36
|
let native: NativeBinding | undefined;
|
|
@@ -212,7 +212,7 @@ export function db(conn: DbConnectionLike): Plugin {
|
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
// Typing side of the plugin — importing `@c9up/atlas/testing` augments the
|
|
215
|
-
// helix test context with `db` (the
|
|
215
|
+
// helix test context with `db` (the helix pattern).
|
|
216
216
|
declare module "@c9up/helix" {
|
|
217
217
|
interface TestContext {
|
|
218
218
|
db: DbAssertions;
|
package/src/testing/TestUtils.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* group.each.setup(() => t.db().truncate()); // teardown truncates
|
|
8
8
|
* });
|
|
9
9
|
*
|
|
10
|
-
* Each method returns the TEARDOWN function a
|
|
10
|
+
* Each method returns the TEARDOWN function a helix/helix hook uses (Adonis
|
|
11
11
|
* parity): `group.each.setup(() => testUtils.db().truncate())`.
|
|
12
12
|
*
|
|
13
13
|
* NOTE (named atlas deviation): Lucid's `withGlobalTransaction()` transparently
|