@aventara/core 0.1.0-pilot.0 → 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/README.md +3 -3
- 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 +13 -57
- 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 +26 -23
- package/dist/protocol/protocol-failure.js +6 -6
- package/dist/protocol/protocol-headers.d.ts +30 -27
- package/dist/protocol/protocol-headers.js +3 -61
- 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
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The caller's say over an in-process request it does not write by hand: the
|
|
3
3
|
* second argument of every `framework.application.*` / `framework.client.*` call
|
|
4
|
-
* and of `framework.transaction
|
|
4
|
+
* and of `framework.transaction`.
|
|
5
5
|
*
|
|
6
|
-
* Not "transport options": an in-process call has no transport. A deferred
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Not "transport options": an in-process call has no transport. A deferred handle
|
|
7
|
+
* (`framework.appTx.*` / `framework.clientTx.*`) takes none — a handle is not a
|
|
8
|
+
* request, and the id belongs to the run that executes it.
|
|
9
9
|
*/
|
|
10
10
|
export type ExecutionOptions = {
|
|
11
11
|
/**
|
|
@@ -8,18 +8,15 @@ import type { PipelineContext } from "./pipeline.js";
|
|
|
8
8
|
* What a write-stage compute callback is told about the field it is computing.
|
|
9
9
|
*
|
|
10
10
|
* `R` is the Resource the behavior is bound to and `Cfg` the authored
|
|
11
|
-
* configuration literal, so `resource` is the literal Resource name and
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* configuration literal, so `resource` is the literal Resource name and `field` —
|
|
12
|
+
* like every other field-key position in the framework — is {@link
|
|
13
|
+
* EffectiveFieldKey}: what the adapter discovered plus what the configuration
|
|
14
|
+
* added.
|
|
15
15
|
*
|
|
16
|
-
* `data` is keyed on the same set and stays `unknown`-valued.
|
|
17
|
-
* *value* typing is `OperationResultFor`'s job in Phase 9 (F-701) and is out
|
|
18
|
-
* of scope here by the plan's hard constraints; what this type buys a callback
|
|
19
|
-
* author is the key set, not the value.
|
|
16
|
+
* `data` is keyed on the same set and stays `unknown`-valued.
|
|
20
17
|
*
|
|
21
18
|
* Under the default `Cfg` the key set degenerates to `string` and this is
|
|
22
|
-
* byte-for-byte the shape it was before `Cfg` existed
|
|
19
|
+
* byte-for-byte the shape it was before `Cfg` existed.
|
|
23
20
|
*/
|
|
24
21
|
type ComputedWriteContext<M extends AdapterModel, Cfg, R extends FrameworkResourceKey<M>> = {
|
|
25
22
|
readonly scope: RequestOrigin;
|
|
@@ -4,12 +4,11 @@ import type { Contract, CoreContract } from "../contracts/contract.js";
|
|
|
4
4
|
import type { OperationExecutionRequest } from "./operation-request.js";
|
|
5
5
|
import type { PipelineContext } from "./pipeline.js";
|
|
6
6
|
/**
|
|
7
|
-
* The two Contracts a write reads
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* stripped before the Adapter call — never sent.
|
|
7
|
+
* The two Contracts a write reads. The **Core** Contract decides what the Adapter
|
|
8
|
+
* is asked to store; the **active** Contract adds the fields its scope declared
|
|
9
|
+
* under `application.fields` / `client.fields`, which Core does not carry. Such a
|
|
10
|
+
* field is VIRTUAL by construction (`contract.compiler.ts`), so its behaviours run
|
|
11
|
+
* and its value is stripped before the Adapter call — never sent.
|
|
13
12
|
*/
|
|
14
13
|
export type WriteContracts = {
|
|
15
14
|
readonly core: CoreContract;
|
|
@@ -2,13 +2,6 @@ import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
|
|
|
2
2
|
import { FrameworkError } from "./framework-error.js";
|
|
3
3
|
import { INTERNAL_FAILURE_MESSAGE } from "./operation-response.js";
|
|
4
4
|
import { normalizeScalarFieldValue } from "./scalar-runtime.js";
|
|
5
|
-
/**
|
|
6
|
-
* The scalar field `fieldName` names on `resourceName` for a write: Core's own
|
|
7
|
-
* field when Core has one — a field the scope hides or narrows is still stored,
|
|
8
|
-
* and its behaviours still run — else the scalar the active scope added. Its
|
|
9
|
-
* values are normalized against Core's registries either way: a scope adds
|
|
10
|
-
* fields, never a scalar or an enum.
|
|
11
|
-
*/
|
|
12
5
|
function writeScalarField(contracts, resourceName, fieldName) {
|
|
13
6
|
const field = lookupField(lookupResource(contracts.core, resourceName), fieldName) ??
|
|
14
7
|
lookupField(lookupResource(contracts.active, resourceName), fieldName);
|
|
@@ -16,15 +9,6 @@ function writeScalarField(contracts, resourceName, fieldName) {
|
|
|
16
9
|
? field
|
|
17
10
|
: undefined;
|
|
18
11
|
}
|
|
19
|
-
/**
|
|
20
|
-
* The sole grammar for relation-mutation record operands. Every write pass
|
|
21
|
-
* maps these sites with its own record leaf, leaving identifier-only
|
|
22
|
-
* directives untouched. The identifier operands this table deliberately leaves
|
|
23
|
-
* empty are described by `RELATION_MUTATION_SELECTOR_SITES` in
|
|
24
|
-
* `transactions/operation-arguments-encoder.ts`, the only pass that has to
|
|
25
|
-
* encode them; both tables are keyed by `RelationMutationDirective`, so a new
|
|
26
|
-
* directive fails to compile in both places.
|
|
27
|
-
*/
|
|
28
12
|
export const RELATION_MUTATION_RECORD_SITES = {
|
|
29
13
|
$create: [{ key: undefined, stage: "create" }],
|
|
30
14
|
$connect: [],
|
|
@@ -38,11 +22,6 @@ export const RELATION_MUTATION_RECORD_SITES = {
|
|
|
38
22
|
{ key: "update", stage: "update" },
|
|
39
23
|
],
|
|
40
24
|
};
|
|
41
|
-
/**
|
|
42
|
-
* Narrows a caller-supplied own key to the shared directive vocabulary. Own
|
|
43
|
-
* membership, not an assertion: `Object.prototype` members resolve through the
|
|
44
|
-
* prototype chain and would otherwise reach a site loop with a non-iterable.
|
|
45
|
-
*/
|
|
46
25
|
export function isRelationMutationDirective(key) {
|
|
47
26
|
return Object.hasOwn(RELATION_MUTATION_RECORD_SITES, key);
|
|
48
27
|
}
|
|
@@ -144,7 +123,6 @@ async function mapRelationMutationRecords(relation, value, leaf) {
|
|
|
144
123
|
}
|
|
145
124
|
return normalized;
|
|
146
125
|
}
|
|
147
|
-
/** Apply effective computed create/update field behavior to post-pipe logical args. */
|
|
148
126
|
export async function applyWriteFieldBehaviors(contracts, behaviors, request, args, contextFactory) {
|
|
149
127
|
if (behaviors === undefined || !isRecord(args)) {
|
|
150
128
|
return args;
|
|
@@ -190,7 +168,6 @@ async function stripVirtualRecord(contracts, resourceName, data) {
|
|
|
190
168
|
}
|
|
191
169
|
return persisted;
|
|
192
170
|
}
|
|
193
|
-
/** Strip VIRTUAL scalar input only at the persistence boundary. */
|
|
194
171
|
export async function stripVirtualMutationInput(contracts, request, args) {
|
|
195
172
|
if (!isRecord(args)) {
|
|
196
173
|
return args;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ValidationIssue } from "../operations/validation-code.js";
|
|
2
|
-
export declare const OPERATION_CODES: readonly ["A1000", "A1001", "A1002", "A1003", "A1004", "A1005", "A1006", "A1007", "A1008", "A1009", "A2000", "A2001", "A2002", "A2003", "A2004", "A2005", "A2006", "A2007", "A2008", "A2009", "A2010", "A2011", "A2012", "A2013", "A2014", "A3000", "A3001", "A3002", "A3003", "A4000", "A4001", "A4002"];
|
|
2
|
+
export declare const OPERATION_CODES: readonly ["A1000", "A1001", "A1002", "A1003", "A1004", "A1005", "A1006", "A1007", "A1008", "A1009", "A2000", "A2001", "A2002", "A2003", "A2004", "A2005", "A2006", "A2007", "A2008", "A2009", "A2010", "A2011", "A2012", "A2013", "A2014", "A3000", "A3001", "A3002", "A3003", "A3004", "A4000", "A4001", "A4002"];
|
|
3
3
|
export type OperationCode = (typeof OPERATION_CODES)[number];
|
|
4
4
|
export type OperationSuccessCode = Extract<OperationCode, `A1${string}`>;
|
|
5
5
|
export type OperationErrorCode = Exclude<OperationCode, OperationSuccessCode>;
|
|
@@ -28,37 +28,21 @@ export const OPERATION_CODES = [
|
|
|
28
28
|
"A3001",
|
|
29
29
|
"A3002",
|
|
30
30
|
"A3003",
|
|
31
|
+
"A3004",
|
|
31
32
|
"A4000",
|
|
32
33
|
"A4001",
|
|
33
34
|
"A4002",
|
|
34
35
|
];
|
|
35
|
-
/**
|
|
36
|
-
* Builds the success-with-data arm. The only sanctioned way to construct
|
|
37
|
-
* one: a runtime-computed `code` (e.g. from a table-driven outcome mapper)
|
|
38
|
-
* still yields a well-typed {@link OperationSuccessResponse}.
|
|
39
|
-
*/
|
|
40
36
|
export function operationSuccess(data, code) {
|
|
41
37
|
return { data, code, cause: null };
|
|
42
38
|
}
|
|
43
|
-
/** Builds the no-match success arm (A1001). */
|
|
44
39
|
export function operationNoMatch() {
|
|
45
40
|
return { data: null, code: "A1001", cause: null };
|
|
46
41
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Builds the failure arm. The only sanctioned way to construct one: a
|
|
49
|
-
* runtime-computed error `code` still yields a well-typed
|
|
50
|
-
* {@link OperationFailureResponse}.
|
|
51
|
-
*/
|
|
52
42
|
export function operationFailure(code, cause) {
|
|
53
43
|
return { data: null, code, cause };
|
|
54
44
|
}
|
|
55
|
-
/** The one generic internal-failure text; every A3000 carries it. */
|
|
56
45
|
export const INTERNAL_FAILURE_MESSAGE = "Internal framework error.";
|
|
57
|
-
/**
|
|
58
|
-
* The generic internal-failure envelope. Nothing about an internal failure is
|
|
59
|
-
* caller-actionable, so every A3000 the framework emits is this exact value -
|
|
60
|
-
* one constant rather than one restatement per boundary.
|
|
61
|
-
*/
|
|
62
46
|
export function internalFailure() {
|
|
63
47
|
return operationFailure("A3000", { message: INTERNAL_FAILURE_MESSAGE });
|
|
64
48
|
}
|
|
@@ -6,6 +6,7 @@ import type { ResolvedFrameworkConfig } from "../config/config.resolver.js";
|
|
|
6
6
|
import type { Contract } from "../contracts/contract.js";
|
|
7
7
|
import type { OperationValidationOptions, SemanticOperationArguments } from "../operations/operation-validator.js";
|
|
8
8
|
import type { FrameworkTransactionRequest } from "../transactions/transaction-request.js";
|
|
9
|
+
import { type CreateRequirementTable } from "./create-requirement.js";
|
|
9
10
|
import type { DiagnosticsReporter } from "./diagnostics-reporter.js";
|
|
10
11
|
import type { ClientProtocolIdentity, FrameworkOperationRequest, OperationExecutionRequest } from "./operation-request.js";
|
|
11
12
|
import type { OperationFailureResponse, OperationResponse } from "./operation-response.js";
|
|
@@ -15,12 +16,14 @@ export type OperationRuntimeDependencies<M extends AdapterModel> = {
|
|
|
15
16
|
readonly config: ResolvedFrameworkConfig<M>;
|
|
16
17
|
};
|
|
17
18
|
/**
|
|
18
|
-
* What a validated operation's pipeline needs: everything except the root
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* type (C-724).
|
|
19
|
+
* What a validated operation's pipeline needs: everything except the root Adapter.
|
|
20
|
+
* Its single Adapter call goes through the executor passed beside it, so inside a
|
|
21
|
+
* transaction plan the root Adapter is unreachable by type.
|
|
22
22
|
*/
|
|
23
|
-
export type OperationPipelineDependencies<M extends AdapterModel> = Omit<OperationRuntimeDependencies<M>, "adapter"
|
|
23
|
+
export type OperationPipelineDependencies<M extends AdapterModel> = Omit<OperationRuntimeDependencies<M>, "adapter"> & {
|
|
24
|
+
/** The Adapter's create requirements, read once at construction. */
|
|
25
|
+
readonly createRequirements: CreateRequirementTable;
|
|
26
|
+
};
|
|
24
27
|
/**
|
|
25
28
|
* The executor core owns no diagnostics channel: the framework factory owns
|
|
26
29
|
* it and hands in one reporter already bound to this request, so every
|
|
@@ -35,11 +38,11 @@ export declare function createOperationRuntime<M extends AdapterModel>(dependenc
|
|
|
35
38
|
/** Validates the request-level protocol identity shared by both execution seams. */
|
|
36
39
|
export declare function validateProtocolIdentity(contracts: FrameworkContracts, request: FrameworkOperationRequest | FrameworkTransactionRequest): OperationFailureResponse | undefined;
|
|
37
40
|
/**
|
|
38
|
-
* The stale-client check
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
41
|
+
* The stale-client check on one client protocol identity: the protocol version
|
|
42
|
+
* first (`A2006`), then the ClientContract hash (`A2005`). Exact match only. The
|
|
43
|
+
* one implementation — the protocol's decoders and absence answers
|
|
44
|
+
* (`@aventara/core/protocol`) call it too, so a stale client hears the same
|
|
45
|
+
* envelope whichever layer answers.
|
|
43
46
|
*/
|
|
44
47
|
export declare function protocolIdentityFailure(contracts: FrameworkContracts, protocol: ClientProtocolIdentity): OperationFailureResponse | undefined;
|
|
45
48
|
export type ValidatedOperationArguments = {
|
|
@@ -53,23 +56,22 @@ export type ValidatedOperationArguments = {
|
|
|
53
56
|
export declare function validateOperationRequest(contract: Contract, request: FrameworkOperationRequest, options?: OperationValidationOptions): ValidatedOperationArguments;
|
|
54
57
|
/**
|
|
55
58
|
* Everything after the Framework Validation Layer: the developer/runtime path
|
|
56
|
-
* wrapped in the post-validation error boundary. Validation failures never
|
|
57
|
-
*
|
|
59
|
+
* wrapped in the post-validation error boundary. Validation failures never reach
|
|
60
|
+
* this function, so developer filters never see them.
|
|
58
61
|
*
|
|
59
|
-
* `executor` is the operation's single Adapter call site
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
62
|
+
* `executor` is the operation's single Adapter call site: standalone execution
|
|
63
|
+
* passes the Adapter itself, a transaction plan passes the transaction-scoped
|
|
64
|
+
* executor its Adapter callback received. Every call owns one AdapterBoundary, so
|
|
65
|
+
* A3001/A3000 origin stays classified per operation.
|
|
63
66
|
*
|
|
64
|
-
* The parameter types keep two values out of reach: `dependencies` carries no
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
67
|
+
* The parameter types keep two values out of reach: `dependencies` carries no root
|
|
68
|
+
* Adapter, so the executor is the only Adapter this function can call, and
|
|
69
|
+
* `request` carries no `arguments`, so no stage can read the unvalidated view
|
|
70
|
+
* instead of `validatedArguments`.
|
|
68
71
|
*
|
|
69
72
|
* `argumentsPath` is the framework's own address for these arguments —
|
|
70
73
|
* `["arguments"]` standalone, `["operations", i, "args"]` inside a plan. The
|
|
71
74
|
* response boundary needs it to decide whether a thrower-supplied issue path
|
|
72
|
-
* denotes the caller's own arguments or is unvouched text
|
|
73
|
-
* decision 4).
|
|
75
|
+
* denotes the caller's own arguments or is unvouched text.
|
|
74
76
|
*/
|
|
75
77
|
export declare function executeValidatedOperation<M extends AdapterModel>(dependencies: OperationPipelineDependencies<M>, request: OperationExecutionRequest, contract: Contract, validatedArguments: unknown, argumentsPath: readonly (string | number)[], executor: AdapterOperationExecutor<M>, report: DiagnosticsReporter): Promise<OperationResponse>;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { AVENTARA_PROTOCOL_VERSION } from "../contracts/contract-protocol.js";
|
|
2
2
|
import { validateOperationArguments } from "../operations/operation-validator.js";
|
|
3
3
|
import { materializeReadOperation, planReadOperation, } from "./computed-read-runtime.js";
|
|
4
|
+
import { missingCreateValues, } from "./create-requirement.js";
|
|
4
5
|
import { AdapterBoundary, OPERATION_ARGUMENTS_PATH, resolveOperationFailure, sanitizeOperationFailure, } from "./error-boundary.js";
|
|
5
6
|
import { applyWriteFieldBehaviors, stripVirtualMutationInput, } from "./mutation-planner.js";
|
|
6
7
|
import { internalFailure, operationFailure } from "./operation-response.js";
|
|
7
8
|
import { mapOperationOutcome, mapValidationOutcome } from "./outcome-mapper.js";
|
|
8
9
|
import { validateOperationOutput } from "./output-validator.js";
|
|
10
|
+
import { refusePipeOutput } from "./pipe-output.validator.js";
|
|
9
11
|
import { normalizeOperationScalarValues } from "./scalar-runtime.js";
|
|
10
|
-
/** Selects the Contract of the request's execution scope for both execution seams. */
|
|
11
12
|
export function activeContract(contracts, request) {
|
|
12
13
|
return request.scope === "application"
|
|
13
14
|
? contracts.application
|
|
@@ -35,13 +36,6 @@ function forbidden() {
|
|
|
35
36
|
message: "Operation denied by a framework guard.",
|
|
36
37
|
});
|
|
37
38
|
}
|
|
38
|
-
/**
|
|
39
|
-
* Output validation is framework validation (spec 16.5), so its failure is
|
|
40
|
-
* framework-owned however it arrives. Reading the result can itself throw -
|
|
41
|
-
* an Adapter row may carry an accessor that explodes - and without this
|
|
42
|
-
* boundary that throw would leave through the post-validation boundary and be
|
|
43
|
-
* offered to developer filters, which could relabel it as any outcome.
|
|
44
|
-
*/
|
|
45
39
|
function outputAccepted(contract, request, projection, data, report) {
|
|
46
40
|
try {
|
|
47
41
|
return validateOperationOutput(contract, request, projection, data).ok;
|
|
@@ -51,8 +45,12 @@ function outputAccepted(contract, request, projection, data, report) {
|
|
|
51
45
|
return false;
|
|
52
46
|
}
|
|
53
47
|
}
|
|
54
|
-
/** Creates the transport-independent operation executor owned by core. */
|
|
55
48
|
export function createOperationRuntime(dependencies) {
|
|
49
|
+
const pipelineDependencies = {
|
|
50
|
+
contracts: dependencies.contracts,
|
|
51
|
+
config: dependencies.config,
|
|
52
|
+
createRequirements: dependencies.adapter.createRequirements ?? {},
|
|
53
|
+
};
|
|
56
54
|
return async (request, report) => {
|
|
57
55
|
let validated;
|
|
58
56
|
try {
|
|
@@ -67,14 +65,9 @@ export function createOperationRuntime(dependencies) {
|
|
|
67
65
|
if (!validated.ok) {
|
|
68
66
|
return validated.response;
|
|
69
67
|
}
|
|
70
|
-
return executeValidatedOperation(
|
|
68
|
+
return executeValidatedOperation(pipelineDependencies, request, validated.contract, validated.arguments, OPERATION_ARGUMENTS_PATH, dependencies.adapter, report);
|
|
71
69
|
};
|
|
72
70
|
}
|
|
73
|
-
/**
|
|
74
|
-
* The Framework Validation Layer (spec 16), inside its own boundary: an
|
|
75
|
-
* unexpected throw here is sanitized directly and reaches zero developer
|
|
76
|
-
* filters, exactly like a validation failure (spec 16.5, 17.3).
|
|
77
|
-
*/
|
|
78
71
|
function validateOperation(dependencies, request) {
|
|
79
72
|
const protocolFailure = validateProtocolIdentity(dependencies.contracts, request);
|
|
80
73
|
if (protocolFailure !== undefined) {
|
|
@@ -87,19 +80,11 @@ function validateOperation(dependencies, request) {
|
|
|
87
80
|
}
|
|
88
81
|
return { ok: true, contract, arguments: validation.arguments };
|
|
89
82
|
}
|
|
90
|
-
/** Validates the request-level protocol identity shared by both execution seams. */
|
|
91
83
|
export function validateProtocolIdentity(contracts, request) {
|
|
92
84
|
return request.scope === "client"
|
|
93
85
|
? protocolIdentityFailure(contracts, request.protocol)
|
|
94
86
|
: undefined;
|
|
95
87
|
}
|
|
96
|
-
/**
|
|
97
|
-
* The stale-client check (§19.4, cross-phase invariant 15) on one client
|
|
98
|
-
* protocol identity: the protocol version first (`A2006`), then the
|
|
99
|
-
* ClientContract hash (`A2005`). Exact match only. The one implementation —
|
|
100
|
-
* the protocol's decoders and absence answers (`@aventara/core/protocol`) call
|
|
101
|
-
* it too, so a stale client hears the same envelope whichever layer answers.
|
|
102
|
-
*/
|
|
103
88
|
export function protocolIdentityFailure(contracts, protocol) {
|
|
104
89
|
if (protocol.version !== AVENTARA_PROTOCOL_VERSION) {
|
|
105
90
|
return operationFailure("A2006", {
|
|
@@ -113,7 +98,6 @@ export function protocolIdentityFailure(contracts, protocol) {
|
|
|
113
98
|
}
|
|
114
99
|
return undefined;
|
|
115
100
|
}
|
|
116
|
-
/** Reusable per-operation Framework Validation Layer argument seam. */
|
|
117
101
|
export function validateOperationRequest(contract, request, options) {
|
|
118
102
|
const validation = validateOperationArguments(contract, request, options);
|
|
119
103
|
if (!validation.ok) {
|
|
@@ -130,27 +114,6 @@ export function validateOperationRequest(contract, request, options) {
|
|
|
130
114
|
function rejected(response) {
|
|
131
115
|
return { ok: false, response };
|
|
132
116
|
}
|
|
133
|
-
/**
|
|
134
|
-
* Everything after the Framework Validation Layer: the developer/runtime path
|
|
135
|
-
* wrapped in the post-validation error boundary. Validation failures never
|
|
136
|
-
* reach this function, so developer filters never see them.
|
|
137
|
-
*
|
|
138
|
-
* `executor` is the operation's single Adapter call site (D-706): standalone
|
|
139
|
-
* execution passes the Adapter itself, a transaction plan passes the
|
|
140
|
-
* transaction-scoped executor its Adapter callback received. Every call owns
|
|
141
|
-
* one AdapterBoundary, so A3001/A3000 origin stays classified per operation.
|
|
142
|
-
*
|
|
143
|
-
* The parameter types keep two values out of reach: `dependencies` carries no
|
|
144
|
-
* root Adapter, so the executor is the only Adapter this function can call,
|
|
145
|
-
* and `request` carries no `arguments`, so no stage can read the unvalidated
|
|
146
|
-
* view instead of `validatedArguments`.
|
|
147
|
-
*
|
|
148
|
-
* `argumentsPath` is the framework's own address for these arguments —
|
|
149
|
-
* `["arguments"]` standalone, `["operations", i, "args"]` inside a plan. The
|
|
150
|
-
* response boundary needs it to decide whether a thrower-supplied issue path
|
|
151
|
-
* denotes the caller's own arguments or is unvouched text (ADR 0008
|
|
152
|
-
* decision 4).
|
|
153
|
-
*/
|
|
154
117
|
export async function executeValidatedOperation(dependencies, request, contract, validatedArguments, argumentsPath, executor, report) {
|
|
155
118
|
const scopeConfig = activeScopeConfig(dependencies.config, request);
|
|
156
119
|
const pipelines = scopeConfig.pipelines;
|
|
@@ -169,13 +132,22 @@ export async function executeValidatedOperation(dependencies, request, contract,
|
|
|
169
132
|
return forbidden();
|
|
170
133
|
}
|
|
171
134
|
}
|
|
172
|
-
for (const pipe of pipelines.pipes) {
|
|
135
|
+
for (const [index, pipe] of pipelines.pipes.entries()) {
|
|
173
136
|
const nextArgs = await pipe(args, pipelineContext(contract, request, args));
|
|
174
137
|
if (nextArgs !== undefined) {
|
|
175
138
|
args = nextArgs;
|
|
176
139
|
}
|
|
140
|
+
const refused = refusePipeOutput({ core: dependencies.contracts.core, active: contract }, request, args, pipe, index);
|
|
141
|
+
if (refused !== undefined) {
|
|
142
|
+
report(refused.error, "A3004");
|
|
143
|
+
return refused.response;
|
|
144
|
+
}
|
|
177
145
|
}
|
|
178
146
|
args = await applyWriteFieldBehaviors({ core: dependencies.contracts.core, active: contract }, scopeConfig.behaviors, request, args, () => pipelineContext(contract, request, args));
|
|
147
|
+
const missing = missingCreateValues(contract, dependencies.createRequirements, request, args, argumentsPath);
|
|
148
|
+
if (missing.length > 0) {
|
|
149
|
+
return mapValidationOutcome(missing);
|
|
150
|
+
}
|
|
179
151
|
for (const hook of pipelines.hooks.before) {
|
|
180
152
|
await hook(pipelineContext(contract, request, args));
|
|
181
153
|
}
|
|
@@ -189,12 +161,6 @@ export async function executeValidatedOperation(dependencies, request, contract,
|
|
|
189
161
|
let invoke = async () => {
|
|
190
162
|
const readPlan = planReadOperation(dependencies.contracts.core, contract, scopeConfig.behaviors, request, args);
|
|
191
163
|
const adapterArguments = await stripVirtualMutationInput({ core: dependencies.contracts.core, active: contract }, request, readPlan.arguments);
|
|
192
|
-
// `family` and `variant` are a correlated pair, so they travel as one
|
|
193
|
-
// OperationIdentity value: naming them separately would let any family
|
|
194
|
-
// pair with any variant. Only `resource` is cast, and only to `M`'s
|
|
195
|
-
// key union - the runtime check standing in for the compiler is
|
|
196
|
-
// `validateOperationArguments`, which has already rejected any
|
|
197
|
-
// resource name the active Contract does not carry.
|
|
198
164
|
const identity = request;
|
|
199
165
|
const adapterData = await adapterBoundary.execute(() => executor.execute({
|
|
200
166
|
...identity,
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
import { internalFailure, operationFailure, operationNoMatch, operationSuccess, } from "./operation-response.js";
|
|
2
|
-
/**
|
|
3
|
-
* Spec section 13.2 success table, resource-agnostic. The mapped key type
|
|
4
|
-
* makes the compiler reject a missing or unknown variant.
|
|
5
|
-
*/
|
|
6
2
|
const OUTCOME_TABLE = {
|
|
7
3
|
"find.first": { success: "A1000", absence: "no-match" },
|
|
8
4
|
"find.unique": { success: "A1000", absence: "not-found" },
|
|
@@ -21,12 +17,6 @@ const OUTCOME_TABLE = {
|
|
|
21
17
|
"delete.count": { success: "A1007" },
|
|
22
18
|
"upsert.unique": { success: "A1008" },
|
|
23
19
|
};
|
|
24
|
-
/**
|
|
25
|
-
* `family` and `variant` are correlated on `OperationIdentity`, but the
|
|
26
|
-
* template built from them is not: it spans every family/variant pair, of
|
|
27
|
-
* which most are not operations. So the lookup is a genuine miss-or-hit and
|
|
28
|
-
* says so, rather than asserting a key it cannot prove.
|
|
29
|
-
*/
|
|
30
20
|
function outcomeRule(operation) {
|
|
31
21
|
const table = OUTCOME_TABLE;
|
|
32
22
|
return table[`${operation.family}.${operation.variant}`];
|
|
@@ -43,11 +33,6 @@ function absenceOutcome(absence) {
|
|
|
43
33
|
return internalFailure();
|
|
44
34
|
}
|
|
45
35
|
}
|
|
46
|
-
/**
|
|
47
|
-
* Maps one validated operation result to its stable outcome envelope. The
|
|
48
|
-
* result must already have passed output validation, so `null` can only
|
|
49
|
-
* reach a variant whose cardinality admits it.
|
|
50
|
-
*/
|
|
51
36
|
export function mapOperationOutcome(operation, data) {
|
|
52
37
|
const rule = outcomeRule(operation);
|
|
53
38
|
if (rule === undefined) {
|
|
@@ -57,12 +42,6 @@ export function mapOperationOutcome(operation, data) {
|
|
|
57
42
|
? absenceOutcome(rule.absence)
|
|
58
43
|
: operationSuccess(data, rule.success);
|
|
59
44
|
}
|
|
60
|
-
/**
|
|
61
|
-
* Spec section 13.2 failure table for the Framework Validation Layer: which
|
|
62
|
-
* A-code a set of validation issues reports. The most specific rejection
|
|
63
|
-
* wins - an unknown Resource before an unavailable operation, and a
|
|
64
|
-
* structural-limit breach before an ordinary argument rejection.
|
|
65
|
-
*/
|
|
66
45
|
function validationCode(issues) {
|
|
67
46
|
if (issues.some((issue) => issue.path?.[0] === "resource")) {
|
|
68
47
|
return "A2001";
|
|
@@ -84,7 +63,6 @@ function validationCode(issues) {
|
|
|
84
63
|
}
|
|
85
64
|
return "A2004";
|
|
86
65
|
}
|
|
87
|
-
/** Maps framework validation issues to their stable failure envelope. */
|
|
88
66
|
export function mapValidationOutcome(issues) {
|
|
89
67
|
return operationFailure(validationCode(issues), {
|
|
90
68
|
message: "Operation arguments failed framework validation.",
|
|
@@ -168,7 +168,6 @@ function validateProjection(contract, operation, projection, value) {
|
|
|
168
168
|
? validateExactRecord(contract, value, projection, ["data"])
|
|
169
169
|
: { ok: true };
|
|
170
170
|
}
|
|
171
|
-
/** Validate final operation output against fixed cardinality and executed projection. */
|
|
172
171
|
export function validateOperationOutput(contract, operation, projection, value) {
|
|
173
172
|
const cardinality = validateOperationOutputCardinality(operation, value);
|
|
174
173
|
return cardinality.ok
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Contract, CoreContract } from "../contracts/contract.js";
|
|
2
|
+
import type { OperationIdentity } from "../operations/operation-identity.js";
|
|
3
|
+
import type { ValidationIssue } from "../operations/validation-code.js";
|
|
4
|
+
import type { OperationFailureResponse } from "./operation-response.js";
|
|
5
|
+
/**
|
|
6
|
+
* The check a pipe's output passes before it can reach the Adapter.
|
|
7
|
+
*
|
|
8
|
+
* The request was validated against the caller's own Contract. A pipe then
|
|
9
|
+
* rewrites it, and may legitimately write what the caller cannot: a field the
|
|
10
|
+
* client Contract hides (a password hash, an owner id taken from the session),
|
|
11
|
+
* a directive the caller's scope does not advertise. So the rewritten arguments
|
|
12
|
+
* are held to the **server's** rules — the Core Contract, plus the fields the
|
|
13
|
+
* active scope itself adds (they are virtual: computed, then stripped before
|
|
14
|
+
* the Adapter call) — in the values' runtime forms (`bigint`, `Date`,
|
|
15
|
+
* `Decimal`, `Uint8Array`), which is what every pipe receives.
|
|
16
|
+
*
|
|
17
|
+
* A refusal is the server's defect, not the caller's: `A3004
|
|
18
|
+
* PIPE_OUTPUT_INVALID`, a 500 that tells the caller no more than any internal
|
|
19
|
+
* failure does — {@link PIPE_OUTPUT_FAILURE_MESSAGE}, and the step in a plan.
|
|
20
|
+
* The pipe, the operation, the first offending field and its V-code go to the
|
|
21
|
+
* diagnostics channel only, as a {@link PipeOutputError}: they name server
|
|
22
|
+
* internals — a pipe's function name, a field the caller's Contract hides.
|
|
23
|
+
*/
|
|
24
|
+
/** The one text every `A3004` carries on the wire. */
|
|
25
|
+
export declare const PIPE_OUTPUT_FAILURE_MESSAGE = "A server pipeline produced invalid arguments.";
|
|
26
|
+
/** The error the diagnostics channel receives for a refused pipe output. */
|
|
27
|
+
export declare class PipeOutputError extends Error {
|
|
28
|
+
readonly issues: readonly ValidationIssue[];
|
|
29
|
+
readonly name = "PipeOutputError";
|
|
30
|
+
constructor(message: string, issues: readonly ValidationIssue[]);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* `undefined` when the pipe's output passes the server's rules; otherwise the
|
|
34
|
+
* `A3004` failure, and the error the diagnostics channel is told.
|
|
35
|
+
*/
|
|
36
|
+
export declare function refusePipeOutput(contracts: {
|
|
37
|
+
readonly core: CoreContract;
|
|
38
|
+
readonly active: Contract;
|
|
39
|
+
}, request: OperationIdentity & {
|
|
40
|
+
readonly resource: string;
|
|
41
|
+
}, args: unknown,
|
|
42
|
+
/** The pipe that produced `args`, named in the message by its function name. */
|
|
43
|
+
pipe: unknown,
|
|
44
|
+
/** Its position in the effective pipe list (root pipes first). */
|
|
45
|
+
index: number): {
|
|
46
|
+
readonly response: OperationFailureResponse;
|
|
47
|
+
readonly error: PipeOutputError;
|
|
48
|
+
} | undefined;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { validateOperationArguments } from "../operations/operation-validator.js";
|
|
2
|
+
import { operationFailure } from "./operation-response.js";
|
|
3
|
+
import { normalizeOperationScalarValues } from "./scalar-runtime.js";
|
|
4
|
+
export const PIPE_OUTPUT_FAILURE_MESSAGE = "A server pipeline produced invalid arguments.";
|
|
5
|
+
function serverContract(core, active) {
|
|
6
|
+
const resources = {};
|
|
7
|
+
for (const [name, resource] of Object.entries(core.resources)) {
|
|
8
|
+
const scoped = Object.hasOwn(active.resources, name)
|
|
9
|
+
? active.resources[name]
|
|
10
|
+
: undefined;
|
|
11
|
+
const added = scoped === undefined
|
|
12
|
+
? {}
|
|
13
|
+
: Object.fromEntries(Object.entries(scoped.fields).filter(([field]) => !Object.hasOwn(resource.fields, field)));
|
|
14
|
+
resources[name] =
|
|
15
|
+
Object.keys(added).length === 0
|
|
16
|
+
? resource
|
|
17
|
+
: { ...resource, fields: { ...resource.fields, ...added } };
|
|
18
|
+
}
|
|
19
|
+
return { ...core, resources };
|
|
20
|
+
}
|
|
21
|
+
const SERVER_VIEWS = new WeakMap();
|
|
22
|
+
function serverContractFor(core, active) {
|
|
23
|
+
let view = SERVER_VIEWS.get(active);
|
|
24
|
+
if (view === undefined) {
|
|
25
|
+
view = serverContract(core, active);
|
|
26
|
+
SERVER_VIEWS.set(active, view);
|
|
27
|
+
}
|
|
28
|
+
return view;
|
|
29
|
+
}
|
|
30
|
+
function serverIssues(contract, request, args) {
|
|
31
|
+
const validation = validateOperationArguments(contract, {
|
|
32
|
+
...request,
|
|
33
|
+
arguments: args,
|
|
34
|
+
});
|
|
35
|
+
if (!validation.ok) {
|
|
36
|
+
return validation.issues;
|
|
37
|
+
}
|
|
38
|
+
const normalization = normalizeOperationScalarValues(contract, { ...request, arguments: validation.arguments }, "application");
|
|
39
|
+
return normalization.ok ? [] : normalization.issues;
|
|
40
|
+
}
|
|
41
|
+
function pipeLabel(pipe, index) {
|
|
42
|
+
let name;
|
|
43
|
+
try {
|
|
44
|
+
name = pipe.name;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
name = undefined;
|
|
48
|
+
}
|
|
49
|
+
return typeof name === "string" && name !== ""
|
|
50
|
+
? `pipes[${index}] "${name}"`
|
|
51
|
+
: `pipes[${index}] (anonymous)`;
|
|
52
|
+
}
|
|
53
|
+
function fieldOf(issue) {
|
|
54
|
+
const path = issue.path ?? [];
|
|
55
|
+
const relative = path[0] === "arguments" ? path.slice(1) : path;
|
|
56
|
+
return relative.length === 0 ? "(arguments)" : relative.join(".");
|
|
57
|
+
}
|
|
58
|
+
export class PipeOutputError extends Error {
|
|
59
|
+
issues;
|
|
60
|
+
name = "PipeOutputError";
|
|
61
|
+
constructor(message, issues) {
|
|
62
|
+
super(message);
|
|
63
|
+
this.issues = issues;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function refusePipeOutput(contracts, request, args, pipe, index) {
|
|
67
|
+
const issues = serverIssues(serverContractFor(contracts.core, contracts.active), request, args);
|
|
68
|
+
const [first] = issues;
|
|
69
|
+
if (first === undefined) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
const message = `Pipe ${pipeLabel(pipe, index)} returned arguments for ${request.resource}.${request.family}.${request.variant} that the server contract refuses at "${fieldOf(first)}" (${first.code}: ${first.message})`;
|
|
73
|
+
return {
|
|
74
|
+
response: operationFailure("A3004", {
|
|
75
|
+
message: PIPE_OUTPUT_FAILURE_MESSAGE,
|
|
76
|
+
}),
|
|
77
|
+
error: new PipeOutputError(message, issues),
|
|
78
|
+
};
|
|
79
|
+
}
|