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