@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.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
- ...contract.profileHash !== void 0 ? { profileHash: contract.profileHash } : {},
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
- ...topLevelWhere ? { where: topLevelWhere } : {}
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
- async aggregate(fn) {
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
- const compiled = compileGroupedAggregate(this.contract, this.tableName, this.baseFilters, this.groupByColumns, aggregateSpec, combineWhereExprs(this.havingFilters));
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
- all() {
2314
- return this.#dispatch();
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
- async first(filter) {
2317
- return (await (filter === void 0 ? this : typeof filter === "function" ? this.where(filter) : this.where(filter)).take(1).#dispatch().toArray())[0] ?? null;
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
- async aggregate(fn) {
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 compiled = compileAggregate(this.contract, this.tableName, this.state.filters, aggregateSpec);
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
- async create(data) {
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.createAll([data]))[0];
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
- async update(data) {
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] }).updateAll(data))[0] ?? null;
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
- async delete() {
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