@astrale-os/sdk 0.6.0-beta.41 → 0.6.0-beta.42
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/README.md +22 -2
- package/dist/application/action/context.d.ts +49 -18
- package/dist/application/action/index.d.ts +1 -1
- package/dist/application/error/error.d.ts +16 -1
- package/dist/application/error/error.js +39 -5
- package/dist/application/error/index.d.ts +1 -1
- package/dist/application/error/index.js +1 -1
- package/dist/application/mutation/authoring/builder.d.ts +81 -33
- package/dist/application/mutation/authoring/builder.js +100 -30
- package/dist/application/mutation/authoring/guard.d.ts +37 -0
- package/dist/application/mutation/authoring/guard.js +150 -0
- package/dist/application/mutation/authoring/index.d.ts +5 -2
- package/dist/application/mutation/authoring/index.js +2 -1
- package/dist/application/mutation/authoring/names.d.ts +19 -0
- package/dist/application/mutation/authoring/names.js +83 -0
- package/dist/application/mutation/authoring/transition.d.ts +3 -2
- package/dist/application/mutation/define.d.ts +24 -10
- package/dist/application/mutation/define.js +49 -53
- package/dist/application/mutation/execution/execute.d.ts +2 -2
- package/dist/application/mutation/execution/execute.js +78 -7
- package/dist/application/mutation/execution/executor.d.ts +4 -1
- package/dist/application/mutation/execution/executor.js +6 -1
- package/dist/application/mutation/failure.d.ts +11 -1
- package/dist/application/mutation/failure.js +18 -3
- package/dist/application/mutation/index.d.ts +8 -4
- package/dist/application/mutation/index.js +5 -2
- package/dist/application/mutation/legacy/v6/builder.d.ts +84 -0
- package/dist/application/mutation/legacy/v6/define.d.ts +18 -0
- package/dist/application/mutation/legacy/v6/define.js +56 -0
- package/dist/application/mutation/legacy/v6/mutation.d.ts +28 -0
- package/dist/application/mutation/legacy/v6/mutation.js +1 -0
- package/dist/application/mutation/{authoring → legacy/v6}/precondition.d.ts +4 -0
- package/dist/application/mutation/mutation.d.ts +30 -19
- package/dist/application/query/answer/answer.d.ts +117 -0
- package/dist/application/query/answer/answer.js +1 -0
- package/dist/application/query/answer/conformance.d.ts +4 -0
- package/dist/application/query/answer/conformance.js +27 -0
- package/dist/application/query/answer/facts.d.ts +17 -0
- package/dist/application/query/answer/facts.js +57 -0
- package/dist/application/query/composite/builder.d.ts +1 -1
- package/dist/application/query/composite/builder.js +1 -1
- package/dist/application/query/composite/plan.d.ts +1 -1
- package/dist/application/query/composite/plan.js +1 -1
- package/dist/application/query/composite/query.d.ts +1 -2
- package/dist/application/query/define.d.ts +2 -33
- package/dist/application/query/define.js +3 -130
- package/dist/application/query/definition.d.ts +12 -0
- package/dist/application/query/definition.js +47 -0
- package/dist/application/query/execution/bounds.d.ts +20 -0
- package/dist/application/query/execution/bounds.js +86 -0
- package/dist/application/query/execution/crossings.d.ts +25 -0
- package/dist/application/query/execution/crossings.js +169 -0
- package/dist/application/query/execution/error.d.ts +68 -0
- package/dist/application/query/execution/error.js +50 -0
- package/dist/application/query/execution/execute.d.ts +2 -7
- package/dist/application/query/execution/execute.js +3 -51
- package/dist/application/query/execution/recipe.d.ts +70 -0
- package/dist/application/query/execution/recipe.js +276 -0
- package/dist/application/query/execution/refusal.d.ts +37 -0
- package/dist/application/query/execution/refusal.js +80 -0
- package/dist/application/query/execution/rows.d.ts +42 -0
- package/dist/application/query/execution/rows.js +308 -0
- package/dist/application/query/execution/split.d.ts +24 -0
- package/dist/application/query/execution/split.js +190 -0
- package/dist/application/query/handles/build.d.ts +10 -0
- package/dist/application/query/handles/build.js +53 -0
- package/dist/application/query/handles/conditions.d.ts +94 -0
- package/dist/application/query/handles/conditions.js +1 -0
- package/dist/application/query/handles/domain.d.ts +157 -0
- package/dist/application/query/handles/domain.js +1 -0
- package/dist/application/query/handles/handles.d.ts +268 -0
- package/dist/application/query/handles/handles.js +1 -0
- package/dist/application/query/handles/model.d.ts +96 -0
- package/dist/application/query/handles/model.js +246 -0
- package/dist/application/query/handles/plan.d.ts +99 -0
- package/dist/application/query/handles/plan.js +37 -0
- package/dist/application/query/handles/reads.d.ts +106 -0
- package/dist/application/query/handles/reads.js +839 -0
- package/dist/application/query/handles/relations.d.ts +100 -0
- package/dist/application/query/handles/relations.js +1 -0
- package/dist/application/query/handles/rows.d.ts +94 -0
- package/dist/application/query/handles/rows.js +4 -0
- package/dist/application/query/handles/terms.d.ts +95 -0
- package/dist/application/query/handles/terms.js +265 -0
- package/dist/application/query/index.d.ts +10 -7
- package/dist/application/query/index.js +9 -4
- package/dist/application/query/language/index.d.ts +2 -0
- package/dist/application/query/language/index.js +2 -0
- package/dist/application/query/language/query.d.ts +323 -0
- package/dist/application/query/language/query.js +2 -0
- package/dist/application/query/{collection → legacy/v6/collection}/identity.d.ts +2 -2
- package/dist/application/query/{collection → legacy/v6/collection}/metadata.d.ts +1 -1
- package/dist/application/query/{collection → legacy/v6/collection}/union.d.ts +3 -3
- package/dist/application/query/{collection → legacy/v6/collection}/union.js +1 -1
- package/dist/application/query/{conformance.d.ts → legacy/v6/conformance.d.ts} +3 -3
- package/dist/application/query/{conformance.js → legacy/v6/conformance.js} +1 -1
- package/dist/application/query/legacy/v6/define.d.ts +37 -0
- package/dist/application/query/legacy/v6/define.js +90 -0
- package/dist/application/query/legacy/v6/execution/execute.d.ts +16 -0
- package/dist/application/query/legacy/v6/execution/execute.js +50 -0
- package/dist/application/query/legacy/v6/index.d.ts +6 -0
- package/dist/application/query/legacy/v6/index.js +3 -0
- package/dist/application/query/legacy/v6/kernel.d.ts +6 -0
- package/dist/application/query/legacy/v6/kernel.js +5 -0
- package/dist/application/query/{single → legacy/v6/single}/pagination.js +1 -1
- package/dist/application/query/{single → legacy/v6/single}/query.d.ts +2 -14
- package/dist/application/query/legacy/v6/single/query.js +1 -0
- package/dist/application/query/query.d.ts +16 -1
- package/dist/application/query/recipe/define.d.ts +45 -0
- package/dist/application/query/recipe/define.js +102 -0
- package/dist/application/query/recipe/recipe.d.ts +134 -0
- package/dist/application/query/recipe/recipe.js +1 -0
- package/dist/application/workflow/durable.d.ts +22 -8
- package/dist/execution/invocation/capabilities/bind.d.ts +6 -4
- package/dist/execution/invocation/capabilities/bind.js +39 -3
- package/dist/execution/invocation/dispatch/action.js +4 -2
- package/dist/execution/invocation/dispatch/context.js +1 -1
- package/dist/execution/invocation/dispatch/workflow.js +4 -2
- package/dist/execution/invocation/result/index.d.ts +1 -0
- package/dist/execution/invocation/result/index.js +1 -0
- package/dist/execution/invocation/result/recipe.d.ts +6 -0
- package/dist/execution/invocation/result/recipe.js +17 -0
- package/dist/execution/service/create.js +1 -0
- package/dist/execution/workflows/failure.d.ts +23 -6
- package/dist/execution/workflows/failure.js +78 -18
- package/dist/execution/workflows/receiver.d.ts +3 -0
- package/dist/execution/workflows/receiver.js +1 -0
- package/dist/execution/workflows/run.js +13 -9
- package/dist/execution/workflows/steps.d.ts +3 -0
- package/dist/execution/workflows/steps.js +2 -2
- package/dist/platform/schema/kernel-compiled.d.ts +2 -0
- package/dist/platform/schema/kernel-compiled.js +2 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +1 -0
- package/dist/testing/recipe.d.ts +17 -0
- package/dist/testing/recipe.js +29 -0
- package/dist/tooling/cli/orchestrate.js +6 -2
- package/dist/tooling/linter/adapters/typescript/index.d.ts +2 -1
- package/dist/tooling/linter/adapters/typescript/index.js +2 -1
- package/dist/tooling/linter/adapters/typescript/query-observation.js +37 -12
- package/dist/tooling/linter/adapters/typescript/recipe-definitions.d.ts +34 -0
- package/dist/tooling/linter/adapters/typescript/recipe-definitions.js +88 -0
- package/dist/tooling/linter/adapters/typescript/type-analysis.d.ts +2 -0
- package/dist/tooling/linter/adapters/typescript/type-analysis.js +10 -0
- package/dist/tooling/linter/implementations/source/global.js +15 -9
- package/dist/tooling/linter/implementations/source/mutations.js +128 -19
- package/dist/tooling/linter/implementations/source/policy-edges.d.ts +2 -0
- package/dist/tooling/linter/implementations/source/policy-edges.js +113 -0
- package/dist/tooling/linter/implementations/source/recipes.d.ts +2 -0
- package/dist/tooling/linter/implementations/source/recipes.js +409 -0
- package/dist/tooling/linter/implementations/source/shared.d.ts +7 -1
- package/dist/tooling/linter/implementations/source/shared.js +31 -10
- package/dist/tooling/linter/implementations/source/verifiers.js +4 -0
- package/dist/tooling/linter/policy/generated.js +2 -2
- package/dist/tooling/linter/requirements/registry.js +4 -0
- package/docs/durable-workflows.md +48 -20
- package/docs/handler-dependencies.md +76 -4
- package/docs/legacy/query-v6.md +62 -0
- package/docs/queries.md +438 -0
- package/package.json +11 -8
- /package/dist/application/{query/single/query.js → mutation/legacy/v6/builder.js} +0 -0
- /package/dist/application/mutation/{authoring → legacy/v6}/precondition.js +0 -0
- /package/dist/application/query/{collection → legacy/v6/collection}/identity.js +0 -0
- /package/dist/application/query/{collection → legacy/v6/collection}/limits.d.ts +0 -0
- /package/dist/application/query/{collection → legacy/v6/collection}/limits.js +0 -0
- /package/dist/application/query/{collection → legacy/v6/collection}/metadata.js +0 -0
- /package/dist/application/query/{execution → legacy/v6/execution}/pages.d.ts +0 -0
- /package/dist/application/query/{execution → legacy/v6/execution}/pages.js +0 -0
- /package/dist/application/query/{single → legacy/v6/single}/index.d.ts +0 -0
- /package/dist/application/query/{single → legacy/v6/single}/index.js +0 -0
- /package/dist/application/query/{single → legacy/v6/single}/node.d.ts +0 -0
- /package/dist/application/query/{single → legacy/v6/single}/node.js +0 -0
- /package/dist/application/query/{single → legacy/v6/single}/pagination.d.ts +0 -0
- /package/dist/application/query/{single → legacy/v6/single}/selection.d.ts +0 -0
- /package/dist/application/query/{single → legacy/v6/single}/selection.js +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mutation v4 guards (S7): a condition the Kernel decides with the Mutation, on the roots of a
|
|
3
|
+
* range. Each verb takes a range built from the handles, or a canonical v7 Query, and writes the
|
|
4
|
+
* document of the guard's own family: the range (`nodes` or `edges`, `as`, `where`) for `none`,
|
|
5
|
+
* `exists` and `count`, its ordered root list for `unchanged`. A range is never rendered, sized
|
|
6
|
+
* or read with a verb, so nothing written on it is left out of the guard.
|
|
7
|
+
*/
|
|
8
|
+
import type { MutationGuardInput } from '@astrale-os/kernel-core/graph/mutate';
|
|
9
|
+
import type { NodeId } from '../../../platform/graph/node/index.js';
|
|
10
|
+
import type { EdgeSet, NodeSet } from '../../query/handles/handles.js';
|
|
11
|
+
import type { EdgeKey } from '../../query/handles/rows.js';
|
|
12
|
+
import type { AnyQuery } from '../../query/language/query.js';
|
|
13
|
+
/**
|
|
14
|
+
* What a guard reads: a range, the Nodes or Edges of a Class handle `build` receives after
|
|
15
|
+
* `where`, `union` and `orderBy`, or a canonical v7 Query.
|
|
16
|
+
*/
|
|
17
|
+
export type GuardRange<S, K extends string> = NodeSet<S, K, {}, 'value', false> | EdgeSet<S, K, 'value', false> | AnyQuery;
|
|
18
|
+
/** How many roots a `count` guard admits: exactly `n`, or within bounds, bounded at least once. */
|
|
19
|
+
export type CountBounds = number | {
|
|
20
|
+
readonly atLeast?: number;
|
|
21
|
+
readonly atMost?: number;
|
|
22
|
+
};
|
|
23
|
+
/** The identity of one root of a list, as a read returns it: a Node's id, or an Edge's key. */
|
|
24
|
+
export type GuardIdentity = NodeId | EdgeKey;
|
|
25
|
+
/** What a precondition verb takes after its condition: the name a refusal reports it by. */
|
|
26
|
+
export interface PreconditionOptions {
|
|
27
|
+
readonly name: string;
|
|
28
|
+
}
|
|
29
|
+
/** `none` and `exists`: whether the range has a root. */
|
|
30
|
+
export declare function existsGuard(range: unknown, expected: boolean): MutationGuardInput;
|
|
31
|
+
/**
|
|
32
|
+
* `count`: the number of the range's roots lies in the bounds. The document counts up to the
|
|
33
|
+
* largest bound, and at least one, so the Kernel tells every bound from the count it reads.
|
|
34
|
+
*/
|
|
35
|
+
export declare function countGuard(range: unknown, bounds: CountBounds): MutationGuardInput;
|
|
36
|
+
/** `unchanged`: the range's root list is exactly these identities, in its order. */
|
|
37
|
+
export declare function sequenceGuard(range: unknown, ids: readonly GuardIdentity[]): MutationGuardInput;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { RelationRead, RootRead } from '../../query/handles/plan.js';
|
|
2
|
+
import { rangeRead } from '../../query/handles/reads.js';
|
|
3
|
+
import { QUERY } from '../../query/language/query.js';
|
|
4
|
+
/** The fields of a root its range keeps, and those its ordered list keeps, in grammar order. */
|
|
5
|
+
const RANGE = ['nodes', 'edges', 'as', 'where'];
|
|
6
|
+
const ORDERED = [...RANGE, 'orderBy'];
|
|
7
|
+
/** The fields of a root by family: a list's, and a range's (`exists`, `count`). */
|
|
8
|
+
const LIST_FIELDS = new Set([...ORDERED, 'select', 'with', 'limit']);
|
|
9
|
+
const RANGE_FIELDS = new Set(RANGE);
|
|
10
|
+
const FAMILIES = ['list', 'exists', 'count'];
|
|
11
|
+
/** `none` and `exists`: whether the range has a root. */
|
|
12
|
+
export function existsGuard(range, expected) {
|
|
13
|
+
return guard({ kind: 'exists', document: { query: QUERY, exists: root(range, RANGE) }, expected });
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* `count`: the number of the range's roots lies in the bounds. The document counts up to the
|
|
17
|
+
* largest bound, and at least one, so the Kernel tells every bound from the count it reads.
|
|
18
|
+
*/
|
|
19
|
+
export function countGuard(range, bounds) {
|
|
20
|
+
const { atLeast, atMost } = countBounds(bounds);
|
|
21
|
+
const upTo = Math.max(1, atLeast ?? 0, atMost ?? 0);
|
|
22
|
+
return guard({
|
|
23
|
+
kind: 'count',
|
|
24
|
+
document: { query: QUERY, count: root(range, RANGE), upTo },
|
|
25
|
+
...(atLeast === undefined ? {} : { atLeast }),
|
|
26
|
+
...(atMost === undefined ? {} : { atMost }),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/** `unchanged`: the range's root list is exactly these identities, in its order. */
|
|
30
|
+
export function sequenceGuard(range, ids) {
|
|
31
|
+
if (!Array.isArray(ids)) {
|
|
32
|
+
throw new TypeError('An unchanged guard names the identities of the list, in its order: an array.');
|
|
33
|
+
}
|
|
34
|
+
const list = root(range, ORDERED);
|
|
35
|
+
const edges = list.edges !== undefined;
|
|
36
|
+
for (const id of ids)
|
|
37
|
+
if (edges ? !isEdgeKey(id) : typeof id !== 'string') {
|
|
38
|
+
throw new TypeError(`An unchanged guard names each root by its identity, as its read returned it: ${edges ? 'an EdgeKey ({ source, target, class }) per Edge' : 'a NodeId per Node'}, \`rows.map((row) => row.id)\`.`);
|
|
39
|
+
}
|
|
40
|
+
return guard({
|
|
41
|
+
kind: 'sequence',
|
|
42
|
+
document: { query: QUERY, list },
|
|
43
|
+
equals: Object.freeze([...ids]),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The fields of the range's root among `keep`, in grammar order, frozen. A field the grammar does
|
|
48
|
+
* not name is refused, never dropped: dropping a misspelled `where` would widen the guard.
|
|
49
|
+
*/
|
|
50
|
+
function root(range, keep) {
|
|
51
|
+
const document = documentOf(range, keep === ORDERED);
|
|
52
|
+
const families = FAMILIES.filter((family) => document[family] !== undefined);
|
|
53
|
+
if (families.length !== 1)
|
|
54
|
+
invalidRange();
|
|
55
|
+
const family = families[0];
|
|
56
|
+
const extra = Object.keys(document).find((key) => key !== 'query' && key !== family && !(family === 'count' && key === 'upTo'));
|
|
57
|
+
if (extra !== undefined)
|
|
58
|
+
unknownField(extra);
|
|
59
|
+
const read = document[family];
|
|
60
|
+
if (read === null || typeof read !== 'object' || Array.isArray(read))
|
|
61
|
+
invalidRange();
|
|
62
|
+
const fields = read;
|
|
63
|
+
const known = family === 'list' ? LIST_FIELDS : RANGE_FIELDS;
|
|
64
|
+
const unknown = Object.keys(fields).find((key) => !known.has(key));
|
|
65
|
+
if (unknown !== undefined)
|
|
66
|
+
unknownField(`${family}.${unknown}`);
|
|
67
|
+
if ((fields.nodes === undefined) === (fields.edges === undefined)) {
|
|
68
|
+
throw new TypeError("A guard's range starts from `nodes` or from `edges`, exactly one.");
|
|
69
|
+
}
|
|
70
|
+
if (fields.with !== undefined && !Array.isArray(fields.with))
|
|
71
|
+
unknownField(`${family}.with`);
|
|
72
|
+
if (Array.isArray(fields.with) && fields.with.some(isRequired)) {
|
|
73
|
+
throw new TypeError("A guard reads its range's roots without their relations, and a required one narrows the " +
|
|
74
|
+
'roots: write it as a condition, `.where((t) => t.via(edge).exists())`.');
|
|
75
|
+
}
|
|
76
|
+
const out = {};
|
|
77
|
+
for (const name of keep)
|
|
78
|
+
if (fields[name] !== undefined)
|
|
79
|
+
out[name] = fields[name];
|
|
80
|
+
return Object.freeze(out);
|
|
81
|
+
}
|
|
82
|
+
/** The canonical document of a range, `ordered` or not, or the canonical v7 Query itself. */
|
|
83
|
+
function documentOf(range, ordered) {
|
|
84
|
+
const read = rangeRead(range, ordered);
|
|
85
|
+
if (read !== undefined) {
|
|
86
|
+
if (read.plan.empty || read.plan.document === undefined) {
|
|
87
|
+
throw new TypeError('A guard on a range whose condition matches nothing (`in([])`, `id.in([])`) has no ' +
|
|
88
|
+
'document to send: decide that guard in build, from the input.');
|
|
89
|
+
}
|
|
90
|
+
return read.plan.document;
|
|
91
|
+
}
|
|
92
|
+
if (range instanceof RelationRead) {
|
|
93
|
+
throw new TypeError("A guard reads a root, and a relation is read per parent: start the guard's range from a " +
|
|
94
|
+
'Class handle, `d.<Class>`.');
|
|
95
|
+
}
|
|
96
|
+
if (range instanceof RootRead) {
|
|
97
|
+
throw new TypeError(range.plan.verb === 'document'
|
|
98
|
+
? `A guard takes a canonical ${QUERY} Query itself, not \`d.document\` of it.`
|
|
99
|
+
: "A guard takes a range, not a read: the Class handle after `where`, `union` and `orderBy`, `d.<Class>.where(…)`, with no verb. A verb's bound would not reach the guard.");
|
|
100
|
+
}
|
|
101
|
+
const document = range;
|
|
102
|
+
if (document === null || typeof document !== 'object' || document.query !== QUERY)
|
|
103
|
+
invalidRange();
|
|
104
|
+
return document;
|
|
105
|
+
}
|
|
106
|
+
function invalidRange() {
|
|
107
|
+
throw new TypeError(`A guard takes a range built from the handles, \`d.<Class>.where(…)\`, or a canonical ${QUERY} Query.`);
|
|
108
|
+
}
|
|
109
|
+
function unknownField(path) {
|
|
110
|
+
throw new TypeError(`A guard's Query holds ${path}, which the ${QUERY} grammar does not name there: a guard is never wider than its Query, so it is refused rather than dropped.`);
|
|
111
|
+
}
|
|
112
|
+
function isEdgeKey(id) {
|
|
113
|
+
const key = id;
|
|
114
|
+
return (key !== null &&
|
|
115
|
+
typeof key === 'object' &&
|
|
116
|
+
typeof key.source === 'string' &&
|
|
117
|
+
typeof key.target === 'string' &&
|
|
118
|
+
typeof key.class === 'string');
|
|
119
|
+
}
|
|
120
|
+
function isRequired(branch) {
|
|
121
|
+
return branch?.mode === 'required';
|
|
122
|
+
}
|
|
123
|
+
function countBounds(bounds) {
|
|
124
|
+
if (bounds === null || typeof bounds !== 'object') {
|
|
125
|
+
const exactly = bound(bounds, 'count');
|
|
126
|
+
return { atLeast: exactly, atMost: exactly };
|
|
127
|
+
}
|
|
128
|
+
const fields = (bounds ?? {});
|
|
129
|
+
const atLeast = fields.atLeast === undefined ? undefined : bound(fields.atLeast, 'atLeast');
|
|
130
|
+
const atMost = fields.atMost === undefined ? undefined : bound(fields.atMost, 'atMost');
|
|
131
|
+
if (atLeast === undefined && atMost === undefined) {
|
|
132
|
+
throw new TypeError('A count guard is bounded at least once: `atLeast`, `atMost` or both.');
|
|
133
|
+
}
|
|
134
|
+
if (atLeast !== undefined && atMost !== undefined && atLeast > atMost) {
|
|
135
|
+
throw new TypeError(`A count guard's atLeast (${atLeast}) exceeds its atMost (${atMost}).`);
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
...(atLeast === undefined ? {} : { atLeast }),
|
|
139
|
+
...(atMost === undefined ? {} : { atMost }),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function bound(value, name) {
|
|
143
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {
|
|
144
|
+
throw new TypeError(`A count guard's ${name} is a non-negative integer.`);
|
|
145
|
+
}
|
|
146
|
+
return value;
|
|
147
|
+
}
|
|
148
|
+
function guard(input) {
|
|
149
|
+
return Object.freeze(input);
|
|
150
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export { richMutationBuilder, type
|
|
1
|
+
export { richMutationBuilder, type BuilderForm, type RecipeMutationBuilder, type RichMutationBuilder, } from './builder.js';
|
|
2
2
|
export type { CreatedNode, ExistingEndpoint, MutationEndpoint } from './endpoint.js';
|
|
3
3
|
export type { RichPropertyConditions, RichPropertyDelta, RichPropertyInput, RichPropertyName, } from './properties.js';
|
|
4
|
-
export type {
|
|
4
|
+
export type { CountBounds, GuardIdentity, GuardRange, PreconditionOptions } from './guard.js';
|
|
5
|
+
export { preconditionNames, type PreconditionNames } from './names.js';
|
|
6
|
+
export type { ClosureMutationBuilder, EdgeSelector } from '../legacy/v6/builder.js';
|
|
7
|
+
export type { QueryPrecondition } from '../legacy/v6/precondition.js';
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export { richMutationBuilder } from './builder.js';
|
|
1
|
+
export { richMutationBuilder, } from './builder.js';
|
|
2
|
+
export { preconditionNames } from './names.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The names a Mutation gives its preconditions, and the name of the one a refusal points at.
|
|
3
|
+
*
|
|
4
|
+
* The Kernel points at a failed precondition by its index in the Mutation's `preconditions`, which
|
|
5
|
+
* the Mutation document orders by canonical JSON, not as written. So a name is not kept by
|
|
6
|
+
* position: each named precondition is built again on its own, with the Mutation's writes, and
|
|
7
|
+
* its entry is matched by value against the entry the refusal points at.
|
|
8
|
+
*/
|
|
9
|
+
import type { MutationBuilder } from '@astrale-os/kernel-core/graph/mutate';
|
|
10
|
+
import { MutationAST } from '@astrale-os/kernel-core/graph/mutate';
|
|
11
|
+
export interface PreconditionNames {
|
|
12
|
+
/** `core`, its every call kept so that a named precondition can be built again on its own. */
|
|
13
|
+
track(core: MutationBuilder): MutationBuilder;
|
|
14
|
+
/** Gives `name` to the one precondition `write` adds; a name is unique in its Mutation. */
|
|
15
|
+
name(name: string | undefined, write: () => void): void;
|
|
16
|
+
/** The name of the precondition at `index` of the built Mutation, when it has one. */
|
|
17
|
+
at(ast: MutationAST, index: number): string | undefined;
|
|
18
|
+
}
|
|
19
|
+
export declare function preconditionNames(): PreconditionNames;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { MutationAST } from '@astrale-os/kernel-core/graph/mutate';
|
|
2
|
+
import { equal } from '@astrale-os/kernel-dsl/value';
|
|
3
|
+
export function preconditionNames() {
|
|
4
|
+
const steps = [];
|
|
5
|
+
const used = new Set();
|
|
6
|
+
let pending;
|
|
7
|
+
const precondition = (core, call) => {
|
|
8
|
+
const name = pending;
|
|
9
|
+
pending = undefined;
|
|
10
|
+
call(core);
|
|
11
|
+
steps.push({ call, precondition: name === undefined ? {} : { name } });
|
|
12
|
+
};
|
|
13
|
+
const write = (core, call) => {
|
|
14
|
+
const result = call(core);
|
|
15
|
+
steps.push({ call });
|
|
16
|
+
return result;
|
|
17
|
+
};
|
|
18
|
+
return Object.freeze({
|
|
19
|
+
track(core) {
|
|
20
|
+
const tracked = Object.freeze({
|
|
21
|
+
expect: Object.freeze({
|
|
22
|
+
node: (input) => (precondition(core, (to) => to.expect.node(input)), tracked),
|
|
23
|
+
edge: (input) => (precondition(core, (to) => to.expect.edge(input)), tracked),
|
|
24
|
+
query: (input) => (precondition(core, (to) => to.expect.query(input)), tracked),
|
|
25
|
+
}),
|
|
26
|
+
createNode: (input) => write(core, (to) => to.createNode(input)),
|
|
27
|
+
updateNode: (input) => (write(core, (to) => to.updateNode(input)), tracked),
|
|
28
|
+
deleteNode: (input) => (write(core, (to) => to.deleteNode(input)), tracked),
|
|
29
|
+
createEdge: (input) => (write(core, (to) => to.createEdge(input)), tracked),
|
|
30
|
+
updateEdge: (input) => (write(core, (to) => to.updateEdge(input)), tracked),
|
|
31
|
+
deleteEdge: (input) => (write(core, (to) => to.deleteEdge(input)), tracked),
|
|
32
|
+
});
|
|
33
|
+
return tracked;
|
|
34
|
+
},
|
|
35
|
+
name(name, add) {
|
|
36
|
+
if (name === undefined)
|
|
37
|
+
return add();
|
|
38
|
+
if (typeof name !== 'string' || name.length === 0) {
|
|
39
|
+
throw new TypeError("A precondition's name is a non-empty string.");
|
|
40
|
+
}
|
|
41
|
+
if (used.has(name)) {
|
|
42
|
+
throw new TypeError(`Two preconditions of one Mutation are named "${name}".`);
|
|
43
|
+
}
|
|
44
|
+
used.add(name);
|
|
45
|
+
pending = name;
|
|
46
|
+
try {
|
|
47
|
+
add();
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
pending = undefined;
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
at(ast, index) {
|
|
54
|
+
const failed = ast.preconditions[index];
|
|
55
|
+
if (failed === undefined)
|
|
56
|
+
return undefined;
|
|
57
|
+
const writes = steps.filter((step) => step.precondition === undefined);
|
|
58
|
+
for (const step of steps) {
|
|
59
|
+
const name = step.precondition?.name;
|
|
60
|
+
if (name === undefined)
|
|
61
|
+
continue;
|
|
62
|
+
const alone = entryAlone(step, writes);
|
|
63
|
+
if (alone !== undefined && equal(alone, failed))
|
|
64
|
+
return name;
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/** The entry `step`'s precondition has in a Mutation of the writes and it alone. */
|
|
71
|
+
function entryAlone(step, writes) {
|
|
72
|
+
try {
|
|
73
|
+
const ast = MutationAST.build((core) => {
|
|
74
|
+
step.call(core);
|
|
75
|
+
for (const write of writes)
|
|
76
|
+
write.call(core);
|
|
77
|
+
});
|
|
78
|
+
return ast.preconditions[0];
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -12,9 +12,10 @@ export type StatePropertyTopology<Class extends ResolvedClass<'node'>, Property
|
|
|
12
12
|
}>>;
|
|
13
13
|
export type StateOfTopology<Topology extends StateTopology> = Extract<keyof Topology['transitions'], string>;
|
|
14
14
|
export type EventOfTopology<Topology extends StateTopology, State extends StateOfTopology<Topology>> = State extends StateOfTopology<Topology> ? Extract<keyof Topology['transitions'][State], string> : never;
|
|
15
|
-
|
|
15
|
+
/** `Selected` is how the input names `Class`: the resolved Class, or its handle (`d.<Class>`). */
|
|
16
|
+
export interface StateTransitionInput<Class extends ResolvedClass<'node'>, Property extends StatePropertyName<Class>, From extends StateOfTopology<StatePropertyTopology<Class, Property>>, Event extends EventOfTopology<StatePropertyTopology<Class, Property>, From>, Selected = Class> {
|
|
16
17
|
readonly node: ExistingEndpoint;
|
|
17
|
-
readonly class:
|
|
18
|
+
readonly class: Selected;
|
|
18
19
|
readonly property: Property;
|
|
19
20
|
readonly from: From;
|
|
20
21
|
readonly event: Event;
|
|
@@ -1,12 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
/**
|
|
2
|
+
* `defineMutation<Schema>()`: one name for both forms. A string id first defines a Mutation recipe
|
|
3
|
+
* (D31); a function first defines a Mutation of the closure form, deprecated, which stays in
|
|
4
|
+
* `legacy/v6`. A recipe is inert: its id and the errors it declares are public, and what it
|
|
5
|
+
* writes is kept for the runtime.
|
|
6
|
+
*/
|
|
7
|
+
import type { schema } from '../../platform/schema/index.js';
|
|
8
|
+
import type { MutationProjector } from './legacy/v6/define.js';
|
|
9
|
+
import type { MutationRecipeDefinitions } from './mutation.js';
|
|
10
|
+
type Callback = (...parameters: never[]) => unknown;
|
|
11
|
+
/** What a Mutation recipe writes and answers: its `build`, `output` and `reject`. */
|
|
12
|
+
export interface MutationRecipeSpec {
|
|
13
|
+
/** Receives `(input, d, mutation)`. */
|
|
14
|
+
readonly build: Callback;
|
|
15
|
+
readonly output?: Callback;
|
|
16
|
+
readonly reject?: Callback;
|
|
8
17
|
}
|
|
9
|
-
/**
|
|
10
|
-
export declare function
|
|
11
|
-
|
|
18
|
+
/** The `build`, `output` and `reject` of a Mutation recipe; `undefined` for anything else. */
|
|
19
|
+
export declare function mutationRecipeSpecOf(recipe: unknown): MutationRecipeSpec | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Define a Mutation: `defineMutation<Schema>()('issues.issue.open', { build, output?, reject?,
|
|
22
|
+
* errors? })`. The closure form, `defineMutation<Schema>()((domain) => ({ id, build, project? }))`,
|
|
23
|
+
* stays, deprecated.
|
|
24
|
+
*/
|
|
25
|
+
export declare function defineMutation<Schema extends schema.DomainSchema>(): MutationRecipeDefinitions<Schema> & MutationProjector<Schema>;
|
|
12
26
|
export {};
|
|
@@ -1,56 +1,52 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const mutation = Object.freeze({ kind: 'mutation' });
|
|
10
|
-
projectors.set(mutation, project);
|
|
11
|
-
return mutation;
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
export function realizeMutation(mutation, domain) {
|
|
15
|
-
const project = projectors.get(mutation);
|
|
16
|
-
if (project === undefined)
|
|
17
|
-
throw new TypeError('Mutation must be produced by defineMutation.');
|
|
18
|
-
if (!isDomain(domain))
|
|
19
|
-
throw new TypeError('Mutation projection requires an admitted Domain.');
|
|
20
|
-
let byDomain = realized.get(mutation);
|
|
21
|
-
const cached = byDomain?.get(domain);
|
|
22
|
-
if (cached !== undefined)
|
|
23
|
-
return cached;
|
|
24
|
-
const authored = project(domain);
|
|
25
|
-
if (isPromiseLike(authored))
|
|
26
|
-
throw new TypeError('Mutation projection must be synchronous.');
|
|
27
|
-
const admitted = admitMutation(authored);
|
|
28
|
-
if (byDomain === undefined) {
|
|
29
|
-
byDomain = new WeakMap();
|
|
30
|
-
realized.set(mutation, byDomain);
|
|
31
|
-
}
|
|
32
|
-
byDomain.set(domain, admitted);
|
|
33
|
-
return admitted;
|
|
1
|
+
import { describe } from '../query/handles/terms.js';
|
|
2
|
+
import { collectRecipe, MAXIMUM_RECIPE_ID, RECIPE_ID } from '../query/recipe/define.js';
|
|
3
|
+
import { defineMutation as defineClosureMutation } from './legacy/v6/define.js';
|
|
4
|
+
const FIELDS = new Set(['build', 'output', 'reject', 'errors']);
|
|
5
|
+
const specs = new WeakMap();
|
|
6
|
+
/** The `build`, `output` and `reject` of a Mutation recipe; `undefined` for anything else. */
|
|
7
|
+
export function mutationRecipeSpecOf(recipe) {
|
|
8
|
+
return typeof recipe === 'object' && recipe !== null ? specs.get(recipe) : undefined;
|
|
34
9
|
}
|
|
35
|
-
function
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
if (typeof
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
10
|
+
function defineMutationRecipe(id, spec) {
|
|
11
|
+
if (typeof id !== 'string' || id.length > MAXIMUM_RECIPE_ID || !RECIPE_ID.test(id))
|
|
12
|
+
throw new TypeError(`A Mutation id is lowercase segments of letters, digits and hyphens joined by dots, at most ${MAXIMUM_RECIPE_ID} characters ('issues.issue.open'); got ${describe(id)}.`);
|
|
13
|
+
if (typeof spec !== 'object' || spec === null || Array.isArray(spec))
|
|
14
|
+
throw new TypeError(`Mutation ${id} takes { build, output?, reject?, errors? }; got ${describe(spec)}.`);
|
|
15
|
+
const fields = spec;
|
|
16
|
+
const unknown = Reflect.ownKeys(fields).filter((key) => typeof key !== 'string' || !FIELDS.has(key));
|
|
17
|
+
if (unknown.length > 0)
|
|
18
|
+
throw new TypeError(`Mutation ${id} takes build, output, reject and errors only; got ${unknown.map(String).join(', ')}.`);
|
|
19
|
+
const { build, output, reject } = fields;
|
|
20
|
+
if (typeof build !== 'function')
|
|
21
|
+
throw new TypeError(`Mutation ${id}'s build is a function of (input, d, mutation); got ${describe(build)}.`);
|
|
22
|
+
if (output !== undefined && typeof output !== 'function')
|
|
23
|
+
throw new TypeError(`Mutation ${id}'s output is a function of (result, input); got ${describe(output)}.`);
|
|
24
|
+
if (reject !== undefined && typeof reject !== 'function')
|
|
25
|
+
throw new TypeError(`Mutation ${id}'s reject is a function of (failure, input, context); got ${describe(reject)}.`);
|
|
26
|
+
const errors = fields.errors ?? {};
|
|
27
|
+
if (typeof errors !== 'object' || errors === null || Array.isArray(errors))
|
|
28
|
+
throw new TypeError(`Mutation ${id}'s errors is a record of declared errors by UPPER_SNAKE code; got ${describe(errors)}.`);
|
|
29
|
+
const recipe = Object.freeze({
|
|
30
|
+
kind: 'mutation',
|
|
31
|
+
id,
|
|
32
|
+
errors: Object.freeze({ ...errors }),
|
|
33
|
+
});
|
|
34
|
+
collectRecipe(recipe);
|
|
35
|
+
specs.set(recipe, Object.freeze({
|
|
36
|
+
build: build,
|
|
37
|
+
...(output === undefined ? {} : { output: output }),
|
|
38
|
+
...(reject === undefined ? {} : { reject: reject }),
|
|
39
|
+
}));
|
|
40
|
+
return recipe;
|
|
51
41
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Define a Mutation: `defineMutation<Schema>()('issues.issue.open', { build, output?, reject?,
|
|
44
|
+
* errors? })`. The closure form, `defineMutation<Schema>()((domain) => ({ id, build, project? }))`,
|
|
45
|
+
* stays, deprecated.
|
|
46
|
+
*/
|
|
47
|
+
export function defineMutation() {
|
|
48
|
+
const closure = defineClosureMutation();
|
|
49
|
+
return ((first, spec) => typeof first === 'string'
|
|
50
|
+
? defineMutationRecipe(first, spec)
|
|
51
|
+
: closure(first));
|
|
56
52
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type MutationAST as MutationASTValue, type MutationResult } from '@astrale-os/kernel-core/graph/mutate';
|
|
2
2
|
import type { GraphMutationOptions } from '../../../platform/client/index.js';
|
|
3
3
|
import type { Domain } from '../../../platform/schema/index.js';
|
|
4
|
-
import type { Mutation } from '../mutation.js';
|
|
4
|
+
import type { Mutation, MutationRecipe } from '../mutation.js';
|
|
5
5
|
/** Exact caller-scoped canonical document capability required by Mutation execution. */
|
|
6
6
|
export type MutationClient = Readonly<{
|
|
7
7
|
mutate(ast: MutationASTValue, options?: GraphMutationOptions): Promise<MutationResult>;
|
|
8
8
|
}>;
|
|
9
|
-
export declare function executeMutation<DomainValue extends Domain, Input, Output>(client: MutationClient, domain: DomainValue, definition: Mutation<DomainValue, Input, Output>, input: Input): Promise<Output>;
|
|
9
|
+
export declare function executeMutation<DomainValue extends Domain, Input, Output>(client: MutationClient, domain: DomainValue, definition: Mutation<DomainValue, Input, Output> | MutationRecipe<string, Input, Output>, input: Input): Promise<Output>;
|
|
@@ -1,21 +1,92 @@
|
|
|
1
1
|
import { MutationAST, } from '@astrale-os/kernel-core/graph/mutate';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { outputContext } from '../../query/execution/recipe.js';
|
|
3
|
+
import { handlesOf } from '../../query/handles/build.js';
|
|
4
|
+
import { modelOf } from '../../query/handles/model.js';
|
|
5
|
+
import { preconditionNames, richMutationBuilder } from '../authoring/index.js';
|
|
6
|
+
import { mutationRecipeSpecOf } from '../define.js';
|
|
4
7
|
import { mutationFailure } from '../failure.js';
|
|
8
|
+
import { realizeMutation } from '../legacy/v6/define.js';
|
|
5
9
|
import { projectMutation } from './projection.js';
|
|
6
10
|
export async function executeMutation(client, domain, definition, input) {
|
|
7
11
|
if (client === null || typeof client !== 'object' || typeof client.mutate !== 'function') {
|
|
8
12
|
throw new TypeError('Mutation execution requires a Client mutate capability.');
|
|
9
13
|
}
|
|
10
|
-
const
|
|
14
|
+
const spec = mutationRecipeSpecOf(definition);
|
|
15
|
+
const operation = spec === undefined
|
|
16
|
+
? closureOperation(definition, domain)
|
|
17
|
+
: recipeOperation(definition, spec, domain);
|
|
18
|
+
const names = preconditionNames();
|
|
19
|
+
let ast;
|
|
11
20
|
try {
|
|
12
|
-
|
|
13
|
-
|
|
21
|
+
// `build` receives the handles a recipe's does, for the Classes it names and the ranges its
|
|
22
|
+
// guards take.
|
|
23
|
+
const d = lazyHandles(domain);
|
|
24
|
+
// What `build` returns reaches the core builder, which refuses a promise or a value.
|
|
25
|
+
ast = MutationAST.build((builder) => operation.build(input, richMutationBuilder(builder, names, operation.form), d));
|
|
26
|
+
return operation.output(await client.mutate(ast), input);
|
|
14
27
|
}
|
|
15
28
|
catch (cause) {
|
|
16
|
-
|
|
29
|
+
// A refusal points at a precondition of the built Mutation by its index.
|
|
30
|
+
const built = ast;
|
|
31
|
+
const expected = mutationFailure(cause, built === undefined ? undefined : (index) => names.at(built, index));
|
|
17
32
|
if (operation.reject !== undefined && expected !== undefined)
|
|
18
|
-
return operation.reject(expected);
|
|
33
|
+
return operation.reject(expected, input);
|
|
19
34
|
throw cause;
|
|
20
35
|
}
|
|
21
36
|
}
|
|
37
|
+
function closureOperation(mutation, domain) {
|
|
38
|
+
const definition = realizeMutation(mutation, domain);
|
|
39
|
+
return {
|
|
40
|
+
form: 'closure',
|
|
41
|
+
build: (input, builder, d) => definition.build(input, builder, d),
|
|
42
|
+
output: (result, input) => projectMutation(definition, result, input),
|
|
43
|
+
...(definition.reject === undefined
|
|
44
|
+
? {}
|
|
45
|
+
: { reject: (failure) => definition.reject(failure) }),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function recipeOperation(recipe, spec, domain) {
|
|
49
|
+
const { build, output, reject } = spec;
|
|
50
|
+
return {
|
|
51
|
+
form: 'recipe',
|
|
52
|
+
build: (input, builder, d) => {
|
|
53
|
+
const written = build(input, d, builder);
|
|
54
|
+
if (isPromiseLike(written)) {
|
|
55
|
+
// Refused before any write is sent: a write after its first await fails, and is observed.
|
|
56
|
+
;
|
|
57
|
+
written.then(undefined, () => undefined);
|
|
58
|
+
throw new TypeError(`Mutation ${recipe.id}'s build is synchronous: it writes the Mutation, and awaits nothing.`);
|
|
59
|
+
}
|
|
60
|
+
return written;
|
|
61
|
+
},
|
|
62
|
+
output: (result, input) => (output === undefined ? result : output(result, input)),
|
|
63
|
+
...(reject === undefined
|
|
64
|
+
? {}
|
|
65
|
+
: {
|
|
66
|
+
reject: (failure, input) => reject(failure, input, outputContext(recipe, modelOf(domain))),
|
|
67
|
+
}),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function isPromiseLike(value) {
|
|
71
|
+
return (value !== null &&
|
|
72
|
+
(typeof value === 'object' || typeof value === 'function') &&
|
|
73
|
+
typeof value.then === 'function');
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The Domain's handles, built on first use: a `build` that reads none never loads the Kernel's
|
|
77
|
+
* Classes they include.
|
|
78
|
+
*/
|
|
79
|
+
function lazyHandles(domain) {
|
|
80
|
+
let handles;
|
|
81
|
+
const resolved = () => (handles ??= handlesOf(domain));
|
|
82
|
+
// An extensible, empty target: every trap may then answer from the handles.
|
|
83
|
+
return new Proxy(Object.create(null), {
|
|
84
|
+
get: (_, key) => Reflect.get(resolved(), key),
|
|
85
|
+
has: (_, key) => Reflect.has(resolved(), key),
|
|
86
|
+
ownKeys: () => Reflect.ownKeys(resolved()),
|
|
87
|
+
getOwnPropertyDescriptor: (_, key) => {
|
|
88
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(resolved(), key);
|
|
89
|
+
return descriptor === undefined ? undefined : { ...descriptor, configurable: true };
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { Domain, schema } from '../../../platform/schema/index.js';
|
|
2
2
|
import type { Mutation } from '../mutation.js';
|
|
3
3
|
import type { MutationClient } from './execute.js';
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* Invocation-bound executor for Mutations of the closure form. A Mutation recipe names its
|
|
6
|
+
* authority on each call, `mutate(recipe, input, { as })`, so it is refused here.
|
|
7
|
+
*/
|
|
5
8
|
export interface MutationExecutor<DomainValue extends Domain = Domain> {
|
|
6
9
|
<Input, Output>(definition: Mutation<DomainValue, Input, Output>, input: Input): Promise<Output>;
|
|
7
10
|
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import { mutationRecipeSpecOf } from '../define.js';
|
|
1
2
|
import { executeMutation } from './execute.js';
|
|
2
3
|
/** Private Runtime binding to one Client and the exact loaded root Domain. */
|
|
3
4
|
export function bindMutation(client, domain) {
|
|
4
5
|
const executable = domain;
|
|
5
|
-
const execute = (definition, input) =>
|
|
6
|
+
const execute = async (definition, input) => {
|
|
7
|
+
if (mutationRecipeSpecOf(definition) !== undefined)
|
|
8
|
+
throw new TypeError(`Mutation ${definition.id} is called with mutate(mutation, input, { as: 'executor' | 'caller' }).`);
|
|
9
|
+
return executeMutation(client, executable, definition, input);
|
|
10
|
+
};
|
|
6
11
|
return execute;
|
|
7
12
|
}
|
|
@@ -7,6 +7,12 @@ export interface KernelMutationFailure extends Error {
|
|
|
7
7
|
* refusal.
|
|
8
8
|
*/
|
|
9
9
|
readonly rule?: string;
|
|
10
|
+
/**
|
|
11
|
+
* The name of the precondition a `MUTATION_PRECONDITION_FAILED` refusal points at: the `name`
|
|
12
|
+
* its `expect.*` verb was given. Absent when that precondition has no name, when the Kernel
|
|
13
|
+
* points at none, and for every other refusal.
|
|
14
|
+
*/
|
|
15
|
+
readonly precondition?: string;
|
|
10
16
|
}
|
|
11
17
|
export interface StateTransitionFailure extends Error {
|
|
12
18
|
readonly code: 'STATE_TRANSITION_ILLEGAL';
|
|
@@ -16,4 +22,8 @@ export interface StateTransitionFailure extends Error {
|
|
|
16
22
|
}
|
|
17
23
|
export type MutationFailure = KernelMutationFailure | StateTransitionFailure;
|
|
18
24
|
export declare function stateTransitionFailure(from: string, event: string): StateTransitionFailure;
|
|
19
|
-
|
|
25
|
+
/**
|
|
26
|
+
* The Mutation refusal `input` carries, if any. `nameAt` names the precondition at an index of
|
|
27
|
+
* the built Mutation's `preconditions`, the index a precondition refusal points at.
|
|
28
|
+
*/
|
|
29
|
+
export declare function mutationFailure(input: unknown, nameAt?: (index: number) => string | undefined): MutationFailure | undefined;
|