@aventara/core 1.0.0-rc.1 → 1.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +295 -1
- package/ERRORS.md +142 -0
- package/README.md +24 -1
- package/dist/adapters/adapter-discovery-diagnostic.guidance.d.ts +6 -0
- package/dist/adapters/adapter-discovery-diagnostic.guidance.js +7 -0
- package/dist/adapters/adapter-field.d.ts +14 -0
- package/dist/adapters/adapter-model.d.ts +9 -0
- package/dist/adapters/adapter.interface.d.ts +10 -0
- package/dist/compiler/contract-compiler-diagnostic.guidance.d.ts +6 -0
- package/dist/compiler/contract-compiler-diagnostic.guidance.js +97 -0
- package/dist/compiler/contract.compiler.js +94 -6
- package/dist/config/computed-fields.d.ts +62 -0
- package/dist/config/computed-fields.js +80 -0
- package/dist/config/config-diagnostic.guidance.d.ts +6 -0
- package/dist/config/config-diagnostic.guidance.js +32 -0
- package/dist/config/define-resource.d.ts +1 -1
- package/dist/config/exact-framework-config.d.ts +48 -20
- package/dist/config/field-compute.resolver.d.ts +2 -0
- package/dist/config/field-compute.resolver.js +8 -0
- package/dist/config/framework-config.d.ts +7 -6
- package/dist/config/framework-resources-config.d.ts +18 -12
- package/dist/config/pipeline-config.d.ts +4 -4
- package/dist/contracts/capability.d.ts +10 -1
- package/dist/contracts/capability.js +2 -0
- package/dist/contracts/client-contract-structure.validator.d.ts +65 -35
- package/dist/contracts/client-contract-structure.validator.js +4 -2
- package/dist/contracts/contract-canonicalizer.d.ts +7 -0
- package/dist/contracts/contract-canonicalizer.js +18 -4
- package/dist/contracts/field.d.ts +30 -1
- package/dist/framework/contract-view.d.ts +4 -4
- package/dist/framework/framework-construction.error.d.ts +12 -3
- package/dist/framework/framework-construction.error.js +5 -1
- package/dist/framework/framework.factory.d.ts +1 -1
- package/dist/framework/framework.factory.js +9 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/internal.d.ts +2 -0
- package/dist/internal.js +1 -0
- package/dist/operations/database-refusal.d.ts +51 -0
- package/dist/operations/database-refusal.js +45 -0
- package/dist/operations/operation-arguments.d.ts +102 -31
- package/dist/operations/operation-validator-core.js +86 -4
- package/dist/operations/validation-code.guidance.d.ts +6 -0
- package/dist/operations/validation-code.guidance.js +90 -0
- package/dist/operations/write-scope.resolver.d.ts +4 -0
- package/dist/operations/write-scope.resolver.js +2 -0
- package/dist/protocol/protocol.handler.js +55 -16
- package/dist/runtime/caller-data.copier.d.ts +17 -0
- package/dist/runtime/caller-data.copier.js +18 -1
- package/dist/runtime/caller-data.door.d.ts +11 -5
- package/dist/runtime/computed-read-runtime.js +15 -2
- package/dist/runtime/computed-value.error.d.ts +3 -2
- package/dist/runtime/computed-value.error.js +3 -2
- package/dist/runtime/create-requirement.d.ts +41 -5
- package/dist/runtime/create-requirement.js +137 -20
- package/dist/runtime/diagnostics-reporter.d.ts +2 -1
- package/dist/runtime/error-boundary.js +4 -0
- package/dist/runtime/error-guidance.d.ts +12 -0
- package/dist/runtime/error-guidance.js +1 -0
- package/dist/runtime/error.catalog.d.ts +49 -0
- package/dist/runtime/error.catalog.js +49 -0
- package/dist/runtime/field-stage.runtime.d.ts +2 -0
- package/dist/runtime/field-stage.runtime.js +8 -4
- package/dist/runtime/field-validator.runtime.d.ts +9 -1
- package/dist/runtime/field-validator.runtime.js +11 -6
- package/dist/runtime/json-value.d.ts +10 -5
- package/dist/runtime/level-lifecycle.runtime.d.ts +2 -2
- package/dist/runtime/level-lifecycle.runtime.js +2 -5
- package/dist/runtime/mutation-planner.js +3 -14
- package/dist/runtime/operation-code.guidance.d.ts +6 -0
- package/dist/runtime/operation-code.guidance.js +167 -0
- package/dist/runtime/operation-runtime.d.ts +20 -6
- package/dist/runtime/operation-runtime.js +77 -35
- package/dist/runtime/owned-execution.registry.d.ts +12 -2
- package/dist/runtime/pipe-output.validator.d.ts +10 -0
- package/dist/runtime/pipe-output.validator.js +4 -2
- package/dist/runtime/pipeline.d.ts +35 -8
- package/dist/runtime/readonly-value.d.ts +14 -3
- package/dist/runtime/readonly-value.js +10 -1
- package/dist/runtime/scalar-runtime.d.ts +5 -1
- package/dist/runtime/scalar-runtime.js +86 -16
- package/dist/runtime/unsent-value.normalizer.d.ts +15 -0
- package/dist/runtime/unsent-value.normalizer.js +128 -0
- package/dist/transactions/operation-fingerprint.js +2 -2
- package/dist/transactions/sql-statement.d.ts +8 -0
- package/dist/transactions/sql-statement.validator.js +36 -3
- package/dist/transactions/transaction-plan-assembler.d.ts +1 -1
- package/dist/transactions/transaction-plan-assembler.js +33 -10
- package/dist/transactions/transaction-plan-validator.d.ts +16 -0
- package/dist/transactions/transaction-plan-validator.js +27 -1
- package/dist/transactions/transaction-reference-validator.d.ts +42 -0
- package/dist/transactions/transaction-reference-validator.js +82 -56
- package/dist/transactions/transaction-reference.js +5 -0
- package/dist/transactions/transaction-runtime.js +47 -22
- package/dist/transactions/transaction-stage.runtime.d.ts +2 -1
- package/dist/transactions/transaction-stage.runtime.js +7 -4
- package/package.json +4 -3
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AvConfigComputedFields } from "../config/computed-fields.js";
|
|
1
2
|
import type { ReferentialActionTable } from "../runtime/referential-action.js";
|
|
2
3
|
import type { AvAdapterModel } from "./adapter-model.js";
|
|
3
4
|
import type { AvAdapterOperationExecutor } from "./adapter-operation-executor.interface.js";
|
|
@@ -30,6 +31,15 @@ export interface AvAdapter<M extends AvAdapterModel = AvAdapterModel> extends Av
|
|
|
30
31
|
* or `$set` runs the target's `update` guards.
|
|
31
32
|
*/
|
|
32
33
|
readonly referentialActions?: ReferentialActionTable;
|
|
34
|
+
/**
|
|
35
|
+
* Optional: which fields the configuration's write compute fills, as the
|
|
36
|
+
* Adapter's generate step read them when it wrote the model's type (the same
|
|
37
|
+
* statement as the type's `"~computedFields"`). When the Framework is
|
|
38
|
+
* constructed it is compared with the configuration, and a difference is a
|
|
39
|
+
* warning that the generated types are stale (`aventara:prepare` regenerates
|
|
40
|
+
* them); it changes no behaviour. Never part of a Contract or its hash.
|
|
41
|
+
*/
|
|
42
|
+
readonly computedFields?: AvConfigComputedFields;
|
|
33
43
|
/**
|
|
34
44
|
* Optional: `true` when the Adapter runs developer SQL statements: standalone
|
|
35
45
|
* through its own {@link executeSql}, and, when it has `transaction`, inside a
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ErrorCodeDescription } from "../runtime/error-guidance.js";
|
|
2
|
+
import type { AvContractCompilerDiagnosticCode } from "./contract-compiler-diagnostic.js";
|
|
3
|
+
/** Meaning, cause and fix of every contract-compilation diagnostic code. */
|
|
4
|
+
export declare const CONTRACT_COMPILER_DIAGNOSTIC_GUIDANCE: {
|
|
5
|
+
readonly [C in AvContractCompilerDiagnosticCode]: ErrorCodeDescription;
|
|
6
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export const CONTRACT_COMPILER_DIAGNOSTIC_GUIDANCE = {
|
|
2
|
+
COMPILER_UNKNOWN_RESOURCE: {
|
|
3
|
+
message: "Unknown Resource.",
|
|
4
|
+
cause: "A Resource name the adapter's model does not have, in resources or in exemptions.",
|
|
5
|
+
fix: "Use the model's exact name. After a schema change run `npm run aventara:prepare`.",
|
|
6
|
+
},
|
|
7
|
+
COMPILER_UNKNOWN_FIELD: {
|
|
8
|
+
message: "Unknown field.",
|
|
9
|
+
cause: "A field that is neither in the schema nor defined at the root.",
|
|
10
|
+
fix: "Fix the name, run aventara:prepare after a schema change, or add the field with a root definition.",
|
|
11
|
+
},
|
|
12
|
+
COMPILER_RELATION_FIELD_CONFIG_UNSUPPORTED: {
|
|
13
|
+
message: "A relation field takes write guards only.",
|
|
14
|
+
cause: "Field pipes or compute on a relation field.",
|
|
15
|
+
fix: "Put the stage on the relation's reference field, or use an operation pipe.",
|
|
16
|
+
},
|
|
17
|
+
COMPILER_INCOMPLETE_FIELD: {
|
|
18
|
+
message: "An added field is incomplete.",
|
|
19
|
+
cause: "A definition that adds a field leaves out type, nullable or list.",
|
|
20
|
+
fix: 'Spell out type, nullable and list, with lifecycle: ["VIRTUAL"] for a field that has no column.',
|
|
21
|
+
},
|
|
22
|
+
COMPILER_STRUCTURAL_FIELD_OVERRIDE: {
|
|
23
|
+
message: "A discovered field's structure was redefined.",
|
|
24
|
+
cause: "A definition on a field the adapter discovered changes its type, format or capabilities.",
|
|
25
|
+
fix: "Restate only its type, nullable and list. Change capabilities through exemptions and the column through the schema.",
|
|
26
|
+
},
|
|
27
|
+
COMPILER_MISSING_SCALAR: {
|
|
28
|
+
message: "Unknown scalar type.",
|
|
29
|
+
cause: "A definition names a scalar type that does not exist.",
|
|
30
|
+
fix: "Use a built-in scalar: string, boolean, int, float, bigint, decimal, datetime, bytes or json.",
|
|
31
|
+
},
|
|
32
|
+
COMPILER_MISSING_ENUM: {
|
|
33
|
+
message: "Unknown enum.",
|
|
34
|
+
cause: "A definition names an enum the model does not have.",
|
|
35
|
+
fix: "Use an enum of the schema, and run aventara:prepare after adding one.",
|
|
36
|
+
},
|
|
37
|
+
COMPILER_DANGLING_RELATION: {
|
|
38
|
+
message: "A visible relation points at a hidden Resource.",
|
|
39
|
+
cause: "A layer hides a Resource but still exposes a relation field that targets it.",
|
|
40
|
+
fix: "Hide the relation field too (`exemptions.resources.<R>.fields.<relation>: true`), or expose the target.",
|
|
41
|
+
},
|
|
42
|
+
COMPILER_UNAVAILABLE_OPERATION: {
|
|
43
|
+
message: "Operation not available.",
|
|
44
|
+
cause: "An operation the adapter does not offer (such as delete.many), or an overlay granting one that its parent hid.",
|
|
45
|
+
fix: "Remove it. To reach an operation from one overlay only, expose it at the root and hide it in the other overlay.",
|
|
46
|
+
},
|
|
47
|
+
COMPILER_UNAVAILABLE_CAPABILITY: {
|
|
48
|
+
message: "Capability not available.",
|
|
49
|
+
cause: "A capability or token the field does not offer, or an overlay (or the agent layer) granting one its parent hid. Overlays only narrow.",
|
|
50
|
+
fix: "Remove it, or expose it in the parent layer and hide it in the layer it must not reach.",
|
|
51
|
+
},
|
|
52
|
+
COMPILER_INVALID_LIFECYCLE: {
|
|
53
|
+
message: "Invalid field lifecycle.",
|
|
54
|
+
cause: "An added field declares a lifecycle other than VIRTUAL, a VIRTUAL field claims a database-generated lifecycle or a provider-side capability, or a definition redefines a discovered field's lifecycle.",
|
|
55
|
+
fix: 'Give an added field lifecycle: ["VIRTUAL"] and only the capabilities it can serve; computed lifecycles come from compute stages.',
|
|
56
|
+
},
|
|
57
|
+
COMPILER_MISSING_READ_COMPUTE: {
|
|
58
|
+
message: "A selectable virtual field has no compute.read.",
|
|
59
|
+
cause: "A VIRTUAL field that can be selected has no pipelines.compute.read stage.",
|
|
60
|
+
fix: "Add a compute.read stage, or make the field write-only with capabilities.select: false.",
|
|
61
|
+
},
|
|
62
|
+
COMPILER_INVALID_COMPUTE_DEPENDENCY: {
|
|
63
|
+
message: "Invalid compute.read dependency.",
|
|
64
|
+
cause: "A dependsOn entry that is not a stored scalar field of the same Resource.",
|
|
65
|
+
fix: "List only stored scalar fields of the Resource in dependsOn.",
|
|
66
|
+
},
|
|
67
|
+
COMPILER_RESERVED_RESOURCE_NAME: {
|
|
68
|
+
message: "Reserved Resource name.",
|
|
69
|
+
cause: "A model name starts with $, which is reserved for the framework's own names (ctx.$sql, $ref).",
|
|
70
|
+
fix: "Rename the model; the table it maps to can keep its name.",
|
|
71
|
+
},
|
|
72
|
+
COMPILER_MISSING_IDENTIFIER: {
|
|
73
|
+
message: "A unique operation has no identifier.",
|
|
74
|
+
cause: "A layer exposes a unique variant (find.unique, update.unique, delete.unique, upsert.unique) and no complete identifier of the Resource: a Pick that did not pick one, or an Omit that hid every one.",
|
|
75
|
+
fix: "Expose an identifier field (id, or every field of one unique), or exempt the unique variants.",
|
|
76
|
+
},
|
|
77
|
+
COMPILER_HIDDEN_REQUIRED_FIELD: {
|
|
78
|
+
message: "A layer that creates a Resource hides a required field.",
|
|
79
|
+
cause: "A layer still creates the Resource (a create or upsert variant, or a nested create) while a required field, or a required relation's reference field, is hidden or has its create capability exempted, and no compute.create fills it.",
|
|
80
|
+
fix: "Expose the field, give it a compute.create stage, or exempt the layer's create operations.",
|
|
81
|
+
},
|
|
82
|
+
COMPILER_INVALID_VALIDATOR: {
|
|
83
|
+
message: "A field validator cannot apply.",
|
|
84
|
+
cause: "A validator that is not built with AvZ, does not fit the field's type (AvZ.maxLength on an int, AvZ.maxItems on a field that is not a list), or sits on a relation or Json field.",
|
|
85
|
+
fix: "Use an AvZ check that fits the field's type; a Json field's checks go in its json shape.",
|
|
86
|
+
},
|
|
87
|
+
COMPILER_INVALID_JSON_SHAPE: {
|
|
88
|
+
message: "A Json shape cannot apply.",
|
|
89
|
+
cause: "A json shape on a field that is not a discovered Json field, a shape not built with AvZ, or one that admits null or absence at its top level.",
|
|
90
|
+
fix: "Put the shape on a Json field, build it with AvZ, and keep its top level non-null (the column's nullability decides null).",
|
|
91
|
+
},
|
|
92
|
+
COMPILER_INVALID_CLIENT_CONTRACT: {
|
|
93
|
+
message: "The compiled client contract is invalid.",
|
|
94
|
+
cause: "The finished client contract failed its own structural check: a framework defect.",
|
|
95
|
+
fix: "Report it with the diagnostic's path and message.",
|
|
96
|
+
},
|
|
97
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { bindShapedField, jsonShapeOf } from "../avz/json-shape.registry.js";
|
|
2
|
-
import { PROJECTION_SCOPES, WRITE_SCOPES } from "../contracts/capability.js";
|
|
2
|
+
import { FILTER_MODES, PROJECTION_SCOPES, WRITE_SCOPES, } from "../contracts/capability.js";
|
|
3
3
|
import { validateClientContractStructure } from "../contracts/client-contract-structure.validator.js";
|
|
4
4
|
import { canonicalizeContract, canonicalizeContractMembers, canonicalizeJson, joinCanonicalContractMembers, recordCanonicalContractText, } from "../contracts/contract-canonicalizer.js";
|
|
5
5
|
import { hashCanonicalContractText, } from "../contracts/contract-hash.js";
|
|
@@ -41,6 +41,12 @@ function normalizeAdapterScalarCapabilities(capabilities) {
|
|
|
41
41
|
...(capabilities.filter !== undefined
|
|
42
42
|
? { filter: [...capabilities.filter] }
|
|
43
43
|
: {}),
|
|
44
|
+
...(capabilities.filterModes !== undefined
|
|
45
|
+
? { filterModes: [...capabilities.filterModes] }
|
|
46
|
+
: {}),
|
|
47
|
+
...(capabilities.pathFilterModes !== undefined
|
|
48
|
+
? { pathFilterModes: [...capabilities.pathFilterModes] }
|
|
49
|
+
: {}),
|
|
44
50
|
...(capabilities.order !== undefined ? { order: capabilities.order } : {}),
|
|
45
51
|
...(capabilities.orderNulls !== undefined
|
|
46
52
|
? { orderNulls: capabilities.orderNulls }
|
|
@@ -71,8 +77,42 @@ function cloneAdapterField(field) {
|
|
|
71
77
|
cardinality: field.cardinality,
|
|
72
78
|
nullable: field.nullable,
|
|
73
79
|
capabilities: cloneValue(field.capabilities),
|
|
80
|
+
...(typeof field.inverse === "string" ? { inverse: field.inverse } : {}),
|
|
81
|
+
...(Array.isArray(field.referenceFields) && field.referenceFields.length > 0
|
|
82
|
+
? { referenceFields: [...field.referenceFields] }
|
|
83
|
+
: {}),
|
|
74
84
|
};
|
|
75
85
|
}
|
|
86
|
+
function pruneHiddenPairings(projection) {
|
|
87
|
+
const resources = projection.resources;
|
|
88
|
+
for (const [name, resource] of Object.entries(resources)) {
|
|
89
|
+
for (const field of Object.values(resource.fields)) {
|
|
90
|
+
if (field.kind !== "relation") {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (Array.isArray(field.referenceFields)) {
|
|
94
|
+
const shown = field.referenceFields.filter((reference) => typeof reference === "string" &&
|
|
95
|
+
Object.hasOwn(resource.fields, reference) &&
|
|
96
|
+
resource.fields[reference]?.kind === "scalar");
|
|
97
|
+
if (shown.length > 0) {
|
|
98
|
+
field.referenceFields = shown;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
delete field.referenceFields;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (typeof field.inverse !== "string") {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const other = Object.hasOwn(resources, String(field.target))
|
|
108
|
+
? resources[String(field.target)]?.fields[field.inverse]
|
|
109
|
+
: undefined;
|
|
110
|
+
if (other?.kind !== "relation" || other.target !== name) {
|
|
111
|
+
delete field.inverse;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
76
116
|
function cloneOperations(operations) {
|
|
77
117
|
return cloneValue(operations);
|
|
78
118
|
}
|
|
@@ -86,8 +126,40 @@ function cloneAdapterResource(resource) {
|
|
|
86
126
|
operations: cloneOperations(resource.operations),
|
|
87
127
|
};
|
|
88
128
|
}
|
|
129
|
+
function canonicalModes(modes) {
|
|
130
|
+
const stated = Array.isArray(modes) ? modes : [];
|
|
131
|
+
const known = FILTER_MODES.filter((mode) => mode === "default" || stated.includes(mode));
|
|
132
|
+
const unknown = stated.filter((mode) => !FILTER_MODES.includes(mode));
|
|
133
|
+
return [...known, ...unknown];
|
|
134
|
+
}
|
|
135
|
+
function qualifyFilterModes(projection) {
|
|
136
|
+
for (const resource of Object.values(projection.resources)) {
|
|
137
|
+
for (const field of Object.values(resource.fields)) {
|
|
138
|
+
if (field.kind !== "scalar") {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const capabilities = field.capabilities;
|
|
142
|
+
const filter = Array.isArray(capabilities.filter)
|
|
143
|
+
? capabilities.filter
|
|
144
|
+
: [];
|
|
145
|
+
if (!filter.includes("mode")) {
|
|
146
|
+
delete capabilities.filterModes;
|
|
147
|
+
delete capabilities.pathFilterModes;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const stated = capabilities.filterModes;
|
|
151
|
+
capabilities.filterModes = canonicalModes(stated);
|
|
152
|
+
if (filter.includes("path")) {
|
|
153
|
+
capabilities.pathFilterModes = canonicalModes(capabilities.pathFilterModes ?? stated);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
delete capabilities.pathFilterModes;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
89
161
|
function createBaseline(model) {
|
|
90
|
-
|
|
162
|
+
const baseline = {
|
|
91
163
|
scalars: Object.fromEntries(Object.entries(model.scalars).map(([key, value]) => [
|
|
92
164
|
key,
|
|
93
165
|
cloneValue(value),
|
|
@@ -106,6 +178,8 @@ function createBaseline(model) {
|
|
|
106
178
|
? {}
|
|
107
179
|
: { referenceWrites: model.referenceWrites }),
|
|
108
180
|
};
|
|
181
|
+
qualifyFilterModes(baseline);
|
|
182
|
+
return baseline;
|
|
109
183
|
}
|
|
110
184
|
function definitionPath(resource, field) {
|
|
111
185
|
return `root.resources.${resource}.fields.${field}.definition`;
|
|
@@ -349,9 +423,14 @@ function checkReservedResourceNames(model, diagnostics) {
|
|
|
349
423
|
}
|
|
350
424
|
}
|
|
351
425
|
}
|
|
426
|
+
function isAdapterStated(axis) {
|
|
427
|
+
return axis?.kind === "limit" || axis?.kind === "adapterTokens";
|
|
428
|
+
}
|
|
352
429
|
const SCALAR_CAPABILITY_AXES = {
|
|
353
430
|
select: { kind: "flag" },
|
|
354
431
|
filter: { kind: "tokens" },
|
|
432
|
+
filterModes: { kind: "adapterTokens", values: FILTER_MODES },
|
|
433
|
+
pathFilterModes: { kind: "adapterTokens", values: FILTER_MODES },
|
|
355
434
|
order: { kind: "flag" },
|
|
356
435
|
orderNulls: { kind: "flag" },
|
|
357
436
|
create: { kind: "tokens" },
|
|
@@ -445,11 +524,11 @@ function fieldName(resource, field) {
|
|
|
445
524
|
}
|
|
446
525
|
function exemptableAxis(walk, universeField, capability, label, path) {
|
|
447
526
|
const axis = capabilityAxisFor(universeField.kind, capability);
|
|
448
|
-
if (axis === undefined || axis
|
|
527
|
+
if (axis === undefined || isAdapterStated(axis)) {
|
|
449
528
|
const names = Object.entries(universeField.kind === "scalar"
|
|
450
529
|
? SCALAR_CAPABILITY_AXES
|
|
451
530
|
: RELATION_CAPABILITY_AXES)
|
|
452
|
-
.filter(([, entry]) => entry
|
|
531
|
+
.filter(([, entry]) => !isAdapterStated(entry))
|
|
453
532
|
.map(([name]) => name);
|
|
454
533
|
unavailableCapability(path, `"${capability}" is not a capability a ${universeField.kind} field's exemption can name; it takes ${names.join(", ")}`, walk.diagnostics);
|
|
455
534
|
return undefined;
|
|
@@ -533,7 +612,7 @@ function exemptField(walk, universeField, parentField, entry, label, path) {
|
|
|
533
612
|
const result = walk.style === "omit" ? cloneValue(parentCapabilities) : {};
|
|
534
613
|
if (walk.style === "pick") {
|
|
535
614
|
for (const [key, value] of Object.entries(parentCapabilities)) {
|
|
536
|
-
if (capabilityAxisFor(universeField.kind, key)
|
|
615
|
+
if (isAdapterStated(capabilityAxisFor(universeField.kind, key))) {
|
|
537
616
|
result[key] = cloneValue(value);
|
|
538
617
|
}
|
|
539
618
|
}
|
|
@@ -552,7 +631,7 @@ function exemptField(walk, universeField, parentField, entry, label, path) {
|
|
|
552
631
|
return undefined;
|
|
553
632
|
}
|
|
554
633
|
if (walk.style === "pick" &&
|
|
555
|
-
!Object.keys(result).some((key) => capabilityAxisFor(universeField.kind, key)
|
|
634
|
+
!Object.keys(result).some((key) => !isAdapterStated(capabilityAxisFor(universeField.kind, key)))) {
|
|
556
635
|
return undefined;
|
|
557
636
|
}
|
|
558
637
|
return { ...cloneValue(parentField), capabilities: result };
|
|
@@ -839,6 +918,11 @@ function representationFault(axis, value) {
|
|
|
839
918
|
return typeof value === "string" && axis.values.includes(value)
|
|
840
919
|
? undefined
|
|
841
920
|
: `one of ${axis.values.join(", ")} is expected`;
|
|
921
|
+
case "adapterTokens":
|
|
922
|
+
return isTokenList(value) &&
|
|
923
|
+
value.every((token) => axis.values.includes(token))
|
|
924
|
+
? undefined
|
|
925
|
+
: `a list of ${axis.values.join(", ")} is expected`;
|
|
842
926
|
case "flag":
|
|
843
927
|
return typeof value === "boolean" ? undefined : "a boolean is expected";
|
|
844
928
|
case "tokens":
|
|
@@ -922,6 +1006,8 @@ function projectCore(model, config, diagnostics) {
|
|
|
922
1006
|
checkLayerValidators(baseline.resources, "root", config.trees.root, diagnostics);
|
|
923
1007
|
const validators = compileValidatorTable(baseline.resources, scope.pipelines);
|
|
924
1008
|
const projection = projectExposure({ layer: "root", style: exposureStyle(scope), diagnostics }, baseline, baseline, scope.exemptions);
|
|
1009
|
+
qualifyFilterModes(projection);
|
|
1010
|
+
pruneHiddenPairings(projection);
|
|
925
1011
|
projectCreateRequirements(model, projection.resources);
|
|
926
1012
|
checkHiddenRequirements(model, "root", baseline.resources, baseline.resources, projection.resources, diagnostics);
|
|
927
1013
|
applyValidators(projection.resources, validators);
|
|
@@ -950,6 +1036,8 @@ function projectDerivedScope(model, scopeName, core, parentView, config, diagnos
|
|
|
950
1036
|
diagnostics,
|
|
951
1037
|
...(scopeName === "agent" ? { parent: config.agent.parent } : {}),
|
|
952
1038
|
}, baseline, parent, scope.exemptions);
|
|
1039
|
+
qualifyFilterModes(projection);
|
|
1040
|
+
pruneHiddenPairings(projection);
|
|
953
1041
|
projectCreateRequirements(model, projection.resources);
|
|
954
1042
|
checkHiddenRequirements(model, scopeName, baseline.resources, parent.resources, projection.resources, diagnostics);
|
|
955
1043
|
applyValidators(projection.resources, validators);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { AvConfigAgentParent, AvConfigLayerName } from "./config-layer.js";
|
|
2
|
+
/** The fields one layer's write compute fills, per Resource and phase. */
|
|
3
|
+
type LayerComputedFields = Readonly<Record<string, {
|
|
4
|
+
/** Fields with a `compute.create` stage, sorted. */
|
|
5
|
+
readonly create?: readonly string[];
|
|
6
|
+
/** Fields with a `compute.update` stage, sorted. */
|
|
7
|
+
readonly update?: readonly string[];
|
|
8
|
+
}>>;
|
|
9
|
+
/**
|
|
10
|
+
* Which fields a configuration's write compute fills: per layer, the
|
|
11
|
+
* Resources, and per Resource the fields with a `compute.create` and a
|
|
12
|
+
* `compute.update` stage, each list sorted; and the layer the agent layer
|
|
13
|
+
* extends. Written by an adapter's generate step from the configuration's type,
|
|
14
|
+
* and compared with the configuration at startup.
|
|
15
|
+
*/
|
|
16
|
+
export type AvConfigComputedFields = {
|
|
17
|
+
readonly agent: LayerComputedFields;
|
|
18
|
+
/** The agent layer's `extends`: `"client"` when the configuration leaves it out. */
|
|
19
|
+
readonly agentExtends: AvConfigAgentParent;
|
|
20
|
+
readonly application: LayerComputedFields;
|
|
21
|
+
readonly client: LayerComputedFields;
|
|
22
|
+
readonly root: LayerComputedFields;
|
|
23
|
+
};
|
|
24
|
+
/** The layers whose compute may have filled a field when a stage of layer `L` runs. */
|
|
25
|
+
type ComputingLayers<Facts extends AvConfigComputedFields, L extends AvConfigLayerName> = L extends "root" ? AvConfigLayerName : L extends "agent" ? "root" | "agent" | Exclude<Facts["agentExtends"], "root"> : "root" | L | (Facts["agentExtends"] extends L ? "agent" : never);
|
|
26
|
+
/** `"<Resource>.<field>"` for every field a layer's `compute.create` fills. */
|
|
27
|
+
type CreateComputedOf<Layer> = Layer extends object ? {
|
|
28
|
+
[R in keyof Layer]: Layer[R] extends {
|
|
29
|
+
readonly create?: readonly (infer Field extends string)[];
|
|
30
|
+
} ? `${R & string}.${Field}` : never;
|
|
31
|
+
}[keyof Layer] : never;
|
|
32
|
+
/**
|
|
33
|
+
* The create fields a variant's guards, pipes and filters of layer `L` may be
|
|
34
|
+
* told without, as `"<Resource>.<field>"`: those a layer whose compute runs for
|
|
35
|
+
* the stage's origins fills on create. `string` (every field) when the model's
|
|
36
|
+
* type carries no statement of them.
|
|
37
|
+
*/
|
|
38
|
+
export type AvPreComputeLift<M, L extends AvConfigLayerName> = M extends {
|
|
39
|
+
readonly "~computedFields"?: infer Facts;
|
|
40
|
+
} ? Exclude<Facts, undefined> extends infer Stated extends AvConfigComputedFields ? string extends keyof Stated["root"] ? string : CreateComputedOf<Stated[ComputingLayers<Stated, L>]> : string : string;
|
|
41
|
+
/** The configuration's layers' `resources` trees and the agent's parent. */
|
|
42
|
+
export type ComputedFieldsSource = {
|
|
43
|
+
readonly trees: Readonly<Record<AvConfigLayerName, unknown>>;
|
|
44
|
+
readonly agentExtends: AvConfigAgentParent;
|
|
45
|
+
};
|
|
46
|
+
/** The computed fields of a configuration, in the form a generate step writes them. */
|
|
47
|
+
export declare function computedFieldsOf(source: ComputedFieldsSource): AvConfigComputedFields;
|
|
48
|
+
/**
|
|
49
|
+
* How the configuration's computed fields differ from the ones the generated
|
|
50
|
+
* types were written with, one sentence per difference; empty when they agree.
|
|
51
|
+
*/
|
|
52
|
+
export declare function computedFieldsDrift(generated: AvConfigComputedFields, configured: AvConfigComputedFields): string[];
|
|
53
|
+
/** The warning's code, as `process.emitWarning` reports it. */
|
|
54
|
+
export declare const STALE_GENERATED_TYPES_WARNING = "AVENTARA_STALE_GENERATED_TYPES";
|
|
55
|
+
/**
|
|
56
|
+
* Warns, through `process.emitWarning` (code
|
|
57
|
+
* {@link STALE_GENERATED_TYPES_WARNING}), when the generated types' computed
|
|
58
|
+
* fields differ from the configuration's. Nothing to compare (an Adapter that
|
|
59
|
+
* carries none) warns nothing.
|
|
60
|
+
*/
|
|
61
|
+
export declare function warnOnStaleComputedFields(generated: AvConfigComputedFields | undefined, configured: AvConfigComputedFields): void;
|
|
62
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { computeOfLayer } from "./field-compute.resolver.js";
|
|
2
|
+
function layerComputedFields(resources) {
|
|
3
|
+
const result = {};
|
|
4
|
+
const compute = computeOfLayer(resources);
|
|
5
|
+
for (const resource of Object.keys(compute).sort()) {
|
|
6
|
+
const fields = compute[resource] ?? {};
|
|
7
|
+
const create = Object.keys(fields)
|
|
8
|
+
.filter((field) => fields[field]?.create !== undefined)
|
|
9
|
+
.sort();
|
|
10
|
+
const update = Object.keys(fields)
|
|
11
|
+
.filter((field) => fields[field]?.update !== undefined)
|
|
12
|
+
.sort();
|
|
13
|
+
if (create.length === 0 && update.length === 0) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
result[resource] = {
|
|
17
|
+
...(create.length === 0 ? {} : { create }),
|
|
18
|
+
...(update.length === 0 ? {} : { update }),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
export function computedFieldsOf(source) {
|
|
24
|
+
return {
|
|
25
|
+
agent: layerComputedFields(source.trees.agent),
|
|
26
|
+
agentExtends: source.agentExtends,
|
|
27
|
+
application: layerComputedFields(source.trees.application),
|
|
28
|
+
client: layerComputedFields(source.trees.client),
|
|
29
|
+
root: layerComputedFields(source.trees.root),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const LAYERS = ["root", "application", "client", "agent"];
|
|
33
|
+
const PHASES = ["create", "update"];
|
|
34
|
+
function missingFrom(left, right) {
|
|
35
|
+
const missing = [];
|
|
36
|
+
for (const layer of LAYERS) {
|
|
37
|
+
for (const [resource, phases] of Object.entries(left[layer])) {
|
|
38
|
+
for (const phase of PHASES) {
|
|
39
|
+
for (const field of phases[phase] ?? []) {
|
|
40
|
+
if (!right[layer][resource]?.[phase]?.includes(field)) {
|
|
41
|
+
missing.push(`${layer} ${resource}.${field} (${phase})`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return missing;
|
|
48
|
+
}
|
|
49
|
+
export function computedFieldsDrift(generated, configured) {
|
|
50
|
+
const drift = [
|
|
51
|
+
...missingFrom(configured, generated).map((entry) => `computed in the configuration, not in the types: ${entry}`),
|
|
52
|
+
...missingFrom(generated, configured).map((entry) => `computed in the types, not in the configuration: ${entry}`),
|
|
53
|
+
];
|
|
54
|
+
if (generated.agentExtends !== configured.agentExtends) {
|
|
55
|
+
drift.push(`the agent layer extends "${configured.agentExtends}", the types say "${generated.agentExtends}"`);
|
|
56
|
+
}
|
|
57
|
+
return drift;
|
|
58
|
+
}
|
|
59
|
+
export const STALE_GENERATED_TYPES_WARNING = "AVENTARA_STALE_GENERATED_TYPES";
|
|
60
|
+
export function warnOnStaleComputedFields(generated, configured) {
|
|
61
|
+
if (generated === undefined) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const drift = computedFieldsDrift(generated, configured);
|
|
65
|
+
if (drift.length === 0) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const message = `generated types are stale: run aventara:prepare. The computed fields the pre-compute stages are typed with differ from the configuration's:\n${drift
|
|
69
|
+
.map((line) => ` - ${line}`)
|
|
70
|
+
.join("\n")}`;
|
|
71
|
+
const emitWarning = globalThis.process?.emitWarning;
|
|
72
|
+
if (typeof emitWarning === "function") {
|
|
73
|
+
emitWarning(message, {
|
|
74
|
+
type: "AventaraWarning",
|
|
75
|
+
code: STALE_GENERATED_TYPES_WARNING,
|
|
76
|
+
});
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
globalThis.console?.warn(`AventaraWarning: ${message}`);
|
|
80
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ErrorCodeDescription } from "../runtime/error-guidance.js";
|
|
2
|
+
import type { AvConfigDiagnosticCode } from "./config-diagnostic.js";
|
|
3
|
+
/** Meaning, cause and fix of every configuration diagnostic code. */
|
|
4
|
+
export declare const CONFIG_DIAGNOSTIC_GUIDANCE: {
|
|
5
|
+
readonly [C in AvConfigDiagnosticCode]: ErrorCodeDescription;
|
|
6
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const CONFIG_DIAGNOSTIC_GUIDANCE = {
|
|
2
|
+
CONFIG_REQUIRED: {
|
|
3
|
+
message: "A required configuration key is missing.",
|
|
4
|
+
cause: "A key that must be present is absent: api, adapter, or an overlay's exposed when the overlay sets exemptions.",
|
|
5
|
+
fix: "Add the key at the diagnostic's path.",
|
|
6
|
+
},
|
|
7
|
+
CONFIG_UNKNOWN_PROPERTY: {
|
|
8
|
+
message: "Unknown configuration key.",
|
|
9
|
+
cause: "A key the configuration does not define at that place: a typo, or a key written at the wrong level of the tree.",
|
|
10
|
+
fix: 'Remove it, or move it where it belongs (the configuration reference\'s "Where a key can go").',
|
|
11
|
+
},
|
|
12
|
+
CONFIG_INVALID_TYPE: {
|
|
13
|
+
message: "A configuration key has the wrong type.",
|
|
14
|
+
cause: "A value of the wrong type, or a key set to an explicit undefined.",
|
|
15
|
+
fix: "Give the value the type the reference states; leave a key out rather than setting it to undefined.",
|
|
16
|
+
},
|
|
17
|
+
CONFIG_INVALID_VALUE: {
|
|
18
|
+
message: "A configuration value is not allowed.",
|
|
19
|
+
cause: 'A malformed api or mcp path, a negative limit, an unknown transactionsStyle, a format that is not a named zod format, or forceTransaction under an effective transactionsStyle "none".',
|
|
20
|
+
fix: "Use a value the message allows.",
|
|
21
|
+
},
|
|
22
|
+
CONFIG_MOVED_PROPERTY: {
|
|
23
|
+
message: "A configuration key moved in rc.1.",
|
|
24
|
+
cause: "Configuration written for 1.0.0-rc.0: restrictions, entrypoint, fields, behaviors, transactions or pipelines.before/after.",
|
|
25
|
+
fix: "Move it where the message says: restrictions to exposed + exemptions, entrypoint to api, fields to `resources.<R>.fields.<f>.definition`, transactions to transactionsStyle, before/after to pipelines.hooks.",
|
|
26
|
+
},
|
|
27
|
+
CONFIG_FORBIDDEN_IN_OVERLAY: {
|
|
28
|
+
message: "A root-only key is written in an overlay.",
|
|
29
|
+
cause: "A field definition in application, client or agent.",
|
|
30
|
+
fix: "Define the field once at the root (`resources.<R>.fields.<f>.definition`); an overlay only chooses its visibility.",
|
|
31
|
+
},
|
|
32
|
+
};
|
|
@@ -18,7 +18,7 @@ type ResourceOverlays = {
|
|
|
18
18
|
};
|
|
19
19
|
/** The overlay blocks `Overlays`, each checked against the root `Block`. */
|
|
20
20
|
type CheckedOverlays<M extends AvAdapterModel, R extends AvConfigResourceKey<M>, Block, Overlays> = {
|
|
21
|
-
readonly [L in keyof Overlays]: L extends OverlayLayer ? NoInfer<AvConfigResource<M, ResourceBlockConfig<R, Block>, R, "overlay"> & ExactOverlayResource<M, ResourceBlockConfig<R, Block>, R, Overlays[L]>> : never;
|
|
21
|
+
readonly [L in keyof Overlays]: L extends OverlayLayer ? NoInfer<AvConfigResource<M, ResourceBlockConfig<R, Block>, R, "overlay", L> & ExactOverlayResource<M, ResourceBlockConfig<R, Block>, R, Overlays[L]>> : never;
|
|
22
22
|
};
|
|
23
23
|
/**
|
|
24
24
|
* What {@link defineResource} returns: `root`, and `application`, `client` and
|
|
@@ -103,9 +103,11 @@ type TokenOf<Member> = Member extends {
|
|
|
103
103
|
} ? {
|
|
104
104
|
[K in keyof Member]: K extends "scopes" ? never : Member[K] extends readonly (infer Token)[] ? Token : never;
|
|
105
105
|
}[keyof Member] : Member extends readonly (infer Token)[] ? Token : never;
|
|
106
|
+
/** The capability members an exemption never names. */
|
|
107
|
+
type AdapterStatedMember = "nestedWriteFilter" | "filterModes" | "pathFilterModes";
|
|
106
108
|
/** A field's exemption against its capabilities: their names, and each one's tokens. */
|
|
107
|
-
type CapabilityExemptionSchema<Caps> = ListOf<Exclude<keyof Caps,
|
|
108
|
-
readonly [K in Exclude<keyof Caps,
|
|
109
|
+
type CapabilityExemptionSchema<Caps> = ListOf<Exclude<keyof Caps, AdapterStatedMember>> & {
|
|
110
|
+
readonly [K in Exclude<keyof Caps, AdapterStatedMember>]-?: ListOf<TokenOf<NonNullable<Caps[K]>>>;
|
|
109
111
|
};
|
|
110
112
|
type FieldExemptionSchema<M extends AvAdapterModel, R extends AvConfigResourceKey<M>, F> = F extends AvAdapterFieldKey<M, R> ? M["resources"][R]["fields"][F] extends {
|
|
111
113
|
readonly kind: "relation";
|
|
@@ -131,33 +133,56 @@ type ExemptionsSchema<M extends AvAdapterModel, Cfg> = {
|
|
|
131
133
|
};
|
|
132
134
|
};
|
|
133
135
|
};
|
|
134
|
-
|
|
135
|
-
|
|
136
|
+
declare const refusal: unique symbol;
|
|
137
|
+
/** What a level refuses an unknown key for: `layer` cannot expose it, `parent` hid it, under `at`. */
|
|
138
|
+
type Refusal<Layer extends string, Parent extends string, At extends string> = {
|
|
139
|
+
readonly [refusal]: {
|
|
140
|
+
readonly layer: Layer;
|
|
141
|
+
readonly parent: Parent;
|
|
142
|
+
readonly at: At;
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
/** The sentence a refused key `K` holding `Value` is typed as; `never` for an entry written `false`. */
|
|
146
|
+
type RefusalOf<Info, K, Value> = [Value] extends [false] ? never : Info extends {
|
|
147
|
+
readonly layer: infer Layer extends string;
|
|
148
|
+
readonly parent: infer Parent extends string;
|
|
149
|
+
readonly at: infer At extends string;
|
|
150
|
+
} ? readonly [
|
|
151
|
+
`Error: ${Layer} cannot expose '${At}${K & string}': hidden by ${Parent}`
|
|
152
|
+
] : never;
|
|
153
|
+
/** A Pick layer's operations exemption, a family or variant its parent hid refused by name. */
|
|
154
|
+
type PickOperationsSchema<Ops, Layer extends string, Parent extends string, R extends string> = ListOf<keyof Ops> & {
|
|
155
|
+
readonly [F in keyof Ops]-?: ListOf<keyof NonNullable<Ops[F]>> & {
|
|
156
|
+
readonly [V in keyof NonNullable<Ops[F]>]: Leaf;
|
|
157
|
+
} & Refusal<Layer, Parent, `${R}.${F & string}.`>;
|
|
158
|
+
} & Refusal<Layer, Parent, `${R}.`>;
|
|
159
|
+
/** The fields of one Resource of a view. */
|
|
160
|
+
type ViewFields<Resource> = Resource extends {
|
|
161
|
+
readonly fields: infer Fields;
|
|
162
|
+
} ? Fields : never;
|
|
163
|
+
/** A Pick layer's exemptions, against its parent's view. */
|
|
164
|
+
type PickExemptionsSchema<Resources, Layer extends string, Parent extends string> = {
|
|
136
165
|
readonly resources: {
|
|
137
166
|
readonly [R in keyof Resources]: {
|
|
138
|
-
readonly operations:
|
|
167
|
+
readonly operations: PickOperationsSchema<Resources[R] extends {
|
|
139
168
|
readonly operations: infer Ops;
|
|
140
|
-
} ? Ops : never>;
|
|
169
|
+
} ? Ops : never, Layer, Parent, R & string>;
|
|
141
170
|
readonly fields: {
|
|
142
|
-
readonly [F in keyof
|
|
143
|
-
readonly fields: infer Fields;
|
|
144
|
-
} ? Fields : never)]: CapabilityExemptionSchema<(Resources[R] extends {
|
|
145
|
-
readonly fields: infer Fields;
|
|
146
|
-
} ? Fields : never)[F] extends {
|
|
171
|
+
readonly [F in keyof ViewFields<Resources[R]>]: CapabilityExemptionSchema<ViewFields<Resources[R]>[F] extends {
|
|
147
172
|
readonly capabilities: infer Caps;
|
|
148
|
-
} ? Caps : never
|
|
149
|
-
}
|
|
173
|
+
} ? Caps : never> & Refusal<Layer, Parent, `${R & string}.${F & string}.`>;
|
|
174
|
+
} & Refusal<Layer, Parent, `${R & string}.`>;
|
|
150
175
|
};
|
|
151
|
-
}
|
|
176
|
+
} & Refusal<Layer, Parent, "">;
|
|
152
177
|
};
|
|
153
178
|
/** A layer's own configuration: `application`, `client` or `agent`. */
|
|
154
179
|
type OverlayOf<Cfg, Layer extends "application" | "client" | "agent"> = (Cfg & {
|
|
155
180
|
readonly [L in Layer]?: unknown;
|
|
156
181
|
})[Layer];
|
|
157
182
|
/** An overlay's exemptions schema, by the overlay's own `exposed`. */
|
|
158
|
-
type OverlayExemptionsSchema<M extends AvAdapterModel, Cfg, Overlay> = Overlay extends {
|
|
183
|
+
type OverlayExemptionsSchema<M extends AvAdapterModel, Cfg, Overlay, Layer extends "application" | "client"> = Overlay extends {
|
|
159
184
|
readonly exposed: false;
|
|
160
|
-
} ? PickExemptionsSchema<RootViewResources<M, Cfg
|
|
185
|
+
} ? PickExemptionsSchema<RootViewResources<M, Cfg>, Layer, "root"> : ExemptionsSchema<M, Cfg>;
|
|
161
186
|
type LayerKeysSchema = {
|
|
162
187
|
readonly exposed: Leaf;
|
|
163
188
|
readonly transactionsStyle: Leaf;
|
|
@@ -183,7 +208,7 @@ type OverlaySchema<M extends AvAdapterModel, Cfg, Layer extends "application" |
|
|
|
183
208
|
readonly resources: {
|
|
184
209
|
readonly [R in AvConfigResourceKey<M>]: ResourceSchema<M, R, FieldsSchema<M, R, "overlay", AvEffectiveFieldKey<M, Cfg, R>>>;
|
|
185
210
|
};
|
|
186
|
-
readonly exemptions: OverlayExemptionsSchema<M, Cfg, OverlayOf<Cfg, Layer
|
|
211
|
+
readonly exemptions: OverlayExemptionsSchema<M, Cfg, OverlayOf<Cfg, Layer>, Layer>;
|
|
187
212
|
};
|
|
188
213
|
type McpSchema = {
|
|
189
214
|
readonly path: Leaf;
|
|
@@ -197,7 +222,7 @@ type AgentParentOf<Cfg> = OverlayOf<Cfg, "agent"> extends {
|
|
|
197
222
|
readonly extends: infer Parent;
|
|
198
223
|
} ? Parent : "client";
|
|
199
224
|
/** A Pick agent's exemptions, against the view of the layer it extends. */
|
|
200
|
-
type AgentPickExemptionsSchema<M extends AvAdapterModel, Cfg, Parent> = Parent extends "application" | "client" ? [Parent] extends ["application"] | ["client"] ? PickExemptionsSchema<OverlayViewResources<M, Cfg, Parent
|
|
225
|
+
type AgentPickExemptionsSchema<M extends AvAdapterModel, Cfg, Parent> = Parent extends "application" | "client" ? [Parent] extends ["application"] | ["client"] ? PickExemptionsSchema<OverlayViewResources<M, Cfg, Parent>, "agent", Parent> : PickExemptionsSchema<RootViewResources<M, Cfg>, "agent", "root"> : PickExemptionsSchema<RootViewResources<M, Cfg>, "agent", "root">;
|
|
201
226
|
type AgentSchema<M extends AvAdapterModel, Cfg> = LayerKeysSchema & {
|
|
202
227
|
readonly extends: Leaf;
|
|
203
228
|
readonly resources: {
|
|
@@ -219,9 +244,12 @@ type RootSchema<M extends AvAdapterModel, Cfg> = LayerKeysSchema & {
|
|
|
219
244
|
readonly client: OverlaySchema<M, Cfg, "client">;
|
|
220
245
|
readonly agent: AgentSchema<M, Cfg>;
|
|
221
246
|
};
|
|
222
|
-
/**
|
|
247
|
+
/**
|
|
248
|
+
* `V` with every key schema `S` lacks mapped to `never`, or to the sentence a
|
|
249
|
+
* refusing level states; every leaf `unknown`.
|
|
250
|
+
*/
|
|
223
251
|
type Exact<S, V> = [S] extends [Leaf] ? unknown : V extends readonly unknown[] ? S extends ListOf<infer Names> ? readonly Names[] : unknown : V extends (...args: any) => any ? unknown : V extends object ? string extends keyof V ? unknown : {
|
|
224
|
-
readonly [K in keyof V]: K extends keyof S ? Exact<S[K], V[K]> : typeof otherKeys extends keyof S ? Exact<S[typeof otherKeys], V[K]> : never;
|
|
252
|
+
readonly [K in keyof V]: K extends keyof S ? Exact<S[K], V[K]> : typeof otherKeys extends keyof S ? Exact<S[typeof otherKeys], V[K]> : typeof refusal extends keyof S ? RefusalOf<S[typeof refusal], K, V[K]> : never;
|
|
225
253
|
} : unknown;
|
|
226
254
|
/**
|
|
227
255
|
* The authored configuration `Cfg`, checked key by key: a key no level of the
|
|
@@ -17,3 +17,5 @@ export declare function computeOfLayer(resources: unknown): ResolvedComputeConfi
|
|
|
17
17
|
* chained phase by phase. Nothing is disabled from below.
|
|
18
18
|
*/
|
|
19
19
|
export declare function chainCompute(root: ResolvedComputeConfig, overlay: ResolvedComputeConfig): ResolvedComputeConfig;
|
|
20
|
+
/** The fields of `resource` with a `compute.create` stage in `compute`. */
|
|
21
|
+
export declare function createComputedFields(compute: ResolvedComputeConfig, resource: string): ReadonlySet<string>;
|
|
@@ -113,3 +113,11 @@ export function chainCompute(root, overlay) {
|
|
|
113
113
|
}
|
|
114
114
|
return result;
|
|
115
115
|
}
|
|
116
|
+
export function createComputedFields(compute, resource) {
|
|
117
|
+
const fields = Object.hasOwn(compute, resource)
|
|
118
|
+
? compute[resource]
|
|
119
|
+
: undefined;
|
|
120
|
+
return new Set(Object.entries(fields ?? {})
|
|
121
|
+
.filter(([, phases]) => phases.create !== undefined)
|
|
122
|
+
.map(([field]) => field));
|
|
123
|
+
}
|