@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.
Files changed (2) hide show
  1. package/index.ts +359 -378
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -1,112 +1,112 @@
1
- import { TSON } from '@southern-aurora/tson'
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?: 'action' | 'stream'
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
- connect: false
26
- delay: number
27
- error: any
28
- },
29
- null,
30
- ]
24
+ {
25
+ connect: false;
26
+ delay: number;
27
+ error: any;
28
+ },
29
+ null,
30
+ ]
31
31
  | [
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
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
- '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
- }
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 === 'function') baseUrl = await baseUrl()
104
- if (baseUrl.endsWith('/')) baseUrl = baseUrl.slice(0, -1)
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['routeSchema']>(
117
+ async execute<Path extends keyof Generated["routeSchema"]>(
118
118
  path: Path,
119
119
  options?: Mixin<
120
120
  ExecuteOptions,
121
121
  {
122
- params?: Generated['routeSchema'][Path]['types']['params']
122
+ params?: Generated["routeSchema"][Path]["types"]["params"];
123
123
  }
124
124
  >,
125
125
  ): Promise<
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
- if (!options) options = {}
133
- if (options.headers === undefined) options.headers = {}
134
-
135
- let url: string
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 === 'function') baseUrl = await baseUrl()
139
- if (baseUrl.endsWith('/')) baseUrl = baseUrl.slice(0, -1)
140
- url = baseUrl + (path as string)
141
- }
142
- else {
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 !== 'stream') {
146
+ if (options.type !== "stream") {
147
147
  // action
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> }
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('milkio:executeBefore', { path: path as string, options: options as any })
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('milkio:fetchBefore', { path: path as string, options: options as any, body })
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: 'POST', body, headers: options.headers })).text()
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
- catch (error) {
170
- reject(error)
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('milkio:executeError', { handleError, path: path as string, options: options as any, error })
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('milkio:executeError', { handleError, path: path as string, options: options as any, error: errorPined })
182
- return [errorPined, null, { executeId: 'unknown' }]
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('milkio:executeError', { handleError, path: path as string, options: options as any, error })
188
- return [error, null, { executeId: 'unknown' }]
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 = 'text/event-stream'
196
- if (options.headers['Content-Type'] === undefined) options.headers['Content-Type'] = 'application/json'
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: number = 0
208
- let iteratorIndex: number = 0
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: 'unknown' }])
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
- catch (error) {
225
- streamResultFetched.reject([{ REQUEST_FAIL: error }, null, { executeId: 'unknown' }])
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
- else {
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
- else {
237
- const stack = withResolvers<IteratorResult<any>>()
238
- stack.resolve({ done: false, value: TSON.parse(event.data) })
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('abort', () => {
249
- iterator.return()
250
- })
242
+ curRequestController = new $abort();
243
+ curRequestController.signal.addEventListener("abort", () => {
244
+ iterator.return();
245
+ });
251
246
  try {
252
- await eventManager.emit('milkio:executeBefore', { path: path as string, options: options as any })
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('milkio:fetchBefore', { path: path as string, options: options as any, body })
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: 'POST',
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('Content-Type')
265
- if (!contentType?.startsWith('text/event-stream')) {
266
- throw new Error(`Expected content-type to be ${'text/event-stream'}, Actual: ${contentType}`)
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
- catch (err) {
274
- if (!curRequestController.signal.aborted) curRequestController.abort()
275
- const error = { REQUEST_FAIL: err }
276
- await eventManager.emit('milkio:executeError', { handleError, path: path as string, options: options as any, error })
277
- await iterator.throw(err)
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: 'NETWORK_ERROR',
312
- message: 'Network Error',
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
- catch (error) {
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
- 'Content-Type': 'application/json',
345
- 'Accept': 'text/event-stream',
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: number = 0
360
- let iteratorIndex: number = 0
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
- else {
369
- const stack = withResolvers<IteratorResult<any>>()
370
- stack.resolve({ done: false, value: TSON.parse(event.data) })
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('abort', () => {
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: 'POST',
376
+ method: "POST",
385
377
  headers,
386
378
  body,
387
379
  signal: curRequestController.signal,
388
- })
380
+ });
389
381
 
390
- const contentType = response.headers.get('Content-Type')
391
- if (!contentType?.startsWith('text/event-stream')) {
392
- throw new Error(`Expected content-type to be ${'text/event-stream'}, Actual: ${contentType}`)
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
- catch (err) {
400
- if (!curRequestController.signal.aborted) curRequestController.abort()
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
- catch (error) {
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: 'HEAD' })
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: `Status code not 204` } } }, null])
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('Content-Type')!.substring(17)) }])
467
- }
468
- catch (error: any) {
469
- const endsTime = Date.now()
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, params: any, headers: Record<string, string>, storage: ClientStorage }) => Promise<void> | void
498
- export type AfterExecuteMiddleware = (data: { path: string, result: { value: any }, storage: ClientStorage }) => Promise<void> | void
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 extends any, Prefix extends string = ''> = {
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] === ControlChars.NewLine) {
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 ControlChars.Colon:
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
- // @ts-ignore:7029 \r case below should fallthrough to \n:
602
- case ControlChars.CarriageReturn:
603
- discardTrailingNewline = true
604
- case ControlChars.NewLine:
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] === ControlChars.Space ? 2 : 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 'data':
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>['resolve']
686
- let reject: PromiseWithResolvers<T>['reject']
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
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@milkio/stargate",
3
3
  "type": "module",
4
- "version": "1.0.0-alpha.93",
4
+ "version": "1.0.0-alpha.95",
5
5
  "module": "index.ts",
6
6
  "dependencies": {
7
7
  "@southern-aurora/tson": "^2.0.2"