@linzumi/cli 0.0.20-beta → 0.0.22-beta

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,324 +0,0 @@
1
- /*
2
- - Date: 2026-04-29
3
- Spec: plans/2026-04-29-local-runner-streaming-forwarding-rca-plan.md
4
- Relationship: Defines the runner-side binary local-forwarding tunnel frames
5
- so editor/preview bytes avoid JSON/base64 Phoenix events.
6
- */
7
- import { type JsonObject, isJsonObject } from "./protocol";
8
-
9
- const textEncoder = new TextEncoder();
10
- const textDecoder = new TextDecoder();
11
- const maxJsonPayloadBytes = 64 * 1024;
12
- const maxChunkBytes = 16 * 1024 * 1024;
13
-
14
- export const forwardTunnelChunkBytes = maxChunkBytes;
15
-
16
- export type ForwardTunnelWebSocketOpcode = "text" | "binary" | "ping" | "pong";
17
-
18
- export type ForwardTunnelFrame =
19
- | {
20
- readonly type: "open_http";
21
- readonly streamId: number;
22
- readonly payload: JsonObject;
23
- }
24
- | {
25
- readonly type: "request_body_chunk";
26
- readonly streamId: number;
27
- readonly payload: Uint8Array;
28
- }
29
- | { readonly type: "request_body_end"; readonly streamId: number }
30
- | {
31
- readonly type: "response_headers";
32
- readonly streamId: number;
33
- readonly payload: JsonObject;
34
- }
35
- | {
36
- readonly type: "response_body_chunk";
37
- readonly streamId: number;
38
- readonly payload: Uint8Array;
39
- }
40
- | { readonly type: "response_end"; readonly streamId: number }
41
- | {
42
- readonly type: "cancel";
43
- readonly streamId: number;
44
- readonly payload: JsonObject;
45
- }
46
- | {
47
- readonly type: "stream_error";
48
- readonly streamId: number;
49
- readonly payload: JsonObject;
50
- }
51
- | {
52
- readonly type: "open_websocket";
53
- readonly streamId: number;
54
- readonly payload: JsonObject;
55
- }
56
- | {
57
- readonly type: "websocket_message";
58
- readonly streamId: number;
59
- readonly opcode: ForwardTunnelWebSocketOpcode;
60
- readonly payload: Uint8Array;
61
- }
62
- | {
63
- readonly type: "websocket_close";
64
- readonly streamId: number;
65
- readonly payload: JsonObject;
66
- };
67
-
68
- export type DecodeForwardTunnelFrameResult =
69
- | { readonly ok: true; readonly frame: ForwardTunnelFrame }
70
- | { readonly ok: false; readonly error: string };
71
-
72
- export function encodeForwardTunnelFrame(
73
- frame: ForwardTunnelFrame,
74
- ): Uint8Array {
75
- const type = frameTypeId(frame.type);
76
- const body = framePayload(frame);
77
- if (body.byteLength > maxChunkBytes) {
78
- throw new Error("streaming_forward_payload_too_large");
79
- }
80
-
81
- const output = new Uint8Array(10 + body.byteLength);
82
- output[0] = 75;
83
- output[1] = 70;
84
- output[2] = 84;
85
- output[3] = 49;
86
- output[4] = type;
87
- output[5] = 0;
88
- const view = new DataView(
89
- output.buffer,
90
- output.byteOffset,
91
- output.byteLength,
92
- );
93
- view.setUint32(6, frame.streamId);
94
- output.set(body, 10);
95
- return output;
96
- }
97
-
98
- export function decodeForwardTunnelFrame(
99
- input: Blob | ArrayBuffer | Uint8Array,
100
- ): Promise<DecodeForwardTunnelFrameResult> {
101
- return bytesFromMessage(input).then((bytes) =>
102
- decodeForwardTunnelBytes(bytes),
103
- );
104
- }
105
-
106
- export function decodeForwardTunnelBytes(
107
- bytes: Uint8Array,
108
- ): DecodeForwardTunnelFrameResult {
109
- if (
110
- bytes.byteLength < 10 ||
111
- bytes[0] !== 75 ||
112
- bytes[1] !== 70 ||
113
- bytes[2] !== 84 ||
114
- bytes[3] !== 49 ||
115
- bytes[5] !== 0
116
- ) {
117
- return { ok: false, error: "invalid_streaming_forward_frame" };
118
- }
119
-
120
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
121
- const type = frameTypeName(bytes[4]);
122
- const streamId = view.getUint32(6);
123
- const payload = bytes.subarray(10);
124
-
125
- if (
126
- type === undefined ||
127
- streamId < 1 ||
128
- payload.byteLength > maxChunkBytes
129
- ) {
130
- return { ok: false, error: "invalid_streaming_forward_frame" };
131
- }
132
-
133
- switch (type) {
134
- case "open_http":
135
- case "response_headers":
136
- case "cancel":
137
- case "stream_error":
138
- case "open_websocket":
139
- case "websocket_close":
140
- return decodeJsonPayload(type, streamId, payload);
141
- case "request_body_chunk":
142
- case "response_body_chunk":
143
- return { ok: true, frame: { type, streamId, payload } };
144
- case "request_body_end":
145
- case "response_end":
146
- return payload.byteLength === 0
147
- ? { ok: true, frame: { type, streamId } }
148
- : { ok: false, error: "invalid_streaming_forward_frame" };
149
- case "websocket_message":
150
- return decodeWebSocketMessage(streamId, payload);
151
- }
152
- }
153
-
154
- function framePayload(frame: ForwardTunnelFrame): Uint8Array {
155
- switch (frame.type) {
156
- case "open_http":
157
- case "response_headers":
158
- case "cancel":
159
- case "stream_error":
160
- case "open_websocket":
161
- case "websocket_close": {
162
- const encoded = textEncoder.encode(JSON.stringify(frame.payload));
163
- if (encoded.byteLength > maxJsonPayloadBytes) {
164
- throw new Error("streaming_forward_payload_too_large");
165
- }
166
- return encoded;
167
- }
168
- case "request_body_chunk":
169
- case "response_body_chunk":
170
- return frame.payload;
171
- case "request_body_end":
172
- case "response_end":
173
- return new Uint8Array();
174
- case "websocket_message": {
175
- const output = new Uint8Array(1 + frame.payload.byteLength);
176
- output[0] = websocketOpcodeId(frame.opcode);
177
- output.set(frame.payload, 1);
178
- return output;
179
- }
180
- }
181
- }
182
-
183
- function decodeJsonPayload(
184
- type:
185
- | "open_http"
186
- | "response_headers"
187
- | "cancel"
188
- | "stream_error"
189
- | "open_websocket"
190
- | "websocket_close",
191
- streamId: number,
192
- payload: Uint8Array,
193
- ): DecodeForwardTunnelFrameResult {
194
- if (payload.byteLength > maxJsonPayloadBytes) {
195
- return { ok: false, error: "streaming_forward_payload_too_large" };
196
- }
197
-
198
- try {
199
- const parsed = JSON.parse(textDecoder.decode(payload));
200
- return isJsonObject(parsed)
201
- ? { ok: true, frame: { type, streamId, payload: parsed } }
202
- : { ok: false, error: "invalid_streaming_forward_frame" };
203
- } catch (_error) {
204
- return { ok: false, error: "invalid_streaming_forward_frame" };
205
- }
206
- }
207
-
208
- function decodeWebSocketMessage(
209
- streamId: number,
210
- payload: Uint8Array,
211
- ): DecodeForwardTunnelFrameResult {
212
- const opcode = websocketOpcodeName(payload[0]);
213
- return opcode === undefined
214
- ? { ok: false, error: "invalid_streaming_forward_frame" }
215
- : {
216
- ok: true,
217
- frame: {
218
- type: "websocket_message",
219
- streamId,
220
- opcode,
221
- payload: payload.subarray(1),
222
- },
223
- };
224
- }
225
-
226
- function bytesFromMessage(
227
- input: Blob | ArrayBuffer | Uint8Array,
228
- ): Promise<Uint8Array> {
229
- if (input instanceof Uint8Array) {
230
- return Promise.resolve(input);
231
- }
232
-
233
- if (input instanceof ArrayBuffer) {
234
- return Promise.resolve(new Uint8Array(input));
235
- }
236
-
237
- return input.arrayBuffer().then((buffer) => new Uint8Array(buffer));
238
- }
239
-
240
- function frameTypeId(type: ForwardTunnelFrame["type"]): number {
241
- switch (type) {
242
- case "open_http":
243
- return 1;
244
- case "request_body_chunk":
245
- return 2;
246
- case "request_body_end":
247
- return 3;
248
- case "response_headers":
249
- return 4;
250
- case "response_body_chunk":
251
- return 5;
252
- case "response_end":
253
- return 6;
254
- case "cancel":
255
- return 7;
256
- case "stream_error":
257
- return 8;
258
- case "open_websocket":
259
- return 9;
260
- case "websocket_message":
261
- return 10;
262
- case "websocket_close":
263
- return 11;
264
- }
265
- }
266
-
267
- function frameTypeName(typeId: number): ForwardTunnelFrame["type"] | undefined {
268
- switch (typeId) {
269
- case 1:
270
- return "open_http";
271
- case 2:
272
- return "request_body_chunk";
273
- case 3:
274
- return "request_body_end";
275
- case 4:
276
- return "response_headers";
277
- case 5:
278
- return "response_body_chunk";
279
- case 6:
280
- return "response_end";
281
- case 7:
282
- return "cancel";
283
- case 8:
284
- return "stream_error";
285
- case 9:
286
- return "open_websocket";
287
- case 10:
288
- return "websocket_message";
289
- case 11:
290
- return "websocket_close";
291
- default:
292
- return undefined;
293
- }
294
- }
295
-
296
- function websocketOpcodeId(opcode: ForwardTunnelWebSocketOpcode): number {
297
- switch (opcode) {
298
- case "text":
299
- return 1;
300
- case "binary":
301
- return 2;
302
- case "ping":
303
- return 9;
304
- case "pong":
305
- return 10;
306
- }
307
- }
308
-
309
- function websocketOpcodeName(
310
- opcodeId: number,
311
- ): ForwardTunnelWebSocketOpcode | undefined {
312
- switch (opcodeId) {
313
- case 1:
314
- return "text";
315
- case 2:
316
- return "binary";
317
- case 9:
318
- return "ping";
319
- case 10:
320
- return "pong";
321
- default:
322
- return undefined;
323
- }
324
- }