ts-procedures 5.7.2 → 5.9.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 +7 -1051
- package/agent_config/claude-code/skills/guide/api-reference.md +21 -16
- package/agent_config/claude-code/skills/guide/patterns.md +3 -1
- package/agent_config/copilot/copilot-instructions.md +7 -5
- package/agent_config/cursor/cursorrules +7 -5
- package/build/codegen/bin/cli.d.ts +2 -0
- package/build/codegen/bin/cli.js +21 -10
- package/build/codegen/bin/cli.js.map +1 -1
- package/build/codegen/bin/cli.test.js +44 -2
- package/build/codegen/bin/cli.test.js.map +1 -1
- package/build/codegen/emit-errors.d.ts +4 -1
- package/build/codegen/emit-errors.js +11 -5
- package/build/codegen/emit-errors.js.map +1 -1
- package/build/codegen/emit-errors.test.js +37 -0
- package/build/codegen/emit-errors.test.js.map +1 -1
- package/build/codegen/emit-index.d.ts +3 -1
- package/build/codegen/emit-index.js +6 -13
- package/build/codegen/emit-index.js.map +1 -1
- package/build/codegen/emit-index.test.js +23 -0
- package/build/codegen/emit-index.test.js.map +1 -1
- package/build/codegen/emit-scope.js +17 -13
- package/build/codegen/emit-scope.js.map +1 -1
- package/build/codegen/emit-scope.test.js +166 -0
- package/build/codegen/emit-scope.test.js.map +1 -1
- package/build/codegen/index.d.ts +1 -0
- package/build/codegen/index.js +1 -0
- package/build/codegen/index.js.map +1 -1
- package/build/codegen/naming.d.ts +7 -0
- package/build/codegen/naming.js +21 -0
- package/build/codegen/naming.js.map +1 -0
- package/build/codegen/naming.test.d.ts +1 -0
- package/build/codegen/naming.test.js +40 -0
- package/build/codegen/naming.test.js.map +1 -0
- package/build/codegen/pipeline.d.ts +1 -0
- package/build/codegen/pipeline.js +7 -3
- package/build/codegen/pipeline.js.map +1 -1
- package/build/codegen/pipeline.test.js +60 -0
- package/build/codegen/pipeline.test.js.map +1 -1
- package/docs/ai-agent-setup.md +61 -0
- package/docs/client-and-codegen.md +193 -0
- package/docs/core.md +473 -0
- package/docs/http-integrations.md +183 -0
- package/docs/streaming.md +199 -0
- package/docs/superpowers/plans/2026-03-30-client-codegen.md +2833 -0
- package/docs/superpowers/specs/2026-03-30-client-codegen-design.md +632 -0
- package/package.json +6 -1
- package/src/implementations/http/README.md +324 -0
- package/src/implementations/http/express-rpc/README.md +281 -0
- package/src/implementations/http/hono-rpc/README.md +358 -0
- package/src/implementations/http/hono-stream/README.md +525 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
[Home](../README.md) | [Core](./core.md) | **Streaming** | [HTTP Integrations](./http-integrations.md) | [Client & Codegen](./client-and-codegen.md) | [AI Agent Setup](./ai-agent-setup.md)
|
|
2
|
+
|
|
3
|
+
# Streaming Procedures
|
|
4
|
+
|
|
5
|
+
Streaming procedures use async generators to yield values over time, enabling SSE (Server-Sent Events), HTTP streaming, and real-time data feeds.
|
|
6
|
+
|
|
7
|
+
For the `CreateStream` function signature and config options, see [Core Procedures](./core.md#createstream-function).
|
|
8
|
+
|
|
9
|
+
## Basic Streaming
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { Procedures } from 'ts-procedures'
|
|
13
|
+
import { v } from 'suretype'
|
|
14
|
+
|
|
15
|
+
const { CreateStream } = Procedures<{ userId: string }>()
|
|
16
|
+
|
|
17
|
+
const { StreamUpdates } = CreateStream(
|
|
18
|
+
'StreamUpdates',
|
|
19
|
+
{
|
|
20
|
+
description: 'Stream real-time updates',
|
|
21
|
+
schema: {
|
|
22
|
+
params: v.object({ topic: v.string().required() }),
|
|
23
|
+
yieldType: v.object({
|
|
24
|
+
id: v.string().required(),
|
|
25
|
+
message: v.string().required(),
|
|
26
|
+
timestamp: v.number().required(),
|
|
27
|
+
}),
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
async function* (ctx, params) {
|
|
31
|
+
// Types are inferred from schema:
|
|
32
|
+
// - params.topic: string
|
|
33
|
+
// - yield value must match { id, message, timestamp }
|
|
34
|
+
// - ctx.signal: AbortSignal for cancellation
|
|
35
|
+
|
|
36
|
+
let counter = 0
|
|
37
|
+
while (!ctx.signal.aborted) {
|
|
38
|
+
yield {
|
|
39
|
+
id: `${counter++}`,
|
|
40
|
+
message: `Update for ${params.topic}`,
|
|
41
|
+
timestamp: Date.now(),
|
|
42
|
+
}
|
|
43
|
+
await new Promise(r => setTimeout(r, 1000))
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
// Consume the stream
|
|
49
|
+
for await (const update of StreamUpdates({ userId: 'user-123' }, { topic: 'news' })) {
|
|
50
|
+
console.log(update.message)
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Yield Validation
|
|
55
|
+
|
|
56
|
+
By default, yielded values are not validated for performance. Enable validation with `validateYields: true`:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
const { ValidatedStream } = CreateStream(
|
|
60
|
+
'ValidatedStream',
|
|
61
|
+
{
|
|
62
|
+
schema: {
|
|
63
|
+
yieldType: v.object({ count: v.number().required() }),
|
|
64
|
+
},
|
|
65
|
+
validateYields: true, // Enable runtime validation of each yield
|
|
66
|
+
},
|
|
67
|
+
async function* () {
|
|
68
|
+
yield { count: 1 } // Valid
|
|
69
|
+
yield { count: 2 } // Valid
|
|
70
|
+
// yield { count: 'invalid' } // Would throw ProcedureYieldValidationError
|
|
71
|
+
},
|
|
72
|
+
)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Abort Signal Integration
|
|
76
|
+
|
|
77
|
+
For abort signal behavior in regular (non-streaming) procedures, see [Core Procedures — Abort Signal](./core.md#abort-signal).
|
|
78
|
+
|
|
79
|
+
The `ctx.signal` allows stream handlers to detect when consumers stop iterating. After completion, `signal.reason` indicates why the stream ended:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
const { CancellableStream } = CreateStream(
|
|
83
|
+
'CancellableStream',
|
|
84
|
+
{},
|
|
85
|
+
async function* (ctx) {
|
|
86
|
+
try {
|
|
87
|
+
while (!ctx.signal.aborted) {
|
|
88
|
+
yield await fetchNextItem()
|
|
89
|
+
}
|
|
90
|
+
} finally {
|
|
91
|
+
// Distinguish normal completion from client disconnect
|
|
92
|
+
if (ctx.signal.reason === 'stream-completed') {
|
|
93
|
+
// Stream finished normally
|
|
94
|
+
} else {
|
|
95
|
+
// Client disconnected or external abort
|
|
96
|
+
}
|
|
97
|
+
await cleanup()
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
// Consumer can break early - signal.aborted becomes true
|
|
103
|
+
for await (const item of CancellableStream({}, {})) {
|
|
104
|
+
if (shouldStop) break // Triggers abort
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## SSE Integration Example
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import express from 'express'
|
|
112
|
+
import { Procedures } from 'ts-procedures'
|
|
113
|
+
|
|
114
|
+
const app = express()
|
|
115
|
+
|
|
116
|
+
const { CreateStream, getProcedures } = Procedures<{ req: express.Request }>({
|
|
117
|
+
onCreate: (proc) => {
|
|
118
|
+
if (proc.isStream) {
|
|
119
|
+
// Register streaming procedures as SSE endpoints
|
|
120
|
+
app.get(`/stream/${proc.name}`, async (req, res) => {
|
|
121
|
+
res.writeHead(200, {
|
|
122
|
+
'Content-Type': 'text/event-stream',
|
|
123
|
+
'Cache-Control': 'no-cache',
|
|
124
|
+
'Connection': 'keep-alive',
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
const generator = proc.handler({ req }, req.query)
|
|
128
|
+
|
|
129
|
+
req.on('close', async () => {
|
|
130
|
+
// Client disconnected - stop the generator
|
|
131
|
+
await generator.return(undefined)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
for await (const data of generator) {
|
|
136
|
+
res.write(`data: ${JSON.stringify(data)}\n\n`)
|
|
137
|
+
}
|
|
138
|
+
} finally {
|
|
139
|
+
res.end()
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
// Define a streaming procedure
|
|
147
|
+
CreateStream(
|
|
148
|
+
'LiveFeed',
|
|
149
|
+
{
|
|
150
|
+
schema: {
|
|
151
|
+
params: v.object({ channel: v.string() }),
|
|
152
|
+
yieldType: v.object({ event: v.string(), data: v.any() }),
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
async function* (ctx, params) {
|
|
156
|
+
while (!ctx.signal.aborted) {
|
|
157
|
+
const event = await pollForEvent(params.channel)
|
|
158
|
+
yield event
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
app.listen(3000)
|
|
164
|
+
// SSE endpoint: GET /stream/LiveFeed?channel=updates
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
For the built-in Hono streaming integration, see the [Hono Stream README](../src/implementations/http/hono-stream/README.md).
|
|
168
|
+
|
|
169
|
+
## Stream Errors
|
|
170
|
+
|
|
171
|
+
Streaming procedures support the same error handling as regular procedures (see [Error Handling](./core.md#error-handling)):
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
const { StreamWithErrors } = CreateStream(
|
|
175
|
+
'StreamWithErrors',
|
|
176
|
+
{},
|
|
177
|
+
async function* (ctx) {
|
|
178
|
+
yield { status: 'starting' }
|
|
179
|
+
|
|
180
|
+
const data = await fetchData()
|
|
181
|
+
if (!data) {
|
|
182
|
+
throw ctx.error('No data available', { code: 'NO_DATA' })
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
yield { status: 'complete', data }
|
|
186
|
+
},
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
for await (const item of StreamWithErrors({}, {})) {
|
|
191
|
+
console.log(item)
|
|
192
|
+
}
|
|
193
|
+
} catch (e) {
|
|
194
|
+
if (e instanceof ProcedureError) {
|
|
195
|
+
console.log(e.message) // 'No data available'
|
|
196
|
+
console.log(e.meta) // { code: 'NO_DATA' }
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|