@aventara/core 1.0.0-rc.1 → 1.0.0-rc.10
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 +943 -16
- package/ERRORS.md +142 -0
- package/README.md +274 -16
- 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 +22 -0
- package/dist/adapters/adapter-identifier.d.ts +9 -0
- package/dist/adapters/adapter-identifier.js +6 -1
- package/dist/adapters/adapter-model.d.ts +9 -0
- package/dist/adapters/adapter-operation.d.ts +18 -3
- package/dist/adapters/adapter-resource.d.ts +5 -0
- package/dist/adapters/adapter.interface.d.ts +10 -0
- package/dist/avz/avz.d.ts +1 -1
- package/dist/avz/json-shape.registry.d.ts +2 -1
- 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 +6 -0
- package/dist/compiler/contract.compiler.js +227 -24
- package/dist/compiler/managed-elsewhere.projector.d.ts +52 -0
- package/dist/compiler/managed-elsewhere.projector.js +179 -0
- package/dist/compiler/managed-elsewhere.validator.d.ts +9 -0
- package/dist/compiler/managed-elsewhere.validator.js +74 -0
- package/dist/config/computed-fields.d.ts +62 -0
- package/dist/config/computed-fields.js +73 -0
- package/dist/config/config-diagnostic.guidance.d.ts +6 -0
- package/dist/config/config-diagnostic.guidance.js +32 -0
- package/dist/config/config.resolver.d.ts +24 -0
- package/dist/config/config.resolver.js +65 -0
- package/dist/config/config.validator.js +222 -8
- package/dist/config/define-resource.d.ts +1 -5
- package/dist/config/entrypoint.normalizer.d.ts +3 -2
- package/dist/config/exact-framework-config.d.ts +67 -22
- 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 +53 -15
- package/dist/config/framework-resources-config.d.ts +25 -14
- package/dist/config/pipeline-config.d.ts +4 -4
- package/dist/config/pipeline-tree.resolver.d.ts +2 -2
- package/dist/contracts/access-route.rules.d.ts +10 -0
- package/dist/contracts/access-route.rules.js +45 -0
- package/dist/contracts/capability.d.ts +26 -1
- package/dist/contracts/capability.js +20 -0
- package/dist/contracts/client-contract-structure.validator.d.ts +301 -89
- package/dist/contracts/client-contract-structure.validator.js +37 -2
- package/dist/contracts/contract-access.d.ts +29 -0
- package/dist/contracts/contract-access.js +1 -0
- package/dist/contracts/contract-canonicalizer.d.ts +9 -1
- package/dist/contracts/contract-canonicalizer.js +30 -4
- package/dist/contracts/contract-hash.d.ts +0 -1
- package/dist/contracts/contract-hash.js +1 -1
- package/dist/contracts/contract.d.ts +27 -1
- package/dist/contracts/field.d.ts +58 -1
- package/dist/contracts/json-schema.projector.d.ts +11 -0
- package/dist/contracts/json-schema.projector.js +78 -0
- package/dist/contracts/scalar-value-type.d.ts +3 -1
- package/dist/framework/access-facade.d.ts +116 -0
- package/dist/framework/access-facade.js +120 -0
- package/dist/framework/contract-view.d.ts +120 -16
- package/dist/framework/framework-construction.error.d.ts +14 -4
- package/dist/framework/framework-construction.error.js +18 -2
- package/dist/framework/framework.d.ts +26 -5
- package/dist/framework/framework.factory.d.ts +1 -1
- package/dist/framework/framework.factory.js +115 -23
- package/dist/framework/operation-facade.d.ts +80 -13
- package/dist/framework/operation-facade.js +2 -1
- package/dist/index.d.ts +17 -5
- package/dist/index.js +1 -0
- package/dist/internal.d.ts +7 -2
- package/dist/internal.js +6 -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 +148 -60
- package/dist/operations/operation-call.d.ts +9 -5
- package/dist/operations/operation-result.d.ts +6 -4
- package/dist/operations/operation-validator-core.js +159 -36
- package/dist/operations/operation-validator.d.ts +1 -1
- package/dist/operations/structural-limits.walker.d.ts +1 -1
- 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/policy/access-provider.interface.d.ts +91 -0
- package/dist/policy/access-provider.interface.js +1 -0
- package/dist/policy/access-provider.registry.d.ts +17 -0
- package/dist/policy/access-provider.registry.js +15 -0
- package/dist/policy/access-question.d.ts +20 -0
- package/dist/policy/access-question.js +1 -0
- package/dist/policy/access-question.registry.d.ts +5 -0
- package/dist/policy/access-question.registry.js +19 -0
- package/dist/policy/access-question.runtime.d.ts +36 -0
- package/dist/policy/access-question.runtime.js +297 -0
- package/dist/policy/access-verb.table.d.ts +56 -0
- package/dist/policy/access-verb.table.js +21 -0
- package/dist/policy/access-verbs.d.ts +46 -0
- package/dist/policy/access-verbs.js +1 -0
- package/dist/policy/policy-admission.d.ts +20 -0
- package/dist/policy/policy-admission.js +409 -0
- package/dist/policy/policy-after-update.runtime.d.ts +79 -0
- package/dist/policy/policy-after-update.runtime.js +208 -0
- package/dist/policy/policy-condition.d.ts +15 -0
- package/dist/policy/policy-condition.js +36 -0
- package/dist/policy/policy-engine.d.ts +205 -0
- package/dist/policy/policy-engine.js +1 -0
- package/dist/policy/policy-named-rows.d.ts +18 -0
- package/dist/policy/policy-named-rows.js +51 -0
- package/dist/policy/policy-nested-write.runtime.d.ts +44 -0
- package/dist/policy/policy-nested-write.runtime.js +545 -0
- package/dist/policy/policy-read.runtime.d.ts +81 -0
- package/dist/policy/policy-read.runtime.js +503 -0
- package/dist/policy/policy-reference-write.runtime.d.ts +51 -0
- package/dist/policy/policy-reference-write.runtime.js +345 -0
- package/dist/policy/policy-request.d.ts +41 -0
- package/dist/policy/policy-request.js +63 -0
- package/dist/policy/policy-session.d.ts +106 -0
- package/dist/policy/policy-session.js +295 -0
- package/dist/policy/policy-startup.validator.d.ts +10 -0
- package/dist/policy/policy-startup.validator.js +112 -0
- package/dist/policy/policy-write.runtime.d.ts +39 -0
- package/dist/policy/policy-write.runtime.js +532 -0
- package/dist/prebuilt/core-version.generated.d.ts +2 -0
- package/dist/prebuilt/core-version.generated.js +1 -0
- package/dist/prebuilt/prebuilt-changes.d.ts +29 -0
- package/dist/prebuilt/prebuilt-changes.js +65 -0
- package/dist/prebuilt/prebuilt-contracts.d.ts +40 -0
- package/dist/prebuilt/prebuilt-contracts.emitter.d.ts +16 -0
- package/dist/prebuilt/prebuilt-contracts.emitter.js +50 -0
- package/dist/prebuilt/prebuilt-contracts.js +1 -0
- package/dist/prebuilt/prebuilt-contracts.loader.d.ts +19 -0
- package/dist/prebuilt/prebuilt-contracts.loader.js +77 -0
- package/dist/prebuilt/prebuilt-inputs.fingerprint.d.ts +16 -0
- package/dist/prebuilt/prebuilt-inputs.fingerprint.js +229 -0
- package/dist/protocol/absent-route.responder.d.ts +9 -27
- package/dist/protocol/absent-route.responder.js +36 -3
- package/dist/protocol/contract-document.responder.d.ts +2 -1
- package/dist/protocol/http-request.decoder.d.ts +19 -3
- package/dist/protocol/http-request.decoder.js +24 -1
- package/dist/protocol/http-request.reader.d.ts +6 -3
- package/dist/protocol/http-request.reader.js +1 -0
- package/dist/protocol/http-response.encoder.js +2 -2
- package/dist/protocol/protocol-headers.d.ts +3 -3
- package/dist/protocol/protocol-surface.d.ts +6 -1
- package/dist/protocol/protocol-surface.js +7 -0
- package/dist/protocol/protocol.binder.d.ts +16 -1
- package/dist/protocol/protocol.binder.js +4 -1
- package/dist/protocol/protocol.handler.d.ts +12 -1
- package/dist/protocol/protocol.handler.js +86 -17
- package/dist/runtime/caller-data.copier.d.ts +18 -3
- package/dist/runtime/caller-data.copier.js +21 -8
- package/dist/runtime/caller-data.door.d.ts +13 -6
- package/dist/runtime/computed-read-runtime.d.ts +6 -1
- package/dist/runtime/computed-read-runtime.js +232 -45
- package/dist/runtime/computed-value.error.d.ts +3 -2
- package/dist/runtime/computed-value.error.js +3 -2
- package/dist/runtime/context-executor.runtime.d.ts +4 -3
- package/dist/runtime/context-executor.runtime.js +4 -2
- package/dist/runtime/core-class.brand.d.ts +10 -0
- package/dist/runtime/core-class.brand.js +21 -0
- package/dist/runtime/create-requirement.d.ts +31 -5
- package/dist/runtime/create-requirement.js +129 -21
- package/dist/runtime/decimal.d.ts +14 -0
- package/dist/runtime/decimal.js +21 -0
- package/dist/runtime/diagnostics-reporter.d.ts +44 -9
- package/dist/runtime/diagnostics-reporter.js +13 -1
- package/dist/runtime/error-boundary.d.ts +9 -2
- package/dist/runtime/error-boundary.js +12 -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/execution-options.d.ts +8 -0
- package/dist/runtime/field-check.evaluator.d.ts +0 -2
- package/dist/runtime/field-check.evaluator.js +1 -1
- package/dist/runtime/field-compute.d.ts +11 -10
- package/dist/runtime/field-stage.runtime.d.ts +3 -1
- package/dist/runtime/field-stage.runtime.js +8 -4
- package/dist/runtime/field-validator.runtime.d.ts +19 -1
- package/dist/runtime/field-validator.runtime.js +47 -8
- package/dist/runtime/framework-error.js +9 -0
- package/dist/runtime/framework-warning.emitter.d.ts +6 -0
- package/dist/runtime/framework-warning.emitter.js +8 -0
- package/dist/runtime/json-value.d.ts +10 -5
- package/dist/runtime/level-lifecycle.runtime.d.ts +4 -3
- package/dist/runtime/level-lifecycle.runtime.js +3 -5
- package/dist/runtime/mutation-planner.d.ts +1 -6
- 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-request.d.ts +16 -9
- package/dist/runtime/operation-runtime.d.ts +35 -17
- package/dist/runtime/operation-runtime.js +220 -49
- package/dist/runtime/outcome-mapper.d.ts +2 -1
- package/dist/runtime/output-validator.d.ts +1 -1
- package/dist/runtime/owned-execution.registry.d.ts +13 -3
- 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 +47 -22
- package/dist/runtime/readonly-value.d.ts +20 -5
- package/dist/runtime/readonly-value.js +74 -26
- package/dist/runtime/referential-action.d.ts +1 -1
- package/dist/runtime/scalar-runtime.d.ts +4 -11
- package/dist/runtime/scalar-runtime.js +91 -22
- package/dist/runtime/unsent-value.normalizer.d.ts +15 -0
- package/dist/runtime/unsent-value.normalizer.js +128 -0
- package/dist/transactions/operation-arguments-encoder.js +8 -3
- package/dist/transactions/operation-fingerprint.js +2 -2
- package/dist/transactions/sql-statement.d.ts +10 -2
- package/dist/transactions/sql-statement.validator.d.ts +2 -2
- package/dist/transactions/sql-statement.validator.js +38 -5
- package/dist/transactions/transaction-envelope.d.ts +5 -4
- package/dist/transactions/transaction-integrity-error.js +2 -0
- package/dist/transactions/transaction-plan-assembler.d.ts +12 -5
- package/dist/transactions/transaction-plan-assembler.js +48 -10
- package/dist/transactions/transaction-plan-validator.d.ts +23 -7
- package/dist/transactions/transaction-plan-validator.js +27 -1
- package/dist/transactions/transaction-reference-resolver.d.ts +3 -2
- package/dist/transactions/transaction-reference-validator.d.ts +43 -3
- package/dist/transactions/transaction-reference-validator.js +83 -57
- package/dist/transactions/transaction-reference.d.ts +1 -1
- package/dist/transactions/transaction-reference.js +5 -0
- package/dist/transactions/transaction-request.d.ts +8 -8
- package/dist/transactions/transaction-runtime.d.ts +5 -2
- package/dist/transactions/transaction-runtime.js +79 -27
- package/dist/transactions/transaction-settlement.runtime.d.ts +4 -3
- package/dist/transactions/transaction-slot-path-error.js +2 -0
- package/dist/transactions/transaction-stage.runtime.d.ts +5 -3
- package/dist/transactions/transaction-stage.runtime.js +7 -4
- package/package.json +6 -5
- package/src/runtime/field-check.evaluator.ts +1 -1
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
import { adapterPrimaryIdentifier } from "../adapters/adapter-identifier.js";
|
|
2
|
+
import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
|
|
3
|
+
import { policyRefusal } from "./policy-admission.js";
|
|
4
|
+
import { allOf, anyOf, everyOf, noneOf, someOf, } from "./policy-condition.js";
|
|
5
|
+
import { fieldAccess } from "./policy-session.js";
|
|
6
|
+
function isRecord(value) {
|
|
7
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
function relationOf(core, resource, name) {
|
|
10
|
+
const field = lookupField(lookupResource(core, resource), name);
|
|
11
|
+
return field?.kind === "relation"
|
|
12
|
+
? field
|
|
13
|
+
: undefined;
|
|
14
|
+
}
|
|
15
|
+
function innerFilters(relation, value) {
|
|
16
|
+
return relation.cardinality === "many"
|
|
17
|
+
? [value.some, value.every, value.none]
|
|
18
|
+
: [value];
|
|
19
|
+
}
|
|
20
|
+
class ReadTargets {
|
|
21
|
+
core;
|
|
22
|
+
resources = new Set();
|
|
23
|
+
constructor(core) {
|
|
24
|
+
this.core = core;
|
|
25
|
+
}
|
|
26
|
+
where(resource, where) {
|
|
27
|
+
if (!isRecord(where)) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
for (const [key, value] of Object.entries(where)) {
|
|
31
|
+
if (key === "AND" || key === "OR" || key === "NOT") {
|
|
32
|
+
for (const entry of Array.isArray(value) ? value : [value]) {
|
|
33
|
+
this.where(resource, entry);
|
|
34
|
+
}
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const relation = relationOf(this.core, resource, key);
|
|
38
|
+
if (relation === undefined || !isRecord(value)) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
this.resources.add(relation.target);
|
|
42
|
+
for (const filter of innerFilters(relation, value)) {
|
|
43
|
+
this.where(relation.target, filter);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
orderBy(resource, orderBy) {
|
|
48
|
+
for (const entry of Array.isArray(orderBy) ? orderBy : [orderBy]) {
|
|
49
|
+
if (!isRecord(entry)) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
for (const [key, value] of Object.entries(entry)) {
|
|
53
|
+
const relation = relationOf(this.core, resource, key);
|
|
54
|
+
if (relation !== undefined) {
|
|
55
|
+
this.resources.add(relation.target);
|
|
56
|
+
this.orderBy(relation.target, value);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
select(resource, select) {
|
|
62
|
+
if (!Array.isArray(select)) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
for (const entry of select) {
|
|
66
|
+
if (!isRecord(entry)) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
for (const [name, value] of Object.entries(entry)) {
|
|
70
|
+
const relation = relationOf(this.core, resource, name);
|
|
71
|
+
if (relation === undefined) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
this.resources.add(relation.target);
|
|
75
|
+
if (isRecord(value)) {
|
|
76
|
+
this.where(relation.target, value.where);
|
|
77
|
+
this.orderBy(relation.target, value.orderBy);
|
|
78
|
+
this.select(relation.target, value.select);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
class ScopedWhere {
|
|
85
|
+
core;
|
|
86
|
+
scopes;
|
|
87
|
+
constructor(core, scopes) {
|
|
88
|
+
this.core = core;
|
|
89
|
+
this.scopes = scopes;
|
|
90
|
+
}
|
|
91
|
+
scope(resource) {
|
|
92
|
+
return this.scopes.get(resource) ?? true;
|
|
93
|
+
}
|
|
94
|
+
of(resource, where) {
|
|
95
|
+
if (!isRecord(where)) {
|
|
96
|
+
return where;
|
|
97
|
+
}
|
|
98
|
+
let changed = false;
|
|
99
|
+
const conditions = [];
|
|
100
|
+
const kept = {};
|
|
101
|
+
for (const [key, value] of Object.entries(where)) {
|
|
102
|
+
if ((key === "AND" || key === "OR") && Array.isArray(value)) {
|
|
103
|
+
const parts = value.map((entry) => this.of(resource, entry));
|
|
104
|
+
if (parts.every((part, index) => part === value[index])) {
|
|
105
|
+
kept[key] = value;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
changed = true;
|
|
109
|
+
conditions.push(key === "AND"
|
|
110
|
+
? everyOf(parts)
|
|
111
|
+
: someOf(parts));
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (key === "NOT") {
|
|
115
|
+
const part = this.of(resource, value);
|
|
116
|
+
if (part === value) {
|
|
117
|
+
kept[key] = value;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
changed = true;
|
|
121
|
+
conditions.push(noneOf(part));
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const relation = relationOf(this.core, resource, key);
|
|
125
|
+
const filter = relation === undefined || !isRecord(value)
|
|
126
|
+
? undefined
|
|
127
|
+
: this.relationFilter(key, relation, value);
|
|
128
|
+
if (filter === undefined) {
|
|
129
|
+
kept[key] = value;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
changed = true;
|
|
133
|
+
conditions.push(...filter);
|
|
134
|
+
}
|
|
135
|
+
if (!changed) {
|
|
136
|
+
return where;
|
|
137
|
+
}
|
|
138
|
+
return everyOf([
|
|
139
|
+
Object.keys(kept).length === 0 ? true : kept,
|
|
140
|
+
...conditions,
|
|
141
|
+
]);
|
|
142
|
+
}
|
|
143
|
+
relationFilter(key, relation, value) {
|
|
144
|
+
const scope = this.scope(relation.target);
|
|
145
|
+
if (relation.cardinality === "one") {
|
|
146
|
+
const inner = this.of(relation.target, value);
|
|
147
|
+
if (scope === true && inner === value) {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
const condition = allOf(inner, scope);
|
|
151
|
+
return [
|
|
152
|
+
typeof condition === "boolean"
|
|
153
|
+
? condition && { [key]: {} }
|
|
154
|
+
: { [key]: condition },
|
|
155
|
+
];
|
|
156
|
+
}
|
|
157
|
+
const inner = Object.keys(value).map((operator) => [operator, this.of(relation.target, value[operator])]);
|
|
158
|
+
if (scope === true &&
|
|
159
|
+
inner.every(([operator, condition]) => condition === value[operator])) {
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
return inner.map(([operator, raw]) => {
|
|
163
|
+
const condition = raw;
|
|
164
|
+
if (operator === "every") {
|
|
165
|
+
const every = anyOf(condition, noneOf(scope));
|
|
166
|
+
return typeof every === "boolean"
|
|
167
|
+
? every || { [key]: { none: {} } }
|
|
168
|
+
: { [key]: { every } };
|
|
169
|
+
}
|
|
170
|
+
const scoped = allOf(condition, scope);
|
|
171
|
+
if (typeof scoped !== "boolean") {
|
|
172
|
+
return { [key]: { [operator]: scoped } };
|
|
173
|
+
}
|
|
174
|
+
if (operator === "some") {
|
|
175
|
+
return scoped && { [key]: { some: {} } };
|
|
176
|
+
}
|
|
177
|
+
return !scoped || { [key]: { none: {} } };
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function emptyRelationValue(select) {
|
|
182
|
+
const entries = Array.isArray(select) ? select : [];
|
|
183
|
+
if (!entries.includes("$count")) {
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
return entries.some((entry) => entry !== "$count")
|
|
187
|
+
? { data: [], count: 0 }
|
|
188
|
+
: { count: 0 };
|
|
189
|
+
}
|
|
190
|
+
function scopedCursor(cursor, scope) {
|
|
191
|
+
if (!isRecord(cursor) || typeof scope === "boolean") {
|
|
192
|
+
return cursor;
|
|
193
|
+
}
|
|
194
|
+
return { ...cursor, AND: [scope] };
|
|
195
|
+
}
|
|
196
|
+
function withIdentifier(select, identifier) {
|
|
197
|
+
const missing = identifier.filter((member) => !select.includes(member));
|
|
198
|
+
return missing.length === 0 ? select : [...select, ...missing];
|
|
199
|
+
}
|
|
200
|
+
export async function enforceReadPolicy(session, core, model, request, plan, adapterArguments, rows) {
|
|
201
|
+
const args = isRecord(adapterArguments) ? adapterArguments : {};
|
|
202
|
+
const root = request.resource;
|
|
203
|
+
const targets = new ReadTargets(core);
|
|
204
|
+
targets.resources.add(root);
|
|
205
|
+
targets.where(root, args.where);
|
|
206
|
+
targets.orderBy(root, args.orderBy);
|
|
207
|
+
targets.select(root, args.select);
|
|
208
|
+
const scopes = new Map(await Promise.all([...targets.resources].map(async (resource) => [resource, await session.scope(resource, "read")])));
|
|
209
|
+
const scoped = new ScopedWhere(core, scopes);
|
|
210
|
+
let sortRefusal = unscopedSort(core, scoped, root, args.orderBy);
|
|
211
|
+
const checks = await returnedRowChecks(session, core, plan);
|
|
212
|
+
let identity = request;
|
|
213
|
+
let rootWhere = args.where;
|
|
214
|
+
let rootCursor = args.cursor;
|
|
215
|
+
const scope = rows === undefined ? scoped.scope(root) : rows.scope;
|
|
216
|
+
if (scope === undefined) {
|
|
217
|
+
}
|
|
218
|
+
else if (request.variant === "unique") {
|
|
219
|
+
if (scope !== true) {
|
|
220
|
+
const within = allOf(args.where, scope);
|
|
221
|
+
if (within === false) {
|
|
222
|
+
return { kind: "empty", data: null };
|
|
223
|
+
}
|
|
224
|
+
const first = firstVariantOf(core, root, request.family);
|
|
225
|
+
if (rows !== undefined &&
|
|
226
|
+
(request.family === "update" || request.family === "delete")) {
|
|
227
|
+
rootWhere = { ...args.where, AND: [scope] };
|
|
228
|
+
}
|
|
229
|
+
else if (first !== undefined) {
|
|
230
|
+
identity = first;
|
|
231
|
+
rootWhere = within;
|
|
232
|
+
}
|
|
233
|
+
else if (!(await session.exists(root, within))) {
|
|
234
|
+
return { kind: "empty", data: null };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
const where = everyOf([
|
|
240
|
+
args.where === undefined
|
|
241
|
+
? true
|
|
242
|
+
: scoped.of(root, args.where),
|
|
243
|
+
scope,
|
|
244
|
+
]);
|
|
245
|
+
if (where === false) {
|
|
246
|
+
return {
|
|
247
|
+
kind: "empty",
|
|
248
|
+
data: request.variant === "many"
|
|
249
|
+
? []
|
|
250
|
+
: request.variant === "count"
|
|
251
|
+
? 0
|
|
252
|
+
: null,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
rootWhere = where === true ? undefined : where;
|
|
256
|
+
rootCursor = scopedCursor(args.cursor, scope);
|
|
257
|
+
}
|
|
258
|
+
const rewriteLevel = (resource, select, level) => {
|
|
259
|
+
const entries = [];
|
|
260
|
+
const emptied = [];
|
|
261
|
+
const nested = new Map();
|
|
262
|
+
for (const entry of Array.isArray(select) ? select : []) {
|
|
263
|
+
const [name] = isRecord(entry) ? Object.keys(entry) : [];
|
|
264
|
+
const relation = name === undefined ? undefined : relationOf(core, resource, name);
|
|
265
|
+
if (relation === undefined || name === undefined) {
|
|
266
|
+
entries.push(entry);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const value = isRecord(entry[name])
|
|
270
|
+
? entry[name]
|
|
271
|
+
: {};
|
|
272
|
+
const nestedLevel = level?.relations.find((candidate) => candidate.field === name)?.projection;
|
|
273
|
+
let rewritten = { ...value };
|
|
274
|
+
sortRefusal ??= unscopedSort(core, scoped, relation.target, value.orderBy);
|
|
275
|
+
if (relation.cardinality === "many") {
|
|
276
|
+
const where = everyOf([
|
|
277
|
+
value.where === undefined
|
|
278
|
+
? true
|
|
279
|
+
: scoped.of(relation.target, value.where),
|
|
280
|
+
scoped.scope(relation.target),
|
|
281
|
+
]);
|
|
282
|
+
if (where === false) {
|
|
283
|
+
emptied.push({
|
|
284
|
+
field: name,
|
|
285
|
+
value: emptyRelationValue(value.select),
|
|
286
|
+
});
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (where === true) {
|
|
290
|
+
delete rewritten.where;
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
rewritten.where = where;
|
|
294
|
+
}
|
|
295
|
+
if (value.cursor !== undefined) {
|
|
296
|
+
rewritten.cursor = scopedCursor(value.cursor, scoped.scope(relation.target));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const inner = rewriteLevel(relation.target, value.select, nestedLevel);
|
|
300
|
+
nested.set(name, inner);
|
|
301
|
+
rewritten = { ...rewritten, select: inner.select };
|
|
302
|
+
entries.push({ [name]: rewritten });
|
|
303
|
+
}
|
|
304
|
+
const checked = level !== undefined &&
|
|
305
|
+
(checks.has(level) ||
|
|
306
|
+
(level === plan.projection && rows?.identified === true));
|
|
307
|
+
const carried = level !== undefined && level === plan.projection
|
|
308
|
+
? (rows?.carried ?? [])
|
|
309
|
+
: [];
|
|
310
|
+
return {
|
|
311
|
+
select: withIdentifier(checked
|
|
312
|
+
? withIdentifier(entries, adapterPrimaryIdentifier(model, resource))
|
|
313
|
+
: entries, carried),
|
|
314
|
+
emptied,
|
|
315
|
+
nested,
|
|
316
|
+
};
|
|
317
|
+
};
|
|
318
|
+
const rewritten = rewriteLevel(root, args.select, plan.projection);
|
|
319
|
+
if (sortRefusal !== undefined) {
|
|
320
|
+
return { kind: "refused", response: policyRefusal(sortRefusal) };
|
|
321
|
+
}
|
|
322
|
+
const runArguments = { ...args };
|
|
323
|
+
if (rootWhere === undefined) {
|
|
324
|
+
delete runArguments.where;
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
runArguments.where = rootWhere;
|
|
328
|
+
}
|
|
329
|
+
if (rootCursor !== undefined) {
|
|
330
|
+
runArguments.cursor = rootCursor;
|
|
331
|
+
}
|
|
332
|
+
if (Array.isArray(args.select)) {
|
|
333
|
+
runArguments.select = rewritten.select;
|
|
334
|
+
}
|
|
335
|
+
const changesShape = checks.size > 0 || hasEmptied(rewritten);
|
|
336
|
+
return {
|
|
337
|
+
kind: "run",
|
|
338
|
+
identity,
|
|
339
|
+
arguments: runArguments,
|
|
340
|
+
plan: changesShape ||
|
|
341
|
+
rows?.identified === true ||
|
|
342
|
+
(rows?.carried?.length ?? 0) > 0
|
|
343
|
+
? { ...plan, materialize: true }
|
|
344
|
+
: plan,
|
|
345
|
+
checks: {
|
|
346
|
+
root: plan.projection === undefined
|
|
347
|
+
? []
|
|
348
|
+
: (checks.get(plan.projection) ?? []),
|
|
349
|
+
nested: [...checks.keys()].some((level) => level !== plan.projection),
|
|
350
|
+
},
|
|
351
|
+
settle: async (data) => (await rowsReadable(request.family === "find" ? session : session.renewed(), core, model, plan.projection, checks, data))
|
|
352
|
+
? { ok: true, data: filledEmptied(rewritten, data) }
|
|
353
|
+
: {
|
|
354
|
+
ok: false,
|
|
355
|
+
response: policyRefusal("The policy does not allow reading every row or field this request returns."),
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
export async function returnedRowChecks(session, core, plan) {
|
|
360
|
+
const checks = new Map();
|
|
361
|
+
const collect = async (level, reachedOne) => {
|
|
362
|
+
if (level === undefined) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const fields = await session.fields(level.resource, { access: "read" });
|
|
366
|
+
const conditions = [];
|
|
367
|
+
if (reachedOne) {
|
|
368
|
+
const scope = await session.scope(level.resource, "read");
|
|
369
|
+
if (scope !== true) {
|
|
370
|
+
conditions.push(noneOf(scope));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
for (const field of level.requested) {
|
|
374
|
+
const access = fieldAccess(core, level.resource, fields, field);
|
|
375
|
+
if (access !== "always" && access !== "never") {
|
|
376
|
+
conditions.push(noneOf(access));
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (conditions.length > 0) {
|
|
380
|
+
checks.set(level, conditions);
|
|
381
|
+
}
|
|
382
|
+
for (const relation of level.relations) {
|
|
383
|
+
await collect(relation.projection, relation.cardinality === "one");
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
await collect(plan.projection, false);
|
|
387
|
+
return checks;
|
|
388
|
+
}
|
|
389
|
+
function firstVariantOf(core, resource, family) {
|
|
390
|
+
const operations = lookupResource(core, resource)?.operations;
|
|
391
|
+
return family !== "create" &&
|
|
392
|
+
family !== "upsert" &&
|
|
393
|
+
operations?.[family]?.first !== undefined
|
|
394
|
+
? { family, variant: "first" }
|
|
395
|
+
: undefined;
|
|
396
|
+
}
|
|
397
|
+
function hasEmptied(level) {
|
|
398
|
+
return (level.emptied.length > 0 ||
|
|
399
|
+
[...level.nested.values()].some((nested) => hasEmptied(nested)));
|
|
400
|
+
}
|
|
401
|
+
function unscopedSort(core, scoped, resource, orderBy) {
|
|
402
|
+
for (const entry of Array.isArray(orderBy) ? orderBy : [orderBy]) {
|
|
403
|
+
if (!isRecord(entry)) {
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
for (const key of Object.keys(entry)) {
|
|
407
|
+
const relation = relationOf(core, resource, key);
|
|
408
|
+
if (relation !== undefined && scoped.scope(relation.target) !== true) {
|
|
409
|
+
return `The policy does not allow sorting by relation "${key}" of Resource "${resource}": it reaches rows of "${relation.target}" this request may not read.`;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return undefined;
|
|
414
|
+
}
|
|
415
|
+
function rowsByLevel(level, rows, into) {
|
|
416
|
+
if (level === undefined) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
const own = into.get(level) ?? [];
|
|
420
|
+
for (const row of rows) {
|
|
421
|
+
if (isRecord(row)) {
|
|
422
|
+
own.push(row);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
into.set(level, own);
|
|
426
|
+
for (const relation of level.relations) {
|
|
427
|
+
const nested = [];
|
|
428
|
+
for (const row of own) {
|
|
429
|
+
const value = row[relation.field];
|
|
430
|
+
if (Array.isArray(value)) {
|
|
431
|
+
nested.push(...value);
|
|
432
|
+
}
|
|
433
|
+
else if (isRecord(value) && Array.isArray(value.data)) {
|
|
434
|
+
nested.push(...value.data);
|
|
435
|
+
}
|
|
436
|
+
else if (relation.cardinality === "one" && isRecord(value)) {
|
|
437
|
+
nested.push(value);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
rowsByLevel(relation.projection, nested, into);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
export function identifiedRows(core, identifier, resource, rows) {
|
|
444
|
+
const [single] = identifier;
|
|
445
|
+
if (identifier.length === 1 && single !== undefined) {
|
|
446
|
+
const values = [...new Set(rows.map((row) => row[single]))];
|
|
447
|
+
const field = lookupField(lookupResource(core, resource), single);
|
|
448
|
+
if (field?.kind === "scalar" && field.capabilities.filter?.includes("in")) {
|
|
449
|
+
return { [single]: { in: values } };
|
|
450
|
+
}
|
|
451
|
+
return someOf(values.map((value) => ({ [single]: value })));
|
|
452
|
+
}
|
|
453
|
+
return someOf(rows.map((row) => Object.fromEntries(identifier.map((member) => [member, row[member]]))));
|
|
454
|
+
}
|
|
455
|
+
async function rowsReadable(session, core, model, projection, checks, data) {
|
|
456
|
+
if (checks.size === 0 || projection === undefined) {
|
|
457
|
+
return true;
|
|
458
|
+
}
|
|
459
|
+
const levels = new Map();
|
|
460
|
+
rowsByLevel(projection, Array.isArray(data) ? data : [data], levels);
|
|
461
|
+
const questions = [...checks].map(async ([level, misses]) => {
|
|
462
|
+
const rows = levels.get(level) ?? [];
|
|
463
|
+
if (rows.length === 0) {
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
const miss = someOf(misses);
|
|
467
|
+
if (miss === false) {
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
if (miss === true) {
|
|
471
|
+
return true;
|
|
472
|
+
}
|
|
473
|
+
const identifier = adapterPrimaryIdentifier(model, level.resource);
|
|
474
|
+
const reach = allOf(identifiedRows(core, identifier, level.resource, rows), miss);
|
|
475
|
+
return (reach !== false &&
|
|
476
|
+
session.exists(level.resource, reach === true ? {} : reach));
|
|
477
|
+
});
|
|
478
|
+
const answers = await Promise.all(questions);
|
|
479
|
+
return !answers.some((answer) => answer === true);
|
|
480
|
+
}
|
|
481
|
+
function filledEmptied(scopedLevel, data) {
|
|
482
|
+
if (!hasEmptied(scopedLevel)) {
|
|
483
|
+
return data;
|
|
484
|
+
}
|
|
485
|
+
if (Array.isArray(data)) {
|
|
486
|
+
return data.map((row) => filledEmptied(scopedLevel, row));
|
|
487
|
+
}
|
|
488
|
+
if (!isRecord(data)) {
|
|
489
|
+
return data;
|
|
490
|
+
}
|
|
491
|
+
const row = { ...data };
|
|
492
|
+
for (const emptied of scopedLevel.emptied) {
|
|
493
|
+
row[emptied.field] = structuredClone(emptied.value);
|
|
494
|
+
}
|
|
495
|
+
for (const [field, nested] of scopedLevel.nested) {
|
|
496
|
+
const value = row[field];
|
|
497
|
+
row[field] =
|
|
498
|
+
isRecord(value) && Array.isArray(value.data)
|
|
499
|
+
? { ...value, data: filledEmptied(nested, value.data) }
|
|
500
|
+
: filledEmptied(nested, value);
|
|
501
|
+
}
|
|
502
|
+
return row;
|
|
503
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { AvAdapterModel } from "../adapters/adapter-model.js";
|
|
2
|
+
import type { AvContractCore } from "../contracts/contract.js";
|
|
3
|
+
import type { AvOperationIdentity } from "../operations/operation-identity.js";
|
|
4
|
+
import type { AvOperationFailureResponse } from "../runtime/operation-response.js";
|
|
5
|
+
import type { PolicySession } from "./policy-session.js";
|
|
6
|
+
/**
|
|
7
|
+
* Whether a reference written to `target` is held to its read scope: where the
|
|
8
|
+
* Core Contract shows `target` and the engine does not list it in `unheld`
|
|
9
|
+
* (see the header). The one test of it: the hold below, a relation directive's
|
|
10
|
+
* link where the writing row holds the reference
|
|
11
|
+
* (`policy-nested-write.runtime.ts`), and the list the engine is told
|
|
12
|
+
* (`heldTargetsOf`) all read it.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isHeldTarget(core: AvContractCore, unheld: readonly string[], target: string): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* The Resources a written reference is held to the read scope of, frozen: what
|
|
17
|
+
* `AvPolicyEngine.hosted` is told once the Framework is built.
|
|
18
|
+
*/
|
|
19
|
+
export declare function heldTargetsOf(core: AvContractCore, unheld: readonly string[]): readonly string[];
|
|
20
|
+
/** A write's top-level records and the stage each is written at. */
|
|
21
|
+
type ReferenceSite = {
|
|
22
|
+
readonly stage: "create" | "update";
|
|
23
|
+
readonly records: readonly unknown[];
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* What the policy does to a write's references: refuse the write before it
|
|
27
|
+
* runs, or hand the adapter its records with the held ones written as links.
|
|
28
|
+
*/
|
|
29
|
+
export type HeldReferences = {
|
|
30
|
+
readonly kind: "refused";
|
|
31
|
+
readonly response: AvOperationFailureResponse;
|
|
32
|
+
} | {
|
|
33
|
+
readonly kind: "held";
|
|
34
|
+
/** Whether a record the write sends is written as links: the write may fail on one. */
|
|
35
|
+
readonly linked: boolean;
|
|
36
|
+
/** The value of a top-level argument (`data`, `create`, `update`) as the adapter runs it. */
|
|
37
|
+
readonly apply: (value: unknown, stage: "create" | "update") => unknown;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* The references a write's records (`sites`, of the request's Resource, nested
|
|
41
|
+
* records included) write, held to their targets' read scopes: the refusal of
|
|
42
|
+
* a value that names no row, or that a record cannot hold inside the write and
|
|
43
|
+
* names a row outside the scope or not there (one count per target Resource);
|
|
44
|
+
* otherwise
|
|
45
|
+
* how the adapter's records write the held references as links. No query for a
|
|
46
|
+
* record written as links, nor where every target's scope is `true`.
|
|
47
|
+
*/
|
|
48
|
+
export declare function heldReferences(session: PolicySession, core: AvContractCore, model: AvAdapterModel, request: AvOperationIdentity & {
|
|
49
|
+
readonly resource: string;
|
|
50
|
+
}, sites: readonly ReferenceSite[]): Promise<HeldReferences>;
|
|
51
|
+
export {};
|