@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
@@ -0,0 +1,65 @@
1
+ import { parseOperationIdentity } from "../operations/operation-identity.js";
2
+ import { validationIssue } from "../operations/validation-code.js";
3
+ import { CallerDataError, copyCallerData, } from "./caller-data.copier.js";
4
+ export function misuseIdentityOf(request) {
5
+ const origin = request.origin === "application" || request.origin === "client"
6
+ ? request.origin
7
+ : undefined;
8
+ const identity = typeof request.family === "string" && typeof request.variant === "string"
9
+ ? parseOperationIdentity(request.family, request.variant)
10
+ : undefined;
11
+ return {
12
+ ...(origin === undefined ? {} : { origin }),
13
+ ...(typeof request.resource === "string"
14
+ ? { resource: request.resource }
15
+ : {}),
16
+ ...(identity === undefined
17
+ ? {}
18
+ : { family: identity.family, variant: identity.variant }),
19
+ };
20
+ }
21
+ export function refuseCallerData(error, address, identity, misuse) {
22
+ const path = [...address, ...error.path];
23
+ misuse({ kind: error.kind, path, ...identity });
24
+ return validationIssue("V1001", path, error.message);
25
+ }
26
+ export function copyAtDoor(value, policy) {
27
+ try {
28
+ return { ok: true, value: copyCallerData(value, policy) };
29
+ }
30
+ catch (error) {
31
+ if (error instanceof CallerDataError) {
32
+ return { ok: false, error };
33
+ }
34
+ throw error;
35
+ }
36
+ }
37
+ export function copyEnvelopeAtDoor(request, members) {
38
+ const shallow = copyAtDoor(request, {
39
+ keep: (value) => value !== request,
40
+ });
41
+ if (!shallow.ok ||
42
+ typeof shallow.value !== "object" ||
43
+ shallow.value === null ||
44
+ Array.isArray(shallow.value)) {
45
+ return shallow;
46
+ }
47
+ const envelope = shallow.value;
48
+ for (const member of members) {
49
+ if (!Object.hasOwn(envelope, member)) {
50
+ continue;
51
+ }
52
+ const copy = copyAtDoor(envelope[member]);
53
+ if (!copy.ok) {
54
+ return {
55
+ ok: false,
56
+ error: new CallerDataError(copy.error.kind, [
57
+ member,
58
+ ...copy.error.path,
59
+ ]),
60
+ };
61
+ }
62
+ envelope[member] = copy.value;
63
+ }
64
+ return { ok: true, value: envelope };
65
+ }
@@ -1,6 +1,5 @@
1
1
  import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
2
- import { FrameworkError } from "./framework-error.js";
3
- import { INTERNAL_FAILURE_MESSAGE } from "./operation-response.js";
2
+ import { ComputedValueError } from "./computed-value.error.js";
4
3
  import { normalizeScalarFieldValue } from "./scalar-runtime.js";
5
4
  function isRecord(value) {
6
5
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -274,12 +273,6 @@ export function planReadOperation(core, active, behaviors, request, args) {
274
273
  materialize: defaults.plan.computed.length > 0,
275
274
  };
276
275
  }
277
- function behaviorFailure(issues) {
278
- throw new FrameworkError("A3000", {
279
- message: INTERNAL_FAILURE_MESSAGE,
280
- issues,
281
- });
282
- }
283
276
  async function materializeRelation(active, behaviors, request, relation, value, contextFactory) {
284
277
  if (value === null || value === undefined) {
285
278
  return value;
@@ -308,7 +301,7 @@ async function materializeRecord(active, behaviors, request, plan, value, contex
308
301
  continue;
309
302
  }
310
303
  const computedValue = await behavior.compute({
311
- scope: request.scope,
304
+ origin: request.origin,
312
305
  resource: plan.resource,
313
306
  field: fieldName,
314
307
  value: record[fieldName],
@@ -320,7 +313,7 @@ async function materializeRecord(active, behaviors, request, plan, value, contex
320
313
  }
321
314
  const validation = normalizeScalarFieldValue(active, field, computedValue, "runtime", ["behaviors", plan.resource, fieldName, "read"]);
322
315
  if (!validation.ok) {
323
- behaviorFailure(validation.issues);
316
+ throw new ComputedValueError(plan.resource, fieldName, "read", validation.issues);
324
317
  }
325
318
  record[fieldName] = validation.value;
326
319
  }
@@ -0,0 +1,13 @@
1
+ import type { ValidationIssue } from "../operations/validation-code.js";
2
+ /**
3
+ * What the diagnostics channel receives when a computed value — a read or write
4
+ * behavior's return — is one its Contract refuses: the developer's broken code,
5
+ * never the caller's. It is thrown, not a deliberate outcome, so the framework's
6
+ * boundary answers the caller the generic `A3000` and reports this error, with the
7
+ * issues, exactly once.
8
+ */
9
+ export declare class ComputedValueError extends Error {
10
+ readonly issues: readonly ValidationIssue[];
11
+ readonly name = "ComputedValueError";
12
+ constructor(resource: string, field: string, stage: "read" | "create" | "update", issues: readonly ValidationIssue[]);
13
+ }
@@ -0,0 +1,9 @@
1
+ export class ComputedValueError extends Error {
2
+ issues;
3
+ name = "ComputedValueError";
4
+ constructor(resource, field, stage, issues) {
5
+ const [first] = issues;
6
+ super(`The ${stage} behavior of ${resource}.${field} returned a value its Contract refuses${first === undefined ? "." : ` (${first.code}: ${first.message})`}`);
7
+ this.issues = issues;
8
+ }
9
+ }
@@ -1,6 +1,7 @@
1
1
  import type { Contract } from "../contracts/contract.js";
2
2
  import type { OperationIdentity } from "../operations/operation-identity.js";
3
3
  import type { ValidationIssue } from "../operations/validation-code.js";
4
+ import type { ReferentialActionTable } from "./referential-action.js";
4
5
  /**
5
6
  * Core's create-requirement vocabulary, and the check that reads it.
6
7
  *
@@ -22,6 +23,24 @@ import type { ValidationIssue } from "../operations/validation-code.js";
22
23
  * which reaches the caller as an internal failure. A missing value is the
23
24
  * caller's to fix, so it answers `A2004` with a `V1000` issue at the path where
24
25
  * the value belongs.
26
+ *
27
+ * **One value, two spellings.** A relation that holds a reference has scalar
28
+ * fields that hold it too — the Adapter states which, on the relation's
29
+ * referential edge (`ReferentialEdge.references`), for every such relation,
30
+ * required or optional. A write that sets both — `authorId` and
31
+ * `author: { $connect: … }` in one record, at create or update — names the
32
+ * reference twice. No store can honour both, and which one the caller meant is
33
+ * not the framework's to guess, so it is the caller's `A2004` too: a `V1012`
34
+ * issue at the relation, in every record the check walks, before any Adapter
35
+ * work. The same edge is what a required relation may instead be written
36
+ * through, so the pairing has one source.
37
+ *
38
+ * **One store, one spelling per record.** A store may take a record's
39
+ * references only one way at a time — every relation that holds one written as
40
+ * a relation, or every one through its fields — and state it as the Contract's
41
+ * `referenceWrites: "uniform"`. Under it, a record that writes a relation
42
+ * holding a reference beside another relation's reference field is the caller's
43
+ * `A2004` as well: a `V1012` issue at the relation write.
25
44
  */
26
45
  /** A relation edge, named by the Resource that holds it and its field there. */
27
46
  export type CreateRequirementEdge = {
@@ -34,9 +53,11 @@ export type CreateRequirementEdge = {
34
53
  * - `field` — a scalar with no default, generated value or other source: it
35
54
  * must be written.
36
55
  * - `relation` — a required to-one relation: it must be written (connected or
37
- * created), or every one of `orFields` must be — the scalar fields that hold
38
- * the reference. When the row is created **through** the relation's other end
39
- * (`inverse`), the parent supplies it, and it is not required.
56
+ * created), or every field that holds its reference must be — the
57
+ * `references` of the relation's referential edge, less any the store fills
58
+ * itself (`DEFAULTED` or `GENERATED_ON_CREATE`). When the row is created
59
+ * **through** the relation's other end (`inverse`), the parent supplies it,
60
+ * and it is not required.
40
61
  */
41
62
  export type CreateRequirement = {
42
63
  readonly kind: "field";
@@ -44,7 +65,6 @@ export type CreateRequirement = {
44
65
  } | {
45
66
  readonly kind: "relation";
46
67
  readonly relation: string;
47
- readonly orFields: readonly string[];
48
68
  readonly inverse?: CreateRequirementEdge;
49
69
  };
50
70
  /**
@@ -53,13 +73,29 @@ export type CreateRequirement = {
53
73
  */
54
74
  export type CreateRequirementTable = Readonly<Record<string, readonly CreateRequirement[]>>;
55
75
  type Path = readonly (string | number)[];
76
+ /**
77
+ * What the Adapter stated that the check reads: its create requirements, and
78
+ * its referential edges for the fields that hold each reference. Read once when
79
+ * the Framework is constructed.
80
+ */
81
+ export type RecordWriteFacts = {
82
+ readonly requirements: CreateRequirementTable;
83
+ readonly references: ReferentialActionTable;
84
+ };
85
+ /** The facts an Adapter stated, read off it once; an absent table states nothing. */
86
+ export declare function recordWriteFactsOf(adapter: {
87
+ readonly createRequirements?: CreateRequirementTable;
88
+ readonly referentialActions?: ReferentialActionTable;
89
+ }): RecordWriteFacts;
56
90
  /**
57
91
  * The `V1000` issues for every create position in one operation's arguments
58
92
  * that lacks a value the Adapter requires — `create.*`'s `data`, `upsert`'s
59
- * `create`, and every nested create under any write — in argument order.
60
- * Empty when nothing is missing. Total over malformed input.
93
+ * `create`, and every nested create under any write — and the `V1012` issues
94
+ * for every write record, create or update, that sets a relation and its
95
+ * reference both, in argument order. Empty when there are none. Total over
96
+ * malformed input.
61
97
  */
62
- export declare function missingCreateValues(contract: Contract, table: CreateRequirementTable, request: OperationIdentity & {
98
+ export declare function createRequirementIssues(contract: Contract, facts: RecordWriteFacts, request: OperationIdentity & {
63
99
  readonly resource: string;
64
100
  }, args: unknown, argumentsPath: Path): readonly ValidationIssue[];
65
101
  export {};
@@ -12,6 +12,23 @@ function requirementsOf(table, resource) {
12
12
  const listed = Object.hasOwn(table, resource) ? table[resource] : undefined;
13
13
  return Array.isArray(listed) ? listed : [];
14
14
  }
15
+ function edgesOf(table, resource) {
16
+ const listed = Object.hasOwn(table, resource) ? table[resource] : undefined;
17
+ return Array.isArray(listed) ? listed : [];
18
+ }
19
+ function referencesOf(edge) {
20
+ return Array.isArray(edge.references) ? edge.references : [];
21
+ }
22
+ function referenceFieldsOf(table, resource, relation) {
23
+ const edge = edgesOf(table, resource).find((candidate) => candidate.field === relation);
24
+ return edge === undefined ? [] : referencesOf(edge);
25
+ }
26
+ function storeSupplied(contract, resource, field) {
27
+ const found = lookupField(lookupResource(contract, resource), field);
28
+ return (found?.kind === "scalar" &&
29
+ (found.lifecycle.includes("DEFAULTED") ||
30
+ found.lifecycle.includes("GENERATED_ON_CREATE")));
31
+ }
15
32
  function creatableScalar(contract, resource, field) {
16
33
  const found = lookupField(lookupResource(contract, resource), field);
17
34
  return found?.kind === "scalar" && found.capabilities.create !== undefined;
@@ -26,7 +43,9 @@ function checkCreateRecord(context, resource, record, path, scope, via) {
26
43
  return;
27
44
  }
28
45
  const { contract, issues } = context;
29
- for (const requirement of requirementsOf(context.table, resource)) {
46
+ const conflicted = referenceConflicts(context, resource, record, path);
47
+ mixedReferenceWrites(context, resource, record, path, conflicted);
48
+ for (const requirement of requirementsOf(context.requirements, resource)) {
30
49
  if (requirement.kind === "field") {
31
50
  if (!written(record, requirement.field) &&
32
51
  creatableScalar(contract, resource, requirement.field)) {
@@ -38,14 +57,15 @@ function checkCreateRecord(context, resource, record, path, scope, via) {
38
57
  }
39
58
  continue;
40
59
  }
60
+ if (conflicted.has(requirement.relation)) {
61
+ continue;
62
+ }
41
63
  if (via !== undefined &&
42
64
  requirement.inverse?.resource === via.resource &&
43
65
  requirement.inverse.relation === via.relation) {
44
66
  continue;
45
67
  }
46
- const orFields = Array.isArray(requirement.orFields)
47
- ? requirement.orFields
48
- : [];
68
+ const orFields = referenceFieldsOf(context.references, resource, requirement.relation).filter((field) => !storeSupplied(contract, resource, field));
49
69
  if (written(record, requirement.relation) ||
50
70
  (orFields.length > 0 && orFields.every((field) => written(record, field)))) {
51
71
  continue;
@@ -69,6 +89,55 @@ function checkCreateRecord(context, resource, record, path, scope, via) {
69
89
  }
70
90
  walkNestedCreates(context, resource, record, path);
71
91
  }
92
+ function referenceConflicts(context, resource, record, path) {
93
+ const conflicted = new Set();
94
+ for (const edge of edgesOf(context.references, resource)) {
95
+ if (!written(record, edge.field)) {
96
+ continue;
97
+ }
98
+ const fields = referencesOf(edge).filter((field) => written(record, field));
99
+ if (fields.length === 0) {
100
+ continue;
101
+ }
102
+ conflicted.add(edge.field);
103
+ context.issues.push({
104
+ code: "V1012",
105
+ path: [...path, edge.field],
106
+ message: `Set ${fields.map((field) => `"${field}"`).join(" and ")} or "${edge.field}", not both.`,
107
+ });
108
+ }
109
+ return conflicted;
110
+ }
111
+ function mixedReferenceWrites(context, resource, record, path, conflicted) {
112
+ if (context.contract.referenceWrites !== "uniform") {
113
+ return;
114
+ }
115
+ const edges = edgesOf(context.references, resource);
116
+ const referenceFields = [...new Set(edges.flatMap(referencesOf))];
117
+ for (const edge of edges) {
118
+ if (!written(record, edge.field) || conflicted.has(edge.field)) {
119
+ continue;
120
+ }
121
+ const fields = referenceFields.filter((field) => written(record, field));
122
+ if (fields.length === 0) {
123
+ continue;
124
+ }
125
+ const named = fields.map((field) => `"${field}"`).join(" and ");
126
+ context.issues.push({
127
+ code: "V1012",
128
+ path: [...path, edge.field],
129
+ message: `Use relation fields only, or reference fields only, in one record: ${named} ${fields.length === 1 ? "is a reference field" : "are reference fields"} and "${edge.field}" is a relation.`,
130
+ });
131
+ }
132
+ }
133
+ function checkUpdateRecord(context, resource, record, path) {
134
+ if (!isRecord(record)) {
135
+ return;
136
+ }
137
+ const conflicted = referenceConflicts(context, resource, record, path);
138
+ mixedReferenceWrites(context, resource, record, path, conflicted);
139
+ walkNestedCreates(context, resource, record, path);
140
+ }
72
141
  function walkNestedCreates(context, resource, record, path) {
73
142
  if (!isRecord(record)) {
74
143
  return;
@@ -100,18 +169,29 @@ function walkNestedCreates(context, resource, record, path) {
100
169
  checkCreateRecord(context, field.target, siteRecord, sitePath, NESTED_WRITE_SCOPE, via);
101
170
  }
102
171
  else {
103
- walkNestedCreates(context, field.target, siteRecord, sitePath);
172
+ checkUpdateRecord(context, field.target, siteRecord, sitePath);
104
173
  }
105
174
  }
106
175
  }
107
176
  }
108
177
  }
109
178
  }
110
- export function missingCreateValues(contract, table, request, args, argumentsPath) {
179
+ export function recordWriteFactsOf(adapter) {
180
+ return {
181
+ requirements: adapter.createRequirements ?? {},
182
+ references: adapter.referentialActions ?? {},
183
+ };
184
+ }
185
+ export function createRequirementIssues(contract, facts, request, args, argumentsPath) {
111
186
  if (!isRecord(args)) {
112
187
  return [];
113
188
  }
114
- const context = { contract, table, issues: [] };
189
+ const context = {
190
+ contract,
191
+ requirements: facts.requirements,
192
+ references: facts.references,
193
+ issues: [],
194
+ };
115
195
  const scope = resolveWriteScope(lookupResource(contract, request.resource), request);
116
196
  if (request.family === "create" && written(args, "data")) {
117
197
  const data = args.data;
@@ -126,13 +206,13 @@ export function missingCreateValues(contract, table, request, args, argumentsPat
126
206
  }
127
207
  if (request.family === "upsert") {
128
208
  checkCreateRecord(context, request.resource, args.create, [...argumentsPath, "create"], scope, undefined);
129
- walkNestedCreates(context, request.resource, args.update, [
209
+ checkUpdateRecord(context, request.resource, args.update, [
130
210
  ...argumentsPath,
131
211
  "update",
132
212
  ]);
133
213
  }
134
214
  if (request.family === "update") {
135
- walkNestedCreates(context, request.resource, args.data, [
215
+ checkUpdateRecord(context, request.resource, args.data, [
136
216
  ...argumentsPath,
137
217
  "data",
138
218
  ]);
@@ -1,12 +1,14 @@
1
- /** Provider-neutral exact decimal runtime value owned by Aventara. */
1
+ /**
2
+ * An exact decimal value, independent of any database driver's decimal class.
3
+ * Results carry `decimal` fields as `Decimal`.
4
+ */
2
5
  export declare class Decimal {
3
6
  #private;
4
7
  constructor(value: string);
5
8
  toString(): string;
6
9
  /**
7
10
  * The decimal string, exactly as given: what `JSON.stringify` writes for a
8
- * Decimal, so a result a developer serializes by hand carries its digits rather
9
- * than `{}`. The protocol's encoders do not rely on it.
11
+ * Decimal, so a result serialized by hand keeps its digits.
10
12
  */
11
13
  toJSON(): string;
12
14
  }
@@ -4,38 +4,52 @@ import type { FrameworkTransactionRequest } from "../transactions/transaction-re
4
4
  import type { FrameworkOperationRequest } from "./operation-request.js";
5
5
  import type { OperationErrorCode } from "./operation-response.js";
6
6
  /**
7
- * One unplanned failure as the framework saw it before sanitization: the raw
8
- * value, the outcome code actually emitted, and enough identity to act on it. The
9
- * code is usually an internal one, because the channel fires for unplanned
10
- * failures; it is the thrower's own code when the framework dropped something it
11
- * could not vouch for from an otherwise honoured deliberate outcome, which changes
12
- * no outcome. Server-side only — a diagnostic is never projected onto the wire,
13
- * which is precisely why it may carry the raw error.
7
+ * One unplanned failure of an operation: the raw error, the code the caller
8
+ * received, and the operation and request it happened in — or a developer's
9
+ * deliberate internal failure, marked `deliberate`. Server-side only:
10
+ * diagnostics are never sent to the caller.
14
11
  */
15
12
  export type OperationDiagnostic = {
16
13
  readonly kind: "operation";
17
14
  readonly error: unknown;
18
15
  readonly code: OperationErrorCode;
19
- readonly scope: RequestOrigin;
16
+ readonly origin: RequestOrigin;
20
17
  readonly resource: string;
21
18
  readonly family: OperationFamily;
22
19
  readonly variant: string;
23
20
  readonly requestId: string;
24
21
  readonly operation?: number;
22
+ /**
23
+ * `true` when `error` is a `FrameworkError` its thrower — a pipeline
24
+ * callback, a behavior, a filter or an Adapter — raised deliberately with an
25
+ * internal (`A3xxx`) code: the developer's own failure, whose message and
26
+ * issues the caller never receives. Absent for a failure the framework did not
27
+ * expect.
28
+ */
29
+ readonly deliberate?: true;
25
30
  };
31
+ /**
32
+ * One unplanned failure of a transaction: the raw error, the code the caller
33
+ * received, and the step it is blamed on (`operation`) when there is one.
34
+ */
26
35
  export type TransactionDiagnostic = {
27
36
  readonly kind: "transaction";
28
37
  readonly error: unknown;
29
38
  readonly code: OperationErrorCode;
30
- readonly scope: RequestOrigin;
39
+ readonly origin: RequestOrigin;
31
40
  readonly requestId: string;
32
41
  readonly operation?: number;
42
+ /**
43
+ * Never present: only an operation diagnostic is marked `deliberate`. Declared
44
+ * so the mark can be read on any {@link FrameworkDiagnostic}.
45
+ */
46
+ readonly deliberate?: never;
33
47
  };
48
+ /** One diagnostic: of an operation, or of a transaction as a whole. */
34
49
  export type FrameworkDiagnostic = OperationDiagnostic | TransactionDiagnostic;
35
50
  /**
36
- * The framework-owned observability channel. Configured once at the
37
- * configuration root: it is deliberately not a pipeline stage, so the
38
- * developer whose bug it reports cannot reorder, scope or remove it.
51
+ * Receives a diagnostic for every unplanned failure. Set once, as the root
52
+ * configuration's `diagnostics`.
39
53
  */
40
54
  export type FrameworkDiagnosticsSink = (diagnostic: FrameworkDiagnostic) => void | Promise<void>;
41
55
  /**
@@ -43,42 +57,24 @@ export type FrameworkDiagnosticsSink = (diagnostic: FrameworkDiagnostic) => void
43
57
  * error and the code being emitted. Total by construction — it returns
44
58
  * before the sink has finished and swallows everything the sink does.
45
59
  */
46
- export type DiagnosticsReporter = (error: unknown, code: OperationErrorCode) => void;
60
+ export type DiagnosticsReporter = (error: unknown, code: OperationErrorCode, marks?: DiagnosticMarks) => void;
61
+ /** What a report states about its error beyond the code: today, whether it was deliberate. */
62
+ export type DiagnosticMarks = Pick<OperationDiagnostic, "deliberate">;
47
63
  /** The identifying half of a diagnostic, captured once per request. */
48
64
  type OperationDiagnosticIdentity = Omit<OperationDiagnostic, "error" | "code">;
49
65
  type TransactionDiagnosticIdentity = Omit<TransactionDiagnostic, "error" | "code">;
50
- /**
51
- * The channel bound to one diagnostic identity, as the reporters below bind
52
- * it. Exported for `@aventara/core/protocol`, whose binding reports a failure
53
- * only it can meet — a result with no wire form (`A3003`) — under the
54
- * identity it holds; never exported from the main entry.
55
- */
66
+ /** The diagnostics channel bound to one diagnostic identity. */
56
67
  export declare function createReporter(sink: FrameworkDiagnosticsSink | undefined, identity: OperationDiagnosticIdentity | TransactionDiagnosticIdentity): DiagnosticsReporter;
57
68
  /** Records the sink `framework` was configured with; called once, at construction. */
58
69
  export declare function recordDiagnosticsSink(framework: object, sink: FrameworkDiagnosticsSink | undefined): void;
59
70
  /** The sink `framework` was configured with, or `undefined` for none. */
60
71
  export declare function diagnosticsSinkOf(framework: object): FrameworkDiagnosticsSink | undefined;
61
72
  /**
62
- * Binds the configured sink to one operation request.
63
- *
64
- * Identity is captured on entry rather than at report time: by the moment a
65
- * failure is reported the request may be exactly what broke, and a diagnostic
66
- * naming no operation is not actionable. A request whose own identity cannot
67
- * be read is past observing, so it silently yields the no-op channel — the
68
- * outermost totality guard still answers with the generic envelope.
69
- *
70
- * The response must be unaffected by observation, so the reporter neither
71
- * awaits the sink nor lets anything the sink does escape: a throw, a rejected
72
- * promise and a booby-trapped thenable all end here.
73
+ * Binds the configured sink to one operation request. The sink is not awaited,
74
+ * and nothing it throws or rejects with reaches the response.
73
75
  */
74
76
  export declare function createDiagnosticsReporter(sink: FrameworkDiagnosticsSink | undefined, request: FrameworkOperationRequest, operation?: number): DiagnosticsReporter;
75
- /**
76
- * The diagnostics channel of one transaction request. Like the operation
77
- * executor's reporter, it is bound by the framework factory, so the
78
- * transaction executor owns no channel and never reads configuration: it only
79
- * chooses which identity a report carries. The plan's identity is captured
80
- * once, on entry, for the same reason the operation reporter captures its own.
81
- */
77
+ /** The diagnostics channel of one transaction request. */
82
78
  export type TransactionDiagnosticsChannel = {
83
79
  /** A plan-level report; `operation` attributes it to a recorded failing index. */
84
80
  readonly transaction: (operation?: number) => DiagnosticsReporter;
@@ -10,9 +10,16 @@ export function createReporter(sink, identity) {
10
10
  if (sink === undefined) {
11
11
  return IGNORE_DIAGNOSTIC;
12
12
  }
13
- return (error, code) => {
13
+ return (error, code, marks) => {
14
14
  try {
15
- const settled = sink({ ...identity, error, code });
15
+ const settled = sink(identity.kind === "operation"
16
+ ? {
17
+ ...identity,
18
+ error,
19
+ code,
20
+ ...(marks?.deliberate === true ? { deliberate: true } : {}),
21
+ }
22
+ : { ...identity, error, code });
16
23
  if (isThenable(settled)) {
17
24
  settled.then(undefined, () => undefined);
18
25
  }
@@ -36,7 +43,7 @@ export function createDiagnosticsReporter(sink, request, operation) {
36
43
  try {
37
44
  identity = {
38
45
  kind: "operation",
39
- scope: request.scope,
46
+ origin: request.origin,
40
47
  resource: request.resource,
41
48
  family: request.family,
42
49
  variant: request.variant,
@@ -54,7 +61,7 @@ export function createTransactionDiagnosticsChannel(sink, request) {
54
61
  try {
55
62
  identity = {
56
63
  kind: "transaction",
57
- scope: request.scope,
64
+ origin: request.origin,
58
65
  requestId: request.requestId,
59
66
  };
60
67
  }
@@ -5,11 +5,7 @@ import type { Filter, PipelineContext } from "./pipeline.js";
5
5
  export type FailureOrigin = "adapter" | "runtime";
6
6
  /** The one generic Adapter-failure text; every unexpected A3001 carries it. */
7
7
  export declare const ADAPTER_FAILURE_MESSAGE = "Adapter execution failed.";
8
- /**
9
- * Structurally records every value thrown across the Adapter seam so the
10
- * final error boundary can classify origin by identity, never by inspecting
11
- * error text. One boundary instance belongs to one operation execution.
12
- */
8
+ /** One boundary instance belongs to one operation execution. */
13
9
  export declare class AdapterBoundary {
14
10
  #private;
15
11
  /** Runs Adapter work, remembering anything it throws before re-throwing it. */
@@ -24,6 +20,8 @@ export declare class AdapterBoundary {
24
20
  export declare const SERIALIZATION_FAILURE_MESSAGE = "Result serialization failed.";
25
21
  /** Where a standalone operation's arguments sit in a framework issue path. */
26
22
  export declare const OPERATION_ARGUMENTS_PATH: readonly (string | number)[];
23
+ /** Where one plan step sits in a framework issue path: its node in the request. */
24
+ export declare function planStepPath(index: number): readonly (string | number)[];
27
25
  /** Where one plan operation's arguments sit in a framework issue path. */
28
26
  export declare function planOperationArgumentsPath(index: number): readonly (string | number)[];
29
27
  /**
@@ -35,6 +33,12 @@ export type CallerArgumentsView = {
35
33
  readonly path: readonly (string | number)[];
36
34
  readonly arguments: unknown;
37
35
  };
36
+ /**
37
+ * The longest issue message a developer's code may send to the caller, in UTF-16
38
+ * code units. A longer one, or one with no visible character, is replaced by the
39
+ * framework's text for its code and reported to the diagnostics channel.
40
+ */
41
+ export declare const MAX_DEVELOPER_ISSUE_MESSAGE_LENGTH = 1000;
38
42
  /**
39
43
  * Final framework sanitization, shared by every boundary. A deliberate
40
44
  * {@link FrameworkError} carries its own stable outcome; anything else is
@@ -43,10 +47,10 @@ export type CallerArgumentsView = {
43
47
  */
44
48
  export declare function sanitizeOperationFailure(error: unknown, origin: FailureOrigin, report: DiagnosticsReporter, view: CallerArgumentsView): OperationFailureResponse;
45
49
  /**
46
- * Post-validation exception flow (spec 17.3): effective filters are stored as
47
- * `[root..., scoped...]`, so reverse iteration tries scoped filters in reverse
48
- * registration order, then root filters in reverse, then final sanitization.
49
- * A filter handles an error only by returning a {@link FrameworkError};
50
- * returning `undefined` or throwing leaves the original error unhandled.
50
+ * Post-validation exception flow: effective filters are stored as `[root...,
51
+ * scoped...]`, so reverse iteration tries scoped filters in reverse registration
52
+ * order, then root filters in reverse, then final sanitization. A filter handles an
53
+ * error only by returning a {@link FrameworkError}; returning `undefined` or throwing
54
+ * leaves the original error unhandled.
51
55
  */
52
56
  export declare function resolveOperationFailure(error: unknown, filters: readonly Filter[], context: PipelineContext, boundary: AdapterBoundary, report: DiagnosticsReporter, view: CallerArgumentsView): Promise<OperationFailureResponse>;