turbo-stream 1.2.1 → 2.0.1

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/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2018-19 [these people](https://github.com/rich-harris/devalue/graphs/contributors)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/dist/flatten.js CHANGED
@@ -9,11 +9,13 @@ function flatten(input) {
9
9
  return existing;
10
10
  if (input === undefined)
11
11
  return utils_js_1.UNDEFINED;
12
+ if (input === null)
13
+ return utils_js_1.NULL;
12
14
  if (Number.isNaN(input))
13
15
  return utils_js_1.NAN;
14
- if (input === Infinity)
16
+ if (input === Number.POSITIVE_INFINITY)
15
17
  return utils_js_1.POSITIVE_INFINITY;
16
- if (input === -Infinity)
18
+ if (input === Number.NEGATIVE_INFINITY)
17
19
  return utils_js_1.NEGATIVE_INFINITY;
18
20
  if (input === 0 && 1 / input < 0)
19
21
  return utils_js_1.NEGATIVE_ZERO;
@@ -38,15 +40,16 @@ function stringify(input, index) {
38
40
  case "bigint":
39
41
  str[index] = `["${utils_js_1.TYPE_BIGINT}","${input}"]`;
40
42
  break;
41
- case "symbol":
43
+ case "symbol": {
42
44
  const keyFor = Symbol.keyFor(input);
43
45
  if (!keyFor)
44
46
  throw new Error("Cannot encode symbol unless created with Symbol.for()");
45
47
  str[index] = `["${utils_js_1.TYPE_SYMBOL}",${JSON.stringify(keyFor)}]`;
46
48
  break;
47
- case "object":
49
+ }
50
+ case "object": {
48
51
  if (!input) {
49
- str[index] = "null";
52
+ str[index] = `${utils_js_1.NULL}`;
50
53
  break;
51
54
  }
52
55
  const isArray = Array.isArray(input);
@@ -59,8 +62,9 @@ function stringify(input, index) {
59
62
  const [pluginIdentifier, ...rest] = pluginResult;
60
63
  str[index] = `[${JSON.stringify(pluginIdentifier)}`;
61
64
  if (rest.length > 0) {
62
- str[index] +=
63
- "," + rest.map((v) => flatten.call(this, v)).join(",");
65
+ str[index] += `,${rest
66
+ .map((v) => flatten.call(this, v))
67
+ .join(",")}`;
64
68
  }
65
69
  str[index] += "]";
66
70
  break;
@@ -74,7 +78,7 @@ function stringify(input, index) {
74
78
  result +=
75
79
  (i ? "," : "") +
76
80
  (i in input ? flatten.call(this, input[i]) : utils_js_1.HOLE);
77
- str[index] = result + "]";
81
+ str[index] = `${result}]`;
78
82
  }
79
83
  else if (input instanceof Date) {
80
84
  str[index] = `["${utils_js_1.TYPE_DATE}",${input.getTime()}]`;
@@ -117,6 +121,7 @@ function stringify(input, index) {
117
121
  }
118
122
  }
119
123
  break;
124
+ }
120
125
  default:
121
126
  throw new Error("Cannot encode function or unexpected type");
122
127
  }
@@ -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
+ }
@@ -1,10 +1,11 @@
1
1
  // src/utils.ts
2
2
  var HOLE = -1;
3
3
  var NAN = -2;
4
- var NEGATIVE_INFINITY = -4;
5
- var NEGATIVE_ZERO = -5;
6
- var POSITIVE_INFINITY = -3;
7
- var UNDEFINED = -1;
4
+ var NEGATIVE_INFINITY = -3;
5
+ var NEGATIVE_ZERO = -4;
6
+ var NULL = -5;
7
+ var POSITIVE_INFINITY = -6;
8
+ var UNDEFINED = -7;
8
9
  var TYPE_BIGINT = "B";
9
10
  var TYPE_DATE = "D";
10
11
  var TYPE_ERROR = "E";
@@ -27,12 +28,12 @@ var Deferred = class {
27
28
  }
28
29
  };
29
30
  function createLineSplittingTransform() {
30
- let decoder = new TextDecoder();
31
+ const decoder = new TextDecoder();
31
32
  let leftover = "";
32
33
  return new TransformStream({
33
34
  transform(chunk, controller) {
34
- let str = decoder.decode(chunk, { stream: true });
35
- let parts = (leftover + str).split("\n");
35
+ const str = decoder.decode(chunk, { stream: true });
36
+ const parts = (leftover + str).split("\n");
36
37
  leftover = parts.pop() || "";
37
38
  for (const part of parts) {
38
39
  controller.enqueue(part);
@@ -54,11 +55,13 @@ function flatten(input) {
54
55
  return existing;
55
56
  if (input === void 0)
56
57
  return UNDEFINED;
58
+ if (input === null)
59
+ return NULL;
57
60
  if (Number.isNaN(input))
58
61
  return NAN;
59
- if (input === Infinity)
62
+ if (input === Number.POSITIVE_INFINITY)
60
63
  return POSITIVE_INFINITY;
61
- if (input === -Infinity)
64
+ if (input === Number.NEGATIVE_INFINITY)
62
65
  return NEGATIVE_INFINITY;
63
66
  if (input === 0 && 1 / input < 0)
64
67
  return NEGATIVE_ZERO;
@@ -80,7 +83,7 @@ function stringify(input, index) {
80
83
  case "bigint":
81
84
  str[index] = `["${TYPE_BIGINT}","${input}"]`;
82
85
  break;
83
- case "symbol":
86
+ case "symbol": {
84
87
  const keyFor = Symbol.keyFor(input);
85
88
  if (!keyFor)
86
89
  throw new Error(
@@ -88,9 +91,10 @@ function stringify(input, index) {
88
91
  );
89
92
  str[index] = `["${TYPE_SYMBOL}",${JSON.stringify(keyFor)}]`;
90
93
  break;
91
- case "object":
94
+ }
95
+ case "object": {
92
96
  if (!input) {
93
- str[index] = "null";
97
+ str[index] = `${NULL}`;
94
98
  break;
95
99
  }
96
100
  const isArray = Array.isArray(input);
@@ -103,7 +107,7 @@ function stringify(input, index) {
103
107
  const [pluginIdentifier, ...rest] = pluginResult;
104
108
  str[index] = `[${JSON.stringify(pluginIdentifier)}`;
105
109
  if (rest.length > 0) {
106
- str[index] += "," + rest.map((v) => flatten.call(this, v)).join(",");
110
+ str[index] += `,${rest.map((v) => flatten.call(this, v)).join(",")}`;
107
111
  }
108
112
  str[index] += "]";
109
113
  break;
@@ -115,7 +119,7 @@ function stringify(input, index) {
115
119
  if (isArray) {
116
120
  for (let i = 0; i < input.length; i++)
117
121
  result += (i ? "," : "") + (i in input ? flatten.call(this, input[i]) : HOLE);
118
- str[index] = result + "]";
122
+ str[index] = `${result}]`;
119
123
  } else if (input instanceof Date) {
120
124
  str[index] = `["${TYPE_DATE}",${input.getTime()}]`;
121
125
  } else if (input instanceof URL) {
@@ -146,6 +150,7 @@ function stringify(input, index) {
146
150
  }
147
151
  }
148
152
  break;
153
+ }
149
154
  default:
150
155
  throw new Error("Cannot encode function or unexpected type");
151
156
  }
@@ -174,6 +179,8 @@ function hydrate(index) {
174
179
  switch (index) {
175
180
  case UNDEFINED:
176
181
  return;
182
+ case NULL:
183
+ return null;
177
184
  case NAN:
178
185
  return NaN;
179
186
  case POSITIVE_INFINITY:
@@ -272,7 +279,8 @@ function hydrate(index) {
272
279
  }
273
280
 
274
281
  // src/turbo-stream.ts
275
- async function decode(readable, plugins) {
282
+ async function decode(readable, options) {
283
+ const { plugins } = options ?? {};
276
284
  const done = new Deferred();
277
285
  const reader = readable.pipeThrough(createLineSplittingTransform()).getReader();
278
286
  const decoder = {
@@ -363,13 +371,15 @@ async function decodeDeferred(reader) {
363
371
  read = await reader.read();
364
372
  }
365
373
  }
366
- function encode(input, plugins) {
374
+ function encode(input, options) {
375
+ const { plugins, signal } = options ?? {};
367
376
  const encoder = {
368
377
  deferred: {},
369
378
  index: 0,
370
379
  indices: /* @__PURE__ */ new Map(),
371
380
  stringified: [],
372
- plugins
381
+ plugins,
382
+ signal
373
383
  };
374
384
  const textEncoder = new TextEncoder();
375
385
  let lastSentIndex = 0;
@@ -392,7 +402,10 @@ function encode(input, plugins) {
392
402
  if (seenPromises.has(deferred))
393
403
  continue;
394
404
  seenPromises.add(
395
- encoder.deferred[Number(deferredId)] = deferred.then(
405
+ encoder.deferred[Number(deferredId)] = raceSignal(
406
+ deferred,
407
+ encoder.signal
408
+ ).then(
396
409
  (resolved) => {
397
410
  const id2 = flatten.call(encoder, resolved);
398
411
  if (id2 < 0) {
@@ -445,6 +458,21 @@ function encode(input, plugins) {
445
458
  });
446
459
  return readable;
447
460
  }
461
+ function raceSignal(promise, signal) {
462
+ if (!signal)
463
+ return promise;
464
+ if (signal.aborted)
465
+ return Promise.reject(signal.reason || new Error("Signal was aborted."));
466
+ const abort = new Promise((resolve, reject) => {
467
+ signal.addEventListener("abort", (event) => {
468
+ reject(signal.reason || new Error("Signal was aborted."));
469
+ });
470
+ promise.then(resolve).catch(reject);
471
+ });
472
+ abort.catch(() => {
473
+ });
474
+ return Promise.race([abort, promise]);
475
+ }
448
476
  export {
449
477
  decode,
450
478
  encode
package/dist/unflatten.js CHANGED
@@ -24,6 +24,8 @@ function hydrate(index) {
24
24
  switch (index) {
25
25
  case utils_js_1.UNDEFINED:
26
26
  return;
27
+ case utils_js_1.NULL:
28
+ return null;
27
29
  case utils_js_1.NAN:
28
30
  return NaN;
29
31
  case utils_js_1.POSITIVE_INFINITY:
package/dist/utils.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  export declare const HOLE = -1;
2
2
  export declare const NAN = -2;
3
- export declare const NEGATIVE_INFINITY = -4;
4
- export declare const NEGATIVE_ZERO = -5;
5
- export declare const POSITIVE_INFINITY = -3;
6
- export declare const UNDEFINED = -1;
3
+ export declare const NEGATIVE_INFINITY = -3;
4
+ export declare const NEGATIVE_ZERO = -4;
5
+ export declare const NULL = -5;
6
+ export declare const POSITIVE_INFINITY = -6;
7
+ export declare const UNDEFINED = -7;
7
8
  export declare const TYPE_BIGINT = "B";
8
9
  export declare const TYPE_DATE = "D";
9
10
  export declare const TYPE_ERROR = "E";
@@ -30,6 +31,7 @@ export interface ThisEncode {
30
31
  stringified: string[];
31
32
  deferred: Record<number, Promise<unknown>>;
32
33
  plugins?: EncodePlugin[];
34
+ signal?: AbortSignal;
33
35
  }
34
36
  export declare class Deferred<T = unknown> {
35
37
  promise: Promise<T>;
package/dist/utils.js CHANGED
@@ -1,12 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createLineSplittingTransform = exports.Deferred = exports.TYPE_URL = exports.TYPE_SYMBOL = exports.TYPE_SET = exports.TYPE_REGEXP = exports.TYPE_PROMISE = exports.TYPE_NULL_OBJECT = exports.TYPE_MAP = exports.TYPE_ERROR = exports.TYPE_DATE = exports.TYPE_BIGINT = exports.UNDEFINED = exports.POSITIVE_INFINITY = exports.NEGATIVE_ZERO = exports.NEGATIVE_INFINITY = exports.NAN = exports.HOLE = void 0;
3
+ exports.createLineSplittingTransform = exports.Deferred = exports.TYPE_URL = exports.TYPE_SYMBOL = exports.TYPE_SET = exports.TYPE_REGEXP = exports.TYPE_PROMISE = exports.TYPE_NULL_OBJECT = exports.TYPE_MAP = exports.TYPE_ERROR = exports.TYPE_DATE = exports.TYPE_BIGINT = exports.UNDEFINED = exports.POSITIVE_INFINITY = exports.NULL = exports.NEGATIVE_ZERO = exports.NEGATIVE_INFINITY = exports.NAN = exports.HOLE = void 0;
4
4
  exports.HOLE = -1;
5
5
  exports.NAN = -2;
6
- exports.NEGATIVE_INFINITY = -4;
7
- exports.NEGATIVE_ZERO = -5;
8
- exports.POSITIVE_INFINITY = -3;
9
- exports.UNDEFINED = -1;
6
+ exports.NEGATIVE_INFINITY = -3;
7
+ exports.NEGATIVE_ZERO = -4;
8
+ exports.NULL = -5;
9
+ exports.POSITIVE_INFINITY = -6;
10
+ exports.UNDEFINED = -7;
10
11
  exports.TYPE_BIGINT = "B";
11
12
  exports.TYPE_DATE = "D";
12
13
  exports.TYPE_ERROR = "E";
@@ -30,12 +31,12 @@ class Deferred {
30
31
  }
31
32
  exports.Deferred = Deferred;
32
33
  function createLineSplittingTransform() {
33
- let decoder = new TextDecoder();
34
+ const decoder = new TextDecoder();
34
35
  let leftover = "";
35
36
  return new TransformStream({
36
37
  transform(chunk, controller) {
37
- let str = decoder.decode(chunk, { stream: true });
38
- let parts = (leftover + str).split("\n");
38
+ const str = decoder.decode(chunk, { stream: true });
39
+ const parts = (leftover + str).split("\n");
39
40
  // The last part might be a partial line, so keep it for the next chunk.
40
41
  leftover = parts.pop() || "";
41
42
  for (const part of parts) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turbo-stream",
3
- "version": "1.2.1",
3
+ "version": "2.0.1",
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",