@aventara/core 1.0.0-rc.0 → 1.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1434 -0
- package/ERRORS.md +142 -0
- package/README.md +247 -57
- package/dist/adapters/adapter-discovery-diagnostic.d.ts +4 -4
- package/dist/adapters/adapter-discovery-diagnostic.guidance.d.ts +6 -0
- package/dist/adapters/adapter-discovery-diagnostic.guidance.js +7 -0
- package/dist/adapters/adapter-field.d.ts +25 -11
- package/dist/adapters/adapter-identifier.d.ts +6 -6
- package/dist/adapters/adapter-model.d.ts +18 -9
- package/dist/adapters/adapter-operation-executor.interface.d.ts +4 -4
- package/dist/adapters/adapter-operation.d.ts +3 -3
- package/dist/adapters/adapter-resource.d.ts +18 -9
- package/dist/adapters/adapter-sql-statement.d.ts +19 -0
- package/dist/adapters/adapter-transaction-executor.interface.d.ts +19 -0
- package/dist/adapters/adapter.interface.d.ts +59 -29
- package/dist/avz/avz-check.factory.d.ts +83 -0
- package/dist/avz/avz-check.factory.js +116 -0
- package/dist/avz/avz-check.registry.d.ts +35 -0
- package/dist/avz/avz-check.registry.js +9 -0
- package/dist/avz/avz.d.ts +203 -0
- package/dist/{json → avz}/avz.js +150 -8
- package/dist/{json → avz}/index.d.ts +2 -1
- package/dist/{json → avz}/json-shape.registry.d.ts +7 -7
- package/dist/{json → avz}/zod-issue.translator.d.ts +8 -2
- package/dist/{json → avz}/zod-issue.translator.js +7 -0
- package/dist/compiler/contract-compiler-diagnostic.d.ts +31 -5
- package/dist/compiler/contract-compiler-diagnostic.guidance.d.ts +6 -0
- package/dist/compiler/contract-compiler-diagnostic.guidance.js +97 -0
- package/dist/compiler/contract.compiler.d.ts +18 -18
- package/dist/compiler/contract.compiler.js +637 -442
- package/dist/compiler/create-requirements.projector.d.ts +49 -0
- package/dist/compiler/create-requirements.projector.js +423 -0
- package/dist/compiler/field-validator.compiler.d.ts +47 -0
- package/dist/compiler/field-validator.compiler.js +238 -0
- package/dist/config/any-framework-config.d.ts +19 -12
- package/dist/config/computed-fields.d.ts +62 -0
- package/dist/config/computed-fields.js +80 -0
- package/dist/config/config-diagnostic.d.ts +11 -4
- package/dist/config/config-diagnostic.guidance.d.ts +6 -0
- package/dist/config/config-diagnostic.guidance.js +32 -0
- package/dist/config/config-layer.d.ts +15 -6
- package/dist/config/config.resolver.d.ts +74 -31
- package/dist/config/config.resolver.js +108 -44
- package/dist/config/config.validator.d.ts +7 -7
- package/dist/config/config.validator.js +581 -72
- package/dist/config/define-resource.d.ts +57 -0
- package/dist/config/define-resource.js +14 -0
- package/dist/config/effective-fields.d.ts +19 -23
- package/dist/config/entrypoint.normalizer.d.ts +1 -1
- package/dist/config/exact-framework-config.d.ts +308 -0
- package/dist/config/field-compute.resolver.d.ts +21 -0
- package/dist/config/field-compute.resolver.js +123 -0
- package/dist/config/field-pipeline-config.d.ts +47 -0
- package/dist/config/forced-operation.validator.d.ts +10 -0
- package/dist/config/forced-operation.validator.js +66 -0
- package/dist/config/framework-config.d.ts +111 -39
- package/dist/config/framework-exemptions-config.d.ts +91 -0
- package/dist/config/framework-exemptions-config.js +1 -0
- package/dist/config/framework-field-definition.d.ts +78 -0
- package/dist/config/framework-field-definition.js +28 -0
- package/dist/config/framework-resources-config.d.ts +122 -0
- package/dist/config/framework-resources-config.js +1 -0
- package/dist/config/pipeline-config.d.ts +56 -20
- package/dist/config/pipeline-tree.resolver.d.ts +79 -0
- package/dist/config/pipeline-tree.resolver.js +220 -0
- package/dist/config/stage-type.eraser.d.ts +65 -0
- package/dist/config/stage-type.eraser.js +1 -0
- package/dist/contracts/capability.d.ts +21 -12
- package/dist/contracts/capability.js +2 -0
- package/dist/contracts/client-contract-structure.validator.d.ts +388 -63
- package/dist/contracts/client-contract-structure.validator.js +156 -10
- package/dist/contracts/contract-canonicalizer.d.ts +26 -3
- package/dist/contracts/contract-canonicalizer.js +156 -29
- package/dist/contracts/contract-hash.d.ts +12 -7
- package/dist/contracts/contract-hash.js +3 -2
- package/dist/contracts/contract-limits.d.ts +4 -1
- package/dist/contracts/contract-limits.js +1 -0
- package/dist/contracts/contract-lookup.d.ts +5 -5
- package/dist/contracts/contract-protocol.d.ts +4 -4
- package/dist/contracts/contract.d.ts +24 -16
- package/dist/contracts/create-requirements.d.ts +35 -0
- package/dist/contracts/create-requirements.js +1 -0
- package/dist/contracts/enum-definition.d.ts +1 -1
- package/dist/contracts/field-check.d.ts +101 -0
- package/dist/contracts/field-check.js +1 -0
- package/dist/contracts/field.d.ts +81 -42
- package/dist/contracts/identifier.d.ts +4 -4
- package/dist/contracts/json-shape.d.ts +12 -1
- package/dist/contracts/lifecycle-trait.d.ts +2 -2
- package/dist/contracts/resource-operations.d.ts +28 -28
- package/dist/contracts/resource.d.ts +15 -9
- package/dist/contracts/scalar-value-type.d.ts +15 -15
- package/dist/contracts/scalar.d.ts +3 -3
- package/dist/contracts/transaction-capability.d.ts +1 -1
- package/dist/contracts/wire-scalar-format.d.ts +4 -4
- package/dist/framework/contract-view.d.ts +213 -147
- package/dist/framework/framework-construction.error.d.ts +19 -10
- package/dist/framework/framework-construction.error.js +7 -3
- package/dist/framework/framework.d.ts +47 -23
- package/dist/framework/framework.factory.d.ts +8 -6
- package/dist/framework/framework.factory.js +49 -22
- package/dist/framework/operation-facade.d.ts +123 -52
- package/dist/framework/operation-facade.js +9 -1
- package/dist/index.d.ts +57 -50
- package/dist/index.js +7 -6
- package/dist/internal.d.ts +17 -10
- package/dist/internal.js +4 -1
- package/dist/operations/database-refusal.d.ts +51 -0
- package/dist/operations/database-refusal.js +45 -0
- package/dist/operations/operation-arguments.d.ts +343 -207
- package/dist/operations/operation-call.d.ts +27 -28
- package/dist/operations/operation-identity.d.ts +10 -10
- package/dist/operations/operation-result.d.ts +21 -21
- package/dist/operations/operation-validator-core.d.ts +6 -6
- package/dist/operations/operation-validator-core.js +99 -6
- package/dist/operations/operation-validator.d.ts +9 -9
- package/dist/operations/projection-scope.resolver.d.ts +6 -6
- package/dist/operations/structural-limits.walker.d.ts +6 -6
- package/dist/operations/structural-limits.walker.js +145 -0
- package/dist/operations/validation-code.d.ts +11 -6
- package/dist/operations/validation-code.guidance.d.ts +6 -0
- package/dist/operations/validation-code.guidance.js +90 -0
- package/dist/operations/validation-code.js +4 -0
- package/dist/operations/write-scope.resolver.d.ts +10 -6
- package/dist/operations/write-scope.resolver.js +2 -0
- package/dist/protocol/absent-route.responder.d.ts +13 -12
- package/dist/protocol/absent-route.responder.js +19 -19
- package/dist/protocol/contract-document.responder.d.ts +3 -3
- package/dist/protocol/contract-document.responder.js +2 -2
- package/dist/protocol/http-request.decoder.d.ts +17 -13
- package/dist/protocol/http-request.decoder.js +10 -10
- package/dist/protocol/http-request.reader.d.ts +3 -3
- package/dist/protocol/http-response.encoder.d.ts +4 -4
- package/dist/protocol/http-response.encoder.js +94 -55
- package/dist/protocol/http-status.table.d.ts +2 -2
- package/dist/protocol/operation-response.guard.d.ts +2 -2
- package/dist/protocol/protocol-surface.d.ts +4 -4
- package/dist/protocol/protocol.binder.d.ts +50 -27
- package/dist/protocol/protocol.binder.js +12 -9
- package/dist/protocol/protocol.handler.d.ts +22 -13
- package/dist/protocol/protocol.handler.js +91 -17
- package/dist/protocol/unmounted-route.error.d.ts +4 -4
- package/dist/protocol/unmounted-route.error.js +2 -2
- package/dist/runtime/caller-data.copier.d.ts +22 -5
- package/dist/runtime/caller-data.copier.js +31 -14
- package/dist/runtime/caller-data.door.d.ts +17 -11
- package/dist/runtime/caller-data.door.js +6 -4
- package/dist/runtime/computed-read-runtime.d.ts +7 -6
- package/dist/runtime/computed-read-runtime.js +76 -50
- package/dist/runtime/computed-value.error.d.ts +7 -6
- package/dist/runtime/computed-value.error.js +5 -4
- package/dist/runtime/context-executor.runtime.d.ts +59 -0
- package/dist/runtime/context-executor.runtime.js +275 -0
- package/dist/runtime/create-requirement.d.ts +44 -79
- package/dist/runtime/create-requirement.js +213 -73
- package/dist/runtime/decimal.d.ts +2 -2
- package/dist/runtime/decimal.js +1 -1
- package/dist/runtime/diagnostics-reporter.d.ts +54 -27
- package/dist/runtime/diagnostics-reporter.js +24 -0
- package/dist/runtime/error-boundary.d.ts +65 -8
- package/dist/runtime/error-boundary.js +49 -19
- package/dist/runtime/error-guidance.d.ts +12 -0
- package/dist/runtime/error-guidance.js +1 -0
- package/dist/runtime/error.catalog.d.ts +49 -0
- package/dist/runtime/error.catalog.js +49 -0
- package/dist/runtime/execution-options.d.ts +10 -2
- package/dist/runtime/field-check.evaluator.d.ts +29 -0
- package/dist/runtime/field-check.evaluator.js +521 -0
- package/dist/runtime/field-compute.d.ts +64 -0
- package/dist/runtime/field-compute.js +1 -0
- package/dist/runtime/field-stage.plan.d.ts +15 -0
- package/dist/runtime/field-stage.plan.js +27 -0
- package/dist/runtime/field-stage.runtime.d.ts +37 -0
- package/dist/runtime/field-stage.runtime.js +147 -0
- package/dist/runtime/field-validator.runtime.d.ts +19 -0
- package/dist/runtime/field-validator.runtime.js +92 -0
- package/dist/runtime/framework-error-class.table.d.ts +33 -0
- package/dist/runtime/framework-error-class.table.js +25 -0
- package/dist/runtime/framework-error.d.ts +68 -8
- package/dist/runtime/framework-error.factory.d.ts +4 -0
- package/dist/runtime/framework-error.factory.js +15 -0
- package/dist/runtime/framework-error.js +23 -2
- package/dist/runtime/interceptor-next.error.d.ts +5 -0
- package/dist/runtime/interceptor-next.error.js +6 -0
- package/dist/runtime/json-value.d.ts +11 -6
- package/dist/runtime/level-lifecycle.runtime.d.ts +25 -0
- package/dist/runtime/level-lifecycle.runtime.js +39 -0
- package/dist/runtime/misuse-reporter.d.ts +12 -12
- package/dist/runtime/mutation-planner.d.ts +19 -10
- package/dist/runtime/mutation-planner.js +48 -35
- package/dist/runtime/nested-write.walker.d.ts +32 -0
- package/dist/runtime/nested-write.walker.js +73 -0
- package/dist/runtime/operation-code.guidance.d.ts +6 -0
- package/dist/runtime/operation-code.guidance.js +167 -0
- package/dist/runtime/operation-request.d.ts +33 -6
- package/dist/runtime/operation-response.d.ts +17 -17
- package/dist/runtime/operation-runtime.d.ts +127 -25
- package/dist/runtime/operation-runtime.js +441 -78
- package/dist/runtime/outcome-mapper.d.ts +16 -5
- package/dist/runtime/outcome-mapper.js +23 -6
- package/dist/runtime/output-validator.d.ts +10 -10
- package/dist/runtime/output-validator.js +3 -3
- package/dist/runtime/owned-execution.registry.d.ts +16 -6
- package/dist/runtime/pipe-output.validator.d.ts +43 -18
- package/dist/runtime/pipe-output.validator.js +15 -13
- package/dist/runtime/pipeline.d.ts +223 -34
- package/dist/runtime/readonly-value.d.ts +43 -0
- package/dist/runtime/readonly-value.js +150 -0
- package/dist/runtime/request-envelope.validator.d.ts +3 -3
- package/dist/runtime/request-envelope.validator.js +12 -7
- package/dist/runtime/request-id.d.ts +1 -1
- package/dist/runtime/request-state.d.ts +14 -0
- package/dist/runtime/request-state.js +1 -0
- package/dist/runtime/request-state.resolver.d.ts +12 -0
- package/dist/runtime/request-state.resolver.js +20 -0
- package/dist/runtime/scalar-runtime.d.ts +35 -16
- package/dist/runtime/scalar-runtime.js +102 -34
- package/dist/runtime/unsent-value.normalizer.d.ts +15 -0
- package/dist/runtime/unsent-value.normalizer.js +128 -0
- package/dist/transactions/deferred-operation.d.ts +35 -20
- package/dist/transactions/operation-arguments-encoder.d.ts +3 -3
- package/dist/transactions/operation-arguments-encoder.js +2 -2
- package/dist/transactions/operation-fingerprint.js +2 -2
- package/dist/transactions/sql-statement.d.ts +85 -0
- package/dist/transactions/sql-statement.js +47 -0
- package/dist/transactions/sql-statement.validator.d.ts +44 -0
- package/dist/transactions/sql-statement.validator.js +167 -0
- package/dist/transactions/transaction-envelope.d.ts +4 -4
- package/dist/transactions/transaction-integrity-error.d.ts +6 -6
- package/dist/transactions/transaction-integrity-error.js +5 -2
- package/dist/transactions/transaction-plan-assembler.d.ts +21 -11
- package/dist/transactions/transaction-plan-assembler.js +169 -32
- package/dist/transactions/transaction-plan-validator.d.ts +45 -20
- package/dist/transactions/transaction-plan-validator.js +96 -38
- package/dist/transactions/transaction-reference-collector.d.ts +5 -5
- package/dist/transactions/transaction-reference-resolver.d.ts +19 -2
- package/dist/transactions/transaction-reference-resolver.js +26 -14
- package/dist/transactions/transaction-reference-validator.d.ts +76 -4
- package/dist/transactions/transaction-reference-validator.js +129 -71
- package/dist/transactions/transaction-reference.d.ts +13 -13
- package/dist/transactions/transaction-reference.js +5 -0
- package/dist/transactions/transaction-request.d.ts +18 -5
- package/dist/transactions/transaction-response.d.ts +6 -6
- package/dist/transactions/transaction-runtime.d.ts +8 -7
- package/dist/transactions/transaction-runtime.js +191 -50
- package/dist/transactions/transaction-settlement.runtime.d.ts +51 -0
- package/dist/transactions/transaction-settlement.runtime.js +120 -0
- package/dist/transactions/transaction-slot-path-error.d.ts +4 -4
- package/dist/transactions/transaction-slot-path-error.js +2 -2
- package/dist/transactions/transaction-slot-writer.js +2 -2
- package/dist/transactions/transaction-stage.runtime.d.ts +82 -0
- package/dist/transactions/transaction-stage.runtime.js +126 -0
- package/package.json +9 -7
- package/src/runtime/field-check.evaluator.ts +720 -0
- package/dist/config/define-fields.d.ts +0 -42
- package/dist/config/define-fields.js +0 -3
- package/dist/config/framework-behaviors-config.d.ts +0 -30
- package/dist/config/framework-fields-config.d.ts +0 -42
- package/dist/config/framework-restrictions-config.d.ts +0 -142
- package/dist/json/avz.d.ts +0 -96
- package/dist/runtime/field-behavior.d.ts +0 -48
- /package/dist/{config/framework-behaviors-config.js → adapters/adapter-sql-statement.js} +0 -0
- /package/dist/{config/framework-fields-config.js → adapters/adapter-transaction-executor.interface.js} +0 -0
- /package/dist/{json → avz}/index.js +0 -0
- /package/dist/{json → avz}/json-shape.registry.js +0 -0
- /package/dist/config/{framework-restrictions-config.js → exact-framework-config.js} +0 -0
- /package/dist/{runtime/field-behavior.js → config/field-pipeline-config.js} +0 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { AvAdapterModel } from "../adapters/adapter-model.js";
|
|
2
|
+
import type { AvConfigLayerName } from "../config/config-layer.js";
|
|
3
|
+
import type { AvCreateRequirements } from "../contracts/create-requirements.js";
|
|
4
|
+
import type { AvContractCompilerDiagnostic } from "./contract-compiler-diagnostic.js";
|
|
5
|
+
/** A field of a projection, as far as this reader needs it. */
|
|
6
|
+
type ProjectedField = {
|
|
7
|
+
readonly kind: "scalar" | "relation";
|
|
8
|
+
readonly [member: string]: unknown;
|
|
9
|
+
};
|
|
10
|
+
/** A Resource of a projection, as far as this reader needs it. */
|
|
11
|
+
type ProjectedResource = {
|
|
12
|
+
readonly fields: Readonly<Record<string, ProjectedField>>;
|
|
13
|
+
createRequirements?: AvCreateRequirements;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Sets every Resource's `createRequirements` in one layer's projection to the
|
|
17
|
+
* Adapter's statement as that layer sees it, and removes it where nothing is
|
|
18
|
+
* left. Reads the projection's final fields: its exposure, and the lifecycle
|
|
19
|
+
* its compute stages gave them.
|
|
20
|
+
*/
|
|
21
|
+
export declare function projectCreateRequirements(model: AvAdapterModel, resources: Readonly<Record<string, ProjectedResource>>): void;
|
|
22
|
+
/** A Resource's operations, as far as this check reads them. */
|
|
23
|
+
type ResourceWithOperations = ProjectedResource & {
|
|
24
|
+
readonly operations: Readonly<Record<string, unknown>>;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Refuses, for one layer, every create requirement its exposure leaves no
|
|
28
|
+
* caller able to satisfy while the layer still creates the Resource: a
|
|
29
|
+
* required field hidden or not writable on create, or a group whose relation is
|
|
30
|
+
* not writable and one of whose reference fields is neither writable nor filled
|
|
31
|
+
* by a `compute.create`. Reported only where the parent could still satisfy it,
|
|
32
|
+
* so a hiding is reported once, by the layer that hides it; a nested create is
|
|
33
|
+
* exempt from what its parent supplies. A group every other create can satisfy
|
|
34
|
+
* but a bulk record cannot is {@link checkBulkGroup}'s refusal.
|
|
35
|
+
*
|
|
36
|
+
* `universe` is the layer's model with its chained compute applied (it holds
|
|
37
|
+
* every field, hidden or not), `parent` what the layer starts from, `projection`
|
|
38
|
+
* its final view.
|
|
39
|
+
*/
|
|
40
|
+
export declare function checkHiddenRequirements(model: AvAdapterModel, layer: AvConfigLayerName, universe: Readonly<Record<string, ProjectedResource>>, parent: Readonly<Record<string, ProjectedResource>>, projection: Readonly<Record<string, ResourceWithOperations>>, diagnostics: AvContractCompilerDiagnostic[]): void;
|
|
41
|
+
/**
|
|
42
|
+
* Every name in the Adapter's create requirements that is not what its member
|
|
43
|
+
* says: a `required` name that is no field of the Resource, a group whose first
|
|
44
|
+
* name is no to-one relation or whose others are no scalar fields, or a
|
|
45
|
+
* `parentSupplies` entry naming no relation to the Resource or a field the
|
|
46
|
+
* statement does not require. An Adapter defect, refused at construction.
|
|
47
|
+
*/
|
|
48
|
+
export declare function checkCreateRequirements(model: AvAdapterModel, diagnostics: AvContractCompilerDiagnostic[]): void;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import { advertisedDirectives, advertisedDirectivesAt, } from "../contracts/field.js";
|
|
2
|
+
import { isOperationVariantAvailable, writeScopeOf, } from "../contracts/resource-operations.js";
|
|
3
|
+
function isRecord(value) {
|
|
4
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
function own(record, key) {
|
|
7
|
+
return Object.hasOwn(record, key) ? record[key] : undefined;
|
|
8
|
+
}
|
|
9
|
+
function creatable(resource, name) {
|
|
10
|
+
const field = own(resource.fields, name);
|
|
11
|
+
const capabilities = isRecord(field?.capabilities)
|
|
12
|
+
? field.capabilities
|
|
13
|
+
: undefined;
|
|
14
|
+
if (field === undefined || capabilities === undefined) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
return field.kind === "scalar"
|
|
18
|
+
? capabilities.create !== undefined
|
|
19
|
+
: advertisedDirectives(capabilities.create) !== undefined;
|
|
20
|
+
}
|
|
21
|
+
function creatableAt(resource, name, scope) {
|
|
22
|
+
const field = own(resource.fields, name);
|
|
23
|
+
if (field?.kind !== "relation") {
|
|
24
|
+
return creatable(resource, name);
|
|
25
|
+
}
|
|
26
|
+
const capabilities = isRecord(field.capabilities)
|
|
27
|
+
? field.capabilities
|
|
28
|
+
: undefined;
|
|
29
|
+
return (advertisedDirectivesAt(capabilities?.create, scope) !== undefined);
|
|
30
|
+
}
|
|
31
|
+
function computedOnCreate(resource, name) {
|
|
32
|
+
const field = own(resource.fields, name);
|
|
33
|
+
return (field?.kind === "scalar" &&
|
|
34
|
+
Array.isArray(field.lifecycle) &&
|
|
35
|
+
field.lifecycle.includes("COMPUTED_ON_CREATE"));
|
|
36
|
+
}
|
|
37
|
+
function projectOne(stated, resource, resources) {
|
|
38
|
+
const required = [];
|
|
39
|
+
const requireOnce = (name) => {
|
|
40
|
+
if (!required.includes(name)) {
|
|
41
|
+
required.push(name);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
for (const name of stated.required ?? []) {
|
|
45
|
+
if (creatable(resource, name) && !computedOnCreate(resource, name)) {
|
|
46
|
+
requireOnce(name);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const oneOf = [];
|
|
50
|
+
for (const [relation, ...references] of stated.oneOf ?? []) {
|
|
51
|
+
const left = references.filter((name) => !computedOnCreate(resource, name));
|
|
52
|
+
const [first, ...rest] = left;
|
|
53
|
+
if (first === undefined) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const relationWritable = creatable(resource, relation);
|
|
57
|
+
const referencesWritable = left.every((name) => creatable(resource, name));
|
|
58
|
+
if (relationWritable && referencesWritable) {
|
|
59
|
+
oneOf.push([relation, first, ...rest]);
|
|
60
|
+
}
|
|
61
|
+
else if (relationWritable) {
|
|
62
|
+
requireOnce(relation);
|
|
63
|
+
}
|
|
64
|
+
else if (referencesWritable) {
|
|
65
|
+
for (const name of left) {
|
|
66
|
+
requireOnce(name);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const named = new Set([...required, ...oneOf.flat()]);
|
|
71
|
+
const parentSupplies = {};
|
|
72
|
+
for (const [parent, byRelation] of Object.entries(stated.parentSupplies ?? {})) {
|
|
73
|
+
const parentResource = own(resources, parent);
|
|
74
|
+
if (parentResource === undefined) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
for (const [relation, members] of Object.entries(byRelation)) {
|
|
78
|
+
if (own(parentResource.fields, relation)?.kind !== "relation") {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const [first, ...rest] = members.filter((name) => named.has(name));
|
|
82
|
+
if (first !== undefined) {
|
|
83
|
+
parentSupplies[parent] ??= {};
|
|
84
|
+
parentSupplies[parent][relation] = [first, ...rest];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const [firstRequired, ...restRequired] = required;
|
|
89
|
+
const [firstGroup, ...restGroups] = oneOf;
|
|
90
|
+
if (firstRequired === undefined && firstGroup === undefined) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
...(firstRequired === undefined
|
|
95
|
+
? {}
|
|
96
|
+
: { required: [firstRequired, ...restRequired] }),
|
|
97
|
+
...(firstGroup === undefined ? {} : { oneOf: [firstGroup, ...restGroups] }),
|
|
98
|
+
...(Object.keys(parentSupplies).length === 0 ? {} : { parentSupplies }),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export function projectCreateRequirements(model, resources) {
|
|
102
|
+
for (const [name, resource] of Object.entries(resources)) {
|
|
103
|
+
delete resource.createRequirements;
|
|
104
|
+
const stated = own(model.resources, name)?.createRequirements;
|
|
105
|
+
if (stated === undefined) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const projected = projectOne(stated, resource, resources);
|
|
109
|
+
if (projected !== undefined) {
|
|
110
|
+
resource.createRequirements = projected;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const CREATING_DIRECTIVES = [
|
|
115
|
+
"$create",
|
|
116
|
+
"$connectOrCreate",
|
|
117
|
+
"$upsert",
|
|
118
|
+
];
|
|
119
|
+
const UPDATING_DIRECTIVES = [
|
|
120
|
+
"$update",
|
|
121
|
+
"$upsert",
|
|
122
|
+
];
|
|
123
|
+
function variantsOf(resource, families) {
|
|
124
|
+
const variants = [];
|
|
125
|
+
for (const family of families) {
|
|
126
|
+
const entry = own(resource.operations, family);
|
|
127
|
+
if (!isRecord(entry)) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
for (const [variant, availability] of Object.entries(entry)) {
|
|
131
|
+
if (isOperationVariantAvailable(availability)) {
|
|
132
|
+
variants.push(`${family}.${variant}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return variants;
|
|
137
|
+
}
|
|
138
|
+
function advertises(field, stage, directives) {
|
|
139
|
+
const capabilities = isRecord(field.capabilities)
|
|
140
|
+
? field.capabilities
|
|
141
|
+
: undefined;
|
|
142
|
+
const advertised = advertisedDirectives(capabilities?.[stage]);
|
|
143
|
+
return advertised?.some((name) => directives.includes(name)) ?? false;
|
|
144
|
+
}
|
|
145
|
+
function createVariantsOf(resource) {
|
|
146
|
+
const variants = [];
|
|
147
|
+
for (const family of ["create", "upsert"]) {
|
|
148
|
+
const entry = own(resource.operations, family);
|
|
149
|
+
if (!isRecord(entry)) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
for (const [variant, availability] of Object.entries(entry)) {
|
|
153
|
+
if (isOperationVariantAvailable(availability)) {
|
|
154
|
+
variants.push({
|
|
155
|
+
name: `${family}.${variant}`,
|
|
156
|
+
scope: writeScopeOf(availability),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return variants;
|
|
162
|
+
}
|
|
163
|
+
function createPaths(resources) {
|
|
164
|
+
const created = new Set();
|
|
165
|
+
const updated = new Set();
|
|
166
|
+
for (const [name, resource] of Object.entries(resources)) {
|
|
167
|
+
if (variantsOf(resource, ["create", "upsert"]).length > 0) {
|
|
168
|
+
created.add(name);
|
|
169
|
+
}
|
|
170
|
+
if (variantsOf(resource, ["update", "upsert"]).length > 0) {
|
|
171
|
+
updated.add(name);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
let grew = true;
|
|
175
|
+
while (grew) {
|
|
176
|
+
grew = false;
|
|
177
|
+
for (const [parent, resource] of Object.entries(resources)) {
|
|
178
|
+
for (const field of Object.values(resource.fields)) {
|
|
179
|
+
const target = field.target;
|
|
180
|
+
if (field.kind !== "relation" ||
|
|
181
|
+
typeof target !== "string" ||
|
|
182
|
+
own(resources, target) === undefined) {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const reachesCreate = (created.has(parent) &&
|
|
186
|
+
advertises(field, "create", CREATING_DIRECTIVES)) ||
|
|
187
|
+
(updated.has(parent) &&
|
|
188
|
+
advertises(field, "update", CREATING_DIRECTIVES));
|
|
189
|
+
const reachesUpdate = updated.has(parent) &&
|
|
190
|
+
advertises(field, "update", UPDATING_DIRECTIVES);
|
|
191
|
+
if (reachesCreate && !created.has(target)) {
|
|
192
|
+
created.add(target);
|
|
193
|
+
grew = true;
|
|
194
|
+
}
|
|
195
|
+
if (reachesUpdate && !updated.has(target)) {
|
|
196
|
+
updated.add(target);
|
|
197
|
+
grew = true;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const paths = new Map();
|
|
203
|
+
const add = (name, path) => {
|
|
204
|
+
const list = paths.get(name) ?? [];
|
|
205
|
+
list.push(path);
|
|
206
|
+
paths.set(name, list);
|
|
207
|
+
};
|
|
208
|
+
for (const [name, resource] of Object.entries(resources)) {
|
|
209
|
+
const variants = createVariantsOf(resource);
|
|
210
|
+
if (variants.length > 0) {
|
|
211
|
+
add(name, { kind: "operation", variants });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
for (const [parent, resource] of Object.entries(resources)) {
|
|
215
|
+
for (const [relation, field] of Object.entries(resource.fields)) {
|
|
216
|
+
const target = field.target;
|
|
217
|
+
if (field.kind !== "relation" || typeof target !== "string") {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if ((created.has(parent) &&
|
|
221
|
+
advertises(field, "create", CREATING_DIRECTIVES)) ||
|
|
222
|
+
(updated.has(parent) &&
|
|
223
|
+
advertises(field, "update", CREATING_DIRECTIVES))) {
|
|
224
|
+
add(target, { kind: "nested", parent, relation });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return paths;
|
|
229
|
+
}
|
|
230
|
+
function pathLabel(path) {
|
|
231
|
+
return path.kind === "operation"
|
|
232
|
+
? path.variants.map((variant) => variant.name).join(", ")
|
|
233
|
+
: `nested through "${path.parent}.${path.relation}"`;
|
|
234
|
+
}
|
|
235
|
+
function alternatives(names, joiner) {
|
|
236
|
+
const quoted = names.map((name) => `"${name}"`);
|
|
237
|
+
const last = quoted.pop();
|
|
238
|
+
return quoted.length === 0
|
|
239
|
+
? (last ?? "")
|
|
240
|
+
: `${quoted.join(", ")} ${joiner} ${last}`;
|
|
241
|
+
}
|
|
242
|
+
function satisfiable(members, resource, universe, scope) {
|
|
243
|
+
const filled = (name) => creatable(resource, name) || computedOnCreate(universe, name);
|
|
244
|
+
const [relation, ...references] = members;
|
|
245
|
+
if (relation === undefined) {
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
return references.length === 0
|
|
249
|
+
? filled(relation)
|
|
250
|
+
: creatableAt(resource, relation, scope) || references.every(filled);
|
|
251
|
+
}
|
|
252
|
+
function listed(names) {
|
|
253
|
+
const rest = [...names];
|
|
254
|
+
const last = rest.pop();
|
|
255
|
+
return rest.length === 0 ? (last ?? "") : `${rest.join(", ")} and ${last}`;
|
|
256
|
+
}
|
|
257
|
+
function memberPhrase(members, blocked) {
|
|
258
|
+
const [relation, ...references] = members;
|
|
259
|
+
if (references.length === 0) {
|
|
260
|
+
return `its required field "${relation}"`;
|
|
261
|
+
}
|
|
262
|
+
const fields = references.filter((name) => blocked.includes(name));
|
|
263
|
+
const parts = [
|
|
264
|
+
...(relation !== undefined && blocked.includes(relation)
|
|
265
|
+
? [`its required relation "${relation}"`]
|
|
266
|
+
: []),
|
|
267
|
+
...(fields.length === 0
|
|
268
|
+
? []
|
|
269
|
+
: [
|
|
270
|
+
`its reference ${fields.length === 1 ? "field" : "fields"} ${alternatives(fields, "and")}`,
|
|
271
|
+
]),
|
|
272
|
+
];
|
|
273
|
+
return parts.join(" and ");
|
|
274
|
+
}
|
|
275
|
+
function checkBulkGroup(layer, name, members, routes, resource, universe, parent, diagnostics) {
|
|
276
|
+
const [relation, ...references] = members;
|
|
277
|
+
if (relation === undefined || references.length === 0) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const positioned = new Map();
|
|
281
|
+
for (const route of routes) {
|
|
282
|
+
if (route.kind !== "operation") {
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
for (const variant of route.variants) {
|
|
286
|
+
if (variant.scope !== "nested") {
|
|
287
|
+
const list = positioned.get(variant.scope) ?? [];
|
|
288
|
+
list.push(variant.name);
|
|
289
|
+
positioned.set(variant.scope, list);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
for (const [scope, variants] of positioned) {
|
|
294
|
+
if (satisfiable(members, resource, universe, scope) ||
|
|
295
|
+
!satisfiable(members, parent, universe, scope)) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
const blocked = references.filter((member) => !creatable(resource, member) && !computedOnCreate(universe, member));
|
|
299
|
+
const absent = blocked.filter((member) => own(resource.fields, member) === undefined);
|
|
300
|
+
const locked = blocked.filter((member) => !absent.includes(member));
|
|
301
|
+
const phrase = (fields) => `its reference ${fields.length === 1 ? "field" : "fields"} ${alternatives(fields, "and")}`;
|
|
302
|
+
const clauses = [
|
|
303
|
+
...(absent.length === 0 ? [] : [`hides ${phrase(absent)}`]),
|
|
304
|
+
...(locked.length === 0
|
|
305
|
+
? []
|
|
306
|
+
: [`${phrase(locked)} cannot be written on create`]),
|
|
307
|
+
];
|
|
308
|
+
const fields = alternatives(blocked, "and");
|
|
309
|
+
diagnostics.push({
|
|
310
|
+
code: "COMPILER_HIDDEN_REQUIRED_FIELD",
|
|
311
|
+
path: `${layer}.exemptions.resources.${name}.fields.${blocked[0] ?? relation}`,
|
|
312
|
+
message: `the ${layer} layer still creates resource "${name}" in bulk (${variants.join(", ")}) but ${clauses.join(", and ")}, which every bulk create must write: a bulk record cannot write the relation "${relation}"; expose ${fields}, give ${fields} a compute.create, or exempt ${listed(variants)}`,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
export function checkHiddenRequirements(model, layer, universe, parent, projection, diagnostics) {
|
|
317
|
+
const paths = createPaths(projection);
|
|
318
|
+
for (const [name, resource] of Object.entries(projection)) {
|
|
319
|
+
const stated = own(model.resources, name)?.createRequirements;
|
|
320
|
+
const routes = paths.get(name) ?? [];
|
|
321
|
+
const universeResource = own(universe, name);
|
|
322
|
+
const parentResource = own(parent, name);
|
|
323
|
+
if (stated === undefined ||
|
|
324
|
+
routes.length === 0 ||
|
|
325
|
+
universeResource === undefined ||
|
|
326
|
+
parentResource === undefined) {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
const items = [
|
|
330
|
+
...(stated.required ?? []).map((field) => [field]),
|
|
331
|
+
...(stated.oneOf ?? []),
|
|
332
|
+
];
|
|
333
|
+
for (const members of items) {
|
|
334
|
+
if (satisfiable(members, resource, universeResource, "nested") ||
|
|
335
|
+
!satisfiable(members, parentResource, universeResource, "nested")) {
|
|
336
|
+
checkBulkGroup(layer, name, members, routes, resource, universeResource, parentResource, diagnostics);
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
const unsupplied = routes.filter((route) => route.kind === "operation" ||
|
|
340
|
+
!(stated.parentSupplies?.[route.parent]?.[route.relation] ?? []).some((member) => members.includes(member)));
|
|
341
|
+
if (unsupplied.length === 0) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
const [relation = "", ...references] = members;
|
|
345
|
+
const blocked = members.filter((member, position) => !creatable(resource, member) &&
|
|
346
|
+
(position === 0 || !computedOnCreate(universeResource, member)));
|
|
347
|
+
const absent = blocked.filter((member) => own(resource.fields, member) === undefined);
|
|
348
|
+
const locked = blocked.filter((member) => !absent.includes(member));
|
|
349
|
+
const clauses = [
|
|
350
|
+
...(absent.length === 0
|
|
351
|
+
? []
|
|
352
|
+
: [`hides ${memberPhrase(members, absent)}`]),
|
|
353
|
+
...(locked.length === 0
|
|
354
|
+
? []
|
|
355
|
+
: [`${memberPhrase(members, locked)} cannot be written on create`]),
|
|
356
|
+
];
|
|
357
|
+
const blockedReferences = references.filter((member) => blocked.includes(member));
|
|
358
|
+
const expose = references.length === 0
|
|
359
|
+
? `"${relation}"`
|
|
360
|
+
: blockedReferences.length === 1
|
|
361
|
+
? `"${relation}" or "${blockedReferences[0]}"`
|
|
362
|
+
: `"${relation}", or all of ${alternatives(blockedReferences, "and")}`;
|
|
363
|
+
const compute = alternatives(references.length === 0 ? [relation] : blockedReferences, "and");
|
|
364
|
+
diagnostics.push({
|
|
365
|
+
code: "COMPILER_HIDDEN_REQUIRED_FIELD",
|
|
366
|
+
path: `${layer}.exemptions.resources.${name}.fields.${relation}`,
|
|
367
|
+
message: `the ${layer} layer still creates resource "${name}" (${unsupplied.map(pathLabel).join("; ")}) but ${clauses.join(", and ")}, which every create must write; expose ${expose}, give ${compute} a compute.create, or exempt the create operation`,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function misnamed(path, message) {
|
|
373
|
+
return { code: "COMPILER_UNKNOWN_FIELD", path, message };
|
|
374
|
+
}
|
|
375
|
+
export function checkCreateRequirements(model, diagnostics) {
|
|
376
|
+
for (const [name, resource] of Object.entries(model.resources)) {
|
|
377
|
+
const stated = resource.createRequirements;
|
|
378
|
+
if (stated === undefined) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
const path = `adapter.model.resources.${name}.createRequirements`;
|
|
382
|
+
const field = (fieldName) => own(resource.fields, fieldName);
|
|
383
|
+
(stated.required ?? []).forEach((fieldName, index) => {
|
|
384
|
+
if (field(fieldName) === undefined) {
|
|
385
|
+
diagnostics.push(misnamed(`${path}.required[${index}]`, `"${fieldName}" is not a field of resource "${name}"`));
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
(stated.oneOf ?? []).forEach((group, index) => {
|
|
389
|
+
group.forEach((fieldName, position) => {
|
|
390
|
+
const found = field(fieldName);
|
|
391
|
+
const fits = position === 0
|
|
392
|
+
? found?.kind === "relation" && found.cardinality === "one"
|
|
393
|
+
: found?.kind === "scalar";
|
|
394
|
+
if (!fits) {
|
|
395
|
+
diagnostics.push(misnamed(`${path}.oneOf[${index}][${position}]`, position === 0
|
|
396
|
+
? `"${fieldName}" is not a to-one relation of resource "${name}"`
|
|
397
|
+
: `"${fieldName}" is not a scalar field of resource "${name}"`));
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
const named = new Set([
|
|
402
|
+
...(stated.required ?? []),
|
|
403
|
+
...(stated.oneOf ?? []).flat(),
|
|
404
|
+
]);
|
|
405
|
+
for (const [parent, byRelation] of Object.entries(stated.parentSupplies ?? {})) {
|
|
406
|
+
for (const [relation, members] of Object.entries(byRelation)) {
|
|
407
|
+
const parentResource = own(model.resources, parent);
|
|
408
|
+
const edge = parentResource === undefined
|
|
409
|
+
? undefined
|
|
410
|
+
: own(parentResource.fields, relation);
|
|
411
|
+
if (edge?.kind !== "relation" || edge.target !== name) {
|
|
412
|
+
diagnostics.push(misnamed(`${path}.parentSupplies.${parent}.${relation}`, `"${parent}.${relation}" is not a relation to resource "${name}"`));
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
members.forEach((member, index) => {
|
|
416
|
+
if (!named.has(member)) {
|
|
417
|
+
diagnostics.push(misnamed(`${path}.parentSupplies.${parent}.${relation}[${index}]`, `"${member}" is not a requirement of resource "${name}"`));
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ResolvedPipelineTree } from "../config/pipeline-tree.resolver.js";
|
|
2
|
+
import type { AvContract } from "../contracts/contract.js";
|
|
3
|
+
import type { AvFieldCheck } from "../contracts/field-check.js";
|
|
4
|
+
import { type FieldCheckSubject } from "../runtime/field-check.evaluator.js";
|
|
5
|
+
import type { AvContractCompilerDiagnostic } from "./contract-compiler-diagnostic.js";
|
|
6
|
+
/** A field as the compiler holds it while it builds a projection. */
|
|
7
|
+
type ProjectedField = Readonly<Record<string, unknown>> & {
|
|
8
|
+
readonly kind: "scalar" | "relation";
|
|
9
|
+
};
|
|
10
|
+
/** The fields of each Resource of a projection, by name. */
|
|
11
|
+
type ProjectedResources = Readonly<Record<string, {
|
|
12
|
+
readonly fields: Readonly<Record<string, ProjectedField>>;
|
|
13
|
+
}>>;
|
|
14
|
+
/** What the runtime needs to judge one field's value against its validators. */
|
|
15
|
+
export type CompiledFieldValidators = {
|
|
16
|
+
readonly checks: readonly AvFieldCheck[];
|
|
17
|
+
readonly subject: FieldCheckSubject | undefined;
|
|
18
|
+
readonly list: boolean;
|
|
19
|
+
};
|
|
20
|
+
/** One layer's compiled validators, by Resource and field. */
|
|
21
|
+
export type FieldValidatorTable = Readonly<Record<string, Readonly<Record<string, CompiledFieldValidators>>>>;
|
|
22
|
+
/**
|
|
23
|
+
* Checks the validators one layer wrote, at the paths it wrote them: on a scalar
|
|
24
|
+
* field that is not Json, each one built with `AvZ` and fitting the field's
|
|
25
|
+
* type. A name the model lacks is `checkResourceTree`'s to report.
|
|
26
|
+
*/
|
|
27
|
+
export declare function checkLayerValidators(resources: ProjectedResources, layer: string, tree: unknown, diagnostics: AvContractCompilerDiagnostic[]): void;
|
|
28
|
+
/**
|
|
29
|
+
* The compiled validators of one origin: every check its pipeline tree holds for
|
|
30
|
+
* a field of `resources`, root's then overlay's. A check
|
|
31
|
+
* {@link checkLayerValidators} refused is left out (the compilation fails anyway).
|
|
32
|
+
*/
|
|
33
|
+
export declare function compileValidatorTable(resources: ProjectedResources, tree: ResolvedPipelineTree): FieldValidatorTable;
|
|
34
|
+
/**
|
|
35
|
+
* Writes each field's compiled validators onto the field a projection exposes: a
|
|
36
|
+
* field the layer hides carries none into its Contract.
|
|
37
|
+
*/
|
|
38
|
+
export declare function applyValidators(resources: Readonly<Record<string, {
|
|
39
|
+
readonly fields: Record<string, Record<string, unknown>>;
|
|
40
|
+
}>>, table: FieldValidatorTable): void;
|
|
41
|
+
/** Binds a final Contract to its origin's compiled validators. Compiler only. */
|
|
42
|
+
export declare function bindFieldValidators(contract: AvContract, table: FieldValidatorTable): void;
|
|
43
|
+
/** Whether `contract`'s origin has any validator at all: a check, or a Json Shape. */
|
|
44
|
+
export declare function hasFieldValidators(contract: AvContract): boolean;
|
|
45
|
+
/** `resource`'s fields with validators under `contract`'s origin, in field order. */
|
|
46
|
+
export declare function validatedFieldsOf(contract: AvContract, resource: string): Readonly<Record<string, CompiledFieldValidators>>;
|
|
47
|
+
export {};
|