@aventara/core 0.1.0-pilot.4 → 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.
- package/CHANGELOG.md +338 -0
- package/README.md +100 -3
- package/dist/adapters/adapter-model.d.ts +8 -0
- package/dist/adapters/adapter.interface.d.ts +6 -2
- package/dist/compiler/contract-compiler-diagnostic.d.ts +6 -0
- package/dist/compiler/contract.compiler.js +67 -0
- package/dist/config/config.resolver.d.ts +3 -0
- package/dist/config/config.resolver.js +1 -0
- package/dist/config/config.validator.js +6 -0
- package/dist/config/define-fields.d.ts +10 -3
- package/dist/config/framework-config.d.ts +4 -1
- package/dist/config/framework-fields-config.d.ts +6 -0
- package/dist/contracts/client-contract-structure.validator.d.ts +21 -1
- package/dist/contracts/client-contract-structure.validator.js +99 -2
- package/dist/contracts/contract-canonicalizer.js +32 -0
- package/dist/contracts/contract.d.ts +7 -0
- package/dist/contracts/field.d.ts +10 -0
- package/dist/contracts/json-shape.d.ts +65 -0
- package/dist/contracts/json-shape.js +1 -0
- package/dist/contracts/reference-write-limit.d.ts +7 -0
- package/dist/contracts/reference-write-limit.js +1 -0
- package/dist/contracts/scalar-value-type.d.ts +12 -1
- package/dist/framework/contract-view.d.ts +126 -21
- package/dist/framework/framework.d.ts +5 -3
- package/dist/framework/framework.factory.js +56 -13
- package/dist/index.d.ts +8 -40
- package/dist/index.js +0 -20
- package/dist/internal.d.ts +35 -0
- package/dist/internal.js +16 -0
- package/dist/json/avz.d.ts +96 -0
- package/dist/json/avz.js +212 -0
- package/dist/json/index.d.ts +6 -0
- package/dist/json/index.js +1 -0
- package/dist/json/json-shape.registry.d.ts +22 -0
- package/dist/json/json-shape.registry.js +16 -0
- package/dist/json/zod-issue.translator.d.ts +51 -0
- package/dist/json/zod-issue.translator.js +142 -0
- package/dist/operations/operation-arguments.d.ts +151 -101
- package/dist/operations/operation-identity.d.ts +2 -0
- package/dist/operations/operation-identity.js +3 -0
- package/dist/operations/operation-validator-core.d.ts +1 -1
- package/dist/operations/operation-validator-core.js +28 -10
- package/dist/operations/operation-validator.d.ts +20 -5
- package/dist/operations/operation-validator.js +26 -10
- package/dist/operations/projection-scope.resolver.d.ts +1 -1
- package/dist/operations/structural-limits.walker.d.ts +19 -0
- package/dist/operations/structural-limits.walker.js +344 -0
- package/dist/protocol/absent-route.responder.d.ts +2 -1
- package/dist/protocol/absent-route.responder.js +32 -6
- package/dist/protocol/http-request.decoder.d.ts +6 -0
- package/dist/protocol/http-request.decoder.js +11 -3
- package/dist/protocol/protocol.binder.d.ts +4 -2
- package/dist/protocol/protocol.binder.js +3 -1
- package/dist/protocol/protocol.handler.d.ts +6 -2
- package/dist/protocol/protocol.handler.js +14 -4
- package/dist/protocol/unmounted-route.error.d.ts +19 -0
- package/dist/protocol/unmounted-route.error.js +10 -0
- package/dist/runtime/caller-data.copier.d.ts +59 -0
- package/dist/runtime/caller-data.copier.js +470 -0
- package/dist/runtime/caller-data.door.d.ts +56 -0
- package/dist/runtime/caller-data.door.js +65 -0
- package/dist/runtime/computed-read-runtime.js +3 -10
- package/dist/runtime/computed-value.error.d.ts +13 -0
- package/dist/runtime/computed-value.error.js +9 -0
- package/dist/runtime/create-requirement.d.ts +43 -7
- package/dist/runtime/create-requirement.js +89 -9
- package/dist/runtime/diagnostics-reporter.d.ts +20 -4
- package/dist/runtime/diagnostics-reporter.js +11 -4
- package/dist/runtime/error-boundary.d.ts +8 -0
- package/dist/runtime/error-boundary.js +65 -35
- package/dist/runtime/field-behavior.d.ts +2 -2
- package/dist/runtime/framework-error.d.ts +12 -0
- package/dist/runtime/misuse-reporter.d.ts +32 -0
- package/dist/runtime/misuse-reporter.js +19 -0
- package/dist/runtime/mutation-planner.js +13 -11
- package/dist/runtime/operation-request.d.ts +4 -11
- package/dist/runtime/operation-runtime.d.ts +14 -7
- package/dist/runtime/operation-runtime.js +37 -18
- package/dist/runtime/outcome-mapper.js +12 -2
- package/dist/runtime/output-validator.d.ts +10 -0
- package/dist/runtime/output-validator.js +11 -0
- package/dist/runtime/owned-execution.registry.d.ts +13 -0
- package/dist/runtime/owned-execution.registry.js +7 -0
- package/dist/runtime/pipeline.d.ts +19 -7
- package/dist/runtime/referential-action.d.ts +7 -0
- package/dist/runtime/request-envelope.validator.d.ts +5 -0
- package/dist/runtime/request-envelope.validator.js +118 -0
- package/dist/runtime/scalar-runtime.d.ts +30 -2
- package/dist/runtime/scalar-runtime.js +162 -79
- package/dist/transactions/transaction-plan-assembler.d.ts +5 -1
- package/dist/transactions/transaction-plan-assembler.js +74 -75
- package/dist/transactions/transaction-plan-validator.d.ts +30 -7
- package/dist/transactions/transaction-plan-validator.js +204 -153
- package/dist/transactions/transaction-reference-collector.d.ts +30 -0
- package/dist/transactions/transaction-reference-collector.js +204 -0
- package/dist/transactions/transaction-reference-resolver.js +10 -5
- package/dist/transactions/transaction-reference-validator.js +24 -17
- package/dist/transactions/transaction-reference.d.ts +23 -51
- package/dist/transactions/transaction-request.d.ts +3 -3
- package/dist/transactions/transaction-response.d.ts +6 -1
- package/dist/transactions/transaction-response.js +24 -1
- package/dist/transactions/transaction-runtime.d.ts +4 -2
- package/dist/transactions/transaction-runtime.js +4 -3
- package/package.json +17 -3
- package/dist/operations/operation-boolean-nodes.d.ts +0 -11
- package/dist/operations/operation-boolean-nodes.js +0 -283
- package/dist/operations/operation-nesting.d.ts +0 -8
- package/dist/operations/operation-nesting.js +0 -307
|
@@ -2,21 +2,25 @@ import type { FrameworkContracts } from "../compiler/contract.compiler.js";
|
|
|
2
2
|
import type { Framework } from "../framework/framework.js";
|
|
3
3
|
import { type FrameworkDiagnosticsSink } from "../runtime/diagnostics-reporter.js";
|
|
4
4
|
import type { ClientOperationRequest } from "../runtime/operation-request.js";
|
|
5
|
+
import type { OwnedExecution } from "../runtime/owned-execution.registry.js";
|
|
5
6
|
import { type AvProtocolRequest } from "./http-request.decoder.js";
|
|
6
7
|
import { type AvProtocolResponse, type UnencodableReport } from "./http-response.encoder.js";
|
|
7
8
|
/**
|
|
8
9
|
* What the shortcuts read off the bound Framework: its compiled Contracts, its
|
|
9
|
-
* execution entry points and
|
|
10
|
+
* execution entry points — the public ones, and the owned ones that run a body this
|
|
11
|
+
* decoder parsed without copying it again (absent for anything but a
|
|
12
|
+
* constructed Framework) — and its configured diagnostics sink.
|
|
10
13
|
*/
|
|
11
14
|
export type HandlerContext = {
|
|
12
15
|
readonly contracts: FrameworkContracts;
|
|
13
16
|
readonly execute: Framework["execute"];
|
|
14
17
|
readonly executeTransaction: Framework["executeTransaction"];
|
|
18
|
+
readonly owned: OwnedExecution | undefined;
|
|
15
19
|
readonly sink: FrameworkDiagnosticsSink | undefined;
|
|
16
20
|
};
|
|
17
21
|
/**
|
|
18
22
|
* Where a plan's `A3003` goes: the Framework's diagnostics sink, as a `"transaction"`
|
|
19
|
-
* diagnostic — client
|
|
23
|
+
* diagnostic — client origin, the request id the plan ran under, and the step the
|
|
20
24
|
* encoder blames as `operation` when it blames one. One spelling for both
|
|
21
25
|
* `protocol.encodeTransaction` and `protocol.handleTransaction`.
|
|
22
26
|
*/
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import { createDiagnosticsReporter, createReporter, } from "../runtime/diagnostics-reporter.js";
|
|
2
|
-
import { decodeOperationRequest, decodeTransactionRequest, } from "./http-request.decoder.js";
|
|
2
|
+
import { decodeOperationRequest, decodeTransactionRequest, isParsedByDecoder, } from "./http-request.decoder.js";
|
|
3
3
|
import { encodeOperationResponse, encodeTransactionResponse, } from "./http-response.encoder.js";
|
|
4
|
+
function operationEntry(context, request) {
|
|
5
|
+
return context.owned !== undefined && isParsedByDecoder(request.arguments)
|
|
6
|
+
? context.owned.execute
|
|
7
|
+
: context.execute;
|
|
8
|
+
}
|
|
9
|
+
function transactionEntry(context, request) {
|
|
10
|
+
return context.owned !== undefined && isParsedByDecoder(request.plan)
|
|
11
|
+
? context.owned.executeTransaction
|
|
12
|
+
: context.executeTransaction;
|
|
13
|
+
}
|
|
4
14
|
export function planUnencodableReport(sink, requestId) {
|
|
5
15
|
return (error, operation) => createReporter(sink, {
|
|
6
16
|
kind: "transaction",
|
|
7
|
-
|
|
17
|
+
origin: "client",
|
|
8
18
|
requestId,
|
|
9
19
|
...(operation === undefined ? {} : { operation }),
|
|
10
20
|
})(error, "A3003");
|
|
@@ -17,7 +27,7 @@ export async function handleOperationRequest(context, request) {
|
|
|
17
27
|
if (!decoded.ok) {
|
|
18
28
|
return decoded.response;
|
|
19
29
|
}
|
|
20
|
-
const response = await context.
|
|
30
|
+
const response = await operationEntry(context, decoded.request)(decoded.request);
|
|
21
31
|
return encodeOperationResponse(response, decoded.requestId, operationUnencodableReport(context.sink, decoded.request));
|
|
22
32
|
}
|
|
23
33
|
export async function handleTransactionRequest(context, request) {
|
|
@@ -25,6 +35,6 @@ export async function handleTransactionRequest(context, request) {
|
|
|
25
35
|
if (!decoded.ok) {
|
|
26
36
|
return decoded.response;
|
|
27
37
|
}
|
|
28
|
-
const response = await context.
|
|
38
|
+
const response = await transactionEntry(context, decoded.request)(decoded.request);
|
|
29
39
|
return encodeTransactionResponse(response, decoded.requestId, planUnencodableReport(context.sink, decoded.requestId));
|
|
30
40
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the diagnostics sink receives when `protocol.answerAbsent` is handed a
|
|
3
|
+
* request for a route the ClientContract advertises, with that route's own
|
|
4
|
+
* method: the host mounted only part of `protocol.surface()`. The caller's
|
|
5
|
+
* `500 A3000` is unchanged; this error names the route the host left out, so the
|
|
6
|
+
* server's diagnostics show it too. It is never sent to the caller.
|
|
7
|
+
*/
|
|
8
|
+
export declare class UnmountedRouteError extends Error {
|
|
9
|
+
/** What the ClientContract advertises: `resource.family.variant`, or `transactions`. */
|
|
10
|
+
readonly advertised: string;
|
|
11
|
+
/** The route as the request reached it: method and entrypoint-relative path. */
|
|
12
|
+
readonly route: string;
|
|
13
|
+
readonly name = "UnmountedRouteError";
|
|
14
|
+
constructor(
|
|
15
|
+
/** What the ClientContract advertises: `resource.family.variant`, or `transactions`. */
|
|
16
|
+
advertised: string,
|
|
17
|
+
/** The route as the request reached it: method and entrypoint-relative path. */
|
|
18
|
+
route: string);
|
|
19
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export class UnmountedRouteError extends Error {
|
|
2
|
+
advertised;
|
|
3
|
+
route;
|
|
4
|
+
name = "UnmountedRouteError";
|
|
5
|
+
constructor(advertised, route) {
|
|
6
|
+
super(`The ClientContract advertises ${advertised}, but the host mounted no route for ${route}, so protocol.answerAbsent answered it 500 A3000. Mount every protocol.surface() entry.`);
|
|
7
|
+
this.advertised = advertised;
|
|
8
|
+
this.route = route;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** Why a caller value could not be copied. */
|
|
2
|
+
export type CallerDataFailureKind = "unreadable-value" | "non-plain-value";
|
|
3
|
+
/** A data location inside the copied value, from its root. */
|
|
4
|
+
export type CallerDataPath = readonly (string | number)[];
|
|
5
|
+
/**
|
|
6
|
+
* The curated failure of a door copy: what failed and where, relative to the copied
|
|
7
|
+
* root. Thrown by {@link copyCallerData} and caught by every door, which answers it as
|
|
8
|
+
* the caller's `A2004`/`V1001` at its own address. It never carries the value.
|
|
9
|
+
*/
|
|
10
|
+
export declare class CallerDataError extends Error {
|
|
11
|
+
readonly kind: CallerDataFailureKind;
|
|
12
|
+
readonly path: CallerDataPath;
|
|
13
|
+
/**
|
|
14
|
+
* The control mirror `path` starts from, when the failure is a mirror's
|
|
15
|
+
* tripwire read after the door; {@link locateCallerDataError} resolves it.
|
|
16
|
+
*/
|
|
17
|
+
readonly within?: object | undefined;
|
|
18
|
+
readonly name = "CallerDataError";
|
|
19
|
+
constructor(kind: CallerDataFailureKind, path: CallerDataPath,
|
|
20
|
+
/**
|
|
21
|
+
* The control mirror `path` starts from, when the failure is a mirror's
|
|
22
|
+
* tripwire read after the door; {@link locateCallerDataError} resolves it.
|
|
23
|
+
*/
|
|
24
|
+
within?: object | undefined);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Objects a door keeps by identity rather than copying: values the framework built
|
|
28
|
+
* itself (a builder's `$ref` placeholder), or a request envelope's members, each
|
|
29
|
+
* copied by its own door. Never a caller's own object that a later reader reads.
|
|
30
|
+
* Asked about every object, arrays included, before anything else of it is read.
|
|
31
|
+
*/
|
|
32
|
+
export type CallerDataKeep = (value: object) => boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Objects a door mirrors by their descriptors rather than copying by value: a plan's
|
|
35
|
+
* `$ref` control objects (see {@link mirrorControlData}). Asked about every object
|
|
36
|
+
* the door does not keep, before anything else of it is read.
|
|
37
|
+
*/
|
|
38
|
+
export type CallerDataMirror = (value: object) => boolean;
|
|
39
|
+
/** How a door treats the objects it meets; copying by value unless stated. */
|
|
40
|
+
export type CallerDataPolicy = {
|
|
41
|
+
readonly keep?: CallerDataKeep;
|
|
42
|
+
readonly mirror?: CallerDataMirror;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Places a mirror tripwire's failure in the value the door copied: its path from the
|
|
46
|
+
* copy's root, where `error.path` started from the mirror. A failure that is not a
|
|
47
|
+
* tripwire's, or a mirror the copy does not hold, is returned as it is.
|
|
48
|
+
*/
|
|
49
|
+
export declare function locateCallerDataError(error: CallerDataError, copy: unknown): CallerDataError;
|
|
50
|
+
/**
|
|
51
|
+
* Copies a caller's value into fresh plain data (see the module note), reading the
|
|
52
|
+
* caller's objects exactly once.
|
|
53
|
+
*
|
|
54
|
+
* @throws {@link CallerDataError} when a value cannot be read or is not plain data,
|
|
55
|
+
* naming its path from `value`.
|
|
56
|
+
*/
|
|
57
|
+
export declare function copyCallerData(value: unknown, policy?: CallerDataPolicy): unknown;
|
|
58
|
+
/** The `V1001` message a door answers a {@link CallerDataError} with. */
|
|
59
|
+
export declare function callerDataFailureMessage(kind: CallerDataFailureKind): string;
|
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
import { Decimal } from "./decimal.js";
|
|
2
|
+
const FAILURE_MESSAGES = {
|
|
3
|
+
"unreadable-value": "Value could not be read.",
|
|
4
|
+
"non-plain-value": "Value must be plain data.",
|
|
5
|
+
};
|
|
6
|
+
export class CallerDataError extends Error {
|
|
7
|
+
kind;
|
|
8
|
+
path;
|
|
9
|
+
within;
|
|
10
|
+
name = "CallerDataError";
|
|
11
|
+
constructor(kind, path, within) {
|
|
12
|
+
super(FAILURE_MESSAGES[kind]);
|
|
13
|
+
this.kind = kind;
|
|
14
|
+
this.path = path;
|
|
15
|
+
this.within = within;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const NO_POLICY = {};
|
|
19
|
+
const OBJECT_PROTOTYPE = Object.prototype;
|
|
20
|
+
const DATE_PROTOTYPE = Date.prototype;
|
|
21
|
+
const dateValueOf = Date.prototype.getTime;
|
|
22
|
+
const decimalValueOf = Decimal.prototype.toString;
|
|
23
|
+
const FAST_WALK_DEPTH = 256;
|
|
24
|
+
const SLOW_WALK = Symbol("slow walk");
|
|
25
|
+
class FastWalkFailure {
|
|
26
|
+
at;
|
|
27
|
+
constructor(at) {
|
|
28
|
+
this.at = at;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const HOLE = Symbol("hole");
|
|
32
|
+
function copyValueType(value, prototype) {
|
|
33
|
+
if (prototype === DATE_PROTOTYPE || value instanceof Date) {
|
|
34
|
+
return new Date(dateValueOf.call(value));
|
|
35
|
+
}
|
|
36
|
+
if (value instanceof Decimal) {
|
|
37
|
+
return new Decimal(decimalValueOf.call(value));
|
|
38
|
+
}
|
|
39
|
+
if (value instanceof Uint8Array) {
|
|
40
|
+
return new Uint8Array(value);
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
function isPlainPrototype(prototype) {
|
|
45
|
+
return prototype === OBJECT_PROTOTYPE || prototype === null;
|
|
46
|
+
}
|
|
47
|
+
function copyRecordShallow(value, prototype) {
|
|
48
|
+
const copy = prototype === null
|
|
49
|
+
? Object.assign(Object.create(null), value)
|
|
50
|
+
: { ...value };
|
|
51
|
+
const symbols = Object.getOwnPropertySymbols(copy);
|
|
52
|
+
for (let index = 0; index < symbols.length; index += 1) {
|
|
53
|
+
delete copy[symbols[index]];
|
|
54
|
+
}
|
|
55
|
+
return copy;
|
|
56
|
+
}
|
|
57
|
+
function defineKey(record, key, value) {
|
|
58
|
+
if (key === "__proto__") {
|
|
59
|
+
Object.defineProperty(record, key, {
|
|
60
|
+
value,
|
|
61
|
+
writable: true,
|
|
62
|
+
enumerable: true,
|
|
63
|
+
configurable: true,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
record[key] = value;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function copyFast(value, depth, policy) {
|
|
71
|
+
try {
|
|
72
|
+
return copyFastNode(value, depth, policy);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error === SLOW_WALK || error instanceof FastWalkFailure) {
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
throw new FastWalkFailure(value);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function copyFastNode(value, depth, policy) {
|
|
82
|
+
if (depth > FAST_WALK_DEPTH) {
|
|
83
|
+
throw SLOW_WALK;
|
|
84
|
+
}
|
|
85
|
+
if (policy.keep?.(value) === true) {
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
if (policy.mirror?.(value) === true) {
|
|
89
|
+
return mirrorControlData(value);
|
|
90
|
+
}
|
|
91
|
+
if (Array.isArray(value)) {
|
|
92
|
+
const length = value.length;
|
|
93
|
+
const copy = [];
|
|
94
|
+
for (let index = 0; index < length; index += 1) {
|
|
95
|
+
const entry = value[index];
|
|
96
|
+
if (entry === undefined && !(index in value)) {
|
|
97
|
+
throw SLOW_WALK;
|
|
98
|
+
}
|
|
99
|
+
copy.push(typeof entry === "object" && entry !== null
|
|
100
|
+
? copyFast(entry, depth + 1, policy)
|
|
101
|
+
: entry);
|
|
102
|
+
}
|
|
103
|
+
return copy;
|
|
104
|
+
}
|
|
105
|
+
const prototype = Object.getPrototypeOf(value);
|
|
106
|
+
if (isPlainPrototype(prototype)) {
|
|
107
|
+
const copy = copyRecordShallow(value, prototype);
|
|
108
|
+
for (const key in copy) {
|
|
109
|
+
const entry = copy[key];
|
|
110
|
+
if (typeof entry === "object" && entry !== null) {
|
|
111
|
+
copy[key] = copyFast(entry, depth + 1, policy);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return copy;
|
|
115
|
+
}
|
|
116
|
+
const copied = copyValueType(value, prototype);
|
|
117
|
+
if (copied === undefined) {
|
|
118
|
+
throw SLOW_WALK;
|
|
119
|
+
}
|
|
120
|
+
return copied;
|
|
121
|
+
}
|
|
122
|
+
function pathOf(link) {
|
|
123
|
+
const segments = [];
|
|
124
|
+
for (let current = link; current !== undefined; current = current.parent) {
|
|
125
|
+
segments.push(current.segment);
|
|
126
|
+
}
|
|
127
|
+
return segments.reverse();
|
|
128
|
+
}
|
|
129
|
+
function childLink(parent, segment) {
|
|
130
|
+
return { parent, segment };
|
|
131
|
+
}
|
|
132
|
+
function readAt(link, read) {
|
|
133
|
+
try {
|
|
134
|
+
return read();
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
throw new CallerDataError("unreadable-value", pathOf(link));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function copySlow(value, policy, failedAt) {
|
|
141
|
+
let root;
|
|
142
|
+
const ancestors = new Map();
|
|
143
|
+
const stack = [
|
|
144
|
+
{
|
|
145
|
+
kind: "visit",
|
|
146
|
+
read: () => value,
|
|
147
|
+
link: undefined,
|
|
148
|
+
assign: (copy) => {
|
|
149
|
+
root = copy;
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
];
|
|
153
|
+
while (stack.length > 0) {
|
|
154
|
+
const task = stack.pop();
|
|
155
|
+
if (task.kind === "leave") {
|
|
156
|
+
if (task.refuse !== false) {
|
|
157
|
+
throw new CallerDataError("unreadable-value", pathOf(task.refuse));
|
|
158
|
+
}
|
|
159
|
+
ancestors.delete(task.container);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const { link } = task;
|
|
163
|
+
const current = readAt(link, task.read);
|
|
164
|
+
if (typeof current !== "object" || current === null) {
|
|
165
|
+
task.assign(current);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (ancestors.has(current)) {
|
|
169
|
+
task.assign(ancestors.get(current));
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const refuse = current === failedAt ? link : false;
|
|
173
|
+
const settle = (copy) => {
|
|
174
|
+
if (refuse !== false) {
|
|
175
|
+
throw new CallerDataError("unreadable-value", pathOf(refuse));
|
|
176
|
+
}
|
|
177
|
+
task.assign(copy);
|
|
178
|
+
};
|
|
179
|
+
if (readAt(link, () => policy.keep?.(current)) === true) {
|
|
180
|
+
settle(current);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (readAt(link, () => policy.mirror?.(current)) === true) {
|
|
184
|
+
settle(readAt(link, () => mirrorControlData(current)));
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (readAt(link, () => Array.isArray(current))) {
|
|
188
|
+
const source = current;
|
|
189
|
+
const length = readAt(link, () => source.length);
|
|
190
|
+
const copy = new Array(length);
|
|
191
|
+
task.assign(copy);
|
|
192
|
+
ancestors.set(current, copy);
|
|
193
|
+
stack.push({ kind: "leave", container: current, refuse });
|
|
194
|
+
for (let index = length - 1; index >= 0; index -= 1) {
|
|
195
|
+
stack.push({
|
|
196
|
+
kind: "visit",
|
|
197
|
+
read: () => (index in source ? source[index] : HOLE),
|
|
198
|
+
link: childLink(link, index),
|
|
199
|
+
assign: (entry) => {
|
|
200
|
+
if (entry !== HOLE) {
|
|
201
|
+
copy[index] = entry;
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const prototype = readAt(link, () => Object.getPrototypeOf(current));
|
|
209
|
+
if (!isPlainPrototype(prototype)) {
|
|
210
|
+
const copied = readAt(link, () => copyValueType(current, prototype));
|
|
211
|
+
if (copied === undefined) {
|
|
212
|
+
throw new CallerDataError("non-plain-value", pathOf(link));
|
|
213
|
+
}
|
|
214
|
+
settle(copied);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const source = current;
|
|
218
|
+
const copy = prototype === null ? Object.create(null) : {};
|
|
219
|
+
task.assign(copy);
|
|
220
|
+
ancestors.set(current, copy);
|
|
221
|
+
stack.push({ kind: "leave", container: current, refuse });
|
|
222
|
+
const keys = readAt(link, () => Object.keys(source));
|
|
223
|
+
for (const key of keys) {
|
|
224
|
+
defineKey(copy, key, undefined);
|
|
225
|
+
}
|
|
226
|
+
for (let index = keys.length - 1; index >= 0; index -= 1) {
|
|
227
|
+
const key = keys[index];
|
|
228
|
+
stack.push({
|
|
229
|
+
kind: "visit",
|
|
230
|
+
read: () => source[key],
|
|
231
|
+
link: childLink(link, key),
|
|
232
|
+
assign: (entry) => {
|
|
233
|
+
defineKey(copy, key, entry);
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return root;
|
|
239
|
+
}
|
|
240
|
+
const UNREADABLE = (() => {
|
|
241
|
+
const { proxy, revoke } = Proxy.revocable({}, {});
|
|
242
|
+
revoke();
|
|
243
|
+
return proxy;
|
|
244
|
+
})();
|
|
245
|
+
const NON_PLAIN_PROTOTYPE = Object.freeze(Object.create(null));
|
|
246
|
+
function reflectNode(source) {
|
|
247
|
+
try {
|
|
248
|
+
const array = Array.isArray(source);
|
|
249
|
+
const prototype = Object.getPrototypeOf(source);
|
|
250
|
+
const keys = Reflect.ownKeys(source);
|
|
251
|
+
const descriptors = [];
|
|
252
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
253
|
+
descriptors.push(Object.getOwnPropertyDescriptor(source, keys[index]) ??
|
|
254
|
+
{});
|
|
255
|
+
}
|
|
256
|
+
return { array, prototype, keys, descriptors };
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function pathSegment(array, key) {
|
|
263
|
+
if (array) {
|
|
264
|
+
const index = Number(key);
|
|
265
|
+
if (Number.isInteger(index) && index >= 0 && String(index) === key) {
|
|
266
|
+
return index;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return key;
|
|
270
|
+
}
|
|
271
|
+
function mirrorControlData(value) {
|
|
272
|
+
const shape = reflectNode(value);
|
|
273
|
+
if (shape === undefined) {
|
|
274
|
+
throw new CallerDataError("unreadable-value", []);
|
|
275
|
+
}
|
|
276
|
+
let root;
|
|
277
|
+
const refuse = (kind, path) => {
|
|
278
|
+
throw new CallerDataError(kind, path, root);
|
|
279
|
+
};
|
|
280
|
+
const mirrors = new Map();
|
|
281
|
+
const stack = [
|
|
282
|
+
{
|
|
283
|
+
source: value,
|
|
284
|
+
shape,
|
|
285
|
+
link: undefined,
|
|
286
|
+
assign: (mirror) => {
|
|
287
|
+
root = mirror;
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
];
|
|
291
|
+
while (stack.length > 0) {
|
|
292
|
+
const { source, shape, link, assign } = stack.pop();
|
|
293
|
+
const known = mirrors.get(source);
|
|
294
|
+
if (known !== undefined) {
|
|
295
|
+
assign(known);
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (!shape.array && !isPlainPrototype(shape.prototype)) {
|
|
299
|
+
let copied;
|
|
300
|
+
try {
|
|
301
|
+
copied = copyValueType(source, shape.prototype);
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
copied = undefined;
|
|
305
|
+
}
|
|
306
|
+
if (copied !== undefined) {
|
|
307
|
+
mirrors.set(source, copied);
|
|
308
|
+
assign(copied);
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const plain = shape.array
|
|
313
|
+
? shape.prototype === Array.prototype || shape.prototype === null
|
|
314
|
+
: isPlainPrototype(shape.prototype);
|
|
315
|
+
const target = shape.array
|
|
316
|
+
? []
|
|
317
|
+
: shape.prototype === null
|
|
318
|
+
? Object.create(null)
|
|
319
|
+
: {};
|
|
320
|
+
if (!plain) {
|
|
321
|
+
Object.setPrototypeOf(target, NON_PLAIN_PROTOTYPE);
|
|
322
|
+
}
|
|
323
|
+
else if (shape.array && shape.prototype === null) {
|
|
324
|
+
Object.setPrototypeOf(target, null);
|
|
325
|
+
}
|
|
326
|
+
let unreadable;
|
|
327
|
+
for (let index = 0; index < shape.keys.length; index += 1) {
|
|
328
|
+
const key = shape.keys[index];
|
|
329
|
+
const descriptor = shape.descriptors[index];
|
|
330
|
+
const enumerable = descriptor.enumerable === true;
|
|
331
|
+
if (shape.array && key === "length") {
|
|
332
|
+
if (typeof descriptor.value === "number") {
|
|
333
|
+
target.length = descriptor.value;
|
|
334
|
+
}
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (typeof key === "symbol") {
|
|
338
|
+
Object.defineProperty(target, key, {
|
|
339
|
+
value: undefined,
|
|
340
|
+
enumerable,
|
|
341
|
+
configurable: true,
|
|
342
|
+
});
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
if (!("value" in descriptor)) {
|
|
346
|
+
const at = childLink(link, pathSegment(shape.array, key));
|
|
347
|
+
Object.defineProperty(target, key, {
|
|
348
|
+
get: () => refuse("unreadable-value", pathOf(at)),
|
|
349
|
+
enumerable,
|
|
350
|
+
configurable: true,
|
|
351
|
+
});
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
const store = enumerable &&
|
|
355
|
+
descriptor.writable === true &&
|
|
356
|
+
descriptor.configurable === true
|
|
357
|
+
? (member) => {
|
|
358
|
+
defineKey(target, key, member);
|
|
359
|
+
}
|
|
360
|
+
: (member) => {
|
|
361
|
+
Object.defineProperty(target, key, {
|
|
362
|
+
value: member,
|
|
363
|
+
writable: descriptor.writable === true,
|
|
364
|
+
enumerable,
|
|
365
|
+
configurable: true,
|
|
366
|
+
});
|
|
367
|
+
};
|
|
368
|
+
const member = descriptor.value;
|
|
369
|
+
if (typeof member !== "object" || member === null) {
|
|
370
|
+
store(member);
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (mirrors.has(member)) {
|
|
374
|
+
store(mirrors.get(member));
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
const memberLink = childLink(link, pathSegment(shape.array, key));
|
|
378
|
+
const memberShape = reflectNode(member);
|
|
379
|
+
if (memberShape === undefined) {
|
|
380
|
+
store(UNREADABLE);
|
|
381
|
+
unreadable ??= new Map();
|
|
382
|
+
unreadable.set(key, memberLink);
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
store(undefined);
|
|
386
|
+
stack.push({
|
|
387
|
+
source: member,
|
|
388
|
+
shape: memberShape,
|
|
389
|
+
link: memberLink,
|
|
390
|
+
assign: store,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
const node = plain && unreadable === undefined
|
|
394
|
+
? target
|
|
395
|
+
: new Proxy(target, {
|
|
396
|
+
get: (into, key, receiver) => {
|
|
397
|
+
if (!plain) {
|
|
398
|
+
return refuse("non-plain-value", pathOf(link));
|
|
399
|
+
}
|
|
400
|
+
const at = typeof key === "string" ? unreadable?.get(key) : undefined;
|
|
401
|
+
return at === undefined
|
|
402
|
+
? Reflect.get(into, key, receiver)
|
|
403
|
+
: refuse("unreadable-value", pathOf(at));
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
mirrors.set(source, node);
|
|
407
|
+
assign(node);
|
|
408
|
+
}
|
|
409
|
+
return root;
|
|
410
|
+
}
|
|
411
|
+
export function locateCallerDataError(error, copy) {
|
|
412
|
+
const within = error.within;
|
|
413
|
+
if (within === undefined) {
|
|
414
|
+
return error;
|
|
415
|
+
}
|
|
416
|
+
const at = pathTo(copy, within);
|
|
417
|
+
return at === undefined
|
|
418
|
+
? error
|
|
419
|
+
: new CallerDataError(error.kind, [...at, ...error.path]);
|
|
420
|
+
}
|
|
421
|
+
function pathTo(copy, target) {
|
|
422
|
+
const seen = new Set();
|
|
423
|
+
const stack = [
|
|
424
|
+
{ value: copy, path: [] },
|
|
425
|
+
];
|
|
426
|
+
while (stack.length > 0) {
|
|
427
|
+
const { value, path } = stack.pop();
|
|
428
|
+
if (value === target) {
|
|
429
|
+
return path;
|
|
430
|
+
}
|
|
431
|
+
if (typeof value !== "object" ||
|
|
432
|
+
value === null ||
|
|
433
|
+
value === UNREADABLE ||
|
|
434
|
+
seen.has(value)) {
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
seen.add(value);
|
|
438
|
+
const array = Array.isArray(value);
|
|
439
|
+
for (const key of Object.getOwnPropertyNames(value)) {
|
|
440
|
+
if (array && key === "length") {
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
444
|
+
if (descriptor !== undefined && "value" in descriptor) {
|
|
445
|
+
stack.push({
|
|
446
|
+
value: descriptor.value,
|
|
447
|
+
path: [...path, pathSegment(array, key)],
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return undefined;
|
|
453
|
+
}
|
|
454
|
+
function guardNative(operation, onFailure) {
|
|
455
|
+
try {
|
|
456
|
+
return operation();
|
|
457
|
+
}
|
|
458
|
+
catch (failure) {
|
|
459
|
+
return onFailure(failure);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
export function copyCallerData(value, policy = NO_POLICY) {
|
|
463
|
+
if (typeof value !== "object" || value === null) {
|
|
464
|
+
return value;
|
|
465
|
+
}
|
|
466
|
+
return guardNative(() => copyFast(value, 0, policy), (failure) => copySlow(value, policy, failure instanceof FastWalkFailure ? failure.at : undefined));
|
|
467
|
+
}
|
|
468
|
+
export function callerDataFailureMessage(kind) {
|
|
469
|
+
return FAILURE_MESSAGES[kind];
|
|
470
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ValidationIssue } from "../operations/validation-code.js";
|
|
2
|
+
import { CallerDataError, type CallerDataPath, type CallerDataPolicy } from "./caller-data.copier.js";
|
|
3
|
+
import type { FrameworkMisuseEvent, MisuseReporter } from "./misuse-reporter.js";
|
|
4
|
+
/**
|
|
5
|
+
* Where the arguments an execution entry receives came from.
|
|
6
|
+
*
|
|
7
|
+
* - `"caller"`: a server-side caller's own objects — copied at the door.
|
|
8
|
+
* - `"owned"`: data no caller holds a reference to — a body the protocol decoder
|
|
9
|
+
* parsed with `JSON.parse` for this request alone, or a plan the typed runner
|
|
10
|
+
* assembled from copies its builders took. It is never copied again.
|
|
11
|
+
*
|
|
12
|
+
* Only core's own modules choose `"owned"`: the public `framework.execute` and
|
|
13
|
+
* `executeTransaction` always pass `"caller"`.
|
|
14
|
+
*/
|
|
15
|
+
export type ArgumentsSource = "caller" | "owned";
|
|
16
|
+
/** The operation identity a misuse event may name, as far as it could be read. */
|
|
17
|
+
export type MisuseIdentity = Omit<FrameworkMisuseEvent, "kind" | "path">;
|
|
18
|
+
/**
|
|
19
|
+
* The identity of a request the framework holds a copy of, keeping only what is
|
|
20
|
+
* identity: an origin, a Resource name and a standard operation, each present only
|
|
21
|
+
* when it is one. Nothing the caller wrote anywhere else is carried.
|
|
22
|
+
*/
|
|
23
|
+
export declare function misuseIdentityOf(request: {
|
|
24
|
+
readonly origin?: unknown;
|
|
25
|
+
readonly resource?: unknown;
|
|
26
|
+
readonly family?: unknown;
|
|
27
|
+
readonly variant?: unknown;
|
|
28
|
+
}): MisuseIdentity;
|
|
29
|
+
/**
|
|
30
|
+
* The caller's `V1001` for a refused value at `address` (the door's own spelling of
|
|
31
|
+
* where the copied value stands), reported once to the misuse hook.
|
|
32
|
+
*/
|
|
33
|
+
export declare function refuseCallerData(error: CallerDataError, address: CallerDataPath, identity: MisuseIdentity, misuse: MisuseReporter): ValidationIssue;
|
|
34
|
+
/**
|
|
35
|
+
* A door's copy of one caller value, or the failure it met. The door holds it until
|
|
36
|
+
* its answer is due — a plan answers its steps in order — so the failure is a value
|
|
37
|
+
* here, not yet a response.
|
|
38
|
+
*/
|
|
39
|
+
export type CallerDataCopy = {
|
|
40
|
+
readonly ok: true;
|
|
41
|
+
readonly value: unknown;
|
|
42
|
+
} | {
|
|
43
|
+
readonly ok: false;
|
|
44
|
+
readonly error: CallerDataError;
|
|
45
|
+
};
|
|
46
|
+
/** Copies `value` at a door, keeping {@link copyCallerData}'s refusal as a value. */
|
|
47
|
+
export declare function copyAtDoor(value: unknown, policy?: CallerDataPolicy): CallerDataCopy;
|
|
48
|
+
/**
|
|
49
|
+
* A request envelope handed to `framework.execute` or `executeTransaction`, copied at
|
|
50
|
+
* the door: its own members read once (a revoked envelope or a throwing member getter
|
|
51
|
+
* is refused at `[]` or `[member]`), the `members` named here — the protocol identity,
|
|
52
|
+
* the transport context — copied whole, and every other member kept as it is for its
|
|
53
|
+
* own door: the arguments, copied after the availability check, and a plan, which the
|
|
54
|
+
* plan validator inspects and copies step by step.
|
|
55
|
+
*/
|
|
56
|
+
export declare function copyEnvelopeAtDoor(request: unknown, members: readonly string[]): CallerDataCopy;
|