ts-procedures 4.0.1 → 5.1.0
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/build/implementations/http/express-rpc/index.js.map +1 -1
- package/build/implementations/http/hono-rpc/index.js.map +1 -1
- package/build/implementations/http/hono-stream/index.d.ts +17 -18
- package/build/implementations/http/hono-stream/index.js +38 -37
- package/build/implementations/http/hono-stream/index.js.map +1 -1
- package/build/implementations/http/hono-stream/index.test.js +306 -61
- package/build/implementations/http/hono-stream/index.test.js.map +1 -1
- package/build/implementations/http/hono-stream/types.d.ts +3 -3
- package/build/implementations/types.d.ts +5 -5
- package/package.json +1 -1
- package/src/implementations/http/express-rpc/index.ts +1 -1
- package/src/implementations/http/hono-rpc/index.ts +1 -1
- package/src/implementations/http/hono-stream/README.md +151 -67
- package/src/implementations/http/hono-stream/index.test.ts +374 -66
- package/src/implementations/http/hono-stream/index.ts +62 -58
- package/src/implementations/http/hono-stream/types.ts +3 -3
- package/src/implementations/types.ts +5 -5
|
@@ -9,28 +9,37 @@ import { ProcedureValidationError } from '../../../errors.js'
|
|
|
9
9
|
|
|
10
10
|
export type { StreamHttpRouteDoc, StreamMode }
|
|
11
11
|
|
|
12
|
-
export type
|
|
13
|
-
data: string | unknown
|
|
12
|
+
export type SSEOptions = {
|
|
14
13
|
event?: string
|
|
15
14
|
id?: string
|
|
16
15
|
retry?: number
|
|
17
16
|
}
|
|
18
17
|
|
|
18
|
+
const sseMetadata = new WeakMap<object, SSEOptions>()
|
|
19
|
+
|
|
20
|
+
export function sse<T extends object>(data: T, options?: SSEOptions): T {
|
|
21
|
+
sseMetadata.set(data, options ?? {})
|
|
22
|
+
return data
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getSSEMeta(value: unknown): SSEOptions | undefined {
|
|
26
|
+
if (typeof value === 'object' && value !== null) {
|
|
27
|
+
return sseMetadata.get(value)
|
|
28
|
+
}
|
|
29
|
+
return undefined
|
|
30
|
+
}
|
|
31
|
+
|
|
19
32
|
/**
|
|
20
33
|
* Result from onMidStreamError callback.
|
|
21
|
-
* @property data - The data to write
|
|
22
|
-
* @property event - Optional SSE event name (defaults to procedure name if data provided, 'error' otherwise)
|
|
23
|
-
* @property id - Optional SSE event id (auto-incremented if not provided)
|
|
34
|
+
* @property data - The data to write as the SSE `data:` field content (should match yieldType schema)
|
|
24
35
|
* @property closeStream - Whether to close the stream after writing (defaults to true)
|
|
25
36
|
*/
|
|
26
|
-
export type MidStreamErrorResult = {
|
|
27
|
-
data:
|
|
28
|
-
event?: string
|
|
29
|
-
id?: string
|
|
37
|
+
export type MidStreamErrorResult<TErrorData = unknown> = {
|
|
38
|
+
data: TErrorData
|
|
30
39
|
closeStream?: boolean
|
|
31
40
|
}
|
|
32
41
|
|
|
33
|
-
export type HonoStreamAppBuilderConfig = {
|
|
42
|
+
export type HonoStreamAppBuilderConfig<TErrorData = unknown> = {
|
|
34
43
|
/**
|
|
35
44
|
* An existing Hono application instance to use.
|
|
36
45
|
* If not provided, a new instance will be created.
|
|
@@ -42,8 +51,8 @@ export type HonoStreamAppBuilderConfig = {
|
|
|
42
51
|
defaultStreamMode?: StreamMode
|
|
43
52
|
onRequestStart?: (c: Context) => void
|
|
44
53
|
onRequestEnd?: (c: Context) => void
|
|
45
|
-
onStreamStart?: (procedure: TStreamProcedureRegistration, c: Context) => void
|
|
46
|
-
onStreamEnd?: (procedure: TStreamProcedureRegistration, c: Context) => void
|
|
54
|
+
onStreamStart?: (procedure: TStreamProcedureRegistration, c: Context, streamMode: StreamMode) => void
|
|
55
|
+
onStreamEnd?: (procedure: TStreamProcedureRegistration, c: Context, streamMode: StreamMode) => void
|
|
47
56
|
/**
|
|
48
57
|
* Called for errors BEFORE streaming starts (validation, auth, context resolution).
|
|
49
58
|
* Return value IS used as the HTTP response.
|
|
@@ -51,7 +60,7 @@ export type HonoStreamAppBuilderConfig = {
|
|
|
51
60
|
onPreStreamError?: (
|
|
52
61
|
procedure: TStreamProcedureRegistration,
|
|
53
62
|
c: Context,
|
|
54
|
-
error: Error
|
|
63
|
+
error: ProcedureValidationError | Error
|
|
55
64
|
) => Response | Promise<Response>
|
|
56
65
|
/**
|
|
57
66
|
* Called for errors DURING streaming (generator throws).
|
|
@@ -59,13 +68,15 @@ export type HonoStreamAppBuilderConfig = {
|
|
|
59
68
|
* Should return a value matching your yieldType schema (e.g., error variant of a union).
|
|
60
69
|
* Return undefined to use default behavior (writes { error: message }).
|
|
61
70
|
*
|
|
62
|
-
*
|
|
71
|
+
* Use sse() to attach SSE metadata (event, id, retry) to the error data object.
|
|
72
|
+
*
|
|
73
|
+
* @returns { data, closeStream? } - data to yield, whether to close after (default true)
|
|
63
74
|
*/
|
|
64
75
|
onMidStreamError?: (
|
|
65
76
|
procedure: TStreamProcedureRegistration,
|
|
66
77
|
c: Context,
|
|
67
78
|
error: Error
|
|
68
|
-
) => MidStreamErrorResult | undefined
|
|
79
|
+
) => MidStreamErrorResult<TErrorData> | undefined
|
|
69
80
|
}
|
|
70
81
|
|
|
71
82
|
/**
|
|
@@ -81,11 +92,11 @@ export type HonoStreamAppBuilderConfig = {
|
|
|
81
92
|
* const app = streamApp.app; // Hono application
|
|
82
93
|
* const docs = streamApp.docs; // Stream route documentation
|
|
83
94
|
*/
|
|
84
|
-
export class HonoStreamAppBuilder {
|
|
95
|
+
export class HonoStreamAppBuilder<TErrorData = unknown> {
|
|
85
96
|
/**
|
|
86
97
|
* Constructor for HonoStreamAppBuilder.
|
|
87
98
|
*/
|
|
88
|
-
constructor(readonly config?: HonoStreamAppBuilderConfig) {
|
|
99
|
+
constructor(readonly config?: HonoStreamAppBuilderConfig<TErrorData>) {
|
|
89
100
|
if (config?.app) {
|
|
90
101
|
this._app = config.app
|
|
91
102
|
}
|
|
@@ -96,7 +107,6 @@ export class HonoStreamAppBuilder {
|
|
|
96
107
|
await next()
|
|
97
108
|
})
|
|
98
109
|
}
|
|
99
|
-
|
|
100
110
|
}
|
|
101
111
|
|
|
102
112
|
/**
|
|
@@ -203,7 +213,7 @@ export class HonoStreamAppBuilder {
|
|
|
203
213
|
}
|
|
204
214
|
|
|
205
215
|
if (this.config?.onStreamStart) {
|
|
206
|
-
this.config.onStreamStart(procedure, c)
|
|
216
|
+
this.config.onStreamStart(procedure, c, streamMode)
|
|
207
217
|
}
|
|
208
218
|
|
|
209
219
|
if (streamMode === 'sse') {
|
|
@@ -242,16 +252,25 @@ export class HonoStreamAppBuilder {
|
|
|
242
252
|
try {
|
|
243
253
|
for await (const value of generator) {
|
|
244
254
|
const currentId = eventId++
|
|
255
|
+
const meta = getSSEMeta(value)
|
|
256
|
+
|
|
257
|
+
const data =
|
|
258
|
+
typeof value === 'string'
|
|
259
|
+
? value
|
|
260
|
+
: value != null
|
|
261
|
+
? JSON.stringify(value)
|
|
262
|
+
: ''
|
|
263
|
+
|
|
245
264
|
await stream.writeSSE({
|
|
246
|
-
data
|
|
247
|
-
event:
|
|
248
|
-
id:
|
|
249
|
-
...(
|
|
265
|
+
data,
|
|
266
|
+
event: meta?.event ?? procedure.name,
|
|
267
|
+
id: meta?.id ?? String(currentId),
|
|
268
|
+
...(meta?.retry !== undefined && { retry: meta.retry }),
|
|
250
269
|
})
|
|
251
270
|
}
|
|
252
271
|
} catch (error) {
|
|
253
272
|
// Get error yield value from callback (onMidStreamError)
|
|
254
|
-
let errorResult: MidStreamErrorResult | undefined
|
|
273
|
+
let errorResult: MidStreamErrorResult<TErrorData> | undefined
|
|
255
274
|
|
|
256
275
|
if (this.config?.onMidStreamError) {
|
|
257
276
|
errorResult = this.config.onMidStreamError(procedure, c, error as Error)
|
|
@@ -259,17 +278,20 @@ export class HonoStreamAppBuilder {
|
|
|
259
278
|
|
|
260
279
|
// Write error value to stream
|
|
261
280
|
const errorData = errorResult?.data ?? { error: (error as Error).message }
|
|
281
|
+
const sseMeta = getSSEMeta(errorData)
|
|
282
|
+
|
|
262
283
|
await stream.writeSSE({
|
|
263
284
|
data: typeof errorData === 'string' ? errorData : JSON.stringify(errorData),
|
|
264
|
-
event:
|
|
265
|
-
id:
|
|
285
|
+
event: sseMeta?.event ?? (errorResult?.data !== undefined ? procedure.name : 'error'),
|
|
286
|
+
id: sseMeta?.id ?? String(eventId++),
|
|
287
|
+
...(sseMeta?.retry !== undefined && { retry: sseMeta.retry }),
|
|
266
288
|
})
|
|
267
289
|
|
|
268
290
|
// closeStream defaults to true if not specified
|
|
269
291
|
// (stream closes naturally after this handler completes)
|
|
270
292
|
} finally {
|
|
271
293
|
if (this.config?.onStreamEnd) {
|
|
272
|
-
this.config.onStreamEnd(procedure, c)
|
|
294
|
+
this.config.onStreamEnd(procedure, c, 'sse')
|
|
273
295
|
}
|
|
274
296
|
if (this.config?.onRequestEnd) {
|
|
275
297
|
this.config.onRequestEnd(c)
|
|
@@ -301,7 +323,7 @@ export class HonoStreamAppBuilder {
|
|
|
301
323
|
}
|
|
302
324
|
} catch (error) {
|
|
303
325
|
// Get error yield value from callback (onMidStreamError)
|
|
304
|
-
let errorResult: MidStreamErrorResult | undefined
|
|
326
|
+
let errorResult: MidStreamErrorResult<TErrorData> | undefined
|
|
305
327
|
|
|
306
328
|
if (this.config?.onMidStreamError) {
|
|
307
329
|
errorResult = this.config.onMidStreamError(procedure, c, error as Error)
|
|
@@ -312,7 +334,7 @@ export class HonoStreamAppBuilder {
|
|
|
312
334
|
await stream.writeln(JSON.stringify(errorData))
|
|
313
335
|
} finally {
|
|
314
336
|
if (this.config?.onStreamEnd) {
|
|
315
|
-
this.config.onStreamEnd(procedure, c)
|
|
337
|
+
this.config.onStreamEnd(procedure, c, 'text')
|
|
316
338
|
}
|
|
317
339
|
if (this.config?.onRequestEnd) {
|
|
318
340
|
this.config.onRequestEnd(c)
|
|
@@ -364,40 +386,22 @@ export class HonoStreamAppBuilder {
|
|
|
364
386
|
prefix: this.config?.pathPrefix,
|
|
365
387
|
})
|
|
366
388
|
const methods = ['get', 'post'] as const
|
|
367
|
-
const jsonSchema: { params?:
|
|
389
|
+
const jsonSchema: { params?: Record<string, unknown>; yieldType?: Record<string, unknown>; returnType?: Record<string, unknown> } = {}
|
|
368
390
|
|
|
369
391
|
if (config.schema?.params) {
|
|
370
392
|
jsonSchema.params = config.schema.params
|
|
371
393
|
}
|
|
372
394
|
if (streamMode === 'sse') {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
jsonSchema.yieldType = {
|
|
384
|
-
...userSchema,
|
|
385
|
-
required: ['data', 'event', 'id'],
|
|
386
|
-
properties: {
|
|
387
|
-
...sseBaseProperties,
|
|
388
|
-
...(userSchema.properties ?? {}),
|
|
389
|
-
},
|
|
390
|
-
}
|
|
391
|
-
} else {
|
|
392
|
-
// No yieldType defined — generate a complete SSE envelope schema
|
|
393
|
-
jsonSchema.yieldType = {
|
|
394
|
-
type: 'object',
|
|
395
|
-
required: ['data', 'event', 'id'],
|
|
396
|
-
properties: {
|
|
397
|
-
data: {},
|
|
398
|
-
...sseBaseProperties,
|
|
399
|
-
},
|
|
400
|
-
}
|
|
395
|
+
jsonSchema.yieldType = {
|
|
396
|
+
type: 'object',
|
|
397
|
+
description: 'SSE message envelope. The data field contains the procedure yield value.',
|
|
398
|
+
required: ['data', 'event', 'id'],
|
|
399
|
+
properties: {
|
|
400
|
+
data: config.schema?.yieldType ?? {},
|
|
401
|
+
event: { type: 'string' },
|
|
402
|
+
id: { type: 'string' },
|
|
403
|
+
retry: { type: 'number' },
|
|
404
|
+
},
|
|
401
405
|
}
|
|
402
406
|
} else if (config.schema?.yieldType) {
|
|
403
407
|
// Text mode: pass through as-is
|
|
@@ -10,9 +10,9 @@ export interface StreamHttpRouteDoc extends RPCConfig {
|
|
|
10
10
|
methods: ('get' | 'post')[]
|
|
11
11
|
streamMode: StreamMode
|
|
12
12
|
jsonSchema: {
|
|
13
|
-
params?:
|
|
14
|
-
yieldType?:
|
|
15
|
-
returnType?:
|
|
13
|
+
params?: Record<string, unknown>
|
|
14
|
+
yieldType?: Record<string, unknown>
|
|
15
|
+
returnType?: Record<string, unknown>
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -16,8 +16,8 @@ export interface RPCHttpRouteDoc extends RPCConfig {
|
|
|
16
16
|
path: string
|
|
17
17
|
method: 'post'
|
|
18
18
|
jsonSchema: {
|
|
19
|
-
body?:
|
|
20
|
-
response?:
|
|
19
|
+
body?: Record<string, unknown>
|
|
20
|
+
response?: Record<string, unknown>
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
@@ -29,9 +29,9 @@ export interface StreamHttpRouteDoc extends RPCConfig {
|
|
|
29
29
|
methods: ('get' | 'post')[]
|
|
30
30
|
streamMode: StreamMode
|
|
31
31
|
jsonSchema: {
|
|
32
|
-
params?:
|
|
33
|
-
yieldType?:
|
|
34
|
-
returnType?:
|
|
32
|
+
params?: Record<string, unknown> // Query params (GET) or body (POST)
|
|
33
|
+
yieldType?: Record<string, unknown> // Schema for each streamed value
|
|
34
|
+
returnType?: Record<string, unknown> // Final return (optional)
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|