@aventara/core 1.0.0-rc.10 → 1.0.0-rc.11

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 (110) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/ERRORS.md +82 -82
  3. package/README.md +57 -1
  4. package/dist/compiler/contract-compiler-diagnostic.guidance.js +1 -1
  5. package/dist/compiler/contract.compiler.js +56 -9
  6. package/dist/compiler/create-requirements.projector.js +1 -3
  7. package/dist/compiler/field-validator.compiler.js +1 -3
  8. package/dist/compiler/managed-elsewhere.projector.js +5 -5
  9. package/dist/config/cache-stage.factory.d.ts +24 -0
  10. package/dist/config/cache-stage.factory.js +3 -0
  11. package/dist/config/config.resolver.js +1 -7
  12. package/dist/config/config.validator.js +61 -13
  13. package/dist/config/exact-framework-config.d.ts +6 -0
  14. package/dist/config/field-compute.resolver.js +1 -3
  15. package/dist/config/pipeline-config.d.ts +16 -0
  16. package/dist/config/pipeline-tree.resolver.js +2 -3
  17. package/dist/contracts/canonical-json.writer.d.ts +7 -0
  18. package/dist/contracts/canonical-json.writer.js +55 -0
  19. package/dist/contracts/client-contract-structure.validator.d.ts +64 -0
  20. package/dist/contracts/client-contract-structure.validator.js +12 -0
  21. package/dist/contracts/contract-canonicalizer.js +5 -0
  22. package/dist/contracts/contract-limits.d.ts +6 -0
  23. package/dist/contracts/contract-limits.js +2 -0
  24. package/dist/contracts/contract.d.ts +15 -0
  25. package/dist/contracts/json-schema.projector.js +1 -3
  26. package/dist/framework/access-facade.js +1 -3
  27. package/dist/framework/framework.factory.js +42 -13
  28. package/dist/framework/operation-facade.d.ts +67 -6
  29. package/dist/framework/operation-facade.js +7 -0
  30. package/dist/framework/operation-set.runner.d.ts +47 -0
  31. package/dist/framework/operation-set.runner.js +168 -0
  32. package/dist/index.d.ts +2 -0
  33. package/dist/index.js +2 -0
  34. package/dist/internal.d.ts +2 -0
  35. package/dist/internal.js +2 -0
  36. package/dist/operations/operation-validator-core.js +2 -4
  37. package/dist/operations/structural-limits.walker.js +1 -3
  38. package/dist/policy/access-question.runtime.js +1 -3
  39. package/dist/policy/policy-admission.js +1 -3
  40. package/dist/policy/policy-after-update.runtime.js +1 -3
  41. package/dist/policy/policy-nested-write.runtime.js +3 -12
  42. package/dist/policy/policy-read.runtime.js +1 -3
  43. package/dist/policy/policy-reference-write.runtime.js +2 -11
  44. package/dist/policy/policy-request.d.ts +31 -0
  45. package/dist/policy/policy-request.js +69 -17
  46. package/dist/policy/policy-session.js +1 -3
  47. package/dist/policy/policy-write.runtime.js +1 -3
  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.js +1 -3
  51. package/dist/prebuilt/prebuilt-contracts.loader.js +1 -3
  52. package/dist/prebuilt/prebuilt-inputs.fingerprint.js +1 -3
  53. package/dist/protocol/absent-route.responder.d.ts +69 -16
  54. package/dist/protocol/absent-route.responder.js +75 -31
  55. package/dist/protocol/contract-document.responder.d.ts +7 -0
  56. package/dist/protocol/contract-document.responder.js +3 -2
  57. package/dist/protocol/http-request.decoder.d.ts +33 -0
  58. package/dist/protocol/http-request.decoder.js +23 -7
  59. package/dist/protocol/http-request.reader.d.ts +4 -0
  60. package/dist/protocol/http-request.reader.js +11 -2
  61. package/dist/protocol/http-response.encoder.d.ts +12 -0
  62. package/dist/protocol/http-response.encoder.js +15 -1
  63. package/dist/protocol/operation-response.guard.js +1 -3
  64. package/dist/protocol/operation-set.decoder.d.ts +48 -0
  65. package/dist/protocol/operation-set.decoder.js +185 -0
  66. package/dist/protocol/operation-set.handler.d.ts +27 -0
  67. package/dist/protocol/operation-set.handler.js +179 -0
  68. package/dist/protocol/protocol-headers.d.ts +9 -0
  69. package/dist/protocol/protocol-headers.js +12 -5
  70. package/dist/protocol/protocol-surface.d.ts +12 -4
  71. package/dist/protocol/protocol-surface.js +34 -1
  72. package/dist/protocol/protocol.binder.d.ts +37 -1
  73. package/dist/protocol/protocol.binder.js +2 -0
  74. package/dist/protocol/protocol.handler.d.ts +35 -2
  75. package/dist/protocol/protocol.handler.js +84 -8
  76. package/dist/protocol/read-cache.responder.d.ts +26 -0
  77. package/dist/protocol/read-cache.responder.js +47 -0
  78. package/dist/protocol/read-query.codec.d.ts +34 -0
  79. package/dist/protocol/read-query.codec.js +125 -0
  80. package/dist/runtime/computed-read-runtime.js +2 -8
  81. package/dist/runtime/create-requirement.js +1 -3
  82. package/dist/runtime/diagnostics-reporter.d.ts +6 -1
  83. package/dist/runtime/diagnostics-reporter.js +3 -8
  84. package/dist/runtime/error.catalog.d.ts +2 -2
  85. package/dist/runtime/error.catalog.js +1 -1
  86. package/dist/runtime/field-stage.runtime.d.ts +0 -2
  87. package/dist/runtime/field-stage.runtime.js +1 -7
  88. package/dist/runtime/field-validator.runtime.js +2 -1
  89. package/dist/runtime/mutation-planner.js +1 -3
  90. package/dist/runtime/nested-write.walker.js +2 -1
  91. package/dist/runtime/output-validator.js +1 -3
  92. package/dist/runtime/owned-execution.registry.d.ts +22 -2
  93. package/dist/runtime/pipeline.d.ts +5 -1
  94. package/dist/runtime/record.guard.d.ts +14 -0
  95. package/dist/runtime/record.guard.js +10 -0
  96. package/dist/runtime/request-envelope.validator.js +13 -10
  97. package/dist/runtime/scalar-runtime.d.ts +0 -10
  98. package/dist/runtime/scalar-runtime.js +1 -10
  99. package/dist/runtime/thenable.guard.d.ts +7 -0
  100. package/dist/runtime/thenable.guard.js +5 -0
  101. package/dist/runtime/unsent-value.normalizer.js +2 -1
  102. package/dist/transactions/operation-arguments-encoder.js +1 -7
  103. package/dist/transactions/sql-statement.validator.js +2 -1
  104. package/dist/transactions/transaction-plan-assembler.js +1 -7
  105. package/dist/transactions/transaction-plan-validator.js +4 -6
  106. package/dist/transactions/transaction-reference-collector.js +8 -10
  107. package/dist/transactions/transaction-reference-resolver.js +1 -3
  108. package/dist/transactions/transaction-runtime.js +31 -2
  109. package/dist/transactions/transaction-stage.runtime.js +1 -3
  110. package/package.json +1 -1
@@ -3,29 +3,40 @@ import { createDiagnosticsReporter, createReporter, } from "../runtime/diagnosti
3
3
  import { sanitizeOperationFailure } from "../runtime/error-boundary.js";
4
4
  import { internalFailure, } from "../runtime/operation-response.js";
5
5
  import { capturePlanAtCall } from "../transactions/transaction-plan-validator.js";
6
- import { decodeCanRequest, decodeOperationRequest, decodeTransactionRequest, isParsedByDecoder, } from "./http-request.decoder.js";
6
+ import { checkOperationRoute } from "./absent-route.responder.js";
7
+ import { adoptParsedBody, decodeCanRequest, decodeOperationRead, decodeTransactionRequest, isParsedByDecoder, transportOf, } from "./http-request.decoder.js";
8
+ import { readRequest } from "./http-request.reader.js";
7
9
  import { encodeOperationResponse, encodeTransactionResponse, } from "./http-response.encoder.js";
8
- function admitOperation(context, request) {
10
+ import { transportFailure } from "./protocol-failure.js";
11
+ import { headerValue } from "./protocol-headers.js";
12
+ import { cachedReadAnswer, uncachedAnswer } from "./read-cache.responder.js";
13
+ import { readArgumentsOf } from "./read-query.codec.js";
14
+ export function admitOperation(context, request) {
9
15
  const owned = context.owned;
10
16
  if (isParsedByDecoder(request.arguments)) {
11
- return { request, run: owned?.execute ?? context.execute };
17
+ return {
18
+ request,
19
+ run: owned === undefined
20
+ ? (admitted) => context.execute(admitted)
21
+ : (admitted, member) => owned.execute(admitted, undefined, member),
22
+ };
12
23
  }
13
24
  const copy = copyAtDoor(request.arguments);
14
25
  if (owned === undefined) {
15
26
  return {
16
27
  request: copy.ok ? { ...request, arguments: copy.value } : request,
17
- run: context.execute,
28
+ run: (admitted) => context.execute(admitted),
18
29
  };
19
30
  }
20
31
  if (copy.ok) {
21
32
  return {
22
33
  request: { ...request, arguments: copy.value },
23
- run: (admitted) => owned.execute(admitted),
34
+ run: (admitted, member) => owned.execute(admitted, undefined, member),
24
35
  };
25
36
  }
26
37
  return {
27
38
  request: { ...request, arguments: {} },
28
- run: (admitted) => owned.execute(admitted, copy.error),
39
+ run: (admitted, member) => owned.execute(admitted, copy.error, member),
29
40
  };
30
41
  }
31
42
  function admitTransaction(context, request) {
@@ -50,7 +61,7 @@ function admitTransaction(context, request) {
50
61
  run: (admitted) => owned.executeTransaction(admitted, captured.refused.size === 0 ? undefined : captured.refused),
51
62
  };
52
63
  }
53
- async function seededState(options, report, view) {
64
+ export async function seededState(options, report, view) {
54
65
  if (options?.state === undefined) {
55
66
  return { ok: true, state: undefined };
56
67
  }
@@ -82,10 +93,20 @@ export function operationUnencodableReport(sink, request) {
82
93
  return (error) => createDiagnosticsReporter(sink, request)(error, "A3003");
83
94
  }
84
95
  export async function handleOperationRequest(context, request, options) {
85
- const decoded = decodeOperationRequest(context.contracts, request, context.origin);
96
+ const read = readRequest(request);
97
+ if (!read.ok) {
98
+ return read.response;
99
+ }
100
+ if (read.request.method === "GET") {
101
+ return handleCachedRead(context, read, options);
102
+ }
103
+ const decoded = decodeOperationRead(context.contracts, read, context.origin);
86
104
  if (!decoded.ok) {
87
105
  return decoded.response;
88
106
  }
107
+ return runOperation(context, decoded, options);
108
+ }
109
+ async function runOperation(context, decoded, options) {
89
110
  const admitted = admitOperation(context, decoded.request);
90
111
  const seeded = await seededState(options, createDiagnosticsReporter(context.sink, admitted.request), { path: ["arguments"], arguments: admitted.request.arguments });
91
112
  const response = seeded.ok
@@ -95,6 +116,61 @@ export async function handleOperationRequest(context, request, options) {
95
116
  : seeded.response;
96
117
  return encodeOperationResponse(response, decoded.requestId, operationUnencodableReport(context.sink, decoded.request));
97
118
  }
119
+ async function handleCachedRead(context, read, options) {
120
+ const route = checkOperationRoute(context.contracts, read, context.origin);
121
+ if (!route.ok) {
122
+ return uncachedAnswer(route.response);
123
+ }
124
+ const { cache, query } = route;
125
+ if (cache === undefined || query === undefined) {
126
+ return uncachedAnswer(transportFailure("A2000", read.requestId));
127
+ }
128
+ const args = await readArgumentsOf(query.q, context.contracts[context.origin].limits.maxRequestBytes);
129
+ if (!args.ok) {
130
+ return uncachedAnswer(transportFailure(args.code, read.requestId));
131
+ }
132
+ adoptParsedBody(args.value);
133
+ const request = {
134
+ origin: context.origin,
135
+ requestId: read.requestId,
136
+ resource: route.resource,
137
+ ...route.operation,
138
+ protocol: route.protocol,
139
+ transport: transportOf(read),
140
+ arguments: args.value,
141
+ };
142
+ const ifNoneMatch = headerValue(read.headers, "if-none-match");
143
+ const owned = context.owned;
144
+ if (cache.for !== "anonymous" || owned === undefined) {
145
+ const answer = await runOperation(context, { ok: true, request, requestId: read.requestId }, options);
146
+ return cachedReadAnswer(answer, cache, ifNoneMatch, false);
147
+ }
148
+ const report = createDiagnosticsReporter(context.sink, request);
149
+ const seeded = await seededState(options, report, {
150
+ path: ["arguments"],
151
+ arguments: request.arguments,
152
+ });
153
+ if (!seeded.ok) {
154
+ return uncachedAnswer(encodeOperationResponse(seeded.response, read.requestId));
155
+ }
156
+ const state = seeded.state ?? {};
157
+ let caller;
158
+ try {
159
+ caller = await owned.caller({ origin: context.origin, state });
160
+ }
161
+ catch (error) {
162
+ report(error, "A3000");
163
+ return uncachedAnswer(encodeOperationResponse(internalFailure(), read.requestId));
164
+ }
165
+ const response = await owned.execute({ ...request, state }, undefined, caller === undefined ? undefined : { caller });
166
+ return cachedReadAnswer(encodeOperationResponse(response, read.requestId, operationUnencodableReport(context.sink, request)), cache, ifNoneMatch, isAnonymous(caller, read.headers));
167
+ }
168
+ function isAnonymous(caller, headers) {
169
+ return caller === undefined
170
+ ? headerValue(headers, "cookie") === undefined &&
171
+ headerValue(headers, "authorization") === undefined
172
+ : caller.principal === null || caller.principal === undefined;
173
+ }
98
174
  export async function handleTransactionRequest(context, request, options) {
99
175
  const decoded = decodeTransactionRequest(context.contracts, request, context.origin);
100
176
  if (!decoded.ok) {
@@ -0,0 +1,26 @@
1
+ import type { AvContractClient } from "../contracts/contract.js";
2
+ import type { AvProtocolResponse } from "./http-response.encoder.js";
3
+ /** A cached read's mark, as the Client Contract states it. */
4
+ type ReadCacheMark = NonNullable<NonNullable<AvContractClient["resources"][string]["cache"]>["first" | "unique" | "many" | "count"]>;
5
+ /**
6
+ * A `GET`'s answer that must not be kept: `Cache-Control: no-store` (with
7
+ * `Referrer-Policy: no-referrer` and `X-Content-Type-Options: nosniff`). Every
8
+ * refusal, a stale client's `A2005` included, and every answer but `200`.
9
+ */
10
+ export declare function uncachedAnswer(response: AvProtocolResponse): AvProtocolResponse;
11
+ /**
12
+ * A cached read's answer. A `200` gets, by the mark's `for`:
13
+ *
14
+ * - `"everyone"`: `Cache-Control: public, max-age=<n>`;
15
+ * - `"anonymous"`: `public` to an `anonymous` caller, `private` otherwise, both
16
+ * with `Vary: Authorization, Cookie`;
17
+ * - `"user"`: `Cache-Control: private, max-age=<n>`, `Vary: Authorization,
18
+ * Cookie`;
19
+ *
20
+ * and `Referrer-Policy: no-referrer`, `X-Content-Type-Options: nosniff` and an
21
+ * `ETag` over its body; it is `304` with no body when `ifNoneMatch` names that
22
+ * tag. A public answer echoes no `Aventara-Request-Id`. Any other status is
23
+ * {@link uncachedAnswer}.
24
+ */
25
+ export declare function cachedReadAnswer(response: AvProtocolResponse, cache: ReadCacheMark, ifNoneMatch: string | undefined, anonymous: boolean): Promise<AvProtocolResponse>;
26
+ export {};
@@ -0,0 +1,47 @@
1
+ import { noneMatchHolds } from "./contract-document.responder.js";
2
+ import { headers, NO_SNIFF } from "./protocol-headers.js";
3
+ const CALLER_VARY = "Authorization, Cookie";
4
+ export function uncachedAnswer(response) {
5
+ return {
6
+ ...response,
7
+ headers: {
8
+ ...response.headers,
9
+ "Cache-Control": "no-store",
10
+ "Referrer-Policy": "no-referrer",
11
+ ...NO_SNIFF,
12
+ },
13
+ };
14
+ }
15
+ export async function cachedReadAnswer(response, cache, ifNoneMatch, anonymous) {
16
+ if (response.status !== 200) {
17
+ return uncachedAnswer(response);
18
+ }
19
+ const shared = cache.for === "everyone" || (cache.for === "anonymous" && anonymous);
20
+ const validators = {
21
+ "Cache-Control": `${shared ? "public" : "private"}, max-age=${cache.maxAge}`,
22
+ ...(cache.for === "everyone" ? {} : { Vary: CALLER_VARY }),
23
+ "Referrer-Policy": "no-referrer",
24
+ ...NO_SNIFF,
25
+ ETag: `"${await bodyDigest(response.body)}"`,
26
+ };
27
+ const kept = { ...response.headers };
28
+ if (shared) {
29
+ delete kept[headers.requestId];
30
+ }
31
+ if (noneMatchHolds(ifNoneMatch, validators.ETag)) {
32
+ delete kept["Content-Type"];
33
+ return { status: 304, headers: { ...kept, ...validators }, body: "" };
34
+ }
35
+ return { ...response, headers: { ...kept, ...validators } };
36
+ }
37
+ async function bodyDigest(body) {
38
+ const digest = new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(body)));
39
+ let binary = "";
40
+ for (const byte of digest) {
41
+ binary += String.fromCharCode(byte);
42
+ }
43
+ return btoa(binary)
44
+ .replace(/\+/g, "-")
45
+ .replace(/\//g, "_")
46
+ .replace(/=+$/, "");
47
+ }
@@ -0,0 +1,34 @@
1
+ /** A cached read's query: its two parameters, each percent-decoded once. */
2
+ export type ReadQuery = {
3
+ /** The arguments, in either form. */
4
+ readonly q: string;
5
+ /** The Client Contract hash the client was generated against. */
6
+ readonly v: string;
7
+ };
8
+ /**
9
+ * The query's `q` and `v`, or `undefined` when it is anything else: absent,
10
+ * either parameter missing or repeated, another parameter, or a percent-escape
11
+ * that does not decode.
12
+ */
13
+ export declare function readQueryOf(query: string | undefined): ReadQuery | undefined;
14
+ /** What reading `q` answers: the arguments object, or the transport code refusing it. */
15
+ type ReadArguments = {
16
+ readonly ok: true;
17
+ readonly value: object;
18
+ } | {
19
+ readonly ok: false;
20
+ readonly code: "A2000" | "A2010";
21
+ };
22
+ /**
23
+ * `q` as the arguments object: plain JSON when it starts with `{`, otherwise
24
+ * base64url(deflate-raw(JSON)). More than `maxBytes` bytes of JSON, or of
25
+ * compressed input, is `A2010`; anything that is not base64url, deflate-raw,
26
+ * UTF-8 or a JSON object is `A2000`.
27
+ */
28
+ export declare function readArgumentsOf(q: string, maxBytes: number): Promise<ReadArguments>;
29
+ /**
30
+ * A read's `q`: its arguments' canonical JSON, percent-encoded, or that JSON
31
+ * deflate-raw'd and base64url'd when that is shorter.
32
+ */
33
+ export declare function readQueryArgumentOf(args: object): Promise<string>;
34
+ export {};
@@ -0,0 +1,125 @@
1
+ import { canonicalJson } from "../contracts/canonical-json.writer.js";
2
+ const BASE64URL = /^[A-Za-z0-9_-]*$/;
3
+ export function readQueryOf(query) {
4
+ if (query === undefined || query === "") {
5
+ return undefined;
6
+ }
7
+ const found = new Map();
8
+ for (const pair of query.split("&")) {
9
+ const equals = pair.indexOf("=");
10
+ const name = equals === -1 ? pair : pair.slice(0, equals);
11
+ if ((name !== "q" && name !== "v") || found.has(name) || equals === -1) {
12
+ return undefined;
13
+ }
14
+ const value = percentDecoded(pair.slice(equals + 1));
15
+ if (value === undefined) {
16
+ return undefined;
17
+ }
18
+ found.set(name, value);
19
+ }
20
+ const q = found.get("q");
21
+ const v = found.get("v");
22
+ return q === undefined || v === undefined ? undefined : { q, v };
23
+ }
24
+ function percentDecoded(text) {
25
+ try {
26
+ return decodeURIComponent(text);
27
+ }
28
+ catch {
29
+ return undefined;
30
+ }
31
+ }
32
+ export async function readArgumentsOf(q, maxBytes) {
33
+ if (q.startsWith("{")) {
34
+ const bytes = new TextEncoder().encode(q);
35
+ return bytes.byteLength > maxBytes
36
+ ? { ok: false, code: "A2010" }
37
+ : parsedObject(q);
38
+ }
39
+ if (!BASE64URL.test(q) || q.length % 4 === 1) {
40
+ return { ok: false, code: "A2000" };
41
+ }
42
+ if (Math.floor((q.length * 3) / 4) > maxBytes) {
43
+ return { ok: false, code: "A2010" };
44
+ }
45
+ const compressed = base64urlBytes(q);
46
+ const inflated = await inflatedWithin(compressed, maxBytes);
47
+ if (inflated.ok === false) {
48
+ return inflated;
49
+ }
50
+ let text;
51
+ try {
52
+ text = new TextDecoder("utf-8", { fatal: true }).decode(inflated.bytes);
53
+ }
54
+ catch {
55
+ return { ok: false, code: "A2000" };
56
+ }
57
+ return parsedObject(text);
58
+ }
59
+ function parsedObject(text) {
60
+ let value;
61
+ try {
62
+ value = JSON.parse(text);
63
+ }
64
+ catch {
65
+ return { ok: false, code: "A2000" };
66
+ }
67
+ return typeof value === "object" && value !== null && !Array.isArray(value)
68
+ ? { ok: true, value }
69
+ : { ok: false, code: "A2000" };
70
+ }
71
+ function base64urlBytes(text) {
72
+ const binary = atob(text.replace(/-/g, "+").replace(/_/g, "/") +
73
+ "=".repeat((4 - (text.length % 4)) % 4));
74
+ const bytes = new Uint8Array(binary.length);
75
+ for (let index = 0; index < binary.length; index += 1) {
76
+ bytes[index] = binary.charCodeAt(index);
77
+ }
78
+ return bytes;
79
+ }
80
+ async function inflatedWithin(compressed, maxBytes) {
81
+ const stream = new Blob([compressed])
82
+ .stream()
83
+ .pipeThrough(new DecompressionStream("deflate-raw"));
84
+ const reader = stream.getReader();
85
+ const chunks = [];
86
+ let total = 0;
87
+ try {
88
+ for (;;) {
89
+ const { done, value } = await reader.read();
90
+ if (done) {
91
+ break;
92
+ }
93
+ total += value.byteLength;
94
+ if (total > maxBytes) {
95
+ await reader.cancel().catch(() => undefined);
96
+ return { ok: false, code: "A2010" };
97
+ }
98
+ chunks.push(value);
99
+ }
100
+ }
101
+ catch {
102
+ return { ok: false, code: "A2000" };
103
+ }
104
+ const bytes = new Uint8Array(total);
105
+ let offset = 0;
106
+ for (const chunk of chunks) {
107
+ bytes.set(chunk, offset);
108
+ offset += chunk.byteLength;
109
+ }
110
+ return { ok: true, bytes };
111
+ }
112
+ export async function readQueryArgumentOf(args) {
113
+ const json = canonicalJson(args);
114
+ const plain = encodeURIComponent(json);
115
+ const deflated = await new Response(new Blob([json]).stream().pipeThrough(new CompressionStream("deflate-raw"))).arrayBuffer();
116
+ let binary = "";
117
+ for (const byte of new Uint8Array(deflated)) {
118
+ binary += String.fromCharCode(byte);
119
+ }
120
+ const compressed = btoa(binary)
121
+ .replace(/\+/g, "-")
122
+ .replace(/\//g, "_")
123
+ .replace(/=+$/, "");
124
+ return compressed.length < plain.length ? compressed : plain;
125
+ }
@@ -2,10 +2,9 @@ import { adapterPrimaryIdentifier } from "../adapters/adapter-identifier.js";
2
2
  import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
3
3
  import { AvComputedValueError } from "./computed-value.error.js";
4
4
  import { frozenCopy } from "./readonly-value.js";
5
+ import { isRecord } from "./record.guard.js";
5
6
  import { normalizeScalarFieldValue } from "./scalar-runtime.js";
6
- function isRecord(value) {
7
- return value !== null && typeof value === "object" && !Array.isArray(value);
8
- }
7
+ import { isThenable } from "./thenable.guard.js";
9
8
  function readCompute(value) {
10
9
  if (typeof value === "function") {
11
10
  return { compute: value, dependsOn: [] };
@@ -342,11 +341,6 @@ function isNestedContainer(value) {
342
341
  !(value instanceof Date) &&
343
342
  !(value instanceof Uint8Array));
344
343
  }
345
- function isThenable(value) {
346
- return (value !== null &&
347
- (typeof value === "object" || typeof value === "function") &&
348
- typeof value.then === "function");
349
- }
350
344
  function setOwn(target, key, value) {
351
345
  if (key in target) {
352
346
  Object.defineProperty(target, key, {
@@ -2,9 +2,7 @@ import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
2
2
  import { advertisedDirectivesAt } from "../contracts/field.js";
3
3
  import { LOOPBACK_WRITE_SCOPE, NESTED_WRITE_SCOPE, resolveWriteScope, } from "../operations/write-scope.resolver.js";
4
4
  import { isRelationMutationDirective, RELATION_MUTATION_RECORD_SITES, } from "./mutation-planner.js";
5
- function isRecord(value) {
6
- return value !== null && typeof value === "object" && !Array.isArray(value);
7
- }
5
+ import { isRecord } from "./record.guard.js";
8
6
  function written(record, key) {
9
7
  return Object.hasOwn(record, key) && record[key] !== undefined;
10
8
  }
@@ -19,6 +19,11 @@ export type AvOperationDiagnostic = {
19
19
  readonly variant: string;
20
20
  readonly requestId: string;
21
21
  readonly operation?: number;
22
+ /**
23
+ * The key of the Operation Set entry (`POST /_all`) the failure met; absent
24
+ * for an operation that ran alone or as a transaction step.
25
+ */
26
+ readonly key?: string;
22
27
  /**
23
28
  * `true` when `error` is an `AvError` its thrower (a pipeline
24
29
  * callback, a compute stage, a filter or an Adapter) raised deliberately with an
@@ -134,7 +139,7 @@ export declare function diagnosticsSinkOf(framework: object): AvDiagnosticsSink
134
139
  * Binds the configured sink to one operation request. The sink is not awaited,
135
140
  * and nothing it throws or rejects with reaches the response.
136
141
  */
137
- export declare function createDiagnosticsReporter(sink: AvDiagnosticsSink | undefined, request: AvOperationRequest, operation?: number): DiagnosticsReporter;
142
+ export declare function createDiagnosticsReporter(sink: AvDiagnosticsSink | undefined, request: AvOperationRequest, operation?: number, key?: string): DiagnosticsReporter;
138
143
  /** The diagnostics channel of one transaction request. */
139
144
  export type TransactionDiagnosticsChannel = {
140
145
  /** A plan-level report; `operation` attributes it to a recorded failing index. */
@@ -1,14 +1,8 @@
1
1
  import { coreBrandOf } from "./core-class.brand.js";
2
2
  import { internalFailure, } from "./operation-response.js";
3
3
  import { requestIdOf } from "./request-id.js";
4
+ import { isThenable } from "./thenable.guard.js";
4
5
  const IGNORE_DIAGNOSTIC = () => undefined;
5
- function isThenable(value) {
6
- if ((typeof value !== "object" || value === null) &&
7
- typeof value !== "function") {
8
- return false;
9
- }
10
- return typeof value.then === "function";
11
- }
12
6
  export function createReporter(sink, identity) {
13
7
  if (sink === undefined) {
14
8
  return IGNORE_DIAGNOSTIC;
@@ -74,7 +68,7 @@ export function recordDiagnosticsSink(framework, sink) {
74
68
  export function diagnosticsSinkOf(framework) {
75
69
  return CONFIGURED_SINKS.get(framework);
76
70
  }
77
- export function createDiagnosticsReporter(sink, request, operation) {
71
+ export function createDiagnosticsReporter(sink, request, operation, key) {
78
72
  let identity;
79
73
  try {
80
74
  identity = {
@@ -85,6 +79,7 @@ export function createDiagnosticsReporter(sink, request, operation) {
85
79
  variant: request.variant,
86
80
  requestId: request.requestId,
87
81
  ...(operation === undefined ? {} : { operation }),
82
+ ...(key === undefined ? {} : { key }),
88
83
  };
89
84
  }
90
85
  catch {
@@ -8,7 +8,7 @@ import { type AvOperationCode } from "./operation-response.js";
8
8
  * The documentation page every catalog entry links into. Each code has its own
9
9
  * anchor there: the code in lower case (`#a2004`, `#config_required`).
10
10
  */
11
- export declare const ERROR_CODES_DOCS_URL = "https://aventara-framework.vercel.app/docs/error-codes";
11
+ export declare const ERROR_CODES_DOCS_URL = "https://aventara.razor34.dev/framework/docs/error-codes";
12
12
  /**
13
13
  * What kind of code an entry is: an outcome code's family (`success` `A1xxx`,
14
14
  * `request` `A2xxx`, `internal` `A3xxx`, `auth` `A4xxx`), a `validation` issue
@@ -43,7 +43,7 @@ export declare function errorDocsUrl(code: AvErrorCatalogCode): string;
43
43
  *
44
44
  * ```ts
45
45
  * AvErrorCatalog[error.code].fix;
46
- * AvErrorCatalog.V1000.docs; // https://aventara-framework.vercel.app/docs/error-codes#v1000
46
+ * AvErrorCatalog.V1000.docs; // https://aventara.razor34.dev/framework/docs/error-codes#v1000
47
47
  * ```
48
48
  */
49
49
  export declare const AvErrorCatalog: AvErrorCatalog;
@@ -5,7 +5,7 @@ import { VALIDATION_CODE_GUIDANCE } from "../operations/validation-code.guidance
5
5
  import { VALIDATION_CODE_MESSAGES, VALIDATION_CODES, } from "../operations/validation-code.js";
6
6
  import { OPERATION_CODE_GUIDANCE } from "./operation-code.guidance.js";
7
7
  import { OPERATION_CODES } from "./operation-response.js";
8
- export const ERROR_CODES_DOCS_URL = "https://aventara-framework.vercel.app/docs/error-codes";
8
+ export const ERROR_CODES_DOCS_URL = "https://aventara.razor34.dev/framework/docs/error-codes";
9
9
  export function errorDocsUrl(code) {
10
10
  return `${ERROR_CODES_DOCS_URL}#${code.toLowerCase()}`;
11
11
  }
@@ -4,8 +4,6 @@ import type { AvOperationFamily } from "../operations/operation-identity.js";
4
4
  import type { AvContext } from "./pipeline.js";
5
5
  export type Path = readonly (string | number)[];
6
6
  type UnknownRecord = Readonly<Record<string, unknown>>;
7
- /** A plain object: what a write record, or a directive's operand, is. */
8
- export declare function isPlainRecord(value: unknown): value is UnknownRecord;
9
7
  /** The containers a family writes, by argument key. */
10
8
  export declare const WRITE_KEYS: Readonly<Partial<Record<AvOperationFamily, readonly string[]>>>;
11
9
  /** The fields of `resource` with stages, in the Resource's field order. */
@@ -1,11 +1,5 @@
1
1
  import { frozenCopy } from "./readonly-value.js";
2
- export function isPlainRecord(value) {
3
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
4
- return false;
5
- }
6
- const prototype = Object.getPrototypeOf(value);
7
- return prototype === Object.prototype || prototype === null;
8
- }
2
+ import { isPlainRecord } from "./record.guard.js";
9
3
  export const WRITE_KEYS = {
10
4
  create: ["data"],
11
5
  update: ["data"],
@@ -1,8 +1,9 @@
1
1
  import { hasFieldValidators, validatedFieldsOf, } from "../compiler/field-validator.compiler.js";
2
2
  import { AvComputedValueError } from "./computed-value.error.js";
3
3
  import { entryCheckIssues, fieldCheckIssues } from "./field-check.evaluator.js";
4
- import { isPlainRecord, valueAt, writeRecordPathsOf, } from "./field-stage.runtime.js";
4
+ import { valueAt, writeRecordPathsOf, } from "./field-stage.runtime.js";
5
5
  import { nestedWriteTargetsOf } from "./nested-write.walker.js";
6
+ import { isPlainRecord } from "./record.guard.js";
6
7
  import { jsonShapeIssues } from "./scalar-runtime.js";
7
8
  function isDirective(value) {
8
9
  return (isPlainRecord(value) &&
@@ -2,6 +2,7 @@ import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
2
2
  import { AvComputedValueError } from "./computed-value.error.js";
3
3
  import { replaceAt, valueAt } from "./field-stage.runtime.js";
4
4
  import { frozenCopy } from "./readonly-value.js";
5
+ import { isRecord } from "./record.guard.js";
5
6
  import { normalizeScalarFieldValue } from "./scalar-runtime.js";
6
7
  function writeScalarField(contracts, resourceName, fieldName) {
7
8
  const field = lookupField(lookupResource(contracts.core, resourceName), fieldName) ??
@@ -26,9 +27,6 @@ export const RELATION_MUTATION_RECORD_SITES = {
26
27
  export function isRelationMutationDirective(key) {
27
28
  return Object.hasOwn(RELATION_MUTATION_RECORD_SITES, key);
28
29
  }
29
- function isRecord(value) {
30
- return value !== null && typeof value === "object" && !Array.isArray(value);
31
- }
32
30
  function cloneRecord(value) {
33
31
  return { ...value };
34
32
  }
@@ -1,6 +1,7 @@
1
1
  import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
2
- import { isPlainRecord, valueAt, writeRecordPathsOf, } from "./field-stage.runtime.js";
2
+ import { valueAt, writeRecordPathsOf, } from "./field-stage.runtime.js";
3
3
  import { isRelationMutationDirective, RELATION_MUTATION_RECORD_SITES, } from "./mutation-planner.js";
4
+ import { isPlainRecord } from "./record.guard.js";
4
5
  const DIRECTIVE_FAMILY = {
5
6
  $create: "create",
6
7
  $connectOrCreate: "create",
@@ -1,12 +1,10 @@
1
1
  import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
2
2
  import { advertisedReducers, } from "../contracts/field.js";
3
+ import { isRecord } from "./record.guard.js";
3
4
  import { normalizeScalarFieldValue } from "./scalar-runtime.js";
4
5
  function issue(message, path = ["data"]) {
5
6
  return { ok: false, issues: [{ path, code: "V1001", message }] };
6
7
  }
7
- function isRecord(value) {
8
- return value !== null && typeof value === "object" && !Array.isArray(value);
9
- }
10
8
  function validatesRecordOrNull(value) {
11
9
  return value === null || isRecord(value);
12
10
  }
@@ -1,15 +1,35 @@
1
+ import type { RemoteRequestOrigin } from "../config/config-layer.js";
2
+ import type { PolicyCaller } from "../policy/policy-request.js";
1
3
  import type { AvAgentTransactionRequest, AvClientTransactionRequest } from "../transactions/transaction-request.js";
2
4
  import type { AvTransactionResponse } from "../transactions/transaction-response.js";
3
5
  import type { AvCallerDataError } from "./caller-data.copier.js";
4
6
  import type { RemoteOperationRequest } from "./operation-request.js";
5
7
  import type { AvOperationResponse } from "./operation-response.js";
6
- /** The two entries that run a request the protocol decoded itself, uncopied. */
8
+ import type { AvState } from "./request-state.js";
9
+ /**
10
+ * The entries that run a request the protocol decoded itself, uncopied, and
11
+ * what an Operation Set reads off the Framework besides.
12
+ */
7
13
  export type OwnedExecution = {
8
14
  /**
9
15
  * Runs `request` uncopied; with `refused`, its arguments are a host's parsed body
10
16
  * whose copy at the call failed, answered as the door answers that failure.
17
+ * `member` is an Operation Set entry's: the principal derived once for the
18
+ * set, and the entry's key, which its diagnostics name.
11
19
  */
12
- readonly execute: (request: RemoteOperationRequest, refused?: AvCallerDataError) => Promise<AvOperationResponse>;
20
+ readonly execute: (request: RemoteOperationRequest, refused?: AvCallerDataError, member?: {
21
+ readonly caller?: PolicyCaller;
22
+ readonly key?: string;
23
+ }) => Promise<AvOperationResponse>;
24
+ /**
25
+ * The principal a remote request with this state runs as, derived once, or
26
+ * `undefined` when no policy checks it. Throws or rejects as
27
+ * `policy.principal` does.
28
+ */
29
+ readonly caller: (request: {
30
+ readonly origin: RemoteRequestOrigin;
31
+ readonly state: AvState;
32
+ }) => PolicyCaller | undefined | Promise<PolicyCaller>;
13
33
  /**
14
34
  * Runs `request` uncopied; with `refused`, its plan is a host's parsed plan taken
15
35
  * at the call, and these steps' arguments could not be copied there: each is
@@ -17,7 +17,11 @@ import type { AvState } from "./request-state.js";
17
17
  * What a pipeline is told about the HTTP request a client-origin operation arrived on.
18
18
  */
19
19
  export type AvTransportContext = {
20
- readonly method: "POST";
20
+ /**
21
+ * `"POST"`, or `"GET"` for a read of a Resource with `cache` (a browser's
22
+ * prefetch or a CDN's refetch is a GET too).
23
+ */
24
+ readonly method: "POST" | "GET";
21
25
  readonly headers: Readonly<Record<string, string>>;
22
26
  };
23
27
  /** What a pipeline ctx states about the request, whatever the stage. */
@@ -0,0 +1,14 @@
1
+ /** An object that is not a list: what core reads as a record of named values. */
2
+ export declare function isRecord<T extends Readonly<Record<string, unknown>> = Readonly<Record<string, unknown>>>(value: unknown): value is T;
3
+ /**
4
+ * A **plain** record: an object literal (prototype `Object.prototype` or
5
+ * `null`), not a `Date`, a core `AvDecimal`, a `Uint8Array` or another class
6
+ * instance. The one discriminant between an argument OBJECT and a VALUE wherever
7
+ * a position admits both: a write record, a directive's operand, a filter
8
+ * position above all, where a plain record is an operator object and anything
9
+ * else is a value that equality shorthand holds to the field's scalar. The
10
+ * validator (`operation-validator-core.ts`) reads a filter position through this
11
+ * same answer, and an adapter's filter translation is expected to, so a position
12
+ * cannot be operators to one reader and a value to another.
13
+ */
14
+ export declare function isPlainRecord<T extends Readonly<Record<string, unknown>> = Readonly<Record<string, unknown>>>(value: unknown): value is T;
@@ -0,0 +1,10 @@
1
+ export function isRecord(value) {
2
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3
+ }
4
+ export function isPlainRecord(value) {
5
+ if (!isRecord(value)) {
6
+ return false;
7
+ }
8
+ const prototype = Object.getPrototypeOf(value);
9
+ return prototype === Object.prototype || prototype === null;
10
+ }