turbo-stream 0.0.6 → 0.0.8

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
@@ -18,10 +18,10 @@ npm install turbo-stream
18
18
  ```js
19
19
  import { decode, encode } from "turbo-stream";
20
20
 
21
- const encodedStream = encode({ answer: Promise.resolve(42) });
21
+ const encodedStream = encode(Promise.resolve(42));
22
22
  const decoded = await decode(encodedStream);
23
- console.log(decoded.value.answer); // a Promise
24
- console.log(await decoded.value.answer); // 42
23
+ console.log(decoded.value); // a Promise
24
+ console.log(await decoded.value); // 42
25
25
  await decoded.done; // wait for the stream to finish
26
26
  ```
27
27
 
package/dist/flatten.js CHANGED
@@ -1,4 +1,4 @@
1
- import { HOLE, NAN, NEGATIVE_INFINITY, NEGATIVE_ZERO, POSITIVE_INFINITY, TYPE_BIGINT, TYPE_DATE, TYPE_MAP, TYPE_PROMISE, TYPE_REGEXP, TYPE_SET, TYPE_SYMBOL, UNDEFINED, } from "./utils.js";
1
+ import { HOLE, NAN, NEGATIVE_INFINITY, NEGATIVE_ZERO, POSITIVE_INFINITY, TYPE_BIGINT, TYPE_DATE, TYPE_ERROR, TYPE_MAP, TYPE_NULL_OBJECT, TYPE_PROMISE, TYPE_REGEXP, TYPE_SET, TYPE_SYMBOL, UNDEFINED, } from "./utils.js";
2
2
  export function flatten(input) {
3
3
  const existing = this.indicies.get(input);
4
4
  if (existing)
@@ -67,6 +67,20 @@ function stringify(input, index) {
67
67
  str[index] = `["${TYPE_PROMISE}",${index}]`;
68
68
  this.deferred[index] = input;
69
69
  }
70
+ else if (input instanceof Error) {
71
+ str[index] = `["${TYPE_ERROR}",${JSON.stringify(input.message)}`;
72
+ if (input.name !== "Error") {
73
+ str[index] += `,${JSON.stringify(input.name)}`;
74
+ }
75
+ str[index] += "]";
76
+ }
77
+ else if (Object.getPrototypeOf(input) === null) {
78
+ str[index] = `["${TYPE_NULL_OBJECT}"`;
79
+ const parts = [];
80
+ for (const key in input)
81
+ parts.push(`${JSON.stringify(key)}:${flatten.call(this, input[key])}`);
82
+ str[index] += ",{" + parts.join(",") + "}]";
83
+ }
70
84
  else if (isPlainObject(input)) {
71
85
  const parts = [];
72
86
  for (const key in input)
@@ -1,6 +1,6 @@
1
1
  import { flatten } from "./flatten.js";
2
2
  import { unflatten } from "./unflatten.js";
3
- import { createLineSplittingTransform, Deferred, TYPE_PROMISE, } from "./utils.js";
3
+ import { createLineSplittingTransform, Deferred, TYPE_PROMISE, TYPE_ERROR, } from "./utils.js";
4
4
  export async function decode(readable) {
5
5
  const done = new Deferred();
6
6
  const reader = readable
@@ -56,7 +56,7 @@ async function decodeDeferred(reader) {
56
56
  continue;
57
57
  const line = read.value;
58
58
  switch (line[0]) {
59
- case TYPE_PROMISE:
59
+ case TYPE_PROMISE: {
60
60
  const colonIndex = line.indexOf(":");
61
61
  const deferredId = Number(line.slice(1, colonIndex));
62
62
  const deferred = this.deferred[deferredId];
@@ -74,9 +74,26 @@ async function decodeDeferred(reader) {
74
74
  const value = unflatten.call(this, jsonLine);
75
75
  deferred.resolve(value);
76
76
  break;
77
- // case TYPE_PROMISE_ERROR:
78
- // // TODO: transport promise rejections
79
- // break;
77
+ }
78
+ case TYPE_ERROR: {
79
+ const colonIndex = line.indexOf(":");
80
+ const deferredId = Number(line.slice(1, colonIndex));
81
+ const deferred = this.deferred[deferredId];
82
+ if (!deferred) {
83
+ throw new Error(`Deferred ID ${deferredId} not found in stream`);
84
+ }
85
+ const lineData = line.slice(colonIndex + 1);
86
+ let jsonLine;
87
+ try {
88
+ jsonLine = JSON.parse(lineData);
89
+ }
90
+ catch (reason) {
91
+ throw new SyntaxError();
92
+ }
93
+ const value = unflatten.call(this, jsonLine);
94
+ deferred.reject(value);
95
+ break;
96
+ }
80
97
  default:
81
98
  throw new SyntaxError();
82
99
  }
@@ -94,7 +111,7 @@ export function encode(input) {
94
111
  let lastSentIndex = 0;
95
112
  const readable = new ReadableStream({
96
113
  async start(controller) {
97
- const id = flatten.call(encoder, await input);
114
+ const id = flatten.call(encoder, input);
98
115
  if (id < 0) {
99
116
  controller.enqueue(textEncoder.encode(`${id}\n`));
100
117
  }
@@ -121,8 +138,22 @@ export function encode(input) {
121
138
  lastSentIndex = encoder.stringified.length - 1;
122
139
  }
123
140
  }, (reason) => {
124
- // TODO: Encode and send errors
125
- throw reason;
141
+ if (!reason ||
142
+ typeof reason !== "object" ||
143
+ !(reason instanceof Error)) {
144
+ reason = new Error("An unknown error occurred");
145
+ }
146
+ const id = flatten.call(encoder, reason);
147
+ if (id < 0) {
148
+ controller.enqueue(textEncoder.encode(`${TYPE_ERROR}${deferredId}:${id}\n`));
149
+ }
150
+ else {
151
+ const values = encoder.stringified
152
+ .slice(lastSentIndex + 1)
153
+ .join(",");
154
+ controller.enqueue(textEncoder.encode(`${TYPE_ERROR}${deferredId}:[${values}]\n`));
155
+ lastSentIndex = encoder.stringified.length - 1;
156
+ }
126
157
  })
127
158
  .finally(() => {
128
159
  delete encoder.deferred[Number(deferredId)];
package/dist/unflatten.js CHANGED
@@ -1,4 +1,9 @@
1
- import { Deferred, HOLE, NAN, NEGATIVE_INFINITY, NEGATIVE_ZERO, POSITIVE_INFINITY, TYPE_BIGINT, TYPE_DATE, TYPE_MAP, TYPE_PROMISE, TYPE_REGEXP, TYPE_SET, TYPE_SYMBOL, UNDEFINED, } from "./utils.js";
1
+ import { Deferred, HOLE, NAN, NEGATIVE_INFINITY, NEGATIVE_ZERO, POSITIVE_INFINITY, TYPE_BIGINT, TYPE_DATE, TYPE_MAP, TYPE_PROMISE, TYPE_REGEXP, TYPE_SET, TYPE_SYMBOL, UNDEFINED, TYPE_ERROR, TYPE_NULL_OBJECT, } from "./utils.js";
2
+ const globalObj = (typeof window !== "undefined"
3
+ ? window
4
+ : typeof globalThis !== "undefined"
5
+ ? globalThis
6
+ : undefined);
2
7
  export function unflatten(parsed) {
3
8
  if (typeof parsed === "number")
4
9
  return hydrate.call(this, parsed);
@@ -52,6 +57,13 @@ function hydrate(index) {
52
57
  map.set(hydrate.call(this, value[i]), hydrate.call(this, value[i + 1]));
53
58
  }
54
59
  return map;
60
+ case TYPE_NULL_OBJECT:
61
+ console.log({ value });
62
+ const obj = Object.create(null);
63
+ hydrated[index] = obj;
64
+ for (const key in value[1])
65
+ obj[key] = hydrate.call(this, value[1][key]);
66
+ return obj;
55
67
  case TYPE_PROMISE:
56
68
  if (hydrated[value[1]]) {
57
69
  return (hydrated[index] = hydrated[value[1]]);
@@ -61,6 +73,13 @@ function hydrate(index) {
61
73
  deferred[value[1]] = d;
62
74
  return (hydrated[index] = d.promise);
63
75
  }
76
+ case TYPE_ERROR:
77
+ const [, message, errorType] = value;
78
+ let error = errorType && globalObj && globalObj[errorType]
79
+ ? new globalObj[errorType](message)
80
+ : new Error(message);
81
+ hydrated[index] = error;
82
+ return error;
64
83
  default:
65
84
  throw new SyntaxError();
66
85
  }
package/dist/utils.d.ts CHANGED
@@ -12,6 +12,7 @@ export declare const TYPE_REGEXP = "R";
12
12
  export declare const TYPE_SYMBOL = "Y";
13
13
  export declare const TYPE_NULL_OBJECT = "N";
14
14
  export declare const TYPE_PROMISE = "P";
15
+ export declare const TYPE_ERROR = "E";
15
16
  export interface ThisDecode {
16
17
  values: unknown[];
17
18
  hydrated: unknown[];
package/dist/utils.js CHANGED
@@ -12,6 +12,7 @@ export const TYPE_REGEXP = "R";
12
12
  export const TYPE_SYMBOL = "Y";
13
13
  export const TYPE_NULL_OBJECT = "N";
14
14
  export const TYPE_PROMISE = "P";
15
+ export const TYPE_ERROR = "E";
15
16
  export class Deferred {
16
17
  promise;
17
18
  resolve;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turbo-stream",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
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
  "type": "module",
6
6
  "files": [