@astrale-os/sdk 0.6.0-beta.36 → 0.6.0-beta.38

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 (82) hide show
  1. package/dist/application/action/context.d.ts +14 -11
  2. package/dist/application/action/index.d.ts +1 -1
  3. package/dist/application/dependency/invoker.d.ts +10 -4
  4. package/dist/application/integration/define.d.ts +8 -1
  5. package/dist/application/integration/index.d.ts +2 -2
  6. package/dist/application/integration/integration.d.ts +13 -1
  7. package/dist/application/integration/operation.d.ts +14 -1
  8. package/dist/application/mutation/execution/execute.js +1 -1
  9. package/dist/application/mutation/failure.d.ts +6 -0
  10. package/dist/application/mutation/failure.js +24 -18
  11. package/dist/application/query/collection/union.js +11 -4
  12. package/dist/application/query/conformance.d.ts +15 -0
  13. package/dist/application/query/conformance.js +46 -0
  14. package/dist/application/query/define.js +4 -3
  15. package/dist/application/query/execution/execute.d.ts +0 -1
  16. package/dist/application/query/execution/execute.js +7 -1
  17. package/dist/application/query/failure.d.ts +8 -0
  18. package/dist/application/query/failure.js +8 -0
  19. package/dist/application/query/index.d.ts +1 -1
  20. package/dist/application/query/index.js +1 -1
  21. package/dist/application/workflow/authoring.d.ts +1 -1
  22. package/dist/application/workflow/context.d.ts +1 -1
  23. package/dist/application/workflow/durable.d.ts +55 -20
  24. package/dist/application/workflow/index.d.ts +1 -1
  25. package/dist/execution/identity/authentication.js +12 -6
  26. package/dist/execution/identity/callback.d.ts +7 -8
  27. package/dist/execution/identity/callback.js +17 -14
  28. package/dist/execution/invocation/capabilities/bind.d.ts +11 -0
  29. package/dist/execution/invocation/capabilities/bind.js +25 -0
  30. package/dist/execution/invocation/capabilities/index.d.ts +1 -0
  31. package/dist/execution/invocation/capabilities/index.js +1 -0
  32. package/dist/execution/invocation/dispatch/context.js +2 -21
  33. package/dist/execution/invocation/integration/authentication.d.ts +3 -3
  34. package/dist/execution/invocation/integration/authentication.js +7 -7
  35. package/dist/execution/invocation/integration/call.js +2 -2
  36. package/dist/execution/invocation/integration/index.d.ts +1 -0
  37. package/dist/execution/invocation/integration/index.js +1 -0
  38. package/dist/execution/invocation/invocation.js +2 -2
  39. package/dist/execution/invocation/lifecycle/active.d.ts +8 -2
  40. package/dist/execution/invocation/lifecycle/active.js +3 -3
  41. package/dist/execution/invocation/lifecycle/index.d.ts +1 -1
  42. package/dist/execution/service/create.js +1 -0
  43. package/dist/execution/workflows/engine.d.ts +12 -2
  44. package/dist/execution/workflows/index.d.ts +1 -1
  45. package/dist/execution/workflows/integrations.d.ts +13 -0
  46. package/dist/execution/workflows/integrations.js +25 -0
  47. package/dist/execution/workflows/options.js +3 -1
  48. package/dist/execution/workflows/receiver.d.ts +3 -0
  49. package/dist/execution/workflows/receiver.js +7 -2
  50. package/dist/execution/workflows/run.d.ts +3 -4
  51. package/dist/execution/workflows/run.js +32 -60
  52. package/dist/execution/workflows/steps.d.ts +42 -0
  53. package/dist/execution/workflows/steps.js +67 -0
  54. package/dist/testing/durable/engine.d.ts +111 -0
  55. package/dist/testing/durable/engine.js +540 -0
  56. package/dist/testing/durable/index.d.ts +3 -0
  57. package/dist/testing/durable/index.js +1 -0
  58. package/dist/testing/durable/steps.d.ts +30 -0
  59. package/dist/testing/durable/steps.js +40 -0
  60. package/dist/testing/index.d.ts +6 -1
  61. package/dist/testing/index.js +5 -1
  62. package/dist/tooling/cli/dotenv.d.ts +3 -3
  63. package/dist/tooling/cli/dotenv.js +3 -3
  64. package/dist/tooling/cli/log.d.ts +0 -2
  65. package/dist/tooling/cli/log.js +0 -2
  66. package/dist/tooling/linter/adapters/typescript/imports.d.ts +0 -1
  67. package/dist/tooling/linter/adapters/typescript/imports.js +1 -5
  68. package/dist/tooling/linter/adapters/typescript/index.d.ts +1 -1
  69. package/dist/tooling/linter/adapters/typescript/index.js +1 -1
  70. package/dist/tooling/linter/adapters/typescript/syntax.d.ts +3 -3
  71. package/dist/tooling/linter/adapters/typescript/syntax.js +13 -25
  72. package/dist/tooling/linter/implementations/source/callable-requirements.d.ts +0 -1
  73. package/dist/tooling/linter/implementations/source/callable-requirements.js +1 -6
  74. package/dist/tooling/linter/implementations/source/dependencies.js +1 -62
  75. package/dist/tooling/linter/implementations/source/mutations.js +1 -12
  76. package/dist/tooling/linter/implementations/source/queries.js +1 -17
  77. package/dist/tooling/linter/implementations/source/shared.d.ts +7 -4
  78. package/dist/tooling/linter/implementations/source/shared.js +93 -46
  79. package/dist/tooling/linter/implementations/source/workflows.js +406 -16
  80. package/dist/tooling/linter/policy/generated.js +2 -2
  81. package/dist/tooling/linter/requirements/registry.js +3 -1
  82. package/package.json +6 -6
@@ -50,20 +50,23 @@ export interface DomainGraphExecutor<DomainValue extends Domain = Domain> {
50
50
  /** Whose authority one Kernel call exercises: the incoming caller's or the installed Domain's. */
51
51
  export type AuthorityMode = 'caller' | 'self';
52
52
  /**
53
- * One value per selectable authority. `self` exists in every invocation; `caller` exists only when
54
- * the call carried a credential, so it is absent from a call that carried none.
53
+ * One value per authority the code may act under. `self` exists in every invocation; `caller`
54
+ * exists only when the call carried a credential, so it is absent from a call that carried none.
55
55
  */
56
- export type AuthoritySelection<Value, Selectable extends AuthorityMode = AuthorityMode> = Readonly<Record<Selectable, Value> & {
57
- readonly [Absent in Exclude<AuthorityMode, Selectable>]?: never;
56
+ export type AuthoritySelection<Value, Authority extends AuthorityMode = AuthorityMode> = Readonly<Record<Authority, Value> & {
57
+ readonly [Absent in Exclude<AuthorityMode, Authority>]?: never;
58
58
  }>;
59
- export type DomainGraphExecutors<DomainValue extends Domain = Domain, Selectable extends AuthorityMode = AuthorityMode> = Readonly<DomainGraphExecutor<DomainValue> & AuthoritySelection<DomainGraphExecutor<DomainValue>, Selectable>>;
59
+ export type DomainGraphExecutors<DomainValue extends Domain = Domain, Authority extends AuthorityMode = AuthorityMode> = Readonly<DomainGraphExecutor<DomainValue> & AuthoritySelection<DomainGraphExecutor<DomainValue>, Authority>>;
60
60
  /** Self-default Kernel session with one level of explicit authority selection. */
61
- export type DomainKernelSessions<Selectable extends AuthorityMode = AuthorityMode> = Readonly<BoundClientSession & AuthoritySelection<BoundClientSession, Selectable>>;
62
- /** What a handler holds toward the Kernel: the installed Domain authority by default. */
63
- interface DomainCapabilities<DomainValue extends Domain, Selectable extends AuthorityMode> {
64
- readonly dependencies: HandlerDependencies<DomainValue, Selectable>;
65
- readonly kernel: DomainKernelSessions<Selectable>;
66
- readonly graph: DomainGraphExecutors<DomainValue, Selectable>;
61
+ export type DomainKernelSessions<Authority extends AuthorityMode = AuthorityMode> = Readonly<BoundClientSession & AuthoritySelection<BoundClientSession, Authority>>;
62
+ /**
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.
65
+ */
66
+ export interface DomainCapabilities<DomainValue extends Domain = Domain, Authority extends AuthorityMode = AuthorityMode> {
67
+ readonly dependencies: HandlerDependencies<DomainValue, Authority>;
68
+ readonly kernel: DomainKernelSessions<Authority>;
69
+ readonly graph: DomainGraphExecutors<DomainValue, Authority>;
67
70
  /** Convenience executor using the installed Domain authority (`graph.self`). */
68
71
  readonly query: QueryExecutor<DomainValue>;
69
72
  /** Convenience executor using the installed Domain authority (`graph.self`). */
@@ -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, DomainGraphExecutor, DomainGraphExecutors, DomainKernelSessions, ResolvedCallable, } from './context.js';
3
+ export type { ActionContext, ActionBinaryInput, ActionExecution, AnonymousCaller, AuthenticatedCaller, AuthorityMode, AuthoritySelection, Caller, DomainCapabilities, DomainGraphExecutor, DomainGraphExecutors, DomainKernelSessions, 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';
@@ -1,5 +1,5 @@
1
1
  import type { Binary, Input } from '../../platform/client/index.js';
2
- import type { SessionRequestOptions, SessionRequestOptionsWithBody } from '../../platform/client/session/index.js';
2
+ import type { ExecutionRef, SessionRequestOptions, SessionRequestOptionsWithBody } from '../../platform/client/session/index.js';
3
3
  import type { NodeId } from '../../platform/graph/node/index.js';
4
4
  import type { CallableInputOf, CallableInputModeOf, CallableOutputOf, CallableResultOf, Domain, ResolvedFunction, ResolvedMethod, KernelSchema, schema } from '../../platform/schema/index.js';
5
5
  import type { AuthorityMode, AuthoritySelection, LocalCallable } from '../action/index.js';
@@ -11,7 +11,13 @@ type WithInputMode<C, Mode extends 'value' | 'binary'> = C extends unknown ? Cal
11
11
  type ValueFunctionOrStaticMethodOf<D extends Domain> = WithInputMode<FunctionOrStaticMethodOf<D>, 'value'>;
12
12
  type BinaryFunctionOf<D extends Domain> = WithInputMode<Extract<LocalCallable<D>, ResolvedFunction>, 'binary'>;
13
13
  type DependencyInputOf<C> = C extends unknown ? unknown extends CallableInputOf<C> ? Input : [CallableInputOf<C>] extends [void] ? undefined : CallableInputOf<C> : never;
14
- type DependencyResultOf<C> = unknown extends CallableOutputOf<C> ? unknown : CallableResultOf<C, Binary>;
14
+ type HandlerResultOf<C> = unknown extends CallableOutputOf<C> ? unknown : CallableResultOf<C, Binary>;
15
+ /** A durable callable answers with its Execution, as the Kernel Client's typed reference does. */
16
+ type DependencyResultOf<C> = C extends {
17
+ readonly execution: {
18
+ readonly durable: true;
19
+ };
20
+ } ? ExecutionRef<HandlerResultOf<C>> : HandlerResultOf<C>;
15
21
  /** One declared dependency, bound to one explicitly selected invocation authority. */
16
22
  export interface DependencyInvoker<D extends Domain> {
17
23
  readonly invoke: {
@@ -25,7 +31,7 @@ export interface DependencyInvoker<D extends Domain> {
25
31
  * self is the executing Domain; caller forwards the incoming caller Identity, and is absent from a
26
32
  * call that carried no credential. The canonical Kernel is exposed through context.kernel.
27
33
  */
28
- export type HandlerDependencies<D extends Domain, Selectable extends AuthorityMode = AuthorityMode> = Readonly<{
29
- [Alias in keyof D['dependencies'] as D['dependencies'][Alias] extends schema.DomainOf<KernelSchema> ? never : Alias]: AuthoritySelection<DependencyInvoker<Extract<D['dependencies'][Alias], Domain>>, Selectable>;
34
+ export type HandlerDependencies<D extends Domain, Authority extends AuthorityMode = AuthorityMode> = Readonly<{
35
+ [Alias in keyof D['dependencies'] as D['dependencies'][Alias] extends schema.DomainOf<KernelSchema> ? never : Alias]: AuthoritySelection<DependencyInvoker<Extract<D['dependencies'][Alias], Domain>>, Authority>;
30
36
  }>;
31
37
  export {};
@@ -1,5 +1,5 @@
1
1
  import type { IntegrationDefinition, Integrations } from './integration.js';
2
- import type { IntegrationOperation, IntegrationOperations, IntegrationReplay } from './operation.js';
2
+ import type { IntegrationOperation, IntegrationOperations, IntegrationReplay, ReplayableIntegrationDecision } from './operation.js';
3
3
  import type { Provider, ProviderImplementation } from './provider.js';
4
4
  type RuntimeDefinition = IntegrationDefinition<string, IntegrationOperations>;
5
5
  type ProviderOperations = Readonly<Record<string, (...input: readonly unknown[]) => unknown>>;
@@ -12,6 +12,13 @@ export interface DefineIntegration {
12
12
  readonly id: Id;
13
13
  readonly operations: Operations;
14
14
  }): IntegrationDefinition<Id, Operations>;
15
+ /**
16
+ * One operation and what another attempt of it may do. A decision that is never `unsafe` keeps
17
+ * the operation available to code that may run more than once, such as a durable Step.
18
+ */
19
+ operation<Input = void, Output = void>(input: {
20
+ readonly replay: ReplayableIntegrationDecision | ((input: Input) => ReplayableIntegrationDecision);
21
+ }): IntegrationOperation<Input, Output, ReplayableIntegrationDecision['kind']>;
15
22
  operation<Input = void, Output = void>(input: {
16
23
  readonly replay: IntegrationReplay<Input>;
17
24
  }): IntegrationOperation<Input, Output>;
@@ -2,7 +2,7 @@ export { createIntegrationClients, bindProviders } from './client.js';
2
2
  export type { IntegrationAttempt, IntegrationCallCoordinates, IntegrationClientFactory, IntegrationInvocationOwner, } from './client.js';
3
3
  export { admitIntegrations, defineIntegration, defineProvider, requireIntegration, requireProvider, } from './define.js';
4
4
  export type { DefineIntegration, ProviderBinding } from './define.js';
5
- export type { IntegrationClient, IntegrationClients, IntegrationDefinition, Integrations, } from './integration.js';
6
- export type { IntegrationExecution, IntegrationInput, IntegrationOperation, IntegrationOperations, IntegrationOutput, IntegrationReplay, IntegrationReplayDecision, } from './operation.js';
5
+ export type { IntegrationClient, IntegrationClients, IntegrationDefinition, Integrations, ReplayableIntegrationClient, ReplayableIntegrationClients, } from './integration.js';
6
+ export type { IntegrationExecution, IntegrationInput, IntegrationOperation, IntegrationOperations, IntegrationOutput, IntegrationReplay, IntegrationReplayDecision, IntegrationReplayKind, IntegrationReplayOf, ReplayableIntegrationDecision, } from './operation.js';
7
7
  export type { Provider, ProviderImplementation, Providers } from './provider.js';
8
8
  export type { IntegrationAuthentication, IntegrationMintInput } from './authentication.js';
@@ -1,4 +1,4 @@
1
- import type { IntegrationOperation, IntegrationOperations } from './operation.js';
1
+ import type { IntegrationOperation, IntegrationOperations, IntegrationReplayOf } from './operation.js';
2
2
  declare const INTEGRATION_DEFINITION: unique symbol;
3
3
  export interface IntegrationDefinition<Id extends string = string, Operations extends IntegrationOperations = IntegrationOperations> {
4
4
  readonly kind: 'integration';
@@ -16,4 +16,16 @@ export type IntegrationClient<Definition extends IntegrationDefinition> = Readon
16
16
  export type IntegrationClients<Definitions extends Integrations> = Readonly<{
17
17
  [Name in keyof Definitions]: IntegrationClient<Definitions[Name]>;
18
18
  }>;
19
+ type ReplayableOperationName<Definition extends IntegrationDefinition> = {
20
+ [Name in keyof Definition['operations']]: 'unsafe' extends IntegrationReplayOf<Definition['operations'][Name]> ? never : Name;
21
+ }[keyof Definition['operations']];
22
+ /** The operations of one Integration that may be attempted again: each is `safe` or keyed. */
23
+ export type ReplayableIntegrationClient<Definition extends IntegrationDefinition> = Pick<IntegrationClient<Definition>, ReplayableOperationName<Definition>>;
24
+ /**
25
+ * Integration clients of code that may run more than once, such as a durable Step. An operation
26
+ * whose replay decision may be `unsafe` is absent: another attempt could repeat its effect.
27
+ */
28
+ export type ReplayableIntegrationClients<Definitions extends Integrations> = Readonly<{
29
+ [Name in keyof Definitions]: ReplayableIntegrationClient<Definitions[Name]>;
30
+ }>;
19
31
  export {};
@@ -1,6 +1,7 @@
1
1
  import type { BoundClientSession } from '../../platform/client/session/index.js';
2
2
  import type { IntegrationAuthentication } from './authentication.js';
3
3
  declare const INTEGRATION_OPERATION: unique symbol;
4
+ declare const INTEGRATION_REPLAY: unique symbol;
4
5
  export type IntegrationReplayDecision = {
5
6
  readonly kind: 'safe';
6
7
  } | {
@@ -13,6 +14,12 @@ export type IntegrationReplayDecision = {
13
14
  } | {
14
15
  readonly kind: 'unsafe';
15
16
  };
17
+ /** What another attempt of an operation may do: anything, once per key, or nothing. */
18
+ export type IntegrationReplayKind = IntegrationReplayDecision['kind'];
19
+ /** A decision that allows another attempt of the operation. */
20
+ export type ReplayableIntegrationDecision = Exclude<IntegrationReplayDecision, {
21
+ readonly kind: 'unsafe';
22
+ }>;
16
23
  export type IntegrationReplay<Input> = IntegrationReplayDecision | ((input: Input) => IntegrationReplayDecision);
17
24
  export interface IntegrationExecution {
18
25
  readonly callId: string;
@@ -24,9 +31,11 @@ export interface IntegrationExecution {
24
31
  /** Domain-self transport authentication; absent only outside a Kernel invocation. */
25
32
  readonly auth?: IntegrationAuthentication;
26
33
  }
27
- export interface IntegrationOperation<Input = unknown, Output = unknown> {
34
+ export interface IntegrationOperation<Input = unknown, Output = unknown, Replay extends IntegrationReplayKind = IntegrationReplayKind> {
28
35
  readonly replay: IntegrationReplay<Input>;
29
36
  readonly [INTEGRATION_OPERATION]: (input: Input) => Output;
37
+ /** Every kind its replay decision may take, as its definition proves them. */
38
+ readonly [INTEGRATION_REPLAY]: Replay;
30
39
  }
31
40
  export type IntegrationOperations = Readonly<Record<string, IntegrationOperation<never, unknown>>>;
32
41
  export type IntegrationInput<Operation> = Operation extends {
@@ -35,4 +44,8 @@ export type IntegrationInput<Operation> = Operation extends {
35
44
  export type IntegrationOutput<Operation> = Operation extends {
36
45
  readonly [INTEGRATION_OPERATION]: (input: never) => infer Output;
37
46
  } ? Output : never;
47
+ /** The replay kinds an operation's definition proves; every kind when it proves none. */
48
+ export type IntegrationReplayOf<Operation> = Operation extends {
49
+ readonly [INTEGRATION_REPLAY]: infer Replay;
50
+ } ? Replay : IntegrationReplayKind;
38
51
  export {};
@@ -10,7 +10,7 @@ export async function executeMutation(client, domain, definition, input) {
10
10
  const operation = realizeMutation(definition, domain);
11
11
  try {
12
12
  const ast = MutationAST.build((builder) => operation.build(input, richMutationBuilder(builder)));
13
- return projectMutation(operation, await client.mutate(ast, { expected: domain.closure }), input);
13
+ return projectMutation(operation, await client.mutate(ast), input);
14
14
  }
15
15
  catch (cause) {
16
16
  const expected = mutationFailure(cause);
@@ -1,6 +1,12 @@
1
1
  export interface KernelMutationFailure extends Error {
2
2
  readonly code: `MUTATION_${string}`;
3
3
  readonly outcomeKnown: boolean;
4
+ /**
5
+ * The post-state rule a `MUTATION_POST_STATE_INVALID` refusal names, such as `duplicate-key`
6
+ * for a value a Class key already holds. Absent when the Kernel names none, and for every other
7
+ * refusal.
8
+ */
9
+ readonly rule?: string;
4
10
  }
5
11
  export interface StateTransitionFailure extends Error {
6
12
  readonly code: 'STATE_TRANSITION_ILLEGAL';
@@ -13,9 +13,10 @@ export function stateTransitionFailure(from, event) {
13
13
  export function mutationFailure(input) {
14
14
  if (!(input instanceof Error))
15
15
  return undefined;
16
- const code = mutationCode(input);
17
- if (code === undefined)
16
+ const reason = mutationReason(input);
17
+ if (reason === undefined)
18
18
  return undefined;
19
+ const { code } = reason;
19
20
  const explicit = input.outcomeKnown;
20
21
  if (explicit !== undefined && typeof explicit !== 'boolean')
21
22
  return undefined;
@@ -28,30 +29,35 @@ export function mutationFailure(input) {
28
29
  return undefined;
29
30
  return input;
30
31
  }
32
+ const rule = code === 'MUTATION_POST_STATE_INVALID' ? reason.rule : undefined;
31
33
  return Object.assign(new Error(input.message, { cause: input }), {
32
34
  code,
33
35
  outcomeKnown: explicit !== false && !UNKNOWN_OUTCOME_CODES.has(code),
36
+ ...(rule === undefined ? {} : { rule }),
34
37
  });
35
38
  }
36
- function mutationCode(input) {
39
+ /**
40
+ * The Mutation refusal an error carries, on itself, as its Kernel reason, or as its protocol data:
41
+ * its code and, from the same carrier's details, the post-state rule it names.
42
+ */
43
+ function mutationReason(input) {
37
44
  const value = input;
38
45
  if (value.code === 'STATE_TRANSITION_ILLEGAL')
39
- return value.code;
40
- for (const candidate of [
41
- value.code,
42
- directOrNestedCode(value.reason),
43
- directOrNestedCode(value.data),
44
- ]) {
45
- if (typeof candidate === 'string' && candidate.startsWith('MUTATION_')) {
46
- return candidate;
47
- }
46
+ return { code: value.code };
47
+ for (const carrier of [value, value.reason, value.data]) {
48
+ const code = typeof carrier === 'string' ? carrier : field(carrier, 'code');
49
+ if (typeof code !== 'string' || !code.startsWith('MUTATION_'))
50
+ continue;
51
+ const rule = field(field(carrier, 'details'), 'rule');
52
+ return {
53
+ code: code,
54
+ ...(typeof rule === 'string' ? { rule } : {}),
55
+ };
48
56
  }
49
57
  return undefined;
50
58
  }
51
- function directOrNestedCode(input) {
52
- return typeof input === 'string'
53
- ? input
54
- : input !== null && typeof input === 'object' && !Array.isArray(input)
55
- ? Reflect.get(input, 'code')
56
- : undefined;
59
+ function field(input, key) {
60
+ return input !== null && typeof input === 'object' && !Array.isArray(input)
61
+ ? Reflect.get(input, key)
62
+ : undefined;
57
63
  }
@@ -1,5 +1,5 @@
1
+ import { definedClass } from '@astrale-os/kernel-client/graph';
1
2
  import { Query, QueryAST as QueryDocument } from '@astrale-os/kernel-core/graph/query';
2
- import { ClassKey } from '@astrale-os/kernel-dsl/v1/addressing';
3
3
  import { QueryCompositionError } from '../composite/errors.js';
4
4
  import { collectionIdentityOf } from './identity.js';
5
5
  import { COLLECTION_QUERY_LIMITS } from './limits.js';
@@ -80,8 +80,13 @@ export function partitionCollectionUnion(domain, compiled, values, maximumLogica
80
80
  if (identities.has(identity))
81
81
  invalid(`Collection union returned duplicate identity ${identity}.`);
82
82
  identities.add(identity);
83
- const names = memberships.get(value.class) ?? memberNames(domain, compiled, value);
83
+ const names = memberships.has(value.class)
84
+ ? memberships.get(value.class)
85
+ : memberNames(domain, compiled, value);
84
86
  memberships.set(value.class, names);
87
+ // A Class added after this client was built belongs to no member it can name.
88
+ if (names === undefined)
89
+ continue;
85
90
  if (names.length === 0) {
86
91
  invalid(`Collection union returned ${value.class} outside every member extent.`);
87
92
  }
@@ -105,9 +110,11 @@ function realizeMember(definition, domain, outputPath, name) {
105
110
  }
106
111
  }
107
112
  function memberNames(domain, compiled, value) {
108
- const exact = domain.definition(ClassKey.ref(ClassKey(value.class)));
113
+ const exact = definedClass(domain, value.class);
114
+ if (exact === undefined)
115
+ return undefined;
109
116
  const kind = valueKind(value);
110
- if (exact?.ref.kind !== 'class' || exact.kind !== kind || exact.abstract) {
117
+ if (exact.kind !== kind || exact.abstract) {
111
118
  invalid(`Collection union returned inactive ${kind} Class ${value.class}.`);
112
119
  }
113
120
  const names = compiled.members.flatMap((member) => {
@@ -0,0 +1,15 @@
1
+ import type { QueryResult } from '@astrale-os/kernel-core/graph/query';
2
+ import type { Edge } from '../../platform/graph/edge/index.js';
3
+ import type { Node } from '../../platform/graph/node/index.js';
4
+ import type { Domain } from '../../platform/schema/index.js';
5
+ /**
6
+ * The members of `values` whose Class `domain` defines, in order. The Kernel answers in its active
7
+ * Schema, so a result may hold values of a Class added after the client was built.
8
+ */
9
+ export declare function definedValues<Value extends Node | Edge>(domain: Domain, values: readonly Value[]): readonly Value[];
10
+ /**
11
+ * Check every value of `result` against the Domain this client was built from, by the Kernel
12
+ * Client's rule: a value fails only when a Property its Class defines holds an invalid value or a
13
+ * required one is absent. Properties and Classes the client does not know pass.
14
+ */
15
+ export declare function requireConformance(domain: Domain, result: QueryResult): void;
@@ -0,0 +1,46 @@
1
+ import { definedClass, validateDefinedProperties } from '@astrale-os/kernel-client/graph';
2
+ import { collectionIdentityOf } from './collection/identity.js';
3
+ import { QueryValueMismatchError } from './failure.js';
4
+ /**
5
+ * The members of `values` whose Class `domain` defines, in order. The Kernel answers in its active
6
+ * Schema, so a result may hold values of a Class added after the client was built.
7
+ */
8
+ export function definedValues(domain, values) {
9
+ const defined = values.filter((value) => definedClass(domain, value.class) !== undefined);
10
+ return defined.length === values.length ? values : Object.freeze(defined);
11
+ }
12
+ /**
13
+ * Check every value of `result` against the Domain this client was built from, by the Kernel
14
+ * Client's rule: a value fails only when a Property its Class defines holds an invalid value or a
15
+ * required one is absent. Properties and Classes the client does not know pass.
16
+ */
17
+ export function requireConformance(domain, result) {
18
+ for (const value of valuesOf(result)) {
19
+ const exact = definedClass(domain, value.class);
20
+ if (exact === undefined)
21
+ continue;
22
+ const [diagnostic] = validateDefinedProperties(exact, value.props);
23
+ if (diagnostic !== undefined) {
24
+ throw new QueryValueMismatchError(`${collectionIdentityOf(value)} at ${diagnostic.pointer}: ${diagnostic.message}`);
25
+ }
26
+ }
27
+ }
28
+ function* valuesOf(result) {
29
+ if (result.kind === 'graph') {
30
+ yield* result.graph.nodes;
31
+ yield* result.graph.edges;
32
+ return;
33
+ }
34
+ if (result.kind === 'nodes') {
35
+ for (const item of result.nodes)
36
+ if (item.kind === 'value')
37
+ yield item.value;
38
+ return;
39
+ }
40
+ for (const item of result.edges) {
41
+ for (const component of [item.edge, item.source, item.target]) {
42
+ if (component.kind === 'value')
43
+ yield component.value;
44
+ }
45
+ }
46
+ }
@@ -3,6 +3,7 @@ import { isDomain } from '@astrale-os/kernel-dsl/v1/domain';
3
3
  import { requireUniqueCollectionValues } from './collection/identity.js';
4
4
  import { COLLECTION_QUERY_LIMITS } from './collection/limits.js';
5
5
  import { realizeCollectionQuery, registerCollectionQuery, } from './collection/metadata.js';
6
+ import { definedValues } from './conformance.js';
6
7
  import { queryResult } from './single/index.js';
7
8
  const DEFAULT_PAGE_SIZE = 256;
8
9
  const MAXIMUM_PAGES = 1_024;
@@ -27,7 +28,7 @@ export function defineCollectionQuery() {
27
28
  kind: 'nodes',
28
29
  projection: { kind: 'value' },
29
30
  }),
30
- project: (pages) => requireUniqueCollectionValues(queryResult.completeNodeValues(pages, specification.id), `Collection Query ${specification.id}`),
31
+ project: (pages) => definedValues(domain, requireUniqueCollectionValues(queryResult.completeNodeValues(pages, specification.id), `Collection Query ${specification.id}`)),
31
32
  };
32
33
  }
33
34
  return {
@@ -38,12 +39,12 @@ export function defineCollectionQuery() {
38
39
  kind: 'edges',
39
40
  projection: { edge: 'value', source: 'reference', target: 'reference' },
40
41
  }),
41
- project: (pages) => requireUniqueCollectionValues(Object.freeze(pages.flatMap((page) => queryResult.edgeItems(page, specification.id).map((item) => {
42
+ project: (pages) => definedValues(domain, requireUniqueCollectionValues(Object.freeze(pages.flatMap((page) => queryResult.edgeItems(page, specification.id).map((item) => {
42
43
  if (item.edge.kind !== 'value') {
43
44
  throw new TypeError(`Collection Query ${specification.id} must project Edge values.`);
44
45
  }
45
46
  return item.edge.value;
46
- }))), `Collection Query ${specification.id}`),
47
+ }))), `Collection Query ${specification.id}`)),
47
48
  };
48
49
  });
49
50
  registerCollectionQuery(definition, project);
@@ -6,7 +6,6 @@ import type { QueryDefinition } from '../query.js';
6
6
  export interface QueryClient {
7
7
  query<Ast extends QueryAST>(ast: Ast, options: {
8
8
  readonly page: QueryPageRequest;
9
- readonly expected?: Domain['closure'];
10
9
  }): Promise<QueryResponse<QueryResultFor<Ast>>>;
11
10
  }
12
11
  export declare function executeQuery<DomainValue extends Domain, Output>(client: QueryClient, domain: DomainValue, query: QueryDefinition<DomainValue, void, Output>): Promise<Output>;
@@ -1,6 +1,7 @@
1
1
  import { COLLECTION_QUERY_LIMITS } from '../collection/limits.js';
2
2
  import { compileCollectionUnion, partitionCollectionUnion } from '../collection/union.js';
3
3
  import { createQueryComposition, executeQueryPlan } from '../composite/index.js';
4
+ import { requireConformance } from '../conformance.js';
4
5
  import { realizeQuery } from '../define.js';
5
6
  import { queryFailure } from '../failure.js';
6
7
  import { completeSelection, queryResult } from '../single/index.js';
@@ -9,7 +10,12 @@ export async function executeQuery(client, domain, query, input) {
9
10
  if (client === null || typeof client !== 'object' || typeof client.query !== 'function') {
10
11
  throw new TypeError('Query execution requires a Client query capability.');
11
12
  }
12
- const execute = (ast, options) => client.query(ast, { ...options, expected: domain.closure });
13
+ // The Kernel answers in its active Schema; each value is checked against this client's Domain.
14
+ const execute = async (ast, options) => {
15
+ const response = await client.query(ast, options);
16
+ requireConformance(domain, response.result);
17
+ return response;
18
+ };
13
19
  const operation = realizeQuery(query, domain);
14
20
  try {
15
21
  if (operation.kind === 'single') {
@@ -7,4 +7,12 @@ export declare class QueryPaginationError extends Error implements QueryFailure
7
7
  readonly name = "QueryPaginationError";
8
8
  constructor(code: QueryPaginationErrorCode, message: string);
9
9
  }
10
+ /**
11
+ * A value the Kernel returned does not have the shape its Class has in the Domain this client was
12
+ * built from: one of its Properties changed type, or a Property the client requires is absent.
13
+ */
14
+ export declare class QueryValueMismatchError extends Error implements QueryFailure {
15
+ readonly name = "QueryValueMismatchError";
16
+ readonly code = "QUERY_VALUE_MISMATCH";
17
+ }
10
18
  export declare function queryFailure(input: unknown): QueryFailure | undefined;
@@ -6,6 +6,14 @@ export class QueryPaginationError extends Error {
6
6
  this.code = code;
7
7
  }
8
8
  }
9
+ /**
10
+ * A value the Kernel returned does not have the shape its Class has in the Domain this client was
11
+ * built from: one of its Properties changed type, or a Property the client requires is absent.
12
+ */
13
+ export class QueryValueMismatchError extends Error {
14
+ name = 'QueryValueMismatchError';
15
+ code = 'QUERY_VALUE_MISMATCH';
16
+ }
9
17
  export function queryFailure(input) {
10
18
  if (!(input instanceof Error))
11
19
  return undefined;
@@ -1,7 +1,7 @@
1
1
  export { defineCollectionQuery, defineCompositeQuery, defineQuery, realizeQuery } from './define.js';
2
2
  export type { CollectionQuerySpec } from './collection/metadata.js';
3
3
  export { executeQuery, type QueryClient, type QueryExecutor } from './execution/index.js';
4
- export { QueryPaginationError, type QueryFailure, type QueryPaginationErrorCode, } from './failure.js';
4
+ export { QueryPaginationError, QueryValueMismatchError, type QueryFailure, type QueryPaginationErrorCode, } from './failure.js';
5
5
  export type { CollectionQueryDefinition, CollectionValue, CompositeQueryDefinition, QueryAstOf, QueryDefinition, QueryInputOf, QueryOutputOf, SingleQueryDefinition, } from './query.js';
6
6
  export { queryResult } from './single/index.js';
7
7
  export type { CompleteQueryProjectionContext, PaginatedQuery, ProjectedQuery, QueryPages, QueryPagination, QueryProjectionContext, RawQuery, SingleQuery, } from './single/index.js';
@@ -1,6 +1,6 @@
1
1
  export { defineCollectionQuery, defineCompositeQuery, defineQuery, realizeQuery } from './define.js';
2
2
  export { executeQuery } from './execution/index.js';
3
- export { QueryPaginationError, } from './failure.js';
3
+ export { QueryPaginationError, QueryValueMismatchError, } from './failure.js';
4
4
  export { queryResult } from './single/index.js';
5
5
  /** Canonical Query language and authoring values accept DSL ResolvedClass directly. */
6
6
  export * from '@astrale-os/kernel-core/graph/query';
@@ -1,5 +1,5 @@
1
1
  export { defineWorkflow } from './define.js';
2
2
  export type { InlineWorkflowContext, WorkflowContext } from './context.js';
3
- export type { DurableCallable, DurableExecution, DurableStep, DurableStepAuthority, DurableWorkflowContext, } from './durable.js';
3
+ export type { DurableCallable, DurableExecution, DurableStep, DurableStepAuthority, DurableWorkflowContext, SignalOutcome, } from './durable.js';
4
4
  export type { Workflow, WorkflowAddress, WorkflowDefinition, WorkflowOptions } from './workflow.js';
5
5
  export type { DeclaredErrorDefinition, DeclaredErrorFactory, DeclaredErrorFamily, DeclaredErrorOptions, DeclaredErrors, } from '../error/index.js';
@@ -12,4 +12,4 @@ export type InlineWorkflowContext<Callable extends ResolvedCallable, DomainValue
12
12
  * Exact Workflow context for one callable: engine-journaled when its Schema declares
13
13
  * `execution: { durable: true }`, otherwise run inside its invocation.
14
14
  */
15
- export type WorkflowContext<Callable extends ResolvedCallable, DomainValue extends Domain = Domain, Definitions extends Integrations = Readonly<Record<never, never>>, Errors extends DeclaredErrors = NoDeclaredErrors> = Callable extends DurableCallable ? DurableWorkflowContext<Callable, DomainValue, Errors> : InlineWorkflowContext<Callable, DomainValue, Definitions, Errors>;
15
+ export type WorkflowContext<Callable extends ResolvedCallable, DomainValue extends Domain = Domain, Definitions extends Integrations = Readonly<Record<never, never>>, Errors extends DeclaredErrors = NoDeclaredErrors> = Callable extends DurableCallable ? DurableWorkflowContext<Callable, DomainValue, Definitions, Errors> : InlineWorkflowContext<Callable, DomainValue, Definitions, Errors>;
@@ -1,11 +1,14 @@
1
1
  import type { IssuerId } from '../../platform/auth/index.js';
2
- import type { BoundClientSession, ExecutionOutcome, ExecutionRef } from '../../platform/client/session/index.js';
2
+ import type { ExecutionOutcome, ExecutionRef } from '../../platform/client/session/index.js';
3
3
  import type { NodeId } from '../../platform/graph/node/index.js';
4
4
  import type { CallableInputOf, Domain, ResolvedMethod } from '../../platform/schema/index.js';
5
5
  import type { Value } from '../../platform/value/index.js';
6
+ import type { DomainCapabilities, DomainKernelSessions } from '../action/index.js';
7
+ import type { HandlerDependencies } from '../dependency/index.js';
6
8
  import type { DeclaredErrorFactory, DeclaredErrors, NoDeclaredErrors } from '../error/index.js';
9
+ import type { Integrations, ReplayableIntegrationClients } from '../integration/index.js';
7
10
  import type { Mutation, MutationExecutor } from '../mutation/index.js';
8
- import type { QueryExecutor } from '../query/index.js';
11
+ import type { QueryDefinition } from '../query/index.js';
9
12
  /** A callable whose Schema declares `execution: { durable: true }`. */
10
13
  export interface DurableCallable {
11
14
  readonly execution: {
@@ -31,31 +34,46 @@ export interface DurableExecution {
31
34
  readonly receiver?: NodeId;
32
35
  }
33
36
  /**
34
- * The Domain's own authority inside one Step. Kernel access exists only here, so every Kernel call
35
- * belongs to a journaled Step and a replay never repeats it.
37
+ * What one Step holds: the capabilities of an Action, acting as the Domain alone. Kernel access
38
+ * exists only here, so every Kernel call belongs to a journaled Step and a replay never repeats it.
39
+ * Every key below comes from one sequence derived from the Execution, the Step and the position of
40
+ * the call, which restarts with each attempt: a retried Step sends the same keys again.
36
41
  */
37
- export interface DurableStepAuthority<DomainValue extends Domain = Domain> {
42
+ export interface DurableStepAuthority<DomainValue extends Domain = Domain, Definitions extends Integrations = Readonly<Record<never, never>>> extends DomainCapabilities<DomainValue, 'self'> {
38
43
  /**
39
44
  * Kernel Session acting as the Domain. Each `invoke` of a durable callable without an explicit
40
- * `idempotencyKey` carries one derived from the Execution, the Step and its position, so a
41
- * retried Step reaches the same durable child Execution. An `invoke` of any other callable
42
- * carries no key, which the Kernel would refuse, and runs again when the Step is retried.
45
+ * `idempotencyKey` carries the next derived key, so a retried Step reaches the same durable child
46
+ * Execution. An `invoke` of any other callable carries no key, which the Kernel would refuse, and
47
+ * runs again when the Step is retried.
43
48
  */
44
- readonly kernel: BoundClientSession;
45
- readonly query: QueryExecutor<DomainValue>;
49
+ readonly kernel: DomainKernelSessions<'self'>;
50
+ /** Dependency invokers acting as the Domain, keyed like `kernel.invoke`. */
51
+ readonly dependencies: HandlerDependencies<DomainValue, 'self'>;
46
52
  /**
47
- * Each Mutation carries a key derived from the Execution, the Step and its position. The Kernel
48
- * commits a receipt with the write and answers a retried Step with its first result.
53
+ * Each Mutation carries the next derived key. The Kernel commits a receipt with the write and
54
+ * answers a retried Step with its first result.
49
55
  */
50
56
  readonly mutate: MutationExecutor<DomainValue>;
57
+ /**
58
+ * Operations a retried Step may attempt again. A keyed operation of source `execution` receives
59
+ * the next derived key as its idempotency key. An operation whose decision may be `unsafe` is
60
+ * absent, and refused before its Provider runs if its decision is `unsafe` anyway.
61
+ */
62
+ readonly integrations: ReplayableIntegrationClients<Definitions>;
51
63
  }
52
- export interface DurableStep<DomainValue extends Domain = Domain> {
64
+ export interface DurableStep<DomainValue extends Domain = Domain, Definitions extends Integrations = Readonly<Record<never, never>>> {
53
65
  /**
54
66
  * Run `execute` once and journal its portable output; a replay returns the journaled output. A
55
- * Step may run again when its outcome was not journaled: its Mutations and durable calls are
56
- * answered from their keys, and any other effect must be idempotent.
67
+ * Step may run again when its outcome was not journaled: its Mutations, durable calls and keyed
68
+ * Integration operations are answered from their keys, and any other effect must be idempotent.
57
69
  */
58
- run<Output>(id: string, execute: (authority: DurableStepAuthority<DomainValue>) => Output | Promise<Output>): Promise<Output>;
70
+ run<Output>(id: string, execute: (authority: DurableStepAuthority<DomainValue, Definitions>) => Output | Promise<Output>): Promise<Output>;
71
+ /**
72
+ * One journaled Query. A replay returns the result of its first run, so a later Step that depends
73
+ * on it resends the same request whenever it is retried.
74
+ */
75
+ query<Output>(id: string, query: QueryDefinition<DomainValue, void, Output>): Promise<Output>;
76
+ query<Input, Output>(id: string, query: QueryDefinition<DomainValue, Input, Output>, input: Input): Promise<Output>;
59
77
  /**
60
78
  * One journaled Mutation. A Step replayed after its commit receives the result of that commit
61
79
  * from the Kernel's receipt instead of writing again.
@@ -67,18 +85,35 @@ type DurableSelfOf<Callable> = Callable extends ResolvedMethod<unknown, unknown,
67
85
  } : {
68
86
  readonly self?: never;
69
87
  };
88
+ /** What a bounded wait for a signal ended with: the signal's value, or its timeout. */
89
+ export type SignalOutcome<Signal extends Value = Value> = {
90
+ readonly kind: 'signal';
91
+ readonly value: Signal;
92
+ } | {
93
+ readonly kind: 'timeout';
94
+ };
70
95
  /** Context of a durable Workflow: engine-journaled, acting as its Domain. */
71
- export type DurableWorkflowContext<Callable, DomainValue extends Domain = Domain, Errors extends DeclaredErrors = NoDeclaredErrors> = {
96
+ export type DurableWorkflowContext<Callable, DomainValue extends Domain = Domain, Definitions extends Integrations = Readonly<Record<never, never>>, Errors extends DeclaredErrors = NoDeclaredErrors> = {
72
97
  readonly domain: DomainValue;
73
98
  readonly input: CallableInputOf<Callable>;
74
99
  readonly execution: DurableExecution;
75
100
  /** Construct one implementation-declared safe business error for `throw error(...)`. */
76
101
  readonly error: DeclaredErrorFactory<Errors>;
77
- readonly step: DurableStep<DomainValue>;
102
+ readonly step: DurableStep<DomainValue, Definitions>;
78
103
  /** Durable timer kept by the engine, even while the Domain is idle or down. */
79
104
  sleep(durationMs: number): Promise<void>;
80
- /** Resolve once `Execution.signal({ name, value })` delivers `name`, before or during the wait. */
81
- promise<Signal extends Value = Value>(name: string): Promise<Signal>;
105
+ /**
106
+ * Wait for the value `Execution.signal({ name, value })` delivers once under `name`, whether it
107
+ * was sent before or during the wait.
108
+ */
109
+ awaitSignal<Signal extends Value = Value>(name: string): Promise<Signal>;
110
+ /**
111
+ * Wait at most `timeoutMs` for the signal `name`. The engine journals which came first, so a
112
+ * replay answers the same. A signal sent after the timeout is kept for a later wait.
113
+ */
114
+ awaitSignal<Signal extends Value = Value>(name: string, options: {
115
+ readonly timeoutMs: number;
116
+ }): Promise<SignalOutcome<Signal>>;
82
117
  /** Whether `error` is the engine's cooperative cancellation, which the Workflow may compensate. */
83
118
  cancelled(error: unknown): boolean;
84
119
  /** Wait durably for another Execution to end, by journaled polling of its `result`. */