graphddb 0.8.0 → 0.8.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.
@@ -1,6 +1,6 @@
1
- import { w as CdcEmulatorOptions, x as ChangeHandler, y as Unsubscribe, F as FaultSpec, z as ConcurrentRecomputeRef, v as ChangeEvent, A as EventLog, G as ReplayOptions, H as ShardId, I as MaintainTrigger, E as EffectPath, g as ProjectionMap, j as MembershipPredicate } from '../key-DR7_lpyk.js';
2
- export { J as BatchResult, K as CdcMode, L as ChangeBatch, N as ChangeEventName, O as ClockMode, V as StartingPosition, X as StreamViewType, Y as SubscribeHandler, Z as SubscribeHandlers, _ as buildSubscribeHandler } from '../key-DR7_lpyk.js';
3
- import { E as EntityMetadata } from '../types-BXLzIcQD.js';
1
+ import { w as CdcEmulatorOptions, x as ChangeHandler, y as Unsubscribe, F as FaultSpec, z as ConcurrentRecomputeRef, v as ChangeEvent, A as EventLog, G as ReplayOptions, H as ShardId, I as MaintainTrigger, E as EffectPath, g as ProjectionMap, j as MembershipPredicate } from '../key-DZtjAQDh.js';
2
+ export { J as BatchResult, K as CdcMode, L as ChangeBatch, N as ChangeEventName, O as ClockMode, V as StartingPosition, X as StreamViewType, Y as SubscribeHandler, Z as SubscribeHandlers, _ as buildSubscribeHandler } from '../key-DZtjAQDh.js';
3
+ import { E as EntityMetadata } from '../types-DW__-Icc.js';
4
4
  import '../types-2PMXEn5x.js';
5
5
 
6
6
  /**
@@ -2,7 +2,7 @@ import {
2
2
  PREPARED_FORMAT_VERSION,
3
3
  buildManifest,
4
4
  entityFingerprint
5
- } from "./chunk-LGHSZIEE.js";
5
+ } from "./chunk-S2NI4PBW.js";
6
6
  import {
7
7
  analyzeWriteRoute,
8
8
  assertBundleSerializable,
@@ -21,7 +21,7 @@ import {
21
21
  isContractKeyRef,
22
22
  isContractParamRef,
23
23
  isQueryModelContract
24
- } from "./chunk-I4LEJ4TF.js";
24
+ } from "./chunk-T44OB5GU.js";
25
25
  import {
26
26
  detectRelationFields,
27
27
  isInlineSnapshotSpec,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  evaluateKey
3
- } from "./chunk-I4LEJ4TF.js";
3
+ } from "./chunk-T44OB5GU.js";
4
4
  import {
5
5
  SPEC_VERSION
6
6
  } from "./chunk-L4QRCHRQ.js";
@@ -4669,8 +4669,11 @@ function mutate(body, options) {
4669
4669
  if (isCommandPlan(body)) {
4670
4670
  return mode !== void 0 ? { command: body, mode } : { command: body };
4671
4671
  }
4672
+ if (isPublicCompositeDescriptor(body)) {
4673
+ return mode !== void 0 ? { ...body, mode } : body;
4674
+ }
4672
4675
  throw new Error(
4673
- "mutate: the body must be a write body \u2014 a `defineScpTransaction(...)` / `defineTransaction(...)` transaction definition or a `mutation(...)` plan. `mutate` is the authoring twin of the runtime `mutate`; its only option is `{ mode }` (the sole home of atomicity)."
4676
+ "mutate: the body must be a write body \u2014 a PUBLIC composite descriptor (`{ writes: $ => ({ alias: descriptor, \u2026 }), input?, result? }`, the import-free multi-entity atomic form, #293), a `defineScpTransaction(...)` / `defineTransaction(...)` transaction definition, or a `mutation(...)` plan. `mutate` is the authoring twin of the runtime `mutate`; its only option is `{ mode }` (the sole home of atomicity)."
4674
4677
  );
4675
4678
  }
4676
4679
  function buildCommandContract(methods) {
@@ -4681,6 +4684,19 @@ function buildCommandContract(methods) {
4681
4684
  resolved[name] = finalizeDescriptorCommand(name, plan2, select, condition, mode, description);
4682
4685
  continue;
4683
4686
  }
4687
+ if (isPublicCompositeDescriptor(body)) {
4688
+ const plan2 = mutation(name, body.writes);
4689
+ const select = body.result !== void 0 && body.result.select !== void 0 ? body.result.select : void 0;
4690
+ resolved[name] = finalizeDescriptorCommand(
4691
+ name,
4692
+ plan2,
4693
+ select,
4694
+ void 0,
4695
+ body.mode ?? "transaction",
4696
+ body.description
4697
+ );
4698
+ continue;
4699
+ }
4684
4700
  if (isPublicComposeDescriptor(body)) {
4685
4701
  const select = body.result !== void 0 && body.result.select !== void 0 ? body.result.select : void 0;
4686
4702
  resolved[name] = finalizeDescriptorCommand(
@@ -4708,7 +4724,7 @@ function buildCommandContract(methods) {
4708
4724
  continue;
4709
4725
  }
4710
4726
  throw new Error(
4711
- `publishCommand: method '${name}' must be a write descriptor (\`{ create | update | remove: Model, key, input?, condition?, result?, mode? }\`), a composite \`{ command: mutation(...), input?, result?, mode? }\` descriptor, a bare \`mutation($ => ({ \u2026 }))\` plan, or an SCP native-syntax \`defineScpTransaction(...)\` multi-write body. The legacy closure form (\`(keys, params) => Model.putItem(...)\`) and the \`command(...).plan(...)\` builder have been removed (#101).`
4727
+ `publishCommand: method '${name}' must be a write descriptor (\`{ create | update | remove: Model, key, input?, condition?, result?, mode? }\`) for a single-entity write, or \u2014 for a multi-entity atomic (composite) write \u2014 a \`{ writes: $ => ({ alias: { create | update | remove: Model, key, input?, condition? }, \u2026 }), input?, result?, mode? }\` descriptor (the PUBLIC composite form; a multi-alias \`writes\` body is compiled internally into one atomic \`TransactWriteItems\`, needing no \`mutation(...)\` / \`defineScpTransaction(...)\` import). The legacy closure form (\`(keys, params) => Model.putItem(...)\`) and the \`command(...).plan(...)\` builder have been removed (#101).`
4712
4728
  );
4713
4729
  }
4714
4730
  return { __isContract: true, kind: "command", methods: resolved };
@@ -4801,6 +4817,9 @@ var WRITE_INTENT_KEYS = ["create", "update", "remove", "upsert"];
4801
4817
  function isPublicComposeDescriptor(value) {
4802
4818
  return typeof value === "object" && value !== null && isCommandPlan(value.command);
4803
4819
  }
4820
+ function isPublicCompositeDescriptor(value) {
4821
+ return typeof value === "object" && value !== null && typeof value.writes === "function";
4822
+ }
4804
4823
  function isPublicReadDescriptor(value) {
4805
4824
  return typeof value === "object" && value !== null && READ_INTENT_KEYS2.some((k) => value[k] !== void 0);
4806
4825
  }
package/dist/cli.js CHANGED
@@ -2,11 +2,11 @@
2
2
  import {
3
3
  buildBridgeBundle,
4
4
  buildOperations
5
- } from "./chunk-N4NWYNGZ.js";
5
+ } from "./chunk-LAT64YCZ.js";
6
6
  import {
7
7
  buildManifest
8
- } from "./chunk-LGHSZIEE.js";
9
- import "./chunk-I4LEJ4TF.js";
8
+ } from "./chunk-S2NI4PBW.js";
9
+ import "./chunk-T44OB5GU.js";
10
10
  import "./chunk-HNY2EJPV.js";
11
11
  import "./chunk-L4QRCHRQ.js";
12
12
  import {
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
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-DR7_lpyk.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-DR7_lpyk.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-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';
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-BXLzIcQD.js';
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
5
  export { $ as LiteralParam, a0 as NumberParam, a1 as Param, a2 as ParamKind, a3 as StringParam, a4 as param } from './types-2PMXEn5x.js';
6
6
 
7
7
  /**
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  mutate,
4
4
  publishCommand,
5
5
  publishQuery
6
- } from "./chunk-I4LEJ4TF.js";
6
+ } from "./chunk-T44OB5GU.js";
7
7
  import {
8
8
  entityWrites,
9
9
  getEntityWrites,
@@ -1,8 +1,8 @@
1
- import { P as PreparedWriteOpSpec, a as PreparedPlanDocument } from '../prepared-artifact-BpPgkXEo.js';
2
- export { b as PreparedBindSpec, c as PreparedPlanSpec, d as PreparedReadRouteSpec } from '../prepared-artifact-BpPgkXEo.js';
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
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-DR7_lpyk.js';
5
- import '../types-BXLzIcQD.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';
6
6
 
7
7
  /**
8
8
  * Static prepared-plan LOADER (issue #208) — the runtime half of the AOT
@@ -2,14 +2,14 @@ import {
2
2
  PREPARED_FORMAT_VERSION,
3
3
  buildManifestEntity,
4
4
  entityFingerprint
5
- } from "../chunk-LGHSZIEE.js";
5
+ } from "../chunk-S2NI4PBW.js";
6
6
  import {
7
7
  LOGICAL_EFFECT_CATEGORIES,
8
8
  PreparedReadStatement,
9
9
  executeLogicalParallelWrites,
10
10
  executeLogicalWriteOps,
11
11
  serializeEffectParams
12
- } from "../chunk-I4LEJ4TF.js";
12
+ } from "../chunk-T44OB5GU.js";
13
13
  import "../chunk-HNY2EJPV.js";
14
14
  import {
15
15
  MARKER_ROW_ENTITY,
@@ -2398,6 +2398,32 @@ interface MutationEntityRef {
2398
2398
  /** The source path, e.g. `$.entity[0].postId` (documentary; surfaces in errors). */
2399
2399
  readonly path: string;
2400
2400
  }
2401
+ /**
2402
+ * The placeholder proxy handed to a mutation body as `$`. In the descriptor-map
2403
+ * authoring form (issue #108) a top-level read `$.<x>` is **ambiguous** until used:
2404
+ *
2405
+ * - `$.role` placed at a `key` / `input` leaf is an **input-field reference**
2406
+ * (a {@link MutationInputRef}, token `{role}`) — bound from the command input;
2407
+ * - `$.membership.role` is a **cross-fragment reference** — the `role` field
2408
+ * written by the fragment named `membership` (an alias). It mints an
2409
+ * {@link AliasFieldRef}, which the {@link mutation} assembler rewrites into the
2410
+ * legacy `$.entity[<index>].<field>` string the compiler resolves (the alias →
2411
+ * declaration-order index is known once every alias key is seen).
2412
+ *
2413
+ * So each top-level read returns a value that is **both** a faithful
2414
+ * {@link MutationInputRef} (when used directly as a leaf) and supports one further
2415
+ * `.<field>` access (when used as an alias root). Any other access / coercion
2416
+ * throws — the binding stays declarative (only a direct `$.field` / `$.alias.field`
2417
+ * reference or a literal is representable, never a transform).
2418
+ *
2419
+ * **Input Port 統一 (issue #264)**: this proxy is one of the three authoring
2420
+ * spellings of the SAME Input Port reference model — see the unification note
2421
+ * on {@link import('./transaction.js').ParamProxy} (`defineTransaction`'s
2422
+ * `p.*` proxy is the model; `{<field>}` templates and `{ref:["input","<field>"]}`
2423
+ * expression refs are the two serialized mirrors of one declared-input
2424
+ * namespace). Behavior here is unchanged in 0.7.x (interop kept).
2425
+ */
2426
+ type MutationInputProxy = Record<string, MutationInputRef>;
2401
2427
  declare const FRAGMENT_BRAND: unique symbol;
2402
2428
  /**
2403
2429
  * The per-field input binding of a fragment: model field name → a faithful
@@ -2406,6 +2432,14 @@ declare const FRAGMENT_BRAND: unique symbol;
2406
2432
  * field they key, never a transform.
2407
2433
  */
2408
2434
  type FragmentInput = Readonly<Record<string, MutationInputRef | string | number | boolean | Date | null>>;
2435
+ /**
2436
+ * A descriptor `key` / `input` value as authored in the descriptor-map form
2437
+ * (issue #108): a `$.field` input reference, a `$.alias.field` cross-fragment
2438
+ * reference (an opaque alias-field ref the assembler rewrites), or a literal.
2439
+ * Structurally `unknown` at the leaf because the alias-field ref is internal; the
2440
+ * assembler validates every leaf via {@link assertFaithfulInput}.
2441
+ */
2442
+ type DescriptorBinding = Readonly<Record<string, unknown>>;
2409
2443
  /** A concrete scalar admissible at a condition operand leaf. */
2410
2444
  type ConditionScalar = MutationInputRef | string | number | boolean | Date | null;
2411
2445
  /** A declarative operator object on one condition field (the #114-A subset). */
@@ -2488,6 +2522,68 @@ interface MutationFragment {
2488
2522
  * `m.create(() => Model, …)` recorder required.
2489
2523
  */
2490
2524
  type ModelRef = ModelStatic<DDBModel> | (new (...args: unknown[]) => unknown) | (() => ModelStatic<DDBModel> | (new (...args: unknown[]) => unknown));
2525
+ /**
2526
+ * One **write descriptor** in the descriptor-map authoring form (issue #108) —
2527
+ * the declarative twin of an in-process `DDBModel.mutate` entry (#101). The intent
2528
+ * key (`create` / `update` / `remove`) is the **discriminator** and carries the
2529
+ * target {@link ModelRef} (direct or thunk). The remaining fields are declarative
2530
+ * bindings (`$.field` references or literals):
2531
+ *
2532
+ * - `key` — the target row's primary-key binding (identifies the row);
2533
+ * - `input` — the non-key field binding (the body / changed fields). Optional for
2534
+ * a `remove` (a delete writes no fields);
2535
+ * - `condition` — an optional declarative write gate (issue #242, Phase 2). It is
2536
+ * the #114-A condition subset (bare equality, operator objects `{ gt }` / `{ in }`
2537
+ * / `{ between }` / …, and `and` / `or` / `not` groups) whose operand leaves are
2538
+ * `$.field` input references or literals. The declaration-DSL compiler **consumes**
2539
+ * it: each `$.field` leaf becomes a {@link import('./contract.js').ContractParamRef}
2540
+ * and the tree is attached to the compiled base op's `condition`, so it is
2541
+ * serialized (into the operation spec) and evaluated at runtime exactly as a #101
2542
+ * public write descriptor's condition is (single-op, in-process
2543
+ * transaction, and the Python bridge). A conditioned `update` / `remove` therefore
2544
+ * fails a CAS-style write when the gate does not hold, rather than silently
2545
+ * committing;
2546
+ * - `result` — an optional read-back projection `{ select, options? }` (likewise
2547
+ * accepted for authoring parity; the public read-back projection of a
2548
+ * `command(...).plan(...)` method is still declared on `command({ select })`).
2549
+ *
2550
+ * Exactly one intent key must be present.
2551
+ */
2552
+ interface WriteDescriptor {
2553
+ readonly create?: ModelRef;
2554
+ readonly update?: ModelRef;
2555
+ readonly remove?: ModelRef;
2556
+ /**
2557
+ * `upsert` — a `PutItem` with **no** `attribute_not_exists(PK)` guard (an
2558
+ * unconditional overwrite; the semantics of the removed `definePut`). Unlike
2559
+ * `create` (which always guards against clobbering an existing row), an `upsert`
2560
+ * writes whether or not the row exists; an author-supplied `condition` is honored
2561
+ * verbatim (never ANDed with a `notExists` guard).
2562
+ */
2563
+ readonly upsert?: ModelRef;
2564
+ /** The target row's primary-key binding (`{ field: $.field | literal }`). */
2565
+ readonly key: DescriptorBinding;
2566
+ /** The non-key field binding; optional (a `remove` writes no fields). */
2567
+ readonly input?: DescriptorBinding;
2568
+ /**
2569
+ * Optional declarative write gate — the #114-A condition subset (issue #242,
2570
+ * Phase 2). Compiled, serialized, and evaluated at runtime; see the interface doc.
2571
+ */
2572
+ readonly condition?: FragmentCondition;
2573
+ /** Optional read-back projection `{ select, options? }` (accepted for #101 parity). */
2574
+ readonly result?: {
2575
+ readonly select?: unknown;
2576
+ readonly options?: unknown;
2577
+ };
2578
+ /**
2579
+ * Optional custom save contract to adopt (the **whole** {@link
2580
+ * EntityWritesDefinition}). Omit to default to the target model's own `writes`.
2581
+ * Per the `use:` rule this is never a single lifecycle — the intent picks it.
2582
+ */
2583
+ readonly use?: EntityWritesDefinition;
2584
+ }
2585
+ /** The descriptor map a {@link mutation} body returns: alias → {@link WriteDescriptor}. */
2586
+ type MutationDescriptorMap = Record<string, WriteDescriptor>;
2491
2587
  declare const COMMAND_PLAN_BRAND: unique symbol;
2492
2588
  /**
2493
2589
  * The recorded **CommandPlan** — a mutation's IR (proposal §3). It carries the
@@ -2503,6 +2599,14 @@ interface CommandPlan {
2503
2599
  /** The declared write fragments, in declaration order (the mutation IR). */
2504
2600
  readonly fragments: readonly MutationFragment[];
2505
2601
  }
2602
+ /**
2603
+ * The body of a {@link mutation} in the descriptor-map authoring form (issue #108):
2604
+ * `$ => ({ alias: descriptor, … })`. It receives the placeholder proxy `$` and
2605
+ * returns an **alias → {@link WriteDescriptor}** map. The map's **insertion order**
2606
+ * is the fragments' declaration (execution) order, and each alias is the name a
2607
+ * later descriptor's `$.alias.field` cross-fragment reference resolves against.
2608
+ */
2609
+ type MutationBody = ($: MutationInputProxy) => MutationDescriptorMap;
2506
2610
 
2507
2611
  /**
2508
2612
  * Declarative transaction definition DSL (issue #46, Python-bridge Phase 4).
@@ -3391,7 +3495,13 @@ type WriteDescriptorResultOf<D> = D extends {
3391
3495
  readonly select: infer S;
3392
3496
  };
3393
3497
  } ? S extends Record<string, unknown> ? Readonly<Record<Extract<keyof S, string>, unknown>> | null : void : void;
3394
- /** A write descriptor's contract Key (from its `key` record). */
3498
+ /**
3499
+ * A write descriptor's contract Key (from its `key` record). A #293 PUBLIC composite
3500
+ * descriptor (`{ writes, input }`) has no single top-level `key` — it is a
3501
+ * multi-entity write with no single primary Key — so it falls through to `unknown`,
3502
+ * exactly as a bare `mutation()` plan / SCP transaction body does; its public params
3503
+ * are still precise via {@link DescriptorParamsOf} (the separate `input` schema).
3504
+ */
3395
3505
  type WriteDescriptorKeyOf<D> = D extends {
3396
3506
  readonly key: infer K;
3397
3507
  } ? KeyOf<K> : unknown;
@@ -3414,7 +3524,7 @@ type ResolvedReadDescriptors<M extends ReadDescriptorMap> = QueryModelContract<M
3414
3524
  * {@link CommandPlan}, or an SCP native-syntax `defineScpTransaction(...)`
3415
3525
  * multi-write body ({@link TransactionDefinition}, Phase 4 S1 / issue #278).
3416
3526
  */
3417
- type WriteDescriptorMap = Record<string, PublicWriteDescriptor | PublicComposeDescriptor | CommandPlan | TransactionDefinition>;
3527
+ type WriteDescriptorMap = Record<string, PublicWriteDescriptor | PublicCompositeDescriptor | PublicComposeDescriptor | CommandPlan | TransactionDefinition>;
3418
3528
  /** Resolve a write-descriptor map to its {@link CommandModelContract}. */
3419
3529
  type ResolvedWriteDescriptors<M extends WriteDescriptorMap> = CommandModelContract<M[keyof M] extends infer D ? WriteDescriptorKeyOf<D> : unknown, {
3420
3530
  readonly [K in keyof M]: CommandMethodSpec<WriteDescriptorKeyOf<M[K]>, DescriptorParamsOf<M[K]>, WriteDescriptorResultOf<M[K]>>;
@@ -3493,9 +3603,13 @@ interface MutateAuthoringOptions {
3493
3603
  * `publishCommand`'s SCP-body branch — declare a mode).
3494
3604
  * - `mutate(plan, { mode })` → a {@link PublicComposeDescriptor} (`{ command,
3495
3605
  * mode }`), the closure-free composite form.
3606
+ * - `mutate(compositeDescriptor, { mode })` → the same PUBLIC composite descriptor
3607
+ * (`{ writes, input?, result? }`, issue #293) with `mode` stamped — the
3608
+ * import-free composite form usable directly as a `publishCommand` method value.
3496
3609
  */
3497
3610
  declare function mutate(body: TransactionDefinition, options?: MutateAuthoringOptions): TransactionDefinition;
3498
3611
  declare function mutate(body: CommandPlan, options?: MutateAuthoringOptions): PublicComposeDescriptor;
3612
+ declare function mutate(body: PublicCompositeDescriptor, options?: MutateAuthoringOptions): PublicCompositeDescriptor;
3499
3613
  /** The per-call execution mode of a command method (#101): replaces `batch`. */
3500
3614
  type CommandMode = 'transaction' | 'parallel';
3501
3615
  /** A public **read** descriptor (issue #101): `{ query | list: Model, key, select, options? }`. */
@@ -3567,6 +3681,54 @@ interface PublicComposeDescriptor {
3567
3681
  */
3568
3682
  readonly description?: string;
3569
3683
  }
3684
+ /**
3685
+ * A public **composite mutation** descriptor (issue #293) — the PUBLIC, closure-free
3686
+ * authoring form for a multi-entity atomic (or non-atomic) write, needing **no**
3687
+ * `mutation(...)` / `defineScpTransaction(...)` import. It carries a `writes` body
3688
+ * (`$ => ({ alias: { create|update|remove|upsert: Model, key, input?, condition? } })`
3689
+ * — the same mutation body the internal `mutation()` builder consumes), a **separate**
3690
+ * `input` schema (`{ field: param.* }`) that names the public input fields the `$`
3691
+ * proxy in the body references, an optional read-back `result.select`, and an optional
3692
+ * execution `mode`.
3693
+ *
3694
+ * `publishCommand` compiles the `writes` body internally via the retained (internal)
3695
+ * `mutation()` builder, then routes the resulting {@link CommandPlan} through the SAME
3696
+ * `buildPlannedCommandMethod` path a bare `mutation()` plan / a
3697
+ * {@link PublicComposeDescriptor} takes. A **single-alias** `writes` body is one write;
3698
+ * a **multi-alias** body is an atomic composite (promoted to one
3699
+ * `TransactWriteItems` by DynamoDB necessity — the existing promotion path), so its
3700
+ * wire is byte-identical to authoring the same composite via the internal `mutation()`.
3701
+ *
3702
+ * The `input` schema is the public param declaration: its `param.*` leaves drive the
3703
+ * generated typed binding's input struct (and the TS-level `params` type). It exists
3704
+ * separately from the body (the owner-final #293 decision) so the mutation body stays a
3705
+ * pure write declaration — options (input schema / read-back) are specified alongside it,
3706
+ * not baked into it.
3707
+ */
3708
+ interface PublicCompositeDescriptor {
3709
+ /**
3710
+ * The mutation body: `$ => ({ alias: writeDescriptor, … })`. Single alias ⇒ one
3711
+ * write; multiple aliases ⇒ an atomic composite. The `$` proxy's fields are the
3712
+ * public input fields declared in {@link input}.
3713
+ */
3714
+ readonly writes: MutationBody;
3715
+ /**
3716
+ * The public input schema: `{ field: param.* }`. Names (and types) the input fields
3717
+ * the `writes` body's `$` proxy references. Optional (a body may bind only literals),
3718
+ * though a composite normally declares at least the key params.
3719
+ */
3720
+ readonly input?: Readonly<Record<string, unknown>>;
3721
+ readonly result?: {
3722
+ readonly select?: Readonly<Record<string, boolean>>;
3723
+ readonly options?: unknown;
3724
+ };
3725
+ readonly mode?: CommandMode;
3726
+ /**
3727
+ * Optional human-readable description of the composite write use case (issue #154).
3728
+ * Pure documentation — propagated to the serialized command method spec.
3729
+ */
3730
+ readonly description?: string;
3731
+ }
3570
3732
 
3571
3733
  /**
3572
3734
  * Maintenance graph builder (Epic #118 issue #124 — the RFC B-案 core).
@@ -1,7 +1,7 @@
1
- import { L as Linter, a as LintRule } from '../linter-C-vypgut.js';
2
- export { b as LintResult } from '../linter-C-vypgut.js';
3
- import '../types-BXLzIcQD.js';
4
- import '../key-DR7_lpyk.js';
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
5
  import '../types-2PMXEn5x.js';
6
6
 
7
7
  /**
@@ -1,4 +1,4 @@
1
- import { E as EntityMetadata } from './types-BXLzIcQD.js';
1
+ import { E as EntityMetadata } from './types-DW__-Icc.js';
2
2
 
3
3
  declare class MetadataRegistry {
4
4
  private static store;
@@ -1,6 +1,6 @@
1
- import { E as EntityMetadata } from './types-BXLzIcQD.js';
1
+ import { E as EntityMetadata } from './types-DW__-Icc.js';
2
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-DR7_lpyk.js';
3
+ import { Q as QueryModelContract, k as QueryMethodSpec, l as CommandModelContract, m as CommandMethodSpec } from './key-DZtjAQDh.js';
4
4
 
5
5
  /**
6
6
  * Contract-layer serialization (issue #59, CQRS Contract layer, Epic #57;
@@ -1,11 +1,11 @@
1
- import { $ as AnyOperationDefinition, a0 as TransactionDefinition, b as PreparedBody } from '../key-DR7_lpyk.js';
2
- export { a1 as CompiledFragment, a2 as CompiledMutationPlan, a3 as DerivedConditionCheck, a4 as DerivedEdgeWrite, a5 as DerivedIdempotencyGuard, a6 as DerivedMaintainOutbox, a7 as DerivedMaintainWrite, a8 as DerivedOutboxEvent, a9 as DerivedUniqueGuard, aa as DerivedUpdate, ab as EntityRefResolver, ac as MAX_TRANSACT_COMPOSE_ITEMS, ad as assertNoCrossFragmentMaintainCollision, ae as compileFragment, af as compileMutationPlan, ag as compileSingleFragmentPlan, ah as resetMaintenanceGraphCache, ai as resolveLifecycle, aj as resolveMaintainers } from '../key-DR7_lpyk.js';
3
- import { M as MetadataRegistry } from '../linter-C-vypgut.js';
4
- import { C as ContractMap, e as ContextOwnershipMap, c as PreparedPlanSpec, a as PreparedPlanDocument } from '../prepared-artifact-BpPgkXEo.js';
5
- export { A as AnyModelContract, B as BuiltContracts, f as ContextOwnership, g as PREPARED_FORMAT_VERSION, h as PreparedBindMap, b as PreparedBindSpec, d as PreparedReadRouteSpec, P as PreparedWriteOpSpec, i as buildContexts, j as buildContracts, k as canonicalJson, l as entityFingerprint, p as planFingerprint } from '../prepared-artifact-BpPgkXEo.js';
1
+ import { $ as AnyOperationDefinition, a0 as TransactionDefinition, b as PreparedBody } from '../key-DZtjAQDh.js';
2
+ export { a1 as CompiledFragment, a2 as CompiledMutationPlan, a3 as DerivedConditionCheck, a4 as DerivedEdgeWrite, a5 as DerivedIdempotencyGuard, a6 as DerivedMaintainOutbox, a7 as DerivedMaintainWrite, a8 as DerivedOutboxEvent, a9 as DerivedUniqueGuard, aa as DerivedUpdate, ab as EntityRefResolver, ac as MAX_TRANSACT_COMPOSE_ITEMS, ad as assertNoCrossFragmentMaintainCollision, ae as compileFragment, af as compileMutationPlan, ag as compileSingleFragmentPlan, ah as resetMaintenanceGraphCache, ai as resolveLifecycle, aj as resolveMaintainers } from '../key-DZtjAQDh.js';
3
+ import { M as MetadataRegistry } from '../linter-DQY7gUEk.js';
4
+ import { C as ContractMap, e as ContextOwnershipMap, c as PreparedPlanSpec, a as PreparedPlanDocument } from '../prepared-artifact-HFealr1q.js';
5
+ export { A as AnyModelContract, B as BuiltContracts, f as ContextOwnership, g as PREPARED_FORMAT_VERSION, h as PreparedBindMap, b as PreparedBindSpec, d as PreparedReadRouteSpec, P as PreparedWriteOpSpec, i as buildContexts, j as buildContracts, k as canonicalJson, l as entityFingerprint, p as planFingerprint } from '../prepared-artifact-HFealr1q.js';
6
6
  import { O as OperationsDocument, Q as QuerySpec, M as Manifest, C as ContractSpec, T as TransactionSpec, B as BridgeBundle, c as ConditionSpec, E as ExpressionSpec, a as CommandSpec, d as SpecVersion } from '../types-2PMXEn5x.js';
7
7
  export { e as CommandContractMethodSpec, f as CommandResolutionTarget, g as ComposeSpec, h as CompositionPlanSpec, b as ContextSpec, i as ContractCardinality, j as ContractCommandResult, k as ContractInputArity, l as ContractKeySpec, m as ContractKind, n as ContractResolution, o as EXPR_VERSION, p as ExecutionPlanSpec, q as ExpressionArrNode, r as ExpressionFloatNode, s as ExpressionIntNode, t as ExpressionNode, u as ExpressionObjNode, v as ExpressionOpNode, w as ExpressionOperator, x as ExpressionRefNode, y as ExpressionRefOptNode, z as ExpressionScalar, F as FilterSpec, A as ManifestEntity, D as ManifestField, G as ManifestFieldType, H as ManifestGsi, I as ManifestKey, J as ManifestRelation, K as ManifestTable, L as OperationSpec, P as ParamSpec, N as QueryContractMethodSpec, R as RangeConditionSpec, U as ReadOperationType, S as SPEC_VERSION, V as SPEC_VERSION_SCP, W as SPEC_VERSION_SUPPORTED, X as TransactionItemSpec, Y as TransactionItemType, Z as WhenSpec, _ as WriteOperationType } from '../types-2PMXEn5x.js';
8
- import '../types-BXLzIcQD.js';
8
+ import '../types-DW__-Icc.js';
9
9
 
10
10
  /**
11
11
  * Static parameterized operation-spec generation (issue #42, Phase 0b).
@@ -10,14 +10,14 @@ import {
10
10
  collectContractBoundaryViolations,
11
11
  collectContractN1Violations,
12
12
  compilePreparedPlan
13
- } from "../chunk-N4NWYNGZ.js";
13
+ } from "../chunk-LAT64YCZ.js";
14
14
  import {
15
15
  PREPARED_FORMAT_VERSION,
16
16
  buildManifest,
17
17
  canonicalJson,
18
18
  entityFingerprint,
19
19
  planFingerprint
20
- } from "../chunk-LGHSZIEE.js";
20
+ } from "../chunk-S2NI4PBW.js";
21
21
  import {
22
22
  MAX_TRANSACT_COMPOSE_ITEMS,
23
23
  assertBundleSerializable,
@@ -32,7 +32,7 @@ import {
32
32
  resetMaintenanceGraphCache,
33
33
  resolveLifecycle,
34
34
  resolveMaintainers
35
- } from "../chunk-I4LEJ4TF.js";
35
+ } from "../chunk-T44OB5GU.js";
36
36
  import "../chunk-HNY2EJPV.js";
37
37
  import {
38
38
  EXPR_VERSION,
@@ -1,4 +1,4 @@
1
- import { n as Executor, o as DynamoDBOperation, R as ReadExecOptions, p as ExecutorResult, B as BatchGetExecInput, q as PutInput, W as WriteExecOptions, r as WriteResult, U as UpdateInput, s as DeleteInput, t as BatchWriteExecItem, u as BatchExecOptions, T as TransactWriteExecItem, M as ModelStatic, D as DDBModel, v as ChangeEvent } from '../key-DR7_lpyk.js';
1
+ import { n as Executor, o as DynamoDBOperation, R as ReadExecOptions, p as ExecutorResult, B as BatchGetExecInput, q as PutInput, W as WriteExecOptions, r as WriteResult, U as UpdateInput, s as DeleteInput, t as BatchWriteExecItem, u as BatchExecOptions, T as TransactWriteExecItem, M as ModelStatic, D as DDBModel, v as ChangeEvent } from '../key-DZtjAQDh.js';
2
2
  import '../types-2PMXEn5x.js';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { d as SegmentedKey, e as ProjectionTransform, f as MaintainEvent, g as ProjectionMap, h as MaintainConsistency, i as MaintainUpdateMode, E as EffectPath, j as MembershipPredicate } from './key-DR7_lpyk.js';
1
+ import { d as SegmentedKey, e as ProjectionTransform, f as MaintainEvent, g as ProjectionMap, h as MaintainConsistency, i as MaintainUpdateMode, E as EffectPath, j as MembershipPredicate } from './key-DZtjAQDh.js';
2
2
 
3
3
  type DynamoType = 'S' | 'N' | 'BOOL' | 'B' | 'SS' | 'NS' | 'L' | 'M';
4
4
  interface FieldOptions {
@@ -263,6 +263,52 @@ addMany: { create: GroupMembership, key: { groupId: param.string(), userId: para
263
263
  `GraphQL contrast`: `mode: 'transaction'` is atomic all-or-nothing; `mode:
264
264
  'parallel'` is non-atomic per-field partial success.
265
265
 
266
+ ### Composite (multi-entity atomic) methods — the `writes` body
267
+
268
+ A single write descriptor covers one entity. A **composite** method writes
269
+ **multiple entities atomically** — the canonical case being a cross-entity write
270
+ that has **no relation to fall back on** (e.g. marking a hash-keyed aggregate node
271
+ `dirty` alongside a chunk's terminal update). Author it with the **`writes` body**
272
+ form:
273
+
274
+ ```ts
275
+ export const ChunkCompleteCommands = graphddb.publishCommand({
276
+ complete: {
277
+ // the public input schema — names + types the params the body references
278
+ input: { chunkId: param.string(), nodeId: param.string() },
279
+ // the mutation body: `$ => ({ alias: descriptor, … })`; `$` refers to the inputs
280
+ writes: ($) => ({
281
+ chunk: { update: Chunk, key: { chunkId: $.chunkId }, input: { done: true } },
282
+ agg: { update: AggNode, key: { nodeId: $.nodeId }, input: { dirty: true } },
283
+ }),
284
+ // optional read-back projection (keyed on the primary — first — fragment)
285
+ result: { select: { chunkId: true, done: true } },
286
+ },
287
+ });
288
+ ```
289
+
290
+ - `writes` is a body `$ => ({ alias: writeDescriptor, … })`. Each alias value is a
291
+ write descriptor (`{ create | update | remove | upsert: Model, key, input?,
292
+ condition? }` — the same shape as a single-descriptor method, minus `result` /
293
+ `mode`, which are declared on the composite as a whole). The `$` proxy fields are
294
+ the input fields declared in the separate **`input`** schema; a later descriptor
295
+ may reference an earlier alias's written field via `$.alias.field`.
296
+ - **Single-alias** `writes` ⇒ one write. **Multi-alias** ⇒ an **atomic composite**:
297
+ it is promoted to a single `TransactWriteItems` by DynamoDB necessity (there is no
298
+ non-atomic multi-item form for a cross-entity write), so no `mode` ceremony is
299
+ needed — a multi-alias composite is always atomic.
300
+ - `input` (the public param schema), `result` (read-back), and `mode` are specified
301
+ **alongside** the body, never baked into it.
302
+ - **No `mutation(...)` / `defineScpTransaction(...)` import is needed** —
303
+ `publishCommand` compiles the `writes` body internally. The emitted
304
+ `operations.json` wire is byte-identical to the equivalent internal composition.
305
+ - The single-descriptor form above stays the sugar for a single-entity write; the
306
+ `writes` body is the additive form for the multi-entity atomic case.
307
+
308
+ > The full **SCP native-syntax** composite body (imperative `$`/`?:`/`&&`/`.map`
309
+ > transaction sources) is a separate surface, deferred to a later release; the
310
+ > `writes` descriptor-map body above is the supported public composite form today.
311
+
266
312
  #### The 25-item limit is a hard error, never a silent split
267
313
 
268
314
  `TransactWriteItems` is capped at **25 items** and is atomic. A
@@ -498,15 +544,20 @@ Definition DSL (`src/define/contract.ts`) — import the `graphddb` namespace
498
544
 
499
545
  ```ts
500
546
  graphddb.publishQuery({ alias: { query | list: Model, key, select, options? } })
547
+ // single-entity write
501
548
  graphddb.publishCommand({ alias: { create | update | remove: Model, key, input, condition?, result?, use?, mode? } })
549
+ // multi-entity atomic (composite) write — the `writes` body form (#293)
550
+ graphddb.publishCommand({ alias: { input, writes: $ => ({ alias: descriptor, … }), result?, mode? } })
502
551
  param.string() / param.number() / ... // descriptor key/input parameters
503
552
  ```
504
553
 
505
554
  A query method may compose another query contract's method (cross-fragment refs
506
- via `$.alias.field`); a command descriptor composes its derived write ops and
507
- maintenance writes into one plan. These are declared inside the
508
- `publishQuery` / `publishCommand` map there is no separate public `query()` /
509
- `mutation()` composite verb (removed in 0.8.0).
555
+ via `$.alias.field`); a command method composes its derived write ops and
556
+ maintenance writes into one plan, and a **composite** command method's `writes`
557
+ body composes a multi-entity atomic write Composite methods). These are all
558
+ declared inside the `publishQuery` / `publishCommand` map there is no separate
559
+ public `query()` / `mutation()` composite verb (removed in 0.8.0; the composite
560
+ `writes` body is compiled internally, needing no import).
510
561
 
511
562
  Execution is the two `graphddb.*` verbs (`graphddb.query` / `graphddb.mutate`) —
512
563
  they route contract methods through the in-process runtime.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphddb",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Graph data modeling on DynamoDB with adjacency list pattern",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",