turbine-orm 0.75.0 → 0.76.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 +48 -7
- package/dist/cjs/cli/compile-query.d.ts +22 -2
- package/dist/cjs/cli/compile-query.js +49 -5
- package/dist/cjs/cli/config.d.ts +2 -0
- package/dist/cjs/cli/config.js +1 -1
- package/dist/cjs/cli/destructive.js +78 -43
- package/dist/cjs/cli/index.d.ts +95 -1
- package/dist/cjs/cli/index.js +609 -145
- package/dist/cjs/cli/mcp.js +30 -1
- package/dist/cjs/cli/pii-predicate-guard.d.ts +25 -0
- package/dist/cjs/cli/pii-predicate-guard.js +72 -12
- package/dist/cjs/cli/rate-limit.js +38 -1
- package/dist/cjs/cli/studio.js +26 -5
- package/dist/cjs/cli/ui.d.ts +33 -0
- package/dist/cjs/cli/ui.js +53 -7
- package/dist/cjs/client.d.ts +13 -1
- package/dist/cjs/client.js +1 -1
- package/dist/cjs/errors.d.ts +12 -1
- package/dist/cjs/errors.js +11 -2
- package/dist/cjs/generate.d.ts +26 -0
- package/dist/cjs/generate.js +174 -27
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/introspect.d.ts +17 -0
- package/dist/cjs/introspect.js +100 -1
- package/dist/cjs/mssql.d.ts +18 -0
- package/dist/cjs/mssql.js +20 -1
- package/dist/cjs/pipeline.js +44 -6
- package/dist/cjs/powql.js +51 -17
- package/dist/cjs/query/batched-loader.js +3 -3
- package/dist/cjs/query/builder.js +1 -1
- package/dist/cjs/query/relations.d.ts +5 -0
- package/dist/cjs/query/relations.js +141 -69
- package/dist/cjs/query/utils.d.ts +13 -0
- package/dist/cjs/query/utils.js +16 -0
- package/dist/cjs/serverless.d.ts +1 -1
- package/dist/cjs/serverless.js +1 -1
- package/dist/cjs/sqlite.d.ts +33 -1
- package/dist/cjs/sqlite.js +84 -3
- package/dist/cli/compile-query.d.ts +22 -2
- package/dist/cli/compile-query.js +50 -6
- package/dist/cli/config.d.ts +2 -0
- package/dist/cli/config.js +1 -1
- package/dist/cli/destructive.js +78 -43
- package/dist/cli/index.d.ts +95 -1
- package/dist/cli/index.js +604 -147
- package/dist/cli/mcp.js +30 -1
- package/dist/cli/pii-predicate-guard.d.ts +25 -0
- package/dist/cli/pii-predicate-guard.js +73 -13
- package/dist/cli/rate-limit.js +38 -1
- package/dist/cli/studio.js +27 -6
- package/dist/cli/ui.d.ts +33 -0
- package/dist/cli/ui.js +51 -7
- package/dist/client.d.ts +13 -1
- package/dist/client.js +1 -1
- package/dist/errors.d.ts +12 -1
- package/dist/errors.js +11 -2
- package/dist/generate.d.ts +26 -0
- package/dist/generate.js +172 -27
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/introspect.d.ts +17 -0
- package/dist/introspect.js +98 -1
- package/dist/mssql.d.ts +18 -0
- package/dist/mssql.js +20 -1
- package/dist/pipeline.js +44 -6
- package/dist/powql.js +53 -19
- package/dist/query/batched-loader.js +4 -4
- package/dist/query/builder.js +2 -2
- package/dist/query/relations.d.ts +5 -0
- package/dist/query/relations.js +141 -70
- package/dist/query/utils.d.ts +13 -0
- package/dist/query/utils.js +15 -0
- package/dist/serverless.d.ts +1 -1
- package/dist/serverless.js +1 -1
- package/dist/sqlite.d.ts +33 -1
- package/dist/sqlite.js +85 -4
- package/package.json +2 -2
package/dist/sqlite.js
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
* - **Case-insensitive matching** uses `COLLATE NOCASE`, which is **ASCII-only**
|
|
35
35
|
* (no Unicode case folding).
|
|
36
36
|
*
|
|
37
|
-
* ## Example, `:memory:` database
|
|
37
|
+
* ## Example, `:memory:` database, from a generated schema
|
|
38
38
|
*
|
|
39
39
|
* ```ts
|
|
40
40
|
* import { turbineSqlite } from 'turbine-orm/sqlite';
|
|
@@ -44,11 +44,43 @@
|
|
|
44
44
|
* const users = await db.users.findMany({ with: { posts: true }, limit: 10 });
|
|
45
45
|
* await db.disconnect();
|
|
46
46
|
* ```
|
|
47
|
+
*
|
|
48
|
+
* ## Example, SQLite with no Postgres anywhere
|
|
49
|
+
*
|
|
50
|
+
* The snippet above needs `turbine generate`, which reads a live **Postgres**
|
|
51
|
+
* catalog, so it is the wrong starting point if SQLite is your only database.
|
|
52
|
+
* Describe the schema in code instead: `schemaToSQL` emits the DDL and
|
|
53
|
+
* `schemaDefToMetadata` derives the runtime metadata (relations included, from
|
|
54
|
+
* the same `references:`), both pure functions with no database involved.
|
|
55
|
+
*
|
|
56
|
+
* ```ts
|
|
57
|
+
* import { defineSchema, schemaDefToMetadata, schemaToSQL } from 'turbine-orm';
|
|
58
|
+
* import { sqliteDialect, turbineSqlite } from 'turbine-orm/sqlite';
|
|
59
|
+
*
|
|
60
|
+
* const schema = defineSchema({
|
|
61
|
+
* users: { id: { type: 'serial', primaryKey: true }, email: { type: 'text', notNull: true } },
|
|
62
|
+
* posts: {
|
|
63
|
+
* id: { type: 'serial', primaryKey: true },
|
|
64
|
+
* userId: { type: 'integer', notNull: true, references: 'users.id' },
|
|
65
|
+
* title: { type: 'text', notNull: true },
|
|
66
|
+
* },
|
|
67
|
+
* });
|
|
68
|
+
*
|
|
69
|
+
* const db = turbineSqlite(':memory:', schemaDefToMetadata(schema));
|
|
70
|
+
* for (const stmt of schemaToSQL(schema, { dialect: sqliteDialect })) {
|
|
71
|
+
* await db.raw([stmt] as never);
|
|
72
|
+
* }
|
|
73
|
+
*
|
|
74
|
+
* // `db.table(...)`, not `db.users`: the typed property accessors are emitted
|
|
75
|
+
* // by `turbine generate`, and this path skips it.
|
|
76
|
+
* const users = await db.table('users').findMany({ with: { posts: true }, orderBy: { id: 'asc' } });
|
|
77
|
+
* await db.disconnect();
|
|
78
|
+
* ```
|
|
47
79
|
*/
|
|
48
80
|
import { createRequire } from 'node:module';
|
|
49
81
|
import { TurbineClient } from './client.js';
|
|
50
82
|
import { postgresDialect, } from './dialect.js';
|
|
51
|
-
import { ConnectionError, UnsupportedFeatureError } from './errors.js';
|
|
83
|
+
import { ConnectionError, UnsupportedFeatureError, ValidationError } from './errors.js';
|
|
52
84
|
import { applyTableFilters, deriveEngineRelations } from './introspect.js';
|
|
53
85
|
import { LRUCache } from './query/utils.js';
|
|
54
86
|
import { isDateType, snakeToCamel, } from './schema.js';
|
|
@@ -241,8 +273,24 @@ function statementReturnsRows(sql) {
|
|
|
241
273
|
* recognizable SQLite error.
|
|
242
274
|
*
|
|
243
275
|
* `wrapPgError` is invoked downstream (in the query executor and the
|
|
244
|
-
* transaction proxy), so
|
|
245
|
-
*
|
|
276
|
+
* transaction proxy), so annotation is the preferred half of the job: every
|
|
277
|
+
* failure whose meaning Postgres already has a SQLSTATE for is expressed as
|
|
278
|
+
* that SQLSTATE and typed by the one classifier.
|
|
279
|
+
*
|
|
280
|
+
* ONE family cannot be handled that way, and it was the family a new user hits
|
|
281
|
+
* first. `SQLITE_ERROR` (primary code 1, "SQL logic error") covers
|
|
282
|
+
* `no such table: users`, `no such column: emial`, and a plain syntax error;
|
|
283
|
+
* Postgres reports the same three as 42P01 / 42703 / 42601, and `wrapPgError`
|
|
284
|
+
* classifies none of them, on either engine. So on SQLite they reached the
|
|
285
|
+
* caller as a bare `Error` with `code: 'ERR_SQLITE_ERROR'`: no `TURBINE_E0NN`,
|
|
286
|
+
* no `.docsUrl`, not a `TurbineError` at all, in a library whose typed-error
|
|
287
|
+
* table is a headline feature. {@link sqliteLogicError} builds the
|
|
288
|
+
* `ValidationError` for them here, the way `wrapPowdbError` builds its errors
|
|
289
|
+
* for a driver with no SQLSTATEs to annotate.
|
|
290
|
+
*
|
|
291
|
+
* Everything else stays annotation, so the two engines cannot drift: fixing a
|
|
292
|
+
* constraint classification in `wrapPgError` fixes it for SQLite in the same
|
|
293
|
+
* commit.
|
|
246
294
|
*/
|
|
247
295
|
function augmentSqliteError(err) {
|
|
248
296
|
if (!err || typeof err !== 'object')
|
|
@@ -290,10 +338,43 @@ function augmentSqliteError(err) {
|
|
|
290
338
|
if (primary === 5 || primary === 6) {
|
|
291
339
|
// Map to serialization_failure so withRetry()/$retry() retry it.
|
|
292
340
|
target.code = '40001';
|
|
341
|
+
return err;
|
|
293
342
|
}
|
|
343
|
+
// SQLITE_ERROR (1): the statement itself is wrong. See the note above.
|
|
344
|
+
if (primary === 1)
|
|
345
|
+
return sqliteLogicError(message, err);
|
|
294
346
|
return err;
|
|
295
347
|
}
|
|
296
348
|
}
|
|
349
|
+
/**
|
|
350
|
+
* The `ValidationError` for a `SQLITE_ERROR`, with a hint for the two spellings
|
|
351
|
+
* that account for nearly all of them.
|
|
352
|
+
*
|
|
353
|
+
* The driver's own text IS the diagnosis (`no such table: users` names the
|
|
354
|
+
* table), so it is kept verbatim rather than replaced.
|
|
355
|
+
*
|
|
356
|
+
* The original error IS attached as `cause`, and safe mode is not a reason not
|
|
357
|
+
* to: `TurbineError` runs every cause it is given through `redactCauseForMode`,
|
|
358
|
+
* so attaching one goes THROUGH the redaction rather than around it. It also
|
|
359
|
+
* discloses nothing new here, because the driver's message is already quoted
|
|
360
|
+
* verbatim into the message above it. What it buys is the stack: without it a
|
|
361
|
+
* `no such table` surfaces with Turbine's frames and none of the driver's.
|
|
362
|
+
*/
|
|
363
|
+
function sqliteLogicError(message, cause) {
|
|
364
|
+
const missingTable = /^no such table:\s*(\S+)/i.exec(message);
|
|
365
|
+
if (missingTable) {
|
|
366
|
+
return new ValidationError(`[turbine] SQLite has no table "${missingTable[1]}": ${message}. ` +
|
|
367
|
+
'Create it first (run your migrations, or execute the CREATE TABLE statements for this schema); ' +
|
|
368
|
+
'an in-memory database starts empty on every connection.', { cause });
|
|
369
|
+
}
|
|
370
|
+
const missingColumn = /^no such column:\s*(\S+)/i.exec(message);
|
|
371
|
+
if (missingColumn) {
|
|
372
|
+
return new ValidationError(`[turbine] SQLite has no column "${missingColumn[1]}": ${message}. ` +
|
|
373
|
+
'Check the spelling against the table as it exists in this database, ' +
|
|
374
|
+
'and re-run `turbine generate` if the schema has changed.', { cause });
|
|
375
|
+
}
|
|
376
|
+
return new ValidationError(`[turbine] SQLite rejected the statement: ${message}`, { cause });
|
|
377
|
+
}
|
|
297
378
|
function normalizeQueryArgs(arg, values) {
|
|
298
379
|
if (typeof arg === 'string')
|
|
299
380
|
return { text: arg, params: values ?? [] };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "turbine-orm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.76.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).",
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
"//coverage:cli": "The CLI coverage gate, split into ONE collection run plus a per-file threshold check for every file in it, and an aggregate. 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.",
|
|
129
129
|
"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:error-catalog && npm run coverage:cli:gate:mcp && npm run coverage:cli:gate:compile-query && npm run coverage:cli:gate:studio && npm run coverage:cli:gate:migrate && npm run coverage:cli:gate:aggregate",
|
|
130
130
|
"//coverage:cli:collect": "--check-coverage=false is load-bearing, not tidying. c8 reads .c8rc.json for defaults, so this COLLECTION step was silently enforcing the MAIN gate's global thresholds against a src/cli-only file set. That is a gate nobody wrote and nobody wanted, and it went unnoticed only because the main floor was low enough (75) for the CLI aggregate (89.29) to clear it by accident. When the main floors were re-baselined to 93/89/78 (see //merge-bug in .c8rc.json) it started failing here, several steps before the per-file gates that are supposed to decide. Thresholds for these files belong to the coverage:cli:gate:* scripts, which pass their own explicitly.",
|
|
131
|
-
"coverage:cli:collect": "DATABASE_URL= c8 --all --check-coverage=false --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 --include src/cli/mcp.ts --include src/cli/compile-query.ts --include src/cli/error-catalog.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/tracking-table-race.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 src/test/mcp.test.ts src/test/mcp-relations.test.ts src/test/mcp-pii.test.ts src/test/mcp-pii-round2.test.ts src/test/mcp-agent-tools.test.ts src/test/mcp-compile-query.test.ts",
|
|
131
|
+
"coverage:cli:collect": "DATABASE_URL= c8 --all --check-coverage=false --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 --include src/cli/mcp.ts --include src/cli/compile-query.ts --include src/cli/error-catalog.ts tsx --test src/test/studio-write.test.ts src/test/pii-guard-symmetry.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/tracking-table-race.test.ts src/test/destructive-dynamic-assembly.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 src/test/mcp.test.ts src/test/mcp-relations.test.ts src/test/mcp-pii.test.ts src/test/mcp-pii-round2.test.ts src/test/mcp-agent-tools.test.ts src/test/mcp-compile-query.test.ts src/test/mcp-perimeter-bounds.test.ts",
|
|
132
132
|
"coverage:cli:gate": "c8 report --all --exclude 'src/test/**' --reporter text --check-coverage",
|
|
133
133
|
"coverage:cli:gate:destructive": "npm run coverage:cli:gate -- --include src/cli/destructive.ts --lines 98 --statements 98 --branches 84 --functions 98",
|
|
134
134
|
"coverage:cli:gate:sql-statements": "npm run coverage:cli:gate -- --include src/cli/sql-statements.ts --lines 100 --statements 100 --branches 98 --functions 100",
|