routeflow-api 0.2.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 +93 -0
- package/dist/adapters/cassandra.cjs +117 -0
- package/dist/adapters/cassandra.cjs.map +1 -0
- package/dist/adapters/cassandra.d.cts +37 -0
- package/dist/adapters/cassandra.d.ts +37 -0
- package/dist/adapters/cassandra.js +90 -0
- package/dist/adapters/cassandra.js.map +1 -0
- package/dist/adapters/dynamodb.cjs +180 -0
- package/dist/adapters/dynamodb.cjs.map +1 -0
- package/dist/adapters/dynamodb.d.cts +48 -0
- package/dist/adapters/dynamodb.d.ts +48 -0
- package/dist/adapters/dynamodb.js +153 -0
- package/dist/adapters/dynamodb.js.map +1 -0
- package/dist/adapters/elasticsearch.cjs +120 -0
- package/dist/adapters/elasticsearch.cjs.map +1 -0
- package/dist/adapters/elasticsearch.d.cts +43 -0
- package/dist/adapters/elasticsearch.d.ts +43 -0
- package/dist/adapters/elasticsearch.js +93 -0
- package/dist/adapters/elasticsearch.js.map +1 -0
- package/dist/adapters/mongodb.cjs +159 -0
- package/dist/adapters/mongodb.cjs.map +1 -0
- package/dist/adapters/mongodb.d.cts +54 -0
- package/dist/adapters/mongodb.d.ts +54 -0
- package/dist/adapters/mongodb.js +132 -0
- package/dist/adapters/mongodb.js.map +1 -0
- package/dist/adapters/mysql.cjs +159 -0
- package/dist/adapters/mysql.cjs.map +1 -0
- package/dist/adapters/mysql.d.cts +63 -0
- package/dist/adapters/mysql.d.ts +63 -0
- package/dist/adapters/mysql.js +132 -0
- package/dist/adapters/mysql.js.map +1 -0
- package/dist/adapters/opensearch.cjs +120 -0
- package/dist/adapters/opensearch.cjs.map +1 -0
- package/dist/adapters/opensearch.d.cts +2 -0
- package/dist/adapters/opensearch.d.ts +2 -0
- package/dist/adapters/opensearch.js +93 -0
- package/dist/adapters/opensearch.js.map +1 -0
- package/dist/adapters/postgres.cjs +271 -0
- package/dist/adapters/postgres.cjs.map +1 -0
- package/dist/adapters/postgres.d.cts +81 -0
- package/dist/adapters/postgres.d.ts +81 -0
- package/dist/adapters/postgres.js +244 -0
- package/dist/adapters/postgres.js.map +1 -0
- package/dist/adapters/redis.cjs +153 -0
- package/dist/adapters/redis.cjs.map +1 -0
- package/dist/adapters/redis.d.cts +40 -0
- package/dist/adapters/redis.d.ts +40 -0
- package/dist/adapters/redis.js +126 -0
- package/dist/adapters/redis.js.map +1 -0
- package/dist/adapters/snowflake.cjs +117 -0
- package/dist/adapters/snowflake.cjs.map +1 -0
- package/dist/adapters/snowflake.d.cts +37 -0
- package/dist/adapters/snowflake.d.ts +37 -0
- package/dist/adapters/snowflake.js +90 -0
- package/dist/adapters/snowflake.js.map +1 -0
- package/dist/client/index.cjs +484 -0
- package/dist/client/index.cjs.map +1 -0
- package/dist/client/index.d.cts +174 -0
- package/dist/client/index.d.ts +174 -0
- package/dist/client/index.js +455 -0
- package/dist/client/index.js.map +1 -0
- package/dist/index.cjs +935 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +190 -0
- package/dist/index.d.ts +190 -0
- package/dist/index.js +890 -0
- package/dist/index.js.map +1 -0
- package/dist/types-tPDla8AE.d.cts +75 -0
- package/dist/types-tPDla8AE.d.ts +75 -0
- package/package.json +157 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
// src/client/errors.ts
|
|
2
|
+
var ReactiveClientError = class extends Error {
|
|
3
|
+
/** Machine-readable error code */
|
|
4
|
+
code;
|
|
5
|
+
/** HTTP status code, if applicable */
|
|
6
|
+
status;
|
|
7
|
+
constructor(code, message, status) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "ReactiveClientError";
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.status = status;
|
|
12
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/client/reactive-websocket.ts
|
|
17
|
+
var DEFAULT_RECONNECT = {
|
|
18
|
+
maxAttempts: 0,
|
|
19
|
+
initialDelayMs: 500,
|
|
20
|
+
backoffFactor: 2,
|
|
21
|
+
maxDelayMs: 3e4
|
|
22
|
+
};
|
|
23
|
+
var ReactiveWebSocket = class {
|
|
24
|
+
ws = null;
|
|
25
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
26
|
+
reconnectOpts;
|
|
27
|
+
onError;
|
|
28
|
+
reconnectAttempts = 0;
|
|
29
|
+
reconnectTimer = null;
|
|
30
|
+
destroyed = false;
|
|
31
|
+
wsUrl;
|
|
32
|
+
constructor(baseUrl, reconnect, onError) {
|
|
33
|
+
this.wsUrl = baseUrl.replace(/^http/, "ws");
|
|
34
|
+
this.reconnectOpts = { ...DEFAULT_RECONNECT, ...reconnect };
|
|
35
|
+
this.onError = onError;
|
|
36
|
+
}
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Public API
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
/**
|
|
41
|
+
* Subscribe to real-time updates for a path.
|
|
42
|
+
* Sends a subscribe message immediately if the socket is open,
|
|
43
|
+
* otherwise queues it for when the connection is established.
|
|
44
|
+
*
|
|
45
|
+
* @returns An unsubscribe function.
|
|
46
|
+
*/
|
|
47
|
+
subscribe(path, callback, query) {
|
|
48
|
+
if (!this.subscriptions.has(path)) {
|
|
49
|
+
this.subscriptions.set(path, /* @__PURE__ */ new Set());
|
|
50
|
+
}
|
|
51
|
+
const record = { path, query, callback };
|
|
52
|
+
this.subscriptions.get(path).add(record);
|
|
53
|
+
this.ensureConnected();
|
|
54
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
55
|
+
this.sendSubscribe(path, query);
|
|
56
|
+
}
|
|
57
|
+
return () => {
|
|
58
|
+
this.subscriptions.get(path)?.delete(record);
|
|
59
|
+
if (this.subscriptions.get(path)?.size === 0) {
|
|
60
|
+
this.subscriptions.delete(path);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Close the socket and stop all reconnection attempts.
|
|
66
|
+
*/
|
|
67
|
+
destroy() {
|
|
68
|
+
this.destroyed = true;
|
|
69
|
+
if (this.reconnectTimer !== null) {
|
|
70
|
+
clearTimeout(this.reconnectTimer);
|
|
71
|
+
this.reconnectTimer = null;
|
|
72
|
+
}
|
|
73
|
+
if (this.ws) {
|
|
74
|
+
this.ws.onclose = null;
|
|
75
|
+
this.ws.close();
|
|
76
|
+
this.ws = null;
|
|
77
|
+
}
|
|
78
|
+
this.subscriptions.clear();
|
|
79
|
+
}
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Private helpers
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
ensureConnected() {
|
|
84
|
+
if (this.destroyed) return;
|
|
85
|
+
if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
this.connect();
|
|
89
|
+
}
|
|
90
|
+
connect() {
|
|
91
|
+
if (this.destroyed) return;
|
|
92
|
+
let ws;
|
|
93
|
+
try {
|
|
94
|
+
ws = new WebSocket(this.wsUrl);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
this.scheduleReconnect();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
this.ws = ws;
|
|
100
|
+
ws.onopen = () => {
|
|
101
|
+
this.reconnectAttempts = 0;
|
|
102
|
+
for (const [path, records] of this.subscriptions) {
|
|
103
|
+
const query = [...records][0]?.query;
|
|
104
|
+
this.sendSubscribe(path, query);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
ws.onmessage = (event) => {
|
|
108
|
+
this.handleMessage(event.data);
|
|
109
|
+
};
|
|
110
|
+
ws.onerror = () => {
|
|
111
|
+
};
|
|
112
|
+
ws.onclose = () => {
|
|
113
|
+
this.ws = null;
|
|
114
|
+
if (!this.destroyed) {
|
|
115
|
+
this.scheduleReconnect();
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
scheduleReconnect() {
|
|
120
|
+
if (this.destroyed) return;
|
|
121
|
+
const { maxAttempts, initialDelayMs, backoffFactor, maxDelayMs } = this.reconnectOpts;
|
|
122
|
+
if (maxAttempts > 0 && this.reconnectAttempts >= maxAttempts) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const delay = Math.min(
|
|
126
|
+
initialDelayMs * Math.pow(backoffFactor, this.reconnectAttempts),
|
|
127
|
+
maxDelayMs
|
|
128
|
+
);
|
|
129
|
+
this.reconnectAttempts++;
|
|
130
|
+
this.reconnectTimer = setTimeout(() => {
|
|
131
|
+
this.reconnectTimer = null;
|
|
132
|
+
this.connect();
|
|
133
|
+
}, delay);
|
|
134
|
+
}
|
|
135
|
+
sendSubscribe(path, query) {
|
|
136
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
|
137
|
+
const msg = { type: "subscribe", path, ...query ? { query } : {} };
|
|
138
|
+
this.ws.send(JSON.stringify(msg));
|
|
139
|
+
}
|
|
140
|
+
handleMessage(raw) {
|
|
141
|
+
let parsed;
|
|
142
|
+
try {
|
|
143
|
+
parsed = JSON.parse(raw);
|
|
144
|
+
} catch {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (!isServerMessage(parsed)) return;
|
|
148
|
+
if (parsed.type === "error") {
|
|
149
|
+
const errInfo = { code: parsed.code, message: parsed.message };
|
|
150
|
+
if (this.onError) {
|
|
151
|
+
this.onError(errInfo);
|
|
152
|
+
} else {
|
|
153
|
+
console.warn(`[RouteFlow/client] Server error ${parsed.code}: ${parsed.message}`);
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (parsed.type === "update") {
|
|
158
|
+
const records = this.subscriptions.get(parsed.path);
|
|
159
|
+
if (!records) return;
|
|
160
|
+
for (const record of records) {
|
|
161
|
+
try {
|
|
162
|
+
record.callback(parsed.data);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
console.error("[RouteFlow/client] Subscription callback threw:", err);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
function isServerMessage(value) {
|
|
171
|
+
if (typeof value !== "object" || value === null) return false;
|
|
172
|
+
const v = value;
|
|
173
|
+
return v["type"] === "update" || v["type"] === "error";
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/client/reactive-sse.ts
|
|
177
|
+
var DEFAULT_RECONNECT2 = {
|
|
178
|
+
maxAttempts: 0,
|
|
179
|
+
initialDelayMs: 500,
|
|
180
|
+
backoffFactor: 2,
|
|
181
|
+
maxDelayMs: 3e4
|
|
182
|
+
};
|
|
183
|
+
var ReactiveSSE = class {
|
|
184
|
+
constructor(baseUrl, path, query, reconnect) {
|
|
185
|
+
this.path = path;
|
|
186
|
+
this.query = query;
|
|
187
|
+
const encodedPath = encodeURIComponent(path);
|
|
188
|
+
const extraParams = query ? "&" + new URLSearchParams(query).toString() : "";
|
|
189
|
+
this.sseUrl = `${baseUrl}/_sse/subscribe?path=${encodedPath}${extraParams}`;
|
|
190
|
+
this.reconnectOpts = { ...DEFAULT_RECONNECT2, ...reconnect };
|
|
191
|
+
this.connect();
|
|
192
|
+
}
|
|
193
|
+
path;
|
|
194
|
+
query;
|
|
195
|
+
source = null;
|
|
196
|
+
subscribers = /* @__PURE__ */ new Set();
|
|
197
|
+
reconnectOpts;
|
|
198
|
+
reconnectAttempts = 0;
|
|
199
|
+
reconnectTimer = null;
|
|
200
|
+
destroyed = false;
|
|
201
|
+
sseUrl;
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// Public API
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
/** Add a callback for this SSE stream. */
|
|
206
|
+
addSubscriber(callback) {
|
|
207
|
+
const record = {
|
|
208
|
+
path: this.path,
|
|
209
|
+
query: this.query,
|
|
210
|
+
callback
|
|
211
|
+
};
|
|
212
|
+
this.subscribers.add(record);
|
|
213
|
+
return () => {
|
|
214
|
+
this.subscribers.delete(record);
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
get subscriberCount() {
|
|
218
|
+
return this.subscribers.size;
|
|
219
|
+
}
|
|
220
|
+
/** Close the SSE connection permanently. */
|
|
221
|
+
destroy() {
|
|
222
|
+
this.destroyed = true;
|
|
223
|
+
if (this.reconnectTimer !== null) {
|
|
224
|
+
clearTimeout(this.reconnectTimer);
|
|
225
|
+
this.reconnectTimer = null;
|
|
226
|
+
}
|
|
227
|
+
this.source?.close();
|
|
228
|
+
this.source = null;
|
|
229
|
+
this.subscribers.clear();
|
|
230
|
+
}
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
// Private helpers
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
connect() {
|
|
235
|
+
if (this.destroyed) return;
|
|
236
|
+
const source = new EventSource(this.sseUrl);
|
|
237
|
+
this.source = source;
|
|
238
|
+
source.onmessage = (event) => {
|
|
239
|
+
this.handleMessage(event.data);
|
|
240
|
+
};
|
|
241
|
+
source.onerror = () => {
|
|
242
|
+
if (source.readyState === EventSource.CLOSED) {
|
|
243
|
+
source.close();
|
|
244
|
+
this.source = null;
|
|
245
|
+
if (!this.destroyed) this.scheduleReconnect();
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
scheduleReconnect() {
|
|
250
|
+
if (this.destroyed) return;
|
|
251
|
+
const { maxAttempts, initialDelayMs, backoffFactor, maxDelayMs } = this.reconnectOpts;
|
|
252
|
+
if (maxAttempts > 0 && this.reconnectAttempts >= maxAttempts) return;
|
|
253
|
+
const delay = Math.min(
|
|
254
|
+
initialDelayMs * Math.pow(backoffFactor, this.reconnectAttempts),
|
|
255
|
+
maxDelayMs
|
|
256
|
+
);
|
|
257
|
+
this.reconnectAttempts++;
|
|
258
|
+
this.reconnectTimer = setTimeout(() => {
|
|
259
|
+
this.reconnectTimer = null;
|
|
260
|
+
this.connect();
|
|
261
|
+
}, delay);
|
|
262
|
+
}
|
|
263
|
+
handleMessage(raw) {
|
|
264
|
+
let parsed;
|
|
265
|
+
try {
|
|
266
|
+
parsed = JSON.parse(raw);
|
|
267
|
+
} catch {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (typeof parsed !== "object" || parsed === null || parsed["type"] !== "update") {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const data = parsed["data"];
|
|
274
|
+
for (const record of this.subscribers) {
|
|
275
|
+
try {
|
|
276
|
+
record.callback(data);
|
|
277
|
+
} catch (err) {
|
|
278
|
+
console.error("[RouteFlow/client] SSE subscription callback threw:", err);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
// src/client/client.ts
|
|
285
|
+
var ReactiveClient = class {
|
|
286
|
+
baseUrl;
|
|
287
|
+
defaultHeaders;
|
|
288
|
+
socket = null;
|
|
289
|
+
/** path → ReactiveSSE instance (one per path for SSE) */
|
|
290
|
+
sseStreams = /* @__PURE__ */ new Map();
|
|
291
|
+
options;
|
|
292
|
+
constructor(options) {
|
|
293
|
+
this.options = { transport: "websocket", ...options };
|
|
294
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
295
|
+
this.defaultHeaders = options.headers ?? {};
|
|
296
|
+
}
|
|
297
|
+
// ---------------------------------------------------------------------------
|
|
298
|
+
// HTTP methods
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
/**
|
|
301
|
+
* Perform a GET request.
|
|
302
|
+
* @param path - Path relative to baseUrl (e.g. '/orders/123')
|
|
303
|
+
* @param query - Optional query string parameters
|
|
304
|
+
* @param headers - Per-request header overrides
|
|
305
|
+
*/
|
|
306
|
+
async get(path, query, headers) {
|
|
307
|
+
return this.request("GET", path, void 0, query, headers);
|
|
308
|
+
}
|
|
309
|
+
/** Perform a POST request. */
|
|
310
|
+
async post(path, body, headers) {
|
|
311
|
+
return this.request("POST", path, body, void 0, headers);
|
|
312
|
+
}
|
|
313
|
+
/** Perform a PUT request. */
|
|
314
|
+
async put(path, body, headers) {
|
|
315
|
+
return this.request("PUT", path, body, void 0, headers);
|
|
316
|
+
}
|
|
317
|
+
/** Perform a PATCH request. */
|
|
318
|
+
async patch(path, body, headers) {
|
|
319
|
+
return this.request("PATCH", path, body, void 0, headers);
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Perform a DELETE request.
|
|
323
|
+
* Named `del` because `delete` is a reserved keyword.
|
|
324
|
+
*/
|
|
325
|
+
async del(path, headers) {
|
|
326
|
+
return this.request("DELETE", path, void 0, void 0, headers);
|
|
327
|
+
}
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
// Real-time subscriptions
|
|
330
|
+
// ---------------------------------------------------------------------------
|
|
331
|
+
/**
|
|
332
|
+
* Subscribe to real-time updates pushed by the server for `path`.
|
|
333
|
+
*
|
|
334
|
+
* Uses WebSocket or SSE depending on the `transport` option passed to `createClient`.
|
|
335
|
+
*
|
|
336
|
+
* @param path - The reactive endpoint path (e.g. '/orders/123/live')
|
|
337
|
+
* @param callback - Invoked with the latest data on each push
|
|
338
|
+
* @param query - Optional query params forwarded in the subscription request
|
|
339
|
+
* @returns An unsubscribe function — call it to stop receiving updates.
|
|
340
|
+
*/
|
|
341
|
+
subscribe(path, callback, query) {
|
|
342
|
+
if (this.options.transport === "sse") {
|
|
343
|
+
return this.subscribeSSE(path, callback, query);
|
|
344
|
+
}
|
|
345
|
+
return this.subscribeWS(path, callback, query);
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Close the real-time transport and release all resources.
|
|
349
|
+
* Call this when the client is no longer needed (e.g. component unmount).
|
|
350
|
+
*/
|
|
351
|
+
destroy() {
|
|
352
|
+
this.socket?.destroy();
|
|
353
|
+
this.socket = null;
|
|
354
|
+
for (const stream of this.sseStreams.values()) {
|
|
355
|
+
stream.destroy();
|
|
356
|
+
}
|
|
357
|
+
this.sseStreams.clear();
|
|
358
|
+
}
|
|
359
|
+
// ---------------------------------------------------------------------------
|
|
360
|
+
// Private helpers
|
|
361
|
+
// ---------------------------------------------------------------------------
|
|
362
|
+
subscribeWS(path, callback, query) {
|
|
363
|
+
if (!this.socket) {
|
|
364
|
+
this.socket = new ReactiveWebSocket(
|
|
365
|
+
this.baseUrl,
|
|
366
|
+
this.options.reconnect,
|
|
367
|
+
this.options.onError
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
return this.socket.subscribe(path, callback, query);
|
|
371
|
+
}
|
|
372
|
+
subscribeSSE(path, callback, query) {
|
|
373
|
+
const streamKey = query ? `${path}?${new URLSearchParams(query)}` : path;
|
|
374
|
+
if (!this.sseStreams.has(streamKey)) {
|
|
375
|
+
this.sseStreams.set(
|
|
376
|
+
streamKey,
|
|
377
|
+
new ReactiveSSE(this.baseUrl, path, query, this.options.reconnect)
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
const stream = this.sseStreams.get(streamKey);
|
|
381
|
+
const unsub = stream.addSubscriber(callback);
|
|
382
|
+
return () => {
|
|
383
|
+
unsub();
|
|
384
|
+
if (stream.subscriberCount === 0) {
|
|
385
|
+
stream.destroy();
|
|
386
|
+
this.sseStreams.delete(streamKey);
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
async request(method, path, body, query, extraHeaders) {
|
|
391
|
+
let url = `${this.baseUrl}${path}`;
|
|
392
|
+
if (query && Object.keys(query).length > 0) {
|
|
393
|
+
url += "?" + new URLSearchParams(query).toString();
|
|
394
|
+
}
|
|
395
|
+
const headers = {
|
|
396
|
+
"Content-Type": "application/json",
|
|
397
|
+
...this.defaultHeaders,
|
|
398
|
+
...extraHeaders
|
|
399
|
+
};
|
|
400
|
+
const init = { method, headers };
|
|
401
|
+
if (body !== void 0) {
|
|
402
|
+
init.body = JSON.stringify(body);
|
|
403
|
+
}
|
|
404
|
+
let response;
|
|
405
|
+
try {
|
|
406
|
+
response = await fetch(url, init);
|
|
407
|
+
} catch (err) {
|
|
408
|
+
throw new ReactiveClientError(
|
|
409
|
+
"NETWORK_ERROR",
|
|
410
|
+
`Network request failed: ${err instanceof Error ? err.message : String(err)}`
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
if (!response.ok) {
|
|
414
|
+
let detail = "";
|
|
415
|
+
try {
|
|
416
|
+
const text = await response.text();
|
|
417
|
+
if (text) {
|
|
418
|
+
try {
|
|
419
|
+
const json = JSON.parse(text);
|
|
420
|
+
detail = typeof json["message"] === "string" ? ` \u2014 ${json["message"]}` : ` \u2014 ${text}`;
|
|
421
|
+
} catch {
|
|
422
|
+
detail = ` \u2014 ${text}`;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
} catch {
|
|
426
|
+
}
|
|
427
|
+
throw new ReactiveClientError(
|
|
428
|
+
"HTTP_ERROR",
|
|
429
|
+
`${method} ${path} failed with status ${response.status}${detail}`,
|
|
430
|
+
response.status
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
const contentLength = response.headers.get("content-length");
|
|
434
|
+
if (response.status === 204 || contentLength === "0") {
|
|
435
|
+
return void 0;
|
|
436
|
+
}
|
|
437
|
+
try {
|
|
438
|
+
return await response.json();
|
|
439
|
+
} catch {
|
|
440
|
+
throw new ReactiveClientError(
|
|
441
|
+
"PARSE_ERROR",
|
|
442
|
+
`Failed to parse JSON response from ${method} ${path}`
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
function createClient(baseUrl, options) {
|
|
448
|
+
return new ReactiveClient({ baseUrl, ...options });
|
|
449
|
+
}
|
|
450
|
+
export {
|
|
451
|
+
ReactiveClient,
|
|
452
|
+
ReactiveClientError,
|
|
453
|
+
createClient
|
|
454
|
+
};
|
|
455
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/client/errors.ts","../../src/client/reactive-websocket.ts","../../src/client/reactive-sse.ts","../../src/client/client.ts"],"sourcesContent":["/**\n * Error thrown by the RouteFlow client for HTTP or WebSocket failures.\n */\nexport class ReactiveClientError extends Error {\n /** Machine-readable error code */\n readonly code: string\n /** HTTP status code, if applicable */\n readonly status?: number\n\n constructor(code: string, message: string, status?: number) {\n super(message)\n this.name = 'ReactiveClientError'\n this.code = code\n this.status = status\n Object.setPrototypeOf(this, new.target.prototype)\n }\n}\n","import type {\n ReconnectOptions,\n ServerMessage,\n SubscribeMessage,\n SubscriptionCallback,\n SubscriptionRecord,\n Unsubscribe,\n} from './types.js'\n\nconst DEFAULT_RECONNECT: Required<ReconnectOptions> = {\n maxAttempts: 0,\n initialDelayMs: 500,\n backoffFactor: 2,\n maxDelayMs: 30_000,\n}\n\n/**\n * Managed WebSocket connection with:\n * - Automatic exponential-backoff reconnection\n * - Subscription restore after reconnect\n * - Path-based fan-out to callbacks\n *\n * Uses the browser-native `WebSocket` API — no Node.js imports.\n */\nexport class ReactiveWebSocket {\n private ws: WebSocket | null = null\n private readonly subscriptions: Map<string, Set<SubscriptionRecord>> = new Map()\n private readonly reconnectOpts: Required<ReconnectOptions>\n private readonly onError?: (error: { code: string; message: string }) => void\n\n private reconnectAttempts = 0\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null\n private destroyed = false\n private readonly wsUrl: string\n\n constructor(\n baseUrl: string,\n reconnect?: ReconnectOptions,\n onError?: (error: { code: string; message: string }) => void,\n ) {\n // Convert http(s):// → ws(s)://\n this.wsUrl = baseUrl.replace(/^http/, 'ws')\n this.reconnectOpts = { ...DEFAULT_RECONNECT, ...reconnect }\n this.onError = onError\n }\n\n // ---------------------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------------------\n\n /**\n * Subscribe to real-time updates for a path.\n * Sends a subscribe message immediately if the socket is open,\n * otherwise queues it for when the connection is established.\n *\n * @returns An unsubscribe function.\n */\n subscribe<T>(\n path: string,\n callback: SubscriptionCallback<T>,\n query?: Record<string, string>,\n ): Unsubscribe {\n if (!this.subscriptions.has(path)) {\n this.subscriptions.set(path, new Set())\n }\n\n const record: SubscriptionRecord<T> = { path, query, callback: callback as SubscriptionCallback<unknown> }\n this.subscriptions.get(path)!.add(record as SubscriptionRecord)\n\n // Ensure socket is open\n this.ensureConnected()\n\n // If already open, send subscribe message right away\n if (this.ws?.readyState === WebSocket.OPEN) {\n this.sendSubscribe(path, query)\n }\n\n return () => {\n this.subscriptions.get(path)?.delete(record as SubscriptionRecord)\n if (this.subscriptions.get(path)?.size === 0) {\n this.subscriptions.delete(path)\n }\n }\n }\n\n /**\n * Close the socket and stop all reconnection attempts.\n */\n destroy(): void {\n this.destroyed = true\n if (this.reconnectTimer !== null) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = null\n }\n if (this.ws) {\n this.ws.onclose = null // prevent reconnect loop\n this.ws.close()\n this.ws = null\n }\n this.subscriptions.clear()\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private ensureConnected(): void {\n if (this.destroyed) return\n if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {\n return\n }\n this.connect()\n }\n\n private connect(): void {\n if (this.destroyed) return\n\n let ws: WebSocket\n try {\n ws = new WebSocket(this.wsUrl)\n } catch (err) {\n this.scheduleReconnect()\n return\n }\n\n this.ws = ws\n\n ws.onopen = () => {\n this.reconnectAttempts = 0\n // Re-subscribe to all active paths (handles reconnect restore)\n for (const [path, records] of this.subscriptions) {\n const query = [...records][0]?.query\n this.sendSubscribe(path, query)\n }\n }\n\n ws.onmessage = (event: MessageEvent) => {\n this.handleMessage(event.data as string)\n }\n\n ws.onerror = () => {\n // onerror always fires before onclose; actual retry happens in onclose\n }\n\n ws.onclose = () => {\n this.ws = null\n if (!this.destroyed) {\n this.scheduleReconnect()\n }\n }\n }\n\n private scheduleReconnect(): void {\n if (this.destroyed) return\n\n const { maxAttempts, initialDelayMs, backoffFactor, maxDelayMs } = this.reconnectOpts\n\n if (maxAttempts > 0 && this.reconnectAttempts >= maxAttempts) {\n return\n }\n\n const delay = Math.min(\n initialDelayMs * Math.pow(backoffFactor, this.reconnectAttempts),\n maxDelayMs,\n )\n\n this.reconnectAttempts++\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null\n this.connect()\n }, delay)\n }\n\n private sendSubscribe(path: string, query?: Record<string, string>): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return\n const msg: SubscribeMessage = { type: 'subscribe', path, ...(query ? { query } : {}) }\n this.ws.send(JSON.stringify(msg))\n }\n\n private handleMessage(raw: string): void {\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n return\n }\n\n if (!isServerMessage(parsed)) return\n\n if (parsed.type === 'error') {\n const errInfo = { code: parsed.code, message: parsed.message }\n if (this.onError) {\n this.onError(errInfo)\n } else {\n console.warn(`[RouteFlow/client] Server error ${parsed.code}: ${parsed.message}`)\n }\n return\n }\n\n if (parsed.type === 'update') {\n const records = this.subscriptions.get(parsed.path)\n if (!records) return\n for (const record of records) {\n try {\n record.callback(parsed.data)\n } catch (err) {\n console.error('[RouteFlow/client] Subscription callback threw:', err)\n }\n }\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Type guards\n// ---------------------------------------------------------------------------\n\nfunction isServerMessage(value: unknown): value is ServerMessage {\n if (typeof value !== 'object' || value === null) return false\n const v = value as Record<string, unknown>\n return v['type'] === 'update' || v['type'] === 'error'\n}\n","import type {\n ReconnectOptions,\n SubscriptionCallback,\n SubscriptionRecord,\n Unsubscribe,\n} from './types.js'\n\nconst DEFAULT_RECONNECT: Required<ReconnectOptions> = {\n maxAttempts: 0,\n initialDelayMs: 500,\n backoffFactor: 2,\n maxDelayMs: 30_000,\n}\n\n/**\n * Managed SSE connection for a single reactive path subscription.\n *\n * Wraps the browser-native `EventSource` API. Each `ReactiveSSE` instance\n * manages one SSE stream (one subscribed path). Reconnection is handled\n * automatically by `EventSource` for transient failures; we add our own\n * backoff logic for persistent errors.\n *\n * Browser-compatible — no Node.js-specific APIs.\n */\nexport class ReactiveSSE {\n private source: EventSource | null = null\n private readonly subscribers: Set<SubscriptionRecord> = new Set()\n private readonly reconnectOpts: Required<ReconnectOptions>\n private reconnectAttempts = 0\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null\n private destroyed = false\n private readonly sseUrl: string\n\n constructor(\n baseUrl: string,\n private readonly path: string,\n private readonly query: Record<string, string> | undefined,\n reconnect?: ReconnectOptions,\n ) {\n const encodedPath = encodeURIComponent(path)\n const extraParams = query ? '&' + new URLSearchParams(query).toString() : ''\n this.sseUrl = `${baseUrl}/_sse/subscribe?path=${encodedPath}${extraParams}`\n this.reconnectOpts = { ...DEFAULT_RECONNECT, ...reconnect }\n this.connect()\n }\n\n // ---------------------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------------------\n\n /** Add a callback for this SSE stream. */\n addSubscriber<T>(\n callback: SubscriptionCallback<T>,\n ): Unsubscribe {\n const record: SubscriptionRecord<T> = {\n path: this.path,\n query: this.query,\n callback: callback as SubscriptionCallback<unknown>,\n }\n this.subscribers.add(record as SubscriptionRecord)\n\n return () => {\n this.subscribers.delete(record as SubscriptionRecord)\n }\n }\n\n get subscriberCount(): number {\n return this.subscribers.size\n }\n\n /** Close the SSE connection permanently. */\n destroy(): void {\n this.destroyed = true\n if (this.reconnectTimer !== null) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = null\n }\n this.source?.close()\n this.source = null\n this.subscribers.clear()\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private connect(): void {\n if (this.destroyed) return\n\n const source = new EventSource(this.sseUrl)\n this.source = source\n\n source.onmessage = (event: MessageEvent) => {\n this.handleMessage(event.data as string)\n }\n\n source.onerror = () => {\n // EventSource auto-reconnects on network errors; we only step in after\n // it has given up (readyState === CLOSED).\n if (source.readyState === EventSource.CLOSED) {\n source.close()\n this.source = null\n if (!this.destroyed) this.scheduleReconnect()\n }\n }\n }\n\n private scheduleReconnect(): void {\n if (this.destroyed) return\n const { maxAttempts, initialDelayMs, backoffFactor, maxDelayMs } = this.reconnectOpts\n\n if (maxAttempts > 0 && this.reconnectAttempts >= maxAttempts) return\n\n const delay = Math.min(\n initialDelayMs * Math.pow(backoffFactor, this.reconnectAttempts),\n maxDelayMs,\n )\n this.reconnectAttempts++\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null\n this.connect()\n }, delay)\n }\n\n private handleMessage(raw: string): void {\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n return\n }\n\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n (parsed as Record<string, unknown>)['type'] !== 'update'\n ) {\n return\n }\n\n const data = (parsed as Record<string, unknown>)['data']\n\n for (const record of this.subscribers) {\n try {\n record.callback(data)\n } catch (err) {\n console.error('[RouteFlow/client] SSE subscription callback threw:', err)\n }\n }\n }\n}\n","import type {\n ClientOptions,\n SubscriptionCallback,\n Unsubscribe,\n} from './types.js'\nimport { ReactiveClientError } from './errors.js'\nimport { ReactiveWebSocket } from './reactive-websocket.js'\nimport { ReactiveSSE } from './reactive-sse.js'\n\n/**\n * RouteFlow client.\n *\n * Provides:\n * - HTTP helpers (`get`, `post`, `put`, `patch`, `del`) backed by the Fetch API\n * - Real-time subscriptions (`subscribe`) via WebSocket or SSE\n *\n * Fully browser-compatible — no Node.js-specific APIs used.\n *\n * @example\n * ```ts\n * const client = createClient('http://localhost:3000')\n *\n * // One-off REST request\n * const orders = await client.get<Order[]>('/orders/123')\n *\n * // Real-time subscription\n * const unsubscribe = client.subscribe<Order[]>('/orders/123/live', (data) => {\n * console.log('updated:', data)\n * })\n *\n * // Later…\n * unsubscribe()\n * client.destroy()\n * ```\n */\nexport class ReactiveClient {\n private readonly baseUrl: string\n private readonly defaultHeaders: Record<string, string>\n private socket: ReactiveWebSocket | null = null\n /** path → ReactiveSSE instance (one per path for SSE) */\n private readonly sseStreams: Map<string, ReactiveSSE> = new Map()\n private readonly options: Required<Pick<ClientOptions, 'transport'>> & ClientOptions\n\n constructor(options: ClientOptions) {\n this.options = { transport: 'websocket', ...options }\n this.baseUrl = options.baseUrl.replace(/\\/$/, '')\n this.defaultHeaders = options.headers ?? {}\n }\n\n // ---------------------------------------------------------------------------\n // HTTP methods\n // ---------------------------------------------------------------------------\n\n /**\n * Perform a GET request.\n * @param path - Path relative to baseUrl (e.g. '/orders/123')\n * @param query - Optional query string parameters\n * @param headers - Per-request header overrides\n */\n async get<T>(\n path: string,\n query?: Record<string, string>,\n headers?: Record<string, string>,\n ): Promise<T> {\n return this.request<T>('GET', path, undefined, query, headers)\n }\n\n /** Perform a POST request. */\n async post<T>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T> {\n return this.request<T>('POST', path, body, undefined, headers)\n }\n\n /** Perform a PUT request. */\n async put<T>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T> {\n return this.request<T>('PUT', path, body, undefined, headers)\n }\n\n /** Perform a PATCH request. */\n async patch<T>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T> {\n return this.request<T>('PATCH', path, body, undefined, headers)\n }\n\n /**\n * Perform a DELETE request.\n * Named `del` because `delete` is a reserved keyword.\n */\n async del<T>(path: string, headers?: Record<string, string>): Promise<T> {\n return this.request<T>('DELETE', path, undefined, undefined, headers)\n }\n\n // ---------------------------------------------------------------------------\n // Real-time subscriptions\n // ---------------------------------------------------------------------------\n\n /**\n * Subscribe to real-time updates pushed by the server for `path`.\n *\n * Uses WebSocket or SSE depending on the `transport` option passed to `createClient`.\n *\n * @param path - The reactive endpoint path (e.g. '/orders/123/live')\n * @param callback - Invoked with the latest data on each push\n * @param query - Optional query params forwarded in the subscription request\n * @returns An unsubscribe function — call it to stop receiving updates.\n */\n subscribe<T>(\n path: string,\n callback: SubscriptionCallback<T>,\n query?: Record<string, string>,\n ): Unsubscribe {\n if (this.options.transport === 'sse') {\n return this.subscribeSSE<T>(path, callback, query)\n }\n return this.subscribeWS<T>(path, callback, query)\n }\n\n /**\n * Close the real-time transport and release all resources.\n * Call this when the client is no longer needed (e.g. component unmount).\n */\n destroy(): void {\n this.socket?.destroy()\n this.socket = null\n for (const stream of this.sseStreams.values()) {\n stream.destroy()\n }\n this.sseStreams.clear()\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private subscribeWS<T>(\n path: string,\n callback: SubscriptionCallback<T>,\n query?: Record<string, string>,\n ): Unsubscribe {\n if (!this.socket) {\n this.socket = new ReactiveWebSocket(\n this.baseUrl,\n this.options.reconnect,\n this.options.onError,\n )\n }\n return this.socket.subscribe<T>(path, callback, query)\n }\n\n private subscribeSSE<T>(\n path: string,\n callback: SubscriptionCallback<T>,\n query?: Record<string, string>,\n ): Unsubscribe {\n // SSE key includes query so different queries get different streams\n const streamKey = query ? `${path}?${new URLSearchParams(query)}` : path\n\n if (!this.sseStreams.has(streamKey)) {\n this.sseStreams.set(\n streamKey,\n new ReactiveSSE(this.baseUrl, path, query, this.options.reconnect),\n )\n }\n\n const stream = this.sseStreams.get(streamKey)!\n const unsub = stream.addSubscriber<T>(callback)\n\n return () => {\n unsub()\n // Clean up the SSE stream when no subscribers remain\n if (stream.subscriberCount === 0) {\n stream.destroy()\n this.sseStreams.delete(streamKey)\n }\n }\n }\n\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n query?: Record<string, string>,\n extraHeaders?: Record<string, string>,\n ): Promise<T> {\n let url = `${this.baseUrl}${path}`\n\n if (query && Object.keys(query).length > 0) {\n url += '?' + new URLSearchParams(query).toString()\n }\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...this.defaultHeaders,\n ...extraHeaders,\n }\n\n const init: RequestInit = { method, headers }\n\n if (body !== undefined) {\n init.body = JSON.stringify(body)\n }\n\n let response: Response\n try {\n response = await fetch(url, init)\n } catch (err) {\n throw new ReactiveClientError(\n 'NETWORK_ERROR',\n `Network request failed: ${err instanceof Error ? err.message : String(err)}`,\n )\n }\n\n if (!response.ok) {\n // Try to extract a server-sent error payload\n let detail = ''\n try {\n const text = await response.text()\n if (text) {\n try {\n const json = JSON.parse(text) as Record<string, unknown>\n detail = typeof json['message'] === 'string' ? ` — ${json['message']}` : ` — ${text}`\n } catch {\n detail = ` — ${text}`\n }\n }\n } catch {\n // ignore\n }\n throw new ReactiveClientError(\n 'HTTP_ERROR',\n `${method} ${path} failed with status ${response.status}${detail}`,\n response.status,\n )\n }\n\n const contentLength = response.headers.get('content-length')\n if (response.status === 204 || contentLength === '0') {\n return undefined as T\n }\n\n try {\n return (await response.json()) as T\n } catch {\n throw new ReactiveClientError(\n 'PARSE_ERROR',\n `Failed to parse JSON response from ${method} ${path}`,\n )\n }\n }\n}\n\n/**\n * Create a new RouteFlow client instance.\n *\n * @example\n * ```ts\n * // WebSocket (default)\n * const client = createClient('http://localhost:3000')\n *\n * // SSE transport\n * const client = createClient('http://localhost:3000', { transport: 'sse' })\n *\n * // With auth header and custom error handler\n * const client = createClient('http://localhost:3000', {\n * headers: { Authorization: 'Bearer token' },\n * onError: (err) => console.error('realtime error:', err),\n * })\n * ```\n */\nexport function createClient(\n baseUrl: string,\n options?: Omit<ClientOptions, 'baseUrl'>,\n): ReactiveClient {\n return new ReactiveClient({ baseUrl, ...options })\n}\n"],"mappings":"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","names":["DEFAULT_RECONNECT"]}
|