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