@angular-helpers/worker-http 21.1.0 → 21.2.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.
package/README.md CHANGED
@@ -88,6 +88,9 @@ Then follow the setup in the `/backend` section below.
88
88
 
89
89
  A framework-agnostic, type-safe bridge between the main thread and a Web Worker. Wraps `postMessage` with request/response correlation, Observable API, and automatic cancellation on unsubscribe.
90
90
 
91
+ <details>
92
+ <summary><strong>API and examples</strong></summary>
93
+
91
94
  ```typescript
92
95
  import { createWorkerTransport } from '@angular-helpers/worker-http/transport';
93
96
 
@@ -146,12 +149,17 @@ transport.execute(request).subscribe({
146
149
  });
147
150
  ```
148
151
 
152
+ </details>
153
+
149
154
  ---
150
155
 
151
156
  ### `/interceptors` — Worker-side pipeline
152
157
 
153
158
  Pure-function interceptors that run inside the worker. No Angular DI, no DOM access — just `(req, next) => Promise<response>`.
154
159
 
160
+ <details>
161
+ <summary><strong>Setup, built-in interceptors, and custom interceptors</strong></summary>
162
+
155
163
  #### Setup in your worker file
156
164
 
157
165
  ```typescript
@@ -283,11 +291,22 @@ export const authTokenInterceptor: WorkerInterceptorFn = (req, next) => {
283
291
  };
284
292
  ```
285
293
 
294
+ </details>
295
+
286
296
  ---
287
297
 
288
298
  ### `/serializer` — Pluggable serialization
289
299
 
290
- Handles the `postMessage` serialization boundary. Structured clone is zero-overhead but loses `Date`, `Map`, `Set` fidelity. `seroval` preserves full type fidelity. The auto-serializer picks the best strategy per payload.
300
+ Handles the `postMessage` serialization boundary. Three strategies, each with a clear sweet spot:
301
+
302
+ - `structuredCloneSerializer` — zero overhead, default
303
+ - `createToonSerializer()` — 30–60% smaller for uniform arrays of objects
304
+ - `createSerovalSerializer()` — full type fidelity (`Date`, `Map`, `Set`, circular refs)
305
+
306
+ The auto-serializer picks the best strategy per payload.
307
+
308
+ <details>
309
+ <summary><strong>Per-strategy API and examples</strong></summary>
291
310
 
292
311
  #### `structuredCloneSerializer` (default)
293
312
 
@@ -316,14 +335,49 @@ const original = serializer.deserialize(payload);
316
335
  // original.tags instanceof Set → true
317
336
  ```
318
337
 
338
+ #### `createToonSerializer()` — Token-Oriented Object Notation
339
+
340
+ Requires `@toon-format/toon` as an optional peer dependency (`npm install @toon-format/toon`).
341
+
342
+ [TOON](https://toonformat.dev) declares object keys once and emits values as CSV-like rows. For uniform arrays of objects (the most common API response shape — `User[]`, `Product[]`, paginated lists), it cuts payload size by **30–60%** compared to JSON, with negligible parsing overhead.
343
+
344
+ ```typescript
345
+ import { createToonSerializer } from '@angular-helpers/worker-http/serializer';
346
+
347
+ const serializer = await createToonSerializer();
348
+
349
+ const payload = serializer.serialize([
350
+ { id: 1, name: 'Alice', role: 'admin' },
351
+ { id: 2, name: 'Bob', role: 'member' },
352
+ { id: 3, name: 'Carol', role: 'member' },
353
+ { id: 4, name: 'Dave', role: 'guest' },
354
+ { id: 5, name: 'Eve', role: 'admin' },
355
+ ]);
356
+
357
+ // payload.data is a TOON string:
358
+ // [5]{id,name,role}:
359
+ // 1,Alice,admin
360
+ // 2,Bob,member
361
+ // 3,Carol,member
362
+ // 4,Dave,guest
363
+ // 5,Eve,admin
364
+ ```
365
+
366
+ **When TOON shines**: uniform arrays of objects with primitive values (numbers, strings, booleans, nulls) at depth-1.
367
+
368
+ **When TOON does NOT help**: payloads with `Date`, `Map`, `Set`, nested objects, or single objects — use `seroval` or structured clone instead.
369
+
319
370
  #### `createAutoSerializer()` — Smart auto-detection
320
371
 
321
- Automatically picks the best strategy per payload. The factory is async (pre-loads `seroval` during initialization), but the returned serializer is fully synchronous.
372
+ Automatically picks the best strategy per payload. The factory is async (pre-loads `seroval` and `@toon-format/toon` during initialization, both optional), but the returned serializer is fully synchronous.
322
373
 
323
- **Detection logic (depth-1):**
374
+ **Detection logic (depth-1, top-down, first match wins):**
324
375
 
325
- - Contains `Date`, `Map`, `Set`, or `RegExp` at the top level or as direct array/object values → `seroval`
326
- - Otherwise structured clone (zero overhead)
376
+ 1. Contains `Date`, `Map`, `Set`, or `RegExp` at the top level or as direct array/object values → `seroval`
377
+ 2. Uniform array of plain objects with primitive values, length ≥ 5 → `toon`
378
+ 3. Otherwise → structured clone (zero overhead)
379
+
380
+ The TOON threshold is conservative (length ≥ 5). Smaller arrays don't justify the encoding overhead.
327
381
 
328
382
  Payloads larger than `transferThreshold` (default: 100 KiB) are encoded to `ArrayBuffer` and transferred zero-copy.
329
383
 
@@ -346,12 +400,17 @@ auto.serialize(hugeDataset); // transferables: [ArrayBuffer]
346
400
 
347
401
  > **Depth-1 limitation**: `[{ createdAt: new Date() }]` — the `Date` is inside a nested object; not detected at depth-1. For deeply nested complex types, use `createSerovalSerializer()` directly.
348
402
 
403
+ </details>
404
+
349
405
  ---
350
406
 
351
407
  ### `/crypto` — WebCrypto primitives
352
408
 
353
409
  Standalone WebCrypto utilities. Useful in both workers and the main thread, but workers provide memory isolation for key material.
354
410
 
411
+ <details>
412
+ <summary><strong>HMAC, AES, hashing examples</strong></summary>
413
+
355
414
  #### `createHmacSigner(config)`
356
415
 
357
416
  ```typescript
@@ -386,12 +445,17 @@ const hasher = createContentHasher();
386
445
  const hash = await hasher.hash('SHA-256', data); // → hex string
387
446
  ```
388
447
 
448
+ </details>
449
+
389
450
  ---
390
451
 
391
452
  ### `/backend` — Angular `HttpBackend` replacement
392
453
 
393
454
  Drop-in replacement for Angular's `HttpBackend` that transparently routes `HttpClient` requests to Web Workers. Use `WorkerHttpClient` exactly like `HttpClient` — the routing is invisible to application code.
394
455
 
456
+ <details>
457
+ <summary><strong>Configuration, providers, and consumer code</strong></summary>
458
+
395
459
  ```typescript
396
460
  // app.config.ts
397
461
  import {
@@ -480,13 +544,18 @@ manual control.
480
544
  - `matchWorkerRoute(url, routes)` — pure utility to test routing rules
481
545
  - `WORKER_HTTP_SIGNAL`, `WORKER_HTTP_TIMEOUT` — `HttpContextToken`s used internally by `WorkerHttpClient`; set them directly on the `HttpContext` if you're using `HttpClient` rather than the wrapper.
482
546
 
547
+ </details>
548
+
483
549
  ---
484
550
 
485
551
  ## Telemetry
486
552
 
487
- Main-thread extension point for APM / metrics. `withTelemetry(...)` registers
488
- a subscriber that fires synchronously at three lifecycle points of every
489
- request handled by `WorkerHttpBackend`:
553
+ Main-thread extension point for APM / metrics. `withTelemetry(...)` registers a subscriber that fires synchronously at three lifecycle points of every request handled by `WorkerHttpBackend` (`onRequest`, `onResponse`, `onError`).
554
+
555
+ <details>
556
+ <summary><strong>Subscriber semantics, examples, and event interface</strong></summary>
557
+
558
+ Lifecycle points:
490
559
 
491
560
  - **`onRequest`** — after worker resolution, before dispatch
492
561
  - **`onResponse`** — when a successful response is emitted
@@ -553,12 +622,17 @@ interface WorkerHttpTelemetryEventBase {
553
622
  // onError adds: kind: 'error', error, durationMs
554
623
  ```
555
624
 
625
+ </details>
626
+
556
627
  ---
557
628
 
558
629
  ### `/esbuild-plugin` — Interceptor auto-bundling
559
630
 
560
631
  An esbuild plugin that automatically discovers and bundles interceptor files into your worker builds. When using Angular with a custom webpack/esbuild configuration, this ensures your interceptors are included in the worker bundle without manual imports.
561
632
 
633
+ <details>
634
+ <summary><strong>Plugin options and example</strong></summary>
635
+
562
636
  ```typescript
563
637
  // esbuild.config.ts
564
638
  import { workerHttpPlugin } from '@angular-helpers/worker-http/esbuild-plugin';
@@ -585,12 +659,17 @@ export default {
585
659
 
586
660
  Discovered interceptors are merged with explicit ones. Test files (`.spec.ts`, `.test.ts`) are automatically excluded.
587
661
 
662
+ </details>
663
+
588
664
  ---
589
665
 
590
666
  ### `/streams-polyfill` — Safari transferable streams
591
667
 
592
668
  Safari 16-17 lack native transferable `ReadableStream`/`TransformStream` support. This ponyfill enables stream transfer in workers for those browsers, loaded lazily only when needed.
593
669
 
670
+ <details>
671
+ <summary><strong>Setup and bundle impact</strong></summary>
672
+
594
673
  ```typescript
595
674
  // Enable in your app config (main thread)
596
675
  import { withWorkerStreamsPolyfill } from '@angular-helpers/worker-http/backend';
@@ -608,12 +687,16 @@ provideWorkerHttpClient(
608
687
 
609
688
  **Bundle impact:** Zero for modern browsers. The polyfill is lazy-loaded only on affected Safari versions when streams are actually used.
610
689
 
690
+ </details>
691
+
611
692
  ---
612
693
 
613
694
  ## SSR + hydration
614
695
 
615
- Worker HTTP integrates transparently with Angular SSR. The two problems SSR
616
- creates for worker-based HTTP are handled out of the box:
696
+ Worker HTTP integrates transparently with Angular SSR. SSR's two problems for worker-based HTTP — missing `Worker` global on the server and the post-hydration re-fetch — are both handled out of the box.
697
+
698
+ <details>
699
+ <summary><strong>How SSR fallback and the transfer cache work</strong></summary>
617
700
 
618
701
  **1. Workers do not exist on the server.**
619
702
  During SSR, `typeof Worker === 'undefined'`. `WorkerHttpBackend` detects this
@@ -663,6 +746,8 @@ To customise which headers are captured or to cache `POST` requests, pass
663
746
  `withHttpTransferCacheOptions(...)` to `provideClientHydration()` — both are
664
747
  re-exported from `@angular/platform-browser`.
665
748
 
749
+ </details>
750
+
666
751
  ---
667
752
 
668
753
  ## Design principles
@@ -677,13 +762,14 @@ re-exported from `@angular/platform-browser`.
677
762
 
678
763
  ## Serialization strategy decision guide
679
764
 
680
- | Payload type | Recommended serializer | Reason |
681
- | ------------------------------------ | -------------------------------------- | --------------------------- |
682
- | Simple objects, arrays of primitives | `structuredCloneSerializer` (default) | Zero overhead |
683
- | Objects with `Date`, `Map`, `Set` | `createSerovalSerializer()` | Full type fidelity |
684
- | Unknown payload shape | `createAutoSerializer()` | Depth-1 auto-detect |
685
- | Large arrays (> 100 KiB) | `createAutoSerializer()` | Auto ArrayBuffer transfer |
686
- | Deeply nested complex types | `createSerovalSerializer()` explicitly | Auto-detect is depth-1 only |
765
+ | Payload type | Recommended serializer | Reason |
766
+ | ------------------------------------------ | -------------------------------------- | --------------------------- |
767
+ | Simple objects, arrays of primitives | `structuredCloneSerializer` (default) | Zero overhead |
768
+ | Uniform array of plain objects (≥ 5 items) | `createToonSerializer()` | 30–60% size reduction |
769
+ | Objects with `Date`, `Map`, `Set` | `createSerovalSerializer()` | Full type fidelity |
770
+ | Unknown payload shape | `createAutoSerializer()` | Depth-1 auto-detect |
771
+ | Large arrays (> 100 KiB) | `createAutoSerializer()` | Auto ArrayBuffer transfer |
772
+ | Deeply nested complex types | `createSerovalSerializer()` explicitly | Auto-detect is depth-1 only |
687
773
 
688
774
  ---
689
775
 
@@ -701,9 +787,12 @@ Server-Side Rendering (SSR) is supported via automatic fallback to the main thre
701
787
 
702
788
  ## Benchmarks
703
789
 
704
- A reproducible benchmark suite ships with the demo app at
705
- [`/demo/worker-http-benchmark`](../../src/app/demo/worker-http-benchmark) and compares three
706
- transport modes across four workloads:
790
+ A reproducible benchmark suite ships with the demo app at [`/demo/worker-http-benchmark`](../../src/app/demo/worker-http-benchmark).
791
+
792
+ <details>
793
+ <summary><strong>Modes, workloads, metrics, and how to run</strong></summary>
794
+
795
+ It compares three transport modes across four workloads:
707
796
 
708
797
  | Mode | What it measures |
709
798
  | --------------- | ---------------------------------------------------------- |
@@ -739,6 +828,8 @@ npm start
739
828
  Numbers vary by hardware, browser, and current system load — always run a scenario several times
740
829
  and watch the trend, not a single value.
741
830
 
831
+ </details>
832
+
742
833
  ---
743
834
 
744
835
  ## Related documentation
@@ -71,6 +71,138 @@ async function createSerovalSerializer() {
71
71
  };
72
72
  }
73
73
 
74
+ let cachedToon = null;
75
+ async function loadToon() {
76
+ if (!cachedToon) {
77
+ try {
78
+ // Dynamic import via variable — keeps @toon-format/toon as optional peer dep (no static reference)
79
+ const id = '@toon-format/toon';
80
+ cachedToon = (await import(/* @vite-ignore */ id));
81
+ }
82
+ catch {
83
+ throw new Error('@toon-format/toon is required as a peer dependency. ' +
84
+ 'Install it with: npm install @toon-format/toon');
85
+ }
86
+ }
87
+ return cachedToon;
88
+ }
89
+ /**
90
+ * Creates a `WorkerSerializer` backed by `@toon-format/toon` (Token-Oriented Object Notation).
91
+ *
92
+ * TOON is a compact, schema-aware encoding of the JSON data model that declares object
93
+ * keys once and emits values as CSV-like rows. It typically reduces size by **30–60%**
94
+ * for uniform arrays of objects (e.g. `User[]`, `Product[]`, paginated lists), with
95
+ * negligible parsing overhead.
96
+ *
97
+ * **When to use it**:
98
+ * - Worker↔main `postMessage` payloads dominated by uniform arrays of objects
99
+ * - Cases where `structuredClone` cost is dominated by repeated key strings
100
+ *
101
+ * **When NOT to use it**:
102
+ * - Payloads containing `Date`, `Map`, `Set`, `RegExp` (use `seroval` instead)
103
+ * - Small / single-object payloads (overhead not justified)
104
+ *
105
+ * The factory is async because it dynamically imports the optional `@toon-format/toon` peer.
106
+ *
107
+ * `@toon-format/toon` must be installed separately:
108
+ * ```
109
+ * npm install @toon-format/toon
110
+ * ```
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * const serializer = await createToonSerializer();
115
+ * const payload = serializer.serialize([
116
+ * { id: 1, name: 'Alice' },
117
+ * { id: 2, name: 'Bob' },
118
+ * { id: 3, name: 'Carol' },
119
+ * { id: 4, name: 'Dave' },
120
+ * { id: 5, name: 'Eve' },
121
+ * ]);
122
+ * worker.postMessage({ payload }, payload.transferables);
123
+ * ```
124
+ *
125
+ * @see https://toonformat.dev
126
+ */
127
+ async function createToonSerializer() {
128
+ const { encode, decode } = await loadToon();
129
+ return {
130
+ serialize(data) {
131
+ return {
132
+ data: encode(data),
133
+ transferables: [],
134
+ format: 'toon',
135
+ };
136
+ },
137
+ deserialize(payload) {
138
+ if (payload.format !== 'toon') {
139
+ throw new Error(`Expected format 'toon', got '${payload.format}'`);
140
+ }
141
+ return decode(payload.data);
142
+ },
143
+ };
144
+ }
145
+ /**
146
+ * Conservative threshold below which TOON's overhead outweighs its size benefit.
147
+ * Auto-serializer keeps shorter arrays on `structured-clone`.
148
+ *
149
+ * Exported for testing and for consumers building custom routing logic.
150
+ */
151
+ const MIN_UNIFORM_ARRAY_LENGTH = 5;
152
+ /**
153
+ * Detects whether a value is a depth-1 uniform array of plain objects with primitive values.
154
+ *
155
+ * Pure function — no side effects. Used by `createAutoSerializer()` to decide
156
+ * whether to route a payload through TOON.
157
+ *
158
+ * Conditions checked (all must pass):
159
+ * 1. Value is an array with `length >= MIN_UNIFORM_ARRAY_LENGTH`
160
+ * 2. Every item is a non-null, non-array plain object
161
+ * 3. Every item has the same set of keys as the first item
162
+ * 4. Every value across all items is a primitive (string, number, boolean, null)
163
+ *
164
+ * @example
165
+ * ```typescript
166
+ * isUniformObjectArray([{a:1},{a:2},{a:3},{a:4},{a:5}]); // true
167
+ * isUniformObjectArray([{a:1},{b:2}]); // false (heterogeneous keys)
168
+ * isUniformObjectArray([{a:[1,2]},{a:[3,4]}]); // false (nested array value)
169
+ * isUniformObjectArray([{a:1}]); // false (length < 5)
170
+ * ```
171
+ */
172
+ function isUniformObjectArray(value) {
173
+ if (!Array.isArray(value) || value.length < MIN_UNIFORM_ARRAY_LENGTH) {
174
+ return false;
175
+ }
176
+ const first = value[0];
177
+ if (first === null || typeof first !== 'object' || Array.isArray(first)) {
178
+ return false;
179
+ }
180
+ const expectedKeys = Object.keys(first)
181
+ .sort()
182
+ .join('\u0000');
183
+ if (expectedKeys === '') {
184
+ return false;
185
+ }
186
+ for (const item of value) {
187
+ if (item === null || typeof item !== 'object' || Array.isArray(item)) {
188
+ return false;
189
+ }
190
+ const record = item;
191
+ if (Object.keys(record).sort().join('\u0000') !== expectedKeys) {
192
+ return false;
193
+ }
194
+ for (const v of Object.values(record)) {
195
+ if (v === null)
196
+ continue;
197
+ const t = typeof v;
198
+ if (t !== 'string' && t !== 'number' && t !== 'boolean') {
199
+ return false;
200
+ }
201
+ }
202
+ }
203
+ return true;
204
+ }
205
+
74
206
  /**
75
207
  * Shallow check for complex types at depth-1 that structured-clone cannot preserve.
76
208
  * Depth-1 is intentional: fast and predictable. For deeply nested complex types,
@@ -107,9 +239,11 @@ function encodeToTransferable(str) {
107
239
  * The factory is async because it pre-loads `seroval` during initialization
108
240
  * so the returned serializer methods are fully synchronous (no await in hot path).
109
241
  *
110
- * Strategy selection per `serialize()` call:
111
- * - Contains `Date`, `Map`, `Set`, or `RegExp` at depth-1 → `seroval` (full fidelity)
112
- * - Otherwise structured clone (native, zero overhead)
242
+ * Strategy selection per `serialize()` call (top-down, first match wins):
243
+ * 1. Contains `Date`, `Map`, `Set`, or `RegExp` at depth-1 → `seroval` (full fidelity)
244
+ * 2. Uniform array of plain objects (length ≥ 5, primitive values, identical key set)
245
+ * → `toon` (30–60% size reduction)
246
+ * 3. Otherwise → structured clone (native, zero overhead)
113
247
  *
114
248
  * Large payloads (> `transferThreshold`, default 100 KiB) are encoded to
115
249
  * `ArrayBuffer` and added to `transferables` for zero-copy `postMessage` transfer.
@@ -130,6 +264,13 @@ async function createAutoSerializer(config) {
130
264
  catch {
131
265
  // seroval not installed — complex types will throw at serialize time with a clear message
132
266
  }
267
+ let toon = null;
268
+ try {
269
+ toon = await createToonSerializer();
270
+ }
271
+ catch {
272
+ // @toon-format/toon not installed — uniform arrays will fall back to structured-clone
273
+ }
133
274
  return {
134
275
  serialize(data) {
135
276
  let payload;
@@ -140,6 +281,9 @@ async function createAutoSerializer(config) {
140
281
  }
141
282
  payload = sv.serialize(data);
142
283
  }
284
+ else if (toon && isUniformObjectArray(data)) {
285
+ payload = toon.serialize(data);
286
+ }
143
287
  else {
144
288
  payload = structuredCloneSerializer.serialize(data);
145
289
  }
@@ -168,6 +312,13 @@ async function createAutoSerializer(config) {
168
312
  }
169
313
  return sv.deserialize(resolved);
170
314
  }
315
+ if (resolved.format === 'toon') {
316
+ if (!toon) {
317
+ throw new Error('@toon-format/toon is required to deserialize this payload. ' +
318
+ 'Install it with: npm install @toon-format/toon');
319
+ }
320
+ return toon.deserialize(resolved);
321
+ }
171
322
  throw new Error(`Unknown serialization format: '${resolved.format}'`);
172
323
  },
173
324
  };
@@ -177,4 +328,4 @@ async function createAutoSerializer(config) {
177
328
  * Generated bundle index. Do not edit.
178
329
  */
179
330
 
180
- export { createAutoSerializer, createSerovalSerializer, structuredCloneSerializer };
331
+ export { MIN_UNIFORM_ARRAY_LENGTH, createAutoSerializer, createSerovalSerializer, createToonSerializer, isUniformObjectArray, structuredCloneSerializer };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular-helpers/worker-http",
3
- "version": "21.1.0",
3
+ "version": "21.2.0",
4
4
  "description": "Angular HTTP over Web Workers — off-main-thread HTTP pipelines with configurable interceptors, WebCrypto security, and pluggable serialization",
5
5
  "schematics": "./schematics/collection.json",
6
6
  "exports": {
@@ -76,7 +76,8 @@
76
76
  "@angular/common": "^21.0.0",
77
77
  "@angular/core": "^21.0.0",
78
78
  "rxjs": "^7.0.0",
79
- "seroval": "^1.0.0"
79
+ "seroval": "^1.0.0",
80
+ "@toon-format/toon": "^2.0.0"
80
81
  },
81
82
  "peerDependenciesMeta": {
82
83
  "@angular/common": {
@@ -88,6 +89,9 @@
88
89
  "seroval": {
89
90
  "optional": true
90
91
  },
92
+ "@toon-format/toon": {
93
+ "optional": true
94
+ },
91
95
  "esbuild": {
92
96
  "optional": true
93
97
  },
@@ -63,15 +63,85 @@ declare const structuredCloneSerializer: WorkerSerializer;
63
63
  */
64
64
  declare function createSerovalSerializer(): Promise<WorkerSerializer>;
65
65
 
66
+ /**
67
+ * Creates a `WorkerSerializer` backed by `@toon-format/toon` (Token-Oriented Object Notation).
68
+ *
69
+ * TOON is a compact, schema-aware encoding of the JSON data model that declares object
70
+ * keys once and emits values as CSV-like rows. It typically reduces size by **30–60%**
71
+ * for uniform arrays of objects (e.g. `User[]`, `Product[]`, paginated lists), with
72
+ * negligible parsing overhead.
73
+ *
74
+ * **When to use it**:
75
+ * - Worker↔main `postMessage` payloads dominated by uniform arrays of objects
76
+ * - Cases where `structuredClone` cost is dominated by repeated key strings
77
+ *
78
+ * **When NOT to use it**:
79
+ * - Payloads containing `Date`, `Map`, `Set`, `RegExp` (use `seroval` instead)
80
+ * - Small / single-object payloads (overhead not justified)
81
+ *
82
+ * The factory is async because it dynamically imports the optional `@toon-format/toon` peer.
83
+ *
84
+ * `@toon-format/toon` must be installed separately:
85
+ * ```
86
+ * npm install @toon-format/toon
87
+ * ```
88
+ *
89
+ * @example
90
+ * ```typescript
91
+ * const serializer = await createToonSerializer();
92
+ * const payload = serializer.serialize([
93
+ * { id: 1, name: 'Alice' },
94
+ * { id: 2, name: 'Bob' },
95
+ * { id: 3, name: 'Carol' },
96
+ * { id: 4, name: 'Dave' },
97
+ * { id: 5, name: 'Eve' },
98
+ * ]);
99
+ * worker.postMessage({ payload }, payload.transferables);
100
+ * ```
101
+ *
102
+ * @see https://toonformat.dev
103
+ */
104
+ declare function createToonSerializer(): Promise<WorkerSerializer>;
105
+ /**
106
+ * Conservative threshold below which TOON's overhead outweighs its size benefit.
107
+ * Auto-serializer keeps shorter arrays on `structured-clone`.
108
+ *
109
+ * Exported for testing and for consumers building custom routing logic.
110
+ */
111
+ declare const MIN_UNIFORM_ARRAY_LENGTH = 5;
112
+ /**
113
+ * Detects whether a value is a depth-1 uniform array of plain objects with primitive values.
114
+ *
115
+ * Pure function — no side effects. Used by `createAutoSerializer()` to decide
116
+ * whether to route a payload through TOON.
117
+ *
118
+ * Conditions checked (all must pass):
119
+ * 1. Value is an array with `length >= MIN_UNIFORM_ARRAY_LENGTH`
120
+ * 2. Every item is a non-null, non-array plain object
121
+ * 3. Every item has the same set of keys as the first item
122
+ * 4. Every value across all items is a primitive (string, number, boolean, null)
123
+ *
124
+ * @example
125
+ * ```typescript
126
+ * isUniformObjectArray([{a:1},{a:2},{a:3},{a:4},{a:5}]); // true
127
+ * isUniformObjectArray([{a:1},{b:2}]); // false (heterogeneous keys)
128
+ * isUniformObjectArray([{a:[1,2]},{a:[3,4]}]); // false (nested array value)
129
+ * isUniformObjectArray([{a:1}]); // false (length < 5)
130
+ * ```
131
+ */
132
+ declare function isUniformObjectArray(value: unknown): boolean;
133
+
66
134
  /**
67
135
  * Creates an auto-detecting `WorkerSerializer` that picks the best strategy per payload.
68
136
  *
69
137
  * The factory is async because it pre-loads `seroval` during initialization
70
138
  * so the returned serializer methods are fully synchronous (no await in hot path).
71
139
  *
72
- * Strategy selection per `serialize()` call:
73
- * - Contains `Date`, `Map`, `Set`, or `RegExp` at depth-1 → `seroval` (full fidelity)
74
- * - Otherwise structured clone (native, zero overhead)
140
+ * Strategy selection per `serialize()` call (top-down, first match wins):
141
+ * 1. Contains `Date`, `Map`, `Set`, or `RegExp` at depth-1 → `seroval` (full fidelity)
142
+ * 2. Uniform array of plain objects (length ≥ 5, primitive values, identical key set)
143
+ * → `toon` (30–60% size reduction)
144
+ * 3. Otherwise → structured clone (native, zero overhead)
75
145
  *
76
146
  * Large payloads (> `transferThreshold`, default 100 KiB) are encoded to
77
147
  * `ArrayBuffer` and added to `transferables` for zero-copy `postMessage` transfer.
@@ -85,5 +155,5 @@ declare function createSerovalSerializer(): Promise<WorkerSerializer>;
85
155
  */
86
156
  declare function createAutoSerializer(config?: AutoSerializerConfig): Promise<WorkerSerializer>;
87
157
 
88
- export { createAutoSerializer, createSerovalSerializer, structuredCloneSerializer };
158
+ export { MIN_UNIFORM_ARRAY_LENGTH, createAutoSerializer, createSerovalSerializer, createToonSerializer, isUniformObjectArray, structuredCloneSerializer };
89
159
  export type { AutoSerializerConfig, SerializedPayload, SerializerStrategy, WorkerSerializer };