turbine-orm 0.65.0 → 0.66.0
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 +34 -32
- package/dist/adapters/cockroachdb.js +21 -3
- package/dist/adapters/index.d.ts +15 -0
- package/dist/adapters/yugabytedb.js +20 -3
- package/dist/cjs/adapters/cockroachdb.js +21 -3
- package/dist/cjs/adapters/index.d.ts +15 -0
- package/dist/cjs/adapters/yugabytedb.js +20 -3
- package/dist/cjs/cli/destructive.d.ts +18 -4
- package/dist/cjs/cli/destructive.js +230 -122
- package/dist/cjs/cli/index.d.ts +21 -4
- package/dist/cjs/cli/index.js +119 -22
- package/dist/cjs/cli/mcp.d.ts +28 -8
- package/dist/cjs/cli/mcp.js +170 -127
- package/dist/cjs/cli/migrate.d.ts +134 -13
- package/dist/cjs/cli/migrate.js +349 -241
- package/dist/cjs/cli/pii-predicate-guard.d.ts +112 -0
- package/dist/cjs/cli/pii-predicate-guard.js +390 -0
- package/dist/cjs/cli/prisma-resolve.js +75 -4
- package/dist/cjs/cli/prisma-schema.d.ts +17 -1
- package/dist/cjs/cli/prisma-schema.js +83 -17
- package/dist/cjs/cli/sql-statements.d.ts +125 -0
- package/dist/cjs/cli/sql-statements.js +378 -0
- package/dist/cjs/cli/studio.js +49 -118
- package/dist/cjs/cli/ui.d.ts +1 -1
- package/dist/cjs/client.d.ts +43 -0
- package/dist/cjs/client.js +125 -6
- package/dist/cjs/dialect.d.ts +123 -0
- package/dist/cjs/dialect.js +33 -0
- package/dist/cjs/errors.d.ts +74 -1
- package/dist/cjs/errors.js +239 -25
- package/dist/cjs/index-advisor.d.ts +33 -1
- package/dist/cjs/index-advisor.js +32 -1
- package/dist/cjs/introspect.d.ts +48 -0
- package/dist/cjs/introspect.js +222 -91
- package/dist/cjs/mssql.js +43 -1
- package/dist/cjs/mysql.d.ts +5 -2
- package/dist/cjs/mysql.js +202 -17
- package/dist/cjs/nested-write.js +6 -1
- package/dist/cjs/pipeline-submittable.js +17 -3
- package/dist/cjs/pipeline.js +75 -9
- package/dist/cjs/powdb.d.ts +23 -0
- package/dist/cjs/powdb.js +33 -1
- package/dist/cjs/powql.d.ts +61 -9
- package/dist/cjs/powql.js +186 -49
- package/dist/cjs/prisma-compat.js +160 -41
- package/dist/cjs/query/aggregates.d.ts +1 -1
- package/dist/cjs/query/aggregates.js +80 -18
- package/dist/cjs/query/batched-loader.d.ts +10 -0
- package/dist/cjs/query/batched-loader.js +268 -7
- package/dist/cjs/query/builder.d.ts +73 -0
- package/dist/cjs/query/builder.js +225 -28
- package/dist/cjs/query/filters.d.ts +162 -0
- package/dist/cjs/query/filters.js +250 -1
- package/dist/cjs/query/relations.d.ts +10 -10
- package/dist/cjs/query/relations.js +93 -12
- package/dist/cjs/query/types.d.ts +14 -1
- package/dist/cjs/query/utils.d.ts +146 -2
- package/dist/cjs/query/utils.js +210 -4
- package/dist/cjs/query/warn-registry.d.ts +10 -0
- package/dist/cjs/query/warn-registry.js +10 -0
- package/dist/cjs/query/where-compile.d.ts +30 -0
- package/dist/cjs/query/where-compile.js +41 -0
- package/dist/cjs/query/where.d.ts +128 -13
- package/dist/cjs/query/where.js +215 -77
- package/dist/cjs/query/writes.d.ts +1 -1
- package/dist/cjs/query/writes.js +39 -15
- package/dist/cjs/schema-builder.d.ts +2 -1
- package/dist/cjs/schema-sql.d.ts +94 -4
- package/dist/cjs/schema-sql.js +506 -30
- package/dist/cjs/schema.d.ts +3 -1
- package/dist/cjs/sqlite.d.ts +6 -0
- package/dist/cjs/sqlite.js +151 -10
- package/dist/cjs/typed-sql.d.ts +29 -1
- package/dist/cjs/typed-sql.js +30 -12
- package/dist/cli/destructive.d.ts +18 -4
- package/dist/cli/destructive.js +229 -121
- package/dist/cli/index.d.ts +21 -4
- package/dist/cli/index.js +120 -24
- package/dist/cli/mcp.d.ts +28 -8
- package/dist/cli/mcp.js +172 -129
- package/dist/cli/migrate.d.ts +134 -13
- package/dist/cli/migrate.js +347 -238
- package/dist/cli/pii-predicate-guard.d.ts +112 -0
- package/dist/cli/pii-predicate-guard.js +386 -0
- package/dist/cli/prisma-resolve.js +75 -4
- package/dist/cli/prisma-schema.d.ts +17 -1
- package/dist/cli/prisma-schema.js +83 -17
- package/dist/cli/sql-statements.d.ts +125 -0
- package/dist/cli/sql-statements.js +373 -0
- package/dist/cli/studio.js +49 -118
- package/dist/cli/ui.d.ts +1 -1
- package/dist/client.d.ts +43 -0
- package/dist/client.js +126 -7
- package/dist/dialect.d.ts +123 -0
- package/dist/dialect.js +33 -0
- package/dist/errors.d.ts +74 -1
- package/dist/errors.js +228 -19
- package/dist/index-advisor.d.ts +33 -1
- package/dist/index-advisor.js +31 -1
- package/dist/introspect.d.ts +48 -0
- package/dist/introspect.js +221 -91
- package/dist/mssql.js +44 -2
- package/dist/mysql.d.ts +5 -2
- package/dist/mysql.js +203 -18
- package/dist/nested-write.js +7 -2
- package/dist/pipeline-submittable.js +18 -4
- package/dist/pipeline.js +76 -10
- package/dist/powdb.d.ts +23 -0
- package/dist/powdb.js +33 -2
- package/dist/powql.d.ts +61 -9
- package/dist/powql.js +187 -50
- package/dist/prisma-compat.js +160 -41
- package/dist/query/aggregates.d.ts +1 -1
- package/dist/query/aggregates.js +82 -20
- package/dist/query/batched-loader.d.ts +10 -0
- package/dist/query/batched-loader.js +270 -9
- package/dist/query/builder.d.ts +73 -0
- package/dist/query/builder.js +226 -30
- package/dist/query/filters.d.ts +162 -0
- package/dist/query/filters.js +246 -1
- package/dist/query/relations.d.ts +10 -10
- package/dist/query/relations.js +94 -14
- package/dist/query/types.d.ts +14 -1
- package/dist/query/utils.d.ts +146 -2
- package/dist/query/utils.js +204 -3
- package/dist/query/warn-registry.d.ts +10 -0
- package/dist/query/warn-registry.js +10 -0
- package/dist/query/where-compile.d.ts +30 -0
- package/dist/query/where-compile.js +40 -1
- package/dist/query/where.d.ts +128 -13
- package/dist/query/where.js +216 -80
- package/dist/query/writes.d.ts +1 -1
- package/dist/query/writes.js +40 -16
- package/dist/schema-builder.d.ts +2 -1
- package/dist/schema-sql.d.ts +94 -4
- package/dist/schema-sql.js +505 -30
- package/dist/schema.d.ts +3 -1
- package/dist/sqlite.d.ts +6 -0
- package/dist/sqlite.js +151 -10
- package/dist/typed-sql.d.ts +29 -1
- package/dist/typed-sql.js +30 -12
- package/package.json +6 -4
package/dist/schema.d.ts
CHANGED
|
@@ -115,7 +115,9 @@ export interface ColumnMetadata {
|
|
|
115
115
|
* Tagged in `defineSchema` (`pii: true`) and carried through generated
|
|
116
116
|
* metadata. A PII column is EXCLUDED from default projections: it comes back
|
|
117
117
|
* only when explicitly named in `select` or when the query passes
|
|
118
|
-
* `includePii:
|
|
118
|
+
* `includePii: UNSAFE` (full opt-in, the imported sentinel symbol, since a
|
|
119
|
+
* literal `true` is what a stray JSON body deserializes to). Studio redacts
|
|
120
|
+
* PII cells by default.
|
|
119
121
|
* Optional / defaults to `false`; untagged schemas behave exactly as before.
|
|
120
122
|
* Introspection never auto-tags PII (it is a code-first declaration).
|
|
121
123
|
*/
|
package/dist/sqlite.d.ts
CHANGED
|
@@ -67,6 +67,12 @@ export declare class SqlitePool implements PgCompatPool {
|
|
|
67
67
|
/** The underlying `node:sqlite` handle, exposed as an escape hatch (seed/DDL). */
|
|
68
68
|
readonly db: DatabaseSync;
|
|
69
69
|
private closed;
|
|
70
|
+
/**
|
|
71
|
+
* `sql -> declared-boolean result columns` (or `null` for none). Bounded like
|
|
72
|
+
* the query builder's SQL-template cache, since a caller can generate
|
|
73
|
+
* unbounded distinct statements (raw SQL, an IN-list that inlines).
|
|
74
|
+
*/
|
|
75
|
+
private readonly booleanCache;
|
|
70
76
|
constructor(db: DatabaseSync);
|
|
71
77
|
query(text: QueryArg, values?: unknown[]): Promise<any>;
|
|
72
78
|
connect(): Promise<PgCompatPoolClient>;
|
package/dist/sqlite.js
CHANGED
|
@@ -50,6 +50,7 @@ import { TurbineClient } from './client.js';
|
|
|
50
50
|
import { postgresDialect, } from './dialect.js';
|
|
51
51
|
import { ConnectionError, UnsupportedFeatureError } from './errors.js';
|
|
52
52
|
import { applyTableFilters, deriveEngineRelations } from './introspect.js';
|
|
53
|
+
import { LRUCache } from './query/utils.js';
|
|
53
54
|
import { isDateType, snakeToCamel, } from './schema.js';
|
|
54
55
|
let cachedDatabaseSync;
|
|
55
56
|
/**
|
|
@@ -123,14 +124,101 @@ function normalizeValue(value) {
|
|
|
123
124
|
}
|
|
124
125
|
return value;
|
|
125
126
|
}
|
|
126
|
-
/**
|
|
127
|
-
|
|
127
|
+
/**
|
|
128
|
+
* Convert a `node:sqlite` null-prototype row into a normalized plain object.
|
|
129
|
+
*
|
|
130
|
+
* `booleanKeys`, when present, names the result columns whose ORIGIN column is
|
|
131
|
+
* declared with a boolean affinity; their 1/0 storage is turned back into
|
|
132
|
+
* `true`/`false` (see {@link booleanResultColumns}). Anything else in such a
|
|
133
|
+
* column, and every column not listed, passes through untouched.
|
|
134
|
+
*/
|
|
135
|
+
function normalizeRow(row, booleanKeys) {
|
|
128
136
|
const out = {};
|
|
129
137
|
for (const key of Object.keys(row)) {
|
|
130
|
-
|
|
138
|
+
const value = normalizeValue(row[key]);
|
|
139
|
+
out[key] = booleanKeys?.has(key) && (value === 1 || value === 0) ? value === 1 : value;
|
|
131
140
|
}
|
|
132
141
|
return out;
|
|
133
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* The result columns of a prepared statement whose ORIGIN column is declared
|
|
145
|
+
* boolean, or `null` when there are none (the overwhelmingly common case).
|
|
146
|
+
*
|
|
147
|
+
* WHY THIS EXISTS. SQLite has no boolean storage class: a `BOOLEAN` column
|
|
148
|
+
* holds 1/0 integers. Turbine still generates `ok: boolean` for it
|
|
149
|
+
* ({@link sqliteTypeToTs} maps `/bool/` to `boolean`), so `row.ok === true` was
|
|
150
|
+
* false, `JSON.stringify(row)` emitted `1`, and a value the ORM itself wrote as
|
|
151
|
+
* `true` came back as a number. The declared type is the ONLY thing that says
|
|
152
|
+
* "this integer is a boolean", and it lives in the schema, not in the value.
|
|
153
|
+
*
|
|
154
|
+
* The decltype comes from the driver rather than from Turbine's metadata
|
|
155
|
+
* because the driver knows which TABLE each result column came from.
|
|
156
|
+
* `StatementSync.columns()` reports `type` as `sqlite3_column_decltype`, which
|
|
157
|
+
* is non-null only for a direct table-column reference and null for any
|
|
158
|
+
* expression, so a computed column can never be coerced by accident.
|
|
159
|
+
*
|
|
160
|
+
* Node gained `columns()` in 22.13 / 23.4 while this engine's floor is 22.5, so
|
|
161
|
+
* it is feature-detected: on the narrow band without it, booleans keep reading
|
|
162
|
+
* back as 1/0 exactly as before rather than the engine refusing to run.
|
|
163
|
+
*/
|
|
164
|
+
function booleanResultColumns(stmt) {
|
|
165
|
+
const columnsFn = stmt.columns;
|
|
166
|
+
if (typeof columnsFn !== 'function')
|
|
167
|
+
return null;
|
|
168
|
+
let described;
|
|
169
|
+
try {
|
|
170
|
+
described = columnsFn.call(stmt);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// A statement the driver cannot describe (some PRAGMA forms) is not worth
|
|
174
|
+
// failing a query over; skip the coercion for it.
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
let out = null;
|
|
178
|
+
for (const c of described) {
|
|
179
|
+
if (typeof c.name !== 'string' || typeof c.type !== 'string')
|
|
180
|
+
continue;
|
|
181
|
+
if (!/bool/i.test(c.type))
|
|
182
|
+
continue;
|
|
183
|
+
if (out === null)
|
|
184
|
+
out = new Set();
|
|
185
|
+
out.add(c.name);
|
|
186
|
+
}
|
|
187
|
+
return out;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* The `node:sqlite` namespace, looked up once, or `null` when this Node build
|
|
191
|
+
* has no such builtin. Deliberately separate from {@link loadDatabaseSync},
|
|
192
|
+
* which THROWS: this is a capability question asked from the pure dialect,
|
|
193
|
+
* which must stay importable on Node 20.
|
|
194
|
+
*/
|
|
195
|
+
let cachedSqliteNamespace;
|
|
196
|
+
/**
|
|
197
|
+
* Can the driver report a result column's DECLARED type?
|
|
198
|
+
*
|
|
199
|
+
* This is the same capability {@link booleanResultColumns} feature-detects, and
|
|
200
|
+
* the two have to be asked the SAME question or they split: that helper is what
|
|
201
|
+
* turns 1/0 into `true`/`false` on every direct read, and the boolean
|
|
202
|
+
* `jsonWireRule` is what does it on the join path. On the Node band with
|
|
203
|
+
* `node:sqlite` but without `StatementSync.columns()` (>= 22.5, < 22.13 / 23.4)
|
|
204
|
+
* the helper returns `null` and every direct read keeps 1/0, so a join that
|
|
205
|
+
* still converted would produce exactly the strategy-dependent type flip the
|
|
206
|
+
* rule exists to prevent, on the one runtime where nothing else can catch it.
|
|
207
|
+
*
|
|
208
|
+
* The prototype property is re-read on each call rather than memoized as a
|
|
209
|
+
* boolean, so the answer cannot outlive the capability.
|
|
210
|
+
*/
|
|
211
|
+
function driverReportsDeclaredTypes() {
|
|
212
|
+
if (cachedSqliteNamespace === undefined) {
|
|
213
|
+
try {
|
|
214
|
+
cachedSqliteNamespace = createRequire(process.cwd())('node:sqlite');
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
cachedSqliteNamespace = null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return typeof cachedSqliteNamespace?.StatementSync?.prototype?.columns === 'function';
|
|
221
|
+
}
|
|
134
222
|
// ---------------------------------------------------------------------------
|
|
135
223
|
// Statement classification + error translation
|
|
136
224
|
// ---------------------------------------------------------------------------
|
|
@@ -234,7 +322,7 @@ function toNamedBinding(values) {
|
|
|
234
322
|
}
|
|
235
323
|
return named;
|
|
236
324
|
}
|
|
237
|
-
function runStatement(db, sql, values) {
|
|
325
|
+
function runStatement(db, sql, values, booleanCache) {
|
|
238
326
|
const binding = toNamedBinding(values);
|
|
239
327
|
let stmt;
|
|
240
328
|
try {
|
|
@@ -246,7 +334,15 @@ function runStatement(db, sql, values) {
|
|
|
246
334
|
stmt.setReadBigInts(true);
|
|
247
335
|
try {
|
|
248
336
|
if (statementReturnsRows(sql)) {
|
|
249
|
-
|
|
337
|
+
// Describe each distinct statement ONCE. `null` (no boolean column) is a
|
|
338
|
+
// cached answer too, so the ordinary query pays a single Map hit per call
|
|
339
|
+
// and nothing per row.
|
|
340
|
+
let booleanKeys = booleanCache?.get(sql);
|
|
341
|
+
if (booleanKeys === undefined) {
|
|
342
|
+
booleanKeys = booleanResultColumns(stmt);
|
|
343
|
+
booleanCache?.set(sql, booleanKeys);
|
|
344
|
+
}
|
|
345
|
+
const rows = (binding ? stmt.all(binding) : stmt.all()).map((r) => normalizeRow(r, booleanKeys ?? undefined));
|
|
250
346
|
return { rows, rowCount: rows.length };
|
|
251
347
|
}
|
|
252
348
|
const info = binding ? stmt.run(binding) : stmt.run();
|
|
@@ -269,21 +365,28 @@ export class SqlitePool {
|
|
|
269
365
|
/** The underlying `node:sqlite` handle, exposed as an escape hatch (seed/DDL). */
|
|
270
366
|
db;
|
|
271
367
|
closed = false;
|
|
368
|
+
/**
|
|
369
|
+
* `sql -> declared-boolean result columns` (or `null` for none). Bounded like
|
|
370
|
+
* the query builder's SQL-template cache, since a caller can generate
|
|
371
|
+
* unbounded distinct statements (raw SQL, an IN-list that inlines).
|
|
372
|
+
*/
|
|
373
|
+
booleanCache = new LRUCache(1000);
|
|
272
374
|
constructor(db) {
|
|
273
375
|
this.db = db;
|
|
274
376
|
}
|
|
275
377
|
// biome-ignore lint/suspicious/noExplicitAny: pg-compat query is generic over the row shape; runStatement returns plain objects.
|
|
276
378
|
async query(text, values) {
|
|
277
379
|
const { text: sql, params } = normalizeQueryArgs(text, values);
|
|
278
|
-
return runStatement(this.db, sql, params);
|
|
380
|
+
return runStatement(this.db, sql, params, this.booleanCache);
|
|
279
381
|
}
|
|
280
382
|
async connect() {
|
|
281
383
|
const db = this.db;
|
|
384
|
+
const booleanCache = this.booleanCache;
|
|
282
385
|
return {
|
|
283
386
|
// biome-ignore lint/suspicious/noExplicitAny: see query() above.
|
|
284
387
|
query: async (text, values) => {
|
|
285
388
|
const { text: sql, params } = normalizeQueryArgs(text, values);
|
|
286
|
-
return runStatement(db, sql, params);
|
|
389
|
+
return runStatement(db, sql, params, booleanCache);
|
|
287
390
|
},
|
|
288
391
|
release: () => {
|
|
289
392
|
// Single shared connection, nothing to return to a pool.
|
|
@@ -367,6 +470,7 @@ export const sqliteDialect = {
|
|
|
367
470
|
supportsReturning: true,
|
|
368
471
|
supportsILike: false,
|
|
369
472
|
supportsVector: false,
|
|
473
|
+
supportsJsonContains: false,
|
|
370
474
|
// FTS5 is a virtual-table feature with its own MATCH syntax, not the
|
|
371
475
|
// `to_tsvector @@ to_tsquery` shape Turbine's `search` filter emits.
|
|
372
476
|
supportsFullTextSearch: false,
|
|
@@ -439,6 +543,33 @@ export const sqliteDialect = {
|
|
|
439
543
|
},
|
|
440
544
|
};
|
|
441
545
|
}
|
|
546
|
+
// A declared-boolean column is 1/0 in storage, and the driver shim turns
|
|
547
|
+
// that back into true/false on every direct read (see
|
|
548
|
+
// booleanResultColumns). `json_object` sees the raw integer and has no
|
|
549
|
+
// decltype to consult, so the join strategy alone would have kept handing
|
|
550
|
+
// back 1/0 while the top-level read, the batched loader and the flatten
|
|
551
|
+
// plan returned booleans, which is the same strategy-dependent type flip
|
|
552
|
+
// the bigint rule above exists to prevent. No cast is needed, only the
|
|
553
|
+
// decode: the JSON number IS the stored value, it just has to be read the
|
|
554
|
+
// same way.
|
|
555
|
+
//
|
|
556
|
+
// Gated on the SAME capability the direct-read half feature-detects (see
|
|
557
|
+
// driverReportsDeclaredTypes). Without it the shim cannot name a boolean
|
|
558
|
+
// result column, so 1/0 is the answer everywhere; converting here alone
|
|
559
|
+
// would put the flip back, pointing the other way.
|
|
560
|
+
if (t.includes('BOOL') && driverReportsDeclaredTypes()) {
|
|
561
|
+
return {
|
|
562
|
+
// Carried as TEXT because that is how the decode half is reached at
|
|
563
|
+
// all: the parser only runs `decode` on a string cell, so a rule that
|
|
564
|
+
// left the JSON number alone would never fire. The CASE narrows the
|
|
565
|
+
// carrier to EXACTLY the two values that mean true/false, so anything
|
|
566
|
+
// else a declared-boolean column happens to hold (SQLite does not
|
|
567
|
+
// enforce the type) stays in its own JSON form and reads back as the
|
|
568
|
+
// driver would have returned it, rather than being stringified.
|
|
569
|
+
sql: (ref) => `CASE WHEN ${ref} IN (0, 1) THEN CAST(${ref} AS TEXT) ELSE ${ref} END`,
|
|
570
|
+
decode: (value) => (value === '1' ? true : value === '0' ? false : value),
|
|
571
|
+
};
|
|
572
|
+
}
|
|
442
573
|
// A BLOB cannot go into JSON at all: SQLite raises "JSON cannot hold BLOB
|
|
443
574
|
// values" and the whole query fails with a raw SQL logic error, where the
|
|
444
575
|
// batched loader returns the row fine. Carry hex and rebuild the bytes.
|
|
@@ -523,9 +654,14 @@ export const sqliteDialect = {
|
|
|
523
654
|
// COLLATE NOCASE is ASCII-only (no Unicode case folding), documented limit.
|
|
524
655
|
return `${column} LIKE ${paramRef} COLLATE NOCASE`;
|
|
525
656
|
},
|
|
657
|
+
// UNREACHABLE while `supportsJsonContains` is false, and kept deliberately:
|
|
658
|
+
// it is the emulation this dialect would use if the encoding mismatch were
|
|
659
|
+
// repaired, and deleting it would delete the record of what was tried. The
|
|
660
|
+
// param is bound as JSON text (`'"gold"'`) while `json_each.value` yields the
|
|
661
|
+
// decoded SQL value (`gold`), so this predicate never holds for ANY operand.
|
|
662
|
+
// See `supportsJsonContains` in dialect.ts for the measurements and the shape
|
|
663
|
+
// a faithful version needs (`json_each.type` alongside the value).
|
|
526
664
|
buildJsonContains(column, paramRef) {
|
|
527
|
-
// Emulated containment: true when any top-level JSON value equals the param.
|
|
528
|
-
// Limited vs Postgres `@>` (no deep/object containment), jsonPathSupport='function'.
|
|
529
665
|
return `EXISTS (SELECT 1 FROM json_each(${column}) WHERE json_each.value = ${paramRef})`;
|
|
530
666
|
},
|
|
531
667
|
buildJsonPathExtract(column, pathParamRef) {
|
|
@@ -608,7 +744,12 @@ export const sqliteDialect = {
|
|
|
608
744
|
function pragma(db, sql) {
|
|
609
745
|
// PRAGMA / SELECT against sqlite_master, read-only, identifiers are SQLite
|
|
610
746
|
// catalog names (never user input here), values normalized for safe ints.
|
|
611
|
-
|
|
747
|
+
// No boolean-key set: PRAGMA output columns are catalog metadata (`notnull`,
|
|
748
|
+
// `pk`, `unique`), which are 0/1 integers the introspector reads AS integers.
|
|
749
|
+
return db
|
|
750
|
+
.prepare(sql)
|
|
751
|
+
.all()
|
|
752
|
+
.map((r) => normalizeRow(r));
|
|
612
753
|
}
|
|
613
754
|
/**
|
|
614
755
|
* Read a live SQLite database (an open `DatabaseSync` handle) into the same
|
package/dist/typed-sql.d.ts
CHANGED
|
@@ -79,7 +79,35 @@ export declare class TypedSqlQuery<T extends Record<string, unknown>> implements
|
|
|
79
79
|
private readonly sql;
|
|
80
80
|
private readonly params;
|
|
81
81
|
private readonly logging;
|
|
82
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Runs the execution under the owning client's `errorMessages` mode. Passed
|
|
84
|
+
* in rather than read here because this module must not import the client.
|
|
85
|
+
*
|
|
86
|
+
* It has to reach the EXECUTION, not the construction, and that is the whole
|
|
87
|
+
* reason it is a constructor parameter instead of a wrapper around
|
|
88
|
+
* `client.sql\`...\``: this builder is lazy, so a scope established around
|
|
89
|
+
* the tagged-template call is torn down before a single row is fetched.
|
|
90
|
+
* Wrapping the template call compiled, ran, and protected nothing.
|
|
91
|
+
*
|
|
92
|
+
* Defaults to calling through, so a `TypedSqlQuery` built outside a client
|
|
93
|
+
* behaves exactly as before.
|
|
94
|
+
*/
|
|
95
|
+
private readonly runScoped;
|
|
96
|
+
constructor(pool: PgCompatPool, sql: string, params: unknown[], logging: boolean,
|
|
97
|
+
/**
|
|
98
|
+
* Runs the execution under the owning client's `errorMessages` mode. Passed
|
|
99
|
+
* in rather than read here because this module must not import the client.
|
|
100
|
+
*
|
|
101
|
+
* It has to reach the EXECUTION, not the construction, and that is the whole
|
|
102
|
+
* reason it is a constructor parameter instead of a wrapper around
|
|
103
|
+
* `client.sql\`...\``: this builder is lazy, so a scope established around
|
|
104
|
+
* the tagged-template call is torn down before a single row is fetched.
|
|
105
|
+
* Wrapping the template call compiled, ran, and protected nothing.
|
|
106
|
+
*
|
|
107
|
+
* Defaults to calling through, so a `TypedSqlQuery` built outside a client
|
|
108
|
+
* behaves exactly as before.
|
|
109
|
+
*/
|
|
110
|
+
runScoped?: <R>(fn: () => R) => R);
|
|
83
111
|
/** Execute and return all rows. Internal; powers `then`, `one`, and `scalar`. */
|
|
84
112
|
private run;
|
|
85
113
|
/**
|
package/dist/typed-sql.js
CHANGED
|
@@ -92,24 +92,42 @@ export class TypedSqlQuery {
|
|
|
92
92
|
sql;
|
|
93
93
|
params;
|
|
94
94
|
logging;
|
|
95
|
-
|
|
95
|
+
runScoped;
|
|
96
|
+
constructor(pool, sql, params, logging,
|
|
97
|
+
/**
|
|
98
|
+
* Runs the execution under the owning client's `errorMessages` mode. Passed
|
|
99
|
+
* in rather than read here because this module must not import the client.
|
|
100
|
+
*
|
|
101
|
+
* It has to reach the EXECUTION, not the construction, and that is the whole
|
|
102
|
+
* reason it is a constructor parameter instead of a wrapper around
|
|
103
|
+
* `client.sql\`...\``: this builder is lazy, so a scope established around
|
|
104
|
+
* the tagged-template call is torn down before a single row is fetched.
|
|
105
|
+
* Wrapping the template call compiled, ran, and protected nothing.
|
|
106
|
+
*
|
|
107
|
+
* Defaults to calling through, so a `TypedSqlQuery` built outside a client
|
|
108
|
+
* behaves exactly as before.
|
|
109
|
+
*/
|
|
110
|
+
runScoped = (fn) => fn()) {
|
|
96
111
|
this.pool = pool;
|
|
97
112
|
this.sql = sql;
|
|
98
113
|
this.params = params;
|
|
99
114
|
this.logging = logging;
|
|
115
|
+
this.runScoped = runScoped;
|
|
100
116
|
}
|
|
101
117
|
/** Execute and return all rows. Internal; powers `then`, `one`, and `scalar`. */
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
118
|
+
run() {
|
|
119
|
+
return this.runScoped(async () => {
|
|
120
|
+
if (this.logging) {
|
|
121
|
+
console.log(`[turbine] Typed SQL: ${this.sql.trim().substring(0, 120)}...`);
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const result = await this.pool.query(this.sql, this.params);
|
|
125
|
+
return result.rows;
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
throw wrapPgError(err);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
113
131
|
}
|
|
114
132
|
/**
|
|
115
133
|
* PromiseLike implementation: `await db.sql<T>\`...\`` resolves to `T[]`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "turbine-orm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.66.0",
|
|
4
4
|
"description": "Postgres-native TypeScript ORM, runs on Neon, Vercel Postgres, Cloudflare, Supabase. Streaming cursors, typed errors, single-query nested relations. One dependency, no WASM engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"//exports": "Each subpath declares its types PER CONDITION. A single shared top-level \"types\" resolves to the ESM declarations for `require` too, which is TS1479 (\"is an ES module ... cannot be require()d\") for any CJS consumer on moduleResolution node16/nodenext. The require condition points at dist/cjs, which ships its own {\"type\":\"commonjs\"} package.json, so those declarations are CJS declarations. Gated in CI by publint + @arethetypeswrong/cli + a real .cts consumer typecheck (see the package-types job in ci.yml).",
|
|
@@ -124,13 +124,15 @@
|
|
|
124
124
|
"test:unit": "DATABASE_URL= tsx --test src/test/*.test.ts",
|
|
125
125
|
"test:coverage": "c8 tsx --test --test-concurrency=1 src/test/*.test.ts",
|
|
126
126
|
"//coverage:cli": "The CLI coverage gate, split into ONE collection run plus FOUR threshold checks. c8 enforces a single threshold set per invocation and its --per-file applies the SAME numbers to every file, neither of which can express 'destructive.ts holds 100 while migrate.ts holds 70'. An aggregate-only floor lets the least-covered file spend the whole slack the best-covered file earned: at the measured 3623/2951 lines, migrate.ts could fall from 70.3% to 66.6% with the aggregate still green. So each file gets its OWN floor, checked by re-reporting the coverage already on disk (c8 report re-reads ./coverage/tmp, so this costs no extra test run). The aggregate check is kept as well: it catches all three sagging together inside their individual margins. Per-file gates run FIRST because their failure names the file. DATABASE_URL is neutralized on the collection run: these test files include live migration tests that create and drop tables, and this script runs from prepublishOnly.",
|
|
127
|
-
"test:coverage:cli": "npm run coverage:cli:collect && npm run coverage:cli:gate:destructive && npm run coverage:cli:gate:studio && npm run coverage:cli:gate:migrate && npm run coverage:cli:gate:aggregate",
|
|
128
|
-
"coverage:cli:collect": "DATABASE_URL= c8 --all --reporter text --exclude 'src/test/**' --include src/cli/studio.ts --include src/cli/migrate.ts --include src/cli/destructive.ts tsx --test src/test/studio-write.test.ts src/test/studio-demo.test.ts src/test/studio.test.ts src/test/studio-security.test.ts src/test/migrate.test.ts src/test/migrate-deploy.test.ts src/test/migrate-smoke-fixes.test.ts src/test/destructive-migrations.test.ts src/test/backfill-recipe.test.ts src/test/cli.test.ts src/test/cli-diff-migration.test.ts src/test/cli-flags.test.ts src/test/cli-first-run.test.ts",
|
|
127
|
+
"test:coverage:cli": "npm run coverage:cli:collect && npm run coverage:cli:gate:destructive && npm run coverage:cli:gate:sql-statements && npm run coverage:cli:gate:pii-guard && npm run coverage:cli:gate:studio && npm run coverage:cli:gate:migrate && npm run coverage:cli:gate:aggregate",
|
|
128
|
+
"coverage:cli:collect": "DATABASE_URL= c8 --all --reporter text --exclude 'src/test/**' --include src/cli/studio.ts --include src/cli/migrate.ts --include src/cli/destructive.ts --include src/cli/sql-statements.ts --include src/cli/pii-predicate-guard.ts tsx --test src/test/studio-write.test.ts src/test/studio-demo.test.ts src/test/studio.test.ts src/test/studio-security.test.ts src/test/migrate.test.ts src/test/migrate-deploy.test.ts src/test/migrate-smoke-fixes.test.ts src/test/destructive-migrations.test.ts src/test/backfill-recipe.test.ts src/test/cli.test.ts src/test/cli-diff-migration.test.ts src/test/cli-flags.test.ts src/test/cli-first-run.test.ts",
|
|
129
129
|
"coverage:cli:gate": "c8 report --all --exclude 'src/test/**' --reporter text --check-coverage",
|
|
130
130
|
"coverage:cli:gate:destructive": "npm run coverage:cli:gate -- --include src/cli/destructive.ts --lines 98 --statements 98 --branches 84 --functions 98",
|
|
131
|
+
"coverage:cli:gate:sql-statements": "npm run coverage:cli:gate -- --include src/cli/sql-statements.ts --lines 100 --statements 100 --branches 98 --functions 100",
|
|
132
|
+
"coverage:cli:gate:pii-guard": "npm run coverage:cli:gate -- --include src/cli/pii-predicate-guard.ts --lines 97 --statements 97 --branches 88 --functions 100",
|
|
131
133
|
"coverage:cli:gate:studio": "npm run coverage:cli:gate -- --include src/cli/studio.ts --lines 82 --statements 82 --branches 81 --functions 86",
|
|
132
134
|
"coverage:cli:gate:migrate": "npm run coverage:cli:gate -- --include src/cli/migrate.ts --lines 69 --statements 69 --branches 91 --functions 73",
|
|
133
|
-
"coverage:cli:gate:aggregate": "npm run coverage:cli:gate -- --include src/cli/studio.ts --include src/cli/migrate.ts --include src/cli/destructive.ts --lines 78 --statements 78 --branches 84 --functions 82",
|
|
135
|
+
"coverage:cli:gate:aggregate": "npm run coverage:cli:gate -- --include src/cli/studio.ts --include src/cli/migrate.ts --include src/cli/destructive.ts --include src/cli/sql-statements.ts --include src/cli/pii-predicate-guard.ts --lines 78 --statements 78 --branches 84 --functions 82",
|
|
134
136
|
"lint": "biome check src/",
|
|
135
137
|
"lint:fix": "biome check --write src/",
|
|
136
138
|
"format": "biome format --write src/",
|