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