@ianmenethil/zp-observer 6.0.0 → 6.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.
Files changed (107) hide show
  1. package/README.md +19 -276
  2. package/dist/adapters/browser-lifecycle-adapter.cjs +51 -0
  3. package/dist/adapters/browser-lifecycle-adapter.js +48 -0
  4. package/dist/adapters/iframe-detector-adapter.cjs +108 -0
  5. package/dist/adapters/iframe-detector-adapter.js +106 -0
  6. package/dist/client/create-telemetry-client.cjs +136 -0
  7. package/dist/client/create-telemetry-client.js +134 -0
  8. package/dist/client/state-machine.cjs +20 -0
  9. package/dist/client/state-machine.js +18 -0
  10. package/dist/diagnostics/diagnostics-buffer.cjs +34 -0
  11. package/dist/diagnostics/diagnostics-buffer.js +32 -0
  12. package/dist/diagnostics/preflight.cjs +36 -0
  13. package/dist/diagnostics/preflight.js +34 -0
  14. package/dist/events/envelope.cjs +23 -0
  15. package/dist/events/envelope.js +21 -0
  16. package/dist/index.cjs +23 -0
  17. package/dist/index.d.ts +230 -0
  18. package/dist/index.js +10 -0
  19. package/dist/persistence/local-storage-outbox.cjs +56 -0
  20. package/dist/persistence/local-storage-outbox.js +54 -0
  21. package/dist/persistence/memory-outbox.cjs +23 -0
  22. package/dist/persistence/memory-outbox.js +21 -0
  23. package/dist/runtime/event-pipeline.cjs +64 -0
  24. package/dist/runtime/event-pipeline.js +62 -0
  25. package/dist/runtime/heartbeat-scheduler.cjs +46 -0
  26. package/dist/runtime/heartbeat-scheduler.js +44 -0
  27. package/dist/runtime/session-manager.cjs +47 -0
  28. package/dist/runtime/session-manager.js +45 -0
  29. package/dist/transport/beacon.cjs +14 -0
  30. package/dist/transport/beacon.js +12 -0
  31. package/dist/transport/callback-transport.cjs +19 -0
  32. package/dist/transport/callback-transport.js +17 -0
  33. package/dist/transport/http-transport.cjs +62 -0
  34. package/dist/transport/http-transport.js +60 -0
  35. package/dist/types/internal.cjs +3 -0
  36. package/dist/types/internal.js +0 -0
  37. package/dist/types/public.cjs +3 -0
  38. package/dist/types/public.js +0 -0
  39. package/dist/utils/ids.cjs +20 -0
  40. package/dist/utils/ids.js +17 -0
  41. package/dist/utils/safe-globals.cjs +11 -0
  42. package/dist/utils/safe-globals.js +9 -0
  43. package/dist/version.cjs +5 -0
  44. package/dist/version.js +2 -0
  45. package/package.json +29 -89
  46. package/PRIVACY.md +0 -67
  47. package/dist/auto-patch.cjs +0 -171
  48. package/dist/auto-patch.cjs.map +0 -7
  49. package/dist/auto-patch.mjs +0 -148
  50. package/dist/auto-patch.mjs.map +0 -7
  51. package/dist/session.cjs +0 -1186
  52. package/dist/session.cjs.map +0 -7
  53. package/dist/session.mjs +0 -1163
  54. package/dist/session.mjs.map +0 -7
  55. package/dist/types/auto-patch.d.ts +0 -9
  56. package/dist/types/auto-patch.d.ts.map +0 -1
  57. package/dist/types/core/beacon.d.ts +0 -6
  58. package/dist/types/core/beacon.d.ts.map +0 -1
  59. package/dist/types/core/detection.d.ts +0 -34
  60. package/dist/types/core/detection.d.ts.map +0 -1
  61. package/dist/types/core/event-bus.d.ts +0 -21
  62. package/dist/types/core/event-bus.d.ts.map +0 -1
  63. package/dist/types/core/experimental.d.ts +0 -35
  64. package/dist/types/core/experimental.d.ts.map +0 -1
  65. package/dist/types/core/heartbeat.d.ts +0 -32
  66. package/dist/types/core/heartbeat.d.ts.map +0 -1
  67. package/dist/types/core/lifecycle.d.ts +0 -31
  68. package/dist/types/core/lifecycle.d.ts.map +0 -1
  69. package/dist/types/core/observer.d.ts +0 -10
  70. package/dist/types/core/observer.d.ts.map +0 -1
  71. package/dist/types/core/outbox.d.ts +0 -20
  72. package/dist/types/core/outbox.d.ts.map +0 -1
  73. package/dist/types/core/random.d.ts +0 -8
  74. package/dist/types/core/random.d.ts.map +0 -1
  75. package/dist/types/core/shortcode.d.ts +0 -17
  76. package/dist/types/core/shortcode.d.ts.map +0 -1
  77. package/dist/types/core/types.d.ts +0 -292
  78. package/dist/types/core/types.d.ts.map +0 -1
  79. package/dist/types/diagnostics/preflight.d.ts +0 -17
  80. package/dist/types/diagnostics/preflight.d.ts.map +0 -1
  81. package/dist/types/index.d.ts +0 -41
  82. package/dist/types/index.d.ts.map +0 -1
  83. package/dist/types/integration/devicefp-bridge.d.ts +0 -31
  84. package/dist/types/integration/devicefp-bridge.d.ts.map +0 -1
  85. package/dist/types/integration/hpp-bridge.d.ts +0 -13
  86. package/dist/types/integration/hpp-bridge.d.ts.map +0 -1
  87. package/dist/types/integration/zenpay-auto-patch.d.ts +0 -28
  88. package/dist/types/integration/zenpay-auto-patch.d.ts.map +0 -1
  89. package/dist/types/outcome.d.ts +0 -20
  90. package/dist/types/outcome.d.ts.map +0 -1
  91. package/dist/types/session.d.ts +0 -54
  92. package/dist/types/session.d.ts.map +0 -1
  93. package/dist/types/transport/callback-transport.d.ts +0 -17
  94. package/dist/types/transport/callback-transport.d.ts.map +0 -1
  95. package/dist/types/transport/http-transport.d.ts +0 -30
  96. package/dist/types/transport/http-transport.d.ts.map +0 -1
  97. package/dist/types/umd.d.ts +0 -16
  98. package/dist/types/umd.d.ts.map +0 -1
  99. package/dist/zp-observer.cjs +0 -1375
  100. package/dist/zp-observer.cjs.map +0 -7
  101. package/dist/zp-observer.js +0 -1377
  102. package/dist/zp-observer.js.map +0 -7
  103. package/dist/zp-observer.min.js +0 -2
  104. package/dist/zp-observer.min.js.map +0 -7
  105. package/dist/zp-observer.min.obf.js +0 -1
  106. package/dist/zp-observer.mjs +0 -1352
  107. package/dist/zp-observer.mjs.map +0 -7
@@ -0,0 +1,230 @@
1
+ export type TelemetryState =
2
+ | 'idle'
3
+ | 'starting'
4
+ | 'active'
5
+ | 'backgrounded'
6
+ | 'degraded'
7
+ | 'closing'
8
+ | 'closed'
9
+ | 'abandoned';
10
+
11
+ export interface TelemetryEventMap {
12
+ 'session.started': {};
13
+ 'session.stopped': { reason: string };
14
+ 'checkout.session_init': { provider: 'zenpay' | 'custom' | 'unknown' };
15
+ 'checkout.iframe_detected': { iframeSrc: string | null };
16
+ 'checkout.modal_opened': { iframeSrc: string | null };
17
+ 'checkout.modal_closed': { reason: string };
18
+ 'fingerprint.started': { attempt: number };
19
+ 'fingerprint.cache_hit': { ageMs: number };
20
+ 'fingerprint.succeeded': {};
21
+ 'fingerprint.failed': { error: string; attempt: number };
22
+ 'merchant.outcome_reported': {
23
+ outcome: 'success' | 'declined' | 'abandoned' | 'error' | 'unknown';
24
+ };
25
+ 'transport.heartbeat': { missedBeats: number };
26
+ 'diagnostic.preflight': { ok: boolean; latencyMs: number };
27
+ }
28
+
29
+ export type TelemetryEventKind = keyof TelemetryEventMap;
30
+ export type TelemetryEventPayload<K extends TelemetryEventKind> = TelemetryEventMap[K];
31
+
32
+ export interface TelemetryContext {
33
+ pageUrl?: string;
34
+ referrer?: string;
35
+ userAgent?: string;
36
+ visibilityState?: string;
37
+ online?: boolean | null;
38
+ }
39
+
40
+ export interface TelemetryEnvelope<
41
+ K extends TelemetryEventKind = TelemetryEventKind,
42
+ P extends TelemetryEventPayload<K> = TelemetryEventPayload<K>,
43
+ > {
44
+ schemaVersion: number;
45
+ libraryVersion: string;
46
+ eventId: string;
47
+ sequence: number;
48
+ kind: K;
49
+ sessionId: string;
50
+ correlationId: string;
51
+ timestamp: number;
52
+ source: 'browser';
53
+ payload: P;
54
+ context: TelemetryContext;
55
+ }
56
+
57
+ export interface TelemetryTransportResult {
58
+ ok: boolean;
59
+ status?: number;
60
+ error?: string;
61
+ }
62
+
63
+ export interface TelemetryTransport {
64
+ send(envelope: TelemetryEnvelope): Promise<TelemetryTransportResult>;
65
+ }
66
+
67
+ export type PersistenceMode = 'memory' | 'localStorage' | 'none';
68
+
69
+ export interface PersistencePolicy {
70
+ shouldPersist(kind: TelemetryEventKind): boolean;
71
+ }
72
+
73
+ export interface TelemetryOutbox {
74
+ push(envelope: TelemetryEnvelope): void;
75
+ remove(eventId: string): void;
76
+ pending(): TelemetryEnvelope[];
77
+ clear(): void;
78
+ }
79
+
80
+ export interface TelemetryDiagnosticsEntry {
81
+ level: 'info' | 'warn' | 'error';
82
+ message: string;
83
+ timestamp: number;
84
+ details?: Record<string, unknown>;
85
+ }
86
+
87
+ export interface TelemetryDiagnosticsSnapshot {
88
+ state: TelemetryState;
89
+ sessionId: string;
90
+ correlationId: string;
91
+ queueDepth: number;
92
+ delivery: {
93
+ attempted: number;
94
+ succeeded: number;
95
+ failed: number;
96
+ };
97
+ recentEnvelopes: TelemetryEnvelope[];
98
+ entries: TelemetryDiagnosticsEntry[];
99
+ }
100
+
101
+ export interface TelemetryStateSnapshot {
102
+ state: TelemetryState;
103
+ sessionId: string;
104
+ correlationId: string;
105
+ startedAt: number | null;
106
+ stoppedAt: number | null;
107
+ eventCount: number;
108
+ elapsedMs: number;
109
+ }
110
+
111
+ export type TelemetryMiddleware = (
112
+ envelope: TelemetryEnvelope,
113
+ ) => TelemetryEnvelope | null | Promise<TelemetryEnvelope | null>;
114
+
115
+ export interface CreateTelemetryClientOptions {
116
+ session: {
117
+ sessionId: string;
118
+ correlationId?: string;
119
+ };
120
+ transport: TelemetryTransport;
121
+ outbox?: TelemetryOutbox;
122
+ persistence?: PersistenceMode;
123
+ middleware?: TelemetryMiddleware[];
124
+ persistencePolicy?: PersistencePolicy;
125
+ context?: TelemetryContext;
126
+ heartbeat?: {
127
+ intervalMs: number;
128
+ missThreshold: number;
129
+ };
130
+ now?: () => number;
131
+ debug?: boolean;
132
+ }
133
+
134
+ export interface TelemetryClient {
135
+ readonly sessionId: string;
136
+ readonly correlationId: string;
137
+ start(): void;
138
+ stop(reason?: string): void;
139
+ emit<K extends TelemetryEventKind>(kind: K, payload: TelemetryEventPayload<K>): Promise<void>;
140
+ flush(): Promise<void>;
141
+ getState(): TelemetryStateSnapshot;
142
+ diagnostics: {
143
+ snapshot(): TelemetryDiagnosticsSnapshot;
144
+ };
145
+ }
146
+
147
+ export interface CallbackTransportOptions {
148
+ onSend?: (
149
+ envelope: TelemetryEnvelope,
150
+ ) => TelemetryTransportResult | Promise<TelemetryTransportResult>;
151
+ }
152
+
153
+ export interface HttpTransportOptions {
154
+ endpoint: string;
155
+ headers?: Record<string, string> | (() => Record<string, string>);
156
+ fetch?: typeof fetch;
157
+ timeoutMs?: number;
158
+ credentials?: RequestCredentials;
159
+ }
160
+
161
+ export interface MemoryOutboxOptions {
162
+ maxEntries?: number;
163
+ }
164
+
165
+ export interface LocalStorageOutboxOptions {
166
+ storageKey?: string;
167
+ maxEntries?: number;
168
+ storage?: Pick<Storage, 'getItem' | 'setItem'>;
169
+ onUnavailable?: (error: string) => void;
170
+ }
171
+
172
+ export interface BrowserLifecycleAdapterOptions {
173
+ emitCloseOnPageHide?: boolean;
174
+ closeReason?: string;
175
+ onHidden?: () => void;
176
+ onVisible?: () => void;
177
+ onPageHide?: (persisted: boolean) => void;
178
+ }
179
+
180
+ export interface IframeDetectorAdapterOptions {
181
+ selectors: string[];
182
+ intervalMs?: number;
183
+ timeoutMs?: number;
184
+ emitCloseOnRemoval?: boolean;
185
+ emitCloseOnBootstrapHidden?: boolean;
186
+ closedReason?: string;
187
+ bootstrapClosedReason?: string;
188
+ onDetected?: (iframe: HTMLIFrameElement) => void;
189
+ onRemoved?: () => void;
190
+ onBootstrapHidden?: () => void;
191
+ onTimeout?: () => void;
192
+ onVisibilityChange?: (visible: boolean) => void;
193
+ }
194
+
195
+ export interface AdapterHandle {
196
+ uninstall(): void;
197
+ }
198
+
199
+ export declare const VERSION: string;
200
+ export declare const SCHEMA_VERSION: number;
201
+ export declare function createTelemetryClient(
202
+ options: CreateTelemetryClientOptions,
203
+ ): TelemetryClient;
204
+ export declare function callbackTransport(
205
+ options?: CallbackTransportOptions,
206
+ ): TelemetryTransport;
207
+ export declare function httpTransport(options: HttpTransportOptions): TelemetryTransport;
208
+ export declare function installBrowserLifecycleAdapter(
209
+ client: Pick<TelemetryClient, 'emit'>,
210
+ options?: BrowserLifecycleAdapterOptions,
211
+ ): AdapterHandle;
212
+ export declare function observeElementVisibility(
213
+ element: Element,
214
+ onChange: (visible: boolean) => void,
215
+ ): AdapterHandle;
216
+ export declare function installIframeDetectorAdapter(
217
+ client: Pick<TelemetryClient, 'emit'>,
218
+ options: IframeDetectorAdapterOptions,
219
+ ): AdapterHandle;
220
+ export declare function memoryOutbox(options?: MemoryOutboxOptions): TelemetryOutbox;
221
+ export declare function localStorageOutbox(options?: LocalStorageOutboxOptions): TelemetryOutbox;
222
+ export declare function preflight(options: {
223
+ transport: TelemetryTransport;
224
+ now?: () => number;
225
+ }): Promise<{
226
+ ok: boolean;
227
+ status?: number;
228
+ latencyMs: number;
229
+ networkError: string | null;
230
+ }>;
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ export { createTelemetryClient } from "./client/create-telemetry-client.js";
2
+ export { installBrowserLifecycleAdapter } from "./adapters/browser-lifecycle-adapter.js";
3
+ export { observeElementVisibility } from "./adapters/browser-lifecycle-adapter.js";
4
+ export { installIframeDetectorAdapter } from "./adapters/iframe-detector-adapter.js";
5
+ export { callbackTransport } from "./transport/callback-transport.js";
6
+ export { httpTransport } from "./transport/http-transport.js";
7
+ export { preflight } from "./diagnostics/preflight.js";
8
+ export { memoryOutbox } from "./persistence/memory-outbox.js";
9
+ export { localStorageOutbox } from "./persistence/local-storage-outbox.js";
10
+ export { VERSION, SCHEMA_VERSION } from "./version.js";
@@ -0,0 +1,56 @@
1
+ const STORAGE_KEY = "__zptelemetry_outbox_v1";
2
+ function localStorageOutbox(options = {}) {
3
+ const maxEntries = options.maxEntries ?? 128;
4
+ const storageKey = options.storageKey ?? STORAGE_KEY;
5
+ const storage = options.storage ?? (typeof localStorage === "undefined" ? undefined : localStorage);
6
+ function notify(error) {
7
+ options.onUnavailable?.(error);
8
+ }
9
+ function readEntries() {
10
+ if (!storage) {
11
+ notify("localStorage is not available in this runtime.");
12
+ return [];
13
+ }
14
+ try {
15
+ const raw = storage.getItem(storageKey);
16
+ if (!raw) {
17
+ return [];
18
+ }
19
+ const parsed = JSON.parse(raw);
20
+ return Array.isArray(parsed) ? parsed : [];
21
+ } catch (error) {
22
+ notify(error instanceof Error ? error.message : "Failed to read localStorage outbox.");
23
+ return [];
24
+ }
25
+ }
26
+ function writeEntries(entries) {
27
+ if (!storage) {
28
+ notify("localStorage is not available in this runtime.");
29
+ return;
30
+ }
31
+ try {
32
+ storage.setItem(storageKey, JSON.stringify(entries.slice(-maxEntries)));
33
+ } catch (error) {
34
+ notify(error instanceof Error ? error.message : "Failed to write localStorage outbox.");
35
+ }
36
+ }
37
+ return {
38
+ push(envelope) {
39
+ const entries = readEntries();
40
+ entries.push(envelope);
41
+ writeEntries(entries);
42
+ },
43
+ remove(eventId) {
44
+ const entries = readEntries().filter((entry) => entry.eventId !== eventId);
45
+ writeEntries(entries);
46
+ },
47
+ pending() {
48
+ return readEntries();
49
+ },
50
+ clear() {
51
+ writeEntries([]);
52
+ }
53
+ };
54
+ }
55
+
56
+ exports.localStorageOutbox = localStorageOutbox;
@@ -0,0 +1,54 @@
1
+ const STORAGE_KEY = "__zptelemetry_outbox_v1";
2
+ export function localStorageOutbox(options = {}) {
3
+ const maxEntries = options.maxEntries ?? 128;
4
+ const storageKey = options.storageKey ?? STORAGE_KEY;
5
+ const storage = options.storage ?? (typeof localStorage === "undefined" ? undefined : localStorage);
6
+ function notify(error) {
7
+ options.onUnavailable?.(error);
8
+ }
9
+ function readEntries() {
10
+ if (!storage) {
11
+ notify("localStorage is not available in this runtime.");
12
+ return [];
13
+ }
14
+ try {
15
+ const raw = storage.getItem(storageKey);
16
+ if (!raw) {
17
+ return [];
18
+ }
19
+ const parsed = JSON.parse(raw);
20
+ return Array.isArray(parsed) ? parsed : [];
21
+ } catch (error) {
22
+ notify(error instanceof Error ? error.message : "Failed to read localStorage outbox.");
23
+ return [];
24
+ }
25
+ }
26
+ function writeEntries(entries) {
27
+ if (!storage) {
28
+ notify("localStorage is not available in this runtime.");
29
+ return;
30
+ }
31
+ try {
32
+ storage.setItem(storageKey, JSON.stringify(entries.slice(-maxEntries)));
33
+ } catch (error) {
34
+ notify(error instanceof Error ? error.message : "Failed to write localStorage outbox.");
35
+ }
36
+ }
37
+ return {
38
+ push(envelope) {
39
+ const entries = readEntries();
40
+ entries.push(envelope);
41
+ writeEntries(entries);
42
+ },
43
+ remove(eventId) {
44
+ const entries = readEntries().filter((entry) => entry.eventId !== eventId);
45
+ writeEntries(entries);
46
+ },
47
+ pending() {
48
+ return readEntries();
49
+ },
50
+ clear() {
51
+ writeEntries([]);
52
+ }
53
+ };
54
+ }
@@ -0,0 +1,23 @@
1
+ function memoryOutbox(options = {}) {
2
+ const maxEntries = options.maxEntries ?? 128;
3
+ let entries = [];
4
+ return {
5
+ push(envelope) {
6
+ entries.push(envelope);
7
+ if (entries.length > maxEntries) {
8
+ entries = entries.slice(-maxEntries);
9
+ }
10
+ },
11
+ remove(eventId) {
12
+ entries = entries.filter((entry) => entry.eventId !== eventId);
13
+ },
14
+ pending() {
15
+ return entries.slice();
16
+ },
17
+ clear() {
18
+ entries = [];
19
+ }
20
+ };
21
+ }
22
+
23
+ exports.memoryOutbox = memoryOutbox;
@@ -0,0 +1,21 @@
1
+ export function memoryOutbox(options = {}) {
2
+ const maxEntries = options.maxEntries ?? 128;
3
+ let entries = [];
4
+ return {
5
+ push(envelope) {
6
+ entries.push(envelope);
7
+ if (entries.length > maxEntries) {
8
+ entries = entries.slice(-maxEntries);
9
+ }
10
+ },
11
+ remove(eventId) {
12
+ entries = entries.filter((entry) => entry.eventId !== eventId);
13
+ },
14
+ pending() {
15
+ return entries.slice();
16
+ },
17
+ clear() {
18
+ entries = [];
19
+ }
20
+ };
21
+ }
@@ -0,0 +1,64 @@
1
+ async function applyMiddleware(envelope, middleware) {
2
+ let current = envelope;
3
+ for (const handler of middleware) {
4
+ if (current === null) {
5
+ return null;
6
+ }
7
+ current = await handler(current);
8
+ }
9
+ return current;
10
+ }
11
+ function createEventPipeline(options) {
12
+ async function sendEnvelope(envelope) {
13
+ const nextEnvelope = await applyMiddleware(envelope, options.middleware);
14
+ if (nextEnvelope === null) {
15
+ options.diagnostics.add({
16
+ level: "info",
17
+ message: "Envelope dropped by middleware.",
18
+ timestamp: Date.now(),
19
+ details: { eventId: envelope.eventId, kind: envelope.kind }
20
+ });
21
+ return { ok: true };
22
+ }
23
+ options.diagnostics.recordEnvelope(nextEnvelope);
24
+ const persist = options.persistencePolicy.shouldPersist(nextEnvelope.kind);
25
+ if (persist) {
26
+ options.outbox?.push(nextEnvelope);
27
+ }
28
+ const result = await options.transport.send(nextEnvelope);
29
+ if (result.ok) {
30
+ options.outbox?.remove(nextEnvelope.eventId);
31
+ } else {
32
+ options.diagnostics.add({
33
+ level: "warn",
34
+ message: "Transport send failed.",
35
+ timestamp: Date.now(),
36
+ details: {
37
+ eventId: nextEnvelope.eventId,
38
+ kind: nextEnvelope.kind,
39
+ status: result.status,
40
+ error: result.error
41
+ }
42
+ });
43
+ }
44
+ return result;
45
+ }
46
+ return {
47
+ dispatch(envelope) {
48
+ return sendEnvelope(envelope);
49
+ },
50
+ async flush() {
51
+ for (const envelope of options.outbox?.pending() ?? []) {
52
+ const result = await options.transport.send(envelope);
53
+ if (result.ok) {
54
+ options.outbox?.remove(envelope.eventId);
55
+ }
56
+ }
57
+ },
58
+ queueDepth() {
59
+ return options.outbox?.pending().length ?? 0;
60
+ }
61
+ };
62
+ }
63
+
64
+ exports.createEventPipeline = createEventPipeline;
@@ -0,0 +1,62 @@
1
+ async function applyMiddleware(envelope, middleware) {
2
+ let current = envelope;
3
+ for (const handler of middleware) {
4
+ if (current === null) {
5
+ return null;
6
+ }
7
+ current = await handler(current);
8
+ }
9
+ return current;
10
+ }
11
+ export function createEventPipeline(options) {
12
+ async function sendEnvelope(envelope) {
13
+ const nextEnvelope = await applyMiddleware(envelope, options.middleware);
14
+ if (nextEnvelope === null) {
15
+ options.diagnostics.add({
16
+ level: "info",
17
+ message: "Envelope dropped by middleware.",
18
+ timestamp: Date.now(),
19
+ details: { eventId: envelope.eventId, kind: envelope.kind }
20
+ });
21
+ return { ok: true };
22
+ }
23
+ options.diagnostics.recordEnvelope(nextEnvelope);
24
+ const persist = options.persistencePolicy.shouldPersist(nextEnvelope.kind);
25
+ if (persist) {
26
+ options.outbox?.push(nextEnvelope);
27
+ }
28
+ const result = await options.transport.send(nextEnvelope);
29
+ if (result.ok) {
30
+ options.outbox?.remove(nextEnvelope.eventId);
31
+ } else {
32
+ options.diagnostics.add({
33
+ level: "warn",
34
+ message: "Transport send failed.",
35
+ timestamp: Date.now(),
36
+ details: {
37
+ eventId: nextEnvelope.eventId,
38
+ kind: nextEnvelope.kind,
39
+ status: result.status,
40
+ error: result.error
41
+ }
42
+ });
43
+ }
44
+ return result;
45
+ }
46
+ return {
47
+ dispatch(envelope) {
48
+ return sendEnvelope(envelope);
49
+ },
50
+ async flush() {
51
+ for (const envelope of options.outbox?.pending() ?? []) {
52
+ const result = await options.transport.send(envelope);
53
+ if (result.ok) {
54
+ options.outbox?.remove(envelope.eventId);
55
+ }
56
+ }
57
+ },
58
+ queueDepth() {
59
+ return options.outbox?.pending().length ?? 0;
60
+ }
61
+ };
62
+ }
@@ -0,0 +1,46 @@
1
+ function createHeartbeatScheduler(options) {
2
+ let heartbeatCount = 0;
3
+ let missedBeats = 0;
4
+ let stopped = false;
5
+ let thresholdNotified = false;
6
+ const timer = setInterval(() => {
7
+ if (stopped) {
8
+ return;
9
+ }
10
+ heartbeatCount += 1;
11
+ options.sendHeartbeat(missedBeats).then((result) => {
12
+ if (result.ok) {
13
+ missedBeats = 0;
14
+ thresholdNotified = false;
15
+ } else {
16
+ missedBeats += 1;
17
+ if (missedBeats >= options.missThreshold && !thresholdNotified) {
18
+ thresholdNotified = true;
19
+ options.onMissThreshold?.(missedBeats);
20
+ }
21
+ }
22
+ options.onTick?.(heartbeatCount, missedBeats);
23
+ }).catch(() => {
24
+ missedBeats += 1;
25
+ if (missedBeats >= options.missThreshold && !thresholdNotified) {
26
+ thresholdNotified = true;
27
+ options.onMissThreshold?.(missedBeats);
28
+ }
29
+ options.onTick?.(heartbeatCount, missedBeats);
30
+ });
31
+ }, options.intervalMs);
32
+ return {
33
+ stop() {
34
+ stopped = true;
35
+ clearInterval(timer);
36
+ },
37
+ heartbeatCount() {
38
+ return heartbeatCount;
39
+ },
40
+ missedBeats() {
41
+ return missedBeats;
42
+ }
43
+ };
44
+ }
45
+
46
+ exports.createHeartbeatScheduler = createHeartbeatScheduler;
@@ -0,0 +1,44 @@
1
+ export function createHeartbeatScheduler(options) {
2
+ let heartbeatCount = 0;
3
+ let missedBeats = 0;
4
+ let stopped = false;
5
+ let thresholdNotified = false;
6
+ const timer = setInterval(() => {
7
+ if (stopped) {
8
+ return;
9
+ }
10
+ heartbeatCount += 1;
11
+ options.sendHeartbeat(missedBeats).then((result) => {
12
+ if (result.ok) {
13
+ missedBeats = 0;
14
+ thresholdNotified = false;
15
+ } else {
16
+ missedBeats += 1;
17
+ if (missedBeats >= options.missThreshold && !thresholdNotified) {
18
+ thresholdNotified = true;
19
+ options.onMissThreshold?.(missedBeats);
20
+ }
21
+ }
22
+ options.onTick?.(heartbeatCount, missedBeats);
23
+ }).catch(() => {
24
+ missedBeats += 1;
25
+ if (missedBeats >= options.missThreshold && !thresholdNotified) {
26
+ thresholdNotified = true;
27
+ options.onMissThreshold?.(missedBeats);
28
+ }
29
+ options.onTick?.(heartbeatCount, missedBeats);
30
+ });
31
+ }, options.intervalMs);
32
+ return {
33
+ stop() {
34
+ stopped = true;
35
+ clearInterval(timer);
36
+ },
37
+ heartbeatCount() {
38
+ return heartbeatCount;
39
+ },
40
+ missedBeats() {
41
+ return missedBeats;
42
+ }
43
+ };
44
+ }
@@ -0,0 +1,47 @@
1
+ const {assertTransition} = require('../client/state-machine.cjs');
2
+ const {generateCorrelationId, generateEventId} = require('../utils/ids.cjs');
3
+ function createSessionManager(options) {
4
+ const now = options.now ?? Date.now;
5
+ const sessionId = options.sessionId;
6
+ const correlationId = options.correlationId ?? generateCorrelationId();
7
+ let state = "idle";
8
+ let sequence = 0;
9
+ let startedAt = null;
10
+ let stoppedAt = null;
11
+ return {
12
+ sessionId,
13
+ correlationId,
14
+ nextSequence() {
15
+ sequence += 1;
16
+ return sequence;
17
+ },
18
+ nextEventId() {
19
+ return generateEventId(sequence);
20
+ },
21
+ transition(next) {
22
+ assertTransition(state, next);
23
+ state = next;
24
+ if (next === "starting" && startedAt === null) {
25
+ startedAt = now();
26
+ }
27
+ if (next === "closed") {
28
+ stoppedAt = now();
29
+ }
30
+ },
31
+ snapshot() {
32
+ const currentNow = now();
33
+ const elapsedMs = startedAt === null ? 0 : (stoppedAt ?? currentNow) - startedAt;
34
+ return {
35
+ state,
36
+ sessionId,
37
+ correlationId,
38
+ startedAt,
39
+ stoppedAt,
40
+ eventCount: sequence,
41
+ elapsedMs
42
+ };
43
+ }
44
+ };
45
+ }
46
+
47
+ exports.createSessionManager = createSessionManager;