@aventara/core 1.0.0-rc.0 → 1.0.0-rc.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/CHANGELOG.md +1434 -0
- package/ERRORS.md +142 -0
- package/README.md +247 -57
- package/dist/adapters/adapter-discovery-diagnostic.d.ts +4 -4
- package/dist/adapters/adapter-discovery-diagnostic.guidance.d.ts +6 -0
- package/dist/adapters/adapter-discovery-diagnostic.guidance.js +7 -0
- package/dist/adapters/adapter-field.d.ts +25 -11
- package/dist/adapters/adapter-identifier.d.ts +6 -6
- package/dist/adapters/adapter-model.d.ts +18 -9
- package/dist/adapters/adapter-operation-executor.interface.d.ts +4 -4
- package/dist/adapters/adapter-operation.d.ts +3 -3
- package/dist/adapters/adapter-resource.d.ts +18 -9
- package/dist/adapters/adapter-sql-statement.d.ts +19 -0
- package/dist/adapters/adapter-transaction-executor.interface.d.ts +19 -0
- package/dist/adapters/adapter.interface.d.ts +59 -29
- package/dist/avz/avz-check.factory.d.ts +83 -0
- package/dist/avz/avz-check.factory.js +116 -0
- package/dist/avz/avz-check.registry.d.ts +35 -0
- package/dist/avz/avz-check.registry.js +9 -0
- package/dist/avz/avz.d.ts +203 -0
- package/dist/{json → avz}/avz.js +150 -8
- package/dist/{json → avz}/index.d.ts +2 -1
- package/dist/{json → avz}/json-shape.registry.d.ts +7 -7
- package/dist/{json → avz}/zod-issue.translator.d.ts +8 -2
- package/dist/{json → avz}/zod-issue.translator.js +7 -0
- package/dist/compiler/contract-compiler-diagnostic.d.ts +31 -5
- package/dist/compiler/contract-compiler-diagnostic.guidance.d.ts +6 -0
- package/dist/compiler/contract-compiler-diagnostic.guidance.js +97 -0
- package/dist/compiler/contract.compiler.d.ts +18 -18
- package/dist/compiler/contract.compiler.js +637 -442
- package/dist/compiler/create-requirements.projector.d.ts +49 -0
- package/dist/compiler/create-requirements.projector.js +423 -0
- package/dist/compiler/field-validator.compiler.d.ts +47 -0
- package/dist/compiler/field-validator.compiler.js +238 -0
- package/dist/config/any-framework-config.d.ts +19 -12
- package/dist/config/computed-fields.d.ts +62 -0
- package/dist/config/computed-fields.js +80 -0
- package/dist/config/config-diagnostic.d.ts +11 -4
- package/dist/config/config-diagnostic.guidance.d.ts +6 -0
- package/dist/config/config-diagnostic.guidance.js +32 -0
- package/dist/config/config-layer.d.ts +15 -6
- package/dist/config/config.resolver.d.ts +74 -31
- package/dist/config/config.resolver.js +108 -44
- package/dist/config/config.validator.d.ts +7 -7
- package/dist/config/config.validator.js +581 -72
- package/dist/config/define-resource.d.ts +57 -0
- package/dist/config/define-resource.js +14 -0
- package/dist/config/effective-fields.d.ts +19 -23
- package/dist/config/entrypoint.normalizer.d.ts +1 -1
- package/dist/config/exact-framework-config.d.ts +308 -0
- package/dist/config/field-compute.resolver.d.ts +21 -0
- package/dist/config/field-compute.resolver.js +123 -0
- package/dist/config/field-pipeline-config.d.ts +47 -0
- package/dist/config/forced-operation.validator.d.ts +10 -0
- package/dist/config/forced-operation.validator.js +66 -0
- package/dist/config/framework-config.d.ts +111 -39
- package/dist/config/framework-exemptions-config.d.ts +91 -0
- package/dist/config/framework-exemptions-config.js +1 -0
- package/dist/config/framework-field-definition.d.ts +78 -0
- package/dist/config/framework-field-definition.js +28 -0
- package/dist/config/framework-resources-config.d.ts +122 -0
- package/dist/config/framework-resources-config.js +1 -0
- package/dist/config/pipeline-config.d.ts +56 -20
- package/dist/config/pipeline-tree.resolver.d.ts +79 -0
- package/dist/config/pipeline-tree.resolver.js +220 -0
- package/dist/config/stage-type.eraser.d.ts +65 -0
- package/dist/config/stage-type.eraser.js +1 -0
- package/dist/contracts/capability.d.ts +21 -12
- package/dist/contracts/capability.js +2 -0
- package/dist/contracts/client-contract-structure.validator.d.ts +388 -63
- package/dist/contracts/client-contract-structure.validator.js +156 -10
- package/dist/contracts/contract-canonicalizer.d.ts +26 -3
- package/dist/contracts/contract-canonicalizer.js +156 -29
- package/dist/contracts/contract-hash.d.ts +12 -7
- package/dist/contracts/contract-hash.js +3 -2
- package/dist/contracts/contract-limits.d.ts +4 -1
- package/dist/contracts/contract-limits.js +1 -0
- package/dist/contracts/contract-lookup.d.ts +5 -5
- package/dist/contracts/contract-protocol.d.ts +4 -4
- package/dist/contracts/contract.d.ts +24 -16
- package/dist/contracts/create-requirements.d.ts +35 -0
- package/dist/contracts/create-requirements.js +1 -0
- package/dist/contracts/enum-definition.d.ts +1 -1
- package/dist/contracts/field-check.d.ts +101 -0
- package/dist/contracts/field-check.js +1 -0
- package/dist/contracts/field.d.ts +81 -42
- package/dist/contracts/identifier.d.ts +4 -4
- package/dist/contracts/json-shape.d.ts +12 -1
- package/dist/contracts/lifecycle-trait.d.ts +2 -2
- package/dist/contracts/resource-operations.d.ts +28 -28
- package/dist/contracts/resource.d.ts +15 -9
- package/dist/contracts/scalar-value-type.d.ts +15 -15
- package/dist/contracts/scalar.d.ts +3 -3
- package/dist/contracts/transaction-capability.d.ts +1 -1
- package/dist/contracts/wire-scalar-format.d.ts +4 -4
- package/dist/framework/contract-view.d.ts +213 -147
- package/dist/framework/framework-construction.error.d.ts +19 -10
- package/dist/framework/framework-construction.error.js +7 -3
- package/dist/framework/framework.d.ts +47 -23
- package/dist/framework/framework.factory.d.ts +8 -6
- package/dist/framework/framework.factory.js +49 -22
- package/dist/framework/operation-facade.d.ts +123 -52
- package/dist/framework/operation-facade.js +9 -1
- package/dist/index.d.ts +57 -50
- package/dist/index.js +7 -6
- package/dist/internal.d.ts +17 -10
- package/dist/internal.js +4 -1
- package/dist/operations/database-refusal.d.ts +51 -0
- package/dist/operations/database-refusal.js +45 -0
- package/dist/operations/operation-arguments.d.ts +343 -207
- package/dist/operations/operation-call.d.ts +27 -28
- package/dist/operations/operation-identity.d.ts +10 -10
- package/dist/operations/operation-result.d.ts +21 -21
- package/dist/operations/operation-validator-core.d.ts +6 -6
- package/dist/operations/operation-validator-core.js +99 -6
- package/dist/operations/operation-validator.d.ts +9 -9
- package/dist/operations/projection-scope.resolver.d.ts +6 -6
- package/dist/operations/structural-limits.walker.d.ts +6 -6
- package/dist/operations/structural-limits.walker.js +145 -0
- package/dist/operations/validation-code.d.ts +11 -6
- package/dist/operations/validation-code.guidance.d.ts +6 -0
- package/dist/operations/validation-code.guidance.js +90 -0
- package/dist/operations/validation-code.js +4 -0
- package/dist/operations/write-scope.resolver.d.ts +10 -6
- package/dist/operations/write-scope.resolver.js +2 -0
- package/dist/protocol/absent-route.responder.d.ts +13 -12
- package/dist/protocol/absent-route.responder.js +19 -19
- package/dist/protocol/contract-document.responder.d.ts +3 -3
- package/dist/protocol/contract-document.responder.js +2 -2
- package/dist/protocol/http-request.decoder.d.ts +17 -13
- package/dist/protocol/http-request.decoder.js +10 -10
- package/dist/protocol/http-request.reader.d.ts +3 -3
- package/dist/protocol/http-response.encoder.d.ts +4 -4
- package/dist/protocol/http-response.encoder.js +94 -55
- package/dist/protocol/http-status.table.d.ts +2 -2
- package/dist/protocol/operation-response.guard.d.ts +2 -2
- package/dist/protocol/protocol-surface.d.ts +4 -4
- package/dist/protocol/protocol.binder.d.ts +50 -27
- package/dist/protocol/protocol.binder.js +12 -9
- package/dist/protocol/protocol.handler.d.ts +22 -13
- package/dist/protocol/protocol.handler.js +91 -17
- package/dist/protocol/unmounted-route.error.d.ts +4 -4
- package/dist/protocol/unmounted-route.error.js +2 -2
- package/dist/runtime/caller-data.copier.d.ts +22 -5
- package/dist/runtime/caller-data.copier.js +31 -14
- package/dist/runtime/caller-data.door.d.ts +17 -11
- package/dist/runtime/caller-data.door.js +6 -4
- package/dist/runtime/computed-read-runtime.d.ts +7 -6
- package/dist/runtime/computed-read-runtime.js +76 -50
- package/dist/runtime/computed-value.error.d.ts +7 -6
- package/dist/runtime/computed-value.error.js +5 -4
- package/dist/runtime/context-executor.runtime.d.ts +59 -0
- package/dist/runtime/context-executor.runtime.js +275 -0
- package/dist/runtime/create-requirement.d.ts +44 -79
- package/dist/runtime/create-requirement.js +213 -73
- package/dist/runtime/decimal.d.ts +2 -2
- package/dist/runtime/decimal.js +1 -1
- package/dist/runtime/diagnostics-reporter.d.ts +54 -27
- package/dist/runtime/diagnostics-reporter.js +24 -0
- package/dist/runtime/error-boundary.d.ts +65 -8
- package/dist/runtime/error-boundary.js +49 -19
- package/dist/runtime/error-guidance.d.ts +12 -0
- package/dist/runtime/error-guidance.js +1 -0
- package/dist/runtime/error.catalog.d.ts +49 -0
- package/dist/runtime/error.catalog.js +49 -0
- package/dist/runtime/execution-options.d.ts +10 -2
- package/dist/runtime/field-check.evaluator.d.ts +29 -0
- package/dist/runtime/field-check.evaluator.js +521 -0
- package/dist/runtime/field-compute.d.ts +64 -0
- package/dist/runtime/field-compute.js +1 -0
- package/dist/runtime/field-stage.plan.d.ts +15 -0
- package/dist/runtime/field-stage.plan.js +27 -0
- package/dist/runtime/field-stage.runtime.d.ts +37 -0
- package/dist/runtime/field-stage.runtime.js +147 -0
- package/dist/runtime/field-validator.runtime.d.ts +19 -0
- package/dist/runtime/field-validator.runtime.js +92 -0
- package/dist/runtime/framework-error-class.table.d.ts +33 -0
- package/dist/runtime/framework-error-class.table.js +25 -0
- package/dist/runtime/framework-error.d.ts +68 -8
- package/dist/runtime/framework-error.factory.d.ts +4 -0
- package/dist/runtime/framework-error.factory.js +15 -0
- package/dist/runtime/framework-error.js +23 -2
- package/dist/runtime/interceptor-next.error.d.ts +5 -0
- package/dist/runtime/interceptor-next.error.js +6 -0
- package/dist/runtime/json-value.d.ts +11 -6
- package/dist/runtime/level-lifecycle.runtime.d.ts +25 -0
- package/dist/runtime/level-lifecycle.runtime.js +39 -0
- package/dist/runtime/misuse-reporter.d.ts +12 -12
- package/dist/runtime/mutation-planner.d.ts +19 -10
- package/dist/runtime/mutation-planner.js +48 -35
- package/dist/runtime/nested-write.walker.d.ts +32 -0
- package/dist/runtime/nested-write.walker.js +73 -0
- package/dist/runtime/operation-code.guidance.d.ts +6 -0
- package/dist/runtime/operation-code.guidance.js +167 -0
- package/dist/runtime/operation-request.d.ts +33 -6
- package/dist/runtime/operation-response.d.ts +17 -17
- package/dist/runtime/operation-runtime.d.ts +127 -25
- package/dist/runtime/operation-runtime.js +441 -78
- package/dist/runtime/outcome-mapper.d.ts +16 -5
- package/dist/runtime/outcome-mapper.js +23 -6
- package/dist/runtime/output-validator.d.ts +10 -10
- package/dist/runtime/output-validator.js +3 -3
- package/dist/runtime/owned-execution.registry.d.ts +16 -6
- package/dist/runtime/pipe-output.validator.d.ts +43 -18
- package/dist/runtime/pipe-output.validator.js +15 -13
- package/dist/runtime/pipeline.d.ts +223 -34
- package/dist/runtime/readonly-value.d.ts +43 -0
- package/dist/runtime/readonly-value.js +150 -0
- package/dist/runtime/request-envelope.validator.d.ts +3 -3
- package/dist/runtime/request-envelope.validator.js +12 -7
- package/dist/runtime/request-id.d.ts +1 -1
- package/dist/runtime/request-state.d.ts +14 -0
- package/dist/runtime/request-state.js +1 -0
- package/dist/runtime/request-state.resolver.d.ts +12 -0
- package/dist/runtime/request-state.resolver.js +20 -0
- package/dist/runtime/scalar-runtime.d.ts +35 -16
- package/dist/runtime/scalar-runtime.js +102 -34
- package/dist/runtime/unsent-value.normalizer.d.ts +15 -0
- package/dist/runtime/unsent-value.normalizer.js +128 -0
- package/dist/transactions/deferred-operation.d.ts +35 -20
- package/dist/transactions/operation-arguments-encoder.d.ts +3 -3
- package/dist/transactions/operation-arguments-encoder.js +2 -2
- package/dist/transactions/operation-fingerprint.js +2 -2
- package/dist/transactions/sql-statement.d.ts +85 -0
- package/dist/transactions/sql-statement.js +47 -0
- package/dist/transactions/sql-statement.validator.d.ts +44 -0
- package/dist/transactions/sql-statement.validator.js +167 -0
- package/dist/transactions/transaction-envelope.d.ts +4 -4
- package/dist/transactions/transaction-integrity-error.d.ts +6 -6
- package/dist/transactions/transaction-integrity-error.js +5 -2
- package/dist/transactions/transaction-plan-assembler.d.ts +21 -11
- package/dist/transactions/transaction-plan-assembler.js +169 -32
- package/dist/transactions/transaction-plan-validator.d.ts +45 -20
- package/dist/transactions/transaction-plan-validator.js +96 -38
- package/dist/transactions/transaction-reference-collector.d.ts +5 -5
- package/dist/transactions/transaction-reference-resolver.d.ts +19 -2
- package/dist/transactions/transaction-reference-resolver.js +26 -14
- package/dist/transactions/transaction-reference-validator.d.ts +76 -4
- package/dist/transactions/transaction-reference-validator.js +129 -71
- package/dist/transactions/transaction-reference.d.ts +13 -13
- package/dist/transactions/transaction-reference.js +5 -0
- package/dist/transactions/transaction-request.d.ts +18 -5
- package/dist/transactions/transaction-response.d.ts +6 -6
- package/dist/transactions/transaction-runtime.d.ts +8 -7
- package/dist/transactions/transaction-runtime.js +191 -50
- package/dist/transactions/transaction-settlement.runtime.d.ts +51 -0
- package/dist/transactions/transaction-settlement.runtime.js +120 -0
- package/dist/transactions/transaction-slot-path-error.d.ts +4 -4
- package/dist/transactions/transaction-slot-path-error.js +2 -2
- package/dist/transactions/transaction-slot-writer.js +2 -2
- package/dist/transactions/transaction-stage.runtime.d.ts +82 -0
- package/dist/transactions/transaction-stage.runtime.js +126 -0
- package/package.json +9 -7
- package/src/runtime/field-check.evaluator.ts +720 -0
- package/dist/config/define-fields.d.ts +0 -42
- package/dist/config/define-fields.js +0 -3
- package/dist/config/framework-behaviors-config.d.ts +0 -30
- package/dist/config/framework-fields-config.d.ts +0 -42
- package/dist/config/framework-restrictions-config.d.ts +0 -142
- package/dist/json/avz.d.ts +0 -96
- package/dist/runtime/field-behavior.d.ts +0 -48
- /package/dist/{config/framework-behaviors-config.js → adapters/adapter-sql-statement.js} +0 -0
- /package/dist/{config/framework-fields-config.js → adapters/adapter-transaction-executor.interface.js} +0 -0
- /package/dist/{json → avz}/index.js +0 -0
- /package/dist/{json → avz}/json-shape.registry.js +0 -0
- /package/dist/config/{framework-restrictions-config.js → exact-framework-config.js} +0 -0
- /package/dist/{runtime/field-behavior.js → config/field-pipeline-config.js} +0 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { bindShapedField, jsonShapeOf } from "../avz/json-shape.registry.js";
|
|
2
|
+
import { FILTER_MODES, PROJECTION_SCOPES, WRITE_SCOPES, } from "../contracts/capability.js";
|
|
2
3
|
import { validateClientContractStructure } from "../contracts/client-contract-structure.validator.js";
|
|
3
|
-
import { canonicalizeContract } from "../contracts/contract-canonicalizer.js";
|
|
4
|
-
import {
|
|
4
|
+
import { canonicalizeContract, canonicalizeContractMembers, canonicalizeJson, joinCanonicalContractMembers, recordCanonicalContractText, } from "../contracts/contract-canonicalizer.js";
|
|
5
|
+
import { hashCanonicalContractText, } from "../contracts/contract-hash.js";
|
|
5
6
|
import { DEFAULT_CONTRACT_LIMITS, } from "../contracts/contract-limits.js";
|
|
6
7
|
import { AVENTARA_PROTOCOL_VERSION } from "../contracts/contract-protocol.js";
|
|
7
|
-
import { advertisedDirectives,
|
|
8
|
+
import { advertisedDirectives, advertisedReducers, holdsInAnyScope, } from "../contracts/field.js";
|
|
8
9
|
import { isOperationVariantAvailable, } from "../contracts/resource-operations.js";
|
|
9
|
-
import {
|
|
10
|
+
import { checkCreateRequirements, checkHiddenRequirements, projectCreateRequirements, } from "./create-requirements.projector.js";
|
|
11
|
+
import { applyValidators, bindFieldValidators, checkLayerValidators, compileValidatorTable, } from "./field-validator.compiler.js";
|
|
10
12
|
const UNIQUE_VARIANTS = [
|
|
11
13
|
["find", "unique"],
|
|
12
14
|
["update", "unique"],
|
|
@@ -39,6 +41,12 @@ function normalizeAdapterScalarCapabilities(capabilities) {
|
|
|
39
41
|
...(capabilities.filter !== undefined
|
|
40
42
|
? { filter: [...capabilities.filter] }
|
|
41
43
|
: {}),
|
|
44
|
+
...(capabilities.filterModes !== undefined
|
|
45
|
+
? { filterModes: [...capabilities.filterModes] }
|
|
46
|
+
: {}),
|
|
47
|
+
...(capabilities.pathFilterModes !== undefined
|
|
48
|
+
? { pathFilterModes: [...capabilities.pathFilterModes] }
|
|
49
|
+
: {}),
|
|
42
50
|
...(capabilities.order !== undefined ? { order: capabilities.order } : {}),
|
|
43
51
|
...(capabilities.orderNulls !== undefined
|
|
44
52
|
? { orderNulls: capabilities.orderNulls }
|
|
@@ -69,8 +77,42 @@ function cloneAdapterField(field) {
|
|
|
69
77
|
cardinality: field.cardinality,
|
|
70
78
|
nullable: field.nullable,
|
|
71
79
|
capabilities: cloneValue(field.capabilities),
|
|
80
|
+
...(typeof field.inverse === "string" ? { inverse: field.inverse } : {}),
|
|
81
|
+
...(Array.isArray(field.referenceFields) && field.referenceFields.length > 0
|
|
82
|
+
? { referenceFields: [...field.referenceFields] }
|
|
83
|
+
: {}),
|
|
72
84
|
};
|
|
73
85
|
}
|
|
86
|
+
function pruneHiddenPairings(projection) {
|
|
87
|
+
const resources = projection.resources;
|
|
88
|
+
for (const [name, resource] of Object.entries(resources)) {
|
|
89
|
+
for (const field of Object.values(resource.fields)) {
|
|
90
|
+
if (field.kind !== "relation") {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (Array.isArray(field.referenceFields)) {
|
|
94
|
+
const shown = field.referenceFields.filter((reference) => typeof reference === "string" &&
|
|
95
|
+
Object.hasOwn(resource.fields, reference) &&
|
|
96
|
+
resource.fields[reference]?.kind === "scalar");
|
|
97
|
+
if (shown.length > 0) {
|
|
98
|
+
field.referenceFields = shown;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
delete field.referenceFields;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (typeof field.inverse !== "string") {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const other = Object.hasOwn(resources, String(field.target))
|
|
108
|
+
? resources[String(field.target)]?.fields[field.inverse]
|
|
109
|
+
: undefined;
|
|
110
|
+
if (other?.kind !== "relation" || other.target !== name) {
|
|
111
|
+
delete field.inverse;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
74
116
|
function cloneOperations(operations) {
|
|
75
117
|
return cloneValue(operations);
|
|
76
118
|
}
|
|
@@ -84,8 +126,40 @@ function cloneAdapterResource(resource) {
|
|
|
84
126
|
operations: cloneOperations(resource.operations),
|
|
85
127
|
};
|
|
86
128
|
}
|
|
129
|
+
function canonicalModes(modes) {
|
|
130
|
+
const stated = Array.isArray(modes) ? modes : [];
|
|
131
|
+
const known = FILTER_MODES.filter((mode) => mode === "default" || stated.includes(mode));
|
|
132
|
+
const unknown = stated.filter((mode) => !FILTER_MODES.includes(mode));
|
|
133
|
+
return [...known, ...unknown];
|
|
134
|
+
}
|
|
135
|
+
function qualifyFilterModes(projection) {
|
|
136
|
+
for (const resource of Object.values(projection.resources)) {
|
|
137
|
+
for (const field of Object.values(resource.fields)) {
|
|
138
|
+
if (field.kind !== "scalar") {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const capabilities = field.capabilities;
|
|
142
|
+
const filter = Array.isArray(capabilities.filter)
|
|
143
|
+
? capabilities.filter
|
|
144
|
+
: [];
|
|
145
|
+
if (!filter.includes("mode")) {
|
|
146
|
+
delete capabilities.filterModes;
|
|
147
|
+
delete capabilities.pathFilterModes;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const stated = capabilities.filterModes;
|
|
151
|
+
capabilities.filterModes = canonicalModes(stated);
|
|
152
|
+
if (filter.includes("path")) {
|
|
153
|
+
capabilities.pathFilterModes = canonicalModes(capabilities.pathFilterModes ?? stated);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
delete capabilities.pathFilterModes;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
87
161
|
function createBaseline(model) {
|
|
88
|
-
|
|
162
|
+
const baseline = {
|
|
89
163
|
scalars: Object.fromEntries(Object.entries(model.scalars).map(([key, value]) => [
|
|
90
164
|
key,
|
|
91
165
|
cloneValue(value),
|
|
@@ -104,9 +178,11 @@ function createBaseline(model) {
|
|
|
104
178
|
? {}
|
|
105
179
|
: { referenceWrites: model.referenceWrites }),
|
|
106
180
|
};
|
|
181
|
+
qualifyFilterModes(baseline);
|
|
182
|
+
return baseline;
|
|
107
183
|
}
|
|
108
|
-
function
|
|
109
|
-
return
|
|
184
|
+
function definitionPath(resource, field) {
|
|
185
|
+
return `root.resources.${resource}.fields.${field}.definition`;
|
|
110
186
|
}
|
|
111
187
|
function structuralValueMatches(field, config, key) {
|
|
112
188
|
if (config[key] === undefined) {
|
|
@@ -114,6 +190,13 @@ function structuralValueMatches(field, config, key) {
|
|
|
114
190
|
}
|
|
115
191
|
return JSON.stringify(field[key]) === JSON.stringify(config[key]);
|
|
116
192
|
}
|
|
193
|
+
function configuredMember(capabilities, key) {
|
|
194
|
+
const value = capabilities?.[key];
|
|
195
|
+
if (value === undefined) {
|
|
196
|
+
return {};
|
|
197
|
+
}
|
|
198
|
+
return { [key]: Array.isArray(value) ? [...value] : cloneValue(value) };
|
|
199
|
+
}
|
|
117
200
|
function addConfiguredScalarField(config, path, diagnostics) {
|
|
118
201
|
if (config.json !== undefined) {
|
|
119
202
|
refuseJsonShape(path, "is not a field of the adapter model", diagnostics);
|
|
@@ -128,7 +211,7 @@ function addConfiguredScalarField(config, path, diagnostics) {
|
|
|
128
211
|
if (configuredLifecycle === undefined ||
|
|
129
212
|
!configuredLifecycle.includes("VIRTUAL") ||
|
|
130
213
|
configuredLifecycle.some((trait) => trait !== "VIRTUAL")) {
|
|
131
|
-
diagnostics.push(diagnostic("COMPILER_INVALID_LIFECYCLE", `${path}.lifecycle`, "framework-added scalar field must declare only VIRTUAL lifecycle; generated lifecycle comes from adapters and computed lifecycle comes from
|
|
214
|
+
diagnostics.push(diagnostic("COMPILER_INVALID_LIFECYCLE", `${path}.lifecycle`, "framework-added scalar field must declare only VIRTUAL lifecycle; generated lifecycle comes from adapters and computed lifecycle comes from compute"));
|
|
132
215
|
return undefined;
|
|
133
216
|
}
|
|
134
217
|
return {
|
|
@@ -140,89 +223,14 @@ function addConfiguredScalarField(config, path, diagnostics) {
|
|
|
140
223
|
lifecycle: [...(config.lifecycle ?? [])],
|
|
141
224
|
capabilities: {
|
|
142
225
|
select: config.capabilities?.select ?? true,
|
|
143
|
-
...(config.capabilities
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
...(config.capabilities
|
|
147
|
-
|
|
148
|
-
: {}),
|
|
149
|
-
...(config.capabilities?.orderNulls !== undefined
|
|
150
|
-
? { orderNulls: config.capabilities.orderNulls }
|
|
151
|
-
: {}),
|
|
152
|
-
...(config.capabilities?.create !== undefined
|
|
153
|
-
? { create: [...config.capabilities.create] }
|
|
154
|
-
: {}),
|
|
155
|
-
...(config.capabilities?.update !== undefined
|
|
156
|
-
? { update: [...config.capabilities.update] }
|
|
157
|
-
: {}),
|
|
226
|
+
...configuredMember(config.capabilities, "filter"),
|
|
227
|
+
...configuredMember(config.capabilities, "order"),
|
|
228
|
+
...configuredMember(config.capabilities, "orderNulls"),
|
|
229
|
+
...configuredMember(config.capabilities, "create"),
|
|
230
|
+
...configuredMember(config.capabilities, "update"),
|
|
158
231
|
},
|
|
159
232
|
};
|
|
160
233
|
}
|
|
161
|
-
export function capabilityAddsBaseline(fieldKind, baseline, key, configured) {
|
|
162
|
-
const axis = capabilityAxisFor(fieldKind, key);
|
|
163
|
-
if (axis === undefined) {
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
166
|
-
const baselineValue = baseline[key];
|
|
167
|
-
switch (axis.kind) {
|
|
168
|
-
case "limit":
|
|
169
|
-
return baselineValue !== undefined && configured !== baselineValue;
|
|
170
|
-
case "flag":
|
|
171
|
-
return configured === true && baselineValue !== true;
|
|
172
|
-
case "tokens": {
|
|
173
|
-
if (!isTokenList(configured)) {
|
|
174
|
-
return false;
|
|
175
|
-
}
|
|
176
|
-
const advertised = isTokenList(baselineValue) ? baselineValue : [];
|
|
177
|
-
return configured.some((token) => !advertised.includes(token));
|
|
178
|
-
}
|
|
179
|
-
case "scopes": {
|
|
180
|
-
const claimed = configured === true
|
|
181
|
-
? axis.axis
|
|
182
|
-
: isTokenList(configured)
|
|
183
|
-
? configured
|
|
184
|
-
: undefined;
|
|
185
|
-
return (claimed?.some((position) => axis.axis.includes(position) &&
|
|
186
|
-
!holdsAt(baselineValue, position)) === true);
|
|
187
|
-
}
|
|
188
|
-
case "scopedTokens": {
|
|
189
|
-
const claim = scopedTokenClaim(axis, configured);
|
|
190
|
-
if (claim === undefined) {
|
|
191
|
-
return false;
|
|
192
|
-
}
|
|
193
|
-
return claim.positions.some((position) => {
|
|
194
|
-
if (!axis.axis.includes(position)) {
|
|
195
|
-
return false;
|
|
196
|
-
}
|
|
197
|
-
const advertised = advertisedTokensAt(axis.tokenKey, baselineValue, position);
|
|
198
|
-
return (advertised === undefined ||
|
|
199
|
-
claim.tokens.some((token) => !advertised.includes(token)));
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
function scopedTokenClaim(axis, configured) {
|
|
205
|
-
if (isTokenList(configured)) {
|
|
206
|
-
return { tokens: configured, positions: axis.axis };
|
|
207
|
-
}
|
|
208
|
-
if (isRecord(configured) &&
|
|
209
|
-
isTokenList(configured[axis.tokenKey]) &&
|
|
210
|
-
isTokenList(configured.scopes)) {
|
|
211
|
-
return {
|
|
212
|
-
tokens: configured[axis.tokenKey],
|
|
213
|
-
positions: configured.scopes,
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
return undefined;
|
|
217
|
-
}
|
|
218
|
-
function advertisedTokensAt(tokenKey, baselineValue, position) {
|
|
219
|
-
if (!isTokenList(baselineValue) && !isRecord(baselineValue)) {
|
|
220
|
-
return undefined;
|
|
221
|
-
}
|
|
222
|
-
return tokenKey === "directives"
|
|
223
|
-
? advertisedDirectivesAt(baselineValue, position)
|
|
224
|
-
: advertisedReducersAt(baselineValue, position);
|
|
225
|
-
}
|
|
226
234
|
function fieldLabel(path) {
|
|
227
235
|
const [, resource, , field] = path.split(".").slice(1);
|
|
228
236
|
return `${resource}.${field}`;
|
|
@@ -238,7 +246,7 @@ function applyJsonShape(field, baselineField, shape, path, diagnostics) {
|
|
|
238
246
|
}
|
|
239
247
|
const runtime = jsonShapeOf(shape);
|
|
240
248
|
if (runtime === undefined) {
|
|
241
|
-
diagnostics.push(diagnostic("COMPILER_INVALID_JSON_SHAPE", `${path}.json`, `field "${fieldLabel(path)}": "json" must be a shape built with AvZ from "@aventara/core/
|
|
249
|
+
diagnostics.push(diagnostic("COMPILER_INVALID_JSON_SHAPE", `${path}.json`, `field "${fieldLabel(path)}": "json" must be a shape built with AvZ from "@aventara/core/avz"; a Zod schema or any other value is not accepted`));
|
|
242
250
|
return;
|
|
243
251
|
}
|
|
244
252
|
if (runtime.validate(null).length === 0 ||
|
|
@@ -255,7 +263,7 @@ function applyExistingScalarConfig(field, baselineField, config, path, diagnosti
|
|
|
255
263
|
}
|
|
256
264
|
}
|
|
257
265
|
if (config.format !== undefined) {
|
|
258
|
-
|
|
266
|
+
diagnostics.push(diagnostic("COMPILER_STRUCTURAL_FIELD_OVERRIDE", `${path}.format`, "a discovered field's format is the adapter's and cannot be set; format is for configured (VIRTUAL) fields"));
|
|
259
267
|
}
|
|
260
268
|
if (config.json !== undefined) {
|
|
261
269
|
applyJsonShape(field, baselineField, config.json, path, diagnostics);
|
|
@@ -264,95 +272,39 @@ function applyExistingScalarConfig(field, baselineField, config, path, diagnosti
|
|
|
264
272
|
diagnostics.push(diagnostic("COMPILER_INVALID_LIFECYCLE", `${path}.lifecycle`, "existing adapter field lifecycle cannot be redefined by framework field configuration"));
|
|
265
273
|
}
|
|
266
274
|
if (config.capabilities !== undefined) {
|
|
267
|
-
|
|
268
|
-
const baseline = baselineField.capabilities;
|
|
269
|
-
for (const [key, value] of Object.entries(config.capabilities)) {
|
|
270
|
-
if (capabilityAddsBaseline(baselineField.kind, baseline, key, value)) {
|
|
271
|
-
diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_CAPABILITY", `${path}.capabilities.${key}`, `capability "${key}" is not available in the canonical baseline`));
|
|
272
|
-
continue;
|
|
273
|
-
}
|
|
274
|
-
current[key] = cloneValue(value);
|
|
275
|
-
}
|
|
275
|
+
diagnostics.push(diagnostic("COMPILER_STRUCTURAL_FIELD_OVERRIDE", `${path}.capabilities`, "a discovered field's capabilities are the adapter's and cannot be set; narrow what it exposes with exemptions"));
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
|
-
function
|
|
279
|
-
for (const key of ["type", "nullable", "list"]) {
|
|
280
|
-
if (config[key] !== undefined) {
|
|
281
|
-
field[key] = cloneValue(config[key]);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
if (config.json !== undefined) {
|
|
285
|
-
refuseJsonShape(path, "is not a field of the adapter model", diagnostics);
|
|
286
|
-
}
|
|
287
|
-
if (config.format !== undefined) {
|
|
288
|
-
field.format = config.format;
|
|
289
|
-
}
|
|
290
|
-
if (config.lifecycle !== undefined) {
|
|
291
|
-
const configuredLifecycle = config.lifecycle;
|
|
292
|
-
if (!configuredLifecycle.includes("VIRTUAL") ||
|
|
293
|
-
configuredLifecycle.some((trait) => trait !== "VIRTUAL")) {
|
|
294
|
-
diagnostics.push(diagnostic("COMPILER_INVALID_LIFECYCLE", `${path}.lifecycle`, "framework-owned scalar field may declare only VIRTUAL lifecycle; computed lifecycle comes from behaviors"));
|
|
295
|
-
}
|
|
296
|
-
else {
|
|
297
|
-
field.lifecycle = [...configuredLifecycle];
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
if (config.capabilities !== undefined) {
|
|
301
|
-
const current = field.capabilities;
|
|
302
|
-
for (const [key, value] of Object.entries(config.capabilities)) {
|
|
303
|
-
current[key] = cloneValue(value);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
function applyFieldConfig(model, projection, scope, config, diagnostics, allowRestrictedMissing = false) {
|
|
308
|
-
if (config === undefined) {
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
278
|
+
function applyFieldDefinitions(model, projection, definitions, diagnostics) {
|
|
311
279
|
const providerBaseline = createBaseline(model);
|
|
312
|
-
for (const [resourceName,
|
|
313
|
-
if (resourceConfig === undefined) {
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
280
|
+
for (const [resourceName, resourceDefinitions] of Object.entries(definitions)) {
|
|
316
281
|
const resource = projection.resources[resourceName];
|
|
317
282
|
const providerResource = providerBaseline.resources[resourceName];
|
|
318
283
|
if (resource === undefined || providerResource === undefined) {
|
|
319
|
-
if (allowRestrictedMissing && providerResource !== undefined) {
|
|
320
|
-
continue;
|
|
321
|
-
}
|
|
322
|
-
diagnostics.push(diagnostic("COMPILER_UNKNOWN_RESOURCE", `${scope}.resources.${resourceName}`, `resource "${resourceName}" is not present in the canonical adapter model`));
|
|
323
284
|
continue;
|
|
324
285
|
}
|
|
325
|
-
for (const [fieldName,
|
|
326
|
-
const
|
|
327
|
-
const path = scalarFieldPath(scope, resourceName, fieldName);
|
|
286
|
+
for (const [fieldName, definition] of Object.entries(resourceDefinitions)) {
|
|
287
|
+
const path = definitionPath(resourceName, fieldName);
|
|
328
288
|
const existing = resource.fields[fieldName];
|
|
329
289
|
const providerField = providerResource.fields[fieldName];
|
|
330
290
|
if (existing === undefined) {
|
|
331
|
-
|
|
332
|
-
continue;
|
|
333
|
-
}
|
|
334
|
-
const added = addConfiguredScalarField(fieldConfig, path, diagnostics);
|
|
291
|
+
const added = addConfiguredScalarField(definition, path, diagnostics);
|
|
335
292
|
if (added !== undefined) {
|
|
336
293
|
resource.fields[fieldName] = added;
|
|
337
294
|
}
|
|
338
295
|
continue;
|
|
339
296
|
}
|
|
340
297
|
if (existing.kind === "relation") {
|
|
341
|
-
diagnostics.push(diagnostic("COMPILER_RELATION_FIELD_CONFIG_UNSUPPORTED", path, "
|
|
298
|
+
diagnostics.push(diagnostic("COMPILER_RELATION_FIELD_CONFIG_UNSUPPORTED", path, "a relation field takes no definition"));
|
|
342
299
|
continue;
|
|
343
300
|
}
|
|
344
301
|
if (providerField?.kind === "scalar") {
|
|
345
|
-
applyExistingScalarConfig(existing, providerField,
|
|
346
|
-
continue;
|
|
302
|
+
applyExistingScalarConfig(existing, providerField, definition, path, diagnostics);
|
|
347
303
|
}
|
|
348
|
-
applyFrameworkOwnedScalarConfig(existing, fieldConfig, path, diagnostics);
|
|
349
304
|
}
|
|
350
305
|
}
|
|
351
306
|
}
|
|
352
|
-
function
|
|
353
|
-
return value !== undefined && value !== false;
|
|
354
|
-
}
|
|
355
|
-
function readBehaviorDependencies(value) {
|
|
307
|
+
function readComputeDependencies(value) {
|
|
356
308
|
if (!isRecord(value) || typeof value.compute !== "function") {
|
|
357
309
|
return undefined;
|
|
358
310
|
}
|
|
@@ -360,60 +312,125 @@ function readBehaviorDependencies(value) {
|
|
|
360
312
|
? value.dependsOn.filter((entry) => typeof entry === "string")
|
|
361
313
|
: undefined;
|
|
362
314
|
}
|
|
363
|
-
function
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
if (
|
|
367
|
-
|
|
315
|
+
function applyCompute(projection, compute) {
|
|
316
|
+
for (const [resourceName, fields] of Object.entries(compute)) {
|
|
317
|
+
const resource = projection.resources[resourceName];
|
|
318
|
+
if (resource === undefined) {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
for (const [fieldName, fieldCompute] of Object.entries(fields)) {
|
|
322
|
+
const field = resource.fields[fieldName];
|
|
323
|
+
if (field?.kind !== "scalar") {
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
const lifecycle = field.lifecycle.filter((trait) => !COMPUTED_TRAITS.has(trait));
|
|
327
|
+
if (fieldCompute.create !== undefined) {
|
|
328
|
+
lifecycle.push("COMPUTED_ON_CREATE");
|
|
329
|
+
}
|
|
330
|
+
if (fieldCompute.update !== undefined) {
|
|
331
|
+
lifecycle.push("COMPUTED_ON_UPDATE");
|
|
332
|
+
}
|
|
333
|
+
if (fieldCompute.read !== undefined) {
|
|
334
|
+
lifecycle.push("COMPUTED_ON_READ");
|
|
335
|
+
}
|
|
336
|
+
field.lifecycle = lifecycle;
|
|
368
337
|
}
|
|
369
|
-
diagnostics.push(diagnostic("COMPILER_UNKNOWN_FIELD", path, `behavior field "${fieldName}" must resolve to a scalar field`));
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
const lifecycle = field.lifecycle.filter((trait) => !COMPUTED_TRAITS.has(trait));
|
|
373
|
-
if (isActiveBehaviorStage(behavior.create)) {
|
|
374
|
-
lifecycle.push("COMPUTED_ON_CREATE");
|
|
375
338
|
}
|
|
376
|
-
|
|
377
|
-
|
|
339
|
+
}
|
|
340
|
+
function storedScalar(model, resource, field) {
|
|
341
|
+
const fields = Object.hasOwn(model.resources, resource)
|
|
342
|
+
? model.resources[resource]?.fields
|
|
343
|
+
: undefined;
|
|
344
|
+
return (fields !== undefined &&
|
|
345
|
+
Object.hasOwn(fields, field) &&
|
|
346
|
+
fields[field]?.kind === "scalar");
|
|
347
|
+
}
|
|
348
|
+
function checkFieldCompute(model, resourceName, fieldName, field, compute, path, diagnostics) {
|
|
349
|
+
if (!isRecord(compute)) {
|
|
350
|
+
return;
|
|
378
351
|
}
|
|
379
|
-
if (
|
|
380
|
-
|
|
352
|
+
if (field?.kind === "relation") {
|
|
353
|
+
diagnostics.push(diagnostic("COMPILER_RELATION_FIELD_CONFIG_UNSUPPORTED", path, `relation field "${fieldName}" takes write guards only; compute derives a scalar field's value`));
|
|
354
|
+
return;
|
|
381
355
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
if (dependencyField === undefined || dependencyField.kind !== "scalar") {
|
|
386
|
-
diagnostics.push(diagnostic("COMPILER_INVALID_BEHAVIOR_DEPENDENCY", `${path}.read.dependsOn`, `read behavior dependency "${dependency}" must reference a scalar field in the same Resource`));
|
|
356
|
+
for (const dependency of readComputeDependencies(compute.read) ?? []) {
|
|
357
|
+
if (!storedScalar(model, resourceName, dependency)) {
|
|
358
|
+
diagnostics.push(diagnostic("COMPILER_INVALID_COMPUTE_DEPENDENCY", `${path}.read.dependsOn`, `compute.read dependency "${dependency}" must name a scalar field the adapter stores on resource "${resourceName}"`));
|
|
387
359
|
}
|
|
388
360
|
}
|
|
389
361
|
}
|
|
390
|
-
function
|
|
391
|
-
if (
|
|
362
|
+
function checkResourceTree(model, baseline, scope, tree, definitions, diagnostics) {
|
|
363
|
+
if (!isRecord(tree)) {
|
|
392
364
|
return;
|
|
393
365
|
}
|
|
394
|
-
for (const [resourceName,
|
|
395
|
-
|
|
366
|
+
for (const [resourceName, entry] of Object.entries(tree)) {
|
|
367
|
+
const resourcePath = `${scope}.resources.${resourceName}`;
|
|
368
|
+
const resource = baseline.resources[resourceName];
|
|
369
|
+
if (resource === undefined) {
|
|
370
|
+
diagnostics.push(diagnostic("COMPILER_UNKNOWN_RESOURCE", resourcePath, `resource "${resourceName}" is not present in the canonical adapter model`));
|
|
396
371
|
continue;
|
|
397
372
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
373
|
+
if (!isRecord(entry)) {
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
if (isRecord(entry.operations)) {
|
|
377
|
+
for (const [family, level] of Object.entries(entry.operations)) {
|
|
378
|
+
const familyPath = `${resourcePath}.operations.${family}`;
|
|
379
|
+
const variants = resource.operations[family];
|
|
380
|
+
if (!isRecord(variants)) {
|
|
381
|
+
diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_OPERATION", familyPath, `operation family "${family}" is not available for resource "${resourceName}"`));
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
if (!isRecord(level)) {
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
for (const variant of Object.keys(level)) {
|
|
388
|
+
if (variant === "pipelines" || variant === "forceTransaction") {
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
if (!isOperationVariantAvailable(variants[variant])) {
|
|
392
|
+
diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_OPERATION", `${familyPath}.${variant}`, `operation "${family}.${variant}" is not available for resource "${resourceName}"`));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
402
395
|
}
|
|
403
|
-
|
|
396
|
+
}
|
|
397
|
+
if (!isRecord(entry.fields)) {
|
|
404
398
|
continue;
|
|
405
399
|
}
|
|
406
|
-
for (const [fieldName,
|
|
407
|
-
|
|
400
|
+
for (const [fieldName, fieldEntry] of Object.entries(entry.fields)) {
|
|
401
|
+
const field = resource.fields[fieldName];
|
|
402
|
+
if (field === undefined &&
|
|
403
|
+
definitions[resourceName]?.[fieldName] === undefined) {
|
|
404
|
+
diagnostics.push(diagnostic("COMPILER_UNKNOWN_FIELD", `${resourcePath}.fields.${fieldName}`, `field "${fieldName}" is neither discovered by the adapter nor defined at the root`));
|
|
408
405
|
continue;
|
|
409
406
|
}
|
|
410
|
-
|
|
407
|
+
if (!isRecord(fieldEntry) || !isRecord(fieldEntry.pipelines)) {
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
const pipelinesPath = `${resourcePath}.fields.${fieldName}.pipelines`;
|
|
411
|
+
if (field?.kind === "relation" &&
|
|
412
|
+
fieldEntry.pipelines.pipes !== undefined) {
|
|
413
|
+
diagnostics.push(diagnostic("COMPILER_RELATION_FIELD_CONFIG_UNSUPPORTED", `${pipelinesPath}.pipes`, `relation field "${fieldName}" takes write guards only; a field pipe transforms a scalar field's value`));
|
|
414
|
+
}
|
|
415
|
+
checkFieldCompute(model, resourceName, fieldName, field, fieldEntry.pipelines.compute, `${pipelinesPath}.compute`, diagnostics);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
function checkReservedResourceNames(model, diagnostics) {
|
|
420
|
+
for (const resourceName of Object.keys(model.resources)) {
|
|
421
|
+
if (resourceName.startsWith("$")) {
|
|
422
|
+
diagnostics.push(diagnostic("COMPILER_RESERVED_RESOURCE_NAME", `adapter.model.resources.${resourceName}`, `resource "${resourceName}" starts with "$", which is reserved for the framework's own names`));
|
|
411
423
|
}
|
|
412
424
|
}
|
|
413
425
|
}
|
|
426
|
+
function isAdapterStated(axis) {
|
|
427
|
+
return axis?.kind === "limit" || axis?.kind === "adapterTokens";
|
|
428
|
+
}
|
|
414
429
|
const SCALAR_CAPABILITY_AXES = {
|
|
415
430
|
select: { kind: "flag" },
|
|
416
431
|
filter: { kind: "tokens" },
|
|
432
|
+
filterModes: { kind: "adapterTokens", values: FILTER_MODES },
|
|
433
|
+
pathFilterModes: { kind: "adapterTokens", values: FILTER_MODES },
|
|
417
434
|
order: { kind: "flag" },
|
|
418
435
|
orderNulls: { kind: "flag" },
|
|
419
436
|
create: { kind: "tokens" },
|
|
@@ -441,226 +458,366 @@ function capabilityAxisFor(kind, key) {
|
|
|
441
458
|
function unavailableCapability(path, message, diagnostics) {
|
|
442
459
|
diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_CAPABILITY", path, message));
|
|
443
460
|
}
|
|
444
|
-
function
|
|
445
|
-
|
|
446
|
-
if (unavailable.length > 0) {
|
|
447
|
-
unavailableCapability(path, `capability "${key}" requested unavailable values: ${unavailable.join(", ")}`, diagnostics);
|
|
448
|
-
return undefined;
|
|
449
|
-
}
|
|
450
|
-
return [...requested];
|
|
461
|
+
function otherOverlay(layer) {
|
|
462
|
+
return layer === "client" ? "application" : "client";
|
|
451
463
|
}
|
|
452
|
-
function
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
464
|
+
function widening(walk, code, path, what) {
|
|
465
|
+
walk.diagnostics.push(diagnostic(code, path, walk.layer === "agent"
|
|
466
|
+
? `${what} is hidden by ${walk.parent ?? "client"}, the layer agent extends, and the agent layer only narrows that view; expose it in ${walk.parent ?? "client"}, or extend a layer that exposes it`
|
|
467
|
+
: `${what} is hidden by the root, and an overlay only narrows the root's view; expose it at the root and hide it in ${otherOverlay(walk.layer)} instead`));
|
|
468
|
+
}
|
|
469
|
+
function exemptsWhole(entry) {
|
|
470
|
+
return entry === true;
|
|
471
|
+
}
|
|
472
|
+
function exemptsAnything(entry) {
|
|
473
|
+
return (entry === true ||
|
|
474
|
+
(Array.isArray(entry) && entry.length > 0) ||
|
|
475
|
+
isRecord(entry));
|
|
476
|
+
}
|
|
477
|
+
function listedNames(entry, path) {
|
|
478
|
+
if (!Array.isArray(entry)) {
|
|
479
|
+
return [];
|
|
460
480
|
}
|
|
461
|
-
return
|
|
481
|
+
return entry.flatMap((name, index) => typeof name === "string"
|
|
482
|
+
? [[name, `${path}[${index}]`]]
|
|
483
|
+
: []);
|
|
462
484
|
}
|
|
463
|
-
function
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
if (narrowing.outcome === "refused") {
|
|
467
|
-
return;
|
|
485
|
+
function memberOffered(axis, value) {
|
|
486
|
+
if (value === undefined || value === false) {
|
|
487
|
+
return false;
|
|
468
488
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
489
|
+
switch (axis.kind) {
|
|
490
|
+
case "tokens":
|
|
491
|
+
return Array.isArray(value);
|
|
492
|
+
case "scopedTokens":
|
|
493
|
+
return memberTokens(axis, value) !== undefined;
|
|
494
|
+
case "scopes":
|
|
495
|
+
return holdsInAnyScope(value);
|
|
496
|
+
default:
|
|
497
|
+
return true;
|
|
472
498
|
}
|
|
473
|
-
current[key] = narrowing.scopes;
|
|
474
499
|
}
|
|
475
|
-
function
|
|
476
|
-
|
|
477
|
-
? value
|
|
478
|
-
: isRecord(value)
|
|
479
|
-
? value[tokenKey]
|
|
480
|
-
: undefined;
|
|
481
|
-
const requestedScopes = isRecord(value) ? value.scopes : undefined;
|
|
482
|
-
if ((requestedTokens !== undefined && !Array.isArray(requestedTokens)) ||
|
|
483
|
-
(requestedScopes !== undefined && !Array.isArray(requestedScopes))) {
|
|
484
|
-
return false;
|
|
500
|
+
function memberTokens(axis, value) {
|
|
501
|
+
if (axis.kind === "tokens") {
|
|
502
|
+
return Array.isArray(value) ? value : undefined;
|
|
485
503
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
unavailableCapability(path, `capability "${key}" is not available in the canonical baseline`, diagnostics);
|
|
491
|
-
return true;
|
|
504
|
+
if (axis.kind === "scopedTokens") {
|
|
505
|
+
return axis.tokenKey === "directives"
|
|
506
|
+
? advertisedDirectives(value)
|
|
507
|
+
: advertisedReducers(value);
|
|
492
508
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
if (
|
|
497
|
-
|
|
509
|
+
return undefined;
|
|
510
|
+
}
|
|
511
|
+
function withTokens(axis, parentValue, tokens) {
|
|
512
|
+
if (axis.kind === "scopedTokens" &&
|
|
513
|
+
isRecord(parentValue) &&
|
|
514
|
+
Array.isArray(parentValue.scopes)) {
|
|
515
|
+
return {
|
|
516
|
+
[axis.tokenKey]: [...tokens],
|
|
517
|
+
scopes: [...parentValue.scopes],
|
|
518
|
+
};
|
|
498
519
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
520
|
+
return [...tokens];
|
|
521
|
+
}
|
|
522
|
+
function fieldName(resource, field) {
|
|
523
|
+
return `"${resource}.${field}"`;
|
|
524
|
+
}
|
|
525
|
+
function exemptableAxis(walk, universeField, capability, label, path) {
|
|
526
|
+
const axis = capabilityAxisFor(universeField.kind, capability);
|
|
527
|
+
if (axis === undefined || isAdapterStated(axis)) {
|
|
528
|
+
const names = Object.entries(universeField.kind === "scalar"
|
|
529
|
+
? SCALAR_CAPABILITY_AXES
|
|
530
|
+
: RELATION_CAPABILITY_AXES)
|
|
531
|
+
.filter(([, entry]) => !isAdapterStated(entry))
|
|
532
|
+
.map(([name]) => name);
|
|
533
|
+
unavailableCapability(path, `"${capability}" is not a capability a ${universeField.kind} field's exemption can name; it takes ${names.join(", ")}`, walk.diagnostics);
|
|
534
|
+
return undefined;
|
|
508
535
|
}
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
return true;
|
|
536
|
+
const capabilities = universeField.capabilities;
|
|
537
|
+
if (!memberOffered(axis, capabilities[capability])) {
|
|
538
|
+
unavailableCapability(path, `capability "${capability}" is not available for field ${label}: the adapter does not offer it`, walk.diagnostics);
|
|
539
|
+
return undefined;
|
|
514
540
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
541
|
+
return axis;
|
|
542
|
+
}
|
|
543
|
+
function offeredTokens(walk, axis, universeValue, listed, capability, label) {
|
|
544
|
+
const offered = memberTokens(axis, universeValue) ?? [];
|
|
545
|
+
const tokens = [];
|
|
546
|
+
for (const [token, tokenPath] of listed) {
|
|
547
|
+
if (!offered.includes(token)) {
|
|
548
|
+
unavailableCapability(tokenPath, `capability "${capability}" of field ${label} does not offer "${token}": the adapter does not offer it`, walk.diagnostics);
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
tokens.push(token);
|
|
518
552
|
}
|
|
519
|
-
|
|
520
|
-
return true;
|
|
553
|
+
return tokens;
|
|
521
554
|
}
|
|
522
|
-
function
|
|
523
|
-
if (
|
|
524
|
-
unavailableCapability(path, `capability "${key}" is an adapter limit and takes no restriction`, diagnostics);
|
|
555
|
+
function exemptCapability(walk, universeField, parentCapabilities, result, capability, entry, label, path) {
|
|
556
|
+
if (entry === undefined) {
|
|
525
557
|
return;
|
|
526
558
|
}
|
|
527
|
-
|
|
528
|
-
|
|
559
|
+
const axis = exemptableAxis(walk, universeField, capability, label, path);
|
|
560
|
+
if (axis === undefined || entry === false) {
|
|
529
561
|
return;
|
|
530
562
|
}
|
|
531
|
-
const
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
unavailableCapability(path, `capability "${key}" is not available in the canonical baseline`, diagnostics);
|
|
563
|
+
const isTokenAxis = axis.kind === "tokens" || axis.kind === "scopedTokens";
|
|
564
|
+
if (entry !== true && !(isTokenAxis && Array.isArray(entry))) {
|
|
565
|
+
unavailableCapability(path, isTokenAxis
|
|
566
|
+
? `capability "${capability}" takes true, false or a list of its tokens`
|
|
567
|
+
: `capability "${capability}" takes true or false`, walk.diagnostics);
|
|
537
568
|
return;
|
|
538
569
|
}
|
|
539
|
-
|
|
540
|
-
|
|
570
|
+
const universeValue = universeField.capabilities[capability];
|
|
571
|
+
const tokens = entry === true
|
|
572
|
+
? undefined
|
|
573
|
+
: offeredTokens(walk, axis, universeValue, listedNames(entry, path), capability, label);
|
|
574
|
+
if (tokens !== undefined && tokens.length !== entry.length) {
|
|
541
575
|
return;
|
|
542
576
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
577
|
+
const parentValue = parentCapabilities[capability];
|
|
578
|
+
const parentHas = memberOffered(axis, parentValue);
|
|
579
|
+
if (walk.style === "omit") {
|
|
580
|
+
if (!parentHas) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
if (tokens === undefined) {
|
|
584
|
+
delete result[capability];
|
|
585
|
+
return;
|
|
549
586
|
}
|
|
587
|
+
const kept = (memberTokens(axis, parentValue) ?? []).filter((token) => !tokens.includes(token));
|
|
588
|
+
result[capability] = withTokens(axis, parentValue, kept);
|
|
550
589
|
return;
|
|
551
590
|
}
|
|
552
|
-
if (
|
|
553
|
-
applyScopeRestriction(axis.axis, baselineValue, current, key, value, path, diagnostics);
|
|
591
|
+
if (tokens !== undefined && tokens.length === 0) {
|
|
554
592
|
return;
|
|
555
593
|
}
|
|
556
|
-
if (
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
}
|
|
594
|
+
if (!parentHas) {
|
|
595
|
+
widening(walk, "COMPILER_UNAVAILABLE_CAPABILITY", path, `capability "${capability}" of field ${label}`);
|
|
596
|
+
return;
|
|
560
597
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
function applyCapabilityRestrictions(inheritedField, restorationField, field, restrictions, path, diagnostics) {
|
|
564
|
-
const capabilityRestrictions = restrictions.capabilities;
|
|
565
|
-
if (capabilityRestrictions === undefined) {
|
|
598
|
+
if (tokens === undefined) {
|
|
599
|
+
result[capability] = cloneValue(parentValue);
|
|
566
600
|
return;
|
|
567
601
|
}
|
|
568
|
-
const
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
602
|
+
const parentTokens = memberTokens(axis, parentValue) ?? [];
|
|
603
|
+
const hidden = tokens.filter((token) => !parentTokens.includes(token));
|
|
604
|
+
if (hidden.length > 0) {
|
|
605
|
+
widening(walk, "COMPILER_UNAVAILABLE_CAPABILITY", path, `${hidden.map((token) => `"${token}"`).join(", ")} of capability "${capability}" of field ${label}`);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
result[capability] = withTokens(axis, parentValue, parentTokens.filter((token) => tokens.includes(token)));
|
|
609
|
+
}
|
|
610
|
+
function exemptField(walk, universeField, parentField, entry, label, path) {
|
|
611
|
+
const parentCapabilities = (parentField?.capabilities ?? {});
|
|
612
|
+
const result = walk.style === "omit" ? cloneValue(parentCapabilities) : {};
|
|
613
|
+
if (walk.style === "pick") {
|
|
614
|
+
for (const [key, value] of Object.entries(parentCapabilities)) {
|
|
615
|
+
if (isAdapterStated(capabilityAxisFor(universeField.kind, key))) {
|
|
616
|
+
result[key] = cloneValue(value);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
if (Array.isArray(entry)) {
|
|
621
|
+
for (const [capability, capabilityPath] of listedNames(entry, path)) {
|
|
622
|
+
exemptCapability(walk, universeField, parentCapabilities, result, capability, true, label, capabilityPath);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
else if (isRecord(entry)) {
|
|
626
|
+
for (const [capability, value] of Object.entries(entry)) {
|
|
627
|
+
exemptCapability(walk, universeField, parentCapabilities, result, capability, value, label, `${path}.${capability}`);
|
|
574
628
|
}
|
|
575
|
-
applyCapabilityRestriction(restorationField.kind, inherited, restoration, current, key, value, `${path}.capabilities.${key}`, diagnostics);
|
|
576
629
|
}
|
|
630
|
+
if (parentField === undefined) {
|
|
631
|
+
return undefined;
|
|
632
|
+
}
|
|
633
|
+
if (walk.style === "pick" &&
|
|
634
|
+
!Object.keys(result).some((key) => !isAdapterStated(capabilityAxisFor(universeField.kind, key)))) {
|
|
635
|
+
return undefined;
|
|
636
|
+
}
|
|
637
|
+
return { ...cloneValue(parentField), capabilities: result };
|
|
577
638
|
}
|
|
578
|
-
function
|
|
579
|
-
|
|
580
|
-
|
|
639
|
+
function exemptFields(walk, resourceName, universe, parent, entries, path) {
|
|
640
|
+
const named = isRecord(entries) ? entries : {};
|
|
641
|
+
for (const fieldName of Object.keys(named)) {
|
|
642
|
+
if (universe.fields[fieldName] === undefined) {
|
|
643
|
+
walk.diagnostics.push(diagnostic("COMPILER_UNKNOWN_FIELD", `${path}.${fieldName}`, `field "${fieldName}" is neither discovered by the adapter nor defined at the root`));
|
|
644
|
+
}
|
|
581
645
|
}
|
|
582
|
-
|
|
583
|
-
|
|
646
|
+
const result = {};
|
|
647
|
+
for (const [name, entry] of Object.entries(named)) {
|
|
648
|
+
const universeField = universe.fields[name];
|
|
649
|
+
if (universeField === undefined) {
|
|
584
650
|
continue;
|
|
585
651
|
}
|
|
586
|
-
const
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
652
|
+
const parentField = parent.fields[name];
|
|
653
|
+
const label = fieldName(resourceName, name);
|
|
654
|
+
const fieldPath = `${path}.${name}`;
|
|
655
|
+
if (walk.style === "pick" &&
|
|
656
|
+
exemptsAnything(entry) &&
|
|
657
|
+
parentField === undefined) {
|
|
658
|
+
widening(walk, "COMPILER_UNAVAILABLE_CAPABILITY", fieldPath, `field ${label}`);
|
|
659
|
+
continue;
|
|
591
660
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
661
|
+
if (exemptsWhole(entry) || !exemptsAnything(entry)) {
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
const exempted = exemptField(walk, universeField, parentField, entry, label, fieldPath);
|
|
665
|
+
if (exempted !== undefined) {
|
|
666
|
+
result[name] = exempted;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
for (const [name, parentField] of Object.entries(parent.fields)) {
|
|
670
|
+
if (Object.hasOwn(result, name)) {
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
const entry = Object.hasOwn(named, name) ? named[name] : undefined;
|
|
674
|
+
if (walk.style === "omit" ? !exemptsWhole(entry) : exemptsWhole(entry)) {
|
|
675
|
+
result[name] = cloneValue(parentField);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
return result;
|
|
679
|
+
}
|
|
680
|
+
function exemptVariants(walk, resourceName, family, universeFamily, parentFamily, entry, path) {
|
|
681
|
+
if (entry === true) {
|
|
682
|
+
return "all";
|
|
683
|
+
}
|
|
684
|
+
const named = Array.isArray(entry)
|
|
685
|
+
? [...listedNames(entry, path)]
|
|
686
|
+
: isRecord(entry)
|
|
687
|
+
? Object.keys(entry).map((variant) => [variant, `${path}.${variant}`])
|
|
688
|
+
: [];
|
|
689
|
+
const variants = [];
|
|
690
|
+
for (const [variant, variantPath] of named) {
|
|
691
|
+
if (!isOperationVariantAvailable(universeFamily[variant])) {
|
|
692
|
+
walk.diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_OPERATION", variantPath, `operation "${family}.${variant}" is not available for resource "${resourceName}"`));
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
if (isRecord(entry)) {
|
|
696
|
+
const value = entry[variant];
|
|
697
|
+
if (value !== true && value !== false) {
|
|
698
|
+
walk.diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_OPERATION", variantPath, "an operation variant takes true or false"));
|
|
599
699
|
}
|
|
600
|
-
|
|
601
|
-
? baselineFamily[variant]
|
|
602
|
-
: undefined;
|
|
603
|
-
if (!isOperationVariantAvailable(baselineAvailability)) {
|
|
604
|
-
diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_OPERATION", `${path}.${family}.${variant}`, `operation "${family}.${variant}" is not available in the canonical baseline`));
|
|
700
|
+
if (value !== true) {
|
|
605
701
|
continue;
|
|
606
702
|
}
|
|
607
|
-
const target = currentFamily;
|
|
608
|
-
if (!isOperationVariantAvailable(target[variant])) {
|
|
609
|
-
target[variant] = cloneValue(baselineAvailability);
|
|
610
|
-
}
|
|
611
703
|
}
|
|
704
|
+
if (walk.style === "pick" &&
|
|
705
|
+
!isOperationVariantAvailable(parentFamily?.[variant])) {
|
|
706
|
+
widening(walk, "COMPILER_UNAVAILABLE_OPERATION", variantPath, `operation "${resourceName}.${family}.${variant}"`);
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
variants.push(variant);
|
|
612
710
|
}
|
|
711
|
+
return variants;
|
|
613
712
|
}
|
|
614
|
-
function
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
713
|
+
function exemptOperations(walk, resourceName, universe, parent, entry, path) {
|
|
714
|
+
const omit = walk.style === "omit";
|
|
715
|
+
if (entry === true) {
|
|
716
|
+
return omit ? {} : cloneValue(parent);
|
|
717
|
+
}
|
|
718
|
+
const families = Array.isArray(entry)
|
|
719
|
+
? listedNames(entry, path).map(([family, familyPath]) => [family, familyPath, true])
|
|
720
|
+
: isRecord(entry)
|
|
721
|
+
? Object.entries(entry).map(([family, value]) => [family, `${path}.${family}`, value])
|
|
722
|
+
: [];
|
|
723
|
+
const result = omit ? cloneValue(parent) : {};
|
|
724
|
+
for (const [family, familyPath, value] of families) {
|
|
725
|
+
const universeFamily = universe[family];
|
|
726
|
+
if (!isRecord(universeFamily)) {
|
|
727
|
+
walk.diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_OPERATION", familyPath, `operation family "${family}" is not available for resource "${resourceName}"`));
|
|
620
728
|
continue;
|
|
621
729
|
}
|
|
622
|
-
|
|
623
|
-
const baselineResource = baselines.restoration.resources[resourceName];
|
|
624
|
-
if (baselineResource === undefined) {
|
|
625
|
-
diagnostics.push(diagnostic("COMPILER_UNKNOWN_RESOURCE", `${scope}.resources.${resourceName}`, `resource "${resourceName}" is not present in the canonical compilation baseline`));
|
|
730
|
+
if (value === false || value === undefined) {
|
|
626
731
|
continue;
|
|
627
732
|
}
|
|
628
|
-
if (
|
|
629
|
-
|
|
733
|
+
if (value !== true && !Array.isArray(value) && !isRecord(value)) {
|
|
734
|
+
walk.diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_OPERATION", familyPath, "an operation family takes true, false, a list of its variants or an object of them"));
|
|
630
735
|
continue;
|
|
631
736
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
737
|
+
const parentFamily = isRecord(parent[family])
|
|
738
|
+
? parent[family]
|
|
739
|
+
: undefined;
|
|
740
|
+
if (value === true && !omit && parentFamily === undefined) {
|
|
741
|
+
widening(walk, "COMPILER_UNAVAILABLE_OPERATION", familyPath, `operation family "${resourceName}.${family}"`);
|
|
742
|
+
continue;
|
|
635
743
|
}
|
|
636
|
-
const
|
|
637
|
-
if (
|
|
744
|
+
const variants = exemptVariants(walk, resourceName, family, universeFamily, parentFamily, value, familyPath);
|
|
745
|
+
if (parentFamily === undefined) {
|
|
638
746
|
continue;
|
|
639
747
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
const
|
|
643
|
-
const
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
}
|
|
648
|
-
if (fieldRestrictions.hidden === true) {
|
|
649
|
-
delete resource.fields[fieldName];
|
|
650
|
-
continue;
|
|
651
|
-
}
|
|
652
|
-
if (fieldRestrictions.hidden === false &&
|
|
653
|
-
resource.fields[fieldName] === undefined) {
|
|
654
|
-
resource.fields[fieldName] = cloneValue(baselineField);
|
|
748
|
+
const named = variants === "all" ? Object.keys(parentFamily) : variants;
|
|
749
|
+
if (omit) {
|
|
750
|
+
const target = result[family];
|
|
751
|
+
for (const variant of named) {
|
|
752
|
+
if (target !== undefined) {
|
|
753
|
+
delete target[variant];
|
|
754
|
+
}
|
|
655
755
|
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
const target = (isRecord(result[family]) ? result[family] : {});
|
|
759
|
+
for (const variant of named) {
|
|
760
|
+
if (isOperationVariantAvailable(parentFamily[variant])) {
|
|
761
|
+
target[variant] = cloneValue(parentFamily[variant]);
|
|
659
762
|
}
|
|
660
|
-
applyCapabilityRestrictions(baselines.narrowing.resources[resourceName]?.fields[fieldName], baselineField, field, fieldRestrictions, path, diagnostics);
|
|
661
763
|
}
|
|
662
|
-
|
|
764
|
+
result[family] = target;
|
|
765
|
+
}
|
|
766
|
+
pruneEmptyOperationFamilies(result);
|
|
767
|
+
return result;
|
|
768
|
+
}
|
|
769
|
+
function exemptResource(walk, resourceName, universe, parent, entry, path) {
|
|
770
|
+
const resource = {
|
|
771
|
+
fields: exemptFields(walk, resourceName, universe, parent, entry.fields, `${path}.fields`),
|
|
772
|
+
identifiers: [],
|
|
773
|
+
operations: exemptOperations(walk, resourceName, universe.operations, parent.operations, entry.operations, `${path}.operations`),
|
|
774
|
+
};
|
|
775
|
+
reconcileIdentifiers(walk, resource, parent, path);
|
|
776
|
+
return resource;
|
|
777
|
+
}
|
|
778
|
+
function projectExposure(walk, universe, parent, exemptions) {
|
|
779
|
+
const path = `${walk.layer}.exemptions.resources`;
|
|
780
|
+
const entries = isRecord(exemptions) && isRecord(exemptions.resources)
|
|
781
|
+
? exemptions.resources
|
|
782
|
+
: {};
|
|
783
|
+
const resources = {};
|
|
784
|
+
for (const [resourceName, entry] of Object.entries(entries)) {
|
|
785
|
+
const resourcePath = `${path}.${resourceName}`;
|
|
786
|
+
const universeResource = universe.resources[resourceName];
|
|
787
|
+
if (universeResource === undefined) {
|
|
788
|
+
walk.diagnostics.push(diagnostic("COMPILER_UNKNOWN_RESOURCE", resourcePath, `resource "${resourceName}" is not present in the canonical adapter model`));
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
const parentResource = parent.resources[resourceName];
|
|
792
|
+
if (walk.style === "pick" &&
|
|
793
|
+
exemptsAnything(entry) &&
|
|
794
|
+
parentResource === undefined) {
|
|
795
|
+
widening(walk, "COMPILER_UNAVAILABLE_CAPABILITY", resourcePath, `resource "${resourceName}"`);
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
798
|
+
if (!isRecord(entry)) {
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
const exempted = exemptResource(walk, resourceName, universeResource, parentResource ?? { fields: {}, identifiers: [], operations: {} }, entry, resourcePath);
|
|
802
|
+
if (parentResource !== undefined &&
|
|
803
|
+
(walk.style === "omit" ||
|
|
804
|
+
Object.keys(exempted.fields).length > 0 ||
|
|
805
|
+
Object.keys(exempted.operations).length > 0)) {
|
|
806
|
+
resources[resourceName] = exempted;
|
|
807
|
+
}
|
|
663
808
|
}
|
|
809
|
+
for (const [resourceName, parentResource] of Object.entries(parent.resources)) {
|
|
810
|
+
if (Object.hasOwn(resources, resourceName)) {
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
const entry = Object.hasOwn(entries, resourceName)
|
|
814
|
+
? entries[resourceName]
|
|
815
|
+
: undefined;
|
|
816
|
+
if (walk.style === "omit" ? !exemptsWhole(entry) : exemptsWhole(entry)) {
|
|
817
|
+
resources[resourceName] = cloneValue(parentResource);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return { ...cloneValue({ ...parent, resources: {} }), resources };
|
|
664
821
|
}
|
|
665
822
|
function pruneEmptyOperationFamilies(operations) {
|
|
666
823
|
for (const [family, variants] of Object.entries(operations)) {
|
|
@@ -669,45 +826,28 @@ function pruneEmptyOperationFamilies(operations) {
|
|
|
669
826
|
}
|
|
670
827
|
}
|
|
671
828
|
}
|
|
672
|
-
function
|
|
673
|
-
|
|
674
|
-
return false;
|
|
675
|
-
}
|
|
676
|
-
const familyRestrictions = restrictions.operations[family];
|
|
677
|
-
return isRecord(familyRestrictions) && familyRestrictions[variant] === false;
|
|
829
|
+
function identifierLabel(identifier) {
|
|
830
|
+
return identifier.map((name) => `\`${name}\``).join(" and ");
|
|
678
831
|
}
|
|
679
|
-
function reconcileIdentifiers(resource,
|
|
680
|
-
resource.identifiers =
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
832
|
+
function reconcileIdentifiers(walk, resource, parent, path) {
|
|
833
|
+
resource.identifiers = parent.identifiers
|
|
834
|
+
.filter((identifier) => identifier.every((name) => resource.fields[name]?.kind === "scalar"))
|
|
835
|
+
.map((identifier) => [...identifier]);
|
|
836
|
+
const first = parent.identifiers[0];
|
|
837
|
+
if (resource.identifiers.length > 0 || first === undefined) {
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
684
840
|
for (const [family, variant] of UNIQUE_VARIANTS) {
|
|
685
841
|
const currentFamily = resource.operations[family];
|
|
686
|
-
if (
|
|
687
|
-
|
|
688
|
-
delete currentFamily[variant];
|
|
689
|
-
}
|
|
690
|
-
continue;
|
|
691
|
-
}
|
|
692
|
-
if (operationIsExplicitlyDisabled(restrictions, family, variant)) {
|
|
693
|
-
if (isRecord(currentFamily)) {
|
|
694
|
-
delete currentFamily[variant];
|
|
695
|
-
}
|
|
842
|
+
if (!isRecord(currentFamily) ||
|
|
843
|
+
!isOperationVariantAvailable(currentFamily[variant])) {
|
|
696
844
|
continue;
|
|
697
845
|
}
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
?
|
|
701
|
-
:
|
|
702
|
-
if (isOperationVariantAvailable(baselineAvailability)) {
|
|
703
|
-
const targetFamily = isRecord(currentFamily) ? currentFamily : {};
|
|
704
|
-
if (!isOperationVariantAvailable(targetFamily[variant])) {
|
|
705
|
-
targetFamily[variant] = cloneValue(baselineAvailability);
|
|
706
|
-
}
|
|
707
|
-
resource.operations[family] = targetFamily;
|
|
708
|
-
}
|
|
846
|
+
const variantPath = `${path}.operations.${family}.${variant}`;
|
|
847
|
+
walk.diagnostics.push(diagnostic("COMPILER_MISSING_IDENTIFIER", variantPath, walk.style === "pick"
|
|
848
|
+
? `${variantPath} needs an identifier field; pick ${identifierLabel(first)} (or another complete identifier) too`
|
|
849
|
+
: `${variantPath} needs an identifier field, and the exemptions hide every complete identifier; keep ${identifierLabel(first)} (or another complete identifier) exposed, or exempt the operation too`));
|
|
709
850
|
}
|
|
710
|
-
pruneEmptyOperationFamilies(resource.operations);
|
|
711
851
|
}
|
|
712
852
|
function validateScalarReference(projection, field, path, diagnostics) {
|
|
713
853
|
const type = field.type;
|
|
@@ -757,7 +897,7 @@ function validateFieldLifecycle(field, path, diagnostics) {
|
|
|
757
897
|
if (lifecycle.includes("VIRTUAL") &&
|
|
758
898
|
holdsInAnyScope(capabilities.select) &&
|
|
759
899
|
!lifecycle.includes("COMPUTED_ON_READ")) {
|
|
760
|
-
diagnostics.push(diagnostic("
|
|
900
|
+
diagnostics.push(diagnostic("COMPILER_MISSING_READ_COMPUTE", path, "selectable VIRTUAL field requires a compute.read stage (pipelines.compute.read)"));
|
|
761
901
|
}
|
|
762
902
|
}
|
|
763
903
|
function isTokenList(value) {
|
|
@@ -778,6 +918,11 @@ function representationFault(axis, value) {
|
|
|
778
918
|
return typeof value === "string" && axis.values.includes(value)
|
|
779
919
|
? undefined
|
|
780
920
|
: `one of ${axis.values.join(", ")} is expected`;
|
|
921
|
+
case "adapterTokens":
|
|
922
|
+
return isTokenList(value) &&
|
|
923
|
+
value.every((token) => axis.values.includes(token))
|
|
924
|
+
? undefined
|
|
925
|
+
: `a list of ${axis.values.join(", ")} is expected`;
|
|
781
926
|
case "flag":
|
|
782
927
|
return typeof value === "boolean" ? undefined : "a boolean is expected";
|
|
783
928
|
case "tokens":
|
|
@@ -815,12 +960,8 @@ function validateCapabilityRepresentation(field, path, diagnostics) {
|
|
|
815
960
|
}
|
|
816
961
|
}
|
|
817
962
|
}
|
|
818
|
-
function validateProjection(projection,
|
|
963
|
+
function validateProjection(projection, scope, diagnostics) {
|
|
819
964
|
for (const [resourceName, resource] of Object.entries(projection.resources)) {
|
|
820
|
-
const baselineResource = restoration.resources[resourceName];
|
|
821
|
-
if (baselineResource !== undefined) {
|
|
822
|
-
reconcileIdentifiers(resource, baselineResource, restrictions?.[resourceName]);
|
|
823
|
-
}
|
|
824
965
|
for (const [fieldName, field] of Object.entries(resource.fields)) {
|
|
825
966
|
const fieldPath = `${scope}.resources.${resourceName}.fields.${fieldName}`;
|
|
826
967
|
validateCapabilityRepresentation(field, fieldPath, diagnostics);
|
|
@@ -841,49 +982,75 @@ function resolveLimits(scope) {
|
|
|
841
982
|
return { ...DEFAULT_CONTRACT_LIMITS, ...(scope.limits ?? {}) };
|
|
842
983
|
}
|
|
843
984
|
function resolveTransactions(model, scopeName, scope, diagnostics) {
|
|
844
|
-
const requested = scope.
|
|
985
|
+
const requested = scope.transactionsStyle ?? model.transactions;
|
|
845
986
|
if (requested === "interactive" && model.transactions === "none") {
|
|
846
|
-
diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_CAPABILITY", `${scopeName}.
|
|
987
|
+
diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_CAPABILITY", `${scopeName}.transactionsStyle`, 'transaction capability "interactive" is not available in the canonical adapter model'));
|
|
847
988
|
}
|
|
848
989
|
return requested;
|
|
849
990
|
}
|
|
850
991
|
function semanticObject(contract) {
|
|
851
992
|
return JSON.parse(canonicalizeContract(contract));
|
|
852
993
|
}
|
|
853
|
-
function
|
|
994
|
+
function exposureStyle(layer) {
|
|
995
|
+
return layer.exposed === false ? "pick" : "omit";
|
|
996
|
+
}
|
|
997
|
+
function projectCore(model, config, diagnostics) {
|
|
998
|
+
const scope = config.root;
|
|
854
999
|
const providerBaseline = createBaseline(model);
|
|
855
1000
|
const baseline = cloneValue(providerBaseline);
|
|
856
1001
|
baseline.limits = resolveLimits(scope);
|
|
857
1002
|
baseline.transactions = resolveTransactions(model, "root", scope, diagnostics);
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
1003
|
+
checkResourceTree(model, baseline, "root", config.trees.root, config.definitions, diagnostics);
|
|
1004
|
+
applyFieldDefinitions(model, baseline, config.definitions, diagnostics);
|
|
1005
|
+
applyCompute(baseline, scope.compute);
|
|
1006
|
+
checkLayerValidators(baseline.resources, "root", config.trees.root, diagnostics);
|
|
1007
|
+
const validators = compileValidatorTable(baseline.resources, scope.pipelines);
|
|
1008
|
+
const projection = projectExposure({ layer: "root", style: exposureStyle(scope), diagnostics }, baseline, baseline, scope.exemptions);
|
|
1009
|
+
qualifyFilterModes(projection);
|
|
1010
|
+
pruneHiddenPairings(projection);
|
|
1011
|
+
projectCreateRequirements(model, projection.resources);
|
|
1012
|
+
checkHiddenRequirements(model, "root", baseline.resources, baseline.resources, projection.resources, diagnostics);
|
|
1013
|
+
applyValidators(projection.resources, validators);
|
|
1014
|
+
validateProjection(projection, "root", diagnostics);
|
|
1015
|
+
return { baseline, projection, validators };
|
|
1016
|
+
}
|
|
1017
|
+
function projectDerivedScope(model, scopeName, core, parentView, config, diagnostics) {
|
|
1018
|
+
const scope = config[scopeName];
|
|
866
1019
|
const baseline = cloneValue(core.baseline);
|
|
867
1020
|
baseline.limits = resolveLimits(scope);
|
|
868
|
-
baseline.transactions = resolveTransactions(model, scopeName, scope,
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
1021
|
+
baseline.transactions = resolveTransactions(model, scopeName, scope, scopeName === "agent" &&
|
|
1022
|
+
scope.transactionsStyle === config[config.agent.parent].transactionsStyle
|
|
1023
|
+
? []
|
|
1024
|
+
: diagnostics);
|
|
1025
|
+
checkResourceTree(model, baseline, scopeName, config.trees[scopeName], config.definitions, diagnostics);
|
|
1026
|
+
applyCompute(baseline, scope.compute);
|
|
1027
|
+
checkLayerValidators(baseline.resources, scopeName, config.trees[scopeName], diagnostics);
|
|
1028
|
+
const validators = compileValidatorTable(baseline.resources, scope.pipelines);
|
|
1029
|
+
const parent = cloneValue(parentView);
|
|
1030
|
+
parent.limits = resolveLimits(scope);
|
|
1031
|
+
parent.transactions = baseline.transactions;
|
|
1032
|
+
applyCompute(parent, scope.compute);
|
|
1033
|
+
const projection = projectExposure({
|
|
1034
|
+
layer: scopeName,
|
|
1035
|
+
style: exposureStyle(scope),
|
|
1036
|
+
diagnostics,
|
|
1037
|
+
...(scopeName === "agent" ? { parent: config.agent.parent } : {}),
|
|
1038
|
+
}, baseline, parent, scope.exemptions);
|
|
1039
|
+
qualifyFilterModes(projection);
|
|
1040
|
+
pruneHiddenPairings(projection);
|
|
1041
|
+
projectCreateRequirements(model, projection.resources);
|
|
1042
|
+
checkHiddenRequirements(model, scopeName, baseline.resources, parent.resources, projection.resources, diagnostics);
|
|
1043
|
+
applyValidators(projection.resources, validators);
|
|
1044
|
+
validateProjection(projection, scopeName, diagnostics);
|
|
1045
|
+
return { projection, validators };
|
|
1046
|
+
}
|
|
1047
|
+
function bindShapedFields(contract, definitions) {
|
|
881
1048
|
for (const [resourceName, resource] of Object.entries(contract.resources)) {
|
|
882
1049
|
for (const [fieldName, field] of Object.entries(resource.fields)) {
|
|
883
1050
|
if (field.kind !== "scalar" || field.json === undefined) {
|
|
884
1051
|
continue;
|
|
885
1052
|
}
|
|
886
|
-
const shape = jsonShapeOf(
|
|
1053
|
+
const shape = jsonShapeOf(definitions[resourceName]?.[fieldName]?.json);
|
|
887
1054
|
if (shape === undefined) {
|
|
888
1055
|
throw new Error(`the Contract compiler found no shape for shaped field "${resourceName}.${fieldName}"`);
|
|
889
1056
|
}
|
|
@@ -894,37 +1061,65 @@ function bindShapedFields(contract, fields) {
|
|
|
894
1061
|
export async function compileContracts(model, config) {
|
|
895
1062
|
const compilerConfig = config;
|
|
896
1063
|
const diagnostics = [];
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
const
|
|
1064
|
+
checkReservedResourceNames(model, diagnostics);
|
|
1065
|
+
checkCreateRequirements(model, diagnostics);
|
|
1066
|
+
const coreCompilation = projectCore(model, compilerConfig, diagnostics);
|
|
1067
|
+
const applicationProjection = projectDerivedScope(model, "application", coreCompilation, coreCompilation.projection, compilerConfig, diagnostics);
|
|
1068
|
+
const clientProjection = projectDerivedScope(model, "client", coreCompilation, coreCompilation.projection, compilerConfig, diagnostics);
|
|
1069
|
+
const agentParent = compilerConfig.agent.parent;
|
|
1070
|
+
const agentProjection = projectDerivedScope(model, "agent", coreCompilation, agentParent === "root"
|
|
1071
|
+
? coreCompilation.projection
|
|
1072
|
+
: agentParent === "application"
|
|
1073
|
+
? applicationProjection.projection
|
|
1074
|
+
: clientProjection.projection, compilerConfig, diagnostics);
|
|
900
1075
|
if (diagnostics.length > 0) {
|
|
901
1076
|
return { ok: false, diagnostics };
|
|
902
1077
|
}
|
|
903
1078
|
const core = semanticObject(coreCompilation.projection);
|
|
904
|
-
const application = semanticObject(applicationProjection);
|
|
905
|
-
const
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
}
|
|
1079
|
+
const application = semanticObject(applicationProjection.projection);
|
|
1080
|
+
const client = await identifiedContract(clientProjection.projection);
|
|
1081
|
+
const agent = await identifiedContract(agentProjection.projection);
|
|
1082
|
+
for (const [layer, contract] of [
|
|
1083
|
+
["client", client],
|
|
1084
|
+
["agent", agent],
|
|
1085
|
+
]) {
|
|
1086
|
+
const structure = validateClientContractStructure(contract);
|
|
1087
|
+
if (!structure.valid) {
|
|
1088
|
+
return {
|
|
1089
|
+
ok: false,
|
|
1090
|
+
diagnostics: [
|
|
1091
|
+
diagnostic("COMPILER_INVALID_CLIENT_CONTRACT", structure.path, layer === "client"
|
|
1092
|
+
? structure.message
|
|
1093
|
+
: `Agent Contract: ${structure.message}`),
|
|
1094
|
+
],
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
922
1097
|
}
|
|
923
|
-
bindShapedFields(core, compilerConfig.
|
|
924
|
-
bindShapedFields(application, compilerConfig.
|
|
925
|
-
bindShapedFields(client, compilerConfig.
|
|
1098
|
+
bindShapedFields(core, compilerConfig.definitions);
|
|
1099
|
+
bindShapedFields(application, compilerConfig.definitions);
|
|
1100
|
+
bindShapedFields(client, compilerConfig.definitions);
|
|
1101
|
+
bindShapedFields(agent, compilerConfig.definitions);
|
|
1102
|
+
bindFieldValidators(core, coreCompilation.validators);
|
|
1103
|
+
bindFieldValidators(application, applicationProjection.validators);
|
|
1104
|
+
bindFieldValidators(client, clientProjection.validators);
|
|
1105
|
+
bindFieldValidators(agent, agentProjection.validators);
|
|
926
1106
|
return {
|
|
927
1107
|
ok: true,
|
|
928
|
-
contracts: { core, application, client },
|
|
1108
|
+
contracts: { core, application, client, agent },
|
|
929
1109
|
};
|
|
930
1110
|
}
|
|
1111
|
+
async function identifiedContract(projection) {
|
|
1112
|
+
const members = canonicalizeContractMembers({
|
|
1113
|
+
...projection,
|
|
1114
|
+
protocol: { version: AVENTARA_PROTOCOL_VERSION },
|
|
1115
|
+
});
|
|
1116
|
+
const hash = await hashCanonicalContractText(joinCanonicalContractMembers(members));
|
|
1117
|
+
const protocol = canonicalizeJson({
|
|
1118
|
+
version: AVENTARA_PROTOCOL_VERSION,
|
|
1119
|
+
hash,
|
|
1120
|
+
});
|
|
1121
|
+
const text = joinCanonicalContractMembers(members.map((member) => member.key === "protocol" ? { key: "protocol", text: protocol } : member));
|
|
1122
|
+
const contract = JSON.parse(text);
|
|
1123
|
+
recordCanonicalContractText(contract, text);
|
|
1124
|
+
return contract;
|
|
1125
|
+
}
|