@milkio/stargate 1.0.0-alpha.93 → 1.0.0-alpha.95
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/index.ts +359 -378
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -1,112 +1,112 @@
|
|
|
1
|
-
import { TSON } from
|
|
1
|
+
import { TSON } from "@southern-aurora/tson";
|
|
2
2
|
|
|
3
3
|
export type MilkioStargateOptions = {
|
|
4
|
-
baseUrl: string | (() => string) | (() => Promise<string>)
|
|
5
|
-
timeout?: number
|
|
6
|
-
fetch?: typeof fetch
|
|
7
|
-
abort?: typeof AbortController
|
|
8
|
-
}
|
|
4
|
+
baseUrl: string | (() => string) | (() => Promise<string>);
|
|
5
|
+
timeout?: number;
|
|
6
|
+
fetch?: typeof fetch;
|
|
7
|
+
abort?: typeof AbortController;
|
|
8
|
+
};
|
|
9
9
|
|
|
10
|
-
export type Mixin<T, U> = U & Omit<T, keyof U
|
|
10
|
+
export type Mixin<T, U> = U & Omit<T, keyof U>;
|
|
11
11
|
|
|
12
12
|
export type ExecuteOptions = {
|
|
13
|
-
params?: Record<any, any
|
|
14
|
-
headers?: Record<string, string
|
|
15
|
-
timeout?: number
|
|
16
|
-
type?:
|
|
17
|
-
baseUrl?: string | (() => string) | (() => Promise<string>)
|
|
18
|
-
}
|
|
13
|
+
params?: Record<any, any>;
|
|
14
|
+
headers?: Record<string, string>;
|
|
15
|
+
timeout?: number;
|
|
16
|
+
type?: "action" | "stream";
|
|
17
|
+
baseUrl?: string | (() => string) | (() => Promise<string>);
|
|
18
|
+
};
|
|
19
19
|
|
|
20
|
-
export type ExecuteResultsOption = { executeId: string }
|
|
20
|
+
export type ExecuteResultsOption = { executeId: string };
|
|
21
21
|
|
|
22
22
|
export type Ping =
|
|
23
23
|
| [
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
{
|
|
25
|
+
connect: false;
|
|
26
|
+
delay: number;
|
|
27
|
+
error: any;
|
|
28
|
+
},
|
|
29
|
+
null,
|
|
30
|
+
]
|
|
31
31
|
| [
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
export async function createStargate<Generated extends { routeSchema: any
|
|
40
|
-
const $fetch = stargateOptions.fetch ?? fetch
|
|
41
|
-
const $abort = stargateOptions.abort ?? AbortController
|
|
32
|
+
null,
|
|
33
|
+
{
|
|
34
|
+
connect: true;
|
|
35
|
+
delay: number;
|
|
36
|
+
serverTimestamp: number;
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
export async function createStargate<Generated extends { routeSchema: any; rejectCode: any }>(stargateOptions: MilkioStargateOptions) {
|
|
40
|
+
const $fetch = stargateOptions.fetch ?? fetch;
|
|
41
|
+
const $abort = stargateOptions.abort ?? AbortController;
|
|
42
42
|
|
|
43
43
|
type StargateEvents = {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
path: string
|
|
48
|
-
options: Mixin<ExecuteOptions, { headers: Record<string, string
|
|
49
|
-
error: Partial<Generated[
|
|
50
|
-
handleError: <K extends keyof Partial<Generated[
|
|
51
|
-
}
|
|
52
|
-
}
|
|
44
|
+
"milkio:executeBefore": { path: string; options: Mixin<ExecuteOptions, { headers: Record<string, string>; baseUrl: string }> };
|
|
45
|
+
"milkio:fetchBefore": { path: string; options: Mixin<ExecuteOptions, { headers: Record<string, string>; baseUrl: string }>; body: string };
|
|
46
|
+
"milkio:executeError": {
|
|
47
|
+
path: string;
|
|
48
|
+
options: Mixin<ExecuteOptions, { headers: Record<string, string>; baseUrl: string }>;
|
|
49
|
+
error: Partial<Generated["rejectCode"]>;
|
|
50
|
+
handleError: <K extends keyof Partial<Generated["rejectCode"]>>(error: any, key: K, handler: (error: Partial<Generated["rejectCode"][K]>) => boolean | Promise<boolean>) => Promise<void>;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
53
|
|
|
54
54
|
const handleError: any = async (error: any, key: string, handler: (error: any) => boolean | Promise<boolean>) => {
|
|
55
55
|
if (key in error) {
|
|
56
|
-
const handled = await handler(error[key])
|
|
57
|
-
if (handled) delete error[key]
|
|
56
|
+
const handled = await handler(error[key]);
|
|
57
|
+
if (handled) delete error[key];
|
|
58
58
|
}
|
|
59
|
-
}
|
|
59
|
+
};
|
|
60
60
|
|
|
61
61
|
const __initEventManager = () => {
|
|
62
|
-
const handlers = new Map<(event: any) => void, string>()
|
|
63
|
-
const indexed = new Map<string, Set<(event: any) => void>>()
|
|
62
|
+
const handlers = new Map<(event: any) => void, string>();
|
|
63
|
+
const indexed = new Map<string, Set<(event: any) => void>>();
|
|
64
64
|
|
|
65
65
|
const eventManager = {
|
|
66
66
|
on: <Key extends keyof StargateEvents, Handler extends (event: StargateEvents[Key]) => void>(key: Key, handler: Handler) => {
|
|
67
|
-
handlers.set(handler, key as string)
|
|
67
|
+
handlers.set(handler, key as string);
|
|
68
68
|
if (indexed.has(key as string) === false) {
|
|
69
|
-
indexed.set(key as string, new Set())
|
|
69
|
+
indexed.set(key as string, new Set());
|
|
70
70
|
}
|
|
71
|
-
const set = indexed.get(key as string)
|
|
72
|
-
set.add(handler)
|
|
73
|
-
handlers.set(handler, key as string)
|
|
71
|
+
const set = indexed.get(key as string)!;
|
|
72
|
+
set.add(handler);
|
|
73
|
+
handlers.set(handler, key as string);
|
|
74
74
|
|
|
75
75
|
return () => {
|
|
76
|
-
handlers.delete(handler)
|
|
77
|
-
set.delete(handler)
|
|
78
|
-
}
|
|
76
|
+
handlers.delete(handler);
|
|
77
|
+
set.delete(handler);
|
|
78
|
+
};
|
|
79
79
|
},
|
|
80
80
|
off: <Key extends keyof StargateEvents, Handler extends (event: StargateEvents[Key]) => void>(key: Key, handler: Handler) => {
|
|
81
|
-
const set = indexed.get(key as string)
|
|
82
|
-
if (!set) return
|
|
83
|
-
handlers.delete(handler)
|
|
84
|
-
set.delete(handler)
|
|
81
|
+
const set = indexed.get(key as string);
|
|
82
|
+
if (!set) return;
|
|
83
|
+
handlers.delete(handler);
|
|
84
|
+
set.delete(handler);
|
|
85
85
|
},
|
|
86
86
|
emit: async <Key extends keyof StargateEvents, Value extends StargateEvents[Key]>(key: Key, value: Value): Promise<void> => {
|
|
87
|
-
const h = indexed.get(key as string)
|
|
87
|
+
const h = indexed.get(key as string);
|
|
88
88
|
if (h) {
|
|
89
89
|
for (const handler of h) {
|
|
90
|
-
await handler(value)
|
|
90
|
+
await handler(value);
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
},
|
|
94
|
-
}
|
|
94
|
+
};
|
|
95
95
|
|
|
96
|
-
return eventManager
|
|
97
|
-
}
|
|
96
|
+
return eventManager;
|
|
97
|
+
};
|
|
98
98
|
|
|
99
|
-
const eventManager = __initEventManager()
|
|
99
|
+
const eventManager = __initEventManager();
|
|
100
100
|
|
|
101
101
|
const bootstrap = async () => {
|
|
102
|
-
let baseUrl = stargateOptions.baseUrl
|
|
103
|
-
if (typeof baseUrl ===
|
|
104
|
-
if (baseUrl.endsWith(
|
|
102
|
+
let baseUrl = stargateOptions.baseUrl;
|
|
103
|
+
if (typeof baseUrl === "function") baseUrl = await baseUrl();
|
|
104
|
+
if (baseUrl.endsWith("/")) baseUrl = baseUrl.slice(0, -1);
|
|
105
105
|
|
|
106
|
-
return baseUrl
|
|
107
|
-
}
|
|
106
|
+
return baseUrl;
|
|
107
|
+
};
|
|
108
108
|
|
|
109
|
-
const baseUrl: Promise<string> = bootstrap()
|
|
109
|
+
const baseUrl: Promise<string> = bootstrap();
|
|
110
110
|
|
|
111
111
|
const stargate = {
|
|
112
112
|
...eventManager,
|
|
@@ -114,412 +114,402 @@ export async function createStargate<Generated extends { routeSchema: any, rejec
|
|
|
114
114
|
generated: void 0 as unknown as Generated,
|
|
115
115
|
},
|
|
116
116
|
options: stargateOptions,
|
|
117
|
-
async execute<Path extends keyof Generated[
|
|
117
|
+
async execute<Path extends keyof Generated["routeSchema"]>(
|
|
118
118
|
path: Path,
|
|
119
119
|
options?: Mixin<
|
|
120
120
|
ExecuteOptions,
|
|
121
121
|
{
|
|
122
|
-
params?: Generated[
|
|
122
|
+
params?: Generated["routeSchema"][Path]["types"]["params"];
|
|
123
123
|
}
|
|
124
124
|
>,
|
|
125
125
|
): Promise<
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
[Partial<Generated[
|
|
129
|
-
|
|
130
|
-
[Partial<Generated[
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (options
|
|
134
|
-
|
|
135
|
-
|
|
126
|
+
Generated["routeSchema"][Path]["types"]["🐣"] extends boolean
|
|
127
|
+
? // action
|
|
128
|
+
[Partial<Generated["rejectCode"]>, null, ExecuteResultsOption] | [null, Generated["routeSchema"][Path]["types"]["result"], ExecuteResultsOption]
|
|
129
|
+
: // stream
|
|
130
|
+
[Partial<Generated["rejectCode"]>, null, ExecuteResultsOption] | [null, AsyncGenerator<[Partial<Generated["rejectCode"]>, null] | [null, GeneratorGeneric<Generated["routeSchema"][Path]["types"]["result"]>], ExecuteResultsOption>]
|
|
131
|
+
> {
|
|
132
|
+
// biome-ignore lint/style/noParameterAssign: <explanation>
|
|
133
|
+
if (!options) options = {};
|
|
134
|
+
if (options.headers === undefined) options.headers = {};
|
|
135
|
+
|
|
136
|
+
let url: string;
|
|
136
137
|
if (options.baseUrl) {
|
|
137
|
-
let baseUrl = options.baseUrl
|
|
138
|
-
if (typeof baseUrl ===
|
|
139
|
-
if (baseUrl.endsWith(
|
|
140
|
-
url = baseUrl + (path as string)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
url = (await baseUrl) + (path as string)
|
|
138
|
+
let baseUrl = options.baseUrl;
|
|
139
|
+
if (typeof baseUrl === "function") baseUrl = await baseUrl();
|
|
140
|
+
if (baseUrl.endsWith("/")) baseUrl = baseUrl.slice(0, -1);
|
|
141
|
+
url = baseUrl + (path as string);
|
|
142
|
+
} else {
|
|
143
|
+
url = (await baseUrl) + (path as string);
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
if (options.type !==
|
|
146
|
+
if (options.type !== "stream") {
|
|
147
147
|
// action
|
|
148
|
-
if (options.headers.Accept === undefined) options.headers.Accept =
|
|
149
|
-
if (options.headers[
|
|
150
|
-
let result: { value: Record<any, any> }
|
|
148
|
+
if (options.headers.Accept === undefined) options.headers.Accept = "application/json";
|
|
149
|
+
if (options.headers["Content-Type"] === undefined) options.headers["Content-Type"] = "application/json";
|
|
150
|
+
let result: { value: Record<any, any> };
|
|
151
151
|
|
|
152
152
|
try {
|
|
153
|
-
await eventManager.emit(
|
|
153
|
+
await eventManager.emit("milkio:executeBefore", { path: path as string, options: options as any });
|
|
154
154
|
|
|
155
|
-
const body = TSON.stringify(options.params) ??
|
|
156
|
-
await eventManager.emit(
|
|
155
|
+
const body = TSON.stringify(options.params) ?? "";
|
|
156
|
+
await eventManager.emit("milkio:fetchBefore", { path: path as string, options: options as any, body });
|
|
157
157
|
|
|
158
|
+
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: <explanation>
|
|
158
159
|
const response = await new Promise<string>(async (resolve, reject) => {
|
|
159
|
-
const timeout = options?.timeout ?? options?.timeout ?? 6000
|
|
160
|
+
const timeout = options?.timeout ?? options?.timeout ?? 6000;
|
|
160
161
|
const timer = setTimeout(() => {
|
|
161
|
-
reject([{ REQUEST_TIMEOUT: { timeout, message: `Execute timeout after ${timeout}ms.` } }, null])
|
|
162
|
-
}, timeout)
|
|
162
|
+
reject([{ REQUEST_TIMEOUT: { timeout, message: `Execute timeout after ${timeout}ms.` } }, null]);
|
|
163
|
+
}, timeout);
|
|
163
164
|
|
|
164
165
|
try {
|
|
165
|
-
const value = await (await $fetch(url, { method:
|
|
166
|
-
clearTimeout(timer)
|
|
167
|
-
resolve(value)
|
|
166
|
+
const value = await (await $fetch(url, { method: "POST", body, headers: options.headers })).text();
|
|
167
|
+
clearTimeout(timer);
|
|
168
|
+
resolve(value);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
reject(error);
|
|
168
171
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
})
|
|
173
|
-
result = { value: TSON.parse(response) }
|
|
174
|
-
}
|
|
175
|
-
catch (error: any) {
|
|
172
|
+
});
|
|
173
|
+
result = { value: TSON.parse(response) };
|
|
174
|
+
} catch (error: any) {
|
|
176
175
|
if (error?.[0]?.REQUEST_TIMEOUT) {
|
|
177
|
-
await eventManager.emit(
|
|
178
|
-
return error
|
|
176
|
+
await eventManager.emit("milkio:executeError", { handleError, path: path as string, options: options as any, error });
|
|
177
|
+
return error;
|
|
179
178
|
}
|
|
180
|
-
const errorPined = { REQUEST_FAIL: error }
|
|
181
|
-
await eventManager.emit(
|
|
182
|
-
return [errorPined, null, { executeId:
|
|
179
|
+
const errorPined = { REQUEST_FAIL: error };
|
|
180
|
+
await eventManager.emit("milkio:executeError", { handleError, path: path as string, options: options as any, error: errorPined });
|
|
181
|
+
return [errorPined, null, { executeId: "unknown" }];
|
|
183
182
|
}
|
|
184
183
|
if (result.value.success !== true) {
|
|
185
|
-
const error: any = {}
|
|
186
|
-
error[result.value.code] = result.value.reject ?? null
|
|
187
|
-
await eventManager.emit(
|
|
188
|
-
return [error, null, { executeId:
|
|
184
|
+
const error: any = {};
|
|
185
|
+
error[result.value.code] = result.value.reject ?? null;
|
|
186
|
+
await eventManager.emit("milkio:executeError", { handleError, path: path as string, options: options as any, error });
|
|
187
|
+
return [error, null, { executeId: "unknown" }];
|
|
189
188
|
}
|
|
190
189
|
|
|
191
|
-
return [null, result.value.data, { executeId: result.value.executeId }] as any
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
190
|
+
return [null, result.value.data, { executeId: result.value.executeId }] as any;
|
|
191
|
+
} else {
|
|
194
192
|
// stream
|
|
195
|
-
if (options.headers.Accept === undefined) options.headers.Accept =
|
|
196
|
-
if (options.headers[
|
|
193
|
+
if (options.headers.Accept === undefined) options.headers.Accept = "text/event-stream";
|
|
194
|
+
if (options.headers["Content-Type"] === undefined) options.headers["Content-Type"] = "application/json";
|
|
197
195
|
|
|
198
196
|
const stacks: Map<
|
|
199
197
|
number,
|
|
200
198
|
{
|
|
201
|
-
done: boolean
|
|
202
|
-
promise: Promise<IteratorResult<any
|
|
203
|
-
resolve: (value: IteratorResult<any>) => void
|
|
204
|
-
reject: (reason: any) => void
|
|
199
|
+
done: boolean;
|
|
200
|
+
promise: Promise<IteratorResult<any>>;
|
|
201
|
+
resolve: (value: IteratorResult<any>) => void;
|
|
202
|
+
reject: (reason: any) => void;
|
|
205
203
|
}
|
|
206
|
-
> = new Map()
|
|
207
|
-
let stacksIndex
|
|
208
|
-
let iteratorIndex
|
|
209
|
-
let streamResult: any
|
|
210
|
-
const streamResultFetched = withResolvers<undefined>()
|
|
204
|
+
> = new Map();
|
|
205
|
+
let stacksIndex = 0;
|
|
206
|
+
let iteratorIndex = 0;
|
|
207
|
+
let streamResult: any;
|
|
208
|
+
const streamResultFetched = withResolvers<undefined>();
|
|
211
209
|
|
|
212
|
-
const timeout = stargateOptions?.timeout ?? options?.timeout ?? 6000
|
|
210
|
+
const timeout = stargateOptions?.timeout ?? options?.timeout ?? 6000;
|
|
213
211
|
const timer = setTimeout(() => {
|
|
214
|
-
streamResultFetched.reject([{ REQUEST_TIMEOUT: { timeout, message: `Execute timeout after ${timeout}ms.` } }, null, { executeId:
|
|
215
|
-
}, timeout)
|
|
212
|
+
streamResultFetched.reject([{ REQUEST_TIMEOUT: { timeout, message: `Execute timeout after ${timeout}ms.` } }, null, { executeId: "unknown" }]);
|
|
213
|
+
}, timeout);
|
|
216
214
|
|
|
217
215
|
const onmessage = (event: EventSourceMessage) => {
|
|
218
|
-
if (event.data.startsWith(
|
|
216
|
+
if (event.data.startsWith("@")) {
|
|
219
217
|
try {
|
|
220
|
-
streamResult = TSON.parse(event.data.slice(1))
|
|
221
|
-
streamResultFetched.resolve(undefined)
|
|
222
|
-
clearTimeout(timer)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
clearTimeout(timer)
|
|
218
|
+
streamResult = TSON.parse(event.data.slice(1));
|
|
219
|
+
streamResultFetched.resolve(undefined);
|
|
220
|
+
clearTimeout(timer);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
streamResultFetched.reject([{ REQUEST_FAIL: error }, null, { executeId: "unknown" }]);
|
|
223
|
+
clearTimeout(timer);
|
|
227
224
|
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const index = ++stacksIndex
|
|
225
|
+
} else {
|
|
226
|
+
const index = ++stacksIndex;
|
|
231
227
|
if (stacks.has(index)) {
|
|
232
|
-
const stack = stacks.get(index)
|
|
233
|
-
stack!.done = true
|
|
234
|
-
stack!.resolve({ done: false, value: TSON.parse(event.data) })
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
stacks.set(index, { ...stack, done: false })
|
|
228
|
+
const stack = stacks.get(index);
|
|
229
|
+
stack!.done = true;
|
|
230
|
+
stack!.resolve({ done: false, value: TSON.parse(event.data) });
|
|
231
|
+
} else {
|
|
232
|
+
const stack = withResolvers<IteratorResult<any>>();
|
|
233
|
+
stack.resolve({ done: false, value: TSON.parse(event.data) });
|
|
234
|
+
stacks.set(index, { ...stack, done: false });
|
|
240
235
|
}
|
|
241
236
|
}
|
|
242
|
-
}
|
|
237
|
+
};
|
|
243
238
|
|
|
244
|
-
let curRequestController: AbortController
|
|
239
|
+
let curRequestController: AbortController;
|
|
245
240
|
|
|
246
241
|
async function create() {
|
|
247
|
-
curRequestController = new $abort()
|
|
248
|
-
curRequestController.signal.addEventListener(
|
|
249
|
-
iterator.return()
|
|
250
|
-
})
|
|
242
|
+
curRequestController = new $abort();
|
|
243
|
+
curRequestController.signal.addEventListener("abort", () => {
|
|
244
|
+
iterator.return();
|
|
245
|
+
});
|
|
251
246
|
try {
|
|
252
|
-
await eventManager.emit(
|
|
247
|
+
await eventManager.emit("milkio:executeBefore", { path: path as string, options: options as any });
|
|
253
248
|
|
|
254
|
-
const body = TSON.stringify(options!.params) ??
|
|
255
|
-
await eventManager.emit(
|
|
249
|
+
const body = TSON.stringify(options!.params) ?? "";
|
|
250
|
+
await eventManager.emit("milkio:fetchBefore", { path: path as string, options: options as any, body });
|
|
256
251
|
|
|
257
252
|
const response = await $fetch(url, {
|
|
258
|
-
method:
|
|
253
|
+
method: "POST",
|
|
259
254
|
headers: options!.headers,
|
|
260
255
|
body,
|
|
261
256
|
signal: curRequestController.signal,
|
|
262
|
-
})
|
|
257
|
+
});
|
|
263
258
|
|
|
264
|
-
const contentType = response.headers.get(
|
|
265
|
-
if (!contentType?.startsWith(
|
|
266
|
-
throw new Error(`Expected content-type to be ${
|
|
259
|
+
const contentType = response.headers.get("Content-Type");
|
|
260
|
+
if (!contentType?.startsWith("text/event-stream")) {
|
|
261
|
+
throw new Error(`Expected content-type to be ${"text/event-stream"}, Actual: ${contentType}`);
|
|
267
262
|
}
|
|
268
263
|
|
|
269
|
-
await getBytes(response.body!, getLines(getMessages(onmessage)))
|
|
264
|
+
await getBytes(response.body!, getLines(getMessages(onmessage)));
|
|
270
265
|
|
|
271
|
-
await iterator.return()
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
await
|
|
277
|
-
|
|
278
|
-
streamResultFetched.reject([error, null, { executeId: 'unknown' }])
|
|
266
|
+
await iterator.return();
|
|
267
|
+
} catch (err) {
|
|
268
|
+
if (!curRequestController.signal.aborted) curRequestController.abort();
|
|
269
|
+
const error = { REQUEST_FAIL: err };
|
|
270
|
+
await eventManager.emit("milkio:executeError", { handleError, path: path as string, options: options as any, error });
|
|
271
|
+
await iterator.throw(err);
|
|
272
|
+
streamResultFetched.reject([error, null, { executeId: "unknown" }]);
|
|
279
273
|
}
|
|
280
274
|
}
|
|
281
275
|
|
|
282
|
-
void create()
|
|
276
|
+
void create();
|
|
283
277
|
|
|
284
278
|
const iterator = {
|
|
285
279
|
...({
|
|
286
280
|
next(): Promise<IteratorResult<unknown>> {
|
|
287
|
-
const index = ++iteratorIndex
|
|
288
|
-
if (stacks.has(index - 2)) stacks.delete(index - 2)
|
|
281
|
+
const index = ++iteratorIndex;
|
|
282
|
+
if (stacks.has(index - 2)) stacks.delete(index - 2);
|
|
289
283
|
if (!stacks.has(index) && !curRequestController.signal.aborted) {
|
|
290
|
-
const stack = withResolvers<IteratorResult<any>>()
|
|
291
|
-
stacks.set(index, { ...stack, done: false })
|
|
292
|
-
return stack.promise
|
|
284
|
+
const stack = withResolvers<IteratorResult<any>>();
|
|
285
|
+
stacks.set(index, { ...stack, done: false });
|
|
286
|
+
return stack.promise;
|
|
293
287
|
}
|
|
294
288
|
if (!stacks.has(index) && curRequestController.signal.aborted) {
|
|
295
|
-
const stack = withResolvers<IteratorResult<any>>()
|
|
296
|
-
stack.resolve({ done: true, value: undefined })
|
|
297
|
-
return stack.promise
|
|
289
|
+
const stack = withResolvers<IteratorResult<any>>();
|
|
290
|
+
stack.resolve({ done: true, value: undefined });
|
|
291
|
+
return stack.promise;
|
|
298
292
|
}
|
|
299
|
-
return stacks.get(index)!.promise
|
|
293
|
+
return stacks.get(index)!.promise;
|
|
300
294
|
},
|
|
301
295
|
async return(): Promise<IteratorResult<void>> {
|
|
302
|
-
if (!curRequestController.signal.aborted) curRequestController.abort()
|
|
303
|
-
for (const [_, iterator] of stacks) iterator.resolve({ done: true, value: undefined })
|
|
304
|
-
return { done: true, value: undefined }
|
|
296
|
+
if (!curRequestController.signal.aborted) curRequestController.abort();
|
|
297
|
+
for (const [_, iterator] of stacks) iterator.resolve({ done: true, value: undefined });
|
|
298
|
+
return { done: true, value: undefined };
|
|
305
299
|
},
|
|
306
300
|
async throw(err: any): Promise<IteratorResult<void>> {
|
|
307
301
|
streamResult = {
|
|
308
302
|
success: false,
|
|
309
|
-
executeId: streamResult?.executeId ??
|
|
303
|
+
executeId: streamResult?.executeId ?? "",
|
|
310
304
|
fail: {
|
|
311
|
-
code:
|
|
312
|
-
message:
|
|
305
|
+
code: "NETWORK_ERROR",
|
|
306
|
+
message: "Network Error",
|
|
313
307
|
fromClient: true,
|
|
314
308
|
data: err,
|
|
315
309
|
},
|
|
316
|
-
}
|
|
310
|
+
};
|
|
317
311
|
for (const [_index, stack] of stacks) {
|
|
318
|
-
if (stack.done) continue
|
|
319
|
-
stack.done = true
|
|
320
|
-
stack.resolve({ done: true, value: undefined })
|
|
312
|
+
if (stack.done) continue;
|
|
313
|
+
stack.done = true;
|
|
314
|
+
stack.resolve({ done: true, value: undefined });
|
|
321
315
|
}
|
|
322
|
-
if (!curRequestController.signal.aborted) curRequestController.abort()
|
|
323
|
-
for (const [_, iterator] of stacks) iterator.resolve({ done: true, value: undefined })
|
|
324
|
-
return { done: true, value: undefined }
|
|
316
|
+
if (!curRequestController.signal.aborted) curRequestController.abort();
|
|
317
|
+
for (const [_, iterator] of stacks) iterator.resolve({ done: true, value: undefined });
|
|
318
|
+
return { done: true, value: undefined };
|
|
325
319
|
},
|
|
326
320
|
} satisfies AsyncIterator<unknown>),
|
|
327
321
|
[Symbol.asyncIterator]() {
|
|
328
|
-
return this
|
|
322
|
+
return this;
|
|
329
323
|
},
|
|
330
|
-
}
|
|
324
|
+
};
|
|
331
325
|
|
|
332
326
|
try {
|
|
333
|
-
await streamResultFetched.promise
|
|
334
|
-
return [null, iterator, { executeId: streamResult.executeId }] as any
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return error as any
|
|
327
|
+
await streamResultFetched.promise;
|
|
328
|
+
return [null, iterator, { executeId: streamResult.executeId }] as any;
|
|
329
|
+
} catch (error) {
|
|
330
|
+
return error as any;
|
|
338
331
|
}
|
|
339
332
|
}
|
|
340
333
|
},
|
|
341
334
|
cookbook: {
|
|
342
335
|
subscribe: async (baseUrl: string) => {
|
|
343
336
|
const headers = {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}
|
|
347
|
-
const params = {}
|
|
337
|
+
"Content-Type": "application/json",
|
|
338
|
+
Accept: "text/event-stream",
|
|
339
|
+
};
|
|
340
|
+
const params = {};
|
|
348
341
|
|
|
349
|
-
const body = TSON.stringify(params) ??
|
|
342
|
+
const body = TSON.stringify(params) ?? "";
|
|
350
343
|
const stacks: Map<
|
|
351
344
|
number,
|
|
352
345
|
{
|
|
353
|
-
done: boolean
|
|
354
|
-
promise: Promise<IteratorResult<any
|
|
355
|
-
resolve: (value: IteratorResult<any>) => void
|
|
356
|
-
reject: (reason: any) => void
|
|
346
|
+
done: boolean;
|
|
347
|
+
promise: Promise<IteratorResult<any>>;
|
|
348
|
+
resolve: (value: IteratorResult<any>) => void;
|
|
349
|
+
reject: (reason: any) => void;
|
|
357
350
|
}
|
|
358
|
-
> = new Map()
|
|
359
|
-
let stacksIndex
|
|
360
|
-
let iteratorIndex
|
|
351
|
+
> = new Map();
|
|
352
|
+
let stacksIndex = 0;
|
|
353
|
+
let iteratorIndex = 0;
|
|
361
354
|
|
|
362
355
|
const onmessage = (event: EventSourceMessage) => {
|
|
363
|
-
const index = ++stacksIndex
|
|
356
|
+
const index = ++stacksIndex;
|
|
364
357
|
if (stacks.has(index)) {
|
|
365
|
-
const stack = stacks.get(index)
|
|
366
|
-
stack!.resolve({ done: false, value: TSON.parse(event.data) })
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
stacks.set(index, { ...stack, done: false })
|
|
358
|
+
const stack = stacks.get(index);
|
|
359
|
+
stack!.resolve({ done: false, value: TSON.parse(event.data) });
|
|
360
|
+
} else {
|
|
361
|
+
const stack = withResolvers<IteratorResult<any>>();
|
|
362
|
+
stack.resolve({ done: false, value: TSON.parse(event.data) });
|
|
363
|
+
stacks.set(index, { ...stack, done: false });
|
|
372
364
|
}
|
|
373
|
-
}
|
|
365
|
+
};
|
|
374
366
|
|
|
375
|
-
let curRequestController: AbortController
|
|
367
|
+
let curRequestController: AbortController;
|
|
376
368
|
|
|
377
369
|
async function create() {
|
|
378
|
-
curRequestController = new $abort()
|
|
379
|
-
curRequestController.signal.addEventListener(
|
|
380
|
-
iterator.return()
|
|
381
|
-
})
|
|
370
|
+
curRequestController = new $abort();
|
|
371
|
+
curRequestController.signal.addEventListener("abort", () => {
|
|
372
|
+
iterator.return();
|
|
373
|
+
});
|
|
382
374
|
try {
|
|
383
375
|
const response = await $fetch(`${baseUrl}/$subscribe`, {
|
|
384
|
-
method:
|
|
376
|
+
method: "POST",
|
|
385
377
|
headers,
|
|
386
378
|
body,
|
|
387
379
|
signal: curRequestController.signal,
|
|
388
|
-
})
|
|
380
|
+
});
|
|
389
381
|
|
|
390
|
-
const contentType = response.headers.get(
|
|
391
|
-
if (!contentType?.startsWith(
|
|
392
|
-
throw new Error(`Expected content-type to be ${
|
|
382
|
+
const contentType = response.headers.get("Content-Type");
|
|
383
|
+
if (!contentType?.startsWith("text/event-stream")) {
|
|
384
|
+
throw new Error(`Expected content-type to be ${"text/event-stream"}, Actual: ${contentType}`);
|
|
393
385
|
}
|
|
394
386
|
|
|
395
|
-
await getBytes(response.body!, getLines(getMessages(onmessage)))
|
|
387
|
+
await getBytes(response.body!, getLines(getMessages(onmessage)));
|
|
396
388
|
|
|
397
|
-
await iterator.return()
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
await iterator.throw(err)
|
|
389
|
+
await iterator.return();
|
|
390
|
+
} catch (err) {
|
|
391
|
+
if (!curRequestController.signal.aborted) curRequestController.abort();
|
|
392
|
+
await iterator.throw(err);
|
|
402
393
|
}
|
|
403
394
|
}
|
|
404
395
|
|
|
405
|
-
void create()
|
|
396
|
+
void create();
|
|
406
397
|
|
|
407
398
|
const iterator = {
|
|
408
399
|
...({
|
|
409
400
|
next(): Promise<IteratorResult<unknown>> {
|
|
410
|
-
const index = ++iteratorIndex
|
|
411
|
-
if (stacks.has(index - 2)) stacks.delete(index - 2)
|
|
401
|
+
const index = ++iteratorIndex;
|
|
402
|
+
if (stacks.has(index - 2)) stacks.delete(index - 2);
|
|
412
403
|
if (!stacks.has(index) && !curRequestController.signal.aborted) {
|
|
413
|
-
const stack = withResolvers<IteratorResult<any>>()
|
|
414
|
-
stacks.set(index, { ...stack, done: false })
|
|
415
|
-
return stack.promise
|
|
404
|
+
const stack = withResolvers<IteratorResult<any>>();
|
|
405
|
+
stacks.set(index, { ...stack, done: false });
|
|
406
|
+
return stack.promise;
|
|
416
407
|
}
|
|
417
408
|
if (!stacks.has(index) && curRequestController.signal.aborted) {
|
|
418
|
-
const stack = withResolvers<IteratorResult<any>>()
|
|
419
|
-
stack.resolve({ done: true, value: undefined })
|
|
420
|
-
return stack.promise
|
|
409
|
+
const stack = withResolvers<IteratorResult<any>>();
|
|
410
|
+
stack.resolve({ done: true, value: undefined });
|
|
411
|
+
return stack.promise;
|
|
421
412
|
}
|
|
422
|
-
return stacks.get(index)!.promise
|
|
413
|
+
return stacks.get(index)!.promise;
|
|
423
414
|
},
|
|
424
415
|
async return(): Promise<IteratorResult<void>> {
|
|
425
|
-
if (!curRequestController.signal.aborted) curRequestController.abort()
|
|
426
|
-
for (const [_, iterator] of stacks) iterator.resolve({ done: true, value: undefined })
|
|
427
|
-
return { done: true, value: undefined }
|
|
416
|
+
if (!curRequestController.signal.aborted) curRequestController.abort();
|
|
417
|
+
for (const [_, iterator] of stacks) iterator.resolve({ done: true, value: undefined });
|
|
418
|
+
return { done: true, value: undefined };
|
|
428
419
|
},
|
|
429
420
|
async throw(err: any): Promise<IteratorResult<void>> {
|
|
430
|
-
if (!curRequestController.signal.aborted) curRequestController.abort()
|
|
431
|
-
for (const [_, iterator] of stacks) iterator.resolve({ done: true, value: undefined })
|
|
432
|
-
return { done: true, value: undefined }
|
|
421
|
+
if (!curRequestController.signal.aborted) curRequestController.abort();
|
|
422
|
+
for (const [_, iterator] of stacks) iterator.resolve({ done: true, value: undefined });
|
|
423
|
+
return { done: true, value: undefined };
|
|
433
424
|
},
|
|
434
425
|
} satisfies AsyncIterator<unknown>),
|
|
435
426
|
[Symbol.asyncIterator]() {
|
|
436
|
-
return this
|
|
427
|
+
return this;
|
|
437
428
|
},
|
|
438
|
-
}
|
|
429
|
+
};
|
|
439
430
|
|
|
440
431
|
try {
|
|
441
|
-
return iterator
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
return error as any
|
|
432
|
+
return iterator;
|
|
433
|
+
} catch (error) {
|
|
434
|
+
return error as any;
|
|
445
435
|
}
|
|
446
436
|
},
|
|
447
437
|
},
|
|
448
438
|
async ping(options?: { timeout?: number }): Promise<Ping> {
|
|
439
|
+
// biome-ignore lint/suspicious/noAsyncPromiseExecutor: <explanation>
|
|
449
440
|
return await new Promise<Ping>(async (resolve) => {
|
|
450
|
-
const url = `${await baseUrl}/generate_204
|
|
451
|
-
const timeout = stargateOptions?.timeout ?? options?.timeout ?? 6000
|
|
452
|
-
const startsTime = Date.now()
|
|
441
|
+
const url = `${await baseUrl}/generate_204`;
|
|
442
|
+
const timeout = stargateOptions?.timeout ?? options?.timeout ?? 6000;
|
|
443
|
+
const startsTime = Date.now();
|
|
453
444
|
const timer = setTimeout(() => {
|
|
454
|
-
const endsTime = Date.now()
|
|
455
|
-
resolve([{ connect: false, delay: endsTime - startsTime, error: { REQUEST_TIMEOUT: { timeout, message: `Execute timeout after ${timeout}ms.` } } }, null])
|
|
456
|
-
}, timeout)
|
|
445
|
+
const endsTime = Date.now();
|
|
446
|
+
resolve([{ connect: false, delay: endsTime - startsTime, error: { REQUEST_TIMEOUT: { timeout, message: `Execute timeout after ${timeout}ms.` } } }, null]);
|
|
447
|
+
}, timeout);
|
|
457
448
|
|
|
458
449
|
try {
|
|
459
|
-
const response = await await $fetch(url, { method:
|
|
460
|
-
const endsTime = Date.now()
|
|
461
|
-
clearTimeout(timer)
|
|
450
|
+
const response = await await $fetch(url, { method: "HEAD" });
|
|
451
|
+
const endsTime = Date.now();
|
|
452
|
+
clearTimeout(timer);
|
|
462
453
|
if (response.status !== 204) {
|
|
463
|
-
resolve([{ connect: false, delay: endsTime - startsTime, error: { REQUEST_FAIL: { response, status: response.status, message:
|
|
454
|
+
resolve([{ connect: false, delay: endsTime - startsTime, error: { REQUEST_FAIL: { response, status: response.status, message: "Status code not 204" } } }, null]);
|
|
464
455
|
}
|
|
465
456
|
|
|
466
|
-
resolve([null, { connect: true, delay: endsTime - startsTime, serverTimestamp: Number(response.headers.get(
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
return [{ connect: false, delay: endsTime - startsTime, error }, null]
|
|
457
|
+
resolve([null, { connect: true, delay: endsTime - startsTime, serverTimestamp: Number(response.headers.get("Content-Type")!.substring(17)) }]);
|
|
458
|
+
} catch (error: any) {
|
|
459
|
+
const endsTime = Date.now();
|
|
460
|
+
return [{ connect: false, delay: endsTime - startsTime, error }, null];
|
|
471
461
|
}
|
|
472
|
-
})
|
|
462
|
+
});
|
|
473
463
|
},
|
|
474
|
-
}
|
|
464
|
+
};
|
|
475
465
|
|
|
476
|
-
return stargate
|
|
466
|
+
return stargate;
|
|
477
467
|
}
|
|
478
468
|
|
|
479
469
|
export interface ExecuteStreamOptions {
|
|
480
|
-
headers?: Record<string, string
|
|
481
|
-
timeout?: number
|
|
470
|
+
headers?: Record<string, string>;
|
|
471
|
+
timeout?: number;
|
|
482
472
|
}
|
|
483
473
|
|
|
484
474
|
export interface ApiSchemaExtend {
|
|
485
475
|
apiValidator: {
|
|
486
|
-
generatedAt: number
|
|
487
|
-
validate: Record<any, any
|
|
488
|
-
}
|
|
489
|
-
apiMethodsSchema: Record<any, any
|
|
490
|
-
apiMethodsTypeSchema: Record<any, any
|
|
491
|
-
apiTestsSchema: Record<any, any
|
|
476
|
+
generatedAt: number;
|
|
477
|
+
validate: Record<any, any>;
|
|
478
|
+
};
|
|
479
|
+
apiMethodsSchema: Record<any, any>;
|
|
480
|
+
apiMethodsTypeSchema: Record<any, any>;
|
|
481
|
+
apiTestsSchema: Record<any, any>;
|
|
492
482
|
}
|
|
493
483
|
|
|
494
|
-
export type FailCodeExtend = Record<any, (...args: Array<any>) => any
|
|
484
|
+
export type FailCodeExtend = Record<any, (...args: Array<any>) => any>;
|
|
495
485
|
|
|
496
|
-
export type BootstrapMiddleware = (data: { storage: ClientStorage }) => Promise<void> | void
|
|
497
|
-
export type BeforeExecuteMiddleware = (data: { path: string
|
|
498
|
-
export type AfterExecuteMiddleware = (data: { path: string
|
|
486
|
+
export type BootstrapMiddleware = (data: { storage: ClientStorage }) => Promise<void> | void;
|
|
487
|
+
export type BeforeExecuteMiddleware = (data: { path: string; params: any; headers: Record<string, string>; storage: ClientStorage }) => Promise<void> | void;
|
|
488
|
+
export type AfterExecuteMiddleware = (data: { path: string; result: { value: any }; storage: ClientStorage }) => Promise<void> | void;
|
|
499
489
|
|
|
500
490
|
export interface MiddlewareOptions {
|
|
501
|
-
bootstrap?: BootstrapMiddleware
|
|
502
|
-
beforeExecute?: BeforeExecuteMiddleware
|
|
503
|
-
afterExecute?: AfterExecuteMiddleware
|
|
491
|
+
bootstrap?: BootstrapMiddleware;
|
|
492
|
+
beforeExecute?: BeforeExecuteMiddleware;
|
|
493
|
+
afterExecute?: AfterExecuteMiddleware;
|
|
504
494
|
}
|
|
505
495
|
|
|
506
496
|
export interface ClientStorage {
|
|
507
|
-
getItem: (key: string) => Promise<string | null
|
|
508
|
-
setItem: (key: string, value: string) => Promise<void
|
|
509
|
-
removeItem: (key: string) => Promise<void
|
|
497
|
+
getItem: (key: string) => Promise<string | null>;
|
|
498
|
+
setItem: (key: string, value: string) => Promise<void>;
|
|
499
|
+
removeItem: (key: string) => Promise<void>;
|
|
510
500
|
}
|
|
511
501
|
|
|
512
502
|
export interface ExecuteResultSuccess<Result> {
|
|
513
|
-
executeId: string
|
|
514
|
-
success: true
|
|
515
|
-
data: Result
|
|
503
|
+
executeId: string;
|
|
504
|
+
success: true;
|
|
505
|
+
data: Result;
|
|
516
506
|
}
|
|
517
507
|
|
|
518
|
-
export type GeneratorGeneric<T> = T extends AsyncGenerator<infer I> ? I : never
|
|
508
|
+
export type GeneratorGeneric<T> = T extends AsyncGenerator<infer I> ? I : never;
|
|
519
509
|
|
|
520
|
-
export type FlattenKeys<T
|
|
510
|
+
export type FlattenKeys<T, Prefix extends string = ""> = {
|
|
521
511
|
[K in keyof T]: T[K] extends object ? FlattenKeys<T[K], `${Prefix}${Exclude<K, symbol>}.`> : `$input.${Prefix}${Exclude<K, symbol>}`;
|
|
522
|
-
}[keyof T]
|
|
512
|
+
}[keyof T];
|
|
523
513
|
|
|
524
514
|
// *** This part of the code is based on `@microsoft/fetch-event-source` rewrite, thanks to the work of Microsoft *** //
|
|
525
515
|
// *** https://github.com/Azure/fetch-event-source/blob/main/src/parse.ts *** //
|
|
@@ -529,7 +519,7 @@ export type FlattenKeys<T extends any, Prefix extends string = ''> = {
|
|
|
529
519
|
* https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format
|
|
530
520
|
*/
|
|
531
521
|
export interface EventSourceMessage {
|
|
532
|
-
data: string
|
|
522
|
+
data: string;
|
|
533
523
|
}
|
|
534
524
|
|
|
535
525
|
/**
|
|
@@ -539,20 +529,14 @@ export interface EventSourceMessage {
|
|
|
539
529
|
* @returns {Promise<void>} A promise that will be resolved when the stream closes.
|
|
540
530
|
*/
|
|
541
531
|
export async function getBytes(stream: ReadableStream<Uint8Array>, onChunk: (arr: Uint8Array) => void) {
|
|
542
|
-
const reader = stream.getReader()
|
|
543
|
-
let result: ReadableStreamReadResult<Uint8Array
|
|
532
|
+
const reader = stream.getReader();
|
|
533
|
+
let result: ReadableStreamReadResult<Uint8Array>;
|
|
534
|
+
// biome-ignore lint/suspicious/noAssignInExpressions: <explanation>
|
|
544
535
|
while (!(result = await reader.read()).done) {
|
|
545
|
-
onChunk(result.value)
|
|
536
|
+
onChunk(result.value);
|
|
546
537
|
}
|
|
547
538
|
}
|
|
548
539
|
|
|
549
|
-
const enum ControlChars {
|
|
550
|
-
NewLine = 10,
|
|
551
|
-
CarriageReturn = 13,
|
|
552
|
-
Space = 32,
|
|
553
|
-
Colon = 58,
|
|
554
|
-
}
|
|
555
|
-
|
|
556
540
|
/**
|
|
557
541
|
* Parses arbitary byte chunks into EventSource line buffers.
|
|
558
542
|
* Each line should be of the format "field: value" and ends with \r, \n, or \r\n.
|
|
@@ -560,75 +544,73 @@ const enum ControlChars {
|
|
|
560
544
|
* @returns A function that should be called for each incoming byte chunk.
|
|
561
545
|
*/
|
|
562
546
|
export function getLines(onLine: (line: Uint8Array, fieldLength: number) => void) {
|
|
563
|
-
let buffer: Uint8Array | undefined
|
|
564
|
-
let position: number // current read position
|
|
565
|
-
let fieldLength: number // length of the `field` portion of the line
|
|
566
|
-
let discardTrailingNewline = false
|
|
547
|
+
let buffer: Uint8Array | undefined;
|
|
548
|
+
let position: number; // current read position
|
|
549
|
+
let fieldLength: number; // length of the `field` portion of the line
|
|
550
|
+
let discardTrailingNewline = false;
|
|
567
551
|
|
|
568
552
|
// return a function that can process each incoming byte chunk:
|
|
569
553
|
return function onChunk(arr: Uint8Array) {
|
|
570
554
|
if (buffer === undefined) {
|
|
571
|
-
buffer = arr
|
|
572
|
-
position = 0
|
|
573
|
-
fieldLength = -1
|
|
574
|
-
}
|
|
575
|
-
else {
|
|
555
|
+
buffer = arr;
|
|
556
|
+
position = 0;
|
|
557
|
+
fieldLength = -1;
|
|
558
|
+
} else {
|
|
576
559
|
// we're still parsing the old line. Append the new bytes into buffer:
|
|
577
|
-
buffer = concat(buffer, arr)
|
|
560
|
+
buffer = concat(buffer, arr);
|
|
578
561
|
}
|
|
579
562
|
|
|
580
|
-
const bufLength = buffer.length
|
|
581
|
-
let lineStart = 0 // index where the current line starts
|
|
563
|
+
const bufLength = buffer.length;
|
|
564
|
+
let lineStart = 0; // index where the current line starts
|
|
582
565
|
while (position < bufLength) {
|
|
583
566
|
if (discardTrailingNewline) {
|
|
584
|
-
if (buffer[position] ===
|
|
585
|
-
lineStart = ++position // skip to next char
|
|
567
|
+
if (buffer[position] === 10) {
|
|
568
|
+
lineStart = ++position; // skip to next char
|
|
586
569
|
}
|
|
587
570
|
|
|
588
|
-
discardTrailingNewline = false
|
|
571
|
+
discardTrailingNewline = false;
|
|
589
572
|
}
|
|
590
573
|
|
|
591
574
|
// start looking forward till the end of line:
|
|
592
|
-
let lineEnd = -1 // index of the \r or \n char
|
|
575
|
+
let lineEnd = -1; // index of the \r or \n char
|
|
593
576
|
for (; position < bufLength && lineEnd === -1; ++position) {
|
|
594
577
|
switch (buffer[position]) {
|
|
595
|
-
case
|
|
578
|
+
case 58:
|
|
596
579
|
if (fieldLength === -1) {
|
|
597
580
|
// first colon in line
|
|
598
|
-
fieldLength = position - lineStart
|
|
581
|
+
fieldLength = position - lineStart;
|
|
599
582
|
}
|
|
600
|
-
break
|
|
601
|
-
//
|
|
602
|
-
case
|
|
603
|
-
discardTrailingNewline = true
|
|
604
|
-
case
|
|
605
|
-
lineEnd = position
|
|
606
|
-
break
|
|
583
|
+
break;
|
|
584
|
+
// biome-ignore lint/suspicious/noFallthroughSwitchClause: <explanation>
|
|
585
|
+
case 13:
|
|
586
|
+
discardTrailingNewline = true;
|
|
587
|
+
case 10:
|
|
588
|
+
lineEnd = position;
|
|
589
|
+
break;
|
|
607
590
|
}
|
|
608
591
|
}
|
|
609
592
|
|
|
610
593
|
if (lineEnd === -1) {
|
|
611
594
|
// We reached the end of the buffer but the line hasn't ended.
|
|
612
595
|
// Wait for the next arr and then continue parsing:
|
|
613
|
-
break
|
|
596
|
+
break;
|
|
614
597
|
}
|
|
615
598
|
|
|
616
599
|
// we've reached the line end, send it out:
|
|
617
|
-
onLine(buffer.subarray(lineStart, lineEnd), fieldLength)
|
|
618
|
-
lineStart = position // we're now on the next line
|
|
619
|
-
fieldLength = -1
|
|
600
|
+
onLine(buffer.subarray(lineStart, lineEnd), fieldLength);
|
|
601
|
+
lineStart = position; // we're now on the next line
|
|
602
|
+
fieldLength = -1;
|
|
620
603
|
}
|
|
621
604
|
|
|
622
605
|
if (lineStart === bufLength) {
|
|
623
|
-
buffer = undefined // we've finished reading it
|
|
624
|
-
}
|
|
625
|
-
else if (lineStart !== 0) {
|
|
606
|
+
buffer = undefined; // we've finished reading it
|
|
607
|
+
} else if (lineStart !== 0) {
|
|
626
608
|
// Create a new view into buffer beginning at lineStart so we don't
|
|
627
609
|
// need to copy over the previous lines when we get the new arr:
|
|
628
|
-
buffer = buffer.subarray(lineStart)
|
|
629
|
-
position -= lineStart
|
|
610
|
+
buffer = buffer.subarray(lineStart);
|
|
611
|
+
position -= lineStart;
|
|
630
612
|
}
|
|
631
|
-
}
|
|
613
|
+
};
|
|
632
614
|
}
|
|
633
615
|
|
|
634
616
|
/**
|
|
@@ -639,54 +621,53 @@ export function getLines(onLine: (line: Uint8Array, fieldLength: number) => void
|
|
|
639
621
|
* @returns A function that should be called for each incoming line buffer.
|
|
640
622
|
*/
|
|
641
623
|
export function getMessages(onMessage?: (msg: EventSourceMessage) => void) {
|
|
642
|
-
let message = newMessage()
|
|
643
|
-
const decoder = new TextDecoder()
|
|
624
|
+
let message = newMessage();
|
|
625
|
+
const decoder = new TextDecoder();
|
|
644
626
|
|
|
645
627
|
// return a function that can process each incoming line buffer:
|
|
646
628
|
return function onLine(line: Uint8Array, fieldLength: number) {
|
|
647
629
|
if (line.length === 0) {
|
|
648
630
|
// empty line denotes end of message. Trigger the callback and start a new message:
|
|
649
|
-
onMessage?.(message)
|
|
650
|
-
message = newMessage()
|
|
651
|
-
}
|
|
652
|
-
else if (fieldLength > 0) {
|
|
631
|
+
onMessage?.(message);
|
|
632
|
+
message = newMessage();
|
|
633
|
+
} else if (fieldLength > 0) {
|
|
653
634
|
// exclude comments and lines with no values
|
|
654
635
|
// line is of format "<field>:<value>" or "<field>: <value>"
|
|
655
636
|
// https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation
|
|
656
|
-
const field = decoder.decode(line.subarray(0, fieldLength))
|
|
657
|
-
const valueOffset = fieldLength + (line[fieldLength + 1] ===
|
|
658
|
-
const value = decoder.decode(line.subarray(valueOffset))
|
|
637
|
+
const field = decoder.decode(line.subarray(0, fieldLength));
|
|
638
|
+
const valueOffset = fieldLength + (line[fieldLength + 1] === 32 ? 2 : 1);
|
|
639
|
+
const value = decoder.decode(line.subarray(valueOffset));
|
|
659
640
|
|
|
660
641
|
switch (field) {
|
|
661
|
-
case
|
|
642
|
+
case "data":
|
|
662
643
|
// if this message already has data, append the new value to the old.
|
|
663
644
|
// otherwise, just set to the new value:
|
|
664
|
-
message.data = message.data ? `${message.data}\n${value}` : value // otherwise,
|
|
665
|
-
break
|
|
645
|
+
message.data = message.data ? `${message.data}\n${value}` : value; // otherwise,
|
|
646
|
+
break;
|
|
666
647
|
}
|
|
667
648
|
}
|
|
668
|
-
}
|
|
649
|
+
};
|
|
669
650
|
}
|
|
670
651
|
|
|
671
652
|
function concat(a: Uint8Array, b: Uint8Array) {
|
|
672
|
-
const res = new Uint8Array(a.length + b.length)
|
|
673
|
-
res.set(a)
|
|
674
|
-
res.set(b, a.length)
|
|
675
|
-
return res
|
|
653
|
+
const res = new Uint8Array(a.length + b.length);
|
|
654
|
+
res.set(a);
|
|
655
|
+
res.set(b, a.length);
|
|
656
|
+
return res;
|
|
676
657
|
}
|
|
677
658
|
|
|
678
659
|
function newMessage(): EventSourceMessage {
|
|
679
660
|
return {
|
|
680
|
-
data:
|
|
681
|
-
}
|
|
661
|
+
data: "",
|
|
662
|
+
};
|
|
682
663
|
}
|
|
683
664
|
|
|
684
665
|
export function withResolvers<T = any>(): PromiseWithResolvers<T> {
|
|
685
|
-
let resolve: PromiseWithResolvers<T>[
|
|
686
|
-
let reject: PromiseWithResolvers<T>[
|
|
666
|
+
let resolve: PromiseWithResolvers<T>["resolve"];
|
|
667
|
+
let reject: PromiseWithResolvers<T>["reject"];
|
|
687
668
|
const promise = new Promise<T>((res, rej) => {
|
|
688
|
-
resolve = res
|
|
689
|
-
reject = rej
|
|
690
|
-
})
|
|
691
|
-
return { promise, resolve: resolve!, reject: reject! }
|
|
669
|
+
resolve = res;
|
|
670
|
+
reject = rej;
|
|
671
|
+
});
|
|
672
|
+
return { promise, resolve: resolve!, reject: reject! };
|
|
692
673
|
}
|