turbo-stream 1.0.4 → 1.1.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/dist/flatten.js CHANGED
@@ -20,7 +20,7 @@ export function flatten(input) {
20
20
  return index;
21
21
  }
22
22
  function stringify(input, index) {
23
- const { deferred } = this;
23
+ const { deferred, plugins } = this;
24
24
  const str = this.stringified;
25
25
  const partsForObj = (obj) => Object.keys(obj)
26
26
  .map((k) => `${JSON.stringify(k)}:${flatten.call(this, obj[k])}`)
@@ -45,51 +45,72 @@ function stringify(input, index) {
45
45
  str[index] = "null";
46
46
  break;
47
47
  }
48
- let result = Array.isArray(input) ? "[" : "{";
49
- if (Array.isArray(input)) {
50
- for (let i = 0; i < input.length; i++)
51
- result +=
52
- (i ? "," : "") + (i in input ? flatten.call(this, input[i]) : HOLE);
53
- str[index] = result + "]";
54
- }
55
- else if (input instanceof Date) {
56
- str[index] = `["${TYPE_DATE}",${input.getTime()}]`;
57
- }
58
- else if (input instanceof URL) {
59
- str[index] = `["${TYPE_URL}",${JSON.stringify(input.href)}]`;
60
- }
61
- else if (input instanceof RegExp) {
62
- str[index] = `["${TYPE_REGEXP}",${JSON.stringify(input.source)},${JSON.stringify(input.flags)}]`;
63
- }
64
- else if (input instanceof Set) {
65
- str[index] = `["${TYPE_SET}",${[...input]
66
- .map((val) => flatten.call(this, val))
67
- .join(",")}]`;
68
- }
69
- else if (input instanceof Map) {
70
- str[index] = `["${TYPE_MAP}",${[...input]
71
- .flatMap(([k, v]) => [flatten.call(this, k), flatten.call(this, v)])
72
- .join(",")}]`;
73
- }
74
- else if (input instanceof Promise) {
75
- str[index] = `["${TYPE_PROMISE}",${index}]`;
76
- deferred[index] = input;
77
- }
78
- else if (input instanceof Error) {
79
- str[index] = `["${TYPE_ERROR}",${JSON.stringify(input.message)}`;
80
- if (input.name !== "Error") {
81
- str[index] += `,${JSON.stringify(input.name)}`;
48
+ const isArray = Array.isArray(input);
49
+ let pluginHandled = false;
50
+ if (!isArray && plugins) {
51
+ for (const plugin of plugins) {
52
+ const pluginResult = plugin(input);
53
+ if (Array.isArray(pluginResult)) {
54
+ pluginHandled = true;
55
+ const [pluginIdentifier, ...rest] = pluginResult;
56
+ str[index] = `[${JSON.stringify(pluginIdentifier)}`;
57
+ if (rest.length > 0) {
58
+ str[index] +=
59
+ "," + rest.map((v) => flatten.call(this, v)).join(",");
60
+ }
61
+ str[index] += "]";
62
+ break;
63
+ }
82
64
  }
83
- str[index] += "]";
84
- }
85
- else if (Object.getPrototypeOf(input) === null) {
86
- str[index] = `["${TYPE_NULL_OBJECT}",{${partsForObj(input)}}]`;
87
65
  }
88
- else if (isPlainObject(input)) {
89
- str[index] = `{${partsForObj(input)}}`;
90
- }
91
- else {
92
- throw new Error("Cannot encode object with prototype");
66
+ if (!pluginHandled) {
67
+ let result = isArray ? "[" : "{";
68
+ if (isArray) {
69
+ for (let i = 0; i < input.length; i++)
70
+ result +=
71
+ (i ? "," : "") +
72
+ (i in input ? flatten.call(this, input[i]) : HOLE);
73
+ str[index] = result + "]";
74
+ }
75
+ else if (input instanceof Date) {
76
+ str[index] = `["${TYPE_DATE}",${input.getTime()}]`;
77
+ }
78
+ else if (input instanceof URL) {
79
+ str[index] = `["${TYPE_URL}",${JSON.stringify(input.href)}]`;
80
+ }
81
+ else if (input instanceof RegExp) {
82
+ str[index] = `["${TYPE_REGEXP}",${JSON.stringify(input.source)},${JSON.stringify(input.flags)}]`;
83
+ }
84
+ else if (input instanceof Set) {
85
+ str[index] = `["${TYPE_SET}",${[...input]
86
+ .map((val) => flatten.call(this, val))
87
+ .join(",")}]`;
88
+ }
89
+ else if (input instanceof Map) {
90
+ str[index] = `["${TYPE_MAP}",${[...input]
91
+ .flatMap(([k, v]) => [flatten.call(this, k), flatten.call(this, v)])
92
+ .join(",")}]`;
93
+ }
94
+ else if (input instanceof Promise) {
95
+ str[index] = `["${TYPE_PROMISE}",${index}]`;
96
+ deferred[index] = input;
97
+ }
98
+ else if (input instanceof Error) {
99
+ str[index] = `["${TYPE_ERROR}",${JSON.stringify(input.message)}`;
100
+ if (input.name !== "Error") {
101
+ str[index] += `,${JSON.stringify(input.name)}`;
102
+ }
103
+ str[index] += "]";
104
+ }
105
+ else if (Object.getPrototypeOf(input) === null) {
106
+ str[index] = `["${TYPE_NULL_OBJECT}",{${partsForObj(input)}}]`;
107
+ }
108
+ else if (isPlainObject(input)) {
109
+ str[index] = `{${partsForObj(input)}}`;
110
+ }
111
+ else {
112
+ throw new Error("Cannot encode object with prototype");
113
+ }
93
114
  }
94
115
  break;
95
116
  default:
@@ -1,5 +1,6 @@
1
- export declare function decode(readable: ReadableStream<Uint8Array>): Promise<{
1
+ import { type DecodePlugin, type EncodePlugin } from "./utils.js";
2
+ export declare function decode(readable: ReadableStream<Uint8Array>, plugins?: DecodePlugin[]): Promise<{
2
3
  done: Promise<undefined>;
3
4
  value: unknown;
4
5
  }>;
5
- export declare function encode(input: unknown): ReadableStream<Uint8Array>;
6
+ export declare function encode(input: unknown, plugins?: EncodePlugin[]): ReadableStream<Uint8Array>;
@@ -1,7 +1,7 @@
1
1
  import { flatten } from "./flatten.js";
2
2
  import { unflatten } from "./unflatten.js";
3
3
  import { Deferred, TYPE_ERROR, TYPE_PROMISE, createLineSplittingTransform, } from "./utils.js";
4
- export async function decode(readable) {
4
+ export async function decode(readable, plugins) {
5
5
  const done = new Deferred();
6
6
  const reader = readable
7
7
  .pipeThrough(createLineSplittingTransform())
@@ -10,6 +10,7 @@ export async function decode(readable) {
10
10
  values: [],
11
11
  hydrated: [],
12
12
  deferred: {},
13
+ plugins,
13
14
  };
14
15
  const decoded = await decodeInitial.call(decoder, reader);
15
16
  let donePromise = done.promise;
@@ -100,12 +101,13 @@ async function decodeDeferred(reader) {
100
101
  read = await reader.read();
101
102
  }
102
103
  }
103
- export function encode(input) {
104
+ export function encode(input, plugins) {
104
105
  const encoder = {
105
106
  deferred: {},
106
107
  index: 0,
107
108
  indicies: new Map(),
108
109
  stringified: [],
110
+ plugins,
109
111
  };
110
112
  const textEncoder = new TextEncoder();
111
113
  let lastSentIndex = 0;
package/dist/unflatten.js CHANGED
@@ -16,7 +16,7 @@ export function unflatten(parsed) {
16
16
  return hydrate.call(this, startIndex);
17
17
  }
18
18
  function hydrate(index) {
19
- const { hydrated, values, deferred } = this;
19
+ const { hydrated, values, deferred, plugins } = this;
20
20
  switch (index) {
21
21
  case UNDEFINED:
22
22
  return;
@@ -36,6 +36,14 @@ function hydrate(index) {
36
36
  return (hydrated[index] = value);
37
37
  if (Array.isArray(value)) {
38
38
  if (typeof value[0] === "string") {
39
+ if (Array.isArray(plugins)) {
40
+ const args = value.slice(1).map((i) => hydrate.call(this, i));
41
+ for (const plugin of plugins) {
42
+ const result = plugin(value[0], ...args);
43
+ if (result)
44
+ return (hydrated[index] = result.value);
45
+ }
46
+ }
39
47
  const [type, b, c] = value;
40
48
  switch (type) {
41
49
  case TYPE_DATE:
package/dist/utils.d.ts CHANGED
@@ -14,16 +14,22 @@ export declare const TYPE_REGEXP = "R";
14
14
  export declare const TYPE_SET = "S";
15
15
  export declare const TYPE_SYMBOL = "Y";
16
16
  export declare const TYPE_URL = "U";
17
+ export type DecodePlugin = (type: string, ...data: unknown[]) => {
18
+ value: unknown;
19
+ } | false | null | undefined;
20
+ export type EncodePlugin = (value: unknown) => [string, ...unknown[]] | false | null | undefined;
17
21
  export interface ThisDecode {
18
22
  values: unknown[];
19
23
  hydrated: unknown[];
20
24
  deferred: Record<number, Deferred<unknown>>;
25
+ plugins?: DecodePlugin[];
21
26
  }
22
27
  export interface ThisEncode {
23
28
  index: number;
24
29
  indicies: Map<unknown, number>;
25
30
  stringified: string[];
26
31
  deferred: Record<number, Promise<unknown>>;
32
+ plugins?: EncodePlugin[];
27
33
  }
28
34
  export declare class Deferred<T = unknown> {
29
35
  promise: Promise<T>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turbo-stream",
3
- "version": "1.0.4",
3
+ "version": "1.1.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
  "type": "module",
6
6
  "files": [