turbine-orm 0.61.0 → 0.62.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 (82) hide show
  1. package/README.md +65 -21
  2. package/dist/cjs/cli/config.d.ts +40 -0
  3. package/dist/cjs/cli/config.js +74 -2
  4. package/dist/cjs/cli/index.d.ts +85 -1
  5. package/dist/cjs/cli/index.js +323 -24
  6. package/dist/cjs/cli/mcp.d.ts +8 -0
  7. package/dist/cjs/cli/mcp.js +448 -29
  8. package/dist/cjs/cli/pii-tags.d.ts +64 -9
  9. package/dist/cjs/cli/pii-tags.js +218 -39
  10. package/dist/cjs/cli/studio-ui.generated.js +1 -1
  11. package/dist/cjs/cli/studio.d.ts +23 -0
  12. package/dist/cjs/cli/studio.js +126 -53
  13. package/dist/cjs/cli/ui.d.ts +15 -1
  14. package/dist/cjs/cli/ui.js +19 -5
  15. package/dist/cjs/client.js +186 -3
  16. package/dist/cjs/errors.d.ts +38 -1
  17. package/dist/cjs/errors.js +235 -24
  18. package/dist/cjs/index.d.ts +2 -2
  19. package/dist/cjs/index.js +7 -2
  20. package/dist/cjs/pipeline.js +15 -2
  21. package/dist/cjs/powql.d.ts +12 -0
  22. package/dist/cjs/powql.js +46 -21
  23. package/dist/cjs/prisma-compat.d.ts +15 -5
  24. package/dist/cjs/prisma-compat.js +273 -78
  25. package/dist/cjs/query/aggregates.d.ts +1 -1
  26. package/dist/cjs/query/aggregates.js +24 -10
  27. package/dist/cjs/query/batched-loader.d.ts +9 -4
  28. package/dist/cjs/query/batched-loader.js +4 -1
  29. package/dist/cjs/query/builder.d.ts +47 -0
  30. package/dist/cjs/query/builder.js +125 -21
  31. package/dist/cjs/query/index.d.ts +3 -1
  32. package/dist/cjs/query/index.js +7 -1
  33. package/dist/cjs/query/option-surface.d.ts +11 -0
  34. package/dist/cjs/query/option-surface.js +13 -0
  35. package/dist/cjs/query/relations.d.ts +8 -0
  36. package/dist/cjs/query/relations.js +21 -1
  37. package/dist/cjs/query/types.d.ts +152 -18
  38. package/dist/cjs/query/types.js +212 -1
  39. package/dist/cjs/query/where.d.ts +3 -3
  40. package/dist/cjs/query/where.js +8 -2
  41. package/dist/cjs/query/writes.js +10 -9
  42. package/dist/cli/config.d.ts +40 -0
  43. package/dist/cli/config.js +73 -2
  44. package/dist/cli/index.d.ts +85 -1
  45. package/dist/cli/index.js +321 -26
  46. package/dist/cli/mcp.d.ts +8 -0
  47. package/dist/cli/mcp.js +448 -29
  48. package/dist/cli/pii-tags.d.ts +64 -9
  49. package/dist/cli/pii-tags.js +217 -39
  50. package/dist/cli/studio-ui.generated.js +1 -1
  51. package/dist/cli/studio.d.ts +23 -0
  52. package/dist/cli/studio.js +125 -53
  53. package/dist/cli/ui.d.ts +15 -1
  54. package/dist/cli/ui.js +18 -4
  55. package/dist/client.js +187 -4
  56. package/dist/errors.d.ts +38 -1
  57. package/dist/errors.js +234 -23
  58. package/dist/index.d.ts +2 -2
  59. package/dist/index.js +5 -2
  60. package/dist/pipeline.js +15 -2
  61. package/dist/powql.d.ts +12 -0
  62. package/dist/powql.js +46 -21
  63. package/dist/prisma-compat.d.ts +15 -5
  64. package/dist/prisma-compat.js +274 -79
  65. package/dist/query/aggregates.d.ts +1 -1
  66. package/dist/query/aggregates.js +24 -10
  67. package/dist/query/batched-loader.d.ts +9 -4
  68. package/dist/query/batched-loader.js +4 -1
  69. package/dist/query/builder.d.ts +47 -0
  70. package/dist/query/builder.js +124 -21
  71. package/dist/query/index.d.ts +3 -1
  72. package/dist/query/index.js +2 -0
  73. package/dist/query/option-surface.d.ts +11 -0
  74. package/dist/query/option-surface.js +13 -0
  75. package/dist/query/relations.d.ts +8 -0
  76. package/dist/query/relations.js +21 -1
  77. package/dist/query/types.d.ts +152 -18
  78. package/dist/query/types.js +207 -2
  79. package/dist/query/where.d.ts +3 -3
  80. package/dist/query/where.js +8 -2
  81. package/dist/query/writes.js +10 -9
  82. package/package.json +13 -3
package/dist/cjs/powql.js CHANGED
@@ -77,6 +77,10 @@ const powdb_js_1 = require("./powdb.js");
77
77
  const aggregates_js_1 = require("./query/aggregates.js");
78
78
  const compound_unique_js_1 = require("./query/compound-unique.js");
79
79
  const filters_js_1 = require("./query/filters.js");
80
+ // The privilege sentinel and its resolver: `includePii` / `allowFullTableScan`
81
+ // are unlocked ONLY by the UNSAFE symbol, on this engine exactly as on the SQL
82
+ // engines, so a spread request body cannot turn either on here either.
83
+ const types_js_1 = require("./query/types.js");
80
84
  const utils_js_1 = require("./query/utils.js");
81
85
  const where_js_1 = require("./query/where.js");
82
86
  const schema_js_1 = require("./schema.js");
@@ -990,6 +994,21 @@ class PowqlInterface {
990
994
  * contract): for identical cross-engine results pass `nulls: 'last'`
991
995
  * explicitly on Postgres, which defaults nulls-first for `desc`.
992
996
  */
997
+ /**
998
+ * Validate one orderBy direction token and return the PowQL keyword.
999
+ *
1000
+ * Every direction site on this engine used to be spelled
1001
+ * `x === 'desc' ? 'desc' : 'asc'`, so `'DESC'`, a token the CORE explicitly
1002
+ * accepts and honours, compiled to `asc` here: identical application code
1003
+ * sorted one way on Postgres and silently the opposite way on PowDB. The rule
1004
+ * is core's {@link assertDirectionToken}, reused rather than re-derived, so
1005
+ * the two engines cannot drift again. `undefined` stays "not specified"
1006
+ * (defaults asc); `null` is a VALUE and is refused like any other bad token.
1007
+ */
1008
+ dirKeyword(value, context) {
1009
+ (0, types_js_1.assertDirectionToken)(value, context);
1010
+ return typeof value === 'string' && value.toLowerCase() === 'desc' ? 'desc' : 'asc';
1011
+ }
993
1012
  buildOrder(orderBy, params, alias) {
994
1013
  if (!orderBy)
995
1014
  return '';
@@ -1010,7 +1029,7 @@ class PowqlInterface {
1010
1029
  if (spec.nulls === 'first') {
1011
1030
  throw new errors_js_1.UnsupportedFeatureError('NULLS FIRST placement', 'PowDB', `field "${field}": PowDB orders NULLs / missing keys LAST in both directions`);
1012
1031
  }
1013
- return `${this.ref(field, alias)} ${spec.sort === 'desc' ? 'desc' : 'asc'}`;
1032
+ return `${this.ref(field, alias)} ${this.dirKeyword(spec.sort, `orderBy on "${field}"`)}`;
1014
1033
  }
1015
1034
  // Name the actual feature in the refusal, a pick-row ordering
1016
1035
  // reported as "vector / distance ordering" sends users hunting for
@@ -1026,7 +1045,7 @@ class PowqlInterface {
1026
1045
  : 'object-valued ordering';
1027
1046
  throw new errors_js_1.UnsupportedFeatureError(feature, 'PowDB', `field "${field}"`);
1028
1047
  }
1029
- return `${this.ref(field, alias)} ${dir === 'desc' ? 'desc' : 'asc'}`;
1048
+ return `${this.ref(field, alias)} ${this.dirKeyword(dir, `orderBy on "${field}"`)}`;
1030
1049
  });
1031
1050
  return ` order ${parts.join(', ')}`;
1032
1051
  }
@@ -1044,7 +1063,7 @@ class PowqlInterface {
1044
1063
  // `type: 'numeric'` casts a JSON STRING number for numeric ordering; native
1045
1064
  // JSON numbers already order numerically without a cast.
1046
1065
  const expr = spec.type === 'numeric' ? `cast(${pathExpr}, "float")` : pathExpr;
1047
- return `${expr} ${spec.direction === 'desc' ? 'desc' : 'asc'}`;
1066
+ return `${expr} ${this.dirKeyword(spec.direction, `JSON-path orderBy on "${field}"`)}`;
1048
1067
  }
1049
1068
  // -------------------------------------------------------------------------
1050
1069
  // Execution plumbing
@@ -1189,7 +1208,7 @@ class PowqlInterface {
1189
1208
  if (linkPlans.length)
1190
1209
  this.attachLinkRows(entities, linkPlans, native);
1191
1210
  if (residualWith) {
1192
- await this.loadRelations(entities, residualWith, args.timeout, 0, { args, resolvedWhere }, args.includePii === true);
1211
+ await this.loadRelations(entities, residualWith, args.timeout, 0, { args, resolvedWhere }, (0, types_js_1.resolveUnsafeFlag)(args.includePii, 'includePii'));
1193
1212
  }
1194
1213
  return entities;
1195
1214
  });
@@ -1212,7 +1231,7 @@ class PowqlInterface {
1212
1231
  throw new errors_js_1.UnsupportedFeatureError('cursor pagination', 'PowDB', 'use limit/offset instead');
1213
1232
  }
1214
1233
  const resolvedWhere = await this.resolveRelationFilters(args.where, args.timeout);
1215
- const cols = this.projectedColumns(args.select, args.omit, args.includePii === true);
1234
+ const cols = this.projectedColumns(args.select, args.omit, (0, types_js_1.resolveUnsafeFlag)(args.includePii, 'includePii'));
1216
1235
  // Partition the `with` clause: nested-projection blocks vs loader residue.
1217
1236
  // A parent `distinct` never nests (distinct over a row containing a JSON
1218
1237
  // array is not a defined comparison), and a relation whose field name
@@ -1228,7 +1247,9 @@ class PowqlInterface {
1228
1247
  if (!opt)
1229
1248
  continue;
1230
1249
  const rel = this.meta.relations[relName];
1231
- const plan = rel && !cols.includes(relName) ? this.planNestedRelation(relName, rel, opt, args.includePii === true) : null;
1250
+ const plan = rel && !cols.includes(relName)
1251
+ ? this.planNestedRelation(relName, rel, opt, (0, types_js_1.resolveUnsafeFlag)(args.includePii, 'includePii'))
1252
+ : null;
1232
1253
  if (plan) {
1233
1254
  nestedPlans.push(plan);
1234
1255
  continue;
@@ -1238,7 +1259,7 @@ class PowqlInterface {
1238
1259
  // inside, only when a genuine candidate reaches it), else it stays on the
1239
1260
  // loaders.
1240
1261
  const linkPlan = rel && !cols.includes(relName)
1241
- ? await this.planLinkPathRelation(relName, rel, opt, args.includePii === true, cols, linkPlans.length + 1)
1262
+ ? await this.planLinkPathRelation(relName, rel, opt, (0, types_js_1.resolveUnsafeFlag)(args.includePii, 'includePii'), cols, linkPlans.length + 1)
1242
1263
  : null;
1243
1264
  if (linkPlan)
1244
1265
  linkPlans.push(linkPlan);
@@ -1331,7 +1352,7 @@ class PowqlInterface {
1331
1352
  if (linkPlans.length)
1332
1353
  this.attachLinkRows(entities, linkPlans, native);
1333
1354
  if (residualWith)
1334
- await this.loadRelations(entities, residualWith, args.timeout, 0, undefined, args.includePii === true);
1355
+ await this.loadRelations(entities, residualWith, args.timeout, 0, undefined, (0, types_js_1.resolveUnsafeFlag)(args.includePii, 'includePii'));
1335
1356
  return entities[0];
1336
1357
  });
1337
1358
  }
@@ -1347,7 +1368,7 @@ class PowqlInterface {
1347
1368
  if (linkPlans.length)
1348
1369
  this.attachLinkRows(entities, linkPlans, native);
1349
1370
  if (residualWith)
1350
- await this.loadRelations(entities, residualWith, args.timeout, 0, undefined, args.includePii === true);
1371
+ await this.loadRelations(entities, residualWith, args.timeout, 0, undefined, (0, types_js_1.resolveUnsafeFlag)(args.includePii, 'includePii'));
1351
1372
  return entities[0];
1352
1373
  });
1353
1374
  }
@@ -1467,7 +1488,9 @@ class PowqlInterface {
1467
1488
  where: childWhere,
1468
1489
  with: options.with,
1469
1490
  timeout: options.timeout ?? timeout,
1470
- includePii,
1491
+ // Re-entering the public findMany, so the opt-in has to be spelled
1492
+ // in its PUBLIC form (the sentinel); a plain `true` is refused there.
1493
+ includePii: includePii ? types_js_1.UNSAFE : undefined,
1471
1494
  }));
1472
1495
  for (const child of children) {
1473
1496
  const k = this.joinKey(child[childKeyField]);
@@ -1572,7 +1595,8 @@ class PowqlInterface {
1572
1595
  where,
1573
1596
  with: options.with,
1574
1597
  timeout: options.timeout ?? timeout,
1575
- includePii,
1598
+ // Public findMany, so the sentinel form. See loadRelation above.
1599
+ includePii: includePii ? types_js_1.UNSAFE : undefined,
1576
1600
  }));
1577
1601
  for (const t of targets)
1578
1602
  targetByPk.set(String(t[targetPkField]), t);
@@ -2329,7 +2353,7 @@ class PowqlInterface {
2329
2353
  const params = [];
2330
2354
  const resolvedWhere = await this.resolveRelationFilters(args.where, args.timeout);
2331
2355
  const where = this.buildWhere(resolvedWhere, params);
2332
- this.assertCompiledWhere(where, args.allowFullTableScan, 'updateMany');
2356
+ this.assertCompiledWhere(where, (0, types_js_1.resolveUnsafeFlag)(args.allowFullTableScan, 'allowFullTableScan'), 'updateMany');
2333
2357
  const setClause = this.buildUpdateAssignments(args.data, params);
2334
2358
  const filter = where ? ` filter ${where}` : '';
2335
2359
  const { rowCount } = await this.exec(`${this.qt}${filter} update { ${setClause} }`, params, args.timeout, 'updateMany');
@@ -2479,7 +2503,7 @@ class PowqlInterface {
2479
2503
  const params = [];
2480
2504
  const resolvedWhere = await this.resolveRelationFilters(args.where, args.timeout);
2481
2505
  const where = this.buildWhere(resolvedWhere, params);
2482
- this.assertCompiledWhere(where, args.allowFullTableScan, 'deleteMany');
2506
+ this.assertCompiledWhere(where, (0, types_js_1.resolveUnsafeFlag)(args.allowFullTableScan, 'allowFullTableScan'), 'deleteMany');
2483
2507
  const filter = where ? ` filter ${where}` : '';
2484
2508
  const { rowCount } = await this.exec(`${this.qt}${filter} delete`, params, args.timeout, 'deleteMany');
2485
2509
  return { count: rowCount };
@@ -2618,7 +2642,7 @@ class PowqlInterface {
2618
2642
  const powfn = fn.slice(1); // sum/avg/min/max
2619
2643
  // Same PII contract as the SQL engines: _min/_max return a stored cell.
2620
2644
  if (fn === '_min' || fn === '_max') {
2621
- (0, aggregates_js_1.assertAggregatePiiOptIn)(this.table, this.meta, field, this.column(field).name, `aggregate ${fn}`, args.includePii);
2645
+ (0, aggregates_js_1.assertAggregatePiiOptIn)(this.table, this.meta, field, this.column(field).name, `aggregate ${fn}`, (0, types_js_1.resolveUnsafeFlag)(args.includePii, 'includePii'));
2622
2646
  }
2623
2647
  acc[field] = await scalar(`${powfn}(${this.qt}${filter} { ${this.ref(field)} })`);
2624
2648
  }
@@ -2672,7 +2696,7 @@ class PowqlInterface {
2672
2696
  for (const entry of args.by) {
2673
2697
  if (typeof entry === 'string') {
2674
2698
  const col = this.column(entry);
2675
- (0, aggregates_js_1.assertAggregatePiiOptIn)(this.table, this.meta, entry, col.name, 'groupBy `by` key', args.includePii);
2699
+ (0, aggregates_js_1.assertAggregatePiiOptIn)(this.table, this.meta, entry, col.name, 'groupBy `by` key', (0, types_js_1.resolveUnsafeFlag)(args.includePii, 'includePii'));
2676
2700
  claim(entry, `column "${col.name}"`);
2677
2701
  if (col.name !== entry)
2678
2702
  claim(col.name, `column "${col.name}"`);
@@ -2686,7 +2710,7 @@ class PowqlInterface {
2686
2710
  if (!(0, powdb_js_1.isJsonColumn)(col)) {
2687
2711
  throw new errors_js_1.ValidationError(`[turbine] groupBy JSON group key on "${entry.field}" (table "${this.table}") requires a json column.`);
2688
2712
  }
2689
- (0, aggregates_js_1.assertAggregatePiiOptIn)(this.table, this.meta, entry.field, col.name, 'groupBy JSON `by` key', args.includePii);
2713
+ (0, aggregates_js_1.assertAggregatePiiOptIn)(this.table, this.meta, entry.field, col.name, 'groupBy JSON `by` key', (0, types_js_1.resolveUnsafeFlag)(args.includePii, 'includePii'));
2690
2714
  this.assertJsonPath('group key', entry.field, entry.path);
2691
2715
  const pathExpr = this.jsonPathExpr(col, entry.path, params);
2692
2716
  const alias = entry.alias ?? String(entry.path[entry.path.length - 1]);
@@ -2737,7 +2761,7 @@ class PowqlInterface {
2737
2761
  if (target === true) {
2738
2762
  const col = this.column(key);
2739
2763
  if (fn === '_min' || fn === '_max') {
2740
- (0, aggregates_js_1.assertAggregatePiiOptIn)(this.table, this.meta, key, col.name, `groupBy ${fn}`, args.includePii);
2764
+ (0, aggregates_js_1.assertAggregatePiiOptIn)(this.table, this.meta, key, col.name, `groupBy ${fn}`, (0, types_js_1.resolveUnsafeFlag)(args.includePii, 'includePii'));
2741
2765
  }
2742
2766
  claim(`${fn}_${col.name}`, `${fn} of column "${col.name}"`);
2743
2767
  const inner = `.${col.name}`;
@@ -2752,7 +2776,7 @@ class PowqlInterface {
2752
2776
  throw new errors_js_1.ValidationError(`[turbine] groupBy ${fn} target "${key}" on "${target.field}" (table "${this.table}") requires a json column.`);
2753
2777
  }
2754
2778
  if (fn === '_min' || fn === '_max') {
2755
- (0, aggregates_js_1.assertAggregatePiiOptIn)(this.table, this.meta, target.field, col.name, `groupBy ${fn} JSON target`, args.includePii);
2779
+ (0, aggregates_js_1.assertAggregatePiiOptIn)(this.table, this.meta, target.field, col.name, `groupBy ${fn} JSON target`, (0, types_js_1.resolveUnsafeFlag)(args.includePii, 'includePii'));
2756
2780
  }
2757
2781
  this.assertJsonPath(`${fn} target "${key}"`, target.field, target.path);
2758
2782
  const alwaysNumeric = fn === '_sum' || fn === '_avg';
@@ -2952,9 +2976,9 @@ class PowqlInterface {
2952
2976
  if (spec.nulls === 'first') {
2953
2977
  throw new errors_js_1.UnsupportedFeatureError('NULLS FIRST placement', 'PowDB', `groupBy orderBy "${keyForMsg}": PowDB orders NULLs / missing keys LAST in both directions`);
2954
2978
  }
2955
- return spec.sort === 'desc' ? 'desc' : 'asc';
2979
+ return this.dirKeyword(spec.sort, `groupBy orderBy "${keyForMsg}"`);
2956
2980
  }
2957
- return value === 'desc' ? 'desc' : 'asc';
2981
+ return this.dirKeyword(value, `groupBy orderBy "${keyForMsg}"`);
2958
2982
  }
2959
2983
  // -------------------------------------------------------------------------
2960
2984
  // Streaming / unsupported
@@ -2991,7 +3015,8 @@ class PowqlInterface {
2991
3015
  if (compiledWhere.length > 0)
2992
3016
  return;
2993
3017
  throw new errors_js_1.ValidationError(`[turbine] ${action} on "${this.table}" refused: the \`where\` clause is empty. ` +
2994
- `Pass \`allowFullTableScan: true\` to opt in, or check that your filter values are defined.`);
3018
+ "Pass `allowFullTableScan: UNSAFE` to opt in (import { UNSAFE } from 'turbine-orm'), " +
3019
+ 'or check that your filter values are defined.');
2995
3020
  }
2996
3021
  }
2997
3022
  exports.PowqlInterface = PowqlInterface;
@@ -40,6 +40,11 @@
40
40
  * by this layer, and a key that is neither a Prisma arg nor a turbine option
41
41
  * gets a dev-mode warning instead of vanishing (see
42
42
  * {@link PRISMA_ARG_KEYS} and `warnUnknownQueryOptions`).
43
+ * The three PRIVILEGE options among them (`skipGlobalFilters`, `includePii`,
44
+ * `allowFullTableScan`) are forwarded VERBATIM, which is what makes them safe
45
+ * here: core accepts only the `UNSAFE` sentinel, so a Prisma-shaped args
46
+ * object carrying `includePii: true` reaches core and is refused there, and
47
+ * this layer never needs its own copy of that rule.
43
48
  *
44
49
  * ## What it deliberately does NOT do (documented divergences)
45
50
  *
@@ -75,11 +80,16 @@
75
80
  * - **`limit` on `updateMany` / `deleteMany`** (Prisma 6.7+) throws. Turbine has
76
81
  * no row-bounded mass mutation, and dropping a SAFETY BOUND with a warning
77
82
  * would turn "change at most 10 rows" into "change every matching row".
78
- * - **Write projections** (`select` / `include` / `omit` on
79
- * create/update/delete/upsert), **`select` on `count`**, and
80
- * **`orderBy` / `cursor` / `take` / `skip` on `aggregate`** are accepted and
81
- * IGNORED (they are legitimate Prisma, so they never warn); the full row / a
82
- * plain number comes back.
83
+ * - **Write projections**: `select` / `omit` on create/update/delete/upsert
84
+ * narrow the RETURNED OBJECT (see {@link resolveWriteProjection}); they do
85
+ * NOT narrow the statement's `RETURNING` list, so the column still crosses
86
+ * the wire, and `pii: true` remains the SQL-level control. A relation key in
87
+ * either (or any `include` on a write) THROWS, since a write returns its own
88
+ * row only. `omit` on READS is translated to core's own `omit`, see
89
+ * {@link translateOmit}.
90
+ * - **`select` on `count`** and **`orderBy` / `cursor` / `take` / `skip` on
91
+ * `aggregate`** are accepted and IGNORED (they are legitimate Prisma, so they
92
+ * never warn); a plain number / the aggregate comes back.
83
93
  *
84
94
  * ## Type dependencies (0.41.0)
85
95
  *