@go-go-scope/adapter-svelte 2.7.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/LICENSE +21 -0
- package/README.md +73 -0
- package/dist/index.d.mts +280 -0
- package/dist/index.mjs +222 -0
- package/package.json +49 -0
- package/src/index.ts +609 -0
- package/tests/TestComponent.svelte +189 -0
- package/tests/svelte.test.ts +199 -0
- package/tsconfig.json +11 -0
- package/vitest.config.ts +13 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte 5 runes integration for go-go-scope
|
|
3
|
+
*
|
|
4
|
+
* Provides native Svelte 5 runes ($state, $effect, $derived) integration.
|
|
5
|
+
* Scopes automatically clean up when components are destroyed.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```svelte
|
|
9
|
+
* <script>
|
|
10
|
+
* import { createScope, createTask } from "@go-go-scope/adapter-svelte";
|
|
11
|
+
*
|
|
12
|
+
* // Auto-disposing scope
|
|
13
|
+
* const scope = createScope({ name: "my-component" });
|
|
14
|
+
*
|
|
15
|
+
* // Reactive task
|
|
16
|
+
* const { data, isLoading } = createTask(
|
|
17
|
+
* async () => fetchUser(userId),
|
|
18
|
+
* { immediate: true }
|
|
19
|
+
* );
|
|
20
|
+
* </script>
|
|
21
|
+
*
|
|
22
|
+
* {#if $isLoading}
|
|
23
|
+
* <p>Loading...</p>
|
|
24
|
+
* {:else}
|
|
25
|
+
* <p>{$data.name}</p>
|
|
26
|
+
* {/if}
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import {
|
|
31
|
+
scope,
|
|
32
|
+
BroadcastChannel,
|
|
33
|
+
type Scope,
|
|
34
|
+
type Result,
|
|
35
|
+
} from "go-go-scope";
|
|
36
|
+
import { onDestroy } from "svelte";
|
|
37
|
+
import { writable, type Readable } from "svelte/store";
|
|
38
|
+
|
|
39
|
+
// ============================================================================
|
|
40
|
+
// Core - Scope
|
|
41
|
+
// ============================================================================
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Options for creating a scope
|
|
45
|
+
*/
|
|
46
|
+
export interface CreateScopeOptions {
|
|
47
|
+
/** Scope name for debugging */
|
|
48
|
+
name?: string;
|
|
49
|
+
/** Timeout in milliseconds */
|
|
50
|
+
timeout?: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Reactive scope with automatic cleanup
|
|
55
|
+
*/
|
|
56
|
+
export interface ReactiveScope extends Scope {
|
|
57
|
+
/** Whether the scope is currently active */
|
|
58
|
+
readonly isActive: Readable<boolean>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create a reactive scope that automatically disposes on component destroy.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```svelte
|
|
66
|
+
* <script>
|
|
67
|
+
* const s = createScope({ name: "my-component" });
|
|
68
|
+
*
|
|
69
|
+
* // Scope is automatically disposed when component is destroyed
|
|
70
|
+
* const [err, result] = await s.task(() => fetchData());
|
|
71
|
+
* </script>
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export function createScope(options: CreateScopeOptions = {}): ReactiveScope {
|
|
75
|
+
const isActive = writable(true);
|
|
76
|
+
|
|
77
|
+
const s = scope({
|
|
78
|
+
name: options.name,
|
|
79
|
+
timeout: options.timeout,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Auto-dispose on component destroy
|
|
83
|
+
onDestroy(() => {
|
|
84
|
+
isActive.set(false);
|
|
85
|
+
s[Symbol.asyncDispose]().catch(() => {
|
|
86
|
+
// Ignore disposal errors
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return Object.assign(s as unknown as ReactiveScope, {
|
|
91
|
+
isActive: { subscribe: isActive.subscribe },
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// Task Store
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Options for creating a task
|
|
101
|
+
*/
|
|
102
|
+
export interface CreateTaskOptions<T> {
|
|
103
|
+
/** Task name for debugging */
|
|
104
|
+
name?: string;
|
|
105
|
+
/** Whether to execute immediately */
|
|
106
|
+
immediate?: boolean;
|
|
107
|
+
/** Timeout in milliseconds */
|
|
108
|
+
timeout?: number;
|
|
109
|
+
/** Retry options */
|
|
110
|
+
retry?: { maxRetries?: number; delay?: number };
|
|
111
|
+
/** Initial data value */
|
|
112
|
+
initialData?: T;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Reactive task state
|
|
117
|
+
*/
|
|
118
|
+
export interface TaskState<T> {
|
|
119
|
+
/** Current data (undefined if not loaded) */
|
|
120
|
+
readonly data: Readable<T | undefined>;
|
|
121
|
+
/** Error if task failed */
|
|
122
|
+
readonly error: Readable<Error | undefined>;
|
|
123
|
+
/** Whether task is currently running */
|
|
124
|
+
readonly isLoading: Readable<boolean>;
|
|
125
|
+
/** Whether task has been executed */
|
|
126
|
+
readonly isReady: Readable<boolean>;
|
|
127
|
+
/** Execute the task */
|
|
128
|
+
readonly execute: () => Promise<Result<Error, T>>;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Create a reactive task that integrates with Svelte's store system.
|
|
133
|
+
* Returns a store-like object that can be used with `$` prefix.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```svelte
|
|
137
|
+
* <script>
|
|
138
|
+
* const task = createTask(
|
|
139
|
+
* async () => fetchUser(userId),
|
|
140
|
+
* { immediate: true }
|
|
141
|
+
* );
|
|
142
|
+
* </script>
|
|
143
|
+
*
|
|
144
|
+
* {#if $task.isLoading}
|
|
145
|
+
* <p>Loading...</p>
|
|
146
|
+
* {:else if $task.error}
|
|
147
|
+
* <p>Error: {$task.error.message}</p>
|
|
148
|
+
* {:else}
|
|
149
|
+
* <p>{$task.data.name}</p>
|
|
150
|
+
* {/if}
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
export function createTask<T>(
|
|
154
|
+
factory: () => Promise<T>,
|
|
155
|
+
options: CreateTaskOptions<T> = {},
|
|
156
|
+
): TaskState<T> {
|
|
157
|
+
const s = createScope({ name: options.name ?? "createTask" });
|
|
158
|
+
|
|
159
|
+
const data = writable<T | undefined>(options.initialData);
|
|
160
|
+
const error = writable<Error | undefined>(undefined);
|
|
161
|
+
const isLoading = writable(false);
|
|
162
|
+
const isReady = writable(false);
|
|
163
|
+
|
|
164
|
+
const execute = async (): Promise<Result<Error, T>> => {
|
|
165
|
+
isLoading.set(true);
|
|
166
|
+
error.set(undefined);
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const [err, result] = await s.task(
|
|
170
|
+
async () => await factory(),
|
|
171
|
+
{
|
|
172
|
+
timeout: options.timeout,
|
|
173
|
+
retry: options.retry,
|
|
174
|
+
},
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
if (err) {
|
|
178
|
+
error.set(err instanceof Error ? err : new Error(String(err)));
|
|
179
|
+
data.set(undefined);
|
|
180
|
+
return [err as Error, undefined];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
data.set(result);
|
|
184
|
+
isReady.set(true);
|
|
185
|
+
return [undefined, result];
|
|
186
|
+
} finally {
|
|
187
|
+
isLoading.set(false);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// Auto-execute if immediate
|
|
192
|
+
if (options.immediate) {
|
|
193
|
+
execute();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
data: { subscribe: data.subscribe },
|
|
198
|
+
error: { subscribe: error.subscribe },
|
|
199
|
+
isLoading: { subscribe: isLoading.subscribe },
|
|
200
|
+
isReady: { subscribe: isReady.subscribe },
|
|
201
|
+
execute,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ============================================================================
|
|
206
|
+
// Parallel Tasks Store
|
|
207
|
+
// ============================================================================
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Options for parallel execution
|
|
211
|
+
*/
|
|
212
|
+
export interface CreateParallelOptions {
|
|
213
|
+
/** Concurrency limit */
|
|
214
|
+
concurrency?: number;
|
|
215
|
+
/** Whether to execute immediately */
|
|
216
|
+
immediate?: boolean;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Reactive parallel task state
|
|
221
|
+
*/
|
|
222
|
+
export interface ParallelState<T> {
|
|
223
|
+
/** Results from all tasks */
|
|
224
|
+
readonly results: Readable<(T | undefined)[]>;
|
|
225
|
+
/** Errors from failed tasks */
|
|
226
|
+
readonly errors: Readable<(Error | undefined)[]>;
|
|
227
|
+
/** Whether any task is running */
|
|
228
|
+
readonly isLoading: Readable<boolean>;
|
|
229
|
+
/** Progress percentage (0-100) */
|
|
230
|
+
readonly progress: Readable<number>;
|
|
231
|
+
/** Execute all tasks */
|
|
232
|
+
readonly execute: () => Promise<Result<Error, T>[]>;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Create reactive parallel task execution.
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* ```svelte
|
|
240
|
+
* <script>
|
|
241
|
+
* const parallel = createParallel(
|
|
242
|
+
* urls.map(url => () => fetch(url).then(r => r.json())),
|
|
243
|
+
* { concurrency: 3, immediate: true }
|
|
244
|
+
* );
|
|
245
|
+
* </script>
|
|
246
|
+
*
|
|
247
|
+
* <progress value={$parallel.progress} max="100" />
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
export function createParallel<T>(
|
|
251
|
+
factories: (() => Promise<T>)[],
|
|
252
|
+
options: CreateParallelOptions = {},
|
|
253
|
+
): ParallelState<T> {
|
|
254
|
+
const s = createScope({ name: "createParallel" });
|
|
255
|
+
|
|
256
|
+
const results = writable<(T | undefined)[]>([]);
|
|
257
|
+
const errors = writable<(Error | undefined)[]>([]);
|
|
258
|
+
const isLoading = writable(false);
|
|
259
|
+
const progress = writable(0);
|
|
260
|
+
|
|
261
|
+
const execute = async (): Promise<Result<Error, T>[]> => {
|
|
262
|
+
isLoading.set(true);
|
|
263
|
+
progress.set(0);
|
|
264
|
+
results.set(new Array(factories.length).fill(undefined));
|
|
265
|
+
errors.set(new Array(factories.length).fill(undefined));
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
const taskFactories = factories.map((factory, index) => {
|
|
269
|
+
return async () => {
|
|
270
|
+
const [err, result] = await s.task(async () => await factory());
|
|
271
|
+
|
|
272
|
+
if (err) {
|
|
273
|
+
errors.update(e => {
|
|
274
|
+
e[index] = err instanceof Error ? err : new Error(String(err));
|
|
275
|
+
return e;
|
|
276
|
+
});
|
|
277
|
+
} else {
|
|
278
|
+
results.update(r => {
|
|
279
|
+
r[index] = result;
|
|
280
|
+
return r;
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
progress.set(Math.round(((index + 1) / factories.length) * 100));
|
|
285
|
+
return [err, result] as Result<Error, T>;
|
|
286
|
+
};
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
return (await s.parallel(taskFactories, {
|
|
290
|
+
concurrency: options.concurrency,
|
|
291
|
+
})) as Result<Error, T>[];
|
|
292
|
+
} finally {
|
|
293
|
+
isLoading.set(false);
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
if (options.immediate) {
|
|
298
|
+
execute();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
results: { subscribe: results.subscribe },
|
|
303
|
+
errors: { subscribe: errors.subscribe },
|
|
304
|
+
isLoading: { subscribe: isLoading.subscribe },
|
|
305
|
+
progress: { subscribe: progress.subscribe },
|
|
306
|
+
execute,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ============================================================================
|
|
311
|
+
// Channel Store
|
|
312
|
+
// ============================================================================
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Options for creating a channel
|
|
316
|
+
*/
|
|
317
|
+
export interface CreateChannelOptions {
|
|
318
|
+
/** Buffer size */
|
|
319
|
+
bufferSize?: number;
|
|
320
|
+
/** Maximum history to keep */
|
|
321
|
+
historySize?: number;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Reactive channel state
|
|
326
|
+
*/
|
|
327
|
+
export interface ChannelState<T> {
|
|
328
|
+
/** Latest received value */
|
|
329
|
+
readonly latest: Readable<T | undefined>;
|
|
330
|
+
/** History of values */
|
|
331
|
+
readonly history: Readable<readonly T[]>;
|
|
332
|
+
/** Whether channel is closed */
|
|
333
|
+
readonly isClosed: Readable<boolean>;
|
|
334
|
+
/** Send a value */
|
|
335
|
+
readonly send: (value: T) => Promise<void>;
|
|
336
|
+
/** Close the channel */
|
|
337
|
+
readonly close: () => void;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Create a reactive channel for Go-style communication.
|
|
342
|
+
*
|
|
343
|
+
* @example
|
|
344
|
+
* ```svelte
|
|
345
|
+
* <script>
|
|
346
|
+
* const ch = createChannel<string>();
|
|
347
|
+
*
|
|
348
|
+
* async function sendMessage() {
|
|
349
|
+
* await ch.send("Hello!");
|
|
350
|
+
* }
|
|
351
|
+
* </script>
|
|
352
|
+
*
|
|
353
|
+
* <p>Latest: {$ch.latest}</p>
|
|
354
|
+
* <button onclick={sendMessage}>Send</button>
|
|
355
|
+
* ```
|
|
356
|
+
*/
|
|
357
|
+
export function createChannel<T>(options: CreateChannelOptions = {}): ChannelState<T> {
|
|
358
|
+
const s = createScope({ name: "createChannel" });
|
|
359
|
+
const ch = s.channel<T>(options.bufferSize ?? 0);
|
|
360
|
+
|
|
361
|
+
const latest = writable<T | undefined>(undefined);
|
|
362
|
+
const history = writable<T[]>([]);
|
|
363
|
+
const isClosed = writable(false);
|
|
364
|
+
|
|
365
|
+
// Start receiving in background
|
|
366
|
+
const receiveLoop = async () => {
|
|
367
|
+
for await (const value of ch) {
|
|
368
|
+
latest.set(value);
|
|
369
|
+
history.update(h => [...h.slice(-(options.historySize ?? 100)), value]);
|
|
370
|
+
}
|
|
371
|
+
isClosed.set(true);
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
receiveLoop();
|
|
375
|
+
|
|
376
|
+
return {
|
|
377
|
+
latest: { subscribe: latest.subscribe },
|
|
378
|
+
history: { subscribe: history.subscribe },
|
|
379
|
+
isClosed: { subscribe: isClosed.subscribe },
|
|
380
|
+
send: async (value: T) => {
|
|
381
|
+
await ch.send(value);
|
|
382
|
+
},
|
|
383
|
+
close: () => {
|
|
384
|
+
ch.close();
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ============================================================================
|
|
390
|
+
// Broadcast Store
|
|
391
|
+
// ============================================================================
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Reactive broadcast state
|
|
395
|
+
*/
|
|
396
|
+
export interface BroadcastState<T> {
|
|
397
|
+
/** Latest broadcasted value */
|
|
398
|
+
readonly latest: Readable<T | undefined>;
|
|
399
|
+
/** Subscribe to broadcasts */
|
|
400
|
+
readonly subscribe: (callback: (value: T) => void) => { unsubscribe: () => void };
|
|
401
|
+
/** Broadcast a value */
|
|
402
|
+
readonly broadcast: (value: T) => void;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Create a reactive broadcast channel.
|
|
407
|
+
*
|
|
408
|
+
* @example
|
|
409
|
+
* ```svelte
|
|
410
|
+
* <script>
|
|
411
|
+
* const bus = createBroadcast<string>();
|
|
412
|
+
*
|
|
413
|
+
* // Subscribe
|
|
414
|
+
* bus.subscribe(msg => console.log(msg));
|
|
415
|
+
*
|
|
416
|
+
* function send() {
|
|
417
|
+
* bus.broadcast("Hello!");
|
|
418
|
+
* }
|
|
419
|
+
* </script>
|
|
420
|
+
*
|
|
421
|
+
* <p>Latest: {$bus.latest}</p>
|
|
422
|
+
* ```
|
|
423
|
+
*/
|
|
424
|
+
export function createBroadcast<T>(): BroadcastState<T> {
|
|
425
|
+
const bc = new BroadcastChannel<T>();
|
|
426
|
+
|
|
427
|
+
const latest = writable<T | undefined>(undefined);
|
|
428
|
+
|
|
429
|
+
return {
|
|
430
|
+
latest: { subscribe: latest.subscribe },
|
|
431
|
+
subscribe: (callback: (value: T) => void) => {
|
|
432
|
+
// Start async iterator to receive broadcasts
|
|
433
|
+
(async () => {
|
|
434
|
+
for await (const value of bc.subscribe()) {
|
|
435
|
+
latest.set(value);
|
|
436
|
+
callback(value);
|
|
437
|
+
}
|
|
438
|
+
})();
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
unsubscribe: () => bc.close(),
|
|
442
|
+
};
|
|
443
|
+
},
|
|
444
|
+
broadcast: async (value: T) => {
|
|
445
|
+
await bc.send(value);
|
|
446
|
+
latest.set(value);
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// ============================================================================
|
|
452
|
+
// Polling Store
|
|
453
|
+
// ============================================================================
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Options for polling
|
|
457
|
+
*/
|
|
458
|
+
export interface CreatePollingOptions {
|
|
459
|
+
/** Interval in milliseconds */
|
|
460
|
+
interval: number;
|
|
461
|
+
/** Whether to start immediately */
|
|
462
|
+
immediate?: boolean;
|
|
463
|
+
/** Continue when tab is hidden */
|
|
464
|
+
continueOnHidden?: boolean;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Reactive polling state
|
|
469
|
+
*/
|
|
470
|
+
export interface PollingState<T> {
|
|
471
|
+
/** Latest data */
|
|
472
|
+
readonly data: Readable<T | undefined>;
|
|
473
|
+
/** Error if polling failed */
|
|
474
|
+
readonly error: Readable<Error | undefined>;
|
|
475
|
+
/** Whether currently polling */
|
|
476
|
+
readonly isPolling: Readable<boolean>;
|
|
477
|
+
/** Number of polls completed */
|
|
478
|
+
readonly pollCount: Readable<number>;
|
|
479
|
+
/** Start polling */
|
|
480
|
+
readonly start: () => void;
|
|
481
|
+
/** Stop polling */
|
|
482
|
+
readonly stop: () => void;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Create a reactive polling mechanism.
|
|
487
|
+
*
|
|
488
|
+
* @example
|
|
489
|
+
* ```svelte
|
|
490
|
+
* <script>
|
|
491
|
+
* const poller = createPolling(
|
|
492
|
+
* async () => fetchLatestData(),
|
|
493
|
+
* { interval: 5000, immediate: true }
|
|
494
|
+
* );
|
|
495
|
+
* </script>
|
|
496
|
+
*
|
|
497
|
+
* {#if $poller.data}
|
|
498
|
+
* <p>{$poller.data}</p>
|
|
499
|
+
* {/if}
|
|
500
|
+
* <button onclick={() => $poller.stop()}>Stop</button>
|
|
501
|
+
* ```
|
|
502
|
+
*/
|
|
503
|
+
export function createPolling<T>(
|
|
504
|
+
factory: () => Promise<T>,
|
|
505
|
+
options: CreatePollingOptions,
|
|
506
|
+
): PollingState<T> {
|
|
507
|
+
const s = createScope({ name: "createPolling" });
|
|
508
|
+
|
|
509
|
+
const data = writable<T | undefined>(undefined);
|
|
510
|
+
const error = writable<Error | undefined>(undefined);
|
|
511
|
+
const isPolling = writable(false);
|
|
512
|
+
const pollCount = writable(0);
|
|
513
|
+
|
|
514
|
+
// Create the poller with onValue callback
|
|
515
|
+
const poller = s.poll(
|
|
516
|
+
async () => {
|
|
517
|
+
try {
|
|
518
|
+
const result = await factory();
|
|
519
|
+
return { success: true, value: result } as const;
|
|
520
|
+
} catch (e) {
|
|
521
|
+
return { success: false, error: e } as const;
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
(result) => {
|
|
525
|
+
pollCount.update(c => c + 1);
|
|
526
|
+
if (result.success) {
|
|
527
|
+
data.set(result.value);
|
|
528
|
+
} else {
|
|
529
|
+
error.set(result.error instanceof Error ? result.error : new Error(String(result.error)));
|
|
530
|
+
}
|
|
531
|
+
},
|
|
532
|
+
{ interval: options.interval, immediate: options.immediate }
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
// Sync isPolling with poller status
|
|
536
|
+
const updateStatus = () => {
|
|
537
|
+
const status = poller.status();
|
|
538
|
+
isPolling.set(status.running);
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
// Handle visibility change
|
|
542
|
+
if (!options.continueOnHidden && typeof document !== "undefined") {
|
|
543
|
+
const handler = () => {
|
|
544
|
+
if (document.hidden) {
|
|
545
|
+
poller.stop();
|
|
546
|
+
updateStatus();
|
|
547
|
+
} else if (options.immediate !== false) {
|
|
548
|
+
poller.start();
|
|
549
|
+
updateStatus();
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
document.addEventListener("visibilitychange", handler);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Set initial polling state
|
|
557
|
+
updateStatus();
|
|
558
|
+
|
|
559
|
+
return {
|
|
560
|
+
data: { subscribe: data.subscribe },
|
|
561
|
+
error: { subscribe: error.subscribe },
|
|
562
|
+
isPolling: { subscribe: isPolling.subscribe },
|
|
563
|
+
pollCount: { subscribe: pollCount.subscribe },
|
|
564
|
+
start: () => {
|
|
565
|
+
poller.start();
|
|
566
|
+
updateStatus();
|
|
567
|
+
},
|
|
568
|
+
stop: () => {
|
|
569
|
+
poller.stop();
|
|
570
|
+
updateStatus();
|
|
571
|
+
},
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// ============================================================================
|
|
576
|
+
// Store-like Interface (Svelte 5 compatible)
|
|
577
|
+
// ============================================================================
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Create a reactive value that can be subscribed to (Svelte store contract).
|
|
581
|
+
* Compatible with Svelte 5's `$` prefix.
|
|
582
|
+
*
|
|
583
|
+
* @example
|
|
584
|
+
* ```svelte
|
|
585
|
+
* <script>
|
|
586
|
+
* const count = createStore(0);
|
|
587
|
+
*
|
|
588
|
+
* function increment() {
|
|
589
|
+
* $count++;
|
|
590
|
+
* }
|
|
591
|
+
* </script>
|
|
592
|
+
*
|
|
593
|
+
* <button onclick={increment}>
|
|
594
|
+
* Count: {$count}
|
|
595
|
+
* </button>
|
|
596
|
+
* ```
|
|
597
|
+
*/
|
|
598
|
+
export function createStore<T>(initialValue: T) {
|
|
599
|
+
const { subscribe, set, update } = writable(initialValue);
|
|
600
|
+
|
|
601
|
+
return {
|
|
602
|
+
subscribe,
|
|
603
|
+
set,
|
|
604
|
+
update,
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Re-export types
|
|
609
|
+
export type { Result, Scope, Channel, BroadcastChannel } from "go-go-scope";
|