@aventara/core 0.1.0-pilot.3 → 1.0.0-rc.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.
Files changed (167) hide show
  1. package/CHANGELOG.md +338 -0
  2. package/README.md +100 -3
  3. package/dist/adapters/adapter-discovery-diagnostic.d.ts +2 -0
  4. package/dist/adapters/adapter-field.d.ts +3 -26
  5. package/dist/adapters/adapter-identifier.d.ts +1 -11
  6. package/dist/adapters/adapter-model.d.ts +12 -0
  7. package/dist/adapters/adapter-operation-executor.interface.d.ts +5 -1
  8. package/dist/adapters/adapter-resource.d.ts +7 -28
  9. package/dist/adapters/adapter.interface.d.ts +25 -26
  10. package/dist/compiler/contract-compiler-diagnostic.d.ts +8 -0
  11. package/dist/compiler/contract.compiler.d.ts +11 -22
  12. package/dist/compiler/contract.compiler.js +67 -0
  13. package/dist/config/any-framework-config.d.ts +5 -26
  14. package/dist/config/config-diagnostic.d.ts +2 -0
  15. package/dist/config/config-layer.d.ts +6 -19
  16. package/dist/config/config.resolver.d.ts +9 -14
  17. package/dist/config/config.resolver.js +1 -0
  18. package/dist/config/config.validator.d.ts +6 -3
  19. package/dist/config/config.validator.js +6 -0
  20. package/dist/config/define-fields.d.ts +15 -48
  21. package/dist/config/effective-fields.d.ts +6 -29
  22. package/dist/config/entrypoint.normalizer.d.ts +6 -18
  23. package/dist/config/framework-behaviors-config.d.ts +11 -10
  24. package/dist/config/framework-config.d.ts +19 -16
  25. package/dist/config/framework-fields-config.d.ts +20 -7
  26. package/dist/config/framework-restrictions-config.d.ts +41 -61
  27. package/dist/config/pipeline-config.d.ts +7 -17
  28. package/dist/contracts/capability.d.ts +13 -49
  29. package/dist/contracts/client-contract-structure.validator.d.ts +28 -54
  30. package/dist/contracts/client-contract-structure.validator.js +99 -2
  31. package/dist/contracts/contract-canonicalizer.d.ts +4 -6
  32. package/dist/contracts/contract-canonicalizer.js +32 -0
  33. package/dist/contracts/contract-hash.d.ts +3 -2
  34. package/dist/contracts/contract-limits.d.ts +7 -1
  35. package/dist/contracts/contract-protocol.d.ts +7 -8
  36. package/dist/contracts/contract.d.ts +20 -4
  37. package/dist/contracts/enum-definition.d.ts +2 -0
  38. package/dist/contracts/field.d.ts +40 -167
  39. package/dist/contracts/identifier.d.ts +1 -16
  40. package/dist/contracts/json-shape.d.ts +65 -0
  41. package/dist/contracts/json-shape.js +1 -0
  42. package/dist/contracts/lifecycle-trait.d.ts +4 -3
  43. package/dist/contracts/reference-write-limit.d.ts +7 -0
  44. package/dist/contracts/reference-write-limit.js +1 -0
  45. package/dist/contracts/resource-operations.d.ts +2 -62
  46. package/dist/contracts/resource.d.ts +2 -0
  47. package/dist/contracts/scalar-value-type.d.ts +28 -48
  48. package/dist/contracts/scalar.d.ts +4 -3
  49. package/dist/contracts/transaction-capability.d.ts +2 -0
  50. package/dist/contracts/wire-scalar-format.d.ts +6 -21
  51. package/dist/framework/contract-view.d.ts +152 -130
  52. package/dist/framework/framework-construction.error.d.ts +6 -1
  53. package/dist/framework/framework.d.ts +33 -46
  54. package/dist/framework/framework.factory.d.ts +3 -17
  55. package/dist/framework/framework.factory.js +56 -13
  56. package/dist/framework/operation-facade.d.ts +3 -7
  57. package/dist/index.d.ts +8 -40
  58. package/dist/index.js +0 -20
  59. package/dist/internal.d.ts +35 -0
  60. package/dist/internal.js +16 -0
  61. package/dist/json/avz.d.ts +96 -0
  62. package/dist/json/avz.js +212 -0
  63. package/dist/json/index.d.ts +6 -0
  64. package/dist/json/index.js +1 -0
  65. package/dist/json/json-shape.registry.d.ts +22 -0
  66. package/dist/json/json-shape.registry.js +16 -0
  67. package/dist/json/zod-issue.translator.d.ts +51 -0
  68. package/dist/json/zod-issue.translator.js +142 -0
  69. package/dist/operations/operation-arguments.d.ts +198 -303
  70. package/dist/operations/operation-call.d.ts +13 -15
  71. package/dist/operations/operation-identity.d.ts +6 -2
  72. package/dist/operations/operation-identity.js +3 -0
  73. package/dist/operations/operation-result.d.ts +15 -61
  74. package/dist/operations/operation-validator-core.d.ts +6 -5
  75. package/dist/operations/operation-validator-core.js +28 -10
  76. package/dist/operations/operation-validator.d.ts +22 -9
  77. package/dist/operations/operation-validator.js +26 -10
  78. package/dist/operations/projection-scope.resolver.d.ts +1 -11
  79. package/dist/operations/structural-limits.walker.d.ts +19 -0
  80. package/dist/operations/structural-limits.walker.js +344 -0
  81. package/dist/operations/validation-code.d.ts +5 -17
  82. package/dist/operations/write-scope.resolver.d.ts +3 -28
  83. package/dist/protocol/absent-route.responder.d.ts +25 -39
  84. package/dist/protocol/absent-route.responder.js +32 -6
  85. package/dist/protocol/av-protocol.d.ts +2 -8
  86. package/dist/protocol/contract-document.responder.d.ts +5 -18
  87. package/dist/protocol/http-request.decoder.d.ts +18 -59
  88. package/dist/protocol/http-request.decoder.js +11 -3
  89. package/dist/protocol/http-request.reader.d.ts +5 -5
  90. package/dist/protocol/http-response.encoder.d.ts +14 -20
  91. package/dist/protocol/http-status.table.d.ts +0 -8
  92. package/dist/protocol/index.d.ts +2 -7
  93. package/dist/protocol/operation-response.guard.d.ts +0 -15
  94. package/dist/protocol/protocol-failure.d.ts +8 -27
  95. package/dist/protocol/protocol-headers.d.ts +15 -42
  96. package/dist/protocol/protocol-surface.d.ts +0 -22
  97. package/dist/protocol/protocol.binder.d.ts +19 -27
  98. package/dist/protocol/protocol.binder.js +3 -1
  99. package/dist/protocol/protocol.handler.d.ts +19 -23
  100. package/dist/protocol/protocol.handler.js +14 -4
  101. package/dist/protocol/unmounted-route.error.d.ts +19 -0
  102. package/dist/protocol/unmounted-route.error.js +10 -0
  103. package/dist/runtime/caller-data.copier.d.ts +59 -0
  104. package/dist/runtime/caller-data.copier.js +470 -0
  105. package/dist/runtime/caller-data.door.d.ts +56 -0
  106. package/dist/runtime/caller-data.door.js +65 -0
  107. package/dist/runtime/computed-read-runtime.js +3 -10
  108. package/dist/runtime/computed-value.error.d.ts +13 -0
  109. package/dist/runtime/computed-value.error.js +9 -0
  110. package/dist/runtime/create-requirement.d.ts +43 -7
  111. package/dist/runtime/create-requirement.js +89 -9
  112. package/dist/runtime/decimal.d.ts +5 -3
  113. package/dist/runtime/diagnostics-reporter.d.ts +33 -37
  114. package/dist/runtime/diagnostics-reporter.js +11 -4
  115. package/dist/runtime/error-boundary.d.ts +14 -10
  116. package/dist/runtime/error-boundary.js +65 -35
  117. package/dist/runtime/execution-options.d.ts +3 -7
  118. package/dist/runtime/field-behavior.d.ts +14 -21
  119. package/dist/runtime/framework-error.d.ts +14 -2
  120. package/dist/runtime/json-value.d.ts +7 -15
  121. package/dist/runtime/misuse-reporter.d.ts +32 -0
  122. package/dist/runtime/misuse-reporter.js +19 -0
  123. package/dist/runtime/mutation-planner.d.ts +6 -12
  124. package/dist/runtime/mutation-planner.js +13 -11
  125. package/dist/runtime/operation-request.d.ts +11 -11
  126. package/dist/runtime/operation-response.d.ts +18 -23
  127. package/dist/runtime/operation-runtime.d.ts +25 -33
  128. package/dist/runtime/operation-runtime.js +37 -18
  129. package/dist/runtime/outcome-mapper.js +12 -2
  130. package/dist/runtime/output-validator.d.ts +10 -0
  131. package/dist/runtime/output-validator.js +11 -0
  132. package/dist/runtime/owned-execution.registry.d.ts +13 -0
  133. package/dist/runtime/owned-execution.registry.js +7 -0
  134. package/dist/runtime/pipeline.d.ts +33 -36
  135. package/dist/runtime/referential-action.d.ts +8 -26
  136. package/dist/runtime/request-envelope.validator.d.ts +5 -0
  137. package/dist/runtime/request-envelope.validator.js +118 -0
  138. package/dist/runtime/request-id.d.ts +3 -10
  139. package/dist/runtime/scalar-runtime.d.ts +42 -14
  140. package/dist/runtime/scalar-runtime.js +162 -79
  141. package/dist/transactions/deferred-operation.d.ts +10 -21
  142. package/dist/transactions/operation-arguments-encoder.d.ts +1 -0
  143. package/dist/transactions/operation-fingerprint.d.ts +3 -1
  144. package/dist/transactions/transaction-envelope.d.ts +2 -4
  145. package/dist/transactions/transaction-integrity-error.d.ts +4 -15
  146. package/dist/transactions/transaction-plan-assembler.d.ts +6 -1
  147. package/dist/transactions/transaction-plan-assembler.js +74 -75
  148. package/dist/transactions/transaction-plan-validator.d.ts +33 -24
  149. package/dist/transactions/transaction-plan-validator.js +204 -153
  150. package/dist/transactions/transaction-reference-collector.d.ts +30 -0
  151. package/dist/transactions/transaction-reference-collector.js +204 -0
  152. package/dist/transactions/transaction-reference-resolver.d.ts +9 -12
  153. package/dist/transactions/transaction-reference-resolver.js +10 -5
  154. package/dist/transactions/transaction-reference-validator.js +24 -17
  155. package/dist/transactions/transaction-reference.d.ts +31 -64
  156. package/dist/transactions/transaction-request.d.ts +6 -8
  157. package/dist/transactions/transaction-response.d.ts +10 -7
  158. package/dist/transactions/transaction-response.js +24 -1
  159. package/dist/transactions/transaction-runtime.d.ts +7 -5
  160. package/dist/transactions/transaction-runtime.js +4 -3
  161. package/dist/transactions/transaction-slot-path-error.d.ts +5 -15
  162. package/dist/transactions/transaction-slot-writer.d.ts +3 -13
  163. package/package.json +17 -3
  164. package/dist/operations/operation-boolean-nodes.d.ts +0 -11
  165. package/dist/operations/operation-boolean-nodes.js +0 -283
  166. package/dist/operations/operation-nesting.d.ts +0 -8
  167. package/dist/operations/operation-nesting.js +0 -307
@@ -13,12 +13,7 @@ type SelectOf<Args> = Args extends {
13
13
  type IncludeOf<Args> = Args extends {
14
14
  readonly include?: infer Include;
15
15
  } ? Exclude<Include, undefined> : never;
16
- /**
17
- * The projection argument, held apart so the caller's literal is inferred and
18
- * nothing else is: only `select`/`include` drive the result, and every other
19
- * argument stays a plain declared type, which keeps its contextual typing and its
20
- * excess-property check.
21
- */
16
+ /** The projection argument (`select`/`include`) of a call, which decides its result type. */
22
17
  type ProjectionArgument<Select, Include> = {
23
18
  readonly select?: Select;
24
19
  readonly include?: never;
@@ -37,10 +32,8 @@ type ArgumentList<Rest, Parameter> = Record<never, never> extends Rest ? [args?:
37
32
  type CallArgumentList<Rest, Parameter, Options> = Record<never, never> extends Rest ? [args?: Parameter, options?: Options] : [args: Parameter, options?: Options];
38
33
  type ResourceOperationsOf<C extends Contract, R extends ResourceKey<C>> = C["resources"][R]["operations"];
39
34
  /**
40
- * The operation grammar `C` advertises: one property per Resource, one per
41
- * advertised family under it, one identity per advertised variant under that.
42
- * The one source every surface maps its calls over, so which operations exist is
43
- * decided once.
35
+ * The operations Contract `C` advertises: one property per Resource, one per
36
+ * family under it, one identity per variant under that.
44
37
  */
45
38
  export type OperationGrammar<C extends Contract> = {
46
39
  readonly [R in ResourceKey<C>]: {
@@ -53,9 +46,9 @@ export type OperationGrammar<C extends Contract> = {
53
46
  };
54
47
  };
55
48
  /**
56
- * An operation's slot: its result, except that a first-style miss contributes
57
- * `null` — where the miss is not the `A1001` arm of a response of its own: a
58
- * committed plan's slot, and a remote call that resolves to the data.
49
+ * An operation's slot: its result, except that a first-style miss is `null`.
50
+ * This is the value a committed transaction or a remote call returns for the
51
+ * operation.
59
52
  */
60
53
  export type OperationSlot<I extends OperationIdentity, Result> = I["variant"] extends "first" ? Result | null : Result;
61
54
  /**
@@ -72,13 +65,18 @@ type PlainCall<C extends Contract, R extends ResourceKey<C>, I extends Operation
72
65
  * projection when it carries one.
73
66
  */
74
67
  export type OperationCall<C extends Contract, R extends ResourceKey<C>, I extends OperationIdentity, Forms extends ScalarForms, ResultForms extends ScalarForms, Settlement extends "response" | "data", Options> = "select" extends keyof OperationArgumentsFor<C, R, I, Forms> ? ProjectingCall<C, R, I, OperationArgumentsFor<C, R, I, Forms>, ResultForms, Settlement, Options> : PlainCall<C, R, I, OperationArgumentsFor<C, R, I, Forms>, ResultForms, Settlement, Options>;
68
+ /**
69
+ * A deferred operation's arguments: `Forms`, with reference positions opened to a
70
+ * `$ref` typed in `ResultForms` — the forms the source's final data is read in, so a
71
+ * reference carries what its source resolves.
72
+ */
75
73
  type DeferredArguments<C extends Contract, R extends ResourceKey<C>, I extends OperationIdentity, Forms extends ScalarForms, ResultForms extends ScalarForms> = ReferenceableArgumentsFor<C, R, I, Forms, ResultForms>;
76
74
  type DeferredProjectingCall<C extends Contract, R extends ResourceKey<C>, I extends OperationIdentity, Args, ResultForms extends ScalarForms> = <const Select extends SelectOf<Args> = never, const Include extends IncludeOf<Args> = never>(...args: ArgumentList<Omit<Args, ProjectionKey>, Omit<Args, ProjectionKey> & ProjectionArgument<Select, Include>>) => Operation<OperationSlot<I, AdmittedOperationResult<C, R, I, InferredProjection<Select, Include>, ResultForms>>>;
77
75
  type DeferredPlainCall<C extends Contract, R extends ResourceKey<C>, I extends OperationIdentity, Args, ResultForms extends ScalarForms> = (...args: ArgumentList<Args, Args>) => Operation<OperationSlot<I, AdmittedOperationResult<C, R, I, unknown, ResultForms>>>;
78
76
  /**
79
77
  * One deferred operation: a handle typed `Operation<OperationSlot<…>>` — `$ref`
80
- * present exactly where the result is one row. A handle is not a request and takes
81
- * no options.
78
+ * present exactly where the result is one row. A handle is not a request and takes no
79
+ * options.
82
80
  */
83
81
  export type DeferredOperationCall<C extends Contract, R extends ResourceKey<C>, I extends OperationIdentity, Forms extends ScalarForms, ResultForms extends ScalarForms> = "select" extends keyof DeferredArguments<C, R, I, Forms, ResultForms> ? DeferredProjectingCall<C, R, I, DeferredArguments<C, R, I, Forms, ResultForms>, ResultForms> : DeferredPlainCall<C, R, I, DeferredArguments<C, R, I, Forms, ResultForms>, ResultForms>;
84
82
  export {};
@@ -1,7 +1,9 @@
1
1
  import type { ResourceOperations } from "../contracts/resource-operations.js";
2
+ /** A standard operation family: `find`, `create`, `update`, `delete` or `upsert`. */
2
3
  export type OperationFamily = Extract<keyof ResourceOperations, string>;
4
+ /** The variants of operation family `F`, such as `many` or `unique`. */
3
5
  export type OperationVariant<F extends OperationFamily> = Extract<keyof NonNullable<ResourceOperations[F]>, string>;
4
- /** Exact standard operation family/variant identity derived from one source. */
6
+ /** A standard operation's `family` and `variant`. */
5
7
  export type OperationIdentity = {
6
8
  [F in OperationFamily]: {
7
9
  [V in OperationVariant<F>]: {
@@ -10,5 +12,7 @@ export type OperationIdentity = {
10
12
  };
11
13
  }[OperationVariant<F>];
12
14
  }[OperationFamily];
13
- /** Parses one exact standard operation family/variant pair. */
15
+ /** Parses a family and variant into an {@link OperationIdentity}, or `undefined` when they name no standard operation. */
14
16
  export declare function parseOperationIdentity(family: string, variant: string): OperationIdentity | undefined;
17
+ /** Whether `family` names one of the standard operation families. */
18
+ export declare function isOperationFamily(family: string): boolean;
@@ -23,3 +23,6 @@ export function parseOperationIdentity(family, variant) {
23
23
  }
24
24
  return { family, variant };
25
25
  }
26
+ export function isOperationFamily(family) {
27
+ return Object.keys(STANDARD_OPERATIONS).some((key) => key.startsWith(`${family}.`));
28
+ }
@@ -5,11 +5,8 @@ import type { FieldKey, OperationArgumentsFor, ResourceKey } from "./operation-a
5
5
  import type { OperationIdentity } from "./operation-identity.js";
6
6
  type FieldOf<C extends Contract, K extends ResourceKey<C>, F extends FieldKey<C, K>> = C["resources"][K]["fields"][F];
7
7
  /**
8
- * The default projection's keys: the scalars whose `select` is exactly `true`.
9
- *
10
- * Strict, as the runtime is: an absent member, `false`, and a member the Contract
11
- * widened to `boolean` are all left out, so a write-only field never appears and a
12
- * widened Contract under-promises rather than over-promises.
8
+ * The fields returned when no projection is given: the scalars whose `select`
9
+ * is exactly `true`. A write-only field never appears.
13
10
  */
14
11
  type DefaultKey<C extends Contract, K extends ResourceKey<C>> = {
15
12
  [F in FieldKey<C, K>]: FieldOf<C, K, F> extends {
@@ -19,16 +16,11 @@ type DefaultKey<C extends Contract, K extends ResourceKey<C>> = {
19
16
  };
20
17
  } ? F : never;
21
18
  }[FieldKey<C, K>];
22
- /**
23
- * The field one projection entry names: a bare key, or a relation entry's key.
24
- * `$count` passes through and is dropped where every caller intersects the result
25
- * with the Resource's field keys, which is the one place that knows what a field is.
26
- */
19
+ /** The field one projection entry names: a bare key, or a relation entry's key. */
27
20
  type EntryKey<Entry> = Entry extends string ? Entry : Entry extends object ? Extract<keyof Entry, string> : never;
28
21
  /**
29
22
  * The argument object a relation entry carries for field `F`, or `never` when
30
- * the relation is only named. The `object` guard keeps a bare string entry from
31
- * matching its own apparent members (`"length"` would otherwise).
23
+ * the relation is only named.
32
24
  */
33
25
  type EntryArguments<Entry, F extends string> = Entry extends object ? Entry extends {
34
26
  readonly [Key in F]: infer Arguments;
@@ -43,12 +35,8 @@ type ProjectedFields<C extends Contract, K extends ResourceKey<C>, Keys extends
43
35
  readonly [F in Keys]: FieldOf<C, K, F> extends ScalarField<C> ? ScalarValueType<C, FieldOf<C, K, F>, Forms> : FieldOf<C, K, F> extends RelationField<C> ? RelationResult<C, Extract<FieldOf<C, K, F>["target"], ResourceKey<C>>, FieldOf<C, K, F>, EntryArguments<Entries, F>, Forms> : never;
44
36
  };
45
37
  /**
46
- * One projection level: `select` is exactly its entries, `include` the defaults
47
- * plus the named relations, and anything else the defaults.
48
- *
49
- * An absent argument (`never`, from {@link EntryArguments}) is tested first and
50
- * explicitly: it would otherwise distribute to `never`, and inside a one-tuple it
51
- * would match the `select` arm, because `never` extends everything.
38
+ * One projection level: `select` returns exactly its entries, `include` the
39
+ * default fields plus the named relations, and no projection the default fields.
52
40
  */
53
41
  type ProjectedRecord<C extends Contract, K extends ResourceKey<C>, Arguments, Forms extends ScalarForms> = [Arguments] extends [never] ? ProjectedFields<C, K, DefaultKey<C, K>, never, Forms> : [Arguments] extends [{
54
42
  readonly select: readonly (infer Entry)[];
@@ -56,10 +44,8 @@ type ProjectedRecord<C extends Contract, K extends ResourceKey<C>, Arguments, Fo
56
44
  readonly include: readonly (infer Entry)[];
57
45
  }] ? ProjectedFields<C, K, DefaultKey<C, K> | Extract<EntryKey<Entry>, FieldKey<C, K>>, Entry, Forms> : ProjectedFields<C, K, DefaultKey<C, K>, never, Forms>;
58
46
  /**
59
- * A to-many entry whose `select` asks for `$count`: the parent's reducer over the
60
- * relation, so the value is the envelope rather than the row array. `$count` and
61
- * the row fields are separated here because they are separate facts — the rows
62
- * are the target's projection, the count is the parent's reducer.
47
+ * A to-many entry whose `select` asks for `$count`: the value is an object with
48
+ * the rows and the count, not the row array.
63
49
  */
64
50
  type CountEnvelope<C extends Contract, Target extends ResourceKey<C>, Entry, Forms extends ScalarForms> = [Exclude<Entry, "$count">] extends [never] ? {
65
51
  readonly count: number;
@@ -67,56 +53,24 @@ type CountEnvelope<C extends Contract, Target extends ResourceKey<C>, Entry, For
67
53
  readonly data: readonly ProjectedFields<C, Target, Extract<EntryKey<Entry>, FieldKey<C, Target>>, Exclude<Entry, "$count">, Forms>[];
68
54
  readonly count: number;
69
55
  };
70
- /**
71
- * The entries of an argument's own `select`, or `never` when it has none.
72
- * Distributive on purpose: an absent argument (`never`) has no entries.
73
- */
56
+ /** The entries of an argument's own `select`, or `never` when it has none. */
74
57
  type SelectEntry<Arguments> = Arguments extends {
75
58
  readonly select: readonly (infer Entry)[];
76
59
  } ? Entry : never;
77
60
  /**
78
- * The value of one relation entry.
79
- *
80
- * The nested projection is the target's own, at every depth: a scope qualifies
81
- * the top level of a projection only (`contracts/capability.ts`
82
- * `ProjectionScope`), and the argument constraint on {@link OperationResultFor}
83
- * has already admitted the entry at the position it sits in.
84
- *
85
- * A to-many entry is the envelope exactly when its own `select` carries `$count`;
86
- * otherwise — no argument, `include`, or a `select` without the reducer — it is
87
- * the row array.
61
+ * The value of one relation entry. A to-many entry is the rows-and-count object
62
+ * when its own `select` asks for `$count`; otherwise the row array.
88
63
  */
89
64
  type RelationResult<C extends Contract, Target extends ResourceKey<C>, Relation extends RelationField<C>, Arguments, Forms extends ScalarForms> = Relation["cardinality"] extends "many" ? [Extract<SelectEntry<Arguments>, "$count">] extends [never] ? readonly ProjectedRecord<C, Target, Arguments, Forms>[] : CountEnvelope<C, Target, SelectEntry<Arguments>, Forms> : ProjectedRecord<C, Target, Arguments, Forms> | (true extends Relation["nullable"] ? null : never);
90
65
  /**
91
66
  * The value a standard operation returns in `OperationResponse.data` on success,
92
- * derived from the Contract, the Resource, the operation identity and the
93
- * projection argument.
94
- *
95
- * - `count` → `number`;
96
- * - `many` → `readonly Record[]`;
97
- * - `first`, `unique`, `one` → the record. **Not `| null`:** absence is an
98
- * outcome, not a value — `A1001` answers `data: null` for a first-style miss and
99
- * `A2003` refuses a strict unique one (`runtime/outcome-mapper.ts`
100
- * `OUTCOME_TABLE`) — so folding `null` into the record would count it twice.
101
- *
102
- * `Args` is constrained to the operation's own argument type, so a projection
103
- * entry the Contract does not advertise at that position — a write-only field, an
104
- * unadvertised `$count`, a relation outside a bulk write's ceiling — is refused
105
- * here instead of typed as present.
106
- *
107
- * `Forms` is the scalar-form map. It defaults to the application forms, which are
108
- * what a framework-produced value is in either scope; a transport or a generated
109
- * client passes the wire forms and gets the same derivation.
67
+ * derived from the Contract, the Resource, the operation identity and the projection
68
+ * argument.
110
69
  */
111
70
  export type OperationResultFor<C extends Contract, K extends ResourceKey<C>, I extends OperationIdentity, Args extends OperationArgumentsFor<C, K, I>, Forms extends ScalarForms = ApplicationScalarForms> = AdmittedOperationResult<C, K, I, Args, Forms>;
112
71
  /**
113
- * {@link OperationResultFor}'s rule, for a caller that has already admitted the
114
- * projection argument against the operation's own argument type — the
115
- * application façade, which constrains its inferred `select`/`include` at the
116
- * call. It reads only the projection, so a caller holding just that need not
117
- * compute the whole argument object a second time to satisfy the constraint.
118
- *
119
- * Not exported from the package: one public spelling of the result rule.
72
+ * {@link OperationResultFor}, for a projection already checked against the
73
+ * operation's argument type.
120
74
  */
121
75
  export type AdmittedOperationResult<C extends Contract, K extends ResourceKey<C>, I extends OperationIdentity, Args, Forms extends ScalarForms = ApplicationScalarForms> = I["variant"] extends "count" ? number : I["variant"] extends "many" ? readonly ProjectedRecord<C, K, Args, Forms>[] : ProjectedRecord<C, K, Args, Forms>;
122
76
  export {};
@@ -1,6 +1,7 @@
1
1
  import type { Contract } from "../contracts/contract.js";
2
- import type { OperationIdentity } from "./operation-identity.js";
2
+ import { type OperationIdentity } from "./operation-identity.js";
3
3
  import type { ValidationIssue } from "./validation-code.js";
4
+ /** An operation's arguments as an object, before validation. */
4
5
  export type SemanticOperationArguments = Readonly<Record<string, unknown>>;
5
6
  type OperationValidationSuccess = {
6
7
  readonly ok: true;
@@ -10,15 +11,15 @@ type OperationValidationFailure = {
10
11
  readonly ok: false;
11
12
  readonly issues: readonly ValidationIssue[];
12
13
  };
14
+ /** The answer of {@link validateOperationArguments}: success, or the issues found. */
13
15
  export type OperationValidationResult = OperationValidationSuccess | OperationValidationFailure;
14
16
  export type OperationValidationOptions = {
15
17
  readonly transactionReferences?: boolean;
16
18
  };
17
19
  /**
18
- * The Framework Validation Layer's issue for a request naming a Resource the
19
- * active Contract does not have (`V1005` at `["resource"]`, rolling up to
20
- * `A2001`). The one spelling — the protocol's absence answers build theirs with
21
- * it.
20
+ * The Framework Validation Layer's issue for a request naming a Resource the active
21
+ * Contract does not have (`V1005` at `["resource"]`, rolling up to `A2001`). The one
22
+ * spelling — the protocol's absence answers build theirs with it.
22
23
  */
23
24
  export declare function resourceUnavailableIssue(resource: string): ValidationIssue;
24
25
  export declare function validateOperationArguments(contract: Contract, request: OperationIdentity & {
@@ -2,6 +2,7 @@ import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
2
2
  import { advertisedDirectivesAt, advertisedReducers, advertisedReducersAt, holdsAt, } from "../contracts/field.js";
3
3
  import { isPlainRecord } from "../runtime/scalar-runtime.js";
4
4
  import { routeTransactionReferenceCandidate } from "../transactions/transaction-reference.js";
5
+ import { isOperationFamily, } from "./operation-identity.js";
5
6
  import { NESTED_PROJECTION_SCOPE, resolveProjectionScope, } from "./projection-scope.resolver.js";
6
7
  import { NESTED_WRITE_SCOPE, resolveWriteScope, } from "./write-scope.resolver.js";
7
8
  const OPERATION_ARGUMENT_RULES = {
@@ -111,7 +112,7 @@ function validateTopLevelArguments(request, args) {
111
112
  const rule = OPERATION_ARGUMENT_RULES[key];
112
113
  const issues = [];
113
114
  if (rule === undefined) {
114
- addIssue(issues, "V1001", ["operation"], `Operation "${key}" is not part of the standard operation grammar.`);
115
+ addIssue(issues, "V1001", [isOperationFamily(request.family) ? "variant" : "family"], `Operation "${key}" is not part of the standard operation grammar.`);
115
116
  return issues;
116
117
  }
117
118
  for (const required of rule.required) {
@@ -168,6 +169,13 @@ function validateScalarFilter(fieldName, field, value, path, state) {
168
169
  }
169
170
  return;
170
171
  }
172
+ if (state.transactionReferences === true &&
173
+ routeTransactionReferenceCandidate(value).kind !== "ordinary") {
174
+ if (!includes(supported, "equals")) {
175
+ addIssue(state.issues, "V1006", path, `Equality filtering is not available on field "${fieldName}".`);
176
+ }
177
+ return;
178
+ }
171
179
  if (!isPlainRecord(value)) {
172
180
  if (!includes(supported, "equals")) {
173
181
  addIssue(state.issues, "V1006", path, `Equality filtering is not available on field "${fieldName}".`);
@@ -270,6 +278,9 @@ function validateWhere(contract, resourceName, where, path, state, depth) {
270
278
  if (field.kind === "scalar") {
271
279
  validateScalarFilter(key, field, value, fieldPath, state);
272
280
  }
281
+ else if (state.scalarOnly === true) {
282
+ addIssue(state.issues, "V1006", fieldPath, `Relation filter "${key}" is not available here: a nested $update or $delete filter on this relation takes scalar fields only.`);
283
+ }
273
284
  else {
274
285
  validateRelationFilter(contract, key, field, value, fieldPath, state, depth);
275
286
  }
@@ -570,7 +581,7 @@ function validateScalarMutation(fieldName, field, value, stage, path, issues, tr
570
581
  }
571
582
  }
572
583
  }
573
- function validateRelationMutationEntry(contract, edge, directive, value, path, issues) {
584
+ function validateRelationMutationEntry(contract, edge, directive, value, path, issues, transactionReferences) {
574
585
  const target = edge.target;
575
586
  if (edge.cardinality === "one" && Array.isArray(value)) {
576
587
  addIssue(issues, "V1012", path, `To-one relation directive "${directive}" does not accept arrays.`);
@@ -619,7 +630,7 @@ function validateRelationMutationEntry(contract, edge, directive, value, path, i
619
630
  if (directive === "$create") {
620
631
  const entries = edge.cardinality === "many" && Array.isArray(value) ? value : [value];
621
632
  for (const entry of entries) {
622
- validateMutationData(contract, target, entry, "create", NESTED_WRITE_SCOPE, path, issues);
633
+ validateMutationData(contract, target, entry, "create", NESTED_WRITE_SCOPE, path, issues, transactionReferences);
623
634
  }
624
635
  return;
625
636
  }
@@ -631,7 +642,7 @@ function validateRelationMutationEntry(contract, edge, directive, value, path, i
631
642
  continue;
632
643
  }
633
644
  validateUniqueSelector(contract, target, entry.where, [...path, "where"], issues);
634
- validateMutationData(contract, target, entry.create, "create", NESTED_WRITE_SCOPE, [...path, "create"], issues);
645
+ validateMutationData(contract, target, entry.create, "create", NESTED_WRITE_SCOPE, [...path, "create"], issues, transactionReferences);
635
646
  }
636
647
  return;
637
648
  }
@@ -650,13 +661,15 @@ function validateRelationMutationEntry(contract, edge, directive, value, path, i
650
661
  const state = {
651
662
  issues,
652
663
  structuralRoot: [...path, "where"],
664
+ transactionReferences,
665
+ scalarOnly: edge.capabilities.nestedWriteFilter === "scalar",
653
666
  booleanFilterNodes: 0,
654
667
  nestingExceeded: false,
655
668
  booleanNodesExceeded: false,
656
669
  };
657
670
  validateWhere(contract, target, entry.where, [...path, "where"], state, 1);
658
671
  }
659
- validateMutationData(contract, target, entry.data, "update", NESTED_WRITE_SCOPE, [...path, "data"], issues);
672
+ validateMutationData(contract, target, entry.data, "update", NESTED_WRITE_SCOPE, [...path, "data"], issues, transactionReferences);
660
673
  }
661
674
  return;
662
675
  }
@@ -671,6 +684,8 @@ function validateRelationMutationEntry(contract, edge, directive, value, path, i
671
684
  const state = {
672
685
  issues,
673
686
  structuralRoot: [...path, "where"],
687
+ transactionReferences,
688
+ scalarOnly: edge.capabilities.nestedWriteFilter === "scalar",
674
689
  booleanFilterNodes: 0,
675
690
  nestingExceeded: false,
676
691
  booleanNodesExceeded: false,
@@ -685,6 +700,8 @@ function validateRelationMutationEntry(contract, edge, directive, value, path, i
685
700
  const state = {
686
701
  issues,
687
702
  structuralRoot: [...path, "where"],
703
+ transactionReferences,
704
+ scalarOnly: edge.capabilities.nestedWriteFilter === "scalar",
688
705
  booleanFilterNodes: 0,
689
706
  nestingExceeded: false,
690
707
  booleanNodesExceeded: false,
@@ -707,12 +724,12 @@ function validateRelationMutationEntry(contract, edge, directive, value, path, i
707
724
  if ("where" in entry) {
708
725
  validateUniqueSelector(contract, target, entry.where, [...path, "where"], issues);
709
726
  }
710
- validateMutationData(contract, target, entry.create, "create", NESTED_WRITE_SCOPE, [...path, "create"], issues);
711
- validateMutationData(contract, target, entry.update, "update", NESTED_WRITE_SCOPE, [...path, "update"], issues);
727
+ validateMutationData(contract, target, entry.create, "create", NESTED_WRITE_SCOPE, [...path, "create"], issues, transactionReferences);
728
+ validateMutationData(contract, target, entry.update, "update", NESTED_WRITE_SCOPE, [...path, "update"], issues, transactionReferences);
712
729
  }
713
730
  }
714
731
  }
715
- function validateRelationMutation(contract, fieldName, field, value, stage, scope, path, issues) {
732
+ function validateRelationMutation(contract, fieldName, field, value, stage, scope, path, issues, transactionReferences) {
716
733
  const directives = advertisedDirectivesAt(field.capabilities[stage], scope);
717
734
  if (directives === undefined) {
718
735
  addIssue(issues, "V1006", path, `Relation "${fieldName}" is not writable during ${stage}.`);
@@ -728,7 +745,7 @@ function validateRelationMutation(contract, fieldName, field, value, stage, scop
728
745
  addIssue(issues, "V1006", directivePath, `Relation mutation directive "${directive}" is not available on relation "${fieldName}".`);
729
746
  continue;
730
747
  }
731
- validateRelationMutationEntry(contract, field, directive, directiveValue, directivePath, issues);
748
+ validateRelationMutationEntry(contract, field, directive, directiveValue, directivePath, issues, transactionReferences);
732
749
  }
733
750
  }
734
751
  function validateMutationData(contract, resourceName, data, stage, scope, path, issues, transactionReferences = false) {
@@ -752,7 +769,7 @@ function validateMutationData(contract, resourceName, data, stage, scope, path,
752
769
  validateScalarMutation(fieldName, field, value, stage, fieldPath, issues, transactionReferences);
753
770
  }
754
771
  else {
755
- validateRelationMutation(contract, fieldName, field, value, stage, scope, fieldPath, issues);
772
+ validateRelationMutation(contract, fieldName, field, value, stage, scope, fieldPath, issues, transactionReferences);
756
773
  }
757
774
  }
758
775
  }
@@ -830,6 +847,7 @@ export function validateOperationArguments(contract, request, options) {
830
847
  const state = {
831
848
  issues,
832
849
  structuralRoot: ["arguments", "where"],
850
+ transactionReferences: options?.transactionReferences === true,
833
851
  booleanFilterNodes: 0,
834
852
  nestingExceeded: false,
835
853
  booleanNodesExceeded: false,
@@ -1,24 +1,37 @@
1
1
  import type { Contract } from "../contracts/contract.js";
2
- import type { OperationIdentity } from "./operation-identity.js";
2
+ import { type OperationIdentity } from "./operation-identity.js";
3
3
  import { type OperationValidationOptions, type OperationValidationResult } from "./operation-validator-core.js";
4
4
  import type { ValidationIssue } from "./validation-code.js";
5
5
  export type { OperationValidationOptions, OperationValidationResult, SemanticOperationArguments, } from "./operation-validator-core.js";
6
+ /** The key of a request's operation identity that makes it unavailable. */
7
+ export declare function unavailableOperationKey(contract: Contract, request: {
8
+ readonly resource: string;
9
+ readonly family: string;
10
+ }): "family" | "variant";
6
11
  /**
7
12
  * The Framework Validation Layer's issue for a `(resource, family, variant)` the
8
- * active Contract does not advertise on a Resource it has (`V1006` at
9
- * `["operation"]`, rolling up to `A2002`). The one spelling — the protocol's
10
- * absence answers build theirs with it, so a caller cannot tell which layer
11
- * answered.
13
+ * active Contract does not advertise on a Resource it has: `V1006`, rolling up to
14
+ * `A2002`, at the key that is wrong (`["family"]` or `["variant"]`, see
15
+ * {@link unavailableOperationKey}). The one spelling — the protocol's absence
16
+ * answers build theirs with it, so a caller cannot tell which layer answered.
12
17
  */
13
- export declare function operationUnavailableIssue(request: {
18
+ export declare function operationUnavailableIssue(contract: Contract, request: {
14
19
  readonly resource: string;
15
20
  readonly family: string;
16
21
  readonly variant: string;
17
22
  }): ValidationIssue;
18
23
  /**
19
- * Validates one standard operation's semantic arguments against the active
20
- * Contract. Operation availability and request-wide structural limits are
21
- * enforced before deeper grammar validation.
24
+ * The availability answer alone — `V1006` when the active Contract does not advertise
25
+ * the operation on a Resource it has — read from the request's identity, never its
26
+ * arguments. A door that refuses unreadable arguments asks this first, so an
27
+ * unadvertised operation answers `A2002` whatever its arguments hold.
28
+ */
29
+ export declare function findOperationAvailabilityIssue(contract: Contract, request: OperationIdentity & {
30
+ readonly resource: string;
31
+ }): ValidationIssue | undefined;
32
+ /**
33
+ * Validates one standard operation's arguments against a Contract. Operation
34
+ * availability and request limits are checked before the arguments' grammar.
22
35
  */
23
36
  export declare function validateOperationArguments(contract: Contract, request: OperationIdentity & {
24
37
  readonly resource: string;
@@ -1,8 +1,8 @@
1
1
  import { lookupOwn, lookupResource } from "../contracts/contract-lookup.js";
2
2
  import { isOperationVariantAvailable, } from "../contracts/resource-operations.js";
3
- import { findOperationBooleanNodeIssue } from "./operation-boolean-nodes.js";
4
- import { findOperationNestingIssue } from "./operation-nesting.js";
3
+ import { isOperationFamily, } from "./operation-identity.js";
5
4
  import { validateOperationArguments as validateSemanticOperationArguments, } from "./operation-validator-core.js";
5
+ import { findStructuralLimitIssues } from "./structural-limits.walker.js";
6
6
  function isArgumentObject(value) {
7
7
  return typeof value === "object" && value !== null && !Array.isArray(value);
8
8
  }
@@ -15,13 +15,28 @@ function isOperationAvailable(contract, request) {
15
15
  const family = lookupOwn(advertised, request.family);
16
16
  return isOperationVariantAvailable(lookupOwn(family, request.variant));
17
17
  }
18
- export function operationUnavailableIssue(request) {
18
+ export function unavailableOperationKey(contract, request) {
19
+ if (!isOperationFamily(request.family)) {
20
+ return "family";
21
+ }
22
+ const variants = lookupOwn(lookupResource(contract, request.resource)?.operations, request.family);
23
+ return variants !== undefined &&
24
+ Object.keys(variants).some((variant) => isOperationVariantAvailable(lookupOwn(variants, variant)))
25
+ ? "variant"
26
+ : "family";
27
+ }
28
+ export function operationUnavailableIssue(contract, request) {
19
29
  return {
20
- path: ["operation"],
30
+ path: [unavailableOperationKey(contract, request)],
21
31
  code: "V1006",
22
32
  message: `Operation "${request.family}.${request.variant}" is not available on Resource "${request.resource}".`,
23
33
  };
24
34
  }
35
+ export function findOperationAvailabilityIssue(contract, request) {
36
+ return isOperationAvailable(contract, request)
37
+ ? undefined
38
+ : operationUnavailableIssue(contract, request);
39
+ }
25
40
  function hasGeneralRootWhere(request) {
26
41
  if (!("where" in request.arguments)) {
27
42
  return false;
@@ -33,15 +48,16 @@ function hasGeneralRootWhere(request) {
33
48
  request.family === "upsert"));
34
49
  }
35
50
  export function validateOperationArguments(contract, request, options) {
36
- if (!isOperationAvailable(contract, request)) {
37
- return { ok: false, issues: [operationUnavailableIssue(request)] };
51
+ const unavailable = findOperationAvailabilityIssue(contract, request);
52
+ if (unavailable !== undefined) {
53
+ return { ok: false, issues: [unavailable] };
38
54
  }
39
55
  if (isArgumentObject(request.arguments)) {
40
- const nestingIssue = findOperationNestingIssue(contract, request.resource, request.arguments);
41
- if (nestingIssue !== undefined) {
42
- return { ok: false, issues: [nestingIssue] };
56
+ const finding = findStructuralLimitIssues(contract, request, request.arguments);
57
+ if (finding.nesting !== undefined) {
58
+ return { ok: false, issues: [finding.nesting] };
43
59
  }
44
- const booleanNodeIssue = findOperationBooleanNodeIssue(contract, request, request.arguments);
60
+ const booleanNodeIssue = finding.booleanNodes;
45
61
  if (booleanNodeIssue !== undefined) {
46
62
  return {
47
63
  ok: false,
@@ -7,7 +7,7 @@ import type { OperationIdentity } from "./operation-identity.js";
7
7
  * in, read off the variant the Resource advertises.
8
8
  *
9
9
  * Two readers need the answer and they run one after the other over the same
10
- * request — the `maxBooleanNodes` walk in `operation-boolean-nodes.ts`, then the
10
+ * request — the `maxBooleanNodes` tally in `structural-limits.walker.ts`, then the
11
11
  * grammar walk in `operation-validator-core.ts`. They must agree: a walk that
12
12
  * resolved a wider scope than the validator would count nodes inside a projection
13
13
  * the validator then refuses (harmless), and a walk that resolved a narrower one
@@ -25,15 +25,5 @@ export declare function resolveProjectionScope(resource: Resource<Contract> | un
25
25
  /**
26
26
  * The position a projection NESTED beneath an admitted relation sits in: the
27
27
  * unrestricted one, at every depth, under every top-level scope.
28
- *
29
- * **Measured at runtime on both pilot engines, 2026-10-03** — not inferred from
30
- * the types, which permit more than they accept. A bulk-returning write accepted
31
- * `include: { <owned to-one>: { include: { <to-many>: true } } }` and populated
32
- * the rows, while the same to-many asked for at the top level was refused
33
- * outright. The generated types say why: a bulk projection types an admitted
34
- * relation as the *unrestricted* projection argument, so the full surface returns
35
- * one level in.
36
- *
37
- * Naming it is the point.
38
28
  */
39
29
  export declare const NESTED_PROJECTION_SCOPE: ProjectionScope;
@@ -0,0 +1,19 @@
1
+ import type { Contract } from "../contracts/contract.js";
2
+ import type { OperationIdentity } from "./operation-identity.js";
3
+ import type { ValidationIssue } from "./validation-code.js";
4
+ type Readable = Readonly<Record<string, unknown>>;
5
+ /** What one walk of an operation's arguments found. */
6
+ export type StructuralLimitsFinding = {
7
+ /** The nesting issue, which wins over any boolean-node issue. */
8
+ readonly nesting: ValidationIssue | undefined;
9
+ /** The boolean-node issue, when there is no nesting issue. */
10
+ readonly booleanNodes: ValidationIssue | undefined;
11
+ };
12
+ /**
13
+ * Enforces the structural limits on one operation's argument object: the nesting
14
+ * issue if there is one, otherwise the boolean-node issue if there is one.
15
+ */
16
+ export declare function findStructuralLimitIssues(contract: Contract, request: OperationIdentity & {
17
+ readonly resource: string;
18
+ }, args: Readable): StructuralLimitsFinding;
19
+ export {};