turbine-orm 0.44.0 → 0.45.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/dist/cjs/powdb.js CHANGED
@@ -254,6 +254,7 @@ const POWDB_FEATURE_MIN_VERSION = {
254
254
  docFieldIndexes: '0.13',
255
255
  serverJoins: '0.13',
256
256
  nestedProjections: '0.18',
257
+ entityLinks: '0.19',
257
258
  };
258
259
  /**
259
260
  * Trusted-caller default: every FEATURE gate on, engine version unknown. Used
@@ -264,6 +265,9 @@ const POWDB_FEATURE_MIN_VERSION = {
264
265
  * never inferred from a bare construction. `nestedProjections` stays OFF for
265
266
  * the same reason: it changes the generated PowQL for every `with` query, and
266
267
  * an unprobed engine below 0.18 would reject the syntax outright.
268
+ * `entityLinks` stays OFF for a stronger reason still: declaring a link
269
+ * one-way-upgrades the on-disk catalog to v7 and locks out pre-0.19 binaries,
270
+ * so it must only ever light up behind a real version probe.
267
271
  */
268
272
  exports.ALL_POWDB_CAPABILITIES = {
269
273
  engineVersion: null,
@@ -272,6 +276,7 @@ exports.ALL_POWDB_CAPABILITIES = {
272
276
  introspection: true,
273
277
  serverJoins: true,
274
278
  nestedProjections: false,
279
+ entityLinks: false,
275
280
  nativeRaw: false,
276
281
  };
277
282
  /** Parse a PowDB semver prefix (`0.13.0`, `0.13`, `1.2.3-rc`) into components, or `null`. */
@@ -301,6 +306,7 @@ function capabilitiesFromVersion(version, opts = {}) {
301
306
  introspection: false,
302
307
  serverJoins: false,
303
308
  nestedProjections: false,
309
+ entityLinks: false,
304
310
  nativeRaw: false,
305
311
  };
306
312
  }
@@ -311,6 +317,7 @@ function capabilitiesFromVersion(version, opts = {}) {
311
317
  docFieldIndexes: atLeastVersion(sem, 0, 13),
312
318
  serverJoins: atLeastVersion(sem, 0, 13),
313
319
  nestedProjections: atLeastVersion(sem, 0, 18),
320
+ entityLinks: atLeastVersion(sem, 0, 19),
314
321
  nativeRaw: Boolean(opts.hasNativeRaw) && atLeastVersion(sem, 0, 13),
315
322
  };
316
323
  }
@@ -810,6 +817,15 @@ function wrapPowdbError(err) {
810
817
  return new errors_js_1.ConnectionError(`[turbine] PowDB could not open the directory read-only: ${msg}. Open it once with a writable handle to ` +
811
818
  'flush the WAL (recover the directory), then reopen it read-only for snapshot serving.', { cause: err });
812
819
  }
820
+ // Catalog-version mismatch → ConnectionError (E004). A pre-0.19 binary/addon
821
+ // cannot open a data directory whose catalog was upgraded to v7 by a `link`
822
+ // declaration (the upgrade is one-way). It is an open-time connection failure,
823
+ // not a query defect, so it runs among the connection families BEFORE the
824
+ // generic validation regexes below.
825
+ if (/unsupported catalog version/i.test(msg)) {
826
+ return new errors_js_1.ConnectionError(`[turbine] PowDB could not open the data directory: ${msg}. This directory uses a newer PowDB catalog ` +
827
+ 'format (a `link` declaration upgrades it to v7); upgrade the PowDB addon/server to a version that can read it.', { cause: err });
828
+ }
813
829
  // Per-query deadline → TimeoutError (E002). Message-path so it fires on the
814
830
  // embedded transport too (code is always 'GenericFailure' there); retryable.
815
831
  // Pass the engine prose through the message override (same pattern as the
@@ -1497,8 +1513,14 @@ function encodePowqlLiteral(value) {
1497
1513
  * {@link PowdbEmbeddedPool.exec}): an embedded addon whose engine line exceeds
1498
1514
  * the ceiling yet still routes through the string wire is refused rather than
1499
1515
  * materialized.
1516
+ *
1517
+ * Verification for the 0.19 line: `crates/query/src/lexer.rs` is byte-identical
1518
+ * between v0.18.0 and v0.19.0 (empty git diff), and `link` was already a lexer
1519
+ * keyword at 0.18.0. The 0.19 entity-links surface adds new STATEMENTS built from
1520
+ * pre-existing tokens, so the tokenization / escape surface this ceiling guards is
1521
+ * unmoved.
1500
1522
  */
1501
- exports.POWQL_LEXER_TESTED_CEILING = '0.18';
1523
+ exports.POWQL_LEXER_TESTED_CEILING = '0.19';
1502
1524
  /** Escape a string into a PowQL `"…"` literal, matching the engine lexer's escape rules. */
1503
1525
  function encodePowqlString(s) {
1504
1526
  let out = '"';
package/dist/cjs/powql.js CHANGED
@@ -360,7 +360,11 @@ class PowqlInterface {
360
360
  conds.push(opVal === null ? `${ref} is null` : `${lhs} = ${this.bind(opVal, params, insensitive)}`);
361
361
  break;
362
362
  case 'not':
363
- conds.push(opVal === null ? `${ref} is not null` : `not (${lhs} = ${this.bind(opVal, params, insensitive)})`);
363
+ // Spell as `lhs != $N` (not `not (lhs = $N)`) so a missing-value row is
364
+ // EXCLUDED, matching SQL null semantics on PowDB >= 0.18.2. Behavior-neutral
365
+ // on older engines (pre-0.18.2 `!=` MATCHED missing rows exactly like the
366
+ // old `not (...)` spelling), so no capability gate is needed.
367
+ conds.push(opVal === null ? `${ref} is not null` : `${lhs} != ${this.bind(opVal, params, insensitive)}`);
364
368
  break;
365
369
  case 'gt':
366
370
  conds.push(`${lhs} > ${this.bind(opVal, params, insensitive)}`);
@@ -502,11 +506,17 @@ class PowqlInterface {
502
506
  /** `lhs [not] in ($1, $2, …)` — empty list collapses to a constant. */
503
507
  buildInList(lhs, values, params, insensitive, negate) {
504
508
  if (!Array.isArray(values) || values.length === 0) {
505
- // `in []` matches nothing; `not in []` matches everything.
509
+ // `in []` matches nothing; `not in []` matches everything (SQL parity requires a
510
+ // missing-value row to match `notIn []`, so NO presence guard is appended here).
506
511
  return negate ? '(1 = 1)' : this.alwaysFalse();
507
512
  }
508
513
  const items = values.map((v) => this.bind(v, params, insensitive)).join(', ');
509
- return `${lhs} ${negate ? 'not in' : 'in'} (${items})`;
514
+ if (negate) {
515
+ // PowQL `not in` matches missing-value rows, so append `and lhs is not null`
516
+ // for SQL null-semantics parity (non-empty list only, per the empty-list note above).
517
+ return `(${lhs} not in (${items}) and ${lhs} is not null)`;
518
+ }
519
+ return `${lhs} in (${items})`;
510
520
  }
511
521
  /**
512
522
  * Pre-resolve every relation filter (`some`/`none`/`every`) in a where clause
package/dist/powdb.d.ts CHANGED
@@ -261,11 +261,19 @@ export interface PowdbCapabilities {
261
261
  * batched loaders.
262
262
  */
263
263
  nestedProjections: boolean;
264
+ /**
265
+ * ≥ 0.19: entity links (`link` DDL, scalar/block traversal). Capability is
266
+ * recognized (probe-only), but query generation deliberately does NOT consume
267
+ * links yet: turbine keeps composing its own nested projections (see the
268
+ * PowDB engine page for the rationale). Declaring a link permanently upgrades
269
+ * the on-disk catalog to v7, so this stays FALSE in ALL_POWDB_CAPABILITIES.
270
+ */
271
+ entityLinks: boolean;
264
272
  /** Networked only: server ≥ 0.13 AND the client exposes `queryNativeRaw`. */
265
273
  nativeRaw: boolean;
266
274
  }
267
275
  /** The feature-gate capability keys (everything except the version/nativeRaw metadata). */
268
- type PowdbFeatureKey = 'jsonDocs' | 'docFieldIndexes' | 'introspection' | 'serverJoins' | 'nestedProjections';
276
+ type PowdbFeatureKey = 'jsonDocs' | 'docFieldIndexes' | 'introspection' | 'serverJoins' | 'nestedProjections' | 'entityLinks';
269
277
  /**
270
278
  * Trusted-caller default: every FEATURE gate on, engine version unknown. Used
271
279
  * for a directly-constructed {@link PowdbPool} / {@link PowdbEmbeddedPool} that
@@ -275,6 +283,9 @@ type PowdbFeatureKey = 'jsonDocs' | 'docFieldIndexes' | 'introspection' | 'serve
275
283
  * never inferred from a bare construction. `nestedProjections` stays OFF for
276
284
  * the same reason: it changes the generated PowQL for every `with` query, and
277
285
  * an unprobed engine below 0.18 would reject the syntax outright.
286
+ * `entityLinks` stays OFF for a stronger reason still: declaring a link
287
+ * one-way-upgrades the on-disk catalog to v7 and locks out pre-0.19 binaries,
288
+ * so it must only ever light up behind a real version probe.
278
289
  */
279
290
  export declare const ALL_POWDB_CAPABILITIES: PowdbCapabilities;
280
291
  /**
@@ -610,8 +621,14 @@ export declare function encodePowqlLiteral(value: unknown): string;
610
621
  * {@link PowdbEmbeddedPool.exec}): an embedded addon whose engine line exceeds
611
622
  * the ceiling yet still routes through the string wire is refused rather than
612
623
  * materialized.
624
+ *
625
+ * Verification for the 0.19 line: `crates/query/src/lexer.rs` is byte-identical
626
+ * between v0.18.0 and v0.19.0 (empty git diff), and `link` was already a lexer
627
+ * keyword at 0.18.0. The 0.19 entity-links surface adds new STATEMENTS built from
628
+ * pre-existing tokens, so the tokenization / escape surface this ceiling guards is
629
+ * unmoved.
613
630
  */
614
- export declare const POWQL_LEXER_TESTED_CEILING = "0.18";
631
+ export declare const POWQL_LEXER_TESTED_CEILING = "0.19";
615
632
  /**
616
633
  * Substitute every `$N` placeholder in a generator-produced PowQL template with
617
634
  * the encoded literal of `params[N-1]`. Safe because the template is produced by
package/dist/powdb.js CHANGED
@@ -197,6 +197,7 @@ const POWDB_FEATURE_MIN_VERSION = {
197
197
  docFieldIndexes: '0.13',
198
198
  serverJoins: '0.13',
199
199
  nestedProjections: '0.18',
200
+ entityLinks: '0.19',
200
201
  };
201
202
  /**
202
203
  * Trusted-caller default: every FEATURE gate on, engine version unknown. Used
@@ -207,6 +208,9 @@ const POWDB_FEATURE_MIN_VERSION = {
207
208
  * never inferred from a bare construction. `nestedProjections` stays OFF for
208
209
  * the same reason: it changes the generated PowQL for every `with` query, and
209
210
  * an unprobed engine below 0.18 would reject the syntax outright.
211
+ * `entityLinks` stays OFF for a stronger reason still: declaring a link
212
+ * one-way-upgrades the on-disk catalog to v7 and locks out pre-0.19 binaries,
213
+ * so it must only ever light up behind a real version probe.
210
214
  */
211
215
  export const ALL_POWDB_CAPABILITIES = {
212
216
  engineVersion: null,
@@ -215,6 +219,7 @@ export const ALL_POWDB_CAPABILITIES = {
215
219
  introspection: true,
216
220
  serverJoins: true,
217
221
  nestedProjections: false,
222
+ entityLinks: false,
218
223
  nativeRaw: false,
219
224
  };
220
225
  /** Parse a PowDB semver prefix (`0.13.0`, `0.13`, `1.2.3-rc`) into components, or `null`. */
@@ -244,6 +249,7 @@ export function capabilitiesFromVersion(version, opts = {}) {
244
249
  introspection: false,
245
250
  serverJoins: false,
246
251
  nestedProjections: false,
252
+ entityLinks: false,
247
253
  nativeRaw: false,
248
254
  };
249
255
  }
@@ -254,6 +260,7 @@ export function capabilitiesFromVersion(version, opts = {}) {
254
260
  docFieldIndexes: atLeastVersion(sem, 0, 13),
255
261
  serverJoins: atLeastVersion(sem, 0, 13),
256
262
  nestedProjections: atLeastVersion(sem, 0, 18),
263
+ entityLinks: atLeastVersion(sem, 0, 19),
257
264
  nativeRaw: Boolean(opts.hasNativeRaw) && atLeastVersion(sem, 0, 13),
258
265
  };
259
266
  }
@@ -753,6 +760,15 @@ export function wrapPowdbError(err) {
753
760
  return new ConnectionError(`[turbine] PowDB could not open the directory read-only: ${msg}. Open it once with a writable handle to ` +
754
761
  'flush the WAL (recover the directory), then reopen it read-only for snapshot serving.', { cause: err });
755
762
  }
763
+ // Catalog-version mismatch → ConnectionError (E004). A pre-0.19 binary/addon
764
+ // cannot open a data directory whose catalog was upgraded to v7 by a `link`
765
+ // declaration (the upgrade is one-way). It is an open-time connection failure,
766
+ // not a query defect, so it runs among the connection families BEFORE the
767
+ // generic validation regexes below.
768
+ if (/unsupported catalog version/i.test(msg)) {
769
+ return new ConnectionError(`[turbine] PowDB could not open the data directory: ${msg}. This directory uses a newer PowDB catalog ` +
770
+ 'format (a `link` declaration upgrades it to v7); upgrade the PowDB addon/server to a version that can read it.', { cause: err });
771
+ }
756
772
  // Per-query deadline → TimeoutError (E002). Message-path so it fires on the
757
773
  // embedded transport too (code is always 'GenericFailure' there); retryable.
758
774
  // Pass the engine prose through the message override (same pattern as the
@@ -1439,8 +1455,14 @@ export function encodePowqlLiteral(value) {
1439
1455
  * {@link PowdbEmbeddedPool.exec}): an embedded addon whose engine line exceeds
1440
1456
  * the ceiling yet still routes through the string wire is refused rather than
1441
1457
  * materialized.
1458
+ *
1459
+ * Verification for the 0.19 line: `crates/query/src/lexer.rs` is byte-identical
1460
+ * between v0.18.0 and v0.19.0 (empty git diff), and `link` was already a lexer
1461
+ * keyword at 0.18.0. The 0.19 entity-links surface adds new STATEMENTS built from
1462
+ * pre-existing tokens, so the tokenization / escape surface this ceiling guards is
1463
+ * unmoved.
1442
1464
  */
1443
- export const POWQL_LEXER_TESTED_CEILING = '0.18';
1465
+ export const POWQL_LEXER_TESTED_CEILING = '0.19';
1444
1466
  /** Escape a string into a PowQL `"…"` literal, matching the engine lexer's escape rules. */
1445
1467
  function encodePowqlString(s) {
1446
1468
  let out = '"';
package/dist/powql.js CHANGED
@@ -324,7 +324,11 @@ export class PowqlInterface {
324
324
  conds.push(opVal === null ? `${ref} is null` : `${lhs} = ${this.bind(opVal, params, insensitive)}`);
325
325
  break;
326
326
  case 'not':
327
- conds.push(opVal === null ? `${ref} is not null` : `not (${lhs} = ${this.bind(opVal, params, insensitive)})`);
327
+ // Spell as `lhs != $N` (not `not (lhs = $N)`) so a missing-value row is
328
+ // EXCLUDED, matching SQL null semantics on PowDB >= 0.18.2. Behavior-neutral
329
+ // on older engines (pre-0.18.2 `!=` MATCHED missing rows exactly like the
330
+ // old `not (...)` spelling), so no capability gate is needed.
331
+ conds.push(opVal === null ? `${ref} is not null` : `${lhs} != ${this.bind(opVal, params, insensitive)}`);
328
332
  break;
329
333
  case 'gt':
330
334
  conds.push(`${lhs} > ${this.bind(opVal, params, insensitive)}`);
@@ -466,11 +470,17 @@ export class PowqlInterface {
466
470
  /** `lhs [not] in ($1, $2, …)` — empty list collapses to a constant. */
467
471
  buildInList(lhs, values, params, insensitive, negate) {
468
472
  if (!Array.isArray(values) || values.length === 0) {
469
- // `in []` matches nothing; `not in []` matches everything.
473
+ // `in []` matches nothing; `not in []` matches everything (SQL parity requires a
474
+ // missing-value row to match `notIn []`, so NO presence guard is appended here).
470
475
  return negate ? '(1 = 1)' : this.alwaysFalse();
471
476
  }
472
477
  const items = values.map((v) => this.bind(v, params, insensitive)).join(', ');
473
- return `${lhs} ${negate ? 'not in' : 'in'} (${items})`;
478
+ if (negate) {
479
+ // PowQL `not in` matches missing-value rows, so append `and lhs is not null`
480
+ // for SQL null-semantics parity (non-empty list only, per the empty-list note above).
481
+ return `(${lhs} not in (${items}) and ${lhs} is not null)`;
482
+ }
483
+ return `${lhs} in (${items})`;
474
484
  }
475
485
  /**
476
486
  * Pre-resolve every relation filter (`some`/`none`/`every`) in a where clause
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turbine-orm",
3
- "version": "0.44.0",
3
+ "version": "0.45.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": {
@@ -108,8 +108,8 @@
108
108
  "@size-limit/esbuild": "^12.1.0",
109
109
  "@size-limit/file": "^12.1.0",
110
110
  "@types/node": "^26.1.0",
111
- "@zvndev/powdb-client": "^0.18.1",
112
- "@zvndev/powdb-embedded": "^0.18.1",
111
+ "@zvndev/powdb-client": "^0.19.0",
112
+ "@zvndev/powdb-embedded": "^0.19.0",
113
113
  "c8": "^11.0.0",
114
114
  "husky": "^9.1.7",
115
115
  "lint-staged": "^17.0.8",