graphddb 0.8.1 → 0.9.1

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.ts CHANGED
@@ -1,8 +1,11 @@
1
- import { ak as SelectableOf, al as PrimaryKeyOf, e as ProjectionTransform, f as MaintainEvent, j as MembershipPredicate, h as MaintainConsistency, i as MaintainUpdateMode, am as MembershipPredicateOp, an as publishQuery, ao as publishCommand, D as DDBModel, ap as RetryPolicy, aq as Middleware } from './key-DZtjAQDh.js';
2
- export { ar as CdcModelRegistry, as as CdcSubscribeHandlers, at as Change, au as Column, av as ColumnMap, aw as CondSlot, ax as CtxBase, ay as CtxModel, az as FilterInput, aA as GsiDefinitionMarker, aB as GsiOptions, aC as InlineSnapshotSpec, aD as Item, aE as KeyDefinitionMarker, aF as KeySegment, aG as KeySlot, aH as KeyStructure, M as ModelStatic, aI as MutateAuthoringOptions, aJ as ParamDescriptor, aK as ParamStructure, aL as PartialQueryKeyOf, aM as PersistCtx, aN as PersistOrigin, aO as QueryKeyOf, aP as QueryResult, aQ as RawCondition, aR as ReadOpCtx, aS as ReadOpKind, aT as ReadParams, aU as ReadRequestCtx, aV as ReadRequestKind, aW as RelationBuilder, aX as RelationSelect, aY as RelationSpec, aZ as RequestContext, a_ as SegmentSpec, d as SegmentedKey, a$ as SelectBuilder, b0 as SelectOf, b1 as UniqueQueryKeyOf, b2 as Updatable, b3 as WriteCtx, b4 as WriteInput, b5 as WriteKind, b6 as WriteMiddleware, b7 as cond, b8 as entityWrites, b9 as getEntityWrites, ba as gsi, bb as identity, bc as k, bd as key, be as mutate, bf as preview, bg as when } from './key-DZtjAQDh.js';
1
+ import { ak as SelectableOf, al as PrimaryKeyOf, e as ProjectionTransform, f as MaintainEvent, j as MembershipPredicate, h as MaintainConsistency, i as MaintainUpdateMode, am as MembershipPredicateOp, an as publishQuery, ao as publishCommand, D as DDBModel, ap as RetryPolicy, aq as Middleware } from './key-CWytoEaE.js';
2
+ export { ar as CdcModelRegistry, as as CdcSubscribeHandlers, at as Change, au as Column, av as ColumnMap, aw as CondSlot, ax as CtxBase, ay as CtxModel, az as FilterInput, aA as GsiDefinitionMarker, aB as GsiOptions, aC as InlineSnapshotSpec, aD as Item, aE as KeyDefinitionMarker, aF as KeySegment, aG as KeySlot, aH as KeyStructure, M as ModelStatic, aI as MutateAuthoringOptions, aJ as ParamDescriptor, aK as ParamStructure, aL as PartialQueryKeyOf, aM as PersistCtx, aN as PersistOrigin, aO as QueryKeyOf, aP as QueryResult, aQ as RawCondition, aR as ReadOpCtx, aS as ReadOpKind, aT as ReadParams, aU as ReadRequestCtx, aV as ReadRequestKind, aW as RelationBuilder, aX as RelationSelect, aY as RelationSpec, aZ as RequestContext, a_ as SegmentSpec, d as SegmentedKey, a$ as SelectBuilder, b0 as SelectOf, b1 as UniqueQueryKeyOf, b2 as Updatable, b3 as WriteCtx, b4 as WriteInput, b5 as WriteKind, b6 as WriteMiddleware, b7 as cond, b8 as entityWrites, b9 as getEntityWrites, ba as gsi, bb as identity, bc as k, bd as key, be as mutate, bf as preview, bg as when } from './key-CWytoEaE.js';
3
3
  import * as _aws_sdk_client_dynamodb from '@aws-sdk/client-dynamodb';
4
- import { M as ModelKind, F as FieldOptions, D as DynamoType, R as RelationOptions, A as AggregateOptions, a as AggregateValue } from './types-DW__-Icc.js';
5
- export { $ as LiteralParam, a0 as NumberParam, a1 as Param, a2 as ParamKind, a3 as StringParam, a4 as param } from './types-2PMXEn5x.js';
4
+ import { p as publishBehaviors } from './behaviors-DDltNivc.js';
5
+ export { b as BehaviorComponentFns, a as BehaviorMethodSpec, B as BehaviorModelContract, P as PublishBehaviorsOptions, c as behaviorComponents, i as isBehaviorModelContract } from './behaviors-DDltNivc.js';
6
+ import { M as ModelKind, F as FieldOptions, D as DynamoType, R as RelationOptions, A as AggregateOptions, a as AggregateValue } from './types-nk5okD7d.js';
7
+ export { a0 as LiteralParam, a1 as MapParam, a2 as NumberParam, a3 as Param, a4 as ParamKind, a5 as StringParam, a6 as param } from './types-CgXS-4Ox.js';
8
+ import 'behavior-contracts';
6
9
 
7
10
  /**
8
11
  * Query options with conditional `consistentRead` availability.
@@ -196,6 +199,128 @@ declare const map: FieldDecorator<Record<string, unknown>>;
196
199
 
197
200
  declare function embedded(modelFactory: () => new (...args: unknown[]) => unknown): (_value: undefined, context: ClassFieldDecoratorContext) => void;
198
201
 
202
+ /**
203
+ * Field groups — reusable, composable field sets for models (issue #295, part B).
204
+ *
205
+ * ## Why composition, not inheritance
206
+ *
207
+ * The metadata pipeline is drain-based: TC39 field decorators push
208
+ * {@link FieldMetadata} into a module-level pending collector, and the topmost
209
+ * `@model` class decorator drains it (`collector.ts`). A **decorated shared base
210
+ * class** breaks that contract — the base's field decorators fire ONCE (at base
211
+ * class definition) and sit pending until the NEXT `@model` drains them, so the
212
+ * first subclass absorbs the base's fields and every later subclass silently
213
+ * loses them (drain-order dependence; the issue-#295 report). Real inheritance
214
+ * support would require re-architecting the collector around prototype-chain
215
+ * walks. A **field group** achieves the reuse the issue wants ("generic meta
216
+ * columns shared by every domain model") with the SAME mechanism the codebase
217
+ * already uses for stacked class-level declarations (`@maintainedFrom`,
218
+ * `@cdcProjected`): a class decorator that pushes into the pending collector
219
+ * BEFORE `@model` drains — per-class, so no drain-order hazard exists.
220
+ *
221
+ * ## Usage
222
+ *
223
+ * ```ts
224
+ * // Shared, generic meta columns — defined ONCE, reused by every domain model.
225
+ * export const TreeMeta = defineFieldGroup(($) => ({
226
+ * part: $.string(),
227
+ * child: $.string(),
228
+ * scope: $.string(),
229
+ * computeVersion: $.number(),
230
+ * computedAt: $.number(),
231
+ * minPendingAt: $.number(),
232
+ * }));
233
+ *
234
+ * // A domain model composes the group with its own typed map value (issue #295):
235
+ * @model({ table: TABLE })
236
+ * @withFields(TreeMeta)
237
+ * export class AvgTreeNodeModel extends DDBModel {
238
+ * static readonly keys = key<{ part: string; child: string }>((c) => ({
239
+ * pk: k`TREE#${c.part}`,
240
+ * sk: k`${c.child}`,
241
+ * }));
242
+ * @map value!: AvgValue; // the domain-specific value class, one M attribute
243
+ * }
244
+ * // TypeScript-side property typing via declaration merging (one line):
245
+ * export interface AvgTreeNodeModel extends FieldsOf<typeof TreeMeta> {}
246
+ * ```
247
+ *
248
+ * Group fields are ordinary {@link FieldMetadata}: they participate in keys
249
+ * (`key<{part, child}>` references them by name), GSIs, params-kind recovery
250
+ * (a `param` bound into a group `$.number()` field serializes as `'number'`),
251
+ * hydration, and codegen exactly as body-declared fields do.
252
+ */
253
+
254
+ /** Brand tag carrying the TS value type a group field represents. */
255
+ declare const GROUP_FIELD_BRAND: unique symbol;
256
+ /**
257
+ * One field definition inside a {@link FieldGroup}: the same storage facts a
258
+ * semantic field decorator records (`dynamoType` + optional `options` /
259
+ * `literals`), plus — purely at the type level — the represented value type `T`
260
+ * (surfaced through {@link FieldsOf}).
261
+ */
262
+ interface GroupFieldDef<out T> {
263
+ /** @internal Type brand carrying the represented value type. */
264
+ readonly [GROUP_FIELD_BRAND]: T;
265
+ readonly dynamoType: DynamoType;
266
+ readonly options?: FieldOptions;
267
+ readonly literals?: readonly string[];
268
+ }
269
+ /** The field-shape record a {@link defineFieldGroup} builder returns. */
270
+ type FieldGroupShape = Record<string, GroupFieldDef<unknown>>;
271
+ /** A reusable, named set of field definitions (issue #295 part B). */
272
+ interface FieldGroup<S extends FieldGroupShape = FieldGroupShape> {
273
+ readonly __isFieldGroup: true;
274
+ readonly fields: S;
275
+ }
276
+ /**
277
+ * The TypeScript property types a {@link FieldGroup} contributes — merge them
278
+ * into the model class with one declaration-merging line:
279
+ * `export interface MyModel extends FieldsOf<typeof MyGroup> {}`.
280
+ */
281
+ type FieldsOf<G extends FieldGroup> = {
282
+ [K in keyof G['fields']]: G['fields'][K] extends GroupFieldDef<infer T> ? T : never;
283
+ };
284
+ /**
285
+ * The field factories a {@link defineFieldGroup} builder receives — one per
286
+ * semantic field decorator, recording the same storage facts (`@string` → `'S'`,
287
+ * `@datetime` → `'S'` + `format`, `@literal` → `'S'` + the literal set, …).
288
+ */
289
+ interface GroupFieldFactories {
290
+ string(options?: FieldOptions): GroupFieldDef<string>;
291
+ number(options?: FieldOptions): GroupFieldDef<number>;
292
+ boolean(options?: FieldOptions): GroupFieldDef<boolean>;
293
+ datetime(options?: FieldOptions): GroupFieldDef<Date>;
294
+ binary(options?: FieldOptions): GroupFieldDef<Uint8Array>;
295
+ stringSet(options?: FieldOptions): GroupFieldDef<Set<string>>;
296
+ numberSet(options?: FieldOptions): GroupFieldDef<Set<number>>;
297
+ list(options?: FieldOptions): GroupFieldDef<unknown[]>;
298
+ /** A free-form map field (one DynamoDB `M` attribute). `T` types the TS surface. */
299
+ map<T extends Record<string, unknown> = Record<string, unknown>>(options?: FieldOptions): GroupFieldDef<T>;
300
+ /** A closed string-literal-union field (the `@literal(...)` counterpart). */
301
+ literal<const L extends string>(...values: readonly [L, ...L[]]): GroupFieldDef<L>;
302
+ }
303
+ /**
304
+ * Define a reusable field group. The builder receives the field factories and
305
+ * returns the `{ fieldName: $.<type>() }` shape; the group is a plain immutable
306
+ * value (no collector interaction happens here — fields are contributed to a
307
+ * model only when `@withFields(group)` runs on that class).
308
+ */
309
+ declare function defineFieldGroup<const S extends FieldGroupShape>(build: (f: GroupFieldFactories) => S): FieldGroup<S>;
310
+ /**
311
+ * `@withFields(...groups)` — contribute each group's fields to the decorated
312
+ * model class. Stacked BELOW `@model` (which must stay topmost): TC39 class
313
+ * decorators apply bottom-up, so `@withFields` pushes into the pending collector
314
+ * and the `@model` drain picks the group fields up together with the class's own
315
+ * decorated fields — the exact `@maintainedFrom` / `@cdcProjected` mechanism.
316
+ *
317
+ * Duplicate field names are rejected LOUDLY: a group field colliding with a
318
+ * body-declared field (already pending at this point — field decorators run
319
+ * before any class decorator) or with an earlier group would register two
320
+ * metadata entries for one property and silently shadow one of them.
321
+ */
322
+ declare function withFields(...groups: readonly FieldGroup[]): (_target: Function, context: ClassDecoratorContext) => void;
323
+
199
324
  /**
200
325
  * Symbolic `(self, source) =>` evaluation for the `@maintainedFrom` view authoring
201
326
  * and the versioned relation `pattern` union (issue #152).
@@ -668,6 +793,7 @@ declare function cdcProjected(): (_target: Function, _context: ClassDecoratorCon
668
793
  declare const graphddb: {
669
794
  readonly publishQuery: typeof publishQuery;
670
795
  readonly publishCommand: typeof publishCommand;
796
+ readonly publishBehaviors: typeof publishBehaviors;
671
797
  readonly query: typeof DDBModel.query;
672
798
  readonly mutate: typeof DDBModel.mutate;
673
799
  readonly prepare: typeof DDBModel.prepare;
@@ -682,4 +808,4 @@ declare const graphddb: {
682
808
  };
683
809
  };
684
810
 
685
- export { DDBModel, type HasManyVersionedCallback, type HasManyVersionedOptions, type HasOneVersionedCallback, type HasOneVersionedOptions, type ListOptions, type MaintainedFromCallback, type MaintainedFromOptions, Middleware, type ModelOptions, PrimaryKeyOf, type ProjectionValue, type QueryOptions, type RefsOptions, SelectableOf, type SelfProxy, type SelfRef, type SourceProxy, type SourceRef, type VersionedHistoryOptions, type VersionedLatestOptions, aggregate, belongsTo, binary, boolean, cdcProjected, count, datetime, embedded, field, graphddb, hasMany, hasOne, list, literal, maintainedFrom, map, max, model, number, numberSet, refs, string, stringSet, ttl, whenMember };
811
+ export { DDBModel, type FieldGroup, type FieldGroupShape, type FieldsOf, type GroupFieldDef, type GroupFieldFactories, type HasManyVersionedCallback, type HasManyVersionedOptions, type HasOneVersionedCallback, type HasOneVersionedOptions, type ListOptions, type MaintainedFromCallback, type MaintainedFromOptions, Middleware, type ModelOptions, PrimaryKeyOf, type ProjectionValue, type QueryOptions, type RefsOptions, SelectableOf, type SelfProxy, type SelfRef, type SourceProxy, type SourceRef, type VersionedHistoryOptions, type VersionedLatestOptions, aggregate, belongsTo, binary, boolean, cdcProjected, count, datetime, defineFieldGroup, embedded, field, graphddb, hasMany, hasOne, list, literal, maintainedFrom, map, max, model, number, numberSet, refs, string, stringSet, ttl, whenMember, withFields };
package/dist/index.js CHANGED
@@ -3,29 +3,70 @@ import {
3
3
  mutate,
4
4
  publishCommand,
5
5
  publishQuery
6
- } from "./chunk-T44OB5GU.js";
6
+ } from "./chunk-HLFNCKFV.js";
7
7
  import {
8
8
  entityWrites,
9
9
  getEntityWrites,
10
10
  identity,
11
11
  preview
12
- } from "./chunk-HNY2EJPV.js";
12
+ } from "./chunk-7OCXY4R6.js";
13
13
  import {
14
14
  when
15
- } from "./chunk-L4QRCHRQ.js";
15
+ } from "./chunk-WOFRHRXY.js";
16
16
  import {
17
17
  ClientManager,
18
+ GRAPHDDB_CATALOG,
18
19
  MetadataRegistry,
20
+ assertComponentsInCatalog,
19
21
  cond,
22
+ deriveContractEffect,
20
23
  gsi,
21
24
  param
22
- } from "./chunk-L2NEDS7U.js";
25
+ } from "./chunk-GWWRXIHF.js";
23
26
  import {
24
27
  TableMapping,
25
28
  k,
26
29
  key
27
30
  } from "./chunk-XTWXMOHD.js";
28
31
 
32
+ // src/define/behaviors.ts
33
+ import {
34
+ catalogComponents,
35
+ compileBehaviors
36
+ } from "behavior-contracts";
37
+ var boundComponents;
38
+ function behaviorComponents() {
39
+ if (boundComponents === void 0) {
40
+ boundComponents = catalogComponents(GRAPHDDB_CATALOG);
41
+ }
42
+ return boundComponents;
43
+ }
44
+ function publishBehaviors(cls, options = {}) {
45
+ const ir = compileBehaviors(cls, {
46
+ ...options.inputPorts !== void 0 ? { inputPorts: options.inputPorts } : {},
47
+ ...options.concurrency !== void 0 ? { concurrency: options.concurrency } : {}
48
+ });
49
+ assertComponentsInCatalog(ir.components);
50
+ const methods = {};
51
+ for (const component of ir.components) {
52
+ methods[component.name] = {
53
+ name: component.name,
54
+ effect: deriveContractEffect(component),
55
+ component
56
+ };
57
+ }
58
+ return Object.freeze({
59
+ kind: "behaviors",
60
+ className: cls.name ?? "<anonymous>",
61
+ ir,
62
+ components: ir.components,
63
+ methods: Object.freeze(methods)
64
+ });
65
+ }
66
+ function isBehaviorModelContract(value) {
67
+ return typeof value === "object" && value !== null && value.kind === "behaviors" && Array.isArray(value.components);
68
+ }
69
+
29
70
  // src/config/index.ts
30
71
  var config = {
31
72
  /**
@@ -123,6 +164,9 @@ function drainFields() {
123
164
  pendingFields = [];
124
165
  return result;
125
166
  }
167
+ function peekPendingFieldNames() {
168
+ return pendingFields.map((f) => f.propertyName);
169
+ }
126
170
  function drainEmbedded() {
127
171
  const result = pendingEmbedded;
128
172
  pendingEmbedded = [];
@@ -267,6 +311,71 @@ function embedded(modelFactory) {
267
311
  };
268
312
  }
269
313
 
314
+ // src/decorators/field-group.ts
315
+ function def(dynamoType, options, literals) {
316
+ const d = { dynamoType };
317
+ if (options !== void 0 && Object.keys(options).length > 0) d.options = options;
318
+ if (literals !== void 0) d.literals = literals;
319
+ return d;
320
+ }
321
+ var factories = {
322
+ string: (options) => def("S", options),
323
+ number: (options) => def("N", options),
324
+ boolean: (options) => def("BOOL", options),
325
+ datetime: (options) => def("S", { format: "datetime", ...options }),
326
+ binary: (options) => def("B", options),
327
+ stringSet: (options) => def("SS", options),
328
+ numberSet: (options) => def("NS", options),
329
+ list: (options) => def("L", options),
330
+ map: (options) => def("M", options),
331
+ literal: (...values) => {
332
+ if (values.length === 0) {
333
+ throw new Error(
334
+ "defineFieldGroup: $.literal(...) requires at least one allowed value."
335
+ );
336
+ }
337
+ return def("S", void 0, [...values]);
338
+ }
339
+ };
340
+ function defineFieldGroup(build) {
341
+ const fields = build(factories);
342
+ if (Object.keys(fields).length === 0) {
343
+ throw new Error("defineFieldGroup: a field group must declare at least one field.");
344
+ }
345
+ return { __isFieldGroup: true, fields };
346
+ }
347
+ function withFields(...groups) {
348
+ if (groups.length === 0) {
349
+ throw new Error("@withFields requires at least one field group.");
350
+ }
351
+ for (const g of groups) {
352
+ if (g === null || typeof g !== "object" || g.__isFieldGroup !== true) {
353
+ throw new Error(
354
+ "@withFields: every argument must be a `defineFieldGroup(...)` result."
355
+ );
356
+ }
357
+ }
358
+ return function(_target, context) {
359
+ const seen = new Set(peekPendingFieldNames());
360
+ for (const group of groups) {
361
+ for (const [propertyName, d] of Object.entries(group.fields)) {
362
+ if (seen.has(propertyName)) {
363
+ throw new Error(
364
+ `@withFields on '${String(context.name)}': field '${propertyName}' is declared more than once (a group field collides with a body-declared field or another group). Each property may be declared exactly once.`
365
+ );
366
+ }
367
+ seen.add(propertyName);
368
+ collectField({
369
+ propertyName,
370
+ dynamoType: d.dynamoType,
371
+ ...d.options !== void 0 ? { options: d.options } : {},
372
+ ...d.literals !== void 0 ? { literals: d.literals } : {}
373
+ });
374
+ }
375
+ }
376
+ };
377
+ }
378
+
270
379
  // src/decorators/maintained-symbolic.ts
271
380
  var SOURCE_REF = /* @__PURE__ */ Symbol("graphddb:maintainedSourceRef");
272
381
  var SELF_REF = /* @__PURE__ */ Symbol("graphddb:maintainedSelfRef");
@@ -620,6 +729,9 @@ function cdcProjected() {
620
729
  var graphddb = {
621
730
  publishQuery,
622
731
  publishCommand,
732
+ // #297 SP3: the shared-authoring verb (SemanticBehavior class → behavior
733
+ // contract; effect derived from the graph, components registered additively).
734
+ publishBehaviors,
623
735
  get query() {
624
736
  return DDBModel.query;
625
737
  },
@@ -647,6 +759,7 @@ var graphddb = {
647
759
  export {
648
760
  DDBModel,
649
761
  aggregate,
762
+ behaviorComponents,
650
763
  belongsTo,
651
764
  binary,
652
765
  boolean,
@@ -654,6 +767,7 @@ export {
654
767
  cond,
655
768
  count,
656
769
  datetime,
770
+ defineFieldGroup,
657
771
  embedded,
658
772
  entityWrites,
659
773
  field,
@@ -663,6 +777,7 @@ export {
663
777
  hasMany,
664
778
  hasOne,
665
779
  identity,
780
+ isBehaviorModelContract,
666
781
  k,
667
782
  key,
668
783
  list,
@@ -681,5 +796,6 @@ export {
681
796
  stringSet,
682
797
  ttl,
683
798
  when,
684
- whenMember
799
+ whenMember,
800
+ withFields
685
801
  };
@@ -1,8 +1,9 @@
1
- import { P as PreparedWriteOpSpec, a as PreparedPlanDocument } from '../prepared-artifact-HFealr1q.js';
2
- export { b as PreparedBindSpec, c as PreparedPlanSpec, d as PreparedReadRouteSpec } from '../prepared-artifact-HFealr1q.js';
3
- import { M as Manifest } from '../types-2PMXEn5x.js';
4
- import { M as ModelStatic, D as DDBModel, S as Slot, P as PreparedWriteExecOptions, C as CommandReturn, a as ParallelOpResult, b as PreparedBody, c as PreparedStatement } from '../key-DZtjAQDh.js';
5
- import '../types-DW__-Icc.js';
1
+ import { P as PreparedWriteOpSpec, a as PreparedPlanDocument } from '../prepared-artifact-CwH5ezFq.js';
2
+ export { b as PreparedBindSpec, c as PreparedPlanSpec, d as PreparedReadRouteSpec } from '../prepared-artifact-CwH5ezFq.js';
3
+ import { M as Manifest } from '../types-CgXS-4Ox.js';
4
+ import { M as ModelStatic, D as DDBModel, S as Slot, P as PreparedWriteExecOptions, C as CommandReturn, a as ParallelOpResult, b as PreparedBody, c as PreparedStatement } from '../key-CWytoEaE.js';
5
+ import '../types-nk5okD7d.js';
6
+ import 'behavior-contracts';
6
7
 
7
8
  /**
8
9
  * Static prepared-plan LOADER (issue #208) — the runtime half of the AOT
@@ -2,26 +2,26 @@ import {
2
2
  PREPARED_FORMAT_VERSION,
3
3
  buildManifestEntity,
4
4
  entityFingerprint
5
- } from "../chunk-S2NI4PBW.js";
5
+ } from "../chunk-PHXUFAY2.js";
6
6
  import {
7
7
  LOGICAL_EFFECT_CATEGORIES,
8
8
  PreparedReadStatement,
9
9
  executeLogicalParallelWrites,
10
10
  executeLogicalWriteOps,
11
11
  serializeEffectParams
12
- } from "../chunk-T44OB5GU.js";
13
- import "../chunk-HNY2EJPV.js";
12
+ } from "../chunk-HLFNCKFV.js";
13
+ import "../chunk-7OCXY4R6.js";
14
14
  import {
15
15
  MARKER_ROW_ENTITY,
16
- SPEC_VERSION,
16
+ SPEC_VERSION_KEY_EXPR,
17
17
  SPEC_VERSION_SUPPORTED
18
- } from "../chunk-L4QRCHRQ.js";
18
+ } from "../chunk-WOFRHRXY.js";
19
19
  import {
20
20
  MetadataRegistry,
21
21
  buildConditionExpression,
22
22
  execItemKeySignature,
23
23
  resolveConditionTree
24
- } from "../chunk-L2NEDS7U.js";
24
+ } from "../chunk-GWWRXIHF.js";
25
25
  import {
26
26
  TableMapping
27
27
  } from "../chunk-XTWXMOHD.js";
@@ -489,7 +489,7 @@ function bindEntities(plan, planId) {
489
489
  entities[name] = entity;
490
490
  tables[metadata.tableName] = { physicalName: TableMapping.resolve(metadata.tableName) };
491
491
  }
492
- return { classes, manifest: { version: SPEC_VERSION, tables, entities } };
492
+ return { classes, manifest: { version: SPEC_VERSION_KEY_EXPR, tables, entities } };
493
493
  }
494
494
  function renderSpecItem(transaction, index, manifest, params) {
495
495
  const single = {
@@ -1,4 +1,4 @@
1
- import { a1 as Param, E as ExpressionSpec, a2 as ParamKind, T as TransactionSpec, X as TransactionItemSpec } from './types-2PMXEn5x.js';
1
+ import { a3 as Param, E as ExpressionSpec, a4 as ParamKind, T as TransactionSpec, Y as TransactionItemSpec } from './types-CgXS-4Ox.js';
2
2
 
3
3
  /**
4
4
  * CDC emulator types (issue #72). These types are **cdc-module-owned**; core
@@ -725,6 +725,22 @@ interface DeleteInput {
725
725
  * with `maxAttempts: 1` so the library is the single source of truth for retry
726
726
  * (documented at `graphddb.config.client` and in the README). The library does NOT
727
727
  * silently mutate a user-provided client.
728
+ *
729
+ * NOTE on the SCP Error Policy Kind (issue #292 / G9, bc `scp-error.md`):
730
+ * everything in this module is **Tuning** (max attempts / delay / backoff /
731
+ * jitter / `isRetryable`) — Runtime-owned, IR-external, Conformance-external.
732
+ * The op-level Policy **Kind** (`fail`/`retry`/`continue`) is a separate,
733
+ * IR-carried control-flow intent interpreted ONLY by behavior-contracts'
734
+ * `runPlan` (graphddb adds no Kind interpretation of its own; the runtimes
735
+ * convert a DynamoDB failure to the shared `ExecOutcome {error}` at the
736
+ * handler boundary and let `runPlan` decide). No double-retry arises today:
737
+ * this executor layer belongs to the TS live executor, which does not run
738
+ * through `runBehavior`, and the 4 spec-consumer runtimes' handlers issue SDK
739
+ * calls without a Kind-driven retry loop (bc's reference `runPlan` treats a
740
+ * persistent `retry`-Kind failure as exhausted, converging on the `fail`
741
+ * propagation). If a runtime later implements a real `retry`-Kind loop, this
742
+ * Tuning (and the SDK's own `maxAttempts`) must be treated as the INNER
743
+ * mechanism of a single logical attempt — never stacked as a second policy.
728
744
  */
729
745
  /** The single-op kind a retry is wrapping — surfaced to {@link RetryPolicy.onRetry}. */
730
746
  type RetryOperationKind = 'get' | 'query' | 'batchGet' | 'put' | 'update' | 'delete' | 'batchWrite' | 'transactWrite';
@@ -1637,6 +1653,13 @@ interface OperationDefinition<T extends DDBModel, Op extends OperationKind, K, S
1637
1653
  readonly select: S;
1638
1654
  /** The parameterized changes for `update`; `undefined` otherwise. */
1639
1655
  readonly changes: Ch;
1656
+ /**
1657
+ * The parameterized atomic-`ADD` record for a standalone `update` (issue #301):
1658
+ * field name → a `param.number()` caller-delta leaf. Distinct from {@link changes}
1659
+ * (a SET) — each entry serializes to `CommandSpec.add` (`ADD #field :delta`).
1660
+ * `undefined` unless the command declared `add`.
1661
+ */
1662
+ readonly add?: Readonly<Record<string, unknown>>;
1640
1663
  /**
1641
1664
  * The declarative write condition for `put` / `update` / `delete`; `undefined`
1642
1665
  * when no condition is attached or for reads (issue #46).
@@ -2496,6 +2519,16 @@ interface MutationFragment {
2496
2519
  readonly intent: MutationIntent;
2497
2520
  readonly entity: EntityRef;
2498
2521
  readonly input: FragmentInput;
2522
+ /**
2523
+ * Atomic-**`ADD`** field binding on the self row (issue #301): model field name →
2524
+ * a {@link MutationInputRef} (`$.field`, a `param.number()` caller delta). Distinct
2525
+ * from {@link input} (a `SET`): the compiler ({@link
2526
+ * import('../spec/mutation-command.js').compileFragment}) lowers each leaf onto the
2527
+ * command spec's `add` record (an `ADD #field :delta`), so the runtime issues an
2528
+ * atomic increment (no read, concurrency-safe). Absent when the fragment declares no
2529
+ * `add`. Present only on an `update` fragment.
2530
+ */
2531
+ readonly add?: FragmentInput;
2499
2532
  readonly use?: EntityWritesDefinition;
2500
2533
  /**
2501
2534
  * The declarative write **condition** (gate) authored on the descriptor (issue
@@ -2565,6 +2598,16 @@ interface WriteDescriptor {
2565
2598
  readonly key: DescriptorBinding;
2566
2599
  /** The non-key field binding; optional (a `remove` writes no fields). */
2567
2600
  readonly input?: DescriptorBinding;
2601
+ /**
2602
+ * Optional atomic-**`ADD`** binding on the self row (issue #301) — `{ field: $.field }`
2603
+ * whose leaves are `$.field` input refs bound from `param.number()` caller deltas.
2604
+ * Distinct from `input` (a `SET`): an `add` field lowers to a `TransactionValueLeaf`
2605
+ * on the compiled command spec's `add` record (an `ADD #field :delta`), NOT to a
2606
+ * `changes` SET. Legal only on an `update` fragment. When present alongside a
2607
+ * `condition`, the runtime applies the ADD unconditionally (two-stage: ADD+SET under
2608
+ * the condition, fallback ADD-only on ConditionalCheckFailed).
2609
+ */
2610
+ readonly add?: DescriptorBinding;
2568
2611
  /**
2569
2612
  * Optional declarative write gate — the #114-A condition subset (issue #242,
2570
2613
  * Phase 2). Compiled, serialized, and evaluated at runtime; see the interface doc.
@@ -3022,6 +3065,13 @@ interface ContractMethodOp<Op extends OperationKind = OperationKind> {
3022
3065
  readonly select?: Readonly<Record<string, unknown>>;
3023
3066
  /** The changes structure for `update`; `undefined` otherwise. */
3024
3067
  readonly changes?: Readonly<Record<string, unknown>>;
3068
+ /**
3069
+ * The atomic-`ADD` structure for `update` (issue #301): model field name → a
3070
+ * `param.number()` caller-delta leaf (a `{token}` template). Distinct from
3071
+ * {@link changes} (a SET): each entry lowers to `ADD #field :delta`. Present only on
3072
+ * a standalone `update` command that declared `add`; `undefined` otherwise.
3073
+ */
3074
+ readonly add?: Readonly<Record<string, unknown>>;
3025
3075
  /** The item structure for `put`; `undefined` otherwise. */
3026
3076
  readonly item?: Readonly<Record<string, unknown>>;
3027
3077
  /** The optional declarative write condition for writes. */
@@ -3364,9 +3414,10 @@ type ParamLeaf<V> = [V] extends [StringLike] ? V | string | Param<string> : V |
3364
3414
  * covariance still admits a single-member subset placeholder, while a value outside
3365
3415
  * the union / a wider placeholder / a wrong scalar kind stays a type error.
3366
3416
  */
3367
- type Parameterize<K> = [Exclude<K, Nullish>] extends [ScalarLeaf] ? [Exclude<K, Nullish>] extends [never] ? K : ParamLeaf<Exclude<K, Nullish>> | Extract<K, Nullish> : K extends object ? {
3417
+ type Parameterize<K> = [Exclude<K, Nullish>] extends [ScalarLeaf] ? [Exclude<K, Nullish>] extends [never] ? K : ParamLeaf<Exclude<K, Nullish>> | Extract<K, Nullish> : K extends object ? // An object-typed leaf (a `@map` / `@embedded` field) additionally accepts a
3418
+ {
3368
3419
  [P in keyof K]: Parameterize<K[P]>;
3369
- } : K;
3420
+ } | Param<K> : K;
3370
3421
  /** Union of all keys across every member of a (possibly union) `Real` type. */
3371
3422
  type KnownKeyOf<Real> = Real extends object ? keyof Real : never;
3372
3423
  /** The value type of key `P` across the members of a (possibly union) `Real`, non-nullable. */
@@ -3610,8 +3661,26 @@ interface MutateAuthoringOptions {
3610
3661
  declare function mutate(body: TransactionDefinition, options?: MutateAuthoringOptions): TransactionDefinition;
3611
3662
  declare function mutate(body: CommandPlan, options?: MutateAuthoringOptions): PublicComposeDescriptor;
3612
3663
  declare function mutate(body: PublicCompositeDescriptor, options?: MutateAuthoringOptions): PublicCompositeDescriptor;
3613
- /** The per-call execution mode of a command method (#101): replaces `batch`. */
3614
- type CommandMode = 'transaction' | 'parallel';
3664
+ /**
3665
+ * The per-call execution mode of a command method (#101): replaces `batch`.
3666
+ *
3667
+ * `'batchWrite'` (issue #298) is the DynamoDB `BatchWriteItem` bulk form —
3668
+ * non-atomic, condition-free, any array size chunked ≤25 per request with
3669
+ * `UnprocessedItems` retry, 1×WCU — the semantics the runtime has always
3670
+ * implemented ({@link import('../runtime/command-runtime.js')}'s
3671
+ * `executeBatchWrite` machinery); #298 makes it declarable/reachable from the
3672
+ * public contract surface. It is only legal for an **unconditional** put /
3673
+ * delete (`upsert` / condition-free `remove`) — `BatchWriteItem` physically
3674
+ * carries no `ConditionExpression` and no `Update`, so a conditional / create
3675
+ * (implicit `notExists` guard) / update / derived-effect method loud-rejects it
3676
+ * at build time.
3677
+ *
3678
+ * **0.9.0 default flip ("bulk is the base case")**: an eligible method that
3679
+ * declares NO mode now defaults to `'batchWrite'` (previously `'transaction'`).
3680
+ * Declare `mode: 'transaction'` explicitly to keep the atomic
3681
+ * `TransactWriteItems` bulk form.
3682
+ */
3683
+ type CommandMode = 'transaction' | 'parallel' | 'batchWrite';
3615
3684
  /** A public **read** descriptor (issue #101): `{ query | list: Model, key, select, options? }`. */
3616
3685
  interface PublicReadDescriptor {
3617
3686
  readonly query?: ModelRef;
@@ -3644,6 +3713,25 @@ interface PublicWriteDescriptor {
3644
3713
  readonly upsert?: ModelRef;
3645
3714
  readonly key: Readonly<Record<string, unknown>>;
3646
3715
  readonly input?: Readonly<Record<string, unknown>>;
3716
+ /**
3717
+ * Atomic **`ADD`** deltas on the SELF row (issue #301) — `{ field: param.number() }`
3718
+ * declaring an `ADD #field :delta` with a **caller-param** delta. Distinct from
3719
+ * `input` (a `SET` that overwrites): `add` is an atomic server-side increment that
3720
+ * needs no prior read and is concurrency-safe (the aggregate-tree `mark`'s
3721
+ * `version++`). Legal only on an `update` intent, with each value a
3722
+ * `param.number()`. An `add` field may NOT also appear in `input` (a field is either
3723
+ * SET or ADD, not both) nor be a primary-key field (a key cannot be incremented).
3724
+ *
3725
+ * **Two-stage semantics**: when an `update` carries BOTH `add` and a `condition`, the
3726
+ * `add` is UNCONDITIONAL (it must always apply — the mark's `version++` must always
3727
+ * happen) while `input` (SET) is gated by `condition`. Because DynamoDB rejects the
3728
+ * ENTIRE `UpdateItem` when the `ConditionExpression` fails, the RUNTIME performs a
3729
+ * transparent two-stage: `ADD + SET` under the condition (1 write, the common path);
3730
+ * on `ConditionalCheckFailedException`, a fallback `ADD`-only write (no SET, no
3731
+ * condition — the `version` still increments). read=0, retry=0. The author does NOT
3732
+ * write the fallback — it is derived from `add` + `condition` presence.
3733
+ */
3734
+ readonly add?: Readonly<Record<string, unknown>>;
3647
3735
  readonly condition?: Readonly<Record<string, unknown>> | RawCondition<any, any>;
3648
3736
  readonly result?: {
3649
3737
  readonly select?: Readonly<Record<string, boolean>>;
@@ -1,8 +1,9 @@
1
- import { L as Linter, a as LintRule } from '../linter-DQY7gUEk.js';
2
- export { b as LintResult } from '../linter-DQY7gUEk.js';
3
- import '../types-DW__-Icc.js';
4
- import '../key-DZtjAQDh.js';
5
- import '../types-2PMXEn5x.js';
1
+ import { L as Linter, a as LintRule } from '../linter-jEwmZotm.js';
2
+ export { b as LintResult } from '../linter-jEwmZotm.js';
3
+ import '../types-nk5okD7d.js';
4
+ import '../key-CWytoEaE.js';
5
+ import '../types-CgXS-4Ox.js';
6
+ import 'behavior-contracts';
6
7
 
7
8
  /**
8
9
  * Creates a Linter pre-loaded with rules safe for Entity registration.
@@ -1,4 +1,4 @@
1
- import { E as EntityMetadata } from './types-DW__-Icc.js';
1
+ import { E as EntityMetadata } from './types-nk5okD7d.js';
2
2
 
3
3
  declare class MetadataRegistry {
4
4
  private static store;
@@ -1,6 +1,6 @@
1
- import { E as EntityMetadata } from './types-DW__-Icc.js';
2
- import { C as ContractSpec, Q as QuerySpec, a as CommandSpec, T as TransactionSpec, b as ContextSpec, S as SPEC_VERSION, P as ParamSpec } from './types-2PMXEn5x.js';
3
- import { Q as QueryModelContract, k as QueryMethodSpec, l as CommandModelContract, m as CommandMethodSpec } from './key-DZtjAQDh.js';
1
+ import { E as EntityMetadata } from './types-nk5okD7d.js';
2
+ import { C as ContractSpec, Q as QuerySpec, a as CommandSpec, T as TransactionSpec, b as ContextSpec, S as SPEC_VERSION_KEY_EXPR, P as ParamSpec } from './types-CgXS-4Ox.js';
3
+ import { Q as QueryModelContract, k as QueryMethodSpec, l as CommandModelContract, m as CommandMethodSpec } from './key-CWytoEaE.js';
4
4
 
5
5
  /**
6
6
  * Contract-layer serialization (issue #59, CQRS Contract layer, Epic #57;
@@ -262,7 +262,7 @@ interface PreparedPlanSpec {
262
262
  interface PreparedPlanDocument {
263
263
  readonly formatVersion: typeof PREPARED_FORMAT_VERSION;
264
264
  /** The shared operation-IR version the plans were compiled against. */
265
- readonly specVersion: typeof SPEC_VERSION;
265
+ readonly specVersion: typeof SPEC_VERSION_KEY_EXPR;
266
266
  /** Plan id (the transform's stable call-site id) → compiled plan. */
267
267
  readonly plans: Readonly<Record<string, PreparedPlanSpec>>;
268
268
  }