@aventara/core 1.0.0-rc.4 → 1.0.0-rc.9
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 +157 -0
- package/ERRORS.md +2 -2
- package/README.md +80 -7
- package/dist/adapters/adapter-field.d.ts +8 -0
- package/dist/adapters/adapter-identifier.d.ts +9 -0
- package/dist/adapters/adapter-identifier.js +6 -1
- package/dist/adapters/adapter-operation.d.ts +12 -6
- package/dist/compiler/contract-compiler-diagnostic.guidance.js +2 -2
- package/dist/compiler/contract.compiler.js +26 -3
- package/dist/config/config.resolver.d.ts +2 -0
- package/dist/config/config.resolver.js +7 -2
- package/dist/config/config.validator.js +36 -12
- package/dist/config/exact-framework-config.d.ts +11 -1
- package/dist/config/framework-config.d.ts +14 -1
- package/dist/contracts/contract-canonicalizer.js +2 -0
- package/dist/contracts/contract.d.ts +15 -1
- package/dist/contracts/field.d.ts +17 -0
- package/dist/contracts/scalar-value-type.d.ts +3 -1
- package/dist/framework/contract-view.d.ts +9 -1
- package/dist/framework/framework-construction.error.d.ts +2 -1
- package/dist/framework/framework-construction.error.js +13 -1
- package/dist/framework/framework.d.ts +26 -5
- package/dist/framework/framework.factory.js +29 -10
- package/dist/framework/operation-facade.d.ts +59 -12
- package/dist/index.d.ts +8 -4
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +2 -0
- package/dist/operations/operation-arguments.d.ts +43 -27
- package/dist/operations/operation-call.d.ts +2 -2
- package/dist/operations/operation-result.d.ts +1 -1
- package/dist/policy/access-provider.interface.d.ts +36 -0
- package/dist/policy/access-provider.interface.js +1 -0
- package/dist/policy/access-provider.registry.d.ts +9 -0
- package/dist/policy/access-provider.registry.js +10 -0
- package/dist/policy/policy-admission.js +12 -3
- package/dist/policy/policy-after-update.runtime.js +5 -4
- package/dist/policy/policy-engine.d.ts +11 -4
- package/dist/policy/policy-nested-write.runtime.js +11 -7
- package/dist/policy/policy-read.runtime.d.ts +0 -2
- package/dist/policy/policy-read.runtime.js +18 -9
- package/dist/policy/policy-request.d.ts +7 -3
- package/dist/policy/policy-request.js +14 -3
- package/dist/policy/policy-session.d.ts +14 -4
- package/dist/policy/policy-session.js +12 -11
- package/dist/policy/policy-startup.validator.js +13 -15
- package/dist/policy/policy-write.runtime.d.ts +7 -0
- package/dist/policy/policy-write.runtime.js +23 -8
- package/dist/prebuilt/core-version.generated.d.ts +1 -1
- package/dist/prebuilt/core-version.generated.js +1 -1
- package/dist/prebuilt/prebuilt-changes.d.ts +28 -0
- package/dist/prebuilt/prebuilt-changes.js +65 -0
- package/dist/prebuilt/prebuilt-contracts.loader.js +11 -56
- package/dist/prebuilt/prebuilt-inputs.fingerprint.js +16 -1
- package/dist/protocol/absent-route.responder.d.ts +4 -4
- package/dist/protocol/http-request.decoder.d.ts +2 -2
- package/dist/protocol/http-request.reader.d.ts +2 -2
- package/dist/protocol/http-response.encoder.js +2 -2
- package/dist/protocol/protocol-headers.d.ts +3 -3
- package/dist/runtime/caller-data.copier.js +3 -4
- package/dist/runtime/computed-read-runtime.js +2 -7
- package/dist/runtime/context-executor.runtime.d.ts +2 -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.js +1 -1
- package/dist/runtime/decimal.d.ts +14 -0
- package/dist/runtime/decimal.js +21 -0
- package/dist/runtime/diagnostics-reporter.d.ts +5 -5
- package/dist/runtime/diagnostics-reporter.js +13 -1
- package/dist/runtime/framework-error.js +9 -0
- package/dist/runtime/operation-request.d.ts +9 -9
- package/dist/runtime/operation-runtime.d.ts +8 -8
- package/dist/runtime/operation-runtime.js +30 -9
- package/dist/runtime/owned-execution.registry.d.ts +2 -2
- package/dist/runtime/readonly-value.d.ts +1 -1
- package/dist/runtime/readonly-value.js +2 -2
- package/dist/runtime/referential-action.d.ts +1 -1
- package/dist/runtime/scalar-runtime.js +4 -5
- package/dist/transactions/operation-arguments-encoder.js +8 -3
- package/dist/transactions/sql-statement.d.ts +2 -2
- package/dist/transactions/sql-statement.validator.js +2 -2
- package/dist/transactions/transaction-integrity-error.js +2 -0
- package/dist/transactions/transaction-plan-assembler.d.ts +11 -4
- package/dist/transactions/transaction-plan-assembler.js +17 -2
- package/dist/transactions/transaction-plan-validator.d.ts +5 -5
- package/dist/transactions/transaction-request.d.ts +8 -8
- package/dist/transactions/transaction-runtime.d.ts +2 -2
- package/dist/transactions/transaction-runtime.js +12 -7
- package/dist/transactions/transaction-slot-path-error.js +2 -0
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { adapterPrimaryIdentifier } from "../adapters/adapter-identifier.js";
|
|
1
2
|
import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
|
|
2
3
|
import { validateOperationArguments } from "../operations/operation-validator-core.js";
|
|
3
4
|
import { normalizeOperationScalarValues } from "../runtime/scalar-runtime.js";
|
|
@@ -84,11 +85,11 @@ function questionKey(resource, where) {
|
|
|
84
85
|
return undefined;
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
|
-
function existsOperation(core, resource, where) {
|
|
88
|
+
function existsOperation(core, model, resource, where) {
|
|
88
89
|
const contract = lookupResource(core, resource);
|
|
89
|
-
const
|
|
90
|
+
const identifier = adapterPrimaryIdentifier(model, resource);
|
|
90
91
|
const first = contract?.operations.find?.first !== undefined;
|
|
91
|
-
return first && identifier
|
|
92
|
+
return first && identifier.length > 0
|
|
92
93
|
? {
|
|
93
94
|
resource,
|
|
94
95
|
family: "find",
|
|
@@ -102,16 +103,16 @@ function existsOperation(core, resource, where) {
|
|
|
102
103
|
arguments: { where },
|
|
103
104
|
};
|
|
104
105
|
}
|
|
105
|
-
export function openPolicySession(policy, core, principal, origin, execution) {
|
|
106
|
+
export function openPolicySession(policy, core, model, principal, origin, execution) {
|
|
106
107
|
const caches = {
|
|
107
108
|
scopes: new Map(),
|
|
108
109
|
createGrants: new Map(),
|
|
109
110
|
fields: new Map(),
|
|
110
111
|
exists: new WeakMap(),
|
|
111
112
|
};
|
|
112
|
-
return sessionOn(policy, core, principal, origin, execution, caches);
|
|
113
|
+
return sessionOn(policy, core, model, principal, origin, execution, caches);
|
|
113
114
|
}
|
|
114
|
-
function sessionOn(policy, core, principal, origin, execution, caches) {
|
|
115
|
+
function sessionOn(policy, core, model, principal, origin, execution, caches) {
|
|
115
116
|
const { engine } = policy;
|
|
116
117
|
const exists = (resource, where) => {
|
|
117
118
|
const key = questionKey(resource, where);
|
|
@@ -124,7 +125,7 @@ function sessionOn(policy, core, principal, origin, execution, caches) {
|
|
|
124
125
|
if (known !== undefined) {
|
|
125
126
|
return known;
|
|
126
127
|
}
|
|
127
|
-
const operation = existsOperation(core, resource, where);
|
|
128
|
+
const operation = existsOperation(core, model, resource, where);
|
|
128
129
|
const answer = execution.boundary
|
|
129
130
|
.execute(() => execution.executor.execute(operation))
|
|
130
131
|
.then((result) => typeof result === "number" ? result > 0 : result != null);
|
|
@@ -174,11 +175,11 @@ function sessionOn(policy, core, principal, origin, execution, caches) {
|
|
|
174
175
|
return scope;
|
|
175
176
|
},
|
|
176
177
|
fields(resource, access) {
|
|
177
|
-
const key = `${resource}\u0000${access}`;
|
|
178
|
+
const key = `${resource}\u0000${access.access}\u0000${access.access === "write" ? access.action : ""}`;
|
|
178
179
|
let fields = caches.fields.get(key);
|
|
179
180
|
if (fields === undefined) {
|
|
180
181
|
fields = Promise.resolve()
|
|
181
|
-
.then(() => engine.fieldsOf({ principal, origin, resource, access }))
|
|
182
|
+
.then(() => engine.fieldsOf({ principal, origin, resource, ...access }))
|
|
182
183
|
.then((answer) => fieldsAnswer(core, resource, answer));
|
|
183
184
|
caches.fields.set(key, fields);
|
|
184
185
|
}
|
|
@@ -211,10 +212,10 @@ function sessionOn(policy, core, principal, origin, execution, caches) {
|
|
|
211
212
|
policy.afterChecks[resource]?.includes(action) === true);
|
|
212
213
|
},
|
|
213
214
|
on(next) {
|
|
214
|
-
return sessionOn(policy, core, principal, origin, next, caches);
|
|
215
|
+
return sessionOn(policy, core, model, principal, origin, next, caches);
|
|
215
216
|
},
|
|
216
217
|
renewed() {
|
|
217
|
-
return sessionOn(policy, core, principal, origin, execution, {
|
|
218
|
+
return sessionOn(policy, core, model, principal, origin, execution, {
|
|
218
219
|
...caches,
|
|
219
220
|
exists: new WeakMap(),
|
|
220
221
|
});
|
|
@@ -3,30 +3,28 @@ const TO_MANY_OPERATORS = new Set(["some", "every", "none"]);
|
|
|
3
3
|
function invalid(path, message) {
|
|
4
4
|
return { code: "CONFIG_INVALID_VALUE", path, message };
|
|
5
5
|
}
|
|
6
|
-
function governedDiagnostics(core, governed) {
|
|
6
|
+
function governedDiagnostics(core, { at, governed }) {
|
|
7
7
|
const diagnostics = [];
|
|
8
8
|
for (const [resourceName, names] of Object.entries(governed)) {
|
|
9
|
-
const path =
|
|
9
|
+
const path = `${at}.governed.${resourceName}`;
|
|
10
10
|
const resource = lookupResource(core, resourceName);
|
|
11
11
|
if (resource === undefined) {
|
|
12
|
-
diagnostics.push(invalid(path,
|
|
12
|
+
diagnostics.push(invalid(path, `"${resourceName}" is no Resource of the Core Contract`));
|
|
13
13
|
continue;
|
|
14
14
|
}
|
|
15
15
|
for (const name of names) {
|
|
16
16
|
if (lookupField(resource, name) === undefined) {
|
|
17
|
-
diagnostics.push(invalid(path,
|
|
17
|
+
diagnostics.push(invalid(path, `"${name}" is no field or relation of Resource "${resourceName}" in the Core Contract`));
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
return diagnostics;
|
|
22
22
|
}
|
|
23
|
-
function afterCheckDiagnostics(core, afterChecks) {
|
|
23
|
+
function afterCheckDiagnostics(core, { at, afterChecks }) {
|
|
24
24
|
return Object.keys(afterChecks).flatMap((resourceName) => {
|
|
25
|
-
const path =
|
|
25
|
+
const path = `${at}.afterChecks.${resourceName}`;
|
|
26
26
|
return lookupResource(core, resourceName) === undefined
|
|
27
|
-
? [
|
|
28
|
-
invalid(path, `${path}: "${resourceName}" is no Resource of the Core Contract`),
|
|
29
|
-
]
|
|
27
|
+
? [invalid(path, `"${resourceName}" is no Resource of the Core Contract`)]
|
|
30
28
|
: [];
|
|
31
29
|
});
|
|
32
30
|
}
|
|
@@ -84,10 +82,10 @@ function pathFault(core, path) {
|
|
|
84
82
|
}
|
|
85
83
|
return `the path ends at Resource "${current}", not at a field`;
|
|
86
84
|
}
|
|
87
|
-
function pathDiagnostics(core,
|
|
85
|
+
function pathDiagnostics(core, policy) {
|
|
88
86
|
const diagnostics = [];
|
|
89
|
-
for (const [rule, list] of Object.entries(paths)) {
|
|
90
|
-
const at =
|
|
87
|
+
for (const [rule, list] of Object.entries(policy.paths)) {
|
|
88
|
+
const at = `${policy.at}.paths.${rule}`;
|
|
91
89
|
for (const path of list) {
|
|
92
90
|
const fault = pathFault(core, path);
|
|
93
91
|
if (fault !== undefined) {
|
|
@@ -99,8 +97,8 @@ function pathDiagnostics(core, paths) {
|
|
|
99
97
|
}
|
|
100
98
|
export function policyStartupDiagnostics(core, policy) {
|
|
101
99
|
return [
|
|
102
|
-
...governedDiagnostics(core, policy
|
|
103
|
-
...pathDiagnostics(core, policy
|
|
104
|
-
...afterCheckDiagnostics(core, policy
|
|
100
|
+
...governedDiagnostics(core, policy),
|
|
101
|
+
...pathDiagnostics(core, policy),
|
|
102
|
+
...afterCheckDiagnostics(core, policy),
|
|
105
103
|
];
|
|
106
104
|
}
|
|
@@ -9,6 +9,13 @@ import type { PolicySession } from "./policy-session.js";
|
|
|
9
9
|
export type PolicyWrite = PolicyRead & {
|
|
10
10
|
/** A unique write that matched nothing: `A4002` for a row the caller can read, `A2003` otherwise. */
|
|
11
11
|
readonly miss?: () => Promise<AvOperationFailureResponse>;
|
|
12
|
+
/**
|
|
13
|
+
* A unique update holding nested writes whose adapter call failed: the miss's
|
|
14
|
+
* answer when its row is outside the scopes (an adapter may fail the nested
|
|
15
|
+
* write there instead of answering the miss), `undefined` when the failure
|
|
16
|
+
* stands.
|
|
17
|
+
*/
|
|
18
|
+
readonly failedMiss?: () => Promise<AvOperationFailureResponse | undefined>;
|
|
12
19
|
};
|
|
13
20
|
/**
|
|
14
21
|
* Whether a write the policy checks needs an interactive transaction to roll
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { adapterPrimaryIdentifier } from "../adapters/adapter-identifier.js";
|
|
1
2
|
import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
|
|
2
3
|
import { uniqueMissFailure } from "../runtime/outcome-mapper.js";
|
|
3
4
|
import { frozenCopy } from "../runtime/readonly-value.js";
|
|
@@ -5,7 +6,7 @@ import { policyRefusal } from "./policy-admission.js";
|
|
|
5
6
|
import { afterCheckFields, afterUpdate, checkChanged, } from "./policy-after-update.runtime.js";
|
|
6
7
|
import { allOf, everyOf, noneOf, someOf, whereOf, } from "./policy-condition.js";
|
|
7
8
|
import { planNestedWrites, writesNested, } from "./policy-nested-write.runtime.js";
|
|
8
|
-
import { enforceReadPolicy, identifiedRows,
|
|
9
|
+
import { enforceReadPolicy, identifiedRows, returnedRowChecks, } from "./policy-read.runtime.js";
|
|
9
10
|
const UNVERIFIABLE = "cannot be verified on this database: it has no interactive transaction to roll the write back in";
|
|
10
11
|
function isRecord(value) {
|
|
11
12
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -52,7 +53,7 @@ async function checkCreated(session, core, model, resource, grants, created) {
|
|
|
52
53
|
if (created.length === 0) {
|
|
53
54
|
return undefined;
|
|
54
55
|
}
|
|
55
|
-
const these = identifiedRows(core,
|
|
56
|
+
const these = identifiedRows(core, adapterPrimaryIdentifier(model, resource), resource, created);
|
|
56
57
|
const missed = allOf(these, someOf([...grants.values()].map(noneOf)));
|
|
57
58
|
if (missed !== false &&
|
|
58
59
|
(await session.renewed().exists(resource, whereOf(missed)))) {
|
|
@@ -72,7 +73,7 @@ function uncheckableCreate(model, request, grants, inTransaction) {
|
|
|
72
73
|
return refused(`${what}, which ${UNVERIFIABLE}.`);
|
|
73
74
|
}
|
|
74
75
|
if (request.variant === "count" ||
|
|
75
|
-
|
|
76
|
+
adapterPrimaryIdentifier(model, request.resource).length === 0) {
|
|
76
77
|
return refused(`${what}, and this create does not return the rows it creates to check by.`);
|
|
77
78
|
}
|
|
78
79
|
return undefined;
|
|
@@ -244,7 +245,8 @@ async function enforceScopedWrite(session, core, model, request, plan, adapterAr
|
|
|
244
245
|
if (changes !== undefined && !inTransaction) {
|
|
245
246
|
return refused(`The policy checks this update on Resource "${resource}" after it runs (${changes.after ? "its grant's after condition" : "whether it moves a row out of what the caller may read"}), which ${UNVERIFIABLE}.`);
|
|
246
247
|
}
|
|
247
|
-
if (changes !== undefined &&
|
|
248
|
+
if (changes !== undefined &&
|
|
249
|
+
adapterPrimaryIdentifier(model, resource).length === 0) {
|
|
248
250
|
return refused(`The policy checks this update on Resource "${resource}" after it runs, and the Resource has no identifier to find the changed rows by.`);
|
|
249
251
|
}
|
|
250
252
|
const nested = action === "update"
|
|
@@ -259,6 +261,19 @@ async function enforceScopedWrite(session, core, model, request, plan, adapterAr
|
|
|
259
261
|
const miss = request.variant === "unique"
|
|
260
262
|
? missOf(session, request, args.where)
|
|
261
263
|
: undefined;
|
|
264
|
+
const failedMiss = miss !== undefined && nested !== undefined
|
|
265
|
+
? async () => {
|
|
266
|
+
const reach = allOf(isRecord(args.where) ? args.where : true, scope);
|
|
267
|
+
return reach !== false &&
|
|
268
|
+
(await session.renewed().exists(resource, whereOf(reach)))
|
|
269
|
+
? undefined
|
|
270
|
+
: miss();
|
|
271
|
+
}
|
|
272
|
+
: undefined;
|
|
273
|
+
const misses = {
|
|
274
|
+
...(miss === undefined ? {} : { miss }),
|
|
275
|
+
...(failedMiss === undefined ? {} : { failedMiss }),
|
|
276
|
+
};
|
|
262
277
|
const carried = changes?.after === true
|
|
263
278
|
? afterCheckFields(core, model, resource)
|
|
264
279
|
: undefined;
|
|
@@ -272,7 +287,7 @@ async function enforceScopedWrite(session, core, model, request, plan, adapterAr
|
|
|
272
287
|
return blocked;
|
|
273
288
|
}
|
|
274
289
|
if (read.kind !== "run") {
|
|
275
|
-
return
|
|
290
|
+
return { ...read, ...misses };
|
|
276
291
|
}
|
|
277
292
|
let run = read;
|
|
278
293
|
if (action === "delete" && run.checks.root.length > 0) {
|
|
@@ -294,7 +309,7 @@ async function enforceScopedWrite(session, core, model, request, plan, adapterAr
|
|
|
294
309
|
...runArgs,
|
|
295
310
|
select: changes.after
|
|
296
311
|
? [...afterCheckFields(core, model, resource)]
|
|
297
|
-
: [...
|
|
312
|
+
: [...adapterPrimaryIdentifier(model, resource)],
|
|
298
313
|
},
|
|
299
314
|
};
|
|
300
315
|
run = withChangedRows(asMany, (data) => checkChanged(session, core, model, resource, changes, rowsOf(data), changes.after));
|
|
@@ -305,7 +320,7 @@ async function enforceScopedWrite(session, core, model, request, plan, adapterAr
|
|
|
305
320
|
}
|
|
306
321
|
if (counted) {
|
|
307
322
|
const runArgs = isRecord(run.arguments) ? run.arguments : {};
|
|
308
|
-
const identifier =
|
|
323
|
+
const identifier = adapterPrimaryIdentifier(model, resource);
|
|
309
324
|
changed = await session.rows(resource, isRecord(runArgs.where) ? runArgs.where : {}, identifier);
|
|
310
325
|
if (changed.length === 0) {
|
|
311
326
|
return { kind: "empty", data: 0 };
|
|
@@ -329,7 +344,7 @@ async function enforceScopedWrite(session, core, model, request, plan, adapterAr
|
|
|
329
344
|
run = withNestedWrites(run, "data", nested);
|
|
330
345
|
}
|
|
331
346
|
run = withoutCarried(run, args, carried);
|
|
332
|
-
return
|
|
347
|
+
return { ...run, ...misses };
|
|
333
348
|
}
|
|
334
349
|
async function enforceUpsert(session, core, model, request, plan, adapterArguments, args, inTransaction) {
|
|
335
350
|
const resource = request.resource;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** The version of this `@aventara/core`. */
|
|
2
|
-
export declare const CORE_VERSION = "1.0.0-rc.
|
|
2
|
+
export declare const CORE_VERSION = "1.0.0-rc.9";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CORE_VERSION = "1.0.0-rc.
|
|
1
|
+
export const CORE_VERSION = "1.0.0-rc.9";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { PrebuiltPartFingerprints } from "./prebuilt-inputs.fingerprint.js";
|
|
2
|
+
/** `a`, `a and b`, `a, b and c`. */
|
|
3
|
+
export declare function listedNames(names: readonly string[]): string;
|
|
4
|
+
/** What a prebuilt file was written in, by and for. */
|
|
5
|
+
export type PrebuiltHeader = {
|
|
6
|
+
readonly format: number;
|
|
7
|
+
readonly core: string;
|
|
8
|
+
readonly protocol: number;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Each way a stored file's header differs from `current`, in the order the
|
|
12
|
+
* runtime checks them: the file format, the core version, the protocol.
|
|
13
|
+
*/
|
|
14
|
+
export declare function prebuiltHeaderChanges(stored: unknown, current: PrebuiltHeader): readonly string[];
|
|
15
|
+
/**
|
|
16
|
+
* Each way a stored file's part fingerprints differ from `current`: the
|
|
17
|
+
* Resources whose inputs changed, the new ones, the gone ones, and, when
|
|
18
|
+
* `sameHeader`, the inputs that belong to no single Resource.
|
|
19
|
+
*/
|
|
20
|
+
export declare function prebuiltInputChanges(stored: unknown, current: PrebuiltPartFingerprints, sameHeader?: boolean): readonly string[];
|
|
21
|
+
/**
|
|
22
|
+
* What differs between a stored prebuilt file and `current`, one clause per
|
|
23
|
+
* difference: its header's, then its inputs' (`… since it was written`).
|
|
24
|
+
* Empty when the two record the same header and inputs.
|
|
25
|
+
*/
|
|
26
|
+
export declare function prebuiltFileChanges(stored: unknown, current: PrebuiltHeader & {
|
|
27
|
+
readonly fingerprints: PrebuiltPartFingerprints;
|
|
28
|
+
}): readonly string[];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
function isRecord(value) {
|
|
2
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
export function listedNames(names) {
|
|
5
|
+
return names.length < 2
|
|
6
|
+
? names.join("")
|
|
7
|
+
: `${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
|
|
8
|
+
}
|
|
9
|
+
export function prebuiltHeaderChanges(stored, current) {
|
|
10
|
+
const header = isRecord(stored) ? stored : {};
|
|
11
|
+
const changes = [];
|
|
12
|
+
if (header.format !== current.format) {
|
|
13
|
+
changes.push(`it is written in file format ${String(header.format)}, and this @aventara/core reads format ${current.format}`);
|
|
14
|
+
}
|
|
15
|
+
if (header.core !== current.core) {
|
|
16
|
+
changes.push(`it was built by @aventara/core ${String(header.core)}, and this is ${current.core}`);
|
|
17
|
+
}
|
|
18
|
+
if (header.protocol !== current.protocol) {
|
|
19
|
+
changes.push(`it was built for protocol version ${String(header.protocol)}, and this @aventara/core speaks version ${current.protocol}`);
|
|
20
|
+
}
|
|
21
|
+
return changes;
|
|
22
|
+
}
|
|
23
|
+
export function prebuiltInputChanges(stored, current, sameHeader = true) {
|
|
24
|
+
const storedResources = isRecord(stored) ? stored.resources : undefined;
|
|
25
|
+
const before = isRecord(storedResources) ? storedResources : {};
|
|
26
|
+
const changed = [];
|
|
27
|
+
const added = [];
|
|
28
|
+
const removed = [];
|
|
29
|
+
for (const [name, fingerprint] of Object.entries(current.resources)) {
|
|
30
|
+
if (!Object.hasOwn(before, name)) {
|
|
31
|
+
added.push(name);
|
|
32
|
+
}
|
|
33
|
+
else if (before[name] !== fingerprint) {
|
|
34
|
+
changed.push(name);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
for (const name of Object.keys(before)) {
|
|
38
|
+
if (!Object.hasOwn(current.resources, name)) {
|
|
39
|
+
removed.push(name);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const resources = (names) => `${names.length === 1 ? "Resource" : "Resources"} ${listedNames(names)}`;
|
|
43
|
+
const be = (names) => (names.length === 1 ? "is" : "are");
|
|
44
|
+
const changes = [];
|
|
45
|
+
if (changed.length > 0) {
|
|
46
|
+
changes.push(`${resources(changed)} changed`);
|
|
47
|
+
}
|
|
48
|
+
if (added.length > 0) {
|
|
49
|
+
changes.push(`${resources(added)} ${be(added)} new`);
|
|
50
|
+
}
|
|
51
|
+
if (removed.length > 0) {
|
|
52
|
+
changes.push(`${resources(removed)} ${be(removed)} gone`);
|
|
53
|
+
}
|
|
54
|
+
if (sameHeader && (!isRecord(stored) || stored.shared !== current.shared)) {
|
|
55
|
+
changes.push("the configuration or model outside the Resources changed");
|
|
56
|
+
}
|
|
57
|
+
return changes;
|
|
58
|
+
}
|
|
59
|
+
export function prebuiltFileChanges(stored, current) {
|
|
60
|
+
const header = prebuiltHeaderChanges(stored, current);
|
|
61
|
+
const inputs = prebuiltInputChanges(isRecord(stored) ? stored.fingerprints : undefined, current.fingerprints, header.length === 0);
|
|
62
|
+
return inputs.length === 0
|
|
63
|
+
? header
|
|
64
|
+
: [...header, `${listedNames(inputs)} since it was written`];
|
|
65
|
+
}
|
|
@@ -3,53 +3,18 @@ import { recordCanonicalContractText } from "../contracts/contract-canonicalizer
|
|
|
3
3
|
import { AVENTARA_PROTOCOL_VERSION } from "../contracts/contract-protocol.js";
|
|
4
4
|
import { emitFrameworkWarning } from "../runtime/framework-warning.emitter.js";
|
|
5
5
|
import { CORE_VERSION } from "./core-version.generated.js";
|
|
6
|
+
import { listedNames, prebuiltHeaderChanges, prebuiltInputChanges, } from "./prebuilt-changes.js";
|
|
6
7
|
import { PREBUILT_FORMAT } from "./prebuilt-contracts.js";
|
|
7
8
|
import { prebuiltInputsFingerprint, prebuiltPartFingerprints, } from "./prebuilt-inputs.fingerprint.js";
|
|
8
9
|
export const PREBUILT_STALE_WARNING = "AVENTARA_PREBUILT_STALE";
|
|
9
10
|
function isRecord(value) {
|
|
10
11
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
11
12
|
}
|
|
12
|
-
function listed(names) {
|
|
13
|
-
return names.length < 2
|
|
14
|
-
? names.join("")
|
|
15
|
-
: `${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
|
|
16
|
-
}
|
|
17
13
|
async function changedInputs(stored, model, config) {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
const before = isRecord(storedResources) ? storedResources : {};
|
|
21
|
-
const changed = [];
|
|
22
|
-
const added = [];
|
|
23
|
-
const removed = [];
|
|
24
|
-
for (const [name, fingerprint] of Object.entries(live.resources)) {
|
|
25
|
-
if (!Object.hasOwn(before, name)) {
|
|
26
|
-
added.push(name);
|
|
27
|
-
}
|
|
28
|
-
else if (before[name] !== fingerprint) {
|
|
29
|
-
changed.push(name);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
for (const name of Object.keys(before)) {
|
|
33
|
-
if (!Object.hasOwn(live.resources, name)) {
|
|
34
|
-
removed.push(name);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
const parts = [];
|
|
38
|
-
if (changed.length > 0) {
|
|
39
|
-
parts.push(`${changed.length === 1 ? "Resource" : "Resources"} ${listed(changed)} changed`);
|
|
40
|
-
}
|
|
41
|
-
if (added.length > 0) {
|
|
42
|
-
parts.push(`${added.length === 1 ? "Resource" : "Resources"} ${listed(added)} ${added.length === 1 ? "is" : "are"} new`);
|
|
43
|
-
}
|
|
44
|
-
if (removed.length > 0) {
|
|
45
|
-
parts.push(`${removed.length === 1 ? "Resource" : "Resources"} ${listed(removed)} ${removed.length === 1 ? "is" : "are"} gone`);
|
|
46
|
-
}
|
|
47
|
-
if (!isRecord(stored) || stored.shared !== live.shared) {
|
|
48
|
-
parts.push("the configuration or model outside the Resources changed");
|
|
49
|
-
}
|
|
50
|
-
return parts.length === 0
|
|
14
|
+
const changes = prebuiltInputChanges(stored, await prebuiltPartFingerprints(model, config));
|
|
15
|
+
return changes.length === 0
|
|
51
16
|
? "its fingerprint does not match the configuration and model"
|
|
52
|
-
: `${
|
|
17
|
+
: `${listedNames(changes)} since it was built`;
|
|
53
18
|
}
|
|
54
19
|
function textAt(record, key) {
|
|
55
20
|
const value = Object.hasOwn(record, key) ? record[key] : undefined;
|
|
@@ -86,23 +51,13 @@ export async function loadPrebuiltContracts(prebuilt, model, config, resolved) {
|
|
|
86
51
|
if (!isRecord(prebuilt)) {
|
|
87
52
|
return { ok: false, reason: "it is not a prebuilt Contracts file" };
|
|
88
53
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
ok: false,
|
|
98
|
-
reason: `it was built by @aventara/core ${String(prebuilt.core)}, and this is ${CORE_VERSION}`,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
if (prebuilt.protocol !== AVENTARA_PROTOCOL_VERSION) {
|
|
102
|
-
return {
|
|
103
|
-
ok: false,
|
|
104
|
-
reason: `it was built for protocol version ${String(prebuilt.protocol)}, and this @aventara/core speaks version ${AVENTARA_PROTOCOL_VERSION}`,
|
|
105
|
-
};
|
|
54
|
+
const [header] = prebuiltHeaderChanges(prebuilt, {
|
|
55
|
+
format: PREBUILT_FORMAT,
|
|
56
|
+
core: CORE_VERSION,
|
|
57
|
+
protocol: AVENTARA_PROTOCOL_VERSION,
|
|
58
|
+
});
|
|
59
|
+
if (header !== undefined) {
|
|
60
|
+
return { ok: false, reason: header };
|
|
106
61
|
}
|
|
107
62
|
if ((await prebuiltInputsFingerprint(model, config)) !== prebuilt.fingerprint) {
|
|
108
63
|
return {
|
|
@@ -126,12 +126,27 @@ function policyInput(policy) {
|
|
|
126
126
|
: undefined;
|
|
127
127
|
return { ...policy, engine: { governed: governed ?? null } };
|
|
128
128
|
}
|
|
129
|
+
function accessInput(access) {
|
|
130
|
+
if (!isRecord(access)) {
|
|
131
|
+
return access;
|
|
132
|
+
}
|
|
133
|
+
const engine = access.policy;
|
|
134
|
+
const governed = engine !== null && typeof engine === "object"
|
|
135
|
+
? engine.governed
|
|
136
|
+
: undefined;
|
|
137
|
+
return { policy: { governed: governed ?? null } };
|
|
138
|
+
}
|
|
129
139
|
function authoredInputs(config) {
|
|
130
140
|
const authored = config;
|
|
131
141
|
const root = {};
|
|
132
142
|
for (const key of Object.keys(authored)) {
|
|
133
143
|
if (!NOT_INPUTS.has(key)) {
|
|
134
|
-
root[key] =
|
|
144
|
+
root[key] =
|
|
145
|
+
key === "policy"
|
|
146
|
+
? policyInput(authored[key])
|
|
147
|
+
: key === "access"
|
|
148
|
+
? accessInput(authored[key])
|
|
149
|
+
: authored[key];
|
|
135
150
|
}
|
|
136
151
|
}
|
|
137
152
|
return root;
|
|
@@ -2,7 +2,7 @@ import type { AvContracts } from "../compiler/contract.compiler.js";
|
|
|
2
2
|
import type { RemoteRequestOrigin } from "../config/config-layer.js";
|
|
3
3
|
import { type AvOperationIdentity } from "../operations/operation-identity.js";
|
|
4
4
|
import { type AvDiagnosticsSink } from "../runtime/diagnostics-reporter.js";
|
|
5
|
-
import type {
|
|
5
|
+
import type { AvRequestProtocol } from "../runtime/operation-request.js";
|
|
6
6
|
import { type AvOperationFailureResponse } from "../runtime/operation-response.js";
|
|
7
7
|
import type { AvProtocolRequest } from "./http-request.decoder.js";
|
|
8
8
|
import { type ReadRequest } from "./http-request.reader.js";
|
|
@@ -36,7 +36,7 @@ export type OperationRouteResolution = {
|
|
|
36
36
|
* is a standard operation the Resource advertises, read through
|
|
37
37
|
* `isOperationVariantAvailable` alone.
|
|
38
38
|
*/
|
|
39
|
-
export declare function resolveOperationRoute(contracts: AvContracts, protocol:
|
|
39
|
+
export declare function resolveOperationRoute(contracts: AvContracts, protocol: AvRequestProtocol, route: OperationRouteSegments, origin?: RemoteRequestOrigin): OperationRouteResolution;
|
|
40
40
|
/**
|
|
41
41
|
* The `/_transactions` route under one client protocol identity: version → hash → the
|
|
42
42
|
* ClientContract's transaction capability. The route exists only when that capability
|
|
@@ -44,14 +44,14 @@ export declare function resolveOperationRoute(contracts: AvContracts, protocol:
|
|
|
44
44
|
* it — and is otherwise absent with core's own refusal of a plan. `undefined` when the
|
|
45
45
|
* route is there.
|
|
46
46
|
*/
|
|
47
|
-
export declare function transactionsRouteFailure(contracts: AvContracts, protocol:
|
|
47
|
+
export declare function transactionsRouteFailure(contracts: AvContracts, protocol: AvRequestProtocol, origin?: RemoteRequestOrigin): AvOperationFailureResponse | undefined;
|
|
48
48
|
/**
|
|
49
49
|
* What a route's checks before its body resolve to: the route a request
|
|
50
50
|
* reaches, or the framework response that answers it instead.
|
|
51
51
|
*/
|
|
52
52
|
type RouteCheck<Route> = ({
|
|
53
53
|
readonly ok: true;
|
|
54
|
-
readonly protocol:
|
|
54
|
+
readonly protocol: AvRequestProtocol;
|
|
55
55
|
} & Route) | {
|
|
56
56
|
readonly ok: false;
|
|
57
57
|
readonly response: AvProtocolResponse;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AvContracts } from "../compiler/contract.compiler.js";
|
|
2
2
|
import type { RemoteRequestOrigin } from "../config/config-layer.js";
|
|
3
3
|
import type { RemoteOperationRequest } from "../runtime/operation-request.js";
|
|
4
|
-
import type {
|
|
4
|
+
import type { AvAgentTransactionRequest, AvClientTransactionRequest } from "../transactions/transaction-request.js";
|
|
5
5
|
import type { AvProtocolResponse } from "./http-response.encoder.js";
|
|
6
6
|
/** One HTTP request as a host hands it to the protocol. */
|
|
7
7
|
export type AvProtocolRequest = {
|
|
@@ -56,7 +56,7 @@ export type DecodedOperationRequest = Extract<DecodedOperation, {
|
|
|
56
56
|
ok: true;
|
|
57
57
|
}>;
|
|
58
58
|
/** What `protocol.decodeTransaction` answers. */
|
|
59
|
-
export type DecodedTransaction = Decoded<
|
|
59
|
+
export type DecodedTransaction = Decoded<AvClientTransactionRequest | AvAgentTransactionRequest>;
|
|
60
60
|
/**
|
|
61
61
|
* `protocol.decodeOperation(request)` — one operation route's HTTP request as
|
|
62
62
|
* the client-origin request `framework.execute` takes, or its answer.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AvRequestProtocol } from "../runtime/operation-request.js";
|
|
2
2
|
import type { OperationRouteSegments } from "./absent-route.responder.js";
|
|
3
3
|
import type { AvProtocolRequest } from "./http-request.decoder.js";
|
|
4
4
|
import type { AvProtocolResponse } from "./http-response.encoder.js";
|
|
@@ -25,7 +25,7 @@ export type ReadRequest = {
|
|
|
25
25
|
/** Adopted from `Aventara-Request-Id`, else minted. */
|
|
26
26
|
readonly requestId: string;
|
|
27
27
|
/** `undefined` when either identity header is absent (`A2000`). */
|
|
28
|
-
readonly protocol:
|
|
28
|
+
readonly protocol: AvRequestProtocol | undefined;
|
|
29
29
|
};
|
|
30
30
|
/** What a value that is not an `AvProtocolRequest` reads as. */
|
|
31
31
|
export type UnreadableRequest = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { encodeBase64, WIRE_SCALAR_PATTERNS, } from "../contracts/wire-scalar-format.js";
|
|
2
|
-
import {
|
|
2
|
+
import { isAvDecimal } from "../runtime/decimal.js";
|
|
3
3
|
import { SERIALIZATION_FAILURE_MESSAGE } from "../runtime/error-boundary.js";
|
|
4
4
|
import { operationFailure, } from "../runtime/operation-response.js";
|
|
5
5
|
import { httpStatus } from "./http-status.table.js";
|
|
@@ -161,7 +161,7 @@ function scalarWireText(value) {
|
|
|
161
161
|
}
|
|
162
162
|
return JSON.stringify(wire);
|
|
163
163
|
}
|
|
164
|
-
if (value
|
|
164
|
+
if (isAvDecimal(value)) {
|
|
165
165
|
return JSON.stringify(value.toString());
|
|
166
166
|
}
|
|
167
167
|
if (value instanceof Uint8Array) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AvRequestProtocol } from "../runtime/operation-request.js";
|
|
2
2
|
/** `AvProtocol.headers` — the header names the protocol owns, as passive data. */
|
|
3
3
|
export declare const headers: Readonly<{
|
|
4
4
|
readonly protocolVersion: "Aventara-Protocol-Version";
|
|
@@ -22,11 +22,11 @@ export declare function normalizeHeaders(received: ReceivedHeaders): Readonly<Re
|
|
|
22
22
|
*/
|
|
23
23
|
export declare function headerValue(normalized: Readonly<Record<string, string>>, name: string): string | undefined;
|
|
24
24
|
/**
|
|
25
|
-
* The identity headers as the `
|
|
25
|
+
* The identity headers as the `AvRequestProtocol` core checks, or `undefined`
|
|
26
26
|
* when either is ABSENT — a request without them is malformed: it does not say which
|
|
27
27
|
* protocol and ClientContract it speaks, so there is nothing to compare.
|
|
28
28
|
*/
|
|
29
|
-
export declare function protocolIdentityOf(normalized: Readonly<Record<string, string>>):
|
|
29
|
+
export declare function protocolIdentityOf(normalized: Readonly<Record<string, string>>): AvRequestProtocol | undefined;
|
|
30
30
|
/**
|
|
31
31
|
* The identity headers a request did not send, in the order the framework sends them —
|
|
32
32
|
* what the `A2000` answering it names. Empty exactly when {@link protocolIdentityOf}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AvDecimal } from "./decimal.js";
|
|
1
|
+
import { AvDecimal, decimalDigitsOf, isAvDecimal } from "./decimal.js";
|
|
2
2
|
const FAILURE_MESSAGES = {
|
|
3
3
|
"unreadable-value": "Value could not be read.",
|
|
4
4
|
"non-plain-value": "Value must be plain data.",
|
|
@@ -19,7 +19,6 @@ const NO_POLICY = {};
|
|
|
19
19
|
const OBJECT_PROTOTYPE = Object.prototype;
|
|
20
20
|
const DATE_PROTOTYPE = Date.prototype;
|
|
21
21
|
const dateValueOf = Date.prototype.getTime;
|
|
22
|
-
const decimalValueOf = AvDecimal.prototype.toString;
|
|
23
22
|
const FAST_WALK_DEPTH = 256;
|
|
24
23
|
const SLOW_WALK = Symbol("slow walk");
|
|
25
24
|
class FastWalkFailure {
|
|
@@ -33,8 +32,8 @@ function copyValueType(value, prototype) {
|
|
|
33
32
|
if (prototype === DATE_PROTOTYPE || value instanceof Date) {
|
|
34
33
|
return new Date(dateValueOf.call(value));
|
|
35
34
|
}
|
|
36
|
-
if (value
|
|
37
|
-
return new AvDecimal(
|
|
35
|
+
if (isAvDecimal(value)) {
|
|
36
|
+
return new AvDecimal(decimalDigitsOf(value));
|
|
38
37
|
}
|
|
39
38
|
if (value instanceof Uint8Array) {
|
|
40
39
|
return new Uint8Array(value);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { adapterPrimaryIdentifier } from "../adapters/adapter-identifier.js";
|
|
1
2
|
import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
|
|
2
3
|
import { AvComputedValueError } from "./computed-value.error.js";
|
|
3
4
|
import { frozenCopy } from "./readonly-value.js";
|
|
@@ -37,12 +38,6 @@ function appendUniqueProjection(values, value) {
|
|
|
37
38
|
values.push(value);
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
|
-
function adapterIdentifier(model, resource) {
|
|
41
|
-
const resources = model.resources;
|
|
42
|
-
return ((Object.hasOwn(resources, resource)
|
|
43
|
-
? resources[resource]?.identifiers?.[0]
|
|
44
|
-
: undefined) ?? []);
|
|
45
|
-
}
|
|
46
41
|
function supportsProjection(request) {
|
|
47
42
|
if (request.family === "upsert") {
|
|
48
43
|
return true;
|
|
@@ -59,7 +54,7 @@ function addScalarProjection(core, model, active, compute, resourceName, fieldNa
|
|
|
59
54
|
if (stage !== undefined) {
|
|
60
55
|
appendUnique(computed, fieldName);
|
|
61
56
|
if (stage.dependsOn.length === 0) {
|
|
62
|
-
for (const member of
|
|
57
|
+
for (const member of adapterPrimaryIdentifier(model, resourceName)) {
|
|
63
58
|
appendUniqueProjection(adapterSelect, member);
|
|
64
59
|
}
|
|
65
60
|
}
|
|
@@ -7,7 +7,7 @@ import type { AvOperationFamily } from "../operations/operation-identity.js";
|
|
|
7
7
|
import { type AvDiagnosticsSink } from "./diagnostics-reporter.js";
|
|
8
8
|
import type { AvComputeContext } from "./field-compute.js";
|
|
9
9
|
import type { MisuseReporter } from "./misuse-reporter.js";
|
|
10
|
-
import type {
|
|
10
|
+
import type { AvApplicationOperationRequest } from "./operation-request.js";
|
|
11
11
|
import type { AvOperationResponse } from "./operation-response.js";
|
|
12
12
|
import type { AvContext, AvTransactionContext, AvTransportContext } from "./pipeline.js";
|
|
13
13
|
import type { AvState } from "./request-state.js";
|
|
@@ -35,7 +35,7 @@ export type ContextFacts = {
|
|
|
35
35
|
readonly $args: unknown;
|
|
36
36
|
};
|
|
37
37
|
/** Runs one raw application-origin call in `transaction`, or standalone. Total. */
|
|
38
|
-
export type RawOperationRunner<M extends AvAdapterModel> = (request:
|
|
38
|
+
export type RawOperationRunner<M extends AvAdapterModel> = (request: AvApplicationOperationRequest, transaction: AvAdapterTransactionExecutor<M> | undefined) => Promise<AvOperationResponse>;
|
|
39
39
|
export type ContextExecutorDependencies<M extends AvAdapterModel> = {
|
|
40
40
|
/** The model, as construction read it once: the ctx's Resources. */
|
|
41
41
|
readonly model: M;
|