turbine-orm 0.49.0 → 0.50.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 (157) hide show
  1. package/README.md +122 -39
  2. package/dist/cjs/adapters/cockroachdb.d.ts +39 -0
  3. package/dist/cjs/adapters/index.d.ts +110 -0
  4. package/dist/cjs/adapters/yugabytedb.d.ts +51 -0
  5. package/dist/cjs/cli/config.d.ts +181 -0
  6. package/dist/cjs/cli/config.js +32 -6
  7. package/dist/cjs/cli/destructive.d.ts +38 -0
  8. package/dist/cjs/cli/index.d.ts +359 -0
  9. package/dist/cjs/cli/index.js +228 -56
  10. package/dist/cjs/cli/loader.d.ts +61 -0
  11. package/dist/cjs/cli/mcp.d.ts +42 -0
  12. package/dist/cjs/cli/migrate.d.ts +356 -0
  13. package/dist/cjs/cli/migrate.js +131 -40
  14. package/dist/cjs/cli/observe-ui.d.ts +1 -0
  15. package/dist/cjs/cli/observe-ui.js +14 -5
  16. package/dist/cjs/cli/observe.d.ts +25 -0
  17. package/dist/cjs/cli/observe.js +49 -12
  18. package/dist/cjs/cli/pii-tags.d.ts +53 -0
  19. package/dist/cjs/cli/prisma-report.d.ts +33 -0
  20. package/dist/cjs/cli/prisma-report.js +73 -0
  21. package/dist/cjs/cli/prisma-resolve.d.ts +106 -0
  22. package/dist/cjs/cli/prisma-resolve.js +1 -0
  23. package/dist/cjs/cli/prisma-schema.d.ts +176 -0
  24. package/dist/cjs/cli/prisma-schema.js +82 -4
  25. package/dist/cjs/cli/rate-limit.d.ts +32 -0
  26. package/dist/cjs/cli/rate-limit.js +45 -0
  27. package/dist/cjs/cli/studio-demo.d.ts +43 -0
  28. package/dist/cjs/cli/studio-ui.generated.d.ts +1 -0
  29. package/dist/cjs/cli/studio.d.ts +207 -0
  30. package/dist/cjs/cli/studio.js +136 -71
  31. package/dist/cjs/cli/ui.d.ts +73 -0
  32. package/dist/cjs/cli/ui.js +51 -9
  33. package/dist/cjs/client.d.ts +837 -0
  34. package/dist/cjs/client.js +3 -0
  35. package/dist/cjs/dialect.d.ts +516 -0
  36. package/dist/cjs/dialect.js +37 -12
  37. package/dist/cjs/errors.d.ts +370 -0
  38. package/dist/cjs/generate.d.ts +137 -0
  39. package/dist/cjs/generate.js +39 -6
  40. package/dist/cjs/index-advisor.d.ts +153 -0
  41. package/dist/cjs/index-stats.d.ts +384 -0
  42. package/dist/cjs/index.d.ts +55 -0
  43. package/dist/cjs/index.js +7 -2
  44. package/dist/cjs/introspect.d.ts +269 -0
  45. package/dist/cjs/mssql.d.ts +232 -0
  46. package/dist/cjs/mssql.js +6 -0
  47. package/dist/cjs/mysql.d.ts +173 -0
  48. package/dist/cjs/mysql.js +16 -0
  49. package/dist/cjs/nested-write.d.ts +96 -0
  50. package/dist/cjs/nested-write.js +414 -24
  51. package/dist/cjs/observe.d.ts +115 -0
  52. package/dist/cjs/optional-peer-import.d.cts +72 -0
  53. package/dist/cjs/pipeline-submittable.d.ts +93 -0
  54. package/dist/cjs/pipeline.d.ts +71 -0
  55. package/dist/cjs/powdb-introspect.d.ts +84 -0
  56. package/dist/cjs/powdb.d.ts +931 -0
  57. package/dist/cjs/powdb.js +106 -21
  58. package/dist/cjs/powql.d.ts +592 -0
  59. package/dist/cjs/powql.js +42 -6
  60. package/dist/cjs/prisma-compat.d.ts +283 -0
  61. package/dist/cjs/prisma-compat.js +167 -9
  62. package/dist/cjs/query/aggregates.d.ts +92 -0
  63. package/dist/cjs/query/aggregates.js +7 -3
  64. package/dist/cjs/query/batched-loader.d.ts +193 -0
  65. package/dist/cjs/query/builder.d.ts +849 -0
  66. package/dist/cjs/query/builder.js +571 -65
  67. package/dist/cjs/query/compound-unique.d.ts +51 -0
  68. package/dist/cjs/query/deferred.d.ts +223 -0
  69. package/dist/cjs/query/filters.d.ts +201 -0
  70. package/dist/cjs/query/index.d.ts +14 -0
  71. package/dist/cjs/query/index.js +6 -1
  72. package/dist/cjs/query/relations.d.ts +609 -0
  73. package/dist/cjs/query/relations.js +693 -46
  74. package/dist/cjs/query/types.d.ts +1300 -0
  75. package/dist/cjs/query/utils.d.ts +209 -0
  76. package/dist/cjs/query/utils.js +208 -1
  77. package/dist/cjs/query/warn-registry.d.ts +68 -0
  78. package/dist/cjs/query/warn-registry.js +9 -0
  79. package/dist/cjs/query/where-compile.d.ts +139 -0
  80. package/dist/cjs/query/where.d.ts +548 -0
  81. package/dist/cjs/query/where.js +58 -22
  82. package/dist/cjs/query/writes.d.ts +172 -0
  83. package/dist/cjs/query/writes.js +105 -12
  84. package/dist/cjs/realtime.d.ts +70 -0
  85. package/dist/cjs/schema-builder.d.ts +354 -0
  86. package/dist/cjs/schema-metadata.d.ts +83 -0
  87. package/dist/cjs/schema-sql.d.ts +217 -0
  88. package/dist/cjs/schema-sql.js +23 -5
  89. package/dist/cjs/schema.d.ts +356 -0
  90. package/dist/cjs/schema.js +125 -0
  91. package/dist/cjs/seed.d.ts +15 -0
  92. package/dist/cjs/serverless.d.ts +142 -0
  93. package/dist/cjs/sqlite.d.ts +143 -0
  94. package/dist/cjs/sqlite.js +4 -0
  95. package/dist/cjs/typed-sql.d.ts +102 -0
  96. package/dist/cli/config.d.ts +18 -4
  97. package/dist/cli/config.js +31 -6
  98. package/dist/cli/index.d.ts +123 -0
  99. package/dist/cli/index.js +223 -58
  100. package/dist/cli/migrate.d.ts +59 -10
  101. package/dist/cli/migrate.js +128 -41
  102. package/dist/cli/observe-ui.d.ts +1 -1
  103. package/dist/cli/observe-ui.js +14 -5
  104. package/dist/cli/observe.d.ts +7 -1
  105. package/dist/cli/observe.js +48 -12
  106. package/dist/cli/prisma-report.d.ts +14 -0
  107. package/dist/cli/prisma-report.js +72 -0
  108. package/dist/cli/prisma-resolve.d.ts +6 -0
  109. package/dist/cli/prisma-resolve.js +1 -0
  110. package/dist/cli/prisma-schema.d.ts +62 -2
  111. package/dist/cli/prisma-schema.js +81 -4
  112. package/dist/cli/rate-limit.d.ts +32 -0
  113. package/dist/cli/rate-limit.js +40 -0
  114. package/dist/cli/studio.d.ts +5 -5
  115. package/dist/cli/studio.js +135 -70
  116. package/dist/cli/ui.d.ts +1 -1
  117. package/dist/cli/ui.js +51 -9
  118. package/dist/client.d.ts +40 -0
  119. package/dist/client.js +3 -0
  120. package/dist/dialect.d.ts +17 -1
  121. package/dist/dialect.js +37 -12
  122. package/dist/generate.js +40 -7
  123. package/dist/index.d.ts +1 -1
  124. package/dist/index.js +1 -1
  125. package/dist/mssql.js +6 -0
  126. package/dist/mysql.js +16 -0
  127. package/dist/nested-write.d.ts +2 -0
  128. package/dist/nested-write.js +415 -25
  129. package/dist/powdb.d.ts +4 -2
  130. package/dist/powdb.js +106 -21
  131. package/dist/powql.d.ts +5 -0
  132. package/dist/powql.js +42 -6
  133. package/dist/prisma-compat.d.ts +2 -0
  134. package/dist/prisma-compat.js +166 -8
  135. package/dist/query/aggregates.js +7 -3
  136. package/dist/query/builder.d.ts +292 -21
  137. package/dist/query/builder.js +570 -64
  138. package/dist/query/deferred.d.ts +39 -0
  139. package/dist/query/index.d.ts +1 -1
  140. package/dist/query/index.js +1 -1
  141. package/dist/query/relations.d.ts +173 -5
  142. package/dist/query/relations.js +688 -47
  143. package/dist/query/types.d.ts +123 -39
  144. package/dist/query/utils.d.ts +116 -0
  145. package/dist/query/utils.js +198 -0
  146. package/dist/query/warn-registry.d.ts +9 -0
  147. package/dist/query/warn-registry.js +9 -0
  148. package/dist/query/where.d.ts +38 -1
  149. package/dist/query/where.js +58 -23
  150. package/dist/query/writes.d.ts +42 -1
  151. package/dist/query/writes.js +104 -13
  152. package/dist/schema-sql.d.ts +14 -0
  153. package/dist/schema-sql.js +23 -5
  154. package/dist/schema.d.ts +38 -0
  155. package/dist/schema.js +123 -0
  156. package/dist/sqlite.js +4 -0
  157. package/package.json +77 -28
@@ -0,0 +1,592 @@
1
+ /**
2
+ * PowqlInterface — Turbine's PowQL query generator (the PowDB analogue of
3
+ * {@link QueryInterface}). It exposes the same public method surface as the SQL
4
+ * `QueryInterface` (`findMany`, `create`, `update`, …) but emits **PowQL** — a
5
+ * pipeline language, not SQL — executed through {@link PowdbPool}.
6
+ *
7
+ * It is a *parallel* implementation rather than a `Dialect` of the SQL builder:
8
+ * PowQL's grammar (`T filter <e> order <k> { .col }`) shares no surface with
9
+ * `SELECT … FROM … WHERE`, so the SQL `Dialect` seam cannot express it. Keeping
10
+ * it separate also means the four SQL engines are untouched.
11
+ *
12
+ * Behavioural deltas from the SQL path, all driven by PowDB's wire reality (see
13
+ * `docs/internal/strategy/powdb-parity-matrix.md`, every row verified against a live
14
+ * server):
15
+ * - `create`/`createMany`/`update`/`delete` use PowDB 0.7.0's trailing
16
+ * `returning` keyword (`RETURNING *`, all columns) to surface affected rows
17
+ * in one round-trip. `upsert` is the lone exception — its statement does not
18
+ * accept `returning`, so it reselects the row by PK (a composite-PK upsert
19
+ * reselects-or-writes inside one flat transaction).
20
+ * - The PK is server-assigned when the column is `isGenerated` (PowDB's `auto`
21
+ * int — read back via `returning`); otherwise a defaulted **string** PK is
22
+ * generated client-side (UUID).
23
+ * - `with` (nested relations) uses **batched N+1 loaders** — D round-trips for
24
+ * depth D, not one query — including manyToMany (junction → targets).
25
+ * - **Relation filters** (`some`/`none`/`every`, all cardinalities incl. m2m)
26
+ * are resolved client-side to a literal `in (…)` list, never an IN-subquery:
27
+ * PowDB's executor caches a subquery's result by plan shape and would return
28
+ * a stale prior result for a later subquery of the same shape.
29
+ * - **Nested writes** (relation ops in `create`/`update` data) run through the
30
+ * shared nested-write engine as one flat top-level transaction (PowDB is
31
+ * single-writer, no savepoints).
32
+ * - pgvector / JSON / array filters and cursor streaming throw
33
+ * {@link UnsupportedFeatureError} (E017) — they have no PowDB equivalent.
34
+ *
35
+ * @module
36
+ */
37
+ import { type PowdbPool } from './powdb.js';
38
+ import type { MiddlewareFn, QueryInterfaceOptions } from './query/index.js';
39
+ import type { AggregateArgs, AggregateResult, CountArgs, CreateArgs, CreateManyArgs, DeleteArgs, DeleteManyArgs, FindManyArgs, FindUniqueArgs, GroupByArgs, UpdateArgs, UpdateManyArgs, UpsertArgs } from './query/types.js';
40
+ import { type SchemaMetadata } from './schema.js';
41
+ /**
42
+ * The PowQL query interface. Constructed by `turbinePowDB` via the
43
+ * `queryInterfaceFactory` seam and cast to `QueryInterface<object>` so
44
+ * `TurbineClient.table()` can return it transparently.
45
+ */
46
+ export declare class PowqlInterface<T extends object = Record<string, unknown>> {
47
+ private readonly pool;
48
+ private readonly table;
49
+ private readonly schema;
50
+ private readonly middlewares;
51
+ private readonly options;
52
+ private readonly meta;
53
+ private readonly defaultLimit?;
54
+ private readonly warnOnUnlimited;
55
+ private readonly onQuery?;
56
+ private warnedUnlimited;
57
+ constructor(pool: PowdbPool, table: string, schema: SchemaMetadata, middlewares?: MiddlewareFn[], options?: QueryInterfaceOptions);
58
+ /** Resolve a camelCase field name (or raw snake) to its column metadata. */
59
+ private column;
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
+ */
67
+ private ref;
68
+ /** Render a raw column name as a PowQL reference, qualified with `alias` when given. */
69
+ private colRefName;
70
+ /**
71
+ * Push a value into the param array and return its `$N` placeholder. When the
72
+ * value targets a `float` column it is wrapped in {@link PowdbFloatParam} so
73
+ * the *embedded* literal encoder emits a float-form literal even for an
74
+ * integer value (PowQL's `42` is an int literal, `42.0` a float). The
75
+ * networked driver unwraps the marker back to the plain number in
76
+ * {@link toPowdbParam}, so the wire param is unchanged.
77
+ */
78
+ private param;
79
+ /**
80
+ * Render a value for a write *assignment* (`col := …`). Every value — float
81
+ * columns included — is sent as a positional `$N` param. PowDB ≥ 0.7.0 fixed
82
+ * the int→float UPDATE coercion bug (`score := $n` with an integer param now
83
+ * reads back the integer value, not the raw i64 bits), so the float-literal
84
+ * inlining workaround Turbine carried for ≤ 0.6.2 is gone. Marks the column as
85
+ * float-typed for the *embedded* literal encoder (which materializes params
86
+ * into PowQL text) so an integer-valued float still encodes as a float literal
87
+ * and coercion stays unambiguous. Non-finite floats are still rejected.
88
+ */
89
+ private writeRef;
90
+ private isFloatCol;
91
+ /**
92
+ * The bound pool's {@link PowdbCapabilities}. Falls back to the trusted-caller
93
+ * default (all feature gates on, `nativeRaw` off) when a directly-constructed
94
+ * pool did not carry them, matching {@link PowdbPool}'s own constructor
95
+ * default so a hand-built test pool never crashes the version gates.
96
+ */
97
+ private get capabilities();
98
+ /** A predicate that is always false — the empty-`in` / contradiction sentinel. */
99
+ private alwaysFalse;
100
+ /**
101
+ * Compile a {@link WhereClause} into a PowQL filter expression, pushing every
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.
110
+ */
111
+ private buildWhere;
112
+ /** Build a single `field: value | operator` condition. */
113
+ private buildFieldCondition;
114
+ /**
115
+ * PowQL JSON path expression `.col->$a->$b…`, binding EVERY path segment as a
116
+ * positional param (a string segment as a `str` token, an integer index as an
117
+ * `int` token). `->` binds tighter than every operator, so no parens are
118
+ * needed around the path in a comparison. Segments are bound (never inlined)
119
+ * to keep {@link materializePowql}'s `$N`-scan invariant intact: a segment
120
+ * that literally contained `$1` would otherwise be rewritten. Shared by the
121
+ * F1 where-filter path and the F2 orderBy / groupBy path emitters.
122
+ *
123
+ * A digit-only STRING segment (`'0'`) binds as an `int` array index, matching
124
+ * the SQL engines: `JsonFilter.path` is typed `string[]`, so an array index
125
+ * can only be expressed as a digit string, and the SQL builder converts it the
126
+ * same way (`/^\d+$/ → [n]`, query/builder.ts). Without this, PowDB's typed
127
+ * `->` treats `'0'` as a string KEY and silently matches nothing on an array
128
+ * (a wrong result, not an error). Same object-key-`'0'` caveat SQL accepts: a
129
+ * json object whose key is literally `"0"` is addressed as an array index.
130
+ */
131
+ private jsonPathExpr;
132
+ /**
133
+ * Compile a {@link JsonFilter} on a json document column into a PowQL filter
134
+ * (≥ 0.12). Operators PowQL cannot express EXACTLY throw a per-operator E017
135
+ * (never a wrong result): containment (`contains`, and `equals` without a
136
+ * `path`) has no PowQL operator. The mapped shapes:
137
+ * - `{ path, equals: v }` → `P = $n` (typed: string→str, bool→bool,
138
+ * integral number→int, fractional→float; NOT stringified)
139
+ * - `{ path, equals: null }` → `P is null` (matches JSON null AND a missing
140
+ * key, a deliberate divergence from the PG driver, documented on
141
+ * {@link JsonFilter})
142
+ * - `{ path, gt|gte|lt|lte: v }` → `P > $n` … (range ops require `path`; the
143
+ * engine coerces int/float numerically)
144
+ * - `{ hasKey: k }` → `json_type(.col->$n) is not null` (top-level key test,
145
+ * ignoring `path`, mirroring PG `col ? key`; includes keys holding JSON
146
+ * null)
147
+ * A bare `{ path }` with no operators compiles to zero clauses (byte-parity
148
+ * with SQL), so a mutation whose only `where` is a bare `{ path }` is refused
149
+ * by the empty-where guard.
150
+ */
151
+ private buildJsonPathCondition;
152
+ /** Bind a value, lowercasing for case-insensitive comparisons. */
153
+ private bind;
154
+ /** Bind a LIKE pattern (already escaped), lowercasing for insensitive mode. */
155
+ private bindLike;
156
+ /** `lhs [not] in ($1, $2, …)` — empty list collapses to a constant. */
157
+ private buildInList;
158
+ /**
159
+ * Pre-resolve every relation filter (`some`/`none`/`every`) in a where clause
160
+ * into a plain scalar `in`/`notIn` condition on the **local key**, by running
161
+ * the inner predicate as its own query and materializing the matching keys as
162
+ * a literal list. The compiled where is then relation-free and `buildWhere`
163
+ * emits only `in (<literal list>)`.
164
+ *
165
+ * Why not an IN-subquery (`.k in (Target filter <e> { .fk })`)? PowDB's
166
+ * executor caches a subquery's result by **plan shape, ignoring the literal**,
167
+ * so a second subquery of the same shape with a different value returns the
168
+ * first one's stale rows (reproduced live on the embedded engine; the
169
+ * single-statement literal `in (list)` form is always correct). Resolving
170
+ * client-side trades extra round-trips for correctness, and recurses — nested
171
+ * relation filters in the inner predicate resolve when the target query runs.
172
+ */
173
+ private resolveRelationFilters;
174
+ /** Resolve one hasMany/hasOne/belongsTo filter to `{ localField: { in|notIn: [...] } }`. */
175
+ private resolveRelationCondition;
176
+ /** Resolve a manyToMany filter through the junction to `{ sourceRefField: { in|notIn: [...] } }`. */
177
+ private resolveManyToManyCondition;
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
+ */
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;
212
+ /** `{ .c1, .c2, … }` projection clause. */
213
+ private projection;
214
+ /**
215
+ * `order .c1 asc, .c2 desc` clause (empty string when no orderBy). Supports,
216
+ * besides a plain direction:
217
+ * - {@link JsonPathOrderBy} on a json column (≥ 0.12): `{ data: { path: […],
218
+ * type?, direction? } }` → `order .data->$n asc` (or
219
+ * `cast(.data->$n, "float")` for `type: 'numeric'`);
220
+ * - {@link OrderBySpec} `{ sort, nulls }`: `nulls: 'last'` is accepted as a
221
+ * no-op (PowDB is always nulls-last), `nulls: 'first'` throws E017.
222
+ *
223
+ * PowDB orders missing / JSON-null keys LAST in BOTH directions (an engine
224
+ * contract): for identical cross-engine results pass `nulls: 'last'`
225
+ * explicitly on Postgres, which defaults nulls-first for `desc`.
226
+ */
227
+ private buildOrder;
228
+ /** Compile one {@link JsonPathOrderBy} entry to `order .col->$n asc` (+ optional numeric cast). */
229
+ private buildJsonPathOrder;
230
+ /**
231
+ * Run PowQL with optional timeout, emitting a query event either way. The
232
+ * `action` is passed PER CALL (never read from shared instance state) so the
233
+ * retry-eligibility and the emitted event action stay correct even when a
234
+ * concurrent operation runs on the same cached interface: a WRITE statement
235
+ * carries a write action and can therefore never be mistaken for a replayable
236
+ * read. Read statements pass a read-shaped action from {@link POWQL_READ_ACTIONS}.
237
+ */
238
+ private exec;
239
+ /** Build the E018 refusal for a write / `begin` on a read-only pool. */
240
+ private readOnlyError;
241
+ /**
242
+ * Execute one statement, with the opt-in single stale-frame READ replay. When
243
+ * `retryStaleReads` is on and a first-statement READ fails with the stale-wire
244
+ * {@link isStaleFramePowdbError} ConnectionError (a socket idle-gap "received
245
+ * unexpected frame" that the client cannot recover), the statement is retried
246
+ * exactly once on a fresh pooled connection (the broken one was destroyed).
247
+ * The replay is refused for writes (an ambiguous mutation reply is unsafe to
248
+ * replay) and inside a transaction (a mid-tx statement cannot move connection),
249
+ * so only the read-shaped actions in {@link POWQL_READ_ACTIONS}, outside a
250
+ * `_txScoped` interface, are eligible. `action` is a per-call argument (never
251
+ * `this`-state), so a concurrent op flipping instance fields cannot turn a
252
+ * write into a retryable read.
253
+ */
254
+ private execOnce;
255
+ /** Is `err` a replayable stale-frame failure for THIS (per-call) read-shaped, non-tx action? */
256
+ private shouldRetryStaleRead;
257
+ private emit;
258
+ /** Run a method body through the middleware chain (mirrors QueryInterface). */
259
+ private withMiddleware;
260
+ /** Map raw rows to typed entities. `native` is the wire that ACTUALLY served
261
+ * this result (threaded from {@link execOnce}, not the pool-level capability),
262
+ * so cells that arrived pre-typed over `queryNativeRaw` (F3) skip the legacy
263
+ * string coercion (a genuine str `"null"` stays `"null"` instead of collapsing
264
+ * to null) while a per-call legacy fallback on a native-capable pool still
265
+ * coerces its string cells correctly. Defaults to the pool capability for the
266
+ * rare caller with no per-result flag (hand-built test pools). */
267
+ private shape;
268
+ findMany(args?: FindManyArgs<T>): Promise<T[]>;
269
+ /**
270
+ * Compile the findMany select into PowQL (no execution), pushing values into
271
+ * `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
+ * When the engine supports nested projections (>= 0.18) and the strategy
276
+ * does not opt out, eligible `with` relations compile INTO this statement as
277
+ * nested-projection blocks (`nestedPlans`) — one round-trip for the whole
278
+ * shape — and only the ineligible remainder (`residualWith`) goes to the
279
+ * post-execution loaders. Without nesting the emitted PowQL is byte-identical
280
+ * to the pre-0.18 output (no alias, `.col` refs).
281
+ */
282
+ private buildFind;
283
+ /** Build + run the findMany select; returns raw rows, the serving wire, the resolved where, and the `with` partition. */
284
+ private runFind;
285
+ /**
286
+ * Diagnostic surface: compile the same PowQL {@link findMany} would run for
287
+ * `args` (no cache) and return the engine's plan as one string per line.
288
+ *
289
+ * Runs as a READ (`explain <query>`), so it is safe on a read-only pool and
290
+ * eligible for the stale-read replay. The line content is engine-owned and is
291
+ * NOT covered by semver (match plan node names / tree shape, never exact
292
+ * bytes; mirrors PowDB's own `explain` contract).
293
+ *
294
+ * Does NOT run through the middleware chain: plan text is a diagnostic, not
295
+ * entity rows, and `QueryInterface.explain` deliberately bypasses middleware
296
+ * too, so both engines agree.
297
+ */
298
+ explain(args?: FindManyArgs<T>): Promise<string[]>;
299
+ findUnique(args: FindUniqueArgs<T>): Promise<T | null>;
300
+ findFirst(args?: FindManyArgs<T>): Promise<T | null>;
301
+ findUniqueOrThrow(args: FindUniqueArgs<T>): Promise<T>;
302
+ findFirstOrThrow(args?: FindManyArgs<T>): Promise<T>;
303
+ /**
304
+ * Load each requested relation for `parents` and attach it onto each row.
305
+ *
306
+ * `parent` is supplied ONLY by the top-level {@link findMany} (its args +
307
+ * resolved where). When the effective `relationLoadStrategy` resolves to an
308
+ * explicit `'join'` and the pool advertises `serverJoins`, an eligible
309
+ * top-level relation is loaded with a native PowQL join instead of the keyed
310
+ * loaders (F2); everything else (nested `with` levels, ineligible shapes, and
311
+ * the default `'batched'` strategy) keeps the loaders. Output is byte-equal
312
+ * either way (the join reuses the same stitch / shape helpers).
313
+ */
314
+ private loadRelations;
315
+ /**
316
+ * manyToMany nested read — a three-hop batched loader (no `json_agg`/join
317
+ * pushdown): (1) read the junction rows for all parents in `sourceKey in (…)`
318
+ * chunks, (2) read the target rows for the collected `targetKey`s, (3) stitch
319
+ * each parent → its junction rows → its targets in memory. Mirrors the
320
+ * single-key N+1 loaders; the junction's source/target columns must be single
321
+ * (composite junction keys would need PowQL tuple-`in`, which it lacks).
322
+ */
323
+ private loadManyToMany;
324
+ /**
325
+ * Resolve the effective relation-load strategy: the per-query arg wins, then
326
+ * the client config, then the PowDB default of `'batched'` (the keyed
327
+ * loaders). PowDB deliberately does NOT inherit the SQL-side implicit `'join'`
328
+ * default (that would silently flip every existing PowDB user onto brand-new
329
+ * join generation). Only a value the user actually set to `'join'` activates it.
330
+ */
331
+ private resolveStrategy;
332
+ /**
333
+ * Per-relation eligibility for the join path (checked before the serverJoins
334
+ * capability). Any `false` here is a SILENT fallback to the keyed loaders (it
335
+ * is never an error), so an off-page or nested-`with` shape still returns
336
+ * correct rows:
337
+ * - the parent query must not be paged (`limit`/`offset`/`take`, including the
338
+ * configured `defaultLimit`): a parent-filter join under a page would scan
339
+ * children of off-page parents, where the loaders are strictly better;
340
+ * - the relation must not request a nested `with` (its subtree stays on the
341
+ * loaders this round) or a `distinct`;
342
+ * - single-column relation keys only (a composite key falls to the loader,
343
+ * which throws the same E017 as today);
344
+ * - the PARENT-SIDE correlation column must be a single-column PK or unique
345
+ * column, or the INNER join would re-emit one child copy per matching
346
+ * parent row (a non-unique correlation key produces duplicate children the
347
+ * loader never would). For hasMany/hasOne/m2m that column is the relation's
348
+ * `referenceKey` on THIS (fetched) table; for belongsTo it is the
349
+ * `referenceKey` on the TARGET table (the join's non-fetched side);
350
+ * - m2m keeps any `orderBy`/`limit`/`offset` on the loader (the junction-order
351
+ * stitch can't be reproduced by the 3-table join deterministically);
352
+ * - a to-one relation `limit`/`offset` (meaningless) stays on the loader, as
353
+ * does a to-many relation `limit`/`offset` when the parent set spills past
354
+ * one loader chunk (the loader limits per chunk, the join once globally).
355
+ */
356
+ private joinEligible;
357
+ /**
358
+ * True when `col` is a single-column unique key of `tableMeta`: the sole
359
+ * primary-key column, a single-column entry in `uniqueColumns` (where a
360
+ * per-column `unique: true` and an introspected single-column unique constraint
361
+ * both land), or a single-column unique index. Used by {@link joinEligible} to
362
+ * keep the INNER-join path off relations whose parent-side correlation column
363
+ * can repeat (which would duplicate children).
364
+ */
365
+ private isSingleColumnUnique;
366
+ /** Dispatch one eligible relation to the correct native-join loader. */
367
+ private loadRelationViaJoin;
368
+ /**
369
+ * manyToMany via chained joins: the target (alias `t`) → junction (alias `j`)
370
+ * → the already-fetched side (alias `p`), correlating `__tpk` from the
371
+ * junction's source key. Always a list, stitched exactly like the loader.
372
+ */
373
+ private loadManyToManyViaJoin;
374
+ /**
375
+ * The target column list to project through the join (honouring select/omit),
376
+ * with a loud guard: a real column named `__tpk` would collide with the
377
+ * reserved correlation alias, so refuse rather than silently mis-stitch.
378
+ */
379
+ private joinChildCols;
380
+ /**
381
+ * `{ __tpk: <tpkExpr>, <col>: <childAlias>.<col>, … }`. Each child column is
382
+ * ALIASED to its bare name (a bare qualified ref `c.col` would come back named
383
+ * `c.col`, not `col`) so the stitched rows shape identically to a flat select.
384
+ */
385
+ private joinProjection;
386
+ /**
387
+ * `filter <parentWhere qualified p> [and <relationWhere qualified childAlias>]`.
388
+ * The parent where is the ALREADY-RESOLVED predicate (relation filters collapsed
389
+ * to literal in-lists before the base query ran); the relation where is resolved
390
+ * on the target the same way before qualifying, so a nested relation filter in
391
+ * the relation `where` never reaches the join unresolved. Params bind in order.
392
+ */
393
+ private joinFilter;
394
+ /** Group join rows by their (normalized) `__tpk`, stripping it and shaping each child. */
395
+ private bucketByTpk;
396
+ /**
397
+ * Normalize a correlation key to a stable string map key so a parent's key
398
+ * value (a shaped entity field) and a child row's `__tpk` cell match across
399
+ * wires and column types. A `Date` maps to microseconds
400
+ * (`getTime()` ms times 1000), because a datetime correlation cell arrives as
401
+ * raw micros (bigint on the native wire, a micros string on the legacy wire),
402
+ * never as ms. bigint / number / string all stringify to the same digits, so
403
+ * an int key matches whether it came back typed or as text.
404
+ */
405
+ private joinKey;
406
+ /**
407
+ * Should this query's `with` compile to nested-projection blocks? Requires
408
+ * the engine capability (>= 0.18), and an EXPLICIT `relationLoadStrategy:
409
+ * 'batched'` (per-query or client-level) opts back out to the keyed loaders.
410
+ * The default and `'join'` both prefer nesting: unlike the F2 INNER join it
411
+ * has no fan-out, keeps childless parents, works under parent paging, and
412
+ * applies per-parent `order`/`limit`/`offset` natively, so it is the
413
+ * strictly-better single-statement path wherever it is eligible. Ineligible
414
+ * relations fall through to the existing strategy resolution untouched.
415
+ */
416
+ private nestedProjectionsPreferred;
417
+ /**
418
+ * Plan one `with` relation as a nested-projection block, or return `null`
419
+ * when the shape must stay on the loaders (ALWAYS a silent fallback with
420
+ * identical output, never an error):
421
+ * - m2m (the block takes exactly one child table; the junction-order
422
+ * stitch has no nested equivalent), and composite relation keys;
423
+ * - a to-one relation carrying `limit`/`offset` (the loaders' semantics);
424
+ * - `distinct` inside the relation options (no nested grammar for it);
425
+ * - a projected child column whose tsType is `bigint` or `Uint8Array`
426
+ * (values ride a JSON array, which cannot carry them losslessly);
427
+ * - a projection key collision (a child column named like a sub-relation);
428
+ * - depth >= 10 (the loader path enforces the same cap by throwing, so the
429
+ * fallback surfaces the identical E003 today's users get);
430
+ * - any ineligible descendant (the WHOLE relation falls back, so one
431
+ * statement never mixes nested and loader semantics mid-subtree).
432
+ */
433
+ private planNestedRelation;
434
+ /**
435
+ * Compile one {@link NestedRelationPlan} into its projection-field block:
436
+ * `name: Target as tN filter <correlation> [and (<child where>)] [order …]
437
+ * [limit …] [offset …] { col: tN.col, …, <sub-blocks> }`. Params bind in
438
+ * emission order (the projection is the statement's final clause, so nested
439
+ * params always follow the parent's filter/order/limit/offset params).
440
+ * Aliases share the parent statement's counter, so arbitrarily deep and
441
+ * self-referential trees stay collision-free (the same discipline as the SQL
442
+ * engine's json_agg subqueries).
443
+ */
444
+ private buildNestedBlock;
445
+ /**
446
+ * Shape the nested JSON children back into typed entities on every parent
447
+ * row. The nested field arrives as a decoded JSON array on the native wire
448
+ * (or JSON text on the legacy wire — parsed here); its values are real JSON
449
+ * types, so each child object goes through the NATIVE coercion policy
450
+ * (`rowToEntity(…, true)`: a date column's micros number becomes a `Date`, a
451
+ * json column's document passes through, a str `"null"` stays a string).
452
+ * to-one relations unwrap to `[0] ?? null`, matching the loaders exactly.
453
+ */
454
+ private attachNestedRows;
455
+ private attachOneNested;
456
+ /**
457
+ * The `schema links` snapshot for this pool, fetched at most once and cached on
458
+ * the pool identity. A fetch failure resolves to `[]` (a missing listing is a
459
+ * silent fallback to loaders, never an error). Only called when the `linkPaths`
460
+ * capability is on, so the listing statement is guaranteed to exist.
461
+ */
462
+ private linksSnapshot;
463
+ private fetchLinksSnapshot;
464
+ /** True when `name` is a bare PowQL identifier (quoting leaves it unchanged). */
465
+ private isBareIdent;
466
+ /**
467
+ * Find the declared to-one link on THIS table that matches `rel` exactly: same
468
+ * target, same correlation columns (owner localKey = the FK on this table, target
469
+ * targetKey = the referenced key on target), cardinality `"to-one"`. Returns the
470
+ * declared link (whose NAME drives the path spelling, which may differ from the
471
+ * relation's own name) or `null` for no verifiable match (→ silent loader
472
+ * fallback, never an error).
473
+ */
474
+ private findMatchingLink;
475
+ /**
476
+ * Plan one belongsTo `with` as a scalar link-path relation, or `null` when it
477
+ * must stay on the loaders (ALWAYS a silent fallback with identical output).
478
+ *
479
+ * SCOPED TIGHT: this fires ONLY for a to-one relation whose child projection
480
+ * includes a bigint/bytes column — exactly the case a JSON nested block cannot
481
+ * carry, so nested projections have already fallen back to a per-relation loader
482
+ * (`planNestedRelation` returned `null` for the same shape). Cases nested
483
+ * projections DO serve keep nested projections: link-bearing statements are
484
+ * NEVER plan-cached upstream, so replacing a cacheable nested projection with a
485
+ * link path would regress a hot path for no gain. Requires: single-column
486
+ * belongsTo; no relation `with` / `where` / `distinct` / `orderBy` /
487
+ * `limit` / `offset` (a scalar path has no per-hop filter/order and cannot
488
+ * reproduce those — such inputs stay on the loader for exact parity); a link
489
+ * name and all projected columns that are bare identifiers (a quoted segment in
490
+ * a dotted link path is outside the verified spelling — fall back); and a
491
+ * DECLARED link that verifiably matches (`findMatchingLink`).
492
+ */
493
+ private planLinkPathRelation;
494
+ /** The flat `l<i>_<col>: t0.<linkName>.<col>` projection fields for one link plan. */
495
+ private linkPathFields;
496
+ /**
497
+ * Reconstruct each link-path relation's child entity from its flat hop fields
498
+ * and attach it under the relation name — output indistinguishable from the
499
+ * loader (same keys, same coercions). Presence: the target PK cell arriving
500
+ * Empty (a null/dangling FK at the hop) means no linked row → `null`, matching
501
+ * the loader's `matches[0] ?? null`. Otherwise the gathered snake cells go
502
+ * through the SAME `rowToEntity` policy the loader uses (native micros → Date,
503
+ * bigint per the int8 policy), and the PK is stripped back off if the user did
504
+ * not project it. `native` is the wire that actually served the parent row.
505
+ */
506
+ private attachLinkRows;
507
+ /** Split `data` into scalar assignments; reject relation (nested-write) keys. */
508
+ private scalarData;
509
+ /**
510
+ * Fill in a client-generated UUID for a defaulted **string** PK that wasn't
511
+ * supplied. A server-generated PK ({@link ColumnMetadata.isGenerated}, e.g. an
512
+ * `int` column with PowDB's `auto` modifier) is left untouched — PowDB assigns
513
+ * it and the trailing `returning` reads it back — as is any non-string PK.
514
+ */
515
+ private applyPkDefault;
516
+ /**
517
+ * The table name as a PowQL type reference — backtick-quoted when it is a
518
+ * reserved word (e.g. a table named `order`). Used in every emitted
519
+ * statement; plain `this.table` stays in error messages.
520
+ */
521
+ private get qt();
522
+ create(args: CreateArgs<T>): Promise<T>;
523
+ createMany(args: CreateManyArgs<T>): Promise<T[]>;
524
+ update(args: UpdateArgs<T>): Promise<T>;
525
+ updateMany(args: UpdateManyArgs<T>): Promise<{
526
+ count: number;
527
+ }>;
528
+ /** Compile `data` into PowQL update assignments, including atomic operators. */
529
+ private buildUpdateAssignments;
530
+ private isTxScoped;
531
+ private nestedCreate;
532
+ private nestedUpdate;
533
+ /** Open a flat PowDB transaction on a pinned connection and run `fn` inside it. */
534
+ private runInImplicitTx;
535
+ /** Already inside a transaction: build a context whose table accessors reuse the pinned pool. */
536
+ private buildNestedCtx;
537
+ delete(args: DeleteArgs<T>): Promise<T>;
538
+ deleteMany(args: DeleteManyArgs<T>): Promise<{
539
+ count: number;
540
+ }>;
541
+ upsert(args: UpsertArgs<T>): Promise<T>;
542
+ /**
543
+ * Composite-key upsert: PowQL's `upsert … on .col` only takes one conflict
544
+ * column, so reselect by the full composite PK and update-or-create inside one
545
+ * flat transaction (PowDB single-writer makes the read-then-write safe from
546
+ * concurrent writers; the transaction makes it atomic with the write).
547
+ */
548
+ private upsertComposite;
549
+ count(args?: CountArgs<T>): Promise<number>;
550
+ aggregate(args: AggregateArgs<T>): Promise<AggregateResult<T>>;
551
+ groupBy(args: GroupByArgs<T>): Promise<Record<string, unknown>[]>;
552
+ /** Validate a JSON-path target (group key / aggregate target): non-empty array of keys/indexes. */
553
+ private assertJsonPath;
554
+ /**
555
+ * `having <expr>` over group aggregates. `_count` compares `count(*)` (parity
556
+ * with the projection); a per-field aggregate re-emits its inner expression
557
+ * (from `aggInner` when the field is a requested aggregate, so a JSON-path
558
+ * aggregate reuses its bound placeholders, else `.field` for a plain column).
559
+ *
560
+ * Every token that reaches the PowQL text is builder-controlled: the field is
561
+ * validated by `ref()`, the aggregate function comes from
562
+ * {@link POWQL_HAVING_AGG_FNS} (unknown keys throw E003), the operator from a
563
+ * fixed map, and every compared value is bound as a `$N` param.
564
+ */
565
+ private buildHaving;
566
+ /**
567
+ * Compile a groupBy `orderBy` into a PowQL `order` body over the group RESULT
568
+ * columns (by-fields, JSON group-key aliases, and requested aggregates). PowQL
569
+ * cannot re-emit an aggregate EXPRESSION in `order` (engine error), but CAN
570
+ * order by a projection alias on a grouped query (probed), so each key maps to
571
+ * its projected alias (`.agg_N` / `.gk_N` / `.col`). Semantics and error
572
+ * surface mirror the SQL `buildGroupByOrderBy` (0.32.2 R3-1): an aggregate not
573
+ * requested in this call, or an unknown by-key, throws E003 listing the valid
574
+ * keys. `nulls: 'first'` stays E017 (PowDB has no NULLS placement grammar).
575
+ */
576
+ private buildGroupOrder;
577
+ /** Resolve a groupBy order direction, refusing `nulls: 'first'` (E017); `nulls: 'last'` is a no-op. */
578
+ private groupOrderDir;
579
+ findManyStream(): AsyncGenerator<T>;
580
+ /** Reselect a single row by its single-column primary key value. */
581
+ private reselectByPk;
582
+ /**
583
+ * Empty-where guard — blocks accidental whole-table writes. Mirrors the SQL
584
+ * path's `assertMutationHasPredicate` (query/builder.ts): it gates on the
585
+ * *compiled* PowQL filter fragment, NOT the shape of the `where` object. A
586
+ * `where` whose conditions all evaporate during compilation — `{}`,
587
+ * `{ id: undefined }`, `{ OR: [] }`, `{ AND: [] }`, `{ NOT: {} }`,
588
+ * `{ OR: [{ f: undefined }] }` — compiles to the empty string and is refused,
589
+ * because emitting a filter-less write would hit every row.
590
+ */
591
+ private assertCompiledWhere;
592
+ }