@restless-stream/core 0.1.0 → 0.1.1
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 +357 -0
- package/package.json +22 -1
package/README.md
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
# @restless-stream/core
|
|
2
|
+
|
|
3
|
+
Official TypeScript SDK for Restless Stream core APIs: <https://restlessapi.stream>
|
|
4
|
+
|
|
5
|
+
Restless Stream turns REST APIs into live Server-Sent Events and WebSocket streams. This package provides the browser-compatible client, shared types, runtime URL builders, SSE parsing, and basic WebSocket iteration used by the other Restless Stream TypeScript packages.
|
|
6
|
+
|
|
7
|
+
Use this package when you need framework-neutral TypeScript support. Use `@restless-stream/node` for Node.js WebSocket subscriptions with handshake headers, and `@restless-stream/react` for React provider and hook APIs.
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- A Restless Stream account and API key.
|
|
12
|
+
- A runtime with `fetch` for REST and SSE support.
|
|
13
|
+
- A runtime with `WebSocket` for WebSocket helpers, or an explicit `WebSocket` implementation passed in options.
|
|
14
|
+
- Node.js `>=20.19.0` for local development and package tests.
|
|
15
|
+
|
|
16
|
+
Do not expose long-lived API keys in public browser applications. For browser clients, prefer backend-created stream or direct-session URLs.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @restless-stream/core
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pnpm add @restless-stream/core
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
yarn add @restless-stream/core
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Getting Started
|
|
33
|
+
|
|
34
|
+
Create a stream with the REST API, then subscribe to the returned SSE URL.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { createRestlessClient } from '@restless-stream/core';
|
|
38
|
+
|
|
39
|
+
const client = createRestlessClient({
|
|
40
|
+
apiKey: process.env.RESTLESS_API_KEY,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const stream = await client.streams.create({
|
|
44
|
+
name: 'Orders',
|
|
45
|
+
description: 'Live order feed',
|
|
46
|
+
status: 'ACTIVE',
|
|
47
|
+
method: 'GET',
|
|
48
|
+
url: 'https://api.example.com/orders',
|
|
49
|
+
payloadMode: 'FULL_DATA',
|
|
50
|
+
pollingInterval: 30,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
for await (const event of client.streams.subscribeSse({ sseUrl: stream.sseUrl })) {
|
|
54
|
+
if (event.type === 'update') {
|
|
55
|
+
console.log('data', event.data);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (event.type === 'error') {
|
|
59
|
+
console.error(event.error.message);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The top-level client methods and the `client.streams` resource expose the same management operations. For example, `client.createStream(input)` and `client.streams.create(input)` both create a stream.
|
|
65
|
+
|
|
66
|
+
## Client Configuration
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
const client = createRestlessClient({
|
|
70
|
+
apiKey: process.env.RESTLESS_API_KEY,
|
|
71
|
+
apiBaseUrl: 'https://api.restlessapi.stream',
|
|
72
|
+
streamBaseUrl: 'https://stream.restlessapi.stream',
|
|
73
|
+
headers: { 'X-App': 'orders-service' },
|
|
74
|
+
fetch: globalThis.fetch,
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
| Option | Description |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| `apiKey` | Sends `x-api-key` on REST requests and `Authorization: Bearer <key>` on SSE runtime requests. |
|
|
81
|
+
| `apiBaseUrl` | REST API base URL. Defaults to `https://api.restlessapi.stream`. |
|
|
82
|
+
| `streamBaseUrl` | Runtime stream base URL. Defaults to `https://stream.restlessapi.stream`. |
|
|
83
|
+
| `headers` | Default headers for REST API requests. |
|
|
84
|
+
| `fetch` | Custom `fetch` implementation. Required only when the runtime does not provide global `fetch`. |
|
|
85
|
+
|
|
86
|
+
`createStream` and `updateStream` automatically add `apiKey` to the JSON body when the client has an API key and the input does not already provide one.
|
|
87
|
+
|
|
88
|
+
## Stream Management
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
const page = await client.streams.list({ limit: 20, offset: 0 });
|
|
92
|
+
const stream = await client.streams.get('stream_123');
|
|
93
|
+
|
|
94
|
+
await client.streams.update(stream.id, {
|
|
95
|
+
name: 'Orders v2',
|
|
96
|
+
pollingInterval: 60,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
await client.streams.stop(stream.id);
|
|
100
|
+
await client.streams.start(stream.id);
|
|
101
|
+
|
|
102
|
+
const usage = await client.streams.creditUsageStats(stream.id);
|
|
103
|
+
const snippets = await client.streams.connectionSnippets({
|
|
104
|
+
streamId: stream.id,
|
|
105
|
+
language: 'javascript',
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
| Method | Description |
|
|
110
|
+
| --- | --- |
|
|
111
|
+
| `streams.list(input?)` / `listStreams(input?)` | List streams with optional `limit` and `offset`. |
|
|
112
|
+
| `streams.get(id)` / `getStream(id)` | Get one stream by ID. |
|
|
113
|
+
| `streams.create(input)` / `createStream(input)` | Create a persisted stream. |
|
|
114
|
+
| `streams.update(id, input)` / `updateStream(id, input)` | Patch stream configuration. |
|
|
115
|
+
| `streams.start(id)` / `startStream(id)` | Mark a stream active. |
|
|
116
|
+
| `streams.stop(id)` / `stopStream(id)` | Mark a stream inactive. |
|
|
117
|
+
| `streams.delete(id)` / `deleteStream(id)` | Delete a stream. |
|
|
118
|
+
| `streams.validateApiKey(apiKey?)` / `validateApiKey(apiKey?)` | Validate an API key. Uses the client API key when omitted. |
|
|
119
|
+
| `streams.creditUsageStats(id)` / `creditUsageStats(id)` | Fetch credit usage totals and daily usage. |
|
|
120
|
+
| `streams.connectionSnippets(input)` / `connectionSnippets(input)` | Generate runtime URLs and connection snippets. |
|
|
121
|
+
|
|
122
|
+
## Direct Streams
|
|
123
|
+
|
|
124
|
+
Direct streams let you subscribe to a REST endpoint without creating a persisted stream first.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
for await (const event of client.direct.subscribeSse({
|
|
128
|
+
url: 'https://api.example.com/orders',
|
|
129
|
+
method: 'GET',
|
|
130
|
+
pollingInterval: 30,
|
|
131
|
+
})) {
|
|
132
|
+
console.log(event);
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Create a reusable direct session when you want a stable runtime URL.
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
const session = await client.direct.createSession({
|
|
140
|
+
dedupeKey: 'orders-feed-v1',
|
|
141
|
+
method: 'GET',
|
|
142
|
+
url: 'https://api.example.com/orders',
|
|
143
|
+
pollingInterval: 30,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
for await (const event of client.streams.subscribeSse({ sseUrl: session.sseUrl })) {
|
|
147
|
+
console.log(event);
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Generate setup commands and snippets without opening a stream connection.
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
const setup = await client.direct.setup({
|
|
155
|
+
method: 'POST',
|
|
156
|
+
url: 'https://api.example.com/search',
|
|
157
|
+
body: { query: 'restless' },
|
|
158
|
+
language: 'javascript',
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
console.log(setup.commands.header.curl);
|
|
162
|
+
console.log(setup.runtime.directSseUrl);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Direct stream input supports:
|
|
166
|
+
|
|
167
|
+
| Field | Description |
|
|
168
|
+
| --- | --- |
|
|
169
|
+
| `url` | Target REST URL. Required. |
|
|
170
|
+
| `method` | HTTP method. Defaults to `GET` when omitted from runtime URLs. |
|
|
171
|
+
| `headers` | JSON object of upstream request headers. |
|
|
172
|
+
| `body` | JSON object request body. |
|
|
173
|
+
| `jqFilter` | Optional jq filter applied by Restless Stream. |
|
|
174
|
+
| `payloadMode` | `FULL_DATA` or `JSON_PATCH`. |
|
|
175
|
+
| `pollingInterval` | Polling interval in seconds. |
|
|
176
|
+
| `pollingStrategies` | Time-windowed polling strategies. |
|
|
177
|
+
| `apiKey` | Optional API key for query-parameter auth in generated direct URLs. |
|
|
178
|
+
|
|
179
|
+
## SSE Streaming
|
|
180
|
+
|
|
181
|
+
`streams.subscribeSse` yields parsed Restless Stream runtime events and skips non-JSON SSE messages.
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
const controller = new AbortController();
|
|
185
|
+
|
|
186
|
+
for await (const event of client.streams.subscribeSse({
|
|
187
|
+
sseUrl: stream.sseUrl,
|
|
188
|
+
cursor: '1700000000000',
|
|
189
|
+
reconnect: true,
|
|
190
|
+
signal: controller.signal,
|
|
191
|
+
})) {
|
|
192
|
+
console.log(event.type, event.meta?.timestamp);
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Use `client.sse` or `streamSse` when you need raw SSE message metadata such as `id`, `event`, `retry`, and `data`.
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
for await (const message of client.sse(stream.sseUrl, { reconnect: false })) {
|
|
200
|
+
console.log(message.id, message.parsed ?? message.data);
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
SSE options include:
|
|
205
|
+
|
|
206
|
+
| Option | Description |
|
|
207
|
+
| --- | --- |
|
|
208
|
+
| `sseUrl` | Runtime SSE URL for `subscribeSse`. |
|
|
209
|
+
| `headers` | Additional runtime request headers. |
|
|
210
|
+
| `signal` | `AbortSignal` used to stop iteration. |
|
|
211
|
+
| `reconnect` | Set `false` to stop after the first connection ends or fails. Defaults to reconnecting. |
|
|
212
|
+
| `reconnectDelayMs` | Initial reconnect delay. Defaults to `1000`. |
|
|
213
|
+
| `maxReconnectDelayMs` | Maximum reconnect delay. Defaults to `30000`. |
|
|
214
|
+
| `cursor` | Resume from a cursor or event ID. |
|
|
215
|
+
| `since` | Resume from a timestamp or cursor when no event cursor is known yet. |
|
|
216
|
+
| `allowApiKeyInUrl` | Adds the client API key as `apiKey` in the URL. Disabled by default because URLs can leak. |
|
|
217
|
+
|
|
218
|
+
## WebSocket Streaming
|
|
219
|
+
|
|
220
|
+
Core WebSocket helpers use the runtime `WebSocket` API and yield parsed JSON messages when possible, otherwise raw strings.
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
for await (const event of client.direct.subscribeWebSocket(
|
|
224
|
+
{
|
|
225
|
+
url: 'https://api.example.com/orders',
|
|
226
|
+
method: 'GET',
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
cursor: '42',
|
|
230
|
+
},
|
|
231
|
+
)) {
|
|
232
|
+
console.log(event);
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
for await (const message of client.websocket('wss://stream.restlessapi.stream/ws?streamId=stream_123')) {
|
|
238
|
+
console.log(message);
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Browser WebSocket constructors do not support custom handshake headers. Use `@restless-stream/node` when you need `Authorization` headers for WebSocket subscriptions in Node.js.
|
|
243
|
+
|
|
244
|
+
## Runtime URL Builders
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
import {
|
|
248
|
+
buildDirectSseUrl,
|
|
249
|
+
buildDirectWebSocketUrl,
|
|
250
|
+
buildSseUrl,
|
|
251
|
+
buildWebSocketUrl,
|
|
252
|
+
toWebSocketStreamUrl,
|
|
253
|
+
} from '@restless-stream/core';
|
|
254
|
+
|
|
255
|
+
const sseUrl = buildSseUrl(undefined, { streamId: 'stream_123' }, { since: '2026-01-01T00:00:00Z' });
|
|
256
|
+
const wsUrl = buildWebSocketUrl(undefined, { sessionId: 'session_123' });
|
|
257
|
+
|
|
258
|
+
const directSseUrl = buildDirectSseUrl(undefined, {
|
|
259
|
+
url: 'https://api.example.com/orders',
|
|
260
|
+
method: 'POST',
|
|
261
|
+
body: { page: 1 },
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const directWsUrl = buildDirectWebSocketUrl(undefined, {
|
|
265
|
+
url: 'https://api.example.com/orders',
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
console.log(toWebSocketStreamUrl(sseUrl));
|
|
269
|
+
console.log(wsUrl, directSseUrl, directWsUrl);
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
The client also exposes URL builders bound to its `streamBaseUrl`.
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
const sse = client.urls.sse({ streamId: 'stream_123' });
|
|
276
|
+
const websocket = client.urls.websocket({ streamId: 'stream_123' });
|
|
277
|
+
const directSse = client.urls.directSse({ url: 'https://api.example.com/orders' });
|
|
278
|
+
const directWebSocket = client.urls.directWebSocket({ url: 'https://api.example.com/orders' });
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Error Handling
|
|
282
|
+
|
|
283
|
+
REST API failures throw `RestlessApiError`.
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
import { RestlessApiError } from '@restless-stream/core';
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
await client.streams.get('missing');
|
|
290
|
+
} catch (error) {
|
|
291
|
+
if (error instanceof RestlessApiError) {
|
|
292
|
+
console.error(error.status, error.statusText, error.body);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Runtime Events
|
|
298
|
+
|
|
299
|
+
Update events contain data from the upstream REST endpoint.
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
type UpdateEvent<TData> = {
|
|
303
|
+
type: 'update';
|
|
304
|
+
meta: {
|
|
305
|
+
timestamp: string;
|
|
306
|
+
attempt_id: string;
|
|
307
|
+
status?: number;
|
|
308
|
+
latency_ms: number;
|
|
309
|
+
payload_mode_fallback?: 'FULL_DATA' | 'JSON_PATCH';
|
|
310
|
+
};
|
|
311
|
+
data: TData;
|
|
312
|
+
signature?: string;
|
|
313
|
+
};
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Error events contain a code and message.
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
type ErrorEvent = {
|
|
320
|
+
type: 'error';
|
|
321
|
+
meta?: Partial<UpdateEvent<unknown>['meta']>;
|
|
322
|
+
error: {
|
|
323
|
+
code: string;
|
|
324
|
+
message: string;
|
|
325
|
+
};
|
|
326
|
+
};
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## Public Exports
|
|
330
|
+
|
|
331
|
+
| Export | Purpose |
|
|
332
|
+
| --- | --- |
|
|
333
|
+
| `createRestlessClient` | Creates the core REST and runtime client. |
|
|
334
|
+
| `DEFAULT_API_BASE_URL` | Default REST API base URL. |
|
|
335
|
+
| `DEFAULT_STREAM_BASE_URL` | Default runtime stream base URL. |
|
|
336
|
+
| `RestlessApiError` | Error type thrown for non-2xx REST API responses. |
|
|
337
|
+
| `streamSse` | Low-level SSE async iterator. |
|
|
338
|
+
| `parseSseStream` | Parses a `ReadableStream<Uint8Array>` into SSE messages. |
|
|
339
|
+
| `streamWebSocket` | Low-level WebSocket async iterator. |
|
|
340
|
+
| `buildSseUrl`, `buildWebSocketUrl` | Build managed stream runtime URLs. |
|
|
341
|
+
| `buildDirectSseUrl`, `buildDirectWebSocketUrl` | Build direct stream runtime URLs. |
|
|
342
|
+
| `appendDirectQueryParams` | Append direct stream query params to `URLSearchParams`. |
|
|
343
|
+
| `normalizeBaseUrl` | Trim a trailing slash from a base URL. |
|
|
344
|
+
| `toWebSocketStreamUrl` | Convert an SSE URL to a WebSocket URL. |
|
|
345
|
+
| `withRuntimeCursor` | Add `cursor` or `since` resume params to a runtime URL. |
|
|
346
|
+
| `RestlessClient`, `RestlessStreamsClient`, `RestlessDirectClient`, `RestlessUrlBuilder` | Client interfaces. |
|
|
347
|
+
| Stream, direct, snippet, and runtime types | Request, response, event, URL, and snippet types exported from `types.ts`. |
|
|
348
|
+
|
|
349
|
+
## Development
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
cd packages/typescript
|
|
353
|
+
pnpm install
|
|
354
|
+
pnpm --filter @restless-stream/core typecheck
|
|
355
|
+
pnpm --filter @restless-stream/core test
|
|
356
|
+
pnpm --filter @restless-stream/core build
|
|
357
|
+
```
|
package/package.json
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@restless-stream/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "TypeScript SDK for Restless Stream.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/restless-stream/sdk.git",
|
|
9
|
+
"directory": "packages/typescript/core"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/restless-stream/sdk/tree/main/packages/typescript/core#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/restless-stream/sdk/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"restless-stream",
|
|
17
|
+
"sdk",
|
|
18
|
+
"sse",
|
|
19
|
+
"websocket",
|
|
20
|
+
"streaming",
|
|
21
|
+
"typescript"
|
|
22
|
+
],
|
|
5
23
|
"type": "module",
|
|
6
24
|
"main": "./dist/index.cjs",
|
|
7
25
|
"module": "./dist/index.js",
|
|
@@ -16,6 +34,9 @@
|
|
|
16
34
|
"files": [
|
|
17
35
|
"dist"
|
|
18
36
|
],
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
19
40
|
"sideEffects": false,
|
|
20
41
|
"devDependencies": {
|
|
21
42
|
"tsup": "^8.5.1",
|