@aventara/core 1.0.0-rc.4 → 1.0.0-rc.9

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 (89) hide show
  1. package/CHANGELOG.md +157 -0
  2. package/ERRORS.md +2 -2
  3. package/README.md +80 -7
  4. package/dist/adapters/adapter-field.d.ts +8 -0
  5. package/dist/adapters/adapter-identifier.d.ts +9 -0
  6. package/dist/adapters/adapter-identifier.js +6 -1
  7. package/dist/adapters/adapter-operation.d.ts +12 -6
  8. package/dist/compiler/contract-compiler-diagnostic.guidance.js +2 -2
  9. package/dist/compiler/contract.compiler.js +26 -3
  10. package/dist/config/config.resolver.d.ts +2 -0
  11. package/dist/config/config.resolver.js +7 -2
  12. package/dist/config/config.validator.js +36 -12
  13. package/dist/config/exact-framework-config.d.ts +11 -1
  14. package/dist/config/framework-config.d.ts +14 -1
  15. package/dist/contracts/contract-canonicalizer.js +2 -0
  16. package/dist/contracts/contract.d.ts +15 -1
  17. package/dist/contracts/field.d.ts +17 -0
  18. package/dist/contracts/scalar-value-type.d.ts +3 -1
  19. package/dist/framework/contract-view.d.ts +9 -1
  20. package/dist/framework/framework-construction.error.d.ts +2 -1
  21. package/dist/framework/framework-construction.error.js +13 -1
  22. package/dist/framework/framework.d.ts +26 -5
  23. package/dist/framework/framework.factory.js +29 -10
  24. package/dist/framework/operation-facade.d.ts +59 -12
  25. package/dist/index.d.ts +8 -4
  26. package/dist/internal.d.ts +2 -2
  27. package/dist/internal.js +2 -0
  28. package/dist/operations/operation-arguments.d.ts +43 -27
  29. package/dist/operations/operation-call.d.ts +2 -2
  30. package/dist/operations/operation-result.d.ts +1 -1
  31. package/dist/policy/access-provider.interface.d.ts +36 -0
  32. package/dist/policy/access-provider.interface.js +1 -0
  33. package/dist/policy/access-provider.registry.d.ts +9 -0
  34. package/dist/policy/access-provider.registry.js +10 -0
  35. package/dist/policy/policy-admission.js +12 -3
  36. package/dist/policy/policy-after-update.runtime.js +5 -4
  37. package/dist/policy/policy-engine.d.ts +11 -4
  38. package/dist/policy/policy-nested-write.runtime.js +11 -7
  39. package/dist/policy/policy-read.runtime.d.ts +0 -2
  40. package/dist/policy/policy-read.runtime.js +18 -9
  41. package/dist/policy/policy-request.d.ts +7 -3
  42. package/dist/policy/policy-request.js +14 -3
  43. package/dist/policy/policy-session.d.ts +14 -4
  44. package/dist/policy/policy-session.js +12 -11
  45. package/dist/policy/policy-startup.validator.js +13 -15
  46. package/dist/policy/policy-write.runtime.d.ts +7 -0
  47. package/dist/policy/policy-write.runtime.js +23 -8
  48. package/dist/prebuilt/core-version.generated.d.ts +1 -1
  49. package/dist/prebuilt/core-version.generated.js +1 -1
  50. package/dist/prebuilt/prebuilt-changes.d.ts +28 -0
  51. package/dist/prebuilt/prebuilt-changes.js +65 -0
  52. package/dist/prebuilt/prebuilt-contracts.loader.js +11 -56
  53. package/dist/prebuilt/prebuilt-inputs.fingerprint.js +16 -1
  54. package/dist/protocol/absent-route.responder.d.ts +4 -4
  55. package/dist/protocol/http-request.decoder.d.ts +2 -2
  56. package/dist/protocol/http-request.reader.d.ts +2 -2
  57. package/dist/protocol/http-response.encoder.js +2 -2
  58. package/dist/protocol/protocol-headers.d.ts +3 -3
  59. package/dist/runtime/caller-data.copier.js +3 -4
  60. package/dist/runtime/computed-read-runtime.js +2 -7
  61. package/dist/runtime/context-executor.runtime.d.ts +2 -2
  62. package/dist/runtime/core-class.brand.d.ts +10 -0
  63. package/dist/runtime/core-class.brand.js +21 -0
  64. package/dist/runtime/create-requirement.js +1 -1
  65. package/dist/runtime/decimal.d.ts +14 -0
  66. package/dist/runtime/decimal.js +21 -0
  67. package/dist/runtime/diagnostics-reporter.d.ts +5 -5
  68. package/dist/runtime/diagnostics-reporter.js +13 -1
  69. package/dist/runtime/framework-error.js +9 -0
  70. package/dist/runtime/operation-request.d.ts +9 -9
  71. package/dist/runtime/operation-runtime.d.ts +8 -8
  72. package/dist/runtime/operation-runtime.js +30 -9
  73. package/dist/runtime/owned-execution.registry.d.ts +2 -2
  74. package/dist/runtime/readonly-value.d.ts +1 -1
  75. package/dist/runtime/readonly-value.js +2 -2
  76. package/dist/runtime/referential-action.d.ts +1 -1
  77. package/dist/runtime/scalar-runtime.js +4 -5
  78. package/dist/transactions/operation-arguments-encoder.js +8 -3
  79. package/dist/transactions/sql-statement.d.ts +2 -2
  80. package/dist/transactions/sql-statement.validator.js +2 -2
  81. package/dist/transactions/transaction-integrity-error.js +2 -0
  82. package/dist/transactions/transaction-plan-assembler.d.ts +11 -4
  83. package/dist/transactions/transaction-plan-assembler.js +17 -2
  84. package/dist/transactions/transaction-plan-validator.d.ts +5 -5
  85. package/dist/transactions/transaction-request.d.ts +8 -8
  86. package/dist/transactions/transaction-runtime.d.ts +2 -2
  87. package/dist/transactions/transaction-runtime.js +12 -7
  88. package/dist/transactions/transaction-slot-path-error.js +2 -0
  89. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ import { adapterPrimaryIdentifier } from "../adapters/adapter-identifier.js";
1
2
  import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
2
3
  import { validateOperationArguments } from "../operations/operation-validator-core.js";
3
4
  import { normalizeOperationScalarValues } from "../runtime/scalar-runtime.js";
@@ -84,11 +85,11 @@ function questionKey(resource, where) {
84
85
  return undefined;
85
86
  }
86
87
  }
87
- function existsOperation(core, resource, where) {
88
+ function existsOperation(core, model, resource, where) {
88
89
  const contract = lookupResource(core, resource);
89
- const [identifier] = contract?.identifiers ?? [];
90
+ const identifier = adapterPrimaryIdentifier(model, resource);
90
91
  const first = contract?.operations.find?.first !== undefined;
91
- return first && identifier !== undefined
92
+ return first && identifier.length > 0
92
93
  ? {
93
94
  resource,
94
95
  family: "find",
@@ -102,16 +103,16 @@ function existsOperation(core, resource, where) {
102
103
  arguments: { where },
103
104
  };
104
105
  }
105
- export function openPolicySession(policy, core, principal, origin, execution) {
106
+ export function openPolicySession(policy, core, model, principal, origin, execution) {
106
107
  const caches = {
107
108
  scopes: new Map(),
108
109
  createGrants: new Map(),
109
110
  fields: new Map(),
110
111
  exists: new WeakMap(),
111
112
  };
112
- return sessionOn(policy, core, principal, origin, execution, caches);
113
+ return sessionOn(policy, core, model, principal, origin, execution, caches);
113
114
  }
114
- function sessionOn(policy, core, principal, origin, execution, caches) {
115
+ function sessionOn(policy, core, model, principal, origin, execution, caches) {
115
116
  const { engine } = policy;
116
117
  const exists = (resource, where) => {
117
118
  const key = questionKey(resource, where);
@@ -124,7 +125,7 @@ function sessionOn(policy, core, principal, origin, execution, caches) {
124
125
  if (known !== undefined) {
125
126
  return known;
126
127
  }
127
- const operation = existsOperation(core, resource, where);
128
+ const operation = existsOperation(core, model, resource, where);
128
129
  const answer = execution.boundary
129
130
  .execute(() => execution.executor.execute(operation))
130
131
  .then((result) => typeof result === "number" ? result > 0 : result != null);
@@ -174,11 +175,11 @@ function sessionOn(policy, core, principal, origin, execution, caches) {
174
175
  return scope;
175
176
  },
176
177
  fields(resource, access) {
177
- const key = `${resource}\u0000${access}`;
178
+ const key = `${resource}\u0000${access.access}\u0000${access.access === "write" ? access.action : ""}`;
178
179
  let fields = caches.fields.get(key);
179
180
  if (fields === undefined) {
180
181
  fields = Promise.resolve()
181
- .then(() => engine.fieldsOf({ principal, origin, resource, access }))
182
+ .then(() => engine.fieldsOf({ principal, origin, resource, ...access }))
182
183
  .then((answer) => fieldsAnswer(core, resource, answer));
183
184
  caches.fields.set(key, fields);
184
185
  }
@@ -211,10 +212,10 @@ function sessionOn(policy, core, principal, origin, execution, caches) {
211
212
  policy.afterChecks[resource]?.includes(action) === true);
212
213
  },
213
214
  on(next) {
214
- return sessionOn(policy, core, principal, origin, next, caches);
215
+ return sessionOn(policy, core, model, principal, origin, next, caches);
215
216
  },
216
217
  renewed() {
217
- return sessionOn(policy, core, principal, origin, execution, {
218
+ return sessionOn(policy, core, model, principal, origin, execution, {
218
219
  ...caches,
219
220
  exists: new WeakMap(),
220
221
  });
@@ -3,30 +3,28 @@ const TO_MANY_OPERATORS = new Set(["some", "every", "none"]);
3
3
  function invalid(path, message) {
4
4
  return { code: "CONFIG_INVALID_VALUE", path, message };
5
5
  }
6
- function governedDiagnostics(core, governed) {
6
+ function governedDiagnostics(core, { at, governed }) {
7
7
  const diagnostics = [];
8
8
  for (const [resourceName, names] of Object.entries(governed)) {
9
- const path = `policy.engine.governed.${resourceName}`;
9
+ const path = `${at}.governed.${resourceName}`;
10
10
  const resource = lookupResource(core, resourceName);
11
11
  if (resource === undefined) {
12
- diagnostics.push(invalid(path, `${path}: "${resourceName}" is no Resource of the Core Contract`));
12
+ diagnostics.push(invalid(path, `"${resourceName}" is no Resource of the Core Contract`));
13
13
  continue;
14
14
  }
15
15
  for (const name of names) {
16
16
  if (lookupField(resource, name) === undefined) {
17
- diagnostics.push(invalid(path, `${path}: "${name}" is no field or relation of Resource "${resourceName}" in the Core Contract`));
17
+ diagnostics.push(invalid(path, `"${name}" is no field or relation of Resource "${resourceName}" in the Core Contract`));
18
18
  }
19
19
  }
20
20
  }
21
21
  return diagnostics;
22
22
  }
23
- function afterCheckDiagnostics(core, afterChecks) {
23
+ function afterCheckDiagnostics(core, { at, afterChecks }) {
24
24
  return Object.keys(afterChecks).flatMap((resourceName) => {
25
- const path = `policy.engine.afterChecks.${resourceName}`;
25
+ const path = `${at}.afterChecks.${resourceName}`;
26
26
  return lookupResource(core, resourceName) === undefined
27
- ? [
28
- invalid(path, `${path}: "${resourceName}" is no Resource of the Core Contract`),
29
- ]
27
+ ? [invalid(path, `"${resourceName}" is no Resource of the Core Contract`)]
30
28
  : [];
31
29
  });
32
30
  }
@@ -84,10 +82,10 @@ function pathFault(core, path) {
84
82
  }
85
83
  return `the path ends at Resource "${current}", not at a field`;
86
84
  }
87
- function pathDiagnostics(core, paths) {
85
+ function pathDiagnostics(core, policy) {
88
86
  const diagnostics = [];
89
- for (const [rule, list] of Object.entries(paths)) {
90
- const at = `policy.engine.paths.${rule}`;
87
+ for (const [rule, list] of Object.entries(policy.paths)) {
88
+ const at = `${policy.at}.paths.${rule}`;
91
89
  for (const path of list) {
92
90
  const fault = pathFault(core, path);
93
91
  if (fault !== undefined) {
@@ -99,8 +97,8 @@ function pathDiagnostics(core, paths) {
99
97
  }
100
98
  export function policyStartupDiagnostics(core, policy) {
101
99
  return [
102
- ...governedDiagnostics(core, policy.governed),
103
- ...pathDiagnostics(core, policy.paths),
104
- ...afterCheckDiagnostics(core, policy.afterChecks),
100
+ ...governedDiagnostics(core, policy),
101
+ ...pathDiagnostics(core, policy),
102
+ ...afterCheckDiagnostics(core, policy),
105
103
  ];
106
104
  }
@@ -9,6 +9,13 @@ import type { PolicySession } from "./policy-session.js";
9
9
  export type PolicyWrite = PolicyRead & {
10
10
  /** A unique write that matched nothing: `A4002` for a row the caller can read, `A2003` otherwise. */
11
11
  readonly miss?: () => Promise<AvOperationFailureResponse>;
12
+ /**
13
+ * A unique update holding nested writes whose adapter call failed: the miss's
14
+ * answer when its row is outside the scopes (an adapter may fail the nested
15
+ * write there instead of answering the miss), `undefined` when the failure
16
+ * stands.
17
+ */
18
+ readonly failedMiss?: () => Promise<AvOperationFailureResponse | undefined>;
12
19
  };
13
20
  /**
14
21
  * Whether a write the policy checks needs an interactive transaction to roll
@@ -1,3 +1,4 @@
1
+ import { adapterPrimaryIdentifier } from "../adapters/adapter-identifier.js";
1
2
  import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
2
3
  import { uniqueMissFailure } from "../runtime/outcome-mapper.js";
3
4
  import { frozenCopy } from "../runtime/readonly-value.js";
@@ -5,7 +6,7 @@ import { policyRefusal } from "./policy-admission.js";
5
6
  import { afterCheckFields, afterUpdate, checkChanged, } from "./policy-after-update.runtime.js";
6
7
  import { allOf, everyOf, noneOf, someOf, whereOf, } from "./policy-condition.js";
7
8
  import { planNestedWrites, writesNested, } from "./policy-nested-write.runtime.js";
8
- import { enforceReadPolicy, identifiedRows, identifierOf, returnedRowChecks, } from "./policy-read.runtime.js";
9
+ import { enforceReadPolicy, identifiedRows, returnedRowChecks, } from "./policy-read.runtime.js";
9
10
  const UNVERIFIABLE = "cannot be verified on this database: it has no interactive transaction to roll the write back in";
10
11
  function isRecord(value) {
11
12
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -52,7 +53,7 @@ async function checkCreated(session, core, model, resource, grants, created) {
52
53
  if (created.length === 0) {
53
54
  return undefined;
54
55
  }
55
- const these = identifiedRows(core, identifierOf(model, resource), resource, created);
56
+ const these = identifiedRows(core, adapterPrimaryIdentifier(model, resource), resource, created);
56
57
  const missed = allOf(these, someOf([...grants.values()].map(noneOf)));
57
58
  if (missed !== false &&
58
59
  (await session.renewed().exists(resource, whereOf(missed)))) {
@@ -72,7 +73,7 @@ function uncheckableCreate(model, request, grants, inTransaction) {
72
73
  return refused(`${what}, which ${UNVERIFIABLE}.`);
73
74
  }
74
75
  if (request.variant === "count" ||
75
- identifierOf(model, request.resource).length === 0) {
76
+ adapterPrimaryIdentifier(model, request.resource).length === 0) {
76
77
  return refused(`${what}, and this create does not return the rows it creates to check by.`);
77
78
  }
78
79
  return undefined;
@@ -244,7 +245,8 @@ async function enforceScopedWrite(session, core, model, request, plan, adapterAr
244
245
  if (changes !== undefined && !inTransaction) {
245
246
  return refused(`The policy checks this update on Resource "${resource}" after it runs (${changes.after ? "its grant's after condition" : "whether it moves a row out of what the caller may read"}), which ${UNVERIFIABLE}.`);
246
247
  }
247
- if (changes !== undefined && identifierOf(model, resource).length === 0) {
248
+ if (changes !== undefined &&
249
+ adapterPrimaryIdentifier(model, resource).length === 0) {
248
250
  return refused(`The policy checks this update on Resource "${resource}" after it runs, and the Resource has no identifier to find the changed rows by.`);
249
251
  }
250
252
  const nested = action === "update"
@@ -259,6 +261,19 @@ async function enforceScopedWrite(session, core, model, request, plan, adapterAr
259
261
  const miss = request.variant === "unique"
260
262
  ? missOf(session, request, args.where)
261
263
  : undefined;
264
+ const failedMiss = miss !== undefined && nested !== undefined
265
+ ? async () => {
266
+ const reach = allOf(isRecord(args.where) ? args.where : true, scope);
267
+ return reach !== false &&
268
+ (await session.renewed().exists(resource, whereOf(reach)))
269
+ ? undefined
270
+ : miss();
271
+ }
272
+ : undefined;
273
+ const misses = {
274
+ ...(miss === undefined ? {} : { miss }),
275
+ ...(failedMiss === undefined ? {} : { failedMiss }),
276
+ };
262
277
  const carried = changes?.after === true
263
278
  ? afterCheckFields(core, model, resource)
264
279
  : undefined;
@@ -272,7 +287,7 @@ async function enforceScopedWrite(session, core, model, request, plan, adapterAr
272
287
  return blocked;
273
288
  }
274
289
  if (read.kind !== "run") {
275
- return miss === undefined ? read : { ...read, miss };
290
+ return { ...read, ...misses };
276
291
  }
277
292
  let run = read;
278
293
  if (action === "delete" && run.checks.root.length > 0) {
@@ -294,7 +309,7 @@ async function enforceScopedWrite(session, core, model, request, plan, adapterAr
294
309
  ...runArgs,
295
310
  select: changes.after
296
311
  ? [...afterCheckFields(core, model, resource)]
297
- : [...identifierOf(model, resource)],
312
+ : [...adapterPrimaryIdentifier(model, resource)],
298
313
  },
299
314
  };
300
315
  run = withChangedRows(asMany, (data) => checkChanged(session, core, model, resource, changes, rowsOf(data), changes.after));
@@ -305,7 +320,7 @@ async function enforceScopedWrite(session, core, model, request, plan, adapterAr
305
320
  }
306
321
  if (counted) {
307
322
  const runArgs = isRecord(run.arguments) ? run.arguments : {};
308
- const identifier = identifierOf(model, resource);
323
+ const identifier = adapterPrimaryIdentifier(model, resource);
309
324
  changed = await session.rows(resource, isRecord(runArgs.where) ? runArgs.where : {}, identifier);
310
325
  if (changed.length === 0) {
311
326
  return { kind: "empty", data: 0 };
@@ -329,7 +344,7 @@ async function enforceScopedWrite(session, core, model, request, plan, adapterAr
329
344
  run = withNestedWrites(run, "data", nested);
330
345
  }
331
346
  run = withoutCarried(run, args, carried);
332
- return miss === undefined ? run : { ...run, miss };
347
+ return { ...run, ...misses };
333
348
  }
334
349
  async function enforceUpsert(session, core, model, request, plan, adapterArguments, args, inTransaction) {
335
350
  const resource = request.resource;
@@ -1,2 +1,2 @@
1
1
  /** The version of this `@aventara/core`. */
2
- export declare const CORE_VERSION = "1.0.0-rc.4";
2
+ export declare const CORE_VERSION = "1.0.0-rc.9";
@@ -1 +1 @@
1
- export const CORE_VERSION = "1.0.0-rc.4";
1
+ export const CORE_VERSION = "1.0.0-rc.9";
@@ -0,0 +1,28 @@
1
+ import type { PrebuiltPartFingerprints } from "./prebuilt-inputs.fingerprint.js";
2
+ /** `a`, `a and b`, `a, b and c`. */
3
+ export declare function listedNames(names: readonly string[]): string;
4
+ /** What a prebuilt file was written in, by and for. */
5
+ export type PrebuiltHeader = {
6
+ readonly format: number;
7
+ readonly core: string;
8
+ readonly protocol: number;
9
+ };
10
+ /**
11
+ * Each way a stored file's header differs from `current`, in the order the
12
+ * runtime checks them: the file format, the core version, the protocol.
13
+ */
14
+ export declare function prebuiltHeaderChanges(stored: unknown, current: PrebuiltHeader): readonly string[];
15
+ /**
16
+ * Each way a stored file's part fingerprints differ from `current`: the
17
+ * Resources whose inputs changed, the new ones, the gone ones, and, when
18
+ * `sameHeader`, the inputs that belong to no single Resource.
19
+ */
20
+ export declare function prebuiltInputChanges(stored: unknown, current: PrebuiltPartFingerprints, sameHeader?: boolean): readonly string[];
21
+ /**
22
+ * What differs between a stored prebuilt file and `current`, one clause per
23
+ * difference: its header's, then its inputs' (`… since it was written`).
24
+ * Empty when the two record the same header and inputs.
25
+ */
26
+ export declare function prebuiltFileChanges(stored: unknown, current: PrebuiltHeader & {
27
+ readonly fingerprints: PrebuiltPartFingerprints;
28
+ }): readonly string[];
@@ -0,0 +1,65 @@
1
+ function isRecord(value) {
2
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3
+ }
4
+ export function listedNames(names) {
5
+ return names.length < 2
6
+ ? names.join("")
7
+ : `${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
8
+ }
9
+ export function prebuiltHeaderChanges(stored, current) {
10
+ const header = isRecord(stored) ? stored : {};
11
+ const changes = [];
12
+ if (header.format !== current.format) {
13
+ changes.push(`it is written in file format ${String(header.format)}, and this @aventara/core reads format ${current.format}`);
14
+ }
15
+ if (header.core !== current.core) {
16
+ changes.push(`it was built by @aventara/core ${String(header.core)}, and this is ${current.core}`);
17
+ }
18
+ if (header.protocol !== current.protocol) {
19
+ changes.push(`it was built for protocol version ${String(header.protocol)}, and this @aventara/core speaks version ${current.protocol}`);
20
+ }
21
+ return changes;
22
+ }
23
+ export function prebuiltInputChanges(stored, current, sameHeader = true) {
24
+ const storedResources = isRecord(stored) ? stored.resources : undefined;
25
+ const before = isRecord(storedResources) ? storedResources : {};
26
+ const changed = [];
27
+ const added = [];
28
+ const removed = [];
29
+ for (const [name, fingerprint] of Object.entries(current.resources)) {
30
+ if (!Object.hasOwn(before, name)) {
31
+ added.push(name);
32
+ }
33
+ else if (before[name] !== fingerprint) {
34
+ changed.push(name);
35
+ }
36
+ }
37
+ for (const name of Object.keys(before)) {
38
+ if (!Object.hasOwn(current.resources, name)) {
39
+ removed.push(name);
40
+ }
41
+ }
42
+ const resources = (names) => `${names.length === 1 ? "Resource" : "Resources"} ${listedNames(names)}`;
43
+ const be = (names) => (names.length === 1 ? "is" : "are");
44
+ const changes = [];
45
+ if (changed.length > 0) {
46
+ changes.push(`${resources(changed)} changed`);
47
+ }
48
+ if (added.length > 0) {
49
+ changes.push(`${resources(added)} ${be(added)} new`);
50
+ }
51
+ if (removed.length > 0) {
52
+ changes.push(`${resources(removed)} ${be(removed)} gone`);
53
+ }
54
+ if (sameHeader && (!isRecord(stored) || stored.shared !== current.shared)) {
55
+ changes.push("the configuration or model outside the Resources changed");
56
+ }
57
+ return changes;
58
+ }
59
+ export function prebuiltFileChanges(stored, current) {
60
+ const header = prebuiltHeaderChanges(stored, current);
61
+ const inputs = prebuiltInputChanges(isRecord(stored) ? stored.fingerprints : undefined, current.fingerprints, header.length === 0);
62
+ return inputs.length === 0
63
+ ? header
64
+ : [...header, `${listedNames(inputs)} since it was written`];
65
+ }
@@ -3,53 +3,18 @@ import { recordCanonicalContractText } from "../contracts/contract-canonicalizer
3
3
  import { AVENTARA_PROTOCOL_VERSION } from "../contracts/contract-protocol.js";
4
4
  import { emitFrameworkWarning } from "../runtime/framework-warning.emitter.js";
5
5
  import { CORE_VERSION } from "./core-version.generated.js";
6
+ import { listedNames, prebuiltHeaderChanges, prebuiltInputChanges, } from "./prebuilt-changes.js";
6
7
  import { PREBUILT_FORMAT } from "./prebuilt-contracts.js";
7
8
  import { prebuiltInputsFingerprint, prebuiltPartFingerprints, } from "./prebuilt-inputs.fingerprint.js";
8
9
  export const PREBUILT_STALE_WARNING = "AVENTARA_PREBUILT_STALE";
9
10
  function isRecord(value) {
10
11
  return value !== null && typeof value === "object" && !Array.isArray(value);
11
12
  }
12
- function listed(names) {
13
- return names.length < 2
14
- ? names.join("")
15
- : `${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
16
- }
17
13
  async function changedInputs(stored, model, config) {
18
- const live = await prebuiltPartFingerprints(model, config);
19
- const storedResources = isRecord(stored) ? stored.resources : undefined;
20
- const before = isRecord(storedResources) ? storedResources : {};
21
- const changed = [];
22
- const added = [];
23
- const removed = [];
24
- for (const [name, fingerprint] of Object.entries(live.resources)) {
25
- if (!Object.hasOwn(before, name)) {
26
- added.push(name);
27
- }
28
- else if (before[name] !== fingerprint) {
29
- changed.push(name);
30
- }
31
- }
32
- for (const name of Object.keys(before)) {
33
- if (!Object.hasOwn(live.resources, name)) {
34
- removed.push(name);
35
- }
36
- }
37
- const parts = [];
38
- if (changed.length > 0) {
39
- parts.push(`${changed.length === 1 ? "Resource" : "Resources"} ${listed(changed)} changed`);
40
- }
41
- if (added.length > 0) {
42
- parts.push(`${added.length === 1 ? "Resource" : "Resources"} ${listed(added)} ${added.length === 1 ? "is" : "are"} new`);
43
- }
44
- if (removed.length > 0) {
45
- parts.push(`${removed.length === 1 ? "Resource" : "Resources"} ${listed(removed)} ${removed.length === 1 ? "is" : "are"} gone`);
46
- }
47
- if (!isRecord(stored) || stored.shared !== live.shared) {
48
- parts.push("the configuration or model outside the Resources changed");
49
- }
50
- return parts.length === 0
14
+ const changes = prebuiltInputChanges(stored, await prebuiltPartFingerprints(model, config));
15
+ return changes.length === 0
51
16
  ? "its fingerprint does not match the configuration and model"
52
- : `${listed(parts)} since it was built`;
17
+ : `${listedNames(changes)} since it was built`;
53
18
  }
54
19
  function textAt(record, key) {
55
20
  const value = Object.hasOwn(record, key) ? record[key] : undefined;
@@ -86,23 +51,13 @@ export async function loadPrebuiltContracts(prebuilt, model, config, resolved) {
86
51
  if (!isRecord(prebuilt)) {
87
52
  return { ok: false, reason: "it is not a prebuilt Contracts file" };
88
53
  }
89
- if (prebuilt.format !== PREBUILT_FORMAT) {
90
- return {
91
- ok: false,
92
- reason: `it is written in file format ${String(prebuilt.format)}, and this @aventara/core reads format ${PREBUILT_FORMAT}`,
93
- };
94
- }
95
- if (prebuilt.core !== CORE_VERSION) {
96
- return {
97
- ok: false,
98
- reason: `it was built by @aventara/core ${String(prebuilt.core)}, and this is ${CORE_VERSION}`,
99
- };
100
- }
101
- if (prebuilt.protocol !== AVENTARA_PROTOCOL_VERSION) {
102
- return {
103
- ok: false,
104
- reason: `it was built for protocol version ${String(prebuilt.protocol)}, and this @aventara/core speaks version ${AVENTARA_PROTOCOL_VERSION}`,
105
- };
54
+ const [header] = prebuiltHeaderChanges(prebuilt, {
55
+ format: PREBUILT_FORMAT,
56
+ core: CORE_VERSION,
57
+ protocol: AVENTARA_PROTOCOL_VERSION,
58
+ });
59
+ if (header !== undefined) {
60
+ return { ok: false, reason: header };
106
61
  }
107
62
  if ((await prebuiltInputsFingerprint(model, config)) !== prebuilt.fingerprint) {
108
63
  return {
@@ -126,12 +126,27 @@ function policyInput(policy) {
126
126
  : undefined;
127
127
  return { ...policy, engine: { governed: governed ?? null } };
128
128
  }
129
+ function accessInput(access) {
130
+ if (!isRecord(access)) {
131
+ return access;
132
+ }
133
+ const engine = access.policy;
134
+ const governed = engine !== null && typeof engine === "object"
135
+ ? engine.governed
136
+ : undefined;
137
+ return { policy: { governed: governed ?? null } };
138
+ }
129
139
  function authoredInputs(config) {
130
140
  const authored = config;
131
141
  const root = {};
132
142
  for (const key of Object.keys(authored)) {
133
143
  if (!NOT_INPUTS.has(key)) {
134
- root[key] = key === "policy" ? policyInput(authored[key]) : authored[key];
144
+ root[key] =
145
+ key === "policy"
146
+ ? policyInput(authored[key])
147
+ : key === "access"
148
+ ? accessInput(authored[key])
149
+ : authored[key];
135
150
  }
136
151
  }
137
152
  return root;
@@ -2,7 +2,7 @@ import type { AvContracts } from "../compiler/contract.compiler.js";
2
2
  import type { RemoteRequestOrigin } from "../config/config-layer.js";
3
3
  import { type AvOperationIdentity } from "../operations/operation-identity.js";
4
4
  import { type AvDiagnosticsSink } from "../runtime/diagnostics-reporter.js";
5
- import type { ClientProtocolIdentity } from "../runtime/operation-request.js";
5
+ import type { AvRequestProtocol } from "../runtime/operation-request.js";
6
6
  import { type AvOperationFailureResponse } from "../runtime/operation-response.js";
7
7
  import type { AvProtocolRequest } from "./http-request.decoder.js";
8
8
  import { type ReadRequest } from "./http-request.reader.js";
@@ -36,7 +36,7 @@ export type OperationRouteResolution = {
36
36
  * is a standard operation the Resource advertises, read through
37
37
  * `isOperationVariantAvailable` alone.
38
38
  */
39
- export declare function resolveOperationRoute(contracts: AvContracts, protocol: ClientProtocolIdentity, route: OperationRouteSegments, origin?: RemoteRequestOrigin): OperationRouteResolution;
39
+ export declare function resolveOperationRoute(contracts: AvContracts, protocol: AvRequestProtocol, route: OperationRouteSegments, origin?: RemoteRequestOrigin): OperationRouteResolution;
40
40
  /**
41
41
  * The `/_transactions` route under one client protocol identity: version → hash → the
42
42
  * ClientContract's transaction capability. The route exists only when that capability
@@ -44,14 +44,14 @@ export declare function resolveOperationRoute(contracts: AvContracts, protocol:
44
44
  * it — and is otherwise absent with core's own refusal of a plan. `undefined` when the
45
45
  * route is there.
46
46
  */
47
- export declare function transactionsRouteFailure(contracts: AvContracts, protocol: ClientProtocolIdentity, origin?: RemoteRequestOrigin): AvOperationFailureResponse | undefined;
47
+ export declare function transactionsRouteFailure(contracts: AvContracts, protocol: AvRequestProtocol, origin?: RemoteRequestOrigin): AvOperationFailureResponse | undefined;
48
48
  /**
49
49
  * What a route's checks before its body resolve to: the route a request
50
50
  * reaches, or the framework response that answers it instead.
51
51
  */
52
52
  type RouteCheck<Route> = ({
53
53
  readonly ok: true;
54
- readonly protocol: ClientProtocolIdentity;
54
+ readonly protocol: AvRequestProtocol;
55
55
  } & Route) | {
56
56
  readonly ok: false;
57
57
  readonly response: AvProtocolResponse;
@@ -1,7 +1,7 @@
1
1
  import type { AvContracts } from "../compiler/contract.compiler.js";
2
2
  import type { RemoteRequestOrigin } from "../config/config-layer.js";
3
3
  import type { RemoteOperationRequest } from "../runtime/operation-request.js";
4
- import type { AgentTransactionRequest, ClientTransactionRequest } from "../transactions/transaction-request.js";
4
+ import type { AvAgentTransactionRequest, AvClientTransactionRequest } from "../transactions/transaction-request.js";
5
5
  import type { AvProtocolResponse } from "./http-response.encoder.js";
6
6
  /** One HTTP request as a host hands it to the protocol. */
7
7
  export type AvProtocolRequest = {
@@ -56,7 +56,7 @@ export type DecodedOperationRequest = Extract<DecodedOperation, {
56
56
  ok: true;
57
57
  }>;
58
58
  /** What `protocol.decodeTransaction` answers. */
59
- export type DecodedTransaction = Decoded<ClientTransactionRequest | AgentTransactionRequest>;
59
+ export type DecodedTransaction = Decoded<AvClientTransactionRequest | AvAgentTransactionRequest>;
60
60
  /**
61
61
  * `protocol.decodeOperation(request)` — one operation route's HTTP request as
62
62
  * the client-origin request `framework.execute` takes, or its answer.
@@ -1,4 +1,4 @@
1
- import type { ClientProtocolIdentity } from "../runtime/operation-request.js";
1
+ import type { AvRequestProtocol } from "../runtime/operation-request.js";
2
2
  import type { OperationRouteSegments } from "./absent-route.responder.js";
3
3
  import type { AvProtocolRequest } from "./http-request.decoder.js";
4
4
  import type { AvProtocolResponse } from "./http-response.encoder.js";
@@ -25,7 +25,7 @@ export type ReadRequest = {
25
25
  /** Adopted from `Aventara-Request-Id`, else minted. */
26
26
  readonly requestId: string;
27
27
  /** `undefined` when either identity header is absent (`A2000`). */
28
- readonly protocol: ClientProtocolIdentity | undefined;
28
+ readonly protocol: AvRequestProtocol | undefined;
29
29
  };
30
30
  /** What a value that is not an `AvProtocolRequest` reads as. */
31
31
  export type UnreadableRequest = {
@@ -1,5 +1,5 @@
1
1
  import { encodeBase64, WIRE_SCALAR_PATTERNS, } from "../contracts/wire-scalar-format.js";
2
- import { AvDecimal } from "../runtime/decimal.js";
2
+ import { isAvDecimal } from "../runtime/decimal.js";
3
3
  import { SERIALIZATION_FAILURE_MESSAGE } from "../runtime/error-boundary.js";
4
4
  import { operationFailure, } from "../runtime/operation-response.js";
5
5
  import { httpStatus } from "./http-status.table.js";
@@ -161,7 +161,7 @@ function scalarWireText(value) {
161
161
  }
162
162
  return JSON.stringify(wire);
163
163
  }
164
- if (value instanceof AvDecimal) {
164
+ if (isAvDecimal(value)) {
165
165
  return JSON.stringify(value.toString());
166
166
  }
167
167
  if (value instanceof Uint8Array) {
@@ -1,4 +1,4 @@
1
- import type { ClientProtocolIdentity } from "../runtime/operation-request.js";
1
+ import type { AvRequestProtocol } from "../runtime/operation-request.js";
2
2
  /** `AvProtocol.headers` — the header names the protocol owns, as passive data. */
3
3
  export declare const headers: Readonly<{
4
4
  readonly protocolVersion: "Aventara-Protocol-Version";
@@ -22,11 +22,11 @@ export declare function normalizeHeaders(received: ReceivedHeaders): Readonly<Re
22
22
  */
23
23
  export declare function headerValue(normalized: Readonly<Record<string, string>>, name: string): string | undefined;
24
24
  /**
25
- * The identity headers as the `ClientProtocolIdentity` core checks, or `undefined`
25
+ * The identity headers as the `AvRequestProtocol` core checks, or `undefined`
26
26
  * when either is ABSENT — a request without them is malformed: it does not say which
27
27
  * protocol and ClientContract it speaks, so there is nothing to compare.
28
28
  */
29
- export declare function protocolIdentityOf(normalized: Readonly<Record<string, string>>): ClientProtocolIdentity | undefined;
29
+ export declare function protocolIdentityOf(normalized: Readonly<Record<string, string>>): AvRequestProtocol | undefined;
30
30
  /**
31
31
  * The identity headers a request did not send, in the order the framework sends them —
32
32
  * what the `A2000` answering it names. Empty exactly when {@link protocolIdentityOf}
@@ -1,4 +1,4 @@
1
- import { AvDecimal } from "./decimal.js";
1
+ import { AvDecimal, decimalDigitsOf, isAvDecimal } from "./decimal.js";
2
2
  const FAILURE_MESSAGES = {
3
3
  "unreadable-value": "Value could not be read.",
4
4
  "non-plain-value": "Value must be plain data.",
@@ -19,7 +19,6 @@ const NO_POLICY = {};
19
19
  const OBJECT_PROTOTYPE = Object.prototype;
20
20
  const DATE_PROTOTYPE = Date.prototype;
21
21
  const dateValueOf = Date.prototype.getTime;
22
- const decimalValueOf = AvDecimal.prototype.toString;
23
22
  const FAST_WALK_DEPTH = 256;
24
23
  const SLOW_WALK = Symbol("slow walk");
25
24
  class FastWalkFailure {
@@ -33,8 +32,8 @@ function copyValueType(value, prototype) {
33
32
  if (prototype === DATE_PROTOTYPE || value instanceof Date) {
34
33
  return new Date(dateValueOf.call(value));
35
34
  }
36
- if (value instanceof AvDecimal) {
37
- return new AvDecimal(decimalValueOf.call(value));
35
+ if (isAvDecimal(value)) {
36
+ return new AvDecimal(decimalDigitsOf(value));
38
37
  }
39
38
  if (value instanceof Uint8Array) {
40
39
  return new Uint8Array(value);
@@ -1,3 +1,4 @@
1
+ import { adapterPrimaryIdentifier } from "../adapters/adapter-identifier.js";
1
2
  import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
2
3
  import { AvComputedValueError } from "./computed-value.error.js";
3
4
  import { frozenCopy } from "./readonly-value.js";
@@ -37,12 +38,6 @@ function appendUniqueProjection(values, value) {
37
38
  values.push(value);
38
39
  }
39
40
  }
40
- function adapterIdentifier(model, resource) {
41
- const resources = model.resources;
42
- return ((Object.hasOwn(resources, resource)
43
- ? resources[resource]?.identifiers?.[0]
44
- : undefined) ?? []);
45
- }
46
41
  function supportsProjection(request) {
47
42
  if (request.family === "upsert") {
48
43
  return true;
@@ -59,7 +54,7 @@ function addScalarProjection(core, model, active, compute, resourceName, fieldNa
59
54
  if (stage !== undefined) {
60
55
  appendUnique(computed, fieldName);
61
56
  if (stage.dependsOn.length === 0) {
62
- for (const member of adapterIdentifier(model, resourceName)) {
57
+ for (const member of adapterPrimaryIdentifier(model, resourceName)) {
63
58
  appendUniqueProjection(adapterSelect, member);
64
59
  }
65
60
  }
@@ -7,7 +7,7 @@ import type { AvOperationFamily } from "../operations/operation-identity.js";
7
7
  import { type AvDiagnosticsSink } from "./diagnostics-reporter.js";
8
8
  import type { AvComputeContext } from "./field-compute.js";
9
9
  import type { MisuseReporter } from "./misuse-reporter.js";
10
- import type { ApplicationOperationRequest } from "./operation-request.js";
10
+ import type { AvApplicationOperationRequest } from "./operation-request.js";
11
11
  import type { AvOperationResponse } from "./operation-response.js";
12
12
  import type { AvContext, AvTransactionContext, AvTransportContext } from "./pipeline.js";
13
13
  import type { AvState } from "./request-state.js";
@@ -35,7 +35,7 @@ export type ContextFacts = {
35
35
  readonly $args: unknown;
36
36
  };
37
37
  /** Runs one raw application-origin call in `transaction`, or standalone. Total. */
38
- export type RawOperationRunner<M extends AvAdapterModel> = (request: ApplicationOperationRequest, transaction: AvAdapterTransactionExecutor<M> | undefined) => Promise<AvOperationResponse>;
38
+ export type RawOperationRunner<M extends AvAdapterModel> = (request: AvApplicationOperationRequest, transaction: AvAdapterTransactionExecutor<M> | undefined) => Promise<AvOperationResponse>;
39
39
  export type ContextExecutorDependencies<M extends AvAdapterModel> = {
40
40
  /** The model, as construction read it once: the ctx's Resources. */
41
41
  readonly model: M;