@tanstack/start-client-core 1.166.8 → 1.166.10

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.
Files changed (39) hide show
  1. package/dist/esm/client/ServerFunctionSerializationAdapter.js +15 -14
  2. package/dist/esm/client/ServerFunctionSerializationAdapter.js.map +1 -1
  3. package/dist/esm/client/hydrateStart.js +26 -31
  4. package/dist/esm/client/hydrateStart.js.map +1 -1
  5. package/dist/esm/client/index.js +1 -4
  6. package/dist/esm/client-rpc/createClientRpc.js +16 -15
  7. package/dist/esm/client-rpc/createClientRpc.js.map +1 -1
  8. package/dist/esm/client-rpc/frame-decoder.js +229 -241
  9. package/dist/esm/client-rpc/frame-decoder.js.map +1 -1
  10. package/dist/esm/client-rpc/index.js +1 -4
  11. package/dist/esm/client-rpc/serverFnFetcher.js +210 -259
  12. package/dist/esm/client-rpc/serverFnFetcher.js.map +1 -1
  13. package/dist/esm/constants.js +41 -49
  14. package/dist/esm/constants.js.map +1 -1
  15. package/dist/esm/createMiddleware.js +25 -36
  16. package/dist/esm/createMiddleware.js.map +1 -1
  17. package/dist/esm/createServerFn.js +187 -261
  18. package/dist/esm/createServerFn.js.map +1 -1
  19. package/dist/esm/createStart.js +25 -29
  20. package/dist/esm/createStart.js.map +1 -1
  21. package/dist/esm/fake-start-entry.js +7 -8
  22. package/dist/esm/fake-start-entry.js.map +1 -1
  23. package/dist/esm/getDefaultSerovalPlugins.js +7 -11
  24. package/dist/esm/getDefaultSerovalPlugins.js.map +1 -1
  25. package/dist/esm/getGlobalStartContext.js +10 -13
  26. package/dist/esm/getGlobalStartContext.js.map +1 -1
  27. package/dist/esm/getRouterInstance.js +7 -6
  28. package/dist/esm/getRouterInstance.js.map +1 -1
  29. package/dist/esm/getStartContextServerOnly.js +7 -6
  30. package/dist/esm/getStartContextServerOnly.js.map +1 -1
  31. package/dist/esm/getStartOptions.js +7 -6
  32. package/dist/esm/getStartOptions.js.map +1 -1
  33. package/dist/esm/index.js +6 -37
  34. package/dist/esm/safeObjectMerge.js +24 -24
  35. package/dist/esm/safeObjectMerge.js.map +1 -1
  36. package/package.json +4 -4
  37. package/dist/esm/client/index.js.map +0 -1
  38. package/dist/esm/client-rpc/index.js.map +0 -1
  39. package/dist/esm/index.js.map +0 -1
@@ -1,243 +1,231 @@
1
- import { FrameType, FRAME_HEADER_SIZE } from "../constants.js";
2
- const textDecoder = new TextDecoder();
3
- const EMPTY_BUFFER = new Uint8Array(0);
4
- const MAX_FRAME_PAYLOAD_SIZE = 16 * 1024 * 1024;
5
- const MAX_BUFFERED_BYTES = 32 * 1024 * 1024;
6
- const MAX_STREAMS = 1024;
7
- const MAX_FRAMES = 1e5;
1
+ import { FrameType } from "../constants.js";
2
+ //#region src/client-rpc/frame-decoder.ts
3
+ /**
4
+ * Client-side frame decoder for multiplexed responses.
5
+ *
6
+ * Decodes binary frame protocol and reconstructs:
7
+ * - JSON stream (NDJSON lines for seroval)
8
+ * - Raw streams (binary data as ReadableStream<Uint8Array>)
9
+ */
10
+ /** Cached TextDecoder for frame decoding */
11
+ var textDecoder = new TextDecoder();
12
+ /** Shared empty buffer for empty buffer case - avoids allocation */
13
+ var EMPTY_BUFFER = new Uint8Array(0);
14
+ /** Hardening limits to prevent memory/CPU DoS */
15
+ var MAX_FRAME_PAYLOAD_SIZE = 16 * 1024 * 1024;
16
+ var MAX_BUFFERED_BYTES = 32 * 1024 * 1024;
17
+ var MAX_STREAMS = 1024;
18
+ var MAX_FRAMES = 1e5;
19
+ /**
20
+ * Creates a frame decoder that processes a multiplexed response stream.
21
+ *
22
+ * @param input The raw response body stream
23
+ * @returns Decoded JSON stream and stream getter function
24
+ */
8
25
  function createFrameDecoder(input) {
9
- const streamControllers = /* @__PURE__ */ new Map();
10
- const streams = /* @__PURE__ */ new Map();
11
- const cancelledStreamIds = /* @__PURE__ */ new Set();
12
- let cancelled = false;
13
- let inputReader = null;
14
- let frameCount = 0;
15
- let jsonController;
16
- const jsonChunks = new ReadableStream({
17
- start(controller) {
18
- jsonController = controller;
19
- },
20
- cancel() {
21
- cancelled = true;
22
- try {
23
- inputReader?.cancel();
24
- } catch {
25
- }
26
- streamControllers.forEach((ctrl) => {
27
- try {
28
- ctrl.error(new Error("Framed response cancelled"));
29
- } catch {
30
- }
31
- });
32
- streamControllers.clear();
33
- streams.clear();
34
- cancelledStreamIds.clear();
35
- }
36
- });
37
- function getOrCreateStream(id) {
38
- const existing = streams.get(id);
39
- if (existing) {
40
- return existing;
41
- }
42
- if (cancelledStreamIds.has(id)) {
43
- return new ReadableStream({
44
- start(controller) {
45
- controller.close();
46
- }
47
- });
48
- }
49
- if (streams.size >= MAX_STREAMS) {
50
- throw new Error(
51
- `Too many raw streams in framed response (max ${MAX_STREAMS})`
52
- );
53
- }
54
- const stream = new ReadableStream({
55
- start(ctrl) {
56
- streamControllers.set(id, ctrl);
57
- },
58
- cancel() {
59
- cancelledStreamIds.add(id);
60
- streamControllers.delete(id);
61
- streams.delete(id);
62
- }
63
- });
64
- streams.set(id, stream);
65
- return stream;
66
- }
67
- function ensureController(id) {
68
- getOrCreateStream(id);
69
- return streamControllers.get(id);
70
- }
71
- (async () => {
72
- const reader = input.getReader();
73
- inputReader = reader;
74
- const bufferList = [];
75
- let totalLength = 0;
76
- function readHeader() {
77
- if (totalLength < FRAME_HEADER_SIZE) return null;
78
- const first = bufferList[0];
79
- if (first.length >= FRAME_HEADER_SIZE) {
80
- const type2 = first[0];
81
- const streamId2 = (first[1] << 24 | first[2] << 16 | first[3] << 8 | first[4]) >>> 0;
82
- const length2 = (first[5] << 24 | first[6] << 16 | first[7] << 8 | first[8]) >>> 0;
83
- return { type: type2, streamId: streamId2, length: length2 };
84
- }
85
- const headerBytes = new Uint8Array(FRAME_HEADER_SIZE);
86
- let offset = 0;
87
- let remaining = FRAME_HEADER_SIZE;
88
- for (let i = 0; i < bufferList.length && remaining > 0; i++) {
89
- const chunk = bufferList[i];
90
- const toCopy = Math.min(chunk.length, remaining);
91
- headerBytes.set(chunk.subarray(0, toCopy), offset);
92
- offset += toCopy;
93
- remaining -= toCopy;
94
- }
95
- const type = headerBytes[0];
96
- const streamId = (headerBytes[1] << 24 | headerBytes[2] << 16 | headerBytes[3] << 8 | headerBytes[4]) >>> 0;
97
- const length = (headerBytes[5] << 24 | headerBytes[6] << 16 | headerBytes[7] << 8 | headerBytes[8]) >>> 0;
98
- return { type, streamId, length };
99
- }
100
- function extractFlattened(count) {
101
- if (count === 0) return EMPTY_BUFFER;
102
- const result = new Uint8Array(count);
103
- let offset = 0;
104
- let remaining = count;
105
- while (remaining > 0 && bufferList.length > 0) {
106
- const chunk = bufferList[0];
107
- if (!chunk) break;
108
- const toCopy = Math.min(chunk.length, remaining);
109
- result.set(chunk.subarray(0, toCopy), offset);
110
- offset += toCopy;
111
- remaining -= toCopy;
112
- if (toCopy === chunk.length) {
113
- bufferList.shift();
114
- } else {
115
- bufferList[0] = chunk.subarray(toCopy);
116
- }
117
- }
118
- totalLength -= count;
119
- return result;
120
- }
121
- try {
122
- while (true) {
123
- const { done, value } = await reader.read();
124
- if (cancelled) break;
125
- if (done) break;
126
- if (!value) continue;
127
- if (totalLength + value.length > MAX_BUFFERED_BYTES) {
128
- throw new Error(
129
- `Framed response buffer exceeded ${MAX_BUFFERED_BYTES} bytes`
130
- );
131
- }
132
- bufferList.push(value);
133
- totalLength += value.length;
134
- while (true) {
135
- const header = readHeader();
136
- if (!header) break;
137
- const { type, streamId, length } = header;
138
- if (type !== FrameType.JSON && type !== FrameType.CHUNK && type !== FrameType.END && type !== FrameType.ERROR) {
139
- throw new Error(`Unknown frame type: ${type}`);
140
- }
141
- if (type === FrameType.JSON) {
142
- if (streamId !== 0) {
143
- throw new Error("Invalid JSON frame streamId (expected 0)");
144
- }
145
- } else {
146
- if (streamId === 0) {
147
- throw new Error("Invalid raw frame streamId (expected non-zero)");
148
- }
149
- }
150
- if (length > MAX_FRAME_PAYLOAD_SIZE) {
151
- throw new Error(
152
- `Frame payload too large: ${length} bytes (max ${MAX_FRAME_PAYLOAD_SIZE})`
153
- );
154
- }
155
- const frameSize = FRAME_HEADER_SIZE + length;
156
- if (totalLength < frameSize) break;
157
- if (++frameCount > MAX_FRAMES) {
158
- throw new Error(
159
- `Too many frames in framed response (max ${MAX_FRAMES})`
160
- );
161
- }
162
- extractFlattened(FRAME_HEADER_SIZE);
163
- const payload = extractFlattened(length);
164
- switch (type) {
165
- case FrameType.JSON: {
166
- try {
167
- jsonController.enqueue(textDecoder.decode(payload));
168
- } catch {
169
- }
170
- break;
171
- }
172
- case FrameType.CHUNK: {
173
- const ctrl = ensureController(streamId);
174
- if (ctrl) {
175
- ctrl.enqueue(payload);
176
- }
177
- break;
178
- }
179
- case FrameType.END: {
180
- const ctrl = ensureController(streamId);
181
- cancelledStreamIds.add(streamId);
182
- if (ctrl) {
183
- try {
184
- ctrl.close();
185
- } catch {
186
- }
187
- streamControllers.delete(streamId);
188
- }
189
- break;
190
- }
191
- case FrameType.ERROR: {
192
- const ctrl = ensureController(streamId);
193
- cancelledStreamIds.add(streamId);
194
- if (ctrl) {
195
- const message = textDecoder.decode(payload);
196
- ctrl.error(new Error(message));
197
- streamControllers.delete(streamId);
198
- }
199
- break;
200
- }
201
- }
202
- }
203
- }
204
- if (totalLength !== 0) {
205
- throw new Error("Incomplete frame at end of framed response");
206
- }
207
- try {
208
- jsonController.close();
209
- } catch {
210
- }
211
- streamControllers.forEach((ctrl) => {
212
- try {
213
- ctrl.close();
214
- } catch {
215
- }
216
- });
217
- streamControllers.clear();
218
- } catch (error) {
219
- try {
220
- jsonController.error(error);
221
- } catch {
222
- }
223
- streamControllers.forEach((ctrl) => {
224
- try {
225
- ctrl.error(error);
226
- } catch {
227
- }
228
- });
229
- streamControllers.clear();
230
- } finally {
231
- try {
232
- reader.releaseLock();
233
- } catch {
234
- }
235
- inputReader = null;
236
- }
237
- })();
238
- return { getOrCreateStream, jsonChunks };
26
+ const streamControllers = /* @__PURE__ */ new Map();
27
+ const streams = /* @__PURE__ */ new Map();
28
+ const cancelledStreamIds = /* @__PURE__ */ new Set();
29
+ let cancelled = false;
30
+ let inputReader = null;
31
+ let frameCount = 0;
32
+ let jsonController;
33
+ const jsonChunks = new ReadableStream({
34
+ start(controller) {
35
+ jsonController = controller;
36
+ },
37
+ cancel() {
38
+ cancelled = true;
39
+ try {
40
+ inputReader?.cancel();
41
+ } catch {}
42
+ streamControllers.forEach((ctrl) => {
43
+ try {
44
+ ctrl.error(/* @__PURE__ */ new Error("Framed response cancelled"));
45
+ } catch {}
46
+ });
47
+ streamControllers.clear();
48
+ streams.clear();
49
+ cancelledStreamIds.clear();
50
+ }
51
+ });
52
+ /**
53
+ * Gets or creates a stream for a given stream ID.
54
+ * Called by deserialize plugin when it encounters a RawStream reference.
55
+ */
56
+ function getOrCreateStream(id) {
57
+ const existing = streams.get(id);
58
+ if (existing) return existing;
59
+ if (cancelledStreamIds.has(id)) return new ReadableStream({ start(controller) {
60
+ controller.close();
61
+ } });
62
+ if (streams.size >= MAX_STREAMS) throw new Error(`Too many raw streams in framed response (max ${MAX_STREAMS})`);
63
+ const stream = new ReadableStream({
64
+ start(ctrl) {
65
+ streamControllers.set(id, ctrl);
66
+ },
67
+ cancel() {
68
+ cancelledStreamIds.add(id);
69
+ streamControllers.delete(id);
70
+ streams.delete(id);
71
+ }
72
+ });
73
+ streams.set(id, stream);
74
+ return stream;
75
+ }
76
+ /**
77
+ * Ensures stream exists and returns its controller for enqueuing data.
78
+ * Used for CHUNK frames where we need to ensure stream is created.
79
+ */
80
+ function ensureController(id) {
81
+ getOrCreateStream(id);
82
+ return streamControllers.get(id);
83
+ }
84
+ (async () => {
85
+ const reader = input.getReader();
86
+ inputReader = reader;
87
+ const bufferList = [];
88
+ let totalLength = 0;
89
+ /**
90
+ * Reads header bytes from buffer chunks without flattening.
91
+ * Returns header data or null if not enough bytes available.
92
+ */
93
+ function readHeader() {
94
+ if (totalLength < 9) return null;
95
+ const first = bufferList[0];
96
+ if (first.length >= 9) return {
97
+ type: first[0],
98
+ streamId: (first[1] << 24 | first[2] << 16 | first[3] << 8 | first[4]) >>> 0,
99
+ length: (first[5] << 24 | first[6] << 16 | first[7] << 8 | first[8]) >>> 0
100
+ };
101
+ const headerBytes = new Uint8Array(9);
102
+ let offset = 0;
103
+ let remaining = 9;
104
+ for (let i = 0; i < bufferList.length && remaining > 0; i++) {
105
+ const chunk = bufferList[i];
106
+ const toCopy = Math.min(chunk.length, remaining);
107
+ headerBytes.set(chunk.subarray(0, toCopy), offset);
108
+ offset += toCopy;
109
+ remaining -= toCopy;
110
+ }
111
+ return {
112
+ type: headerBytes[0],
113
+ streamId: (headerBytes[1] << 24 | headerBytes[2] << 16 | headerBytes[3] << 8 | headerBytes[4]) >>> 0,
114
+ length: (headerBytes[5] << 24 | headerBytes[6] << 16 | headerBytes[7] << 8 | headerBytes[8]) >>> 0
115
+ };
116
+ }
117
+ /**
118
+ * Flattens buffer list into single Uint8Array and removes from list.
119
+ */
120
+ function extractFlattened(count) {
121
+ if (count === 0) return EMPTY_BUFFER;
122
+ const result = new Uint8Array(count);
123
+ let offset = 0;
124
+ let remaining = count;
125
+ while (remaining > 0 && bufferList.length > 0) {
126
+ const chunk = bufferList[0];
127
+ if (!chunk) break;
128
+ const toCopy = Math.min(chunk.length, remaining);
129
+ result.set(chunk.subarray(0, toCopy), offset);
130
+ offset += toCopy;
131
+ remaining -= toCopy;
132
+ if (toCopy === chunk.length) bufferList.shift();
133
+ else bufferList[0] = chunk.subarray(toCopy);
134
+ }
135
+ totalLength -= count;
136
+ return result;
137
+ }
138
+ try {
139
+ while (true) {
140
+ const { done, value } = await reader.read();
141
+ if (cancelled) break;
142
+ if (done) break;
143
+ if (!value) continue;
144
+ if (totalLength + value.length > MAX_BUFFERED_BYTES) throw new Error(`Framed response buffer exceeded ${MAX_BUFFERED_BYTES} bytes`);
145
+ bufferList.push(value);
146
+ totalLength += value.length;
147
+ while (true) {
148
+ const header = readHeader();
149
+ if (!header) break;
150
+ const { type, streamId, length } = header;
151
+ if (type !== FrameType.JSON && type !== FrameType.CHUNK && type !== FrameType.END && type !== FrameType.ERROR) throw new Error(`Unknown frame type: ${type}`);
152
+ if (type === FrameType.JSON) {
153
+ if (streamId !== 0) throw new Error("Invalid JSON frame streamId (expected 0)");
154
+ } else if (streamId === 0) throw new Error("Invalid raw frame streamId (expected non-zero)");
155
+ if (length > MAX_FRAME_PAYLOAD_SIZE) throw new Error(`Frame payload too large: ${length} bytes (max ${MAX_FRAME_PAYLOAD_SIZE})`);
156
+ const frameSize = 9 + length;
157
+ if (totalLength < frameSize) break;
158
+ if (++frameCount > MAX_FRAMES) throw new Error(`Too many frames in framed response (max ${MAX_FRAMES})`);
159
+ extractFlattened(9);
160
+ const payload = extractFlattened(length);
161
+ switch (type) {
162
+ case FrameType.JSON:
163
+ try {
164
+ jsonController.enqueue(textDecoder.decode(payload));
165
+ } catch {}
166
+ break;
167
+ case FrameType.CHUNK: {
168
+ const ctrl = ensureController(streamId);
169
+ if (ctrl) ctrl.enqueue(payload);
170
+ break;
171
+ }
172
+ case FrameType.END: {
173
+ const ctrl = ensureController(streamId);
174
+ cancelledStreamIds.add(streamId);
175
+ if (ctrl) {
176
+ try {
177
+ ctrl.close();
178
+ } catch {}
179
+ streamControllers.delete(streamId);
180
+ }
181
+ break;
182
+ }
183
+ case FrameType.ERROR: {
184
+ const ctrl = ensureController(streamId);
185
+ cancelledStreamIds.add(streamId);
186
+ if (ctrl) {
187
+ const message = textDecoder.decode(payload);
188
+ ctrl.error(new Error(message));
189
+ streamControllers.delete(streamId);
190
+ }
191
+ break;
192
+ }
193
+ }
194
+ }
195
+ }
196
+ if (totalLength !== 0) throw new Error("Incomplete frame at end of framed response");
197
+ try {
198
+ jsonController.close();
199
+ } catch {}
200
+ streamControllers.forEach((ctrl) => {
201
+ try {
202
+ ctrl.close();
203
+ } catch {}
204
+ });
205
+ streamControllers.clear();
206
+ } catch (error) {
207
+ try {
208
+ jsonController.error(error);
209
+ } catch {}
210
+ streamControllers.forEach((ctrl) => {
211
+ try {
212
+ ctrl.error(error);
213
+ } catch {}
214
+ });
215
+ streamControllers.clear();
216
+ } finally {
217
+ try {
218
+ reader.releaseLock();
219
+ } catch {}
220
+ inputReader = null;
221
+ }
222
+ })();
223
+ return {
224
+ getOrCreateStream,
225
+ jsonChunks
226
+ };
239
227
  }
240
- export {
241
- createFrameDecoder
242
- };
243
- //# sourceMappingURL=frame-decoder.js.map
228
+ //#endregion
229
+ export { createFrameDecoder };
230
+
231
+ //# sourceMappingURL=frame-decoder.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"frame-decoder.js","sources":["../../../src/client-rpc/frame-decoder.ts"],"sourcesContent":["/**\n * Client-side frame decoder for multiplexed responses.\n *\n * Decodes binary frame protocol and reconstructs:\n * - JSON stream (NDJSON lines for seroval)\n * - Raw streams (binary data as ReadableStream<Uint8Array>)\n */\n\nimport { FRAME_HEADER_SIZE, FrameType } from '../constants'\n\n/** Cached TextDecoder for frame decoding */\nconst textDecoder = new TextDecoder()\n\n/** Shared empty buffer for empty buffer case - avoids allocation */\nconst EMPTY_BUFFER = new Uint8Array(0)\n\n/** Hardening limits to prevent memory/CPU DoS */\nconst MAX_FRAME_PAYLOAD_SIZE = 16 * 1024 * 1024 // 16MiB\nconst MAX_BUFFERED_BYTES = 32 * 1024 * 1024 // 32MiB\nconst MAX_STREAMS = 1024\nconst MAX_FRAMES = 100_000 // Limit total frames to prevent CPU DoS\n\n/**\n * Result of frame decoding.\n */\nexport interface FrameDecoderResult {\n /** Gets or creates a raw stream by ID (for use by deserialize plugin) */\n getOrCreateStream: (id: number) => ReadableStream<Uint8Array>\n /** Stream of JSON strings (NDJSON lines) */\n jsonChunks: ReadableStream<string>\n}\n\n/**\n * Creates a frame decoder that processes a multiplexed response stream.\n *\n * @param input The raw response body stream\n * @returns Decoded JSON stream and stream getter function\n */\nexport function createFrameDecoder(\n input: ReadableStream<Uint8Array>,\n): FrameDecoderResult {\n const streamControllers = new Map<\n number,\n ReadableStreamDefaultController<Uint8Array>\n >()\n const streams = new Map<number, ReadableStream<Uint8Array>>()\n const cancelledStreamIds = new Set<number>()\n\n let cancelled = false as boolean\n let inputReader: ReadableStreamReader<Uint8Array> | null = null\n let frameCount = 0\n\n let jsonController!: ReadableStreamDefaultController<string>\n const jsonChunks = new ReadableStream<string>({\n start(controller) {\n jsonController = controller\n },\n cancel() {\n cancelled = true\n try {\n inputReader?.cancel()\n } catch {\n // Ignore\n }\n\n streamControllers.forEach((ctrl) => {\n try {\n ctrl.error(new Error('Framed response cancelled'))\n } catch {\n // Ignore\n }\n })\n streamControllers.clear()\n streams.clear()\n cancelledStreamIds.clear()\n },\n })\n\n /**\n * Gets or creates a stream for a given stream ID.\n * Called by deserialize plugin when it encounters a RawStream reference.\n */\n function getOrCreateStream(id: number): ReadableStream<Uint8Array> {\n const existing = streams.get(id)\n if (existing) {\n return existing\n }\n\n // If we already received an END/ERROR for this streamId, returning a fresh stream\n // would hang consumers. Return an already-closed stream instead.\n if (cancelledStreamIds.has(id)) {\n return new ReadableStream<Uint8Array>({\n start(controller) {\n controller.close()\n },\n })\n }\n\n if (streams.size >= MAX_STREAMS) {\n throw new Error(\n `Too many raw streams in framed response (max ${MAX_STREAMS})`,\n )\n }\n\n const stream = new ReadableStream<Uint8Array>({\n start(ctrl) {\n streamControllers.set(id, ctrl)\n },\n cancel() {\n cancelledStreamIds.add(id)\n streamControllers.delete(id)\n streams.delete(id)\n },\n })\n streams.set(id, stream)\n return stream\n }\n\n /**\n * Ensures stream exists and returns its controller for enqueuing data.\n * Used for CHUNK frames where we need to ensure stream is created.\n */\n function ensureController(\n id: number,\n ): ReadableStreamDefaultController<Uint8Array> | undefined {\n getOrCreateStream(id)\n return streamControllers.get(id)\n }\n\n // Process frames asynchronously\n ;(async () => {\n const reader = input.getReader()\n inputReader = reader\n\n const bufferList: Array<Uint8Array> = []\n let totalLength = 0\n\n /**\n * Reads header bytes from buffer chunks without flattening.\n * Returns header data or null if not enough bytes available.\n */\n function readHeader(): {\n type: number\n streamId: number\n length: number\n } | null {\n if (totalLength < FRAME_HEADER_SIZE) return null\n\n const first = bufferList[0]!\n\n // Fast path: header fits entirely in first chunk (common case)\n if (first.length >= FRAME_HEADER_SIZE) {\n const type = first[0]!\n const streamId =\n ((first[1]! << 24) |\n (first[2]! << 16) |\n (first[3]! << 8) |\n first[4]!) >>>\n 0\n const length =\n ((first[5]! << 24) |\n (first[6]! << 16) |\n (first[7]! << 8) |\n first[8]!) >>>\n 0\n return { type, streamId, length }\n }\n\n // Slow path: header spans multiple chunks - flatten header bytes only\n const headerBytes = new Uint8Array(FRAME_HEADER_SIZE)\n let offset = 0\n let remaining = FRAME_HEADER_SIZE\n for (let i = 0; i < bufferList.length && remaining > 0; i++) {\n const chunk = bufferList[i]!\n const toCopy = Math.min(chunk.length, remaining)\n headerBytes.set(chunk.subarray(0, toCopy), offset)\n offset += toCopy\n remaining -= toCopy\n }\n\n const type = headerBytes[0]!\n const streamId =\n ((headerBytes[1]! << 24) |\n (headerBytes[2]! << 16) |\n (headerBytes[3]! << 8) |\n headerBytes[4]!) >>>\n 0\n const length =\n ((headerBytes[5]! << 24) |\n (headerBytes[6]! << 16) |\n (headerBytes[7]! << 8) |\n headerBytes[8]!) >>>\n 0\n\n return { type, streamId, length }\n }\n\n /**\n * Flattens buffer list into single Uint8Array and removes from list.\n */\n function extractFlattened(count: number): Uint8Array {\n if (count === 0) return EMPTY_BUFFER\n\n const result = new Uint8Array(count)\n let offset = 0\n let remaining = count\n\n while (remaining > 0 && bufferList.length > 0) {\n const chunk = bufferList[0]\n if (!chunk) break\n const toCopy = Math.min(chunk.length, remaining)\n result.set(chunk.subarray(0, toCopy), offset)\n\n offset += toCopy\n remaining -= toCopy\n\n if (toCopy === chunk.length) {\n bufferList.shift()\n } else {\n bufferList[0] = chunk.subarray(toCopy)\n }\n }\n\n totalLength -= count\n return result\n }\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const { done, value } = await reader.read()\n if (cancelled) break\n if (done) break\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!value) continue\n\n // Append incoming chunk to buffer list\n if (totalLength + value.length > MAX_BUFFERED_BYTES) {\n throw new Error(\n `Framed response buffer exceeded ${MAX_BUFFERED_BYTES} bytes`,\n )\n }\n bufferList.push(value)\n totalLength += value.length\n\n // Parse complete frames from buffer\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const header = readHeader()\n if (!header) break // Not enough bytes for header\n\n const { type, streamId, length } = header\n\n if (\n type !== FrameType.JSON &&\n type !== FrameType.CHUNK &&\n type !== FrameType.END &&\n type !== FrameType.ERROR\n ) {\n throw new Error(`Unknown frame type: ${type}`)\n }\n\n // Enforce stream id conventions: JSON uses streamId 0, raw streams use non-zero ids\n if (type === FrameType.JSON) {\n if (streamId !== 0) {\n throw new Error('Invalid JSON frame streamId (expected 0)')\n }\n } else {\n if (streamId === 0) {\n throw new Error('Invalid raw frame streamId (expected non-zero)')\n }\n }\n\n if (length > MAX_FRAME_PAYLOAD_SIZE) {\n throw new Error(\n `Frame payload too large: ${length} bytes (max ${MAX_FRAME_PAYLOAD_SIZE})`,\n )\n }\n\n const frameSize = FRAME_HEADER_SIZE + length\n if (totalLength < frameSize) break // Wait for more data\n\n if (++frameCount > MAX_FRAMES) {\n throw new Error(\n `Too many frames in framed response (max ${MAX_FRAMES})`,\n )\n }\n\n // Extract and consume header bytes\n extractFlattened(FRAME_HEADER_SIZE)\n\n // Extract payload\n const payload = extractFlattened(length)\n\n // Process frame by type\n switch (type) {\n case FrameType.JSON: {\n try {\n jsonController.enqueue(textDecoder.decode(payload))\n } catch {\n // JSON stream may be cancelled/closed\n }\n break\n }\n\n case FrameType.CHUNK: {\n const ctrl = ensureController(streamId)\n if (ctrl) {\n ctrl.enqueue(payload)\n }\n break\n }\n\n case FrameType.END: {\n const ctrl = ensureController(streamId)\n cancelledStreamIds.add(streamId)\n if (ctrl) {\n try {\n ctrl.close()\n } catch {\n // Already closed\n }\n streamControllers.delete(streamId)\n }\n break\n }\n\n case FrameType.ERROR: {\n const ctrl = ensureController(streamId)\n cancelledStreamIds.add(streamId)\n if (ctrl) {\n const message = textDecoder.decode(payload)\n ctrl.error(new Error(message))\n streamControllers.delete(streamId)\n }\n break\n }\n }\n }\n }\n\n if (totalLength !== 0) {\n throw new Error('Incomplete frame at end of framed response')\n }\n\n // Close JSON stream when done\n try {\n jsonController.close()\n } catch {\n // JSON stream may be cancelled/closed\n }\n\n // Close any remaining streams (shouldn't happen in normal operation)\n streamControllers.forEach((ctrl) => {\n try {\n ctrl.close()\n } catch {\n // Already closed\n }\n })\n streamControllers.clear()\n } catch (error) {\n // Error reading - propagate to all streams\n try {\n jsonController.error(error)\n } catch {\n // Already errored/closed\n }\n streamControllers.forEach((ctrl) => {\n try {\n ctrl.error(error)\n } catch {\n // Already errored/closed\n }\n })\n streamControllers.clear()\n } finally {\n try {\n reader.releaseLock()\n } catch {\n // Ignore\n }\n inputReader = null\n }\n })()\n\n return { getOrCreateStream, jsonChunks }\n}\n"],"names":["type","streamId","length"],"mappings":"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"}
1
+ {"version":3,"file":"frame-decoder.js","names":[],"sources":["../../../src/client-rpc/frame-decoder.ts"],"sourcesContent":["/**\n * Client-side frame decoder for multiplexed responses.\n *\n * Decodes binary frame protocol and reconstructs:\n * - JSON stream (NDJSON lines for seroval)\n * - Raw streams (binary data as ReadableStream<Uint8Array>)\n */\n\nimport { FRAME_HEADER_SIZE, FrameType } from '../constants'\n\n/** Cached TextDecoder for frame decoding */\nconst textDecoder = new TextDecoder()\n\n/** Shared empty buffer for empty buffer case - avoids allocation */\nconst EMPTY_BUFFER = new Uint8Array(0)\n\n/** Hardening limits to prevent memory/CPU DoS */\nconst MAX_FRAME_PAYLOAD_SIZE = 16 * 1024 * 1024 // 16MiB\nconst MAX_BUFFERED_BYTES = 32 * 1024 * 1024 // 32MiB\nconst MAX_STREAMS = 1024\nconst MAX_FRAMES = 100_000 // Limit total frames to prevent CPU DoS\n\n/**\n * Result of frame decoding.\n */\nexport interface FrameDecoderResult {\n /** Gets or creates a raw stream by ID (for use by deserialize plugin) */\n getOrCreateStream: (id: number) => ReadableStream<Uint8Array>\n /** Stream of JSON strings (NDJSON lines) */\n jsonChunks: ReadableStream<string>\n}\n\n/**\n * Creates a frame decoder that processes a multiplexed response stream.\n *\n * @param input The raw response body stream\n * @returns Decoded JSON stream and stream getter function\n */\nexport function createFrameDecoder(\n input: ReadableStream<Uint8Array>,\n): FrameDecoderResult {\n const streamControllers = new Map<\n number,\n ReadableStreamDefaultController<Uint8Array>\n >()\n const streams = new Map<number, ReadableStream<Uint8Array>>()\n const cancelledStreamIds = new Set<number>()\n\n let cancelled = false as boolean\n let inputReader: ReadableStreamReader<Uint8Array> | null = null\n let frameCount = 0\n\n let jsonController!: ReadableStreamDefaultController<string>\n const jsonChunks = new ReadableStream<string>({\n start(controller) {\n jsonController = controller\n },\n cancel() {\n cancelled = true\n try {\n inputReader?.cancel()\n } catch {\n // Ignore\n }\n\n streamControllers.forEach((ctrl) => {\n try {\n ctrl.error(new Error('Framed response cancelled'))\n } catch {\n // Ignore\n }\n })\n streamControllers.clear()\n streams.clear()\n cancelledStreamIds.clear()\n },\n })\n\n /**\n * Gets or creates a stream for a given stream ID.\n * Called by deserialize plugin when it encounters a RawStream reference.\n */\n function getOrCreateStream(id: number): ReadableStream<Uint8Array> {\n const existing = streams.get(id)\n if (existing) {\n return existing\n }\n\n // If we already received an END/ERROR for this streamId, returning a fresh stream\n // would hang consumers. Return an already-closed stream instead.\n if (cancelledStreamIds.has(id)) {\n return new ReadableStream<Uint8Array>({\n start(controller) {\n controller.close()\n },\n })\n }\n\n if (streams.size >= MAX_STREAMS) {\n throw new Error(\n `Too many raw streams in framed response (max ${MAX_STREAMS})`,\n )\n }\n\n const stream = new ReadableStream<Uint8Array>({\n start(ctrl) {\n streamControllers.set(id, ctrl)\n },\n cancel() {\n cancelledStreamIds.add(id)\n streamControllers.delete(id)\n streams.delete(id)\n },\n })\n streams.set(id, stream)\n return stream\n }\n\n /**\n * Ensures stream exists and returns its controller for enqueuing data.\n * Used for CHUNK frames where we need to ensure stream is created.\n */\n function ensureController(\n id: number,\n ): ReadableStreamDefaultController<Uint8Array> | undefined {\n getOrCreateStream(id)\n return streamControllers.get(id)\n }\n\n // Process frames asynchronously\n ;(async () => {\n const reader = input.getReader()\n inputReader = reader\n\n const bufferList: Array<Uint8Array> = []\n let totalLength = 0\n\n /**\n * Reads header bytes from buffer chunks without flattening.\n * Returns header data or null if not enough bytes available.\n */\n function readHeader(): {\n type: number\n streamId: number\n length: number\n } | null {\n if (totalLength < FRAME_HEADER_SIZE) return null\n\n const first = bufferList[0]!\n\n // Fast path: header fits entirely in first chunk (common case)\n if (first.length >= FRAME_HEADER_SIZE) {\n const type = first[0]!\n const streamId =\n ((first[1]! << 24) |\n (first[2]! << 16) |\n (first[3]! << 8) |\n first[4]!) >>>\n 0\n const length =\n ((first[5]! << 24) |\n (first[6]! << 16) |\n (first[7]! << 8) |\n first[8]!) >>>\n 0\n return { type, streamId, length }\n }\n\n // Slow path: header spans multiple chunks - flatten header bytes only\n const headerBytes = new Uint8Array(FRAME_HEADER_SIZE)\n let offset = 0\n let remaining = FRAME_HEADER_SIZE\n for (let i = 0; i < bufferList.length && remaining > 0; i++) {\n const chunk = bufferList[i]!\n const toCopy = Math.min(chunk.length, remaining)\n headerBytes.set(chunk.subarray(0, toCopy), offset)\n offset += toCopy\n remaining -= toCopy\n }\n\n const type = headerBytes[0]!\n const streamId =\n ((headerBytes[1]! << 24) |\n (headerBytes[2]! << 16) |\n (headerBytes[3]! << 8) |\n headerBytes[4]!) >>>\n 0\n const length =\n ((headerBytes[5]! << 24) |\n (headerBytes[6]! << 16) |\n (headerBytes[7]! << 8) |\n headerBytes[8]!) >>>\n 0\n\n return { type, streamId, length }\n }\n\n /**\n * Flattens buffer list into single Uint8Array and removes from list.\n */\n function extractFlattened(count: number): Uint8Array {\n if (count === 0) return EMPTY_BUFFER\n\n const result = new Uint8Array(count)\n let offset = 0\n let remaining = count\n\n while (remaining > 0 && bufferList.length > 0) {\n const chunk = bufferList[0]\n if (!chunk) break\n const toCopy = Math.min(chunk.length, remaining)\n result.set(chunk.subarray(0, toCopy), offset)\n\n offset += toCopy\n remaining -= toCopy\n\n if (toCopy === chunk.length) {\n bufferList.shift()\n } else {\n bufferList[0] = chunk.subarray(toCopy)\n }\n }\n\n totalLength -= count\n return result\n }\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const { done, value } = await reader.read()\n if (cancelled) break\n if (done) break\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n if (!value) continue\n\n // Append incoming chunk to buffer list\n if (totalLength + value.length > MAX_BUFFERED_BYTES) {\n throw new Error(\n `Framed response buffer exceeded ${MAX_BUFFERED_BYTES} bytes`,\n )\n }\n bufferList.push(value)\n totalLength += value.length\n\n // Parse complete frames from buffer\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const header = readHeader()\n if (!header) break // Not enough bytes for header\n\n const { type, streamId, length } = header\n\n if (\n type !== FrameType.JSON &&\n type !== FrameType.CHUNK &&\n type !== FrameType.END &&\n type !== FrameType.ERROR\n ) {\n throw new Error(`Unknown frame type: ${type}`)\n }\n\n // Enforce stream id conventions: JSON uses streamId 0, raw streams use non-zero ids\n if (type === FrameType.JSON) {\n if (streamId !== 0) {\n throw new Error('Invalid JSON frame streamId (expected 0)')\n }\n } else {\n if (streamId === 0) {\n throw new Error('Invalid raw frame streamId (expected non-zero)')\n }\n }\n\n if (length > MAX_FRAME_PAYLOAD_SIZE) {\n throw new Error(\n `Frame payload too large: ${length} bytes (max ${MAX_FRAME_PAYLOAD_SIZE})`,\n )\n }\n\n const frameSize = FRAME_HEADER_SIZE + length\n if (totalLength < frameSize) break // Wait for more data\n\n if (++frameCount > MAX_FRAMES) {\n throw new Error(\n `Too many frames in framed response (max ${MAX_FRAMES})`,\n )\n }\n\n // Extract and consume header bytes\n extractFlattened(FRAME_HEADER_SIZE)\n\n // Extract payload\n const payload = extractFlattened(length)\n\n // Process frame by type\n switch (type) {\n case FrameType.JSON: {\n try {\n jsonController.enqueue(textDecoder.decode(payload))\n } catch {\n // JSON stream may be cancelled/closed\n }\n break\n }\n\n case FrameType.CHUNK: {\n const ctrl = ensureController(streamId)\n if (ctrl) {\n ctrl.enqueue(payload)\n }\n break\n }\n\n case FrameType.END: {\n const ctrl = ensureController(streamId)\n cancelledStreamIds.add(streamId)\n if (ctrl) {\n try {\n ctrl.close()\n } catch {\n // Already closed\n }\n streamControllers.delete(streamId)\n }\n break\n }\n\n case FrameType.ERROR: {\n const ctrl = ensureController(streamId)\n cancelledStreamIds.add(streamId)\n if (ctrl) {\n const message = textDecoder.decode(payload)\n ctrl.error(new Error(message))\n streamControllers.delete(streamId)\n }\n break\n }\n }\n }\n }\n\n if (totalLength !== 0) {\n throw new Error('Incomplete frame at end of framed response')\n }\n\n // Close JSON stream when done\n try {\n jsonController.close()\n } catch {\n // JSON stream may be cancelled/closed\n }\n\n // Close any remaining streams (shouldn't happen in normal operation)\n streamControllers.forEach((ctrl) => {\n try {\n ctrl.close()\n } catch {\n // Already closed\n }\n })\n streamControllers.clear()\n } catch (error) {\n // Error reading - propagate to all streams\n try {\n jsonController.error(error)\n } catch {\n // Already errored/closed\n }\n streamControllers.forEach((ctrl) => {\n try {\n ctrl.error(error)\n } catch {\n // Already errored/closed\n }\n })\n streamControllers.clear()\n } finally {\n try {\n reader.releaseLock()\n } catch {\n // Ignore\n }\n inputReader = null\n }\n })()\n\n return { getOrCreateStream, jsonChunks }\n}\n"],"mappings":"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"}
@@ -1,5 +1,2 @@
1
1
  import { createClientRpc } from "./createClientRpc.js";
2
- export {
3
- createClientRpc
4
- };
5
- //# sourceMappingURL=index.js.map
2
+ export { createClientRpc };