turbo-stream 1.2.0 → 2.0.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.
@@ -1,7 +1,12 @@
1
1
  import { type DecodePlugin, type EncodePlugin } from "./utils.js";
2
2
  export type { DecodePlugin, EncodePlugin };
3
- export declare function decode(readable: ReadableStream<Uint8Array>, plugins?: DecodePlugin[]): Promise<{
3
+ export declare function decode(readable: ReadableStream<Uint8Array>, options?: {
4
+ plugins?: DecodePlugin[];
5
+ }): Promise<{
4
6
  done: Promise<undefined>;
5
7
  value: unknown;
6
8
  }>;
7
- export declare function encode(input: unknown, plugins?: EncodePlugin[]): ReadableStream<Uint8Array>;
9
+ export declare function encode(input: unknown, options?: {
10
+ plugins?: EncodePlugin[];
11
+ signal?: AbortSignal;
12
+ }): ReadableStream<Uint8Array>;
@@ -4,7 +4,8 @@ exports.encode = exports.decode = void 0;
4
4
  const flatten_js_1 = require("./flatten.js");
5
5
  const unflatten_js_1 = require("./unflatten.js");
6
6
  const utils_js_1 = require("./utils.js");
7
- async function decode(readable, plugins) {
7
+ async function decode(readable, options) {
8
+ const { plugins } = options ?? {};
8
9
  const done = new utils_js_1.Deferred();
9
10
  const reader = readable
10
11
  .pipeThrough((0, utils_js_1.createLineSplittingTransform)())
@@ -105,13 +106,15 @@ async function decodeDeferred(reader) {
105
106
  read = await reader.read();
106
107
  }
107
108
  }
108
- function encode(input, plugins) {
109
+ function encode(input, options) {
110
+ const { plugins, signal } = options ?? {};
109
111
  const encoder = {
110
112
  deferred: {},
111
113
  index: 0,
112
114
  indices: new Map(),
113
115
  stringified: [],
114
116
  plugins,
117
+ signal,
115
118
  };
116
119
  const textEncoder = new TextEncoder();
117
120
  let lastSentIndex = 0;
@@ -130,7 +133,7 @@ function encode(input, plugins) {
130
133
  for (const [deferredId, deferred] of Object.entries(encoder.deferred)) {
131
134
  if (seenPromises.has(deferred))
132
135
  continue;
133
- seenPromises.add((encoder.deferred[Number(deferredId)] = deferred
136
+ seenPromises.add((encoder.deferred[Number(deferredId)] = raceSignal(deferred, encoder.signal)
134
137
  .then((resolved) => {
135
138
  const id = flatten_js_1.flatten.call(encoder, resolved);
136
139
  if (id < 0) {
@@ -174,3 +177,17 @@ function encode(input, plugins) {
174
177
  return readable;
175
178
  }
176
179
  exports.encode = encode;
180
+ function raceSignal(promise, signal) {
181
+ if (!signal)
182
+ return promise;
183
+ if (signal.aborted)
184
+ return Promise.reject(signal.reason || new Error("Signal was aborted."));
185
+ const abort = new Promise((resolve, reject) => {
186
+ signal.addEventListener("abort", (event) => {
187
+ reject(signal.reason || new Error("Signal was aborted."));
188
+ });
189
+ promise.then(resolve).catch(reject);
190
+ });
191
+ abort.catch(() => { });
192
+ return Promise.race([abort, promise]);
193
+ }
@@ -190,14 +190,6 @@ function hydrate(index) {
190
190
  return hydrated[index] = value;
191
191
  if (Array.isArray(value)) {
192
192
  if (typeof value[0] === "string") {
193
- if (Array.isArray(plugins)) {
194
- const args = value.slice(1).map((i) => hydrate.call(this, i));
195
- for (const plugin of plugins) {
196
- const result = plugin(value[0], ...args);
197
- if (result)
198
- return hydrated[index] = result.value;
199
- }
200
- }
201
193
  const [type, b, c] = value;
202
194
  switch (type) {
203
195
  case TYPE_DATE:
@@ -246,6 +238,14 @@ function hydrate(index) {
246
238
  hydrated[index] = error;
247
239
  return error;
248
240
  default:
241
+ if (Array.isArray(plugins)) {
242
+ const args = value.slice(1).map((i) => hydrate.call(this, i));
243
+ for (const plugin of plugins) {
244
+ const result = plugin(value[0], ...args);
245
+ if (result)
246
+ return hydrated[index] = result.value;
247
+ }
248
+ }
249
249
  throw new SyntaxError();
250
250
  }
251
251
  } else {
@@ -272,7 +272,8 @@ function hydrate(index) {
272
272
  }
273
273
 
274
274
  // src/turbo-stream.ts
275
- async function decode(readable, plugins) {
275
+ async function decode(readable, options) {
276
+ const { plugins } = options ?? {};
276
277
  const done = new Deferred();
277
278
  const reader = readable.pipeThrough(createLineSplittingTransform()).getReader();
278
279
  const decoder = {
@@ -363,13 +364,15 @@ async function decodeDeferred(reader) {
363
364
  read = await reader.read();
364
365
  }
365
366
  }
366
- function encode(input, plugins) {
367
+ function encode(input, options) {
368
+ const { plugins, signal } = options ?? {};
367
369
  const encoder = {
368
370
  deferred: {},
369
371
  index: 0,
370
372
  indices: /* @__PURE__ */ new Map(),
371
373
  stringified: [],
372
- plugins
374
+ plugins,
375
+ signal
373
376
  };
374
377
  const textEncoder = new TextEncoder();
375
378
  let lastSentIndex = 0;
@@ -392,7 +395,10 @@ function encode(input, plugins) {
392
395
  if (seenPromises.has(deferred))
393
396
  continue;
394
397
  seenPromises.add(
395
- encoder.deferred[Number(deferredId)] = deferred.then(
398
+ encoder.deferred[Number(deferredId)] = raceSignal(
399
+ deferred,
400
+ encoder.signal
401
+ ).then(
396
402
  (resolved) => {
397
403
  const id2 = flatten.call(encoder, resolved);
398
404
  if (id2 < 0) {
@@ -445,6 +451,21 @@ function encode(input, plugins) {
445
451
  });
446
452
  return readable;
447
453
  }
454
+ function raceSignal(promise, signal) {
455
+ if (!signal)
456
+ return promise;
457
+ if (signal.aborted)
458
+ return Promise.reject(signal.reason || new Error("Signal was aborted."));
459
+ const abort = new Promise((resolve, reject) => {
460
+ signal.addEventListener("abort", (event) => {
461
+ reject(signal.reason || new Error("Signal was aborted."));
462
+ });
463
+ promise.then(resolve).catch(reject);
464
+ });
465
+ abort.catch(() => {
466
+ });
467
+ return Promise.race([abort, promise]);
468
+ }
448
469
  export {
449
470
  decode,
450
471
  encode
package/dist/unflatten.js CHANGED
@@ -40,14 +40,6 @@ function hydrate(index) {
40
40
  return (hydrated[index] = value);
41
41
  if (Array.isArray(value)) {
42
42
  if (typeof value[0] === "string") {
43
- if (Array.isArray(plugins)) {
44
- const args = value.slice(1).map((i) => hydrate.call(this, i));
45
- for (const plugin of plugins) {
46
- const result = plugin(value[0], ...args);
47
- if (result)
48
- return (hydrated[index] = result.value);
49
- }
50
- }
51
43
  const [type, b, c] = value;
52
44
  switch (type) {
53
45
  case utils_js_1.TYPE_DATE:
@@ -96,6 +88,16 @@ function hydrate(index) {
96
88
  hydrated[index] = error;
97
89
  return error;
98
90
  default:
91
+ // Run plugins at the end so we have a chance to resolve primitives
92
+ // without running into a loop
93
+ if (Array.isArray(plugins)) {
94
+ const args = value.slice(1).map((i) => hydrate.call(this, i));
95
+ for (const plugin of plugins) {
96
+ const result = plugin(value[0], ...args);
97
+ if (result)
98
+ return (hydrated[index] = result.value);
99
+ }
100
+ }
99
101
  throw new SyntaxError();
100
102
  }
101
103
  }
package/dist/utils.d.ts CHANGED
@@ -30,6 +30,7 @@ export interface ThisEncode {
30
30
  stringified: string[];
31
31
  deferred: Record<number, Promise<unknown>>;
32
32
  plugins?: EncodePlugin[];
33
+ signal?: AbortSignal;
33
34
  }
34
35
  export declare class Deferred<T = unknown> {
35
36
  promise: Promise<T>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turbo-stream",
3
- "version": "1.2.0",
3
+ "version": "2.0.0",
4
4
  "description": "A streaming data transport format that aims to support built-in features such as Promises, Dates, RegExps, Maps, Sets and more.",
5
5
  "files": [
6
6
  "dist",