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.
Files changed (50) hide show
  1. package/README.md +7 -1051
  2. package/agent_config/claude-code/skills/guide/api-reference.md +21 -16
  3. package/agent_config/claude-code/skills/guide/patterns.md +3 -1
  4. package/agent_config/copilot/copilot-instructions.md +7 -5
  5. package/agent_config/cursor/cursorrules +7 -5
  6. package/build/codegen/bin/cli.d.ts +2 -0
  7. package/build/codegen/bin/cli.js +21 -10
  8. package/build/codegen/bin/cli.js.map +1 -1
  9. package/build/codegen/bin/cli.test.js +44 -2
  10. package/build/codegen/bin/cli.test.js.map +1 -1
  11. package/build/codegen/emit-errors.d.ts +4 -1
  12. package/build/codegen/emit-errors.js +11 -5
  13. package/build/codegen/emit-errors.js.map +1 -1
  14. package/build/codegen/emit-errors.test.js +37 -0
  15. package/build/codegen/emit-errors.test.js.map +1 -1
  16. package/build/codegen/emit-index.d.ts +3 -1
  17. package/build/codegen/emit-index.js +6 -13
  18. package/build/codegen/emit-index.js.map +1 -1
  19. package/build/codegen/emit-index.test.js +23 -0
  20. package/build/codegen/emit-index.test.js.map +1 -1
  21. package/build/codegen/emit-scope.js +17 -13
  22. package/build/codegen/emit-scope.js.map +1 -1
  23. package/build/codegen/emit-scope.test.js +166 -0
  24. package/build/codegen/emit-scope.test.js.map +1 -1
  25. package/build/codegen/index.d.ts +1 -0
  26. package/build/codegen/index.js +1 -0
  27. package/build/codegen/index.js.map +1 -1
  28. package/build/codegen/naming.d.ts +7 -0
  29. package/build/codegen/naming.js +21 -0
  30. package/build/codegen/naming.js.map +1 -0
  31. package/build/codegen/naming.test.d.ts +1 -0
  32. package/build/codegen/naming.test.js +40 -0
  33. package/build/codegen/naming.test.js.map +1 -0
  34. package/build/codegen/pipeline.d.ts +1 -0
  35. package/build/codegen/pipeline.js +7 -3
  36. package/build/codegen/pipeline.js.map +1 -1
  37. package/build/codegen/pipeline.test.js +60 -0
  38. package/build/codegen/pipeline.test.js.map +1 -1
  39. package/docs/ai-agent-setup.md +61 -0
  40. package/docs/client-and-codegen.md +193 -0
  41. package/docs/core.md +473 -0
  42. package/docs/http-integrations.md +183 -0
  43. package/docs/streaming.md +199 -0
  44. package/docs/superpowers/plans/2026-03-30-client-codegen.md +2833 -0
  45. package/docs/superpowers/specs/2026-03-30-client-codegen-design.md +632 -0
  46. package/package.json +6 -1
  47. package/src/implementations/http/README.md +324 -0
  48. package/src/implementations/http/express-rpc/README.md +281 -0
  49. package/src/implementations/http/hono-rpc/README.md +358 -0
  50. 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
+ ```