ts-procedures 4.0.1 → 5.0.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/hono-stream/index.d.ts +3 -3
- package/build/implementations/http/hono-stream/index.js +31 -32
- package/build/implementations/http/hono-stream/index.js.map +1 -1
- package/build/implementations/http/hono-stream/index.test.js +140 -59
- 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 +58 -31
- package/src/implementations/http/hono-stream/index.test.ts +162 -63
- package/src/implementations/http/hono-stream/index.ts +39 -36
|
@@ -3,15 +3,15 @@ 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
|
|
7
|
-
data: string | unknown;
|
|
6
|
+
export type SSEOptions = {
|
|
8
7
|
event?: string;
|
|
9
8
|
id?: string;
|
|
10
9
|
retry?: number;
|
|
11
10
|
};
|
|
11
|
+
export declare function sse<T extends object>(data: T, options?: SSEOptions): T;
|
|
12
12
|
/**
|
|
13
13
|
* Result from onMidStreamError callback.
|
|
14
|
-
* @property data - The data to write
|
|
14
|
+
* @property data - The data to write as the SSE `data:` field content (should match yieldType schema)
|
|
15
15
|
* @property event - Optional SSE event name (defaults to procedure name if data provided, 'error' otherwise)
|
|
16
16
|
* @property id - Optional SSE event id (auto-incremented if not provided)
|
|
17
17
|
* @property closeStream - Whether to close the stream after writing (defaults to true)
|
|
@@ -3,6 +3,17 @@ import { streamSSE, streamText } from 'hono/streaming';
|
|
|
3
3
|
import { kebabCase } from 'es-toolkit/string';
|
|
4
4
|
import { castArray } from 'es-toolkit/compat';
|
|
5
5
|
import { ProcedureValidationError } from '../../../errors.js';
|
|
6
|
+
const sseMetadata = new WeakMap();
|
|
7
|
+
export function sse(data, options) {
|
|
8
|
+
sseMetadata.set(data, options ?? {});
|
|
9
|
+
return data;
|
|
10
|
+
}
|
|
11
|
+
function getSSEMeta(value) {
|
|
12
|
+
if (typeof value === 'object' && value !== null) {
|
|
13
|
+
return sseMetadata.get(value);
|
|
14
|
+
}
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
6
17
|
/**
|
|
7
18
|
* Builder class for creating a Hono application with streaming RPC routes.
|
|
8
19
|
*
|
|
@@ -128,11 +139,17 @@ export class HonoStreamAppBuilder {
|
|
|
128
139
|
try {
|
|
129
140
|
for await (const value of generator) {
|
|
130
141
|
const currentId = eventId++;
|
|
142
|
+
const meta = getSSEMeta(value);
|
|
143
|
+
const data = typeof value === 'string'
|
|
144
|
+
? value
|
|
145
|
+
: value != null
|
|
146
|
+
? JSON.stringify(value)
|
|
147
|
+
: '';
|
|
131
148
|
await stream.writeSSE({
|
|
132
|
-
data
|
|
133
|
-
event:
|
|
134
|
-
id:
|
|
135
|
-
...(
|
|
149
|
+
data,
|
|
150
|
+
event: meta?.event ?? procedure.name,
|
|
151
|
+
id: meta?.id ?? String(currentId),
|
|
152
|
+
...(meta?.retry !== undefined && { retry: meta.retry }),
|
|
136
153
|
});
|
|
137
154
|
}
|
|
138
155
|
}
|
|
@@ -233,35 +250,17 @@ export class HonoStreamAppBuilder {
|
|
|
233
250
|
jsonSchema.params = config.schema.params;
|
|
234
251
|
}
|
|
235
252
|
if (streamMode === 'sse') {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
253
|
+
jsonSchema.yieldType = {
|
|
254
|
+
type: 'object',
|
|
255
|
+
description: 'SSE message envelope. The data field contains the procedure yield value.',
|
|
256
|
+
required: ['data', 'event', 'id'],
|
|
257
|
+
properties: {
|
|
258
|
+
data: config.schema?.yieldType ?? {},
|
|
259
|
+
event: { type: 'string' },
|
|
260
|
+
id: { type: 'string' },
|
|
261
|
+
retry: { type: 'number' },
|
|
262
|
+
},
|
|
240
263
|
};
|
|
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
264
|
}
|
|
266
265
|
else if (config.schema?.yieldType) {
|
|
267
266
|
// Text mode: pass through as-is
|
|
@@ -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":"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"}
|
|
@@ -3,7 +3,7 @@ import { describe, expect, test, vi, beforeEach } from 'vitest';
|
|
|
3
3
|
import { Hono } from 'hono';
|
|
4
4
|
import { v } from 'suretype';
|
|
5
5
|
import { Procedures } from '../../../index.js';
|
|
6
|
-
import { HonoStreamAppBuilder } from './index.js';
|
|
6
|
+
import { HonoStreamAppBuilder, sse } from './index.js';
|
|
7
7
|
/**
|
|
8
8
|
* HonoStreamAppBuilder Test Suite
|
|
9
9
|
*
|
|
@@ -20,7 +20,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
20
20
|
const RPC = Procedures();
|
|
21
21
|
RPC.CreateStream('StreamMessages', { scope: 'messages', version: 1 }, async function* () {
|
|
22
22
|
yield { message: 'hello' };
|
|
23
|
-
yield {
|
|
23
|
+
yield { message: 'world' };
|
|
24
24
|
});
|
|
25
25
|
builder.register(RPC, () => ({ userId: '123' }));
|
|
26
26
|
const app = builder.build();
|
|
@@ -36,7 +36,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
36
36
|
const builder = new HonoStreamAppBuilder({ app: customApp });
|
|
37
37
|
const RPC = Procedures();
|
|
38
38
|
RPC.CreateStream('StreamData', { scope: 'data', version: 1 }, async function* () {
|
|
39
|
-
yield { data:
|
|
39
|
+
yield { data: 1 };
|
|
40
40
|
});
|
|
41
41
|
builder.register(RPC, () => ({ userId: '123' }));
|
|
42
42
|
const app = builder.build();
|
|
@@ -68,9 +68,9 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
68
68
|
const builder = new HonoStreamAppBuilder();
|
|
69
69
|
const RPC = Procedures();
|
|
70
70
|
RPC.CreateStream('Counter', { scope: 'counter', version: 1 }, async function* () {
|
|
71
|
-
yield {
|
|
72
|
-
yield {
|
|
73
|
-
yield {
|
|
71
|
+
yield { count: 1 };
|
|
72
|
+
yield { count: 2 };
|
|
73
|
+
yield { count: 3 };
|
|
74
74
|
});
|
|
75
75
|
builder.register(RPC, () => ({}));
|
|
76
76
|
const app = builder.build();
|
|
@@ -90,7 +90,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
90
90
|
const builder = new HonoStreamAppBuilder();
|
|
91
91
|
const RPC = Procedures();
|
|
92
92
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
93
|
-
yield {
|
|
93
|
+
yield { ok: true };
|
|
94
94
|
});
|
|
95
95
|
builder.register(RPC, () => ({}));
|
|
96
96
|
const app = builder.build();
|
|
@@ -101,7 +101,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
101
101
|
const builder = new HonoStreamAppBuilder({ defaultStreamMode: 'sse' });
|
|
102
102
|
const RPC = Procedures();
|
|
103
103
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
104
|
-
yield {
|
|
104
|
+
yield { ok: true };
|
|
105
105
|
});
|
|
106
106
|
builder.register(RPC, () => ({}));
|
|
107
107
|
const app = builder.build();
|
|
@@ -153,7 +153,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
153
153
|
const builder = new HonoStreamAppBuilder();
|
|
154
154
|
const RPC = Procedures();
|
|
155
155
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
156
|
-
yield {
|
|
156
|
+
yield { method: 'works' };
|
|
157
157
|
});
|
|
158
158
|
builder.register(RPC, () => ({}));
|
|
159
159
|
const app = builder.build();
|
|
@@ -164,7 +164,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
164
164
|
const builder = new HonoStreamAppBuilder();
|
|
165
165
|
const RPC = Procedures();
|
|
166
166
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
167
|
-
yield {
|
|
167
|
+
yield { method: 'works' };
|
|
168
168
|
});
|
|
169
169
|
builder.register(RPC, () => ({}));
|
|
170
170
|
const app = builder.build();
|
|
@@ -214,7 +214,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
214
214
|
const builder = new HonoStreamAppBuilder({ pathPrefix: '/api/v1' });
|
|
215
215
|
const RPC = Procedures();
|
|
216
216
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
217
|
-
yield {
|
|
217
|
+
yield { ok: true };
|
|
218
218
|
});
|
|
219
219
|
builder.register(RPC, () => ({}));
|
|
220
220
|
const app = builder.build();
|
|
@@ -225,7 +225,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
225
225
|
const builder = new HonoStreamAppBuilder({ pathPrefix: 'custom' });
|
|
226
226
|
const RPC = Procedures();
|
|
227
227
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
228
|
-
yield {
|
|
228
|
+
yield { ok: true };
|
|
229
229
|
});
|
|
230
230
|
builder.register(RPC, () => ({}));
|
|
231
231
|
const app = builder.build();
|
|
@@ -236,7 +236,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
236
236
|
const builder = new HonoStreamAppBuilder({ pathPrefix: '/api' });
|
|
237
237
|
const RPC = Procedures();
|
|
238
238
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
239
|
-
yield {
|
|
239
|
+
yield {};
|
|
240
240
|
});
|
|
241
241
|
builder.register(RPC, () => ({}));
|
|
242
242
|
builder.build();
|
|
@@ -252,7 +252,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
252
252
|
const builder = new HonoStreamAppBuilder({ onRequestStart });
|
|
253
253
|
const RPC = Procedures();
|
|
254
254
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
255
|
-
yield {
|
|
255
|
+
yield { ok: true };
|
|
256
256
|
});
|
|
257
257
|
builder.register(RPC, () => ({}));
|
|
258
258
|
const app = builder.build();
|
|
@@ -265,7 +265,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
265
265
|
const builder = new HonoStreamAppBuilder({ onRequestEnd });
|
|
266
266
|
const RPC = Procedures();
|
|
267
267
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
268
|
-
yield {
|
|
268
|
+
yield { ok: true };
|
|
269
269
|
});
|
|
270
270
|
builder.register(RPC, () => ({}));
|
|
271
271
|
const app = builder.build();
|
|
@@ -279,7 +279,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
279
279
|
const builder = new HonoStreamAppBuilder({ onStreamStart });
|
|
280
280
|
const RPC = Procedures();
|
|
281
281
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
282
|
-
yield {
|
|
282
|
+
yield { ok: true };
|
|
283
283
|
});
|
|
284
284
|
builder.register(RPC, () => ({}));
|
|
285
285
|
const app = builder.build();
|
|
@@ -292,7 +292,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
292
292
|
const builder = new HonoStreamAppBuilder({ onStreamEnd });
|
|
293
293
|
const RPC = Procedures();
|
|
294
294
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
295
|
-
yield {
|
|
295
|
+
yield { ok: true };
|
|
296
296
|
});
|
|
297
297
|
builder.register(RPC, () => ({}));
|
|
298
298
|
const app = builder.build();
|
|
@@ -313,7 +313,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
313
313
|
const RPC = Procedures();
|
|
314
314
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
315
315
|
order.push('handler');
|
|
316
|
-
yield {
|
|
316
|
+
yield { ok: true };
|
|
317
317
|
});
|
|
318
318
|
builder.register(RPC, () => ({}));
|
|
319
319
|
const app = builder.build();
|
|
@@ -352,7 +352,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
352
352
|
params: v.object({ count: v.number() }),
|
|
353
353
|
},
|
|
354
354
|
}, async function* (ctx, params) {
|
|
355
|
-
yield {
|
|
355
|
+
yield { count: params.count };
|
|
356
356
|
});
|
|
357
357
|
builder.register(RPC, () => ({}));
|
|
358
358
|
const app = builder.build();
|
|
@@ -368,7 +368,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
368
368
|
const builder = new HonoStreamAppBuilder();
|
|
369
369
|
const RPC = Procedures();
|
|
370
370
|
RPC.CreateStream('ErrorStream', { scope: 'error', version: 1 }, async function* () {
|
|
371
|
-
yield {
|
|
371
|
+
yield { count: 1 };
|
|
372
372
|
throw new Error('Stream error');
|
|
373
373
|
});
|
|
374
374
|
builder.register(RPC, () => ({}));
|
|
@@ -405,7 +405,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
405
405
|
params: v.object({ count: v.number() }),
|
|
406
406
|
},
|
|
407
407
|
}, async function* (ctx, params) {
|
|
408
|
-
yield {
|
|
408
|
+
yield { count: params.count };
|
|
409
409
|
});
|
|
410
410
|
builder.register(RPC, () => ({}));
|
|
411
411
|
const app = builder.build();
|
|
@@ -429,7 +429,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
429
429
|
params: v.object({ count: v.number() }),
|
|
430
430
|
},
|
|
431
431
|
}, async function* (ctx, params) {
|
|
432
|
-
yield {
|
|
432
|
+
yield { count: params.count };
|
|
433
433
|
});
|
|
434
434
|
builder.register(RPC, () => ({}));
|
|
435
435
|
const app = builder.build();
|
|
@@ -448,7 +448,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
448
448
|
const builder = new HonoStreamAppBuilder({ onPreStreamError });
|
|
449
449
|
const RPC = Procedures();
|
|
450
450
|
RPC.CreateStream('SecureStream', { scope: 'secure', version: 1 }, async function* (ctx) {
|
|
451
|
-
yield {
|
|
451
|
+
yield { userId: ctx.userId };
|
|
452
452
|
});
|
|
453
453
|
builder.register(RPC, () => {
|
|
454
454
|
throw new Error('Authentication required');
|
|
@@ -475,7 +475,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
475
475
|
const builder = new HonoStreamAppBuilder({ onMidStreamError });
|
|
476
476
|
const RPC = Procedures();
|
|
477
477
|
RPC.CreateStream('ErrorStream', { scope: 'error', version: 1 }, async function* () {
|
|
478
|
-
yield {
|
|
478
|
+
yield { type: 'data', value: 1 };
|
|
479
479
|
throw new Error('Something broke');
|
|
480
480
|
});
|
|
481
481
|
builder.register(RPC, () => ({}));
|
|
@@ -611,14 +611,15 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
611
611
|
expect(doc.streamMode).toBe('sse');
|
|
612
612
|
expect(doc.jsonSchema.params).toBeDefined();
|
|
613
613
|
expect(doc.jsonSchema.returnType).toBeDefined();
|
|
614
|
-
//
|
|
614
|
+
// yieldType is nested under SSE envelope's data property
|
|
615
615
|
const yt = doc.jsonSchema.yieldType;
|
|
616
|
+
expect(yt.description).toBe('SSE message envelope. The data field contains the procedure yield value.');
|
|
616
617
|
expect(yt.required).toEqual(['data', 'event', 'id']);
|
|
617
618
|
expect(yt.properties.event).toEqual({ type: 'string' });
|
|
618
619
|
expect(yt.properties.id).toEqual({ type: 'string' });
|
|
619
620
|
expect(yt.properties.retry).toEqual({ type: 'number' });
|
|
620
|
-
// Developer's
|
|
621
|
-
expect(yt.properties.message).toBeDefined();
|
|
621
|
+
// Developer's yieldType is nested under data
|
|
622
|
+
expect(yt.properties.data.properties.message).toBeDefined();
|
|
622
623
|
});
|
|
623
624
|
test('SSE mode generates SSE envelope even when no yieldType is defined', () => {
|
|
624
625
|
const builder = new HonoStreamAppBuilder();
|
|
@@ -631,7 +632,9 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
631
632
|
const yt = builder.docs[0].jsonSchema.yieldType;
|
|
632
633
|
expect(yt).toBeDefined();
|
|
633
634
|
expect(yt.type).toBe('object');
|
|
635
|
+
expect(yt.description).toBe('SSE message envelope. The data field contains the procedure yield value.');
|
|
634
636
|
expect(yt.required).toEqual(['data', 'event', 'id']);
|
|
637
|
+
// data is empty schema when no yieldType defined
|
|
635
638
|
expect(yt.properties.data).toEqual({});
|
|
636
639
|
expect(yt.properties.event).toEqual({ type: 'string' });
|
|
637
640
|
expect(yt.properties.id).toEqual({ type: 'string' });
|
|
@@ -653,27 +656,27 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
653
656
|
expect(yt.properties?.id).toBeUndefined();
|
|
654
657
|
expect(yt.properties?.retry).toBeUndefined();
|
|
655
658
|
});
|
|
656
|
-
test('
|
|
659
|
+
test('yieldType with id property does not collide with SSE id field', () => {
|
|
660
|
+
// User's yieldType has an `id` field (number) — this should be nested under
|
|
661
|
+
// the SSE envelope's `data` property, not collide with the SSE `id` (string)
|
|
657
662
|
const yieldSchema = v.object({
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
id: v.string().const('fixed-id'),
|
|
663
|
+
id: v.number(),
|
|
664
|
+
message: v.string(),
|
|
661
665
|
});
|
|
662
666
|
const builder = new HonoStreamAppBuilder();
|
|
663
667
|
const RPC = Procedures();
|
|
664
|
-
RPC.CreateStream('
|
|
665
|
-
|
|
666
|
-
yield { data: { msg: 'hi' }, event: 'custom-event', id: 'fixed-id' };
|
|
668
|
+
RPC.CreateStream('Notifications', { scope: 'test', version: 1, schema: { yieldType: yieldSchema } }, async function* () {
|
|
669
|
+
yield { id: 42, message: 'hello' };
|
|
667
670
|
});
|
|
668
671
|
builder.register(RPC, () => ({}));
|
|
669
672
|
builder.build();
|
|
670
673
|
const yt = builder.docs[0].jsonSchema.yieldType;
|
|
671
674
|
expect(yt.required).toEqual(['data', 'event', 'id']);
|
|
672
|
-
//
|
|
673
|
-
expect(yt.properties.
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
expect(yt.properties.
|
|
675
|
+
// SSE envelope id is a string
|
|
676
|
+
expect(yt.properties.id).toEqual({ type: 'string' });
|
|
677
|
+
// User's id (number) is safely nested under data
|
|
678
|
+
expect(yt.properties.data.properties.id.type).toBe('number');
|
|
679
|
+
expect(yt.properties.data.properties.message.type).toBe('string');
|
|
677
680
|
});
|
|
678
681
|
test('streamMode is recorded in docs', () => {
|
|
679
682
|
const builder = new HonoStreamAppBuilder({ defaultStreamMode: 'text' });
|
|
@@ -709,7 +712,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
709
712
|
}));
|
|
710
713
|
// Streaming procedure (should be registered)
|
|
711
714
|
RPC.CreateStream('Stream', { scope: 'stream', version: 1 }, async function* () {
|
|
712
|
-
yield {
|
|
715
|
+
yield { ok: true };
|
|
713
716
|
});
|
|
714
717
|
builder.register(RPC, () => ({}));
|
|
715
718
|
const app = builder.build();
|
|
@@ -732,7 +735,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
732
735
|
const builder = new HonoStreamAppBuilder();
|
|
733
736
|
const RPC = Procedures();
|
|
734
737
|
RPC.CreateStream('StreamEvents', { scope: 'events', version: 1 }, async function* () {
|
|
735
|
-
yield {
|
|
738
|
+
yield {};
|
|
736
739
|
});
|
|
737
740
|
builder.register(RPC, () => ({}), {
|
|
738
741
|
extendProcedureDoc: ({ procedure }) => ({
|
|
@@ -751,7 +754,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
751
754
|
const builder = new HonoStreamAppBuilder();
|
|
752
755
|
const RPC = Procedures();
|
|
753
756
|
RPC.CreateStream('Test', { scope: 'test', version: 1 }, async function* () {
|
|
754
|
-
yield {
|
|
757
|
+
yield {};
|
|
755
758
|
});
|
|
756
759
|
builder.register(RPC, () => ({}), {
|
|
757
760
|
extendProcedureDoc: () => ({
|
|
@@ -801,7 +804,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
801
804
|
const SSERPC = Procedures();
|
|
802
805
|
const TextRPC = Procedures();
|
|
803
806
|
SSERPC.CreateStream('SSEStream', { scope: 'sse', version: 1 }, async function* () {
|
|
804
|
-
yield {
|
|
807
|
+
yield { mode: 'sse' };
|
|
805
808
|
});
|
|
806
809
|
TextRPC.CreateStream('TextStream', { scope: 'text', version: 1 }, async function* () {
|
|
807
810
|
yield { mode: 'text' };
|
|
@@ -852,7 +855,7 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
852
855
|
const RPC = Procedures();
|
|
853
856
|
RPC.CreateStream('CheckPrevalidated', { scope: 'check', version: 1 }, async function* (ctx) {
|
|
854
857
|
receivedIsPrevalidated = ctx.isPrevalidated;
|
|
855
|
-
yield {
|
|
858
|
+
yield { ok: true };
|
|
856
859
|
});
|
|
857
860
|
builder.register(RPC, () => ({}));
|
|
858
861
|
const app = builder.build();
|
|
@@ -915,12 +918,12 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
915
918
|
// SSE Yield Shape Tests
|
|
916
919
|
// --------------------------------------------------------------------------
|
|
917
920
|
describe('SSE yield shape', () => {
|
|
918
|
-
test('custom event names
|
|
921
|
+
test('custom event names via sse() helper', async () => {
|
|
919
922
|
const builder = new HonoStreamAppBuilder();
|
|
920
923
|
const RPC = Procedures();
|
|
921
924
|
RPC.CreateStream('Events', { scope: 'events', version: 1 }, async function* () {
|
|
922
|
-
yield {
|
|
923
|
-
yield {
|
|
925
|
+
yield sse({ type: 'user_joined' }, { event: 'join' });
|
|
926
|
+
yield sse({ type: 'message' }, { event: 'chat' });
|
|
924
927
|
});
|
|
925
928
|
builder.register(RPC, () => ({}));
|
|
926
929
|
const app = builder.build();
|
|
@@ -930,12 +933,12 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
930
933
|
expect(text).toContain('event: chat');
|
|
931
934
|
expect(text).not.toContain('event: Events');
|
|
932
935
|
});
|
|
933
|
-
test('custom id
|
|
936
|
+
test('custom id via sse() helper', async () => {
|
|
934
937
|
const builder = new HonoStreamAppBuilder();
|
|
935
938
|
const RPC = Procedures();
|
|
936
939
|
RPC.CreateStream('Events', { scope: 'events', version: 1 }, async function* () {
|
|
937
|
-
yield {
|
|
938
|
-
yield {
|
|
940
|
+
yield sse({ msg: 'first' }, { id: 'msg-001' });
|
|
941
|
+
yield sse({ msg: 'second' }, { id: 'msg-002' });
|
|
939
942
|
});
|
|
940
943
|
builder.register(RPC, () => ({}));
|
|
941
944
|
const app = builder.build();
|
|
@@ -948,8 +951,8 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
948
951
|
const builder = new HonoStreamAppBuilder();
|
|
949
952
|
const RPC = Procedures();
|
|
950
953
|
RPC.CreateStream('Events', { scope: 'events', version: 1 }, async function* () {
|
|
951
|
-
yield
|
|
952
|
-
yield {
|
|
954
|
+
yield 'already a string';
|
|
955
|
+
yield { needs: 'stringify' };
|
|
953
956
|
});
|
|
954
957
|
builder.register(RPC, () => ({}));
|
|
955
958
|
const app = builder.build();
|
|
@@ -964,9 +967,9 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
964
967
|
const builder = new HonoStreamAppBuilder();
|
|
965
968
|
const RPC = Procedures();
|
|
966
969
|
RPC.CreateStream('MyProcedure', { scope: 'test', version: 1 }, async function* () {
|
|
967
|
-
yield {
|
|
968
|
-
yield {
|
|
969
|
-
yield {
|
|
970
|
+
yield { value: 1 };
|
|
971
|
+
yield sse({ value: 2 }, { event: 'custom' });
|
|
972
|
+
yield { value: 3 };
|
|
970
973
|
});
|
|
971
974
|
builder.register(RPC, () => ({}));
|
|
972
975
|
const app = builder.build();
|
|
@@ -983,6 +986,84 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
983
986
|
});
|
|
984
987
|
});
|
|
985
988
|
// --------------------------------------------------------------------------
|
|
989
|
+
// sse() Helper Tests
|
|
990
|
+
// --------------------------------------------------------------------------
|
|
991
|
+
describe('sse() helper', () => {
|
|
992
|
+
test('tagged yields with custom event/id/retry', async () => {
|
|
993
|
+
const builder = new HonoStreamAppBuilder();
|
|
994
|
+
const RPC = Procedures();
|
|
995
|
+
RPC.CreateStream('Tagged', { scope: 'tagged', version: 1 }, async function* () {
|
|
996
|
+
yield sse({ count: 1 }, { event: 'tick', id: 'evt-1', retry: 5000 });
|
|
997
|
+
});
|
|
998
|
+
builder.register(RPC, () => ({}));
|
|
999
|
+
const app = builder.build();
|
|
1000
|
+
const res = await app.request('/tagged/tagged/1');
|
|
1001
|
+
const text = await res.text();
|
|
1002
|
+
expect(text).toContain('event: tick');
|
|
1003
|
+
expect(text).toContain('id: evt-1');
|
|
1004
|
+
expect(text).toContain('retry: 5000');
|
|
1005
|
+
expect(text).toContain('data: {"count":1}');
|
|
1006
|
+
});
|
|
1007
|
+
test('plain domain objects use procedure name and auto-incremented id', async () => {
|
|
1008
|
+
const builder = new HonoStreamAppBuilder();
|
|
1009
|
+
const RPC = Procedures();
|
|
1010
|
+
RPC.CreateStream('Plain', { scope: 'plain', version: 1 }, async function* () {
|
|
1011
|
+
yield { a: 1 };
|
|
1012
|
+
yield { a: 2 };
|
|
1013
|
+
});
|
|
1014
|
+
builder.register(RPC, () => ({}));
|
|
1015
|
+
const app = builder.build();
|
|
1016
|
+
const res = await app.request('/plain/plain/1');
|
|
1017
|
+
const text = await res.text();
|
|
1018
|
+
const messages = text.split('\n\n').filter(Boolean);
|
|
1019
|
+
expect(messages[0]).toContain('event: Plain');
|
|
1020
|
+
expect(messages[0]).toContain('id: 0');
|
|
1021
|
+
expect(messages[0]).toContain('data: {"a":1}');
|
|
1022
|
+
expect(messages[1]).toContain('event: Plain');
|
|
1023
|
+
expect(messages[1]).toContain('id: 1');
|
|
1024
|
+
expect(messages[1]).toContain('data: {"a":2}');
|
|
1025
|
+
});
|
|
1026
|
+
test('sse() metadata is invisible in text mode', async () => {
|
|
1027
|
+
const builder = new HonoStreamAppBuilder({ defaultStreamMode: 'text' });
|
|
1028
|
+
const RPC = Procedures();
|
|
1029
|
+
RPC.CreateStream('TextTagged', { scope: 'text', version: 1 }, async function* () {
|
|
1030
|
+
yield sse({ count: 1 }, { event: 'tick' });
|
|
1031
|
+
yield { count: 2 };
|
|
1032
|
+
});
|
|
1033
|
+
builder.register(RPC, () => ({}));
|
|
1034
|
+
const app = builder.build();
|
|
1035
|
+
const res = await app.request('/text/text-tagged/1');
|
|
1036
|
+
const text = await res.text();
|
|
1037
|
+
const lines = text.trim().split('\n');
|
|
1038
|
+
// Text mode just JSON-stringifies — sse() metadata is not visible
|
|
1039
|
+
expect(JSON.parse(lines[0])).toEqual({ count: 1 });
|
|
1040
|
+
expect(JSON.parse(lines[1])).toEqual({ count: 2 });
|
|
1041
|
+
});
|
|
1042
|
+
test('sse() with partial options', async () => {
|
|
1043
|
+
const builder = new HonoStreamAppBuilder();
|
|
1044
|
+
const RPC = Procedures();
|
|
1045
|
+
RPC.CreateStream('Partial', { scope: 'partial', version: 1 }, async function* () {
|
|
1046
|
+
yield sse({ v: 1 }, { event: 'custom' });
|
|
1047
|
+
yield sse({ v: 2 }, { id: 'my-id' });
|
|
1048
|
+
yield sse({ v: 3 });
|
|
1049
|
+
});
|
|
1050
|
+
builder.register(RPC, () => ({}));
|
|
1051
|
+
const app = builder.build();
|
|
1052
|
+
const res = await app.request('/partial/partial/1');
|
|
1053
|
+
const text = await res.text();
|
|
1054
|
+
const messages = text.split('\n\n').filter(Boolean);
|
|
1055
|
+
// First: custom event, auto id
|
|
1056
|
+
expect(messages[0]).toContain('event: custom');
|
|
1057
|
+
expect(messages[0]).toContain('id: 0');
|
|
1058
|
+
// Second: default event, custom id
|
|
1059
|
+
expect(messages[1]).toContain('event: Partial');
|
|
1060
|
+
expect(messages[1]).toContain('id: my-id');
|
|
1061
|
+
// Third: sse() with no options — same as plain object (defaults)
|
|
1062
|
+
expect(messages[2]).toContain('event: Partial');
|
|
1063
|
+
expect(messages[2]).toContain('id: 2');
|
|
1064
|
+
});
|
|
1065
|
+
});
|
|
1066
|
+
// --------------------------------------------------------------------------
|
|
986
1067
|
// Integration Test
|
|
987
1068
|
// --------------------------------------------------------------------------
|
|
988
1069
|
describe('integration', () => {
|
|
@@ -1010,8 +1091,8 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
1010
1091
|
defaultStreamMode: 'text',
|
|
1011
1092
|
onRequestStart: () => events.push('request-start'),
|
|
1012
1093
|
onRequestEnd: () => events.push('request-end'),
|
|
1013
|
-
onStreamStart: (
|
|
1014
|
-
onStreamEnd: (
|
|
1094
|
+
onStreamStart: () => events.push('stream-start'),
|
|
1095
|
+
onStreamEnd: () => events.push('stream-end'),
|
|
1015
1096
|
});
|
|
1016
1097
|
builder.register(RPC, (c) => ({
|
|
1017
1098
|
userId: c.req.header('x-user-id') || 'anonymous',
|
|
@@ -1033,8 +1114,8 @@ describe('HonoStreamAppBuilder', () => {
|
|
|
1033
1114
|
expect(JSON.parse(lines[1])).toEqual({ id: 2, message: 'Notification 2 for user-123' });
|
|
1034
1115
|
// Verify hooks were called
|
|
1035
1116
|
expect(events).toContain('request-start');
|
|
1036
|
-
expect(events).toContain('stream-start
|
|
1037
|
-
expect(events).toContain('stream-end
|
|
1117
|
+
expect(events).toContain('stream-start');
|
|
1118
|
+
expect(events).toContain('stream-end');
|
|
1038
1119
|
expect(events).toContain('request-end');
|
|
1039
1120
|
});
|
|
1040
1121
|
});
|