@aventara/core 0.1.0-pilot.1 → 0.1.0-pilot.2
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/dist/adapters/adapter-field.d.ts +4 -4
- package/dist/adapters/adapter-resource.d.ts +10 -12
- package/dist/adapters/adapter.interface.d.ts +20 -10
- package/dist/compiler/contract-compiler-diagnostic.d.ts +3 -4
- package/dist/compiler/contract.compiler.d.ts +5 -4
- package/dist/compiler/contract.compiler.js +0 -132
- package/dist/config/config.resolver.d.ts +2 -2
- package/dist/config/config.resolver.js +0 -5
- package/dist/config/config.validator.d.ts +3 -3
- package/dist/config/config.validator.js +0 -8
- package/dist/config/define-fields.d.ts +2 -7
- package/dist/config/define-fields.js +0 -15
- package/dist/config/effective-fields.d.ts +4 -8
- package/dist/config/entrypoint.normalizer.d.ts +7 -7
- package/dist/config/entrypoint.normalizer.js +0 -22
- package/dist/config/framework-behaviors-config.d.ts +6 -6
- package/dist/config/framework-config.d.ts +3 -5
- package/dist/config/framework-restrictions-config.d.ts +19 -29
- package/dist/config/pipeline-config.d.ts +8 -18
- package/dist/contracts/capability.d.ts +18 -21
- package/dist/contracts/capability.js +0 -75
- package/dist/contracts/client-contract-structure.validator.d.ts +15 -22
- package/dist/contracts/client-contract-structure.validator.js +0 -27
- package/dist/contracts/contract-canonicalizer.js +0 -96
- package/dist/contracts/contract-hash.js +0 -2
- package/dist/contracts/contract-limits.js +0 -1
- package/dist/contracts/contract-lookup.js +0 -10
- package/dist/contracts/contract-protocol.js +0 -10
- package/dist/contracts/field.d.ts +23 -26
- package/dist/contracts/field.js +0 -107
- package/dist/contracts/lifecycle-trait.js +0 -5
- package/dist/contracts/resource-operations.d.ts +17 -18
- package/dist/contracts/resource-operations.js +0 -63
- package/dist/contracts/scalar-value-type.d.ts +12 -22
- package/dist/contracts/scalar.js +0 -1
- package/dist/contracts/wire-scalar-format.d.ts +20 -20
- package/dist/contracts/wire-scalar-format.js +0 -26
- package/dist/framework/contract-view.d.ts +7 -8
- package/dist/framework/framework-construction.error.js +0 -1
- package/dist/framework/framework.d.ts +26 -27
- package/dist/framework/framework.factory.js +0 -49
- package/dist/framework/operation-facade.js +0 -17
- package/dist/operations/operation-arguments.d.ts +36 -46
- package/dist/operations/operation-boolean-nodes.js +0 -16
- package/dist/operations/operation-call.d.ts +4 -9
- package/dist/operations/operation-identity.js +0 -1
- package/dist/operations/operation-nesting.js +0 -5
- package/dist/operations/operation-result.d.ts +4 -4
- package/dist/operations/operation-validator-core.d.ts +2 -2
- package/dist/operations/operation-validator-core.js +6 -71
- package/dist/operations/operation-validator.d.ts +4 -4
- package/dist/operations/operation-validator.js +0 -12
- package/dist/operations/projection-scope.resolver.d.ts +8 -10
- package/dist/operations/projection-scope.resolver.js +0 -35
- package/dist/operations/validation-code.d.ts +11 -18
- package/dist/operations/validation-code.js +0 -24
- package/dist/operations/write-scope.resolver.d.ts +4 -7
- package/dist/operations/write-scope.resolver.js +0 -37
- package/dist/protocol/absent-route.responder.d.ts +33 -48
- package/dist/protocol/absent-route.responder.js +0 -55
- package/dist/protocol/av-protocol.d.ts +6 -6
- package/dist/protocol/av-protocol.js +0 -10
- package/dist/protocol/contract-document.responder.d.ts +15 -18
- package/dist/protocol/contract-document.responder.js +0 -33
- package/dist/protocol/http-request.decoder.d.ts +55 -61
- package/dist/protocol/http-request.decoder.js +0 -62
- package/dist/protocol/http-request.reader.d.ts +12 -13
- package/dist/protocol/http-request.reader.js +0 -45
- package/dist/protocol/http-response.encoder.d.ts +17 -18
- package/dist/protocol/http-response.encoder.js +0 -59
- package/dist/protocol/http-status.table.d.ts +7 -11
- package/dist/protocol/http-status.table.js +1 -15
- package/dist/protocol/index.d.ts +5 -5
- package/dist/protocol/index.js +0 -9
- package/dist/protocol/operation-response.guard.d.ts +12 -19
- package/dist/protocol/operation-response.guard.js +0 -29
- package/dist/protocol/protocol-failure.d.ts +21 -26
- package/dist/protocol/protocol-failure.js +0 -12
- package/dist/protocol/protocol-headers.d.ts +25 -30
- package/dist/protocol/protocol-headers.js +0 -66
- package/dist/protocol/protocol-surface.d.ts +25 -29
- package/dist/protocol/protocol-surface.js +0 -19
- package/dist/protocol/protocol.binder.d.ts +58 -71
- package/dist/protocol/protocol.binder.js +0 -8
- package/dist/protocol/protocol.handler.d.ts +28 -32
- package/dist/protocol/protocol.handler.js +0 -43
- package/dist/runtime/computed-read-runtime.js +0 -29
- package/dist/runtime/create-requirement.d.ts +65 -0
- package/dist/runtime/create-requirement.js +141 -0
- package/dist/runtime/decimal.d.ts +2 -2
- package/dist/runtime/decimal.js +0 -8
- package/dist/runtime/diagnostics-reporter.d.ts +6 -6
- package/dist/runtime/diagnostics-reporter.js +0 -35
- package/dist/runtime/error-boundary.d.ts +3 -3
- package/dist/runtime/error-boundary.js +2 -167
- package/dist/runtime/execution-options.d.ts +4 -4
- package/dist/runtime/field-behavior.d.ts +6 -9
- package/dist/runtime/framework-error.js +0 -4
- package/dist/runtime/mutation-planner.d.ts +5 -6
- package/dist/runtime/mutation-planner.js +0 -23
- package/dist/runtime/operation-response.d.ts +1 -1
- package/dist/runtime/operation-response.js +1 -17
- package/dist/runtime/operation-runtime.d.ts +24 -22
- package/dist/runtime/operation-runtime.js +18 -52
- package/dist/runtime/outcome-mapper.js +0 -22
- package/dist/runtime/output-validator.js +0 -1
- package/dist/runtime/pipe-output.validator.d.ts +48 -0
- package/dist/runtime/pipe-output.validator.js +79 -0
- package/dist/runtime/pipeline.d.ts +20 -26
- package/dist/runtime/referential-action.d.ts +14 -19
- package/dist/runtime/referential-action.js +0 -23
- package/dist/runtime/request-id.js +0 -21
- package/dist/runtime/scalar-runtime.d.ts +14 -16
- package/dist/runtime/scalar-runtime.js +12 -57
- package/dist/transactions/deferred-operation.d.ts +9 -14
- package/dist/transactions/operation-arguments-encoder.d.ts +0 -1
- package/dist/transactions/operation-arguments-encoder.js +0 -14
- package/dist/transactions/operation-fingerprint.js +0 -1
- package/dist/transactions/transaction-envelope.js +0 -14
- package/dist/transactions/transaction-integrity-error.d.ts +10 -11
- package/dist/transactions/transaction-integrity-error.js +0 -5
- package/dist/transactions/transaction-plan-assembler.d.ts +0 -1
- package/dist/transactions/transaction-plan-assembler.js +0 -35
- package/dist/transactions/transaction-plan-validator.d.ts +5 -5
- package/dist/transactions/transaction-plan-validator.js +0 -63
- package/dist/transactions/transaction-reference-resolver.d.ts +12 -13
- package/dist/transactions/transaction-reference-resolver.js +0 -16
- package/dist/transactions/transaction-reference-validator.js +0 -8
- package/dist/transactions/transaction-reference.d.ts +19 -43
- package/dist/transactions/transaction-reference.js +0 -11
- package/dist/transactions/transaction-request.d.ts +4 -5
- package/dist/transactions/transaction-response.d.ts +1 -1
- package/dist/transactions/transaction-runtime.d.ts +2 -3
- package/dist/transactions/transaction-runtime.js +5 -44
- package/dist/transactions/transaction-slot-path-error.d.ts +10 -11
- package/dist/transactions/transaction-slot-path-error.js +0 -7
- package/dist/transactions/transaction-slot-writer.d.ts +12 -12
- package/dist/transactions/transaction-slot-writer.js +0 -34
- package/package.json +2 -2
|
@@ -6,38 +6,32 @@ import { type AvProtocolResponse } from "./http-response.encoder.js";
|
|
|
6
6
|
import { transportFailure } from "./protocol-failure.js";
|
|
7
7
|
import { type SurfaceRoute } from "./protocol-surface.js";
|
|
8
8
|
/**
|
|
9
|
-
* The protocol bound to ONE Framework instance — what `AvProtocol.bind`
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* entrypoint or a limit, because the binding already read them.
|
|
13
|
-
*
|
|
14
|
-
* Grows one member per slice (§7); the exact set is pinned in
|
|
15
|
-
* `index.test-d.ts`.
|
|
9
|
+
* The protocol bound to ONE Framework instance — what `AvProtocol.bind` returns. A
|
|
10
|
+
* host binds once, at startup, and calls these members per request; none takes a
|
|
11
|
+
* Contract, an entrypoint or a limit, because the binding already read them.
|
|
16
12
|
*/
|
|
17
13
|
export type AvProtocolBinding = {
|
|
18
14
|
/**
|
|
19
|
-
* Every route a host mounts for the bound ClientContract, from one list,
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* `
|
|
24
|
-
* `
|
|
25
|
-
*
|
|
26
|
-
* every call answers the same deep-frozen list, which is passive data a
|
|
27
|
-
* host may serialize.
|
|
15
|
+
* Every route a host mounts for the bound ClientContract, from one list, each
|
|
16
|
+
* tagged by `kind`: one `POST` `operation` route per `(resource, family, variant)`
|
|
17
|
+
* it advertises — nothing it does not — then the `contract` route `GET /_contract`
|
|
18
|
+
* (always), then the `transactions` route `POST /_transactions` exactly when its
|
|
19
|
+
* `transactions` is `"interactive"`. Each path is relative to
|
|
20
|
+
* `framework.entrypoint`. Read once at bind time: every call answers the same
|
|
21
|
+
* deep-frozen list, which is passive data a host may serialize.
|
|
28
22
|
*
|
|
29
|
-
* A host that mounts one route per entry still needs a fallback for the
|
|
30
|
-
*
|
|
31
|
-
*
|
|
23
|
+
* A host that mounts one route per entry still needs a fallback for the paths it
|
|
24
|
+
* did not mount: that is the absence responder's, not a catch-all into framework
|
|
25
|
+
* handling.
|
|
32
26
|
*/
|
|
33
27
|
readonly surface: () => readonly SurfaceRoute[];
|
|
34
28
|
/**
|
|
35
29
|
* An operation route's HTTP request → the client-origin request
|
|
36
|
-
* `framework.execute` takes, or the framework response that answers it
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
30
|
+
* `framework.execute` takes, or the framework response that answers it instead,
|
|
31
|
+
* with the request id either way. Answers absence for an unadvertised triple
|
|
32
|
+
* without executing it, so a catch-all host is safe. Raw body: size, content type,
|
|
33
|
+
* JSON and shape enforced; parsed body: shape only — the rest was the host's
|
|
34
|
+
* parser's.
|
|
41
35
|
*/
|
|
42
36
|
readonly decodeOperation: (request: AvProtocolRequest) => DecodedOperation;
|
|
43
37
|
/**
|
|
@@ -47,77 +41,70 @@ export type AvProtocolBinding = {
|
|
|
47
41
|
*/
|
|
48
42
|
readonly decodeTransaction: (request: AvProtocolRequest) => DecodedTransaction;
|
|
49
43
|
/**
|
|
50
|
-
* One operation's framework response — what `framework.execute` answered —
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* it the answer is the same and nothing is reported.
|
|
44
|
+
* One operation's framework response — what `framework.execute` answered — as the
|
|
45
|
+
* HTTP response a host writes: the code's status, `data` in wire forms,
|
|
46
|
+
* `Aventara-Request-Id` echoed. A result with no wire form answers `A3003` → 500.
|
|
47
|
+
* A diagnostic of one operation names it (`resource`, `family`, `variant`), so the
|
|
48
|
+
* `A3003` reaches the diagnostics sink only when the host passes `decoded` —
|
|
49
|
+
* `decodeOperation`'s success value — as `handleOperation` does; without it the
|
|
50
|
+
* answer is the same and nothing is reported.
|
|
58
51
|
*/
|
|
59
52
|
readonly encodeOperation: (response: OperationResponse, requestId: string, decoded?: DecodedOperationRequest) => AvProtocolResponse;
|
|
60
53
|
/**
|
|
61
|
-
* The same for `/_transactions` — what `framework.executeTransaction`
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* the
|
|
65
|
-
* scope, this `requestId`, the step it blames as `operation` — with the
|
|
66
|
-
* value that had no wire form (or what its getter threw) as `error`.
|
|
54
|
+
* The same for `/_transactions` — what `framework.executeTransaction` answered. An
|
|
55
|
+
* `A3003` is reported to the Framework's diagnostics sink as a `"transaction"`
|
|
56
|
+
* diagnostic — client scope, this `requestId`, the step it blames as `operation` —
|
|
57
|
+
* with the value that had no wire form (or what its getter threw) as `error`.
|
|
67
58
|
*/
|
|
68
59
|
readonly encodeTransaction: (response: TransactionResponse, requestId: string) => AvProtocolResponse;
|
|
69
60
|
/**
|
|
70
61
|
* A transport code — `A2000`, `A2010`, `A2011` or `A2012` — as the complete
|
|
71
|
-
* framework response, with framework-authored text (ruled 2026-10-05). For a
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* `Allow` (the director, 2026-10-05).
|
|
62
|
+
* framework response, with framework-authored text (ruled 2026-10-05). For a host
|
|
63
|
+
* that parses the body itself and must answer its parser's failure. `A2012`
|
|
64
|
+
* requires the methods its route allows, so every 405 carries `Allow`.
|
|
75
65
|
*/
|
|
76
66
|
readonly failure: typeof transportFailure;
|
|
77
67
|
/**
|
|
78
|
-
* The shortcut
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* rejects.
|
|
68
|
+
* The shortcut: `decodeOperation` → `framework.execute` → `encodeOperation`,
|
|
69
|
+
* resolving the response the host writes for an operation route. A decode failure
|
|
70
|
+
* is answered without executing; an `A3003` is reported to the diagnostics sink
|
|
71
|
+
* with the operation's identity and request id. Total over any value an untyped
|
|
72
|
+
* host hands it: one that is not an `AvProtocolRequest` answers `400 A2000`; it
|
|
73
|
+
* never rejects.
|
|
85
74
|
*/
|
|
86
75
|
readonly handleOperation: (request: AvProtocolRequest) => Promise<AvProtocolResponse>;
|
|
87
76
|
/**
|
|
88
77
|
* The same for `/_transactions`: `decodeTransaction` →
|
|
89
|
-
* `framework.executeTransaction` → `encodeTransaction`. Every step runs
|
|
90
|
-
*
|
|
91
|
-
*
|
|
78
|
+
* `framework.executeTransaction` → `encodeTransaction`. Every step runs under
|
|
79
|
+
* ClientContract rules; a committed plan is `200 A1009`, a rolled-back one its
|
|
80
|
+
* failing step's own status.
|
|
92
81
|
*/
|
|
93
82
|
readonly handleTransaction: (request: AvProtocolRequest) => Promise<AvProtocolResponse>;
|
|
94
83
|
/**
|
|
95
|
-
* The `GET /_contract` responder
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
84
|
+
* The `GET /_contract` responder: the bound ClientContract's JCS-canonical bytes,
|
|
85
|
+
* `ETag` its quoted hash, `Cache-Control: no-cache`, and `304` with no body when
|
|
86
|
+
* `If-None-Match` matches (weak comparison, a list, `*`). Requires no identity
|
|
87
|
+
* header; any other method is `405 A2012` with `Allow: GET`. The bytes are written
|
|
88
|
+
* once, at bind time.
|
|
100
89
|
*/
|
|
101
90
|
readonly encodeContract: (request: AvProtocolRequest) => AvProtocolResponse;
|
|
102
91
|
/**
|
|
103
|
-
* The absence responder
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* that
|
|
109
|
-
*
|
|
110
|
-
* protocol's namespace, which the host answers itself (the architect,
|
|
111
|
-
* 2026-10-05).
|
|
92
|
+
* The absence responder: the fallback a host mounts for every path it did not
|
|
93
|
+
* mount from `surface()` — never a catch-all into framework handling (it reads no
|
|
94
|
+
* body, runs no pipeline, calls no Adapter). Answers what the catch-all decoder
|
|
95
|
+
* answers before a body — `A2000`, `A2006`, `A2005`, `A2001`, `A2002`, `A2012`
|
|
96
|
+
* with `Allow` — byte for byte; a route that exists reached with its own method as
|
|
97
|
+
* `500 A3000` (a host that mounted only part of the surface); and `undefined` for
|
|
98
|
+
* a path outside the protocol's namespace, which the host answers itself.
|
|
112
99
|
*/
|
|
113
100
|
readonly answerAbsent: (request: AvProtocolRequest) => AvProtocolResponse | undefined;
|
|
114
101
|
};
|
|
115
102
|
/**
|
|
116
|
-
* `AvProtocol.bind(framework)`: reads the instance's compiled ClientContract
|
|
117
|
-
*
|
|
103
|
+
* `AvProtocol.bind(framework)`: reads the instance's compiled ClientContract once
|
|
104
|
+
* and returns the bound members, frozen.
|
|
118
105
|
*
|
|
119
|
-
* It validates nothing construction did not: `createFramework` already refused
|
|
120
|
-
*
|
|
121
|
-
*
|
|
106
|
+
* It validates nothing construction did not: `createFramework` already refused a
|
|
107
|
+
* bad entrypoint and compiled the ClientContract this reads, so binding cannot
|
|
108
|
+
* fail.
|
|
122
109
|
*/
|
|
123
110
|
export declare function bind(framework: Framework): AvProtocolBinding;
|
|
@@ -6,14 +6,6 @@ import { encodeOperationResponse, encodeTransactionResponse, } from "./http-resp
|
|
|
6
6
|
import { handleOperationRequest, handleTransactionRequest, operationUnencodableReport, planUnencodableReport, } from "./protocol.handler.js";
|
|
7
7
|
import { transportFailure } from "./protocol-failure.js";
|
|
8
8
|
import { surfaceOf } from "./protocol-surface.js";
|
|
9
|
-
/**
|
|
10
|
-
* `AvProtocol.bind(framework)`: reads the instance's compiled ClientContract
|
|
11
|
-
* once and returns the bound members, frozen.
|
|
12
|
-
*
|
|
13
|
-
* It validates nothing construction did not: `createFramework` already refused
|
|
14
|
-
* a bad entrypoint (R4) and compiled the ClientContract this reads, so binding
|
|
15
|
-
* cannot fail.
|
|
16
|
-
*/
|
|
17
9
|
export function bind(framework) {
|
|
18
10
|
const { contracts } = framework;
|
|
19
11
|
const surface = surfaceOf(contracts.client);
|
|
@@ -16,48 +16,44 @@ export type HandlerContext = {
|
|
|
16
16
|
};
|
|
17
17
|
/**
|
|
18
18
|
* Where a plan's `A3003` goes: the Framework's diagnostics sink, as a
|
|
19
|
-
* `"transaction"` diagnostic — client scope
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* `protocol.encodeTransaction` and `protocol.handleTransaction`.
|
|
19
|
+
* `"transaction"` diagnostic — client scope, the request id the plan ran under,
|
|
20
|
+
* and the step the encoder blames as `operation` when it blames one. One spelling
|
|
21
|
+
* for both `protocol.encodeTransaction` and `protocol.handleTransaction`.
|
|
23
22
|
*/
|
|
24
23
|
export declare function planUnencodableReport(sink: FrameworkDiagnosticsSink | undefined, requestId: string): UnencodableReport;
|
|
25
24
|
/**
|
|
26
25
|
* Where one operation's `A3003` goes: the Framework's diagnostics sink, as an
|
|
27
|
-
* `"operation"` diagnostic carrying the decoded request's identity and id —
|
|
28
|
-
*
|
|
29
|
-
* `protocol.handleOperation` and for `protocol.encodeOperation` given the
|
|
30
|
-
*
|
|
26
|
+
* `"operation"` diagnostic carrying the decoded request's identity and id — the
|
|
27
|
+
* shape the public `OperationDiagnostic` already has. One spelling for
|
|
28
|
+
* `protocol.handleOperation` and for `protocol.encodeOperation` given the decoded
|
|
29
|
+
* request.
|
|
31
30
|
*/
|
|
32
31
|
export declare function operationUnencodableReport(sink: FrameworkDiagnosticsSink | undefined, request: ClientOperationRequest): UnencodableReport;
|
|
33
32
|
/**
|
|
34
|
-
* `protocol.handleOperation(request)
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
33
|
+
* `protocol.handleOperation(request)`: the three steps a host would otherwise
|
|
34
|
+
* spell out — `decodeOperation` → `framework.execute` → `encodeOperation` — in one
|
|
35
|
+
* call, resolving the `AvProtocolResponse` the host writes. A request the decoder
|
|
36
|
+
* answers itself (a transport failure, an identity mismatch, an unadvertised
|
|
37
|
+
* route) is answered without executing.
|
|
39
38
|
*
|
|
40
|
-
* Total over ANY value, never rejecting: the decoder answers every value
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* which only this shortcut holds: the bare `encodeOperation` is given the
|
|
48
|
-
* response and the request id alone.
|
|
39
|
+
* Total over ANY value, never rejecting: the decoder answers every value,
|
|
40
|
+
* `execute` resolves for every request it is handed, and the encoder answers every
|
|
41
|
+
* response — a result with no wire form as `A3003` →
|
|
42
|
+
* 500. That `A3003` reaches the Framework's diagnostics sink as an `"operation"`
|
|
43
|
+
* diagnostic carrying the decoded request's identity and id, which only this
|
|
44
|
+
* shortcut holds: the bare `encodeOperation` is given the response and the
|
|
45
|
+
* request id alone.
|
|
49
46
|
*/
|
|
50
47
|
export declare function handleOperationRequest(context: HandlerContext, request: AvProtocolRequest): Promise<AvProtocolResponse>;
|
|
51
48
|
/**
|
|
52
|
-
* `protocol.handleTransaction(request)
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* client-hidden
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* reported through {@link planUnencodableReport}.
|
|
49
|
+
* `protocol.handleTransaction(request)`: the same shortcut for `/_transactions` —
|
|
50
|
+
* `decodeTransaction` → `framework.executeTransaction` → `encodeTransaction`. The
|
|
51
|
+
* decoded plan is client origin and cannot say otherwise, so every step runs under
|
|
52
|
+
* ClientContract rules: a client-hidden field, a client-restricted variant or a
|
|
53
|
+
* client-hidden Resource is refused as through its single-operation route, plus
|
|
54
|
+
* `cause.operation`. A committed plan is `200 A1009`; a rolled-back one answers
|
|
55
|
+
* its failing step's code at that code's status. Total over any value, as {@link
|
|
56
|
+
* handleOperationRequest} is; an `A3003` is reported through {@link
|
|
57
|
+
* planUnencodableReport}.
|
|
62
58
|
*/
|
|
63
59
|
export declare function handleTransactionRequest(context: HandlerContext, request: AvProtocolRequest): Promise<AvProtocolResponse>;
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import { createDiagnosticsReporter, createReporter, } from "../runtime/diagnostics-reporter.js";
|
|
2
2
|
import { decodeOperationRequest, decodeTransactionRequest, } from "./http-request.decoder.js";
|
|
3
3
|
import { encodeOperationResponse, encodeTransactionResponse, } from "./http-response.encoder.js";
|
|
4
|
-
/**
|
|
5
|
-
* Where a plan's `A3003` goes: the Framework's diagnostics sink, as a
|
|
6
|
-
* `"transaction"` diagnostic — client scope (the protocol has no other
|
|
7
|
-
* origin, P4), the request id the plan ran under, and the step the encoder
|
|
8
|
-
* blames as `operation` when it blames one. One spelling for both
|
|
9
|
-
* `protocol.encodeTransaction` and `protocol.handleTransaction`.
|
|
10
|
-
*/
|
|
11
4
|
export function planUnencodableReport(sink, requestId) {
|
|
12
5
|
return (error, operation) => createReporter(sink, {
|
|
13
6
|
kind: "transaction",
|
|
@@ -16,33 +9,9 @@ export function planUnencodableReport(sink, requestId) {
|
|
|
16
9
|
...(operation === undefined ? {} : { operation }),
|
|
17
10
|
})(error, "A3003");
|
|
18
11
|
}
|
|
19
|
-
/**
|
|
20
|
-
* Where one operation's `A3003` goes: the Framework's diagnostics sink, as an
|
|
21
|
-
* `"operation"` diagnostic carrying the decoded request's identity and id —
|
|
22
|
-
* the shape the public `OperationDiagnostic` already has. One spelling for
|
|
23
|
-
* `protocol.handleOperation` and for `protocol.encodeOperation` given the
|
|
24
|
-
* decoded request (the architect, 2026-10-05).
|
|
25
|
-
*/
|
|
26
12
|
export function operationUnencodableReport(sink, request) {
|
|
27
13
|
return (error) => createDiagnosticsReporter(sink, request)(error, "A3003");
|
|
28
14
|
}
|
|
29
|
-
/**
|
|
30
|
-
* `protocol.handleOperation(request)` (§5.9, R2; S8): the three steps a host
|
|
31
|
-
* would otherwise spell out — `decodeOperation` → `framework.execute` →
|
|
32
|
-
* `encodeOperation` — in one call, resolving the `AvProtocolResponse` the host
|
|
33
|
-
* writes. A request the decoder answers itself (a transport failure, an
|
|
34
|
-
* identity mismatch, an unadvertised route) is answered without executing.
|
|
35
|
-
*
|
|
36
|
-
* Total over ANY value, never rejecting: the decoder answers every value (one
|
|
37
|
-
* that is not an `AvProtocolRequest` as `400 A2000`; the director,
|
|
38
|
-
* 2026-10-05), `execute` resolves for every request it is handed, and the
|
|
39
|
-
* encoder answers every response — a result with no wire form as `A3003` →
|
|
40
|
-
* 500. That `A3003`
|
|
41
|
-
* reaches the Framework's diagnostics sink as an `"operation"` diagnostic
|
|
42
|
-
* carrying the decoded request's identity and id (the director, 2026-10-05),
|
|
43
|
-
* which only this shortcut holds: the bare `encodeOperation` is given the
|
|
44
|
-
* response and the request id alone.
|
|
45
|
-
*/
|
|
46
15
|
export async function handleOperationRequest(context, request) {
|
|
47
16
|
const decoded = decodeOperationRequest(context.contracts, request);
|
|
48
17
|
if (!decoded.ok) {
|
|
@@ -51,18 +20,6 @@ export async function handleOperationRequest(context, request) {
|
|
|
51
20
|
const response = await context.execute(decoded.request);
|
|
52
21
|
return encodeOperationResponse(response, decoded.requestId, operationUnencodableReport(context.sink, decoded.request));
|
|
53
22
|
}
|
|
54
|
-
/**
|
|
55
|
-
* `protocol.handleTransaction(request)` (§5.9, R2; S9): the same shortcut for
|
|
56
|
-
* `/_transactions` — `decodeTransaction` → `framework.executeTransaction` →
|
|
57
|
-
* `encodeTransaction`. The decoded plan is client origin and cannot say
|
|
58
|
-
* otherwise, so every step runs under ClientContract rules (R1): a
|
|
59
|
-
* client-hidden field, a client-restricted variant or a client-hidden
|
|
60
|
-
* Resource is refused as through its single-operation route, plus
|
|
61
|
-
* `cause.operation`. A committed plan is `200 A1009` (P3); a rolled-back one
|
|
62
|
-
* answers its failing step's code at that code's status (Q4). Total over any
|
|
63
|
-
* value, as {@link handleOperationRequest} is; an `A3003` is
|
|
64
|
-
* reported through {@link planUnencodableReport}.
|
|
65
|
-
*/
|
|
66
23
|
export async function handleTransactionRequest(context, request) {
|
|
67
24
|
const decoded = decodeTransactionRequest(context.contracts, request);
|
|
68
25
|
if (!decoded.ok) {
|
|
@@ -35,17 +35,6 @@ function supportsProjection(request) {
|
|
|
35
35
|
}
|
|
36
36
|
return request.variant !== "count";
|
|
37
37
|
}
|
|
38
|
-
/**
|
|
39
|
-
* Plan one scalar of the read. Two Contracts answer two different questions:
|
|
40
|
-
* the **active** Contract decides what the read RETURNS, and the **Core**
|
|
41
|
-
* Contract decides what the Adapter is ASKED for — only a field it stores.
|
|
42
|
-
*
|
|
43
|
-
* A field added at a scope (`application.fields`) exists in that scope's
|
|
44
|
-
* Contract and not in Core, and is VIRTUAL by construction
|
|
45
|
-
* (`contract.compiler.ts`), so it is returned and computed, never requested.
|
|
46
|
-
* Resolving the returned field against Core instead dropped it from the default
|
|
47
|
-
* read and sent an explicit `select` of it to the Adapter as a column (D2).
|
|
48
|
-
*/
|
|
49
38
|
function addScalarProjection(core, active, behaviors, resourceName, fieldName, adapterSelect, requested, computed) {
|
|
50
39
|
const activeField = lookupField(lookupResource(active, resourceName), fieldName);
|
|
51
40
|
if (activeField?.kind !== "scalar") {
|
|
@@ -253,13 +242,6 @@ function replaceWithInternalSelect(args, select) {
|
|
|
253
242
|
internal.select = select;
|
|
254
243
|
return internal;
|
|
255
244
|
}
|
|
256
|
-
/**
|
|
257
|
-
* Clone the caller projection into the internal Adapter projection: the read
|
|
258
|
-
* set the Adapter is asked for, always explicit for a projecting variant, plus
|
|
259
|
-
* whatever computed scalar fields need to materialize. Caller-visible arguments
|
|
260
|
-
* are never mutated and hidden dependencies are resolved through the Core
|
|
261
|
-
* Contract.
|
|
262
|
-
*/
|
|
263
245
|
export function planReadOperation(core, active, behaviors, request, args) {
|
|
264
246
|
if (!isRecord(args) || !supportsProjection(request)) {
|
|
265
247
|
return { arguments: args, projection: undefined, materialize: false };
|
|
@@ -285,11 +267,6 @@ export function planReadOperation(core, active, behaviors, request, args) {
|
|
|
285
267
|
materialize: true,
|
|
286
268
|
};
|
|
287
269
|
}
|
|
288
|
-
// No caller projection: the read set is the default projection, issued
|
|
289
|
-
// explicitly and unconditionally. Leaving it unspecified would mean "whatever
|
|
290
|
-
// the Adapter's provider reads by default", which is every stored column —
|
|
291
|
-
// including one the active scope has removed from reads, fetched into server
|
|
292
|
-
// memory before the output validator could refuse it (D1).
|
|
293
270
|
const defaults = defaultProjectionLevel(core, active, behaviors, request.resource);
|
|
294
271
|
return {
|
|
295
272
|
arguments: { ...args, select: defaults.select },
|
|
@@ -319,8 +296,6 @@ async function materializeRecord(active, behaviors, request, plan, value, contex
|
|
|
319
296
|
if (!isRecord(value)) {
|
|
320
297
|
return value;
|
|
321
298
|
}
|
|
322
|
-
// The active Contract planned this read, so it is the one that knows every
|
|
323
|
-
// computed field in it — a field added at its scope included (D2).
|
|
324
299
|
const resource = lookupResource(active, plan.resource);
|
|
325
300
|
if (resource === undefined) {
|
|
326
301
|
return value;
|
|
@@ -359,10 +334,6 @@ async function materializeRecord(active, behaviors, request, plan, value, contex
|
|
|
359
334
|
.filter((fieldName) => Object.hasOwn(record, fieldName))
|
|
360
335
|
.map((fieldName) => [fieldName, record[fieldName]]));
|
|
361
336
|
}
|
|
362
|
-
/**
|
|
363
|
-
* Materialize computed scalar reads and remove internal dependency fields,
|
|
364
|
-
* against `active` — the Contract `planReadOperation` was given as `active`.
|
|
365
|
-
*/
|
|
366
337
|
export async function materializeReadOperation(active, behaviors, request, plan, value, contextFactory) {
|
|
367
338
|
const projection = plan.projection;
|
|
368
339
|
if (!plan.materialize || projection === undefined) {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Contract } from "../contracts/contract.js";
|
|
2
|
+
import type { OperationIdentity } from "../operations/operation-identity.js";
|
|
3
|
+
import type { ValidationIssue } from "../operations/validation-code.js";
|
|
4
|
+
/**
|
|
5
|
+
* Core's create-requirement vocabulary, and the check that reads it.
|
|
6
|
+
*
|
|
7
|
+
* **The facts are an Adapter's.** Which values a store cannot create a row
|
|
8
|
+
* without is a property of the store's schema — a column with no default, a
|
|
9
|
+
* reference that must point somewhere — and only the Adapter can read it. The
|
|
10
|
+
* words are Aventara's, so an Adapter translates its provider's facts into them,
|
|
11
|
+
* and the reasoning over them is core's, so no Adapter re-implements it.
|
|
12
|
+
*
|
|
13
|
+
* None of this enters a Contract. A Contract carries no foreign-key detail and
|
|
14
|
+
* no knowledge of which relation is the other end of which, and a requirement
|
|
15
|
+
* needs both; it is a server-side fact, read once when the Framework is
|
|
16
|
+
* constructed, like the referential actions.
|
|
17
|
+
*
|
|
18
|
+
* **When the check runs.** After guards, pipes and computed write behaviours —
|
|
19
|
+
* the moment the arguments are what the Adapter will be handed — and before
|
|
20
|
+
* any `before` hook or Adapter work. Earlier would refuse a value a pipe or a
|
|
21
|
+
* computed behaviour was about to supply; later is the store's own refusal,
|
|
22
|
+
* which reaches the caller as an internal failure. A missing value is the
|
|
23
|
+
* caller's to fix, so it answers `A2004` with a `V1000` issue at the path where
|
|
24
|
+
* the value belongs.
|
|
25
|
+
*/
|
|
26
|
+
/** A relation edge, named by the Resource that holds it and its field there. */
|
|
27
|
+
export type CreateRequirementEdge = {
|
|
28
|
+
readonly resource: string;
|
|
29
|
+
readonly relation: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* One value a create of a Resource cannot do without.
|
|
33
|
+
*
|
|
34
|
+
* - `field` — a scalar with no default, generated value or other source: it
|
|
35
|
+
* must be written.
|
|
36
|
+
* - `relation` — a required to-one relation: it must be written (connected or
|
|
37
|
+
* created), or every one of `orFields` must be — the scalar fields that hold
|
|
38
|
+
* the reference. When the row is created **through** the relation's other end
|
|
39
|
+
* (`inverse`), the parent supplies it, and it is not required.
|
|
40
|
+
*/
|
|
41
|
+
export type CreateRequirement = {
|
|
42
|
+
readonly kind: "field";
|
|
43
|
+
readonly field: string;
|
|
44
|
+
} | {
|
|
45
|
+
readonly kind: "relation";
|
|
46
|
+
readonly relation: string;
|
|
47
|
+
readonly orFields: readonly string[];
|
|
48
|
+
readonly inverse?: CreateRequirementEdge;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Every Resource's create requirements, keyed by Resource. A Resource that is
|
|
52
|
+
* absent, or carries an empty list, requires nothing core can check.
|
|
53
|
+
*/
|
|
54
|
+
export type CreateRequirementTable = Readonly<Record<string, readonly CreateRequirement[]>>;
|
|
55
|
+
type Path = readonly (string | number)[];
|
|
56
|
+
/**
|
|
57
|
+
* The `V1000` issues for every create position in one operation's arguments
|
|
58
|
+
* that lacks a value the Adapter requires — `create.*`'s `data`, `upsert`'s
|
|
59
|
+
* `create`, and every nested create under any write — in argument order.
|
|
60
|
+
* Empty when nothing is missing. Total over malformed input.
|
|
61
|
+
*/
|
|
62
|
+
export declare function missingCreateValues(contract: Contract, table: CreateRequirementTable, request: OperationIdentity & {
|
|
63
|
+
readonly resource: string;
|
|
64
|
+
}, args: unknown, argumentsPath: Path): readonly ValidationIssue[];
|
|
65
|
+
export {};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
|
|
2
|
+
import { advertisedDirectivesAt } from "../contracts/field.js";
|
|
3
|
+
import { NESTED_WRITE_SCOPE, resolveWriteScope, } from "../operations/write-scope.resolver.js";
|
|
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
|
+
}
|
|
8
|
+
function written(record, key) {
|
|
9
|
+
return Object.hasOwn(record, key) && record[key] !== undefined;
|
|
10
|
+
}
|
|
11
|
+
function requirementsOf(table, resource) {
|
|
12
|
+
const listed = Object.hasOwn(table, resource) ? table[resource] : undefined;
|
|
13
|
+
return Array.isArray(listed) ? listed : [];
|
|
14
|
+
}
|
|
15
|
+
function creatableScalar(contract, resource, field) {
|
|
16
|
+
const found = lookupField(lookupResource(contract, resource), field);
|
|
17
|
+
return found?.kind === "scalar" && found.capabilities.create !== undefined;
|
|
18
|
+
}
|
|
19
|
+
function creatableRelation(contract, resource, relation, scope) {
|
|
20
|
+
const found = lookupField(lookupResource(contract, resource), relation);
|
|
21
|
+
return (found?.kind === "relation" &&
|
|
22
|
+
advertisedDirectivesAt(found.capabilities.create, scope) !== undefined);
|
|
23
|
+
}
|
|
24
|
+
function checkCreateRecord(context, resource, record, path, scope, via) {
|
|
25
|
+
if (!isRecord(record)) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const { contract, issues } = context;
|
|
29
|
+
for (const requirement of requirementsOf(context.table, resource)) {
|
|
30
|
+
if (requirement.kind === "field") {
|
|
31
|
+
if (!written(record, requirement.field) &&
|
|
32
|
+
creatableScalar(contract, resource, requirement.field)) {
|
|
33
|
+
issues.push({
|
|
34
|
+
code: "V1000",
|
|
35
|
+
path: [...path, requirement.field],
|
|
36
|
+
message: `Field "${requirement.field}" is required to create ${resource}.`,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (via !== undefined &&
|
|
42
|
+
requirement.inverse?.resource === via.resource &&
|
|
43
|
+
requirement.inverse.relation === via.relation) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const orFields = Array.isArray(requirement.orFields)
|
|
47
|
+
? requirement.orFields
|
|
48
|
+
: [];
|
|
49
|
+
if (written(record, requirement.relation) ||
|
|
50
|
+
(orFields.length > 0 && orFields.every((field) => written(record, field)))) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (creatableRelation(contract, resource, requirement.relation, scope)) {
|
|
54
|
+
issues.push({
|
|
55
|
+
code: "V1000",
|
|
56
|
+
path: [...path, requirement.relation],
|
|
57
|
+
message: `Relation "${requirement.relation}" is required to create ${resource}${orFields.length > 0 ? `: write it, or ${orFields.map((field) => `"${field}"`).join(" and ")}` : ""}.`,
|
|
58
|
+
});
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const missing = orFields.find((field) => !written(record, field) && creatableScalar(contract, resource, field));
|
|
62
|
+
if (missing !== undefined) {
|
|
63
|
+
issues.push({
|
|
64
|
+
code: "V1000",
|
|
65
|
+
path: [...path, missing],
|
|
66
|
+
message: `Field "${missing}" is required to create ${resource}.`,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
walkNestedCreates(context, resource, record, path);
|
|
71
|
+
}
|
|
72
|
+
function walkNestedCreates(context, resource, record, path) {
|
|
73
|
+
if (!isRecord(record)) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const owner = lookupResource(context.contract, resource);
|
|
77
|
+
for (const [key, value] of Object.entries(record)) {
|
|
78
|
+
const field = lookupField(owner, key);
|
|
79
|
+
if (field?.kind !== "relation" || !isRecord(value)) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const via = { resource, relation: key };
|
|
83
|
+
for (const [directive, operand] of Object.entries(value)) {
|
|
84
|
+
if (!isRelationMutationDirective(directive)) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const entries = Array.isArray(operand)
|
|
88
|
+
? operand.map((entry, index) => ({ entry, at: [index] }))
|
|
89
|
+
: [{ entry: operand, at: [] }];
|
|
90
|
+
for (const { entry, at } of entries) {
|
|
91
|
+
const entryPath = [...path, key, directive, ...at];
|
|
92
|
+
for (const site of RELATION_MUTATION_RECORD_SITES[directive]) {
|
|
93
|
+
const siteRecord = site.key === undefined
|
|
94
|
+
? entry
|
|
95
|
+
: isRecord(entry)
|
|
96
|
+
? entry[site.key]
|
|
97
|
+
: undefined;
|
|
98
|
+
const sitePath = site.key === undefined ? entryPath : [...entryPath, site.key];
|
|
99
|
+
if (site.stage === "create") {
|
|
100
|
+
checkCreateRecord(context, field.target, siteRecord, sitePath, NESTED_WRITE_SCOPE, via);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
walkNestedCreates(context, field.target, siteRecord, sitePath);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export function missingCreateValues(contract, table, request, args, argumentsPath) {
|
|
111
|
+
if (!isRecord(args)) {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
const context = { contract, table, issues: [] };
|
|
115
|
+
const scope = resolveWriteScope(lookupResource(contract, request.resource), request);
|
|
116
|
+
if (request.family === "create" && written(args, "data")) {
|
|
117
|
+
const data = args.data;
|
|
118
|
+
if (Array.isArray(data)) {
|
|
119
|
+
data.forEach((entry, index) => {
|
|
120
|
+
checkCreateRecord(context, request.resource, entry, [...argumentsPath, "data", index], scope, undefined);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
checkCreateRecord(context, request.resource, data, [...argumentsPath, "data"], scope, undefined);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (request.family === "upsert") {
|
|
128
|
+
checkCreateRecord(context, request.resource, args.create, [...argumentsPath, "create"], scope, undefined);
|
|
129
|
+
walkNestedCreates(context, request.resource, args.update, [
|
|
130
|
+
...argumentsPath,
|
|
131
|
+
"update",
|
|
132
|
+
]);
|
|
133
|
+
}
|
|
134
|
+
if (request.family === "update") {
|
|
135
|
+
walkNestedCreates(context, request.resource, args.data, [
|
|
136
|
+
...argumentsPath,
|
|
137
|
+
"data",
|
|
138
|
+
]);
|
|
139
|
+
}
|
|
140
|
+
return context.issues;
|
|
141
|
+
}
|
|
@@ -5,8 +5,8 @@ export declare class Decimal {
|
|
|
5
5
|
toString(): string;
|
|
6
6
|
/**
|
|
7
7
|
* The decimal string, exactly as given: what `JSON.stringify` writes for a
|
|
8
|
-
* Decimal
|
|
9
|
-
*
|
|
8
|
+
* Decimal, so a result a developer serializes by hand carries its digits rather
|
|
9
|
+
* than `{}`. The protocol's encoders do not rely on it.
|
|
10
10
|
*/
|
|
11
11
|
toJSON(): string;
|
|
12
12
|
}
|