@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
@@ -5,47 +5,46 @@ import type { AdapterOperationExecutor } from "./adapter-operation-executor.inte
5
5
  /** Provider-specific implementation of Aventara discovery and execution. */
6
6
  export interface Adapter<M extends AdapterModel = AdapterModel> extends AdapterOperationExecutor<M> {
7
7
  /**
8
- * Canonical provider-discovery facts, held by the adapter and read
9
- * synchronously at framework construction. Discovery is a property, not a
10
- * call: the model is a settled fact of the constructed adapter, so it
11
- * cannot fail, change, or be observed twice during construction.
8
+ * What the adapter discovered about the data source, in Aventara's vocabulary.
9
+ * Read once, synchronously, when the framework is constructed.
12
10
  */
13
11
  readonly model: M;
14
12
  /**
15
13
  * Optional referential actions: for each Resource that holds a reference,
16
- * what deleting a referenced row does to its rows, in Aventara's words
17
- * (`runtime/referential-action.ts`). Read once at framework construction,
18
- * like `model`, and never part of a Contract or its hash.
14
+ * every relation holding one — the fields that hold it (`references`) and
15
+ * what deleting a referenced row does to its rows. Read once when the
16
+ * framework is constructed; never part of a Contract or its hash.
19
17
  *
20
- * Direction, because getting it backwards is silent: an edge listed under
21
- * Resource `R` says **deleting a `target` row does `onDelete` to rows of
22
- * `R`**.
18
+ * An edge listed under Resource `R` says **deleting a `target` row does
19
+ * `onDelete` to rows of `R`**, and that `R`'s `references` fields hold the
20
+ * relation `field`'s reference.
23
21
  *
24
- * Optional so an Adapter that cannot state them is believed: core then plans
25
- * no cascade, and a transaction plan is never refused for one (`V1019`).
22
+ * When absent, no cascade is planned and no transaction plan is refused
23
+ * because of one (`V1019`); a write that sets a relation and a field holding
24
+ * its reference both is not refused before the store; and a required
25
+ * relation can be satisfied only by writing the relation.
26
26
  */
27
27
  readonly referentialActions?: ReferentialActionTable;
28
28
  /**
29
29
  * Optional create requirements: for each Resource, the values its store
30
- * cannot create a row without, in Aventara's words
31
- * (`runtime/create-requirement.ts`). Read once at framework construction,
32
- * like `model`, and never part of a Contract or its hash.
30
+ * cannot create a row without. Read once when the framework is constructed;
31
+ * never part of a Contract or its hash.
33
32
  *
34
- * Optional so an Adapter that cannot state them is believed: core then checks
35
- * nothing, and a missing value reaches the store as before.
33
+ * When absent, creates are not checked for missing values before they reach
34
+ * the store.
36
35
  */
37
36
  readonly createRequirements?: CreateRequirementTable;
38
37
  /**
39
- * Optional callback transaction capability, required exactly when the discovered
40
- * model declares `transactions: "interactive"`. The callback receives only a
41
- * transaction-scoped operation executor, never the provider client itself, and
42
- * every operation of a plan executes through it.
38
+ * Optional callback transaction capability, required exactly when the
39
+ * discovered model declares `transactions: "interactive"`. The callback
40
+ * receives only a transaction-scoped operation executor, never the provider
41
+ * client itself, and every operation of a plan executes through it.
43
42
  *
44
- * The implementation MUST reject with the exact value `work` threw — by identity,
45
- * never a wrapper — and MUST NOT resolve after `work` rejected; rollback is
46
- * implied by that rejection. Framework guards, pipes, hooks and interceptors of
47
- * every plan operation run inside `work` while the provider transaction is open,
48
- * so they must not perform network I/O or unrelated slow work.
43
+ * The implementation must reject with the exact value `work` threw — by identity,
44
+ * never a wrapper — and must not resolve after `work` rejected; rollback is
45
+ * implied by that rejection. Guards, pipes, hooks and interceptors of every plan
46
+ * operation run inside `work` while the provider transaction is open, so they must
47
+ * not perform network I/O or unrelated slow work.
49
48
  */
50
49
  transaction?<T>(work: (executor: AdapterOperationExecutor<M>) => Promise<T>): Promise<T>;
51
50
  }
@@ -1,4 +1,11 @@
1
+ /** Codes of {@link ContractCompilerDiagnostic}. */
1
2
  export type ContractCompilerDiagnosticCode = "COMPILER_UNKNOWN_RESOURCE" | "COMPILER_UNKNOWN_FIELD" | "COMPILER_RELATION_FIELD_CONFIG_UNSUPPORTED" | "COMPILER_INCOMPLETE_FIELD" | "COMPILER_STRUCTURAL_FIELD_OVERRIDE" | "COMPILER_MISSING_SCALAR" | "COMPILER_MISSING_ENUM" | "COMPILER_DANGLING_RELATION" | "COMPILER_UNAVAILABLE_OPERATION" | "COMPILER_UNAVAILABLE_CAPABILITY" | "COMPILER_INVALID_LIFECYCLE" | "COMPILER_MISSING_READ_BEHAVIOR" | "COMPILER_INVALID_BEHAVIOR_DEPENDENCY"
3
+ /**
4
+ * A field's `json` shape cannot apply: the field is not a Json field the
5
+ * adapter discovered, the value is not a shape built with `AvZ`, or the shape
6
+ * admits `null` or absence at its top level.
7
+ */
8
+ | "COMPILER_INVALID_JSON_SHAPE"
2
9
  /**
3
10
  * The finished, hashed client projection is not a structurally valid
4
11
  * `ClientContract`. A framework-bug guard: the server never serves what its own
@@ -6,6 +13,7 @@ export type ContractCompilerDiagnosticCode = "COMPILER_UNKNOWN_RESOURCE" | "COMP
6
13
  * (`$.enums.status.values[1]`).
7
14
  */
8
15
  | "COMPILER_INVALID_CLIENT_CONTRACT";
16
+ /** One reason a configuration could not be compiled into Contracts. */
9
17
  export type ContractCompilerDiagnostic = {
10
18
  readonly code: ContractCompilerDiagnosticCode;
11
19
  readonly path: string;
@@ -2,11 +2,16 @@ import type { AdapterModel } from "../adapters/adapter-model.js";
2
2
  import type { ResolvedFrameworkConfig } from "../config/config.resolver.js";
3
3
  import type { ApplicationContract, ClientContract, CoreContract } from "../contracts/contract.js";
4
4
  import type { ContractCompilerDiagnostic } from "./contract-compiler-diagnostic.js";
5
+ /** The Core, Application and Client Contracts compiled from one configuration. */
5
6
  export type FrameworkContracts = {
6
7
  readonly core: CoreContract;
7
8
  readonly application: ApplicationContract;
8
9
  readonly client: ClientContract;
9
10
  };
11
+ /**
12
+ * The result of {@link compileContracts}: the three Contracts, or the
13
+ * diagnostics that stopped compilation.
14
+ */
10
15
  export type ContractCompilationResult = {
11
16
  readonly ok: true;
12
17
  readonly contracts: FrameworkContracts;
@@ -16,28 +21,12 @@ export type ContractCompilationResult = {
16
21
  };
17
22
  /**
18
23
  * Whether writing `configured` VERBATIM as capability member `key` would advertise
19
- * something `baseline` does not — the question the `fields` walk asks before it
20
- * copies a value onto an adapter field.
21
- *
22
- * Answered on the member's AXIS, off the same `satisfies`-pinned table the
23
- * restriction walk and the output gate read, and the baseline is read through the
24
- * shape's own owners: {@link holdsAt} for a position list, the `…At` readers for a
25
- * scope-qualified token arm. Never off the runtime shape of the value: an
26
- * object-shaped arm used to match no branch and pass unexamined, and `true` was
27
- * compared to the baseline with `!== true`, which reads a qualified baseline as
28
- * *not advertised* even where it holds in every position.
29
- *
30
- * The unqualified forms keep their meaning: `true` and a bare token list claim
31
- * every position on the axis, so either one over a qualified baseline is an
32
- * addition — writing it verbatim would widen the member.
33
- *
34
- * A key no axis covers, and a value its axis does not admit, are not additions;
35
- * they are unrepresentable, and the output gate ({@link
36
- * validateCapabilityRepresentation}) is the one place that says so. So is a
37
- * position off the axis.
38
- *
39
- * Exported for its unit test only; it is not part of the package surface.
24
+ * something `baseline` does not — the question the `fields` walk asks before it copies
25
+ * a value onto an adapter field.
40
26
  */
41
27
  export declare function capabilityAddsBaseline(fieldKind: "scalar" | "relation", baseline: Readonly<Record<string, unknown>>, key: string, configured: unknown): boolean;
42
- /** Compile Core first, then derive Application and Client from the Core compilation lineage. */
28
+ /**
29
+ * Compiles the Core Contract from the adapter model and the root
30
+ * configuration, then the Application and Client Contracts from it.
31
+ */
43
32
  export declare function compileContracts<M extends AdapterModel>(model: M, config: ResolvedFrameworkConfig<M>): Promise<ContractCompilationResult>;
@@ -6,6 +6,7 @@ import { DEFAULT_CONTRACT_LIMITS, } from "../contracts/contract-limits.js";
6
6
  import { AVENTARA_PROTOCOL_VERSION } from "../contracts/contract-protocol.js";
7
7
  import { advertisedDirectives, advertisedDirectivesAt, advertisedReducers, advertisedReducersAt, holdsAt, holdsInAnyScope, } from "../contracts/field.js";
8
8
  import { isOperationVariantAvailable, } from "../contracts/resource-operations.js";
9
+ import { bindShapedField, jsonShapeOf } from "../json/json-shape.registry.js";
9
10
  const UNIQUE_VARIANTS = [
10
11
  ["find", "unique"],
11
12
  ["update", "unique"],
@@ -99,6 +100,9 @@ function createBaseline(model) {
99
100
  ])),
100
101
  limits: { ...DEFAULT_CONTRACT_LIMITS },
101
102
  transactions: model.transactions,
103
+ ...(model.referenceWrites === undefined
104
+ ? {}
105
+ : { referenceWrites: model.referenceWrites }),
102
106
  };
103
107
  }
104
108
  function scalarFieldPath(scope, resource, field) {
@@ -111,6 +115,10 @@ function structuralValueMatches(field, config, key) {
111
115
  return JSON.stringify(field[key]) === JSON.stringify(config[key]);
112
116
  }
113
117
  function addConfiguredScalarField(config, path, diagnostics) {
118
+ if (config.json !== undefined) {
119
+ refuseJsonShape(path, "is not a field of the adapter model", diagnostics);
120
+ return undefined;
121
+ }
114
122
  const missing = ["type", "nullable", "list"].filter((key) => config[key] === undefined);
115
123
  if (missing.length > 0) {
116
124
  diagnostics.push(diagnostic("COMPILER_INCOMPLETE_FIELD", path, `framework-added scalar field is missing required properties: ${missing.join(", ")}`));
@@ -157,6 +165,8 @@ export function capabilityAddsBaseline(fieldKind, baseline, key, configured) {
157
165
  }
158
166
  const baselineValue = baseline[key];
159
167
  switch (axis.kind) {
168
+ case "limit":
169
+ return baselineValue !== undefined && configured !== baselineValue;
160
170
  case "flag":
161
171
  return configured === true && baselineValue !== true;
162
172
  case "tokens": {
@@ -213,6 +223,31 @@ function advertisedTokensAt(tokenKey, baselineValue, position) {
213
223
  ? advertisedDirectivesAt(baselineValue, position)
214
224
  : advertisedReducersAt(baselineValue, position);
215
225
  }
226
+ function fieldLabel(path) {
227
+ const [, resource, , field] = path.split(".").slice(1);
228
+ return `${resource}.${field}`;
229
+ }
230
+ function refuseJsonShape(path, reason, diagnostics) {
231
+ diagnostics.push(diagnostic("COMPILER_INVALID_JSON_SHAPE", `${path}.json`, `field "${fieldLabel(path)}" ${reason}; only a Json field the adapter discovered may declare a "json" shape`));
232
+ }
233
+ function applyJsonShape(field, baselineField, shape, path, diagnostics) {
234
+ const type = baselineField.type;
235
+ if (type.scalar !== "json") {
236
+ refuseJsonShape(path, `is ${type.scalar === undefined ? "an enum" : `${/^[aeiou]/.test(type.scalar) ? "an" : "a"} ${type.scalar}`} field`, diagnostics);
237
+ return;
238
+ }
239
+ const runtime = jsonShapeOf(shape);
240
+ if (runtime === undefined) {
241
+ diagnostics.push(diagnostic("COMPILER_INVALID_JSON_SHAPE", `${path}.json`, `field "${fieldLabel(path)}": "json" must be a shape built with AvZ from "@aventara/core/json"; a Zod schema or any other value is not accepted`));
242
+ return;
243
+ }
244
+ if (runtime.validate(null).length === 0 ||
245
+ runtime.validate(undefined).length === 0) {
246
+ diagnostics.push(diagnostic("COMPILER_INVALID_JSON_SHAPE", `${path}.json`, `field "${fieldLabel(path)}": a field's shape cannot admit null or absence at its top level; whether the field may be null comes from the adapter model`));
247
+ return;
248
+ }
249
+ field.json = cloneValue(runtime.schema);
250
+ }
216
251
  function applyExistingScalarConfig(field, baselineField, config, path, diagnostics) {
217
252
  for (const key of ["type", "nullable", "list"]) {
218
253
  if (!structuralValueMatches(field, config, key)) {
@@ -222,6 +257,9 @@ function applyExistingScalarConfig(field, baselineField, config, path, diagnosti
222
257
  if (config.format !== undefined) {
223
258
  field.format = config.format;
224
259
  }
260
+ if (config.json !== undefined) {
261
+ applyJsonShape(field, baselineField, config.json, path, diagnostics);
262
+ }
225
263
  if (config.lifecycle !== undefined) {
226
264
  diagnostics.push(diagnostic("COMPILER_INVALID_LIFECYCLE", `${path}.lifecycle`, "existing adapter field lifecycle cannot be redefined by framework field configuration"));
227
265
  }
@@ -243,6 +281,9 @@ function applyFrameworkOwnedScalarConfig(field, config, path, diagnostics) {
243
281
  field[key] = cloneValue(config[key]);
244
282
  }
245
283
  }
284
+ if (config.json !== undefined) {
285
+ refuseJsonShape(path, "is not a field of the adapter model", diagnostics);
286
+ }
246
287
  if (config.format !== undefined) {
247
288
  field.format = config.format;
248
289
  }
@@ -391,6 +432,7 @@ const RELATION_CAPABILITY_AXES = {
391
432
  },
392
433
  create: { kind: "scopedTokens", tokenKey: "directives", axis: WRITE_SCOPES },
393
434
  update: { kind: "scopedTokens", tokenKey: "directives", axis: WRITE_SCOPES },
435
+ nestedWriteFilter: { kind: "limit", values: ["scalar"] },
394
436
  };
395
437
  function capabilityAxisFor(kind, key) {
396
438
  const axes = kind === "scalar" ? SCALAR_CAPABILITY_AXES : RELATION_CAPABILITY_AXES;
@@ -478,6 +520,10 @@ function applyScopedTokenRestriction(tokenKey, axis, baselineValue, current, key
478
520
  return true;
479
521
  }
480
522
  function applyCapabilityRestriction(fieldKind, inherited, restoration, current, key, value, path, diagnostics) {
523
+ if (capabilityAxisFor(fieldKind, key)?.kind === "limit") {
524
+ unavailableCapability(path, `capability "${key}" is an adapter limit and takes no restriction`, diagnostics);
525
+ return;
526
+ }
481
527
  if (value === false) {
482
528
  delete current[key];
483
529
  return;
@@ -728,6 +774,10 @@ function positionListFault(axis, positions) {
728
774
  }
729
775
  function representationFault(axis, value) {
730
776
  switch (axis.kind) {
777
+ case "limit":
778
+ return typeof value === "string" && axis.values.includes(value)
779
+ ? undefined
780
+ : `one of ${axis.values.join(", ")} is expected`;
731
781
  case "flag":
732
782
  return typeof value === "boolean" ? undefined : "a boolean is expected";
733
783
  case "tokens":
@@ -827,6 +877,20 @@ function projectDerivedScope(model, scopeName, core, scope, diagnostics) {
827
877
  validateProjection(projection, baseline, scopeName, scope.restrictions, diagnostics);
828
878
  return projection;
829
879
  }
880
+ function bindShapedFields(contract, fields) {
881
+ for (const [resourceName, resource] of Object.entries(contract.resources)) {
882
+ for (const [fieldName, field] of Object.entries(resource.fields)) {
883
+ if (field.kind !== "scalar" || field.json === undefined) {
884
+ continue;
885
+ }
886
+ const shape = jsonShapeOf(fields?.[resourceName]?.[fieldName]?.json);
887
+ if (shape === undefined) {
888
+ throw new Error(`the Contract compiler found no shape for shaped field "${resourceName}.${fieldName}"`);
889
+ }
890
+ bindShapedField(field, shape);
891
+ }
892
+ }
893
+ }
830
894
  export async function compileContracts(model, config) {
831
895
  const compilerConfig = config;
832
896
  const diagnostics = [];
@@ -856,6 +920,9 @@ export async function compileContracts(model, config) {
856
920
  ],
857
921
  };
858
922
  }
923
+ bindShapedFields(core, compilerConfig.root.fields);
924
+ bindShapedFields(application, compilerConfig.application.fields);
925
+ bindShapedFields(client, compilerConfig.client.fields);
859
926
  return {
860
927
  ok: true,
861
928
  contracts: { core, application, client },
@@ -1,11 +1,4 @@
1
- /**
2
- * A `fields` block whose resource and field names are both unconstrained.
3
- *
4
- * The `undefined` in the value position is required, not incidental: under
5
- * `exactOptionalPropertyTypes` a real `fields` block is a `Partial` record,
6
- * so its values include `undefined` and it would not otherwise be assignable
7
- * to the widest block.
8
- */
1
+ /** A `fields` block whose resource and field names are both unconstrained. */
9
2
  export type AnyFieldsBlock = Readonly<Record<string, Readonly<Record<string, unknown>> | undefined>>;
10
3
  /** The widest scope overlay: a `fields` block, and anything else. */
11
4
  export type AnyScopeConfig = {
@@ -13,24 +6,10 @@ export type AnyScopeConfig = {
13
6
  readonly fields?: AnyFieldsBlock;
14
7
  };
15
8
  /**
16
- * The widest authoring configuration: every resource admits every field name.
17
- *
18
- * This is the default argument of the `Cfg` parameter threaded through
19
- * `FrameworkScopeConfig`, `FrameworkConfig`, `Framework` and
20
- * `createFramework`. Because it constrains nothing, a `FrameworkConfig<M>`
21
- * written without a `Cfg` argument behaves exactly as it did before `Cfg`
22
- * existed: narrowing below the resource key is opt-in, never imposed.
23
- *
24
- * `Cfg` is the authored configuration literal itself, not a shape the
25
- * framework validates. Its only job is to carry the field names the
26
- * configuration adds — at the root layer and at each scope overlay — so that
27
- * a resource's effective field-key set is computable from the adapter model
28
- * plus the configuration alone, and never from a compiled Contract.
29
- *
30
- * The index signature is what makes "widest" true. Without it this is a weak
31
- * type, and a configuration that happens to declare no `fields`,
32
- * `application` or `client` would have no property in common with it and be
33
- * rejected — the opposite of the intended default.
9
+ * The widest configuration type: every resource admits every field name. It
10
+ * is the default `Cfg` of `FrameworkConfig`, `Framework` and
11
+ * `createFramework`; pass your own configuration's type instead to get field
12
+ * names checked.
34
13
  */
35
14
  export type AnyFrameworkConfig = {
36
15
  readonly [key: string]: unknown;
@@ -1,4 +1,6 @@
1
+ /** Codes of {@link ConfigDiagnostic}. */
1
2
  export type ConfigDiagnosticCode = "CONFIG_REQUIRED" | "CONFIG_UNKNOWN_PROPERTY" | "CONFIG_INVALID_TYPE" | "CONFIG_INVALID_VALUE";
3
+ /** One problem found in a configuration, at its path. */
2
4
  export type ConfigDiagnostic = {
3
5
  readonly code: ConfigDiagnosticCode;
4
6
  readonly path: string;
@@ -1,25 +1,12 @@
1
1
  /**
2
- * One layer of the configuration tree the framework is built on: `root` is the
3
- * configuration's own top level and compiles to the Core Contract; `application`
4
- * and `client` are the overlays resolved on top of it and compile to the
5
- * Application and Client Contracts.
6
- *
7
- * The one source of truth for the layer set. {@link RequestOrigin} is derived
8
- * from it rather than declared beside it, so the two cannot drift.
9
- *
10
- * Deliberately not named `…Scope`: that word already carries several unrelated
11
- * meanings in this package (the framework scope configuration, the pipeline
12
- * `scope`, and the capability positions `ProjectionScope`/`WriteScope`), and a
13
- * layer of the configuration tree is none of them.
2
+ * One layer of the configuration: `root` is the configuration's own top level
3
+ * and compiles to the Core Contract; `application` and `client` are overlays
4
+ * on top of it and compile to the Application and Client Contracts.
14
5
  */
15
6
  export type ConfigLayer = "root" | "application" | "client";
16
7
  /**
17
- * The layers a request can originate at: every {@link ConfigLayer} except
18
- * `root`. `root` is a real layer for the compiler — the Core Contract every
19
- * other layer derives from — but no request is ever made against it, so it is
20
- * not an origin.
21
- *
22
- * It is also the set of overlays a configuration may declare, because each
23
- * origin sees exactly the root layer plus its own overlay.
8
+ * The layers a request can come from: `application` (in-process calls) or
9
+ * `client` (remote calls). Each sees the root configuration plus its own
10
+ * overlay.
24
11
  */
25
12
  export type RequestOrigin = Exclude<ConfigLayer, "root">;
@@ -2,39 +2,34 @@ import type { Adapter } from "../adapters/adapter.interface.js";
2
2
  import type { AdapterModel } from "../adapters/adapter-model.js";
3
3
  import type { TransactionCapability } from "../contracts/transaction-capability.js";
4
4
  import type { FrameworkDiagnosticsSink } from "../runtime/diagnostics-reporter.js";
5
+ import type { FrameworkMisuseSink } from "../runtime/misuse-reporter.js";
5
6
  import type { FrameworkBehaviorsConfig } from "./framework-behaviors-config.js";
6
7
  import type { FrameworkConfig, FrameworkLimitsConfig } from "./framework-config.js";
7
8
  import type { FrameworkFieldsConfig } from "./framework-fields-config.js";
8
9
  import type { FrameworkRestrictionsConfig } from "./framework-restrictions-config.js";
9
10
  import type { ResolvedPipelineConfig } from "./pipeline-config.js";
11
+ /** One resolved layer of the configuration: root, application or client. */
10
12
  export type ResolvedFrameworkScopeConfig<M extends AdapterModel = AdapterModel> = {
11
13
  readonly fields: FrameworkFieldsConfig<M> | undefined;
12
- /**
13
- * The **erased** behaviors view, deliberately not `FrameworkBehaviorsConfig<M>`.
14
- *
15
- * A compute context carries `resource: R`, so a behavior bound to one
16
- * Resource is a callback that accepts only that Resource's name. That makes
17
- * `FrameworkBehaviorsConfig<M>` contravariant in the Resource key and *not*
18
- * a subtype of the widest instantiation — correctly so. The runtime dispatches
19
- * by string key and can never recover which literal it holds, so it consumes
20
- * behaviors erased, exactly as it consumes the compiled Contract: narrowing is
21
- * an authoring-time affordance, and resolution is the boundary where it ends.
22
- */
14
+ /** The behaviors configuration, with Resource and field keys as plain strings. */
23
15
  readonly behaviors: FrameworkBehaviorsConfig | undefined;
24
16
  readonly restrictions: FrameworkRestrictionsConfig<M> | undefined;
25
17
  readonly limits: FrameworkLimitsConfig | undefined;
26
18
  readonly pipelines: ResolvedPipelineConfig;
27
19
  readonly transactions: TransactionCapability | undefined;
28
20
  };
21
+ /** A configuration after defaults and overlays are resolved, before compilation. */
29
22
  export type ResolvedFrameworkConfig<M extends AdapterModel = AdapterModel> = {
30
23
  /**
31
- * The mount path in its canonical form: `""` at the root, otherwise `/seg(/seg)*`.
32
- * Normalized once, here; every reader sees this.
24
+ * The mount path in its canonical form: `""` at the root, otherwise
25
+ * `/seg(/seg)*`.
33
26
  */
34
27
  readonly entrypoint: string;
35
28
  readonly adapter: Adapter<M>;
36
- /** Root-only, never scoped: the framework owns this channel. */
29
+ /** The diagnostics sink, from the root configuration only. */
37
30
  readonly diagnostics: FrameworkDiagnosticsSink | undefined;
31
+ /** The misuse sink, from the root configuration only. */
32
+ readonly misuse: FrameworkMisuseSink | undefined;
38
33
  readonly root: ResolvedFrameworkScopeConfig<M>;
39
34
  readonly application: ResolvedFrameworkScopeConfig<M>;
40
35
  readonly client: ResolvedFrameworkScopeConfig<M>;
@@ -103,6 +103,7 @@ export function resolveFrameworkConfig(config) {
103
103
  entrypoint: resolveEntrypoint(config.entrypoint),
104
104
  adapter: config.adapter,
105
105
  diagnostics: config.diagnostics,
106
+ misuse: config.misuse,
106
107
  root: resolveScope(root),
107
108
  application: resolveScope(root, config.application),
108
109
  client: resolveScope(root, config.client),
@@ -1,9 +1,12 @@
1
1
  import type { ConfigDiagnostic } from "./config-diagnostic.js";
2
2
  /**
3
3
  * The refusal of an entrypoint whose mount path is not canonical, from the
4
- * normalizer's message: it names the defect and the rule — never the value, which
5
- * may carry credentials.
4
+ * normalizer's message: it names the defect and the rule — never the value, which may
5
+ * carry credentials.
6
6
  */
7
7
  export declare function entrypointRefusalDiagnostic(message: string): ConfigDiagnostic;
8
- /** Validate framework-owned authoring structure; later semantic leaves remain opaque. */
8
+ /**
9
+ * Checks the structure of a configuration object and reports each problem as
10
+ * a {@link ConfigDiagnostic}.
11
+ */
9
12
  export declare function validateFrameworkConfig(value: unknown): readonly ConfigDiagnostic[];
@@ -10,6 +10,7 @@ const ROOT_PROPERTIES = new Set([
10
10
  "pipelines",
11
11
  "transactions",
12
12
  "diagnostics",
13
+ "misuse",
13
14
  "application",
14
15
  "client",
15
16
  ]);
@@ -206,6 +207,11 @@ export function validateFrameworkConfig(value) {
206
207
  typeof value.diagnostics !== "function") {
207
208
  diagnostics.push(diagnostic("CONFIG_INVALID_TYPE", "diagnostics", "diagnostics must be a function when provided"));
208
209
  }
210
+ if (Object.hasOwn(value, "misuse") &&
211
+ value.misuse !== undefined &&
212
+ typeof value.misuse !== "function") {
213
+ diagnostics.push(diagnostic("CONFIG_INVALID_TYPE", "misuse", "misuse must be a function when provided"));
214
+ }
209
215
  if (Object.hasOwn(value, "application")) {
210
216
  validateScope(value.application, "application", diagnostics);
211
217
  }
@@ -2,55 +2,33 @@ import type { AdapterModel } from "../adapters/adapter-model.js";
2
2
  import type { AdapterFieldKey } from "../adapters/adapter-resource.js";
3
3
  import type { FrameworkResourceKey, FrameworkScalarFieldConfig, FrameworkScalarTypeRef } from "./framework-fields-config.js";
4
4
  /**
5
- * What an author may still say about a field the adapter already discovered.
6
- *
7
- * Everything in {@link FrameworkScalarFieldConfig} except `type`: the adapter
8
- * owns a discovered field's scalar/enum identity, so restating it here would
9
- * create a second source of truth for a fact discovery already settled.
10
- * `type?: never` rather than an omission so the rejection is a pointed
11
- * assignability error at the offending property, not a whole-object one.
5
+ * What a configuration may say about a field the adapter already discovered:
6
+ * everything in {@link FrameworkScalarFieldConfig} except `type`.
12
7
  */
13
8
  export type KnownScalarFieldConfig<M extends AdapterModel = AdapterModel> = Omit<FrameworkScalarFieldConfig<M>, "type"> & {
14
9
  readonly type?: never;
15
10
  };
16
11
  /**
17
- * A scalar field the configuration adds, which the adapter never discovered.
18
- *
19
- * Nothing upstream can supply the structural leaves for a name that exists
20
- * nowhere in the provider, so `type`, `nullable` and `list` are required
21
- * here while they stay optional on the augmentation shape.
12
+ * A scalar field the configuration adds, which the adapter did not discover.
13
+ * `type`, `nullable` and `list` are required; it takes no `json` shape.
22
14
  */
23
- export type AddedScalarFieldConfig<M extends AdapterModel = AdapterModel> = Omit<FrameworkScalarFieldConfig<M>, "type" | "nullable" | "list"> & {
15
+ export type AddedScalarFieldConfig<M extends AdapterModel = AdapterModel> = Omit<FrameworkScalarFieldConfig<M>, "type" | "nullable" | "list" | "json"> & {
24
16
  readonly type: FrameworkScalarTypeRef<M>;
25
17
  readonly nullable: boolean;
26
18
  readonly list: boolean;
19
+ readonly json?: never;
27
20
  };
28
- /**
29
- * The shape one authored field name admits, by what the adapter knows of it.
30
- *
31
- * Where there is no discovered key set to discriminate on, the answer is today's
32
- * undivided {@link FrameworkScalarFieldConfig}.
33
- */
21
+ /** The shape one authored field name admits, by what the adapter knows of it. */
34
22
  type ScalarFieldConfigFor<M extends AdapterModel, R extends FrameworkResourceKey<M>, F> = string extends AdapterFieldKey<M, R> ? FrameworkScalarFieldConfig<M> : F extends AdapterFieldKey<M, R> ? M["resources"][R]["fields"][F] extends {
35
23
  readonly kind: "relation";
36
- } ? never : KnownScalarFieldConfig<M> : AddedScalarFieldConfig<M>;
37
- /**
38
- * The constraint an authored `fields` block is checked against, keyed on the
39
- * block's **own** names.
40
- *
41
- * `Self` is the literal being checked, which is why {@link defineFields} is
42
- * F-bounded: a `satisfies FrameworkFieldsConfig<M>` annotation maps the
43
- * value shape over an open key set, which collapses to a single index
44
- * signature, and an index signature cannot tell a discovered name from an
45
- * added one. The three buckets are only reachable from a mapped type keyed
46
- * on `keyof Self`.
47
- *
48
- * The self-reference is legal here despite the workstream's stopping rule 3
49
- * because the `fields` block carries no callbacks: nothing inside it needs
50
- * contextual typing, so there is no inference to collapse. A callback
51
- * appearing in this block is the signal that this encoding has stopped
52
- * being safe.
53
- */
24
+ } ? never : M["resources"][R]["fields"][F] extends {
25
+ readonly type: {
26
+ readonly scalar: "json";
27
+ };
28
+ } ? KnownScalarFieldConfig<M> : Omit<KnownScalarFieldConfig<M>, "json"> & {
29
+ readonly json?: never;
30
+ } : AddedScalarFieldConfig<M>;
31
+ /** The shape an authored `fields` block is checked against, keyed on its own names. */
54
32
  export type FrameworkFieldsShape<M extends AdapterModel, Self> = {
55
33
  readonly [R in keyof Self]: R extends FrameworkResourceKey<M> ? {
56
34
  readonly [F in keyof Self[R]]: ScalarFieldConfigFor<M, R, F>;
@@ -59,17 +37,6 @@ export type FrameworkFieldsShape<M extends AdapterModel, Self> = {
59
37
  /**
60
38
  * Authors stage 1 of a configuration: the `fields` block, checked against
61
39
  * what the adapter discovered and returned unwidened.
62
- *
63
- * The returned literal is stage 2's `Cfg` fields block, so the names it adds
64
- * are nameable from `restrictions`, `behaviors` and `dependsOn`.
65
- *
66
- * `M` is bound by the **model value**, not a type argument: TypeScript has
67
- * no partial type-argument inference, so `defineFields<M>({ … })` would
68
- * force `Self` to be written out by hand. The model is already a value at
69
- * the authoring site, so binding it there costs nothing.
70
- *
71
- * The model argument is type-level only — the returned block is the authored
72
- * one, unchanged.
73
40
  */
74
41
  export declare function defineFields<M extends AdapterModel, const Self extends FrameworkFieldsShape<M, Self>>(_model: M, fields: Self): Self;
75
42
  export {};
@@ -13,41 +13,18 @@ import type { FrameworkResourceKey } from "./framework-fields-config.js";
13
13
  type FieldsBlockOf<Layer> = Layer extends {
14
14
  readonly fields?: infer Block;
15
15
  } ? Block extends AnyFieldsBlock ? Block : never : never;
16
- /**
17
- * The field names a single `fields` block declares for `R`.
18
- *
19
- * The `[Block] extends [never]` guard is load-bearing, not defensive: `keyof
20
- * never` is `string | number | symbol`, so a layer with no `fields` block
21
- * would otherwise widen the whole effective key set back to `string` and
22
- * silently erase every narrowing downstream of it.
23
- */
16
+ /** The field names a single `fields` block declares for `R`. */
24
17
  type FieldsBlockKey<Block, R extends string> = [Block] extends [never] ? never : R extends keyof Block ? Extract<keyof NonNullable<Block[R]>, string> : never;
25
- /**
26
- * The field names `Cfg`'s own root `fields` block adds for `R`.
27
- *
28
- * Under the default `Cfg` this degenerates to `string`: `AnyFieldsBlock` maps
29
- * every resource name to an open record, so the effective key set widens back to
30
- * an index-signature-compatible `string` and today's wide acceptance is preserved.
31
- */
18
+ /** The field names the configuration's root `fields` block adds to Resource `R`. */
32
19
  export type ConfigFieldKey<M extends AdapterModel, Cfg, R extends FrameworkResourceKey<M>> = FieldsBlockKey<FieldsBlockOf<Cfg>, R>;
33
- /**
34
- * The single answer to "what are `R`'s field names under this configuration".
35
- *
36
- * Nothing else in the framework may compute a field-key set; `restrictions`,
37
- * `behaviors` and `dependsOn` all key on this type.
38
- *
39
- * There is deliberately no subtraction here. A `HiddenFieldKey` subtraction would
40
- * contradict the Accepted ADR and must not be reintroduced.
41
- */
20
+ /** The single answer to "what are `R`'s field names under this configuration". */
42
21
  export type EffectiveFieldKey<M extends AdapterModel, Cfg, R extends FrameworkResourceKey<M>> = AdapterFieldKey<M, R> | ConfigFieldKey<M, Cfg, R>;
43
22
  /** The overlay `Cfg` carries for `Origin`, or `never` when it declares none. */
44
23
  type OriginOverlayOf<Cfg, Origin extends RequestOrigin> = Origin extends keyof Cfg ? NonNullable<Cfg[Origin]> : never;
45
24
  /**
46
- * `R`'s effective field names as seen from one request origin: the root layer
47
- * plus whatever the `application` or `client` overlay adds on top of it.
48
- *
49
- * Origins are siblings, never parents: an overlay contributes to its own origin
50
- * only, so a name added under `client` is not visible under `application`.
25
+ * `R`'s field names as seen from one request origin: the root layer plus
26
+ * whatever the `application` or `client` overlay adds. A name added under
27
+ * `client` is not visible under `application`, and the other way round.
51
28
  */
52
29
  export type EffectiveFieldKeyAt<M extends AdapterModel, Cfg, R extends FrameworkResourceKey<M>, Origin extends RequestOrigin> = EffectiveFieldKey<M, Cfg, R> | FieldsBlockKey<FieldsBlockOf<OriginOverlayOf<Cfg, Origin>>, R>;
53
30
  export {};