@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
package/README.md
CHANGED
|
@@ -58,7 +58,8 @@ Install the URL printed by `astrale-domain deploy` on each intended instance.
|
|
|
58
58
|
|
|
59
59
|
## Handler authority and dependencies
|
|
60
60
|
|
|
61
|
-
Handlers use Domain self authority for `kernel`, `graph`,
|
|
61
|
+
Handlers use Domain self authority for `kernel`, `graph`, Query V6 definitions, and a `mutate`
|
|
62
|
+
given no options; a Query recipe, and a `mutate` given `{ as }`, name their authority on each call.
|
|
62
63
|
`dependencies.directory.caller.invoke(d => d.functions.lookup, input)` calls a direct declared
|
|
63
64
|
dependency on behalf of the incoming caller Identity; `.self` calls it as the executing Domain.
|
|
64
65
|
Explicit `caller` or `self` authority is also available on `kernel` and `graph`; the former
|
|
@@ -66,6 +67,19 @@ handler `client` is removed. A call to an `auth: 'anonymous'` callable that carr
|
|
|
66
67
|
has no caller: its handler acts as the Domain alone, without the `caller` selections. See
|
|
67
68
|
[the API and migration guide](docs/handler-dependencies.md).
|
|
68
69
|
|
|
70
|
+
## Queries
|
|
71
|
+
|
|
72
|
+
`defineQuery<Schema>()(id, { build, output?, errors? })` defines a Query recipe: typed handles over
|
|
73
|
+
the Domain (`d.Article.where((a) => a.state.eq('published')).first(50)`), reads bounded by
|
|
74
|
+
their verbs, a pure `output` and declared errors. An Action calls
|
|
75
|
+
`query(recipe, input, { as: 'executor' | 'caller' })`; a durable Workflow reads in
|
|
76
|
+
`step.query(step, recipe, input, { as: 'executor' })`. Before anything is sent, the SDK checks the
|
|
77
|
+
recipe's bounds and the requirements its relations need; `checkRecipe` from
|
|
78
|
+
`@astrale-os/sdk/testing` runs both checks in a test, on the first stage it builds for a sample
|
|
79
|
+
input. A recipe reads on a Host whose Kernel serves Query v7 and the Class of each row. Query V6
|
|
80
|
+
definitions keep their meaning and are deprecated. See [the Query guide](docs/queries.md) and
|
|
81
|
+
[Query V6 and the move to v7](docs/legacy/query-v6.md).
|
|
82
|
+
|
|
69
83
|
## Durable Workflows
|
|
70
84
|
|
|
71
85
|
A callable declared with `execution: { durable: true }` is implemented by `defineWorkflow` and runs
|
|
@@ -332,7 +346,13 @@ a missing association makes that simulation unavailable.
|
|
|
332
346
|
Use `session.release()` for the intrinsic Kernel's Release without its schema bundle or Views.
|
|
333
347
|
It revalidates issuer, URL, size and ETag. Concurrent release and snapshot observations share
|
|
334
348
|
acquisition; cancelling one observer does not cancel another. `session.snapshot()` returns one
|
|
335
|
-
coherent Release
|
|
349
|
+
coherent Release and Bundle observation.
|
|
350
|
+
|
|
351
|
+
A View is a Node that carries its own placement, whether its Domain declares it or a Mutation
|
|
352
|
+
created it. Read Views from the graph, under the caller's read authority: `session.view(path)`
|
|
353
|
+
reads one View by any Path, a Domain's entrypoint included, and `session.view({ domain })` lists
|
|
354
|
+
the Views a Domain declares. The snapshot's `views` catalog and `session.viewsFor` are deprecated:
|
|
355
|
+
they read Views from the Release rather than from their Nodes.
|
|
336
356
|
|
|
337
357
|
## Headless scripts and Domain callables
|
|
338
358
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BinaryRequestBody } from '@astrale-os/kernel-core/callable';
|
|
1
2
|
import type { AuthenticationEvidence, IdentityId } from '../../platform/auth/index.js';
|
|
2
3
|
import type { BoundClientSession } from '../../platform/client/session/index.js';
|
|
3
4
|
import type { NodeId } from '../../platform/graph/node/index.js';
|
|
@@ -5,15 +6,15 @@ import type { CallableInputOf, CallableInputModeOf, Domain, ResolvedFunction, Re
|
|
|
5
6
|
import type { HandlerDependencies } from '../dependency/index.js';
|
|
6
7
|
import type { DeclaredErrorFactory, DeclaredErrors, NoDeclaredErrors } from '../error/index.js';
|
|
7
8
|
import type { IntegrationClients, Integrations } from '../integration/index.js';
|
|
8
|
-
import type { MutationExecutor } from '../mutation/index.js';
|
|
9
|
-
import type { QueryExecutor } from '../query/index.js';
|
|
9
|
+
import type { Mutation, MutationExecutor, MutationInputOf, MutationOutputOf, MutationRecipe } from '../mutation/index.js';
|
|
10
|
+
import type { InputOf, OutputOf, QueryDefinition, QueryExecutor, Recipe, RecipeOptions } from '../query/index.js';
|
|
10
11
|
export type ResolvedCallable = ResolvedFunction | ResolvedMethod;
|
|
11
12
|
/** Bearer-free caller evidence established before an Action or Workflow runs. */
|
|
12
13
|
export interface AuthenticatedCaller {
|
|
13
14
|
readonly kind: 'authenticated';
|
|
14
15
|
/** The one Identity whose authority this invocation exercises: ownership and attribution. */
|
|
15
16
|
readonly identity: IdentityId;
|
|
16
|
-
/**
|
|
17
|
+
/** Identity whose credential directly authenticated this invocation (for example this Domain). */
|
|
17
18
|
readonly principal: IdentityId;
|
|
18
19
|
readonly authentication: AuthenticationEvidence;
|
|
19
20
|
}
|
|
@@ -33,11 +34,11 @@ export interface ActionExecution {
|
|
|
33
34
|
body(): Uint8Array;
|
|
34
35
|
};
|
|
35
36
|
}
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
/**
|
|
38
|
+
* One admitted, single-consumer binary Function request body: the Core request body itself, so a
|
|
39
|
+
* handler can hand it as it is to the Kernel's content upload without reading it first.
|
|
40
|
+
*/
|
|
41
|
+
export type ActionBinaryInput = BinaryRequestBody;
|
|
41
42
|
type BinaryInputOf<Callable> = CallableInputModeOf<Callable> extends 'binary' ? {
|
|
42
43
|
readonly body: ActionBinaryInput;
|
|
43
44
|
} : {
|
|
@@ -47,8 +48,35 @@ export interface DomainGraphExecutor<DomainValue extends Domain = Domain> {
|
|
|
47
48
|
readonly query: QueryExecutor<DomainValue>;
|
|
48
49
|
readonly mutate: MutationExecutor<DomainValue>;
|
|
49
50
|
}
|
|
50
|
-
/** Whose authority one Kernel call exercises: the incoming caller's or the
|
|
51
|
+
/** Whose authority one Kernel call exercises: the incoming caller's or the executor's (`self`). */
|
|
51
52
|
export type AuthorityMode = 'caller' | 'self';
|
|
53
|
+
/**
|
|
54
|
+
* The name a call gives an authority with `as`: `executor` is `self`, the authority the handler
|
|
55
|
+
* executes with.
|
|
56
|
+
*/
|
|
57
|
+
export type AuthorityName<Authority extends AuthorityMode = AuthorityMode> = Authority extends 'self' ? 'executor' : 'caller';
|
|
58
|
+
/**
|
|
59
|
+
* Reads under the authority `as` names. A recipe names it on every call; a V6 Query definition
|
|
60
|
+
* reads as the executor.
|
|
61
|
+
*/
|
|
62
|
+
export interface DomainQuery<DomainValue extends Domain = Domain, As extends AuthorityName = AuthorityName> {
|
|
63
|
+
<R extends Recipe>(recipe: R, input: InputOf<R>, options: RecipeOptions<R, As>): Promise<OutputOf<R>>;
|
|
64
|
+
<Output>(query: QueryDefinition<DomainValue, void, Output>): Promise<Output>;
|
|
65
|
+
<Input, Output>(query: QueryDefinition<DomainValue, Input, Output>, input: Input): Promise<Output>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Writes under the authority `as` names. A Mutation recipe names it on every call; a Mutation of
|
|
69
|
+
* the closure form may omit it, and writes as the executor.
|
|
70
|
+
*/
|
|
71
|
+
export interface DomainMutate<DomainValue extends Domain = Domain, As extends AuthorityName = AuthorityName> {
|
|
72
|
+
<M extends MutationRecipe>(mutation: M, input: MutationInputOf<M>, options: {
|
|
73
|
+
readonly as: As;
|
|
74
|
+
}): Promise<MutationOutputOf<M>>;
|
|
75
|
+
<Input, Output>(definition: Mutation<DomainValue, Input, Output>, input: Input, options: {
|
|
76
|
+
readonly as: As;
|
|
77
|
+
}): Promise<Output>;
|
|
78
|
+
<Input, Output>(definition: Mutation<DomainValue, Input, Output>, input: Input): Promise<Output>;
|
|
79
|
+
}
|
|
52
80
|
/**
|
|
53
81
|
* One value per authority the code may act under. `self` exists in every invocation; `caller`
|
|
54
82
|
* exists only when the call carried a credential, so it is absent from a call that carried none.
|
|
@@ -60,21 +88,24 @@ export type DomainGraphExecutors<DomainValue extends Domain = Domain, Authority
|
|
|
60
88
|
/** Self-default Kernel session with one level of explicit authority selection. */
|
|
61
89
|
export type DomainKernelSessions<Authority extends AuthorityMode = AuthorityMode> = Readonly<BoundClientSession & AuthoritySelection<BoundClientSession, Authority>>;
|
|
62
90
|
/**
|
|
63
|
-
* What code acting under `Authority` holds toward the Kernel and its dependencies
|
|
64
|
-
*
|
|
91
|
+
* What code acting under `Authority` holds toward the Kernel and its dependencies: `kernel` and
|
|
92
|
+
* `graph` act with the executor's authority by default. An Action and a durable Step receive the
|
|
93
|
+
* same capabilities.
|
|
94
|
+
* `query` and `mutate` take the authorities `As` names.
|
|
65
95
|
*/
|
|
66
|
-
export interface DomainCapabilities<DomainValue extends Domain = Domain, Authority extends AuthorityMode = AuthorityMode
|
|
96
|
+
export interface DomainCapabilities<DomainValue extends Domain = Domain, Authority extends AuthorityMode = AuthorityMode, As extends AuthorityName = AuthorityName<Authority>> {
|
|
67
97
|
readonly dependencies: HandlerDependencies<DomainValue, Authority>;
|
|
68
98
|
readonly kernel: DomainKernelSessions<Authority>;
|
|
69
99
|
readonly graph: DomainGraphExecutors<DomainValue, Authority>;
|
|
70
|
-
/**
|
|
71
|
-
readonly query:
|
|
72
|
-
/**
|
|
73
|
-
readonly mutate:
|
|
100
|
+
/** A recipe under `as`; a V6 Query definition as the executor (`graph.self`). */
|
|
101
|
+
readonly query: DomainQuery<DomainValue, As>;
|
|
102
|
+
/** A Mutation under `as`, or as the executor (`graph.self`). */
|
|
103
|
+
readonly mutate: DomainMutate<DomainValue, As>;
|
|
74
104
|
}
|
|
75
105
|
/**
|
|
76
106
|
* A call to an `anonymous` callable that carries no credential has no caller, and its handler acts
|
|
77
|
-
* as the
|
|
107
|
+
* as the executor alone. Every other call also selects its authenticated caller. An `anonymous`
|
|
108
|
+
* callable's `query` and `mutate` name the executor alone, whichever call it is.
|
|
78
109
|
*/
|
|
79
110
|
type AuthorityOf<Callable, DomainValue extends Domain> = Callable extends {
|
|
80
111
|
readonly auth: 'anonymous';
|
|
@@ -82,7 +113,7 @@ type AuthorityOf<Callable, DomainValue extends Domain> = Callable extends {
|
|
|
82
113
|
readonly caller: AnonymousCaller;
|
|
83
114
|
} & DomainCapabilities<DomainValue, 'self'>) | ({
|
|
84
115
|
readonly caller: AuthenticatedCaller;
|
|
85
|
-
} & DomainCapabilities<DomainValue, AuthorityMode>) : {
|
|
116
|
+
} & DomainCapabilities<DomainValue, AuthorityMode, 'executor'>) : {
|
|
86
117
|
readonly caller: AuthenticatedCaller;
|
|
87
118
|
} & DomainCapabilities<DomainValue, AuthorityMode>;
|
|
88
119
|
type SelfOf<Callable> = Callable extends ResolvedMethod<unknown, unknown, false> ? {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { defineAction, isAction } from './define.js';
|
|
2
2
|
export type { Action, ActionAddress, ActionAddressOf, ActionDefinition, CallableAtAddress, LocalCallable, ResolvedDomainOf, } from './action.js';
|
|
3
|
-
export type { ActionContext, ActionBinaryInput, ActionExecution, AnonymousCaller, AuthenticatedCaller, AuthorityMode, AuthoritySelection, Caller, DomainCapabilities, DomainGraphExecutor, DomainGraphExecutors, DomainKernelSessions, ResolvedCallable, } from './context.js';
|
|
3
|
+
export type { ActionContext, ActionBinaryInput, ActionExecution, AnonymousCaller, AuthenticatedCaller, AuthorityMode, AuthorityName, AuthoritySelection, Caller, DomainCapabilities, DomainGraphExecutor, DomainGraphExecutors, DomainKernelSessions, DomainMutate, DomainQuery, ResolvedCallable, } from './context.js';
|
|
4
4
|
export type { ActionBinary, ActionOutputOf } from './output.js';
|
|
5
5
|
export type { DeclaredErrorDefinition, DeclaredErrorFactory, DeclaredErrorFamily, DeclaredErrorOptions, DeclaredErrors, } from '../error/index.js';
|
|
@@ -10,6 +10,8 @@ export interface DeclaredErrorOptions<out Errors extends DeclaredErrors = Declar
|
|
|
10
10
|
export type DeclaredErrorFactory<Errors extends DeclaredErrors> = <Code extends Extract<keyof Errors, string>>(code: Code, details: z.input<Errors[Code]['details']>) => Error;
|
|
11
11
|
interface DeclaredErrorRecord {
|
|
12
12
|
readonly definition: object;
|
|
13
|
+
/** The declaration as written: the object a Function re-declares a recipe's error with. */
|
|
14
|
+
readonly declaration: object;
|
|
13
15
|
readonly code: string;
|
|
14
16
|
readonly family: DeclaredErrorDefinition['family'];
|
|
15
17
|
readonly message: string;
|
|
@@ -17,7 +19,20 @@ interface DeclaredErrorRecord {
|
|
|
17
19
|
}
|
|
18
20
|
/** Retain one implementation-local expected-error contract without publishing it in the Schema. */
|
|
19
21
|
export declare function rememberDeclaredErrors(definition: object, input: unknown): void;
|
|
22
|
+
/**
|
|
23
|
+
* Retain a recipe's declared errors, as an implementation's are. A Function that declares one of
|
|
24
|
+
* its codes with the same definition object exposes that error to its own caller.
|
|
25
|
+
*/
|
|
26
|
+
export declare function rememberRecipeErrors(recipe: object, id: string, input: unknown): void;
|
|
20
27
|
/** Create a receiver-free factory closed over one admitted Action or Workflow definition. */
|
|
21
28
|
export declare function declaredErrorFactory<Errors extends DeclaredErrors>(definition: object): DeclaredErrorFactory<Errors>;
|
|
22
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Return admitted evidence only when the error belongs to the active implementation: raised by it,
|
|
31
|
+
* or by a recipe whose code it declares with the same definition object.
|
|
32
|
+
*/
|
|
23
33
|
export declare function declaredErrorOf(input: unknown, definition: object): DeclaredErrorRecord | undefined;
|
|
34
|
+
/** The code and the recipe id of a recipe's declared error, whichever Function it reaches. */
|
|
35
|
+
export declare function recipeErrorOf(input: unknown): {
|
|
36
|
+
readonly code: string;
|
|
37
|
+
readonly recipe: string;
|
|
38
|
+
} | undefined;
|
|
@@ -5,6 +5,8 @@ const MAXIMUM_DETAILS_BYTES = 16 * 1024;
|
|
|
5
5
|
const CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,127}$/u;
|
|
6
6
|
const declarations = new WeakMap();
|
|
7
7
|
const errors = new WeakMap();
|
|
8
|
+
/** Each recipe's id, by recipe: a recipe's errors cross into a Function that re-declares them. */
|
|
9
|
+
const recipes = new WeakMap();
|
|
8
10
|
class BusinessError extends Error {
|
|
9
11
|
name = 'BusinessError';
|
|
10
12
|
constructor(code) {
|
|
@@ -25,17 +27,29 @@ export function rememberDeclaredErrors(definition, input) {
|
|
|
25
27
|
for (const [code, candidate] of entries) {
|
|
26
28
|
if (!CODE_PATTERN.test(code))
|
|
27
29
|
throw new TypeError(`Declared error code ${JSON.stringify(code)} is invalid.`);
|
|
28
|
-
accepted.set(code,
|
|
30
|
+
accepted.set(code, {
|
|
31
|
+
accepted: acceptErrorDefinition(candidate, `Declared error ${JSON.stringify(code)}`),
|
|
32
|
+
declaration: candidate,
|
|
33
|
+
});
|
|
29
34
|
}
|
|
30
35
|
declarations.set(definition, accepted);
|
|
31
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Retain a recipe's declared errors, as an implementation's are. A Function that declares one of
|
|
39
|
+
* its codes with the same definition object exposes that error to its own caller.
|
|
40
|
+
*/
|
|
41
|
+
export function rememberRecipeErrors(recipe, id, input) {
|
|
42
|
+
rememberDeclaredErrors(recipe, { errors: input });
|
|
43
|
+
recipes.set(recipe, id);
|
|
44
|
+
}
|
|
32
45
|
/** Create a receiver-free factory closed over one admitted Action or Workflow definition. */
|
|
33
46
|
export function declaredErrorFactory(definition) {
|
|
34
47
|
return ((code, input) => {
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
48
|
+
const declared = declarations.get(definition)?.get(code);
|
|
49
|
+
if (declared === undefined) {
|
|
37
50
|
throw new TypeError(`Business error ${JSON.stringify(code)} is not declared by this implementation.`);
|
|
38
51
|
}
|
|
52
|
+
const { accepted: declaration } = declared;
|
|
39
53
|
const parsed = declaration.details.safeParse(input);
|
|
40
54
|
if (!parsed.success) {
|
|
41
55
|
throw new TypeError(`Business error ${code} details are invalid.`, { cause: parsed.error });
|
|
@@ -56,6 +70,7 @@ export function declaredErrorFactory(definition) {
|
|
|
56
70
|
const error = new BusinessError(code);
|
|
57
71
|
errors.set(error, Object.freeze({
|
|
58
72
|
definition,
|
|
73
|
+
declaration: declared.declaration,
|
|
59
74
|
code,
|
|
60
75
|
family: declaration.family,
|
|
61
76
|
message: declaration.message,
|
|
@@ -64,12 +79,31 @@ export function declaredErrorFactory(definition) {
|
|
|
64
79
|
return error;
|
|
65
80
|
});
|
|
66
81
|
}
|
|
67
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* Return admitted evidence only when the error belongs to the active implementation: raised by it,
|
|
84
|
+
* or by a recipe whose code it declares with the same definition object.
|
|
85
|
+
*/
|
|
68
86
|
export function declaredErrorOf(input, definition) {
|
|
69
87
|
if (!(input instanceof Error))
|
|
70
88
|
return undefined;
|
|
71
89
|
const record = errors.get(input);
|
|
72
|
-
|
|
90
|
+
if (record === undefined)
|
|
91
|
+
return undefined;
|
|
92
|
+
if (record.definition === definition)
|
|
93
|
+
return record;
|
|
94
|
+
if (!recipes.has(record.definition))
|
|
95
|
+
return undefined;
|
|
96
|
+
return declarations.get(definition)?.get(record.code)?.declaration === record.declaration
|
|
97
|
+
? record
|
|
98
|
+
: undefined;
|
|
99
|
+
}
|
|
100
|
+
/** The code and the recipe id of a recipe's declared error, whichever Function it reaches. */
|
|
101
|
+
export function recipeErrorOf(input) {
|
|
102
|
+
const record = input instanceof Error ? errors.get(input) : undefined;
|
|
103
|
+
if (record === undefined)
|
|
104
|
+
return undefined;
|
|
105
|
+
const recipe = recipes.get(record.definition);
|
|
106
|
+
return recipe === undefined ? undefined : { code: record.code, recipe };
|
|
73
107
|
}
|
|
74
108
|
function isRecord(input) {
|
|
75
109
|
return input !== null && typeof input === 'object' && !Array.isArray(input);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export type { DeclaredErrorDefinition, DeclaredErrorFactory, DeclaredErrorFamily, DeclaredErrorOptions, DeclaredErrors, NoDeclaredErrors, } from './error.js';
|
|
2
|
-
export { declaredErrorFactory, declaredErrorOf, rememberDeclaredErrors } from './error.js';
|
|
2
|
+
export { declaredErrorFactory, declaredErrorOf, recipeErrorOf, rememberDeclaredErrors, rememberRecipeErrors, } from './error.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { declaredErrorFactory, declaredErrorOf, rememberDeclaredErrors } from './error.js';
|
|
1
|
+
export { declaredErrorFactory, declaredErrorOf, recipeErrorOf, rememberDeclaredErrors, rememberRecipeErrors, } from './error.js';
|
|
@@ -1,55 +1,103 @@
|
|
|
1
1
|
import type { EdgeSlug } from '@astrale-os/kernel-core/graph/edge';
|
|
2
2
|
import type { MutationBuilder } from '@astrale-os/kernel-core/graph/mutate';
|
|
3
|
-
import type { QueryEdgeAST, QueryNodeAST } from '@astrale-os/kernel-core/graph/query';
|
|
4
3
|
import type { ResolvedClass } from '@astrale-os/kernel-dsl/v1';
|
|
4
|
+
import type { Cls } from '../../query/handles/domain.js';
|
|
5
|
+
import type { EdgeSet, NodeSet } from '../../query/handles/handles.js';
|
|
6
|
+
import type { ClosureMutationBuilder } from '../legacy/v6/builder.js';
|
|
5
7
|
import type { CreatedNode, ExistingEndpoint, MutationEndpoint } from './endpoint.js';
|
|
6
|
-
import type {
|
|
8
|
+
import type { CountBounds, GuardIdentity, GuardRange, PreconditionOptions } from './guard.js';
|
|
7
9
|
import type { RichPropertyConditions, RichPropertyDelta, RichPropertyInput } from './properties.js';
|
|
10
|
+
import { type PreconditionNames } from './names.js';
|
|
8
11
|
import { type EventOfTopology, type StatePropertyName, type StatePropertyTopology, type StateOfTopology, type StateTransitionInput } from './transition.js';
|
|
9
|
-
|
|
12
|
+
/**
|
|
13
|
+
* The builder a Mutation's `build` receives: of the recipe form over `Schema`
|
|
14
|
+
* (`RichMutationBuilder<Schema>`), or of the deprecated closure form (`RichMutationBuilder`).
|
|
15
|
+
*/
|
|
16
|
+
export type RichMutationBuilder<Schema = never> = [Schema] extends [never] ? ClosureMutationBuilder : RecipeMutationBuilder<Schema>;
|
|
17
|
+
/** A write names one Class: the handle of a union, `d.A.union(d.B)`, names none. */
|
|
18
|
+
type OneClass<K, All = K> = K extends unknown ? [All] extends [K] ? unknown : '✗ a write names one Class, not a union' : never;
|
|
19
|
+
/** A Node Class handle, `d.<Class>`, on which no method was called. */
|
|
20
|
+
type NodeClassHandle<S, K extends string> = NodeSet<S, K, {}, 'value', false> & OneClass<K>;
|
|
21
|
+
/** An Edge Class handle, `d.<Edge>`, on which no method was called. */
|
|
22
|
+
type EdgeClassHandle<S, E extends string> = EdgeSet<S, E, 'value', false>;
|
|
23
|
+
type NodeClassOf<S, K extends string> = Extract<Cls<S, K>, ResolvedClass<'node'>>;
|
|
24
|
+
interface HandleEdgeSelector<S, E extends string> {
|
|
25
|
+
readonly source: ExistingEndpoint;
|
|
26
|
+
readonly target: ExistingEndpoint;
|
|
27
|
+
readonly class: EdgeClassHandle<S, E>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The builder a Mutation recipe's `build` receives (D31): each verb names its Class by its handle,
|
|
31
|
+
* `d.<Class>`, and each guard takes a range. Each precondition verb (`expect.*`) takes `{ name }`
|
|
32
|
+
* last: a refusal of the Mutation because that precondition fails carries the name as
|
|
33
|
+
* `failure.precondition`. A name is unique in its Mutation.
|
|
34
|
+
*/
|
|
35
|
+
export interface RecipeMutationBuilder<S> {
|
|
10
36
|
readonly expect: {
|
|
11
|
-
|
|
37
|
+
/** The Node exists, of this Class, and its Properties meet `props`. */
|
|
38
|
+
node<K extends string>(input: {
|
|
12
39
|
readonly node: ExistingEndpoint;
|
|
13
|
-
readonly class:
|
|
14
|
-
readonly props?: RichPropertyConditions<
|
|
15
|
-
}):
|
|
16
|
-
|
|
40
|
+
readonly class: NodeClassHandle<S, K>;
|
|
41
|
+
readonly props?: RichPropertyConditions<Cls<S, K>>;
|
|
42
|
+
}, options?: PreconditionOptions): RecipeMutationBuilder<S>;
|
|
43
|
+
/** The Edge exists, its slug is `slug`, and its Properties meet `props`. */
|
|
44
|
+
edge<E extends string>(input: HandleEdgeSelector<S, E> & {
|
|
17
45
|
readonly slug?: EdgeSlug | null;
|
|
18
|
-
readonly props?: RichPropertyConditions<
|
|
19
|
-
}):
|
|
20
|
-
|
|
46
|
+
readonly props?: RichPropertyConditions<Cls<S, E>>;
|
|
47
|
+
}, options?: PreconditionOptions): RecipeMutationBuilder<S>;
|
|
48
|
+
/**
|
|
49
|
+
* A guard (Mutation v4): no root of the range matches. A range is a Class handle after
|
|
50
|
+
* `where`, `union` and `orderBy`, `d.<Class>.where(…)`, or a canonical v7 Query.
|
|
51
|
+
*/
|
|
52
|
+
none<K extends string>(range: GuardRange<S, K>, options?: PreconditionOptions): RecipeMutationBuilder<S>;
|
|
53
|
+
/** A guard (Mutation v4): a root of the range matches. */
|
|
54
|
+
exists<K extends string>(range: GuardRange<S, K>, options?: PreconditionOptions): RecipeMutationBuilder<S>;
|
|
55
|
+
/**
|
|
56
|
+
* A guard (Mutation v4): the number of the range's roots is `bounds`, or lies within
|
|
57
|
+
* `{ atLeast?, atMost? }`, bounded at least once.
|
|
58
|
+
*/
|
|
59
|
+
count<K extends string>(range: GuardRange<S, K>, bounds: CountBounds, options?: PreconditionOptions): RecipeMutationBuilder<S>;
|
|
60
|
+
/**
|
|
61
|
+
* A guard (Mutation v4): the range's roots, in its `orderBy`, are exactly these identities, in
|
|
62
|
+
* the order a read of them returned them: a `NodeId` per Node, an `EdgeKey` per Edge.
|
|
63
|
+
*/
|
|
64
|
+
unchanged<K extends string>(range: GuardRange<S, K>, ids: readonly GuardIdentity[], options?: PreconditionOptions): RecipeMutationBuilder<S>;
|
|
21
65
|
};
|
|
22
|
-
createNode<
|
|
66
|
+
createNode<K extends string>(input: {
|
|
23
67
|
readonly as: string;
|
|
24
|
-
readonly class:
|
|
25
|
-
readonly props?: RichPropertyInput<
|
|
26
|
-
}): CreatedNode<
|
|
27
|
-
updateNode<
|
|
68
|
+
readonly class: NodeClassHandle<S, K>;
|
|
69
|
+
readonly props?: RichPropertyInput<Cls<S, K>>;
|
|
70
|
+
}): CreatedNode<NodeClassOf<S, K>>;
|
|
71
|
+
updateNode<K extends string>(input: {
|
|
28
72
|
readonly node: ExistingEndpoint;
|
|
29
|
-
readonly class:
|
|
30
|
-
readonly props: RichPropertyDelta<
|
|
73
|
+
readonly class: NodeClassHandle<S, K>;
|
|
74
|
+
readonly props: RichPropertyDelta<Cls<S, K>>;
|
|
31
75
|
}): this;
|
|
32
|
-
transition<
|
|
33
|
-
deleteNode<
|
|
76
|
+
transition<K extends string, Property extends StatePropertyName<NodeClassOf<S, K>>, From extends StateOfTopology<StatePropertyTopology<NodeClassOf<S, K>, Property>>, Event extends EventOfTopology<StatePropertyTopology<NodeClassOf<S, K>, Property>, From>>(input: StateTransitionInput<NodeClassOf<S, K>, Property, From, Event, NodeClassHandle<S, K>>): this;
|
|
77
|
+
deleteNode<K extends string>(input: {
|
|
34
78
|
readonly node: ExistingEndpoint;
|
|
35
|
-
readonly class:
|
|
79
|
+
readonly class: NodeClassHandle<S, K>;
|
|
36
80
|
}): this;
|
|
37
|
-
createEdge<
|
|
38
|
-
readonly class:
|
|
81
|
+
createEdge<E extends string>(input: {
|
|
82
|
+
readonly class: EdgeClassHandle<S, E>;
|
|
39
83
|
readonly source: MutationEndpoint;
|
|
40
84
|
readonly target: MutationEndpoint;
|
|
41
85
|
readonly slug?: EdgeSlug;
|
|
42
|
-
readonly props?: RichPropertyInput<
|
|
86
|
+
readonly props?: RichPropertyInput<Cls<S, E>>;
|
|
43
87
|
}): this;
|
|
44
|
-
updateEdge<
|
|
88
|
+
updateEdge<E extends string>(input: HandleEdgeSelector<S, E> & {
|
|
45
89
|
readonly slug?: EdgeSlug | null;
|
|
46
|
-
readonly props?: RichPropertyDelta<
|
|
90
|
+
readonly props?: RichPropertyDelta<Cls<S, E>>;
|
|
47
91
|
}): this;
|
|
48
|
-
deleteEdge<
|
|
49
|
-
}
|
|
50
|
-
export interface EdgeSelector<Class extends ResolvedClass<'edge'>> {
|
|
51
|
-
readonly source: ExistingEndpoint;
|
|
52
|
-
readonly target: ExistingEndpoint;
|
|
53
|
-
readonly class: Class;
|
|
92
|
+
deleteEdge<E extends string>(input: HandleEdgeSelector<S, E>): this;
|
|
54
93
|
}
|
|
55
|
-
|
|
94
|
+
/** The form of the Mutation a builder writes: a recipe's, or the deprecated closure form's. */
|
|
95
|
+
export type BuilderForm = 'recipe' | 'closure';
|
|
96
|
+
/**
|
|
97
|
+
* The SDK's builder over the core one, for both forms: a verb's Class is a Class handle
|
|
98
|
+
* (`d.<Class>`) or, in the closure form only, the resolved Class, and only the closure form has a
|
|
99
|
+
* V6 Query precondition (`expect.query`). `names` keeps the names of its preconditions, so that
|
|
100
|
+
* the Mutation's execution can name the one a refusal points at.
|
|
101
|
+
*/
|
|
102
|
+
export declare function richMutationBuilder(portable: MutationBuilder, names?: PreconditionNames, form?: BuilderForm): ClosureMutationBuilder;
|
|
103
|
+
export {};
|
|
@@ -1,77 +1,125 @@
|
|
|
1
1
|
import { ClassKey } from '@astrale-os/kernel-dsl/v1/addressing';
|
|
2
2
|
import { LocalAlias } from '../../../platform/graph/alias.js';
|
|
3
|
+
import { handleClass } from '../../query/handles/reads.js';
|
|
4
|
+
import { precondition } from '../legacy/v6/precondition.js';
|
|
3
5
|
import { endpoint, existingEndpoint } from './endpoint.js';
|
|
4
|
-
import {
|
|
6
|
+
import { countGuard, existsGuard, sequenceGuard } from './guard.js';
|
|
7
|
+
import { preconditionNames } from './names.js';
|
|
5
8
|
import { conditions, delta, properties } from './properties.js';
|
|
6
9
|
import { authorStateTransition, } from './transition.js';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
/**
|
|
11
|
+
* The SDK's builder over the core one, for both forms: a verb's Class is a Class handle
|
|
12
|
+
* (`d.<Class>`) or, in the closure form only, the resolved Class, and only the closure form has a
|
|
13
|
+
* V6 Query precondition (`expect.query`). `names` keeps the names of its preconditions, so that
|
|
14
|
+
* the Mutation's execution can name the one a refusal points at.
|
|
15
|
+
*/
|
|
16
|
+
export function richMutationBuilder(portable, names = preconditionNames(), form = 'closure') {
|
|
17
|
+
const core = names.track(portable);
|
|
18
|
+
const nodeClass = (selected) => writtenClass(selected, 'node', form);
|
|
19
|
+
const edgeClass = (selected) => writtenClass(selected, 'edge', form);
|
|
20
|
+
const guards = {
|
|
21
|
+
node(input, options) {
|
|
22
|
+
const cls = nodeClass(input.class);
|
|
23
|
+
names.name(nameOf(options), () => {
|
|
24
|
+
core.expect.node({
|
|
25
|
+
node: existingEndpoint(input.node),
|
|
26
|
+
class: classKey(cls),
|
|
27
|
+
...(input.props === undefined ? {} : { props: conditions(cls, input.props) }),
|
|
28
|
+
});
|
|
14
29
|
});
|
|
15
30
|
return rich;
|
|
16
31
|
},
|
|
17
|
-
edge(input) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
32
|
+
edge(input, options) {
|
|
33
|
+
const cls = edgeClass(input.class);
|
|
34
|
+
names.name(nameOf(options), () => {
|
|
35
|
+
core.expect.edge({
|
|
36
|
+
source: existingEndpoint(input.source),
|
|
37
|
+
target: existingEndpoint(input.target),
|
|
38
|
+
class: classKey(cls),
|
|
39
|
+
...(Object.hasOwn(input, 'slug') ? { slug: input.slug ?? null } : {}),
|
|
40
|
+
...(input.props === undefined ? {} : { props: conditions(cls, input.props) }),
|
|
41
|
+
});
|
|
24
42
|
});
|
|
25
43
|
return rich;
|
|
26
44
|
},
|
|
27
|
-
|
|
28
|
-
|
|
45
|
+
none(range, options) {
|
|
46
|
+
names.name(nameOf(options), () => core.expect.query(existsGuard(range, false)));
|
|
29
47
|
return rich;
|
|
30
48
|
},
|
|
31
|
-
|
|
49
|
+
exists(range, options) {
|
|
50
|
+
names.name(nameOf(options), () => core.expect.query(existsGuard(range, true)));
|
|
51
|
+
return rich;
|
|
52
|
+
},
|
|
53
|
+
count(range, bounds, options) {
|
|
54
|
+
names.name(nameOf(options), () => core.expect.query(countGuard(range, bounds)));
|
|
55
|
+
return rich;
|
|
56
|
+
},
|
|
57
|
+
unchanged(range, ids, options) {
|
|
58
|
+
names.name(nameOf(options), () => core.expect.query(sequenceGuard(range, ids)));
|
|
59
|
+
return rich;
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
const expectation = Object.freeze(form === 'recipe'
|
|
63
|
+
? guards
|
|
64
|
+
: {
|
|
65
|
+
...guards,
|
|
66
|
+
query(input) {
|
|
67
|
+
precondition(core, input);
|
|
68
|
+
return rich;
|
|
69
|
+
},
|
|
70
|
+
});
|
|
32
71
|
const rich = Object.freeze({
|
|
33
72
|
expect: expectation,
|
|
34
73
|
createNode(input) {
|
|
74
|
+
const cls = nodeClass(input.class);
|
|
35
75
|
const created = core.createNode({
|
|
36
76
|
as: LocalAlias(input.as),
|
|
37
|
-
class: classKey(
|
|
38
|
-
...(input.props === undefined ? {} : { props: properties(
|
|
77
|
+
class: classKey(cls),
|
|
78
|
+
...(input.props === undefined ? {} : { props: properties(cls, input.props) }),
|
|
39
79
|
});
|
|
40
|
-
return Object.freeze({ ...created, class:
|
|
80
|
+
return Object.freeze({ ...created, class: cls });
|
|
41
81
|
},
|
|
42
82
|
updateNode(input) {
|
|
83
|
+
const cls = nodeClass(input.class);
|
|
43
84
|
core.updateNode({
|
|
44
85
|
node: existingEndpoint(input.node),
|
|
45
|
-
class: classKey(
|
|
46
|
-
props: delta(
|
|
86
|
+
class: classKey(cls),
|
|
87
|
+
props: delta(cls, input.props),
|
|
47
88
|
});
|
|
48
89
|
return rich;
|
|
49
90
|
},
|
|
50
91
|
transition(input) {
|
|
51
|
-
|
|
92
|
+
// Each coordinate is read once: the copy is what the transition reads.
|
|
93
|
+
const coordinates = { ...input };
|
|
94
|
+
authorStateTransition(core, { ...coordinates, class: nodeClass(coordinates.class) });
|
|
52
95
|
return rich;
|
|
53
96
|
},
|
|
54
97
|
deleteNode(input) {
|
|
55
|
-
core.deleteNode({
|
|
98
|
+
core.deleteNode({
|
|
99
|
+
node: existingEndpoint(input.node),
|
|
100
|
+
class: classKey(nodeClass(input.class)),
|
|
101
|
+
});
|
|
56
102
|
return rich;
|
|
57
103
|
},
|
|
58
104
|
createEdge(input) {
|
|
105
|
+
const cls = edgeClass(input.class);
|
|
59
106
|
core.createEdge({
|
|
60
|
-
class: classKey(
|
|
107
|
+
class: classKey(cls),
|
|
61
108
|
source: endpoint(input.source),
|
|
62
109
|
target: endpoint(input.target),
|
|
63
110
|
...(input.slug === undefined ? {} : { slug: input.slug }),
|
|
64
|
-
...(input.props === undefined ? {} : { props: properties(
|
|
111
|
+
...(input.props === undefined ? {} : { props: properties(cls, input.props) }),
|
|
65
112
|
});
|
|
66
113
|
return rich;
|
|
67
114
|
},
|
|
68
115
|
updateEdge(input) {
|
|
116
|
+
const cls = edgeClass(input.class);
|
|
69
117
|
const selector = {
|
|
70
118
|
source: existingEndpoint(input.source),
|
|
71
119
|
target: existingEndpoint(input.target),
|
|
72
|
-
class: classKey(
|
|
120
|
+
class: classKey(cls),
|
|
73
121
|
};
|
|
74
|
-
const patch = input.props === undefined ? undefined : delta(
|
|
122
|
+
const patch = input.props === undefined ? undefined : delta(cls, input.props);
|
|
75
123
|
if (Object.hasOwn(input, 'slug')) {
|
|
76
124
|
core.updateEdge({
|
|
77
125
|
...selector,
|
|
@@ -90,13 +138,35 @@ export function richMutationBuilder(core) {
|
|
|
90
138
|
core.deleteEdge({
|
|
91
139
|
source: existingEndpoint(input.source),
|
|
92
140
|
target: existingEndpoint(input.target),
|
|
93
|
-
class: classKey(input.class),
|
|
141
|
+
class: classKey(edgeClass(input.class)),
|
|
94
142
|
});
|
|
95
143
|
return rich;
|
|
96
144
|
},
|
|
97
145
|
});
|
|
98
146
|
return rich;
|
|
99
147
|
}
|
|
148
|
+
/** The Class a verb names: by its handle, or, in the closure form, as resolved. */
|
|
149
|
+
function writtenClass(selected, kind, form) {
|
|
150
|
+
const cls = handleClass(selected);
|
|
151
|
+
if (cls === undefined) {
|
|
152
|
+
if (form === 'recipe')
|
|
153
|
+
throw new TypeError('A Mutation recipe names each Class by its handle, `d.<Class>`.');
|
|
154
|
+
return selected;
|
|
155
|
+
}
|
|
156
|
+
if (kind === 'node' && cls.kind === 'edge')
|
|
157
|
+
throw new TypeError(`This verb writes a Node, and ${cls.key} is an Edge Class.`);
|
|
158
|
+
if (kind === 'edge' && cls.kind !== 'edge')
|
|
159
|
+
throw new TypeError(`This verb writes an Edge, and ${cls.key} is a Node Class.`);
|
|
160
|
+
return cls;
|
|
161
|
+
}
|
|
100
162
|
function classKey(selected) {
|
|
101
163
|
return ClassKey(selected.key);
|
|
102
164
|
}
|
|
165
|
+
function nameOf(options) {
|
|
166
|
+
if (options === undefined)
|
|
167
|
+
return undefined;
|
|
168
|
+
if (options === null || typeof options !== 'object') {
|
|
169
|
+
throw new TypeError("A precondition's options are `{ name }`.");
|
|
170
|
+
}
|
|
171
|
+
return options.name;
|
|
172
|
+
}
|