@aventara/core 1.0.0-rc.0 → 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 +1140 -0
- package/README.md +224 -57
- 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 +9 -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 -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.d.ts +18 -18
- package/dist/compiler/contract.compiler.js +548 -441
- 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 +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 +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 +110 -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 +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 +323 -28
- package/dist/contracts/client-contract-structure.validator.js +152 -8
- package/dist/contracts/contract-canonicalizer.d.ts +19 -3
- package/dist/contracts/contract-canonicalizer.js +141 -28
- 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 +52 -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 +10 -10
- package/dist/framework/framework-construction.error.js +2 -2
- package/dist/framework/framework.d.ts +47 -23
- package/dist/framework/framework.factory.d.ts +8 -6
- package/dist/framework/framework.factory.js +40 -20
- package/dist/framework/operation-facade.d.ts +123 -52
- package/dist/framework/operation-facade.js +9 -1
- package/dist/index.d.ts +55 -50
- package/dist/index.js +6 -6
- package/dist/internal.d.ts +15 -10
- package/dist/internal.js +3 -1
- package/dist/operations/operation-arguments.d.ts +267 -202
- 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 +13 -2
- 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.js +4 -0
- package/dist/operations/write-scope.resolver.d.ts +6 -6
- 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 +44 -9
- 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 +5 -5
- package/dist/runtime/caller-data.copier.js +13 -13
- package/dist/runtime/caller-data.door.d.ts +6 -6
- 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 +61 -48
- package/dist/runtime/computed-value.error.d.ts +6 -6
- package/dist/runtime/computed-value.error.js +3 -3
- 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 +10 -81
- package/dist/runtime/create-requirement.js +80 -57
- package/dist/runtime/decimal.d.ts +2 -2
- package/dist/runtime/decimal.js +1 -1
- package/dist/runtime/diagnostics-reporter.d.ts +53 -27
- package/dist/runtime/diagnostics-reporter.js +24 -0
- package/dist/runtime/error-boundary.d.ts +65 -8
- package/dist/runtime/error-boundary.js +45 -19
- 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 +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 +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 +12 -12
- package/dist/runtime/mutation-planner.d.ts +19 -10
- package/dist/runtime/mutation-planner.js +54 -30
- 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 +33 -6
- package/dist/runtime/operation-response.d.ts +17 -17
- package/dist/runtime/operation-runtime.d.ts +113 -25
- package/dist/runtime/operation-runtime.js +395 -74
- 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 +6 -6
- 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 +196 -34
- package/dist/runtime/readonly-value.d.ts +32 -0
- package/dist/runtime/readonly-value.js +141 -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 +31 -16
- package/dist/runtime/scalar-runtime.js +16 -18
- 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 +21 -11
- package/dist/transactions/transaction-plan-assembler.js +148 -34
- package/dist/transactions/transaction-plan-validator.d.ts +29 -20
- package/dist/transactions/transaction-plan-validator.js +70 -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 +34 -4
- package/dist/transactions/transaction-reference-validator.js +58 -26
- package/dist/transactions/transaction-reference.d.ts +13 -13
- 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 +166 -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 +6 -5
- 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 { 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"],
|
|
@@ -105,8 +107,8 @@ function createBaseline(model) {
|
|
|
105
107
|
: { referenceWrites: model.referenceWrites }),
|
|
106
108
|
};
|
|
107
109
|
}
|
|
108
|
-
function
|
|
109
|
-
return
|
|
110
|
+
function definitionPath(resource, field) {
|
|
111
|
+
return `root.resources.${resource}.fields.${field}.definition`;
|
|
110
112
|
}
|
|
111
113
|
function structuralValueMatches(field, config, key) {
|
|
112
114
|
if (config[key] === undefined) {
|
|
@@ -114,6 +116,13 @@ function structuralValueMatches(field, config, key) {
|
|
|
114
116
|
}
|
|
115
117
|
return JSON.stringify(field[key]) === JSON.stringify(config[key]);
|
|
116
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
|
+
}
|
|
117
126
|
function addConfiguredScalarField(config, path, diagnostics) {
|
|
118
127
|
if (config.json !== undefined) {
|
|
119
128
|
refuseJsonShape(path, "is not a field of the adapter model", diagnostics);
|
|
@@ -128,7 +137,7 @@ function addConfiguredScalarField(config, path, diagnostics) {
|
|
|
128
137
|
if (configuredLifecycle === undefined ||
|
|
129
138
|
!configuredLifecycle.includes("VIRTUAL") ||
|
|
130
139
|
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
|
|
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"));
|
|
132
141
|
return undefined;
|
|
133
142
|
}
|
|
134
143
|
return {
|
|
@@ -140,89 +149,14 @@ function addConfiguredScalarField(config, path, diagnostics) {
|
|
|
140
149
|
lifecycle: [...(config.lifecycle ?? [])],
|
|
141
150
|
capabilities: {
|
|
142
151
|
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
|
-
: {}),
|
|
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"),
|
|
158
157
|
},
|
|
159
158
|
};
|
|
160
159
|
}
|
|
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
160
|
function fieldLabel(path) {
|
|
227
161
|
const [, resource, , field] = path.split(".").slice(1);
|
|
228
162
|
return `${resource}.${field}`;
|
|
@@ -238,7 +172,7 @@ function applyJsonShape(field, baselineField, shape, path, diagnostics) {
|
|
|
238
172
|
}
|
|
239
173
|
const runtime = jsonShapeOf(shape);
|
|
240
174
|
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/
|
|
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`));
|
|
242
176
|
return;
|
|
243
177
|
}
|
|
244
178
|
if (runtime.validate(null).length === 0 ||
|
|
@@ -255,7 +189,7 @@ function applyExistingScalarConfig(field, baselineField, config, path, diagnosti
|
|
|
255
189
|
}
|
|
256
190
|
}
|
|
257
191
|
if (config.format !== undefined) {
|
|
258
|
-
|
|
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"));
|
|
259
193
|
}
|
|
260
194
|
if (config.json !== undefined) {
|
|
261
195
|
applyJsonShape(field, baselineField, config.json, path, diagnostics);
|
|
@@ -264,95 +198,39 @@ function applyExistingScalarConfig(field, baselineField, config, path, diagnosti
|
|
|
264
198
|
diagnostics.push(diagnostic("COMPILER_INVALID_LIFECYCLE", `${path}.lifecycle`, "existing adapter field lifecycle cannot be redefined by framework field configuration"));
|
|
265
199
|
}
|
|
266
200
|
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
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
function applyFrameworkOwnedScalarConfig(field, config, path, diagnostics) {
|
|
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
|
-
}
|
|
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"));
|
|
305
202
|
}
|
|
306
203
|
}
|
|
307
|
-
function
|
|
308
|
-
if (config === undefined) {
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
204
|
+
function applyFieldDefinitions(model, projection, definitions, diagnostics) {
|
|
311
205
|
const providerBaseline = createBaseline(model);
|
|
312
|
-
for (const [resourceName,
|
|
313
|
-
if (resourceConfig === undefined) {
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
206
|
+
for (const [resourceName, resourceDefinitions] of Object.entries(definitions)) {
|
|
316
207
|
const resource = projection.resources[resourceName];
|
|
317
208
|
const providerResource = providerBaseline.resources[resourceName];
|
|
318
209
|
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
210
|
continue;
|
|
324
211
|
}
|
|
325
|
-
for (const [fieldName,
|
|
326
|
-
const
|
|
327
|
-
const path = scalarFieldPath(scope, resourceName, fieldName);
|
|
212
|
+
for (const [fieldName, definition] of Object.entries(resourceDefinitions)) {
|
|
213
|
+
const path = definitionPath(resourceName, fieldName);
|
|
328
214
|
const existing = resource.fields[fieldName];
|
|
329
215
|
const providerField = providerResource.fields[fieldName];
|
|
330
216
|
if (existing === undefined) {
|
|
331
|
-
|
|
332
|
-
continue;
|
|
333
|
-
}
|
|
334
|
-
const added = addConfiguredScalarField(fieldConfig, path, diagnostics);
|
|
217
|
+
const added = addConfiguredScalarField(definition, path, diagnostics);
|
|
335
218
|
if (added !== undefined) {
|
|
336
219
|
resource.fields[fieldName] = added;
|
|
337
220
|
}
|
|
338
221
|
continue;
|
|
339
222
|
}
|
|
340
223
|
if (existing.kind === "relation") {
|
|
341
|
-
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"));
|
|
342
225
|
continue;
|
|
343
226
|
}
|
|
344
227
|
if (providerField?.kind === "scalar") {
|
|
345
|
-
applyExistingScalarConfig(existing, providerField,
|
|
346
|
-
continue;
|
|
228
|
+
applyExistingScalarConfig(existing, providerField, definition, path, diagnostics);
|
|
347
229
|
}
|
|
348
|
-
applyFrameworkOwnedScalarConfig(existing, fieldConfig, path, diagnostics);
|
|
349
230
|
}
|
|
350
231
|
}
|
|
351
232
|
}
|
|
352
|
-
function
|
|
353
|
-
return value !== undefined && value !== false;
|
|
354
|
-
}
|
|
355
|
-
function readBehaviorDependencies(value) {
|
|
233
|
+
function readComputeDependencies(value) {
|
|
356
234
|
if (!isRecord(value) || typeof value.compute !== "function") {
|
|
357
235
|
return undefined;
|
|
358
236
|
}
|
|
@@ -360,54 +238,114 @@ function readBehaviorDependencies(value) {
|
|
|
360
238
|
? value.dependsOn.filter((entry) => typeof entry === "string")
|
|
361
239
|
: undefined;
|
|
362
240
|
}
|
|
363
|
-
function
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
if (
|
|
367
|
-
|
|
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;
|
|
368
263
|
}
|
|
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
264
|
}
|
|
376
|
-
|
|
377
|
-
|
|
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;
|
|
378
277
|
}
|
|
379
|
-
if (
|
|
380
|
-
|
|
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;
|
|
381
281
|
}
|
|
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`));
|
|
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}"`));
|
|
387
285
|
}
|
|
388
286
|
}
|
|
389
287
|
}
|
|
390
|
-
function
|
|
391
|
-
if (
|
|
288
|
+
function checkResourceTree(model, baseline, scope, tree, definitions, diagnostics) {
|
|
289
|
+
if (!isRecord(tree)) {
|
|
392
290
|
return;
|
|
393
291
|
}
|
|
394
|
-
for (const [resourceName,
|
|
395
|
-
|
|
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`));
|
|
396
297
|
continue;
|
|
397
298
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
+
}
|
|
402
321
|
}
|
|
403
|
-
|
|
322
|
+
}
|
|
323
|
+
if (!isRecord(entry.fields)) {
|
|
404
324
|
continue;
|
|
405
325
|
}
|
|
406
|
-
for (const [fieldName,
|
|
407
|
-
|
|
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`));
|
|
408
331
|
continue;
|
|
409
332
|
}
|
|
410
|
-
|
|
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`));
|
|
411
349
|
}
|
|
412
350
|
}
|
|
413
351
|
}
|
|
@@ -441,226 +379,366 @@ function capabilityAxisFor(kind, key) {
|
|
|
441
379
|
function unavailableCapability(path, message, diagnostics) {
|
|
442
380
|
diagnostics.push(diagnostic("COMPILER_UNAVAILABLE_CAPABILITY", path, message));
|
|
443
381
|
}
|
|
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];
|
|
382
|
+
function otherOverlay(layer) {
|
|
383
|
+
return layer === "client" ? "application" : "client";
|
|
451
384
|
}
|
|
452
|
-
function
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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 [];
|
|
460
401
|
}
|
|
461
|
-
return
|
|
402
|
+
return entry.flatMap((name, index) => typeof name === "string"
|
|
403
|
+
? [[name, `${path}[${index}]`]]
|
|
404
|
+
: []);
|
|
462
405
|
}
|
|
463
|
-
function
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
if (narrowing.outcome === "refused") {
|
|
467
|
-
return;
|
|
406
|
+
function memberOffered(axis, value) {
|
|
407
|
+
if (value === undefined || value === false) {
|
|
408
|
+
return false;
|
|
468
409
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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;
|
|
472
419
|
}
|
|
473
|
-
current[key] = narrowing.scopes;
|
|
474
420
|
}
|
|
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;
|
|
421
|
+
function memberTokens(axis, value) {
|
|
422
|
+
if (axis.kind === "tokens") {
|
|
423
|
+
return Array.isArray(value) ? value : undefined;
|
|
485
424
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
unavailableCapability(path, `capability "${key}" is not available in the canonical baseline`, diagnostics);
|
|
491
|
-
return true;
|
|
425
|
+
if (axis.kind === "scopedTokens") {
|
|
426
|
+
return axis.tokenKey === "directives"
|
|
427
|
+
? advertisedDirectives(value)
|
|
428
|
+
: advertisedReducers(value);
|
|
492
429
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
if (
|
|
497
|
-
|
|
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
|
+
};
|
|
498
440
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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;
|
|
508
456
|
}
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
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;
|
|
514
461
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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);
|
|
518
473
|
}
|
|
519
|
-
|
|
520
|
-
return true;
|
|
474
|
+
return tokens;
|
|
521
475
|
}
|
|
522
|
-
function
|
|
523
|
-
if (
|
|
524
|
-
unavailableCapability(path, `capability "${key}" is an adapter limit and takes no restriction`, diagnostics);
|
|
476
|
+
function exemptCapability(walk, universeField, parentCapabilities, result, capability, entry, label, path) {
|
|
477
|
+
if (entry === undefined) {
|
|
525
478
|
return;
|
|
526
479
|
}
|
|
527
|
-
|
|
528
|
-
|
|
480
|
+
const axis = exemptableAxis(walk, universeField, capability, label, path);
|
|
481
|
+
if (axis === undefined || entry === false) {
|
|
529
482
|
return;
|
|
530
483
|
}
|
|
531
|
-
const
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
unavailableCapability(path, `capability "${key}" is not available in the canonical baseline`, diagnostics);
|
|
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);
|
|
537
489
|
return;
|
|
538
490
|
}
|
|
539
|
-
|
|
540
|
-
|
|
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) {
|
|
541
496
|
return;
|
|
542
497
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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;
|
|
549
507
|
}
|
|
508
|
+
const kept = (memberTokens(axis, parentValue) ?? []).filter((token) => !tokens.includes(token));
|
|
509
|
+
result[capability] = withTokens(axis, parentValue, kept);
|
|
550
510
|
return;
|
|
551
511
|
}
|
|
552
|
-
if (
|
|
553
|
-
applyScopeRestriction(axis.axis, baselineValue, current, key, value, path, diagnostics);
|
|
512
|
+
if (tokens !== undefined && tokens.length === 0) {
|
|
554
513
|
return;
|
|
555
514
|
}
|
|
556
|
-
if (
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
}
|
|
515
|
+
if (!parentHas) {
|
|
516
|
+
widening(walk, "COMPILER_UNAVAILABLE_CAPABILITY", path, `capability "${capability}" of field ${label}`);
|
|
517
|
+
return;
|
|
560
518
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
function applyCapabilityRestrictions(inheritedField, restorationField, field, restrictions, path, diagnostics) {
|
|
564
|
-
const capabilityRestrictions = restrictions.capabilities;
|
|
565
|
-
if (capabilityRestrictions === undefined) {
|
|
519
|
+
if (tokens === undefined) {
|
|
520
|
+
result[capability] = cloneValue(parentValue);
|
|
566
521
|
return;
|
|
567
522
|
}
|
|
568
|
-
const
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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);
|
|
574
544
|
}
|
|
575
|
-
applyCapabilityRestriction(restorationField.kind, inherited, restoration, current, key, value, `${path}.capabilities.${key}`, diagnostics);
|
|
576
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 };
|
|
577
559
|
}
|
|
578
|
-
function
|
|
579
|
-
|
|
580
|
-
|
|
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
|
+
}
|
|
581
566
|
}
|
|
582
|
-
|
|
583
|
-
|
|
567
|
+
const result = {};
|
|
568
|
+
for (const [name, entry] of Object.entries(named)) {
|
|
569
|
+
const universeField = universe.fields[name];
|
|
570
|
+
if (universeField === undefined) {
|
|
584
571
|
continue;
|
|
585
572
|
}
|
|
586
|
-
const
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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;
|
|
591
581
|
}
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
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"));
|
|
599
620
|
}
|
|
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`));
|
|
621
|
+
if (value !== true) {
|
|
605
622
|
continue;
|
|
606
623
|
}
|
|
607
|
-
const target = currentFamily;
|
|
608
|
-
if (!isOperationVariantAvailable(target[variant])) {
|
|
609
|
-
target[variant] = cloneValue(baselineAvailability);
|
|
610
|
-
}
|
|
611
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);
|
|
612
631
|
}
|
|
632
|
+
return variants;
|
|
613
633
|
}
|
|
614
|
-
function
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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}"`));
|
|
620
649
|
continue;
|
|
621
650
|
}
|
|
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`));
|
|
651
|
+
if (value === false || value === undefined) {
|
|
626
652
|
continue;
|
|
627
653
|
}
|
|
628
|
-
if (
|
|
629
|
-
|
|
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"));
|
|
630
656
|
continue;
|
|
631
657
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
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;
|
|
635
664
|
}
|
|
636
|
-
const
|
|
637
|
-
if (
|
|
665
|
+
const variants = exemptVariants(walk, resourceName, family, universeFamily, parentFamily, value, familyPath);
|
|
666
|
+
if (parentFamily === undefined) {
|
|
638
667
|
continue;
|
|
639
668
|
}
|
|
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);
|
|
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
|
+
}
|
|
655
676
|
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
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]);
|
|
659
683
|
}
|
|
660
|
-
applyCapabilityRestrictions(baselines.narrowing.resources[resourceName]?.fields[fieldName], baselineField, field, fieldRestrictions, path, diagnostics);
|
|
661
684
|
}
|
|
662
|
-
|
|
685
|
+
result[family] = target;
|
|
663
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
|
+
}
|
|
740
|
+
}
|
|
741
|
+
return { ...cloneValue({ ...parent, resources: {} }), resources };
|
|
664
742
|
}
|
|
665
743
|
function pruneEmptyOperationFamilies(operations) {
|
|
666
744
|
for (const [family, variants] of Object.entries(operations)) {
|
|
@@ -669,45 +747,28 @@ function pruneEmptyOperationFamilies(operations) {
|
|
|
669
747
|
}
|
|
670
748
|
}
|
|
671
749
|
}
|
|
672
|
-
function
|
|
673
|
-
|
|
674
|
-
return false;
|
|
675
|
-
}
|
|
676
|
-
const familyRestrictions = restrictions.operations[family];
|
|
677
|
-
return isRecord(familyRestrictions) && familyRestrictions[variant] === false;
|
|
750
|
+
function identifierLabel(identifier) {
|
|
751
|
+
return identifier.map((name) => `\`${name}\``).join(" and ");
|
|
678
752
|
}
|
|
679
|
-
function reconcileIdentifiers(resource,
|
|
680
|
-
resource.identifiers =
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
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
|
+
}
|
|
684
761
|
for (const [family, variant] of UNIQUE_VARIANTS) {
|
|
685
762
|
const currentFamily = resource.operations[family];
|
|
686
|
-
if (
|
|
687
|
-
|
|
688
|
-
delete currentFamily[variant];
|
|
689
|
-
}
|
|
763
|
+
if (!isRecord(currentFamily) ||
|
|
764
|
+
!isOperationVariantAvailable(currentFamily[variant])) {
|
|
690
765
|
continue;
|
|
691
766
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
}
|
|
696
|
-
continue;
|
|
697
|
-
}
|
|
698
|
-
const baselineFamily = baseline.operations[family];
|
|
699
|
-
const baselineAvailability = isRecord(baselineFamily)
|
|
700
|
-
? baselineFamily[variant]
|
|
701
|
-
: undefined;
|
|
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
|
-
}
|
|
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`));
|
|
709
771
|
}
|
|
710
|
-
pruneEmptyOperationFamilies(resource.operations);
|
|
711
772
|
}
|
|
712
773
|
function validateScalarReference(projection, field, path, diagnostics) {
|
|
713
774
|
const type = field.type;
|
|
@@ -757,7 +818,7 @@ function validateFieldLifecycle(field, path, diagnostics) {
|
|
|
757
818
|
if (lifecycle.includes("VIRTUAL") &&
|
|
758
819
|
holdsInAnyScope(capabilities.select) &&
|
|
759
820
|
!lifecycle.includes("COMPUTED_ON_READ")) {
|
|
760
|
-
diagnostics.push(diagnostic("
|
|
821
|
+
diagnostics.push(diagnostic("COMPILER_MISSING_READ_COMPUTE", path, "selectable VIRTUAL field requires a compute.read stage (pipelines.compute.read)"));
|
|
761
822
|
}
|
|
762
823
|
}
|
|
763
824
|
function isTokenList(value) {
|
|
@@ -815,12 +876,8 @@ function validateCapabilityRepresentation(field, path, diagnostics) {
|
|
|
815
876
|
}
|
|
816
877
|
}
|
|
817
878
|
}
|
|
818
|
-
function validateProjection(projection,
|
|
879
|
+
function validateProjection(projection, scope, diagnostics) {
|
|
819
880
|
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
881
|
for (const [fieldName, field] of Object.entries(resource.fields)) {
|
|
825
882
|
const fieldPath = `${scope}.resources.${resourceName}.fields.${fieldName}`;
|
|
826
883
|
validateCapabilityRepresentation(field, fieldPath, diagnostics);
|
|
@@ -841,49 +898,71 @@ function resolveLimits(scope) {
|
|
|
841
898
|
return { ...DEFAULT_CONTRACT_LIMITS, ...(scope.limits ?? {}) };
|
|
842
899
|
}
|
|
843
900
|
function resolveTransactions(model, scopeName, scope, diagnostics) {
|
|
844
|
-
const requested = scope.
|
|
901
|
+
const requested = scope.transactionsStyle ?? model.transactions;
|
|
845
902
|
if (requested === "interactive" && model.transactions === "none") {
|
|
846
|
-
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'));
|
|
847
904
|
}
|
|
848
905
|
return requested;
|
|
849
906
|
}
|
|
850
907
|
function semanticObject(contract) {
|
|
851
908
|
return JSON.parse(canonicalizeContract(contract));
|
|
852
909
|
}
|
|
853
|
-
function
|
|
910
|
+
function exposureStyle(layer) {
|
|
911
|
+
return layer.exposed === false ? "pick" : "omit";
|
|
912
|
+
}
|
|
913
|
+
function projectCore(model, config, diagnostics) {
|
|
914
|
+
const scope = config.root;
|
|
854
915
|
const providerBaseline = createBaseline(model);
|
|
855
916
|
const baseline = cloneValue(providerBaseline);
|
|
856
917
|
baseline.limits = resolveLimits(scope);
|
|
857
918
|
baseline.transactions = resolveTransactions(model, "root", scope, diagnostics);
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
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];
|
|
866
933
|
const baseline = cloneValue(core.baseline);
|
|
867
934
|
baseline.limits = resolveLimits(scope);
|
|
868
|
-
baseline.transactions = resolveTransactions(model, scopeName, scope,
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
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) {
|
|
881
960
|
for (const [resourceName, resource] of Object.entries(contract.resources)) {
|
|
882
961
|
for (const [fieldName, field] of Object.entries(resource.fields)) {
|
|
883
962
|
if (field.kind !== "scalar" || field.json === undefined) {
|
|
884
963
|
continue;
|
|
885
964
|
}
|
|
886
|
-
const shape = jsonShapeOf(
|
|
965
|
+
const shape = jsonShapeOf(definitions[resourceName]?.[fieldName]?.json);
|
|
887
966
|
if (shape === undefined) {
|
|
888
967
|
throw new Error(`the Contract compiler found no shape for shaped field "${resourceName}.${fieldName}"`);
|
|
889
968
|
}
|
|
@@ -894,37 +973,65 @@ function bindShapedFields(contract, fields) {
|
|
|
894
973
|
export async function compileContracts(model, config) {
|
|
895
974
|
const compilerConfig = config;
|
|
896
975
|
const diagnostics = [];
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
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);
|
|
900
987
|
if (diagnostics.length > 0) {
|
|
901
988
|
return { ok: false, diagnostics };
|
|
902
989
|
}
|
|
903
990
|
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
|
-
}
|
|
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
|
+
}
|
|
922
1009
|
}
|
|
923
|
-
bindShapedFields(core, compilerConfig.
|
|
924
|
-
bindShapedFields(application, compilerConfig.
|
|
925
|
-
bindShapedFields(client, compilerConfig.
|
|
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);
|
|
926
1018
|
return {
|
|
927
1019
|
ok: true,
|
|
928
|
-
contracts: { core, application, client },
|
|
1020
|
+
contracts: { core, application, client, agent },
|
|
929
1021
|
};
|
|
930
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
|
+
}
|