@apollo-deploy/tesseract 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.
Files changed (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +235 -0
  3. package/dist/adapters/types.d.ts +15 -0
  4. package/dist/adapters/types.d.ts.map +1 -0
  5. package/dist/adapters/types.js +6 -0
  6. package/dist/adapters/types.js.map +1 -0
  7. package/dist/adapters/typescript/index.d.ts +11 -0
  8. package/dist/adapters/typescript/index.d.ts.map +1 -0
  9. package/dist/adapters/typescript/index.js +112 -0
  10. package/dist/adapters/typescript/index.js.map +1 -0
  11. package/dist/adapters/typescript/model-emitter.d.ts +13 -0
  12. package/dist/adapters/typescript/model-emitter.d.ts.map +1 -0
  13. package/dist/adapters/typescript/model-emitter.js +415 -0
  14. package/dist/adapters/typescript/model-emitter.js.map +1 -0
  15. package/dist/adapters/typescript/shared.d.ts +26 -0
  16. package/dist/adapters/typescript/shared.d.ts.map +1 -0
  17. package/dist/adapters/typescript/shared.js +65 -0
  18. package/dist/adapters/typescript/shared.js.map +1 -0
  19. package/dist/cli.d.ts +6 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +53 -0
  22. package/dist/cli.js.map +1 -0
  23. package/dist/helpers/handlebars.d.ts +15 -0
  24. package/dist/helpers/handlebars.d.ts.map +1 -0
  25. package/dist/helpers/handlebars.js +172 -0
  26. package/dist/helpers/handlebars.js.map +1 -0
  27. package/dist/index.d.ts +16 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +36 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/pipeline/enrich.d.ts +7 -0
  32. package/dist/pipeline/enrich.d.ts.map +1 -0
  33. package/dist/pipeline/enrich.js +277 -0
  34. package/dist/pipeline/enrich.js.map +1 -0
  35. package/dist/pipeline/intake.d.ts +11 -0
  36. package/dist/pipeline/intake.d.ts.map +1 -0
  37. package/dist/pipeline/intake.js +508 -0
  38. package/dist/pipeline/intake.js.map +1 -0
  39. package/dist/pipeline/write.d.ts +15 -0
  40. package/dist/pipeline/write.d.ts.map +1 -0
  41. package/dist/pipeline/write.js +33 -0
  42. package/dist/pipeline/write.js.map +1 -0
  43. package/dist/types/config.d.ts +47 -0
  44. package/dist/types/config.d.ts.map +1 -0
  45. package/dist/types/config.js +24 -0
  46. package/dist/types/config.js.map +1 -0
  47. package/dist/types/ir.d.ts +172 -0
  48. package/dist/types/ir.d.ts.map +1 -0
  49. package/dist/types/ir.js +7 -0
  50. package/dist/types/ir.js.map +1 -0
  51. package/dist/types/manifest.d.ts +112 -0
  52. package/dist/types/manifest.d.ts.map +1 -0
  53. package/dist/types/manifest.js +32 -0
  54. package/dist/types/manifest.js.map +1 -0
  55. package/dist/utils/format.d.ts +5 -0
  56. package/dist/utils/format.d.ts.map +1 -0
  57. package/dist/utils/format.js +21 -0
  58. package/dist/utils/format.js.map +1 -0
  59. package/dist/utils/naming.d.ts +26 -0
  60. package/dist/utils/naming.d.ts.map +1 -0
  61. package/dist/utils/naming.js +270 -0
  62. package/dist/utils/naming.js.map +1 -0
  63. package/package.json +58 -0
  64. package/templates/typescript/client.hbs +145 -0
  65. package/templates/typescript/domain-index.hbs +10 -0
  66. package/templates/typescript/domain.hbs +127 -0
  67. package/templates/typescript/index.hbs +64 -0
  68. package/templates/typescript/package-json.hbs +29 -0
  69. package/templates/typescript/readme.hbs +78 -0
  70. package/templates/typescript/transport-sse.hbs +377 -0
  71. package/templates/typescript/transport.hbs +662 -0
  72. package/templates/typescript/tsconfig.hbs +18 -0
  73. package/templates/typescript/types-common.hbs +98 -0
  74. package/templates/typescript/types-errors.hbs +191 -0
  75. package/templates/typescript/types.hbs +33 -0
  76. package/templates/typescript/utils-index.hbs +7 -0
  77. package/templates/typescript/utils-query.hbs +29 -0
  78. package/templates/typescript/webhook-handler.hbs +447 -0
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @generated
3
+ * This file was automatically generated by sdk-forge.
4
+ * DO NOT MODIFY THIS FILE DIRECTLY.
5
+ */
6
+
7
+ // Client
8
+ export { create{{meta.clientName}}Client } from './src/client.js';
9
+ export type { {{meta.clientName}}Client, {{meta.clientName}}ClientConfig } from './src/client.js';
10
+
11
+ // Error class
12
+ export { SDKError } from './src/types/errors.js';
13
+
14
+ // Transport
15
+ export { createTransport, executeRequest } from './src/transport/axios.js';
16
+ export type {
17
+ RetryConfig,
18
+ TransportConfig,
19
+ ResolvedTransportConfig,
20
+ SDKTransport,
21
+ SDKPlugin,
22
+ RequestOptions,
23
+ RequestMeta,
24
+ ResponseMeta,
25
+ ErrorMeta,
26
+ } from './src/transport/axios.js';
27
+
28
+ {{#if hasSSE}}
29
+ // SSE (Server-Sent Events)
30
+ export { createSSEStream, parseSSEChunk, parseSSEEventData } from './src/transport/sse.js';
31
+ export type { SSEEvent, SSEOptions, SSEReconnectConfig } from './src/transport/sse.js';
32
+ {{/if}}
33
+
34
+ // Common types
35
+ export type {
36
+ PaginationQuery,
37
+ PageMeta,
38
+ Page,
39
+ SuccessResponse,
40
+ MessageResponse,
41
+ ApiErrorEnvelope,
42
+ } from './src/types/common.js';
43
+
44
+ // Domain API types
45
+ {{#each groups}}
46
+ export type { {{interfaceName}} } from './src/domain/{{fileName}}.js';
47
+ {{/each}}
48
+
49
+ {{#if hasWebhooks}}
50
+ // Webhooks
51
+ export { createWebhookRegistry } from './src/webhooks/handler.js';
52
+ export type {
53
+ WebhookEventMap,
54
+ WebhookEventName,
55
+ WebhookHandler,
56
+ WebhookMeta,
57
+ WebhookVerifyOptions,
58
+ WebhookRegistry,
59
+ WebhookRegistryConfig,
60
+ } from './src/webhooks/handler.js';
61
+ {{/if}}
62
+
63
+ // Types
64
+ export * from './src/types/index.js';
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "{{packageName}}",
3
+ "version": "{{version}}",
4
+ "type": "module",
5
+ "description": "{{#if description}}{{description}}{{else}}TypeScript SDK for {{title}}{{/if}}",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "prepublishOnly": "tsc"
17
+ },
18
+ "dependencies": {
19
+ "axios": "1.14.0"{{#each externalPackages}},
20
+ "{{name}}": "{{version}}"{{/each}}
21
+ },
22
+ "devDependencies": {
23
+ "typescript": "^5.5.0"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "README.md"
28
+ ]
29
+ }
@@ -0,0 +1,78 @@
1
+ # {{title}} SDK
2
+
3
+ {{#if description}}
4
+ {{description}}
5
+
6
+ {{/if}}
7
+ > This SDK was automatically generated by [sdk-forge](https://github.com/sdk-forge).
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install {{packageName}}
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```typescript
18
+ import { create{{clientName}}Client } from '{{packageName}}';
19
+
20
+ const client = create{{clientName}}Client({
21
+ baseUrl: '{{baseUrl}}',
22
+ {{#each securitySchemes}}
23
+ {{configKey}}: 'your-{{kebabCase name}}-credential',
24
+ {{/each}}
25
+ });
26
+ const api = client.{{groups.[0].name}};
27
+ ```
28
+
29
+ ## Effect Runtime
30
+
31
+ ```typescript
32
+ import { create{{clientName}}Client, Effect } from '{{packageName}}';
33
+
34
+ const client = create{{clientName}}Client();
35
+
36
+ const program = client.{{groups.[0].name}}.{{groups.[0].operations.[0].name}}Effect(/* params */);
37
+ const result = await Effect.runPromise(program);
38
+ ```
39
+
40
+ ## API Reference
41
+
42
+ {{#each groups}}
43
+ ### {{interfaceName}}
44
+
45
+ {{#if description}}
46
+ {{description}}
47
+
48
+ {{/if}}
49
+ | Method | HTTP | Path | Description |
50
+ |--------|------|------|-------------|
51
+ {{#each operations}}
52
+ | `{{name}}` | {{httpMethod}} | `{{path}}` | {{#if description}}{{description}}{{else}}-{{/if}} |
53
+ {{/each}}
54
+
55
+ {{/each}}
56
+ ## Types
57
+
58
+ {{#each schemas}}
59
+ {{#if isEnum}}
60
+ - `{{name}}` — enum: {{#each enumValues}}`{{this}}`{{#unless @last}}, {{/unless}}{{/each}}
61
+ {{else}}
62
+ - `{{name}}`{{#if description}} — {{description}}{{/if}}
63
+ {{/if}}
64
+ {{/each}}
65
+
66
+ ## Error Handling
67
+
68
+ ```typescript
69
+ import { SDKError } from '{{packageName}}';
70
+
71
+ try {
72
+ const result = await client.someApi.someMethod();
73
+ } catch (error) {
74
+ if (error instanceof SDKError) {
75
+ console.error(`Error ${error.status}: ${error.message} (${error.code})`);
76
+ }
77
+ }
78
+ ```
@@ -0,0 +1,377 @@
1
+ /**
2
+ * @generated
3
+ * This file was automatically generated by sdk-forge.
4
+ * DO NOT MODIFY THIS FILE DIRECTLY.
5
+ */
6
+
7
+ /* eslint-disable @typescript-eslint/no-explicit-any */
8
+
9
+ // ── SSE Types ────────────────────────────────────────
10
+
11
+ /**
12
+ * A parsed Server-Sent Event with typed data payload.
13
+ */
14
+ export interface SSEEvent<T = unknown> {
15
+ /** Event type (from the `event:` field, defaults to "message") */
16
+ type: string;
17
+ /** Parsed JSON data payload */
18
+ data: T;
19
+ /** Last event ID (from the `id:` field) */
20
+ id?: string;
21
+ /** Retry interval in milliseconds (from the `retry:` field) */
22
+ retry?: number;
23
+ }
24
+
25
+ /**
26
+ * Options for SSE stream subscriptions.
27
+ */
28
+ export interface SSEOptions {
29
+ /** AbortSignal for cooperative cancellation */
30
+ signal?: AbortSignal;
31
+ /** Per-request timeout for the initial connection in milliseconds */
32
+ timeoutMs?: number;
33
+ /** Additional headers merged into the request */
34
+ headers?: Record<string, string>;
35
+ /** Custom event types to listen for (defaults to all events) */
36
+ eventTypes?: string[];
37
+ /**
38
+ * Called when the connection encounters a retryable error.
39
+ * Return `false` to stop reconnection attempts.
40
+ */
41
+ onRetry?: (attempt: number, error: unknown) => boolean | void;
42
+ /**
43
+ * Called when the SSE connection is established successfully.
44
+ * Useful for resetting UI state or logging.
45
+ */
46
+ onOpen?: (response: Response) => void;
47
+ /**
48
+ * Called when the SSE connection is closed (cleanly or after max retries).
49
+ */
50
+ onClose?: () => void;
51
+ /**
52
+ * Custom data parser. By default, attempts JSON.parse and falls back to raw string.
53
+ * Useful when the server sends non-JSON payloads or when you need custom deserialization.
54
+ */
55
+ parseData?: <R>(raw: string, eventType: string) => R;
56
+ /**
57
+ * Maximum buffer size in bytes before discarding incomplete data.
58
+ * Protects against memory exhaustion from malformed streams. Default: 5MB.
59
+ */
60
+ maxBufferSize?: number;
61
+ /**
62
+ * Heartbeat timeout in milliseconds. If no data is received within this period,
63
+ * the connection is considered dead and will be reconnected.
64
+ * Set to 0 to disable. Default: 0 (disabled).
65
+ */
66
+ heartbeatTimeoutMs?: number;
67
+ /**
68
+ * Whether to automatically reconnect when the stream ends cleanly (server closes connection).
69
+ * Default: false (only reconnects on errors).
70
+ */
71
+ reconnectOnComplete?: boolean;
72
+ }
73
+
74
+ /**
75
+ * Configuration for SSE reconnection behavior.
76
+ */
77
+ export interface SSEReconnectConfig {
78
+ /** Maximum number of reconnection attempts (default: 5) */
79
+ maxAttempts: number;
80
+ /** Base delay between reconnection attempts in ms (default: 1000) */
81
+ baseDelayMs: number;
82
+ /** Maximum delay between reconnection attempts in ms (default: 30000) */
83
+ maxDelayMs: number;
84
+ /** Whether to apply jitter to reconnection delays (default: true) */
85
+ jitter?: boolean;
86
+ }
87
+
88
+ /**
89
+ * Fetch request init with headers narrowed to a plain string record.
90
+ * Avoids type conflicts from the broader `HeadersInit` union in `RequestInit`.
91
+ */
92
+ export type SSERequestInit = Omit<RequestInit, 'headers'> & {
93
+ headers?: Record<string, string>;
94
+ };
95
+
96
+ // ── SSE Parser ───────────────────────────────────────
97
+
98
+ /**
99
+ * Parse a raw SSE text chunk into individual events.
100
+ * Handles multi-line `data:` fields, all standard SSE fields,
101
+ * and both `\n` and `\r\n` line endings per the SSE specification.
102
+ */
103
+ export function parseSSEChunk(chunk: string): SSEEvent<string>[] {
104
+ const events: SSEEvent<string>[] = [];
105
+ // Normalize \r\n and \r to \n for consistent parsing (SSE spec allows all three)
106
+ const normalized = chunk.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
107
+ const blocks = normalized.split(/\n{2,}/);
108
+
109
+ for (const block of blocks) {
110
+ const trimmed = block.trim();
111
+ if (!trimmed) continue;
112
+ // Skip blocks that are entirely comments
113
+ if (trimmed.split('\n').every((line) => line.startsWith(':'))) continue;
114
+
115
+ let type = 'message';
116
+ let data = '';
117
+ let id: string | undefined;
118
+ let retry: number | undefined;
119
+
120
+ for (const line of trimmed.split('\n')) {
121
+ // Comment lines
122
+ if (line.startsWith(':')) continue;
123
+
124
+ const colonIdx = line.indexOf(':');
125
+ let field: string;
126
+ let value: string;
127
+
128
+ if (colonIdx === -1) {
129
+ // Field-only lines (no colon) — per spec, the value is empty string
130
+ field = line;
131
+ value = '';
132
+ } else {
133
+ field = line.slice(0, colonIdx);
134
+ // Per SSE spec: if there's a space after the colon, strip exactly one space
135
+ const rawValue = line.slice(colonIdx + 1);
136
+ value = rawValue.startsWith(' ') ? rawValue.slice(1) : rawValue;
137
+ }
138
+
139
+ switch (field) {
140
+ case 'event':
141
+ type = value;
142
+ break;
143
+ case 'data':
144
+ data += (data ? '\n' : '') + value;
145
+ break;
146
+ case 'id':
147
+ // Per spec: id field must not contain null characters
148
+ if (!value.includes('\0')) {
149
+ id = value;
150
+ }
151
+ break;
152
+ case 'retry': {
153
+ const parsed = Number.parseInt(value, 10);
154
+ if (Number.isFinite(parsed) && parsed >= 0) retry = parsed;
155
+ break;
156
+ }
157
+ // Unknown fields are ignored per spec
158
+ }
159
+ }
160
+
161
+ if (data) {
162
+ const event: SSEEvent<string> = { type, data };
163
+ if (id !== undefined) event.id = id;
164
+ if (retry !== undefined) event.retry = retry;
165
+ events.push(event);
166
+ }
167
+ }
168
+
169
+ return events;
170
+ }
171
+
172
+ /**
173
+ * Parse and type-cast an SSE event's data field from JSON.
174
+ * Falls back to the raw string value if JSON parsing fails.
175
+ */
176
+ export function parseSSEEventData<T>(event: SSEEvent<string>, parseData?: <R>(raw: string, type: string) => R): SSEEvent<T> {
177
+ if (parseData) {
178
+ return { ...event, data: parseData<T>(event.data, event.type) };
179
+ }
180
+ let parsed: T;
181
+ try {
182
+ parsed = JSON.parse(event.data) as T;
183
+ } catch {
184
+ parsed = event.data as unknown as T;
185
+ }
186
+ return { ...event, data: parsed };
187
+ }
188
+
189
+ // ── SSE Stream Factory ──────────────────────────────
190
+
191
+ const DEFAULT_RECONNECT: SSEReconnectConfig = {
192
+ maxAttempts: 5,
193
+ baseDelayMs: 1000,
194
+ maxDelayMs: 30_000,
195
+ jitter: true,
196
+ };
197
+
198
+ const DEFAULT_MAX_BUFFER = 5 * 1024 * 1024; // 5MB
199
+
200
+ /**
201
+ * Create an async iterable that reads from an SSE endpoint using fetch.
202
+ * Automatically handles reconnection with exponential backoff.
203
+ *
204
+ * @example
205
+ * ```ts
206
+ * const stream = createSSEStream<MyEvent>(url, { method: 'GET' }, undefined, {
207
+ * signal: controller.signal,
208
+ * eventTypes: ['update', 'complete'],
209
+ * heartbeatTimeoutMs: 30000,
210
+ * onOpen: () => console.log('Connected'),
211
+ * onClose: () => console.log('Disconnected'),
212
+ * });
213
+ *
214
+ * for await (const event of stream) {
215
+ * console.log(event.type, event.data);
216
+ * }
217
+ * ```
218
+ */
219
+ export async function* createSSEStream<T>(
220
+ url: string,
221
+ init: SSERequestInit,
222
+ reconnect?: Partial<SSEReconnectConfig>,
223
+ options?: SSEOptions,
224
+ ): AsyncIterable<SSEEvent<T>> {
225
+ const rc: SSEReconnectConfig = { ...DEFAULT_RECONNECT, ...reconnect };
226
+ const maxBuffer = options?.maxBufferSize ?? DEFAULT_MAX_BUFFER;
227
+ let lastEventId: string | undefined;
228
+ let attempt = 0;
229
+ let serverRetryMs: number | undefined;
230
+
231
+ try {
232
+ while (true) {
233
+ const headers: Record<string, string> = {
234
+ Accept: 'text/event-stream',
235
+ 'Cache-Control': 'no-cache',
236
+ ...(init.headers ?? {}),
237
+ ...(options?.headers ?? {}),
238
+ };
239
+
240
+ if (lastEventId) {
241
+ headers['Last-Event-ID'] = lastEventId;
242
+ }
243
+
244
+ const controller = new AbortController();
245
+ const externalSignal = options?.signal;
246
+
247
+ if (externalSignal?.aborted) return;
248
+
249
+ // Link external signal to internal controller
250
+ const onAbort = () => controller.abort();
251
+ externalSignal?.addEventListener('abort', onAbort, { once: true });
252
+
253
+ let connectionTimeoutId: ReturnType<typeof setTimeout> | undefined;
254
+ if (options?.timeoutMs) {
255
+ connectionTimeoutId = setTimeout(() => controller.abort(), options.timeoutMs);
256
+ }
257
+
258
+ try {
259
+ const response = await fetch(url, {
260
+ ...init,
261
+ headers,
262
+ signal: controller.signal,
263
+ });
264
+
265
+ if (connectionTimeoutId) {
266
+ clearTimeout(connectionTimeoutId);
267
+ connectionTimeoutId = undefined;
268
+ }
269
+
270
+ if (!response.ok) {
271
+ throw new Error(`SSE connection failed: ${response.status} ${response.statusText}`);
272
+ }
273
+
274
+ if (!response.body) {
275
+ throw new Error('SSE response has no body');
276
+ }
277
+
278
+ // Connection established successfully
279
+ attempt = 0;
280
+ options?.onOpen?.(response);
281
+
282
+ const reader = response.body.getReader();
283
+ const decoder = new TextDecoder();
284
+ let buffer = '';
285
+ let heartbeatTimer: ReturnType<typeof setTimeout> | undefined;
286
+
287
+ const resetHeartbeat = () => {
288
+ if (heartbeatTimer) clearTimeout(heartbeatTimer);
289
+ if (options?.heartbeatTimeoutMs && options.heartbeatTimeoutMs > 0) {
290
+ heartbeatTimer = setTimeout(() => {
291
+ reader.cancel().catch(() => {});
292
+ }, options.heartbeatTimeoutMs);
293
+ }
294
+ };
295
+
296
+ try {
297
+ resetHeartbeat();
298
+
299
+ while (true) {
300
+ if (externalSignal?.aborted) return;
301
+
302
+ const { done, value } = await reader.read();
303
+ if (done) break;
304
+
305
+ resetHeartbeat();
306
+
307
+ buffer += decoder.decode(value, { stream: true });
308
+
309
+ // Guard against memory exhaustion from malformed streams
310
+ if (buffer.length > maxBuffer) {
311
+ // Discard everything before the last double newline, or the whole buffer
312
+ const lastBreak = buffer.lastIndexOf('\n\n');
313
+ if (lastBreak !== -1) {
314
+ buffer = buffer.slice(lastBreak + 2);
315
+ } else {
316
+ buffer = '';
317
+ }
318
+ }
319
+
320
+ // Process complete events (separated by double newlines)
321
+ // Handle both \n\n and \r\n\r\n
322
+ const lastDoubleNewline = Math.max(
323
+ buffer.lastIndexOf('\n\n'),
324
+ buffer.lastIndexOf('\r\n\r\n'),
325
+ );
326
+ if (lastDoubleNewline === -1) continue;
327
+
328
+ const breakLen = buffer[lastDoubleNewline] === '\r' ? 4 : 2;
329
+ const complete = buffer.slice(0, lastDoubleNewline + breakLen);
330
+ buffer = buffer.slice(lastDoubleNewline + breakLen);
331
+
332
+ const rawEvents = parseSSEChunk(complete);
333
+ for (const rawEvent of rawEvents) {
334
+ if (rawEvent.id !== undefined) lastEventId = rawEvent.id;
335
+ if (rawEvent.retry !== undefined) serverRetryMs = rawEvent.retry;
336
+
337
+ if (options?.eventTypes && !options.eventTypes.includes(rawEvent.type)) {
338
+ continue;
339
+ }
340
+
341
+ yield parseSSEEventData<T>(rawEvent, options?.parseData);
342
+ }
343
+ }
344
+ } finally {
345
+ if (heartbeatTimer) clearTimeout(heartbeatTimer);
346
+ reader.releaseLock();
347
+ externalSignal?.removeEventListener('abort', onAbort);
348
+ }
349
+
350
+ // Stream ended cleanly
351
+ if (!options?.reconnectOnComplete) return;
352
+ // Fall through to reconnection logic below
353
+ } catch (error: unknown) {
354
+ if (connectionTimeoutId) clearTimeout(connectionTimeoutId);
355
+ externalSignal?.removeEventListener('abort', onAbort);
356
+
357
+ if (externalSignal?.aborted) return;
358
+
359
+ attempt++;
360
+ if (attempt > rc.maxAttempts) throw error;
361
+
362
+ const shouldRetry = options?.onRetry?.(attempt, error);
363
+ if (shouldRetry === false) throw error;
364
+ }
365
+
366
+ // Reconnection delay
367
+ const baseDelay = serverRetryMs ?? rc.baseDelayMs * Math.pow(2, attempt - 1);
368
+ let delayMs = Math.min(baseDelay, rc.maxDelayMs);
369
+ if (rc.jitter !== false) {
370
+ delayMs = Math.floor(Math.random() * (delayMs + 1));
371
+ }
372
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
373
+ }
374
+ } finally {
375
+ options?.onClose?.();
376
+ }
377
+ }