@spooky-sync/client-solid2 0.0.1-alpha.1 → 0.0.1-alpha.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/AGENTS.md CHANGED
@@ -45,7 +45,7 @@ Provider props are identical to client-solid: `config`, `fallback`, `preload` (a
45
45
  - `isSettled()` — authoritative AND idle; gate windowed-list end detection on it. Not monotonic; latch it (a `createMemo(prev => prev || q.isSettled())`) for a verdict that must stick.
46
46
  - Options: `enabled?: () => boolean`, `deregisterOnCleanup?: boolean`.
47
47
  - **`createSubmission(fn)`** — button pending/error state around a mutation: `submit()`, `pending()`, `error()`, `result()`, `clearError()`. There is deliberately no `action()` / `createOptimisticStore` layer (see `src/lib/create-submission.ts`).
48
- - **`useSyncActivity(options?)`** — `{ fetchingQueries, pendingMutations, isDownloading, isUploading }`. One subscription on the engine's aggregate fetch count plus the outbox depth; `isDownloading()` is debounced ON by `downloadDelayMs` (200) and off at once, `isUploading()` is `pendingMutations() > uploadThreshold` (1). Drive a logo/traffic indicator off it; pair with `useSyncStatus().isOffline()` to show the unsaved count while disconnected.
48
+ - **`useSyncActivity(options?)`** — `{ fetchingQueries, pendingMutations, isDownloading, isUploading }`. One subscription on the engine's aggregate fetch count plus the outbox depth; both are debounced ON by their delay (`downloadDelayMs` 200, `uploadDelayMs` 150) and off at once, and `isUploading()` additionally needs `pendingMutations() > uploadThreshold` (0, so any unacknowledged write counts). Drive a logo/traffic indicator off it; pair with `useSyncStatus().connection()` / `.isOffline()` to switch the same mark to a reconnecting or offline state and show the unsaved count.
49
49
  - **`createPreload`**, **`usePendingMutations`**, **`useSyncStatus`**, **`useStorageStatus`**, **`useFeatureFlag`**, **`useAppRelease`**, **`useCrdtField`**, **`useFileUpload`**, **`useDownloadFile`** — same shapes as client-solid.
50
50
  - **`conflate`**, **`fromSubscription`** — the async-iterable helpers the hooks are built on; useful for wrapping other engine subscriptions.
51
51
 
package/QUICK_START.md CHANGED
@@ -116,7 +116,7 @@ Track a backend job's progress by querying its outbox row: `createQuery(() => db
116
116
 
117
117
  ## Status hooks
118
118
 
119
- Same shapes as client-solid, rebuilt on async-iterable-backed memos: `useSyncStatus`, `useStorageStatus`, `usePendingMutations`, `useSyncActivity` (new: `isDownloading()` after 200 ms of fetching, `isUploading()` above one queued write, for a traffic indicator), `useFeatureFlag`, `useAppRelease`, `useCrdtField`, `useFileUpload`, `useDownloadFile`, `createPreload`.
119
+ Same shapes as client-solid, rebuilt on async-iterable-backed memos: `useSyncStatus`, `useStorageStatus`, `usePendingMutations`, `useSyncActivity` (new: `isDownloading()` after 200 ms of fetching, `isUploading()` after 150 ms of a queued write, for a traffic indicator), `useFeatureFlag`, `useAppRelease`, `useCrdtField`, `useFileUpload`, `useDownloadFile`, `createPreload`.
120
120
 
121
121
  ## Migrating from client-solid
122
122
 
package/README.md CHANGED
@@ -8,7 +8,7 @@ What "native" means here:
8
8
 
9
9
  - Query results are a plain store written from the engine's live subscription and merged keyed by `id`: row identity preserved, coarse `<For>` readers notified, only changed fields written. Not an async-generator projection - see `create-query.ts` for why a never-returning generator breaks navigation transitions.
10
10
  - `createQuery` exposes both worlds: non-suspending accessors (`data`, `isLoading`, `isFetching`, `isAuthoritative`, `hasData`, `isEmpty`, `isSettled`, `error`) and a suspending `ready()` for `<Loading>` boundaries. Born committed, so local-first cache paints never suspend. `isLoading()` is true only on a cold first load (nothing cached, server not heard yet); `isEmpty()` is the server saying "no rows".
11
- - Status hooks (`useSyncStatus`, `useSyncActivity`, `useStorageStatus`, `usePendingMutations`, feature flags, app release) are async-iterable-backed memos with `loadingValue`. `useSyncActivity` is the traffic indicator pair: `isDownloading()` (queries fetching for longer than 200 ms) and `isUploading()` (more than one write queued), plus the raw `fetchingQueries()` / `pendingMutations()` counts.
11
+ - Status hooks (`useSyncStatus`, `useSyncActivity`, `useStorageStatus`, `usePendingMutations`, feature flags, app release) are async-iterable-backed memos with `loadingValue`. `useSyncActivity` is the traffic indicator pair: `isDownloading()` (queries fetching for longer than 200 ms) and `isUploading()` (writes queued for longer than 150 ms), plus the raw `fetchingQueries()` / `pendingMutations()` counts.
12
12
  - Mutations stay plain async calls — the engine is already optimistic local-first end to end (local commit → live re-emit → outbox sync). `createSubmission` adds button pending/error state.
13
13
 
14
14
  See `QUICK_START.md` for usage and the migration table from client-solid, and `src/lib/__tests__/rc-semantics.test.ts` for the probed Solid 2 rc contracts this package depends on (run them after every Solid version bump).
package/dist/index.cjs CHANGED
@@ -391,10 +391,10 @@ function useSyncStatus() {
391
391
  * The two directions of sync traffic, for an indicator in the app chrome.
392
392
  *
393
393
  * `fetchingQueries` is one subscription on the engine's aggregate fetch count,
394
- * not one per query; `pendingMutations` is the outbox depth. `isDownloading`
395
- * is the fetch count debounced ON by `downloadDelayMs` (and off at once), so a
396
- * local-first page that answers from cache and confirms with the server in a
397
- * few milliseconds never flickers. Must be used within a `<Sp00kyProvider>`, or
394
+ * not one per query; `pendingMutations` is the outbox depth. Both
395
+ * `isDownloading` and `isUploading` are debounced ON by their delay (and off at
396
+ * once), so a local-first page that answers from cache and confirms with the
397
+ * server in a few milliseconds never flickers. Must be used within a `<Sp00kyProvider>`, or
398
398
  * pass the `SyncedDb` explicitly.
399
399
  */
400
400
  function useSyncActivity(dbOrOptions, maybeOptions) {
@@ -402,38 +402,47 @@ function useSyncActivity(dbOrOptions, maybeOptions) {
402
402
  const options = (explicitDb ? maybeOptions : dbOrOptions) ?? {};
403
403
  const db = explicitDb ?? useDb();
404
404
  const downloadDelayMs = options.downloadDelayMs ?? 200;
405
- const uploadThreshold = options.uploadThreshold ?? 1;
405
+ const uploadThreshold = options.uploadThreshold ?? 0;
406
+ const uploadDelayMs = options.uploadDelayMs ?? 150;
406
407
  const fetchingQueries = fromSubscription((cb) => db.subscribeToFetchActivity(cb), db.fetchingQueryCount);
407
408
  const pendingMutations = fromSubscription((cb) => db.subscribeToPendingMutations(cb), db.pendingMutationCount);
408
- const [isDownloading, setIsDownloading] = (0, solid_js.createSignal)(false, { ownedWrite: true });
409
+ return {
410
+ fetchingQueries,
411
+ pendingMutations,
412
+ isDownloading: delayedOn(() => fetchingQueries() > 0, downloadDelayMs),
413
+ isUploading: delayedOn(() => pendingMutations() > uploadThreshold, uploadDelayMs)
414
+ };
415
+ }
416
+ /**
417
+ * `busy` held for `delayMs`, dropped the instant it clears. Both directions of
418
+ * traffic use it, so a burst too short to see never flickers the indicator and
419
+ * neither one can latch on after the work is done.
420
+ */
421
+ function delayedOn(busy, delayMs) {
422
+ const [on, setOn] = (0, solid_js.createSignal)(false, { ownedWrite: true });
409
423
  let timer;
410
- (0, solid_js.createEffect)(() => fetchingQueries() > 0, (busy) => {
424
+ (0, solid_js.createEffect)(busy, (isBusy) => {
411
425
  if (timer !== void 0) {
412
426
  clearTimeout(timer);
413
427
  timer = void 0;
414
428
  }
415
- if (!busy) {
416
- setIsDownloading(false);
429
+ if (!isBusy) {
430
+ setOn(false);
417
431
  return;
418
432
  }
419
- if (downloadDelayMs <= 0) {
420
- setIsDownloading(true);
433
+ if (delayMs <= 0) {
434
+ setOn(true);
421
435
  return;
422
436
  }
423
437
  timer = setTimeout(() => {
424
438
  timer = void 0;
425
- setIsDownloading(true);
426
- }, downloadDelayMs);
439
+ setOn(true);
440
+ }, delayMs);
427
441
  });
428
442
  (0, solid_js.onCleanup)(() => {
429
443
  if (timer !== void 0) clearTimeout(timer);
430
444
  });
431
- return {
432
- fetchingQueries,
433
- pendingMutations,
434
- isDownloading,
435
- isUploading: () => pendingMutations() > uploadThreshold
436
- };
445
+ return on;
437
446
  }
438
447
 
439
448
  //#endregion