@spooky-sync/client-solid2 0.0.1-canary.238 → 0.0.1-canary.240
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 +1 -0
- package/QUICK_START.md +1 -1
- package/README.md +1 -1
- package/dist/index.cjs +65 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +46 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +65 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/skills/sp00ky-solid2/SKILL.md +1 -1
- package/src/index.ts +20 -0
- package/src/lib/__tests__/use-sync-activity.test.ts +104 -0
- package/src/lib/use-sync-activity.ts +99 -0
package/AGENTS.md
CHANGED
|
@@ -45,6 +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
49
|
- **`createPreload`**, **`usePendingMutations`**, **`useSyncStatus`**, **`useStorageStatus`**, **`useFeatureFlag`**, **`useAppRelease`**, **`useCrdtField`**, **`useFileUpload`**, **`useDownloadFile`** — same shapes as client-solid.
|
|
49
50
|
- **`conflate`**, **`fromSubscription`** — the async-iterable helpers the hooks are built on; useful for wrapping other engine subscriptions.
|
|
50
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`, `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()` above one 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`, `useStorageStatus`, `usePendingMutations`, feature flags, app release) are async-iterable-backed memos with `loadingValue`.
|
|
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.
|
|
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
|
@@ -387,6 +387,57 @@ function useSyncStatus() {
|
|
|
387
387
|
};
|
|
388
388
|
}
|
|
389
389
|
|
|
390
|
+
//#endregion
|
|
391
|
+
//#region src/lib/use-sync-activity.ts
|
|
392
|
+
/**
|
|
393
|
+
* The two directions of sync traffic, for an indicator in the app chrome.
|
|
394
|
+
*
|
|
395
|
+
* `fetchingQueries` is one subscription on the engine's aggregate fetch count,
|
|
396
|
+
* not one per query; `pendingMutations` is the outbox depth. `isDownloading`
|
|
397
|
+
* is the fetch count debounced ON by `downloadDelayMs` (and off at once), so a
|
|
398
|
+
* local-first page that answers from cache and confirms with the server in a
|
|
399
|
+
* few milliseconds never flickers. Must be used within a `<Sp00kyProvider>`, or
|
|
400
|
+
* pass the `SyncedDb` explicitly.
|
|
401
|
+
*/
|
|
402
|
+
function useSyncActivity(dbOrOptions, maybeOptions) {
|
|
403
|
+
const explicitDb = dbOrOptions && typeof dbOrOptions.subscribeToFetchActivity === "function" ? dbOrOptions : void 0;
|
|
404
|
+
const options = (explicitDb ? maybeOptions : dbOrOptions) ?? {};
|
|
405
|
+
const db = explicitDb ?? useDb();
|
|
406
|
+
const downloadDelayMs = options.downloadDelayMs ?? 200;
|
|
407
|
+
const uploadThreshold = options.uploadThreshold ?? 1;
|
|
408
|
+
const fetchingQueries = fromSubscription((cb) => db.subscribeToFetchActivity(cb), db.fetchingQueryCount);
|
|
409
|
+
const pendingMutations = fromSubscription((cb) => db.subscribeToPendingMutations(cb), db.pendingMutationCount);
|
|
410
|
+
const [isDownloading, setIsDownloading] = (0, solid_js.createSignal)(false, { ownedWrite: true });
|
|
411
|
+
let timer;
|
|
412
|
+
(0, solid_js.createEffect)(() => fetchingQueries() > 0, (busy) => {
|
|
413
|
+
if (timer !== void 0) {
|
|
414
|
+
clearTimeout(timer);
|
|
415
|
+
timer = void 0;
|
|
416
|
+
}
|
|
417
|
+
if (!busy) {
|
|
418
|
+
setIsDownloading(false);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
if (downloadDelayMs <= 0) {
|
|
422
|
+
setIsDownloading(true);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
timer = setTimeout(() => {
|
|
426
|
+
timer = void 0;
|
|
427
|
+
setIsDownloading(true);
|
|
428
|
+
}, downloadDelayMs);
|
|
429
|
+
});
|
|
430
|
+
(0, solid_js.onCleanup)(() => {
|
|
431
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
432
|
+
});
|
|
433
|
+
return {
|
|
434
|
+
fetchingQueries,
|
|
435
|
+
pendingMutations,
|
|
436
|
+
isDownloading,
|
|
437
|
+
isUploading: () => pendingMutations() > uploadThreshold
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
|
|
390
441
|
//#endregion
|
|
391
442
|
//#region src/lib/use-storage-status.ts
|
|
392
443
|
/**
|
|
@@ -1163,6 +1214,19 @@ var SyncedDb = class {
|
|
|
1163
1214
|
if (!this.sp00ky) throw new Error("SyncedDb not initialized");
|
|
1164
1215
|
return this.sp00ky.subscribeToPendingMutations(cb);
|
|
1165
1216
|
}
|
|
1217
|
+
/** Queries mid-fetch right now. See {@link useSyncActivity}. */
|
|
1218
|
+
get fetchingQueryCount() {
|
|
1219
|
+
if (!this.sp00ky) throw new Error("SyncedDb not initialized");
|
|
1220
|
+
return this.sp00ky.fetchingQueryCount;
|
|
1221
|
+
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Observe how many queries are fetching from the server. Fires immediately
|
|
1224
|
+
* and on every change. Prefer the `useSyncActivity` hook in components.
|
|
1225
|
+
*/
|
|
1226
|
+
subscribeToFetchActivity(cb) {
|
|
1227
|
+
if (!this.sp00ky) throw new Error("SyncedDb not initialized");
|
|
1228
|
+
return this.sp00ky.subscribeToFetchActivity(cb);
|
|
1229
|
+
}
|
|
1166
1230
|
/** Current sync-health snapshot. See {@link useSyncStatus}. */
|
|
1167
1231
|
get syncHealth() {
|
|
1168
1232
|
if (!this.sp00ky) throw new Error("SyncedDb not initialized");
|
|
@@ -1223,5 +1287,6 @@ exports.useFileUpload = useFileUpload;
|
|
|
1223
1287
|
exports.usePendingMutations = usePendingMutations;
|
|
1224
1288
|
exports.useQuery = useQuery;
|
|
1225
1289
|
exports.useStorageStatus = useStorageStatus;
|
|
1290
|
+
exports.useSyncActivity = useSyncActivity;
|
|
1226
1291
|
exports.useSyncStatus = useSyncStatus;
|
|
1227
1292
|
//# sourceMappingURL=index.cjs.map
|