@aventara/core 1.0.0-rc.4 → 1.0.0-rc.8
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 +101 -0
- package/ERRORS.md +1 -1
- package/README.md +5 -1
- 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.js +26 -3
- package/dist/config/framework-config.d.ts +5 -1
- 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/framework-construction.error.d.ts +2 -1
- package/dist/framework/framework-construction.error.js +11 -1
- package/dist/framework/framework.d.ts +4 -4
- package/dist/framework/operation-facade.d.ts +6 -6
- package/dist/index.d.ts +6 -3
- package/dist/internal.d.ts +1 -2
- package/dist/internal.js +1 -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/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 +3 -5
- 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/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/protocol-headers.d.ts +3 -3
- package/dist/runtime/computed-read-runtime.js +2 -7
- package/dist/runtime/context-executor.runtime.d.ts +2 -2
- package/dist/runtime/create-requirement.js +1 -1
- package/dist/runtime/diagnostics-reporter.d.ts +5 -5
- 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/referential-action.d.ts +1 -1
- package/dist/transactions/sql-statement.d.ts +2 -2
- package/dist/transactions/transaction-plan-assembler.d.ts +2 -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/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
|
});
|
|
@@ -9,12 +9,12 @@ function governedDiagnostics(core, governed) {
|
|
|
9
9
|
const path = `policy.engine.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
|
}
|
|
@@ -24,9 +24,7 @@ function afterCheckDiagnostics(core, afterChecks) {
|
|
|
24
24
|
return Object.keys(afterChecks).flatMap((resourceName) => {
|
|
25
25
|
const path = `policy.engine.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
|
}
|
|
@@ -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.8";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CORE_VERSION = "1.0.0-rc.
|
|
1
|
+
export const CORE_VERSION = "1.0.0-rc.8";
|
|
@@ -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 {
|
|
@@ -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,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,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;
|
|
@@ -13,7 +13,7 @@ function edgesOf(table, resource) {
|
|
|
13
13
|
return Array.isArray(listed) ? listed : [];
|
|
14
14
|
}
|
|
15
15
|
function referencesOf(edge) {
|
|
16
|
-
return Array.isArray(edge.
|
|
16
|
+
return Array.isArray(edge.referenceFields) ? edge.referenceFields : [];
|
|
17
17
|
}
|
|
18
18
|
function creatableScalar(contract, resource, field) {
|
|
19
19
|
const found = lookupField(lookupResource(contract, resource), field);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AvRequestOrigin } from "../config/config-layer.js";
|
|
2
2
|
import type { AvOperationFamily } from "../operations/operation-identity.js";
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
3
|
+
import type { AvTransactionRequest } from "../transactions/transaction-request.js";
|
|
4
|
+
import type { AvOperationRequest } from "./operation-request.js";
|
|
5
5
|
import { type AvOperationErrorCode, type AvOperationFailureResponse } from "./operation-response.js";
|
|
6
6
|
/**
|
|
7
7
|
* One unplanned failure of an operation: the raw error, the code the caller
|
|
@@ -100,14 +100,14 @@ export declare function diagnosticsSinkOf(framework: object): AvDiagnosticsSink
|
|
|
100
100
|
* Binds the configured sink to one operation request. The sink is not awaited,
|
|
101
101
|
* and nothing it throws or rejects with reaches the response.
|
|
102
102
|
*/
|
|
103
|
-
export declare function createDiagnosticsReporter(sink: AvDiagnosticsSink | undefined, request:
|
|
103
|
+
export declare function createDiagnosticsReporter(sink: AvDiagnosticsSink | undefined, request: AvOperationRequest, operation?: number): DiagnosticsReporter;
|
|
104
104
|
/** The diagnostics channel of one transaction request. */
|
|
105
105
|
export type TransactionDiagnosticsChannel = {
|
|
106
106
|
/** A plan-level report; `operation` attributes it to a recorded failing index. */
|
|
107
107
|
readonly transaction: (operation?: number) => DiagnosticsReporter;
|
|
108
108
|
/** A report carrying one plan operation's own identity and index. */
|
|
109
|
-
readonly operation: (request:
|
|
109
|
+
readonly operation: (request: AvOperationRequest, operation: number) => DiagnosticsReporter;
|
|
110
110
|
};
|
|
111
111
|
/** Binds the configured sink to one transaction request and its plan operations. */
|
|
112
|
-
export declare function createTransactionDiagnosticsChannel(sink: AvDiagnosticsSink | undefined, request:
|
|
112
|
+
export declare function createTransactionDiagnosticsChannel(sink: AvDiagnosticsSink | undefined, request: AvTransactionRequest): TransactionDiagnosticsChannel;
|
|
113
113
|
export {};
|
|
@@ -6,7 +6,7 @@ import type { AvState } from "./request-state.js";
|
|
|
6
6
|
* The protocol version and Contract hash a remote caller says it was built for:
|
|
7
7
|
* the Client Contract's for a client, the Agent Contract's for an agent.
|
|
8
8
|
*/
|
|
9
|
-
export type
|
|
9
|
+
export type AvRequestProtocol = {
|
|
10
10
|
readonly version: number;
|
|
11
11
|
readonly hash: string;
|
|
12
12
|
};
|
|
@@ -21,7 +21,7 @@ type RuntimeOperationBase = AvOperationIdentity & {
|
|
|
21
21
|
readonly state?: AvState;
|
|
22
22
|
};
|
|
23
23
|
/** An in-process request, run under the Application Contract. */
|
|
24
|
-
export type
|
|
24
|
+
export type AvApplicationOperationRequest = RuntimeOperationBase & {
|
|
25
25
|
readonly origin: "application";
|
|
26
26
|
readonly protocol?: never;
|
|
27
27
|
readonly transport?: never;
|
|
@@ -30,24 +30,24 @@ export type ApplicationOperationRequest = RuntimeOperationBase & {
|
|
|
30
30
|
* A remote request, run under the Client Contract; `protocol` is the identity
|
|
31
31
|
* the client sent.
|
|
32
32
|
*/
|
|
33
|
-
export type
|
|
33
|
+
export type AvClientOperationRequest = RuntimeOperationBase & {
|
|
34
34
|
readonly origin: "client";
|
|
35
|
-
readonly protocol:
|
|
35
|
+
readonly protocol: AvRequestProtocol;
|
|
36
36
|
readonly transport?: AvTransportContext;
|
|
37
37
|
};
|
|
38
38
|
/**
|
|
39
39
|
* An AI agent's request, run under the Agent Contract; `protocol` is the
|
|
40
40
|
* identity of the Agent Contract the caller (an MCP server) was built from.
|
|
41
41
|
*/
|
|
42
|
-
export type
|
|
42
|
+
export type AvAgentOperationRequest = RuntimeOperationBase & {
|
|
43
43
|
readonly origin: "agent";
|
|
44
|
-
readonly protocol:
|
|
44
|
+
readonly protocol: AvRequestProtocol;
|
|
45
45
|
readonly transport?: AvTransportContext;
|
|
46
46
|
};
|
|
47
47
|
/** A request from outside the server: a client's or an agent's. */
|
|
48
|
-
export type RemoteOperationRequest =
|
|
48
|
+
export type RemoteOperationRequest = AvClientOperationRequest | AvAgentOperationRequest;
|
|
49
49
|
/** One operation request, as `framework.execute` takes it. */
|
|
50
|
-
export type
|
|
50
|
+
export type AvOperationRequest = AvApplicationOperationRequest | AvClientOperationRequest | AvAgentOperationRequest;
|
|
51
51
|
type WithoutArguments<Request> = Request extends unknown ? Omit<Request, "arguments"> : never;
|
|
52
52
|
/**
|
|
53
53
|
* A request as every stage after the Framework Validation Layer sees it:
|
|
@@ -57,7 +57,7 @@ type WithoutArguments<Request> = Request extends unknown ? Omit<Request, "argume
|
|
|
57
57
|
* unreachable by type. Distributive, so the origin and family/variant
|
|
58
58
|
* correlations survive the omission.
|
|
59
59
|
*/
|
|
60
|
-
export type OperationExecutionRequest = WithoutArguments<
|
|
60
|
+
export type OperationExecutionRequest = WithoutArguments<AvOperationRequest>;
|
|
61
61
|
/**
|
|
62
62
|
* A request whose stages run: its `state` settled once, the seed or the `{}`
|
|
63
63
|
* the runtime made for it, so every stage is told the same object.
|