stitchkit 0.76.2 → 0.77.0

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.
@@ -13,6 +13,9 @@ import {
13
13
  AppError
14
14
  } from "./index-0w9abg87.js";
15
15
 
16
+ // src/application/kernel.ts
17
+ import { z } from "zod";
18
+
16
19
  // src/application/graph.ts
17
20
  function resolveResourceGraph(resources) {
18
21
  const entries = resources.map((resource, declarationIndex) => ({
@@ -82,6 +85,15 @@ var ApplicationShutdownBudgetSchema = ShutdownOptionsSchema.pick({
82
85
  gracePeriodMs: true,
83
86
  forceTimeoutMs: true
84
87
  });
88
+ var ApplicationRestartInputSchema = z.object({ resourceId: z.string().min(1) }).strict().readonly();
89
+ var ApplicationRestartOutcomeSchema = z.enum(["restarted", "failed", "refused"]);
90
+ var ApplicationRestartResultSchema = z.object({
91
+ resourceId: z.string(),
92
+ affected: z.array(z.string()).readonly(),
93
+ outcome: ApplicationRestartOutcomeSchema,
94
+ reason: z.string().optional(),
95
+ durationMs: z.number().int().nonnegative()
96
+ }).strict().readonly();
85
97
 
86
98
  class ApplicationAdmissionError extends AppError {
87
99
  constructor() {
@@ -294,138 +306,221 @@ function createApplication(config) {
294
306
  publish();
295
307
  }
296
308
  };
297
- const runStart = async () => {
298
- lifecycle = "starting";
299
- publish();
300
- let startFailure;
301
- try {
302
- for (const entry of ordered) {
303
- if (shutdownRequested || startupAbort.signal.aborted) {
309
+ const startEach = async (entries, signal) => {
310
+ for (const entry of entries) {
311
+ if (shutdownRequested || signal.aborted) {
312
+ throw new ApplicationStartupInterruptedError;
313
+ }
314
+ const record = records.get(entry.id);
315
+ if (!record)
316
+ throw new Error("Managed resource record disappeared");
317
+ const dependencyFailed = entry.dependsOn.some((dependencyId) => records.get(dependencyId)?.state !== "ready");
318
+ if (dependencyFailed) {
319
+ record.state = "failed";
320
+ record.health = "unhealthy";
321
+ record.failures.push("start");
322
+ publish();
323
+ if (entry.required) {
324
+ throw new Error(`[stitchkit] required resource "${entry.id}" has an unavailable dependency`);
325
+ }
326
+ continue;
327
+ }
328
+ record.attempted = true;
329
+ record.state = "starting";
330
+ publish();
331
+ try {
332
+ const started = await entry.resource.start(contextFor(record, { signal }));
333
+ if (shutdownRequested || signal.aborted) {
304
334
  throw new ApplicationStartupInterruptedError;
305
335
  }
306
- const record = records.get(entry.id);
307
- if (!record)
308
- throw new Error("Managed resource record disappeared");
309
- const dependencyFailed = entry.dependsOn.some((dependencyId) => records.get(dependencyId)?.state !== "ready");
310
- if (dependencyFailed) {
336
+ if (isStartResult(started)) {
337
+ record.runtime = started;
338
+ if (started.value !== undefined)
339
+ published.set(entry.id, started.value);
340
+ let resourceReady = started.ready === undefined;
341
+ let completionSettled = false;
342
+ let completionFailure;
343
+ const completion = started.completion?.then(() => {
344
+ completionSettled = true;
345
+ if (resourceReady)
346
+ markLateCompletion(record);
347
+ }, (error) => {
348
+ completionSettled = true;
349
+ completionFailure = error;
350
+ if (resourceReady)
351
+ markLateCompletion(record, { error });
352
+ });
353
+ if (started.ready && completion) {
354
+ const readiness = started.ready.then(() => "ready");
355
+ const completionBeforeReady = completion.then(() => "completion");
356
+ const first = await Promise.race([readiness, completionBeforeReady]);
357
+ if (first === "completion") {
358
+ throw new ResourceCompletionBeforeReadyError(entry.id, completionFailure);
359
+ }
360
+ resourceReady = true;
361
+ if (completionSettled) {
362
+ throw new ResourceCompletionBeforeReadyError(entry.id, completionFailure);
363
+ }
364
+ } else if (started.ready) {
365
+ await started.ready;
366
+ resourceReady = true;
367
+ } else if (completion) {}
368
+ }
369
+ if (shutdownRequested || signal.aborted) {
370
+ throw new ApplicationStartupInterruptedError;
371
+ }
372
+ record.state = "ready";
373
+ if (!record.healthReported) {
374
+ record.health = "healthy";
375
+ record.everHealthy = true;
376
+ }
377
+ publish();
378
+ } catch (error) {
379
+ const interrupted = shutdownRequested || signal.aborted;
380
+ if (!(error instanceof ApplicationStartupInterruptedError)) {
381
+ record.failures.push(error instanceof ResourceCompletionBeforeReadyError ? "completion" : record.runtime?.ready ? "ready" : "start");
311
382
  record.state = "failed";
312
383
  record.health = "unhealthy";
313
- record.failures.push("start");
384
+ reportFailure(entry.id, record.failures[record.failures.length - 1] ?? "start", error);
314
385
  publish();
315
- if (entry.required) {
316
- throw new Error(`[stitchkit] required resource "${entry.id}" has an unavailable dependency`);
317
- }
318
- continue;
319
386
  }
320
- record.attempted = true;
321
- record.state = "starting";
387
+ if (interrupted || entry.required)
388
+ throw error;
389
+ }
390
+ }
391
+ };
392
+ const activateEach = async (entries, signal) => {
393
+ for (const entry of entries) {
394
+ if (shutdownRequested || signal.aborted) {
395
+ throw new ApplicationStartupInterruptedError;
396
+ }
397
+ const record = records.get(entry.id);
398
+ if (record?.state !== "ready")
399
+ continue;
400
+ const dependencyUnavailable = entry.dependsOn.some((dependencyId) => {
401
+ const dependency = records.get(dependencyId);
402
+ return dependency?.state !== "ready" || !dependency.activated;
403
+ });
404
+ if (dependencyUnavailable) {
405
+ record.failures.push("start");
406
+ record.state = "failed";
407
+ record.health = "unhealthy";
322
408
  publish();
323
- try {
324
- const started = await entry.resource.start(contextFor(record, { signal: startupAbort.signal }));
325
- if (shutdownRequested || startupAbort.signal.aborted) {
326
- throw new ApplicationStartupInterruptedError;
327
- }
328
- if (isStartResult(started)) {
329
- record.runtime = started;
330
- if (started.value !== undefined)
331
- published.set(entry.id, started.value);
332
- let resourceReady = started.ready === undefined;
333
- let completionSettled = false;
334
- let completionFailure;
335
- const completion = started.completion?.then(() => {
336
- completionSettled = true;
337
- if (resourceReady)
338
- markLateCompletion(record);
339
- }, (error) => {
340
- completionSettled = true;
341
- completionFailure = error;
342
- if (resourceReady)
343
- markLateCompletion(record, { error });
344
- });
345
- if (started.ready && completion) {
346
- const readiness = started.ready.then(() => "ready");
347
- const completionBeforeReady = completion.then(() => "completion");
348
- const first = await Promise.race([readiness, completionBeforeReady]);
349
- if (first === "completion") {
350
- throw new ResourceCompletionBeforeReadyError(entry.id, completionFailure);
351
- }
352
- resourceReady = true;
353
- if (completionSettled) {
354
- throw new ResourceCompletionBeforeReadyError(entry.id, completionFailure);
355
- }
356
- } else if (started.ready) {
357
- await started.ready;
358
- resourceReady = true;
359
- } else if (completion) {}
360
- }
361
- if (shutdownRequested || startupAbort.signal.aborted) {
362
- throw new ApplicationStartupInterruptedError;
363
- }
364
- record.state = "ready";
365
- if (!record.healthReported) {
366
- record.health = "healthy";
367
- record.everHealthy = true;
368
- }
369
- publish();
370
- } catch (error) {
371
- const interrupted = shutdownRequested || startupAbort.signal.aborted;
372
- if (!(error instanceof ApplicationStartupInterruptedError)) {
373
- record.failures.push(error instanceof ResourceCompletionBeforeReadyError ? "completion" : record.runtime?.ready ? "ready" : "start");
374
- record.state = "failed";
375
- record.health = "unhealthy";
376
- reportFailure(entry.id, record.failures[record.failures.length - 1] ?? "start", error);
377
- publish();
378
- }
379
- if (interrupted || entry.required)
380
- throw error;
409
+ if (entry.required) {
410
+ throw new Error(`[stitchkit] required resource "${entry.id}" has an unavailable activation dependency`);
381
411
  }
412
+ continue;
382
413
  }
383
- lifecycle = "ready";
384
- publish();
385
- for (const entry of ordered) {
386
- if (shutdownRequested || startupAbort.signal.aborted) {
414
+ try {
415
+ await entry.resource.activate?.(contextFor(record));
416
+ if (shutdownRequested || signal.aborted) {
387
417
  throw new ApplicationStartupInterruptedError;
388
418
  }
389
- const record = records.get(entry.id);
390
- if (record?.state !== "ready")
391
- continue;
392
- const dependencyUnavailable = entry.dependsOn.some((dependencyId) => {
393
- const dependency = records.get(dependencyId);
394
- return dependency?.state !== "ready" || !dependency.activated;
395
- });
396
- if (dependencyUnavailable) {
419
+ record.activated = true;
420
+ if (entry.required && (record.state !== "ready" || record.health !== "healthy")) {
421
+ const observed = `${record.state}/${record.health}`;
422
+ throw new Error(record.everHealthy ? `[stitchkit] required resource "${entry.id}" lost readiness during activation (${observed})` : `[stitchkit] required resource "${entry.id}" is not healthy (${observed}). A required resource must be healthy for the application to be ready; a resource that is expected to start degraded belongs behind \`required: false\`.`);
423
+ }
424
+ } catch (error) {
425
+ const interrupted = shutdownRequested || signal.aborted;
426
+ if (!(error instanceof ApplicationStartupInterruptedError)) {
397
427
  record.failures.push("start");
398
428
  record.state = "failed";
399
429
  record.health = "unhealthy";
430
+ reportFailure(entry.id, "start", error);
400
431
  publish();
401
- if (entry.required) {
402
- throw new Error(`[stitchkit] required resource "${entry.id}" has an unavailable activation dependency`);
403
- }
404
- continue;
405
- }
406
- try {
407
- await entry.resource.activate?.(contextFor(record));
408
- if (shutdownRequested || startupAbort.signal.aborted) {
409
- throw new ApplicationStartupInterruptedError;
410
- }
411
- record.activated = true;
412
- if (entry.required && (record.state !== "ready" || record.health !== "healthy")) {
413
- const observed = `${record.state}/${record.health}`;
414
- throw new Error(record.everHealthy ? `[stitchkit] required resource "${entry.id}" lost readiness during activation (${observed})` : `[stitchkit] required resource "${entry.id}" is not healthy (${observed}). A required resource must be healthy for the application to be ready; a resource that is expected to start degraded belongs behind \`required: false\`.`);
415
- }
416
- } catch (error) {
417
- const interrupted = shutdownRequested || startupAbort.signal.aborted;
418
- if (!(error instanceof ApplicationStartupInterruptedError)) {
419
- record.failures.push("start");
420
- record.state = "failed";
421
- record.health = "unhealthy";
422
- reportFailure(entry.id, "start", error);
423
- publish();
424
- }
425
- if (interrupted || entry.required)
426
- throw error;
427
432
  }
433
+ if (interrupted || entry.required)
434
+ throw error;
435
+ }
436
+ }
437
+ };
438
+ const subtreeOf = (resourceId) => {
439
+ const affected = new Set([resourceId]);
440
+ for (const entry of ordered) {
441
+ if (entry.dependsOn.some((dependencyId) => affected.has(dependencyId))) {
442
+ affected.add(entry.id);
428
443
  }
444
+ }
445
+ return ordered.filter((entry) => affected.has(entry.id));
446
+ };
447
+ const closeOne = async (entry, record, signal) => {
448
+ const context = () => contextFor(record, { signal });
449
+ if (record.attempted && !record.closed) {
450
+ await entry.resource.stopAdmission?.(context());
451
+ await entry.resource.drain?.(context());
452
+ record.closeInvoked = true;
453
+ await entry.resource.close?.(context());
454
+ }
455
+ record.closed = false;
456
+ record.closeInvoked = false;
457
+ record.attempted = false;
458
+ record.activated = false;
459
+ record.state = "registered";
460
+ record.health = "unknown";
461
+ record.healthReported = false;
462
+ record.runtime = undefined;
463
+ published.delete(entry.id);
464
+ };
465
+ let restarting = Promise.resolve();
466
+ const runRestart = async (input) => {
467
+ const startedAt = Date.now();
468
+ const parsed = ApplicationRestartInputSchema.parse(input);
469
+ const affected = subtreeOf(parsed.resourceId);
470
+ const affectedIds = affected.map((entry) => entry.id);
471
+ const refuse = (reason) => ({
472
+ resourceId: parsed.resourceId,
473
+ affected: affectedIds,
474
+ outcome: "refused",
475
+ reason,
476
+ durationMs: Date.now() - startedAt
477
+ });
478
+ if (!records.has(parsed.resourceId)) {
479
+ return refuse(`no resource is registered as "${parsed.resourceId}"`);
480
+ }
481
+ if (shutdownRequested) {
482
+ return refuse("the application is shutting down");
483
+ }
484
+ if (lifecycle !== "ready") {
485
+ return refuse(`the application is ${lifecycle}, not ready`);
486
+ }
487
+ const restartAbort = new AbortController;
488
+ try {
489
+ for (const entry of [...affected].reverse()) {
490
+ const record = records.get(entry.id);
491
+ if (record)
492
+ await closeOne(entry, record, restartAbort.signal);
493
+ }
494
+ publish();
495
+ await startEach(affected, restartAbort.signal);
496
+ await activateEach(affected, restartAbort.signal);
497
+ publish();
498
+ return {
499
+ resourceId: parsed.resourceId,
500
+ affected: affectedIds,
501
+ outcome: "restarted",
502
+ durationMs: Date.now() - startedAt
503
+ };
504
+ } catch (error) {
505
+ publish();
506
+ return {
507
+ resourceId: parsed.resourceId,
508
+ affected: affectedIds,
509
+ outcome: "failed",
510
+ reason: error instanceof Error ? error.message : String(error),
511
+ durationMs: Date.now() - startedAt
512
+ };
513
+ }
514
+ };
515
+ const runStart = async () => {
516
+ lifecycle = "starting";
517
+ publish();
518
+ let startFailure;
519
+ try {
520
+ await startEach(ordered, startupAbort.signal);
521
+ lifecycle = "ready";
522
+ publish();
523
+ await activateEach(ordered, startupAbort.signal);
429
524
  if (shutdownRequested) {
430
525
  throw new ApplicationStartupInterruptedError;
431
526
  }
@@ -714,8 +809,17 @@ function createApplication(config) {
714
809
  listener(snapshot());
715
810
  return () => listeners.delete(listener);
716
811
  },
717
- shutdown
812
+ shutdown,
813
+ restart(input) {
814
+ const queued = restarting.then(() => runRestart(input));
815
+ restarting = queued.then(() => {
816
+ return;
817
+ }, () => {
818
+ return;
819
+ });
820
+ return queued;
821
+ }
718
822
  };
719
823
  }
720
824
 
721
- export { ApplicationShutdownOptionsSchema, ApplicationShutdownBudgetSchema, ApplicationAdmissionError, createApplication };
825
+ export { ApplicationShutdownOptionsSchema, ApplicationShutdownBudgetSchema, ApplicationRestartInputSchema, ApplicationRestartOutcomeSchema, ApplicationRestartResultSchema, ApplicationAdmissionError, createApplication };
@@ -0,0 +1,31 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * One answer to "may this happen?", from one answerer.
4
+ *
5
+ * Three outcomes and no fourth, because the third is the one that gets left out
6
+ * and then invented: `defer` means *not my call*, which is a different statement
7
+ * from `allow`. Collapsing them makes every answerer that had no opinion into an
8
+ * answerer that approved, and nothing in the result says which happened.
9
+ *
10
+ * `deny` carries its reason as a required field. A refusal without one reaches a
11
+ * human as "denied" and sends them to read the policy source to find out what
12
+ * they did, which is the moment a policy engine stops being worth having.
13
+ *
14
+ * Browser-safe and shared on purpose: an event listener voting on a topic
15
+ * (`stitchkit/live`) and a policy in an ordered pipeline
16
+ * (`stitchkit/application`) are different mechanisms answering the same
17
+ * question, and one question with two vocabularies is a codebase where `grep`
18
+ * finds half the answerers.
19
+ */
20
+ export declare const PolicyDecisionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
21
+ outcome: z.ZodLiteral<"allow">;
22
+ }, z.core.$strict>, z.ZodObject<{
23
+ outcome: z.ZodLiteral<"deny">;
24
+ reason: z.ZodString;
25
+ }, z.core.$strict>, z.ZodObject<{
26
+ outcome: z.ZodLiteral<"defer">;
27
+ }, z.core.$strict>], "outcome">;
28
+ export type PolicyDecision = z.infer<typeof PolicyDecisionSchema>;
29
+ /** What is concluded when nobody claimed the question — see each mechanism for which it uses. */
30
+ export type UndecidedOutcome = 'allow' | 'deny';
31
+ //# sourceMappingURL=decision.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision.d.ts","sourceRoot":"","sources":["../../src/internal/decision.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,oBAAoB;;;;;;;+BAI/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,iGAAiG;AACjG,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,MAAM,CAAC"}
@@ -21,6 +21,7 @@
21
21
  * → ADR 0150.
22
22
  */
23
23
  import { z } from 'zod';
24
+ import type { UndecidedOutcome } from '../internal/decision.js';
24
25
  import type { RealtimeContract, RealtimeEventRegistry } from '../realtime/contract.js';
25
26
  /**
26
27
  * How a topic reaches the listeners registered for it **in this process**.
@@ -38,23 +39,17 @@ import type { RealtimeContract, RealtimeEventRegistry } from '../realtime/contra
38
39
  */
39
40
  export type EventDeliveryMode = 'emit' | 'serial' | 'decision';
40
41
  /**
41
- * One listener's vote on a `decision` topic.
42
+ * One listener's vote on a `decision` topic — the framework's one decision
43
+ * vocabulary, shared with the policy pipeline in `stitchkit/application`.
42
44
  *
43
45
  * `defer` is a real answer — "not my call" — and it is distinct from `allow` on
44
46
  * purpose: an event where every listener defers is one nobody claimed, and what
45
47
  * should happen then is a policy the topic has to state rather than a default
46
- * somebody guesses. See `whenAllDefer`.
48
+ * somebody guesses. See `whenAllDefer`. (A policy pipeline treats that ending
49
+ * as a defect instead, because an operation nothing decided cannot be answered
50
+ * either way; same words, different mechanisms, and each says which it is.)
47
51
  */
48
- export type EventDecision = {
49
- readonly outcome: 'allow';
50
- } | {
51
- readonly outcome: 'deny';
52
- readonly reason: string;
53
- } | {
54
- readonly outcome: 'defer';
55
- };
56
- /** What a `decision` topic concludes when every listener deferred, or there were none. */
57
- export type EventUndecided = 'allow' | 'deny';
52
+ export type { PolicyDecision, UndecidedOutcome } from '../internal/decision.js';
58
53
  export interface EventTopicDeclaration<TSchema extends z.ZodType = z.ZodType> {
59
54
  /** The payload schema. One payload per topic — a topic is not a function call. */
60
55
  readonly schema: TSchema;
@@ -67,7 +62,7 @@ export interface EventTopicDeclaration<TSchema extends z.ZodType = z.ZodType> {
67
62
  * every topic whose author never thought about it. Both are decisions; a
68
63
  * default makes them silently.
69
64
  */
70
- readonly whenAllDefer?: EventUndecided;
65
+ readonly whenAllDefer?: UndecidedOutcome;
71
66
  /**
72
67
  * How long one listener may take on a `serial` or `decision` topic before the
73
68
  * dispatcher stops waiting for it, in milliseconds.
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/live/events.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/live/events.ts"],"names":[],"mappings":"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"}
package/dist/live.d.ts CHANGED
@@ -12,7 +12,7 @@
12
12
  * delivery is `createEventBus` from `stitchkit/server`, and the server halves of
13
13
  * a watched read live in `stitchkit/application`. → ADR 0150.
14
14
  */
15
- export { defineEvents, type EventDecision, type EventDeliveryMode, type EventPayloads, type EventsConfig, type EventsDeclaration, type EventTopicDeclaration, type EventTopicRegistry, type EventTopicsOfMode, type EventUndecided, toRealtimeContract, type WireTopic, } from './live/events.js';
15
+ export { defineEvents, type EventDeliveryMode, type EventPayloads, type EventsConfig, type EventsDeclaration, type EventTopicDeclaration, type EventTopicRegistry, type EventTopicsOfMode, type PolicyDecision, toRealtimeContract, type UndecidedOutcome, type WireTopic, } from './live/events.js';
16
16
  export { createWatchClient, type RealtimeClientLike, type TypedWatchClient, type WatchClientConfig, type WatchHandle, type WatchInboundEvents, type WatchListeners, type WatchTransport, watchTransport, } from './live/watch-client.js';
17
17
  export { WATCH_CLOSE, WATCH_OPEN, WATCH_STATE, WATCH_VALUE, type WatchKey, WatchKeySchema, type WatchStateFrame, WatchStateSchema, type WatchValueFrame, WatchValueSchema, watchContract, watchKeyString, } from './live/watch-contract.js';
18
18
  //# sourceMappingURL=live.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../src/live.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EACL,YAAY,EACZ,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,kBAAkB,EAClB,KAAK,SAAS,GACf,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,iBAAiB,EACjB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,cAAc,GACf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,WAAW,EACX,UAAU,EACV,WAAW,EACX,WAAW,EACX,KAAK,QAAQ,EACb,cAAc,EACd,KAAK,eAAe,EACpB,gBAAgB,EAChB,KAAK,eAAe,EACpB,gBAAgB,EAChB,aAAa,EACb,cAAc,GACf,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../src/live.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EACL,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,kBAAkB,EAClB,KAAK,gBAAgB,EACrB,KAAK,SAAS,GACf,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,iBAAiB,EACjB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,cAAc,GACf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,WAAW,EACX,UAAU,EACV,WAAW,EACX,WAAW,EACX,KAAK,QAAQ,EACb,cAAc,EACd,KAAK,eAAe,EACpB,gBAAgB,EAChB,KAAK,eAAe,EACpB,gBAAgB,EAChB,aAAa,EACb,cAAc,GACf,MAAM,uBAAuB,CAAC"}
@@ -1,4 +1,5 @@
1
- import type { EventDecision, EventTopicDeclaration } from '../live/events.js';
1
+ import type { PolicyDecision } from '../internal/decision.js';
2
+ import type { EventTopicDeclaration } from '../live/events.js';
2
3
  /**
3
4
  * A listener's return value.
4
5
  *
@@ -50,7 +51,7 @@ export interface EventBus<M extends Record<string, unknown> = DefaultEventMap> {
50
51
  * the same isolation would mean "counted as consent". A listener that was
51
52
  * asked and did not answer has not agreed.
52
53
  */
53
- decide<K extends keyof M & string>(event: K, data: M[K]): Promise<EventDecision>;
54
+ decide<K extends keyof M & string>(event: K, data: M[K]): Promise<PolicyDecision>;
54
55
  on<K extends keyof M & string>(event: K, handler: EventHandler<M[K]>): () => void;
55
56
  once<K extends keyof M & string>(event: K, handler: EventHandler<M[K]>): () => void;
56
57
  off<K extends keyof M & string>(event: K, handler: EventHandler<M[K]>): void;
@@ -1 +1 @@
1
- {"version":3,"file":"event-bus.d.ts","sourceRoot":"","sources":["../../src/server/event-bus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,qBAAqB,EAAkB,MAAM,gBAAgB,CAAC;AAE3F;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC;AAE7D,sEAAsE;AACtE,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEtD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe;IAC3E;;;OAGG;IACH,IAAI,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAC7D;;;;;;OAMG;IACH,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5E;;;;;;;;;;OAUG;IACH,MAAM,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IACjF,EAAE,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC;IAClF,IAAI,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC;IACpF,GAAG,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAC7E,KAAK,IAAI,IAAI,CAAC;CACf;AAYD,oCAAoC;AACpC,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1D;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAAC;CAC1D;AAkCD,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe,EAChF,OAAO,GAAE,eAAoB,GAC5B,QAAQ,CAAC,CAAC,CAAC,CAwKb"}
1
+ {"version":3,"file":"event-bus.d.ts","sourceRoot":"","sources":["../../src/server/event-bus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAoB,MAAM,sBAAsB,CAAC;AAC7E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAE5D;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC;AAE7D,sEAAsE;AACtE,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEtD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe;IAC3E;;;OAGG;IACH,IAAI,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAC7D;;;;;;OAMG;IACH,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5E;;;;;;;;;;OAUG;IACH,MAAM,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAClF,EAAE,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC;IAClF,IAAI,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC;IACpF,GAAG,CAAC,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAC7E,KAAK,IAAI,IAAI,CAAC;CACf;AAYD,oCAAoC;AACpC,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1D;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAAC;CAC1D;AAkCD,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe,EAChF,OAAO,GAAE,eAAoB,GAC5B,QAAQ,CAAC,CAAC,CAAC,CAwKb"}
package/dist/testing.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  } from "./index-x1th9s8c.js";
5
5
  import {
6
6
  createApplication
7
- } from "./index-hvftzz91.js";
7
+ } from "./index-vc1b0b1b.js";
8
8
  import"./index-2k4yrqkc.js";
9
9
  import"./index-8eywc9zv.js";
10
10
  import {