@durable-streams/client 0.2.0 → 0.2.2
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 +201 -8
- package/bin/intent.js +6 -0
- package/dist/index.cjs +624 -135
- package/dist/index.d.cts +139 -9
- package/dist/index.d.ts +139 -9
- package/dist/index.js +622 -136
- package/package.json +10 -3
- package/skills/getting-started/SKILL.md +223 -0
- package/skills/go-to-production/SKILL.md +243 -0
- package/skills/reading-streams/SKILL.md +247 -0
- package/skills/reading-streams/references/stream-response-methods.md +133 -0
- package/skills/server-deployment/SKILL.md +211 -0
- package/skills/writing-data/SKILL.md +311 -0
- package/src/constants.ts +19 -2
- package/src/error.ts +20 -0
- package/src/idempotent-producer.ts +144 -5
- package/src/index.ts +7 -0
- package/src/response.ts +376 -188
- package/src/sse.ts +10 -1
- package/src/stream-api.ts +13 -0
- package/src/stream-response-state.ts +306 -0
- package/src/stream.ts +147 -26
- package/src/types.ts +73 -0
- package/src/utils.ts +10 -1
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reading-streams
|
|
3
|
+
description: >
|
|
4
|
+
All stream reading patterns for @durable-streams/client. stream() function,
|
|
5
|
+
DurableStream.stream(), LiveMode (false, true, "long-poll", "sse"),
|
|
6
|
+
StreamResponse state machine, .json(), .text(), .jsonStream(), .textStream(),
|
|
7
|
+
.subscribeJson(), .subscribeBytes(), .subscribeText(), SSE resilience with
|
|
8
|
+
auto-fallback to long-poll, visibility-based pause, binary SSE base64
|
|
9
|
+
auto-decode, dynamic headers for auth token refresh, backoff config,
|
|
10
|
+
StreamErrorHandler onError for error recovery.
|
|
11
|
+
type: core
|
|
12
|
+
library: durable-streams
|
|
13
|
+
library_version: "0.2.1"
|
|
14
|
+
requires:
|
|
15
|
+
- getting-started
|
|
16
|
+
sources:
|
|
17
|
+
- "durable-streams/durable-streams:packages/client/src/stream-api.ts"
|
|
18
|
+
- "durable-streams/durable-streams:packages/client/src/response.ts"
|
|
19
|
+
- "durable-streams/durable-streams:packages/client/src/types.ts"
|
|
20
|
+
- "durable-streams/durable-streams:packages/client/src/fetch.ts"
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
This skill builds on durable-streams/getting-started. Read it first for setup and offset basics.
|
|
24
|
+
|
|
25
|
+
# Durable Streams — Reading Streams
|
|
26
|
+
|
|
27
|
+
Use `stream()` for read-only access (fetch-like API). Use `DurableStream.stream()`
|
|
28
|
+
when you already have a `DurableStream` handle for read/write operations. Both
|
|
29
|
+
return a `StreamResponse` with identical consumption methods.
|
|
30
|
+
|
|
31
|
+
## Setup
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { stream } from "@durable-streams/client"
|
|
35
|
+
|
|
36
|
+
// Catch-up read (returns all existing data, then stops)
|
|
37
|
+
const res = await stream<{ event: string; userId: string }>({
|
|
38
|
+
url: "https://your-server.com/v1/stream/my-stream",
|
|
39
|
+
offset: "-1",
|
|
40
|
+
live: false,
|
|
41
|
+
})
|
|
42
|
+
const items = await res.json()
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Core Patterns
|
|
46
|
+
|
|
47
|
+
### Live modes
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { stream } from "@durable-streams/client"
|
|
51
|
+
|
|
52
|
+
// Catch-up only — stop at end of existing data
|
|
53
|
+
const catchUp = await stream({ url, offset: "-1", live: false })
|
|
54
|
+
|
|
55
|
+
// Auto-select best transport (SSE for JSON, long-poll for binary)
|
|
56
|
+
const auto = await stream({ url, offset: "-1", live: true })
|
|
57
|
+
|
|
58
|
+
// Explicit long-poll
|
|
59
|
+
const longPoll = await stream({ url, offset: "-1", live: "long-poll" })
|
|
60
|
+
|
|
61
|
+
// Explicit SSE
|
|
62
|
+
const sse = await stream({ url, offset: "-1", live: "sse" })
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Dynamic headers for auth token refresh
|
|
66
|
+
|
|
67
|
+
Header functions are called **per-request**, allowing token refresh during long-lived live streams:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { stream } from "@durable-streams/client"
|
|
71
|
+
|
|
72
|
+
const res = await stream({
|
|
73
|
+
url: "https://your-server.com/v1/stream/my-stream",
|
|
74
|
+
offset: "-1",
|
|
75
|
+
live: true,
|
|
76
|
+
headers: {
|
|
77
|
+
Authorization: async () => `Bearer ${await getAccessToken()}`,
|
|
78
|
+
},
|
|
79
|
+
})
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Error recovery with onError
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { stream } from "@durable-streams/client"
|
|
86
|
+
|
|
87
|
+
const res = await stream({
|
|
88
|
+
url: "https://your-server.com/v1/stream/my-stream",
|
|
89
|
+
offset: "-1",
|
|
90
|
+
live: true,
|
|
91
|
+
onError: (error) => {
|
|
92
|
+
if (error.status === 401) {
|
|
93
|
+
// Refresh auth and retry with new headers
|
|
94
|
+
return { headers: { Authorization: `Bearer ${newToken}` } }
|
|
95
|
+
}
|
|
96
|
+
if (error.status === 404) {
|
|
97
|
+
return // Stop retrying (void = propagate error)
|
|
98
|
+
}
|
|
99
|
+
return {} // Retry with same params
|
|
100
|
+
},
|
|
101
|
+
})
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### SSE resilience with auto-fallback
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import { stream } from "@durable-streams/client"
|
|
108
|
+
|
|
109
|
+
const res = await stream({
|
|
110
|
+
url: "https://your-server.com/v1/stream/my-stream",
|
|
111
|
+
offset: "-1",
|
|
112
|
+
live: "sse",
|
|
113
|
+
sseResilience: {
|
|
114
|
+
minConnectionDuration: 1000, // Connections under 1s are "short"
|
|
115
|
+
maxShortConnections: 3, // Fall back after 3 short connections
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Common Mistakes
|
|
121
|
+
|
|
122
|
+
### CRITICAL Not saving offset for resumption
|
|
123
|
+
|
|
124
|
+
Wrong:
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
res.subscribeJson((batch) => {
|
|
128
|
+
processItems(batch.items)
|
|
129
|
+
// offset not saved!
|
|
130
|
+
})
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Correct:
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
res.subscribeJson((batch) => {
|
|
137
|
+
processItems(batch.items)
|
|
138
|
+
saveCheckpoint(batch.offset)
|
|
139
|
+
})
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The whole point of durable streams is resumability. Without persisting the offset, you lose the ability to resume after disconnect.
|
|
143
|
+
|
|
144
|
+
Source: README.md resume from offset section
|
|
145
|
+
|
|
146
|
+
### HIGH Using .json() on non-JSON content type streams
|
|
147
|
+
|
|
148
|
+
Wrong:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// Stream created with contentType: "text/plain"
|
|
152
|
+
const res = await stream({ url, offset: "-1", live: false })
|
|
153
|
+
const data = await res.json() // throws DurableStreamError!
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Correct:
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
const res = await stream({ url, offset: "-1", live: false })
|
|
160
|
+
const text = await res.text()
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`.json()`, `.jsonStream()`, and `.subscribeJson()` only work on JSON-mode streams (`contentType: "application/json"`). Use `.text()` or `.body()` for other content types.
|
|
164
|
+
|
|
165
|
+
Source: packages/client/src/response.ts
|
|
166
|
+
|
|
167
|
+
### HIGH Ignoring onError handler for live streams
|
|
168
|
+
|
|
169
|
+
Wrong:
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
const res = await stream({ url, offset: "-1", live: true })
|
|
173
|
+
// No onError — auth failures retry forever with exponential backoff
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Correct:
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const res = await stream({
|
|
180
|
+
url,
|
|
181
|
+
offset: "-1",
|
|
182
|
+
live: true,
|
|
183
|
+
onError: (error) => {
|
|
184
|
+
if (error.status === 401) return // Stop retrying
|
|
185
|
+
return {} // Retry for transient errors
|
|
186
|
+
},
|
|
187
|
+
})
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Without `onError`, permanent errors (401, 403) silently retry forever with exponential backoff.
|
|
191
|
+
|
|
192
|
+
Source: packages/client/src/types.ts StreamErrorHandler
|
|
193
|
+
|
|
194
|
+
### HIGH Returning void from onError to retry
|
|
195
|
+
|
|
196
|
+
Wrong:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
onError: (error) => {
|
|
200
|
+
console.log("retrying...")
|
|
201
|
+
// Returns undefined — error propagates instead of retrying!
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Correct:
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
onError: (error) => {
|
|
209
|
+
console.log("retrying...")
|
|
210
|
+
return {} // Return an object to signal retry
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The `onError` handler must return an object (`{}` or `{ headers, params }`) to signal retry. Returning `void`/`undefined` propagates the error.
|
|
215
|
+
|
|
216
|
+
Source: packages/client/src/types.ts RetryOpts
|
|
217
|
+
|
|
218
|
+
### MEDIUM Using HTTP instead of HTTPS in browser
|
|
219
|
+
|
|
220
|
+
Wrong:
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
const res = await stream({ url: "http://api.example.com/v1/stream/my-stream" })
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Correct:
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
const res = await stream({ url: "https://api.example.com/v1/stream/my-stream" })
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
HTTP/1.1 in browsers limits to ~6 concurrent connections per origin. With multiple live streams, this can freeze the app.
|
|
233
|
+
|
|
234
|
+
Source: packages/client/src/utils.ts warnIfUsingHttpInBrowser
|
|
235
|
+
|
|
236
|
+
## References
|
|
237
|
+
|
|
238
|
+
- [StreamResponse consumption methods](references/stream-response-methods.md)
|
|
239
|
+
|
|
240
|
+
## See also
|
|
241
|
+
|
|
242
|
+
- [getting-started](../getting-started/SKILL.md) — Basic setup and offset concepts
|
|
243
|
+
- [stream-db](../../../state/skills/stream-db/SKILL.md) — StreamDB uses stream reading internally
|
|
244
|
+
|
|
245
|
+
## Version
|
|
246
|
+
|
|
247
|
+
Targets @durable-streams/client v0.2.1.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# StreamResponse Consumption Methods
|
|
2
|
+
|
|
3
|
+
A `StreamResponse<TJson>` provides three families of consumption methods.
|
|
4
|
+
Choose one per response — calling a second method throws `ALREADY_CONSUMED`.
|
|
5
|
+
|
|
6
|
+
## Promise-Based (await for complete result)
|
|
7
|
+
|
|
8
|
+
Use for catch-up reads (`live: false`) or when you want all data at once.
|
|
9
|
+
|
|
10
|
+
### `.json(): Promise<TJson[]>`
|
|
11
|
+
|
|
12
|
+
Returns all items as a parsed JSON array. JSON-mode streams only.
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
const res = await stream<{ id: string }>({ url, offset: "-1", live: false })
|
|
16
|
+
const items = await res.json()
|
|
17
|
+
// items: Array<{ id: string }>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### `.text(): Promise<string>`
|
|
21
|
+
|
|
22
|
+
Returns the complete body as a string. Works with any content type.
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
const res = await stream({ url, offset: "-1", live: false })
|
|
26
|
+
const text = await res.text()
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### `.body(): Promise<Uint8Array>`
|
|
30
|
+
|
|
31
|
+
Returns the complete body as raw bytes.
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
const res = await stream({ url, offset: "-1", live: false })
|
|
35
|
+
const bytes = await res.body()
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## ReadableStream-Based (async iteration)
|
|
39
|
+
|
|
40
|
+
Use for processing items one at a time as they arrive.
|
|
41
|
+
|
|
42
|
+
### `.jsonStream(): ReadableStream<TJson>`
|
|
43
|
+
|
|
44
|
+
Yields individual JSON items. JSON-mode streams only.
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
const res = await stream<{ event: string }>({ url, offset: "-1", live: true })
|
|
48
|
+
for await (const item of res.jsonStream()) {
|
|
49
|
+
console.log(item.event)
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### `.textStream(): ReadableStream<string>`
|
|
54
|
+
|
|
55
|
+
Yields text chunks as they arrive.
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
const res = await stream({ url, offset: "-1", live: true })
|
|
59
|
+
for await (const chunk of res.textStream()) {
|
|
60
|
+
process.stdout.write(chunk)
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### `.bodyStream(): ReadableStream<Uint8Array>`
|
|
65
|
+
|
|
66
|
+
Yields raw byte chunks.
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
const res = await stream({ url, offset: "-1", live: true })
|
|
70
|
+
for await (const chunk of res.bodyStream()) {
|
|
71
|
+
processBytes(chunk)
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Subscriber-Based (callback with offset tracking)
|
|
76
|
+
|
|
77
|
+
Use for live subscriptions where you need the offset for checkpointing.
|
|
78
|
+
|
|
79
|
+
### `.subscribeJson(callback): () => void`
|
|
80
|
+
|
|
81
|
+
Calls back with batches of JSON items and offset.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
const res = await stream<{ event: string }>({ url, offset: "-1", live: true })
|
|
85
|
+
res.subscribeJson(async (batch) => {
|
|
86
|
+
for (const item of batch.items) {
|
|
87
|
+
console.log(item.event)
|
|
88
|
+
}
|
|
89
|
+
saveCheckpoint(batch.offset)
|
|
90
|
+
})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### `.subscribeText(callback): () => void`
|
|
94
|
+
|
|
95
|
+
Calls back with text data and offset.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
const res = await stream({ url, offset: "-1", live: true })
|
|
99
|
+
res.subscribeText(async (chunk) => {
|
|
100
|
+
appendToUI(chunk.text)
|
|
101
|
+
saveCheckpoint(chunk.offset)
|
|
102
|
+
})
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### `.subscribeBytes(callback): () => void`
|
|
106
|
+
|
|
107
|
+
Calls back with byte data and offset.
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
const res = await stream({ url, offset: "-1", live: true })
|
|
111
|
+
res.subscribeBytes(async (chunk) => {
|
|
112
|
+
processBytes(chunk.data)
|
|
113
|
+
saveCheckpoint(chunk.offset)
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Properties
|
|
118
|
+
|
|
119
|
+
| Property | Type | Description |
|
|
120
|
+
| -------------- | --------------------- | ----------------------------------------- |
|
|
121
|
+
| `offset` | `string` | Current offset (updates after each chunk) |
|
|
122
|
+
| `contentType` | `string \| undefined` | Content type from stream creation |
|
|
123
|
+
| `live` | `LiveMode` | The live mode for this session |
|
|
124
|
+
| `startOffset` | `string` | The offset this session started from |
|
|
125
|
+
| `upToDate` | `boolean` | Whether caught up with all existing data |
|
|
126
|
+
| `streamClosed` | `boolean` | Whether the stream is permanently closed |
|
|
127
|
+
| `closed` | `Promise<void>` | Resolves when the stream session ends |
|
|
128
|
+
|
|
129
|
+
## Methods
|
|
130
|
+
|
|
131
|
+
| Method | Description |
|
|
132
|
+
| ---------- | ------------------------- |
|
|
133
|
+
| `cancel()` | Cancel the stream session |
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: server-deployment
|
|
3
|
+
description: >
|
|
4
|
+
Running durable stream servers. DurableStreamTestServer for development
|
|
5
|
+
(Node.js, @durable-streams/server, not for production), Caddy plugin for
|
|
6
|
+
production with Caddyfile configuration, data_dir for file-backed persistence,
|
|
7
|
+
max_file_handles tuning, long_poll_timeout, server binary downloads for macOS
|
|
8
|
+
Linux Windows, @durable-streams/cli tool setup, conformance test runner.
|
|
9
|
+
type: lifecycle
|
|
10
|
+
library: durable-streams
|
|
11
|
+
library_version: "0.2.1"
|
|
12
|
+
sources:
|
|
13
|
+
- "durable-streams/durable-streams:packages/caddy-plugin/README.md"
|
|
14
|
+
- "durable-streams/durable-streams:packages/server/README.md"
|
|
15
|
+
- "durable-streams/durable-streams:packages/cli/README.md"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# Durable Streams — Server Deployment
|
|
19
|
+
|
|
20
|
+
Two server options: a Node.js development server for prototyping and a
|
|
21
|
+
Caddy-based production server with file persistence and CDN support.
|
|
22
|
+
|
|
23
|
+
## Setup
|
|
24
|
+
|
|
25
|
+
### Development server (Node.js)
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { DurableStreamTestServer } from "@durable-streams/server"
|
|
29
|
+
|
|
30
|
+
const server = new DurableStreamTestServer({
|
|
31
|
+
port: 4437,
|
|
32
|
+
host: "127.0.0.1",
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
await server.start()
|
|
36
|
+
console.log(`Dev server running on ${server.url}`)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Production server (Caddy binary)
|
|
40
|
+
|
|
41
|
+
Download the binary from [GitHub releases](https://github.com/durable-streams/durable-streams/releases) for your platform.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Start the server
|
|
45
|
+
./durable-streams-server run --config Caddyfile
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Caddyfile configuration:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
:8787 {
|
|
52
|
+
route /v1/stream/* {
|
|
53
|
+
durable_streams {
|
|
54
|
+
data_dir ./data
|
|
55
|
+
max_file_handles 200
|
|
56
|
+
long_poll_timeout 60s
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Core Patterns
|
|
63
|
+
|
|
64
|
+
### CLI for testing
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Install CLI
|
|
68
|
+
npm install -g @durable-streams/cli
|
|
69
|
+
|
|
70
|
+
# Set server URL
|
|
71
|
+
export STREAM_URL=http://localhost:4437
|
|
72
|
+
|
|
73
|
+
# Create, write, read
|
|
74
|
+
durable-stream create my-stream
|
|
75
|
+
durable-stream write my-stream "Hello, world!"
|
|
76
|
+
durable-stream read my-stream
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Running conformance tests
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Against your server
|
|
83
|
+
npx @durable-streams/server-conformance-tests --run http://localhost:4437
|
|
84
|
+
|
|
85
|
+
# Watch mode for development
|
|
86
|
+
npx @durable-streams/server-conformance-tests --watch src http://localhost:4437
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Programmatic dev server with stream creation
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { DurableStreamTestServer } from "@durable-streams/server"
|
|
93
|
+
import { DurableStream } from "@durable-streams/client"
|
|
94
|
+
|
|
95
|
+
const server = new DurableStreamTestServer({ port: 0 }) // Random port
|
|
96
|
+
await server.start()
|
|
97
|
+
|
|
98
|
+
// Streams must be created before clients can connect
|
|
99
|
+
await DurableStream.create({
|
|
100
|
+
url: `${server.url}/v1/stream/my-app`,
|
|
101
|
+
contentType: "application/json",
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
console.log(`Dev server running on ${server.url}`)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Programmatic dev server in tests
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { DurableStreamTestServer } from "@durable-streams/server"
|
|
111
|
+
|
|
112
|
+
const server = new DurableStreamTestServer({ port: 0 }) // Random port
|
|
113
|
+
await server.start()
|
|
114
|
+
|
|
115
|
+
// Run your tests against server.url
|
|
116
|
+
|
|
117
|
+
await server.stop()
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Caddy configuration options
|
|
121
|
+
|
|
122
|
+
| Option | Default | Description |
|
|
123
|
+
| ------------------- | ------------------ | -------------------------------------- |
|
|
124
|
+
| `data_dir` | (none — in-memory) | Directory for file-backed persistence |
|
|
125
|
+
| `max_file_handles` | 100 | Max concurrent open file handles |
|
|
126
|
+
| `long_poll_timeout` | 60s | How long to hold long-poll connections |
|
|
127
|
+
|
|
128
|
+
## Common Mistakes
|
|
129
|
+
|
|
130
|
+
### CRITICAL Using the Node.js dev server in production
|
|
131
|
+
|
|
132
|
+
Wrong:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// production deployment
|
|
136
|
+
import { DurableStreamTestServer } from "@durable-streams/server"
|
|
137
|
+
const server = new DurableStreamTestServer({ port: 4437 })
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Correct:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# Use the Caddy plugin binary
|
|
144
|
+
./durable-streams-server run --config Caddyfile
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`DurableStreamTestServer` is explicitly not for production. It uses in-memory storage, has no CDN integration, and is single-process only.
|
|
148
|
+
|
|
149
|
+
Source: packages/server/README.md
|
|
150
|
+
|
|
151
|
+
### CRITICAL Not configuring data_dir for persistence
|
|
152
|
+
|
|
153
|
+
Wrong:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
:8787 {
|
|
157
|
+
route /v1/stream/* {
|
|
158
|
+
durable_streams
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Correct:
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
:8787 {
|
|
167
|
+
route /v1/stream/* {
|
|
168
|
+
durable_streams {
|
|
169
|
+
data_dir ./data
|
|
170
|
+
max_file_handles 200
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Without `data_dir`, the Caddy plugin uses in-memory storage. Server restarts lose all data.
|
|
177
|
+
|
|
178
|
+
Source: packages/caddy-plugin/README.md
|
|
179
|
+
|
|
180
|
+
### MEDIUM Setting max_file_handles too low for production
|
|
181
|
+
|
|
182
|
+
Wrong:
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
durable_streams {
|
|
186
|
+
data_dir ./data
|
|
187
|
+
# default 100 handles — fine for dev, low for production
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Correct:
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
durable_streams {
|
|
195
|
+
data_dir ./data
|
|
196
|
+
max_file_handles 500 # Tune based on active stream count
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Default is 100 file handles. High-throughput deployments with many concurrent streams can exhaust the pool, causing latency spikes.
|
|
201
|
+
|
|
202
|
+
Source: packages/caddy-plugin/store/filepool.go
|
|
203
|
+
|
|
204
|
+
## See also
|
|
205
|
+
|
|
206
|
+
- [getting-started](../getting-started/SKILL.md) — Connect a client to your server
|
|
207
|
+
- [go-to-production](../go-to-production/SKILL.md) — CDN caching, TTL, and HTTPS for production
|
|
208
|
+
|
|
209
|
+
## Version
|
|
210
|
+
|
|
211
|
+
Targets durable-streams-server v0.2.1.
|