turbine-orm 0.51.0 → 0.52.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 (74) hide show
  1. package/README.md +33 -5
  2. package/dist/cjs/cli/studio-ui.generated.js +1 -1
  3. package/dist/cjs/client.d.ts +106 -2
  4. package/dist/cjs/client.js +111 -5
  5. package/dist/cjs/dialect.d.ts +33 -0
  6. package/dist/cjs/dialect.js +14 -0
  7. package/dist/cjs/engine-config.d.ts +49 -0
  8. package/dist/cjs/engine-config.js +19 -0
  9. package/dist/cjs/index-advisor.js +0 -0
  10. package/dist/cjs/index.d.ts +1 -1
  11. package/dist/cjs/index.js +3 -2
  12. package/dist/cjs/mssql.d.ts +8 -3
  13. package/dist/cjs/mssql.js +22 -3
  14. package/dist/cjs/mysql.d.ts +7 -3
  15. package/dist/cjs/mysql.js +20 -3
  16. package/dist/cjs/nested-write.d.ts +31 -0
  17. package/dist/cjs/nested-write.js +80 -2
  18. package/dist/cjs/powdb-introspect.d.ts +10 -1
  19. package/dist/cjs/powdb-introspect.js +10 -1
  20. package/dist/cjs/powdb.d.ts +116 -6
  21. package/dist/cjs/powdb.js +169 -10
  22. package/dist/cjs/powql.d.ts +161 -1
  23. package/dist/cjs/powql.js +299 -19
  24. package/dist/cjs/prisma-compat.d.ts +54 -8
  25. package/dist/cjs/prisma-compat.js +136 -20
  26. package/dist/cjs/query/batched-loader.d.ts +7 -0
  27. package/dist/cjs/query/batched-loader.js +97 -15
  28. package/dist/cjs/query/builder.d.ts +131 -5
  29. package/dist/cjs/query/builder.js +223 -19
  30. package/dist/cjs/query/compound-unique.js +0 -0
  31. package/dist/cjs/query/index.d.ts +1 -1
  32. package/dist/cjs/query/index.js +2 -1
  33. package/dist/cjs/query/warn-registry.d.ts +10 -0
  34. package/dist/cjs/query/warn-registry.js +10 -0
  35. package/dist/cjs/query/writes.js +115 -7
  36. package/dist/cjs/sqlite.d.ts +10 -4
  37. package/dist/cjs/sqlite.js +18 -4
  38. package/dist/cli/studio-ui.generated.js +1 -1
  39. package/dist/client.d.ts +106 -2
  40. package/dist/client.js +111 -5
  41. package/dist/dialect.d.ts +33 -0
  42. package/dist/dialect.js +14 -0
  43. package/dist/engine-config.d.ts +49 -0
  44. package/dist/engine-config.js +18 -0
  45. package/dist/index-advisor.js +0 -0
  46. package/dist/index.d.ts +1 -1
  47. package/dist/index.js +1 -1
  48. package/dist/mssql.d.ts +8 -3
  49. package/dist/mssql.js +22 -3
  50. package/dist/mysql.d.ts +7 -3
  51. package/dist/mysql.js +20 -3
  52. package/dist/nested-write.d.ts +31 -0
  53. package/dist/nested-write.js +79 -2
  54. package/dist/powdb-introspect.d.ts +10 -1
  55. package/dist/powdb-introspect.js +10 -1
  56. package/dist/powdb.d.ts +116 -6
  57. package/dist/powdb.js +167 -9
  58. package/dist/powql.d.ts +161 -1
  59. package/dist/powql.js +299 -19
  60. package/dist/prisma-compat.d.ts +54 -8
  61. package/dist/prisma-compat.js +136 -20
  62. package/dist/query/batched-loader.d.ts +7 -0
  63. package/dist/query/batched-loader.js +98 -16
  64. package/dist/query/builder.d.ts +131 -5
  65. package/dist/query/builder.js +222 -18
  66. package/dist/query/compound-unique.js +0 -0
  67. package/dist/query/index.d.ts +1 -1
  68. package/dist/query/index.js +1 -1
  69. package/dist/query/warn-registry.d.ts +10 -0
  70. package/dist/query/warn-registry.js +10 -0
  71. package/dist/query/writes.js +116 -8
  72. package/dist/sqlite.d.ts +10 -4
  73. package/dist/sqlite.js +19 -5
  74. package/package.json +3 -3
@@ -38,6 +38,18 @@ import { type PowdbPool } from './powdb.js';
38
38
  import type { MiddlewareFn, QueryInterfaceOptions } from './query/index.js';
39
39
  import type { AggregateArgs, AggregateResult, CountArgs, CreateArgs, CreateManyArgs, DeleteArgs, DeleteManyArgs, FindManyArgs, FindUniqueArgs, GroupByArgs, UpdateArgs, UpdateManyArgs, UpsertArgs } from './query/types.js';
40
40
  import { type SchemaMetadata } from './schema.js';
41
+ /**
42
+ * Max values one `in` / `notIn` on a PowDB-native `datetime` column may carry.
43
+ *
44
+ * Such a list is never sent as a list: it is expanded into an equality chain
45
+ * (see {@link PowqlInterface.buildDatetimeInList}), and PowQL spends one level
46
+ * of its 64-level nesting budget per chain term. Measured on the 0.20.0 addon:
47
+ * 63 terms parse at the top level, 61 one level deep, whatever else the
48
+ * predicate contains. 32 leaves room for the surrounding filter, and is also the
49
+ * key-chunk size the relation loaders use for a datetime correlation column, so
50
+ * a loader can never build a chain the engine will reject.
51
+ */
52
+ export declare const MAX_POWQL_DATETIME_TERMS = 32;
41
53
  /**
42
54
  * The PowQL query interface. Constructed by `turbinePowDB` via the
43
55
  * `queryInterfaceFactory` seam and cast to `QueryInterface<object>` so
@@ -95,6 +107,25 @@ export declare class PowqlInterface<T extends object = Record<string, unknown>>
95
107
  * default so a hand-built test pool never crashes the version gates.
96
108
  */
97
109
  private get capabilities();
110
+ /**
111
+ * The `limit` a query actually emits: the explicit `limit`, Prisma's `take`
112
+ * alias, then the client-level `defaultLimit`. Shared by {@link buildFind} and
113
+ * the {@link findMany} zero short-circuit so the two can never disagree about
114
+ * which limit is in force.
115
+ */
116
+ private effectiveLimit;
117
+ /**
118
+ * Reject a negative `limit` / `offset` before it reaches the engine. PowDB
119
+ * casts both with `as usize` at execution, so below engine 0.20 a negative
120
+ * limit wrapped to `usize::MAX` and silently returned EVERY row, the opposite
121
+ * of what the caller asked for; 0.20 refuses it. Validating client-side makes
122
+ * the refusal identical on every engine version and names the argument.
123
+ *
124
+ * `limit: 0` is legal and means "no rows" (SQL `LIMIT 0`). It is not emitted:
125
+ * callers short-circuit it, because PowDB's projection fast path returned ONE
126
+ * row for `limit 0` below 0.20.
127
+ */
128
+ private assertPagination;
98
129
  /** A predicate that is always false, the empty-`in` / contradiction sentinel. */
99
130
  private alwaysFalse;
100
131
  /**
@@ -109,6 +140,37 @@ export declare class PowqlInterface<T extends object = Record<string, unknown>>
109
140
  * throws, so an unresolved relation filter can never leak into a join.
110
141
  */
111
142
  private buildWhere;
143
+ /**
144
+ * Gate a predicate on a PowDB-native `datetime` column. Turbine binds a JS
145
+ * `Date` as an integer count of microseconds, and PowDB writes a timestamp
146
+ * literal as a plain integer, so every such predicate is a `DateTime` vs `Int`
147
+ * comparison. Below engine 0.20 that pairing was unhandled and fell back to
148
+ * comparing TYPE TAGS, so `>` matched every non-null row, `=` and `<` matched
149
+ * none, and the answer additionally changed with the column's access path
150
+ * (indexed vs scanned). 0.20 compares microseconds and every binary operator
151
+ * (`=`, `!=`, `<`, `<=`, `>`, `>=`) is correct.
152
+ *
153
+ * ONE gate covers the whole family, whatever spelling the caller used. `in` /
154
+ * `not in` are included because their still-broken LIST form is never emitted:
155
+ * {@link buildInList} expands a datetime list into the equality chain the
156
+ * engine does answer correctly, which is the same binary comparison this flag
157
+ * governs. So on >= 0.20 every path is served (direct predicates, relation
158
+ * filters, the batched loaders, nested projections, native joins, and the
159
+ * findUnique / update / delete / upsert by-key paths), and below 0.20 every
160
+ * path that compares against a literal is refused with one message, which
161
+ * names the read paths that do not.
162
+ *
163
+ * `is null` / `is not null` are never gated: they compare no literal and are
164
+ * correct on every version. Ordering, grouping and `min`/`max` are likewise
165
+ * unaffected (they compare datetimes against each other, never against an int),
166
+ * as are nested-projection and join correlations, which are column-to-column.
167
+ *
168
+ * Only PowDB's native `datetime` type is affected. Turbine's own DDL emits
169
+ * `int` epoch micros for a `Date` column, so a Turbine-provisioned database
170
+ * never reaches this: the exposed shape is a table created outside Turbine and
171
+ * read through `introspectPowdbDatabase`, or hand-written metadata.
172
+ */
173
+ private assertDatetimePredicateSupported;
112
174
  /** Build a single `field: value | operator` condition. */
113
175
  private buildFieldCondition;
114
176
  /**
@@ -153,8 +215,60 @@ export declare class PowqlInterface<T extends object = Record<string, unknown>>
153
215
  private bind;
154
216
  /** Bind a LIKE pattern (already escaped), lowercasing for insensitive mode. */
155
217
  private bindLike;
156
- /** `lhs [not] in ($1, $2, …)`, empty list collapses to a constant. */
218
+ /**
219
+ * `lhs [not] in ($1, $2, …)`, empty list collapses to a constant.
220
+ *
221
+ * A PowDB-native `datetime` column takes the expanded form instead (see
222
+ * {@link buildDatetimeInList}): its `in` list is still broken upstream at 0.20.
223
+ */
157
224
  private buildInList;
225
+ /**
226
+ * `in` / `not in` on a PowDB-native `datetime` column, expanded into the
227
+ * equality chain the engine answers correctly:
228
+ *
229
+ * `in` → `(.ts = $1 or .ts = $2 …)`
230
+ * `notIn` → `(.ts != $1 and .ts != $2 … and .ts is not null)`
231
+ *
232
+ * PowQL's LIST form compares a datetime column against integer timestamp
233
+ * literals by TYPE TAG as of engine 0.20 (the 0.20 timestamp fix covered the
234
+ * binary operators only): measured on the 0.20.0 addon, `filter .ts in
235
+ * (<micros>, …)` matches nothing and `not in` matches everything, while the
236
+ * identical lists against an `int` control column answer correctly. The
237
+ * expanded chain uses the operators 0.20 DID fix, so it answers correctly and
238
+ * matches the int control exactly.
239
+ *
240
+ * This is what keeps the relation family coherent: relation filters and the
241
+ * batched loaders both compile to a key `in` list, so without the rewrite the
242
+ * same relation was refused through one strategy and served through another
243
+ * (nested projections and joins correlate column to column and never emit a
244
+ * list at all).
245
+ *
246
+ * The cost is the chain's width. PowQL bounds the SHAPE of the predicate tree,
247
+ * and a flat `or` / `and` chain spends one level per term against the same
248
+ * 64-level budget as nested parens (measured on 0.20: 63 terms at the top
249
+ * level, 61 one level deep), so the expansion is capped at
250
+ * {@link MAX_POWQL_DATETIME_TERMS} with headroom for whatever predicate it
251
+ * sits inside. The loaders chunk their key lists to that cap, so only a
252
+ * caller-written list (or a relation filter matching very many distinct
253
+ * timestamps) can exceed it, and that raises a typed E017 saying so rather
254
+ * than an engine parse failure.
255
+ */
256
+ private buildDatetimeInList;
257
+ /**
258
+ * A literal `ref in (…)` clause for the hand-built key lists the relation
259
+ * loaders emit (they bypass {@link buildWhere}). Routes a PowDB-native
260
+ * `datetime` key column through the same equality-chain expansion the
261
+ * where-builder uses, so a datetime junction / correlation key behaves
262
+ * identically however the statement was assembled.
263
+ */
264
+ private inClause;
265
+ /**
266
+ * Key-chunk size for a relation loader. A PowDB-native `datetime` correlation
267
+ * column's `in` list is expanded into an equality chain, which PowQL's nesting
268
+ * budget bounds, so those keys chunk at {@link MAX_POWQL_DATETIME_TERMS}
269
+ * (more, smaller round-trips) instead of {@link MAX_RELATION_KEYS}.
270
+ */
271
+ private keyChunkSize;
158
272
  /**
159
273
  * Pre-resolve every relation filter (`some`/`none`/`every`) in a where clause
160
274
  * into a plain scalar `in`/`notIn` condition on the **local key**, by running
@@ -421,6 +535,10 @@ export declare class PowqlInterface<T extends object = Record<string, unknown>>
421
535
  * - m2m (the block takes exactly one child table; the junction-order
422
536
  * stitch has no nested equivalent), and composite relation keys;
423
537
  * - a to-one relation carrying `limit`/`offset` (the loaders' semantics);
538
+ * - a relation `limit` of exactly 0 (a nested block would emit `limit 0`,
539
+ * which PowDB's projection fast path answered with ONE row below engine
540
+ * 0.20; the loader path resolves it client-side and is correct on every
541
+ * version). A NEGATIVE relation limit is refused outright, not fallen back;
424
542
  * - `distinct` inside the relation options (no nested grammar for it);
425
543
  * - a projected child column whose tsType is `bigint` or `Uint8Array`
426
544
  * (values ride a JSON array, which cannot carry them losslessly);
@@ -520,6 +638,27 @@ export declare class PowqlInterface<T extends object = Record<string, unknown>>
520
638
  */
521
639
  private get qt();
522
640
  create(args: CreateArgs<T>): Promise<T>;
641
+ /**
642
+ * Refuse a `createMany` whose rows do not all name the SAME fields.
643
+ *
644
+ * PowQL could express it: a multi-row insert carries one `{ col := … }` tuple
645
+ * per row, each with its own column list, so a ragged call inserts every
646
+ * value. The SQL engines cannot, their single statement takes its column list
647
+ * from the first row, so a field only a later row names is dropped and a field
648
+ * only the first row names is written as NULL over that column's default. They
649
+ * refuse it (ValidationError, query/writes.ts), and PowDB matching that
650
+ * refusal is what makes the call portable: a shape accepted here and rejected
651
+ * by every other engine turns a PowDB-to-Postgres move into a hard error found
652
+ * in production rather than at the first run.
653
+ *
654
+ * Runs AFTER `applyPkDefault`, on the rows as they will actually be written:
655
+ * a defaulted string PK is filled in on every row, so `[{}, { name }]` is
656
+ * refused for the missing `name`, not for the PK the client supplied itself.
657
+ * Rows that all name the same fields (the overwhelmingly common shape,
658
+ * including N rows of pure defaults) cost one `Object.keys` pass and emit
659
+ * byte-identical PowQL.
660
+ */
661
+ private assertUniformCreateManyRows;
523
662
  createMany(args: CreateManyArgs<T>): Promise<T[]>;
524
663
  update(args: UpdateArgs<T>): Promise<T>;
525
664
  updateMany(args: UpdateManyArgs<T>): Promise<{
@@ -547,6 +686,27 @@ export declare class PowqlInterface<T extends object = Record<string, unknown>>
547
686
  */
548
687
  private upsertComposite;
549
688
  count(args?: CountArgs<T>): Promise<number>;
689
+ /**
690
+ * Gate ONE field of a per-field `_count`.
691
+ *
692
+ * `_count: { col: true }` compiles to `count(T { .col })`, which counts
693
+ * NON-NULL values of the column (SQL's `COUNT(col)`) only from engine 0.20 on.
694
+ * Below 0.20 both PowDB frontends ignored the projection and returned the ROW
695
+ * count.
696
+ *
697
+ * That divergence only EXISTS on a nullable column: where the column is NOT
698
+ * NULL the row count and the non-null count are the same number, so the
699
+ * pre-0.20 answer was already the right one and the query keeps working. The
700
+ * gate is therefore per-column, not per-call: refusing the whole feature would
701
+ * take a correct, working call away from every user on the engine line Turbine
702
+ * shipped against and hand them nothing.
703
+ *
704
+ * Nullability comes from the column metadata (`introspectPowdbDatabase` reads
705
+ * PowDB's `required` modifier; `defineSchema` declares it). Metadata that
706
+ * claims NOT NULL for a column the live catalog lets be null would count rows
707
+ * instead of values below 0.20, the same drift any stale-metadata query has.
708
+ */
709
+ private assertProjectedCountSupported;
550
710
  aggregate(args: AggregateArgs<T>): Promise<AggregateResult<T>>;
551
711
  groupBy(args: GroupByArgs<T>): Promise<Record<string, unknown>[]>;
552
712
  /** Validate a JSON-path target (group key / aggregate target): non-empty array of keys/indexes. */