turbine-orm 0.34.0 → 0.36.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 (76) hide show
  1. package/README.md +18 -16
  2. package/dist/cjs/cli/index.js +109 -16
  3. package/dist/cjs/cli/migrate.js +78 -3
  4. package/dist/cjs/cli/studio-ui.generated.js +1 -1
  5. package/dist/cjs/cli/studio.js +333 -22
  6. package/dist/cjs/cli/ui.js +7 -1
  7. package/dist/cjs/client.js +26 -4
  8. package/dist/cjs/dialect.js +2 -1
  9. package/dist/cjs/errors.js +41 -1
  10. package/dist/cjs/generate.js +23 -2
  11. package/dist/cjs/index.js +4 -2
  12. package/dist/cjs/mssql.js +27 -5
  13. package/dist/cjs/mysql.js +4 -0
  14. package/dist/cjs/powdb.js +197 -25
  15. package/dist/cjs/powql.js +515 -51
  16. package/dist/cjs/query/aggregates.js +683 -0
  17. package/dist/cjs/query/batched-loader.js +2 -0
  18. package/dist/cjs/query/builder.js +361 -4508
  19. package/dist/cjs/query/filters.js +12 -0
  20. package/dist/cjs/query/relations.js +1698 -0
  21. package/dist/cjs/query/where-compile.js +180 -0
  22. package/dist/cjs/query/where.js +1491 -0
  23. package/dist/cjs/query/writes.js +680 -0
  24. package/dist/cjs/schema-builder.js +6 -0
  25. package/dist/cjs/schema-metadata.js +4 -0
  26. package/dist/cjs/schema-sql.js +265 -3
  27. package/dist/cjs/sqlite.js +4 -1
  28. package/dist/cli/index.d.ts +8 -2
  29. package/dist/cli/index.js +111 -18
  30. package/dist/cli/migrate.d.ts +24 -1
  31. package/dist/cli/migrate.js +77 -3
  32. package/dist/cli/studio-ui.generated.js +1 -1
  33. package/dist/cli/studio.d.ts +46 -13
  34. package/dist/cli/studio.js +331 -23
  35. package/dist/cli/ui.js +7 -1
  36. package/dist/client.d.ts +32 -5
  37. package/dist/client.js +26 -4
  38. package/dist/dialect.d.ts +28 -6
  39. package/dist/dialect.js +2 -1
  40. package/dist/errors.d.ts +36 -0
  41. package/dist/errors.js +39 -0
  42. package/dist/generate.js +23 -2
  43. package/dist/index.d.ts +3 -3
  44. package/dist/index.js +2 -2
  45. package/dist/mssql.js +27 -5
  46. package/dist/mysql.js +4 -0
  47. package/dist/powdb.d.ts +135 -9
  48. package/dist/powdb.js +197 -25
  49. package/dist/powql.d.ts +166 -4
  50. package/dist/powql.js +516 -52
  51. package/dist/query/aggregates.d.ts +74 -0
  52. package/dist/query/aggregates.js +641 -0
  53. package/dist/query/batched-loader.d.ts +6 -0
  54. package/dist/query/batched-loader.js +2 -0
  55. package/dist/query/builder.d.ts +98 -830
  56. package/dist/query/builder.js +366 -4513
  57. package/dist/query/deferred.d.ts +13 -2
  58. package/dist/query/filters.d.ts +7 -0
  59. package/dist/query/filters.js +11 -0
  60. package/dist/query/relations.d.ts +441 -0
  61. package/dist/query/relations.js +1627 -0
  62. package/dist/query/types.d.ts +25 -6
  63. package/dist/query/where-compile.d.ts +139 -0
  64. package/dist/query/where-compile.js +175 -0
  65. package/dist/query/where.d.ts +494 -0
  66. package/dist/query/where.js +1431 -0
  67. package/dist/query/writes.d.ts +131 -0
  68. package/dist/query/writes.js +626 -0
  69. package/dist/schema-builder.d.ts +18 -3
  70. package/dist/schema-builder.js +6 -0
  71. package/dist/schema-metadata.js +4 -0
  72. package/dist/schema-sql.d.ts +60 -3
  73. package/dist/schema-sql.js +261 -4
  74. package/dist/schema.d.ts +10 -0
  75. package/dist/sqlite.js +4 -1
  76. package/package.json +4 -4
package/dist/powql.d.ts CHANGED
@@ -57,8 +57,16 @@ export declare class PowqlInterface<T extends object = Record<string, unknown>>
57
57
  constructor(pool: PowdbPool, table: string, schema: SchemaMetadata, middlewares?: MiddlewareFn[], options?: QueryInterfaceOptions);
58
58
  /** Resolve a camelCase field name (or raw snake) to its column metadata. */
59
59
  private column;
60
- /** PowQL column reference (`.snake_name`) for a field. */
60
+ /**
61
+ * PowQL column reference for a field. Unqualified it is a dotted field
62
+ * reference (`.snake_name`), which bypasses keyword lookup. When an `alias`
63
+ * is supplied (the F2 join path) it is qualified (`alias.snake_name`) and the
64
+ * column name is backtick-quoted if it is a reserved word (a qualified
65
+ * `p.order` does NOT bypass keyword lookup, unlike the dotted `.order`).
66
+ */
61
67
  private ref;
68
+ /** Render a raw column name as a PowQL reference, qualified with `alias` when given. */
69
+ private colRefName;
62
70
  /**
63
71
  * Push a value into the param array and return its `$N` placeholder. When the
64
72
  * value targets a `float` column it is wrapped in {@link PowdbFloatParam} so
@@ -92,6 +100,13 @@ export declare class PowqlInterface<T extends object = Record<string, unknown>>
92
100
  /**
93
101
  * Compile a {@link WhereClause} into a PowQL filter expression, pushing every
94
102
  * value as a positional `$N` param. Returns `''` when there are no conditions.
103
+ *
104
+ * When `alias` is supplied (the F2 native-join path) every field reference is
105
+ * qualified with it (`.col` → `alias.col`, JSON path bases too); params bind
106
+ * exactly as in the unqualified path. The caller only ever passes an alias for
107
+ * an already-RESOLVED where (relation filters pre-resolved to literal in-lists
108
+ * by {@link resolveRelationFilters}): the relation-key branch below still
109
+ * throws, so an unresolved relation filter can never leak into a join.
95
110
  */
96
111
  private buildWhere;
97
112
  /** Build a single `field: value | operator` condition. */
@@ -160,8 +175,40 @@ export declare class PowqlInterface<T extends object = Record<string, unknown>>
160
175
  private resolveRelationCondition;
161
176
  /** Resolve a manyToMany filter through the junction to `{ sourceRefField: { in|notIn: [...] } }`. */
162
177
  private resolveManyToManyCondition;
163
- /** Resolve the set of columns to project, honouring `select` / `omit`. */
178
+ /**
179
+ * Resolve the set of columns to project, honouring `select` / `omit` and the
180
+ * query-level `includePii` opt-in. PII-tagged (`defineSchema` `pii: true`)
181
+ * columns are EXCLUDED from a default (or omit-only) projection unless
182
+ * `includePii` is true; an explicit `select` naming a PII column IS the opt-in
183
+ * and returns it regardless. Untagged tables project exactly as before.
184
+ */
164
185
  private projectedColumns;
186
+ /**
187
+ * The snake_case names of this table's PII-tagged columns. Empty for a table
188
+ * with no `pii: true` column, so untagged tables keep their prior projection.
189
+ */
190
+ private piiColumnNames;
191
+ /**
192
+ * The camelCase field names of this table's PII-tagged columns: the read
193
+ * policy applied to a write's returned row (create/update/upsert/delete accept
194
+ * no `includePii`/`select`, so their result always drops PII; you may still
195
+ * write PII fields freely).
196
+ *
197
+ * SPEC LIMITATION (PowQL): the driver contract
198
+ * (`docs/integrations/powql-for-drivers.md`) exposes `returning` only as a
199
+ * bare keyword that hands back every column; it accepts NO column list, so
200
+ * (unlike the SQL engines, which emit an explicit non-PII `RETURNING`/`OUTPUT`
201
+ * projection) the create/update/delete `returning` paths cannot exclude PII at
202
+ * the query-language level and must strip it here after the fact. This is the
203
+ * client-side strip of last resort, not defense-in-depth, for those paths; we
204
+ * do NOT reverse-engineer an undocumented projection form. The upsert path is
205
+ * different: it has no `returning` and reselects by PK through the read
206
+ * projection ({@link projectedColumns}), which already omits PII, so PII never
207
+ * crosses the wire there. If a future spec revision lets `returning` take a
208
+ * projection, switch the write paths to emit the non-PII list and this strip
209
+ * becomes a no-op like {@link parseWriteRow} on the SQL engines.
210
+ */
211
+ private stripWritePii;
165
212
  /** `{ .c1, .c2, … }` projection clause. */
166
213
  private projection;
167
214
  /**
@@ -189,6 +236,8 @@ export declare class PowqlInterface<T extends object = Record<string, unknown>>
189
236
  * read. Read statements pass a read-shaped action from {@link POWQL_READ_ACTIONS}.
190
237
  */
191
238
  private exec;
239
+ /** Build the E018 refusal for a write / `begin` on a read-only pool. */
240
+ private readOnlyError;
192
241
  /**
193
242
  * Execute one statement, with the opt-in single stale-frame READ replay. When
194
243
  * `retryStaleReads` is on and a first-statement READ fails with the stale-wire
@@ -217,13 +266,44 @@ export declare class PowqlInterface<T extends object = Record<string, unknown>>
217
266
  * rare caller with no per-result flag (hand-built test pools). */
218
267
  private shape;
219
268
  findMany(args?: FindManyArgs<T>): Promise<T[]>;
220
- /** Build + run the flat findMany select; returns raw rows + the serving wire. */
269
+ /**
270
+ * Compile the flat findMany select into PowQL (no execution), pushing values
271
+ * into `params`. Returns the query plus the RESOLVED where (relation filters
272
+ * already collapsed to literal in-lists) so the F2 join path can re-emit the
273
+ * exact parent predicate alias-qualified, and so {@link explain} can wrap it.
274
+ */
275
+ private buildFind;
276
+ /** Build + run the flat findMany select; returns raw rows, the serving wire, and the resolved where. */
221
277
  private runFind;
278
+ /**
279
+ * Diagnostic surface: compile the same PowQL {@link findMany} would run for
280
+ * `args` (no cache) and return the engine's plan as one string per line.
281
+ *
282
+ * Runs as a READ (`explain <query>`), so it is safe on a read-only pool and
283
+ * eligible for the stale-read replay. The line content is engine-owned and is
284
+ * NOT covered by semver (match plan node names / tree shape, never exact
285
+ * bytes; mirrors PowDB's own `explain` contract).
286
+ *
287
+ * Does NOT run through the middleware chain: plan text is a diagnostic, not
288
+ * entity rows, and `QueryInterface.explain` deliberately bypasses middleware
289
+ * too, so both engines agree.
290
+ */
291
+ explain(args?: FindManyArgs<T>): Promise<string[]>;
222
292
  findUnique(args: FindUniqueArgs<T>): Promise<T | null>;
223
293
  findFirst(args?: FindManyArgs<T>): Promise<T | null>;
224
294
  findUniqueOrThrow(args: FindUniqueArgs<T>): Promise<T>;
225
295
  findFirstOrThrow(args?: FindManyArgs<T>): Promise<T>;
226
- /** Load each requested relation for `parents` and attach it onto each row. */
296
+ /**
297
+ * Load each requested relation for `parents` and attach it onto each row.
298
+ *
299
+ * `parent` is supplied ONLY by the top-level {@link findMany} (its args +
300
+ * resolved where). When the effective `relationLoadStrategy` resolves to an
301
+ * explicit `'join'` and the pool advertises `serverJoins`, an eligible
302
+ * top-level relation is loaded with a native PowQL join instead of the keyed
303
+ * loaders (F2); everything else (nested `with` levels, ineligible shapes, and
304
+ * the default `'batched'` strategy) keeps the loaders. Output is byte-equal
305
+ * either way (the join reuses the same stitch / shape helpers).
306
+ */
227
307
  private loadRelations;
228
308
  /**
229
309
  * manyToMany nested read — a three-hop batched loader (no `json_agg`/join
@@ -234,6 +314,88 @@ export declare class PowqlInterface<T extends object = Record<string, unknown>>
234
314
  * (composite junction keys would need PowQL tuple-`in`, which it lacks).
235
315
  */
236
316
  private loadManyToMany;
317
+ /**
318
+ * Resolve the effective relation-load strategy: the per-query arg wins, then
319
+ * the client config, then the PowDB default of `'batched'` (the keyed
320
+ * loaders). PowDB deliberately does NOT inherit the SQL-side implicit `'join'`
321
+ * default (that would silently flip every existing PowDB user onto brand-new
322
+ * join generation). Only a value the user actually set to `'join'` activates it.
323
+ */
324
+ private resolveStrategy;
325
+ /**
326
+ * Per-relation eligibility for the join path (checked before the serverJoins
327
+ * capability). Any `false` here is a SILENT fallback to the keyed loaders (it
328
+ * is never an error), so an off-page or nested-`with` shape still returns
329
+ * correct rows:
330
+ * - the parent query must not be paged (`limit`/`offset`/`take`, including the
331
+ * configured `defaultLimit`): a parent-filter join under a page would scan
332
+ * children of off-page parents, where the loaders are strictly better;
333
+ * - the relation must not request a nested `with` (its subtree stays on the
334
+ * loaders this round) or a `distinct`;
335
+ * - single-column relation keys only (a composite key falls to the loader,
336
+ * which throws the same E017 as today);
337
+ * - the PARENT-SIDE correlation column must be a single-column PK or unique
338
+ * column, or the INNER join would re-emit one child copy per matching
339
+ * parent row (a non-unique correlation key produces duplicate children the
340
+ * loader never would). For hasMany/hasOne/m2m that column is the relation's
341
+ * `referenceKey` on THIS (fetched) table; for belongsTo it is the
342
+ * `referenceKey` on the TARGET table (the join's non-fetched side);
343
+ * - m2m keeps any `orderBy`/`limit`/`offset` on the loader (the junction-order
344
+ * stitch can't be reproduced by the 3-table join deterministically);
345
+ * - a to-one relation `limit`/`offset` (meaningless) stays on the loader, as
346
+ * does a to-many relation `limit`/`offset` when the parent set spills past
347
+ * one loader chunk (the loader limits per chunk, the join once globally).
348
+ */
349
+ private joinEligible;
350
+ /**
351
+ * True when `col` is a single-column unique key of `tableMeta`: the sole
352
+ * primary-key column, a single-column entry in `uniqueColumns` (where a
353
+ * per-column `unique: true` and an introspected single-column unique constraint
354
+ * both land), or a single-column unique index. Used by {@link joinEligible} to
355
+ * keep the INNER-join path off relations whose parent-side correlation column
356
+ * can repeat (which would duplicate children).
357
+ */
358
+ private isSingleColumnUnique;
359
+ /** Dispatch one eligible relation to the correct native-join loader. */
360
+ private loadRelationViaJoin;
361
+ /**
362
+ * manyToMany via chained joins: the target (alias `t`) → junction (alias `j`)
363
+ * → the already-fetched side (alias `p`), correlating `__tpk` from the
364
+ * junction's source key. Always a list, stitched exactly like the loader.
365
+ */
366
+ private loadManyToManyViaJoin;
367
+ /**
368
+ * The target column list to project through the join (honouring select/omit),
369
+ * with a loud guard: a real column named `__tpk` would collide with the
370
+ * reserved correlation alias, so refuse rather than silently mis-stitch.
371
+ */
372
+ private joinChildCols;
373
+ /**
374
+ * `{ __tpk: <tpkExpr>, <col>: <childAlias>.<col>, … }`. Each child column is
375
+ * ALIASED to its bare name (a bare qualified ref `c.col` would come back named
376
+ * `c.col`, not `col`) so the stitched rows shape identically to a flat select.
377
+ */
378
+ private joinProjection;
379
+ /**
380
+ * `filter <parentWhere qualified p> [and <relationWhere qualified childAlias>]`.
381
+ * The parent where is the ALREADY-RESOLVED predicate (relation filters collapsed
382
+ * to literal in-lists before the base query ran); the relation where is resolved
383
+ * on the target the same way before qualifying, so a nested relation filter in
384
+ * the relation `where` never reaches the join unresolved. Params bind in order.
385
+ */
386
+ private joinFilter;
387
+ /** Group join rows by their (normalized) `__tpk`, stripping it and shaping each child. */
388
+ private bucketByTpk;
389
+ /**
390
+ * Normalize a correlation key to a stable string map key so a parent's key
391
+ * value (a shaped entity field) and a child row's `__tpk` cell match across
392
+ * wires and column types. A `Date` maps to microseconds
393
+ * (`getTime()` ms times 1000), because a datetime correlation cell arrives as
394
+ * raw micros (bigint on the native wire, a micros string on the legacy wire),
395
+ * never as ms. bigint / number / string all stringify to the same digits, so
396
+ * an int key matches whether it came back typed or as text.
397
+ */
398
+ private joinKey;
237
399
  /** Split `data` into scalar assignments; reject relation (nested-write) keys. */
238
400
  private scalarData;
239
401
  /**