@prisma-next/sql-orm-client 0.7.0 → 0.8.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/index.d.mts +70 -17
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +210 -41
- package/dist/index.mjs.map +1 -1
- package/package.json +21 -21
- package/src/collection.ts +262 -56
- package/src/grouped-collection.ts +34 -8
- package/src/query-plan-meta.ts +57 -3
- package/src/query-plan-select.ts +4 -3
- package/src/query-plan.ts +1 -0
- package/src/types.ts +10 -0
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AsyncIterableResult } from "@prisma-next/framework-components/runtime";
|
|
1
|
+
import { AsyncIterableResult, createMetaBuilder } from "@prisma-next/framework-components/runtime";
|
|
2
2
|
import { AggregateExpr, AndExpr, BinaryExpr, ColumnRef, DefaultValueExpr, DeleteAst, DerivedTableSource, EqColJoinOn, ExistsExpr, InsertAst, InsertOnConflict, JoinAst, JsonArrayAggExpr, JsonObjectExpr, ListExpression, LiteralExpr, NotExpr, NullCheckExpr, OrExpr, OrderByItem, ParamRef, ProjectionItem, SelectAst, SubqueryExpr, TableSource, UpdateAst, collectOrderedParamRefs, isWhereExpr } from "@prisma-next/sql-relational-core/ast";
|
|
3
3
|
import { codecRefForStorageColumn } from "@prisma-next/sql-relational-core/codec-descriptor-registry";
|
|
4
4
|
import { ifDefined } from "@prisma-next/utils/defined";
|
|
@@ -448,20 +448,54 @@ function resolveTableColumns(contract, tableName) {
|
|
|
448
448
|
if (!table) throw new Error(`Unknown table "${tableName}" in SQL ORM query planner`);
|
|
449
449
|
return Object.keys(table.columns);
|
|
450
450
|
}
|
|
451
|
-
function buildOrmPlanMeta(contract) {
|
|
451
|
+
function buildOrmPlanMeta(contract, annotations) {
|
|
452
|
+
const annotationRecord = annotations !== void 0 && annotations.size > 0 ? Object.freeze(Object.fromEntries(annotations)) : void 0;
|
|
452
453
|
return {
|
|
453
454
|
target: contract.target,
|
|
454
455
|
targetFamily: contract.targetFamily,
|
|
455
456
|
storageHash: contract.storage.storageHash,
|
|
456
|
-
...
|
|
457
|
+
...ifDefined("profileHash", contract.profileHash),
|
|
458
|
+
...ifDefined("annotations", annotationRecord),
|
|
457
459
|
lane: "orm-client"
|
|
458
460
|
};
|
|
459
461
|
}
|
|
460
|
-
function buildOrmQueryPlan(contract, ast, params) {
|
|
462
|
+
function buildOrmQueryPlan(contract, ast, params, annotations) {
|
|
461
463
|
return Object.freeze({
|
|
462
464
|
ast,
|
|
463
465
|
params: [...params],
|
|
464
|
-
meta: buildOrmPlanMeta(contract)
|
|
466
|
+
meta: buildOrmPlanMeta(contract, annotations)
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Merges annotations into an existing `SqlQueryPlan`'s
|
|
471
|
+
* `meta.annotations` and returns a new frozen plan.
|
|
472
|
+
*
|
|
473
|
+
* Used by the ORM dispatch path to attach terminal-call annotations to
|
|
474
|
+
* plans produced by mutation compile functions (which don't take
|
|
475
|
+
* annotations as parameters). Reads compile through `compileSelect`-
|
|
476
|
+
* family functions that pass `state.annotations` directly to
|
|
477
|
+
* `buildOrmQueryPlan`; this helper is the alternate path for write
|
|
478
|
+
* terminals where annotations arrive at the call site, not via state.
|
|
479
|
+
*
|
|
480
|
+
* Returns the input plan unchanged when `annotations` is undefined
|
|
481
|
+
* or empty. Reserved framework namespaces (`codecs`, `limit`) on the
|
|
482
|
+
* input plan win over caller-supplied entries under the same key —
|
|
483
|
+
* see the reserved-namespace policy on `defineAnnotation`.
|
|
484
|
+
*/
|
|
485
|
+
function mergeAnnotations(plan, annotations) {
|
|
486
|
+
if (annotations === void 0 || annotations.size === 0) return plan;
|
|
487
|
+
const callerEntries = {};
|
|
488
|
+
for (const [namespace, value] of annotations) callerEntries[namespace] = value;
|
|
489
|
+
const mergedAnnotations = Object.freeze({
|
|
490
|
+
...callerEntries,
|
|
491
|
+
...plan.meta.annotations ?? {}
|
|
492
|
+
});
|
|
493
|
+
return Object.freeze({
|
|
494
|
+
...plan,
|
|
495
|
+
meta: Object.freeze({
|
|
496
|
+
...plan.meta,
|
|
497
|
+
annotations: mergedAnnotations
|
|
498
|
+
})
|
|
465
499
|
});
|
|
466
500
|
}
|
|
467
501
|
//#endregion
|
|
@@ -967,7 +1001,7 @@ function compileSelect(contract, tableName, state, modelName) {
|
|
|
967
1001
|
includeProjection: mtiArtifacts.projection
|
|
968
1002
|
} : void 0);
|
|
969
1003
|
const { params } = deriveParamsFromAst(ast);
|
|
970
|
-
return buildOrmQueryPlan(contract, ast, params);
|
|
1004
|
+
return buildOrmQueryPlan(contract, ast, params, state.annotations);
|
|
971
1005
|
}
|
|
972
1006
|
function compileRelationSelect(contract, relatedTableName, targetColumn, parentPks, nestedState) {
|
|
973
1007
|
const inFilter = BinaryExpr.in(ColumnRef.of(relatedTableName, targetColumn), ListExpression.fromValues(parentPks));
|
|
@@ -1006,10 +1040,10 @@ function compileSelectWithIncludeStrategy(contract, tableName, state, strategy,
|
|
|
1006
1040
|
}, {
|
|
1007
1041
|
joins: includeJoins,
|
|
1008
1042
|
includeProjection,
|
|
1009
|
-
...
|
|
1043
|
+
...ifDefined("where", topLevelWhere)
|
|
1010
1044
|
});
|
|
1011
1045
|
const { params } = deriveParamsFromAst(ast);
|
|
1012
|
-
return buildOrmQueryPlan(contract, ast, params);
|
|
1046
|
+
return buildOrmQueryPlan(contract, ast, params, state.annotations);
|
|
1013
1047
|
}
|
|
1014
1048
|
//#endregion
|
|
1015
1049
|
//#region src/collection-dispatch.ts
|
|
@@ -1430,12 +1464,25 @@ var GroupedCollection = class GroupedCollection {
|
|
|
1430
1464
|
havingFilters: [...this.havingFilters, havingExpr]
|
|
1431
1465
|
});
|
|
1432
1466
|
}
|
|
1433
|
-
|
|
1467
|
+
/**
|
|
1468
|
+
* Read terminal: run a grouped aggregate query.
|
|
1469
|
+
*
|
|
1470
|
+
* Accepts an optional `configure` callback that receives a
|
|
1471
|
+
* `MetaBuilder<'read'>` for attaching typed annotations.
|
|
1472
|
+
* Annotations are merged into the compiled plan's `meta.annotations`.
|
|
1473
|
+
*/
|
|
1474
|
+
async aggregate(fn, configure) {
|
|
1434
1475
|
const aggregateSpec = fn(createAggregateBuilder(this.contract, this.modelName));
|
|
1435
1476
|
const aggregateEntries = Object.entries(aggregateSpec);
|
|
1436
1477
|
if (aggregateEntries.length === 0) throw new Error("groupBy().aggregate() requires at least one aggregation selector");
|
|
1437
1478
|
for (const [alias, selector] of aggregateEntries) if (!isAggregateSelector(selector)) throw new Error(`groupBy().aggregate() selector "${alias}" is invalid`);
|
|
1438
|
-
|
|
1479
|
+
let annotationsMap;
|
|
1480
|
+
if (configure !== void 0) {
|
|
1481
|
+
const meta = createMetaBuilder("read", "groupBy.aggregate");
|
|
1482
|
+
configure(meta);
|
|
1483
|
+
if (meta.annotations.size > 0) annotationsMap = meta.annotations;
|
|
1484
|
+
}
|
|
1485
|
+
const compiled = mergeAnnotations(compileGroupedAggregate(this.contract, this.tableName, this.baseFilters, this.groupByColumns, aggregateSpec, combineWhereExprs(this.havingFilters)), annotationsMap);
|
|
1439
1486
|
return (await executeQueryPlan(this.ctx.runtime, compiled).toArray()).map((row) => {
|
|
1440
1487
|
const mapped = mapStorageRowToModelFields(this.contract, this.modelName, row);
|
|
1441
1488
|
for (const [alias, selector] of aggregateEntries) mapped[alias] = coerceAggregateValue(selector.fn, row[alias]);
|
|
@@ -1555,7 +1602,8 @@ function emptyState() {
|
|
|
1555
1602
|
selectedFields: void 0,
|
|
1556
1603
|
limit: void 0,
|
|
1557
1604
|
offset: void 0,
|
|
1558
|
-
variantName: void 0
|
|
1605
|
+
variantName: void 0,
|
|
1606
|
+
annotations: /* @__PURE__ */ new Map()
|
|
1559
1607
|
};
|
|
1560
1608
|
}
|
|
1561
1609
|
function param(codec, value) {
|
|
@@ -2310,22 +2358,69 @@ var Collection = class Collection {
|
|
|
2310
2358
|
skip(n) {
|
|
2311
2359
|
return this.#clone({ offset: n });
|
|
2312
2360
|
}
|
|
2313
|
-
|
|
2314
|
-
|
|
2361
|
+
/**
|
|
2362
|
+
* Read terminal: stream all rows matching the current state.
|
|
2363
|
+
*
|
|
2364
|
+
* Accepts an optional `configure` callback that receives a
|
|
2365
|
+
* `MetaBuilder<'read'>` so the caller can attach typed user
|
|
2366
|
+
* annotations to the executed plan. `meta.annotate(...)` enforces
|
|
2367
|
+
* applicability at the type level and at runtime; annotations are
|
|
2368
|
+
* merged into `plan.meta.annotations` at compile time.
|
|
2369
|
+
*/
|
|
2370
|
+
all(configure) {
|
|
2371
|
+
return this.#withAnnotationsFromMeta(configure, "all").#dispatch();
|
|
2315
2372
|
}
|
|
2316
|
-
|
|
2317
|
-
|
|
2373
|
+
/**
|
|
2374
|
+
* Read terminal: return the first matching row, or `null`.
|
|
2375
|
+
*
|
|
2376
|
+
* Accepts an optional `filter` (function or shorthand) followed by an
|
|
2377
|
+
* optional `configure` callback that receives a `MetaBuilder<'read'>`
|
|
2378
|
+
* for attaching typed annotations. To attach annotations without
|
|
2379
|
+
* narrowing further, pass `undefined` as the filter (or chain
|
|
2380
|
+
* `.where(...)` first):
|
|
2381
|
+
*
|
|
2382
|
+
* ```typescript
|
|
2383
|
+
* await db.User.first({ id }, (meta) => meta.annotate(cacheAnnotation({ ttl: 60 })));
|
|
2384
|
+
* await db.User.first(undefined, (meta) => meta.annotate(cacheAnnotation({ ttl: 60 })));
|
|
2385
|
+
* ```
|
|
2386
|
+
*/
|
|
2387
|
+
async first(filter, configure) {
|
|
2388
|
+
return (await (filter === void 0 ? this : typeof filter === "function" ? this.where(filter) : this.where(filter)).take(1).#withAnnotationsFromMeta(configure, "first").#dispatch().toArray())[0] ?? null;
|
|
2318
2389
|
}
|
|
2319
|
-
|
|
2390
|
+
/**
|
|
2391
|
+
* Read terminal: run an aggregate query (count, sum, avg, min, max)
|
|
2392
|
+
* built via the `AggregateBuilder` callback.
|
|
2393
|
+
*
|
|
2394
|
+
* Accepts an optional `configure` callback that receives a
|
|
2395
|
+
* `MetaBuilder<'read'>` for attaching typed annotations.
|
|
2396
|
+
* Annotations are merged into the compiled plan's `meta.annotations`.
|
|
2397
|
+
*/
|
|
2398
|
+
async aggregate(fn, configure) {
|
|
2320
2399
|
const aggregateSpec = fn(createAggregateBuilder(this.contract, this.modelName));
|
|
2321
2400
|
const entries = Object.entries(aggregateSpec);
|
|
2322
2401
|
if (entries.length === 0) throw new Error("aggregate() requires at least one aggregation selector");
|
|
2323
2402
|
for (const [alias, selector] of entries) if (!isAggregateSelector(selector)) throw new Error(`aggregate() selector "${alias}" is invalid`);
|
|
2324
|
-
const
|
|
2403
|
+
const annotationsMap = this.#collectAnnotationsFromMeta(configure, "read", "aggregate");
|
|
2404
|
+
const compiled = mergeAnnotations(compileAggregate(this.contract, this.tableName, this.state.filters, aggregateSpec), annotationsMap);
|
|
2325
2405
|
return normalizeAggregateResult(aggregateSpec, (await executeQueryPlan(this.ctx.runtime, compiled).toArray())[0] ?? {});
|
|
2326
2406
|
}
|
|
2327
|
-
|
|
2407
|
+
/**
|
|
2408
|
+
* Write terminal: insert one row and return it.
|
|
2409
|
+
*
|
|
2410
|
+
* Accepts an optional `configure` callback that receives a
|
|
2411
|
+
* `MetaBuilder<'write'>` for attaching typed annotations.
|
|
2412
|
+
* Annotations are merged into the compiled mutation plan's
|
|
2413
|
+
* `meta.annotations`.
|
|
2414
|
+
*
|
|
2415
|
+
* Note: when the input contains nested-mutation callbacks, the
|
|
2416
|
+
* operation is executed as a graph of internal queries via
|
|
2417
|
+
* `withMutationScope`. In that path, annotations apply to the
|
|
2418
|
+
* logical `create()` call but do not currently flow into each
|
|
2419
|
+
* constituent SQL statement issued for the related rows.
|
|
2420
|
+
*/
|
|
2421
|
+
async create(data, configure) {
|
|
2328
2422
|
assertReturningCapability(this.contract, "create()");
|
|
2423
|
+
const annotationsMap = this.#collectAnnotationsFromMeta(configure, "write", "create");
|
|
2329
2424
|
if (hasNestedMutationCallbacks(this.contract, this.modelName, data)) {
|
|
2330
2425
|
const createdRow = await executeNestedCreateMutation({
|
|
2331
2426
|
context: this.ctx.context,
|
|
@@ -2338,11 +2433,14 @@ var Collection = class Collection {
|
|
|
2338
2433
|
if (!reloaded) throw new Error(`create() for model "${this.modelName}" did not return a row`);
|
|
2339
2434
|
return reloaded;
|
|
2340
2435
|
}
|
|
2341
|
-
const created = (await this
|
|
2436
|
+
const created = (await this.#createAllWithAnnotations([data], annotationsMap))[0];
|
|
2342
2437
|
if (created) return created;
|
|
2343
2438
|
throw new Error(`create() for model "${this.modelName}" did not return a row`);
|
|
2344
2439
|
}
|
|
2345
|
-
createAll(data) {
|
|
2440
|
+
createAll(data, configure) {
|
|
2441
|
+
return this.#createAllWithAnnotations(data, this.#collectAnnotationsFromMeta(configure, "write", "createAll"));
|
|
2442
|
+
}
|
|
2443
|
+
#createAllWithAnnotations(data, annotationsMap) {
|
|
2346
2444
|
if (data.length === 0) {
|
|
2347
2445
|
const generator = async function* () {};
|
|
2348
2446
|
return new AsyncIterableResult(generator());
|
|
@@ -2356,7 +2454,7 @@ var Collection = class Collection {
|
|
|
2356
2454
|
const parentJoinColumns = this.state.includes.map((include) => include.localColumn);
|
|
2357
2455
|
const { selectedForQuery: selectedForInsert, hiddenColumns } = augmentSelectionForJoinColumns(this.state.selectedFields, parentJoinColumns);
|
|
2358
2456
|
if (this.contract.capabilities?.["sql"]?.["defaultInInsert"] !== true) {
|
|
2359
|
-
const plans = compileInsertReturningSplit(this.contract, this.tableName, mappedRows, selectedForInsert);
|
|
2457
|
+
const plans = compileInsertReturningSplit(this.contract, this.tableName, mappedRows, selectedForInsert).map((plan) => mergeAnnotations(plan, annotationsMap));
|
|
2360
2458
|
return dispatchSplitMutationRows({
|
|
2361
2459
|
contract: this.contract,
|
|
2362
2460
|
runtime: this.ctx.runtime,
|
|
@@ -2367,7 +2465,7 @@ var Collection = class Collection {
|
|
|
2367
2465
|
mapRow: (mapped) => mapped
|
|
2368
2466
|
});
|
|
2369
2467
|
}
|
|
2370
|
-
const compiled = compileInsertReturning(this.contract, this.tableName, mappedRows, selectedForInsert);
|
|
2468
|
+
const compiled = mergeAnnotations(compileInsertReturning(this.contract, this.tableName, mappedRows, selectedForInsert), annotationsMap);
|
|
2371
2469
|
return dispatchMutationRows({
|
|
2372
2470
|
contract: this.contract,
|
|
2373
2471
|
runtime: this.ctx.runtime,
|
|
@@ -2470,18 +2568,19 @@ var Collection = class Collection {
|
|
|
2470
2568
|
return mapped;
|
|
2471
2569
|
});
|
|
2472
2570
|
}
|
|
2473
|
-
async createCount(data) {
|
|
2571
|
+
async createCount(data, configure) {
|
|
2474
2572
|
if (data.length === 0) return 0;
|
|
2475
2573
|
this.#assertNotMtiVariant("createCount()");
|
|
2574
|
+
const annotationsMap = this.#collectAnnotationsFromMeta(configure, "write", "createCount");
|
|
2476
2575
|
const rows = data;
|
|
2477
2576
|
const mappedRows = this.#mapCreateRows(rows);
|
|
2478
2577
|
applyCreateDefaults(this.ctx, this.tableName, mappedRows);
|
|
2479
2578
|
if (this.contract.capabilities?.["sql"]?.["defaultInInsert"] !== true) {
|
|
2480
|
-
const plans = compileInsertCountSplit(this.contract, this.tableName, mappedRows);
|
|
2579
|
+
const plans = compileInsertCountSplit(this.contract, this.tableName, mappedRows).map((plan) => mergeAnnotations(plan, annotationsMap));
|
|
2481
2580
|
for (const plan of plans) await executeQueryPlan(this.ctx.runtime, plan).toArray();
|
|
2482
2581
|
return data.length;
|
|
2483
2582
|
}
|
|
2484
|
-
const compiled = compileInsertCount(this.contract, this.tableName, mappedRows);
|
|
2583
|
+
const compiled = mergeAnnotations(compileInsertCount(this.contract, this.tableName, mappedRows), annotationsMap);
|
|
2485
2584
|
await executeQueryPlan(this.ctx.runtime, compiled).toArray();
|
|
2486
2585
|
return data.length;
|
|
2487
2586
|
}
|
|
@@ -2490,9 +2589,10 @@ var Collection = class Collection {
|
|
|
2490
2589
|
* On conflict, `ON CONFLICT DO NOTHING RETURNING ...` may return zero rows,
|
|
2491
2590
|
* so this method may issue a follow-up reload query to return the existing row.
|
|
2492
2591
|
*/
|
|
2493
|
-
async upsert(input) {
|
|
2592
|
+
async upsert(input, configure) {
|
|
2494
2593
|
assertReturningCapability(this.contract, "upsert()");
|
|
2495
2594
|
this.#assertNotMtiVariant("upsert()");
|
|
2595
|
+
const annotationsMap = this.#collectAnnotationsFromMeta(configure, "write", "upsert");
|
|
2496
2596
|
const createValues = this.#mapCreateRows([input.create])[0] ?? {};
|
|
2497
2597
|
applyCreateDefaults(this.ctx, this.tableName, [createValues]);
|
|
2498
2598
|
const updateValues = mapModelDataToStorageRow(this.contract, this.modelName, input.update);
|
|
@@ -2502,7 +2602,7 @@ var Collection = class Collection {
|
|
|
2502
2602
|
if (conflictColumns.length === 0) throw new Error(`upsert() for model "${this.modelName}" requires conflict columns`);
|
|
2503
2603
|
const parentJoinColumns = this.state.includes.map((include) => include.localColumn);
|
|
2504
2604
|
const { selectedForQuery: selectedForUpsert, hiddenColumns } = augmentSelectionForJoinColumns(this.state.selectedFields, parentJoinColumns);
|
|
2505
|
-
const compiled = compileUpsertReturning(this.contract, this.tableName, createValues, updateValues, conflictColumns, selectedForUpsert);
|
|
2605
|
+
const compiled = mergeAnnotations(compileUpsertReturning(this.contract, this.tableName, createValues, updateValues, conflictColumns, selectedForUpsert), annotationsMap);
|
|
2506
2606
|
const row = await executeMutationReturningSingleRow({
|
|
2507
2607
|
contract: this.contract,
|
|
2508
2608
|
runtime: this.ctx.runtime,
|
|
@@ -2521,8 +2621,22 @@ var Collection = class Collection {
|
|
|
2521
2621
|
}
|
|
2522
2622
|
throw new Error(`upsert() for model "${this.modelName}" did not return a row`);
|
|
2523
2623
|
}
|
|
2524
|
-
|
|
2624
|
+
/**
|
|
2625
|
+
* Write terminal: update matching rows and return the first one (or
|
|
2626
|
+
* null when no row matched).
|
|
2627
|
+
*
|
|
2628
|
+
* Accepts an optional `configure` callback that receives a
|
|
2629
|
+
* `MetaBuilder<'write'>` for attaching typed annotations.
|
|
2630
|
+
*
|
|
2631
|
+
* Note: when the input contains nested-mutation callbacks, the
|
|
2632
|
+
* operation is executed as a graph of internal queries via
|
|
2633
|
+
* `withMutationScope`. In that path, annotations apply to the logical
|
|
2634
|
+
* `update()` call but do not currently flow into each constituent SQL
|
|
2635
|
+
* statement issued for the related rows.
|
|
2636
|
+
*/
|
|
2637
|
+
async update(data, configure) {
|
|
2525
2638
|
assertReturningCapability(this.contract, "update()");
|
|
2639
|
+
const annotationsMap = this.#collectAnnotationsFromMeta(configure, "write", "update");
|
|
2526
2640
|
if (hasNestedMutationCallbacks(this.contract, this.modelName, data)) {
|
|
2527
2641
|
const updatedRow = await executeNestedUpdateMutation({
|
|
2528
2642
|
context: this.ctx.context,
|
|
@@ -2539,10 +2653,13 @@ var Collection = class Collection {
|
|
|
2539
2653
|
const scoped = this.#withRuntime(scope);
|
|
2540
2654
|
const identityWhere = await scoped.#findFirstMatchingRowIdentityWhere();
|
|
2541
2655
|
if (!identityWhere) return null;
|
|
2542
|
-
return (await scoped.#clone({ filters: [identityWhere] })
|
|
2656
|
+
return (await scoped.#clone({ filters: [identityWhere] }).#updateAllWithAnnotations(data, annotationsMap))[0] ?? null;
|
|
2543
2657
|
});
|
|
2544
2658
|
}
|
|
2545
|
-
updateAll(data) {
|
|
2659
|
+
updateAll(data, configure) {
|
|
2660
|
+
return this.#updateAllWithAnnotations(data, this.#collectAnnotationsFromMeta(configure, "write", "updateAll"));
|
|
2661
|
+
}
|
|
2662
|
+
#updateAllWithAnnotations(data, annotationsMap) {
|
|
2546
2663
|
assertReturningCapability(this.contract, "updateAll()");
|
|
2547
2664
|
const mappedData = mapModelDataToStorageRow(this.contract, this.modelName, data);
|
|
2548
2665
|
if (Object.keys(mappedData).length === 0) {
|
|
@@ -2552,7 +2669,7 @@ var Collection = class Collection {
|
|
|
2552
2669
|
applyUpdateDefaults(this.ctx, this.tableName, mappedData);
|
|
2553
2670
|
const parentJoinColumns = this.state.includes.map((include) => include.localColumn);
|
|
2554
2671
|
const { selectedForQuery: selectedForUpdate, hiddenColumns } = augmentSelectionForJoinColumns(this.state.selectedFields, parentJoinColumns);
|
|
2555
|
-
const compiled = compileUpdateReturning(this.contract, this.tableName, mappedData, this.state.filters, selectedForUpdate);
|
|
2672
|
+
const compiled = mergeAnnotations(compileUpdateReturning(this.contract, this.tableName, mappedData, this.state.filters, selectedForUpdate), annotationsMap);
|
|
2556
2673
|
return dispatchMutationRows({
|
|
2557
2674
|
contract: this.contract,
|
|
2558
2675
|
runtime: this.ctx.runtime,
|
|
@@ -2563,10 +2680,11 @@ var Collection = class Collection {
|
|
|
2563
2680
|
mapRow: (mapped) => mapped
|
|
2564
2681
|
});
|
|
2565
2682
|
}
|
|
2566
|
-
async updateCount(data) {
|
|
2683
|
+
async updateCount(data, configure) {
|
|
2567
2684
|
const mappedData = mapModelDataToStorageRow(this.contract, this.modelName, data);
|
|
2568
2685
|
if (Object.keys(mappedData).length === 0) return 0;
|
|
2569
2686
|
applyUpdateDefaults(this.ctx, this.tableName, mappedData);
|
|
2687
|
+
const annotationsMap = this.#collectAnnotationsFromMeta(configure, "write", "updateCount");
|
|
2570
2688
|
const primaryKeyColumn = resolvePrimaryKeyColumn(this.contract, this.tableName);
|
|
2571
2689
|
const countState = {
|
|
2572
2690
|
...emptyState(),
|
|
@@ -2575,27 +2693,38 @@ var Collection = class Collection {
|
|
|
2575
2693
|
};
|
|
2576
2694
|
const countCompiled = compileSelect(this.contract, this.tableName, countState);
|
|
2577
2695
|
const matchingRows = await executeQueryPlan(this.ctx.runtime, countCompiled).toArray();
|
|
2578
|
-
const compiled = compileUpdateCount(this.contract, this.tableName, mappedData, this.state.filters);
|
|
2696
|
+
const compiled = mergeAnnotations(compileUpdateCount(this.contract, this.tableName, mappedData, this.state.filters), annotationsMap);
|
|
2579
2697
|
await executeQueryPlan(this.ctx.runtime, compiled).toArray();
|
|
2580
2698
|
return matchingRows.length;
|
|
2581
2699
|
}
|
|
2582
|
-
|
|
2700
|
+
/**
|
|
2701
|
+
* Write terminal: delete matching rows and return the first one (or
|
|
2702
|
+
* null when no row matched).
|
|
2703
|
+
*
|
|
2704
|
+
* Accepts an optional `configure` callback that receives a
|
|
2705
|
+
* `MetaBuilder<'write'>` for attaching typed annotations.
|
|
2706
|
+
*/
|
|
2707
|
+
async delete(configure) {
|
|
2583
2708
|
assertReturningCapability(this.contract, "delete()");
|
|
2709
|
+
const annotationsMap = this.#collectAnnotationsFromMeta(configure, "write", "delete");
|
|
2584
2710
|
return withMutationScope(this.ctx.runtime, async (scope) => {
|
|
2585
2711
|
const scoped = this.#withRuntime(scope);
|
|
2586
2712
|
const identityWhere = await scoped.#findFirstMatchingRowIdentityWhere();
|
|
2587
2713
|
if (!identityWhere) return null;
|
|
2588
|
-
return (await scoped.#clone({ filters: [identityWhere] }).#executeDeleteReturning().toArray())[0] ?? null;
|
|
2714
|
+
return (await scoped.#clone({ filters: [identityWhere] }).#executeDeleteReturning(annotationsMap).toArray())[0] ?? null;
|
|
2589
2715
|
});
|
|
2590
2716
|
}
|
|
2591
|
-
deleteAll() {
|
|
2717
|
+
deleteAll(configure) {
|
|
2718
|
+
return this.#deleteAllWithAnnotations(this.#collectAnnotationsFromMeta(configure, "write", "deleteAll"));
|
|
2719
|
+
}
|
|
2720
|
+
#deleteAllWithAnnotations(annotationsMap) {
|
|
2592
2721
|
assertReturningCapability(this.contract, "deleteAll()");
|
|
2593
|
-
return this.#executeDeleteReturning();
|
|
2722
|
+
return this.#executeDeleteReturning(annotationsMap);
|
|
2594
2723
|
}
|
|
2595
|
-
#executeDeleteReturning() {
|
|
2724
|
+
#executeDeleteReturning(annotationsMap) {
|
|
2596
2725
|
const parentJoinColumns = this.state.includes.map((include) => include.localColumn);
|
|
2597
2726
|
const { selectedForQuery: selectedForDelete, hiddenColumns } = augmentSelectionForJoinColumns(this.state.selectedFields, parentJoinColumns);
|
|
2598
|
-
const compiled = compileDeleteReturning(this.contract, this.tableName, this.state.filters, selectedForDelete);
|
|
2727
|
+
const compiled = mergeAnnotations(compileDeleteReturning(this.contract, this.tableName, this.state.filters, selectedForDelete), annotationsMap);
|
|
2599
2728
|
return dispatchMutationRows({
|
|
2600
2729
|
contract: this.contract,
|
|
2601
2730
|
runtime: this.ctx.runtime,
|
|
@@ -2606,7 +2735,8 @@ var Collection = class Collection {
|
|
|
2606
2735
|
mapRow: (mapped) => mapped
|
|
2607
2736
|
});
|
|
2608
2737
|
}
|
|
2609
|
-
async deleteCount() {
|
|
2738
|
+
async deleteCount(configure) {
|
|
2739
|
+
const annotationsMap = this.#collectAnnotationsFromMeta(configure, "write", "deleteCount");
|
|
2610
2740
|
const primaryKeyColumn = resolvePrimaryKeyColumn(this.contract, this.tableName);
|
|
2611
2741
|
const countState = {
|
|
2612
2742
|
...emptyState(),
|
|
@@ -2615,7 +2745,7 @@ var Collection = class Collection {
|
|
|
2615
2745
|
};
|
|
2616
2746
|
const countCompiled = compileSelect(this.contract, this.tableName, countState);
|
|
2617
2747
|
const matchingRows = await executeQueryPlan(this.ctx.runtime, countCompiled).toArray();
|
|
2618
|
-
const compiled = compileDeleteCount(this.contract, this.tableName, this.state.filters);
|
|
2748
|
+
const compiled = mergeAnnotations(compileDeleteCount(this.contract, this.tableName, this.state.filters), annotationsMap);
|
|
2619
2749
|
await executeQueryPlan(this.ctx.runtime, compiled).toArray();
|
|
2620
2750
|
return matchingRows.length;
|
|
2621
2751
|
}
|
|
@@ -2722,6 +2852,45 @@ var Collection = class Collection {
|
|
|
2722
2852
|
modelName: this.modelName
|
|
2723
2853
|
});
|
|
2724
2854
|
}
|
|
2855
|
+
/**
|
|
2856
|
+
* Invokes the user-supplied configurator (if any) against a freshly
|
|
2857
|
+
* constructed read meta builder, and returns a clone whose
|
|
2858
|
+
* `state.annotations` carries the recorded map. Used by read
|
|
2859
|
+
* terminals that flow annotations through state (`all`, `first`).
|
|
2860
|
+
*
|
|
2861
|
+
* Returns the receiver unchanged when no configurator was supplied
|
|
2862
|
+
* or when the configurator did not call `meta.annotate(...)`. The
|
|
2863
|
+
* meta builder's `annotate` method enforces applicability at the
|
|
2864
|
+
* type level and at runtime, so terminal code does not need to
|
|
2865
|
+
* re-validate.
|
|
2866
|
+
*/
|
|
2867
|
+
#withAnnotationsFromMeta(configure, terminalName) {
|
|
2868
|
+
if (configure === void 0) return this;
|
|
2869
|
+
const meta = createMetaBuilder("read", terminalName);
|
|
2870
|
+
configure(meta);
|
|
2871
|
+
if (meta.annotations.size === 0) return this;
|
|
2872
|
+
const next = new Map(this.state.annotations);
|
|
2873
|
+
for (const [namespace, value] of meta.annotations) next.set(namespace, value);
|
|
2874
|
+
return this.#clone({ annotations: next });
|
|
2875
|
+
}
|
|
2876
|
+
/**
|
|
2877
|
+
* Invokes the user-supplied configurator (if any) against a freshly
|
|
2878
|
+
* constructed meta builder of the given operation kind, and returns
|
|
2879
|
+
* the recorded annotation map (or `undefined` when empty). Used by
|
|
2880
|
+
* terminals where annotations don't flow through `state` — the
|
|
2881
|
+
* compiled plan is post-wrapped via `mergeAnnotations` instead.
|
|
2882
|
+
* Read terminals `all` and `first` populate `state.annotations`
|
|
2883
|
+
* via `#withAnnotationsFromMeta` instead; `aggregate` uses this
|
|
2884
|
+
* post-wrap path because its compile function doesn't take `state`.
|
|
2885
|
+
* The meta builder's `annotate` method enforces applicability at the
|
|
2886
|
+
* type level and at runtime.
|
|
2887
|
+
*/
|
|
2888
|
+
#collectAnnotationsFromMeta(configure, kind, terminalName) {
|
|
2889
|
+
if (configure === void 0) return;
|
|
2890
|
+
const meta = createMetaBuilder(kind, terminalName);
|
|
2891
|
+
configure(meta);
|
|
2892
|
+
return meta.annotations.size === 0 ? void 0 : meta.annotations;
|
|
2893
|
+
}
|
|
2725
2894
|
};
|
|
2726
2895
|
//#endregion
|
|
2727
2896
|
//#region src/orm.ts
|