turbine-orm 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/generate.js +4 -0
- package/dist/cjs/introspect.js +6 -0
- package/dist/cjs/powdb.js +16 -2
- package/dist/cjs/powql.js +402 -43
- package/dist/generate.js +4 -0
- package/dist/introspect.js +6 -0
- package/dist/powdb.d.ts +4 -1
- package/dist/powdb.js +16 -2
- package/dist/powql.d.ts +61 -9
- package/dist/powql.js +369 -43
- package/dist/schema.d.ts +10 -0
- package/package.json +1 -1
package/dist/powdb.js
CHANGED
|
@@ -201,18 +201,32 @@ function isDateColumn(col) {
|
|
|
201
201
|
* Generate PowQL DDL (`type T { … }`) for every table in a schema. Used to
|
|
202
202
|
* provision a PowDB database from a code-first `defineSchema`/`SchemaMetadata`
|
|
203
203
|
* (PowDB has no migration runner yet). The primary key column is declared
|
|
204
|
-
* `required unique`; non-nullable columns are `required`.
|
|
204
|
+
* `required unique`; non-nullable columns are `required`. A server-generated
|
|
205
|
+
* column ({@link ColumnMetadata.isGenerated}) that maps to PowQL `int` gets the
|
|
206
|
+
* `auto` modifier, so PowDB assigns a monotonic id on insert and Turbine stops
|
|
207
|
+
* synthesizing a client-side value for it.
|
|
205
208
|
*/
|
|
206
209
|
export function powqlSchemaDDL(schema) {
|
|
207
210
|
const stmts = [];
|
|
208
211
|
for (const meta of Object.values(schema.tables)) {
|
|
209
212
|
const pkSet = new Set(meta.primaryKey);
|
|
213
|
+
// PowDB's `unique` is single-column only — there is no composite-unique
|
|
214
|
+
// constraint (`add unique` takes one `.column`). So the per-field `unique`
|
|
215
|
+
// modifier is emitted only for a single-column PK; a composite PK (e.g. a
|
|
216
|
+
// m2m junction's `(source_id, target_id)`) marks its columns `required` but
|
|
217
|
+
// cannot enforce the tuple's uniqueness at the engine level.
|
|
218
|
+
const pkIsSingle = meta.primaryKey.length === 1;
|
|
210
219
|
const fields = meta.columns.map((col) => {
|
|
211
220
|
const mods = [];
|
|
212
221
|
if (!col.nullable || pkSet.has(col.name))
|
|
213
222
|
mods.push('required');
|
|
214
|
-
if (pkSet.has(col.name))
|
|
223
|
+
if (pkSet.has(col.name) && pkIsSingle)
|
|
215
224
|
mods.push('unique');
|
|
225
|
+
// `auto` = server-generated monotonic int. PowDB requires it be `int` and
|
|
226
|
+
// rejects it alongside a `default`; non-int generated columns fall back to
|
|
227
|
+
// a plain typed column (Turbine assigns the value client-side instead).
|
|
228
|
+
if (col.isGenerated && powqlColumnType(col) === 'int')
|
|
229
|
+
mods.push('auto');
|
|
216
230
|
return ` ${mods.join(' ')}${mods.length ? ' ' : ''}${col.name}: ${powqlColumnType(col)}`;
|
|
217
231
|
});
|
|
218
232
|
stmts.push(`type ${meta.name} {\n${fields.join(',\n')}\n}`);
|
package/dist/powql.d.ts
CHANGED
|
@@ -15,10 +15,20 @@
|
|
|
15
15
|
* - `create`/`createMany`/`update`/`delete` use PowDB 0.7.0's trailing
|
|
16
16
|
* `returning` keyword (`RETURNING *`, all columns) to surface affected rows
|
|
17
17
|
* in one round-trip. `upsert` is the lone exception — its statement does not
|
|
18
|
-
* accept `returning`, so it reselects the row by PK
|
|
19
|
-
*
|
|
20
|
-
* -
|
|
21
|
-
*
|
|
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).
|
|
22
32
|
* - pgvector / JSON / array filters and cursor streaming throw
|
|
23
33
|
* {@link UnsupportedFeatureError} (E017) — they have no PowDB equivalent.
|
|
24
34
|
*
|
|
@@ -87,11 +97,25 @@ export declare class PowqlInterface<T extends object = Record<string, unknown>>
|
|
|
87
97
|
/** `lhs [not] in ($1, $2, …)` — empty list collapses to a constant. */
|
|
88
98
|
private buildInList;
|
|
89
99
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
100
|
+
* Pre-resolve every relation filter (`some`/`none`/`every`) in a where clause
|
|
101
|
+
* into a plain scalar `in`/`notIn` condition on the **local key**, by running
|
|
102
|
+
* the inner predicate as its own query and materializing the matching keys as
|
|
103
|
+
* a literal list. The compiled where is then relation-free and `buildWhere`
|
|
104
|
+
* emits only `in (<literal list>)`.
|
|
105
|
+
*
|
|
106
|
+
* Why not an IN-subquery (`.k in (Target filter <e> { .fk })`)? PowDB's
|
|
107
|
+
* executor caches a subquery's result by **plan shape, ignoring the literal**,
|
|
108
|
+
* so a second subquery of the same shape with a different value returns the
|
|
109
|
+
* first one's stale rows (reproduced live on the embedded engine; the
|
|
110
|
+
* single-statement literal `in (list)` form is always correct). Resolving
|
|
111
|
+
* client-side trades extra round-trips for correctness, and recurses — nested
|
|
112
|
+
* relation filters in the inner predicate resolve when the target query runs.
|
|
93
113
|
*/
|
|
94
|
-
private
|
|
114
|
+
private resolveRelationFilters;
|
|
115
|
+
/** Resolve one hasMany/hasOne/belongsTo filter to `{ localField: { in|notIn: [...] } }`. */
|
|
116
|
+
private resolveRelationCondition;
|
|
117
|
+
/** Resolve a manyToMany filter through the junction to `{ sourceRefField: { in|notIn: [...] } }`. */
|
|
118
|
+
private resolveManyToManyCondition;
|
|
95
119
|
/** Resolve the set of columns to project, honouring `select` / `omit`. */
|
|
96
120
|
private projectedColumns;
|
|
97
121
|
/** `{ .c1, .c2, … }` projection clause. */
|
|
@@ -114,9 +138,23 @@ export declare class PowqlInterface<T extends object = Record<string, unknown>>
|
|
|
114
138
|
findFirstOrThrow(args?: FindManyArgs<T>): Promise<T>;
|
|
115
139
|
/** Load each requested relation for `parents` and attach it onto each row. */
|
|
116
140
|
private loadRelations;
|
|
141
|
+
/**
|
|
142
|
+
* manyToMany nested read — a three-hop batched loader (no `json_agg`/join
|
|
143
|
+
* pushdown): (1) read the junction rows for all parents in `sourceKey in (…)`
|
|
144
|
+
* chunks, (2) read the target rows for the collected `targetKey`s, (3) stitch
|
|
145
|
+
* each parent → its junction rows → its targets in memory. Mirrors the
|
|
146
|
+
* single-key N+1 loaders; the junction's source/target columns must be single
|
|
147
|
+
* (composite junction keys would need PowQL tuple-`in`, which it lacks).
|
|
148
|
+
*/
|
|
149
|
+
private loadManyToMany;
|
|
117
150
|
/** Split `data` into scalar assignments; reject relation (nested-write) keys. */
|
|
118
151
|
private scalarData;
|
|
119
|
-
/**
|
|
152
|
+
/**
|
|
153
|
+
* Fill in a client-generated UUID for a defaulted **string** PK that wasn't
|
|
154
|
+
* supplied. A server-generated PK ({@link ColumnMetadata.isGenerated}, e.g. an
|
|
155
|
+
* `int` column with PowDB's `auto` modifier) is left untouched — PowDB assigns
|
|
156
|
+
* it and the trailing `returning` reads it back — as is any non-string PK.
|
|
157
|
+
*/
|
|
120
158
|
private applyPkDefault;
|
|
121
159
|
create(args: CreateArgs<T>): Promise<T>;
|
|
122
160
|
createMany(args: CreateManyArgs<T>): Promise<T[]>;
|
|
@@ -126,11 +164,25 @@ export declare class PowqlInterface<T extends object = Record<string, unknown>>
|
|
|
126
164
|
}>;
|
|
127
165
|
/** Compile `data` into PowQL update assignments, including atomic operators. */
|
|
128
166
|
private buildUpdateAssignments;
|
|
167
|
+
private isTxScoped;
|
|
168
|
+
private nestedCreate;
|
|
169
|
+
private nestedUpdate;
|
|
170
|
+
/** Open a flat PowDB transaction on a pinned connection and run `fn` inside it. */
|
|
171
|
+
private runInImplicitTx;
|
|
172
|
+
/** Already inside a transaction: build a context whose table accessors reuse the pinned pool. */
|
|
173
|
+
private buildNestedCtx;
|
|
129
174
|
delete(args: DeleteArgs<T>): Promise<T>;
|
|
130
175
|
deleteMany(args: DeleteManyArgs<T>): Promise<{
|
|
131
176
|
count: number;
|
|
132
177
|
}>;
|
|
133
178
|
upsert(args: UpsertArgs<T>): Promise<T>;
|
|
179
|
+
/**
|
|
180
|
+
* Composite-key upsert: PowQL's `upsert … on .col` only takes one conflict
|
|
181
|
+
* column, so reselect by the full composite PK and update-or-create inside one
|
|
182
|
+
* flat transaction (PowDB single-writer makes the read-then-write safe from
|
|
183
|
+
* concurrent writers; the transaction makes it atomic with the write).
|
|
184
|
+
*/
|
|
185
|
+
private upsertComposite;
|
|
134
186
|
count(args?: CountArgs<T>): Promise<number>;
|
|
135
187
|
aggregate(args: AggregateArgs<T>): Promise<AggregateResult<T>>;
|
|
136
188
|
groupBy(args: GroupByArgs<T>): Promise<Record<string, unknown>[]>;
|