@restless-stream/react 0.1.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/dist/index.cjs +310 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +56 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +278 -0
- package/dist/index.js.map +1 -0
- package/package.json +41 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
DEFAULT_MAX_EVENTS: () => DEFAULT_MAX_EVENTS,
|
|
24
|
+
RestlessProvider: () => RestlessProvider,
|
|
25
|
+
useDirectStream: () => useDirectStream,
|
|
26
|
+
useManagedStream: () => useManagedStream,
|
|
27
|
+
useRestlessClient: () => useRestlessClient,
|
|
28
|
+
useStreamSubscription: () => useStreamSubscription
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
|
+
|
|
32
|
+
// src/provider.ts
|
|
33
|
+
var import_core = require("@restless-stream/core");
|
|
34
|
+
var import_react = require("react");
|
|
35
|
+
var RestlessClientContext = (0, import_react.createContext)(null);
|
|
36
|
+
function RestlessProvider({
|
|
37
|
+
apiBaseUrl,
|
|
38
|
+
apiKey,
|
|
39
|
+
children,
|
|
40
|
+
client,
|
|
41
|
+
config,
|
|
42
|
+
fetch,
|
|
43
|
+
headers,
|
|
44
|
+
streamBaseUrl
|
|
45
|
+
}) {
|
|
46
|
+
const inlineConfig = (0, import_react.useMemo)(
|
|
47
|
+
() => compactConfig({
|
|
48
|
+
apiBaseUrl,
|
|
49
|
+
apiKey,
|
|
50
|
+
fetch,
|
|
51
|
+
headers,
|
|
52
|
+
streamBaseUrl
|
|
53
|
+
}),
|
|
54
|
+
[apiBaseUrl, apiKey, fetch, headers, streamBaseUrl]
|
|
55
|
+
);
|
|
56
|
+
const providerConfig = config ?? inlineConfig;
|
|
57
|
+
const contextClient = (0, import_react.useMemo)(() => client ?? (0, import_core.createRestlessClient)(providerConfig), [client, providerConfig]);
|
|
58
|
+
return (0, import_react.createElement)(RestlessClientContext.Provider, { value: contextClient }, children);
|
|
59
|
+
}
|
|
60
|
+
function useRestlessClient() {
|
|
61
|
+
const client = (0, import_react.useContext)(RestlessClientContext);
|
|
62
|
+
if (!client) {
|
|
63
|
+
throw new Error("useRestlessClient must be used within a RestlessProvider or with an explicit client option.");
|
|
64
|
+
}
|
|
65
|
+
return client;
|
|
66
|
+
}
|
|
67
|
+
function useOptionalRestlessClient() {
|
|
68
|
+
return (0, import_react.useContext)(RestlessClientContext);
|
|
69
|
+
}
|
|
70
|
+
function compactConfig(config) {
|
|
71
|
+
return Object.fromEntries(Object.entries(config).filter(([, value]) => value !== void 0));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/stream-subscription.ts
|
|
75
|
+
var import_react2 = require("react");
|
|
76
|
+
|
|
77
|
+
// src/stream-subscription-types.ts
|
|
78
|
+
var DEFAULT_MAX_EVENTS = 100;
|
|
79
|
+
|
|
80
|
+
// src/stream-subscription-utils.ts
|
|
81
|
+
function isNonEmptyString(value) {
|
|
82
|
+
return typeof value === "string" && value.length > 0;
|
|
83
|
+
}
|
|
84
|
+
function appendEvent(currentEvents, event, maxEvents) {
|
|
85
|
+
if (maxEvents === 0) {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
const nextEvents = [...currentEvents, event];
|
|
89
|
+
return nextEvents.length > maxEvents ? nextEvents.slice(nextEvents.length - maxEvents) : nextEvents;
|
|
90
|
+
}
|
|
91
|
+
function normalizeMaxEvents(maxEvents) {
|
|
92
|
+
if (!Number.isFinite(maxEvents)) {
|
|
93
|
+
return DEFAULT_MAX_EVENTS;
|
|
94
|
+
}
|
|
95
|
+
return Math.max(0, Math.floor(maxEvents));
|
|
96
|
+
}
|
|
97
|
+
function resolveReconnectDelay(delay, attempt) {
|
|
98
|
+
const resolvedDelay = typeof delay === "function" ? delay(attempt) : delay;
|
|
99
|
+
return Number.isFinite(resolvedDelay) ? Math.max(0, resolvedDelay) : 0;
|
|
100
|
+
}
|
|
101
|
+
async function sleep(milliseconds, signal) {
|
|
102
|
+
if (milliseconds === 0 || signal.aborted) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
return new Promise((resolve) => {
|
|
106
|
+
const timeout = setTimeout(resolve, milliseconds);
|
|
107
|
+
signal.addEventListener(
|
|
108
|
+
"abort",
|
|
109
|
+
() => {
|
|
110
|
+
clearTimeout(timeout);
|
|
111
|
+
resolve();
|
|
112
|
+
},
|
|
113
|
+
{ once: true }
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function stableOptionsKey(options) {
|
|
118
|
+
return JSON.stringify(sortRecord(options));
|
|
119
|
+
}
|
|
120
|
+
function toError(error) {
|
|
121
|
+
if (error instanceof Error) {
|
|
122
|
+
return error;
|
|
123
|
+
}
|
|
124
|
+
return new Error(typeof error === "string" ? error : "Restless stream subscription failed.");
|
|
125
|
+
}
|
|
126
|
+
function sortRecord(value) {
|
|
127
|
+
if (Array.isArray(value)) {
|
|
128
|
+
return value.map((item) => sortRecord(item));
|
|
129
|
+
}
|
|
130
|
+
if (typeof value === "function") {
|
|
131
|
+
return value.toString();
|
|
132
|
+
}
|
|
133
|
+
if (value === null || value === void 0 || typeof value !== "object") {
|
|
134
|
+
return value;
|
|
135
|
+
}
|
|
136
|
+
return Object.fromEntries(
|
|
137
|
+
Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, nestedValue]) => [key, sortRecord(nestedValue)])
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/stream-subscription.ts
|
|
142
|
+
function useStreamSubscription(options) {
|
|
143
|
+
const {
|
|
144
|
+
client: explicitClient,
|
|
145
|
+
enabled = true,
|
|
146
|
+
maxEvents = DEFAULT_MAX_EVENTS,
|
|
147
|
+
maxReconnectAttempts = 0,
|
|
148
|
+
onError,
|
|
149
|
+
onEvent,
|
|
150
|
+
reconnect = false,
|
|
151
|
+
reconnectDelayMs = 1e3,
|
|
152
|
+
...subscribeOptions
|
|
153
|
+
} = options;
|
|
154
|
+
const contextClient = useOptionalRestlessClient();
|
|
155
|
+
const client = explicitClient ?? contextClient;
|
|
156
|
+
if (!client) {
|
|
157
|
+
throw new Error("useStreamSubscription requires a RestlessProvider or an explicit client option.");
|
|
158
|
+
}
|
|
159
|
+
const subscriptionClient = client;
|
|
160
|
+
const boundedMaxEvents = normalizeMaxEvents(maxEvents);
|
|
161
|
+
const subscribeOptionsKey = stableOptionsKey(subscribeOptions);
|
|
162
|
+
const abortControllerRef = (0, import_react2.useRef)(null);
|
|
163
|
+
const onErrorRef = (0, import_react2.useRef)(onError);
|
|
164
|
+
const onEventRef = (0, import_react2.useRef)(onEvent);
|
|
165
|
+
const reconnectDelayRef = (0, import_react2.useRef)(reconnectDelayMs);
|
|
166
|
+
const subscribeOptionsRef = (0, import_react2.useRef)(subscribeOptions);
|
|
167
|
+
const [connectionToken, setConnectionToken] = (0, import_react2.useState)(0);
|
|
168
|
+
const [events, setEvents] = (0, import_react2.useState)([]);
|
|
169
|
+
const [error, setError] = (0, import_react2.useState)(null);
|
|
170
|
+
const [shouldConnect, setShouldConnect] = (0, import_react2.useState)(enabled);
|
|
171
|
+
const [status, setStatus] = (0, import_react2.useState)(enabled ? "connecting" : "idle");
|
|
172
|
+
onErrorRef.current = onError;
|
|
173
|
+
onEventRef.current = onEvent;
|
|
174
|
+
reconnectDelayRef.current = reconnectDelayMs;
|
|
175
|
+
subscribeOptionsRef.current = subscribeOptions;
|
|
176
|
+
(0, import_react2.useEffect)(() => {
|
|
177
|
+
setShouldConnect(enabled);
|
|
178
|
+
setStatus((currentStatus) => {
|
|
179
|
+
if (enabled) {
|
|
180
|
+
return currentStatus === "idle" || currentStatus === "closed" ? "connecting" : currentStatus;
|
|
181
|
+
}
|
|
182
|
+
return "idle";
|
|
183
|
+
});
|
|
184
|
+
}, [enabled]);
|
|
185
|
+
const connect = (0, import_react2.useCallback)(() => {
|
|
186
|
+
setError(null);
|
|
187
|
+
setShouldConnect(true);
|
|
188
|
+
setStatus((currentStatus) => currentStatus === "open" ? currentStatus : "connecting");
|
|
189
|
+
setConnectionToken((currentToken) => currentToken + 1);
|
|
190
|
+
}, []);
|
|
191
|
+
const disconnect = (0, import_react2.useCallback)(() => {
|
|
192
|
+
setShouldConnect(false);
|
|
193
|
+
abortControllerRef.current?.abort();
|
|
194
|
+
abortControllerRef.current = null;
|
|
195
|
+
setStatus("closed");
|
|
196
|
+
}, []);
|
|
197
|
+
const reset = (0, import_react2.useCallback)(() => {
|
|
198
|
+
setEvents([]);
|
|
199
|
+
setError(null);
|
|
200
|
+
}, []);
|
|
201
|
+
(0, import_react2.useEffect)(() => {
|
|
202
|
+
if (!shouldConnect) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
let cancelled = false;
|
|
206
|
+
const abortController = new AbortController();
|
|
207
|
+
abortControllerRef.current?.abort();
|
|
208
|
+
abortControllerRef.current = abortController;
|
|
209
|
+
async function subscribe() {
|
|
210
|
+
let reconnectAttempt = 0;
|
|
211
|
+
while (!cancelled) {
|
|
212
|
+
setStatus(reconnectAttempt > 0 ? "reconnecting" : "connecting");
|
|
213
|
+
try {
|
|
214
|
+
const stream = subscriptionClient.streams.subscribeSse({
|
|
215
|
+
...subscribeOptionsRef.current,
|
|
216
|
+
signal: abortController.signal
|
|
217
|
+
});
|
|
218
|
+
setError(null);
|
|
219
|
+
setStatus("open");
|
|
220
|
+
for await (const event of stream) {
|
|
221
|
+
if (cancelled) {
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
onEventRef.current?.(event);
|
|
225
|
+
setEvents((currentEvents) => appendEvent(currentEvents, event, boundedMaxEvents));
|
|
226
|
+
}
|
|
227
|
+
if (!cancelled) {
|
|
228
|
+
setStatus("closed");
|
|
229
|
+
}
|
|
230
|
+
break;
|
|
231
|
+
} catch (caughtError) {
|
|
232
|
+
if (cancelled || abortController.signal.aborted) {
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
const normalizedError = toError(caughtError);
|
|
236
|
+
setError(normalizedError);
|
|
237
|
+
onErrorRef.current?.(normalizedError);
|
|
238
|
+
if (!reconnect || reconnectAttempt >= maxReconnectAttempts) {
|
|
239
|
+
setStatus("error");
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
reconnectAttempt += 1;
|
|
243
|
+
setStatus("reconnecting");
|
|
244
|
+
await sleep(resolveReconnectDelay(reconnectDelayRef.current, reconnectAttempt), abortController.signal);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
void subscribe();
|
|
249
|
+
return () => {
|
|
250
|
+
cancelled = true;
|
|
251
|
+
abortController.abort();
|
|
252
|
+
if (abortControllerRef.current === abortController) {
|
|
253
|
+
abortControllerRef.current = null;
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
}, [
|
|
257
|
+
boundedMaxEvents,
|
|
258
|
+
connectionToken,
|
|
259
|
+
maxReconnectAttempts,
|
|
260
|
+
reconnect,
|
|
261
|
+
shouldConnect,
|
|
262
|
+
subscribeOptionsKey,
|
|
263
|
+
subscriptionClient
|
|
264
|
+
]);
|
|
265
|
+
const latestEvent = events.at(-1) ?? null;
|
|
266
|
+
return (0, import_react2.useMemo)(
|
|
267
|
+
() => ({
|
|
268
|
+
connect,
|
|
269
|
+
disconnect,
|
|
270
|
+
error,
|
|
271
|
+
events,
|
|
272
|
+
isConnected: status === "open",
|
|
273
|
+
isConnecting: status === "connecting" || status === "reconnecting",
|
|
274
|
+
latestEvent,
|
|
275
|
+
reset,
|
|
276
|
+
status
|
|
277
|
+
}),
|
|
278
|
+
[connect, disconnect, error, events, latestEvent, reset, status]
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
function useManagedStream(options) {
|
|
282
|
+
const { sseUrl, stream, enabled, ...subscriptionOptions } = options;
|
|
283
|
+
const managedSseUrl = sseUrl ?? stream?.sseUrl ?? "";
|
|
284
|
+
const shouldEnable = typeof enabled === "boolean" ? enabled : isNonEmptyString(managedSseUrl);
|
|
285
|
+
return useStreamSubscription({
|
|
286
|
+
...subscriptionOptions,
|
|
287
|
+
enabled: shouldEnable,
|
|
288
|
+
sseUrl: managedSseUrl
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
function useDirectStream(options) {
|
|
292
|
+
const { directUrl, enabled, session, sseUrl, ...subscriptionOptions } = options;
|
|
293
|
+
const directSseUrl = directUrl ?? sseUrl ?? session?.sseUrl ?? "";
|
|
294
|
+
const shouldEnable = typeof enabled === "boolean" ? enabled : isNonEmptyString(directSseUrl);
|
|
295
|
+
return useStreamSubscription({
|
|
296
|
+
...subscriptionOptions,
|
|
297
|
+
enabled: shouldEnable,
|
|
298
|
+
sseUrl: directSseUrl
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
302
|
+
0 && (module.exports = {
|
|
303
|
+
DEFAULT_MAX_EVENTS,
|
|
304
|
+
RestlessProvider,
|
|
305
|
+
useDirectStream,
|
|
306
|
+
useManagedStream,
|
|
307
|
+
useRestlessClient,
|
|
308
|
+
useStreamSubscription
|
|
309
|
+
});
|
|
310
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/provider.ts","../src/stream-subscription.ts","../src/stream-subscription-types.ts","../src/stream-subscription-utils.ts"],"sourcesContent":["export {\n RestlessProvider,\n type RestlessProviderProps,\n useRestlessClient,\n} from './provider.js';\nexport {\n DEFAULT_MAX_EVENTS,\n type StreamSubscriptionControls,\n type StreamSubscriptionStatus,\n type UseDirectStreamOptions,\n type UseManagedStreamOptions,\n type UseStreamSubscriptionOptions,\n type UseStreamSubscriptionResult,\n useDirectStream,\n useManagedStream,\n useStreamSubscription,\n} from './stream-subscription.js';\n","import { createRestlessClient, type RestlessClient, type RestlessClientConfig } from '@restless-stream/core';\nimport { createContext, createElement, type ReactNode, useContext, useMemo } from 'react';\n\nconst RestlessClientContext = createContext<RestlessClient | null>(null);\ntype InlineRestlessClientConfig = {\n [K in keyof RestlessClientConfig]-?: RestlessClientConfig[K] | undefined;\n};\n\nexport type RestlessProviderProps = Partial<RestlessClientConfig> & {\n children?: ReactNode;\n client?: RestlessClient;\n config?: RestlessClientConfig;\n};\n\nexport function RestlessProvider({\n apiBaseUrl,\n apiKey,\n children,\n client,\n config,\n fetch,\n headers,\n streamBaseUrl,\n}: RestlessProviderProps) {\n const inlineConfig = useMemo(\n () =>\n compactConfig({\n apiBaseUrl,\n apiKey,\n fetch,\n headers,\n streamBaseUrl,\n }),\n [apiBaseUrl, apiKey, fetch, headers, streamBaseUrl],\n );\n const providerConfig = config ?? inlineConfig;\n const contextClient = useMemo(() => client ?? createRestlessClient(providerConfig), [client, providerConfig]);\n\n return createElement(RestlessClientContext.Provider, { value: contextClient }, children);\n}\n\nexport function useRestlessClient(): RestlessClient {\n const client = useContext(RestlessClientContext);\n\n if (!client) {\n throw new Error('useRestlessClient must be used within a RestlessProvider or with an explicit client option.');\n }\n\n return client;\n}\n\nexport function useOptionalRestlessClient(): RestlessClient | null {\n return useContext(RestlessClientContext);\n}\n\nfunction compactConfig(config: InlineRestlessClientConfig): RestlessClientConfig {\n return Object.fromEntries(Object.entries(config).filter(([, value]) => value !== undefined));\n}\n","import type { RestlessStreamEvent } from '@restless-stream/core';\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\nimport { useOptionalRestlessClient } from './provider.js';\nimport {\n DEFAULT_MAX_EVENTS,\n type StreamSubscriptionStatus,\n type UseDirectStreamOptions,\n type UseManagedStreamOptions,\n type UseStreamSubscriptionOptions,\n type UseStreamSubscriptionResult,\n} from './stream-subscription-types.js';\nimport {\n appendEvent,\n isNonEmptyString,\n normalizeMaxEvents,\n resolveReconnectDelay,\n sleep,\n stableOptionsKey,\n toError,\n} from './stream-subscription-utils.js';\n\nexport { DEFAULT_MAX_EVENTS } from './stream-subscription-types.js';\nexport type {\n StreamSubscriptionControls,\n StreamSubscriptionStatus,\n UseDirectStreamOptions,\n UseManagedStreamOptions,\n UseStreamSubscriptionOptions,\n UseStreamSubscriptionResult,\n} from './stream-subscription-types.js';\n\nexport function useStreamSubscription(options: UseStreamSubscriptionOptions): UseStreamSubscriptionResult {\n const {\n client: explicitClient,\n enabled = true,\n maxEvents = DEFAULT_MAX_EVENTS,\n maxReconnectAttempts = 0,\n onError,\n onEvent,\n reconnect = false,\n reconnectDelayMs = 1000,\n ...subscribeOptions\n } = options;\n const contextClient = useOptionalRestlessClient();\n const client = explicitClient ?? contextClient;\n\n if (!client) {\n throw new Error('useStreamSubscription requires a RestlessProvider or an explicit client option.');\n }\n\n const subscriptionClient = client;\n\n const boundedMaxEvents = normalizeMaxEvents(maxEvents);\n const subscribeOptionsKey = stableOptionsKey(subscribeOptions);\n const abortControllerRef = useRef<AbortController | null>(null);\n const onErrorRef = useRef(onError);\n const onEventRef = useRef(onEvent);\n const reconnectDelayRef = useRef(reconnectDelayMs);\n const subscribeOptionsRef = useRef(subscribeOptions);\n const [connectionToken, setConnectionToken] = useState(0);\n const [events, setEvents] = useState<RestlessStreamEvent[]>([]);\n const [error, setError] = useState<Error | null>(null);\n const [shouldConnect, setShouldConnect] = useState(enabled);\n const [status, setStatus] = useState<StreamSubscriptionStatus>(enabled ? 'connecting' : 'idle');\n\n onErrorRef.current = onError;\n onEventRef.current = onEvent;\n reconnectDelayRef.current = reconnectDelayMs;\n subscribeOptionsRef.current = subscribeOptions;\n\n useEffect(() => {\n setShouldConnect(enabled);\n setStatus((currentStatus) => {\n if (enabled) {\n return currentStatus === 'idle' || currentStatus === 'closed' ? 'connecting' : currentStatus;\n }\n\n return 'idle';\n });\n }, [enabled]);\n\n const connect = useCallback(() => {\n setError(null);\n setShouldConnect(true);\n setStatus((currentStatus) => (currentStatus === 'open' ? currentStatus : 'connecting'));\n setConnectionToken((currentToken) => currentToken + 1);\n }, []);\n\n const disconnect = useCallback(() => {\n setShouldConnect(false);\n abortControllerRef.current?.abort();\n abortControllerRef.current = null;\n setStatus('closed');\n }, []);\n\n const reset = useCallback(() => {\n setEvents([]);\n setError(null);\n }, []);\n\n useEffect(() => {\n if (!shouldConnect) {\n return;\n }\n\n let cancelled = false;\n const abortController = new AbortController();\n abortControllerRef.current?.abort();\n abortControllerRef.current = abortController;\n\n async function subscribe() {\n let reconnectAttempt = 0;\n\n while (!cancelled) {\n setStatus(reconnectAttempt > 0 ? 'reconnecting' : 'connecting');\n\n try {\n const stream = subscriptionClient.streams.subscribeSse({\n ...subscribeOptionsRef.current,\n signal: abortController.signal,\n });\n\n setError(null);\n setStatus('open');\n\n for await (const event of stream) {\n if (cancelled) {\n break;\n }\n\n onEventRef.current?.(event);\n setEvents((currentEvents) => appendEvent(currentEvents, event, boundedMaxEvents));\n }\n\n if (!cancelled) {\n setStatus('closed');\n }\n\n break;\n } catch (caughtError) {\n if (cancelled || abortController.signal.aborted) {\n break;\n }\n\n const normalizedError = toError(caughtError);\n setError(normalizedError);\n onErrorRef.current?.(normalizedError);\n\n if (!reconnect || reconnectAttempt >= maxReconnectAttempts) {\n setStatus('error');\n break;\n }\n\n reconnectAttempt += 1;\n setStatus('reconnecting');\n await sleep(resolveReconnectDelay(reconnectDelayRef.current, reconnectAttempt), abortController.signal);\n }\n }\n }\n\n void subscribe();\n\n return () => {\n cancelled = true;\n abortController.abort();\n\n if (abortControllerRef.current === abortController) {\n abortControllerRef.current = null;\n }\n };\n }, [\n boundedMaxEvents,\n connectionToken,\n maxReconnectAttempts,\n reconnect,\n shouldConnect,\n subscribeOptionsKey,\n subscriptionClient,\n ]);\n\n const latestEvent = events.at(-1) ?? null;\n\n return useMemo(\n () => ({\n connect,\n disconnect,\n error,\n events,\n isConnected: status === 'open',\n isConnecting: status === 'connecting' || status === 'reconnecting',\n latestEvent,\n reset,\n status,\n }),\n [connect, disconnect, error, events, latestEvent, reset, status],\n );\n}\n\nexport function useManagedStream(options: UseManagedStreamOptions): UseStreamSubscriptionResult {\n const { sseUrl, stream, enabled, ...subscriptionOptions } = options;\n const managedSseUrl = sseUrl ?? stream?.sseUrl ?? '';\n const shouldEnable = typeof enabled === 'boolean' ? enabled : isNonEmptyString(managedSseUrl);\n\n return useStreamSubscription({\n ...subscriptionOptions,\n enabled: shouldEnable,\n sseUrl: managedSseUrl,\n });\n}\n\nexport function useDirectStream(options: UseDirectStreamOptions): UseStreamSubscriptionResult {\n const { directUrl, enabled, session, sseUrl, ...subscriptionOptions } = options;\n const directSseUrl = directUrl ?? sseUrl ?? session?.sseUrl ?? '';\n const shouldEnable = typeof enabled === 'boolean' ? enabled : isNonEmptyString(directSseUrl);\n\n return useStreamSubscription({\n ...subscriptionOptions,\n enabled: shouldEnable,\n sseUrl: directSseUrl,\n });\n}\n","import type { RestlessClient, RestlessStreamEvent, SubscribeSseOptions } from '@restless-stream/core';\n\nexport const DEFAULT_MAX_EVENTS = 100;\n\nexport type StreamSubscriptionStatus = 'idle' | 'connecting' | 'open' | 'reconnecting' | 'closed' | 'error';\n\nexport interface StreamSubscriptionControls {\n connect: () => void;\n disconnect: () => void;\n reset: () => void;\n}\n\nexport type UseStreamSubscriptionOptions = Omit<SubscribeSseOptions, 'reconnect' | 'reconnectDelayMs'> & {\n client?: RestlessClient;\n enabled?: boolean;\n maxEvents?: number;\n maxReconnectAttempts?: number;\n onError?: (error: Error) => void;\n onEvent?: (event: RestlessStreamEvent) => void;\n reconnect?: boolean;\n reconnectDelayMs?: number | ((attempt: number) => number);\n};\n\nexport type UseStreamSubscriptionResult = StreamSubscriptionControls & {\n error: Error | null;\n events: RestlessStreamEvent[];\n isConnected: boolean;\n isConnecting: boolean;\n latestEvent: RestlessStreamEvent | null;\n status: StreamSubscriptionStatus;\n};\n\nexport type UseManagedStreamOptions = Omit<UseStreamSubscriptionOptions, 'sseUrl'> & {\n sseUrl?: SubscribeSseOptions['sseUrl'] | null;\n stream?: { sseUrl?: string | null } | null;\n};\n\nexport type UseDirectStreamOptions = Omit<UseStreamSubscriptionOptions, 'sseUrl'> & {\n directUrl?: string | null;\n session?: { sseUrl?: string | null } | null;\n sseUrl?: SubscribeSseOptions['sseUrl'] | null;\n};\n","import type { RestlessStreamEvent, SubscribeSseOptions } from '@restless-stream/core';\n\nimport { DEFAULT_MAX_EVENTS } from './stream-subscription-types.js';\n\nexport function isNonEmptyString(value: unknown): boolean {\n return typeof value === 'string' && value.length > 0;\n}\n\nexport function appendEvent(\n currentEvents: RestlessStreamEvent[],\n event: RestlessStreamEvent,\n maxEvents: number,\n): RestlessStreamEvent[] {\n if (maxEvents === 0) {\n return [];\n }\n\n const nextEvents = [...currentEvents, event];\n return nextEvents.length > maxEvents ? nextEvents.slice(nextEvents.length - maxEvents) : nextEvents;\n}\n\nexport function normalizeMaxEvents(maxEvents: number): number {\n if (!Number.isFinite(maxEvents)) {\n return DEFAULT_MAX_EVENTS;\n }\n\n return Math.max(0, Math.floor(maxEvents));\n}\n\nexport function resolveReconnectDelay(delay: number | ((attempt: number) => number), attempt: number): number {\n const resolvedDelay = typeof delay === 'function' ? delay(attempt) : delay;\n return Number.isFinite(resolvedDelay) ? Math.max(0, resolvedDelay) : 0;\n}\n\nexport async function sleep(milliseconds: number, signal: AbortSignal): Promise<void> {\n if (milliseconds === 0 || signal.aborted) {\n return;\n }\n\n return new Promise((resolve) => {\n const timeout = setTimeout(resolve, milliseconds);\n\n signal.addEventListener(\n 'abort',\n () => {\n clearTimeout(timeout);\n resolve();\n },\n { once: true },\n );\n });\n}\n\nexport function stableOptionsKey(options: Partial<SubscribeSseOptions>): string {\n return JSON.stringify(sortRecord(options));\n}\n\nexport function toError(error: unknown): Error {\n if (error instanceof Error) {\n return error;\n }\n\n return new Error(typeof error === 'string' ? error : 'Restless stream subscription failed.');\n}\n\nfunction sortRecord(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map((item) => sortRecord(item));\n }\n\n if (typeof value === 'function') {\n return value.toString();\n }\n\n if (value === null || value === undefined || typeof value !== 'object') {\n return value;\n }\n\n return Object.fromEntries(\n Object.entries(value)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, nestedValue]) => [key, sortRecord(nestedValue)]),\n );\n}\n"],"mappings":"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","names":["import_react"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { RestlessClientConfig, RestlessClient, SubscribeSseOptions, RestlessStreamEvent } from '@restless-stream/core';
|
|
4
|
+
|
|
5
|
+
type RestlessProviderProps = Partial<RestlessClientConfig> & {
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
client?: RestlessClient;
|
|
8
|
+
config?: RestlessClientConfig;
|
|
9
|
+
};
|
|
10
|
+
declare function RestlessProvider({ apiBaseUrl, apiKey, children, client, config, fetch, headers, streamBaseUrl, }: RestlessProviderProps): react.FunctionComponentElement<react.ProviderProps<RestlessClient | null>>;
|
|
11
|
+
declare function useRestlessClient(): RestlessClient;
|
|
12
|
+
|
|
13
|
+
declare const DEFAULT_MAX_EVENTS = 100;
|
|
14
|
+
type StreamSubscriptionStatus = 'idle' | 'connecting' | 'open' | 'reconnecting' | 'closed' | 'error';
|
|
15
|
+
interface StreamSubscriptionControls {
|
|
16
|
+
connect: () => void;
|
|
17
|
+
disconnect: () => void;
|
|
18
|
+
reset: () => void;
|
|
19
|
+
}
|
|
20
|
+
type UseStreamSubscriptionOptions = Omit<SubscribeSseOptions, 'reconnect' | 'reconnectDelayMs'> & {
|
|
21
|
+
client?: RestlessClient;
|
|
22
|
+
enabled?: boolean;
|
|
23
|
+
maxEvents?: number;
|
|
24
|
+
maxReconnectAttempts?: number;
|
|
25
|
+
onError?: (error: Error) => void;
|
|
26
|
+
onEvent?: (event: RestlessStreamEvent) => void;
|
|
27
|
+
reconnect?: boolean;
|
|
28
|
+
reconnectDelayMs?: number | ((attempt: number) => number);
|
|
29
|
+
};
|
|
30
|
+
type UseStreamSubscriptionResult = StreamSubscriptionControls & {
|
|
31
|
+
error: Error | null;
|
|
32
|
+
events: RestlessStreamEvent[];
|
|
33
|
+
isConnected: boolean;
|
|
34
|
+
isConnecting: boolean;
|
|
35
|
+
latestEvent: RestlessStreamEvent | null;
|
|
36
|
+
status: StreamSubscriptionStatus;
|
|
37
|
+
};
|
|
38
|
+
type UseManagedStreamOptions = Omit<UseStreamSubscriptionOptions, 'sseUrl'> & {
|
|
39
|
+
sseUrl?: SubscribeSseOptions['sseUrl'] | null;
|
|
40
|
+
stream?: {
|
|
41
|
+
sseUrl?: string | null;
|
|
42
|
+
} | null;
|
|
43
|
+
};
|
|
44
|
+
type UseDirectStreamOptions = Omit<UseStreamSubscriptionOptions, 'sseUrl'> & {
|
|
45
|
+
directUrl?: string | null;
|
|
46
|
+
session?: {
|
|
47
|
+
sseUrl?: string | null;
|
|
48
|
+
} | null;
|
|
49
|
+
sseUrl?: SubscribeSseOptions['sseUrl'] | null;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
declare function useStreamSubscription(options: UseStreamSubscriptionOptions): UseStreamSubscriptionResult;
|
|
53
|
+
declare function useManagedStream(options: UseManagedStreamOptions): UseStreamSubscriptionResult;
|
|
54
|
+
declare function useDirectStream(options: UseDirectStreamOptions): UseStreamSubscriptionResult;
|
|
55
|
+
|
|
56
|
+
export { DEFAULT_MAX_EVENTS, RestlessProvider, type RestlessProviderProps, type StreamSubscriptionControls, type StreamSubscriptionStatus, type UseDirectStreamOptions, type UseManagedStreamOptions, type UseStreamSubscriptionOptions, type UseStreamSubscriptionResult, useDirectStream, useManagedStream, useRestlessClient, useStreamSubscription };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { RestlessClientConfig, RestlessClient, SubscribeSseOptions, RestlessStreamEvent } from '@restless-stream/core';
|
|
4
|
+
|
|
5
|
+
type RestlessProviderProps = Partial<RestlessClientConfig> & {
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
client?: RestlessClient;
|
|
8
|
+
config?: RestlessClientConfig;
|
|
9
|
+
};
|
|
10
|
+
declare function RestlessProvider({ apiBaseUrl, apiKey, children, client, config, fetch, headers, streamBaseUrl, }: RestlessProviderProps): react.FunctionComponentElement<react.ProviderProps<RestlessClient | null>>;
|
|
11
|
+
declare function useRestlessClient(): RestlessClient;
|
|
12
|
+
|
|
13
|
+
declare const DEFAULT_MAX_EVENTS = 100;
|
|
14
|
+
type StreamSubscriptionStatus = 'idle' | 'connecting' | 'open' | 'reconnecting' | 'closed' | 'error';
|
|
15
|
+
interface StreamSubscriptionControls {
|
|
16
|
+
connect: () => void;
|
|
17
|
+
disconnect: () => void;
|
|
18
|
+
reset: () => void;
|
|
19
|
+
}
|
|
20
|
+
type UseStreamSubscriptionOptions = Omit<SubscribeSseOptions, 'reconnect' | 'reconnectDelayMs'> & {
|
|
21
|
+
client?: RestlessClient;
|
|
22
|
+
enabled?: boolean;
|
|
23
|
+
maxEvents?: number;
|
|
24
|
+
maxReconnectAttempts?: number;
|
|
25
|
+
onError?: (error: Error) => void;
|
|
26
|
+
onEvent?: (event: RestlessStreamEvent) => void;
|
|
27
|
+
reconnect?: boolean;
|
|
28
|
+
reconnectDelayMs?: number | ((attempt: number) => number);
|
|
29
|
+
};
|
|
30
|
+
type UseStreamSubscriptionResult = StreamSubscriptionControls & {
|
|
31
|
+
error: Error | null;
|
|
32
|
+
events: RestlessStreamEvent[];
|
|
33
|
+
isConnected: boolean;
|
|
34
|
+
isConnecting: boolean;
|
|
35
|
+
latestEvent: RestlessStreamEvent | null;
|
|
36
|
+
status: StreamSubscriptionStatus;
|
|
37
|
+
};
|
|
38
|
+
type UseManagedStreamOptions = Omit<UseStreamSubscriptionOptions, 'sseUrl'> & {
|
|
39
|
+
sseUrl?: SubscribeSseOptions['sseUrl'] | null;
|
|
40
|
+
stream?: {
|
|
41
|
+
sseUrl?: string | null;
|
|
42
|
+
} | null;
|
|
43
|
+
};
|
|
44
|
+
type UseDirectStreamOptions = Omit<UseStreamSubscriptionOptions, 'sseUrl'> & {
|
|
45
|
+
directUrl?: string | null;
|
|
46
|
+
session?: {
|
|
47
|
+
sseUrl?: string | null;
|
|
48
|
+
} | null;
|
|
49
|
+
sseUrl?: SubscribeSseOptions['sseUrl'] | null;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
declare function useStreamSubscription(options: UseStreamSubscriptionOptions): UseStreamSubscriptionResult;
|
|
53
|
+
declare function useManagedStream(options: UseManagedStreamOptions): UseStreamSubscriptionResult;
|
|
54
|
+
declare function useDirectStream(options: UseDirectStreamOptions): UseStreamSubscriptionResult;
|
|
55
|
+
|
|
56
|
+
export { DEFAULT_MAX_EVENTS, RestlessProvider, type RestlessProviderProps, type StreamSubscriptionControls, type StreamSubscriptionStatus, type UseDirectStreamOptions, type UseManagedStreamOptions, type UseStreamSubscriptionOptions, type UseStreamSubscriptionResult, useDirectStream, useManagedStream, useRestlessClient, useStreamSubscription };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
// src/provider.ts
|
|
2
|
+
import { createRestlessClient } from "@restless-stream/core";
|
|
3
|
+
import { createContext, createElement, useContext, useMemo } from "react";
|
|
4
|
+
var RestlessClientContext = createContext(null);
|
|
5
|
+
function RestlessProvider({
|
|
6
|
+
apiBaseUrl,
|
|
7
|
+
apiKey,
|
|
8
|
+
children,
|
|
9
|
+
client,
|
|
10
|
+
config,
|
|
11
|
+
fetch,
|
|
12
|
+
headers,
|
|
13
|
+
streamBaseUrl
|
|
14
|
+
}) {
|
|
15
|
+
const inlineConfig = useMemo(
|
|
16
|
+
() => compactConfig({
|
|
17
|
+
apiBaseUrl,
|
|
18
|
+
apiKey,
|
|
19
|
+
fetch,
|
|
20
|
+
headers,
|
|
21
|
+
streamBaseUrl
|
|
22
|
+
}),
|
|
23
|
+
[apiBaseUrl, apiKey, fetch, headers, streamBaseUrl]
|
|
24
|
+
);
|
|
25
|
+
const providerConfig = config ?? inlineConfig;
|
|
26
|
+
const contextClient = useMemo(() => client ?? createRestlessClient(providerConfig), [client, providerConfig]);
|
|
27
|
+
return createElement(RestlessClientContext.Provider, { value: contextClient }, children);
|
|
28
|
+
}
|
|
29
|
+
function useRestlessClient() {
|
|
30
|
+
const client = useContext(RestlessClientContext);
|
|
31
|
+
if (!client) {
|
|
32
|
+
throw new Error("useRestlessClient must be used within a RestlessProvider or with an explicit client option.");
|
|
33
|
+
}
|
|
34
|
+
return client;
|
|
35
|
+
}
|
|
36
|
+
function useOptionalRestlessClient() {
|
|
37
|
+
return useContext(RestlessClientContext);
|
|
38
|
+
}
|
|
39
|
+
function compactConfig(config) {
|
|
40
|
+
return Object.fromEntries(Object.entries(config).filter(([, value]) => value !== void 0));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/stream-subscription.ts
|
|
44
|
+
import { useCallback, useEffect, useMemo as useMemo2, useRef, useState } from "react";
|
|
45
|
+
|
|
46
|
+
// src/stream-subscription-types.ts
|
|
47
|
+
var DEFAULT_MAX_EVENTS = 100;
|
|
48
|
+
|
|
49
|
+
// src/stream-subscription-utils.ts
|
|
50
|
+
function isNonEmptyString(value) {
|
|
51
|
+
return typeof value === "string" && value.length > 0;
|
|
52
|
+
}
|
|
53
|
+
function appendEvent(currentEvents, event, maxEvents) {
|
|
54
|
+
if (maxEvents === 0) {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
const nextEvents = [...currentEvents, event];
|
|
58
|
+
return nextEvents.length > maxEvents ? nextEvents.slice(nextEvents.length - maxEvents) : nextEvents;
|
|
59
|
+
}
|
|
60
|
+
function normalizeMaxEvents(maxEvents) {
|
|
61
|
+
if (!Number.isFinite(maxEvents)) {
|
|
62
|
+
return DEFAULT_MAX_EVENTS;
|
|
63
|
+
}
|
|
64
|
+
return Math.max(0, Math.floor(maxEvents));
|
|
65
|
+
}
|
|
66
|
+
function resolveReconnectDelay(delay, attempt) {
|
|
67
|
+
const resolvedDelay = typeof delay === "function" ? delay(attempt) : delay;
|
|
68
|
+
return Number.isFinite(resolvedDelay) ? Math.max(0, resolvedDelay) : 0;
|
|
69
|
+
}
|
|
70
|
+
async function sleep(milliseconds, signal) {
|
|
71
|
+
if (milliseconds === 0 || signal.aborted) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
return new Promise((resolve) => {
|
|
75
|
+
const timeout = setTimeout(resolve, milliseconds);
|
|
76
|
+
signal.addEventListener(
|
|
77
|
+
"abort",
|
|
78
|
+
() => {
|
|
79
|
+
clearTimeout(timeout);
|
|
80
|
+
resolve();
|
|
81
|
+
},
|
|
82
|
+
{ once: true }
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function stableOptionsKey(options) {
|
|
87
|
+
return JSON.stringify(sortRecord(options));
|
|
88
|
+
}
|
|
89
|
+
function toError(error) {
|
|
90
|
+
if (error instanceof Error) {
|
|
91
|
+
return error;
|
|
92
|
+
}
|
|
93
|
+
return new Error(typeof error === "string" ? error : "Restless stream subscription failed.");
|
|
94
|
+
}
|
|
95
|
+
function sortRecord(value) {
|
|
96
|
+
if (Array.isArray(value)) {
|
|
97
|
+
return value.map((item) => sortRecord(item));
|
|
98
|
+
}
|
|
99
|
+
if (typeof value === "function") {
|
|
100
|
+
return value.toString();
|
|
101
|
+
}
|
|
102
|
+
if (value === null || value === void 0 || typeof value !== "object") {
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
return Object.fromEntries(
|
|
106
|
+
Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, nestedValue]) => [key, sortRecord(nestedValue)])
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/stream-subscription.ts
|
|
111
|
+
function useStreamSubscription(options) {
|
|
112
|
+
const {
|
|
113
|
+
client: explicitClient,
|
|
114
|
+
enabled = true,
|
|
115
|
+
maxEvents = DEFAULT_MAX_EVENTS,
|
|
116
|
+
maxReconnectAttempts = 0,
|
|
117
|
+
onError,
|
|
118
|
+
onEvent,
|
|
119
|
+
reconnect = false,
|
|
120
|
+
reconnectDelayMs = 1e3,
|
|
121
|
+
...subscribeOptions
|
|
122
|
+
} = options;
|
|
123
|
+
const contextClient = useOptionalRestlessClient();
|
|
124
|
+
const client = explicitClient ?? contextClient;
|
|
125
|
+
if (!client) {
|
|
126
|
+
throw new Error("useStreamSubscription requires a RestlessProvider or an explicit client option.");
|
|
127
|
+
}
|
|
128
|
+
const subscriptionClient = client;
|
|
129
|
+
const boundedMaxEvents = normalizeMaxEvents(maxEvents);
|
|
130
|
+
const subscribeOptionsKey = stableOptionsKey(subscribeOptions);
|
|
131
|
+
const abortControllerRef = useRef(null);
|
|
132
|
+
const onErrorRef = useRef(onError);
|
|
133
|
+
const onEventRef = useRef(onEvent);
|
|
134
|
+
const reconnectDelayRef = useRef(reconnectDelayMs);
|
|
135
|
+
const subscribeOptionsRef = useRef(subscribeOptions);
|
|
136
|
+
const [connectionToken, setConnectionToken] = useState(0);
|
|
137
|
+
const [events, setEvents] = useState([]);
|
|
138
|
+
const [error, setError] = useState(null);
|
|
139
|
+
const [shouldConnect, setShouldConnect] = useState(enabled);
|
|
140
|
+
const [status, setStatus] = useState(enabled ? "connecting" : "idle");
|
|
141
|
+
onErrorRef.current = onError;
|
|
142
|
+
onEventRef.current = onEvent;
|
|
143
|
+
reconnectDelayRef.current = reconnectDelayMs;
|
|
144
|
+
subscribeOptionsRef.current = subscribeOptions;
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
setShouldConnect(enabled);
|
|
147
|
+
setStatus((currentStatus) => {
|
|
148
|
+
if (enabled) {
|
|
149
|
+
return currentStatus === "idle" || currentStatus === "closed" ? "connecting" : currentStatus;
|
|
150
|
+
}
|
|
151
|
+
return "idle";
|
|
152
|
+
});
|
|
153
|
+
}, [enabled]);
|
|
154
|
+
const connect = useCallback(() => {
|
|
155
|
+
setError(null);
|
|
156
|
+
setShouldConnect(true);
|
|
157
|
+
setStatus((currentStatus) => currentStatus === "open" ? currentStatus : "connecting");
|
|
158
|
+
setConnectionToken((currentToken) => currentToken + 1);
|
|
159
|
+
}, []);
|
|
160
|
+
const disconnect = useCallback(() => {
|
|
161
|
+
setShouldConnect(false);
|
|
162
|
+
abortControllerRef.current?.abort();
|
|
163
|
+
abortControllerRef.current = null;
|
|
164
|
+
setStatus("closed");
|
|
165
|
+
}, []);
|
|
166
|
+
const reset = useCallback(() => {
|
|
167
|
+
setEvents([]);
|
|
168
|
+
setError(null);
|
|
169
|
+
}, []);
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
if (!shouldConnect) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
let cancelled = false;
|
|
175
|
+
const abortController = new AbortController();
|
|
176
|
+
abortControllerRef.current?.abort();
|
|
177
|
+
abortControllerRef.current = abortController;
|
|
178
|
+
async function subscribe() {
|
|
179
|
+
let reconnectAttempt = 0;
|
|
180
|
+
while (!cancelled) {
|
|
181
|
+
setStatus(reconnectAttempt > 0 ? "reconnecting" : "connecting");
|
|
182
|
+
try {
|
|
183
|
+
const stream = subscriptionClient.streams.subscribeSse({
|
|
184
|
+
...subscribeOptionsRef.current,
|
|
185
|
+
signal: abortController.signal
|
|
186
|
+
});
|
|
187
|
+
setError(null);
|
|
188
|
+
setStatus("open");
|
|
189
|
+
for await (const event of stream) {
|
|
190
|
+
if (cancelled) {
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
onEventRef.current?.(event);
|
|
194
|
+
setEvents((currentEvents) => appendEvent(currentEvents, event, boundedMaxEvents));
|
|
195
|
+
}
|
|
196
|
+
if (!cancelled) {
|
|
197
|
+
setStatus("closed");
|
|
198
|
+
}
|
|
199
|
+
break;
|
|
200
|
+
} catch (caughtError) {
|
|
201
|
+
if (cancelled || abortController.signal.aborted) {
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
const normalizedError = toError(caughtError);
|
|
205
|
+
setError(normalizedError);
|
|
206
|
+
onErrorRef.current?.(normalizedError);
|
|
207
|
+
if (!reconnect || reconnectAttempt >= maxReconnectAttempts) {
|
|
208
|
+
setStatus("error");
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
reconnectAttempt += 1;
|
|
212
|
+
setStatus("reconnecting");
|
|
213
|
+
await sleep(resolveReconnectDelay(reconnectDelayRef.current, reconnectAttempt), abortController.signal);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
void subscribe();
|
|
218
|
+
return () => {
|
|
219
|
+
cancelled = true;
|
|
220
|
+
abortController.abort();
|
|
221
|
+
if (abortControllerRef.current === abortController) {
|
|
222
|
+
abortControllerRef.current = null;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
}, [
|
|
226
|
+
boundedMaxEvents,
|
|
227
|
+
connectionToken,
|
|
228
|
+
maxReconnectAttempts,
|
|
229
|
+
reconnect,
|
|
230
|
+
shouldConnect,
|
|
231
|
+
subscribeOptionsKey,
|
|
232
|
+
subscriptionClient
|
|
233
|
+
]);
|
|
234
|
+
const latestEvent = events.at(-1) ?? null;
|
|
235
|
+
return useMemo2(
|
|
236
|
+
() => ({
|
|
237
|
+
connect,
|
|
238
|
+
disconnect,
|
|
239
|
+
error,
|
|
240
|
+
events,
|
|
241
|
+
isConnected: status === "open",
|
|
242
|
+
isConnecting: status === "connecting" || status === "reconnecting",
|
|
243
|
+
latestEvent,
|
|
244
|
+
reset,
|
|
245
|
+
status
|
|
246
|
+
}),
|
|
247
|
+
[connect, disconnect, error, events, latestEvent, reset, status]
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
function useManagedStream(options) {
|
|
251
|
+
const { sseUrl, stream, enabled, ...subscriptionOptions } = options;
|
|
252
|
+
const managedSseUrl = sseUrl ?? stream?.sseUrl ?? "";
|
|
253
|
+
const shouldEnable = typeof enabled === "boolean" ? enabled : isNonEmptyString(managedSseUrl);
|
|
254
|
+
return useStreamSubscription({
|
|
255
|
+
...subscriptionOptions,
|
|
256
|
+
enabled: shouldEnable,
|
|
257
|
+
sseUrl: managedSseUrl
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
function useDirectStream(options) {
|
|
261
|
+
const { directUrl, enabled, session, sseUrl, ...subscriptionOptions } = options;
|
|
262
|
+
const directSseUrl = directUrl ?? sseUrl ?? session?.sseUrl ?? "";
|
|
263
|
+
const shouldEnable = typeof enabled === "boolean" ? enabled : isNonEmptyString(directSseUrl);
|
|
264
|
+
return useStreamSubscription({
|
|
265
|
+
...subscriptionOptions,
|
|
266
|
+
enabled: shouldEnable,
|
|
267
|
+
sseUrl: directSseUrl
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
export {
|
|
271
|
+
DEFAULT_MAX_EVENTS,
|
|
272
|
+
RestlessProvider,
|
|
273
|
+
useDirectStream,
|
|
274
|
+
useManagedStream,
|
|
275
|
+
useRestlessClient,
|
|
276
|
+
useStreamSubscription
|
|
277
|
+
};
|
|
278
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/provider.ts","../src/stream-subscription.ts","../src/stream-subscription-types.ts","../src/stream-subscription-utils.ts"],"sourcesContent":["import { createRestlessClient, type RestlessClient, type RestlessClientConfig } from '@restless-stream/core';\nimport { createContext, createElement, type ReactNode, useContext, useMemo } from 'react';\n\nconst RestlessClientContext = createContext<RestlessClient | null>(null);\ntype InlineRestlessClientConfig = {\n [K in keyof RestlessClientConfig]-?: RestlessClientConfig[K] | undefined;\n};\n\nexport type RestlessProviderProps = Partial<RestlessClientConfig> & {\n children?: ReactNode;\n client?: RestlessClient;\n config?: RestlessClientConfig;\n};\n\nexport function RestlessProvider({\n apiBaseUrl,\n apiKey,\n children,\n client,\n config,\n fetch,\n headers,\n streamBaseUrl,\n}: RestlessProviderProps) {\n const inlineConfig = useMemo(\n () =>\n compactConfig({\n apiBaseUrl,\n apiKey,\n fetch,\n headers,\n streamBaseUrl,\n }),\n [apiBaseUrl, apiKey, fetch, headers, streamBaseUrl],\n );\n const providerConfig = config ?? inlineConfig;\n const contextClient = useMemo(() => client ?? createRestlessClient(providerConfig), [client, providerConfig]);\n\n return createElement(RestlessClientContext.Provider, { value: contextClient }, children);\n}\n\nexport function useRestlessClient(): RestlessClient {\n const client = useContext(RestlessClientContext);\n\n if (!client) {\n throw new Error('useRestlessClient must be used within a RestlessProvider or with an explicit client option.');\n }\n\n return client;\n}\n\nexport function useOptionalRestlessClient(): RestlessClient | null {\n return useContext(RestlessClientContext);\n}\n\nfunction compactConfig(config: InlineRestlessClientConfig): RestlessClientConfig {\n return Object.fromEntries(Object.entries(config).filter(([, value]) => value !== undefined));\n}\n","import type { RestlessStreamEvent } from '@restless-stream/core';\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\nimport { useOptionalRestlessClient } from './provider.js';\nimport {\n DEFAULT_MAX_EVENTS,\n type StreamSubscriptionStatus,\n type UseDirectStreamOptions,\n type UseManagedStreamOptions,\n type UseStreamSubscriptionOptions,\n type UseStreamSubscriptionResult,\n} from './stream-subscription-types.js';\nimport {\n appendEvent,\n isNonEmptyString,\n normalizeMaxEvents,\n resolveReconnectDelay,\n sleep,\n stableOptionsKey,\n toError,\n} from './stream-subscription-utils.js';\n\nexport { DEFAULT_MAX_EVENTS } from './stream-subscription-types.js';\nexport type {\n StreamSubscriptionControls,\n StreamSubscriptionStatus,\n UseDirectStreamOptions,\n UseManagedStreamOptions,\n UseStreamSubscriptionOptions,\n UseStreamSubscriptionResult,\n} from './stream-subscription-types.js';\n\nexport function useStreamSubscription(options: UseStreamSubscriptionOptions): UseStreamSubscriptionResult {\n const {\n client: explicitClient,\n enabled = true,\n maxEvents = DEFAULT_MAX_EVENTS,\n maxReconnectAttempts = 0,\n onError,\n onEvent,\n reconnect = false,\n reconnectDelayMs = 1000,\n ...subscribeOptions\n } = options;\n const contextClient = useOptionalRestlessClient();\n const client = explicitClient ?? contextClient;\n\n if (!client) {\n throw new Error('useStreamSubscription requires a RestlessProvider or an explicit client option.');\n }\n\n const subscriptionClient = client;\n\n const boundedMaxEvents = normalizeMaxEvents(maxEvents);\n const subscribeOptionsKey = stableOptionsKey(subscribeOptions);\n const abortControllerRef = useRef<AbortController | null>(null);\n const onErrorRef = useRef(onError);\n const onEventRef = useRef(onEvent);\n const reconnectDelayRef = useRef(reconnectDelayMs);\n const subscribeOptionsRef = useRef(subscribeOptions);\n const [connectionToken, setConnectionToken] = useState(0);\n const [events, setEvents] = useState<RestlessStreamEvent[]>([]);\n const [error, setError] = useState<Error | null>(null);\n const [shouldConnect, setShouldConnect] = useState(enabled);\n const [status, setStatus] = useState<StreamSubscriptionStatus>(enabled ? 'connecting' : 'idle');\n\n onErrorRef.current = onError;\n onEventRef.current = onEvent;\n reconnectDelayRef.current = reconnectDelayMs;\n subscribeOptionsRef.current = subscribeOptions;\n\n useEffect(() => {\n setShouldConnect(enabled);\n setStatus((currentStatus) => {\n if (enabled) {\n return currentStatus === 'idle' || currentStatus === 'closed' ? 'connecting' : currentStatus;\n }\n\n return 'idle';\n });\n }, [enabled]);\n\n const connect = useCallback(() => {\n setError(null);\n setShouldConnect(true);\n setStatus((currentStatus) => (currentStatus === 'open' ? currentStatus : 'connecting'));\n setConnectionToken((currentToken) => currentToken + 1);\n }, []);\n\n const disconnect = useCallback(() => {\n setShouldConnect(false);\n abortControllerRef.current?.abort();\n abortControllerRef.current = null;\n setStatus('closed');\n }, []);\n\n const reset = useCallback(() => {\n setEvents([]);\n setError(null);\n }, []);\n\n useEffect(() => {\n if (!shouldConnect) {\n return;\n }\n\n let cancelled = false;\n const abortController = new AbortController();\n abortControllerRef.current?.abort();\n abortControllerRef.current = abortController;\n\n async function subscribe() {\n let reconnectAttempt = 0;\n\n while (!cancelled) {\n setStatus(reconnectAttempt > 0 ? 'reconnecting' : 'connecting');\n\n try {\n const stream = subscriptionClient.streams.subscribeSse({\n ...subscribeOptionsRef.current,\n signal: abortController.signal,\n });\n\n setError(null);\n setStatus('open');\n\n for await (const event of stream) {\n if (cancelled) {\n break;\n }\n\n onEventRef.current?.(event);\n setEvents((currentEvents) => appendEvent(currentEvents, event, boundedMaxEvents));\n }\n\n if (!cancelled) {\n setStatus('closed');\n }\n\n break;\n } catch (caughtError) {\n if (cancelled || abortController.signal.aborted) {\n break;\n }\n\n const normalizedError = toError(caughtError);\n setError(normalizedError);\n onErrorRef.current?.(normalizedError);\n\n if (!reconnect || reconnectAttempt >= maxReconnectAttempts) {\n setStatus('error');\n break;\n }\n\n reconnectAttempt += 1;\n setStatus('reconnecting');\n await sleep(resolveReconnectDelay(reconnectDelayRef.current, reconnectAttempt), abortController.signal);\n }\n }\n }\n\n void subscribe();\n\n return () => {\n cancelled = true;\n abortController.abort();\n\n if (abortControllerRef.current === abortController) {\n abortControllerRef.current = null;\n }\n };\n }, [\n boundedMaxEvents,\n connectionToken,\n maxReconnectAttempts,\n reconnect,\n shouldConnect,\n subscribeOptionsKey,\n subscriptionClient,\n ]);\n\n const latestEvent = events.at(-1) ?? null;\n\n return useMemo(\n () => ({\n connect,\n disconnect,\n error,\n events,\n isConnected: status === 'open',\n isConnecting: status === 'connecting' || status === 'reconnecting',\n latestEvent,\n reset,\n status,\n }),\n [connect, disconnect, error, events, latestEvent, reset, status],\n );\n}\n\nexport function useManagedStream(options: UseManagedStreamOptions): UseStreamSubscriptionResult {\n const { sseUrl, stream, enabled, ...subscriptionOptions } = options;\n const managedSseUrl = sseUrl ?? stream?.sseUrl ?? '';\n const shouldEnable = typeof enabled === 'boolean' ? enabled : isNonEmptyString(managedSseUrl);\n\n return useStreamSubscription({\n ...subscriptionOptions,\n enabled: shouldEnable,\n sseUrl: managedSseUrl,\n });\n}\n\nexport function useDirectStream(options: UseDirectStreamOptions): UseStreamSubscriptionResult {\n const { directUrl, enabled, session, sseUrl, ...subscriptionOptions } = options;\n const directSseUrl = directUrl ?? sseUrl ?? session?.sseUrl ?? '';\n const shouldEnable = typeof enabled === 'boolean' ? enabled : isNonEmptyString(directSseUrl);\n\n return useStreamSubscription({\n ...subscriptionOptions,\n enabled: shouldEnable,\n sseUrl: directSseUrl,\n });\n}\n","import type { RestlessClient, RestlessStreamEvent, SubscribeSseOptions } from '@restless-stream/core';\n\nexport const DEFAULT_MAX_EVENTS = 100;\n\nexport type StreamSubscriptionStatus = 'idle' | 'connecting' | 'open' | 'reconnecting' | 'closed' | 'error';\n\nexport interface StreamSubscriptionControls {\n connect: () => void;\n disconnect: () => void;\n reset: () => void;\n}\n\nexport type UseStreamSubscriptionOptions = Omit<SubscribeSseOptions, 'reconnect' | 'reconnectDelayMs'> & {\n client?: RestlessClient;\n enabled?: boolean;\n maxEvents?: number;\n maxReconnectAttempts?: number;\n onError?: (error: Error) => void;\n onEvent?: (event: RestlessStreamEvent) => void;\n reconnect?: boolean;\n reconnectDelayMs?: number | ((attempt: number) => number);\n};\n\nexport type UseStreamSubscriptionResult = StreamSubscriptionControls & {\n error: Error | null;\n events: RestlessStreamEvent[];\n isConnected: boolean;\n isConnecting: boolean;\n latestEvent: RestlessStreamEvent | null;\n status: StreamSubscriptionStatus;\n};\n\nexport type UseManagedStreamOptions = Omit<UseStreamSubscriptionOptions, 'sseUrl'> & {\n sseUrl?: SubscribeSseOptions['sseUrl'] | null;\n stream?: { sseUrl?: string | null } | null;\n};\n\nexport type UseDirectStreamOptions = Omit<UseStreamSubscriptionOptions, 'sseUrl'> & {\n directUrl?: string | null;\n session?: { sseUrl?: string | null } | null;\n sseUrl?: SubscribeSseOptions['sseUrl'] | null;\n};\n","import type { RestlessStreamEvent, SubscribeSseOptions } from '@restless-stream/core';\n\nimport { DEFAULT_MAX_EVENTS } from './stream-subscription-types.js';\n\nexport function isNonEmptyString(value: unknown): boolean {\n return typeof value === 'string' && value.length > 0;\n}\n\nexport function appendEvent(\n currentEvents: RestlessStreamEvent[],\n event: RestlessStreamEvent,\n maxEvents: number,\n): RestlessStreamEvent[] {\n if (maxEvents === 0) {\n return [];\n }\n\n const nextEvents = [...currentEvents, event];\n return nextEvents.length > maxEvents ? nextEvents.slice(nextEvents.length - maxEvents) : nextEvents;\n}\n\nexport function normalizeMaxEvents(maxEvents: number): number {\n if (!Number.isFinite(maxEvents)) {\n return DEFAULT_MAX_EVENTS;\n }\n\n return Math.max(0, Math.floor(maxEvents));\n}\n\nexport function resolveReconnectDelay(delay: number | ((attempt: number) => number), attempt: number): number {\n const resolvedDelay = typeof delay === 'function' ? delay(attempt) : delay;\n return Number.isFinite(resolvedDelay) ? Math.max(0, resolvedDelay) : 0;\n}\n\nexport async function sleep(milliseconds: number, signal: AbortSignal): Promise<void> {\n if (milliseconds === 0 || signal.aborted) {\n return;\n }\n\n return new Promise((resolve) => {\n const timeout = setTimeout(resolve, milliseconds);\n\n signal.addEventListener(\n 'abort',\n () => {\n clearTimeout(timeout);\n resolve();\n },\n { once: true },\n );\n });\n}\n\nexport function stableOptionsKey(options: Partial<SubscribeSseOptions>): string {\n return JSON.stringify(sortRecord(options));\n}\n\nexport function toError(error: unknown): Error {\n if (error instanceof Error) {\n return error;\n }\n\n return new Error(typeof error === 'string' ? error : 'Restless stream subscription failed.');\n}\n\nfunction sortRecord(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map((item) => sortRecord(item));\n }\n\n if (typeof value === 'function') {\n return value.toString();\n }\n\n if (value === null || value === undefined || typeof value !== 'object') {\n return value;\n }\n\n return Object.fromEntries(\n Object.entries(value)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, nestedValue]) => [key, sortRecord(nestedValue)]),\n );\n}\n"],"mappings":"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","names":["useMemo","useMemo"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@restless-stream/react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lightweight React bindings for the Restless Stream SDK.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"react": ">=18.2 <20"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@restless-stream/core": "0.1.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@testing-library/react": "^16.3.0",
|
|
28
|
+
"@vitejs/plugin-react": "^5.1.2",
|
|
29
|
+
"@types/react": "^19.2.7",
|
|
30
|
+
"@types/react-dom": "^19.2.3",
|
|
31
|
+
"jsdom": "^27.2.0",
|
|
32
|
+
"react": "^19.2.1",
|
|
33
|
+
"react-dom": "^19.2.1"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsup",
|
|
37
|
+
"test": "vitest run",
|
|
38
|
+
"test:live": "vitest run src/live.test.tsx --config vitest.live.config.ts",
|
|
39
|
+
"typecheck": "tsc --noEmit"
|
|
40
|
+
}
|
|
41
|
+
}
|