@kraken-e2e/signaling 0.1.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.
@@ -0,0 +1,43 @@
1
+ import type { SignalRecord, SignalScope } from './types.js';
2
+ export interface SignalTimeoutDetail {
3
+ readonly scope: SignalScope;
4
+ /** Unknown at transport level; the SignalBus enriches it. */
5
+ readonly subscriberId?: string | undefined;
6
+ readonly signalName: string;
7
+ readonly timeoutMs: number;
8
+ /** The cursor (afterSeq) the wait was issued with. */
9
+ readonly cursor: number;
10
+ /** Everything published in the scope so far — the "what actually happened" dump. */
11
+ readonly historySnapshot: readonly SignalRecord[];
12
+ /** Signal names in the scope within edit distance 2 of the waited name (typo diagnosis). */
13
+ readonly nearMissNames: readonly string[];
14
+ }
15
+ /** A waited-for signal never arrived within the budget. This is a TEST failure. */
16
+ export declare class SignalTimeoutError extends Error {
17
+ readonly name = "SignalTimeoutError";
18
+ readonly detail: SignalTimeoutDetail;
19
+ constructor(detail: SignalTimeoutDetail);
20
+ }
21
+ /** The scope was destroyed (or never created); the operation cannot proceed. */
22
+ export declare class ScopeClosedError extends Error {
23
+ readonly name = "ScopeClosedError";
24
+ constructor(scope: SignalScope, operation: string);
25
+ }
26
+ /** A pending wait was cancelled through its AbortSignal (e.g. failFast teardown). */
27
+ export declare class SignalWaitAbortedError extends Error {
28
+ readonly name = "SignalWaitAbortedError";
29
+ constructor(signalName: string);
30
+ }
31
+ /** Infrastructure failure (transport down/unreachable). NOT a test failure. */
32
+ export declare class TransportUnavailableError extends Error {
33
+ readonly name = "TransportUnavailableError";
34
+ }
35
+ /** The payload is not JSON-serializable or exceeds the configured size cap. */
36
+ export declare class SignalPayloadError extends Error {
37
+ readonly name = "SignalPayloadError";
38
+ }
39
+ /** API misuse (e.g. two concurrent identical waits by one subscriber — ADR-0003 D3). */
40
+ export declare class SignalUsageError extends Error {
41
+ readonly name = "SignalUsageError";
42
+ }
43
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE5D,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,6DAA6D;IAC7D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,sDAAsD;IACtD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,oFAAoF;IACpF,QAAQ,CAAC,eAAe,EAAE,SAAS,YAAY,EAAE,CAAC;IAClD,4FAA4F;IAC5F,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C;AAED,mFAAmF;AACnF,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,SAAkB,IAAI,wBAAwB;IAC9C,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;gBAEzB,MAAM,EAAE,mBAAmB;CAkBxC;AAED,gFAAgF;AAChF,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,SAAkB,IAAI,sBAAsB;gBAEhC,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM;CAMlD;AAED,qFAAqF;AACrF,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,SAAkB,IAAI,4BAA4B;gBAEtC,UAAU,EAAE,MAAM;CAG/B;AAED,+EAA+E;AAC/E,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,SAAkB,IAAI,+BAA+B;CACtD;AAED,+EAA+E;AAC/E,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,SAAkB,IAAI,wBAAwB;CAC/C;AAED,wFAAwF;AACxF,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,SAAkB,IAAI,sBAAsB;CAC7C"}
package/dist/errors.js ADDED
@@ -0,0 +1,47 @@
1
+ /** A waited-for signal never arrived within the budget. This is a TEST failure. */
2
+ export class SignalTimeoutError extends Error {
3
+ name = 'SignalTimeoutError';
4
+ detail;
5
+ constructor(detail) {
6
+ const who = detail.subscriberId ? `subscriber "${detail.subscriberId}"` : 'a subscriber';
7
+ const seen = detail.historySnapshot.length === 0
8
+ ? 'No signals were published in this scope.'
9
+ : `Signals published so far: ${detail.historySnapshot
10
+ .map((r) => `"${r.name}" by ${r.from} (seq ${r.seq})`)
11
+ .join(', ')}.`;
12
+ const nearMiss = detail.nearMissNames.length > 0
13
+ ? ` Did you mean ${detail.nearMissNames.map((n) => `"${n}"`).join(' or ')}?`
14
+ : '';
15
+ super(`Timed out after ${detail.timeoutMs}ms: ${who} was waiting for signal "${detail.signalName}" ` +
16
+ `(after seq ${detail.cursor}) in scope ${detail.scope.runId}/${detail.scope.scenarioId}. ${seen}${nearMiss}`);
17
+ this.detail = detail;
18
+ }
19
+ }
20
+ /** The scope was destroyed (or never created); the operation cannot proceed. */
21
+ export class ScopeClosedError extends Error {
22
+ name = 'ScopeClosedError';
23
+ constructor(scope, operation) {
24
+ super(`Cannot ${operation}: signal scope ${scope.runId}/${scope.scenarioId} is not open ` +
25
+ '(never created or already destroyed).');
26
+ }
27
+ }
28
+ /** A pending wait was cancelled through its AbortSignal (e.g. failFast teardown). */
29
+ export class SignalWaitAbortedError extends Error {
30
+ name = 'SignalWaitAbortedError';
31
+ constructor(signalName) {
32
+ super(`Wait for signal "${signalName}" was aborted.`);
33
+ }
34
+ }
35
+ /** Infrastructure failure (transport down/unreachable). NOT a test failure. */
36
+ export class TransportUnavailableError extends Error {
37
+ name = 'TransportUnavailableError';
38
+ }
39
+ /** The payload is not JSON-serializable or exceeds the configured size cap. */
40
+ export class SignalPayloadError extends Error {
41
+ name = 'SignalPayloadError';
42
+ }
43
+ /** API misuse (e.g. two concurrent identical waits by one subscriber — ADR-0003 D3). */
44
+ export class SignalUsageError extends Error {
45
+ name = 'SignalUsageError';
46
+ }
47
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAgBA,mFAAmF;AACnF,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IACzB,IAAI,GAAG,oBAAoB,CAAC;IACrC,MAAM,CAAsB;IAErC,YAAY,MAA2B;QACrC,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,eAAe,MAAM,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC;QACzF,MAAM,IAAI,GACR,MAAM,CAAC,eAAe,CAAC,MAAM,KAAK,CAAC;YACjC,CAAC,CAAC,0CAA0C;YAC5C,CAAC,CAAC,6BAA6B,MAAM,CAAC,eAAe;iBAChD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,GAAG,GAAG,CAAC;iBACrD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;QACvB,MAAM,QAAQ,GACZ,MAAM,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC;YAC7B,CAAC,CAAC,iBAAiB,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG;YAC5E,CAAC,CAAC,EAAE,CAAC;QACT,KAAK,CACH,mBAAmB,MAAM,CAAC,SAAS,OAAO,GAAG,4BAA4B,MAAM,CAAC,UAAU,IAAI;YAC5F,cAAc,MAAM,CAAC,MAAM,cAAc,MAAM,CAAC,KAAK,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,UAAU,KAAK,IAAI,GAAG,QAAQ,EAAE,CAC/G,CAAC;QACF,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED,gFAAgF;AAChF,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IACvB,IAAI,GAAG,kBAAkB,CAAC;IAE5C,YAAY,KAAkB,EAAE,SAAiB;QAC/C,KAAK,CACH,UAAU,SAAS,kBAAkB,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,UAAU,eAAe;YACjF,uCAAuC,CAC1C,CAAC;IACJ,CAAC;CACF;AAED,qFAAqF;AACrF,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAC7B,IAAI,GAAG,wBAAwB,CAAC;IAElD,YAAY,UAAkB;QAC5B,KAAK,CAAC,oBAAoB,UAAU,gBAAgB,CAAC,CAAC;IACxD,CAAC;CACF;AAED,+EAA+E;AAC/E,MAAM,OAAO,yBAA0B,SAAQ,KAAK;IAChC,IAAI,GAAG,2BAA2B,CAAC;CACtD;AAED,+EAA+E;AAC/E,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IACzB,IAAI,GAAG,oBAAoB,CAAC;CAC/C;AAED,wFAAwF;AACxF,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IACvB,IAAI,GAAG,kBAAkB,CAAC;CAC7C"}
@@ -0,0 +1,21 @@
1
+ import { type SignalPayload, type SignalQuery, type SignalRecord, type SignalScope, type SignalTransport, type TransportWaitOptions } from './types.js';
2
+ /**
3
+ * Reference transport: a Map-based append-only log per scope. Deliberately "as
4
+ * awkward as a network": microtask-deferred resolution (never synchronous) and
5
+ * JSON round-tripped payloads, so local tests cannot accrete behaviors a
6
+ * distributed transport can't honor (ADR-0003).
7
+ */
8
+ export declare class InMemoryTransport implements SignalTransport {
9
+ #private;
10
+ createScope(scope: SignalScope): Promise<void>;
11
+ publish(scope: SignalScope, signal: {
12
+ name: string;
13
+ from: string;
14
+ payload: SignalPayload;
15
+ }): Promise<SignalRecord>;
16
+ waitFor(scope: SignalScope, query: SignalQuery, opts: TransportWaitOptions): Promise<SignalRecord>;
17
+ history(scope: SignalScope): Promise<readonly SignalRecord[]>;
18
+ destroyScope(scope: SignalScope): Promise<void>;
19
+ ping(): Promise<void>;
20
+ }
21
+ //# sourceMappingURL=in-memory-transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-memory-transport.d.ts","sourceRoot":"","sources":["../src/in-memory-transport.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,eAAe,EAEpB,KAAK,oBAAoB,EAC1B,MAAM,YAAY,CAAC;AAwCpB;;;;;GAKG;AACH,qBAAa,iBAAkB,YAAW,eAAe;;IAWjD,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ9C,OAAO,CACX,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,aAAa,CAAA;KAAE,GAC7D,OAAO,CAAC,YAAY,CAAC;IA2BlB,OAAO,CACX,KAAK,EAAE,WAAW,EAClB,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,YAAY,CAAC;IA2DlB,OAAO,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC;IAM7D,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAY/C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAG5B"}
@@ -0,0 +1,142 @@
1
+ import { ScopeClosedError, SignalPayloadError, SignalTimeoutError, SignalWaitAbortedError, } from './errors.js';
2
+ import { nearMissNames } from './near-miss.js';
3
+ import { scopeKey, } from './types.js';
4
+ function matches(query, record) {
5
+ return (record.seq > query.afterSeq &&
6
+ record.name === query.name &&
7
+ (query.from === undefined || record.from === query.from));
8
+ }
9
+ /** Deep-copies through JSON so shared-mutable-reference bugs fail locally too (ADR-0003 D5). */
10
+ function cloneJson(payload) {
11
+ let serialized;
12
+ try {
13
+ serialized = JSON.stringify(payload ?? null);
14
+ }
15
+ catch (cause) {
16
+ throw new SignalPayloadError(`Signal payload is not JSON-serializable: ${cause instanceof Error ? cause.message : String(cause)}`);
17
+ }
18
+ if (serialized === undefined) {
19
+ throw new SignalPayloadError('Signal payload is not JSON-serializable.');
20
+ }
21
+ return JSON.parse(serialized);
22
+ }
23
+ const microtask = () => new Promise((resolve) => queueMicrotask(resolve));
24
+ /**
25
+ * Reference transport: a Map-based append-only log per scope. Deliberately "as
26
+ * awkward as a network": microtask-deferred resolution (never synchronous) and
27
+ * JSON round-tripped payloads, so local tests cannot accrete behaviors a
28
+ * distributed transport can't honor (ADR-0003).
29
+ */
30
+ export class InMemoryTransport {
31
+ #scopes = new Map();
32
+ #state(scope, operation) {
33
+ const state = this.#scopes.get(scopeKey(scope));
34
+ if (!state) {
35
+ throw new ScopeClosedError(scope, operation);
36
+ }
37
+ return state;
38
+ }
39
+ async createScope(scope) {
40
+ const key = scopeKey(scope);
41
+ if (!this.#scopes.has(key)) {
42
+ this.#scopes.set(key, { records: [], nextSeq: 1, waiters: new Set() });
43
+ }
44
+ await microtask();
45
+ }
46
+ async publish(scope, signal) {
47
+ const state = this.#state(scope, `publish signal "${signal.name}"`);
48
+ // Seq assignment is synchronous with the call (call order = total order);
49
+ // only the RESOLUTION and waiter notification are deferred.
50
+ const record = {
51
+ seq: state.nextSeq,
52
+ name: signal.name,
53
+ from: signal.from,
54
+ payload: cloneJson(signal.payload),
55
+ publishedAt: Date.now(),
56
+ };
57
+ state.nextSeq += 1;
58
+ state.records.push(record);
59
+ queueMicrotask(() => {
60
+ for (const waiter of [...state.waiters]) {
61
+ if (matches(waiter.query, record)) {
62
+ state.waiters.delete(waiter);
63
+ waiter.settle(record);
64
+ }
65
+ }
66
+ });
67
+ await microtask();
68
+ return record;
69
+ }
70
+ // Async so that even "scope closed" surfaces as a rejection, never a
71
+ // synchronous throw (the same Zalgo rule the resolutions follow).
72
+ async waitFor(scope, query, opts) {
73
+ const state = this.#state(scope, `wait for signal "${query.name}"`);
74
+ return new Promise((resolve, reject) => {
75
+ const existing = state.records.find((record) => matches(query, record));
76
+ if (existing) {
77
+ // Replay-first, but never synchronously (Zalgo prevention).
78
+ queueMicrotask(() => resolve(existing));
79
+ return;
80
+ }
81
+ if (opts.signal?.aborted) {
82
+ queueMicrotask(() => reject(new SignalWaitAbortedError(query.name)));
83
+ return;
84
+ }
85
+ let cleanup = () => { };
86
+ const waiter = {
87
+ query,
88
+ settle(record) {
89
+ cleanup();
90
+ resolve(record);
91
+ },
92
+ fail(error) {
93
+ cleanup();
94
+ reject(error);
95
+ },
96
+ };
97
+ const timer = setTimeout(() => {
98
+ state.waiters.delete(waiter);
99
+ cleanup();
100
+ reject(new SignalTimeoutError({
101
+ scope,
102
+ signalName: query.name,
103
+ timeoutMs: opts.timeoutMs,
104
+ cursor: query.afterSeq,
105
+ historySnapshot: [...state.records],
106
+ nearMissNames: nearMissNames(query.name, state.records),
107
+ }));
108
+ }, opts.timeoutMs);
109
+ const onAbort = () => {
110
+ state.waiters.delete(waiter);
111
+ cleanup();
112
+ reject(new SignalWaitAbortedError(query.name));
113
+ };
114
+ opts.signal?.addEventListener('abort', onAbort, { once: true });
115
+ cleanup = () => {
116
+ clearTimeout(timer);
117
+ opts.signal?.removeEventListener('abort', onAbort);
118
+ };
119
+ state.waiters.add(waiter);
120
+ });
121
+ }
122
+ async history(scope) {
123
+ const state = this.#state(scope, 'read signal history');
124
+ await microtask();
125
+ return [...state.records];
126
+ }
127
+ async destroyScope(scope) {
128
+ const state = this.#scopes.get(scopeKey(scope));
129
+ if (state) {
130
+ this.#scopes.delete(scopeKey(scope));
131
+ for (const waiter of [...state.waiters]) {
132
+ waiter.fail(new ScopeClosedError(scope, `wait for signal "${waiter.query.name}"`));
133
+ }
134
+ state.waiters.clear();
135
+ }
136
+ await microtask();
137
+ }
138
+ async ping() {
139
+ await microtask();
140
+ }
141
+ }
142
+ //# sourceMappingURL=in-memory-transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-memory-transport.js","sourceRoot":"","sources":["../src/in-memory-transport.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAML,QAAQ,GAET,MAAM,YAAY,CAAC;AAcpB,SAAS,OAAO,CAAC,KAAkB,EAAE,MAAoB;IACvD,OAAO,CACL,MAAM,CAAC,GAAG,GAAG,KAAK,CAAC,QAAQ;QAC3B,MAAM,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI;QAC1B,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC,CACzD,CAAC;AACJ,CAAC;AAED,gGAAgG;AAChG,SAAS,SAAS,CAAC,OAAsB;IACvC,IAAI,UAA8B,CAAC;IACnC,IAAI,CAAC;QACH,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC;IAC/C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,kBAAkB,CAC1B,4CAA4C,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACrG,CAAC;IACJ,CAAC;IACD,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,kBAAkB,CAAC,0CAA0C,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,CAAkB,CAAC;AACjD,CAAC;AAED,MAAM,SAAS,GAAG,GAAkB,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;AAEzF;;;;;GAKG;AACH,MAAM,OAAO,iBAAiB;IACnB,OAAO,GAAG,IAAI,GAAG,EAAsB,CAAC;IAEjD,MAAM,CAAC,KAAkB,EAAE,SAAiB;QAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;QAChD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,gBAAgB,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QAC/C,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,KAAkB;QAClC,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC5B,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC;QACzE,CAAC;QACD,MAAM,SAAS,EAAE,CAAC;IACpB,CAAC;IAED,KAAK,CAAC,OAAO,CACX,KAAkB,EAClB,MAA8D;QAE9D,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,mBAAmB,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC;QACpE,0EAA0E;QAC1E,4DAA4D;QAC5D,MAAM,MAAM,GAAiB;YAC3B,GAAG,EAAE,KAAK,CAAC,OAAO;YAClB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,OAAO,EAAE,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC;YAClC,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE;SACxB,CAAC;QACF,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;QACnB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC3B,cAAc,CAAC,GAAG,EAAE;YAClB,KAAK,MAAM,MAAM,IAAI,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;gBACxC,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC;oBAClC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;oBAC7B,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBACxB,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;QACH,MAAM,SAAS,EAAE,CAAC;QAClB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,qEAAqE;IACrE,kEAAkE;IAClE,KAAK,CAAC,OAAO,CACX,KAAkB,EAClB,KAAkB,EAClB,IAA0B;QAE1B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,oBAAoB,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC;QAEpE,OAAO,IAAI,OAAO,CAAe,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACnD,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;YACxE,IAAI,QAAQ,EAAE,CAAC;gBACb,4DAA4D;gBAC5D,cAAc,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;gBACxC,OAAO;YACT,CAAC;YACD,IAAI,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;gBACzB,cAAc,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,sBAAsB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBACrE,OAAO;YACT,CAAC;YAED,IAAI,OAAO,GAAG,GAAS,EAAE,GAAE,CAAC,CAAC;YAC7B,MAAM,MAAM,GAAW;gBACrB,KAAK;gBACL,MAAM,CAAC,MAAM;oBACX,OAAO,EAAE,CAAC;oBACV,OAAO,CAAC,MAAM,CAAC,CAAC;gBAClB,CAAC;gBACD,IAAI,CAAC,KAAK;oBACR,OAAO,EAAE,CAAC;oBACV,MAAM,CAAC,KAAK,CAAC,CAAC;gBAChB,CAAC;aACF,CAAC;YAEF,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC5B,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBAC7B,OAAO,EAAE,CAAC;gBACV,MAAM,CACJ,IAAI,kBAAkB,CAAC;oBACrB,KAAK;oBACL,UAAU,EAAE,KAAK,CAAC,IAAI;oBACtB,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,MAAM,EAAE,KAAK,CAAC,QAAQ;oBACtB,eAAe,EAAE,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC;oBACnC,aAAa,EAAE,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC;iBACxD,CAAC,CACH,CAAC;YACJ,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;YAEnB,MAAM,OAAO,GAAG,GAAS,EAAE;gBACzB,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBAC7B,OAAO,EAAE,CAAC;gBACV,MAAM,CAAC,IAAI,sBAAsB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YACjD,CAAC,CAAC;YACF,IAAI,CAAC,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAEhE,OAAO,GAAG,GAAG,EAAE;gBACb,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,IAAI,CAAC,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACrD,CAAC,CAAC;YAEF,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,KAAkB;QAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,qBAAqB,CAAC,CAAC;QACxD,MAAM,SAAS,EAAE,CAAC;QAClB,OAAO,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,KAAkB;QACnC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;QAChD,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;YACrC,KAAK,MAAM,MAAM,IAAI,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;gBACxC,MAAM,CAAC,IAAI,CAAC,IAAI,gBAAgB,CAAC,KAAK,EAAE,oBAAoB,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;YACrF,CAAC;YACD,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;QACD,MAAM,SAAS,EAAE,CAAC;IACpB,CAAC;IAED,KAAK,CAAC,IAAI;QACR,MAAM,SAAS,EAAE,CAAC;IACpB,CAAC;CACF"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @kraken-e2e/signaling — standalone multi-actor signal log (ADR-0001 §5.7, ADR-0003).
3
+ *
4
+ * A scoped append-only log with per-(subscriber, signal-name) cursors:
5
+ * publish-before-wait always delivers (replay-first), repeated signals count
6
+ * FIFO per subscriber, and distinct subscribers each receive every record
7
+ * (broadcast). Zero runtime dependencies; usable without Gherkin or WebdriverIO.
8
+ *
9
+ * The transport conformance suite lives at `@kraken-e2e/signaling/conformance`.
10
+ */
11
+ export { ActorSignals, ScopedSignals, SignalBus, type SignalBusOptions, type SignalHandle, type WaitOptions, } from './bus.js';
12
+ export { type ChaosOperation, type ChaosOptions, ChaosTransport, } from './chaos-transport.js';
13
+ export { ScopeClosedError, SignalPayloadError, type SignalTimeoutDetail, SignalTimeoutError, SignalUsageError, SignalWaitAbortedError, TransportUnavailableError, } from './errors.js';
14
+ export { InMemoryTransport } from './in-memory-transport.js';
15
+ export { levenshtein, nearMissNames } from './near-miss.js';
16
+ export { type SignalPayload, type SignalQuery, type SignalRecord, type SignalScope, type SignalTransport, scopeKey, type TransportWaitOptions, } from './types.js';
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,YAAY,EACZ,aAAa,EACb,SAAS,EACT,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,KAAK,WAAW,GACjB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,cAAc,GACf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,mBAAmB,EACxB,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,yBAAyB,GAC1B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,QAAQ,EACR,KAAK,oBAAoB,GAC1B,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @kraken-e2e/signaling — standalone multi-actor signal log (ADR-0001 §5.7, ADR-0003).
3
+ *
4
+ * A scoped append-only log with per-(subscriber, signal-name) cursors:
5
+ * publish-before-wait always delivers (replay-first), repeated signals count
6
+ * FIFO per subscriber, and distinct subscribers each receive every record
7
+ * (broadcast). Zero runtime dependencies; usable without Gherkin or WebdriverIO.
8
+ *
9
+ * The transport conformance suite lives at `@kraken-e2e/signaling/conformance`.
10
+ */
11
+ export { ActorSignals, ScopedSignals, SignalBus, } from './bus.js';
12
+ export { ChaosTransport, } from './chaos-transport.js';
13
+ export { ScopeClosedError, SignalPayloadError, SignalTimeoutError, SignalUsageError, SignalWaitAbortedError, TransportUnavailableError, } from './errors.js';
14
+ export { InMemoryTransport } from './in-memory-transport.js';
15
+ export { levenshtein, nearMissNames } from './near-miss.js';
16
+ export { scopeKey, } from './types.js';
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,YAAY,EACZ,aAAa,EACb,SAAS,GAIV,MAAM,UAAU,CAAC;AAClB,OAAO,EAGL,cAAc,GACf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAElB,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,yBAAyB,GAC1B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAML,QAAQ,GAET,MAAM,YAAY,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { SignalRecord } from './types.js';
2
+ /** Classic dynamic-programming Levenshtein distance (small inputs only). */
3
+ export declare function levenshtein(a: string, b: string): number;
4
+ /** Signal names in the history within edit distance 2 of the waited name (typo diagnosis). */
5
+ export declare function nearMissNames(waited: string, history: readonly SignalRecord[]): readonly string[];
6
+ //# sourceMappingURL=near-miss.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"near-miss.d.ts","sourceRoot":"","sources":["../src/near-miss.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAkBxD;AAED,8FAA8F;AAC9F,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,SAAS,MAAM,EAAE,CAQjG"}
@@ -0,0 +1,30 @@
1
+ /** Classic dynamic-programming Levenshtein distance (small inputs only). */
2
+ export function levenshtein(a, b) {
3
+ if (a === b)
4
+ return 0;
5
+ const rows = a.length + 1;
6
+ const cols = b.length + 1;
7
+ const distance = Array.from({ length: rows * cols }, () => 0);
8
+ for (let i = 0; i < rows; i += 1)
9
+ distance[i * cols] = i;
10
+ for (let j = 0; j < cols; j += 1)
11
+ distance[j] = j;
12
+ for (let i = 1; i < rows; i += 1) {
13
+ for (let j = 1; j < cols; j += 1) {
14
+ const substitutionCost = a[i - 1] === b[j - 1] ? 0 : 1;
15
+ distance[i * cols + j] = Math.min((distance[(i - 1) * cols + j] ?? 0) + 1, (distance[i * cols + (j - 1)] ?? 0) + 1, (distance[(i - 1) * cols + (j - 1)] ?? 0) + substitutionCost);
16
+ }
17
+ }
18
+ return distance[rows * cols - 1] ?? 0;
19
+ }
20
+ /** Signal names in the history within edit distance 2 of the waited name (typo diagnosis). */
21
+ export function nearMissNames(waited, history) {
22
+ const seen = new Set();
23
+ for (const record of history) {
24
+ if (record.name !== waited && levenshtein(record.name, waited) <= 2) {
25
+ seen.add(record.name);
26
+ }
27
+ }
28
+ return [...seen];
29
+ }
30
+ //# sourceMappingURL=near-miss.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"near-miss.js","sourceRoot":"","sources":["../src/near-miss.ts"],"names":[],"mappings":"AAEA,4EAA4E;AAC5E,MAAM,UAAU,WAAW,CAAC,CAAS,EAAE,CAAS;IAC9C,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACtB,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IAC1B,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IAC1B,MAAM,QAAQ,GAAa,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;IACxE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC;QAAE,QAAQ,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;IACzD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC;QAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAClD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACjC,MAAM,gBAAgB,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACvD,QAAQ,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAC/B,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EACvC,CAAC,QAAQ,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EACvC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,gBAAgB,CAC7D,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,OAAgC;IAC5E,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,IAAI,WAAW,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YACpE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;AACnB,CAAC"}
@@ -0,0 +1,46 @@
1
+ import { type SignalPayload, type SignalQuery, type SignalRecord, type SignalScope, type SignalTransport, type TransportWaitOptions } from './types.js';
2
+ /** The node-redis client slice this transport consumes (mock/type firewall). */
3
+ export interface RedisClientLike {
4
+ connect(): Promise<unknown>;
5
+ close(): Promise<unknown> | void;
6
+ on(event: string, listener: (...args: unknown[]) => void): unknown;
7
+ eval(script: string, options: {
8
+ keys: string[];
9
+ arguments: string[];
10
+ }): Promise<unknown>;
11
+ xRange(key: string, start: string, end: string): Promise<Array<{
12
+ id: string;
13
+ message: Record<string, string>;
14
+ }>>;
15
+ exists(key: string): Promise<number>;
16
+ set(key: string, value: string): Promise<unknown>;
17
+ del(keys: string[]): Promise<unknown>;
18
+ ping(): Promise<unknown>;
19
+ }
20
+ export interface RedisTransportOptions {
21
+ /** redis:// URL (ignored when `client` is provided). */
22
+ readonly url?: string;
23
+ /** Pre-built client (tests, custom config). Must NOT be connected yet. */
24
+ readonly client?: RedisClientLike;
25
+ /** Key namespace; instances with different prefixes are fully isolated. */
26
+ readonly keyPrefix?: string;
27
+ /** waitFor poll interval. */
28
+ readonly pollMs?: number;
29
+ }
30
+ export declare class RedisStreamTransport implements SignalTransport {
31
+ #private;
32
+ constructor(options?: RedisTransportOptions);
33
+ createScope(scope: SignalScope): Promise<void>;
34
+ publish(scope: SignalScope, signal: {
35
+ name: string;
36
+ from: string;
37
+ payload: SignalPayload;
38
+ }): Promise<SignalRecord>;
39
+ waitFor(scope: SignalScope, query: SignalQuery, opts: TransportWaitOptions): Promise<SignalRecord>;
40
+ history(scope: SignalScope): Promise<readonly SignalRecord[]>;
41
+ destroyScope(scope: SignalScope): Promise<void>;
42
+ ping(): Promise<void>;
43
+ /** Closes the underlying client (transport-level teardown, not per-scope). */
44
+ close(): Promise<void>;
45
+ }
46
+ //# sourceMappingURL=redis-transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-transport.d.ts","sourceRoot":"","sources":["../src/redis-transport.ts"],"names":[],"mappings":"AAsBA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,eAAe,EAEpB,KAAK,oBAAoB,EAC1B,MAAM,YAAY,CAAC;AAEpB,gFAAgF;AAChF,MAAM,WAAW,eAAe;IAC9B,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5B,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;IACjC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC;IACnE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzF,MAAM,CACJ,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC,CAAC,CAAC;IACnE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACrC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClD,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1B;AAED,MAAM,WAAW,qBAAqB;IACpC,wDAAwD;IACxD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,CAAC,EAAE,eAAe,CAAC;IAClC,2EAA2E;IAC3E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,6BAA6B;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAYD,qBAAa,oBAAqB,YAAW,eAAe;;gBAO9C,OAAO,GAAE,qBAA0B;IA0DzC,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAK9C,OAAO,CACX,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,aAAa,CAAA;KAAE,GAC7D,OAAO,CAAC,YAAY,CAAC;IA4ClB,OAAO,CACX,KAAK,EAAE,WAAW,EAClB,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,YAAY,CAAC;IA8BlB,OAAO,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC;IAM7D,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ/C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B,8EAA8E;IACxE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAO7B"}
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Distributed SignalTransport over Redis Streams (ADR-0001 D11: node-redis,
3
+ * never ioredis; live-verified against node-redis 6.1 / Redis 8, 2026-07-05).
4
+ *
5
+ * Mapping: one stream per scope; Kraken's monotonic `seq` is assigned by an
6
+ * atomic Lua INCR+XADD (explicit stream id `${seq}-0`), so stream order IS
7
+ * seq order and catch-up reads are `XRANGE (seq-0 +` (exclusive start).
8
+ * waitFor polls (default 15ms) — dumb-log semantics, no per-waiter blocking
9
+ * connections; the SignalBus above owns cursors and FIFO fairness.
10
+ *
11
+ * This module is a SUBPATH export (`@kraken-e2e/signaling/redis`); `redis` is an
12
+ * OPTIONAL peer dependency loaded dynamically on first use — installing
13
+ * Kraken never drags Redis in (same import-safety rule as the drivers).
14
+ */
15
+ import { ScopeClosedError, SignalPayloadError, SignalTimeoutError, SignalWaitAbortedError, TransportUnavailableError, } from './errors.js';
16
+ import { nearMissNames } from './near-miss.js';
17
+ import { scopeKey, } from './types.js';
18
+ /** Atomic seq assignment + append: INCR the scope counter, XADD at `${seq}-0`. */
19
+ const PUBLISH_LUA = `
20
+ local seq = redis.call('INCR', KEYS[2])
21
+ redis.call('XADD', KEYS[1], seq .. '-0',
22
+ 'name', ARGV[1], 'from', ARGV[2], 'payload', ARGV[3], 'publishedAt', ARGV[4])
23
+ return seq
24
+ `;
25
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
26
+ export class RedisStreamTransport {
27
+ #options;
28
+ #prefix;
29
+ #pollMs;
30
+ #client;
31
+ #connecting;
32
+ constructor(options = {}) {
33
+ this.#options = options;
34
+ this.#prefix = options.keyPrefix ?? 'kraken';
35
+ this.#pollMs = options.pollMs ?? 15;
36
+ }
37
+ async #connected() {
38
+ if (this.#client)
39
+ return this.#client;
40
+ this.#connecting ??= (async () => {
41
+ try {
42
+ let client = this.#options.client;
43
+ if (!client) {
44
+ const redisSpecifier = 'redis';
45
+ const redis = (await import(redisSpecifier));
46
+ client = redis.createClient(this.#options.url !== undefined ? { url: this.#options.url } : {});
47
+ }
48
+ // MANDATORY: without an error listener node-redis throws uncaught.
49
+ client.on('error', () => { });
50
+ await client.connect();
51
+ this.#client = client;
52
+ return client;
53
+ }
54
+ catch (cause) {
55
+ this.#connecting = undefined;
56
+ throw new TransportUnavailableError(`Redis transport could not connect${this.#options.url ? ` to ${this.#options.url}` : ''}: ${cause instanceof Error ? cause.message : String(cause)}`);
57
+ }
58
+ })();
59
+ return this.#connecting;
60
+ }
61
+ #keys(scope) {
62
+ const base = `${this.#prefix}:${scopeKey(scope)}`;
63
+ return { stream: `${base}:stream`, seq: `${base}:seq`, open: `${base}:open` };
64
+ }
65
+ async #assertOpen(client, scope, operation) {
66
+ if ((await client.exists(this.#keys(scope).open)) !== 1) {
67
+ throw new ScopeClosedError(scope, operation);
68
+ }
69
+ }
70
+ #toRecord(entry) {
71
+ return {
72
+ seq: Number(entry.id.split('-')[0]),
73
+ name: entry.message['name'] ?? '',
74
+ from: entry.message['from'] ?? '',
75
+ payload: JSON.parse(entry.message['payload'] ?? 'null'),
76
+ publishedAt: Number(entry.message['publishedAt'] ?? 0),
77
+ };
78
+ }
79
+ async createScope(scope) {
80
+ const client = await this.#connected();
81
+ await client.set(this.#keys(scope).open, '1');
82
+ }
83
+ async publish(scope, signal) {
84
+ const client = await this.#connected();
85
+ await this.#assertOpen(client, scope, `publish signal "${signal.name}"`);
86
+ let serialized;
87
+ try {
88
+ serialized = JSON.stringify(signal.payload ?? null);
89
+ }
90
+ catch (cause) {
91
+ throw new SignalPayloadError(`Signal payload is not JSON-serializable: ${cause instanceof Error ? cause.message : String(cause)}`);
92
+ }
93
+ if (serialized === undefined) {
94
+ throw new SignalPayloadError('Signal payload is not JSON-serializable.');
95
+ }
96
+ const publishedAt = Date.now();
97
+ const keys = this.#keys(scope);
98
+ const seq = Number(await client.eval(PUBLISH_LUA, {
99
+ keys: [keys.stream, keys.seq],
100
+ arguments: [signal.name, signal.from, serialized, String(publishedAt)],
101
+ }));
102
+ return {
103
+ seq,
104
+ name: signal.name,
105
+ from: signal.from,
106
+ payload: JSON.parse(serialized),
107
+ publishedAt,
108
+ };
109
+ }
110
+ async #readAfter(client, scope, afterSeq) {
111
+ const entries = await client.xRange(this.#keys(scope).stream, afterSeq > 0 ? `(${afterSeq}-0` : '-', '+');
112
+ return entries.map((entry) => this.#toRecord(entry));
113
+ }
114
+ async waitFor(scope, query, opts) {
115
+ const client = await this.#connected();
116
+ const deadline = Date.now() + opts.timeoutMs;
117
+ for (;;) {
118
+ await this.#assertOpen(client, scope, `wait for signal "${query.name}"`);
119
+ if (opts.signal?.aborted) {
120
+ throw new SignalWaitAbortedError(query.name);
121
+ }
122
+ const records = await this.#readAfter(client, scope, query.afterSeq);
123
+ const match = records.find((record) => record.name === query.name && (query.from === undefined || record.from === query.from));
124
+ if (match)
125
+ return match;
126
+ const remaining = deadline - Date.now();
127
+ if (remaining <= 0) {
128
+ const historySnapshot = await this.history(scope);
129
+ throw new SignalTimeoutError({
130
+ scope,
131
+ signalName: query.name,
132
+ timeoutMs: opts.timeoutMs,
133
+ cursor: query.afterSeq,
134
+ historySnapshot,
135
+ nearMissNames: nearMissNames(query.name, historySnapshot),
136
+ });
137
+ }
138
+ await sleep(Math.min(this.#pollMs, remaining));
139
+ }
140
+ }
141
+ async history(scope) {
142
+ const client = await this.#connected();
143
+ await this.#assertOpen(client, scope, 'read signal history');
144
+ return this.#readAfter(client, scope, 0);
145
+ }
146
+ async destroyScope(scope) {
147
+ const client = await this.#connected();
148
+ const keys = this.#keys(scope);
149
+ // Pending waiters observe the missing :open flag on their next poll and
150
+ // reject with ScopeClosedError — no server-side waiter registry to clear.
151
+ await client.del([keys.open, keys.stream, keys.seq]);
152
+ }
153
+ async ping() {
154
+ try {
155
+ const client = await this.#connected();
156
+ await client.ping();
157
+ }
158
+ catch (cause) {
159
+ if (cause instanceof TransportUnavailableError)
160
+ throw cause;
161
+ throw new TransportUnavailableError(`Redis ping failed: ${cause instanceof Error ? cause.message : String(cause)}`);
162
+ }
163
+ }
164
+ /** Closes the underlying client (transport-level teardown, not per-scope). */
165
+ async close() {
166
+ if (this.#client) {
167
+ await this.#client.close();
168
+ this.#client = undefined;
169
+ this.#connecting = undefined;
170
+ }
171
+ }
172
+ }
173
+ //# sourceMappingURL=redis-transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-transport.js","sourceRoot":"","sources":["../src/redis-transport.ts"],"names":[],"mappings":"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"}