ts-procedures 3.2.0 → 3.3.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/README.md +222 -2
- package/build/errors.d.ts +5 -0
- package/build/errors.js +14 -0
- package/build/errors.js.map +1 -1
- package/build/implementations/http/hono-stream/index.d.ts +92 -0
- package/build/implementations/http/hono-stream/index.js +229 -0
- package/build/implementations/http/hono-stream/index.js.map +1 -0
- package/build/implementations/http/hono-stream/index.test.d.ts +1 -0
- package/build/implementations/http/hono-stream/index.test.js +681 -0
- package/build/implementations/http/hono-stream/index.test.js.map +1 -0
- package/build/implementations/http/hono-stream/types.d.ts +24 -0
- package/build/implementations/http/hono-stream/types.js +2 -0
- package/build/implementations/http/hono-stream/types.js.map +1 -0
- package/build/implementations/types.d.ts +15 -1
- package/build/index.d.ts +62 -3
- package/build/index.js +96 -1
- package/build/index.js.map +1 -1
- package/build/index.test.js +283 -2
- package/build/index.test.js.map +1 -1
- package/build/schema/compute-schema.d.ts +6 -1
- package/build/schema/compute-schema.js +4 -1
- package/build/schema/compute-schema.js.map +1 -1
- package/build/schema/parser.d.ts +6 -0
- package/build/schema/parser.js +42 -0
- package/build/schema/parser.js.map +1 -1
- package/build/schema/types.d.ts +1 -0
- package/package.json +1 -1
- package/src/errors.ts +16 -0
- package/src/implementations/http/README.md +87 -55
- package/src/implementations/http/hono-stream/README.md +261 -0
- package/src/implementations/http/hono-stream/index.test.ts +1009 -0
- package/src/implementations/http/hono-stream/index.ts +327 -0
- package/src/implementations/http/hono-stream/types.ts +29 -0
- package/src/implementations/types.ts +17 -1
- package/src/index.test.ts +402 -44
- package/src/index.ts +189 -3
- package/src/schema/compute-schema.ts +10 -3
- package/src/schema/parser.ts +55 -4
- package/src/schema/types.ts +4 -0
|
@@ -1,19 +1,38 @@
|
|
|
1
|
-
# HTTP
|
|
1
|
+
# HTTP Implementations
|
|
2
2
|
|
|
3
|
-
HTTP
|
|
3
|
+
HTTP implementation builders for `ts-procedures` that create type-safe, versioned endpoints with automatic path generation, schema-based validation, and route documentation.
|
|
4
4
|
|
|
5
5
|
## Available Implementations
|
|
6
6
|
|
|
7
|
+
### RPC (Request/Response)
|
|
8
|
+
|
|
9
|
+
For procedures created with `Create()` - standard request/response pattern using POST.
|
|
10
|
+
|
|
11
|
+
| Framework | Package | Description |
|
|
12
|
+
|-----------|---------|-------------|
|
|
13
|
+
| [Express RPC](./express-rpc/README.md) | `express-rpc` | Express.js integration |
|
|
14
|
+
| [Hono RPC](./hono-rpc/README.md) | `hono-rpc` | Hono integration (Bun, Deno, Cloudflare Workers, Node.js) |
|
|
15
|
+
|
|
16
|
+
### Streaming
|
|
17
|
+
|
|
18
|
+
For procedures created with `CreateStream()` - server-sent events and streaming responses.
|
|
19
|
+
|
|
7
20
|
| Framework | Package | Description |
|
|
8
21
|
|-----------|---------|-------------|
|
|
9
|
-
| [
|
|
10
|
-
|
|
22
|
+
| [Hono Stream](./hono-stream/README.md) | `hono-stream` | SSE and text streaming for async generators |
|
|
23
|
+
|
|
24
|
+
## Procedure Types
|
|
25
|
+
|
|
26
|
+
| Type | Created With | Handler Return | HTTP Methods | Use Case |
|
|
27
|
+
|------|--------------|----------------|--------------|----------|
|
|
28
|
+
| RPC | `Create()` | `Promise<T>` | POST | Standard request/response |
|
|
29
|
+
| Stream | `CreateStream()` | `AsyncGenerator<T>` | GET, POST | Real-time updates, SSE |
|
|
11
30
|
|
|
12
31
|
## Core Concepts
|
|
13
32
|
|
|
14
|
-
###
|
|
33
|
+
### Config Interface
|
|
15
34
|
|
|
16
|
-
All HTTP
|
|
35
|
+
All HTTP implementations use a shared configuration interface:
|
|
17
36
|
|
|
18
37
|
```typescript
|
|
19
38
|
interface RPCConfig {
|
|
@@ -24,13 +43,13 @@ interface RPCConfig {
|
|
|
24
43
|
|
|
25
44
|
### Path Generation
|
|
26
45
|
|
|
27
|
-
Routes are generated using kebab-case conversion
|
|
46
|
+
Routes are generated using kebab-case conversion:
|
|
28
47
|
|
|
29
48
|
```
|
|
30
49
|
/{pathPrefix}/{scope...}/{procedureName}/{version}
|
|
31
50
|
```
|
|
32
51
|
|
|
33
|
-
**
|
|
52
|
+
**Examples:**
|
|
34
53
|
|
|
35
54
|
| Scope | Procedure Name | Version | Generated Path |
|
|
36
55
|
|-------|----------------|---------|----------------|
|
|
@@ -46,85 +65,98 @@ Routes are generated using kebab-case conversion with the formula:
|
|
|
46
65
|
| `'users'` | `'Create'` | `1` | `/api/v1/users/create/1` |
|
|
47
66
|
| `['users', 'admin']` | `'Delete'` | `2` | `/api/v1/users/admin/delete/2` |
|
|
48
67
|
|
|
49
|
-
### Context Resolution
|
|
68
|
+
### Context Resolution
|
|
50
69
|
|
|
51
70
|
The `factoryContext` parameter supports three patterns:
|
|
52
71
|
|
|
53
72
|
```typescript
|
|
54
73
|
// 1. Static object
|
|
55
|
-
builder.register(
|
|
74
|
+
builder.register(Factory, { userId: 'static-123' })
|
|
56
75
|
|
|
57
76
|
// 2. Sync function
|
|
58
|
-
builder.register(
|
|
59
|
-
userId: req.
|
|
77
|
+
builder.register(Factory, (c) => ({
|
|
78
|
+
userId: c.req.header('x-user-id')
|
|
60
79
|
}))
|
|
61
80
|
|
|
62
81
|
// 3. Async function
|
|
63
|
-
builder.register(
|
|
64
|
-
const user = await validateToken(req.
|
|
82
|
+
builder.register(Factory, async (c) => {
|
|
83
|
+
const user = await validateToken(c.req.header('authorization'))
|
|
65
84
|
return { userId: user.id }
|
|
66
85
|
})
|
|
67
86
|
```
|
|
68
87
|
|
|
69
88
|
### Lifecycle Hooks
|
|
70
89
|
|
|
71
|
-
|
|
90
|
+
**RPC Implementations:**
|
|
72
91
|
|
|
73
92
|
```
|
|
74
93
|
onRequestStart → handler → onSuccess → onRequestEnd
|
|
75
94
|
↓
|
|
76
95
|
(on error)
|
|
77
96
|
↓
|
|
78
|
-
|
|
97
|
+
onError handler
|
|
79
98
|
↓
|
|
80
|
-
|
|
99
|
+
onRequestEnd
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Stream Implementations:**
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
onRequestStart → onStreamStart → [yields...] → onStreamEnd → onRequestEnd
|
|
106
|
+
↓
|
|
107
|
+
(on error)
|
|
108
|
+
↓
|
|
109
|
+
error in stream
|
|
110
|
+
↓
|
|
111
|
+
onStreamEnd
|
|
81
112
|
```
|
|
82
113
|
|
|
83
|
-
| Hook |
|
|
84
|
-
|
|
85
|
-
| `onRequestStart` | Before route handler |
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
114
|
+
| Hook | Available In | Trigger |
|
|
115
|
+
|------|--------------|---------|
|
|
116
|
+
| `onRequestStart` | Both | Before route handler |
|
|
117
|
+
| `onRequestEnd` | Both | After response sent |
|
|
118
|
+
| `onSuccess` | RPC | After successful handler |
|
|
119
|
+
| `onError` | RPC | On handler error |
|
|
120
|
+
| `onStreamStart` | Stream | Before first yield |
|
|
121
|
+
| `onStreamEnd` | Stream | After stream completes |
|
|
122
|
+
| `onStreamError` | Stream | On stream error |
|
|
89
123
|
|
|
90
124
|
### Route Documentation
|
|
91
125
|
|
|
92
|
-
Each registered procedure generates
|
|
126
|
+
Each registered procedure generates documentation accessible via `builder.docs`.
|
|
127
|
+
|
|
128
|
+
**RPC Documentation (`RPCHttpRouteDoc`):**
|
|
93
129
|
|
|
94
130
|
```typescript
|
|
95
131
|
interface RPCHttpRouteDoc {
|
|
96
|
-
|
|
97
|
-
|
|
132
|
+
name: string
|
|
133
|
+
path: string
|
|
134
|
+
method: 'post'
|
|
135
|
+
scope: string | string[]
|
|
136
|
+
version: number
|
|
98
137
|
jsonSchema: {
|
|
99
|
-
body?: object
|
|
100
|
-
response?: object
|
|
138
|
+
body?: object // From schema.params
|
|
139
|
+
response?: object // From schema.returnType
|
|
101
140
|
}
|
|
102
141
|
}
|
|
103
142
|
```
|
|
104
143
|
|
|
105
|
-
|
|
144
|
+
**Stream Documentation (`StreamHttpRouteDoc`):**
|
|
106
145
|
|
|
107
146
|
```typescript
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
responses: {
|
|
120
|
-
200: doc.jsonSchema.response ? {
|
|
121
|
-
content: { 'application/json': { schema: doc.jsonSchema.response } }
|
|
122
|
-
} : undefined
|
|
123
|
-
}
|
|
124
|
-
}
|
|
147
|
+
interface StreamHttpRouteDoc {
|
|
148
|
+
name: string
|
|
149
|
+
path: string
|
|
150
|
+
methods: ('get' | 'post')[]
|
|
151
|
+
streamMode: 'sse' | 'text'
|
|
152
|
+
scope: string | string[]
|
|
153
|
+
version: number
|
|
154
|
+
jsonSchema: {
|
|
155
|
+
params?: object // From schema.params
|
|
156
|
+
yieldType?: object // From schema.yieldType
|
|
157
|
+
returnType?: object // From schema.returnType
|
|
125
158
|
}
|
|
126
|
-
|
|
127
|
-
}, {})
|
|
159
|
+
}
|
|
128
160
|
```
|
|
129
161
|
|
|
130
162
|
### Builder Pattern
|
|
@@ -132,9 +164,9 @@ const openApiPaths = builder.docs.reduce((acc, doc) => {
|
|
|
132
164
|
All implementations follow the same builder pattern:
|
|
133
165
|
|
|
134
166
|
```typescript
|
|
135
|
-
const builder = new
|
|
136
|
-
.register(
|
|
137
|
-
.register(
|
|
167
|
+
const builder = new AppBuilder(config)
|
|
168
|
+
.register(PublicFactory, publicContextResolver)
|
|
169
|
+
.register(ProtectedFactory, protectedContextResolver)
|
|
138
170
|
|
|
139
171
|
const app = builder.build()
|
|
140
172
|
const docs = builder.docs
|
|
@@ -144,16 +176,15 @@ const docs = builder.docs
|
|
|
144
176
|
|
|
145
177
|
| Method | Returns | Description |
|
|
146
178
|
|--------|---------|-------------|
|
|
147
|
-
| `register(factory, context)` | `this` | Register a procedure factory
|
|
179
|
+
| `register(factory, context, options?)` | `this` | Register a procedure factory |
|
|
148
180
|
| `build()` | Framework app | Create routes and return the application |
|
|
149
|
-
| `makeRPCHttpRoutePath(config)` | `string` | Generate path for an RPCConfig |
|
|
150
181
|
|
|
151
182
|
**Properties:**
|
|
152
183
|
|
|
153
184
|
| Property | Type | Description |
|
|
154
185
|
|----------|------|-------------|
|
|
155
186
|
| `app` | Framework app | The underlying framework application |
|
|
156
|
-
| `docs` |
|
|
187
|
+
| `docs` | Route doc array | Route documentation (populated after `build()`) |
|
|
157
188
|
|
|
158
189
|
## Framework Comparison
|
|
159
190
|
|
|
@@ -164,9 +195,10 @@ const docs = builder.docs
|
|
|
164
195
|
| Body access | `req.body` | `await c.req.json()` |
|
|
165
196
|
| Header access | `req.headers['x-id']` | `c.req.header('x-id')` |
|
|
166
197
|
| JSON middleware | Auto-added (or manual) | Built-in |
|
|
198
|
+
| Streaming support | Not yet | `hono-stream` |
|
|
167
199
|
|
|
168
200
|
## TypeScript Types
|
|
169
201
|
|
|
170
202
|
```typescript
|
|
171
|
-
import { RPCConfig, RPCHttpRouteDoc } from 'ts-procedures/
|
|
203
|
+
import { RPCConfig, RPCHttpRouteDoc, StreamHttpRouteDoc, StreamMode } from 'ts-procedures/implementations/types'
|
|
172
204
|
```
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# Hono Stream Implementation
|
|
2
|
+
|
|
3
|
+
HTTP streaming and SSE endpoints for streaming procedures created with `CreateStream`.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`HonoStreamAppBuilder` provides a builder pattern for creating streaming HTTP endpoints in Hono. It handles `AsyncGenerator` handlers and supports both Server-Sent Events (SSE) and plain text streaming modes.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Requires `hono` as a peer dependency:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install hono
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { Procedures } from 'ts-procedures'
|
|
21
|
+
import { HonoStreamAppBuilder } from 'ts-procedures/implementations/http/hono-stream'
|
|
22
|
+
|
|
23
|
+
// Define your context and config types
|
|
24
|
+
type StreamContext = { userId: string }
|
|
25
|
+
interface RPCConfig {
|
|
26
|
+
scope: string | string[]
|
|
27
|
+
version: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Create a procedures factory
|
|
31
|
+
const StreamRPC = Procedures<StreamContext, RPCConfig>()
|
|
32
|
+
|
|
33
|
+
// Create a streaming procedure
|
|
34
|
+
StreamRPC.CreateStream(
|
|
35
|
+
'WatchNotifications',
|
|
36
|
+
{
|
|
37
|
+
scope: ['user', 'notifications'],
|
|
38
|
+
version: 1,
|
|
39
|
+
schema: {
|
|
40
|
+
yieldType: v.object({ id: v.number(), message: v.string() }),
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
async function* (ctx) {
|
|
44
|
+
for (let i = 1; i <= 10; i++) {
|
|
45
|
+
yield { id: i, message: `Notification ${i}` }
|
|
46
|
+
await new Promise((r) => setTimeout(r, 1000))
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
// Build the Hono app
|
|
52
|
+
const builder = new HonoStreamAppBuilder()
|
|
53
|
+
.register(StreamRPC, (c) => ({
|
|
54
|
+
userId: c.req.header('x-user-id') || 'anonymous',
|
|
55
|
+
}))
|
|
56
|
+
|
|
57
|
+
const app = builder.build()
|
|
58
|
+
|
|
59
|
+
// Access documentation
|
|
60
|
+
const docs = builder.docs
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## HTTP Methods
|
|
64
|
+
|
|
65
|
+
Both GET and POST methods are supported for each streaming endpoint:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
GET /{pathPrefix}/{scope...}/{procedureName}/{version}?param1=value1
|
|
69
|
+
POST /{pathPrefix}/{scope...}/{procedureName}/{version} (JSON body)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- **GET**: For EventSource/SSE clients, params passed via query string
|
|
73
|
+
- **POST**: For clients needing complex JSON body params
|
|
74
|
+
|
|
75
|
+
## Stream Modes
|
|
76
|
+
|
|
77
|
+
### SSE Mode (default)
|
|
78
|
+
|
|
79
|
+
Returns `text/event-stream` content type with SSE-formatted messages:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
const builder = new HonoStreamAppBuilder({ defaultStreamMode: 'sse' })
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Response format:
|
|
86
|
+
```
|
|
87
|
+
event: ProcedureName
|
|
88
|
+
data: {"key":"value"}
|
|
89
|
+
id: 0
|
|
90
|
+
|
|
91
|
+
event: ProcedureName
|
|
92
|
+
data: {"key":"value2"}
|
|
93
|
+
id: 1
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Client usage:
|
|
97
|
+
```typescript
|
|
98
|
+
const eventSource = new EventSource('/user/notifications/watch-notifications/1')
|
|
99
|
+
eventSource.addEventListener('WatchNotifications', (event) => {
|
|
100
|
+
const data = JSON.parse(event.data)
|
|
101
|
+
console.log(data)
|
|
102
|
+
})
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Text Mode
|
|
106
|
+
|
|
107
|
+
Returns `text/plain` content type with newline-delimited JSON:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
const builder = new HonoStreamAppBuilder({ defaultStreamMode: 'text' })
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Response format:
|
|
114
|
+
```
|
|
115
|
+
{"key":"value"}
|
|
116
|
+
{"key":"value2"}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Client usage:
|
|
120
|
+
```typescript
|
|
121
|
+
const response = await fetch('/user/notifications/watch-notifications/1')
|
|
122
|
+
const reader = response.body.getReader()
|
|
123
|
+
const decoder = new TextDecoder()
|
|
124
|
+
|
|
125
|
+
while (true) {
|
|
126
|
+
const { done, value } = await reader.read()
|
|
127
|
+
if (done) break
|
|
128
|
+
const lines = decoder.decode(value).split('\n')
|
|
129
|
+
for (const line of lines) {
|
|
130
|
+
if (line) console.log(JSON.parse(line))
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Configuration
|
|
136
|
+
|
|
137
|
+
### Builder Config
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
interface HonoStreamAppBuilderConfig {
|
|
141
|
+
app?: Hono // Use existing Hono instance
|
|
142
|
+
pathPrefix?: string // Prefix for all routes
|
|
143
|
+
defaultStreamMode?: 'sse' | 'text' // Default: 'sse'
|
|
144
|
+
onRequestStart?: (c: Context) => void
|
|
145
|
+
onRequestEnd?: (c: Context) => void
|
|
146
|
+
onStreamStart?: (procedure: TStreamProcedureRegistration, c: Context) => void
|
|
147
|
+
onStreamEnd?: (procedure: TStreamProcedureRegistration, c: Context) => void
|
|
148
|
+
onStreamError?: (procedure, c, error) => Response | Promise<Response>
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Per-Factory Options
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
builder.register(factory, context, {
|
|
156
|
+
streamMode: 'text', // Override default mode for this factory
|
|
157
|
+
extendProcedureDoc: ({ base, procedure }) => ({
|
|
158
|
+
summary: 'Custom documentation',
|
|
159
|
+
tags: ['streaming'],
|
|
160
|
+
}),
|
|
161
|
+
})
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Lifecycle Hooks
|
|
165
|
+
|
|
166
|
+
Hooks execute in the following order:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
onRequestStart → onStreamStart → [yields...] → onStreamEnd → onRequestEnd
|
|
170
|
+
↓
|
|
171
|
+
(on error)
|
|
172
|
+
↓
|
|
173
|
+
error sent in stream
|
|
174
|
+
↓
|
|
175
|
+
onStreamEnd
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Error Handling
|
|
179
|
+
|
|
180
|
+
Errors during streaming are sent as special messages:
|
|
181
|
+
|
|
182
|
+
**SSE Mode:**
|
|
183
|
+
```
|
|
184
|
+
event: error
|
|
185
|
+
data: {"error":"Error message"}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Text Mode:**
|
|
189
|
+
```
|
|
190
|
+
{"error":"Error message"}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
For errors before streaming starts (e.g., context resolution), use `onStreamError`:
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
const builder = new HonoStreamAppBuilder({
|
|
197
|
+
onStreamError: (procedure, c, error) => {
|
|
198
|
+
return c.json({ error: error.message }, 500)
|
|
199
|
+
},
|
|
200
|
+
})
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Route Documentation
|
|
204
|
+
|
|
205
|
+
Access generated documentation via `builder.docs`:
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
interface StreamHttpRouteDoc {
|
|
209
|
+
name: string
|
|
210
|
+
path: string
|
|
211
|
+
methods: ('get' | 'post')[]
|
|
212
|
+
streamMode: 'sse' | 'text'
|
|
213
|
+
scope: string | string[]
|
|
214
|
+
version: number
|
|
215
|
+
jsonSchema: {
|
|
216
|
+
params?: object // From schema.params
|
|
217
|
+
yieldType?: object // From schema.yieldType
|
|
218
|
+
returnType?: object // From schema.returnType
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Procedure Filtering
|
|
224
|
+
|
|
225
|
+
Only streaming procedures (created with `CreateStream`) are registered. Regular procedures created with `Create` are ignored by this builder.
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
const RPC = Procedures<Context, RPCConfig>()
|
|
229
|
+
|
|
230
|
+
// This will NOT be registered by HonoStreamAppBuilder
|
|
231
|
+
RPC.Create('GetUser', config, async (ctx) => ({ user: 'data' }))
|
|
232
|
+
|
|
233
|
+
// This WILL be registered
|
|
234
|
+
RPC.CreateStream('WatchUser', config, async function* (ctx) {
|
|
235
|
+
yield { update: 'data' }
|
|
236
|
+
})
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Client Disconnect Handling
|
|
240
|
+
|
|
241
|
+
When a client disconnects, the stream's `onAbort` handler is triggered, which calls `generator.return()` to clean up. The `ctx.signal` in your handler will be aborted:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
RPC.CreateStream('LongStream', config, async function* (ctx) {
|
|
245
|
+
while (!ctx.signal.aborted) {
|
|
246
|
+
yield { tick: Date.now() }
|
|
247
|
+
await new Promise((r) => setTimeout(r, 1000))
|
|
248
|
+
}
|
|
249
|
+
})
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## TypeScript Types
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
import {
|
|
256
|
+
HonoStreamAppBuilder,
|
|
257
|
+
HonoStreamAppBuilderConfig,
|
|
258
|
+
StreamHttpRouteDoc,
|
|
259
|
+
StreamMode,
|
|
260
|
+
} from 'ts-procedures/implementations/http/hono-stream'
|
|
261
|
+
```
|