floppy-disk 3.7.1 → 3.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/react/create-stream.d.mts +281 -0
- package/esm/react.mjs +11 -5
- package/package.json +1 -1
- package/react/create-stream.d.ts +281 -0
- package/react.js +11 -5
|
@@ -33,6 +33,50 @@ type StreamDataState<TData, TError> = {
|
|
|
33
33
|
error: TError;
|
|
34
34
|
errorUpdatedAt: number;
|
|
35
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* Represents the full state of a stream.
|
|
38
|
+
*
|
|
39
|
+
* @remarks
|
|
40
|
+
* A stream consists of two independent concerns:
|
|
41
|
+
*
|
|
42
|
+
* 1. **Connection state** — lifecycle of the underlying connection
|
|
43
|
+
* 2. **Data state** — lifecycle of emitted data
|
|
44
|
+
*
|
|
45
|
+
* These two are combined into a single state object.
|
|
46
|
+
*
|
|
47
|
+
* ---
|
|
48
|
+
*
|
|
49
|
+
* ## Connection lifecycle
|
|
50
|
+
*
|
|
51
|
+
* - `INITIAL` → no connection has been established
|
|
52
|
+
* - `CONNECTING` → connection is being established
|
|
53
|
+
* - `CONNECTED` → connection is active
|
|
54
|
+
* - `DISCONNECTED` → connection was previously established but is now closed
|
|
55
|
+
*
|
|
56
|
+
* Timestamps:
|
|
57
|
+
* - `connectingAt` → when connection attempt started
|
|
58
|
+
* - `connectedAt` → when connection was established
|
|
59
|
+
* - `disconnectedAt` → when connection was closed
|
|
60
|
+
*
|
|
61
|
+
* ---
|
|
62
|
+
*
|
|
63
|
+
* ## Data lifecycle
|
|
64
|
+
*
|
|
65
|
+
* - `INITIAL` → no data has been received
|
|
66
|
+
* - `SUCCESS` → data has been received successfully
|
|
67
|
+
* - `ERROR` → error occurred before any data
|
|
68
|
+
* - `SUCCESS_BUT_THEN_ERROR` → data exists, but a later error occurred
|
|
69
|
+
*
|
|
70
|
+
* ---
|
|
71
|
+
*
|
|
72
|
+
* ## Notes
|
|
73
|
+
*
|
|
74
|
+
* - Connection state and data state evolve independently.
|
|
75
|
+
* - A stream may be:
|
|
76
|
+
* - connected but have no data yet
|
|
77
|
+
* - disconnected but still retain previous data
|
|
78
|
+
* - Errors do not necessarily reset data.
|
|
79
|
+
*/
|
|
36
80
|
export type StreamState<TData, TError> = ({
|
|
37
81
|
connectionState: "INITIAL";
|
|
38
82
|
connectingAt: undefined;
|
|
@@ -60,25 +104,218 @@ type DisconnectTrigger = "last-unsubscribe" | "document-hidden" | "offline";
|
|
|
60
104
|
type ReconnectTrigger = "first-subscribe" | "document-visible" | "online";
|
|
61
105
|
type AdditionalStoreApi<TConnection> = {
|
|
62
106
|
variableHash: string;
|
|
107
|
+
/**
|
|
108
|
+
* Connection controls for the stream.
|
|
109
|
+
*
|
|
110
|
+
* @remarks
|
|
111
|
+
* Provides imperative control over the underlying connection.
|
|
112
|
+
*/
|
|
63
113
|
connection: {
|
|
114
|
+
/**
|
|
115
|
+
* Returns the current connection instance.
|
|
116
|
+
*
|
|
117
|
+
* @returns The active connection or `undefined` if not connected
|
|
118
|
+
*/
|
|
64
119
|
get: () => Readonly<TConnection> | undefined;
|
|
120
|
+
/**
|
|
121
|
+
* Forces a reconnection.
|
|
122
|
+
*
|
|
123
|
+
* @remarks
|
|
124
|
+
* - Cancels any scheduled disconnect
|
|
125
|
+
* - Starts a new connection if not already connecting
|
|
126
|
+
*/
|
|
65
127
|
reconnect: () => void;
|
|
128
|
+
/**
|
|
129
|
+
* Immediately disconnects the current connection.
|
|
130
|
+
*
|
|
131
|
+
* @remarks
|
|
132
|
+
* - Ignores disconnect delay rules
|
|
133
|
+
* - Updates connection state to `DISCONNECTED`
|
|
134
|
+
*/
|
|
66
135
|
disconnect: () => void;
|
|
67
136
|
};
|
|
137
|
+
/**
|
|
138
|
+
* Data controls for the stream.
|
|
139
|
+
*/
|
|
68
140
|
data: {
|
|
141
|
+
/**
|
|
142
|
+
* Resets the data state back to `INITIAL`.
|
|
143
|
+
*
|
|
144
|
+
* @remarks
|
|
145
|
+
* - Does not affect connection state
|
|
146
|
+
* - Useful for clearing stale or invalid data
|
|
147
|
+
*/
|
|
69
148
|
reset: () => void;
|
|
70
149
|
};
|
|
150
|
+
/**
|
|
151
|
+
* Deletes the stream instance.
|
|
152
|
+
*
|
|
153
|
+
* @returns `true` if deleted, `false` otherwise
|
|
154
|
+
*
|
|
155
|
+
* @remarks
|
|
156
|
+
* - Cannot delete while there are active subscribers
|
|
157
|
+
* - Clears connection, state, and cached instance
|
|
158
|
+
*/
|
|
71
159
|
delete: () => boolean;
|
|
72
160
|
};
|
|
161
|
+
/**
|
|
162
|
+
* Configuration options for a stream.
|
|
163
|
+
*
|
|
164
|
+
* @remarks
|
|
165
|
+
* Controls connection lifecycle, reconnection behavior, and data retention.
|
|
166
|
+
*/
|
|
73
167
|
export type StreamOptions<TConnection, TData, TError = Error> = InitStoreOptions<StreamState<TData, TError>, AdditionalStoreApi<TConnection>> & {
|
|
168
|
+
/**
|
|
169
|
+
* Connection-related behavior.
|
|
170
|
+
*/
|
|
74
171
|
connection?: {
|
|
172
|
+
/**
|
|
173
|
+
* Determines when a connection should be disconnected.
|
|
174
|
+
*
|
|
175
|
+
* @param trigger - The reason for the disconnect attempt
|
|
176
|
+
* @param state - Current stream state
|
|
177
|
+
*
|
|
178
|
+
* @returns
|
|
179
|
+
* - `number` → delay (ms) before disconnecting
|
|
180
|
+
* - `false` → prevent disconnection
|
|
181
|
+
*
|
|
182
|
+
* @default Disconnect after 5 seconds for any triggers
|
|
183
|
+
*
|
|
184
|
+
* @remarks
|
|
185
|
+
* Triggers:
|
|
186
|
+
* - `"last-unsubscribe"` → no active subscribers
|
|
187
|
+
* - `"document-hidden"` → tab becomes hidden
|
|
188
|
+
* - `"offline"` → network goes offline
|
|
189
|
+
*/
|
|
75
190
|
disconnectOn?: (trigger: DisconnectTrigger, state: StreamState<TData, TError>) => false | number;
|
|
191
|
+
/**
|
|
192
|
+
* Determines whether a connection should reconnect.
|
|
193
|
+
*
|
|
194
|
+
* @param trigger - The reason for the reconnect attempt
|
|
195
|
+
* @param state - Current stream state
|
|
196
|
+
*
|
|
197
|
+
* @returns `true` to reconnect, otherwise `false`
|
|
198
|
+
*
|
|
199
|
+
* @default No reconnection if already connected
|
|
200
|
+
*
|
|
201
|
+
* @remarks
|
|
202
|
+
* Triggers:
|
|
203
|
+
* - `"first-subscribe"` → first subscriber appears
|
|
204
|
+
* - `"document-visible"` → tab becomes visible
|
|
205
|
+
* - `"online"` → network reconnects
|
|
206
|
+
*/
|
|
76
207
|
reconnectOn?: (trigger: ReconnectTrigger, state: StreamState<TData, TError>) => boolean;
|
|
77
208
|
};
|
|
209
|
+
/**
|
|
210
|
+
* Data-related behavior.
|
|
211
|
+
*/
|
|
78
212
|
data?: {
|
|
213
|
+
/**
|
|
214
|
+
* Time (in milliseconds) before unused stream data is garbage collected.
|
|
215
|
+
*
|
|
216
|
+
* Starts counting after disconnection.
|
|
217
|
+
*
|
|
218
|
+
* @default 5 minutes
|
|
219
|
+
*/
|
|
79
220
|
gcTime?: number;
|
|
80
221
|
};
|
|
81
222
|
};
|
|
223
|
+
/**
|
|
224
|
+
* Creates a stream factory for managing real-time connections.
|
|
225
|
+
*
|
|
226
|
+
* @param connect - Function to establish a connection
|
|
227
|
+
* @param disconnect - Function to close a connection
|
|
228
|
+
* @param options - Optional configuration for lifecycle and behavior
|
|
229
|
+
*
|
|
230
|
+
* @returns A function to retrieve or create a stream instance by variable
|
|
231
|
+
*
|
|
232
|
+
* @remarks
|
|
233
|
+
* This utility is designed for **long-lived, push-based async sources**, such as:
|
|
234
|
+
* - WebSocket
|
|
235
|
+
* - Server-Sent Events (SSE)
|
|
236
|
+
* - Firebase / realtime databases
|
|
237
|
+
*
|
|
238
|
+
* ---
|
|
239
|
+
*
|
|
240
|
+
* ## Key concepts
|
|
241
|
+
*
|
|
242
|
+
* ### 1. Connection lifecycle (managed automatically)
|
|
243
|
+
*
|
|
244
|
+
* - Connection is established when needed (e.g. first subscriber)
|
|
245
|
+
* - Connection may be disconnected based on triggers:
|
|
246
|
+
* - no subscribers
|
|
247
|
+
* - tab hidden
|
|
248
|
+
* - offline
|
|
249
|
+
* - Reconnection is controlled via `reconnectOn`
|
|
250
|
+
*
|
|
251
|
+
* ---
|
|
252
|
+
*
|
|
253
|
+
* ### 2. Data flow (push-based)
|
|
254
|
+
*
|
|
255
|
+
* The `connect` function receives an `emit` API:
|
|
256
|
+
*
|
|
257
|
+
* - `emit.connected()` → mark connection as established
|
|
258
|
+
* - `emit.data(fn)` → update data using reducer
|
|
259
|
+
* - `emit.error(err)` → report error
|
|
260
|
+
*
|
|
261
|
+
* Data updates are **incremental** and controlled by the stream source.
|
|
262
|
+
*
|
|
263
|
+
* ---
|
|
264
|
+
*
|
|
265
|
+
* ### 3. Store-per-variable
|
|
266
|
+
*
|
|
267
|
+
* - Each unique `variable` creates a separate stream instance
|
|
268
|
+
* - Variables are deterministically hashed for stable identity
|
|
269
|
+
* - Each instance manages its own:
|
|
270
|
+
* - connection
|
|
271
|
+
* - state
|
|
272
|
+
* - subscribers
|
|
273
|
+
*
|
|
274
|
+
* ---
|
|
275
|
+
*
|
|
276
|
+
* ### 4. React integration (Proxy-based)
|
|
277
|
+
*
|
|
278
|
+
* - The returned hook exposes the full state as a Proxy
|
|
279
|
+
* - Components automatically subscribe to accessed properties
|
|
280
|
+
* - No selector or memoization is required
|
|
281
|
+
*
|
|
282
|
+
* ---
|
|
283
|
+
*
|
|
284
|
+
* ## Execution model
|
|
285
|
+
*
|
|
286
|
+
* - Streams are **lazy**:
|
|
287
|
+
* - No connection until there is a subscriber
|
|
288
|
+
* - Streams are **shared**:
|
|
289
|
+
* - Multiple subscribers reuse the same connection
|
|
290
|
+
* - Streams are **stateful**:
|
|
291
|
+
* - Data persists across reconnects (unless reset or GC)
|
|
292
|
+
*
|
|
293
|
+
* ---
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* const chatStream = createStream(
|
|
297
|
+
* (roomId, emit) => {
|
|
298
|
+
* const ws = new WebSocket(`/chat/${roomId}`);
|
|
299
|
+
*
|
|
300
|
+
* ws.onopen = () => emit.connected();
|
|
301
|
+
* ws.onmessage = (e) => {
|
|
302
|
+
* const msg = JSON.parse(e.data);
|
|
303
|
+
* emit.data((prev) => [...(prev ?? []), msg]);
|
|
304
|
+
* };
|
|
305
|
+
* ws.onerror = (err) => emit.error(err);
|
|
306
|
+
*
|
|
307
|
+
* return ws;
|
|
308
|
+
* },
|
|
309
|
+
* (ws) => ws.close()
|
|
310
|
+
* );
|
|
311
|
+
*
|
|
312
|
+
* function Chat({ roomId }) {
|
|
313
|
+
* const useChat = chatStream(roomId);
|
|
314
|
+
* const state = useChat();
|
|
315
|
+
*
|
|
316
|
+
* return <div>{state.data?.length}</div>;
|
|
317
|
+
* }
|
|
318
|
+
*/
|
|
82
319
|
export declare const experimental_createStream: <TConnection, TData, TVariable extends StoreKey, TError = Error>(connect: (variable: TVariable, emit: {
|
|
83
320
|
connected: () => void;
|
|
84
321
|
data: (reducer: (data: TData | undefined) => TData) => void;
|
|
@@ -91,14 +328,58 @@ export declare const experimental_createStream: <TConnection, TData, TVariable e
|
|
|
91
328
|
subscribe: (subscriber: import("../vanilla.d.mts").Subscriber<StreamState<TData, TError>>) => () => void;
|
|
92
329
|
getSubscriberCount: () => number;
|
|
93
330
|
variableHash: string;
|
|
331
|
+
/**
|
|
332
|
+
* Connection controls for the stream.
|
|
333
|
+
*
|
|
334
|
+
* @remarks
|
|
335
|
+
* Provides imperative control over the underlying connection.
|
|
336
|
+
*/
|
|
94
337
|
connection: {
|
|
338
|
+
/**
|
|
339
|
+
* Returns the current connection instance.
|
|
340
|
+
*
|
|
341
|
+
* @returns The active connection or `undefined` if not connected
|
|
342
|
+
*/
|
|
95
343
|
get: () => Readonly<TConnection> | undefined;
|
|
344
|
+
/**
|
|
345
|
+
* Forces a reconnection.
|
|
346
|
+
*
|
|
347
|
+
* @remarks
|
|
348
|
+
* - Cancels any scheduled disconnect
|
|
349
|
+
* - Starts a new connection if not already connecting
|
|
350
|
+
*/
|
|
96
351
|
reconnect: () => void;
|
|
352
|
+
/**
|
|
353
|
+
* Immediately disconnects the current connection.
|
|
354
|
+
*
|
|
355
|
+
* @remarks
|
|
356
|
+
* - Ignores disconnect delay rules
|
|
357
|
+
* - Updates connection state to `DISCONNECTED`
|
|
358
|
+
*/
|
|
97
359
|
disconnect: () => void;
|
|
98
360
|
};
|
|
361
|
+
/**
|
|
362
|
+
* Data controls for the stream.
|
|
363
|
+
*/
|
|
99
364
|
data: {
|
|
365
|
+
/**
|
|
366
|
+
* Resets the data state back to `INITIAL`.
|
|
367
|
+
*
|
|
368
|
+
* @remarks
|
|
369
|
+
* - Does not affect connection state
|
|
370
|
+
* - Useful for clearing stale or invalid data
|
|
371
|
+
*/
|
|
100
372
|
reset: () => void;
|
|
101
373
|
};
|
|
374
|
+
/**
|
|
375
|
+
* Deletes the stream instance.
|
|
376
|
+
*
|
|
377
|
+
* @returns `true` if deleted, `false` otherwise
|
|
378
|
+
*
|
|
379
|
+
* @remarks
|
|
380
|
+
* - Cannot delete while there are active subscribers
|
|
381
|
+
* - Clears connection, state, and cached instance
|
|
382
|
+
*/
|
|
102
383
|
delete: () => boolean;
|
|
103
384
|
};
|
|
104
385
|
export {};
|
package/esm/react.mjs
CHANGED
|
@@ -873,11 +873,14 @@ const experimental_createStream = (connect, disconnect, options = {}) => {
|
|
|
873
873
|
store.connection = {};
|
|
874
874
|
store.connection.get = () => connections.get(store);
|
|
875
875
|
store.connection.reconnect = () => {
|
|
876
|
-
var _a;
|
|
877
876
|
clearAllTimeouts(store);
|
|
878
877
|
const { connectionState } = store.getState();
|
|
879
878
|
if (connectionState === "CONNECTING") return;
|
|
880
|
-
|
|
879
|
+
const prevDisconnect = disconnectFns.get(store);
|
|
880
|
+
if (prevDisconnect) {
|
|
881
|
+
prevDisconnect();
|
|
882
|
+
disconnectFns.delete(store);
|
|
883
|
+
}
|
|
881
884
|
store.setState({
|
|
882
885
|
connectionState: "CONNECTING",
|
|
883
886
|
connectingAt: Date.now(),
|
|
@@ -894,10 +897,10 @@ const experimental_createStream = (connect, disconnect, options = {}) => {
|
|
|
894
897
|
},
|
|
895
898
|
data: (reducer) => {
|
|
896
899
|
store.setState((prev) => {
|
|
897
|
-
var
|
|
900
|
+
var _a;
|
|
898
901
|
return {
|
|
899
902
|
connectionState: "CONNECTED",
|
|
900
|
-
connectedAt: (
|
|
903
|
+
connectedAt: (_a = prev.connectedAt) != null ? _a : Date.now(),
|
|
901
904
|
state: "SUCCESS",
|
|
902
905
|
isSuccess: true,
|
|
903
906
|
isError: false,
|
|
@@ -960,7 +963,10 @@ const experimental_createStream = (connect, disconnect, options = {}) => {
|
|
|
960
963
|
clearAllTimeouts(store);
|
|
961
964
|
const { connectionState } = store.getState();
|
|
962
965
|
if (connectionState === "INITIAL" || connectionState === "DISCONNECTED") {
|
|
963
|
-
|
|
966
|
+
queueMicrotask(() => {
|
|
967
|
+
store.connection.reconnect();
|
|
968
|
+
});
|
|
969
|
+
return;
|
|
964
970
|
}
|
|
965
971
|
const shouldReconnect = reconnectOn(trigger, store.getState());
|
|
966
972
|
if (shouldReconnect) store.connection.reconnect();
|
package/package.json
CHANGED
package/react/create-stream.d.ts
CHANGED
|
@@ -33,6 +33,50 @@ type StreamDataState<TData, TError> = {
|
|
|
33
33
|
error: TError;
|
|
34
34
|
errorUpdatedAt: number;
|
|
35
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* Represents the full state of a stream.
|
|
38
|
+
*
|
|
39
|
+
* @remarks
|
|
40
|
+
* A stream consists of two independent concerns:
|
|
41
|
+
*
|
|
42
|
+
* 1. **Connection state** — lifecycle of the underlying connection
|
|
43
|
+
* 2. **Data state** — lifecycle of emitted data
|
|
44
|
+
*
|
|
45
|
+
* These two are combined into a single state object.
|
|
46
|
+
*
|
|
47
|
+
* ---
|
|
48
|
+
*
|
|
49
|
+
* ## Connection lifecycle
|
|
50
|
+
*
|
|
51
|
+
* - `INITIAL` → no connection has been established
|
|
52
|
+
* - `CONNECTING` → connection is being established
|
|
53
|
+
* - `CONNECTED` → connection is active
|
|
54
|
+
* - `DISCONNECTED` → connection was previously established but is now closed
|
|
55
|
+
*
|
|
56
|
+
* Timestamps:
|
|
57
|
+
* - `connectingAt` → when connection attempt started
|
|
58
|
+
* - `connectedAt` → when connection was established
|
|
59
|
+
* - `disconnectedAt` → when connection was closed
|
|
60
|
+
*
|
|
61
|
+
* ---
|
|
62
|
+
*
|
|
63
|
+
* ## Data lifecycle
|
|
64
|
+
*
|
|
65
|
+
* - `INITIAL` → no data has been received
|
|
66
|
+
* - `SUCCESS` → data has been received successfully
|
|
67
|
+
* - `ERROR` → error occurred before any data
|
|
68
|
+
* - `SUCCESS_BUT_THEN_ERROR` → data exists, but a later error occurred
|
|
69
|
+
*
|
|
70
|
+
* ---
|
|
71
|
+
*
|
|
72
|
+
* ## Notes
|
|
73
|
+
*
|
|
74
|
+
* - Connection state and data state evolve independently.
|
|
75
|
+
* - A stream may be:
|
|
76
|
+
* - connected but have no data yet
|
|
77
|
+
* - disconnected but still retain previous data
|
|
78
|
+
* - Errors do not necessarily reset data.
|
|
79
|
+
*/
|
|
36
80
|
export type StreamState<TData, TError> = ({
|
|
37
81
|
connectionState: "INITIAL";
|
|
38
82
|
connectingAt: undefined;
|
|
@@ -60,25 +104,218 @@ type DisconnectTrigger = "last-unsubscribe" | "document-hidden" | "offline";
|
|
|
60
104
|
type ReconnectTrigger = "first-subscribe" | "document-visible" | "online";
|
|
61
105
|
type AdditionalStoreApi<TConnection> = {
|
|
62
106
|
variableHash: string;
|
|
107
|
+
/**
|
|
108
|
+
* Connection controls for the stream.
|
|
109
|
+
*
|
|
110
|
+
* @remarks
|
|
111
|
+
* Provides imperative control over the underlying connection.
|
|
112
|
+
*/
|
|
63
113
|
connection: {
|
|
114
|
+
/**
|
|
115
|
+
* Returns the current connection instance.
|
|
116
|
+
*
|
|
117
|
+
* @returns The active connection or `undefined` if not connected
|
|
118
|
+
*/
|
|
64
119
|
get: () => Readonly<TConnection> | undefined;
|
|
120
|
+
/**
|
|
121
|
+
* Forces a reconnection.
|
|
122
|
+
*
|
|
123
|
+
* @remarks
|
|
124
|
+
* - Cancels any scheduled disconnect
|
|
125
|
+
* - Starts a new connection if not already connecting
|
|
126
|
+
*/
|
|
65
127
|
reconnect: () => void;
|
|
128
|
+
/**
|
|
129
|
+
* Immediately disconnects the current connection.
|
|
130
|
+
*
|
|
131
|
+
* @remarks
|
|
132
|
+
* - Ignores disconnect delay rules
|
|
133
|
+
* - Updates connection state to `DISCONNECTED`
|
|
134
|
+
*/
|
|
66
135
|
disconnect: () => void;
|
|
67
136
|
};
|
|
137
|
+
/**
|
|
138
|
+
* Data controls for the stream.
|
|
139
|
+
*/
|
|
68
140
|
data: {
|
|
141
|
+
/**
|
|
142
|
+
* Resets the data state back to `INITIAL`.
|
|
143
|
+
*
|
|
144
|
+
* @remarks
|
|
145
|
+
* - Does not affect connection state
|
|
146
|
+
* - Useful for clearing stale or invalid data
|
|
147
|
+
*/
|
|
69
148
|
reset: () => void;
|
|
70
149
|
};
|
|
150
|
+
/**
|
|
151
|
+
* Deletes the stream instance.
|
|
152
|
+
*
|
|
153
|
+
* @returns `true` if deleted, `false` otherwise
|
|
154
|
+
*
|
|
155
|
+
* @remarks
|
|
156
|
+
* - Cannot delete while there are active subscribers
|
|
157
|
+
* - Clears connection, state, and cached instance
|
|
158
|
+
*/
|
|
71
159
|
delete: () => boolean;
|
|
72
160
|
};
|
|
161
|
+
/**
|
|
162
|
+
* Configuration options for a stream.
|
|
163
|
+
*
|
|
164
|
+
* @remarks
|
|
165
|
+
* Controls connection lifecycle, reconnection behavior, and data retention.
|
|
166
|
+
*/
|
|
73
167
|
export type StreamOptions<TConnection, TData, TError = Error> = InitStoreOptions<StreamState<TData, TError>, AdditionalStoreApi<TConnection>> & {
|
|
168
|
+
/**
|
|
169
|
+
* Connection-related behavior.
|
|
170
|
+
*/
|
|
74
171
|
connection?: {
|
|
172
|
+
/**
|
|
173
|
+
* Determines when a connection should be disconnected.
|
|
174
|
+
*
|
|
175
|
+
* @param trigger - The reason for the disconnect attempt
|
|
176
|
+
* @param state - Current stream state
|
|
177
|
+
*
|
|
178
|
+
* @returns
|
|
179
|
+
* - `number` → delay (ms) before disconnecting
|
|
180
|
+
* - `false` → prevent disconnection
|
|
181
|
+
*
|
|
182
|
+
* @default Disconnect after 5 seconds for any triggers
|
|
183
|
+
*
|
|
184
|
+
* @remarks
|
|
185
|
+
* Triggers:
|
|
186
|
+
* - `"last-unsubscribe"` → no active subscribers
|
|
187
|
+
* - `"document-hidden"` → tab becomes hidden
|
|
188
|
+
* - `"offline"` → network goes offline
|
|
189
|
+
*/
|
|
75
190
|
disconnectOn?: (trigger: DisconnectTrigger, state: StreamState<TData, TError>) => false | number;
|
|
191
|
+
/**
|
|
192
|
+
* Determines whether a connection should reconnect.
|
|
193
|
+
*
|
|
194
|
+
* @param trigger - The reason for the reconnect attempt
|
|
195
|
+
* @param state - Current stream state
|
|
196
|
+
*
|
|
197
|
+
* @returns `true` to reconnect, otherwise `false`
|
|
198
|
+
*
|
|
199
|
+
* @default No reconnection if already connected
|
|
200
|
+
*
|
|
201
|
+
* @remarks
|
|
202
|
+
* Triggers:
|
|
203
|
+
* - `"first-subscribe"` → first subscriber appears
|
|
204
|
+
* - `"document-visible"` → tab becomes visible
|
|
205
|
+
* - `"online"` → network reconnects
|
|
206
|
+
*/
|
|
76
207
|
reconnectOn?: (trigger: ReconnectTrigger, state: StreamState<TData, TError>) => boolean;
|
|
77
208
|
};
|
|
209
|
+
/**
|
|
210
|
+
* Data-related behavior.
|
|
211
|
+
*/
|
|
78
212
|
data?: {
|
|
213
|
+
/**
|
|
214
|
+
* Time (in milliseconds) before unused stream data is garbage collected.
|
|
215
|
+
*
|
|
216
|
+
* Starts counting after disconnection.
|
|
217
|
+
*
|
|
218
|
+
* @default 5 minutes
|
|
219
|
+
*/
|
|
79
220
|
gcTime?: number;
|
|
80
221
|
};
|
|
81
222
|
};
|
|
223
|
+
/**
|
|
224
|
+
* Creates a stream factory for managing real-time connections.
|
|
225
|
+
*
|
|
226
|
+
* @param connect - Function to establish a connection
|
|
227
|
+
* @param disconnect - Function to close a connection
|
|
228
|
+
* @param options - Optional configuration for lifecycle and behavior
|
|
229
|
+
*
|
|
230
|
+
* @returns A function to retrieve or create a stream instance by variable
|
|
231
|
+
*
|
|
232
|
+
* @remarks
|
|
233
|
+
* This utility is designed for **long-lived, push-based async sources**, such as:
|
|
234
|
+
* - WebSocket
|
|
235
|
+
* - Server-Sent Events (SSE)
|
|
236
|
+
* - Firebase / realtime databases
|
|
237
|
+
*
|
|
238
|
+
* ---
|
|
239
|
+
*
|
|
240
|
+
* ## Key concepts
|
|
241
|
+
*
|
|
242
|
+
* ### 1. Connection lifecycle (managed automatically)
|
|
243
|
+
*
|
|
244
|
+
* - Connection is established when needed (e.g. first subscriber)
|
|
245
|
+
* - Connection may be disconnected based on triggers:
|
|
246
|
+
* - no subscribers
|
|
247
|
+
* - tab hidden
|
|
248
|
+
* - offline
|
|
249
|
+
* - Reconnection is controlled via `reconnectOn`
|
|
250
|
+
*
|
|
251
|
+
* ---
|
|
252
|
+
*
|
|
253
|
+
* ### 2. Data flow (push-based)
|
|
254
|
+
*
|
|
255
|
+
* The `connect` function receives an `emit` API:
|
|
256
|
+
*
|
|
257
|
+
* - `emit.connected()` → mark connection as established
|
|
258
|
+
* - `emit.data(fn)` → update data using reducer
|
|
259
|
+
* - `emit.error(err)` → report error
|
|
260
|
+
*
|
|
261
|
+
* Data updates are **incremental** and controlled by the stream source.
|
|
262
|
+
*
|
|
263
|
+
* ---
|
|
264
|
+
*
|
|
265
|
+
* ### 3. Store-per-variable
|
|
266
|
+
*
|
|
267
|
+
* - Each unique `variable` creates a separate stream instance
|
|
268
|
+
* - Variables are deterministically hashed for stable identity
|
|
269
|
+
* - Each instance manages its own:
|
|
270
|
+
* - connection
|
|
271
|
+
* - state
|
|
272
|
+
* - subscribers
|
|
273
|
+
*
|
|
274
|
+
* ---
|
|
275
|
+
*
|
|
276
|
+
* ### 4. React integration (Proxy-based)
|
|
277
|
+
*
|
|
278
|
+
* - The returned hook exposes the full state as a Proxy
|
|
279
|
+
* - Components automatically subscribe to accessed properties
|
|
280
|
+
* - No selector or memoization is required
|
|
281
|
+
*
|
|
282
|
+
* ---
|
|
283
|
+
*
|
|
284
|
+
* ## Execution model
|
|
285
|
+
*
|
|
286
|
+
* - Streams are **lazy**:
|
|
287
|
+
* - No connection until there is a subscriber
|
|
288
|
+
* - Streams are **shared**:
|
|
289
|
+
* - Multiple subscribers reuse the same connection
|
|
290
|
+
* - Streams are **stateful**:
|
|
291
|
+
* - Data persists across reconnects (unless reset or GC)
|
|
292
|
+
*
|
|
293
|
+
* ---
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* const chatStream = createStream(
|
|
297
|
+
* (roomId, emit) => {
|
|
298
|
+
* const ws = new WebSocket(`/chat/${roomId}`);
|
|
299
|
+
*
|
|
300
|
+
* ws.onopen = () => emit.connected();
|
|
301
|
+
* ws.onmessage = (e) => {
|
|
302
|
+
* const msg = JSON.parse(e.data);
|
|
303
|
+
* emit.data((prev) => [...(prev ?? []), msg]);
|
|
304
|
+
* };
|
|
305
|
+
* ws.onerror = (err) => emit.error(err);
|
|
306
|
+
*
|
|
307
|
+
* return ws;
|
|
308
|
+
* },
|
|
309
|
+
* (ws) => ws.close()
|
|
310
|
+
* );
|
|
311
|
+
*
|
|
312
|
+
* function Chat({ roomId }) {
|
|
313
|
+
* const useChat = chatStream(roomId);
|
|
314
|
+
* const state = useChat();
|
|
315
|
+
*
|
|
316
|
+
* return <div>{state.data?.length}</div>;
|
|
317
|
+
* }
|
|
318
|
+
*/
|
|
82
319
|
export declare const experimental_createStream: <TConnection, TData, TVariable extends StoreKey, TError = Error>(connect: (variable: TVariable, emit: {
|
|
83
320
|
connected: () => void;
|
|
84
321
|
data: (reducer: (data: TData | undefined) => TData) => void;
|
|
@@ -91,14 +328,58 @@ export declare const experimental_createStream: <TConnection, TData, TVariable e
|
|
|
91
328
|
subscribe: (subscriber: import("../vanilla.ts").Subscriber<StreamState<TData, TError>>) => () => void;
|
|
92
329
|
getSubscriberCount: () => number;
|
|
93
330
|
variableHash: string;
|
|
331
|
+
/**
|
|
332
|
+
* Connection controls for the stream.
|
|
333
|
+
*
|
|
334
|
+
* @remarks
|
|
335
|
+
* Provides imperative control over the underlying connection.
|
|
336
|
+
*/
|
|
94
337
|
connection: {
|
|
338
|
+
/**
|
|
339
|
+
* Returns the current connection instance.
|
|
340
|
+
*
|
|
341
|
+
* @returns The active connection or `undefined` if not connected
|
|
342
|
+
*/
|
|
95
343
|
get: () => Readonly<TConnection> | undefined;
|
|
344
|
+
/**
|
|
345
|
+
* Forces a reconnection.
|
|
346
|
+
*
|
|
347
|
+
* @remarks
|
|
348
|
+
* - Cancels any scheduled disconnect
|
|
349
|
+
* - Starts a new connection if not already connecting
|
|
350
|
+
*/
|
|
96
351
|
reconnect: () => void;
|
|
352
|
+
/**
|
|
353
|
+
* Immediately disconnects the current connection.
|
|
354
|
+
*
|
|
355
|
+
* @remarks
|
|
356
|
+
* - Ignores disconnect delay rules
|
|
357
|
+
* - Updates connection state to `DISCONNECTED`
|
|
358
|
+
*/
|
|
97
359
|
disconnect: () => void;
|
|
98
360
|
};
|
|
361
|
+
/**
|
|
362
|
+
* Data controls for the stream.
|
|
363
|
+
*/
|
|
99
364
|
data: {
|
|
365
|
+
/**
|
|
366
|
+
* Resets the data state back to `INITIAL`.
|
|
367
|
+
*
|
|
368
|
+
* @remarks
|
|
369
|
+
* - Does not affect connection state
|
|
370
|
+
* - Useful for clearing stale or invalid data
|
|
371
|
+
*/
|
|
100
372
|
reset: () => void;
|
|
101
373
|
};
|
|
374
|
+
/**
|
|
375
|
+
* Deletes the stream instance.
|
|
376
|
+
*
|
|
377
|
+
* @returns `true` if deleted, `false` otherwise
|
|
378
|
+
*
|
|
379
|
+
* @remarks
|
|
380
|
+
* - Cannot delete while there are active subscribers
|
|
381
|
+
* - Clears connection, state, and cached instance
|
|
382
|
+
*/
|
|
102
383
|
delete: () => boolean;
|
|
103
384
|
};
|
|
104
385
|
export {};
|
package/react.js
CHANGED
|
@@ -875,11 +875,14 @@ const experimental_createStream = (connect, disconnect, options = {}) => {
|
|
|
875
875
|
store.connection = {};
|
|
876
876
|
store.connection.get = () => connections.get(store);
|
|
877
877
|
store.connection.reconnect = () => {
|
|
878
|
-
var _a;
|
|
879
878
|
clearAllTimeouts(store);
|
|
880
879
|
const { connectionState } = store.getState();
|
|
881
880
|
if (connectionState === "CONNECTING") return;
|
|
882
|
-
|
|
881
|
+
const prevDisconnect = disconnectFns.get(store);
|
|
882
|
+
if (prevDisconnect) {
|
|
883
|
+
prevDisconnect();
|
|
884
|
+
disconnectFns.delete(store);
|
|
885
|
+
}
|
|
883
886
|
store.setState({
|
|
884
887
|
connectionState: "CONNECTING",
|
|
885
888
|
connectingAt: Date.now(),
|
|
@@ -896,10 +899,10 @@ const experimental_createStream = (connect, disconnect, options = {}) => {
|
|
|
896
899
|
},
|
|
897
900
|
data: (reducer) => {
|
|
898
901
|
store.setState((prev) => {
|
|
899
|
-
var
|
|
902
|
+
var _a;
|
|
900
903
|
return {
|
|
901
904
|
connectionState: "CONNECTED",
|
|
902
|
-
connectedAt: (
|
|
905
|
+
connectedAt: (_a = prev.connectedAt) != null ? _a : Date.now(),
|
|
903
906
|
state: "SUCCESS",
|
|
904
907
|
isSuccess: true,
|
|
905
908
|
isError: false,
|
|
@@ -962,7 +965,10 @@ const experimental_createStream = (connect, disconnect, options = {}) => {
|
|
|
962
965
|
clearAllTimeouts(store);
|
|
963
966
|
const { connectionState } = store.getState();
|
|
964
967
|
if (connectionState === "INITIAL" || connectionState === "DISCONNECTED") {
|
|
965
|
-
|
|
968
|
+
queueMicrotask(() => {
|
|
969
|
+
store.connection.reconnect();
|
|
970
|
+
});
|
|
971
|
+
return;
|
|
966
972
|
}
|
|
967
973
|
const shouldReconnect = reconnectOn(trigger, store.getState());
|
|
968
974
|
if (shouldReconnect) store.connection.reconnect();
|