ts-procedures 3.4.0 → 4.0.1
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/hono-stream/index.d.ts +13 -3
- package/build/implementations/http/hono-stream/index.js +44 -11
- package/build/implementations/http/hono-stream/index.js.map +1 -1
- package/build/implementations/http/hono-stream/index.test.js +167 -33
- package/build/implementations/http/hono-stream/index.test.js.map +1 -1
- package/package.json +1 -1
- package/src/implementations/http/hono-stream/README.md +62 -14
- package/src/implementations/http/hono-stream/index.test.ts +213 -33
- package/src/implementations/http/hono-stream/index.ts +57 -14
|
@@ -3,13 +3,23 @@ import { TStreamProcedureRegistration } from '../../../index.js';
|
|
|
3
3
|
import { ExtractConfig, ExtractContext, ProceduresFactory, RPCConfig } from '../../types.js';
|
|
4
4
|
import { StreamHttpRouteDoc, StreamMode } from './types.js';
|
|
5
5
|
export type { StreamHttpRouteDoc, StreamMode };
|
|
6
|
+
export type SSEYield = {
|
|
7
|
+
data: string | unknown;
|
|
8
|
+
event?: string;
|
|
9
|
+
id?: string;
|
|
10
|
+
retry?: number;
|
|
11
|
+
};
|
|
6
12
|
/**
|
|
7
13
|
* Result from onMidStreamError callback.
|
|
8
|
-
* @property
|
|
14
|
+
* @property data - The data to write to the stream (should match yieldType schema)
|
|
15
|
+
* @property event - Optional SSE event name (defaults to procedure name if data provided, 'error' otherwise)
|
|
16
|
+
* @property id - Optional SSE event id (auto-incremented if not provided)
|
|
9
17
|
* @property closeStream - Whether to close the stream after writing (defaults to true)
|
|
10
18
|
*/
|
|
11
19
|
export type MidStreamErrorResult = {
|
|
12
|
-
|
|
20
|
+
data: unknown;
|
|
21
|
+
event?: string;
|
|
22
|
+
id?: string;
|
|
13
23
|
closeStream?: boolean;
|
|
14
24
|
};
|
|
15
25
|
export type HonoStreamAppBuilderConfig = {
|
|
@@ -37,7 +47,7 @@ export type HonoStreamAppBuilderConfig = {
|
|
|
37
47
|
* Should return a value matching your yieldType schema (e.g., error variant of a union).
|
|
38
48
|
* Return undefined to use default behavior (writes { error: message }).
|
|
39
49
|
*
|
|
40
|
-
* @returns {
|
|
50
|
+
* @returns { data, event?, id?, closeStream? } - data to yield, optional SSE fields, whether to close after (default true)
|
|
41
51
|
*/
|
|
42
52
|
onMidStreamError?: (procedure: TStreamProcedureRegistration, c: Context, error: Error) => MidStreamErrorResult | undefined;
|
|
43
53
|
};
|
|
@@ -127,10 +127,12 @@ export class HonoStreamAppBuilder {
|
|
|
127
127
|
let eventId = 0;
|
|
128
128
|
try {
|
|
129
129
|
for await (const value of generator) {
|
|
130
|
+
const currentId = eventId++;
|
|
130
131
|
await stream.writeSSE({
|
|
131
|
-
data: JSON.stringify(value),
|
|
132
|
-
event: procedure.name,
|
|
133
|
-
id: String(
|
|
132
|
+
data: typeof value.data === 'string' ? value.data : JSON.stringify(value.data),
|
|
133
|
+
event: value.event ?? procedure.name,
|
|
134
|
+
id: value.id ?? String(currentId),
|
|
135
|
+
...(value.retry !== undefined && { retry: value.retry }),
|
|
134
136
|
});
|
|
135
137
|
}
|
|
136
138
|
}
|
|
@@ -141,12 +143,11 @@ export class HonoStreamAppBuilder {
|
|
|
141
143
|
errorResult = this.config.onMidStreamError(procedure, c, error);
|
|
142
144
|
}
|
|
143
145
|
// Write error value to stream
|
|
144
|
-
const
|
|
146
|
+
const errorData = errorResult?.data ?? { error: error.message };
|
|
145
147
|
await stream.writeSSE({
|
|
146
|
-
data: JSON.stringify(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
id: String(eventId++),
|
|
148
|
+
data: typeof errorData === 'string' ? errorData : JSON.stringify(errorData),
|
|
149
|
+
event: errorResult?.event ?? (errorResult?.data !== undefined ? procedure.name : 'error'),
|
|
150
|
+
id: errorResult?.id ?? String(eventId++),
|
|
150
151
|
});
|
|
151
152
|
// closeStream defaults to true if not specified
|
|
152
153
|
// (stream closes naturally after this handler completes)
|
|
@@ -183,8 +184,8 @@ export class HonoStreamAppBuilder {
|
|
|
183
184
|
errorResult = this.config.onMidStreamError(procedure, c, error);
|
|
184
185
|
}
|
|
185
186
|
// Write error value to stream
|
|
186
|
-
const
|
|
187
|
-
await stream.writeln(JSON.stringify(
|
|
187
|
+
const errorData = errorResult?.data ?? { error: error.message };
|
|
188
|
+
await stream.writeln(JSON.stringify(errorData));
|
|
188
189
|
}
|
|
189
190
|
finally {
|
|
190
191
|
if (this.config?.onStreamEnd) {
|
|
@@ -231,7 +232,39 @@ export class HonoStreamAppBuilder {
|
|
|
231
232
|
if (config.schema?.params) {
|
|
232
233
|
jsonSchema.params = config.schema.params;
|
|
233
234
|
}
|
|
234
|
-
if (
|
|
235
|
+
if (streamMode === 'sse') {
|
|
236
|
+
const sseBaseProperties = {
|
|
237
|
+
event: { type: 'string' },
|
|
238
|
+
id: { type: 'string' },
|
|
239
|
+
retry: { type: 'number' },
|
|
240
|
+
};
|
|
241
|
+
if (config.schema?.yieldType) {
|
|
242
|
+
// Developer's yieldType describes the full SSEYield envelope.
|
|
243
|
+
// Merge in SSE base fields for any the developer didn't define.
|
|
244
|
+
const userSchema = config.schema.yieldType;
|
|
245
|
+
jsonSchema.yieldType = {
|
|
246
|
+
...userSchema,
|
|
247
|
+
required: ['data', 'event', 'id'],
|
|
248
|
+
properties: {
|
|
249
|
+
...sseBaseProperties,
|
|
250
|
+
...(userSchema.properties ?? {}),
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
// No yieldType defined — generate a complete SSE envelope schema
|
|
256
|
+
jsonSchema.yieldType = {
|
|
257
|
+
type: 'object',
|
|
258
|
+
required: ['data', 'event', 'id'],
|
|
259
|
+
properties: {
|
|
260
|
+
data: {},
|
|
261
|
+
...sseBaseProperties,
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
else if (config.schema?.yieldType) {
|
|
267
|
+
// Text mode: pass through as-is
|
|
235
268
|
jsonSchema.yieldType = config.schema.yieldType;
|
|
236
269
|
}
|
|
237
270
|
if (config.schema?.returnType) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/implementations/http/hono-stream/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAW,MAAM,MAAM,CAAA;AACpC,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAI7C,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/implementations/http/hono-stream/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAW,MAAM,MAAM,CAAA;AACpC,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAI7C,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAA;AA+D7D;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,oBAAoB;IAIV;IAHrB;;OAEG;IACH,YAAqB,MAAmC;QAAnC,WAAM,GAAN,MAAM,CAA6B;QACtD,IAAI,MAAM,EAAE,GAAG,EAAE,CAAC;YAChB,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,GAAG,CAAA;QACxB,CAAC;QAED,IAAI,MAAM,EAAE,cAAc,EAAE,CAAC;YAC3B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;gBACnC,MAAM,CAAC,cAAe,CAAC,CAAC,CAAC,CAAA;gBACzB,MAAM,IAAI,EAAE,CAAA;YACd,CAAC,CAAC,CAAA;QACJ,CAAC;IAEH,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,uBAAuB,CAAC,EAC7B,IAAI,EACJ,MAAM,EACN,MAAM,GAKP;QACC,MAAM,gBAAgB,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QAEvF,OAAO,GAAG,gBAAgB,IAAI,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAA;IACtI,CAAC;IAED;;OAEG;IACH,uBAAuB,CAAC,IAAY,EAAE,MAAiB;QACrD,OAAO,oBAAoB,CAAC,uBAAuB,CAAC;YAClD,IAAI;YACJ,MAAM;YACN,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU;SAChC,CAAC,CAAA;IACJ,CAAC;IAEO,SAAS,GAAiC,EAAE,CAAA;IAE5C,IAAI,GAAS,IAAI,IAAI,EAAE,CAAA;IACvB,KAAK,GAAoC,EAAE,CAAA;IAEnD,IAAI,GAAG;QACL,OAAO,IAAI,CAAC,IAAI,CAAA;IAClB,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAA;IACnB,CAAC;IAED;;;OAGG;IACH,QAAQ,CACN,OAAiB,EACjB,cAEkF,EAClF,OAMC;QAED,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;YAClB,OAAO;YACP,cAAc;YACd,UAAU,EAAE,OAAO,EAAE,UAAU;YAC/B,kBAAkB,EAAE,OAAO,EAAE,kBAAkB;SAClB,CAAC,CAAA;QAChC,OAAO,IAAI,CAAA;IACb,CAAC;IAED;;OAEG;IACK,mBAAmB,CACzB,SAAuC,EACvC,cAAuD,EACvD,UAAsB;QAEtB,OAAO,KAAK,EAAE,CAAU,EAAE,EAAE;YAC1B,IAAI,CAAC;gBACH,MAAM,OAAO,GACX,OAAO,cAAc,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAA;gBAEjF,qCAAqC;gBACrC,MAAM,MAAM,GACV,CAAC,CAAC,GAAG,CAAC,MAAM,KAAK,KAAK;oBACpB,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC;oBACrD,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;gBAE1C,6CAA6C;gBAC7C,IAAI,SAAS,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC;oBACxC,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;oBAC7D,IAAI,MAAM,EAAE,CAAC;wBACX,MAAM,KAAK,GAAG,IAAI,wBAAwB,CACxC,SAAS,CAAC,IAAI,EACd,wBAAwB,SAAS,CAAC,IAAI,EAAE,EACxC,MAAM,CACP,CAAA;wBACD,mCAAmC;wBACnC,IAAI,IAAI,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC;4BAClC,OAAO,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,EAAE,KAAK,CAAC,CAAA;wBAC1D,CAAC;wBACD,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC,CAAA;oBAC9C,CAAC;gBACH,CAAC;gBAED,IAAI,IAAI,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC;oBAC/B,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,EAAE,CAAC,CAAC,CAAA;gBACzC,CAAC;gBAED,IAAI,UAAU,KAAK,KAAK,EAAE,CAAC;oBACzB,OAAO,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;gBAC5D,CAAC;qBAAM,CAAC;oBACN,OAAO,IAAI,CAAC,gBAAgB,CAAC,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;gBAC7D,CAAC;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,qDAAqD;gBACrD,IAAI,IAAI,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC;oBAClC,OAAO,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,EAAE,KAAc,CAAC,CAAA;gBACnE,CAAC;gBACD,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAG,KAAe,CAAC,OAAO,EAAE,EAAE,GAAG,CAAC,CAAA;YACzD,CAAC;QACH,CAAC,CAAA;IACH,CAAC;IAED;;OAEG;IACK,eAAe,CACrB,SAAuC,EACvC,OAAY,EACZ,MAAW,EACX,CAAU;QAEV,OAAO,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;YACnC,qFAAqF;YACrF,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,EAAE,GAAG,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,EAAE,MAAM,CAAC,CAAA;YAEjF,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE;gBACxB,MAAM,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;YACnC,CAAC,CAAC,CAAA;YAEF,IAAI,OAAO,GAAG,CAAC,CAAA;YACf,IAAI,CAAC;gBACH,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;oBACpC,MAAM,SAAS,GAAG,OAAO,EAAE,CAAA;oBAC3B,MAAM,MAAM,CAAC,QAAQ,CAAC;wBACpB,IAAI,EAAE,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC;wBAC9E,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,SAAS,CAAC,IAAI;wBACpC,EAAE,EAAE,KAAK,CAAC,EAAE,IAAI,MAAM,CAAC,SAAS,CAAC;wBACjC,GAAG,CAAC,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;qBACzD,CAAC,CAAA;gBACJ,CAAC;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,yDAAyD;gBACzD,IAAI,WAA6C,CAAA;gBAEjD,IAAI,IAAI,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC;oBAClC,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,EAAE,KAAc,CAAC,CAAA;gBAC1E,CAAC;gBAED,8BAA8B;gBAC9B,MAAM,SAAS,GAAG,WAAW,EAAE,IAAI,IAAI,EAAE,KAAK,EAAG,KAAe,CAAC,OAAO,EAAE,CAAA;gBAC1E,MAAM,MAAM,CAAC,QAAQ,CAAC;oBACpB,IAAI,EAAE,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;oBAC3E,KAAK,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC,WAAW,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;oBACzF,EAAE,EAAE,WAAW,EAAE,EAAE,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;iBACzC,CAAC,CAAA;gBAEF,gDAAgD;gBAChD,yDAAyD;YAC3D,CAAC;oBAAS,CAAC;gBACT,IAAI,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC;oBAC7B,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC,CAAC,CAAA;gBACvC,CAAC;gBACD,IAAI,IAAI,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC;oBAC9B,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;gBAC7B,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAA;IACJ,CAAC;IAED;;OAEG;IACK,gBAAgB,CACtB,SAAuC,EACvC,OAAY,EACZ,MAAW,EACX,CAAU;QAEV,OAAO,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;YACpC,qFAAqF;YACrF,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,EAAE,GAAG,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,EAAE,MAAM,CAAC,CAAA;YAEjF,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE;gBACxB,MAAM,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;YACnC,CAAC,CAAC,CAAA;YAEF,IAAI,CAAC;gBACH,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;oBACpC,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAA;gBAC7C,CAAC;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,yDAAyD;gBACzD,IAAI,WAA6C,CAAA;gBAEjD,IAAI,IAAI,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC;oBAClC,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,EAAE,KAAc,CAAC,CAAA;gBAC1E,CAAC;gBAED,8BAA8B;gBAC9B,MAAM,SAAS,GAAG,WAAW,EAAE,IAAI,IAAI,EAAE,KAAK,EAAG,KAAe,CAAC,OAAO,EAAE,CAAA;gBAC1E,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAA;YACjD,CAAC;oBAAS,CAAC;gBACT,IAAI,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC;oBAC7B,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC,CAAC,CAAA;gBACvC,CAAC;gBACD,IAAI,IAAI,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC;oBAC9B,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;gBAC7B,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAA;IACJ,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,kBAAkB,EAAE,EAAE,EAAE;YACrF,MAAM,IAAI,GAAG,UAAU,IAAI,IAAI,CAAC,MAAM,EAAE,iBAAiB,IAAI,KAAK,CAAA;YAElE,OAAO;iBACJ,aAAa,EAAE;iBACf,MAAM,CACL,CAAC,CAAyB,EAAqC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,IAAI,CACtF;iBACA,OAAO,CAAC,CAAC,SAAuD,EAAE,EAAE;gBACnE,MAAM,KAAK,GAAG,IAAI,CAAC,uBAAuB,CAAC,SAAS,EAAE,IAAI,EAAE,kBAAkB,CAAC,CAAA;gBAE/E,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;gBAEtB,MAAM,OAAO,GAAG,IAAI,CAAC,mBAAmB,CAAC,SAAS,EAAE,cAAc,EAAE,IAAI,CAAC,CAAA;gBAEzE,sCAAsC;gBACtC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;gBAClC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;YACrC,CAAC,CAAC,CAAA;QACN,CAAC,CAAC,CAAA;QAEF,OAAO,IAAI,CAAC,IAAI,CAAA;IAClB,CAAC;IAED;;OAEG;IACK,uBAAuB,CAC7B,SAAuD,EACvD,UAAsB,EACtB,kBAAgE;QAEhE,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;QAC5B,MAAM,IAAI,GAAG,oBAAoB,CAAC,uBAAuB,CAAC;YACxD,IAAI,EAAE,SAAS,CAAC,IAAI;YACpB,MAAM;YACN,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU;SAChC,CAAC,CAAA;QACF,MAAM,OAAO,GAAG,CAAC,KAAK,EAAE,MAAM,CAAU,CAAA;QACxC,MAAM,UAAU,GAAiE,EAAE,CAAA;QAEnF,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;YAC1B,UAAU,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAA;QAC1C,CAAC;QACD,IAAI,UAAU,KAAK,KAAK,EAAE,CAAC;YACzB,MAAM,iBAAiB,GAAG;gBACxB,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACzB,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACtB,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;aAC1B,CAAA;YAED,IAAI,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC;gBAC7B,8DAA8D;gBAC9D,gEAAgE;gBAChE,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,SAAgC,CAAA;gBACjE,UAAU,CAAC,SAAS,GAAG;oBACrB,GAAG,UAAU;oBACb,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;oBACjC,UAAU,EAAE;wBACV,GAAG,iBAAiB;wBACpB,GAAG,CAAC,UAAU,CAAC,UAAU,IAAI,EAAE,CAAC;qBACjC;iBACF,CAAA;YACH,CAAC;iBAAM,CAAC;gBACN,iEAAiE;gBACjE,UAAU,CAAC,SAAS,GAAG;oBACrB,IAAI,EAAE,QAAQ;oBACd,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;oBACjC,UAAU,EAAE;wBACV,IAAI,EAAE,EAAE;wBACR,GAAG,iBAAiB;qBACrB;iBACF,CAAA;YACH,CAAC;QACH,CAAC;aAAM,IAAI,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC;YACpC,gCAAgC;YAChC,UAAU,CAAC,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,SAAS,CAAA;QAChD,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC;YAC9B,UAAU,CAAC,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,UAAU,CAAA;QAClD,CAAC;QAED,MAAM,IAAI,GAAuB;YAC/B,IAAI,EAAE,SAAS,CAAC,IAAI;YACpB,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,IAAI;YACJ,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC;YACrB,UAAU;YACV,UAAU;SACX,CAAA;QAED,IAAI,WAAW,GAAW,EAAE,CAAA;QAE5B,IAAI,kBAAkB,EAAE,CAAC;YACvB,WAAW,GAAG,kBAAkB,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAA;QACvD,CAAC;QAED,OAAO;YACL,GAAG,WAAW;YACd,GAAG,IAAI;SACR,CAAA;IACH,CAAC;CACF"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-empty-object-type, @typescript-eslint/explicit-function-return-type */
|
|
1
2
|
import { describe, expect, test, vi, beforeEach } from 'vitest';
|
|
2
3
|
import { Hono } from 'hono';
|
|
3
4
|
import { v } from 'suretype';
|
|
@@ -17,9 +18,9 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
17
18
|
test('creates default Hono app', async () => {
|
|
18
19
|
const builder = new HonoStreamAppBuilder();
|
|
19
20
|
const RPC = Procedures();
|
|
20
|
-
RPC.CreateStream('StreamMessages', { scope: 'messages', version: 1 }, async function* (
|
|
21
|
+
RPC.CreateStream('StreamMessages', { scope: 'messages', version: 1 }, async function* () {
|
|
21
22
|
yield { message: 'hello' };
|
|
22
|
-
yield { message: 'world' };
|
|
23
|
+
yield { data: { message: 'world' } };
|
|
23
24
|
});
|
|
24
25
|
builder.register(RPC, () => ({ userId: '123' }));
|
|
25
26
|
const app = builder.build();
|
|
@@ -35,7 +36,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
35
36
|
const builder = new HonoStreamAppBuilder({ app: customApp });
|
|
36
37
|
const RPC = Procedures();
|
|
37
38
|
RPC.CreateStream('StreamData', { scope: 'data', version: 1 }, async function* () {
|
|
38
|
-
yield { data: 1 };
|
|
39
|
+
yield { data: { data: 1 } };
|
|
39
40
|
});
|
|
40
41
|
builder.register(RPC, () => ({ userId: '123' }));
|
|
41
42
|
const app = builder.build();
|
|
@@ -67,9 +68,9 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
67
68
|
const builder = new HonoStreamAppBuilder();
|
|
68
69
|
const RPC = Procedures();
|
|
69
70
|
RPC.CreateStream('Counter', { scope: 'counter', version: 1 }, async function* () {
|
|
70
|
-
yield { count: 1 };
|
|
71
|
-
yield { count: 2 };
|
|
72
|
-
yield { count: 3 };
|
|
71
|
+
yield { data: { count: 1 } };
|
|
72
|
+
yield { data: { count: 2 } };
|
|
73
|
+
yield { data: { count: 3 } };
|
|
73
74
|
});
|
|
74
75
|
builder.register(RPC, () => ({}));
|
|
75
76
|
const app = builder.build();
|
|
@@ -89,7 +90,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
89
90
|
const builder = new HonoStreamAppBuilder();
|
|
90
91
|
const RPC = Procedures();
|
|
91
92
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
92
|
-
yield { ok: true };
|
|
93
|
+
yield { data: { ok: true } };
|
|
93
94
|
});
|
|
94
95
|
builder.register(RPC, () => ({}));
|
|
95
96
|
const app = builder.build();
|
|
@@ -100,7 +101,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
100
101
|
const builder = new HonoStreamAppBuilder({ defaultStreamMode: 'sse' });
|
|
101
102
|
const RPC = Procedures();
|
|
102
103
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
103
|
-
yield { ok: true };
|
|
104
|
+
yield { data: { ok: true } };
|
|
104
105
|
});
|
|
105
106
|
builder.register(RPC, () => ({}));
|
|
106
107
|
const app = builder.build();
|
|
@@ -152,7 +153,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
152
153
|
const builder = new HonoStreamAppBuilder();
|
|
153
154
|
const RPC = Procedures();
|
|
154
155
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
155
|
-
yield { method: 'works' };
|
|
156
|
+
yield { data: { method: 'works' } };
|
|
156
157
|
});
|
|
157
158
|
builder.register(RPC, () => ({}));
|
|
158
159
|
const app = builder.build();
|
|
@@ -163,7 +164,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
163
164
|
const builder = new HonoStreamAppBuilder();
|
|
164
165
|
const RPC = Procedures();
|
|
165
166
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
166
|
-
yield { method: 'works' };
|
|
167
|
+
yield { data: { method: 'works' } };
|
|
167
168
|
});
|
|
168
169
|
builder.register(RPC, () => ({}));
|
|
169
170
|
const app = builder.build();
|
|
@@ -213,7 +214,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
213
214
|
const builder = new HonoStreamAppBuilder({ pathPrefix: '/api/v1' });
|
|
214
215
|
const RPC = Procedures();
|
|
215
216
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
216
|
-
yield { ok: true };
|
|
217
|
+
yield { data: { ok: true } };
|
|
217
218
|
});
|
|
218
219
|
builder.register(RPC, () => ({}));
|
|
219
220
|
const app = builder.build();
|
|
@@ -224,7 +225,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
224
225
|
const builder = new HonoStreamAppBuilder({ pathPrefix: 'custom' });
|
|
225
226
|
const RPC = Procedures();
|
|
226
227
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
227
|
-
yield { ok: true };
|
|
228
|
+
yield { data: { ok: true } };
|
|
228
229
|
});
|
|
229
230
|
builder.register(RPC, () => ({}));
|
|
230
231
|
const app = builder.build();
|
|
@@ -235,7 +236,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
235
236
|
const builder = new HonoStreamAppBuilder({ pathPrefix: '/api' });
|
|
236
237
|
const RPC = Procedures();
|
|
237
238
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
238
|
-
yield {};
|
|
239
|
+
yield { data: {} };
|
|
239
240
|
});
|
|
240
241
|
builder.register(RPC, () => ({}));
|
|
241
242
|
builder.build();
|
|
@@ -251,7 +252,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
251
252
|
const builder = new HonoStreamAppBuilder({ onRequestStart });
|
|
252
253
|
const RPC = Procedures();
|
|
253
254
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
254
|
-
yield { ok: true };
|
|
255
|
+
yield { data: { ok: true } };
|
|
255
256
|
});
|
|
256
257
|
builder.register(RPC, () => ({}));
|
|
257
258
|
const app = builder.build();
|
|
@@ -264,7 +265,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
264
265
|
const builder = new HonoStreamAppBuilder({ onRequestEnd });
|
|
265
266
|
const RPC = Procedures();
|
|
266
267
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
267
|
-
yield { ok: true };
|
|
268
|
+
yield { data: { ok: true } };
|
|
268
269
|
});
|
|
269
270
|
builder.register(RPC, () => ({}));
|
|
270
271
|
const app = builder.build();
|
|
@@ -278,7 +279,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
278
279
|
const builder = new HonoStreamAppBuilder({ onStreamStart });
|
|
279
280
|
const RPC = Procedures();
|
|
280
281
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
281
|
-
yield { ok: true };
|
|
282
|
+
yield { data: { ok: true } };
|
|
282
283
|
});
|
|
283
284
|
builder.register(RPC, () => ({}));
|
|
284
285
|
const app = builder.build();
|
|
@@ -291,7 +292,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
291
292
|
const builder = new HonoStreamAppBuilder({ onStreamEnd });
|
|
292
293
|
const RPC = Procedures();
|
|
293
294
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
294
|
-
yield { ok: true };
|
|
295
|
+
yield { data: { ok: true } };
|
|
295
296
|
});
|
|
296
297
|
builder.register(RPC, () => ({}));
|
|
297
298
|
const app = builder.build();
|
|
@@ -312,7 +313,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
312
313
|
const RPC = Procedures();
|
|
313
314
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
314
315
|
order.push('handler');
|
|
315
|
-
yield { ok: true };
|
|
316
|
+
yield { data: { ok: true } };
|
|
316
317
|
});
|
|
317
318
|
builder.register(RPC, () => ({}));
|
|
318
319
|
const app = builder.build();
|
|
@@ -351,7 +352,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
351
352
|
params: v.object({ count: v.number() }),
|
|
352
353
|
},
|
|
353
354
|
}, async function* (ctx, params) {
|
|
354
|
-
yield { count: params.count };
|
|
355
|
+
yield { data: { count: params.count } };
|
|
355
356
|
});
|
|
356
357
|
builder.register(RPC, () => ({}));
|
|
357
358
|
const app = builder.build();
|
|
@@ -367,7 +368,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
367
368
|
const builder = new HonoStreamAppBuilder();
|
|
368
369
|
const RPC = Procedures();
|
|
369
370
|
RPC.CreateStream('ErrorStream', { scope: 'error', version: 1 }, async function* () {
|
|
370
|
-
yield { count: 1 };
|
|
371
|
+
yield { data: { count: 1 } };
|
|
371
372
|
throw new Error('Stream error');
|
|
372
373
|
});
|
|
373
374
|
builder.register(RPC, () => ({}));
|
|
@@ -404,7 +405,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
404
405
|
params: v.object({ count: v.number() }),
|
|
405
406
|
},
|
|
406
407
|
}, async function* (ctx, params) {
|
|
407
|
-
yield { count: params.count };
|
|
408
|
+
yield { data: { count: params.count } };
|
|
408
409
|
});
|
|
409
410
|
builder.register(RPC, () => ({}));
|
|
410
411
|
const app = builder.build();
|
|
@@ -428,7 +429,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
428
429
|
params: v.object({ count: v.number() }),
|
|
429
430
|
},
|
|
430
431
|
}, async function* (ctx, params) {
|
|
431
|
-
yield { count: params.count };
|
|
432
|
+
yield { data: { count: params.count } };
|
|
432
433
|
});
|
|
433
434
|
builder.register(RPC, () => ({}));
|
|
434
435
|
const app = builder.build();
|
|
@@ -447,7 +448,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
447
448
|
const builder = new HonoStreamAppBuilder({ onPreStreamError });
|
|
448
449
|
const RPC = Procedures();
|
|
449
450
|
RPC.CreateStream('SecureStream', { scope: 'secure', version: 1 }, async function* (ctx) {
|
|
450
|
-
yield { userId: ctx.userId };
|
|
451
|
+
yield { data: { userId: ctx.userId } };
|
|
451
452
|
});
|
|
452
453
|
builder.register(RPC, () => {
|
|
453
454
|
throw new Error('Authentication required');
|
|
@@ -462,7 +463,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
462
463
|
test('onMidStreamError returns custom value written to SSE stream', async () => {
|
|
463
464
|
const onMidStreamError = vi.fn((procedure, c, error) => {
|
|
464
465
|
return {
|
|
465
|
-
|
|
466
|
+
data: {
|
|
466
467
|
type: 'error',
|
|
467
468
|
code: 'STREAM_FAILED',
|
|
468
469
|
message: error.message,
|
|
@@ -474,7 +475,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
474
475
|
const builder = new HonoStreamAppBuilder({ onMidStreamError });
|
|
475
476
|
const RPC = Procedures();
|
|
476
477
|
RPC.CreateStream('ErrorStream', { scope: 'error', version: 1 }, async function* () {
|
|
477
|
-
yield { type: 'data', value: 1 };
|
|
478
|
+
yield { data: { type: 'data', value: 1 } };
|
|
478
479
|
throw new Error('Something broke');
|
|
479
480
|
});
|
|
480
481
|
builder.register(RPC, () => ({}));
|
|
@@ -493,7 +494,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
493
494
|
test('onMidStreamError returns custom value written to text stream', async () => {
|
|
494
495
|
const onMidStreamError = vi.fn((procedure, c, error) => {
|
|
495
496
|
return {
|
|
496
|
-
|
|
497
|
+
data: { type: 'error', message: error.message },
|
|
497
498
|
};
|
|
498
499
|
});
|
|
499
500
|
const builder = new HonoStreamAppBuilder({
|
|
@@ -609,8 +610,70 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
609
610
|
expect(doc.methods).toEqual(['get', 'post']);
|
|
610
611
|
expect(doc.streamMode).toBe('sse');
|
|
611
612
|
expect(doc.jsonSchema.params).toBeDefined();
|
|
612
|
-
expect(doc.jsonSchema.yieldType).toBeDefined();
|
|
613
613
|
expect(doc.jsonSchema.returnType).toBeDefined();
|
|
614
|
+
// SSE envelope fields are merged into yieldType
|
|
615
|
+
const yt = doc.jsonSchema.yieldType;
|
|
616
|
+
expect(yt.required).toEqual(['data', 'event', 'id']);
|
|
617
|
+
expect(yt.properties.event).toEqual({ type: 'string' });
|
|
618
|
+
expect(yt.properties.id).toEqual({ type: 'string' });
|
|
619
|
+
expect(yt.properties.retry).toEqual({ type: 'number' });
|
|
620
|
+
// Developer's data property is preserved
|
|
621
|
+
expect(yt.properties.message).toBeDefined();
|
|
622
|
+
});
|
|
623
|
+
test('SSE mode generates SSE envelope even when no yieldType is defined', () => {
|
|
624
|
+
const builder = new HonoStreamAppBuilder();
|
|
625
|
+
const RPC = Procedures();
|
|
626
|
+
RPC.CreateStream('NoYield', { scope: 'test', version: 1 }, async function* () {
|
|
627
|
+
yield {};
|
|
628
|
+
});
|
|
629
|
+
builder.register(RPC, () => ({}));
|
|
630
|
+
builder.build();
|
|
631
|
+
const yt = builder.docs[0].jsonSchema.yieldType;
|
|
632
|
+
expect(yt).toBeDefined();
|
|
633
|
+
expect(yt.type).toBe('object');
|
|
634
|
+
expect(yt.required).toEqual(['data', 'event', 'id']);
|
|
635
|
+
expect(yt.properties.data).toEqual({});
|
|
636
|
+
expect(yt.properties.event).toEqual({ type: 'string' });
|
|
637
|
+
expect(yt.properties.id).toEqual({ type: 'string' });
|
|
638
|
+
expect(yt.properties.retry).toEqual({ type: 'number' });
|
|
639
|
+
});
|
|
640
|
+
test('text mode passes yieldType through as-is', () => {
|
|
641
|
+
const yieldSchema = v.object({ chunk: v.string() });
|
|
642
|
+
const builder = new HonoStreamAppBuilder({ defaultStreamMode: 'text' });
|
|
643
|
+
const RPC = Procedures();
|
|
644
|
+
RPC.CreateStream('TextStream', { scope: 'test', version: 1, schema: { yieldType: yieldSchema } }, async function* () {
|
|
645
|
+
yield { chunk: 'hi' };
|
|
646
|
+
});
|
|
647
|
+
builder.register(RPC, () => ({}));
|
|
648
|
+
builder.build();
|
|
649
|
+
const yt = builder.docs[0].jsonSchema.yieldType;
|
|
650
|
+
expect(yt).toBeDefined();
|
|
651
|
+
// Text mode should NOT have SSE envelope fields injected
|
|
652
|
+
expect(yt.properties?.event).toBeUndefined();
|
|
653
|
+
expect(yt.properties?.id).toBeUndefined();
|
|
654
|
+
expect(yt.properties?.retry).toBeUndefined();
|
|
655
|
+
});
|
|
656
|
+
test('developer-defined event/id in yieldType are preserved', () => {
|
|
657
|
+
const yieldSchema = v.object({
|
|
658
|
+
data: v.object({ msg: v.string() }),
|
|
659
|
+
event: v.string().const('custom-event'),
|
|
660
|
+
id: v.string().const('fixed-id'),
|
|
661
|
+
});
|
|
662
|
+
const builder = new HonoStreamAppBuilder();
|
|
663
|
+
const RPC = Procedures();
|
|
664
|
+
RPC.CreateStream('CustomSSE', { scope: 'test', version: 1, schema: { yieldType: yieldSchema } }, async function* () {
|
|
665
|
+
// suretype sucks, we have to assert this as the exact type to satisfy the const assertions in the schema
|
|
666
|
+
yield { data: { msg: 'hi' }, event: 'custom-event', id: 'fixed-id' };
|
|
667
|
+
});
|
|
668
|
+
builder.register(RPC, () => ({}));
|
|
669
|
+
builder.build();
|
|
670
|
+
const yt = builder.docs[0].jsonSchema.yieldType;
|
|
671
|
+
expect(yt.required).toEqual(['data', 'event', 'id']);
|
|
672
|
+
// Developer's custom event/id definitions take precedence
|
|
673
|
+
expect(yt.properties.event.const).toBe('custom-event');
|
|
674
|
+
expect(yt.properties.id.const).toBe('fixed-id');
|
|
675
|
+
// retry is still added as a base default
|
|
676
|
+
expect(yt.properties.retry).toEqual({ type: 'number' });
|
|
614
677
|
});
|
|
615
678
|
test('streamMode is recorded in docs', () => {
|
|
616
679
|
const builder = new HonoStreamAppBuilder({ defaultStreamMode: 'text' });
|
|
@@ -646,7 +709,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
646
709
|
}));
|
|
647
710
|
// Streaming procedure (should be registered)
|
|
648
711
|
RPC.CreateStream('Stream', { scope: 'stream', version: 1 }, async function* () {
|
|
649
|
-
yield { ok: true };
|
|
712
|
+
yield { data: { ok: true } };
|
|
650
713
|
});
|
|
651
714
|
builder.register(RPC, () => ({}));
|
|
652
715
|
const app = builder.build();
|
|
@@ -669,10 +732,10 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
669
732
|
const builder = new HonoStreamAppBuilder();
|
|
670
733
|
const RPC = Procedures();
|
|
671
734
|
RPC.CreateStream('StreamEvents', { scope: 'events', version: 1 }, async function* () {
|
|
672
|
-
yield {};
|
|
735
|
+
yield { data: {} };
|
|
673
736
|
});
|
|
674
737
|
builder.register(RPC, () => ({}), {
|
|
675
|
-
extendProcedureDoc: ({
|
|
738
|
+
extendProcedureDoc: ({ procedure }) => ({
|
|
676
739
|
summary: `Stream events endpoint`,
|
|
677
740
|
tags: ['events'],
|
|
678
741
|
operationId: procedure.name,
|
|
@@ -688,7 +751,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
688
751
|
const builder = new HonoStreamAppBuilder();
|
|
689
752
|
const RPC = Procedures();
|
|
690
753
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
691
|
-
yield {};
|
|
754
|
+
yield { data: {} };
|
|
692
755
|
});
|
|
693
756
|
builder.register(RPC, () => ({}), {
|
|
694
757
|
extendProcedureDoc: () => ({
|
|
@@ -738,7 +801,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
738
801
|
const SSERPC = Procedures();
|
|
739
802
|
const TextRPC = Procedures();
|
|
740
803
|
SSERPC.CreateStream('SSEStream', { scope: 'sse', version: 1 }, async function* () {
|
|
741
|
-
yield { mode: 'sse' };
|
|
804
|
+
yield { data: { mode: 'sse' } };
|
|
742
805
|
});
|
|
743
806
|
TextRPC.CreateStream('TextStream', { scope: 'text', version: 1 }, async function* () {
|
|
744
807
|
yield { mode: 'text' };
|
|
@@ -789,7 +852,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
789
852
|
const RPC = Procedures();
|
|
790
853
|
RPC.CreateStream('CheckPrevalidated', { scope: 'check', version: 1 }, async function* (ctx) {
|
|
791
854
|
receivedIsPrevalidated = ctx.isPrevalidated;
|
|
792
|
-
yield { ok: true };
|
|
855
|
+
yield { data: { ok: true } };
|
|
793
856
|
});
|
|
794
857
|
builder.register(RPC, () => ({}));
|
|
795
858
|
const app = builder.build();
|
|
@@ -849,6 +912,77 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
849
912
|
});
|
|
850
913
|
});
|
|
851
914
|
// --------------------------------------------------------------------------
|
|
915
|
+
// SSE Yield Shape Tests
|
|
916
|
+
// --------------------------------------------------------------------------
|
|
917
|
+
describe('SSE yield shape', () => {
|
|
918
|
+
test('custom event names in yields', async () => {
|
|
919
|
+
const builder = new HonoStreamAppBuilder();
|
|
920
|
+
const RPC = Procedures();
|
|
921
|
+
RPC.CreateStream('Events', { scope: 'events', version: 1 }, async function* () {
|
|
922
|
+
yield { data: { type: 'user_joined' }, event: 'join' };
|
|
923
|
+
yield { data: { type: 'message' }, event: 'chat' };
|
|
924
|
+
});
|
|
925
|
+
builder.register(RPC, () => ({}));
|
|
926
|
+
const app = builder.build();
|
|
927
|
+
const res = await app.request('/events/events/1');
|
|
928
|
+
const text = await res.text();
|
|
929
|
+
expect(text).toContain('event: join');
|
|
930
|
+
expect(text).toContain('event: chat');
|
|
931
|
+
expect(text).not.toContain('event: Events');
|
|
932
|
+
});
|
|
933
|
+
test('custom id in yields', async () => {
|
|
934
|
+
const builder = new HonoStreamAppBuilder();
|
|
935
|
+
const RPC = Procedures();
|
|
936
|
+
RPC.CreateStream('Events', { scope: 'events', version: 1 }, async function* () {
|
|
937
|
+
yield { data: { msg: 'first' }, id: 'msg-001' };
|
|
938
|
+
yield { data: { msg: 'second' }, id: 'msg-002' };
|
|
939
|
+
});
|
|
940
|
+
builder.register(RPC, () => ({}));
|
|
941
|
+
const app = builder.build();
|
|
942
|
+
const res = await app.request('/events/events/1');
|
|
943
|
+
const text = await res.text();
|
|
944
|
+
expect(text).toContain('id: msg-001');
|
|
945
|
+
expect(text).toContain('id: msg-002');
|
|
946
|
+
});
|
|
947
|
+
test('string data pass-through without double-stringify', async () => {
|
|
948
|
+
const builder = new HonoStreamAppBuilder();
|
|
949
|
+
const RPC = Procedures();
|
|
950
|
+
RPC.CreateStream('Events', { scope: 'events', version: 1 }, async function* () {
|
|
951
|
+
yield { data: 'already a string' };
|
|
952
|
+
yield { data: { needs: 'stringify' } };
|
|
953
|
+
});
|
|
954
|
+
builder.register(RPC, () => ({}));
|
|
955
|
+
const app = builder.build();
|
|
956
|
+
const res = await app.request('/events/events/1');
|
|
957
|
+
const text = await res.text();
|
|
958
|
+
// String data should be passed through as-is (not JSON-stringified again)
|
|
959
|
+
expect(text).toContain('data: already a string');
|
|
960
|
+
// Object data should be JSON-stringified
|
|
961
|
+
expect(text).toContain('data: {"needs":"stringify"}');
|
|
962
|
+
});
|
|
963
|
+
test('default event falls back to procedure name when omitted', async () => {
|
|
964
|
+
const builder = new HonoStreamAppBuilder();
|
|
965
|
+
const RPC = Procedures();
|
|
966
|
+
RPC.CreateStream('MyProcedure', { scope: 'test', version: 1 }, async function* () {
|
|
967
|
+
yield { data: { value: 1 } };
|
|
968
|
+
yield { data: { value: 2 }, event: 'custom' };
|
|
969
|
+
yield { data: { value: 3 } };
|
|
970
|
+
});
|
|
971
|
+
builder.register(RPC, () => ({}));
|
|
972
|
+
const app = builder.build();
|
|
973
|
+
const res = await app.request('/test/my-procedure/1');
|
|
974
|
+
const text = await res.text();
|
|
975
|
+
// Split into individual SSE messages
|
|
976
|
+
const messages = text.split('\n\n').filter(Boolean);
|
|
977
|
+
// First and third should use procedure name as event
|
|
978
|
+
expect(messages[0]).toContain('event: MyProcedure');
|
|
979
|
+
// Second should use custom event
|
|
980
|
+
expect(messages[1]).toContain('event: custom');
|
|
981
|
+
// Third should fall back to procedure name
|
|
982
|
+
expect(messages[2]).toContain('event: MyProcedure');
|
|
983
|
+
});
|
|
984
|
+
});
|
|
985
|
+
// --------------------------------------------------------------------------
|
|
852
986
|
// Integration Test
|
|
853
987
|
// --------------------------------------------------------------------------
|
|
854
988
|
describe('integration', () => {
|