@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.
Files changed (175) hide show
  1. package/README.md +22 -2
  2. package/dist/application/action/context.d.ts +49 -18
  3. package/dist/application/action/index.d.ts +1 -1
  4. package/dist/application/error/error.d.ts +16 -1
  5. package/dist/application/error/error.js +39 -5
  6. package/dist/application/error/index.d.ts +1 -1
  7. package/dist/application/error/index.js +1 -1
  8. package/dist/application/mutation/authoring/builder.d.ts +81 -33
  9. package/dist/application/mutation/authoring/builder.js +100 -30
  10. package/dist/application/mutation/authoring/guard.d.ts +37 -0
  11. package/dist/application/mutation/authoring/guard.js +150 -0
  12. package/dist/application/mutation/authoring/index.d.ts +5 -2
  13. package/dist/application/mutation/authoring/index.js +2 -1
  14. package/dist/application/mutation/authoring/names.d.ts +19 -0
  15. package/dist/application/mutation/authoring/names.js +83 -0
  16. package/dist/application/mutation/authoring/transition.d.ts +3 -2
  17. package/dist/application/mutation/define.d.ts +24 -10
  18. package/dist/application/mutation/define.js +49 -53
  19. package/dist/application/mutation/execution/execute.d.ts +2 -2
  20. package/dist/application/mutation/execution/execute.js +78 -7
  21. package/dist/application/mutation/execution/executor.d.ts +4 -1
  22. package/dist/application/mutation/execution/executor.js +6 -1
  23. package/dist/application/mutation/failure.d.ts +11 -1
  24. package/dist/application/mutation/failure.js +18 -3
  25. package/dist/application/mutation/index.d.ts +8 -4
  26. package/dist/application/mutation/index.js +5 -2
  27. package/dist/application/mutation/legacy/v6/builder.d.ts +84 -0
  28. package/dist/application/mutation/legacy/v6/define.d.ts +18 -0
  29. package/dist/application/mutation/legacy/v6/define.js +56 -0
  30. package/dist/application/mutation/legacy/v6/mutation.d.ts +28 -0
  31. package/dist/application/mutation/legacy/v6/mutation.js +1 -0
  32. package/dist/application/mutation/{authoring → legacy/v6}/precondition.d.ts +4 -0
  33. package/dist/application/mutation/mutation.d.ts +30 -19
  34. package/dist/application/query/answer/answer.d.ts +117 -0
  35. package/dist/application/query/answer/answer.js +1 -0
  36. package/dist/application/query/answer/conformance.d.ts +4 -0
  37. package/dist/application/query/answer/conformance.js +27 -0
  38. package/dist/application/query/answer/facts.d.ts +17 -0
  39. package/dist/application/query/answer/facts.js +57 -0
  40. package/dist/application/query/composite/builder.d.ts +1 -1
  41. package/dist/application/query/composite/builder.js +1 -1
  42. package/dist/application/query/composite/plan.d.ts +1 -1
  43. package/dist/application/query/composite/plan.js +1 -1
  44. package/dist/application/query/composite/query.d.ts +1 -2
  45. package/dist/application/query/define.d.ts +2 -33
  46. package/dist/application/query/define.js +3 -130
  47. package/dist/application/query/definition.d.ts +12 -0
  48. package/dist/application/query/definition.js +47 -0
  49. package/dist/application/query/execution/bounds.d.ts +20 -0
  50. package/dist/application/query/execution/bounds.js +86 -0
  51. package/dist/application/query/execution/crossings.d.ts +25 -0
  52. package/dist/application/query/execution/crossings.js +169 -0
  53. package/dist/application/query/execution/error.d.ts +68 -0
  54. package/dist/application/query/execution/error.js +50 -0
  55. package/dist/application/query/execution/execute.d.ts +2 -7
  56. package/dist/application/query/execution/execute.js +3 -51
  57. package/dist/application/query/execution/recipe.d.ts +70 -0
  58. package/dist/application/query/execution/recipe.js +276 -0
  59. package/dist/application/query/execution/refusal.d.ts +37 -0
  60. package/dist/application/query/execution/refusal.js +80 -0
  61. package/dist/application/query/execution/rows.d.ts +42 -0
  62. package/dist/application/query/execution/rows.js +308 -0
  63. package/dist/application/query/execution/split.d.ts +24 -0
  64. package/dist/application/query/execution/split.js +190 -0
  65. package/dist/application/query/handles/build.d.ts +10 -0
  66. package/dist/application/query/handles/build.js +53 -0
  67. package/dist/application/query/handles/conditions.d.ts +94 -0
  68. package/dist/application/query/handles/conditions.js +1 -0
  69. package/dist/application/query/handles/domain.d.ts +157 -0
  70. package/dist/application/query/handles/domain.js +1 -0
  71. package/dist/application/query/handles/handles.d.ts +268 -0
  72. package/dist/application/query/handles/handles.js +1 -0
  73. package/dist/application/query/handles/model.d.ts +96 -0
  74. package/dist/application/query/handles/model.js +246 -0
  75. package/dist/application/query/handles/plan.d.ts +99 -0
  76. package/dist/application/query/handles/plan.js +37 -0
  77. package/dist/application/query/handles/reads.d.ts +106 -0
  78. package/dist/application/query/handles/reads.js +839 -0
  79. package/dist/application/query/handles/relations.d.ts +100 -0
  80. package/dist/application/query/handles/relations.js +1 -0
  81. package/dist/application/query/handles/rows.d.ts +94 -0
  82. package/dist/application/query/handles/rows.js +4 -0
  83. package/dist/application/query/handles/terms.d.ts +95 -0
  84. package/dist/application/query/handles/terms.js +265 -0
  85. package/dist/application/query/index.d.ts +10 -7
  86. package/dist/application/query/index.js +9 -4
  87. package/dist/application/query/language/index.d.ts +2 -0
  88. package/dist/application/query/language/index.js +2 -0
  89. package/dist/application/query/language/query.d.ts +323 -0
  90. package/dist/application/query/language/query.js +2 -0
  91. package/dist/application/query/{collection → legacy/v6/collection}/identity.d.ts +2 -2
  92. package/dist/application/query/{collection → legacy/v6/collection}/metadata.d.ts +1 -1
  93. package/dist/application/query/{collection → legacy/v6/collection}/union.d.ts +3 -3
  94. package/dist/application/query/{collection → legacy/v6/collection}/union.js +1 -1
  95. package/dist/application/query/{conformance.d.ts → legacy/v6/conformance.d.ts} +3 -3
  96. package/dist/application/query/{conformance.js → legacy/v6/conformance.js} +1 -1
  97. package/dist/application/query/legacy/v6/define.d.ts +37 -0
  98. package/dist/application/query/legacy/v6/define.js +90 -0
  99. package/dist/application/query/legacy/v6/execution/execute.d.ts +16 -0
  100. package/dist/application/query/legacy/v6/execution/execute.js +50 -0
  101. package/dist/application/query/legacy/v6/index.d.ts +6 -0
  102. package/dist/application/query/legacy/v6/index.js +3 -0
  103. package/dist/application/query/legacy/v6/kernel.d.ts +6 -0
  104. package/dist/application/query/legacy/v6/kernel.js +5 -0
  105. package/dist/application/query/{single → legacy/v6/single}/pagination.js +1 -1
  106. package/dist/application/query/{single → legacy/v6/single}/query.d.ts +2 -14
  107. package/dist/application/query/legacy/v6/single/query.js +1 -0
  108. package/dist/application/query/query.d.ts +16 -1
  109. package/dist/application/query/recipe/define.d.ts +45 -0
  110. package/dist/application/query/recipe/define.js +102 -0
  111. package/dist/application/query/recipe/recipe.d.ts +134 -0
  112. package/dist/application/query/recipe/recipe.js +1 -0
  113. package/dist/application/workflow/durable.d.ts +22 -8
  114. package/dist/execution/invocation/capabilities/bind.d.ts +6 -4
  115. package/dist/execution/invocation/capabilities/bind.js +39 -3
  116. package/dist/execution/invocation/dispatch/action.js +4 -2
  117. package/dist/execution/invocation/dispatch/context.js +1 -1
  118. package/dist/execution/invocation/dispatch/workflow.js +4 -2
  119. package/dist/execution/invocation/result/index.d.ts +1 -0
  120. package/dist/execution/invocation/result/index.js +1 -0
  121. package/dist/execution/invocation/result/recipe.d.ts +6 -0
  122. package/dist/execution/invocation/result/recipe.js +17 -0
  123. package/dist/execution/service/create.js +1 -0
  124. package/dist/execution/workflows/failure.d.ts +23 -6
  125. package/dist/execution/workflows/failure.js +78 -18
  126. package/dist/execution/workflows/receiver.d.ts +3 -0
  127. package/dist/execution/workflows/receiver.js +1 -0
  128. package/dist/execution/workflows/run.js +13 -9
  129. package/dist/execution/workflows/steps.d.ts +3 -0
  130. package/dist/execution/workflows/steps.js +2 -2
  131. package/dist/platform/schema/kernel-compiled.d.ts +2 -0
  132. package/dist/platform/schema/kernel-compiled.js +2 -0
  133. package/dist/testing/index.d.ts +2 -0
  134. package/dist/testing/index.js +1 -0
  135. package/dist/testing/recipe.d.ts +17 -0
  136. package/dist/testing/recipe.js +29 -0
  137. package/dist/tooling/cli/orchestrate.js +6 -2
  138. package/dist/tooling/linter/adapters/typescript/index.d.ts +2 -1
  139. package/dist/tooling/linter/adapters/typescript/index.js +2 -1
  140. package/dist/tooling/linter/adapters/typescript/query-observation.js +37 -12
  141. package/dist/tooling/linter/adapters/typescript/recipe-definitions.d.ts +34 -0
  142. package/dist/tooling/linter/adapters/typescript/recipe-definitions.js +88 -0
  143. package/dist/tooling/linter/adapters/typescript/type-analysis.d.ts +2 -0
  144. package/dist/tooling/linter/adapters/typescript/type-analysis.js +10 -0
  145. package/dist/tooling/linter/implementations/source/global.js +15 -9
  146. package/dist/tooling/linter/implementations/source/mutations.js +128 -19
  147. package/dist/tooling/linter/implementations/source/policy-edges.d.ts +2 -0
  148. package/dist/tooling/linter/implementations/source/policy-edges.js +113 -0
  149. package/dist/tooling/linter/implementations/source/recipes.d.ts +2 -0
  150. package/dist/tooling/linter/implementations/source/recipes.js +409 -0
  151. package/dist/tooling/linter/implementations/source/shared.d.ts +7 -1
  152. package/dist/tooling/linter/implementations/source/shared.js +31 -10
  153. package/dist/tooling/linter/implementations/source/verifiers.js +4 -0
  154. package/dist/tooling/linter/policy/generated.js +2 -2
  155. package/dist/tooling/linter/requirements/registry.js +4 -0
  156. package/docs/durable-workflows.md +48 -20
  157. package/docs/handler-dependencies.md +76 -4
  158. package/docs/legacy/query-v6.md +62 -0
  159. package/docs/queries.md +438 -0
  160. package/package.json +11 -8
  161. /package/dist/application/{query/single/query.js → mutation/legacy/v6/builder.js} +0 -0
  162. /package/dist/application/mutation/{authoring → legacy/v6}/precondition.js +0 -0
  163. /package/dist/application/query/{collection → legacy/v6/collection}/identity.js +0 -0
  164. /package/dist/application/query/{collection → legacy/v6/collection}/limits.d.ts +0 -0
  165. /package/dist/application/query/{collection → legacy/v6/collection}/limits.js +0 -0
  166. /package/dist/application/query/{collection → legacy/v6/collection}/metadata.js +0 -0
  167. /package/dist/application/query/{execution → legacy/v6/execution}/pages.d.ts +0 -0
  168. /package/dist/application/query/{execution → legacy/v6/execution}/pages.js +0 -0
  169. /package/dist/application/query/{single → legacy/v6/single}/index.d.ts +0 -0
  170. /package/dist/application/query/{single → legacy/v6/single}/index.js +0 -0
  171. /package/dist/application/query/{single → legacy/v6/single}/node.d.ts +0 -0
  172. /package/dist/application/query/{single → legacy/v6/single}/node.js +0 -0
  173. /package/dist/application/query/{single → legacy/v6/single}/pagination.d.ts +0 -0
  174. /package/dist/application/query/{single → legacy/v6/single}/selection.d.ts +0 -0
  175. /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`, `query`, and `mutate`.
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, Bundle and View catalog observation.
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
- /** Executor whose credential directly authenticated this invocation (for example this Domain). */
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
- /** One admitted, single-consumer binary Function request body. */
37
- export interface ActionBinaryInput {
38
- readonly body: ReadableStream<Uint8Array>;
39
- readonly length: number;
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 installed Domain's. */
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, with the
64
- * installed Domain authority by default. An Action and a durable Step receive the same capabilities.
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
- /** Convenience executor using the installed Domain authority (`graph.self`). */
71
- readonly query: QueryExecutor<DomainValue>;
72
- /** Convenience executor using the installed Domain authority (`graph.self`). */
73
- readonly mutate: MutationExecutor<DomainValue>;
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 installed Domain alone. Every other call also selects its authenticated caller.
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
- /** Return admitted evidence only when the error belongs to the active implementation. */
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, acceptErrorDefinition(candidate, `Declared error ${JSON.stringify(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 declaration = declarations.get(definition)?.get(code);
36
- if (declaration === undefined) {
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
- /** Return admitted evidence only when the error belongs to the active implementation. */
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
- return record?.definition === definition ? record : undefined;
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 { QueryPrecondition } from './precondition.js';
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
- export interface RichMutationBuilder {
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
- node<Class extends ResolvedClass<'node'>>(input: {
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: Class;
14
- readonly props?: RichPropertyConditions<Class>;
15
- }): RichMutationBuilder;
16
- edge<Class extends ResolvedClass<'edge'>>(input: EdgeSelector<Class> & {
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<Class>;
19
- }): RichMutationBuilder;
20
- query<Query extends QueryNodeAST | QueryEdgeAST>(input: QueryPrecondition<Query>): RichMutationBuilder;
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<Class extends ResolvedClass<'node'>>(input: {
66
+ createNode<K extends string>(input: {
23
67
  readonly as: string;
24
- readonly class: Class;
25
- readonly props?: RichPropertyInput<Class>;
26
- }): CreatedNode<Class>;
27
- updateNode<Class extends ResolvedClass<'node'>>(input: {
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: Class;
30
- readonly props: RichPropertyDelta<Class>;
73
+ readonly class: NodeClassHandle<S, K>;
74
+ readonly props: RichPropertyDelta<Cls<S, K>>;
31
75
  }): this;
32
- transition<Class extends ResolvedClass<'node'>, Property extends StatePropertyName<Class>, From extends StateOfTopology<StatePropertyTopology<Class, Property>>, Event extends EventOfTopology<StatePropertyTopology<Class, Property>, From>>(input: StateTransitionInput<Class, Property, From, Event>): this;
33
- deleteNode<Class extends ResolvedClass<'node'>>(input: {
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: Class;
79
+ readonly class: NodeClassHandle<S, K>;
36
80
  }): this;
37
- createEdge<Class extends ResolvedClass<'edge'>>(input: {
38
- readonly class: 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<Class>;
86
+ readonly props?: RichPropertyInput<Cls<S, E>>;
43
87
  }): this;
44
- updateEdge<Class extends ResolvedClass<'edge'>>(input: EdgeSelector<Class> & {
88
+ updateEdge<E extends string>(input: HandleEdgeSelector<S, E> & {
45
89
  readonly slug?: EdgeSlug | null;
46
- readonly props?: RichPropertyDelta<Class>;
90
+ readonly props?: RichPropertyDelta<Cls<S, E>>;
47
91
  }): this;
48
- deleteEdge<Class extends ResolvedClass<'edge'>>(input: EdgeSelector<Class>): this;
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
- export declare function richMutationBuilder(core: MutationBuilder): RichMutationBuilder;
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 { precondition } from './precondition.js';
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
- export function richMutationBuilder(core) {
8
- const expectation = Object.freeze({
9
- node(input) {
10
- core.expect.node({
11
- node: existingEndpoint(input.node),
12
- class: classKey(input.class),
13
- ...(input.props === undefined ? {} : { props: conditions(input.class, input.props) }),
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
- core.expect.edge({
19
- source: existingEndpoint(input.source),
20
- target: existingEndpoint(input.target),
21
- class: classKey(input.class),
22
- ...(Object.hasOwn(input, 'slug') ? { slug: input.slug ?? null } : {}),
23
- ...(input.props === undefined ? {} : { props: conditions(input.class, input.props) }),
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
- query(input) {
28
- precondition(core, input);
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(input.class),
38
- ...(input.props === undefined ? {} : { props: properties(input.class, input.props) }),
77
+ class: classKey(cls),
78
+ ...(input.props === undefined ? {} : { props: properties(cls, input.props) }),
39
79
  });
40
- return Object.freeze({ ...created, class: input.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(input.class),
46
- props: delta(input.class, input.props),
86
+ class: classKey(cls),
87
+ props: delta(cls, input.props),
47
88
  });
48
89
  return rich;
49
90
  },
50
91
  transition(input) {
51
- authorStateTransition(core, input);
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({ node: existingEndpoint(input.node), class: classKey(input.class) });
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(input.class),
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(input.class, input.props) }),
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(input.class),
120
+ class: classKey(cls),
73
121
  };
74
- const patch = input.props === undefined ? undefined : delta(input.class, input.props);
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
+ }