@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
|
@@ -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
|
}
|
package/dist/runtime/decimal.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { WIRE_SCALAR_PATTERNS } from "../contracts/wire-scalar-format.js";
|
|
2
|
-
/** Provider-neutral exact decimal runtime value owned by Aventara. */
|
|
3
2
|
export class Decimal {
|
|
4
3
|
#value;
|
|
5
4
|
constructor(value) {
|
|
6
|
-
// `RegExp#test` coerces its argument, so without this guard a number from
|
|
7
|
-
// untyped code passed as `"5"` and `toString()` then returned the number.
|
|
8
5
|
if (typeof value !== "string") {
|
|
9
6
|
throw new TypeError("Decimal value must be a string.");
|
|
10
7
|
}
|
|
@@ -16,11 +13,6 @@ export class Decimal {
|
|
|
16
13
|
toString() {
|
|
17
14
|
return this.#value;
|
|
18
15
|
}
|
|
19
|
-
/**
|
|
20
|
-
* The decimal string, exactly as given: what `JSON.stringify` writes for a
|
|
21
|
-
* Decimal (Q16), so a result a developer serializes by hand carries its
|
|
22
|
-
* digits rather than `{}`. The protocol's encoders do not rely on it.
|
|
23
|
-
*/
|
|
24
16
|
toJSON() {
|
|
25
17
|
return this.#value;
|
|
26
18
|
}
|
|
@@ -5,12 +5,12 @@ import type { FrameworkOperationRequest } from "./operation-request.js";
|
|
|
5
5
|
import type { OperationErrorCode } from "./operation-response.js";
|
|
6
6
|
/**
|
|
7
7
|
* One unplanned failure as the framework saw it before sanitization: the raw
|
|
8
|
-
* value, the outcome code actually emitted, and enough identity to act on
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
8
|
+
* value, the outcome code actually emitted, and enough identity to act on it. The
|
|
9
|
+
* code is usually an internal one, because the channel fires for unplanned
|
|
10
|
+
* failures; it is the thrower's own code when the framework dropped something it
|
|
11
|
+
* could not vouch for from an otherwise honoured deliberate outcome, which changes
|
|
12
|
+
* no outcome. Server-side only — a diagnostic is never projected onto the wire,
|
|
13
|
+
* which is precisely why it may carry the raw error.
|
|
14
14
|
*/
|
|
15
15
|
export type OperationDiagnostic = {
|
|
16
16
|
readonly kind: "operation";
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
/** The default channel: no sink configured, so nothing is observed. */
|
|
2
1
|
const IGNORE_DIAGNOSTIC = () => undefined;
|
|
3
|
-
/**
|
|
4
|
-
* A sink may return a promise. Reading `then` off an arbitrary return value
|
|
5
|
-
* can itself throw, so this runs inside the reporter's own guard.
|
|
6
|
-
*/
|
|
7
2
|
function isThenable(value) {
|
|
8
3
|
if ((typeof value !== "object" || value === null) &&
|
|
9
4
|
typeof value !== "function") {
|
|
@@ -11,12 +6,6 @@ function isThenable(value) {
|
|
|
11
6
|
}
|
|
12
7
|
return typeof value.then === "function";
|
|
13
8
|
}
|
|
14
|
-
/**
|
|
15
|
-
* The channel bound to one diagnostic identity, as the reporters below bind
|
|
16
|
-
* it. Exported for `@aventara/core/protocol`, whose binding reports a failure
|
|
17
|
-
* only it can meet — a result with no wire form (`A3003`) — under the
|
|
18
|
-
* identity it holds; never exported from the main entry.
|
|
19
|
-
*/
|
|
20
9
|
export function createReporter(sink, identity) {
|
|
21
10
|
if (sink === undefined) {
|
|
22
11
|
return IGNORE_DIAGNOSTIC;
|
|
@@ -33,38 +22,15 @@ export function createReporter(sink, identity) {
|
|
|
33
22
|
}
|
|
34
23
|
};
|
|
35
24
|
}
|
|
36
|
-
/**
|
|
37
|
-
* The sink each constructed Framework was configured with, keyed by the
|
|
38
|
-
* Framework object `createFramework` returned. The protocol's binding
|
|
39
|
-
* (`AvProtocol.bind(framework)`, `@aventara/core/protocol`) reads it here, so
|
|
40
|
-
* the direction stays one-way — no execution module imports `protocol/` — and
|
|
41
|
-
* the public `Framework` type gains no member. A WeakMap: it keeps no
|
|
42
|
-
* Framework alive. A Framework built without a sink has no entry.
|
|
43
|
-
*/
|
|
44
25
|
const CONFIGURED_SINKS = new WeakMap();
|
|
45
|
-
/** Records the sink `framework` was configured with; called once, at construction. */
|
|
46
26
|
export function recordDiagnosticsSink(framework, sink) {
|
|
47
27
|
if (sink !== undefined) {
|
|
48
28
|
CONFIGURED_SINKS.set(framework, sink);
|
|
49
29
|
}
|
|
50
30
|
}
|
|
51
|
-
/** The sink `framework` was configured with, or `undefined` for none. */
|
|
52
31
|
export function diagnosticsSinkOf(framework) {
|
|
53
32
|
return CONFIGURED_SINKS.get(framework);
|
|
54
33
|
}
|
|
55
|
-
/**
|
|
56
|
-
* Binds the configured sink to one operation request.
|
|
57
|
-
*
|
|
58
|
-
* Identity is captured on entry rather than at report time: by the moment a
|
|
59
|
-
* failure is reported the request may be exactly what broke, and a diagnostic
|
|
60
|
-
* naming no operation is not actionable. A request whose own identity cannot
|
|
61
|
-
* be read is past observing, so it silently yields the no-op channel — the
|
|
62
|
-
* outermost totality guard still answers with the generic envelope.
|
|
63
|
-
*
|
|
64
|
-
* The response must be unaffected by observation, so the reporter neither
|
|
65
|
-
* awaits the sink nor lets anything the sink does escape: a throw, a rejected
|
|
66
|
-
* promise and a booby-trapped thenable all end here.
|
|
67
|
-
*/
|
|
68
34
|
export function createDiagnosticsReporter(sink, request, operation) {
|
|
69
35
|
let identity;
|
|
70
36
|
try {
|
|
@@ -83,7 +49,6 @@ export function createDiagnosticsReporter(sink, request, operation) {
|
|
|
83
49
|
}
|
|
84
50
|
return createReporter(sink, identity);
|
|
85
51
|
}
|
|
86
|
-
/** Binds the configured sink to one transaction request and its plan operations. */
|
|
87
52
|
export function createTransactionDiagnosticsChannel(sink, request) {
|
|
88
53
|
let identity;
|
|
89
54
|
try {
|
|
@@ -17,9 +17,9 @@ export declare class AdapterBoundary {
|
|
|
17
17
|
originOf(error: unknown): FailureOrigin;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
* The one result-serialization text; every A3003 carries it. Its producer is
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* The one result-serialization text; every A3003 carries it. Its producer is the
|
|
21
|
+
* protocol's response encoder, which imports it from here so the code has one text
|
|
22
|
+
* wherever it is emitted.
|
|
23
23
|
*/
|
|
24
24
|
export declare const SERIALIZATION_FAILURE_MESSAGE = "Result serialization failed.";
|
|
25
25
|
/** Where a standalone operation's arguments sit in a framework issue path. */
|
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import { VALIDATION_CODE_MESSAGES, VALIDATION_CODES, } from "../operations/validation-code.js";
|
|
2
2
|
import { FrameworkError } from "./framework-error.js";
|
|
3
3
|
import { INTERNAL_FAILURE_MESSAGE, internalFailure, OPERATION_CODES, operationFailure, } from "./operation-response.js";
|
|
4
|
-
|
|
4
|
+
import { PIPE_OUTPUT_FAILURE_MESSAGE } from "./pipe-output.validator.js";
|
|
5
5
|
export const ADAPTER_FAILURE_MESSAGE = "Adapter execution failed.";
|
|
6
|
-
/**
|
|
7
|
-
* Structurally records every value thrown across the Adapter seam so the
|
|
8
|
-
* final error boundary can classify origin by identity, never by inspecting
|
|
9
|
-
* error text. One boundary instance belongs to one operation execution.
|
|
10
|
-
*/
|
|
11
6
|
export class AdapterBoundary {
|
|
12
7
|
#thrown = new Set();
|
|
13
|
-
/** Runs Adapter work, remembering anything it throws before re-throwing it. */
|
|
14
8
|
async execute(work) {
|
|
15
9
|
try {
|
|
16
10
|
return await work();
|
|
@@ -24,29 +18,17 @@ export class AdapterBoundary {
|
|
|
24
18
|
return this.#thrown.has(error) ? "adapter" : "runtime";
|
|
25
19
|
}
|
|
26
20
|
}
|
|
27
|
-
/**
|
|
28
|
-
* The one result-serialization text; every A3003 carries it. Its producer is
|
|
29
|
-
* the protocol's response encoder (`@aventara/core/protocol`, P1), which
|
|
30
|
-
* imports it from here so the code has one text wherever it is emitted.
|
|
31
|
-
*/
|
|
32
21
|
export const SERIALIZATION_FAILURE_MESSAGE = "Result serialization failed.";
|
|
33
|
-
/**
|
|
34
|
-
* The framework's own text for each internal-failure code. An A3xxx cause is
|
|
35
|
-
* never caller-actionable, so whatever text its thrower supplied is replaced
|
|
36
|
-
* by this table — the realistic thrower is a careless Adapter forwarding a
|
|
37
|
-
* provider message, and spec 13.5 forbids that text on the wire.
|
|
38
|
-
*/
|
|
39
22
|
const INTERNAL_FAILURE_MESSAGES = {
|
|
40
23
|
A3000: INTERNAL_FAILURE_MESSAGE,
|
|
41
24
|
A3001: ADAPTER_FAILURE_MESSAGE,
|
|
42
25
|
A3002: "Transaction failed.",
|
|
43
26
|
A3003: SERIALIZATION_FAILURE_MESSAGE,
|
|
27
|
+
A3004: PIPE_OUTPUT_FAILURE_MESSAGE,
|
|
44
28
|
};
|
|
45
|
-
/** The internal code an unexpected failure of one origin is emitted under. */
|
|
46
29
|
function unexpectedCode(origin) {
|
|
47
30
|
return origin === "adapter" ? "A3001" : "A3000";
|
|
48
31
|
}
|
|
49
|
-
/** The generic, text-free envelope for an unexpected failure of one origin. */
|
|
50
32
|
function unexpectedFailure(origin) {
|
|
51
33
|
return origin === "adapter"
|
|
52
34
|
? operationFailure("A3001", { message: ADAPTER_FAILURE_MESSAGE })
|
|
@@ -58,10 +40,6 @@ function isInternalCode(code) {
|
|
|
58
40
|
function isPathSegment(value) {
|
|
59
41
|
return typeof value === "string" || typeof value === "number";
|
|
60
42
|
}
|
|
61
|
-
/**
|
|
62
|
-
* Whether an untrusted value is a list. `Array.isArray` is not itself total:
|
|
63
|
-
* it throws on a revoked Proxy, and a thrown value may be one.
|
|
64
|
-
*/
|
|
65
43
|
function isList(value) {
|
|
66
44
|
try {
|
|
67
45
|
return Array.isArray(value);
|
|
@@ -70,15 +48,6 @@ function isList(value) {
|
|
|
70
48
|
return false;
|
|
71
49
|
}
|
|
72
50
|
}
|
|
73
|
-
/**
|
|
74
|
-
* Copies an untrusted list into a plain array the framework owns. Traversal,
|
|
75
|
-
* not just property reads, is untrusted here: an index accessor, a Proxy
|
|
76
|
-
* trap, an overridden `map` and a hijacked `Symbol.species` all run
|
|
77
|
-
* thrower-supplied code, and none of it may become the boundary's own
|
|
78
|
-
* failure. Elements are read one at a time through {@link readProperty}, so
|
|
79
|
-
* an unreadable element is `undefined` — a value every projection below
|
|
80
|
-
* already rejects. `undefined` means the value is no list at all.
|
|
81
|
-
*/
|
|
82
51
|
function readList(value) {
|
|
83
52
|
if (!isList(value)) {
|
|
84
53
|
return undefined;
|
|
@@ -93,12 +62,6 @@ function readList(value) {
|
|
|
93
62
|
}
|
|
94
63
|
return items;
|
|
95
64
|
}
|
|
96
|
-
/**
|
|
97
|
-
* Projects an issue path onto a plain array of segments the framework owns.
|
|
98
|
-
* Copying rather than forwarding is the point: a planted path arrives as a
|
|
99
|
-
* live handle to a caller-held array, and its non-index properties would
|
|
100
|
-
* otherwise ride into the envelope for every in-memory consumer of it.
|
|
101
|
-
*/
|
|
102
65
|
function projectPath(value) {
|
|
103
66
|
const items = readList(value);
|
|
104
67
|
if (items === undefined) {
|
|
@@ -113,31 +76,13 @@ function projectPath(value) {
|
|
|
113
76
|
}
|
|
114
77
|
return path;
|
|
115
78
|
}
|
|
116
|
-
/** Where a standalone operation's arguments sit in a framework issue path. */
|
|
117
79
|
export const OPERATION_ARGUMENTS_PATH = [
|
|
118
80
|
"arguments",
|
|
119
81
|
];
|
|
120
|
-
/** Where one plan operation's arguments sit in a framework issue path. */
|
|
121
82
|
export function planOperationArgumentsPath(index) {
|
|
122
83
|
return ["operations", index, "args"];
|
|
123
84
|
}
|
|
124
|
-
/** A segment that named no own, data-backed member of its container. */
|
|
125
85
|
const UNRESOLVED = Symbol("unresolved-path-segment");
|
|
126
|
-
/**
|
|
127
|
-
* Reads one own, data-backed member of an untrusted container.
|
|
128
|
-
*
|
|
129
|
-
* Own only: satisfying a segment from the prototype chain would let `toString`
|
|
130
|
-
* or `constructor` pose as caller data. Data-backed only: the descriptor is
|
|
131
|
-
* consulted and only its `value` used, so a getter never runs — running a
|
|
132
|
-
* caller's accessor while sanitizing an error is a side effect in the worst
|
|
133
|
-
* possible place.
|
|
134
|
-
*
|
|
135
|
-
* The reflection itself can throw: a revoked Proxy rejects
|
|
136
|
-
* `getOwnPropertyDescriptor`. That is not caught here. The whole walk has one
|
|
137
|
-
* containment point, in {@link resolvesAgainstArguments}, so there is exactly
|
|
138
|
-
* one place where an unwalkable path and an unresolvable one become the same
|
|
139
|
-
* answer.
|
|
140
|
-
*/
|
|
141
86
|
function readOwnMember(container, key) {
|
|
142
87
|
if (typeof container !== "object" || container === null) {
|
|
143
88
|
return UNRESOLVED;
|
|
@@ -147,7 +92,6 @@ function readOwnMember(container, key) {
|
|
|
147
92
|
? descriptor.value
|
|
148
93
|
: UNRESOLVED;
|
|
149
94
|
}
|
|
150
|
-
/** Resolves one path segment; a numeric segment indexes an array and nothing else. */
|
|
151
95
|
function readSegment(container, segment) {
|
|
152
96
|
if (typeof segment === "number") {
|
|
153
97
|
return Number.isInteger(segment) && segment >= 0 && isList(container)
|
|
@@ -156,22 +100,6 @@ function readSegment(container, segment) {
|
|
|
156
100
|
}
|
|
157
101
|
return readOwnMember(container, segment);
|
|
158
102
|
}
|
|
159
|
-
/**
|
|
160
|
-
* Whether a thrower-supplied path denotes a member of the caller's own
|
|
161
|
-
* arguments — the one thing that makes it safe to emit.
|
|
162
|
-
*
|
|
163
|
-
* A path that resolves echoes back a key sequence the caller already holds;
|
|
164
|
-
* anything else is unvouched content on the same channel spec 13.5 closes for
|
|
165
|
-
* the rest of `cause` (ADR 0008 decision 4, applied to the third axis). The
|
|
166
|
-
* prefix must be the framework's own address for these arguments, and every
|
|
167
|
-
* remaining segment must resolve: a resolving prefix does not license the
|
|
168
|
-
* rest, so a partial match is no match.
|
|
169
|
-
*
|
|
170
|
-
* Total, like every step the boundary takes on a thrown value. The walk runs
|
|
171
|
-
* over data the framework does not own — a revoked Proxy on it throws from
|
|
172
|
-
* reflection alone — so the whole walk sits inside one guard: an unresolvable
|
|
173
|
-
* path and an unwalkable one are the same answer.
|
|
174
|
-
*/
|
|
175
103
|
function resolvesAgainstArguments(path, view) {
|
|
176
104
|
try {
|
|
177
105
|
if (path.length < view.path.length) {
|
|
@@ -199,41 +127,16 @@ function resolvesAgainstArguments(path, view) {
|
|
|
199
127
|
return false;
|
|
200
128
|
}
|
|
201
129
|
}
|
|
202
|
-
/**
|
|
203
|
-
* Only an allocated Validation code (spec 13.3, amended by ADR 0006 DA-4) may
|
|
204
|
-
* be emitted. This is ADR 0005's rule on the axis it did not cover: the union
|
|
205
|
-
* is a compile-time promise a separately compiled Adapter or a developer
|
|
206
|
-
* filter never made, so the set is tested by identity before emission.
|
|
207
|
-
*/
|
|
208
130
|
function isValidationCode(value) {
|
|
209
131
|
return (typeof value === "string" &&
|
|
210
132
|
VALIDATION_CODES.includes(value));
|
|
211
133
|
}
|
|
212
|
-
/**
|
|
213
|
-
* Projects one issue onto the spec ValidationIssue shape; unknown keys drop.
|
|
214
|
-
*
|
|
215
|
-
* All three fields are framework-owned by the time they are emitted.
|
|
216
|
-
* An unrecognised `code` drops the whole issue rather than riding onto the
|
|
217
|
-
* wire in a field the spec defines as a closed set — and rather than being
|
|
218
|
-
* rewritten to a plausible neighbour, which would misreport what was wrong.
|
|
219
|
-
* The `message` is replaced by the framework's own published text for that
|
|
220
|
-
* code, because a thrower-supplied message is the same provider-text channel
|
|
221
|
-
* spec 13.5 closes for the rest of `cause`. The `path` is emitted only when
|
|
222
|
-
* every segment of it resolves against the caller's own arguments; it is an
|
|
223
|
-
* open-ended string channel otherwise, and spec 13.1 makes the field
|
|
224
|
-
* optional, so a path that cannot be vouched for is simply absent — the
|
|
225
|
-
* issue, its code and the outcome are unchanged. Each dropped value is
|
|
226
|
-
* appended to `unvouched`, so the boundary can hand it to the ADR 0004
|
|
227
|
-
* diagnostics channel — the only place it still exists.
|
|
228
|
-
*/
|
|
229
134
|
function projectIssue(value, unvouched, view) {
|
|
230
135
|
if (typeof value !== "object" || value === null) {
|
|
231
136
|
return undefined;
|
|
232
137
|
}
|
|
233
138
|
const code = readProperty(value, "code");
|
|
234
139
|
if (!isValidationCode(code)) {
|
|
235
|
-
// `undefined` is an unreadable or absent code, not a supplied one:
|
|
236
|
-
// reporting it would be noise, and the issue is malformed either way.
|
|
237
140
|
if (code !== undefined) {
|
|
238
141
|
unvouched.push(code);
|
|
239
142
|
}
|
|
@@ -255,18 +158,6 @@ function projectIssue(value, unvouched, view) {
|
|
|
255
158
|
}
|
|
256
159
|
return { path, code, message: owned };
|
|
257
160
|
}
|
|
258
|
-
/**
|
|
259
|
-
* Projects a thrower-supplied cause onto the spec 13 `Cause` shape:
|
|
260
|
-
* `{ message, issues?, operation? }`. Unknown keys are dropped rather than
|
|
261
|
-
* forwarded, so a provider error assigned wholesale as a cause loses its
|
|
262
|
-
* `sql`, `stack`, DSN and whatever else it carried. A cause without a string
|
|
263
|
-
* message is no cause at all.
|
|
264
|
-
*
|
|
265
|
-
* Total, like every step the boundary takes on a thrown value: a cause it
|
|
266
|
-
* cannot traverse yields the parts it could read, never an exception. A
|
|
267
|
-
* throw here would escape the boundary that was sanitizing it and cost the
|
|
268
|
-
* thrower the very outcome it deliberately named.
|
|
269
|
-
*/
|
|
270
161
|
function projectCause(value, unvouched, view) {
|
|
271
162
|
const message = readProperty(value, "message");
|
|
272
163
|
if (typeof message !== "string") {
|
|
@@ -279,29 +170,16 @@ function projectCause(value, unvouched, view) {
|
|
|
279
170
|
.filter((issue) => issue !== undefined);
|
|
280
171
|
return {
|
|
281
172
|
message,
|
|
282
|
-
// An empty list is no list: dropping every issue leaves the cause
|
|
283
|
-
// without issues rather than fabricating one to fill the field.
|
|
284
173
|
...(issues !== undefined && issues.length > 0 ? { issues } : {}),
|
|
285
174
|
...(Number.isInteger(rawOperation) && rawOperation >= 0
|
|
286
175
|
? { operation: rawOperation }
|
|
287
176
|
: {}),
|
|
288
177
|
};
|
|
289
178
|
}
|
|
290
|
-
/**
|
|
291
|
-
* Internal failures carry no caller-actionable diagnostics: the message is
|
|
292
|
-
* the framework's own and `issues` raised while validating computed/Adapter
|
|
293
|
-
* output describe server internals and stay behind the boundary (available
|
|
294
|
-
* to filters, never emitted).
|
|
295
|
-
*/
|
|
296
179
|
function internalCause(code, cause) {
|
|
297
180
|
const { issues: _issues, message: _message, ...emitted } = cause;
|
|
298
181
|
return { ...emitted, message: INTERNAL_FAILURE_MESSAGES[code] };
|
|
299
182
|
}
|
|
300
|
-
/**
|
|
301
|
-
* Reads one property of a thrown value. Thrown values are untrusted: a
|
|
302
|
-
* getter or Proxy trap may itself throw, and that must never become the
|
|
303
|
-
* boundary's own failure.
|
|
304
|
-
*/
|
|
305
183
|
function readProperty(value, key) {
|
|
306
184
|
try {
|
|
307
185
|
return value[key];
|
|
@@ -318,34 +196,16 @@ function isFrameworkErrorInstance(error) {
|
|
|
318
196
|
return false;
|
|
319
197
|
}
|
|
320
198
|
}
|
|
321
|
-
/**
|
|
322
|
-
* A {@link FrameworkError} that fails vetting. Its thrower deliberately named
|
|
323
|
-
* an outcome the framework cannot honour, so the failure is the broken
|
|
324
|
-
* outcome contract itself — an internal error, whatever the origin — and the
|
|
325
|
-
* offending value is replaced, never echoed.
|
|
326
|
-
*/
|
|
327
199
|
const MALFORMED_OUTCOME = {
|
|
328
200
|
code: "A3000",
|
|
329
201
|
cause: { message: INTERNAL_FAILURE_MESSAGE },
|
|
330
202
|
unvouched: [],
|
|
331
203
|
};
|
|
332
|
-
/**
|
|
333
|
-
* Only a recognised error code (spec 13.2) is a deliberate outcome. An
|
|
334
|
-
* unknown code, or a success code arriving on the failure path, would break
|
|
335
|
-
* the ADR 0003 union on the wire.
|
|
336
|
-
*/
|
|
337
204
|
function isOperationErrorCode(value) {
|
|
338
205
|
return (typeof value === "string" &&
|
|
339
206
|
OPERATION_CODES.includes(value) &&
|
|
340
207
|
!value.startsWith("A1"));
|
|
341
208
|
}
|
|
342
|
-
/**
|
|
343
|
-
* Vets a value as a deliberate {@link FrameworkError}. `undefined` means it
|
|
344
|
-
* is not one at all. Passing the `instanceof` check is not enough:
|
|
345
|
-
* `Object.create(FrameworkError.prototype)`, a booby-trapped accessor, an
|
|
346
|
-
* unrecognised code or a cause without a message all reach here, and each
|
|
347
|
-
* is {@link MALFORMED_OUTCOME} rather than something to dereference or emit.
|
|
348
|
-
*/
|
|
349
209
|
function vetDeliberateOutcome(error, view) {
|
|
350
210
|
if (!isFrameworkErrorInstance(error)) {
|
|
351
211
|
return undefined;
|
|
@@ -361,31 +221,16 @@ function vetDeliberateOutcome(error, view) {
|
|
|
361
221
|
const cause = projectCause(rawCause, unvouched, view);
|
|
362
222
|
return cause === undefined ? MALFORMED_OUTCOME : { code, cause, unvouched };
|
|
363
223
|
}
|
|
364
|
-
/**
|
|
365
|
-
* Hands every dropped issue code and issue path to the ADR 0004 channel under
|
|
366
|
-
* the outcome actually emitted. Dropping something the framework cannot vouch
|
|
367
|
-
* for is no grounds to escalate the thrower's chosen outcome, so the code
|
|
368
|
-
* stands and the channel carries the whole consequence. Reporting cannot
|
|
369
|
-
* affect the response: the reporter is total and swallows whatever the sink
|
|
370
|
-
* does.
|
|
371
|
-
*/
|
|
372
224
|
function reportUnvouched(outcome, report) {
|
|
373
225
|
for (const value of outcome.unvouched) {
|
|
374
226
|
report(value, outcome.code);
|
|
375
227
|
}
|
|
376
228
|
}
|
|
377
|
-
/** A deliberate outcome: the stable code and cause its thrower chose. */
|
|
378
229
|
function deliberateFailure(outcome) {
|
|
379
230
|
return operationFailure(outcome.code, isInternalCode(outcome.code)
|
|
380
231
|
? internalCause(outcome.code, outcome.cause)
|
|
381
232
|
: outcome.cause);
|
|
382
233
|
}
|
|
383
|
-
/**
|
|
384
|
-
* Final framework sanitization, shared by every boundary. A deliberate
|
|
385
|
-
* {@link FrameworkError} carries its own stable outcome; anything else is
|
|
386
|
-
* replaced by a generic cause so no provider text, stack, path, or secret
|
|
387
|
-
* can cross the response boundary. Total: it never throws.
|
|
388
|
-
*/
|
|
389
234
|
export function sanitizeOperationFailure(error, origin, report, view) {
|
|
390
235
|
const outcome = vetDeliberateOutcome(error, view);
|
|
391
236
|
if (outcome === undefined) {
|
|
@@ -398,13 +243,6 @@ export function sanitizeOperationFailure(error, origin, report, view) {
|
|
|
398
243
|
reportUnvouched(outcome, report);
|
|
399
244
|
return deliberateFailure(outcome);
|
|
400
245
|
}
|
|
401
|
-
/**
|
|
402
|
-
* Post-validation exception flow (spec 17.3): effective filters are stored as
|
|
403
|
-
* `[root..., scoped...]`, so reverse iteration tries scoped filters in reverse
|
|
404
|
-
* registration order, then root filters in reverse, then final sanitization.
|
|
405
|
-
* A filter handles an error only by returning a {@link FrameworkError};
|
|
406
|
-
* returning `undefined` or throwing leaves the original error unhandled.
|
|
407
|
-
*/
|
|
408
246
|
export async function resolveOperationFailure(error, filters, context, boundary, report, view) {
|
|
409
247
|
for (let index = filters.length - 1; index >= 0; index -= 1) {
|
|
410
248
|
const filter = filters[index];
|
|
@@ -413,9 +251,6 @@ export async function resolveOperationFailure(error, filters, context, boundary,
|
|
|
413
251
|
}
|
|
414
252
|
const handled = await attemptFilter(filter, error, context, view);
|
|
415
253
|
if (handled !== undefined) {
|
|
416
|
-
// A filter that names an outcome the framework cannot honour has
|
|
417
|
-
// swallowed the original error on the developer's behalf; the
|
|
418
|
-
// diagnostics channel is the only place it still exists.
|
|
419
254
|
if (handled === MALFORMED_OUTCOME) {
|
|
420
255
|
report(error, "A3000");
|
|
421
256
|
}
|