@auth-craft/client 0.1.0 → 0.1.2

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 (109) hide show
  1. package/README.md +144 -8
  2. package/dist/core/core.d.ts +72 -0
  3. package/dist/core/core.d.ts.map +1 -0
  4. package/dist/core/core.js +105 -0
  5. package/dist/core/core.js.map +1 -0
  6. package/dist/core/proxy.d.ts +79 -0
  7. package/dist/core/proxy.d.ts.map +1 -0
  8. package/dist/core/proxy.js +99 -0
  9. package/dist/core/proxy.js.map +1 -0
  10. package/dist/index.d.ts +15 -28
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +23 -55
  13. package/dist/index.js.map +1 -1
  14. package/dist/transport/client.d.ts +250 -0
  15. package/dist/transport/client.d.ts.map +1 -0
  16. package/dist/transport/client.js +940 -0
  17. package/dist/transport/client.js.map +1 -0
  18. package/dist/transport/constants.d.ts +9 -0
  19. package/dist/transport/constants.d.ts.map +1 -0
  20. package/dist/transport/constants.js +9 -0
  21. package/dist/transport/constants.js.map +1 -0
  22. package/dist/transport/error-codes.d.ts +41 -0
  23. package/dist/transport/error-codes.d.ts.map +1 -0
  24. package/dist/transport/error-codes.js +38 -0
  25. package/dist/transport/error-codes.js.map +1 -0
  26. package/dist/transport/errors.d.ts +55 -0
  27. package/dist/transport/errors.d.ts.map +1 -0
  28. package/dist/transport/errors.js +55 -0
  29. package/dist/transport/errors.js.map +1 -0
  30. package/dist/transport/helpers.d.ts +135 -0
  31. package/dist/transport/helpers.d.ts.map +1 -0
  32. package/dist/transport/helpers.js +185 -0
  33. package/dist/transport/helpers.js.map +1 -0
  34. package/dist/transport/index.d.ts +22 -0
  35. package/dist/transport/index.d.ts.map +1 -0
  36. package/dist/transport/index.js +32 -0
  37. package/dist/transport/index.js.map +1 -0
  38. package/dist/transport/messages.d.ts +104 -0
  39. package/dist/transport/messages.d.ts.map +1 -0
  40. package/dist/transport/messages.js +24 -0
  41. package/dist/transport/messages.js.map +1 -0
  42. package/dist/transport/provider/create-provider.d.ts +38 -0
  43. package/dist/transport/provider/create-provider.d.ts.map +1 -0
  44. package/dist/transport/provider/create-provider.js +127 -0
  45. package/dist/transport/provider/create-provider.js.map +1 -0
  46. package/dist/transport/provider/parser/body.d.ts +7 -0
  47. package/dist/transport/provider/parser/body.d.ts.map +1 -0
  48. package/dist/transport/provider/parser/body.js +17 -0
  49. package/dist/transport/provider/parser/body.js.map +1 -0
  50. package/dist/transport/provider/parser/cookie.d.ts +8 -0
  51. package/dist/transport/provider/parser/cookie.d.ts.map +1 -0
  52. package/dist/transport/provider/parser/cookie.js +17 -0
  53. package/dist/transport/provider/parser/cookie.js.map +1 -0
  54. package/dist/transport/provider/parser/normalize.d.ts +6 -0
  55. package/dist/transport/provider/parser/normalize.d.ts.map +1 -0
  56. package/dist/transport/provider/parser/normalize.js +21 -0
  57. package/dist/transport/provider/parser/normalize.js.map +1 -0
  58. package/dist/transport/provider/presets.d.ts +31 -0
  59. package/dist/transport/provider/presets.d.ts.map +1 -0
  60. package/dist/transport/provider/presets.js +35 -0
  61. package/dist/transport/provider/presets.js.map +1 -0
  62. package/dist/transport/provider/register-presets.d.ts +10 -0
  63. package/dist/transport/provider/register-presets.d.ts.map +1 -0
  64. package/dist/transport/provider/register-presets.js +32 -0
  65. package/dist/transport/provider/register-presets.js.map +1 -0
  66. package/dist/transport/provider/storage/indexeddb.d.ts +26 -0
  67. package/dist/transport/provider/storage/indexeddb.d.ts.map +1 -0
  68. package/dist/transport/provider/storage/indexeddb.js +72 -0
  69. package/dist/transport/provider/storage/indexeddb.js.map +1 -0
  70. package/dist/transport/provider/strategy/body.d.ts +28 -0
  71. package/dist/transport/provider/strategy/body.d.ts.map +1 -0
  72. package/dist/transport/provider/strategy/body.js +73 -0
  73. package/dist/transport/provider/strategy/body.js.map +1 -0
  74. package/dist/transport/provider/strategy/cookie.d.ts +29 -0
  75. package/dist/transport/provider/strategy/cookie.d.ts.map +1 -0
  76. package/dist/transport/provider/strategy/cookie.js +70 -0
  77. package/dist/transport/provider/strategy/cookie.js.map +1 -0
  78. package/dist/transport/result.d.ts +28 -0
  79. package/dist/transport/result.d.ts.map +1 -0
  80. package/dist/transport/result.js +87 -0
  81. package/dist/transport/result.js.map +1 -0
  82. package/dist/transport/types.d.ts +488 -0
  83. package/dist/transport/types.d.ts.map +1 -0
  84. package/dist/transport/types.js +3 -0
  85. package/dist/transport/types.js.map +1 -0
  86. package/dist/transport/utils/binary.d.ts +20 -0
  87. package/dist/transport/utils/binary.d.ts.map +1 -0
  88. package/dist/transport/utils/binary.js +51 -0
  89. package/dist/transport/utils/binary.js.map +1 -0
  90. package/dist/transport/utils/formdata.d.ts +26 -0
  91. package/dist/transport/utils/formdata.d.ts.map +1 -0
  92. package/dist/transport/utils/formdata.js +77 -0
  93. package/dist/transport/utils/formdata.js.map +1 -0
  94. package/dist/transport/utils/registry.d.ts +26 -0
  95. package/dist/transport/utils/registry.d.ts.map +1 -0
  96. package/dist/transport/utils/registry.js +51 -0
  97. package/dist/transport/utils/registry.js.map +1 -0
  98. package/dist/transport/worker-post.d.ts +44 -0
  99. package/dist/transport/worker-post.d.ts.map +1 -0
  100. package/dist/transport/worker-post.js +104 -0
  101. package/dist/transport/worker-post.js.map +1 -0
  102. package/dist/transport/worker.d.ts +10 -0
  103. package/dist/transport/worker.d.ts.map +1 -0
  104. package/dist/transport/worker.js +421 -0
  105. package/dist/transport/worker.js.map +1 -0
  106. package/dist/types.test-d.js +3 -9
  107. package/dist/types.test-d.js.map +1 -1
  108. package/eslint-isolation.mjs +82 -0
  109. package/package.json +30 -16
@@ -0,0 +1,488 @@
1
+ import type { Result } from 'ts-micro-result';
2
+ /**
3
+ * FetchGuard Business Types
4
+ *
5
+ * Contains domain logic types: Provider, Config, API Response, etc.
6
+ * For message protocol types, see messages.ts
7
+ */
8
+ /**
9
+ * Token info returned from provider
10
+ *
11
+ * All fields are optional to support various custom auth methods:
12
+ * - Standard login/refresh: returns token + optional fields
13
+ * - Update user info: may only return user (no token update)
14
+ * - Verify OTP: may return nothing (just validation)
15
+ * - Custom auth flows: flexible field combinations
16
+ */
17
+ export interface TokenInfo {
18
+ token?: string | null;
19
+ expiresAt?: number | null;
20
+ refreshToken?: string | null;
21
+ user?: unknown;
22
+ }
23
+ /**
24
+ * Options for token exchange operation
25
+ *
26
+ * Used when switching tenant, changing scope, or any operation
27
+ * that exchanges current token for a new one with different claims
28
+ */
29
+ export interface ExchangeTokenOptions {
30
+ /** HTTP method to use. Default: 'POST' */
31
+ method?: 'POST' | 'PUT';
32
+ /** Payload to send with the request (e.g., tenantId, scope) */
33
+ payload?: Record<string, unknown>;
34
+ /** Custom headers for this request. Overrides defaultHeaders if same key. */
35
+ headers?: Record<string, string>;
36
+ }
37
+ /**
38
+ * Auth result returned from auth operations and auth state changes
39
+ * Used by: login(), logout(), refreshToken(), onAuthStateChanged()
40
+ */
41
+ export interface AuthResult {
42
+ /** Whether user is authenticated (has valid non-expired token) */
43
+ authenticated: boolean;
44
+ /** User info from token (if available) */
45
+ user?: unknown;
46
+ /** Token expiry timestamp in milliseconds (if available) */
47
+ expiresAt?: number | null;
48
+ }
49
+ /**
50
+ * Interface for token provider
51
+ *
52
+ * Provider has 3 required methods:
53
+ * - refreshToken: Refresh access token when expired
54
+ * - login: Login with credentials
55
+ * - logout: Logout (clear tokens)
56
+ *
57
+ * User can add custom auth methods (loginWithPhone, loginWithGoogle, etc.)
58
+ * All custom methods must return Result<TokenInfo> for token retrieval
59
+ */
60
+ export interface TokenProvider {
61
+ /**
62
+ * Refresh tokens (required)
63
+ * @param refreshToken - Current refresh token (from worker memory, null if not available)
64
+ * @returns Result<TokenInfo> with new tokens
65
+ */
66
+ refreshToken(refreshToken: string | null): Promise<Result<TokenInfo>>;
67
+ /**
68
+ * Login with credentials (required)
69
+ * @param payload - Login credentials (email/password, etc.)
70
+ * @param url - Optional URL override (if not provided, uses configured loginUrl)
71
+ * @returns Result<TokenInfo> with tokens
72
+ */
73
+ login(payload: unknown, url?: string): Promise<Result<TokenInfo>>;
74
+ /**
75
+ * Logout - clear tokens (required)
76
+ * @param payload - Optional logout payload
77
+ * @returns Result<TokenInfo> with all fields reset (token = '', refreshToken = undefined, user = undefined)
78
+ */
79
+ logout(payload?: unknown): Promise<Result<TokenInfo>>;
80
+ /**
81
+ * Exchange current token for a new one with different context
82
+ *
83
+ * Useful for switching tenants, changing scopes, or any operation
84
+ * that requires exchanging the current token for a new one.
85
+ *
86
+ * @param accessToken - Current access token (injected by worker)
87
+ * @param url - URL to call for token exchange
88
+ * @param options - Exchange options (method, payload)
89
+ * @returns Result<TokenInfo> with new tokens
90
+ */
91
+ exchangeToken(accessToken: string, url: string, options?: ExchangeTokenOptions): Promise<Result<TokenInfo>>;
92
+ /**
93
+ * Custom auth methods (optional)
94
+ * Examples: loginWithPhone, loginWithGoogle, loginWithFacebook, etc.
95
+ * All must return Result<TokenInfo> for token retrieval
96
+ *
97
+ * Note: `never[]` args (not `any[]`) keep the index signature compatible with
98
+ * the specific method signatures above (parameter bivariance) without `any`.
99
+ */
100
+ [key: string]: (...args: never[]) => Promise<Result<TokenInfo>>;
101
+ }
102
+ /**
103
+ * Storage error context for debugging
104
+ */
105
+ export type StorageErrorContext = 'get' | 'set' | 'delete' | 'open';
106
+ /**
107
+ * Storage error callback type
108
+ * Called when IndexedDB operations fail (quota exceeded, permission denied, etc.)
109
+ * Storage still fails closed (returns null), but this allows logging/debugging.
110
+ */
111
+ export type StorageErrorCallback = (error: Error, context: StorageErrorContext) => void;
112
+ /**
113
+ * Interface for refresh token storage - only stores refresh token
114
+ *
115
+ * Access token is always stored in worker memory.
116
+ * Refresh token storage is OPTIONAL:
117
+ * - If available (IndexedDB): persist refresh token for reuse after reload
118
+ * - If not (undefined): cookie-based auth (httpOnly cookie)
119
+ */
120
+ export interface RefreshTokenStorage {
121
+ get(): Promise<string | null>;
122
+ set(token: string | null): Promise<void>;
123
+ }
124
+ /**
125
+ * Interface for token parser - parse token from backend response
126
+ * Parser returns complete TokenInfo (including user data)
127
+ */
128
+ export interface TokenParser {
129
+ parse(response: Response): Promise<TokenInfo>;
130
+ }
131
+ /**
132
+ * Interface for auth strategy - defines how to call auth APIs
133
+ *
134
+ * Strategy focuses only on API calls, returns Response
135
+ * Provider handles parsing and storage
136
+ *
137
+ * All methods are required
138
+ */
139
+ export interface AuthStrategy {
140
+ /** Refresh access token */
141
+ refresh(refreshToken: string | null): Promise<Response>;
142
+ /**
143
+ * Login with credentials
144
+ * @param payload - Login credentials
145
+ * @param url - Optional URL override (if not provided, uses configured loginUrl)
146
+ */
147
+ login(payload: unknown, url?: string): Promise<Response>;
148
+ /** Logout */
149
+ logout(payload?: unknown): Promise<Response>;
150
+ /**
151
+ * Exchange current token for a new one with different context
152
+ * @param accessToken - Current access token
153
+ * @param url - URL to call for token exchange
154
+ * @param options - Exchange options (method, payload)
155
+ */
156
+ exchangeToken(accessToken: string, url: string, options?: ExchangeTokenOptions): Promise<Response>;
157
+ }
158
+ /**
159
+ * Provider preset configuration for built-in auth strategies
160
+ */
161
+ export interface ProviderPresetConfig {
162
+ type: 'cookie-auth' | 'body-auth';
163
+ refreshUrl: string;
164
+ loginUrl: string;
165
+ logoutUrl: string;
166
+ refreshTokenKey?: string;
167
+ /** Custom headers to include in all auth requests (login, logout, refresh) */
168
+ headers?: Record<string, string>;
169
+ }
170
+ /**
171
+ * Configuration for FetchGuard client
172
+ */
173
+ export interface FetchGuardOptions {
174
+ /**
175
+ * Token provider - 3 options:
176
+ * 1. TokenProvider instance (for custom providers)
177
+ * 2. ProviderPresetConfig object (for built-in presets)
178
+ * 3. string (for registry lookup - advanced usage)
179
+ */
180
+ provider: TokenProvider | ProviderPresetConfig | string;
181
+ /** List of allowed domains (wildcard supported) */
182
+ allowedDomains?: string[];
183
+ /** Early refresh time for tokens (ms) */
184
+ refreshEarlyMs?: number;
185
+ /** Default headers to include in all requests */
186
+ defaultHeaders?: Record<string, string>;
187
+ /**
188
+ * Maximum concurrent requests to worker (default: 6)
189
+ * Controls how many requests can be in-flight simultaneously.
190
+ * Set to 1 for strictly sequential processing.
191
+ * Higher values increase throughput but may cause worker congestion.
192
+ */
193
+ maxConcurrent?: number;
194
+ /**
195
+ * Maximum queue size for pending requests (default: 1000)
196
+ * When queue is full, new requests will immediately fail with QUEUE_FULL error.
197
+ * Prevents memory leak if worker is unresponsive.
198
+ */
199
+ maxQueueSize?: number;
200
+ /**
201
+ * Worker setup timeout in milliseconds (default: 10000)
202
+ * How long to wait for worker to be ready before failing.
203
+ */
204
+ setupTimeout?: number;
205
+ /**
206
+ * Default request timeout in milliseconds (default: 30000)
207
+ * How long to wait for a request to complete before timing out.
208
+ * Can be overridden per-request via fetch options.
209
+ */
210
+ requestTimeout?: number;
211
+ /**
212
+ * Debug hooks for observing operations (logging, monitoring)
213
+ * All hooks are observe-only - they cannot modify requests/responses.
214
+ */
215
+ debug?: DebugHooks;
216
+ /**
217
+ * Retry configuration for network errors
218
+ * Only retries on transport failures, NOT on HTTP errors (4xx/5xx)
219
+ */
220
+ retry?: RetryConfig;
221
+ /**
222
+ * Request deduplication configuration
223
+ * When enabled, duplicate requests to the same URL within a time window
224
+ * will share the same response instead of making multiple requests.
225
+ */
226
+ dedupe?: DedupeConfig;
227
+ /**
228
+ * Custom worker factory function
229
+ *
230
+ * Use this when you need a custom provider with parser/strategy functions.
231
+ * Create a custom worker file that imports 'fetchguard/worker' and registers
232
+ * your provider, then pass a factory function that creates that worker.
233
+ *
234
+ * @example
235
+ * ```ts
236
+ * // my-worker.ts
237
+ * import 'fetchguard/worker'
238
+ * import { registerProvider, createProvider, ... } from 'fetchguard'
239
+ * const myProvider = createProvider({ parser: myParser, strategy: myStrategy, ... })
240
+ * registerProvider('my-auth', myProvider)
241
+ *
242
+ * // main.ts
243
+ * import MyWorker from './my-worker?worker'
244
+ * const api = createClient({
245
+ * provider: 'my-auth',
246
+ * workerFactory: () => new MyWorker()
247
+ * })
248
+ * ```
249
+ */
250
+ workerFactory?: () => Worker;
251
+ }
252
+ /**
253
+ * Internal worker configuration
254
+ */
255
+ export interface WorkerConfig {
256
+ allowedDomains: string[];
257
+ refreshEarlyMs: number;
258
+ defaultHeaders: Record<string, string>;
259
+ }
260
+ /**
261
+ * Extended RequestInit with FetchGuard-specific options
262
+ */
263
+ export interface FetchGuardRequestInit extends RequestInit {
264
+ /** Whether this request requires authentication. Default: true */
265
+ requiresAuth?: boolean;
266
+ /** Include response headers in result metadata and FETCH_RESULT payload */
267
+ includeHeaders?: boolean;
268
+ }
269
+ /**
270
+ * Fetch envelope - raw HTTP response from worker
271
+ *
272
+ * Worker only fetches and returns raw data, does NOT judge HTTP status.
273
+ * Client receives envelope and decides ok/err based on business logic.
274
+ *
275
+ * - status: HTTP status code (2xx, 3xx, 4xx, 5xx)
276
+ * - body: string (text/JSON) or base64 (binary)
277
+ * - contentType: always present, indicates how to decode body
278
+ * - headers: empty object if includeHeaders: false
279
+ */
280
+ export interface FetchEnvelope {
281
+ status: number;
282
+ body: string;
283
+ contentType: string;
284
+ headers: Record<string, string>;
285
+ }
286
+ /**
287
+ * Serialized file data for transfer over postMessage
288
+ * Uses ArrayBuffer for zero-copy transfer via Transferable
289
+ */
290
+ export interface SerializedFile {
291
+ name: string;
292
+ type: string;
293
+ /** ArrayBuffer - transferred via postMessage Transferable for zero-copy */
294
+ buffer: ArrayBuffer;
295
+ }
296
+ /**
297
+ * Serialized FormData entry - can be string or file
298
+ */
299
+ export type SerializedFormDataEntry = string | SerializedFile;
300
+ /**
301
+ * Serialized FormData for transfer over postMessage
302
+ */
303
+ export interface SerializedFormData {
304
+ _type: 'FormData';
305
+ entries: Array<[string, SerializedFormDataEntry]>;
306
+ }
307
+ /**
308
+ * Result of FormData serialization with transferables
309
+ * Used for zero-copy transfer via postMessage
310
+ */
311
+ export interface SerializedFormDataResult {
312
+ data: SerializedFormData;
313
+ /** ArrayBuffers to transfer - pass to postMessage as second argument */
314
+ transferables: ArrayBuffer[];
315
+ }
316
+ /**
317
+ * Network error detail for transport failures
318
+ * Used when no HTTP response is received (connection failed, timeout, cancelled)
319
+ */
320
+ export interface NetworkErrorDetail {
321
+ code: 'NETWORK_ERROR' | 'REQUEST_CANCELLED' | 'RESPONSE_PARSE_FAILED';
322
+ message: string;
323
+ }
324
+ /**
325
+ * Reason for token refresh
326
+ */
327
+ export type RefreshReason = 'expired' | 'proactive' | 'manual';
328
+ /**
329
+ * Request timing metrics for performance monitoring
330
+ *
331
+ * All times are in milliseconds.
332
+ */
333
+ export interface RequestMetrics {
334
+ /** When request was initiated (Date.now()) */
335
+ startTime: number;
336
+ /** When response was received (Date.now()) */
337
+ endTime: number;
338
+ /** Total duration (endTime - startTime) */
339
+ duration: number;
340
+ /** Time spent waiting in queue before processing */
341
+ queueTime: number;
342
+ /** Time spent in IPC (postMessage round-trip overhead) */
343
+ ipcTime: number;
344
+ }
345
+ /**
346
+ * Debug hooks for observing FetchGuard operations
347
+ *
348
+ * All hooks are observe-only - they cannot modify requests/responses.
349
+ * Useful for logging, debugging, and monitoring.
350
+ *
351
+ * Note: Hooks run synchronously and should not perform heavy operations.
352
+ */
353
+ export interface DebugHooks {
354
+ /**
355
+ * Called before each request is sent to worker
356
+ * @param url - Request URL
357
+ * @param options - Request options (method, headers, etc.)
358
+ */
359
+ onRequest?: (url: string, options: FetchGuardRequestInit) => void;
360
+ /**
361
+ * Called when response is received from worker
362
+ * @param url - Request URL
363
+ * @param envelope - Response envelope (status, body, headers)
364
+ * @param metrics - Request timing metrics (optional, for performance monitoring)
365
+ */
366
+ onResponse?: (url: string, envelope: FetchEnvelope, metrics?: RequestMetrics) => void;
367
+ /**
368
+ * Called when token refresh occurs
369
+ * @param reason - Why refresh happened: 'expired', 'proactive', or 'manual'
370
+ */
371
+ onRefresh?: (reason: RefreshReason) => void;
372
+ /**
373
+ * Called when transport error occurs (network failure, timeout, cancelled)
374
+ * @param url - Request URL
375
+ * @param error - Error detail with code and message
376
+ * @param metrics - Request timing metrics (optional)
377
+ */
378
+ onError?: (url: string, error: NetworkErrorDetail, metrics?: RequestMetrics) => void;
379
+ /**
380
+ * Called when worker is ready after initialization
381
+ */
382
+ onWorkerReady?: () => void;
383
+ /**
384
+ * Called when worker encounters a fatal error
385
+ * @param error - Error event from worker
386
+ */
387
+ onWorkerError?: (error: ErrorEvent) => void;
388
+ }
389
+ /**
390
+ * Retry configuration for network errors
391
+ *
392
+ * Only retries on transport failures (network error, timeout).
393
+ * Does NOT retry on HTTP errors (4xx/5xx) - those are valid responses.
394
+ * Does NOT retry cancelled requests.
395
+ */
396
+ export interface RetryConfig {
397
+ /**
398
+ * Maximum number of retry attempts (default: 0 = no retry)
399
+ */
400
+ maxAttempts?: number;
401
+ /**
402
+ * Delay between retries in milliseconds (default: 1000)
403
+ */
404
+ delay?: number;
405
+ /**
406
+ * Exponential backoff multiplier (default: 1 = no backoff)
407
+ * Example: delay=1000, backoff=2 => 1s, 2s, 4s, 8s...
408
+ */
409
+ backoff?: number;
410
+ /**
411
+ * Maximum delay in milliseconds (default: 30000)
412
+ * Caps the delay when using exponential backoff
413
+ */
414
+ maxDelay?: number;
415
+ /**
416
+ * Jitter factor to add randomness to retry delays (default: 0 = no jitter)
417
+ * Range: 0 to 1 (e.g., 0.5 = ±50% randomness)
418
+ * Helps prevent thundering herd when many clients retry simultaneously.
419
+ *
420
+ * Note: Jitter is only applied when shouldRetry returns true.
421
+ * If request fails permanently, no jitter delay occurs.
422
+ *
423
+ * Example: delay=1000, jitter=0.5 => delay between 500ms and 1500ms
424
+ */
425
+ jitter?: number;
426
+ /**
427
+ * Custom condition to determine if error should be retried
428
+ * Default: retry on NETWORK_ERROR only
429
+ * @param error - The error that occurred
430
+ * @returns true to retry, false to fail immediately
431
+ */
432
+ shouldRetry?: (error: NetworkErrorDetail) => boolean;
433
+ }
434
+ /**
435
+ * Request deduplication configuration
436
+ *
437
+ * When enabled, identical GET requests (same URL) within a time window
438
+ * will share the same in-flight request instead of making duplicates.
439
+ *
440
+ * IMPORTANT:
441
+ * - Only applies to GET requests (POST/PUT/DELETE are never deduplicated)
442
+ * - Only deduplicates in-flight requests (not caching)
443
+ * - Safe for most read operations
444
+ */
445
+ export interface DedupeConfig {
446
+ /**
447
+ * Enable deduplication (default: false)
448
+ */
449
+ enabled?: boolean;
450
+ /**
451
+ * Time window in milliseconds to consider requests as duplicates (default: 0)
452
+ * 0 = only dedupe concurrent/in-flight requests
453
+ * >0 = also dedupe requests within this time window after completion
454
+ */
455
+ window?: number;
456
+ /**
457
+ * Custom key generator for deduplication
458
+ * Default: uses URL only for GET requests
459
+ * @param url - Request URL
460
+ * @param options - Request options
461
+ * @returns Key string, or null to skip deduplication for this request
462
+ */
463
+ keyGenerator?: (url: string, options: FetchGuardRequestInit) => string | null;
464
+ }
465
+ /**
466
+ * Transport result - represents the outcome of a network request
467
+ *
468
+ * IMPORTANT: This is a TRANSPORT result, not a business result.
469
+ * - ok = HTTP response received (check envelope.status for 2xx/4xx/5xx)
470
+ * - err = Network failure (no response received)
471
+ *
472
+ * Example:
473
+ * ```typescript
474
+ * const result = await api.get('/users')
475
+ * if (result.ok) {
476
+ * // Transport succeeded - got HTTP response
477
+ * if (result.data.status >= 200 && result.data.status < 400) {
478
+ * // Business success
479
+ * } else {
480
+ * // Business error (4xx/5xx) - still has response body
481
+ * }
482
+ * } else {
483
+ * // Transport failed - no response (network error, timeout, cancelled)
484
+ * }
485
+ * ```
486
+ */
487
+ export type TransportResult = Result<FetchEnvelope>;
488
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/transport/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /// <reference lib="webworker" />
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/transport/types.ts"],"names":[],"mappings":"AAAA,iCAAiC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Binary data utilities for Worker
3
+ * Handles ArrayBuffer <-> Base64 conversion for binary responses
4
+ */
5
+ /**
6
+ * Convert ArrayBuffer to base64 string
7
+ * Used in worker to encode binary responses for postMessage transfer
8
+ */
9
+ export declare function arrayBufferToBase64(buffer: ArrayBuffer): string;
10
+ /**
11
+ * Convert base64 string to ArrayBuffer
12
+ * Used in client to decode binary responses
13
+ */
14
+ export declare function base64ToArrayBuffer(base64: string): ArrayBuffer;
15
+ /**
16
+ * Check if content type is binary (should be base64 encoded)
17
+ * Returns true for images, PDFs, videos, etc.
18
+ */
19
+ export declare function isBinaryContentType(contentType: string): boolean;
20
+ //# sourceMappingURL=binary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binary.d.ts","sourceRoot":"","sources":["../../../src/transport/utils/binary.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAO/D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAO/D;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAahE"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Binary data utilities for Worker
3
+ * Handles ArrayBuffer <-> Base64 conversion for binary responses
4
+ */
5
+ /**
6
+ * Convert ArrayBuffer to base64 string
7
+ * Used in worker to encode binary responses for postMessage transfer
8
+ */
9
+ export function arrayBufferToBase64(buffer) {
10
+ const bytes = new Uint8Array(buffer);
11
+ let binary = '';
12
+ for (let i = 0; i < bytes.length; i++) {
13
+ binary += String.fromCharCode(bytes[i]);
14
+ }
15
+ return btoa(binary);
16
+ }
17
+ /**
18
+ * Convert base64 string to ArrayBuffer
19
+ * Used in client to decode binary responses
20
+ */
21
+ export function base64ToArrayBuffer(base64) {
22
+ const binary = atob(base64);
23
+ const bytes = new Uint8Array(binary.length);
24
+ for (let i = 0; i < binary.length; i++) {
25
+ bytes[i] = binary.charCodeAt(i);
26
+ }
27
+ return bytes.buffer;
28
+ }
29
+ /**
30
+ * Check if content type is binary (should be base64 encoded)
31
+ * Returns true for images, PDFs, videos, etc.
32
+ */
33
+ export function isBinaryContentType(contentType) {
34
+ const normalized = contentType.toLowerCase();
35
+ // Text types - NOT binary
36
+ if (normalized.includes('text/'))
37
+ return false;
38
+ if (normalized.includes('json'))
39
+ return false;
40
+ if (normalized.includes('xml'))
41
+ return false;
42
+ if (normalized.includes('javascript'))
43
+ return false;
44
+ if (normalized.includes('ecmascript'))
45
+ return false;
46
+ if (normalized.includes('html'))
47
+ return false;
48
+ // Everything else is considered binary
49
+ return true;
50
+ }
51
+ //# sourceMappingURL=binary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binary.js","sourceRoot":"","sources":["../../../src/transport/utils/binary.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAmB;IACrD,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;IACrC,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC;AACtB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAc;IAChD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,CAAC;AACtB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,WAAmB;IACrD,MAAM,UAAU,GAAG,WAAW,CAAC,WAAW,EAAE,CAAC;IAE7C,0BAA0B;IAC1B,IAAI,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/C,IAAI,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9C,IAAI,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7C,IAAI,UAAU,CAAC,QAAQ,CAAC,YAAY,CAAC;QAAE,OAAO,KAAK,CAAC;IACpD,IAAI,UAAU,CAAC,QAAQ,CAAC,YAAY,CAAC;QAAE,OAAO,KAAK,CAAC;IACpD,IAAI,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IAE9C,uCAAuC;IACvC,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,26 @@
1
+ import type { SerializedFormData, SerializedFormDataResult } from '../types';
2
+ /**
3
+ * Serialize FormData for transfer over postMessage
4
+ *
5
+ * FormData cannot be cloned via postMessage, so we need to serialize it first.
6
+ * Files are converted to ArrayBuffer and returned as transferables for zero-copy transfer.
7
+ *
8
+ * IMPORTANT: Preserves original field order by using single-pass iteration.
9
+ *
10
+ * @returns SerializedFormDataResult with data and transferables array
11
+ */
12
+ export declare function serializeFormData(formData: FormData): Promise<SerializedFormDataResult>;
13
+ /**
14
+ * Deserialize SerializedFormData back to FormData in worker
15
+ * Reconstructs File objects from transferred ArrayBuffers
16
+ */
17
+ export declare function deserializeFormData(serialized: SerializedFormData): FormData;
18
+ /**
19
+ * Check if body is FormData
20
+ */
21
+ export declare function isFormData(body: unknown): body is FormData;
22
+ /**
23
+ * Check if serialized body is SerializedFormData
24
+ */
25
+ export declare function isSerializedFormData(body: unknown): body is SerializedFormData;
26
+ //# sourceMappingURL=formdata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formdata.d.ts","sourceRoot":"","sources":["../../../src/transport/utils/formdata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,kBAAkB,EAA2B,wBAAwB,EAAE,MAAM,UAAU,CAAC;AAEtH;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAuC7F;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,kBAAkB,GAAG,QAAQ,CAc5E;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,QAAQ,CAE1D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,kBAAkB,CAE9E"}