@saga-a2ui/core 0.5.6-rc.1 → 0.7.6-rc.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @saga-a2ui/core
2
2
 
3
- Renderer-independent actions and A2UI v0.9 decoding. Entry points: `/actions`, `/actions/action-button-schema`, `/protocols/a2ui/v0_9`, `/protocols/a2ui/zod3`.
3
+ Renderer-independent actions and A2UI v0.9 decoding. Entry points: `/actions`, `/actions/action-button-schema`, `/delivered-result` (the neutral whole-result type a delivery hands a view), `/host-links`, `/protocols/a2ui/v0_9`, `/protocols/a2ui/zod3`.
4
4
 
5
5
  Requires Node >=22.12.0 and a TypeScript-aware bundler; exports are TypeScript source. Apache-2.0, with dependency licenses retained separately.
6
6
 
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@saga-a2ui/core","private":false,"description":"Renderer-independent SAGA A2UI processing: version-specific decoding, validated message cases and the message session, and the renderer-independent action runtime (local and server actions, prerequisites, bounded hooks, resource ownership) at /actions","license":"Apache-2.0","type":"module","exports":{"./actions":"./src/actions/index.ts","./actions/action-button-schema":"./src/actions/action-button-schema.ts","./protocols/a2ui/v0_9":"./src/protocols/a2ui/v0_9/index.ts","./protocols/a2ui/zod3":"./src/protocols/a2ui/zod3.ts"},"files":["src","!src/**/*.test.ts","!src/**/*.test-support.ts"],"scripts":{"typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.test.json"},"dependencies":{"@a2ui/web_core":"0.13.0","zod3":"npm:zod@3.25.76"},"devDependencies":{"@types/node":"22.20.5","typescript":"^5.9.0","vitest":"^5.0.4"},"version":"0.5.6-rc.1","engines":{"node":">=22.12.0"},"repository":{"type":"git","url":"https://github.com/MachineWisdomAI/saga-a2ui.git","directory":"packages/core"},"publishConfig":{"access":"public","tag":"next"}}
1
+ {"name":"@saga-a2ui/core","private":false,"description":"Renderer-independent SAGA A2UI processing: version-specific decoding, validated message cases and the message session, and the renderer-independent action runtime (local and server actions, prerequisites, bounded hooks, resource ownership) at /actions","license":"Apache-2.0","type":"module","exports":{"./actions":"./src/actions/index.ts","./actions/action-button-schema":"./src/actions/action-button-schema.ts","./delivered-result":"./src/delivered-result.ts","./host-links":"./src/host-links.ts","./protocols/a2ui/v0_9":"./src/protocols/a2ui/v0_9/index.ts","./protocols/a2ui/zod3":"./src/protocols/a2ui/zod3.ts"},"files":["src","!src/**/*.test.ts","!src/**/*.test-support.ts"],"scripts":{"typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.test.json"},"dependencies":{"@a2ui/web_core":"0.13.0","zod3":"npm:zod@3.25.76"},"devDependencies":{"@types/node":"22.20.5","typescript":"^5.9.0","vitest":"^5.0.4"},"version":"0.7.6-rc.1","engines":{"node":">=22.12.0"},"repository":{"type":"git","url":"https://github.com/MachineWisdomAI/saga-a2ui.git","directory":"packages/core"},"publishConfig":{"access":"public","tag":"next"}}
@@ -1,5 +1,10 @@
1
1
  import { planOf, type ActionDefinition } from './definition.js';
2
- import { emitObservation, Invocation, type InvocationOwner, type Seam } from './invocation.js';
2
+ import {
3
+ emitObservation,
4
+ Invocation,
5
+ type InvocationOwner,
6
+ type Seam,
7
+ } from './invocation.js';
3
8
  import { resolveLimits, type Limits } from './limits.js';
4
9
  import { discardOutcome, isThenable } from './thenable.js';
5
10
 
@@ -63,7 +68,10 @@ let bindingCount = 0;
63
68
  export function createBinding<Event>(
64
69
  definition: ActionDefinition<Event>,
65
70
  seam: BindingSeam,
66
- ): { readonly binding: ActionBinding; readonly activate: (event: Event) => void } {
71
+ ): {
72
+ readonly binding: ActionBinding;
73
+ readonly activate: (event: Event) => void;
74
+ } {
67
75
  const plan = planOf(definition);
68
76
  // Resolved here, once, from two immutable layers: an invocation's clocks cannot change afterwards.
69
77
  const limits = resolveLimits(plan.limits, seam.limits ?? {});
@@ -100,7 +108,8 @@ export function createBinding<Event>(
100
108
  bindingId: id,
101
109
  isDisposed,
102
110
  decided(invocation) {
103
- if (owner === invocation && !isDisposed() && phase() === 'running') setPhase('settling');
111
+ if (owner === invocation && !isDisposed() && phase() === 'running')
112
+ setPhase('settling');
104
113
  },
105
114
  settled(invocation) {
106
115
  unsettled.delete(invocation);
@@ -137,7 +146,11 @@ export function createBinding<Event>(
137
146
  };
138
147
 
139
148
  const ignore = (busy: Invocation<Event>): void => {
140
- emitObservation(plan, id, Date.now(), { phase: 'ignored', ignoredReason: 'busy', busyInvocationId: busy.id });
149
+ emitObservation(plan, id, Date.now(), {
150
+ phase: 'ignored',
151
+ ignoredReason: 'busy',
152
+ busyInvocationId: busy.id,
153
+ });
141
154
  };
142
155
 
143
156
  const activate = (event: Event): void => {
@@ -152,7 +165,12 @@ export function createBinding<Event>(
152
165
  ignore(owner);
153
166
  return;
154
167
  }
155
- const invocation = new Invocation<Event>(plan, invocationOwner, seam, limits);
168
+ const invocation = new Invocation<Event>(
169
+ plan,
170
+ invocationOwner,
171
+ seam,
172
+ limits,
173
+ );
156
174
  const replaced = owner;
157
175
  owner = invocation;
158
176
  replaced?.replaced();
@@ -17,7 +17,8 @@ export type KnownErrorCategory =
17
17
  * Open on purpose: a consumer must handle a category it does not know, and `'unknown'` is the
18
18
  * fallback when nothing established a cause. `(string & {})` keeps the known names in editors.
19
19
  */
20
- export type ErrorCategory = KnownErrorCategory | (string & Record<never, never>);
20
+ export type ErrorCategory =
21
+ KnownErrorCategory | (string & Record<never, never>);
21
22
 
22
23
  /**
23
24
  * A cause that says what happened. An application's handler, prerequisite or hook throws it when
@@ -30,7 +31,11 @@ export type ErrorCategory = KnownErrorCategory | (string & Record<never, never>)
30
31
  export class ActionError extends Error {
31
32
  readonly category: ErrorCategory;
32
33
 
33
- constructor(category: ErrorCategory, message: string, options?: { readonly cause?: unknown }) {
34
+ constructor(
35
+ category: ErrorCategory,
36
+ message: string,
37
+ options?: { readonly cause?: unknown },
38
+ ) {
34
39
  super(message, options);
35
40
  if (typeof category !== 'string' || category === '') {
36
41
  throw new TypeError('An ActionError needs a non-empty category.');
@@ -45,12 +50,16 @@ export class ActionError extends Error {
45
50
  * failure the runtime itself decided on (for example input that could not be read) is that
46
51
  * evidence and is passed in as the fallback.
47
52
  */
48
- export function categorize(error: unknown, fallback: ErrorCategory = 'unknown'): ErrorCategory {
53
+ export function categorize(
54
+ error: unknown,
55
+ fallback: ErrorCategory = 'unknown',
56
+ ): ErrorCategory {
49
57
  if (error instanceof ActionError) return error.category;
50
58
  // The platform's own typed errors (what `AbortSignal.timeout` and `fetch` reject with) are
51
59
  // DOMExceptions with these exact names. A plain Error that merely carries the same `name` is
52
60
  // not typed evidence.
53
- const platform: unknown = (globalThis as { DOMException?: unknown }).DOMException;
61
+ const platform: unknown = (globalThis as { DOMException?: unknown })
62
+ .DOMException;
54
63
  if (typeof platform === 'function' && error instanceof platform) {
55
64
  const { name } = error as { name?: unknown };
56
65
  if (name === 'TimeoutError') return 'timeout';
@@ -11,7 +11,12 @@ import type {
11
11
  import { readLimits, type Limits } from './limits.js';
12
12
  import type { Prerequisite } from './prerequisite.js';
13
13
  import type { ActivationScope, InvocationScope } from './scope.js';
14
- import { checkTransport, type ServerCalls, type ServerReply, type ServerTransport } from './server.js';
14
+ import {
15
+ checkTransport,
16
+ type ServerCalls,
17
+ type ServerReply,
18
+ type ServerTransport,
19
+ } from './server.js';
15
20
  import type { Synchronous } from './thenable.js';
16
21
 
17
22
  /** What `atActivation` receives, in the event's own call stack. */
@@ -22,7 +27,11 @@ export interface ActivationContext<Event> {
22
27
  }
23
28
 
24
29
  /** What the handler receives for one activation. */
25
- export interface HandlerContext<Params, Activation, Provided = unknown> extends Freshness {
30
+ export interface HandlerContext<
31
+ Params,
32
+ Activation,
33
+ Provided = unknown,
34
+ > extends Freshness {
26
35
  /** What `input` copied out of the event, or `undefined` for an action without `input`. */
27
36
  readonly params: Params;
28
37
  /** What `atActivation` returned, or `undefined` for an action without it. */
@@ -51,7 +60,12 @@ interface HookContext extends Freshness {
51
60
  // the resource this invocation took, whatever stage the outcome was decided in. A failure of the
52
61
  // prefix itself has none: `atActivation` did not return one.
53
62
 
54
- export interface SuccessContext<Params, Activation, Provided, Result> extends HookContext {
63
+ export interface SuccessContext<
64
+ Params,
65
+ Activation,
66
+ Provided,
67
+ Result,
68
+ > extends HookContext {
55
69
  readonly params: Params;
56
70
  readonly activation: Activation;
57
71
  readonly provided: Provided;
@@ -76,30 +90,31 @@ export type FailureContext<Params, Activation, Provided, Result> = HookContext &
76
90
  }
77
91
  );
78
92
 
79
- export type CanceledContext<Params, Activation, Provided, Result> = HookContext &
80
- (
81
- | {
82
- readonly stage: 'prerequisites';
83
- readonly cause: CancelCause;
84
- readonly params: Params;
85
- readonly activation: Activation;
86
- }
87
- | {
88
- readonly stage: 'running';
89
- readonly cause: CancelCause;
90
- readonly params: Params;
91
- readonly activation: Activation;
92
- readonly provided: Provided;
93
- }
94
- | {
95
- readonly stage: 'completing';
96
- readonly cause: CancelCause;
97
- readonly params: Params;
98
- readonly activation: Activation;
99
- readonly provided: Provided;
100
- readonly result: Result;
101
- }
102
- );
93
+ export type CanceledContext<Params, Activation, Provided, Result> =
94
+ HookContext &
95
+ (
96
+ | {
97
+ readonly stage: 'prerequisites';
98
+ readonly cause: CancelCause;
99
+ readonly params: Params;
100
+ readonly activation: Activation;
101
+ }
102
+ | {
103
+ readonly stage: 'running';
104
+ readonly cause: CancelCause;
105
+ readonly params: Params;
106
+ readonly activation: Activation;
107
+ readonly provided: Provided;
108
+ }
109
+ | {
110
+ readonly stage: 'completing';
111
+ readonly cause: CancelCause;
112
+ readonly params: Params;
113
+ readonly activation: Activation;
114
+ readonly provided: Provided;
115
+ readonly result: Result;
116
+ }
117
+ );
103
118
 
104
119
  /**
105
120
  * What final reporting can know: the immutable primary outcome as earlier hooks received it, plus
@@ -131,7 +146,10 @@ export interface ActionDefinition<Event> {
131
146
  * any number of `before`, then `run`. `Calls` is what `run` also receives: nothing for a local
132
147
  * action, the server call for a server action.
133
148
  */
134
- export interface ActionDraft<Event, Calls = unknown> extends ActionDraftAfterInput<Event, undefined, Calls> {
149
+ export interface ActionDraft<
150
+ Event,
151
+ Calls = unknown,
152
+ > extends ActionDraftAfterInput<Event, undefined, Calls> {
135
153
  /**
136
154
  * Copies what the handler needs out of the event, once, when it occurs. It runs synchronously
137
155
  * in the event's own call stack and must not perform effects. A thrown error, or a returned
@@ -143,7 +161,11 @@ export interface ActionDraft<Event, Calls = unknown> extends ActionDraftAfterInp
143
161
  ): ActionDraftAfterInput<Event, Params, Calls>;
144
162
  }
145
163
 
146
- export interface ActionDraftAfterInput<Event, Params, Calls = unknown> extends ActionDraftChain<Event, Params, undefined, unknown, Calls> {
164
+ export interface ActionDraftAfterInput<
165
+ Event,
166
+ Params,
167
+ Calls = unknown,
168
+ > extends ActionDraftChain<Event, Params, undefined, unknown, Calls> {
147
169
  /**
148
170
  * Work that an API requires inside the event's own call stack, such as taking a lease before
149
171
  * anything is awaited. It runs synchronously after `input`, in the environment and event stack
@@ -159,7 +181,13 @@ export interface ActionDraftAfterInput<Event, Params, Calls = unknown> extends A
159
181
  ): ActionDraftChain<Event, Params, Activation, unknown, Calls>;
160
182
  }
161
183
 
162
- export interface ActionDraftChain<Event, Params, Activation, Provided = unknown, Calls = unknown> {
184
+ export interface ActionDraftChain<
185
+ Event,
186
+ Params,
187
+ Activation,
188
+ Provided = unknown,
189
+ Calls = unknown,
190
+ > {
163
191
  /**
164
192
  * A prerequisite: application work that must finish before the handler and may stop it. They
165
193
  * run strictly in the order written, each after the previous one proceeded. The compiler
@@ -174,14 +202,32 @@ export interface ActionDraftChain<Event, Params, Activation, Provided = unknown,
174
202
  * server action's handler also receives `call`, which sends its request through the transport.
175
203
  */
176
204
  run<Result>(
177
- handler: (context: HandlerContext<Params, Activation, Provided> & Calls) => Result | PromiseLike<Result>,
178
- ): ActionDraftWithHandler<Event, Params, Activation, Provided, Result, never, Calls>;
205
+ handler: (
206
+ context: HandlerContext<Params, Activation, Provided> & Calls,
207
+ ) => Result | PromiseLike<Result>,
208
+ ): ActionDraftWithHandler<
209
+ Event,
210
+ Params,
211
+ Activation,
212
+ Provided,
213
+ Result,
214
+ never,
215
+ Calls
216
+ >;
179
217
  }
180
218
 
181
219
  type HookName = 'onSuccess' | 'onFailure' | 'onCanceled' | 'onFinally';
182
220
  type TailName = 'limits' | 'whenBusy' | 'observe' | HookName;
183
221
 
184
- interface TailMembers<Event, Params, Activation, Provided, Result, Used extends TailName, Calls> {
222
+ interface TailMembers<
223
+ Event,
224
+ Params,
225
+ Activation,
226
+ Provided,
227
+ Result,
228
+ Used extends TailName,
229
+ Calls,
230
+ > {
185
231
  /**
186
232
  * Overrides the bounds for this action alone, for a local and a server action alike. Each
187
233
  * setting is optional and resolves on its own: this override, then the owning SDK instance's
@@ -192,27 +238,89 @@ interface TailMembers<Event, Params, Activation, Provided, Result, Used extends
192
238
  */
193
239
  limits(
194
240
  limits: Limits,
195
- ): ActionDraftWithHandler<Event, Params, Activation, Provided, Result, Used | 'limits', Calls>;
241
+ ): ActionDraftWithHandler<
242
+ Event,
243
+ Params,
244
+ Activation,
245
+ Provided,
246
+ Result,
247
+ Used | 'limits',
248
+ Calls
249
+ >;
196
250
  /** `ignore` (the default) drops an activation while busy; `replace` cancels the running invocation. */
197
251
  whenBusy(
198
252
  policy: BusyPolicy,
199
- ): ActionDraftWithHandler<Event, Params, Activation, Provided, Result, Used | 'whenBusy', Calls>;
253
+ ): ActionDraftWithHandler<
254
+ Event,
255
+ Params,
256
+ Activation,
257
+ Provided,
258
+ Result,
259
+ Used | 'whenBusy',
260
+ Calls
261
+ >;
200
262
  observe(
201
263
  observer: ActionObserver,
202
- ): ActionDraftWithHandler<Event, Params, Activation, Provided, Result, Used | 'observe', Calls>;
264
+ ): ActionDraftWithHandler<
265
+ Event,
266
+ Params,
267
+ Activation,
268
+ Provided,
269
+ Result,
270
+ Used | 'observe',
271
+ Calls
272
+ >;
203
273
  /** Required client work that decides success. Its exception or overrun fails the invocation. */
204
274
  onSuccess(
205
- hook: (context: SuccessContext<Params, Activation, Provided, Result>) => void | PromiseLike<void>,
206
- ): ActionDraftWithHandler<Event, Params, Activation, Provided, Result, Used | 'onSuccess', Calls>;
275
+ hook: (
276
+ context: SuccessContext<Params, Activation, Provided, Result>,
277
+ ) => void | PromiseLike<void>,
278
+ ): ActionDraftWithHandler<
279
+ Event,
280
+ Params,
281
+ Activation,
282
+ Provided,
283
+ Result,
284
+ Used | 'onSuccess',
285
+ Calls
286
+ >;
207
287
  onFailure(
208
- hook: (context: FailureContext<Params, Activation, Provided, Result>) => void | PromiseLike<void>,
209
- ): ActionDraftWithHandler<Event, Params, Activation, Provided, Result, Used | 'onFailure', Calls>;
288
+ hook: (
289
+ context: FailureContext<Params, Activation, Provided, Result>,
290
+ ) => void | PromiseLike<void>,
291
+ ): ActionDraftWithHandler<
292
+ Event,
293
+ Params,
294
+ Activation,
295
+ Provided,
296
+ Result,
297
+ Used | 'onFailure',
298
+ Calls
299
+ >;
210
300
  onCanceled(
211
- hook: (context: CanceledContext<Params, Activation, Provided, Result>) => void | PromiseLike<void>,
212
- ): ActionDraftWithHandler<Event, Params, Activation, Provided, Result, Used | 'onCanceled', Calls>;
301
+ hook: (
302
+ context: CanceledContext<Params, Activation, Provided, Result>,
303
+ ) => void | PromiseLike<void>,
304
+ ): ActionDraftWithHandler<
305
+ Event,
306
+ Params,
307
+ Activation,
308
+ Provided,
309
+ Result,
310
+ Used | 'onCanceled',
311
+ Calls
312
+ >;
213
313
  onFinally(
214
314
  hook: (context: FinallyContext<Result>) => void | PromiseLike<void>,
215
- ): ActionDraftWithHandler<Event, Params, Activation, Provided, Result, Used | 'onFinally', Calls>;
315
+ ): ActionDraftWithHandler<
316
+ Event,
317
+ Params,
318
+ Activation,
319
+ Provided,
320
+ Result,
321
+ Used | 'onFinally',
322
+ Calls
323
+ >;
216
324
  build(): ActionDefinition<Event>;
217
325
  }
218
326
 
@@ -225,7 +333,10 @@ export type ActionDraftWithHandler<
225
333
  Result = unknown,
226
334
  Used extends TailName = never,
227
335
  Calls = unknown,
228
- > = Omit<TailMembers<Event, Params, Activation, Provided, Result, Used, Calls>, Used>;
336
+ > = Omit<
337
+ TailMembers<Event, Params, Activation, Provided, Result, Used, Calls>,
338
+ Used
339
+ >;
229
340
 
230
341
  // ---------------------------------------------------------------------------------------------
231
342
  // What the runtime reads. Internal: authors only see the definition.
@@ -233,9 +344,15 @@ export type ActionDraftWithHandler<
233
344
 
234
345
  /** Hooks with their types erased: the builder's types are what tie them to a result. */
235
346
  export interface ErasedHooks {
236
- readonly onSuccess?: (context: SuccessContext<unknown, unknown, unknown, unknown>) => unknown;
237
- readonly onFailure?: (context: FailureContext<unknown, unknown, unknown, unknown>) => unknown;
238
- readonly onCanceled?: (context: CanceledContext<unknown, unknown, unknown, unknown>) => unknown;
347
+ readonly onSuccess?: (
348
+ context: SuccessContext<unknown, unknown, unknown, unknown>,
349
+ ) => unknown;
350
+ readonly onFailure?: (
351
+ context: FailureContext<unknown, unknown, unknown, unknown>,
352
+ ) => unknown;
353
+ readonly onCanceled?: (
354
+ context: CanceledContext<unknown, unknown, unknown, unknown>,
355
+ ) => unknown;
239
356
  readonly onFinally?: (context: FinallyContext<unknown>) => unknown;
240
357
  }
241
358
 
@@ -243,9 +360,12 @@ export interface ErasedHooks {
243
360
  export interface ActionPlan<Event> {
244
361
  readonly label: string;
245
362
  readonly extract: ((event: Event) => unknown) | undefined;
246
- readonly activate: ((context: ActivationContext<Event>) => unknown) | undefined;
363
+ readonly activate:
364
+ ((context: ActivationContext<Event>) => unknown) | undefined;
247
365
  readonly prerequisites: readonly Prerequisite<object, unknown, unknown>[];
248
- readonly handler: (context: HandlerContext<unknown, unknown, unknown>) => unknown;
366
+ readonly handler: (
367
+ context: HandlerContext<unknown, unknown, unknown>,
368
+ ) => unknown;
249
369
  /** This action's own overrides: what the definition declared, validated and copied. Possibly empty. */
250
370
  readonly limits: Limits;
251
371
  readonly hooks: ErasedHooks;
@@ -265,9 +385,13 @@ class BuiltAction<Event> implements ActionDefinition<Event> {
265
385
  }
266
386
 
267
387
  /** The plan behind a definition. Refuses a value that `Action` did not build. */
268
- export function planOf<Event>(definition: ActionDefinition<Event>): ActionPlan<Event> {
388
+ export function planOf<Event>(
389
+ definition: ActionDefinition<Event>,
390
+ ): ActionPlan<Event> {
269
391
  if (definition instanceof BuiltAction) return definition.plan;
270
- throw new TypeError('This is not an action definition. Build one with Action.local(...).run(...).build().');
392
+ throw new TypeError(
393
+ 'This is not an action definition. Build one with Action.local(...).run(...).build().',
394
+ );
271
395
  }
272
396
 
273
397
  type HookKey = keyof ErasedHooks;
@@ -304,7 +428,8 @@ class Draft {
304
428
  }
305
429
 
306
430
  private mustFollowRun(step: string): void {
307
- if (this.declared.handler === undefined) throw new Error(`Action '${this.label}': ${step} can only follow run.`);
431
+ if (this.declared.handler === undefined)
432
+ throw new Error(`Action '${this.label}': ${step} can only follow run.`);
308
433
  }
309
434
 
310
435
  input(extract: unknown): Draft {
@@ -315,66 +440,106 @@ class Draft {
315
440
  declared.prerequisites.length > 0 ||
316
441
  declared.handler !== undefined
317
442
  ) {
318
- throw new Error(`Action '${this.label}': input can be declared once, and only before atActivation, before and run.`);
443
+ throw new Error(
444
+ `Action '${this.label}': input can be declared once, and only before atActivation, before and run.`,
445
+ );
319
446
  }
320
- if (typeof extract !== 'function') throw new TypeError(`Action '${this.label}': input needs a function.`);
447
+ if (typeof extract !== 'function')
448
+ throw new TypeError(`Action '${this.label}': input needs a function.`);
321
449
  return this.next({ extract: extract as Declared['extract'] });
322
450
  }
323
451
 
324
452
  atActivation(perform: unknown): Draft {
325
453
  const { declared } = this;
326
- if (declared.activate !== undefined || declared.prerequisites.length > 0 || declared.handler !== undefined) {
327
- throw new Error(`Action '${this.label}': atActivation can be declared once, and only before before and run.`);
454
+ if (
455
+ declared.activate !== undefined ||
456
+ declared.prerequisites.length > 0 ||
457
+ declared.handler !== undefined
458
+ ) {
459
+ throw new Error(
460
+ `Action '${this.label}': atActivation can be declared once, and only before before and run.`,
461
+ );
328
462
  }
329
- if (typeof perform !== 'function') throw new TypeError(`Action '${this.label}': atActivation needs a function.`);
463
+ if (typeof perform !== 'function')
464
+ throw new TypeError(
465
+ `Action '${this.label}': atActivation needs a function.`,
466
+ );
330
467
  return this.next({ activate: perform as Declared['activate'] });
331
468
  }
332
469
 
333
470
  before(candidate: unknown): Draft {
334
471
  const { declared } = this;
335
- if (declared.handler !== undefined) throw new Error(`Action '${this.label}': before can only precede run.`);
336
- const { label, run } = (candidate ?? {}) as { label?: unknown; run?: unknown };
337
- if (typeof label !== 'string' || label === '' || typeof run !== 'function') {
338
- throw new TypeError(`Action '${this.label}': before needs a prerequisite with a label and a run function.`);
472
+ if (declared.handler !== undefined)
473
+ throw new Error(`Action '${this.label}': before can only precede run.`);
474
+ const { label, run } = (candidate ?? {}) as {
475
+ label?: unknown;
476
+ run?: unknown;
477
+ };
478
+ if (
479
+ typeof label !== 'string' ||
480
+ label === '' ||
481
+ typeof run !== 'function'
482
+ ) {
483
+ throw new TypeError(
484
+ `Action '${this.label}': before needs a prerequisite with a label and a run function.`,
485
+ );
339
486
  }
340
- const kept = Object.freeze({ label, run }) as Prerequisite<object, unknown, unknown>;
341
- return this.next({ prerequisites: Object.freeze([...declared.prerequisites, kept]) });
487
+ const kept = Object.freeze({ label, run }) as Prerequisite<
488
+ object,
489
+ unknown,
490
+ unknown
491
+ >;
492
+ return this.next({
493
+ prerequisites: Object.freeze([...declared.prerequisites, kept]),
494
+ });
342
495
  }
343
496
 
344
497
  run(handler: unknown): Draft {
345
- if (this.declared.handler !== undefined) throw new Error(`Action '${this.label}': run can be declared once.`);
346
- if (typeof handler !== 'function') throw new TypeError(`Action '${this.label}': run needs a function.`);
498
+ if (this.declared.handler !== undefined)
499
+ throw new Error(`Action '${this.label}': run can be declared once.`);
500
+ if (typeof handler !== 'function')
501
+ throw new TypeError(`Action '${this.label}': run needs a function.`);
347
502
  return this.next({ handler: handler as Declared['handler'] });
348
503
  }
349
504
 
350
505
  limits(limits: unknown): Draft {
351
506
  this.mustFollowRun('limits');
352
- if (this.declared.limits !== undefined) throw new Error(`Action '${this.label}': limits can be declared once.`);
507
+ if (this.declared.limits !== undefined)
508
+ throw new Error(`Action '${this.label}': limits can be declared once.`);
353
509
  // Checked now, copied, and kept apart from the caller's object.
354
510
  return this.next({ limits: readLimits(`Action '${this.label}'`, limits) });
355
511
  }
356
512
 
357
513
  whenBusy(policy: unknown): Draft {
358
514
  this.mustFollowRun('whenBusy');
359
- if (this.declared.busy !== undefined) throw new Error(`Action '${this.label}': whenBusy can be declared once.`);
515
+ if (this.declared.busy !== undefined)
516
+ throw new Error(`Action '${this.label}': whenBusy can be declared once.`);
360
517
  if (policy !== 'ignore' && policy !== 'replace') {
361
- throw new TypeError(`Action '${this.label}': whenBusy takes 'ignore' or 'replace'.`);
518
+ throw new TypeError(
519
+ `Action '${this.label}': whenBusy takes 'ignore' or 'replace'.`,
520
+ );
362
521
  }
363
522
  return this.next({ busy: policy });
364
523
  }
365
524
 
366
525
  observe(observer: unknown): Draft {
367
526
  this.mustFollowRun('observe');
368
- if (this.declared.observer !== undefined) throw new Error(`Action '${this.label}': observe can be declared once.`);
369
- if (typeof observer !== 'function') throw new TypeError(`Action '${this.label}': observe needs a function.`);
527
+ if (this.declared.observer !== undefined)
528
+ throw new Error(`Action '${this.label}': observe can be declared once.`);
529
+ if (typeof observer !== 'function')
530
+ throw new TypeError(`Action '${this.label}': observe needs a function.`);
370
531
  return this.next({ observer: observer as ActionObserver });
371
532
  }
372
533
 
373
534
  private withHook(key: HookKey, hook: unknown): Draft {
374
535
  this.mustFollowRun(key);
375
- if (this.declared.hooks[key] !== undefined) throw new Error(`Action '${this.label}': ${key} can be declared once.`);
376
- if (typeof hook !== 'function') throw new TypeError(`Action '${this.label}': ${key} needs a function.`);
377
- return this.next({ hooks: Object.freeze({ ...this.declared.hooks, [key]: hook }) });
536
+ if (this.declared.hooks[key] !== undefined)
537
+ throw new Error(`Action '${this.label}': ${key} can be declared once.`);
538
+ if (typeof hook !== 'function')
539
+ throw new TypeError(`Action '${this.label}': ${key} needs a function.`);
540
+ return this.next({
541
+ hooks: Object.freeze({ ...this.declared.hooks, [key]: hook }),
542
+ });
378
543
  }
379
544
 
380
545
  onSuccess(hook: unknown): Draft {
@@ -393,7 +558,8 @@ class Draft {
393
558
  build(): ActionDefinition<never> {
394
559
  const { label, declared } = this;
395
560
  const { handler } = declared;
396
- if (handler === undefined) throw new Error(`Action '${label}' cannot be built before run.`);
561
+ if (handler === undefined)
562
+ throw new Error(`Action '${label}' cannot be built before run.`);
397
563
  return new BuiltAction<never>(label, {
398
564
  label,
399
565
  // Interop assertions, the only ones in this module. The typed interfaces above are what tie
@@ -413,8 +579,12 @@ class Draft {
413
579
  }
414
580
  }
415
581
 
416
- function startDraft(label: string, transport: ServerTransport | undefined): Draft {
417
- if (typeof label !== 'string' || label === '') throw new TypeError('An action needs a non-empty label.');
582
+ function startDraft(
583
+ label: string,
584
+ transport: ServerTransport | undefined,
585
+ ): Draft {
586
+ if (typeof label !== 'string' || label === '')
587
+ throw new TypeError('An action needs a non-empty label.');
418
588
  return new Draft(label, {
419
589
  extract: undefined,
420
590
  activate: undefined,
@@ -449,6 +619,9 @@ export const Action = {
449
619
  transport: ServerTransport<Reply>,
450
620
  ): ActionDraft<Event, ServerCalls<Reply>> {
451
621
  // Interop assertion, as for `local`.
452
- return startDraft(label, checkTransport(label, transport)) as unknown as ActionDraft<Event, ServerCalls<Reply>>;
622
+ return startDraft(
623
+ label,
624
+ checkTransport(label, transport),
625
+ ) as unknown as ActionDraft<Event, ServerCalls<Reply>>;
453
626
  },
454
627
  };
@@ -42,9 +42,21 @@ export type {
42
42
  } from './server.js';
43
43
  export type { Synchronous } from './thenable.js';
44
44
  export { InvocationEndedError } from './scope.js';
45
- export type { ActivationScope, Held, InvocationScope, Release, ReleaseContext } from './scope.js';
45
+ export type {
46
+ ActivationScope,
47
+ Held,
48
+ InvocationScope,
49
+ Release,
50
+ ReleaseContext,
51
+ } from './scope.js';
46
52
  export { prerequisite, proceed, refuse } from './prerequisite.js';
47
- export type { Prerequisite, PrerequisiteContext, PrerequisiteResult, Proceed, Refuse } from './prerequisite.js';
53
+ export type {
54
+ Prerequisite,
55
+ PrerequisiteContext,
56
+ PrerequisiteResult,
57
+ Proceed,
58
+ Refuse,
59
+ } from './prerequisite.js';
48
60
  export type {
49
61
  ActionObserver,
50
62
  CancelCause,