turbine-orm 0.67.0 → 0.70.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.
Files changed (56) hide show
  1. package/dist/cjs/cli/error-catalog.d.ts +77 -0
  2. package/dist/cjs/cli/error-catalog.js +388 -0
  3. package/dist/cjs/cli/index.js +3 -2
  4. package/dist/cjs/cli/mcp.d.ts +19 -3
  5. package/dist/cjs/cli/mcp.js +709 -22
  6. package/dist/cjs/cli/migrate.d.ts +19 -2
  7. package/dist/cjs/cli/observe.d.ts +2 -2
  8. package/dist/cjs/cli/observe.js +20 -2
  9. package/dist/cjs/cli/pii-predicate-guard.d.ts +6 -2
  10. package/dist/cjs/cli/pii-predicate-guard.js +6 -2
  11. package/dist/cjs/cli/studio-ui.generated.js +1 -1
  12. package/dist/cjs/client.d.ts +30 -75
  13. package/dist/cjs/client.js +31 -11
  14. package/dist/cjs/introspect.d.ts +113 -17
  15. package/dist/cjs/introspect.js +229 -33
  16. package/dist/cjs/pg-types.d.ts +153 -0
  17. package/dist/cjs/pg-types.js +38 -0
  18. package/dist/cjs/pipeline.d.ts +3 -3
  19. package/dist/cjs/query/batched-loader.d.ts +2 -2
  20. package/dist/cjs/query/builder.d.ts +2 -2
  21. package/dist/cjs/query/builder.js +10 -1
  22. package/dist/cjs/query/deferred.d.ts +6 -6
  23. package/dist/cjs/query/filters.d.ts +13 -7
  24. package/dist/cjs/query/filters.js +13 -14
  25. package/dist/cjs/query/where.d.ts +2 -2
  26. package/dist/cjs/schema-sql.d.ts +18 -0
  27. package/dist/cjs/schema-sql.js +18 -0
  28. package/dist/cli/error-catalog.d.ts +77 -0
  29. package/dist/cli/error-catalog.js +383 -0
  30. package/dist/cli/index.js +3 -2
  31. package/dist/cli/mcp.d.ts +19 -3
  32. package/dist/cli/mcp.js +709 -23
  33. package/dist/cli/migrate.d.ts +19 -2
  34. package/dist/cli/observe.d.ts +2 -2
  35. package/dist/cli/observe.js +20 -2
  36. package/dist/cli/pii-predicate-guard.d.ts +6 -2
  37. package/dist/cli/pii-predicate-guard.js +6 -2
  38. package/dist/cli/studio-ui.generated.js +1 -1
  39. package/dist/client.d.ts +30 -75
  40. package/dist/client.js +31 -11
  41. package/dist/introspect.d.ts +113 -17
  42. package/dist/introspect.js +227 -33
  43. package/dist/pg-types.d.ts +153 -0
  44. package/dist/pg-types.js +37 -0
  45. package/dist/pipeline.d.ts +3 -3
  46. package/dist/query/batched-loader.d.ts +2 -2
  47. package/dist/query/builder.d.ts +2 -2
  48. package/dist/query/builder.js +10 -1
  49. package/dist/query/deferred.d.ts +6 -6
  50. package/dist/query/filters.d.ts +13 -7
  51. package/dist/query/filters.js +13 -13
  52. package/dist/query/where-compile.js +1 -1
  53. package/dist/query/where.d.ts +2 -2
  54. package/dist/schema-sql.d.ts +18 -0
  55. package/dist/schema-sql.js +18 -0
  56. package/package.json +19 -7
@@ -67,23 +67,23 @@ export const COLUMN_REF_OPERATORS = new Set(['equals', 'not', 'gt', 'gte', 'lt',
67
67
  * dependency is fixed: `cli/` and the prisma-compat shim may import from the
68
68
  * query path, and the query path may never import from `cli/`
69
69
  * (`scripts/check-import-cycles.mjs`).
70
+ *
71
+ * ## STATUS: a landing spot, not yet a deduplication. Read this before trusting it.
72
+ *
73
+ * NOTHING IMPORTS THIS YET. The three copies listed above are all still in
74
+ * place and still hand-synced; declaring the canonical home did not by itself
75
+ * move any of them onto it. It is kept, rather than deleted as unused, because
76
+ * `cli/pii-predicate-guard.ts` names this module as where its own copy wants to
77
+ * go, and deleting the destination is the one change that makes converging
78
+ * harder. Wiring the copies up is a separate change: each one sits on a
79
+ * different walk, so each has to be re-tested on its own.
80
+ *
81
+ * So do not read this constant as evidence that the walkers agree. The comment
82
+ * on each copy is still the only thing holding them in step.
70
83
  */
71
84
  export const RELATION_FILTER_WRAPPERS = ['some', 'none', 'every', 'is', 'isNot'];
72
85
  /** {@link RELATION_FILTER_WRAPPERS} as a membership set, for the walkers. */
73
86
  export const RELATION_FILTER_WRAPPER_SET = new Set(RELATION_FILTER_WRAPPERS);
74
- /**
75
- * True when a normalized relation-filter body carries at least one cardinality
76
- * wrapper. THE predicate the SQL compiler branches on: a key that names a
77
- * relation but whose value is not one of these falls through to the scalar
78
- * path.
79
- */
80
- export function hasRelationFilterWrapper(filterObj) {
81
- for (const wrapper of RELATION_FILTER_WRAPPERS) {
82
- if (wrapper in filterObj)
83
- return true;
84
- }
85
- return false;
86
- }
87
87
  /**
88
88
  * Check if an operator value is a column reference: a plain object whose ONLY
89
89
  * key is `col` with a string value. Anything else (extra keys, non-string
@@ -34,7 +34,7 @@
34
34
  * step.
35
35
  */
36
36
  import { ValidationError } from '../errors.js';
37
- import { findArrayUniqueKey, findJsonUniqueKey, fingerprintArrayFilterShape, fingerprintJsonFilterShape, fingerprintOperatorShape, hasRelationFilterWrapper, isArrayFilter, isJsonFilter, isTextSearchFilter, isUnmatchedPlainObject, isVectorFilter, isWhereOperator, sortedKeys, VECTOR_DISTANCE_COMPARATORS, } from './filters.js';
37
+ import { findArrayUniqueKey, findJsonUniqueKey, fingerprintArrayFilterShape, fingerprintJsonFilterShape, fingerprintOperatorShape, isArrayFilter, isJsonFilter, isTextSearchFilter, isUnmatchedPlainObject, isVectorFilter, isWhereOperator, sortedKeys, VECTOR_DISTANCE_COMPARATORS, } from './filters.js';
38
38
  import { ownLookup } from './utils.js';
39
39
  /**
40
40
  * Maximum nesting of `OR` / `AND` / `NOT` combinators and relation-filter
@@ -10,9 +10,9 @@
10
10
  * instance (built once in its constructor) exposing exactly the class-resident
11
11
  * primitives this module needs. See builder.ts for the thin delegating methods.
12
12
  */
13
- import type pg from 'pg';
14
13
  import type { Dialect } from '../dialect.js';
15
14
  import { ValidationError } from '../errors.js';
15
+ import type { PgCompatQueryResult } from '../pg-types.js';
16
16
  import type { RelationDef, SchemaMetadata, TableMetadata } from '../schema.js';
17
17
  import type { TemporalInfinityReading } from './deferred.js';
18
18
  import type { ArrayFilter, ColumnRef, GlobalFilters, JsonFilter, JsonPathOrderBy, ResolvedSkipGlobalFilters, TextSearchFilter, VectorFilter, WhereClause, WhereOperator } from './types.js';
@@ -127,7 +127,7 @@ export interface BuilderCtx {
127
127
  resolveOrderByColumn(table: string, meta: TableMetadata, key: string): string;
128
128
  buildJsonPathOrderEntry(table: string, meta: TableMetadata, field: string, spec: JsonPathOrderBy, prefix: string, params?: unknown[]): string;
129
129
  toSqlColumn(field: string): string;
130
- mutationInsertId(result: pg.QueryResult): unknown;
130
+ mutationInsertId(result: PgCompatQueryResult): unknown;
131
131
  acquireSql(cacheKey: string, build: (params: unknown[]) => string): SqlCacheEntry;
132
132
  crossCheckCache(op: string, cacheKey: string, entry: SqlCacheEntry, build: (params: unknown[]) => string, collectedParams: unknown[]): void;
133
133
  readonly jsonEncoding: 'object' | 'positional';
@@ -35,6 +35,24 @@ export declare function schemaToSQL(schema: SchemaDef, options?: SchemaSqlOption
35
35
  * column list), or null when the definitions agree. Expression/partial indexes
36
36
  * in the DB never structurally match a plain column list, which is the
37
37
  * intended outcome: the operator gets a warning rather than a silent skip.
38
+ *
39
+ * ## Parser 3 of 3, and what it is safe for
40
+ *
41
+ * THREE indexdef parsers coexist in this repo, catalogued on
42
+ * `parseIndexKeyEntries` in introspect.ts: that character-by-character scanner
43
+ * (the one to prefer for new callers), `parsePlainUniqueIndexColumns` in the
44
+ * same file, and this one. The two in introspect.ts do NOT feed this function
45
+ * and a fix to either does not reach it.
46
+ *
47
+ * This still uses the `USING \w+ \(([^)]*)\)` regex the scanner was written to
48
+ * replace, so it inherits the same weaknesses: it stops at the FIRST `)`, and it
49
+ * splits on every comma, which misreads an expression key (`lower(email)`), a
50
+ * quoted identifier containing a comma or a paren, and an opclass'd key. Safe
51
+ * here because every outcome of a misread is the SAME outcome as a genuine
52
+ * mismatch: a string describing the difference, which the caller turns into a
53
+ * warning for a human to read. It never drops or alters an index, and it is
54
+ * never consulted about whether a column set is unique. Do not reuse it
55
+ * anywhere those two properties stop holding.
38
56
  */
39
57
  export declare function describeIndexDefMismatch(idx: ColumnIndexDef, indexdef: string): string | null;
40
58
  /**
@@ -384,6 +384,24 @@ function declaredIndexName(tableName, idx) {
384
384
  * column list), or null when the definitions agree. Expression/partial indexes
385
385
  * in the DB never structurally match a plain column list, which is the
386
386
  * intended outcome: the operator gets a warning rather than a silent skip.
387
+ *
388
+ * ## Parser 3 of 3, and what it is safe for
389
+ *
390
+ * THREE indexdef parsers coexist in this repo, catalogued on
391
+ * `parseIndexKeyEntries` in introspect.ts: that character-by-character scanner
392
+ * (the one to prefer for new callers), `parsePlainUniqueIndexColumns` in the
393
+ * same file, and this one. The two in introspect.ts do NOT feed this function
394
+ * and a fix to either does not reach it.
395
+ *
396
+ * This still uses the `USING \w+ \(([^)]*)\)` regex the scanner was written to
397
+ * replace, so it inherits the same weaknesses: it stops at the FIRST `)`, and it
398
+ * splits on every comma, which misreads an expression key (`lower(email)`), a
399
+ * quoted identifier containing a comma or a paren, and an opclass'd key. Safe
400
+ * here because every outcome of a misread is the SAME outcome as a genuine
401
+ * mismatch: a string describing the difference, which the caller turns into a
402
+ * warning for a human to read. It never drops or alters an index, and it is
403
+ * never consulted about whether a column set is unique. Do not reuse it
404
+ * anywhere those two properties stop holding.
387
405
  */
388
406
  export function describeIndexDefMismatch(idx, indexdef) {
389
407
  const dbUnique = /^\s*CREATE\s+UNIQUE\s+INDEX\b/i.test(indexdef);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turbine-orm",
3
- "version": "0.67.0",
3
+ "version": "0.70.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,24 +124,35 @@
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: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",
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:error-catalog && npm run coverage:cli:gate:mcp && 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 --include src/cli/mcp.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/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",
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
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
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",
133
+ "//coverage:cli:gate:mcp": "The MCP server is a network-facing read-only surface with its own PII perimeter (sample_rows never fetches a hidden column, explain_query refuses a predicate on one, sanitizeIndex strips literals out of an index definition), so it belongs in this gate for the same reason cli/studio.ts does. It was added to coverage:cli:collect with no gate of its own and was absent from the aggregate's --include list, which meant it was COLLECTED and enforced by nothing: a slower lane holding a floor of zero. Floors measured 2026-08-14 at 93.47 lines / 93.47 statements / 81.77 branches / 92.30 functions.",
134
+ "coverage:cli:gate:mcp": "npm run coverage:cli:gate -- --include src/cli/mcp.ts --lines 93 --statements 93 --branches 81 --functions 91",
135
+ "coverage:cli:gate:error-catalog": "npm run coverage:cli:gate -- --include src/cli/error-catalog.ts --lines 100 --statements 100 --branches 90 --functions 100",
133
136
  "coverage:cli:gate:studio": "npm run coverage:cli:gate -- --include src/cli/studio.ts --lines 82 --statements 82 --branches 81 --functions 86",
134
137
  "coverage:cli:gate:migrate": "npm run coverage:cli:gate -- --include src/cli/migrate.ts --lines 69 --statements 69 --branches 91 --functions 73",
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",
136
- "lint": "biome check src/",
138
+ "//coverage:cli:gate:aggregate": "Re-baselined 2026-08-14 when mcp.ts and error-catalog.ts joined the --include list: measured 88.15 lines / 88.15 statements / 85.82 branches / 87.57 functions, up from a 78/78/84/82 floor set over five files. Raise as coverage improves, never lower one without recording why.",
139
+ "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 --include src/cli/mcp.ts --include src/cli/error-catalog.ts --lines 87 --statements 87 --branches 85 --functions 86",
140
+ "//lint": "`--error-on-warnings` is load-bearing, not cosmetic. Biome exits 0 on a warning, so `npm run lint` passed while reporting an unused import, and both the lint CI job and prepublishOnly read only the exit code. Every rule this repo runs is one it means, so a warning nobody has to act on is a rule that should not be enabled.",
141
+ "lint": "biome check --error-on-warnings src/",
137
142
  "lint:fix": "biome check --write src/",
138
143
  "format": "biome format --write src/",
139
144
  "check:error-codes": "tsx scripts/check-error-codes.ts",
140
145
  "check:changelog": "node scripts/check-changelog-headings.mjs",
141
146
  "check:cycles": "node scripts/check-import-cycles.mjs",
147
+ "//check:skip-gates": "Guards the TURBINE_REQUIRE_ENGINE coupling, which is a regex against a human-readable skipGate reason string: rewording a reason silently disarms the guard and the engine job it protects goes green over a suite that never ran. Fails on a reason no engine pattern claims and no allowlist entry excuses, on a token no CI job sets, and on a workflow token that is not a known engine.",
148
+ "check:skip-gates": "tsx scripts/check-skip-gate-reasons.ts",
149
+ "//check:private-terms": "The `--all` mode of the pre-commit private-material guard, over every TRACKED file rather than the staged diff. The hook is advisory (`git commit --no-verify` skips it) and nothing downstream re-asked, so this runs in CI's lint job. `.private-terms` is gitignored, so CI enforces the builtin provenance patterns only.",
150
+ "check:private-terms": "node scripts/check-private-terms.mjs --all",
142
151
  "check:release-tests": "node scripts/check-release-tests.mjs",
143
152
  "check:package": "publint --strict && attw --pack . --profile node16",
144
- "prepublishOnly": "npm run build && npm run typecheck && npm run lint && npm run test:unit && npm run test:coverage:cli && npm run check:error-codes && npm run check:changelog && npm run check:cycles && npm run check:package && npm run size && npm run check:release-tests",
153
+ "//check:package-types": "The @types/pg tripwire, and the permanent guard on the v0.28.1 regression: no reference to the pg module family may reach a published .d.ts, or a types-only package becomes a hard requirement for every consumer compiling under strict. It lived as an inline grep in ONE ci.yml job, which left the gate proving the change safe off the publish path entirely (a tag push and a local `npm publish` both reached npm without asking). The logic is scripts/check-no-pg-types.mjs now and this script is its single entry point, called by prepublishOnly and by the `consumer-types` job in BOTH ci.yml and release.yml. Reads dist/, so it must run after `npm run build`.",
154
+ "check:package-types": "node scripts/check-no-pg-types.mjs",
155
+ "prepublishOnly": "npm run build && npm run typecheck && npm run lint && npm run test:unit && npm run test:coverage:cli && npm run check:error-codes && npm run check:changelog && npm run check:cycles && npm run check:skip-gates && npm run check:package && npm run check:package-types && npm run size && npm run check:release-tests",
145
156
  "prepack": "node scripts/strip-prepare.mjs",
146
157
  "postpack": "node scripts/restore-prepare.mjs",
147
158
  "size": "size-limit",
@@ -162,8 +173,8 @@
162
173
  "engines": {
163
174
  "node": ">=20.0.0"
164
175
  },
176
+ "//dependencies": "EXACTLY ONE runtime dependency, `pg`. `@types/pg` sat here until 0.70.0 because the published declarations named `pg.Pool` / `pg.PoolClient` / `pg.QueryResult`, which makes a types-only package a hard requirement for every consumer compiling under strict (the v0.28.1 regression). The declaration surface is now pg-free (see src/pg-types.ts) and the `consumer-types` CI job proves it against the real tarball, so the types package is a devDependency. The order is one-way: clear the surface FIRST, then move the dependency.",
165
177
  "dependencies": {
166
- "@types/pg": "^8.11.11",
167
178
  "pg": "^8.13.1"
168
179
  },
169
180
  "devDependencies": {
@@ -172,6 +183,7 @@
172
183
  "@size-limit/esbuild": "^12.1.0",
173
184
  "@size-limit/file": "^12.1.0",
174
185
  "@types/node": "^26.1.0",
186
+ "@types/pg": "^8.11.11",
175
187
  "@zvndev/powdb-client": "^0.20.0",
176
188
  "@zvndev/powdb-embedded": "^0.20.0",
177
189
  "c8": "^11.0.0",