@spooky-sync/client-solid 0.0.1-canary.151 → 0.0.1-canary.153

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 CHANGED
@@ -173,6 +173,29 @@ function useSyncStatus() {
173
173
  };
174
174
  }
175
175
 
176
+ //#endregion
177
+ //#region src/lib/use-storage-status.ts
178
+ /**
179
+ * Observe how durable the LOCAL cache is, for a "no local storage" warning.
180
+ *
181
+ * Under `localEngine: 'sqlite'` with `store: 'indexeddb'` the durable store is
182
+ * the OPFS SAHPool VFS, and only ONE client per bucket can hold it open: a
183
+ * second tab of the same app cannot get it and runs in memory instead (the
184
+ * engine retries first, so a closing tab's lock is usually waited out). Must be
185
+ * used within a `<Sp00kyProvider>`.
186
+ */
187
+ function useStorageStatus() {
188
+ const db = useDb();
189
+ const [health, setHealth] = (0, solid_js.createSignal)(db.storageHealth);
190
+ (0, solid_js.onCleanup)(db.subscribeToStorageHealth(setHealth));
191
+ return {
192
+ health,
193
+ status: () => health().status,
194
+ isPersistent: () => health().status === "persistent",
195
+ isMemoryFallback: () => health().fallback
196
+ };
197
+ }
198
+
176
199
  //#endregion
177
200
  //#region src/lib/use-crdt-field.ts
178
201
  function useCrdtField(table, recordId, field, fallbackText) {
@@ -724,6 +747,20 @@ var SyncedDb = class {
724
747
  if (!this.sp00ky) throw new Error("SyncedDb not initialized");
725
748
  return this.sp00ky.subscribeToSyncHealth(cb);
726
749
  }
750
+ /** Current local-store durability snapshot. See {@link useStorageStatus}. */
751
+ get storageHealth() {
752
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
753
+ return this.sp00ky.storageHealth;
754
+ }
755
+ /**
756
+ * Observe local-store durability. Fires immediately with the current snapshot
757
+ * and again on change. Prefer the `useStorageStatus` hook in components; this
758
+ * is the imperative escape hatch.
759
+ */
760
+ subscribeToStorageHealth(cb) {
761
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
762
+ return this.sp00ky.subscribeToStorageHealth(cb);
763
+ }
727
764
  bucket(name) {
728
765
  if (!this.sp00ky) throw new Error("SyncedDb not initialized");
729
766
  return this.sp00ky.bucket(name);
@@ -746,5 +783,6 @@ exports.useDownloadFile = useDownloadFile;
746
783
  exports.useFeatureFlag = useFeatureFlag;
747
784
  exports.useFileUpload = useFileUpload;
748
785
  exports.useQuery = useQuery;
786
+ exports.useStorageStatus = useStorageStatus;
749
787
  exports.useSyncStatus = useSyncStatus;
750
788
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["Sp00kyClient","RecordId"],"sources":["../src/lib/context.ts","../src/lib/use-query.ts","../src/lib/create-preload.ts","../src/lib/use-sync-status.ts","../src/lib/use-crdt-field.ts","../src/lib/use-feature-flag.ts","../src/lib/use-app-release.ts","../src/lib/use-file-upload.ts","../src/lib/use-download-file.ts","../src/lib/Sp00kyProvider.ts","../src/index.ts"],"sourcesContent":["import { createContext, useContext } from 'solid-js';\nimport type { SchemaStructure } from '@spooky/query-builder';\nimport type { SyncedDb } from '../index';\n\nexport const Sp00kyContext = createContext<SyncedDb<any> | undefined>();\n\nexport function useDb<S extends SchemaStructure>(): SyncedDb<S> {\n const db = useContext(Sp00kyContext);\n if (!db) {\n throw new Error('useDb must be used within a <Sp00kyProvider>. Wrap your app in <Sp00kyProvider config={...}>.');\n }\n return db as SyncedDb<S>;\n}\n","import type {\n ColumnSchema,\n FinalQuery,\n SchemaStructure,\n TableNames,\n QueryResult,\n} from '@spooky-sync/query-builder';\nimport { createEffect, createSignal, onCleanup, useContext } from 'solid-js';\nimport { createStore, reconcile } from 'solid-js/store';\nimport { SyncedDb } from '..';\nimport type { Sp00kyQueryResultPromise } from '@spooky-sync/core';\nimport { Sp00kyContext } from './context';\n\ntype QueryArg<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n> =\n | FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>\n | (() =>\n | FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>\n | null\n | undefined);\n\ntype QueryOptions = {\n enabled?: () => boolean;\n /**\n * Tear down the query (remote `_00_query` view + local WASM view) when this\n * hook is disposed and no other subscriber remains, instead of keeping it\n * resident for cheap re-subscription. Use for viewport-windowed lists that\n * mount/unmount a query per scroll window and want off-screen windows\n * cancelled. Trade-off: scrolling back to a torn-down window re-registers it.\n */\n deregisterOnCleanup?: boolean;\n};\n\n// Overload: context-based (no explicit db)\nexport function useQuery<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n TData = QueryResult<S, TableName, RelatedFields, IsOne> | null,\n>(\n finalQuery: QueryArg<S, TableName, T, RelatedFields, IsOne>,\n options?: QueryOptions,\n): {\n data: () => TData | undefined;\n error: () => Error | undefined;\n isLoading: () => boolean;\n isFetching: () => boolean;\n isSettled: () => boolean;\n};\n\n// Overload: explicit db (backward-compatible)\nexport function useQuery<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n TData = QueryResult<S, TableName, RelatedFields, IsOne> | null,\n>(\n db: SyncedDb<S>,\n finalQuery: QueryArg<S, TableName, T, RelatedFields, IsOne>,\n options?: QueryOptions,\n): {\n data: () => TData | undefined;\n error: () => Error | undefined;\n isLoading: () => boolean;\n isFetching: () => boolean;\n isSettled: () => boolean;\n};\n\n// Implementation\nexport function useQuery<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends {\n columns: Record<string, ColumnSchema>;\n },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n TData = QueryResult<S, TableName, RelatedFields, IsOne> | null,\n>(\n dbOrQuery:\n | SyncedDb<S>\n | QueryArg<S, TableName, T, RelatedFields, IsOne>,\n queryOrOptions?:\n | QueryArg<S, TableName, T, RelatedFields, IsOne>\n | QueryOptions,\n maybeOptions?: QueryOptions,\n) {\n let db: SyncedDb<S>;\n let finalQuery: QueryArg<S, TableName, T, RelatedFields, IsOne>;\n let options: QueryOptions | undefined;\n\n if (dbOrQuery instanceof SyncedDb) {\n // Explicit db overload: useQuery(db, query, options?)\n db = dbOrQuery;\n finalQuery = queryOrOptions as QueryArg<S, TableName, T, RelatedFields, IsOne>;\n options = maybeOptions;\n } else {\n // Context-based overload: useQuery(query, options?)\n const contextDb = useContext(Sp00kyContext);\n if (!contextDb) {\n throw new Error(\n 'useQuery: No db argument provided and no Sp00kyContext found. ' +\n 'Either pass a SyncedDb instance or wrap your app in <Sp00kyProvider>.'\n );\n }\n db = contextDb as SyncedDb<S>;\n finalQuery = dbOrQuery;\n options = queryOrOptions as QueryOptions | undefined;\n }\n\n const [error, setError] = createSignal<Error | undefined>(undefined);\n const [isFetched, setIsFetched] = createSignal(false);\n const [isFetching, setIsFetching] = createSignal(false);\n // Results live in a store (not a signal) so consecutive live-query emissions\n // are merged with `reconcile`: unchanged rows keep their object identity and\n // changed rows are mutated in place. That keeps Solid's reference-keyed `<For>`\n // rows — and any `useQuery` subscriptions mounted inside them — alive across\n // updates, instead of tearing every row down and re-registering its queries.\n const [state, setState] = createStore<{ value: TData | undefined }>({ value: undefined });\n // `reconcile` (below) merges each emission into `state.value` IN PLACE, keeping\n // the array reference stable. That's ideal for granular per-row reactivity, but\n // it means a *coarse* reader of `data()` — `<For each={data()}>`, or an effect\n // that copies the whole array elsewhere (e.g. GameList's windowed store) — is\n // NOT re-run when rows are added/removed/reordered within a same-length result\n // (the classic case: deleting a row in a windowed list shifts the next one in,\n // so length stays 50 and the array ref never changes). Bump a version on every\n // emission and read it in `data()` so every consumer re-runs on any change while\n // reconcile still preserves row identity underneath.\n const [version, setVersion] = createSignal(0);\n const data = () => {\n version();\n return state.value;\n };\n\n let prevQueryString: string | undefined;\n // Monotonic token for each subscription generation. Bumped whenever the query\n // identity changes or the hook is disposed, so a slow async `initQuery`\n // continuation can detect it was superseded and avoid installing a stale (and\n // leaked) subscription.\n let runId = 0;\n let activeUnsub: (() => void) | undefined;\n // The hash of the currently-installed subscription, for opt-in deregister on\n // dispose (see `deregisterOnCleanup`).\n let activeHash: string | undefined;\n\n const teardownActive = () => {\n activeUnsub?.();\n activeUnsub = undefined;\n };\n\n const sp00ky = db.getSp00ky();\n\n const initQuery = async (\n query: FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>,\n myRun: number\n ) => {\n const { hash } = await query.run();\n // A newer query identity (or disposal) won the race while we awaited run().\n if (myRun !== runId) return;\n activeHash = hash;\n setError(undefined);\n\n let isFirstCall = true;\n const unsub = await sp00ky.subscribe(\n hash,\n (e) => {\n const queryData = (query.isOne ? e[0] : e) as TData;\n // Merge into the store by record id: unchanged rows keep their identity,\n // changed rows update in place. Replaces wholesale for `one()`/null.\n // Time the reconcile → report as the \"frontend\" phase for DevTools/MCP.\n const reconcileStart = performance.now();\n setState('value', reconcile(queryData as any, { key: 'id' }));\n // Notify coarse `data()` readers (see the `version` note above): reconcile\n // keeps the array ref stable, so this is what re-runs `<For>`/copy-effects\n // on add/remove/reorder.\n setVersion((v) => v + 1);\n sp00ky.reportFrontendTiming(hash, performance.now() - reconcileStart);\n // The first (immediate) callback with no data likely means the local DB\n // hasn't synced yet — don't mark as fetched so UI shows loading state\n const hasData = query.isOne ? queryData !== null && queryData !== undefined : (e as any[]).length > 0;\n if (!isFirstCall || hasData) {\n setIsFetched(true);\n }\n isFirstCall = false;\n },\n { immediate: true }\n );\n\n // Mirror the query's fetch status so the UI can show a \"loading more\"\n // state while the sync engine pulls missing records in the background.\n const unsubStatus = sp00ky.subscribeQueryStatus(\n hash,\n (status) => setIsFetching(status === 'fetching'),\n { immediate: true }\n );\n\n const teardown = () => {\n unsub();\n unsubStatus();\n };\n\n // Superseded while awaiting subscribe()? Don't leak — tear down immediately.\n if (myRun !== runId) {\n teardown();\n return;\n }\n activeUnsub = teardown;\n };\n\n createEffect(() => {\n const enabled = options?.enabled?.() ?? true;\n\n // If disabled, clear error and don't run query\n if (!enabled) {\n setError(undefined);\n return;\n }\n\n // Init Query\n const query = typeof finalQuery === 'function' ? finalQuery() : finalQuery;\n if (!query) {\n return;\n }\n\n // Dedup on the query's stable identity hash (cyrb53 of surql + vars), not a\n // full `JSON.stringify` of the FinalQuery (which walks the whole schema +\n // inner query on every reactive tick and isn't guaranteed stable). When the\n // identity is unchanged we keep the existing subscription alive.\n const queryString = String(query.hash);\n if (queryString === prevQueryString) {\n return;\n }\n prevQueryString = queryString;\n\n // New query identity → supersede the previous subscription and start fresh.\n const myRun = ++runId;\n teardownActive();\n setIsFetched(false);\n initQuery(query, myRun);\n });\n\n // Tear down the live subscription when the hook's owner is disposed. Registered\n // on the hook (component) scope rather than inside the effect, so an effect\n // re-run that early-returns (unchanged query) doesn't clean up the still-valid\n // subscription. Bumping runId also invalidates any in-flight initQuery.\n onCleanup(() => {\n runId++;\n teardownActive();\n // Opt-in: cancel the query once this hook (its last subscriber) is gone.\n // teardownActive() above already removed this hook's callback, so\n // deregisterQuery's refcount guard sees the true remaining-subscriber count.\n if (options?.deregisterOnCleanup && activeHash) {\n sp00ky.deregisterQuery(activeHash);\n }\n });\n\n const isLoading = () => {\n return !isFetched() && error() === undefined;\n };\n\n // True once the query has delivered a result AND no fetch cycle is in flight\n // (registration + initial sync included — the core holds `fetching` across\n // the whole registration and flushes debounced results before flipping back\n // to idle). While settled, the results are authoritative: a windowed query\n // returning fewer rows than its LIMIT really is the end of the list, so\n // virtualized lists may size themselves to it without the scrollbar jumping\n // when a still-syncing window transiently reports short. Resets to false\n // whenever the query identity changes.\n const isSettled = () => isFetched() && !isFetching();\n\n return {\n data,\n error,\n isLoading,\n isFetching,\n isSettled,\n };\n}\n","import type {\n ColumnSchema,\n FinalQuery,\n SchemaStructure,\n TableNames,\n} from '@spooky-sync/query-builder';\nimport { createEffect, useContext } from 'solid-js';\nimport { SyncedDb } from '..';\nimport type { Sp00kyQueryResultPromise, PreloadOptions as CorePreloadOptions } from '@spooky-sync/core';\nimport { Sp00kyContext } from './context';\n\ntype PreloadArg<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n> =\n | FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>\n | (() =>\n | FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>\n | null\n | undefined);\n\ntype PreloadOptions = CorePreloadOptions & {\n /** Only preload while this returns true (defaults to always). */\n enabled?: () => boolean;\n};\n\n// Overload: context-based (no explicit db)\nexport function createPreload<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n>(\n finalQuery: PreloadArg<S, TableName, T, RelatedFields, IsOne>,\n options?: PreloadOptions,\n): void;\n\n// Overload: explicit db\nexport function createPreload<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n>(\n db: SyncedDb<S>,\n finalQuery: PreloadArg<S, TableName, T, RelatedFields, IsOne>,\n options?: PreloadOptions,\n): void;\n\n/**\n * Reactive, fire-and-forget prewarm. Resolves the query (calling it if it's a\n * function so it tracks reactive deps), dedupes on the query's stable identity\n * hash, and warms it into the local cache via `db.preload`. No subscription and\n * no cleanup: preload registers nothing that needs tearing down.\n *\n * Typical use: inside a list row, preload the detail query the user is likely\n * to open next, so navigation paints from cache instead of the network.\n */\nexport function createPreload<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n>(\n dbOrQuery: SyncedDb<S> | PreloadArg<S, TableName, T, RelatedFields, IsOne>,\n queryOrOptions?: PreloadArg<S, TableName, T, RelatedFields, IsOne> | PreloadOptions,\n maybeOptions?: PreloadOptions,\n): void {\n let db: SyncedDb<S>;\n let finalQuery: PreloadArg<S, TableName, T, RelatedFields, IsOne>;\n let options: PreloadOptions | undefined;\n\n if (dbOrQuery instanceof SyncedDb) {\n db = dbOrQuery;\n finalQuery = queryOrOptions as PreloadArg<S, TableName, T, RelatedFields, IsOne>;\n options = maybeOptions;\n } else {\n const contextDb = useContext(Sp00kyContext);\n if (!contextDb) {\n throw new Error(\n 'createPreload: No db argument provided and no Sp00kyContext found. ' +\n 'Either pass a SyncedDb instance or wrap your app in <Sp00kyProvider>.',\n );\n }\n db = contextDb as SyncedDb<S>;\n finalQuery = dbOrQuery;\n options = queryOrOptions as PreloadOptions | undefined;\n }\n\n let prevHash: number | undefined;\n\n createEffect(() => {\n if (!(options?.enabled?.() ?? true)) return;\n\n const query = typeof finalQuery === 'function' ? finalQuery() : finalQuery;\n if (!query) return;\n\n // Dedupe on the query's stable identity hash so a reactive re-run with an\n // unchanged query doesn't refetch (the core also dedupes per session).\n if (query.hash === prevHash) return;\n prevHash = query.hash;\n\n void db.getSp00ky().preload(query, { refresh: options?.refresh, staleTime: options?.staleTime });\n });\n}\n","import { createSignal, onCleanup, type Accessor } from 'solid-js';\nimport { useDb } from './context';\nimport type { SyncHealth, SyncHealthStatus } from '@spooky-sync/core';\n\nexport interface UseSyncStatus {\n /** Full health snapshot; updates reactively on every transition. */\n health: Accessor<SyncHealth>;\n /** `'healthy'` | `'degraded'`. */\n status: Accessor<SyncHealthStatus>;\n isHealthy: Accessor<boolean>;\n /** `true` once sync has failed for a sustained run — drive a banner off this. */\n isDegraded: Accessor<boolean>;\n /** `true` once at least one sync round has succeeded this session. */\n everConnected: Accessor<boolean>;\n /**\n * `true` only for a real lost connection: degraded AFTER a first successful\n * sync. Stays `false` during the initial \"connecting\" phase (degraded but\n * never reached the server yet), so an indicator can show nothing until the\n * app has actually connected once.\n */\n isOffline: Accessor<boolean>;\n}\n\n/**\n * Observe sync health for a \"can't reach the server\" banner / indicator.\n *\n * Backed by `db.subscribeToSyncHealth`. Individual sync failures (a transient\n * remote 500 on query registration, a dropped socket) are absorbed by the\n * retry and never flip this; `isDegraded()` only goes true once failures\n * persist for the configured number of consecutive rounds (sp00ky core config\n * `syncHealth.degradeAfterConsecutiveFailures`, default 3), and flips back on\n * the next successful round. Must be used within a `<Sp00kyProvider>`.\n */\nexport function useSyncStatus(): UseSyncStatus {\n const db = useDb();\n // subscribeToSyncHealth fires synchronously with the current status, so the\n // signal is correct from first read; the initial value just avoids a flash.\n const [health, setHealth] = createSignal<SyncHealth>(db.syncHealth);\n const unsub = db.subscribeToSyncHealth(setHealth);\n onCleanup(unsub);\n\n return {\n health,\n status: () => health().status,\n isHealthy: () => health().status === 'healthy',\n isDegraded: () => health().status === 'degraded',\n everConnected: () => health().everConnected,\n isOffline: () => health().status === 'degraded' && health().everConnected,\n };\n}\n","import { createEffect, createSignal, onCleanup, useContext, type Accessor } from 'solid-js';\nimport { Sp00kyContext } from './context';\nimport type { CrdtField } from '@spooky-sync/core';\n\nexport function useCrdtField(\n table: string,\n recordId: () => string | undefined,\n field: string,\n fallbackText?: () => string | undefined,\n): Accessor<CrdtField | null> {\n const db = useContext(Sp00kyContext);\n if (!db) {\n throw new Error('useCrdtField must be used within a <Sp00kyProvider>');\n }\n\n const [crdtField, setCrdtField] = createSignal<CrdtField | null>(null);\n let currentId: string | undefined;\n let initialized = false;\n\n createEffect(() => {\n const id = recordId();\n\n // Skip if the ID hasn't changed (but allow the first non-undefined value through)\n if (initialized && id === currentId) return;\n\n // Close previous field\n if (currentId && crdtField()) {\n db.getSp00ky().closeCrdtField(table, currentId, field);\n setCrdtField(null);\n }\n\n currentId = id;\n initialized = true;\n\n if (!id) return;\n\n const sp00ky = db.getSp00ky();\n const text = fallbackText?.();\n sp00ky\n .openCrdtField(table, id, field, text)\n .then((cf) => {\n if (currentId === id) {\n setCrdtField(cf);\n }\n })\n .catch((err) => {\n // Silent rejections here leave the consumer's `Show when={field()}`\n // permanently stuck on its fallback (typically a static `<p>` with\n // no editing UI), with no error trail. Surface the failure so the\n // root cause (missing `@crdt` annotation, schema codegen drift,\n // local DB query failure, etc.) is visible in the console instead\n // of silently breaking collaborative fields.\n console.error(\n `[useCrdtField] Failed to open CRDT field ${table}.${field} on ${id}:`,\n err,\n );\n });\n });\n\n onCleanup(() => {\n if (currentId && crdtField()) {\n db.getSp00ky().closeCrdtField(table, currentId, field);\n setCrdtField(null);\n }\n });\n\n return crdtField;\n}\n","import { createSignal, onCleanup, type Accessor } from 'solid-js';\nimport { useDb } from './context';\nimport type { FeatureFlagOptions } from '@spooky-sync/core';\n\nexport interface UseFeatureFlag {\n variant: Accessor<string | undefined>;\n payload: Accessor<unknown | undefined>;\n enabled: Accessor<boolean>;\n}\n\n/**\n * Subscribe to a feature flag for the currently authenticated user.\n *\n * Returns three Solid accessors that update reactively whenever the\n * server-materialized assignment in `_00_user_feature` changes. Backed by\n * the same SSP + sync pipeline that powers `useQuery`, so toggling a flag\n * via `spky flag enable <key>` propagates to the UI without a refresh.\n *\n * `enabled()` is `true` when the resolved variant exists and is not 'off'.\n * For multi-variant flags, prefer `variant()` directly.\n */\nexport function useFeatureFlag(\n key: string,\n options?: FeatureFlagOptions,\n): UseFeatureFlag {\n const db = useDb();\n const handle = db.getSp00ky().feature(key, options);\n\n const [variant, setVariant] = createSignal<string | undefined>(handle.variant());\n const [payload, setPayload] = createSignal<unknown | undefined>(handle.payload());\n\n const unsub = handle.subscribe((s) => {\n setVariant(s.variant ?? options?.fallback);\n setPayload(s.payload);\n });\n\n onCleanup(() => {\n unsub();\n handle.close();\n });\n\n return {\n variant,\n payload,\n enabled: () => {\n const v = variant();\n return v !== undefined && v !== 'off';\n },\n };\n}\n","import { createSignal, onCleanup, type Accessor } from 'solid-js';\nimport { useDb } from './context';\nimport { semverGt, type AppReleaseOptions, type AppReleaseSnapshot } from '@spooky-sync/core';\n\nexport interface UseAppReleaseOptions extends AppReleaseOptions {\n /** App name from sp00ky.yml, e.g. `web`. */\n app: string;\n /**\n * The running build's version (X.Y.Z), typically baked in at build time\n * (e.g. a vite `define` from package.json). `updateAvailable()` is true when\n * the announced release is semver-newer than this.\n */\n currentVersion: string;\n}\n\nexport interface UseAppRelease {\n /** Latest announced version for the app, or undefined when no row exists. */\n latestVersion: Accessor<string | undefined>;\n /** Announced version is semver-newer than the running build. */\n updateAvailable: Accessor<boolean>;\n /** The newer release asks clients to update/reload without prompting. */\n mandatory: Accessor<boolean>;\n /** The newer release asks reloads to clear service-worker caches first. */\n cacheBust: Accessor<boolean>;\n /**\n * Reload onto the announced release. Plain `location.reload()` normally;\n * when the release is flagged cache-bust, CacheStorage is cleared, the\n * service-worker registration is nudged to update, and navigation carries a\n * `?cb=` token to punch through intermediary caches. The service worker is\n * deliberately NOT unregistered: navigating while still controlled by a\n * just-unregistered worker strands subresource fetches on the dead worker\n * and the page hangs until a manual reload.\n */\n reload: () => Promise<void>;\n}\n\nasync function reloadForSnapshot(snapshot: AppReleaseSnapshot): Promise<void> {\n if (typeof window === 'undefined') return;\n if (snapshot.cacheBust) {\n try {\n if (window.caches) {\n const keys = await window.caches.keys();\n await Promise.all(keys.map((k) => window.caches.delete(k)));\n }\n if (navigator.serviceWorker) {\n const regs = await navigator.serviceWorker.getRegistrations();\n for (const r of regs) r.update().catch(() => {});\n }\n window.location.href = window.location.pathname + '?cb=' + Date.now();\n return;\n } catch {\n /* fall through to a plain reload */\n }\n }\n window.location.reload();\n}\n\n/**\n * Observe the app's announced release (`_00_app_release:<app>`, written by\n * `spky deploy` / `spky release`) and compare it against the running build.\n *\n * Typical use: mount a small \"new version available — Reload\" notification\n * gated on `updateAvailable()`, auto-invoking `reload()` when `mandatory()`\n * (guard the auto path against reload loops with a per-version marker, since\n * a client can reload while the deploy is still rolling out and land on the\n * old bundle again).\n */\nexport function useAppRelease(options: UseAppReleaseOptions): UseAppRelease {\n const db = useDb();\n const handle = db.getSp00ky().appRelease(options.app, { ttl: options.ttl });\n\n const [snapshot, setSnapshot] = createSignal<AppReleaseSnapshot>(handle.snapshot());\n const unsub = handle.subscribe(setSnapshot);\n\n onCleanup(() => {\n unsub();\n handle.close();\n });\n\n const updateAvailable = () => semverGt(snapshot().version, options.currentVersion);\n\n return {\n latestVersion: () => snapshot().version,\n updateAvailable,\n mandatory: () => updateAvailable() && snapshot().mandatory,\n cacheBust: () => snapshot().cacheBust,\n reload: () => reloadForSnapshot(snapshot()),\n };\n}\n","import { createSignal, onCleanup } from 'solid-js';\nimport type { SchemaStructure, BucketNames } from '@spooky-sync/query-builder';\nimport { fileToUint8Array } from '@spooky-sync/core';\nimport type { SyncedDb } from '../index';\nimport { useDb } from './context';\n\nexport interface FileUploadResult {\n isUploading: () => boolean;\n error: () => Error | null;\n clearError: () => void;\n upload: (path: string, file: File | Blob) => Promise<void>;\n download: (path: string) => Promise<string | null>;\n remove: (path: string) => Promise<void>;\n exists: (path: string) => Promise<boolean>;\n}\n\nexport function useFileUpload<S extends SchemaStructure>(\n bucketName: BucketNames<S>,\n): FileUploadResult;\nexport function useFileUpload<S extends SchemaStructure>(\n db: SyncedDb<S>,\n bucketName: BucketNames<S>,\n): FileUploadResult;\nexport function useFileUpload<S extends SchemaStructure>(\n dbOrBucketName: SyncedDb<S> | BucketNames<S>,\n maybeBucketName?: BucketNames<S>,\n): FileUploadResult {\n let db: SyncedDb<S>;\n let bucketName: BucketNames<S>;\n\n if (typeof dbOrBucketName === 'string') {\n db = useDb<S>();\n bucketName = dbOrBucketName as BucketNames<S>;\n } else {\n db = dbOrBucketName as SyncedDb<S>;\n // oxlint-disable-next-line no-non-null-assertion\n bucketName = maybeBucketName!;\n }\n\n const [isUploading, setIsUploading] = createSignal(false);\n const [error, setError] = createSignal<Error | null>(null);\n\n const objectUrls: string[] = [];\n onCleanup(() => {\n for (const url of objectUrls) {\n URL.revokeObjectURL(url);\n }\n });\n\n const clearError = () => setError(null);\n\n const validate = (file: File | Blob): void => {\n const config = db.getBucketConfig(bucketName as string);\n if (!config) return;\n\n if (config.maxSize !== null && config.maxSize !== undefined && file.size > config.maxSize) {\n const maxMB = (config.maxSize / (1024 * 1024)).toFixed(1);\n throw new Error(`File exceeds maximum size of ${maxMB} MB.`);\n }\n\n if (config.allowedExtensions && config.allowedExtensions.length > 0) {\n const fileName = (file as File).name;\n if (fileName) {\n const ext = fileName.split('.').pop()?.toLowerCase();\n if (!ext || !config.allowedExtensions.includes(ext)) {\n throw new Error(\n `File type not allowed. Accepted: ${config.allowedExtensions.join(', ')}.`\n );\n }\n }\n }\n };\n\n const upload = async (path: string, file: File | Blob): Promise<void> => {\n setError(null);\n try {\n validate(file);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return;\n }\n\n setIsUploading(true);\n try {\n const bytes = await fileToUint8Array(file);\n await db.bucket(bucketName).put(path, bytes);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n } finally {\n setIsUploading(false);\n }\n };\n\n const download = async (path: string): Promise<string | null> => {\n setError(null);\n try {\n const content = await db.bucket(bucketName).get(path);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n objectUrls.push(objectUrl);\n return objectUrl;\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return null;\n }\n };\n\n const remove = async (path: string): Promise<void> => {\n setError(null);\n try {\n await db.bucket(bucketName).delete(path);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n }\n };\n\n const exists = async (path: string): Promise<boolean> => {\n setError(null);\n try {\n return await db.bucket(bucketName).exists(path);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return false;\n }\n };\n\n return {\n isUploading,\n error,\n clearError,\n upload,\n download,\n remove,\n exists,\n };\n}\n","import { createSignal, createEffect, onCleanup, type Accessor } from 'solid-js';\nimport type { SchemaStructure, BucketNames } from '@spooky-sync/query-builder';\nimport type { SyncedDb } from '../index';\nimport { useDb } from './context';\n\nexport interface UseDownloadFileOptions {\n cache?: boolean;\n}\n\nexport interface UseDownloadFileResult {\n url: Accessor<string | null>;\n isLoading: Accessor<boolean>;\n error: Accessor<Error | null>;\n refetch: () => void;\n}\n\ninterface CacheEntry {\n url: string;\n refCount: number;\n}\n\nconst downloadCache = new Map<string, CacheEntry>();\nconst inflightRequests = new Map<string, Promise<string | null>>();\n\nfunction cacheKey(bucket: string, path: string): string {\n return `${bucket}:${path}`;\n}\n\nfunction releaseEntry(key: string): void {\n const entry = downloadCache.get(key);\n if (!entry) return;\n entry.refCount--;\n if (entry.refCount <= 0) {\n URL.revokeObjectURL(entry.url);\n downloadCache.delete(key);\n }\n}\n\nexport function useDownloadFile<S extends SchemaStructure>(\n bucketName: BucketNames<S>,\n path: Accessor<string | null | undefined>,\n options?: UseDownloadFileOptions,\n): UseDownloadFileResult;\nexport function useDownloadFile<S extends SchemaStructure>(\n db: SyncedDb<S>,\n bucketName: BucketNames<S>,\n path: Accessor<string | null | undefined>,\n options?: UseDownloadFileOptions,\n): UseDownloadFileResult;\nexport function useDownloadFile<S extends SchemaStructure>(\n dbOrBucketName: SyncedDb<S> | BucketNames<S>,\n bucketNameOrPath?: BucketNames<S> | Accessor<string | null | undefined>,\n pathOrOptions?: Accessor<string | null | undefined> | UseDownloadFileOptions,\n maybeOptions?: UseDownloadFileOptions,\n): UseDownloadFileResult {\n let db: SyncedDb<S>;\n let bucketName: BucketNames<S>;\n let path: Accessor<string | null | undefined>;\n let options: UseDownloadFileOptions;\n\n if (typeof dbOrBucketName === 'string') {\n db = useDb<S>();\n bucketName = dbOrBucketName as BucketNames<S>;\n path = bucketNameOrPath as Accessor<string | null | undefined>;\n options = (pathOrOptions as UseDownloadFileOptions) ?? {};\n } else {\n db = dbOrBucketName as SyncedDb<S>;\n bucketName = bucketNameOrPath as BucketNames<S>;\n path = pathOrOptions as Accessor<string | null | undefined>;\n options = maybeOptions ?? {};\n }\n\n const useCache = options.cache !== false;\n\n const [url, setUrl] = createSignal<string | null>(null);\n const [isLoading, setIsLoading] = createSignal(false);\n const [error, setError] = createSignal<Error | null>(null);\n\n let currentKey: string | null = null;\n let privateUrl: string | null = null;\n const [refetchSignal, setRefetchSignal] = createSignal(0);\n const refetchTrigger = () => setRefetchSignal((n) => n + 1);\n\n async function doDownload(key: string, filePath: string): Promise<string | null> {\n if (useCache) {\n // Check cache\n const cached = downloadCache.get(key);\n if (cached) {\n cached.refCount++;\n currentKey = key;\n return cached.url;\n }\n\n // Check inflight\n const inflight = inflightRequests.get(key);\n if (inflight) {\n const result = await inflight;\n if (result) {\n const entry = downloadCache.get(key);\n if (entry) {\n entry.refCount++;\n currentKey = key;\n }\n }\n return result;\n }\n\n // Start new download\n const promise = (async () => {\n const content = await db.bucket(bucketName).get(filePath);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n downloadCache.set(key, { url: objectUrl, refCount: 1 });\n return objectUrl;\n })();\n\n inflightRequests.set(key, promise);\n try {\n const result = await promise;\n currentKey = key;\n return result;\n } finally {\n inflightRequests.delete(key);\n }\n } else {\n // No caching — private URL per instance\n const content = await db.bucket(bucketName).get(filePath);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n privateUrl = objectUrl;\n return objectUrl;\n }\n }\n\n function releaseCurrentEntry() {\n if (useCache && currentKey) {\n releaseEntry(currentKey);\n currentKey = null;\n }\n if (!useCache && privateUrl) {\n URL.revokeObjectURL(privateUrl);\n privateUrl = null;\n }\n }\n\n createEffect(() => {\n const filePath = path();\n // Subscribe to refetch signal so effect re-runs\n refetchSignal();\n\n // Release previous entry\n releaseCurrentEntry();\n\n if (!filePath) {\n setUrl(null);\n setIsLoading(false);\n setError(null);\n return;\n }\n\n const key = cacheKey(bucketName as string, filePath);\n\n // Synchronous cache hit\n if (useCache) {\n const cached = downloadCache.get(key);\n if (cached) {\n cached.refCount++;\n currentKey = key;\n setUrl(cached.url);\n setIsLoading(false);\n setError(null);\n return;\n }\n }\n\n let cancelled = false;\n setIsLoading(true);\n setError(null);\n\n doDownload(key, filePath).then(\n (result) => {\n if (!cancelled) {\n setUrl(result);\n setIsLoading(false);\n }\n return undefined;\n },\n (err) => {\n if (!cancelled) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setIsLoading(false);\n }\n },\n );\n\n onCleanup(() => {\n cancelled = true;\n });\n });\n\n onCleanup(() => {\n releaseCurrentEntry();\n });\n\n const refetch = () => {\n // Evict current entry from cache before re-triggering\n if (useCache && currentKey) {\n const entry = downloadCache.get(currentKey);\n if (entry) {\n URL.revokeObjectURL(entry.url);\n downloadCache.delete(currentKey);\n }\n currentKey = null;\n }\n refetchTrigger();\n };\n\n return { url, isLoading, error, refetch };\n}\n","import type { JSX} from 'solid-js';\nimport { createSignal, onMount, createComponent, createMemo, mergeProps } from 'solid-js';\nimport type { SchemaStructure } from '@spooky/query-builder';\nimport type { SyncedDbConfig } from '../types';\nimport { SyncedDb } from '../index';\nimport { Sp00kyContext } from './context';\n\nexport interface Sp00kyProviderProps<S extends SchemaStructure> {\n config: SyncedDbConfig<S>;\n fallback?: JSX.Element;\n onError?: (error: Error) => void;\n onReady?: (db: SyncedDb<S>) => void;\n /**\n * Prewarm data into the local cache before revealing the UI. Runs after\n * `init()`; the `fallback` stays visible until it resolves. Use awaitable\n * `db.preload(...)` calls here to gate first-load on essential data (e.g.\n * config). On warm loads preload returns instantly, so there's no perceptible\n * gate after the first run. Best-effort: a rejection is caught and the UI is\n * revealed anyway.\n */\n preload?: (db: SyncedDb<S>) => Promise<void>;\n children: JSX.Element;\n}\n\nexport function Sp00kyProvider<S extends SchemaStructure>(\n props: Sp00kyProviderProps<S>\n): JSX.Element {\n const merged = mergeProps(\n {\n fallback: undefined as JSX.Element | undefined,\n },\n props\n );\n\n const [db, setDb] = createSignal<SyncedDb<S> | undefined>(undefined);\n\n onMount(async () => {\n try {\n const instance = new SyncedDb<S>(merged.config);\n await instance.init();\n // Gate first-load UI on prewarmed data. Best-effort: never let a preload\n // failure keep the app stuck on the fallback.\n if (merged.preload) {\n try {\n await merged.preload(instance);\n } catch (e) {\n // oxlint-disable-next-line no-console\n console.error('Sp00kyProvider: preload failed; revealing UI anyway', e);\n }\n }\n setDb(() => instance);\n merged.onReady?.(instance);\n } catch (e) {\n const error = e instanceof Error ? e : new Error(String(e));\n if (merged.onError) {\n merged.onError(error);\n } else {\n // oxlint-disable-next-line no-console\n console.error('Sp00kyProvider: Failed to initialize database', error);\n }\n }\n });\n\n const content = createMemo(() => {\n const instance = db();\n if (!instance) return merged.fallback;\n return createComponent(Sp00kyContext.Provider, {\n value: instance,\n get children() {\n return merged.children;\n },\n });\n });\n\n return content as unknown as JSX.Element;\n}\n","import type { SyncedDbConfig } from './types';\nimport {\n Sp00kyClient,\n type Sp00kyQueryResultPromise,\n type AuthService,\n type BucketHandle,\n type UpdateOptions,\n type RunOptions,\n type SyncHealth,\n type PreloadOptions,\n type PreloadRefresh,\n} from '@spooky-sync/core';\n\nimport type {\n GetTable,\n QueryBuilder,\n SchemaStructure,\n TableModel,\n TableNames,\n QueryResult,\n RelatedFieldsMap,\n RelationshipFieldsFromSchema,\n GetRelationship,\n RelatedFieldMapEntry,\n FinalQuery,\n InnerQuery,\n BackendNames,\n BackendRoutes,\n RoutePayload,\n BucketNames,\n BucketDefinitionSchema,\n QueryModifier,\n QueryModifierBuilder,\n QueryInfo,\n RelationshipsMetadata,\n RelationshipDefinition,\n InferRelatedModelFromMetadata,\n GetCardinality,\n} from '@spooky-sync/query-builder';\n\nimport { RecordId, Uuid, type Surreal } from 'surrealdb';\nexport { RecordId, Uuid };\nexport type { Model, GenericModel, GenericSchema, ModelPayload } from './lib/models';\nexport { useQuery } from './lib/use-query';\nexport { createPreload } from './lib/create-preload';\nexport type { PreloadOptions, PreloadRefresh } from '@spooky-sync/core';\nexport { useSyncStatus, type UseSyncStatus } from './lib/use-sync-status';\nexport type { SyncHealth, SyncHealthStatus, SyncHealthConfig } from '@spooky-sync/core';\nexport { useCrdtField } from './lib/use-crdt-field';\nexport { useFeatureFlag, type UseFeatureFlag } from './lib/use-feature-flag';\nexport {\n useAppRelease,\n type UseAppRelease,\n type UseAppReleaseOptions,\n} from './lib/use-app-release';\nexport { useFileUpload, type FileUploadResult } from './lib/use-file-upload';\nexport { useDownloadFile, type UseDownloadFileOptions, type UseDownloadFileResult } from './lib/use-download-file';\nexport { Sp00kyProvider, type Sp00kyProviderProps } from './lib/Sp00kyProvider';\nexport { useDb } from './lib/context';\n\n// export { AuthEventTypes } from \"@spooky-sync/core\"; // TODO: Verify if AuthEventTypes exists in core\n\n\n// Re-export query builder types for convenience\nexport type {\n QueryModifier,\n QueryModifierBuilder,\n QueryInfo,\n RelationshipsMetadata,\n RelationshipDefinition,\n InferRelatedModelFromMetadata,\n GetCardinality,\n GetTable,\n TableModel,\n TableNames,\n QueryResult,\n};\n\nexport type RelationshipField<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n Field extends RelationshipFieldsFromSchema<Schema, TableName>,\n> = GetRelationship<Schema, TableName, Field>;\n\nexport type RelatedFieldsTableScoped<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n RelatedFields extends RelationshipFieldsFromSchema<Schema, TableName> =\n RelationshipFieldsFromSchema<Schema, TableName>,\n> = {\n [K in RelatedFields]: {\n to: RelationshipField<Schema, TableName, K>['to'];\n relatedFields: RelatedFieldsMap;\n cardinality: RelationshipField<Schema, TableName, K>['cardinality'];\n };\n};\n\nexport type InferModel<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n RelatedFields extends RelatedFieldsTableScoped<Schema, TableName>,\n> = QueryResult<Schema, TableName, RelatedFields, true>;\n\nexport type WithRelated<Field extends string, RelatedFields extends RelatedFieldsMap = {}> = {\n [K in Field]: Omit<RelatedFieldMapEntry, 'relatedFields'> & {\n relatedFields: RelatedFields;\n };\n};\n\nexport type WithRelatedMany<Field extends string, RelatedFields extends RelatedFieldsMap = {}> = {\n [K in Field]: {\n to: Field;\n relatedFields: RelatedFields;\n cardinality: 'many';\n };\n};\n\n/**\n * SyncedDb - A thin wrapper around sp00ky-ts for Solid.js integration\n * Delegates all logic to the underlying sp00ky-ts instance\n */\nexport class SyncedDb<S extends SchemaStructure> {\n private config: SyncedDbConfig<S>;\n private sp00ky: Sp00kyClient<S> | null = null;\n private _initialized = false;\n\n constructor(config: SyncedDbConfig<S>) {\n this.config = config;\n }\n\n public getSp00ky(): Sp00kyClient<S> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky;\n }\n\n /**\n * Initialize the sp00ky-ts instance\n */\n async init(): Promise<void> {\n if (this._initialized) return;\n this.sp00ky = new Sp00kyClient<S>(this.config);\n await this.sp00ky.init();\n this._initialized = true;\n }\n\n /**\n * Create a new record in the database\n */\n async create(id: string, payload: Record<string, unknown>): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.create(id, payload as Record<string, unknown>);\n }\n\n /**\n * Update an existing record in the database\n */\n async update<TName extends TableNames<S>>(\n tableName: TName,\n recordId: string,\n payload: Partial<TableModel<GetTable<S, TName>>>,\n options?: UpdateOptions\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.update(\n tableName as string,\n recordId,\n payload as Record<string, unknown>,\n options\n );\n }\n\n /**\n * Delete an existing record in the database\n */\n async delete<TName extends TableNames<S>>(\n tableName: TName,\n selector: string | RecordId | InnerQuery<GetTable<S, TName>, boolean>\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n // Accept a `\"table:id\"` string OR a RecordId — live-query rows carry their\n // `id` as a RecordId, so callers can pass `db.delete('game', row.id)`\n // directly. Build the canonical string from the raw id part (not\n // `RecordId.toString()`, which escapes special chars) so it round-trips\n // through the engine's `parseRecordIdString`. InnerQuery selectors are not\n // supported yet. (cross-package RecordId instances → match by constructor name.)\n const isRecordId =\n selector instanceof RecordId || (selector as any)?.constructor?.name === 'RecordId';\n let id: string;\n if (typeof selector === 'string') {\n id = selector;\n } else if (isRecordId) {\n id = `${tableName as string}:${(selector as RecordId).id}`;\n } else {\n throw new Error('Only string ID or RecordId selectors are supported currently with core');\n }\n await this.sp00ky.delete(tableName as string, id);\n }\n\n /**\n * Preload/prewarm a built query into the local cache without registering a\n * live view. Fetches once and stores the rows (+ embedded related children)\n * locally so a later `useQuery` for the same data paints instantly. Best-effort.\n */\n public async preload(\n finalQuery: FinalQuery<S, any, any, any, any, Sp00kyQueryResultPromise>,\n options?: PreloadOptions\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.preload(finalQuery, options);\n }\n\n /**\n * Query data from the database\n */\n public query<TName extends TableNames<S>>(\n table: TName\n ): QueryBuilder<S, TName, Sp00kyQueryResultPromise, {}, false> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.query(table, {});\n }\n\n /**\n * Run a backend operation\n */\n public async run<\n B extends BackendNames<S>,\n R extends BackendRoutes<S, B>,\n >(\n backend: B,\n path: R,\n payload: RoutePayload<S, B, R>,\n options?: RunOptions,\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.run(backend, path, payload, options);\n }\n\n /**\n * Authenticate with the database\n */\n public async authenticate(token: string): Promise<RecordId<string>> {\n await this.sp00ky?.authenticate(token);\n // Sp00kyClient.authenticate returns whatever remote.authenticate returns (boolean or token usually?)\n // Wait, checked Sp00kyClient: return this.remote.getClient().authenticate(token);\n // SurrealDB authenticate returns void? or token?\n // Assuming void or token.\n return new RecordId('user', 'me'); // Placeholder or actual?\n }\n\n /**\n * Deauthenticate from the database\n * @deprecated Use signOut() instead\n */\n public async deauthenticate(): Promise<void> {\n await this.signOut();\n }\n\n /**\n * Sign out, clear session and local storage\n */\n public async signOut(): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.auth.signOut();\n }\n\n /**\n * Execute a function with direct access to the remote database connection\n */\n public async useRemote<T>(fn: (db: Surreal) => T | Promise<T>): Promise<T> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return await this.sp00ky.useRemote(fn);\n }\n /**\n * Access the remote database service directly\n */\n get remote(): Sp00kyClient<S>['remoteClient'] {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.remoteClient;\n }\n\n /**\n * Access the local database service directly\n */\n get local(): Sp00kyClient<S>['localClient'] {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.localClient;\n }\n\n /**\n * Access the auth service\n */\n get auth(): AuthService<S> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.auth;\n }\n\n get pendingMutationCount(): number {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.pendingMutationCount;\n }\n\n /** Diagnostic — see `Sp00kyClient.liveRetryCount`. */\n get liveRetryCount(): number {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.liveRetryCount;\n }\n\n subscribeToPendingMutations(cb: (count: number) => void): () => void {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.subscribeToPendingMutations(cb);\n }\n\n /** Current sync-health snapshot. See {@link useSyncStatus}. */\n get syncHealth(): SyncHealth {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.syncHealth;\n }\n\n /**\n * Observe sync health. Fires immediately with the current status and again\n * on every healthy↔degraded transition. Prefer the `useSyncStatus` hook in\n * components; this is the imperative escape hatch.\n */\n subscribeToSyncHealth(cb: (health: SyncHealth) => void): () => void {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.subscribeToSyncHealth(cb);\n }\n\n bucket<B extends BucketNames<S>>(name: B): BucketHandle {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.bucket(name);\n }\n\n getBucketConfig(name: string): BucketDefinitionSchema | undefined {\n return this.config.schema.buckets?.find((b) => b.name === name);\n }\n}\n\nexport * from './types';\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["Sp00kyClient","RecordId"],"sources":["../src/lib/context.ts","../src/lib/use-query.ts","../src/lib/create-preload.ts","../src/lib/use-sync-status.ts","../src/lib/use-storage-status.ts","../src/lib/use-crdt-field.ts","../src/lib/use-feature-flag.ts","../src/lib/use-app-release.ts","../src/lib/use-file-upload.ts","../src/lib/use-download-file.ts","../src/lib/Sp00kyProvider.ts","../src/index.ts"],"sourcesContent":["import { createContext, useContext } from 'solid-js';\nimport type { SchemaStructure } from '@spooky/query-builder';\nimport type { SyncedDb } from '../index';\n\nexport const Sp00kyContext = createContext<SyncedDb<any> | undefined>();\n\nexport function useDb<S extends SchemaStructure>(): SyncedDb<S> {\n const db = useContext(Sp00kyContext);\n if (!db) {\n throw new Error('useDb must be used within a <Sp00kyProvider>. Wrap your app in <Sp00kyProvider config={...}>.');\n }\n return db as SyncedDb<S>;\n}\n","import type {\n ColumnSchema,\n FinalQuery,\n SchemaStructure,\n TableNames,\n QueryResult,\n} from '@spooky-sync/query-builder';\nimport { createEffect, createSignal, onCleanup, useContext } from 'solid-js';\nimport { createStore, reconcile } from 'solid-js/store';\nimport { SyncedDb } from '..';\nimport type { Sp00kyQueryResultPromise } from '@spooky-sync/core';\nimport { Sp00kyContext } from './context';\n\ntype QueryArg<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n> =\n | FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>\n | (() =>\n | FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>\n | null\n | undefined);\n\ntype QueryOptions = {\n enabled?: () => boolean;\n /**\n * Tear down the query (remote `_00_query` view + local WASM view) when this\n * hook is disposed and no other subscriber remains, instead of keeping it\n * resident for cheap re-subscription. Use for viewport-windowed lists that\n * mount/unmount a query per scroll window and want off-screen windows\n * cancelled. Trade-off: scrolling back to a torn-down window re-registers it.\n */\n deregisterOnCleanup?: boolean;\n};\n\n// Overload: context-based (no explicit db)\nexport function useQuery<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n TData = QueryResult<S, TableName, RelatedFields, IsOne> | null,\n>(\n finalQuery: QueryArg<S, TableName, T, RelatedFields, IsOne>,\n options?: QueryOptions,\n): {\n data: () => TData | undefined;\n error: () => Error | undefined;\n isLoading: () => boolean;\n isFetching: () => boolean;\n isSettled: () => boolean;\n};\n\n// Overload: explicit db (backward-compatible)\nexport function useQuery<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n TData = QueryResult<S, TableName, RelatedFields, IsOne> | null,\n>(\n db: SyncedDb<S>,\n finalQuery: QueryArg<S, TableName, T, RelatedFields, IsOne>,\n options?: QueryOptions,\n): {\n data: () => TData | undefined;\n error: () => Error | undefined;\n isLoading: () => boolean;\n isFetching: () => boolean;\n isSettled: () => boolean;\n};\n\n// Implementation\nexport function useQuery<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends {\n columns: Record<string, ColumnSchema>;\n },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n TData = QueryResult<S, TableName, RelatedFields, IsOne> | null,\n>(\n dbOrQuery:\n | SyncedDb<S>\n | QueryArg<S, TableName, T, RelatedFields, IsOne>,\n queryOrOptions?:\n | QueryArg<S, TableName, T, RelatedFields, IsOne>\n | QueryOptions,\n maybeOptions?: QueryOptions,\n) {\n let db: SyncedDb<S>;\n let finalQuery: QueryArg<S, TableName, T, RelatedFields, IsOne>;\n let options: QueryOptions | undefined;\n\n if (dbOrQuery instanceof SyncedDb) {\n // Explicit db overload: useQuery(db, query, options?)\n db = dbOrQuery;\n finalQuery = queryOrOptions as QueryArg<S, TableName, T, RelatedFields, IsOne>;\n options = maybeOptions;\n } else {\n // Context-based overload: useQuery(query, options?)\n const contextDb = useContext(Sp00kyContext);\n if (!contextDb) {\n throw new Error(\n 'useQuery: No db argument provided and no Sp00kyContext found. ' +\n 'Either pass a SyncedDb instance or wrap your app in <Sp00kyProvider>.'\n );\n }\n db = contextDb as SyncedDb<S>;\n finalQuery = dbOrQuery;\n options = queryOrOptions as QueryOptions | undefined;\n }\n\n const [error, setError] = createSignal<Error | undefined>(undefined);\n const [isFetched, setIsFetched] = createSignal(false);\n const [isFetching, setIsFetching] = createSignal(false);\n // Results live in a store (not a signal) so consecutive live-query emissions\n // are merged with `reconcile`: unchanged rows keep their object identity and\n // changed rows are mutated in place. That keeps Solid's reference-keyed `<For>`\n // rows — and any `useQuery` subscriptions mounted inside them — alive across\n // updates, instead of tearing every row down and re-registering its queries.\n const [state, setState] = createStore<{ value: TData | undefined }>({ value: undefined });\n // `reconcile` (below) merges each emission into `state.value` IN PLACE, keeping\n // the array reference stable. That's ideal for granular per-row reactivity, but\n // it means a *coarse* reader of `data()` — `<For each={data()}>`, or an effect\n // that copies the whole array elsewhere (e.g. GameList's windowed store) — is\n // NOT re-run when rows are added/removed/reordered within a same-length result\n // (the classic case: deleting a row in a windowed list shifts the next one in,\n // so length stays 50 and the array ref never changes). Bump a version on every\n // emission and read it in `data()` so every consumer re-runs on any change while\n // reconcile still preserves row identity underneath.\n const [version, setVersion] = createSignal(0);\n const data = () => {\n version();\n return state.value;\n };\n\n let prevQueryString: string | undefined;\n // Monotonic token for each subscription generation. Bumped whenever the query\n // identity changes or the hook is disposed, so a slow async `initQuery`\n // continuation can detect it was superseded and avoid installing a stale (and\n // leaked) subscription.\n let runId = 0;\n let activeUnsub: (() => void) | undefined;\n // The hash of the currently-installed subscription, for opt-in deregister on\n // dispose (see `deregisterOnCleanup`).\n let activeHash: string | undefined;\n\n const teardownActive = () => {\n activeUnsub?.();\n activeUnsub = undefined;\n };\n\n const sp00ky = db.getSp00ky();\n\n const initQuery = async (\n query: FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>,\n myRun: number\n ) => {\n const { hash } = await query.run();\n // A newer query identity (or disposal) won the race while we awaited run().\n if (myRun !== runId) return;\n activeHash = hash;\n setError(undefined);\n\n let isFirstCall = true;\n const unsub = await sp00ky.subscribe(\n hash,\n (e) => {\n const queryData = (query.isOne ? e[0] : e) as TData;\n // Merge into the store by record id: unchanged rows keep their identity,\n // changed rows update in place. Replaces wholesale for `one()`/null.\n // Time the reconcile → report as the \"frontend\" phase for DevTools/MCP.\n const reconcileStart = performance.now();\n setState('value', reconcile(queryData as any, { key: 'id' }));\n // Notify coarse `data()` readers (see the `version` note above): reconcile\n // keeps the array ref stable, so this is what re-runs `<For>`/copy-effects\n // on add/remove/reorder.\n setVersion((v) => v + 1);\n sp00ky.reportFrontendTiming(hash, performance.now() - reconcileStart);\n // The first (immediate) callback with no data likely means the local DB\n // hasn't synced yet — don't mark as fetched so UI shows loading state\n const hasData = query.isOne ? queryData !== null && queryData !== undefined : (e as any[]).length > 0;\n if (!isFirstCall || hasData) {\n setIsFetched(true);\n }\n isFirstCall = false;\n },\n { immediate: true }\n );\n\n // Mirror the query's fetch status so the UI can show a \"loading more\"\n // state while the sync engine pulls missing records in the background.\n const unsubStatus = sp00ky.subscribeQueryStatus(\n hash,\n (status) => setIsFetching(status === 'fetching'),\n { immediate: true }\n );\n\n const teardown = () => {\n unsub();\n unsubStatus();\n };\n\n // Superseded while awaiting subscribe()? Don't leak — tear down immediately.\n if (myRun !== runId) {\n teardown();\n return;\n }\n activeUnsub = teardown;\n };\n\n createEffect(() => {\n const enabled = options?.enabled?.() ?? true;\n\n // If disabled, clear error and don't run query\n if (!enabled) {\n setError(undefined);\n return;\n }\n\n // Init Query\n const query = typeof finalQuery === 'function' ? finalQuery() : finalQuery;\n if (!query) {\n return;\n }\n\n // Dedup on the query's stable identity hash (cyrb53 of surql + vars), not a\n // full `JSON.stringify` of the FinalQuery (which walks the whole schema +\n // inner query on every reactive tick and isn't guaranteed stable). When the\n // identity is unchanged we keep the existing subscription alive.\n const queryString = String(query.hash);\n if (queryString === prevQueryString) {\n return;\n }\n prevQueryString = queryString;\n\n // New query identity → supersede the previous subscription and start fresh.\n const myRun = ++runId;\n teardownActive();\n setIsFetched(false);\n initQuery(query, myRun);\n });\n\n // Tear down the live subscription when the hook's owner is disposed. Registered\n // on the hook (component) scope rather than inside the effect, so an effect\n // re-run that early-returns (unchanged query) doesn't clean up the still-valid\n // subscription. Bumping runId also invalidates any in-flight initQuery.\n onCleanup(() => {\n runId++;\n teardownActive();\n // Opt-in: cancel the query once this hook (its last subscriber) is gone.\n // teardownActive() above already removed this hook's callback, so\n // deregisterQuery's refcount guard sees the true remaining-subscriber count.\n if (options?.deregisterOnCleanup && activeHash) {\n sp00ky.deregisterQuery(activeHash);\n }\n });\n\n const isLoading = () => {\n return !isFetched() && error() === undefined;\n };\n\n // True once the query has delivered a result AND no fetch cycle is in flight\n // (registration + initial sync included — the core holds `fetching` across\n // the whole registration and flushes debounced results before flipping back\n // to idle). While settled, the results are authoritative: a windowed query\n // returning fewer rows than its LIMIT really is the end of the list, so\n // virtualized lists may size themselves to it without the scrollbar jumping\n // when a still-syncing window transiently reports short. Resets to false\n // whenever the query identity changes.\n const isSettled = () => isFetched() && !isFetching();\n\n return {\n data,\n error,\n isLoading,\n isFetching,\n isSettled,\n };\n}\n","import type {\n ColumnSchema,\n FinalQuery,\n SchemaStructure,\n TableNames,\n} from '@spooky-sync/query-builder';\nimport { createEffect, useContext } from 'solid-js';\nimport { SyncedDb } from '..';\nimport type { Sp00kyQueryResultPromise, PreloadOptions as CorePreloadOptions } from '@spooky-sync/core';\nimport { Sp00kyContext } from './context';\n\ntype PreloadArg<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n> =\n | FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>\n | (() =>\n | FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>\n | null\n | undefined);\n\ntype PreloadOptions = CorePreloadOptions & {\n /** Only preload while this returns true (defaults to always). */\n enabled?: () => boolean;\n};\n\n// Overload: context-based (no explicit db)\nexport function createPreload<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n>(\n finalQuery: PreloadArg<S, TableName, T, RelatedFields, IsOne>,\n options?: PreloadOptions,\n): void;\n\n// Overload: explicit db\nexport function createPreload<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n>(\n db: SyncedDb<S>,\n finalQuery: PreloadArg<S, TableName, T, RelatedFields, IsOne>,\n options?: PreloadOptions,\n): void;\n\n/**\n * Reactive, fire-and-forget prewarm. Resolves the query (calling it if it's a\n * function so it tracks reactive deps), dedupes on the query's stable identity\n * hash, and warms it into the local cache via `db.preload`. No subscription and\n * no cleanup: preload registers nothing that needs tearing down.\n *\n * Typical use: inside a list row, preload the detail query the user is likely\n * to open next, so navigation paints from cache instead of the network.\n */\nexport function createPreload<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n>(\n dbOrQuery: SyncedDb<S> | PreloadArg<S, TableName, T, RelatedFields, IsOne>,\n queryOrOptions?: PreloadArg<S, TableName, T, RelatedFields, IsOne> | PreloadOptions,\n maybeOptions?: PreloadOptions,\n): void {\n let db: SyncedDb<S>;\n let finalQuery: PreloadArg<S, TableName, T, RelatedFields, IsOne>;\n let options: PreloadOptions | undefined;\n\n if (dbOrQuery instanceof SyncedDb) {\n db = dbOrQuery;\n finalQuery = queryOrOptions as PreloadArg<S, TableName, T, RelatedFields, IsOne>;\n options = maybeOptions;\n } else {\n const contextDb = useContext(Sp00kyContext);\n if (!contextDb) {\n throw new Error(\n 'createPreload: No db argument provided and no Sp00kyContext found. ' +\n 'Either pass a SyncedDb instance or wrap your app in <Sp00kyProvider>.',\n );\n }\n db = contextDb as SyncedDb<S>;\n finalQuery = dbOrQuery;\n options = queryOrOptions as PreloadOptions | undefined;\n }\n\n let prevHash: number | undefined;\n\n createEffect(() => {\n if (!(options?.enabled?.() ?? true)) return;\n\n const query = typeof finalQuery === 'function' ? finalQuery() : finalQuery;\n if (!query) return;\n\n // Dedupe on the query's stable identity hash so a reactive re-run with an\n // unchanged query doesn't refetch (the core also dedupes per session).\n if (query.hash === prevHash) return;\n prevHash = query.hash;\n\n void db.getSp00ky().preload(query, { refresh: options?.refresh, staleTime: options?.staleTime });\n });\n}\n","import { createSignal, onCleanup, type Accessor } from 'solid-js';\nimport { useDb } from './context';\nimport type { SyncHealth, SyncHealthStatus } from '@spooky-sync/core';\n\nexport interface UseSyncStatus {\n /** Full health snapshot; updates reactively on every transition. */\n health: Accessor<SyncHealth>;\n /** `'healthy'` | `'degraded'`. */\n status: Accessor<SyncHealthStatus>;\n isHealthy: Accessor<boolean>;\n /** `true` once sync has failed for a sustained run — drive a banner off this. */\n isDegraded: Accessor<boolean>;\n /** `true` once at least one sync round has succeeded this session. */\n everConnected: Accessor<boolean>;\n /**\n * `true` only for a real lost connection: degraded AFTER a first successful\n * sync. Stays `false` during the initial \"connecting\" phase (degraded but\n * never reached the server yet), so an indicator can show nothing until the\n * app has actually connected once.\n */\n isOffline: Accessor<boolean>;\n}\n\n/**\n * Observe sync health for a \"can't reach the server\" banner / indicator.\n *\n * Backed by `db.subscribeToSyncHealth`. Individual sync failures (a transient\n * remote 500 on query registration, a dropped socket) are absorbed by the\n * retry and never flip this; `isDegraded()` only goes true once failures\n * persist for the configured number of consecutive rounds (sp00ky core config\n * `syncHealth.degradeAfterConsecutiveFailures`, default 3), and flips back on\n * the next successful round. Must be used within a `<Sp00kyProvider>`.\n */\nexport function useSyncStatus(): UseSyncStatus {\n const db = useDb();\n // subscribeToSyncHealth fires synchronously with the current status, so the\n // signal is correct from first read; the initial value just avoids a flash.\n const [health, setHealth] = createSignal<SyncHealth>(db.syncHealth);\n const unsub = db.subscribeToSyncHealth(setHealth);\n onCleanup(unsub);\n\n return {\n health,\n status: () => health().status,\n isHealthy: () => health().status === 'healthy',\n isDegraded: () => health().status === 'degraded',\n everConnected: () => health().everConnected,\n isOffline: () => health().status === 'degraded' && health().everConnected,\n };\n}\n","import { createSignal, onCleanup, type Accessor } from 'solid-js';\nimport { useDb } from './context';\nimport type { StorageHealth, StorageHealthStatus } from '@spooky-sync/core';\n\nexport interface UseStorageStatus {\n /** Full durability snapshot; updates reactively. */\n health: Accessor<StorageHealth>;\n /** `'unknown'` | `'persistent'` | `'memory'`. */\n status: Accessor<StorageHealthStatus>;\n /** `true` when the local store survives a reload. */\n isPersistent: Accessor<boolean>;\n /**\n * `true` only when durable storage was requested and could NOT be opened, so\n * the dataset is sitting in RAM and local writes die on reload. Drive a\n * warning off this, not off `status`: a store configured as in-memory reports\n * `'memory'` too, and that is a choice rather than a problem.\n */\n isMemoryFallback: Accessor<boolean>;\n}\n\n/**\n * Observe how durable the LOCAL cache is, for a \"no local storage\" warning.\n *\n * Under `localEngine: 'sqlite'` with `store: 'indexeddb'` the durable store is\n * the OPFS SAHPool VFS, and only ONE client per bucket can hold it open: a\n * second tab of the same app cannot get it and runs in memory instead (the\n * engine retries first, so a closing tab's lock is usually waited out). Must be\n * used within a `<Sp00kyProvider>`.\n */\nexport function useStorageStatus(): UseStorageStatus {\n const db = useDb();\n // subscribeToStorageHealth fires synchronously with the current snapshot, so\n // the signal is correct from the first read; the initial value avoids a flash.\n const [health, setHealth] = createSignal<StorageHealth>(db.storageHealth);\n const unsub = db.subscribeToStorageHealth(setHealth);\n onCleanup(unsub);\n\n return {\n health,\n status: () => health().status,\n isPersistent: () => health().status === 'persistent',\n isMemoryFallback: () => health().fallback,\n };\n}\n","import { createEffect, createSignal, onCleanup, useContext, type Accessor } from 'solid-js';\nimport { Sp00kyContext } from './context';\nimport type { CrdtField } from '@spooky-sync/core';\n\nexport function useCrdtField(\n table: string,\n recordId: () => string | undefined,\n field: string,\n fallbackText?: () => string | undefined,\n): Accessor<CrdtField | null> {\n const db = useContext(Sp00kyContext);\n if (!db) {\n throw new Error('useCrdtField must be used within a <Sp00kyProvider>');\n }\n\n const [crdtField, setCrdtField] = createSignal<CrdtField | null>(null);\n let currentId: string | undefined;\n let initialized = false;\n\n createEffect(() => {\n const id = recordId();\n\n // Skip if the ID hasn't changed (but allow the first non-undefined value through)\n if (initialized && id === currentId) return;\n\n // Close previous field\n if (currentId && crdtField()) {\n db.getSp00ky().closeCrdtField(table, currentId, field);\n setCrdtField(null);\n }\n\n currentId = id;\n initialized = true;\n\n if (!id) return;\n\n const sp00ky = db.getSp00ky();\n const text = fallbackText?.();\n sp00ky\n .openCrdtField(table, id, field, text)\n .then((cf) => {\n if (currentId === id) {\n setCrdtField(cf);\n }\n })\n .catch((err) => {\n // Silent rejections here leave the consumer's `Show when={field()}`\n // permanently stuck on its fallback (typically a static `<p>` with\n // no editing UI), with no error trail. Surface the failure so the\n // root cause (missing `@crdt` annotation, schema codegen drift,\n // local DB query failure, etc.) is visible in the console instead\n // of silently breaking collaborative fields.\n console.error(\n `[useCrdtField] Failed to open CRDT field ${table}.${field} on ${id}:`,\n err,\n );\n });\n });\n\n onCleanup(() => {\n if (currentId && crdtField()) {\n db.getSp00ky().closeCrdtField(table, currentId, field);\n setCrdtField(null);\n }\n });\n\n return crdtField;\n}\n","import { createSignal, onCleanup, type Accessor } from 'solid-js';\nimport { useDb } from './context';\nimport type { FeatureFlagOptions } from '@spooky-sync/core';\n\nexport interface UseFeatureFlag {\n variant: Accessor<string | undefined>;\n payload: Accessor<unknown | undefined>;\n enabled: Accessor<boolean>;\n}\n\n/**\n * Subscribe to a feature flag for the currently authenticated user.\n *\n * Returns three Solid accessors that update reactively whenever the\n * server-materialized assignment in `_00_user_feature` changes. Backed by\n * the same SSP + sync pipeline that powers `useQuery`, so toggling a flag\n * via `spky flag enable <key>` propagates to the UI without a refresh.\n *\n * `enabled()` is `true` when the resolved variant exists and is not 'off'.\n * For multi-variant flags, prefer `variant()` directly.\n */\nexport function useFeatureFlag(\n key: string,\n options?: FeatureFlagOptions,\n): UseFeatureFlag {\n const db = useDb();\n const handle = db.getSp00ky().feature(key, options);\n\n const [variant, setVariant] = createSignal<string | undefined>(handle.variant());\n const [payload, setPayload] = createSignal<unknown | undefined>(handle.payload());\n\n const unsub = handle.subscribe((s) => {\n setVariant(s.variant ?? options?.fallback);\n setPayload(s.payload);\n });\n\n onCleanup(() => {\n unsub();\n handle.close();\n });\n\n return {\n variant,\n payload,\n enabled: () => {\n const v = variant();\n return v !== undefined && v !== 'off';\n },\n };\n}\n","import { createSignal, onCleanup, type Accessor } from 'solid-js';\nimport { useDb } from './context';\nimport { semverGt, type AppReleaseOptions, type AppReleaseSnapshot } from '@spooky-sync/core';\n\nexport interface UseAppReleaseOptions extends AppReleaseOptions {\n /** App name from sp00ky.yml, e.g. `web`. */\n app: string;\n /**\n * The running build's version (X.Y.Z), typically baked in at build time\n * (e.g. a vite `define` from package.json). `updateAvailable()` is true when\n * the announced release is semver-newer than this.\n */\n currentVersion: string;\n}\n\nexport interface UseAppRelease {\n /** Latest announced version for the app, or undefined when no row exists. */\n latestVersion: Accessor<string | undefined>;\n /** Announced version is semver-newer than the running build. */\n updateAvailable: Accessor<boolean>;\n /** The newer release asks clients to update/reload without prompting. */\n mandatory: Accessor<boolean>;\n /** The newer release asks reloads to clear service-worker caches first. */\n cacheBust: Accessor<boolean>;\n /**\n * Reload onto the announced release. Plain `location.reload()` normally;\n * when the release is flagged cache-bust, CacheStorage is cleared, the\n * service-worker registration is nudged to update, and navigation carries a\n * `?cb=` token to punch through intermediary caches. The service worker is\n * deliberately NOT unregistered: navigating while still controlled by a\n * just-unregistered worker strands subresource fetches on the dead worker\n * and the page hangs until a manual reload.\n */\n reload: () => Promise<void>;\n}\n\nasync function reloadForSnapshot(snapshot: AppReleaseSnapshot): Promise<void> {\n if (typeof window === 'undefined') return;\n if (snapshot.cacheBust) {\n try {\n if (window.caches) {\n const keys = await window.caches.keys();\n await Promise.all(keys.map((k) => window.caches.delete(k)));\n }\n if (navigator.serviceWorker) {\n const regs = await navigator.serviceWorker.getRegistrations();\n for (const r of regs) r.update().catch(() => {});\n }\n window.location.href = window.location.pathname + '?cb=' + Date.now();\n return;\n } catch {\n /* fall through to a plain reload */\n }\n }\n window.location.reload();\n}\n\n/**\n * Observe the app's announced release (`_00_app_release:<app>`, written by\n * `spky deploy` / `spky release`) and compare it against the running build.\n *\n * Typical use: mount a small \"new version available — Reload\" notification\n * gated on `updateAvailable()`, auto-invoking `reload()` when `mandatory()`\n * (guard the auto path against reload loops with a per-version marker, since\n * a client can reload while the deploy is still rolling out and land on the\n * old bundle again).\n */\nexport function useAppRelease(options: UseAppReleaseOptions): UseAppRelease {\n const db = useDb();\n const handle = db.getSp00ky().appRelease(options.app, { ttl: options.ttl });\n\n const [snapshot, setSnapshot] = createSignal<AppReleaseSnapshot>(handle.snapshot());\n const unsub = handle.subscribe(setSnapshot);\n\n onCleanup(() => {\n unsub();\n handle.close();\n });\n\n const updateAvailable = () => semverGt(snapshot().version, options.currentVersion);\n\n return {\n latestVersion: () => snapshot().version,\n updateAvailable,\n mandatory: () => updateAvailable() && snapshot().mandatory,\n cacheBust: () => snapshot().cacheBust,\n reload: () => reloadForSnapshot(snapshot()),\n };\n}\n","import { createSignal, onCleanup } from 'solid-js';\nimport type { SchemaStructure, BucketNames } from '@spooky-sync/query-builder';\nimport { fileToUint8Array } from '@spooky-sync/core';\nimport type { SyncedDb } from '../index';\nimport { useDb } from './context';\n\nexport interface FileUploadResult {\n isUploading: () => boolean;\n error: () => Error | null;\n clearError: () => void;\n upload: (path: string, file: File | Blob) => Promise<void>;\n download: (path: string) => Promise<string | null>;\n remove: (path: string) => Promise<void>;\n exists: (path: string) => Promise<boolean>;\n}\n\nexport function useFileUpload<S extends SchemaStructure>(\n bucketName: BucketNames<S>,\n): FileUploadResult;\nexport function useFileUpload<S extends SchemaStructure>(\n db: SyncedDb<S>,\n bucketName: BucketNames<S>,\n): FileUploadResult;\nexport function useFileUpload<S extends SchemaStructure>(\n dbOrBucketName: SyncedDb<S> | BucketNames<S>,\n maybeBucketName?: BucketNames<S>,\n): FileUploadResult {\n let db: SyncedDb<S>;\n let bucketName: BucketNames<S>;\n\n if (typeof dbOrBucketName === 'string') {\n db = useDb<S>();\n bucketName = dbOrBucketName as BucketNames<S>;\n } else {\n db = dbOrBucketName as SyncedDb<S>;\n // oxlint-disable-next-line no-non-null-assertion\n bucketName = maybeBucketName!;\n }\n\n const [isUploading, setIsUploading] = createSignal(false);\n const [error, setError] = createSignal<Error | null>(null);\n\n const objectUrls: string[] = [];\n onCleanup(() => {\n for (const url of objectUrls) {\n URL.revokeObjectURL(url);\n }\n });\n\n const clearError = () => setError(null);\n\n const validate = (file: File | Blob): void => {\n const config = db.getBucketConfig(bucketName as string);\n if (!config) return;\n\n if (config.maxSize !== null && config.maxSize !== undefined && file.size > config.maxSize) {\n const maxMB = (config.maxSize / (1024 * 1024)).toFixed(1);\n throw new Error(`File exceeds maximum size of ${maxMB} MB.`);\n }\n\n if (config.allowedExtensions && config.allowedExtensions.length > 0) {\n const fileName = (file as File).name;\n if (fileName) {\n const ext = fileName.split('.').pop()?.toLowerCase();\n if (!ext || !config.allowedExtensions.includes(ext)) {\n throw new Error(\n `File type not allowed. Accepted: ${config.allowedExtensions.join(', ')}.`\n );\n }\n }\n }\n };\n\n const upload = async (path: string, file: File | Blob): Promise<void> => {\n setError(null);\n try {\n validate(file);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return;\n }\n\n setIsUploading(true);\n try {\n const bytes = await fileToUint8Array(file);\n await db.bucket(bucketName).put(path, bytes);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n } finally {\n setIsUploading(false);\n }\n };\n\n const download = async (path: string): Promise<string | null> => {\n setError(null);\n try {\n const content = await db.bucket(bucketName).get(path);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n objectUrls.push(objectUrl);\n return objectUrl;\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return null;\n }\n };\n\n const remove = async (path: string): Promise<void> => {\n setError(null);\n try {\n await db.bucket(bucketName).delete(path);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n }\n };\n\n const exists = async (path: string): Promise<boolean> => {\n setError(null);\n try {\n return await db.bucket(bucketName).exists(path);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return false;\n }\n };\n\n return {\n isUploading,\n error,\n clearError,\n upload,\n download,\n remove,\n exists,\n };\n}\n","import { createSignal, createEffect, onCleanup, type Accessor } from 'solid-js';\nimport type { SchemaStructure, BucketNames } from '@spooky-sync/query-builder';\nimport type { SyncedDb } from '../index';\nimport { useDb } from './context';\n\nexport interface UseDownloadFileOptions {\n cache?: boolean;\n}\n\nexport interface UseDownloadFileResult {\n url: Accessor<string | null>;\n isLoading: Accessor<boolean>;\n error: Accessor<Error | null>;\n refetch: () => void;\n}\n\ninterface CacheEntry {\n url: string;\n refCount: number;\n}\n\nconst downloadCache = new Map<string, CacheEntry>();\nconst inflightRequests = new Map<string, Promise<string | null>>();\n\nfunction cacheKey(bucket: string, path: string): string {\n return `${bucket}:${path}`;\n}\n\nfunction releaseEntry(key: string): void {\n const entry = downloadCache.get(key);\n if (!entry) return;\n entry.refCount--;\n if (entry.refCount <= 0) {\n URL.revokeObjectURL(entry.url);\n downloadCache.delete(key);\n }\n}\n\nexport function useDownloadFile<S extends SchemaStructure>(\n bucketName: BucketNames<S>,\n path: Accessor<string | null | undefined>,\n options?: UseDownloadFileOptions,\n): UseDownloadFileResult;\nexport function useDownloadFile<S extends SchemaStructure>(\n db: SyncedDb<S>,\n bucketName: BucketNames<S>,\n path: Accessor<string | null | undefined>,\n options?: UseDownloadFileOptions,\n): UseDownloadFileResult;\nexport function useDownloadFile<S extends SchemaStructure>(\n dbOrBucketName: SyncedDb<S> | BucketNames<S>,\n bucketNameOrPath?: BucketNames<S> | Accessor<string | null | undefined>,\n pathOrOptions?: Accessor<string | null | undefined> | UseDownloadFileOptions,\n maybeOptions?: UseDownloadFileOptions,\n): UseDownloadFileResult {\n let db: SyncedDb<S>;\n let bucketName: BucketNames<S>;\n let path: Accessor<string | null | undefined>;\n let options: UseDownloadFileOptions;\n\n if (typeof dbOrBucketName === 'string') {\n db = useDb<S>();\n bucketName = dbOrBucketName as BucketNames<S>;\n path = bucketNameOrPath as Accessor<string | null | undefined>;\n options = (pathOrOptions as UseDownloadFileOptions) ?? {};\n } else {\n db = dbOrBucketName as SyncedDb<S>;\n bucketName = bucketNameOrPath as BucketNames<S>;\n path = pathOrOptions as Accessor<string | null | undefined>;\n options = maybeOptions ?? {};\n }\n\n const useCache = options.cache !== false;\n\n const [url, setUrl] = createSignal<string | null>(null);\n const [isLoading, setIsLoading] = createSignal(false);\n const [error, setError] = createSignal<Error | null>(null);\n\n let currentKey: string | null = null;\n let privateUrl: string | null = null;\n const [refetchSignal, setRefetchSignal] = createSignal(0);\n const refetchTrigger = () => setRefetchSignal((n) => n + 1);\n\n async function doDownload(key: string, filePath: string): Promise<string | null> {\n if (useCache) {\n // Check cache\n const cached = downloadCache.get(key);\n if (cached) {\n cached.refCount++;\n currentKey = key;\n return cached.url;\n }\n\n // Check inflight\n const inflight = inflightRequests.get(key);\n if (inflight) {\n const result = await inflight;\n if (result) {\n const entry = downloadCache.get(key);\n if (entry) {\n entry.refCount++;\n currentKey = key;\n }\n }\n return result;\n }\n\n // Start new download\n const promise = (async () => {\n const content = await db.bucket(bucketName).get(filePath);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n downloadCache.set(key, { url: objectUrl, refCount: 1 });\n return objectUrl;\n })();\n\n inflightRequests.set(key, promise);\n try {\n const result = await promise;\n currentKey = key;\n return result;\n } finally {\n inflightRequests.delete(key);\n }\n } else {\n // No caching — private URL per instance\n const content = await db.bucket(bucketName).get(filePath);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n privateUrl = objectUrl;\n return objectUrl;\n }\n }\n\n function releaseCurrentEntry() {\n if (useCache && currentKey) {\n releaseEntry(currentKey);\n currentKey = null;\n }\n if (!useCache && privateUrl) {\n URL.revokeObjectURL(privateUrl);\n privateUrl = null;\n }\n }\n\n createEffect(() => {\n const filePath = path();\n // Subscribe to refetch signal so effect re-runs\n refetchSignal();\n\n // Release previous entry\n releaseCurrentEntry();\n\n if (!filePath) {\n setUrl(null);\n setIsLoading(false);\n setError(null);\n return;\n }\n\n const key = cacheKey(bucketName as string, filePath);\n\n // Synchronous cache hit\n if (useCache) {\n const cached = downloadCache.get(key);\n if (cached) {\n cached.refCount++;\n currentKey = key;\n setUrl(cached.url);\n setIsLoading(false);\n setError(null);\n return;\n }\n }\n\n let cancelled = false;\n setIsLoading(true);\n setError(null);\n\n doDownload(key, filePath).then(\n (result) => {\n if (!cancelled) {\n setUrl(result);\n setIsLoading(false);\n }\n return undefined;\n },\n (err) => {\n if (!cancelled) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setIsLoading(false);\n }\n },\n );\n\n onCleanup(() => {\n cancelled = true;\n });\n });\n\n onCleanup(() => {\n releaseCurrentEntry();\n });\n\n const refetch = () => {\n // Evict current entry from cache before re-triggering\n if (useCache && currentKey) {\n const entry = downloadCache.get(currentKey);\n if (entry) {\n URL.revokeObjectURL(entry.url);\n downloadCache.delete(currentKey);\n }\n currentKey = null;\n }\n refetchTrigger();\n };\n\n return { url, isLoading, error, refetch };\n}\n","import type { JSX} from 'solid-js';\nimport { createSignal, onMount, createComponent, createMemo, mergeProps } from 'solid-js';\nimport type { SchemaStructure } from '@spooky/query-builder';\nimport type { SyncedDbConfig } from '../types';\nimport { SyncedDb } from '../index';\nimport { Sp00kyContext } from './context';\n\nexport interface Sp00kyProviderProps<S extends SchemaStructure> {\n config: SyncedDbConfig<S>;\n fallback?: JSX.Element;\n onError?: (error: Error) => void;\n onReady?: (db: SyncedDb<S>) => void;\n /**\n * Prewarm data into the local cache before revealing the UI. Runs after\n * `init()`; the `fallback` stays visible until it resolves. Use awaitable\n * `db.preload(...)` calls here to gate first-load on essential data (e.g.\n * config). On warm loads preload returns instantly, so there's no perceptible\n * gate after the first run. Best-effort: a rejection is caught and the UI is\n * revealed anyway.\n */\n preload?: (db: SyncedDb<S>) => Promise<void>;\n children: JSX.Element;\n}\n\nexport function Sp00kyProvider<S extends SchemaStructure>(\n props: Sp00kyProviderProps<S>\n): JSX.Element {\n const merged = mergeProps(\n {\n fallback: undefined as JSX.Element | undefined,\n },\n props\n );\n\n const [db, setDb] = createSignal<SyncedDb<S> | undefined>(undefined);\n\n onMount(async () => {\n try {\n const instance = new SyncedDb<S>(merged.config);\n await instance.init();\n // Gate first-load UI on prewarmed data. Best-effort: never let a preload\n // failure keep the app stuck on the fallback.\n if (merged.preload) {\n try {\n await merged.preload(instance);\n } catch (e) {\n // oxlint-disable-next-line no-console\n console.error('Sp00kyProvider: preload failed; revealing UI anyway', e);\n }\n }\n setDb(() => instance);\n merged.onReady?.(instance);\n } catch (e) {\n const error = e instanceof Error ? e : new Error(String(e));\n if (merged.onError) {\n merged.onError(error);\n } else {\n // oxlint-disable-next-line no-console\n console.error('Sp00kyProvider: Failed to initialize database', error);\n }\n }\n });\n\n const content = createMemo(() => {\n const instance = db();\n if (!instance) return merged.fallback;\n return createComponent(Sp00kyContext.Provider, {\n value: instance,\n get children() {\n return merged.children;\n },\n });\n });\n\n return content as unknown as JSX.Element;\n}\n","import type { SyncedDbConfig } from './types';\nimport {\n Sp00kyClient,\n type Sp00kyQueryResultPromise,\n type AuthService,\n type BucketHandle,\n type UpdateOptions,\n type RunOptions,\n type SyncHealth,\n type StorageHealth,\n type PreloadOptions,\n type PreloadRefresh,\n} from '@spooky-sync/core';\n\nimport type {\n GetTable,\n QueryBuilder,\n SchemaStructure,\n TableModel,\n TableNames,\n QueryResult,\n RelatedFieldsMap,\n RelationshipFieldsFromSchema,\n GetRelationship,\n RelatedFieldMapEntry,\n FinalQuery,\n InnerQuery,\n BackendNames,\n BackendRoutes,\n RoutePayload,\n BucketNames,\n BucketDefinitionSchema,\n QueryModifier,\n QueryModifierBuilder,\n QueryInfo,\n RelationshipsMetadata,\n RelationshipDefinition,\n InferRelatedModelFromMetadata,\n GetCardinality,\n} from '@spooky-sync/query-builder';\n\nimport { RecordId, Uuid, type Surreal } from 'surrealdb';\nexport { RecordId, Uuid };\nexport type { Model, GenericModel, GenericSchema, ModelPayload } from './lib/models';\nexport { useQuery } from './lib/use-query';\nexport { createPreload } from './lib/create-preload';\nexport type { PreloadOptions, PreloadRefresh } from '@spooky-sync/core';\nexport { useSyncStatus, type UseSyncStatus } from './lib/use-sync-status';\nexport type { SyncHealth, SyncHealthStatus, SyncHealthConfig } from '@spooky-sync/core';\nexport { useStorageStatus, type UseStorageStatus } from './lib/use-storage-status';\nexport type { StorageHealth, StorageHealthStatus } from '@spooky-sync/core';\nexport { useCrdtField } from './lib/use-crdt-field';\nexport { useFeatureFlag, type UseFeatureFlag } from './lib/use-feature-flag';\nexport {\n useAppRelease,\n type UseAppRelease,\n type UseAppReleaseOptions,\n} from './lib/use-app-release';\nexport { useFileUpload, type FileUploadResult } from './lib/use-file-upload';\nexport { useDownloadFile, type UseDownloadFileOptions, type UseDownloadFileResult } from './lib/use-download-file';\nexport { Sp00kyProvider, type Sp00kyProviderProps } from './lib/Sp00kyProvider';\nexport { useDb } from './lib/context';\n\n// export { AuthEventTypes } from \"@spooky-sync/core\"; // TODO: Verify if AuthEventTypes exists in core\n\n\n// Re-export query builder types for convenience\nexport type {\n QueryModifier,\n QueryModifierBuilder,\n QueryInfo,\n RelationshipsMetadata,\n RelationshipDefinition,\n InferRelatedModelFromMetadata,\n GetCardinality,\n GetTable,\n TableModel,\n TableNames,\n QueryResult,\n};\n\nexport type RelationshipField<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n Field extends RelationshipFieldsFromSchema<Schema, TableName>,\n> = GetRelationship<Schema, TableName, Field>;\n\nexport type RelatedFieldsTableScoped<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n RelatedFields extends RelationshipFieldsFromSchema<Schema, TableName> =\n RelationshipFieldsFromSchema<Schema, TableName>,\n> = {\n [K in RelatedFields]: {\n to: RelationshipField<Schema, TableName, K>['to'];\n relatedFields: RelatedFieldsMap;\n cardinality: RelationshipField<Schema, TableName, K>['cardinality'];\n };\n};\n\nexport type InferModel<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n RelatedFields extends RelatedFieldsTableScoped<Schema, TableName>,\n> = QueryResult<Schema, TableName, RelatedFields, true>;\n\nexport type WithRelated<Field extends string, RelatedFields extends RelatedFieldsMap = {}> = {\n [K in Field]: Omit<RelatedFieldMapEntry, 'relatedFields'> & {\n relatedFields: RelatedFields;\n };\n};\n\nexport type WithRelatedMany<Field extends string, RelatedFields extends RelatedFieldsMap = {}> = {\n [K in Field]: {\n to: Field;\n relatedFields: RelatedFields;\n cardinality: 'many';\n };\n};\n\n/**\n * SyncedDb - A thin wrapper around sp00ky-ts for Solid.js integration\n * Delegates all logic to the underlying sp00ky-ts instance\n */\nexport class SyncedDb<S extends SchemaStructure> {\n private config: SyncedDbConfig<S>;\n private sp00ky: Sp00kyClient<S> | null = null;\n private _initialized = false;\n\n constructor(config: SyncedDbConfig<S>) {\n this.config = config;\n }\n\n public getSp00ky(): Sp00kyClient<S> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky;\n }\n\n /**\n * Initialize the sp00ky-ts instance\n */\n async init(): Promise<void> {\n if (this._initialized) return;\n this.sp00ky = new Sp00kyClient<S>(this.config);\n await this.sp00ky.init();\n this._initialized = true;\n }\n\n /**\n * Create a new record in the database\n */\n async create(id: string, payload: Record<string, unknown>): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.create(id, payload as Record<string, unknown>);\n }\n\n /**\n * Update an existing record in the database\n */\n async update<TName extends TableNames<S>>(\n tableName: TName,\n recordId: string,\n payload: Partial<TableModel<GetTable<S, TName>>>,\n options?: UpdateOptions\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.update(\n tableName as string,\n recordId,\n payload as Record<string, unknown>,\n options\n );\n }\n\n /**\n * Delete an existing record in the database\n */\n async delete<TName extends TableNames<S>>(\n tableName: TName,\n selector: string | RecordId | InnerQuery<GetTable<S, TName>, boolean>\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n // Accept a `\"table:id\"` string OR a RecordId — live-query rows carry their\n // `id` as a RecordId, so callers can pass `db.delete('game', row.id)`\n // directly. Build the canonical string from the raw id part (not\n // `RecordId.toString()`, which escapes special chars) so it round-trips\n // through the engine's `parseRecordIdString`. InnerQuery selectors are not\n // supported yet. (cross-package RecordId instances → match by constructor name.)\n const isRecordId =\n selector instanceof RecordId || (selector as any)?.constructor?.name === 'RecordId';\n let id: string;\n if (typeof selector === 'string') {\n id = selector;\n } else if (isRecordId) {\n id = `${tableName as string}:${(selector as RecordId).id}`;\n } else {\n throw new Error('Only string ID or RecordId selectors are supported currently with core');\n }\n await this.sp00ky.delete(tableName as string, id);\n }\n\n /**\n * Preload/prewarm a built query into the local cache without registering a\n * live view. Fetches once and stores the rows (+ embedded related children)\n * locally so a later `useQuery` for the same data paints instantly. Best-effort.\n */\n public async preload(\n finalQuery: FinalQuery<S, any, any, any, any, Sp00kyQueryResultPromise>,\n options?: PreloadOptions\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.preload(finalQuery, options);\n }\n\n /**\n * Query data from the database\n */\n public query<TName extends TableNames<S>>(\n table: TName\n ): QueryBuilder<S, TName, Sp00kyQueryResultPromise, {}, false> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.query(table, {});\n }\n\n /**\n * Run a backend operation\n */\n public async run<\n B extends BackendNames<S>,\n R extends BackendRoutes<S, B>,\n >(\n backend: B,\n path: R,\n payload: RoutePayload<S, B, R>,\n options?: RunOptions,\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.run(backend, path, payload, options);\n }\n\n /**\n * Authenticate with the database\n */\n public async authenticate(token: string): Promise<RecordId<string>> {\n await this.sp00ky?.authenticate(token);\n // Sp00kyClient.authenticate returns whatever remote.authenticate returns (boolean or token usually?)\n // Wait, checked Sp00kyClient: return this.remote.getClient().authenticate(token);\n // SurrealDB authenticate returns void? or token?\n // Assuming void or token.\n return new RecordId('user', 'me'); // Placeholder or actual?\n }\n\n /**\n * Deauthenticate from the database\n * @deprecated Use signOut() instead\n */\n public async deauthenticate(): Promise<void> {\n await this.signOut();\n }\n\n /**\n * Sign out, clear session and local storage\n */\n public async signOut(): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.auth.signOut();\n }\n\n /**\n * Execute a function with direct access to the remote database connection\n */\n public async useRemote<T>(fn: (db: Surreal) => T | Promise<T>): Promise<T> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return await this.sp00ky.useRemote(fn);\n }\n /**\n * Access the remote database service directly\n */\n get remote(): Sp00kyClient<S>['remoteClient'] {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.remoteClient;\n }\n\n /**\n * Access the local database service directly\n */\n get local(): Sp00kyClient<S>['localClient'] {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.localClient;\n }\n\n /**\n * Access the auth service\n */\n get auth(): AuthService<S> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.auth;\n }\n\n get pendingMutationCount(): number {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.pendingMutationCount;\n }\n\n /** Diagnostic — see `Sp00kyClient.liveRetryCount`. */\n get liveRetryCount(): number {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.liveRetryCount;\n }\n\n subscribeToPendingMutations(cb: (count: number) => void): () => void {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.subscribeToPendingMutations(cb);\n }\n\n /** Current sync-health snapshot. See {@link useSyncStatus}. */\n get syncHealth(): SyncHealth {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.syncHealth;\n }\n\n /**\n * Observe sync health. Fires immediately with the current status and again\n * on every healthy↔degraded transition. Prefer the `useSyncStatus` hook in\n * components; this is the imperative escape hatch.\n */\n subscribeToSyncHealth(cb: (health: SyncHealth) => void): () => void {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.subscribeToSyncHealth(cb);\n }\n\n /** Current local-store durability snapshot. See {@link useStorageStatus}. */\n get storageHealth(): StorageHealth {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.storageHealth;\n }\n\n /**\n * Observe local-store durability. Fires immediately with the current snapshot\n * and again on change. Prefer the `useStorageStatus` hook in components; this\n * is the imperative escape hatch.\n */\n subscribeToStorageHealth(cb: (health: StorageHealth) => void): () => void {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.subscribeToStorageHealth(cb);\n }\n\n bucket<B extends BucketNames<S>>(name: B): BucketHandle {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.bucket(name);\n }\n\n getBucketConfig(name: string): BucketDefinitionSchema | undefined {\n return this.config.schema.buckets?.find((b) => b.name === name);\n }\n}\n\nexport * from './types';\n"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { RecordId, RecordId as RecordId$1, Surreal, Uuid } from "surrealdb";
2
2
  import { GenericModel, GenericSchema, SchemaStructure } from "@spooky/query-builder";
3
- import { AppReleaseOptions, AuthService, BucketHandle, CrdtField, FeatureFlagOptions, PreloadOptions, PreloadOptions as PreloadOptions$1, PreloadRefresh, RunOptions, Sp00kyClient, Sp00kyConfig, Sp00kyQueryResultPromise, SyncHealth, SyncHealth as SyncHealth$1, SyncHealthConfig, SyncHealthStatus, SyncHealthStatus as SyncHealthStatus$1, UpdateOptions } from "@spooky-sync/core";
3
+ import { AppReleaseOptions, AuthService, BucketHandle, CrdtField, FeatureFlagOptions, PreloadOptions, PreloadOptions as PreloadOptions$1, PreloadRefresh, RunOptions, Sp00kyClient, Sp00kyConfig, Sp00kyQueryResultPromise, StorageHealth, StorageHealth as StorageHealth$1, StorageHealthStatus, StorageHealthStatus as StorageHealthStatus$1, SyncHealth, SyncHealth as SyncHealth$1, SyncHealthConfig, SyncHealthStatus, SyncHealthStatus as SyncHealthStatus$1, UpdateOptions } from "@spooky-sync/core";
4
4
  import { BackendNames, BackendRoutes, BucketDefinitionSchema, BucketNames, ColumnSchema, FinalQuery, GetCardinality, GetRelationship, GetTable, GetTable as GetTable$1, InferRelatedModelFromMetadata, InnerQuery, QueryBuilder, QueryInfo, QueryModifier, QueryModifierBuilder, QueryResult, QueryResult as QueryResult$1, RelatedFieldMapEntry, RelatedFieldsMap, RelationshipDefinition, RelationshipFieldsFromSchema, RelationshipsMetadata, RoutePayload, SchemaStructure as SchemaStructure$1, TableModel, TableModel as TableModel$1, TableNames, TableNames as TableNames$1 } from "@spooky-sync/query-builder";
5
5
  import { Accessor, JSX } from "solid-js";
6
6
 
@@ -120,6 +120,34 @@ interface UseSyncStatus {
120
120
  declare function useSyncStatus(): UseSyncStatus;
121
121
  //# sourceMappingURL=use-sync-status.d.ts.map
122
122
  //#endregion
123
+ //#region src/lib/use-storage-status.d.ts
124
+ interface UseStorageStatus {
125
+ /** Full durability snapshot; updates reactively. */
126
+ health: Accessor<StorageHealth$1>;
127
+ /** `'unknown'` | `'persistent'` | `'memory'`. */
128
+ status: Accessor<StorageHealthStatus$1>;
129
+ /** `true` when the local store survives a reload. */
130
+ isPersistent: Accessor<boolean>;
131
+ /**
132
+ * `true` only when durable storage was requested and could NOT be opened, so
133
+ * the dataset is sitting in RAM and local writes die on reload. Drive a
134
+ * warning off this, not off `status`: a store configured as in-memory reports
135
+ * `'memory'` too, and that is a choice rather than a problem.
136
+ */
137
+ isMemoryFallback: Accessor<boolean>;
138
+ }
139
+ /**
140
+ * Observe how durable the LOCAL cache is, for a "no local storage" warning.
141
+ *
142
+ * Under `localEngine: 'sqlite'` with `store: 'indexeddb'` the durable store is
143
+ * the OPFS SAHPool VFS, and only ONE client per bucket can hold it open: a
144
+ * second tab of the same app cannot get it and runs in memory instead (the
145
+ * engine retries first, so a closing tab's lock is usually waited out). Must be
146
+ * used within a `<Sp00kyProvider>`.
147
+ */
148
+ declare function useStorageStatus(): UseStorageStatus;
149
+ //# sourceMappingURL=use-storage-status.d.ts.map
150
+ //#endregion
123
151
  //#region src/lib/use-crdt-field.d.ts
124
152
  declare function useCrdtField(table: string, recordId: () => string | undefined, field: string, fallbackText?: () => string | undefined): Accessor<CrdtField | null>;
125
153
  //# sourceMappingURL=use-crdt-field.d.ts.map
@@ -338,9 +366,17 @@ declare class SyncedDb<S extends SchemaStructure$1> {
338
366
  * components; this is the imperative escape hatch.
339
367
  */
340
368
  subscribeToSyncHealth(cb: (health: SyncHealth$1) => void): () => void;
369
+ /** Current local-store durability snapshot. See {@link useStorageStatus}. */
370
+ get storageHealth(): StorageHealth$1;
371
+ /**
372
+ * Observe local-store durability. Fires immediately with the current snapshot
373
+ * and again on change. Prefer the `useStorageStatus` hook in components; this
374
+ * is the imperative escape hatch.
375
+ */
376
+ subscribeToStorageHealth(cb: (health: StorageHealth$1) => void): () => void;
341
377
  bucket<B extends BucketNames<S>>(name: B): BucketHandle;
342
378
  getBucketConfig(name: string): BucketDefinitionSchema | undefined;
343
379
  }
344
380
  //#endregion
345
- export { CacheStrategy, type FileUploadResult, type GenericModel, type GenericSchema, type GetCardinality, type GetTable, InferModel, type InferRelatedModelFromMetadata, InferRelationshipsFromConst, InferSchemaFromConst, type Model, type ModelPayload, type PreloadOptions, type PreloadRefresh, ProvisionOptions, type QueryInfo, type QueryModifier, type QueryModifierBuilder, type QueryResult, RecordId, RelatedFieldsTableScoped, type RelationshipDefinition, RelationshipField, type RelationshipsMetadata, Sp00kyProvider, type Sp00kyProviderProps, type SyncHealth, type SyncHealthConfig, type SyncHealthStatus, SyncedDb, SyncedDbConfig, type TableModel, type TableNames, type UseAppRelease, type UseAppReleaseOptions, type UseDownloadFileOptions, type UseDownloadFileResult, type UseFeatureFlag, type UseSyncStatus, Uuid, WithRelated, WithRelatedMany, createPreload, useAppRelease, useCrdtField, useDb, useDownloadFile, useFeatureFlag, useFileUpload, useQuery, useSyncStatus };
381
+ export { CacheStrategy, type FileUploadResult, type GenericModel, type GenericSchema, type GetCardinality, type GetTable, InferModel, type InferRelatedModelFromMetadata, InferRelationshipsFromConst, InferSchemaFromConst, type Model, type ModelPayload, type PreloadOptions, type PreloadRefresh, ProvisionOptions, type QueryInfo, type QueryModifier, type QueryModifierBuilder, type QueryResult, RecordId, RelatedFieldsTableScoped, type RelationshipDefinition, RelationshipField, type RelationshipsMetadata, Sp00kyProvider, type Sp00kyProviderProps, type StorageHealth, type StorageHealthStatus, type SyncHealth, type SyncHealthConfig, type SyncHealthStatus, SyncedDb, SyncedDbConfig, type TableModel, type TableNames, type UseAppRelease, type UseAppReleaseOptions, type UseDownloadFileOptions, type UseDownloadFileResult, type UseFeatureFlag, type UseStorageStatus, type UseSyncStatus, Uuid, WithRelated, WithRelatedMany, createPreload, useAppRelease, useCrdtField, useDb, useDownloadFile, useFeatureFlag, useFileUpload, useQuery, useStorageStatus, useSyncStatus };
346
382
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../../src/lib/models.ts","../../../src/types/index.ts","../../../src/lib/use-query.ts","../../../src/lib/create-preload.ts","../../../src/lib/use-sync-status.ts","../../../src/lib/use-crdt-field.ts","../../../src/lib/use-feature-flag.ts","../../../src/lib/use-app-release.ts","../../../src/lib/use-file-upload.ts","../../../src/lib/use-download-file.ts","../../../src/lib/Sp00kyProvider.ts","../../../src/lib/context.ts","../../../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;KAMY,WAAW;KACX,kBAAkB;MAAU;;;;;;;;AAD5B,UCEK,gBAAA,CDFO;EACZ;EAAY,KAAA,CAAA,EAAA,OAAA;;ACQP,UAAA,CAAA,CAAA;AAPA,KAWL,aAAA,GAXqB,QAAA,GAAA,WAAA;AAOhB;AAIjB;AAKA;AAAgC,KAApB,oBAAoB,CAAA,UAAW,iBAAX,CAAA,GAAA,QACxB,YADmC,CACxB,CADwB,CAAA,GACnB,YADmB,CACR,UADQ,CACC,CADD,EACI,CADJ,CAAA,CAAA;;;;;AACnB,KAOZ,2BAPY,CAAA,UAO0B,iBAP1B,EAAA,eAO0D,aAP1D,CAAA,GAAA,gBAQR,YARkB,CAQP,CARO,CAAA,GAAA,UAStB,OAFA,CAEQ,CAFR,CAAA,eAA2B,CAAA,CAAA,MAAA,CAAA,EAAA;EAAA,IAAA,EAEiB,SAFjB;AAAW,CAAA,CAAA,IAEsB,GAFtB,CAAA,OAAA,CAAA,GAAA;EAAgC,KAAA,EAGrE,GAHqE,CAAA,IAAA,CAAA,SAAA,MAG7C,MAH6C,GAGpC,MAHoC,CAG7B,GAH6B,CAAA,IAAA,CAAA,CAAA,GAAA,GAAA;EACvD,KAAA,EAGd,GAHc,CAAA,IAAA,CAAA;EAAX,WAAA,EAIG,GAJH,CAAA,aAAA,CAAA;AACI,CAAA;KASf,QATmE,CAAA,CAAA,CAAA,GAAA,QAC3D,MAQoB,CARpB,GAQwB,CARxB,CAQ0B,CAR1B,CAAA;AAAiC,KAUlC,cAVkC,CAAA,UAUT,iBAVS,CAAA,GAUU,QAVV,CAUmB,YAVnB,CAUgC,CAVhC,CAAA,CAAA;;;KCtBzC,mBACO,qCACQ,aAAW;WACR,eAAe;yBACd,8CAGpB,WAAW,GAAG,WAAW,GAAG,eAAe,OAAO,mCAE9C,WAAW,GAAG,WAAW,GAAG,eAAe,OAAO;KAIrD,YAAA;EFpBO,OAAA,CAAK,EAAA,GAAA,GAAA,OAAO;EACZ;;;;;;;;ACCZ,CAAA;AAOiB,iBCwBD,QDxBC,CAAA,UCyBL,iBDzBK,EAAA,kBC0BG,YD1BH,CC0Bc,CD1Bd,CAAA,EAAA,UAAA;EAIL,OAAA,ECuBW,MDvBX,CAAa,MAAA,ECuBa,YDvBb,CAAA;AAKzB,CAAA,EAAA,sBCmBwB,MDnBQ,CAAA,MAAA,EAAA,GAAA,CAAA,EAAA,cAAA,OAAA,EAAA,QCqBtB,aDrBsB,CCqBV,CDrBU,ECqBP,SDrBO,ECqBI,aDrBJ,ECqBmB,KDrBnB,CAAA,GAAA,IAAA,CAAA,CAAA,UAAA,ECuBlB,QDvBkB,CCuBT,CDvBS,ECuBN,SDvBM,ECuBK,CDvBL,ECuBQ,aDvBR,ECuBuB,KDvBvB,CAAA,EAAA,OAAA,CAAA,ECwBpB,YDxBoB,CAAA,EAAA;EAAA,IAAA,EAAA,GAAA,GC0BlB,KD1BkB,GAAA,SAAA;OAAW,EAAA,GAAA,GC2B5B,KD3B4B,GAAA,SAAA;WACxB,EAAA,GAAA,GAAA,OAAA;YAAX,EAAA,GAAA,GAAA,OAAA;WAAoC,EAAA,GAAA,GAAA,OAAA;;AAAT,iBCiCnB,QDjCmB,CAAA,UCkCvB,iBDlCuB,EAAA,kBCmCf,YDnCe,CCmCJ,CDnCI,CAAA,EAAA,UAAA;SAAX,ECoCD,MDpCC,CAAA,MAAA,ECoCc,YDpCd,CAAA;CAAU,EAAA,sBCqCV,MDrCU,CAAA,MAAA,EAAA,GAAA,CAAA,EAAA,cAAA,OAAA,EAAA,QCuCxB,aDvCwB,CCuCZ,CDvCY,ECuCT,SDvCS,ECuCE,aDvCF,ECuCiB,KDvCjB,CAAA,GAAA,IAAA,CAAA,CAAA,EAAA,ECyC5B,QDzC4B,CCyCnB,CDzCmB,CAAA,EAAA,UAAA,EC0CpB,QD1CoB,CC0CX,CD1CW,EC0CR,SD1CQ,EC0CG,CD1CH,EC0CM,aD1CN,EC0CqB,KD1CrB,CAAA,EAAA,OAAA,CAAA,EC2CtB,YD3CsB,CAAA,EAAA;EAOtB,IAAA,EAAA,GAAA,GCsCE,KDtCF,GAAA,SAAA;EAA2B,KAAA,EAAA,GAAA,GCuCxB,KDvCwB,GAAA,SAAA;WAAW,EAAA,GAAA,GAAA,OAAA;YAAgC,EAAA,GAAA,GAAA,OAAA;WACvD,EAAA,GAAA,GAAA,OAAA;;;;KEtBtB,qBACO,qCACQ,aAAW;WACR,eAAe;yBACd,8CAGpB,WAAW,GAAG,WAAW,GAAG,eAAe,OAAO,mCAE9C,WAAW,GAAG,WAAW,GAAG,eAAe,OAAO;KAIrD,gBAAA,GAAiB;EHlBV;EACA,OAAA,CAAA,EAAA,GAAA,GAAY,OAAA;CAAA;AAAM,iBGuBd,aHvBc,CAAA,UGwBlB,iBHxBkB,EAAA,kBGyBV,YHzBU,CGyBC,CHzBD,CAAA,EAAA,UAAA;SAAU,EG0BjB,MH1BiB,CAAA,MAAA,EG0BF,YH1BE,CAAA;CAAQ,EAAA,sBG2BxB,MH3BwB,CAAA,MAAA,EAAA,GAAA,CAAA,EAAA,cAAA,OAAA,CAAA,CAAA,UAAA,EG8BlC,UH9BkC,CG8BvB,CH9BuB,EG8BpB,SH9BoB,EG8BT,CH9BS,EG8BN,aH9BM,EG8BS,KH9BT,CAAA,EAAA,OAAA,CAAA,EG+BpC,gBH/BoC,CAAA,EAAA,IAAA;iBGmChC,wBACJ,qCACQ,aAAW;WACR,eAAe;yBACd,gDAGlB,SAAS,gBACD,WAAW,GAAG,WAAW,GAAG,eAAe,kBAC7C;;;UC/CK,aAAA;;UAEP,SAAS;;UAET,SAAS;EJFP,SAAK,EIGJ,QJHU,CAAA,OAAC,CAAA;EACZ;EAAY,UAAA,EIIV,QJJU,CAAA,OAAA,CAAA;;eAAgB,EIMvB,QJNuB,CAAA,OAAA,CAAA;EAAQ;;;;ACChD;AAOiB;EAIL,SAAA,EGCC,QHDY,CAAA,OAAA,CAAA;AAKzB;;;;;;;;;;AAQA;AAAuC,iBGCvB,aAAA,CAAA,CHDuB,EGCN,aHDM;;;;iBI5BvB,YAAA,6GAKb,SAAS;;;;UCLK,cAAA;WACN;WACA;WACA;;ANDX;AACA;;;;;;;;ACCA;AAOiB;AAIL,iBKEI,cAAA,CLFS,GAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EKIb,kBLJa,CAAA,EKKtB,cLLsB;AAKzB;;;UMpBiB,oBAAA,SAA6B;;;;;APE9C;AACA;;gBAA8B,EAAA,MAAA;;AAAkB,UOQ/B,aAAA,CPR+B;;iBOU/B;;ENTA,eAAA,EMWE,QNXc,CAAA,OAAA,CAAA;EAOhB;EAIL,SAAA,EMEC,QNFY,CAAA,OAAA,CAAA;EAKb;EAAoB,SAAA,EMDnB,QNCmB,CAAA,OAAA,CAAA;;;;;;;;;AAQhC;EAAuC,MAAA,EAAA,GAAA,GMCvB,ONDuB,CAAA,IAAA,CAAA;;;;;;;;;;;;AAGc,iBMgCrC,aAAA,CNhCqC,OAAA,EMgCd,oBNhCc,CAAA,EMgCS,aNhCT;;;;UO7BpC,gBAAA;;eAEF;;+BAEgB,OAAO,SAAS;ERJnC,QAAK,EAAA,CAAA,IAAA,EAAA,MAAO,EAAA,GQKM,ORLN,CAAA,MAAA,GAAA,IAAA,CAAA;EACZ,MAAA,EAAA,CAAA,IAAA,EAAY,MAAA,EAAA,GQKI,ORLJ,CAAA,IAAA,CAAA;EAAA,MAAA,EAAA,CAAA,IAAA,EAAA,MAAA,EAAA,GQMI,ORNJ,CAAA,OAAA,CAAA;;AAAgB,iBQSxB,aRTwB,CAAA,UQSA,iBRTA,CAAA,CAAA,UAAA,EQU1B,WRV0B,CQUd,CRVc,CAAA,CAAA,EQWrC,gBRXqC;AAAQ,iBQYhC,aRZgC,CAAA,UQYR,iBRZQ,CAAA,CAAA,EAAA,EQa1C,QRb0C,CQajC,CRbiC,CAAA,EAAA,UAAA,EQclC,WRdkC,CQctB,CRdsB,CAAA,CAAA,EQe7C,gBRf6C;;;;USF/B,sBAAA;;;UAIA,qBAAA;ETHL,GAAA,ESIL,QTJU,CAAA,MAAM,GAAC,IAAA,CAAA;EACZ,SAAA,ESIC,QTJW,CAAA,OAAA,CAAA;EAAA,KAAA,ESKf,QTLe,CSKN,KTLM,GAAA,IAAA,CAAA;SAAM,EAAA,GAAA,GAAA,IAAA;;AAAkB,iBS+BhC,eT/BgC,CAAA,US+BN,iBT/BM,CAAA,CAAA,UAAA,ESgClC,WThCkC,CSgCtB,CThCsB,CAAA,EAAA,IAAA,ESiCxC,QTjCwC,CAAA,MAAA,GAAA,IAAA,GAAA,SAAA,CAAA,EAAA,OAAA,CAAA,ESkCpC,sBTlCoC,CAAA,ESmC7C,qBTnC6C;iBSoChC,0BAA0B,uBACpC,SAAS,gBACD,YAAY,UAClB,+CACI,yBACT;;;;UCzCc,8BAA8B;UACrC,eAAe;aACZ,GAAA,CAAI;EVHL,OAAA,CAAK,EAAA,CAAA,KAAA,EUIG,KVJI,EAAA,GAAA,IAAA;EACZ,OAAA,CAAA,EAAA,CAAA,EAAA,EUIK,QVJO,CUIE,CVJF,CAAA,EAAA,GAAA,IAAA;EAAA;;;;;;;ACCxB;EAOiB,OAAA,CAAA,EAAA,CAAA,EAAA,ESKA,QTLA,CSKS,CTLT,CAAA,EAAA,GSKgB,OTLhB,CAAA,IAAA,CAAA;EAIL,QAAA,ESEA,GAAA,CAAI,OTFS;AAKzB;AAAgC,iBSAhB,cTAgB,CAAA,USAS,eTAT,CAAA,CAAA,KAAA,ESCvB,mBTDuB,CSCH,CTDG,CAAA,CAAA,ESE7B,GAAA,CAAI,OTFyB;;;;iBUlBhB,gBAAgB,oBAAoB,SAAS;;;;;AVkBjD,KWsDA,iBXtDoB,CAAA,eWuDf,iBXvDe,EAAA,kBWwDZ,UXxDY,CWwDD,MXxDC,CAAA,EAAA,cWyDhB,4BXzDgB,CWyDa,MXzDb,EWyDqB,SXzDrB,CAAA,CAAA,GW0D5B,eX1D4B,CW0DZ,MX1DY,EW0DJ,SX1DI,EW0DO,KX1DP,CAAA;AAAA,KW4DpB,wBX5DoB,CAAA,eW6Df,iBX7De,EAAA,kBW8DZ,UX9DY,CW8DD,MX9DC,CAAA,EAAA,sBW+DR,4BX/DQ,CW+DqB,MX/DrB,EW+D6B,SX/D7B,CAAA,GWgE5B,4BXhE4B,CWgEC,MXhED,EWgES,SXhET,CAAA,CAAA,GAAA,QWkExB,aXlEmC,GAAA;EACxB,EAAA,EWkEX,iBXlEW,CWkEO,MXlEP,EWkEe,SXlEf,EWkE0B,CXlE1B,CAAA,CAAA,IAAA,CAAA;EAAX,aAAA,EWmEW,gBXnEX;EAAoC,WAAA,EWoE3B,iBXpE2B,CWoET,MXpES,EWoED,SXpEC,EWoEU,CXpEV,CAAA,CAAA,aAAA,CAAA;;AAApB,KWwEZ,UXxEY,CAAA,eWyEP,iBXzEO,EAAA,kBW0EJ,UX1EI,CW0EO,MX1EP,CAAA,EAAA,sBW2EA,wBX3EA,CW2EyB,MX3EzB,EW2EiC,SX3EjC,CAAA,CAAA,GW4EpB,WX5EoB,CW4ER,MX5EQ,EW4EA,SX5EA,EW4EW,aX5EX,EAAA,IAAA,CAAA;AAAU,KW8EtB,WX9EsB,CAAA,cAAA,MAAA,EAAA,sBW8EkC,gBX9ElC,GAAA,CAAA,CAAA,CAAA,GAAA,QW+E1B,KXxEI,GWwEI,IXxEJ,CWwES,oBXxEkB,EAAA,eAAA,CAAA,GAAA;EAAA,aAAA,EWyEpB,aXzEoB;;AACZ,KW4Ef,eX5Ee,CAAA,cAAA,MAAA,EAAA,sBW4E6C,gBX5E7C,GAAA,CAAA,CAAA,CAAA,GAAA,QW6EnB,KX7EQ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"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../../src/lib/models.ts","../../../src/types/index.ts","../../../src/lib/use-query.ts","../../../src/lib/create-preload.ts","../../../src/lib/use-sync-status.ts","../../../src/lib/use-storage-status.ts","../../../src/lib/use-crdt-field.ts","../../../src/lib/use-feature-flag.ts","../../../src/lib/use-app-release.ts","../../../src/lib/use-file-upload.ts","../../../src/lib/use-download-file.ts","../../../src/lib/Sp00kyProvider.ts","../../../src/lib/context.ts","../../../src/index.ts"],"sourcesContent":[],"mappings":"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Z,OAAA,CAAA,EAAA,CAAA,EAAA,EWIK,QXJO,CWIE,CXJF,CAAA,EAAA,GAAA,IAAA;EAAA;;;;;;;ACCxB;EAOiB,OAAA,CAAA,EAAA,CAAA,EAAA,EUKA,QVLA,CUKS,CVLT,CAAA,EAAA,GUKgB,OVLhB,CAAA,IAAA,CAAA;EAIL,QAAA,EUEA,GAAA,CAAI,OVFS;AAKzB;AAAgC,iBUAhB,cVAgB,CAAA,UUAS,eVAT,CAAA,CAAA,KAAA,EUCvB,mBVDuB,CUCH,CVDG,CAAA,CAAA,EUE7B,GAAA,CAAI,OVFyB;;;;iBWlBhB,gBAAgB,oBAAoB,SAAS;;;;;AXkBlB,KYyD/B,iBZzD+B,CAAA,eY0D1B,iBZ1D0B,EAAA,kBY2DvB,UZ3DuB,CY2DZ,MZ3DY,CAAA,EAAA,cY4D3B,4BZ5D2B,CY4DE,MZ5DF,EY4DU,SZ5DV,CAAA,CAAA,GY6DvC,eZ7DuC,CY6DvB,MZ7DuB,EY6Df,SZ7De,EY6DJ,KZ7DI,CAAA;AACxB,KY8DP,wBZ9DO,CAAA,eY+DF,iBZ/DE,EAAA,kBYgEC,UZhED,CYgEY,MZhEZ,CAAA,EAAA,sBYiEK,4BZjEL,CYiEkC,MZjElC,EYiE0C,SZjE1C,CAAA,GYkEf,4BZlEe,CYkEc,MZlEd,EYkEsB,SZlEtB,CAAA,CAAA,GAAA,QYoEX,aZpEA,GAAA;EAAoC,EAAA,EYqEpC,iBZrEoC,CYqElB,MZrEkB,EYqEV,SZrEU,EYqEC,CZrED,CAAA,CAAA,IAAA,CAAA;EAAG,aAAA,EYsE5B,gBZtE4B;EAAZ,WAAA,EYuElB,iBZvEkB,CYuEA,MZvEA,EYuEQ,SZvER,EYuEmB,CZvEnB,CAAA,CAAA,aAAA,CAAA;GAAD;AAOtB,KYoEA,UZpEA,CAAA,eYqEK,iBZrEsB,EAAA,kBYsEnB,UZtEmB,CYsER,MZtEQ,CAAA,EAAA,sBYuEf,wBZvEe,CYuEU,MZvEV,EYuEkB,SZvElB,CAAA,CAAA,GYwEnC,WZxEmC,CYwEvB,MZxEuB,EYwEf,SZxEe,EYwEJ,aZxEI,EAAA,IAAA,CAAA;AAAA,KY0E3B,WZ1E2B,CAAA,cAAA,MAAA,EAAA,sBY0E6B,gBZ1E7B,GAAA,CAAA,CAAA,CAAA,G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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AppReleaseOptions, AuthService, BucketHandle, CrdtField, FeatureFlagOptions, PreloadOptions, PreloadOptions as PreloadOptions$1, PreloadRefresh, RunOptions, Sp00kyClient, Sp00kyConfig, Sp00kyQueryResultPromise, SyncHealth, SyncHealth as SyncHealth$1, SyncHealthConfig, SyncHealthStatus, SyncHealthStatus as SyncHealthStatus$1, UpdateOptions } from "@spooky-sync/core";
1
+ import { AppReleaseOptions, AuthService, BucketHandle, CrdtField, FeatureFlagOptions, PreloadOptions, PreloadOptions as PreloadOptions$1, PreloadRefresh, RunOptions, Sp00kyClient, Sp00kyConfig, Sp00kyQueryResultPromise, StorageHealth, StorageHealth as StorageHealth$1, StorageHealthStatus, StorageHealthStatus as StorageHealthStatus$1, SyncHealth, SyncHealth as SyncHealth$1, SyncHealthConfig, SyncHealthStatus, SyncHealthStatus as SyncHealthStatus$1, UpdateOptions } from "@spooky-sync/core";
2
2
  import { RecordId, RecordId as RecordId$1, Surreal, Uuid } from "surrealdb";
3
3
  import { Accessor, JSX } from "solid-js";
4
4
  import { GenericModel, GenericSchema, SchemaStructure } from "@spooky/query-builder";
@@ -120,6 +120,34 @@ interface UseSyncStatus {
120
120
  declare function useSyncStatus(): UseSyncStatus;
121
121
  //# sourceMappingURL=use-sync-status.d.ts.map
122
122
  //#endregion
123
+ //#region src/lib/use-storage-status.d.ts
124
+ interface UseStorageStatus {
125
+ /** Full durability snapshot; updates reactively. */
126
+ health: Accessor<StorageHealth$1>;
127
+ /** `'unknown'` | `'persistent'` | `'memory'`. */
128
+ status: Accessor<StorageHealthStatus$1>;
129
+ /** `true` when the local store survives a reload. */
130
+ isPersistent: Accessor<boolean>;
131
+ /**
132
+ * `true` only when durable storage was requested and could NOT be opened, so
133
+ * the dataset is sitting in RAM and local writes die on reload. Drive a
134
+ * warning off this, not off `status`: a store configured as in-memory reports
135
+ * `'memory'` too, and that is a choice rather than a problem.
136
+ */
137
+ isMemoryFallback: Accessor<boolean>;
138
+ }
139
+ /**
140
+ * Observe how durable the LOCAL cache is, for a "no local storage" warning.
141
+ *
142
+ * Under `localEngine: 'sqlite'` with `store: 'indexeddb'` the durable store is
143
+ * the OPFS SAHPool VFS, and only ONE client per bucket can hold it open: a
144
+ * second tab of the same app cannot get it and runs in memory instead (the
145
+ * engine retries first, so a closing tab's lock is usually waited out). Must be
146
+ * used within a `<Sp00kyProvider>`.
147
+ */
148
+ declare function useStorageStatus(): UseStorageStatus;
149
+ //# sourceMappingURL=use-storage-status.d.ts.map
150
+ //#endregion
123
151
  //#region src/lib/use-crdt-field.d.ts
124
152
  declare function useCrdtField(table: string, recordId: () => string | undefined, field: string, fallbackText?: () => string | undefined): Accessor<CrdtField | null>;
125
153
  //# sourceMappingURL=use-crdt-field.d.ts.map
@@ -338,9 +366,17 @@ declare class SyncedDb<S extends SchemaStructure$1> {
338
366
  * components; this is the imperative escape hatch.
339
367
  */
340
368
  subscribeToSyncHealth(cb: (health: SyncHealth$1) => void): () => void;
369
+ /** Current local-store durability snapshot. See {@link useStorageStatus}. */
370
+ get storageHealth(): StorageHealth$1;
371
+ /**
372
+ * Observe local-store durability. Fires immediately with the current snapshot
373
+ * and again on change. Prefer the `useStorageStatus` hook in components; this
374
+ * is the imperative escape hatch.
375
+ */
376
+ subscribeToStorageHealth(cb: (health: StorageHealth$1) => void): () => void;
341
377
  bucket<B extends BucketNames<S>>(name: B): BucketHandle;
342
378
  getBucketConfig(name: string): BucketDefinitionSchema | undefined;
343
379
  }
344
380
  //#endregion
345
- export { CacheStrategy, type FileUploadResult, type GenericModel, type GenericSchema, type GetCardinality, type GetTable, InferModel, type InferRelatedModelFromMetadata, InferRelationshipsFromConst, InferSchemaFromConst, type Model, type ModelPayload, type PreloadOptions, type PreloadRefresh, ProvisionOptions, type QueryInfo, type QueryModifier, type QueryModifierBuilder, type QueryResult, RecordId, RelatedFieldsTableScoped, type RelationshipDefinition, RelationshipField, type RelationshipsMetadata, Sp00kyProvider, type Sp00kyProviderProps, type SyncHealth, type SyncHealthConfig, type SyncHealthStatus, SyncedDb, SyncedDbConfig, type TableModel, type TableNames, type UseAppRelease, type UseAppReleaseOptions, type UseDownloadFileOptions, type UseDownloadFileResult, type UseFeatureFlag, type UseSyncStatus, Uuid, WithRelated, WithRelatedMany, createPreload, useAppRelease, useCrdtField, useDb, useDownloadFile, useFeatureFlag, useFileUpload, useQuery, useSyncStatus };
381
+ export { CacheStrategy, type FileUploadResult, type GenericModel, type GenericSchema, type GetCardinality, type GetTable, InferModel, type InferRelatedModelFromMetadata, InferRelationshipsFromConst, InferSchemaFromConst, type Model, type ModelPayload, type PreloadOptions, type PreloadRefresh, ProvisionOptions, type QueryInfo, type QueryModifier, type QueryModifierBuilder, type QueryResult, RecordId, RelatedFieldsTableScoped, type RelationshipDefinition, RelationshipField, type RelationshipsMetadata, Sp00kyProvider, type Sp00kyProviderProps, type StorageHealth, type StorageHealthStatus, type SyncHealth, type SyncHealthConfig, type SyncHealthStatus, SyncedDb, SyncedDbConfig, type TableModel, type TableNames, type UseAppRelease, type UseAppReleaseOptions, type UseDownloadFileOptions, type UseDownloadFileResult, type UseFeatureFlag, type UseStorageStatus, type UseSyncStatus, Uuid, WithRelated, WithRelatedMany, createPreload, useAppRelease, useCrdtField, useDb, useDownloadFile, useFeatureFlag, useFileUpload, useQuery, useStorageStatus, useSyncStatus };
346
382
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/lib/models.ts","../../../src/types/index.ts","../../../src/lib/use-query.ts","../../../src/lib/create-preload.ts","../../../src/lib/use-sync-status.ts","../../../src/lib/use-crdt-field.ts","../../../src/lib/use-feature-flag.ts","../../../src/lib/use-app-release.ts","../../../src/lib/use-file-upload.ts","../../../src/lib/use-download-file.ts","../../../src/lib/Sp00kyProvider.ts","../../../src/lib/context.ts","../../../src/index.ts"],"sourcesContent":[],"mappings":"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AA,CAAA;EAIL,QAAA,ESEA,GAAA,CAAI,OTFS;AAKzB;AAAgC,iBSAhB,cTAgB,CAAA,USAS,eTAT,CAAA,CAAA,KAAA,ESCvB,mBTDuB,CSCH,CTDG,CAAA,CAAA,ESE7B,GAAA,CAAI,OTFyB;;;;iBUlBhB,gBAAgB,oBAAoB,SAAS;;;;;AVkBjD,KWsDA,iBXtDoB,CAAA,eWuDf,iBXvDe,EAAA,kBWwDZ,UXxDY,CWwDD,MXxDC,CAAA,EAAA,cWyDhB,4BXzDgB,CWyDa,MXzDb,EWyDqB,SXzDrB,CAAA,CAAA,GW0D5B,eX1D4B,CW0DZ,MX1DY,EW0DJ,SX1DI,EW0DO,KX1DP,CAAA;AAAA,KW4DpB,wBX5DoB,CAAA,eW6Df,iBX7De,EAAA,kBW8DZ,UX9DY,CW8DD,MX9DC,CAAA,EAAA,sBW+DR,4BX/DQ,CW+DqB,MX/DrB,EW+D6B,SX/D7B,CAAA,GWgE5B,4BXhE4B,CWgEC,MXhED,EWgES,SXhET,CAAA,CAAA,GAAA,QWkExB,aXlEmC,GAAA;EACxB,EAAA,EWkEX,iBXlEW,CWkEO,MXlEP,EWkEe,SXlEf,EWkE0B,CXlE1B,CAAA,CAAA,IAAA,CAAA;EAAX,aAAA,EWmEW,gBXnEX;EAAoC,WAAA,EWoE3B,iBXpE2B,CWoET,MXpES,EWoED,SXpEC,EWoEU,CXpEV,CAAA,CAAA,aAAA,CAAA;;AAApB,KWwEZ,UXxEY,CAAA,eWyEP,iBXzEO,EAAA,kBW0EJ,UX1EI,CW0EO,MX1EP,CAAA,EAAA,sBW2EA,wBX3EA,CW2EyB,MX3EzB,EW2EiC,SX3EjC,CAAA,CAAA,GW4EpB,WX5EoB,CW4ER,MX5EQ,EW4EA,SX5EA,EW4EW,aX5EX,EAAA,IAAA,CAAA;AAAU,KW8EtB,WX9EsB,CAAA,cAAA,MAAA,EAAA,sBW8EkC,gBX9ElC,GAAA,CAAA,CAAA,CAAA,GAAA,QW+E1B,KXxEI,GWwEI,IXxEJ,CWwES,oBXxEkB,EAAA,eAAA,CAAA,GAAA;EAAA,aAAA,EWyEpB,aXzEoB;;AACZ,KW4Ef,eX5Ee,CAAA,cAAA,MAAA,EAAA,sBW4E6C,gBX5E7C,GAAA,CAAA,CAAA,CAAA,GAAA,QW6EnB,KX7EQ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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/lib/models.ts","../../../src/types/index.ts","../../../src/lib/use-query.ts","../../../src/lib/create-preload.ts","../../../src/lib/use-sync-status.ts","../../../src/lib/use-storage-status.ts","../../../src/lib/use-crdt-field.ts","../../../src/lib/use-feature-flag.ts","../../../src/lib/use-app-release.ts","../../../src/lib/use-file-upload.ts","../../../src/lib/use-download-file.ts","../../../src/lib/Sp00kyProvider.ts","../../../src/lib/context.ts","../../../src/index.ts"],"sourcesContent":[],"mappings":"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Z,OAAA,CAAA,EAAA,CAAA,EAAA,EWIK,QXJO,CWIE,CXJF,CAAA,EAAA,GAAA,IAAA;EAAA;;;;;;;ACCxB;EAOiB,OAAA,CAAA,EAAA,CAAA,EAAA,EUKA,QVLA,CUKS,CVLT,CAAA,EAAA,GUKgB,OVLhB,CAAA,IAAA,CAAA;EAIL,QAAA,EUEA,GAAA,CAAI,OVFS;AAKzB;AAAgC,iBUAhB,cVAgB,CAAA,UUAS,eVAT,CAAA,CAAA,KAAA,EUCvB,mBVDuB,CUCH,CVDG,CAAA,CAAA,EUE7B,GAAA,CAAI,OVFyB;;;;iBWlBhB,gBAAgB,oBAAoB,SAAS;;;;;AXkBlB,KYyD/B,iBZzD+B,CAAA,eY0D1B,iBZ1D0B,EAAA,kBY2DvB,UZ3DuB,CY2DZ,MZ3DY,CAAA,EAAA,cY4D3B,4BZ5D2B,CY4DE,MZ5DF,EY4DU,SZ5DV,CAAA,CAAA,GY6DvC,eZ7DuC,CY6DvB,MZ7DuB,EY6Df,SZ7De,EY6DJ,KZ7DI,CAAA;AACxB,KY8DP,wBZ9DO,CAAA,eY+DF,iBZ/DE,EAAA,kBYgEC,UZhED,CYgEY,MZhEZ,CAAA,EAAA,sBYiEK,4BZjEL,CYiEkC,MZjElC,EYiE0C,SZjE1C,CAAA,GYkEf,4BZlEe,CYkEc,MZlEd,EYkEsB,SZlEtB,CAAA,CAAA,GAAA,QYoEX,aZpEA,GAAA;EAAoC,EAAA,EYqEpC,iBZrEoC,CYqElB,MZrEkB,EYqEV,SZrEU,EYqEC,CZrED,CAAA,CAAA,IAAA,CAAA;EAAG,aAAA,EYsE5B,gBZtE4B;EAAZ,WAAA,EYuElB,iBZvEkB,CYuEA,MZvEA,EYuEQ,SZvER,EYuEmB,CZvEnB,CAAA,CAAA,aAAA,CAAA;GAAD;AAOtB,KYoEA,UZpEA,CAAA,eYqEK,iBZrEsB,EAAA,kBYsEnB,UZtEmB,CYsER,MZtEQ,CAAA,EAAA,sBYuEf,wBZvEe,CYuEU,MZvEV,EYuEkB,SZvElB,CAAA,CAAA,GYwEnC,WZxEmC,CYwEvB,MZxEuB,EYwEf,SZxEe,EYwEJ,aZxEI,EAAA,IAAA,CAAA;AAAA,KY0E3B,WZ1E2B,CAAA,cAAA,MAAA,EAAA,sBY0E6B,gBZ1E7B,GAAA,CAAA,CAAA,CAAA,G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"}
package/dist/index.js CHANGED
@@ -172,6 +172,29 @@ function useSyncStatus() {
172
172
  };
173
173
  }
174
174
 
175
+ //#endregion
176
+ //#region src/lib/use-storage-status.ts
177
+ /**
178
+ * Observe how durable the LOCAL cache is, for a "no local storage" warning.
179
+ *
180
+ * Under `localEngine: 'sqlite'` with `store: 'indexeddb'` the durable store is
181
+ * the OPFS SAHPool VFS, and only ONE client per bucket can hold it open: a
182
+ * second tab of the same app cannot get it and runs in memory instead (the
183
+ * engine retries first, so a closing tab's lock is usually waited out). Must be
184
+ * used within a `<Sp00kyProvider>`.
185
+ */
186
+ function useStorageStatus() {
187
+ const db = useDb();
188
+ const [health, setHealth] = createSignal(db.storageHealth);
189
+ onCleanup(db.subscribeToStorageHealth(setHealth));
190
+ return {
191
+ health,
192
+ status: () => health().status,
193
+ isPersistent: () => health().status === "persistent",
194
+ isMemoryFallback: () => health().fallback
195
+ };
196
+ }
197
+
175
198
  //#endregion
176
199
  //#region src/lib/use-crdt-field.ts
177
200
  function useCrdtField(table, recordId, field, fallbackText) {
@@ -723,6 +746,20 @@ var SyncedDb = class {
723
746
  if (!this.sp00ky) throw new Error("SyncedDb not initialized");
724
747
  return this.sp00ky.subscribeToSyncHealth(cb);
725
748
  }
749
+ /** Current local-store durability snapshot. See {@link useStorageStatus}. */
750
+ get storageHealth() {
751
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
752
+ return this.sp00ky.storageHealth;
753
+ }
754
+ /**
755
+ * Observe local-store durability. Fires immediately with the current snapshot
756
+ * and again on change. Prefer the `useStorageStatus` hook in components; this
757
+ * is the imperative escape hatch.
758
+ */
759
+ subscribeToStorageHealth(cb) {
760
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
761
+ return this.sp00ky.subscribeToStorageHealth(cb);
762
+ }
726
763
  bucket(name) {
727
764
  if (!this.sp00ky) throw new Error("SyncedDb not initialized");
728
765
  return this.sp00ky.bucket(name);
@@ -733,5 +770,5 @@ var SyncedDb = class {
733
770
  };
734
771
 
735
772
  //#endregion
736
- export { RecordId, Sp00kyProvider, SyncedDb, Uuid, createPreload, useAppRelease, useCrdtField, useDb, useDownloadFile, useFeatureFlag, useFileUpload, useQuery, useSyncStatus };
773
+ export { RecordId, Sp00kyProvider, SyncedDb, Uuid, createPreload, useAppRelease, useCrdtField, useDb, useDownloadFile, useFeatureFlag, useFileUpload, useQuery, useStorageStatus, useSyncStatus };
737
774
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/lib/context.ts","../src/lib/use-query.ts","../src/lib/create-preload.ts","../src/lib/use-sync-status.ts","../src/lib/use-crdt-field.ts","../src/lib/use-feature-flag.ts","../src/lib/use-app-release.ts","../src/lib/use-file-upload.ts","../src/lib/use-download-file.ts","../src/lib/Sp00kyProvider.ts","../src/index.ts"],"sourcesContent":["import { createContext, useContext } from 'solid-js';\nimport type { SchemaStructure } from '@spooky/query-builder';\nimport type { SyncedDb } from '../index';\n\nexport const Sp00kyContext = createContext<SyncedDb<any> | undefined>();\n\nexport function useDb<S extends SchemaStructure>(): SyncedDb<S> {\n const db = useContext(Sp00kyContext);\n if (!db) {\n throw new Error('useDb must be used within a <Sp00kyProvider>. Wrap your app in <Sp00kyProvider config={...}>.');\n }\n return db as SyncedDb<S>;\n}\n","import type {\n ColumnSchema,\n FinalQuery,\n SchemaStructure,\n TableNames,\n QueryResult,\n} from '@spooky-sync/query-builder';\nimport { createEffect, createSignal, onCleanup, useContext } from 'solid-js';\nimport { createStore, reconcile } from 'solid-js/store';\nimport { SyncedDb } from '..';\nimport type { Sp00kyQueryResultPromise } from '@spooky-sync/core';\nimport { Sp00kyContext } from './context';\n\ntype QueryArg<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n> =\n | FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>\n | (() =>\n | FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>\n | null\n | undefined);\n\ntype QueryOptions = {\n enabled?: () => boolean;\n /**\n * Tear down the query (remote `_00_query` view + local WASM view) when this\n * hook is disposed and no other subscriber remains, instead of keeping it\n * resident for cheap re-subscription. Use for viewport-windowed lists that\n * mount/unmount a query per scroll window and want off-screen windows\n * cancelled. Trade-off: scrolling back to a torn-down window re-registers it.\n */\n deregisterOnCleanup?: boolean;\n};\n\n// Overload: context-based (no explicit db)\nexport function useQuery<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n TData = QueryResult<S, TableName, RelatedFields, IsOne> | null,\n>(\n finalQuery: QueryArg<S, TableName, T, RelatedFields, IsOne>,\n options?: QueryOptions,\n): {\n data: () => TData | undefined;\n error: () => Error | undefined;\n isLoading: () => boolean;\n isFetching: () => boolean;\n isSettled: () => boolean;\n};\n\n// Overload: explicit db (backward-compatible)\nexport function useQuery<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n TData = QueryResult<S, TableName, RelatedFields, IsOne> | null,\n>(\n db: SyncedDb<S>,\n finalQuery: QueryArg<S, TableName, T, RelatedFields, IsOne>,\n options?: QueryOptions,\n): {\n data: () => TData | undefined;\n error: () => Error | undefined;\n isLoading: () => boolean;\n isFetching: () => boolean;\n isSettled: () => boolean;\n};\n\n// Implementation\nexport function useQuery<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends {\n columns: Record<string, ColumnSchema>;\n },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n TData = QueryResult<S, TableName, RelatedFields, IsOne> | null,\n>(\n dbOrQuery:\n | SyncedDb<S>\n | QueryArg<S, TableName, T, RelatedFields, IsOne>,\n queryOrOptions?:\n | QueryArg<S, TableName, T, RelatedFields, IsOne>\n | QueryOptions,\n maybeOptions?: QueryOptions,\n) {\n let db: SyncedDb<S>;\n let finalQuery: QueryArg<S, TableName, T, RelatedFields, IsOne>;\n let options: QueryOptions | undefined;\n\n if (dbOrQuery instanceof SyncedDb) {\n // Explicit db overload: useQuery(db, query, options?)\n db = dbOrQuery;\n finalQuery = queryOrOptions as QueryArg<S, TableName, T, RelatedFields, IsOne>;\n options = maybeOptions;\n } else {\n // Context-based overload: useQuery(query, options?)\n const contextDb = useContext(Sp00kyContext);\n if (!contextDb) {\n throw new Error(\n 'useQuery: No db argument provided and no Sp00kyContext found. ' +\n 'Either pass a SyncedDb instance or wrap your app in <Sp00kyProvider>.'\n );\n }\n db = contextDb as SyncedDb<S>;\n finalQuery = dbOrQuery;\n options = queryOrOptions as QueryOptions | undefined;\n }\n\n const [error, setError] = createSignal<Error | undefined>(undefined);\n const [isFetched, setIsFetched] = createSignal(false);\n const [isFetching, setIsFetching] = createSignal(false);\n // Results live in a store (not a signal) so consecutive live-query emissions\n // are merged with `reconcile`: unchanged rows keep their object identity and\n // changed rows are mutated in place. That keeps Solid's reference-keyed `<For>`\n // rows — and any `useQuery` subscriptions mounted inside them — alive across\n // updates, instead of tearing every row down and re-registering its queries.\n const [state, setState] = createStore<{ value: TData | undefined }>({ value: undefined });\n // `reconcile` (below) merges each emission into `state.value` IN PLACE, keeping\n // the array reference stable. That's ideal for granular per-row reactivity, but\n // it means a *coarse* reader of `data()` — `<For each={data()}>`, or an effect\n // that copies the whole array elsewhere (e.g. GameList's windowed store) — is\n // NOT re-run when rows are added/removed/reordered within a same-length result\n // (the classic case: deleting a row in a windowed list shifts the next one in,\n // so length stays 50 and the array ref never changes). Bump a version on every\n // emission and read it in `data()` so every consumer re-runs on any change while\n // reconcile still preserves row identity underneath.\n const [version, setVersion] = createSignal(0);\n const data = () => {\n version();\n return state.value;\n };\n\n let prevQueryString: string | undefined;\n // Monotonic token for each subscription generation. Bumped whenever the query\n // identity changes or the hook is disposed, so a slow async `initQuery`\n // continuation can detect it was superseded and avoid installing a stale (and\n // leaked) subscription.\n let runId = 0;\n let activeUnsub: (() => void) | undefined;\n // The hash of the currently-installed subscription, for opt-in deregister on\n // dispose (see `deregisterOnCleanup`).\n let activeHash: string | undefined;\n\n const teardownActive = () => {\n activeUnsub?.();\n activeUnsub = undefined;\n };\n\n const sp00ky = db.getSp00ky();\n\n const initQuery = async (\n query: FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>,\n myRun: number\n ) => {\n const { hash } = await query.run();\n // A newer query identity (or disposal) won the race while we awaited run().\n if (myRun !== runId) return;\n activeHash = hash;\n setError(undefined);\n\n let isFirstCall = true;\n const unsub = await sp00ky.subscribe(\n hash,\n (e) => {\n const queryData = (query.isOne ? e[0] : e) as TData;\n // Merge into the store by record id: unchanged rows keep their identity,\n // changed rows update in place. Replaces wholesale for `one()`/null.\n // Time the reconcile → report as the \"frontend\" phase for DevTools/MCP.\n const reconcileStart = performance.now();\n setState('value', reconcile(queryData as any, { key: 'id' }));\n // Notify coarse `data()` readers (see the `version` note above): reconcile\n // keeps the array ref stable, so this is what re-runs `<For>`/copy-effects\n // on add/remove/reorder.\n setVersion((v) => v + 1);\n sp00ky.reportFrontendTiming(hash, performance.now() - reconcileStart);\n // The first (immediate) callback with no data likely means the local DB\n // hasn't synced yet — don't mark as fetched so UI shows loading state\n const hasData = query.isOne ? queryData !== null && queryData !== undefined : (e as any[]).length > 0;\n if (!isFirstCall || hasData) {\n setIsFetched(true);\n }\n isFirstCall = false;\n },\n { immediate: true }\n );\n\n // Mirror the query's fetch status so the UI can show a \"loading more\"\n // state while the sync engine pulls missing records in the background.\n const unsubStatus = sp00ky.subscribeQueryStatus(\n hash,\n (status) => setIsFetching(status === 'fetching'),\n { immediate: true }\n );\n\n const teardown = () => {\n unsub();\n unsubStatus();\n };\n\n // Superseded while awaiting subscribe()? Don't leak — tear down immediately.\n if (myRun !== runId) {\n teardown();\n return;\n }\n activeUnsub = teardown;\n };\n\n createEffect(() => {\n const enabled = options?.enabled?.() ?? true;\n\n // If disabled, clear error and don't run query\n if (!enabled) {\n setError(undefined);\n return;\n }\n\n // Init Query\n const query = typeof finalQuery === 'function' ? finalQuery() : finalQuery;\n if (!query) {\n return;\n }\n\n // Dedup on the query's stable identity hash (cyrb53 of surql + vars), not a\n // full `JSON.stringify` of the FinalQuery (which walks the whole schema +\n // inner query on every reactive tick and isn't guaranteed stable). When the\n // identity is unchanged we keep the existing subscription alive.\n const queryString = String(query.hash);\n if (queryString === prevQueryString) {\n return;\n }\n prevQueryString = queryString;\n\n // New query identity → supersede the previous subscription and start fresh.\n const myRun = ++runId;\n teardownActive();\n setIsFetched(false);\n initQuery(query, myRun);\n });\n\n // Tear down the live subscription when the hook's owner is disposed. Registered\n // on the hook (component) scope rather than inside the effect, so an effect\n // re-run that early-returns (unchanged query) doesn't clean up the still-valid\n // subscription. Bumping runId also invalidates any in-flight initQuery.\n onCleanup(() => {\n runId++;\n teardownActive();\n // Opt-in: cancel the query once this hook (its last subscriber) is gone.\n // teardownActive() above already removed this hook's callback, so\n // deregisterQuery's refcount guard sees the true remaining-subscriber count.\n if (options?.deregisterOnCleanup && activeHash) {\n sp00ky.deregisterQuery(activeHash);\n }\n });\n\n const isLoading = () => {\n return !isFetched() && error() === undefined;\n };\n\n // True once the query has delivered a result AND no fetch cycle is in flight\n // (registration + initial sync included — the core holds `fetching` across\n // the whole registration and flushes debounced results before flipping back\n // to idle). While settled, the results are authoritative: a windowed query\n // returning fewer rows than its LIMIT really is the end of the list, so\n // virtualized lists may size themselves to it without the scrollbar jumping\n // when a still-syncing window transiently reports short. Resets to false\n // whenever the query identity changes.\n const isSettled = () => isFetched() && !isFetching();\n\n return {\n data,\n error,\n isLoading,\n isFetching,\n isSettled,\n };\n}\n","import type {\n ColumnSchema,\n FinalQuery,\n SchemaStructure,\n TableNames,\n} from '@spooky-sync/query-builder';\nimport { createEffect, useContext } from 'solid-js';\nimport { SyncedDb } from '..';\nimport type { Sp00kyQueryResultPromise, PreloadOptions as CorePreloadOptions } from '@spooky-sync/core';\nimport { Sp00kyContext } from './context';\n\ntype PreloadArg<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n> =\n | FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>\n | (() =>\n | FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>\n | null\n | undefined);\n\ntype PreloadOptions = CorePreloadOptions & {\n /** Only preload while this returns true (defaults to always). */\n enabled?: () => boolean;\n};\n\n// Overload: context-based (no explicit db)\nexport function createPreload<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n>(\n finalQuery: PreloadArg<S, TableName, T, RelatedFields, IsOne>,\n options?: PreloadOptions,\n): void;\n\n// Overload: explicit db\nexport function createPreload<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n>(\n db: SyncedDb<S>,\n finalQuery: PreloadArg<S, TableName, T, RelatedFields, IsOne>,\n options?: PreloadOptions,\n): void;\n\n/**\n * Reactive, fire-and-forget prewarm. Resolves the query (calling it if it's a\n * function so it tracks reactive deps), dedupes on the query's stable identity\n * hash, and warms it into the local cache via `db.preload`. No subscription and\n * no cleanup: preload registers nothing that needs tearing down.\n *\n * Typical use: inside a list row, preload the detail query the user is likely\n * to open next, so navigation paints from cache instead of the network.\n */\nexport function createPreload<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n>(\n dbOrQuery: SyncedDb<S> | PreloadArg<S, TableName, T, RelatedFields, IsOne>,\n queryOrOptions?: PreloadArg<S, TableName, T, RelatedFields, IsOne> | PreloadOptions,\n maybeOptions?: PreloadOptions,\n): void {\n let db: SyncedDb<S>;\n let finalQuery: PreloadArg<S, TableName, T, RelatedFields, IsOne>;\n let options: PreloadOptions | undefined;\n\n if (dbOrQuery instanceof SyncedDb) {\n db = dbOrQuery;\n finalQuery = queryOrOptions as PreloadArg<S, TableName, T, RelatedFields, IsOne>;\n options = maybeOptions;\n } else {\n const contextDb = useContext(Sp00kyContext);\n if (!contextDb) {\n throw new Error(\n 'createPreload: No db argument provided and no Sp00kyContext found. ' +\n 'Either pass a SyncedDb instance or wrap your app in <Sp00kyProvider>.',\n );\n }\n db = contextDb as SyncedDb<S>;\n finalQuery = dbOrQuery;\n options = queryOrOptions as PreloadOptions | undefined;\n }\n\n let prevHash: number | undefined;\n\n createEffect(() => {\n if (!(options?.enabled?.() ?? true)) return;\n\n const query = typeof finalQuery === 'function' ? finalQuery() : finalQuery;\n if (!query) return;\n\n // Dedupe on the query's stable identity hash so a reactive re-run with an\n // unchanged query doesn't refetch (the core also dedupes per session).\n if (query.hash === prevHash) return;\n prevHash = query.hash;\n\n void db.getSp00ky().preload(query, { refresh: options?.refresh, staleTime: options?.staleTime });\n });\n}\n","import { createSignal, onCleanup, type Accessor } from 'solid-js';\nimport { useDb } from './context';\nimport type { SyncHealth, SyncHealthStatus } from '@spooky-sync/core';\n\nexport interface UseSyncStatus {\n /** Full health snapshot; updates reactively on every transition. */\n health: Accessor<SyncHealth>;\n /** `'healthy'` | `'degraded'`. */\n status: Accessor<SyncHealthStatus>;\n isHealthy: Accessor<boolean>;\n /** `true` once sync has failed for a sustained run — drive a banner off this. */\n isDegraded: Accessor<boolean>;\n /** `true` once at least one sync round has succeeded this session. */\n everConnected: Accessor<boolean>;\n /**\n * `true` only for a real lost connection: degraded AFTER a first successful\n * sync. Stays `false` during the initial \"connecting\" phase (degraded but\n * never reached the server yet), so an indicator can show nothing until the\n * app has actually connected once.\n */\n isOffline: Accessor<boolean>;\n}\n\n/**\n * Observe sync health for a \"can't reach the server\" banner / indicator.\n *\n * Backed by `db.subscribeToSyncHealth`. Individual sync failures (a transient\n * remote 500 on query registration, a dropped socket) are absorbed by the\n * retry and never flip this; `isDegraded()` only goes true once failures\n * persist for the configured number of consecutive rounds (sp00ky core config\n * `syncHealth.degradeAfterConsecutiveFailures`, default 3), and flips back on\n * the next successful round. Must be used within a `<Sp00kyProvider>`.\n */\nexport function useSyncStatus(): UseSyncStatus {\n const db = useDb();\n // subscribeToSyncHealth fires synchronously with the current status, so the\n // signal is correct from first read; the initial value just avoids a flash.\n const [health, setHealth] = createSignal<SyncHealth>(db.syncHealth);\n const unsub = db.subscribeToSyncHealth(setHealth);\n onCleanup(unsub);\n\n return {\n health,\n status: () => health().status,\n isHealthy: () => health().status === 'healthy',\n isDegraded: () => health().status === 'degraded',\n everConnected: () => health().everConnected,\n isOffline: () => health().status === 'degraded' && health().everConnected,\n };\n}\n","import { createEffect, createSignal, onCleanup, useContext, type Accessor } from 'solid-js';\nimport { Sp00kyContext } from './context';\nimport type { CrdtField } from '@spooky-sync/core';\n\nexport function useCrdtField(\n table: string,\n recordId: () => string | undefined,\n field: string,\n fallbackText?: () => string | undefined,\n): Accessor<CrdtField | null> {\n const db = useContext(Sp00kyContext);\n if (!db) {\n throw new Error('useCrdtField must be used within a <Sp00kyProvider>');\n }\n\n const [crdtField, setCrdtField] = createSignal<CrdtField | null>(null);\n let currentId: string | undefined;\n let initialized = false;\n\n createEffect(() => {\n const id = recordId();\n\n // Skip if the ID hasn't changed (but allow the first non-undefined value through)\n if (initialized && id === currentId) return;\n\n // Close previous field\n if (currentId && crdtField()) {\n db.getSp00ky().closeCrdtField(table, currentId, field);\n setCrdtField(null);\n }\n\n currentId = id;\n initialized = true;\n\n if (!id) return;\n\n const sp00ky = db.getSp00ky();\n const text = fallbackText?.();\n sp00ky\n .openCrdtField(table, id, field, text)\n .then((cf) => {\n if (currentId === id) {\n setCrdtField(cf);\n }\n })\n .catch((err) => {\n // Silent rejections here leave the consumer's `Show when={field()}`\n // permanently stuck on its fallback (typically a static `<p>` with\n // no editing UI), with no error trail. Surface the failure so the\n // root cause (missing `@crdt` annotation, schema codegen drift,\n // local DB query failure, etc.) is visible in the console instead\n // of silently breaking collaborative fields.\n console.error(\n `[useCrdtField] Failed to open CRDT field ${table}.${field} on ${id}:`,\n err,\n );\n });\n });\n\n onCleanup(() => {\n if (currentId && crdtField()) {\n db.getSp00ky().closeCrdtField(table, currentId, field);\n setCrdtField(null);\n }\n });\n\n return crdtField;\n}\n","import { createSignal, onCleanup, type Accessor } from 'solid-js';\nimport { useDb } from './context';\nimport type { FeatureFlagOptions } from '@spooky-sync/core';\n\nexport interface UseFeatureFlag {\n variant: Accessor<string | undefined>;\n payload: Accessor<unknown | undefined>;\n enabled: Accessor<boolean>;\n}\n\n/**\n * Subscribe to a feature flag for the currently authenticated user.\n *\n * Returns three Solid accessors that update reactively whenever the\n * server-materialized assignment in `_00_user_feature` changes. Backed by\n * the same SSP + sync pipeline that powers `useQuery`, so toggling a flag\n * via `spky flag enable <key>` propagates to the UI without a refresh.\n *\n * `enabled()` is `true` when the resolved variant exists and is not 'off'.\n * For multi-variant flags, prefer `variant()` directly.\n */\nexport function useFeatureFlag(\n key: string,\n options?: FeatureFlagOptions,\n): UseFeatureFlag {\n const db = useDb();\n const handle = db.getSp00ky().feature(key, options);\n\n const [variant, setVariant] = createSignal<string | undefined>(handle.variant());\n const [payload, setPayload] = createSignal<unknown | undefined>(handle.payload());\n\n const unsub = handle.subscribe((s) => {\n setVariant(s.variant ?? options?.fallback);\n setPayload(s.payload);\n });\n\n onCleanup(() => {\n unsub();\n handle.close();\n });\n\n return {\n variant,\n payload,\n enabled: () => {\n const v = variant();\n return v !== undefined && v !== 'off';\n },\n };\n}\n","import { createSignal, onCleanup, type Accessor } from 'solid-js';\nimport { useDb } from './context';\nimport { semverGt, type AppReleaseOptions, type AppReleaseSnapshot } from '@spooky-sync/core';\n\nexport interface UseAppReleaseOptions extends AppReleaseOptions {\n /** App name from sp00ky.yml, e.g. `web`. */\n app: string;\n /**\n * The running build's version (X.Y.Z), typically baked in at build time\n * (e.g. a vite `define` from package.json). `updateAvailable()` is true when\n * the announced release is semver-newer than this.\n */\n currentVersion: string;\n}\n\nexport interface UseAppRelease {\n /** Latest announced version for the app, or undefined when no row exists. */\n latestVersion: Accessor<string | undefined>;\n /** Announced version is semver-newer than the running build. */\n updateAvailable: Accessor<boolean>;\n /** The newer release asks clients to update/reload without prompting. */\n mandatory: Accessor<boolean>;\n /** The newer release asks reloads to clear service-worker caches first. */\n cacheBust: Accessor<boolean>;\n /**\n * Reload onto the announced release. Plain `location.reload()` normally;\n * when the release is flagged cache-bust, CacheStorage is cleared, the\n * service-worker registration is nudged to update, and navigation carries a\n * `?cb=` token to punch through intermediary caches. The service worker is\n * deliberately NOT unregistered: navigating while still controlled by a\n * just-unregistered worker strands subresource fetches on the dead worker\n * and the page hangs until a manual reload.\n */\n reload: () => Promise<void>;\n}\n\nasync function reloadForSnapshot(snapshot: AppReleaseSnapshot): Promise<void> {\n if (typeof window === 'undefined') return;\n if (snapshot.cacheBust) {\n try {\n if (window.caches) {\n const keys = await window.caches.keys();\n await Promise.all(keys.map((k) => window.caches.delete(k)));\n }\n if (navigator.serviceWorker) {\n const regs = await navigator.serviceWorker.getRegistrations();\n for (const r of regs) r.update().catch(() => {});\n }\n window.location.href = window.location.pathname + '?cb=' + Date.now();\n return;\n } catch {\n /* fall through to a plain reload */\n }\n }\n window.location.reload();\n}\n\n/**\n * Observe the app's announced release (`_00_app_release:<app>`, written by\n * `spky deploy` / `spky release`) and compare it against the running build.\n *\n * Typical use: mount a small \"new version available — Reload\" notification\n * gated on `updateAvailable()`, auto-invoking `reload()` when `mandatory()`\n * (guard the auto path against reload loops with a per-version marker, since\n * a client can reload while the deploy is still rolling out and land on the\n * old bundle again).\n */\nexport function useAppRelease(options: UseAppReleaseOptions): UseAppRelease {\n const db = useDb();\n const handle = db.getSp00ky().appRelease(options.app, { ttl: options.ttl });\n\n const [snapshot, setSnapshot] = createSignal<AppReleaseSnapshot>(handle.snapshot());\n const unsub = handle.subscribe(setSnapshot);\n\n onCleanup(() => {\n unsub();\n handle.close();\n });\n\n const updateAvailable = () => semverGt(snapshot().version, options.currentVersion);\n\n return {\n latestVersion: () => snapshot().version,\n updateAvailable,\n mandatory: () => updateAvailable() && snapshot().mandatory,\n cacheBust: () => snapshot().cacheBust,\n reload: () => reloadForSnapshot(snapshot()),\n };\n}\n","import { createSignal, onCleanup } from 'solid-js';\nimport type { SchemaStructure, BucketNames } from '@spooky-sync/query-builder';\nimport { fileToUint8Array } from '@spooky-sync/core';\nimport type { SyncedDb } from '../index';\nimport { useDb } from './context';\n\nexport interface FileUploadResult {\n isUploading: () => boolean;\n error: () => Error | null;\n clearError: () => void;\n upload: (path: string, file: File | Blob) => Promise<void>;\n download: (path: string) => Promise<string | null>;\n remove: (path: string) => Promise<void>;\n exists: (path: string) => Promise<boolean>;\n}\n\nexport function useFileUpload<S extends SchemaStructure>(\n bucketName: BucketNames<S>,\n): FileUploadResult;\nexport function useFileUpload<S extends SchemaStructure>(\n db: SyncedDb<S>,\n bucketName: BucketNames<S>,\n): FileUploadResult;\nexport function useFileUpload<S extends SchemaStructure>(\n dbOrBucketName: SyncedDb<S> | BucketNames<S>,\n maybeBucketName?: BucketNames<S>,\n): FileUploadResult {\n let db: SyncedDb<S>;\n let bucketName: BucketNames<S>;\n\n if (typeof dbOrBucketName === 'string') {\n db = useDb<S>();\n bucketName = dbOrBucketName as BucketNames<S>;\n } else {\n db = dbOrBucketName as SyncedDb<S>;\n // oxlint-disable-next-line no-non-null-assertion\n bucketName = maybeBucketName!;\n }\n\n const [isUploading, setIsUploading] = createSignal(false);\n const [error, setError] = createSignal<Error | null>(null);\n\n const objectUrls: string[] = [];\n onCleanup(() => {\n for (const url of objectUrls) {\n URL.revokeObjectURL(url);\n }\n });\n\n const clearError = () => setError(null);\n\n const validate = (file: File | Blob): void => {\n const config = db.getBucketConfig(bucketName as string);\n if (!config) return;\n\n if (config.maxSize !== null && config.maxSize !== undefined && file.size > config.maxSize) {\n const maxMB = (config.maxSize / (1024 * 1024)).toFixed(1);\n throw new Error(`File exceeds maximum size of ${maxMB} MB.`);\n }\n\n if (config.allowedExtensions && config.allowedExtensions.length > 0) {\n const fileName = (file as File).name;\n if (fileName) {\n const ext = fileName.split('.').pop()?.toLowerCase();\n if (!ext || !config.allowedExtensions.includes(ext)) {\n throw new Error(\n `File type not allowed. Accepted: ${config.allowedExtensions.join(', ')}.`\n );\n }\n }\n }\n };\n\n const upload = async (path: string, file: File | Blob): Promise<void> => {\n setError(null);\n try {\n validate(file);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return;\n }\n\n setIsUploading(true);\n try {\n const bytes = await fileToUint8Array(file);\n await db.bucket(bucketName).put(path, bytes);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n } finally {\n setIsUploading(false);\n }\n };\n\n const download = async (path: string): Promise<string | null> => {\n setError(null);\n try {\n const content = await db.bucket(bucketName).get(path);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n objectUrls.push(objectUrl);\n return objectUrl;\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return null;\n }\n };\n\n const remove = async (path: string): Promise<void> => {\n setError(null);\n try {\n await db.bucket(bucketName).delete(path);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n }\n };\n\n const exists = async (path: string): Promise<boolean> => {\n setError(null);\n try {\n return await db.bucket(bucketName).exists(path);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return false;\n }\n };\n\n return {\n isUploading,\n error,\n clearError,\n upload,\n download,\n remove,\n exists,\n };\n}\n","import { createSignal, createEffect, onCleanup, type Accessor } from 'solid-js';\nimport type { SchemaStructure, BucketNames } from '@spooky-sync/query-builder';\nimport type { SyncedDb } from '../index';\nimport { useDb } from './context';\n\nexport interface UseDownloadFileOptions {\n cache?: boolean;\n}\n\nexport interface UseDownloadFileResult {\n url: Accessor<string | null>;\n isLoading: Accessor<boolean>;\n error: Accessor<Error | null>;\n refetch: () => void;\n}\n\ninterface CacheEntry {\n url: string;\n refCount: number;\n}\n\nconst downloadCache = new Map<string, CacheEntry>();\nconst inflightRequests = new Map<string, Promise<string | null>>();\n\nfunction cacheKey(bucket: string, path: string): string {\n return `${bucket}:${path}`;\n}\n\nfunction releaseEntry(key: string): void {\n const entry = downloadCache.get(key);\n if (!entry) return;\n entry.refCount--;\n if (entry.refCount <= 0) {\n URL.revokeObjectURL(entry.url);\n downloadCache.delete(key);\n }\n}\n\nexport function useDownloadFile<S extends SchemaStructure>(\n bucketName: BucketNames<S>,\n path: Accessor<string | null | undefined>,\n options?: UseDownloadFileOptions,\n): UseDownloadFileResult;\nexport function useDownloadFile<S extends SchemaStructure>(\n db: SyncedDb<S>,\n bucketName: BucketNames<S>,\n path: Accessor<string | null | undefined>,\n options?: UseDownloadFileOptions,\n): UseDownloadFileResult;\nexport function useDownloadFile<S extends SchemaStructure>(\n dbOrBucketName: SyncedDb<S> | BucketNames<S>,\n bucketNameOrPath?: BucketNames<S> | Accessor<string | null | undefined>,\n pathOrOptions?: Accessor<string | null | undefined> | UseDownloadFileOptions,\n maybeOptions?: UseDownloadFileOptions,\n): UseDownloadFileResult {\n let db: SyncedDb<S>;\n let bucketName: BucketNames<S>;\n let path: Accessor<string | null | undefined>;\n let options: UseDownloadFileOptions;\n\n if (typeof dbOrBucketName === 'string') {\n db = useDb<S>();\n bucketName = dbOrBucketName as BucketNames<S>;\n path = bucketNameOrPath as Accessor<string | null | undefined>;\n options = (pathOrOptions as UseDownloadFileOptions) ?? {};\n } else {\n db = dbOrBucketName as SyncedDb<S>;\n bucketName = bucketNameOrPath as BucketNames<S>;\n path = pathOrOptions as Accessor<string | null | undefined>;\n options = maybeOptions ?? {};\n }\n\n const useCache = options.cache !== false;\n\n const [url, setUrl] = createSignal<string | null>(null);\n const [isLoading, setIsLoading] = createSignal(false);\n const [error, setError] = createSignal<Error | null>(null);\n\n let currentKey: string | null = null;\n let privateUrl: string | null = null;\n const [refetchSignal, setRefetchSignal] = createSignal(0);\n const refetchTrigger = () => setRefetchSignal((n) => n + 1);\n\n async function doDownload(key: string, filePath: string): Promise<string | null> {\n if (useCache) {\n // Check cache\n const cached = downloadCache.get(key);\n if (cached) {\n cached.refCount++;\n currentKey = key;\n return cached.url;\n }\n\n // Check inflight\n const inflight = inflightRequests.get(key);\n if (inflight) {\n const result = await inflight;\n if (result) {\n const entry = downloadCache.get(key);\n if (entry) {\n entry.refCount++;\n currentKey = key;\n }\n }\n return result;\n }\n\n // Start new download\n const promise = (async () => {\n const content = await db.bucket(bucketName).get(filePath);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n downloadCache.set(key, { url: objectUrl, refCount: 1 });\n return objectUrl;\n })();\n\n inflightRequests.set(key, promise);\n try {\n const result = await promise;\n currentKey = key;\n return result;\n } finally {\n inflightRequests.delete(key);\n }\n } else {\n // No caching — private URL per instance\n const content = await db.bucket(bucketName).get(filePath);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n privateUrl = objectUrl;\n return objectUrl;\n }\n }\n\n function releaseCurrentEntry() {\n if (useCache && currentKey) {\n releaseEntry(currentKey);\n currentKey = null;\n }\n if (!useCache && privateUrl) {\n URL.revokeObjectURL(privateUrl);\n privateUrl = null;\n }\n }\n\n createEffect(() => {\n const filePath = path();\n // Subscribe to refetch signal so effect re-runs\n refetchSignal();\n\n // Release previous entry\n releaseCurrentEntry();\n\n if (!filePath) {\n setUrl(null);\n setIsLoading(false);\n setError(null);\n return;\n }\n\n const key = cacheKey(bucketName as string, filePath);\n\n // Synchronous cache hit\n if (useCache) {\n const cached = downloadCache.get(key);\n if (cached) {\n cached.refCount++;\n currentKey = key;\n setUrl(cached.url);\n setIsLoading(false);\n setError(null);\n return;\n }\n }\n\n let cancelled = false;\n setIsLoading(true);\n setError(null);\n\n doDownload(key, filePath).then(\n (result) => {\n if (!cancelled) {\n setUrl(result);\n setIsLoading(false);\n }\n return undefined;\n },\n (err) => {\n if (!cancelled) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setIsLoading(false);\n }\n },\n );\n\n onCleanup(() => {\n cancelled = true;\n });\n });\n\n onCleanup(() => {\n releaseCurrentEntry();\n });\n\n const refetch = () => {\n // Evict current entry from cache before re-triggering\n if (useCache && currentKey) {\n const entry = downloadCache.get(currentKey);\n if (entry) {\n URL.revokeObjectURL(entry.url);\n downloadCache.delete(currentKey);\n }\n currentKey = null;\n }\n refetchTrigger();\n };\n\n return { url, isLoading, error, refetch };\n}\n","import type { JSX} from 'solid-js';\nimport { createSignal, onMount, createComponent, createMemo, mergeProps } from 'solid-js';\nimport type { SchemaStructure } from '@spooky/query-builder';\nimport type { SyncedDbConfig } from '../types';\nimport { SyncedDb } from '../index';\nimport { Sp00kyContext } from './context';\n\nexport interface Sp00kyProviderProps<S extends SchemaStructure> {\n config: SyncedDbConfig<S>;\n fallback?: JSX.Element;\n onError?: (error: Error) => void;\n onReady?: (db: SyncedDb<S>) => void;\n /**\n * Prewarm data into the local cache before revealing the UI. Runs after\n * `init()`; the `fallback` stays visible until it resolves. Use awaitable\n * `db.preload(...)` calls here to gate first-load on essential data (e.g.\n * config). On warm loads preload returns instantly, so there's no perceptible\n * gate after the first run. Best-effort: a rejection is caught and the UI is\n * revealed anyway.\n */\n preload?: (db: SyncedDb<S>) => Promise<void>;\n children: JSX.Element;\n}\n\nexport function Sp00kyProvider<S extends SchemaStructure>(\n props: Sp00kyProviderProps<S>\n): JSX.Element {\n const merged = mergeProps(\n {\n fallback: undefined as JSX.Element | undefined,\n },\n props\n );\n\n const [db, setDb] = createSignal<SyncedDb<S> | undefined>(undefined);\n\n onMount(async () => {\n try {\n const instance = new SyncedDb<S>(merged.config);\n await instance.init();\n // Gate first-load UI on prewarmed data. Best-effort: never let a preload\n // failure keep the app stuck on the fallback.\n if (merged.preload) {\n try {\n await merged.preload(instance);\n } catch (e) {\n // oxlint-disable-next-line no-console\n console.error('Sp00kyProvider: preload failed; revealing UI anyway', e);\n }\n }\n setDb(() => instance);\n merged.onReady?.(instance);\n } catch (e) {\n const error = e instanceof Error ? e : new Error(String(e));\n if (merged.onError) {\n merged.onError(error);\n } else {\n // oxlint-disable-next-line no-console\n console.error('Sp00kyProvider: Failed to initialize database', error);\n }\n }\n });\n\n const content = createMemo(() => {\n const instance = db();\n if (!instance) return merged.fallback;\n return createComponent(Sp00kyContext.Provider, {\n value: instance,\n get children() {\n return merged.children;\n },\n });\n });\n\n return content as unknown as JSX.Element;\n}\n","import type { SyncedDbConfig } from './types';\nimport {\n Sp00kyClient,\n type Sp00kyQueryResultPromise,\n type AuthService,\n type BucketHandle,\n type UpdateOptions,\n type RunOptions,\n type SyncHealth,\n type PreloadOptions,\n type PreloadRefresh,\n} from '@spooky-sync/core';\n\nimport type {\n GetTable,\n QueryBuilder,\n SchemaStructure,\n TableModel,\n TableNames,\n QueryResult,\n RelatedFieldsMap,\n RelationshipFieldsFromSchema,\n GetRelationship,\n RelatedFieldMapEntry,\n FinalQuery,\n InnerQuery,\n BackendNames,\n BackendRoutes,\n RoutePayload,\n BucketNames,\n BucketDefinitionSchema,\n QueryModifier,\n QueryModifierBuilder,\n QueryInfo,\n RelationshipsMetadata,\n RelationshipDefinition,\n InferRelatedModelFromMetadata,\n GetCardinality,\n} from '@spooky-sync/query-builder';\n\nimport { RecordId, Uuid, type Surreal } from 'surrealdb';\nexport { RecordId, Uuid };\nexport type { Model, GenericModel, GenericSchema, ModelPayload } from './lib/models';\nexport { useQuery } from './lib/use-query';\nexport { createPreload } from './lib/create-preload';\nexport type { PreloadOptions, PreloadRefresh } from '@spooky-sync/core';\nexport { useSyncStatus, type UseSyncStatus } from './lib/use-sync-status';\nexport type { SyncHealth, SyncHealthStatus, SyncHealthConfig } from '@spooky-sync/core';\nexport { useCrdtField } from './lib/use-crdt-field';\nexport { useFeatureFlag, type UseFeatureFlag } from './lib/use-feature-flag';\nexport {\n useAppRelease,\n type UseAppRelease,\n type UseAppReleaseOptions,\n} from './lib/use-app-release';\nexport { useFileUpload, type FileUploadResult } from './lib/use-file-upload';\nexport { useDownloadFile, type UseDownloadFileOptions, type UseDownloadFileResult } from './lib/use-download-file';\nexport { Sp00kyProvider, type Sp00kyProviderProps } from './lib/Sp00kyProvider';\nexport { useDb } from './lib/context';\n\n// export { AuthEventTypes } from \"@spooky-sync/core\"; // TODO: Verify if AuthEventTypes exists in core\n\n\n// Re-export query builder types for convenience\nexport type {\n QueryModifier,\n QueryModifierBuilder,\n QueryInfo,\n RelationshipsMetadata,\n RelationshipDefinition,\n InferRelatedModelFromMetadata,\n GetCardinality,\n GetTable,\n TableModel,\n TableNames,\n QueryResult,\n};\n\nexport type RelationshipField<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n Field extends RelationshipFieldsFromSchema<Schema, TableName>,\n> = GetRelationship<Schema, TableName, Field>;\n\nexport type RelatedFieldsTableScoped<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n RelatedFields extends RelationshipFieldsFromSchema<Schema, TableName> =\n RelationshipFieldsFromSchema<Schema, TableName>,\n> = {\n [K in RelatedFields]: {\n to: RelationshipField<Schema, TableName, K>['to'];\n relatedFields: RelatedFieldsMap;\n cardinality: RelationshipField<Schema, TableName, K>['cardinality'];\n };\n};\n\nexport type InferModel<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n RelatedFields extends RelatedFieldsTableScoped<Schema, TableName>,\n> = QueryResult<Schema, TableName, RelatedFields, true>;\n\nexport type WithRelated<Field extends string, RelatedFields extends RelatedFieldsMap = {}> = {\n [K in Field]: Omit<RelatedFieldMapEntry, 'relatedFields'> & {\n relatedFields: RelatedFields;\n };\n};\n\nexport type WithRelatedMany<Field extends string, RelatedFields extends RelatedFieldsMap = {}> = {\n [K in Field]: {\n to: Field;\n relatedFields: RelatedFields;\n cardinality: 'many';\n };\n};\n\n/**\n * SyncedDb - A thin wrapper around sp00ky-ts for Solid.js integration\n * Delegates all logic to the underlying sp00ky-ts instance\n */\nexport class SyncedDb<S extends SchemaStructure> {\n private config: SyncedDbConfig<S>;\n private sp00ky: Sp00kyClient<S> | null = null;\n private _initialized = false;\n\n constructor(config: SyncedDbConfig<S>) {\n this.config = config;\n }\n\n public getSp00ky(): Sp00kyClient<S> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky;\n }\n\n /**\n * Initialize the sp00ky-ts instance\n */\n async init(): Promise<void> {\n if (this._initialized) return;\n this.sp00ky = new Sp00kyClient<S>(this.config);\n await this.sp00ky.init();\n this._initialized = true;\n }\n\n /**\n * Create a new record in the database\n */\n async create(id: string, payload: Record<string, unknown>): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.create(id, payload as Record<string, unknown>);\n }\n\n /**\n * Update an existing record in the database\n */\n async update<TName extends TableNames<S>>(\n tableName: TName,\n recordId: string,\n payload: Partial<TableModel<GetTable<S, TName>>>,\n options?: UpdateOptions\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.update(\n tableName as string,\n recordId,\n payload as Record<string, unknown>,\n options\n );\n }\n\n /**\n * Delete an existing record in the database\n */\n async delete<TName extends TableNames<S>>(\n tableName: TName,\n selector: string | RecordId | InnerQuery<GetTable<S, TName>, boolean>\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n // Accept a `\"table:id\"` string OR a RecordId — live-query rows carry their\n // `id` as a RecordId, so callers can pass `db.delete('game', row.id)`\n // directly. Build the canonical string from the raw id part (not\n // `RecordId.toString()`, which escapes special chars) so it round-trips\n // through the engine's `parseRecordIdString`. InnerQuery selectors are not\n // supported yet. (cross-package RecordId instances → match by constructor name.)\n const isRecordId =\n selector instanceof RecordId || (selector as any)?.constructor?.name === 'RecordId';\n let id: string;\n if (typeof selector === 'string') {\n id = selector;\n } else if (isRecordId) {\n id = `${tableName as string}:${(selector as RecordId).id}`;\n } else {\n throw new Error('Only string ID or RecordId selectors are supported currently with core');\n }\n await this.sp00ky.delete(tableName as string, id);\n }\n\n /**\n * Preload/prewarm a built query into the local cache without registering a\n * live view. Fetches once and stores the rows (+ embedded related children)\n * locally so a later `useQuery` for the same data paints instantly. Best-effort.\n */\n public async preload(\n finalQuery: FinalQuery<S, any, any, any, any, Sp00kyQueryResultPromise>,\n options?: PreloadOptions\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.preload(finalQuery, options);\n }\n\n /**\n * Query data from the database\n */\n public query<TName extends TableNames<S>>(\n table: TName\n ): QueryBuilder<S, TName, Sp00kyQueryResultPromise, {}, false> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.query(table, {});\n }\n\n /**\n * Run a backend operation\n */\n public async run<\n B extends BackendNames<S>,\n R extends BackendRoutes<S, B>,\n >(\n backend: B,\n path: R,\n payload: RoutePayload<S, B, R>,\n options?: RunOptions,\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.run(backend, path, payload, options);\n }\n\n /**\n * Authenticate with the database\n */\n public async authenticate(token: string): Promise<RecordId<string>> {\n await this.sp00ky?.authenticate(token);\n // Sp00kyClient.authenticate returns whatever remote.authenticate returns (boolean or token usually?)\n // Wait, checked Sp00kyClient: return this.remote.getClient().authenticate(token);\n // SurrealDB authenticate returns void? or token?\n // Assuming void or token.\n return new RecordId('user', 'me'); // Placeholder or actual?\n }\n\n /**\n * Deauthenticate from the database\n * @deprecated Use signOut() instead\n */\n public async deauthenticate(): Promise<void> {\n await this.signOut();\n }\n\n /**\n * Sign out, clear session and local storage\n */\n public async signOut(): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.auth.signOut();\n }\n\n /**\n * Execute a function with direct access to the remote database connection\n */\n public async useRemote<T>(fn: (db: Surreal) => T | Promise<T>): Promise<T> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return await this.sp00ky.useRemote(fn);\n }\n /**\n * Access the remote database service directly\n */\n get remote(): Sp00kyClient<S>['remoteClient'] {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.remoteClient;\n }\n\n /**\n * Access the local database service directly\n */\n get local(): Sp00kyClient<S>['localClient'] {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.localClient;\n }\n\n /**\n * Access the auth service\n */\n get auth(): AuthService<S> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.auth;\n }\n\n get pendingMutationCount(): number {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.pendingMutationCount;\n }\n\n /** Diagnostic — see `Sp00kyClient.liveRetryCount`. */\n get liveRetryCount(): number {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.liveRetryCount;\n }\n\n subscribeToPendingMutations(cb: (count: number) => void): () => void {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.subscribeToPendingMutations(cb);\n }\n\n /** Current sync-health snapshot. See {@link useSyncStatus}. */\n get syncHealth(): SyncHealth {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.syncHealth;\n }\n\n /**\n * Observe sync health. Fires immediately with the current status and again\n * on every healthy↔degraded transition. Prefer the `useSyncStatus` hook in\n * components; this is the imperative escape hatch.\n */\n subscribeToSyncHealth(cb: (health: SyncHealth) => void): () => void {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.subscribeToSyncHealth(cb);\n }\n\n bucket<B extends BucketNames<S>>(name: B): BucketHandle {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.bucket(name);\n }\n\n getBucketConfig(name: string): BucketDefinitionSchema | undefined {\n return this.config.schema.buckets?.find((b) => b.name === name);\n }\n}\n\nexport * from './types';\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/lib/context.ts","../src/lib/use-query.ts","../src/lib/create-preload.ts","../src/lib/use-sync-status.ts","../src/lib/use-storage-status.ts","../src/lib/use-crdt-field.ts","../src/lib/use-feature-flag.ts","../src/lib/use-app-release.ts","../src/lib/use-file-upload.ts","../src/lib/use-download-file.ts","../src/lib/Sp00kyProvider.ts","../src/index.ts"],"sourcesContent":["import { createContext, useContext } from 'solid-js';\nimport type { SchemaStructure } from '@spooky/query-builder';\nimport type { SyncedDb } from '../index';\n\nexport const Sp00kyContext = createContext<SyncedDb<any> | undefined>();\n\nexport function useDb<S extends SchemaStructure>(): SyncedDb<S> {\n const db = useContext(Sp00kyContext);\n if (!db) {\n throw new Error('useDb must be used within a <Sp00kyProvider>. Wrap your app in <Sp00kyProvider config={...}>.');\n }\n return db as SyncedDb<S>;\n}\n","import type {\n ColumnSchema,\n FinalQuery,\n SchemaStructure,\n TableNames,\n QueryResult,\n} from '@spooky-sync/query-builder';\nimport { createEffect, createSignal, onCleanup, useContext } from 'solid-js';\nimport { createStore, reconcile } from 'solid-js/store';\nimport { SyncedDb } from '..';\nimport type { Sp00kyQueryResultPromise } from '@spooky-sync/core';\nimport { Sp00kyContext } from './context';\n\ntype QueryArg<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n> =\n | FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>\n | (() =>\n | FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>\n | null\n | undefined);\n\ntype QueryOptions = {\n enabled?: () => boolean;\n /**\n * Tear down the query (remote `_00_query` view + local WASM view) when this\n * hook is disposed and no other subscriber remains, instead of keeping it\n * resident for cheap re-subscription. Use for viewport-windowed lists that\n * mount/unmount a query per scroll window and want off-screen windows\n * cancelled. Trade-off: scrolling back to a torn-down window re-registers it.\n */\n deregisterOnCleanup?: boolean;\n};\n\n// Overload: context-based (no explicit db)\nexport function useQuery<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n TData = QueryResult<S, TableName, RelatedFields, IsOne> | null,\n>(\n finalQuery: QueryArg<S, TableName, T, RelatedFields, IsOne>,\n options?: QueryOptions,\n): {\n data: () => TData | undefined;\n error: () => Error | undefined;\n isLoading: () => boolean;\n isFetching: () => boolean;\n isSettled: () => boolean;\n};\n\n// Overload: explicit db (backward-compatible)\nexport function useQuery<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n TData = QueryResult<S, TableName, RelatedFields, IsOne> | null,\n>(\n db: SyncedDb<S>,\n finalQuery: QueryArg<S, TableName, T, RelatedFields, IsOne>,\n options?: QueryOptions,\n): {\n data: () => TData | undefined;\n error: () => Error | undefined;\n isLoading: () => boolean;\n isFetching: () => boolean;\n isSettled: () => boolean;\n};\n\n// Implementation\nexport function useQuery<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends {\n columns: Record<string, ColumnSchema>;\n },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n TData = QueryResult<S, TableName, RelatedFields, IsOne> | null,\n>(\n dbOrQuery:\n | SyncedDb<S>\n | QueryArg<S, TableName, T, RelatedFields, IsOne>,\n queryOrOptions?:\n | QueryArg<S, TableName, T, RelatedFields, IsOne>\n | QueryOptions,\n maybeOptions?: QueryOptions,\n) {\n let db: SyncedDb<S>;\n let finalQuery: QueryArg<S, TableName, T, RelatedFields, IsOne>;\n let options: QueryOptions | undefined;\n\n if (dbOrQuery instanceof SyncedDb) {\n // Explicit db overload: useQuery(db, query, options?)\n db = dbOrQuery;\n finalQuery = queryOrOptions as QueryArg<S, TableName, T, RelatedFields, IsOne>;\n options = maybeOptions;\n } else {\n // Context-based overload: useQuery(query, options?)\n const contextDb = useContext(Sp00kyContext);\n if (!contextDb) {\n throw new Error(\n 'useQuery: No db argument provided and no Sp00kyContext found. ' +\n 'Either pass a SyncedDb instance or wrap your app in <Sp00kyProvider>.'\n );\n }\n db = contextDb as SyncedDb<S>;\n finalQuery = dbOrQuery;\n options = queryOrOptions as QueryOptions | undefined;\n }\n\n const [error, setError] = createSignal<Error | undefined>(undefined);\n const [isFetched, setIsFetched] = createSignal(false);\n const [isFetching, setIsFetching] = createSignal(false);\n // Results live in a store (not a signal) so consecutive live-query emissions\n // are merged with `reconcile`: unchanged rows keep their object identity and\n // changed rows are mutated in place. That keeps Solid's reference-keyed `<For>`\n // rows — and any `useQuery` subscriptions mounted inside them — alive across\n // updates, instead of tearing every row down and re-registering its queries.\n const [state, setState] = createStore<{ value: TData | undefined }>({ value: undefined });\n // `reconcile` (below) merges each emission into `state.value` IN PLACE, keeping\n // the array reference stable. That's ideal for granular per-row reactivity, but\n // it means a *coarse* reader of `data()` — `<For each={data()}>`, or an effect\n // that copies the whole array elsewhere (e.g. GameList's windowed store) — is\n // NOT re-run when rows are added/removed/reordered within a same-length result\n // (the classic case: deleting a row in a windowed list shifts the next one in,\n // so length stays 50 and the array ref never changes). Bump a version on every\n // emission and read it in `data()` so every consumer re-runs on any change while\n // reconcile still preserves row identity underneath.\n const [version, setVersion] = createSignal(0);\n const data = () => {\n version();\n return state.value;\n };\n\n let prevQueryString: string | undefined;\n // Monotonic token for each subscription generation. Bumped whenever the query\n // identity changes or the hook is disposed, so a slow async `initQuery`\n // continuation can detect it was superseded and avoid installing a stale (and\n // leaked) subscription.\n let runId = 0;\n let activeUnsub: (() => void) | undefined;\n // The hash of the currently-installed subscription, for opt-in deregister on\n // dispose (see `deregisterOnCleanup`).\n let activeHash: string | undefined;\n\n const teardownActive = () => {\n activeUnsub?.();\n activeUnsub = undefined;\n };\n\n const sp00ky = db.getSp00ky();\n\n const initQuery = async (\n query: FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>,\n myRun: number\n ) => {\n const { hash } = await query.run();\n // A newer query identity (or disposal) won the race while we awaited run().\n if (myRun !== runId) return;\n activeHash = hash;\n setError(undefined);\n\n let isFirstCall = true;\n const unsub = await sp00ky.subscribe(\n hash,\n (e) => {\n const queryData = (query.isOne ? e[0] : e) as TData;\n // Merge into the store by record id: unchanged rows keep their identity,\n // changed rows update in place. Replaces wholesale for `one()`/null.\n // Time the reconcile → report as the \"frontend\" phase for DevTools/MCP.\n const reconcileStart = performance.now();\n setState('value', reconcile(queryData as any, { key: 'id' }));\n // Notify coarse `data()` readers (see the `version` note above): reconcile\n // keeps the array ref stable, so this is what re-runs `<For>`/copy-effects\n // on add/remove/reorder.\n setVersion((v) => v + 1);\n sp00ky.reportFrontendTiming(hash, performance.now() - reconcileStart);\n // The first (immediate) callback with no data likely means the local DB\n // hasn't synced yet — don't mark as fetched so UI shows loading state\n const hasData = query.isOne ? queryData !== null && queryData !== undefined : (e as any[]).length > 0;\n if (!isFirstCall || hasData) {\n setIsFetched(true);\n }\n isFirstCall = false;\n },\n { immediate: true }\n );\n\n // Mirror the query's fetch status so the UI can show a \"loading more\"\n // state while the sync engine pulls missing records in the background.\n const unsubStatus = sp00ky.subscribeQueryStatus(\n hash,\n (status) => setIsFetching(status === 'fetching'),\n { immediate: true }\n );\n\n const teardown = () => {\n unsub();\n unsubStatus();\n };\n\n // Superseded while awaiting subscribe()? Don't leak — tear down immediately.\n if (myRun !== runId) {\n teardown();\n return;\n }\n activeUnsub = teardown;\n };\n\n createEffect(() => {\n const enabled = options?.enabled?.() ?? true;\n\n // If disabled, clear error and don't run query\n if (!enabled) {\n setError(undefined);\n return;\n }\n\n // Init Query\n const query = typeof finalQuery === 'function' ? finalQuery() : finalQuery;\n if (!query) {\n return;\n }\n\n // Dedup on the query's stable identity hash (cyrb53 of surql + vars), not a\n // full `JSON.stringify` of the FinalQuery (which walks the whole schema +\n // inner query on every reactive tick and isn't guaranteed stable). When the\n // identity is unchanged we keep the existing subscription alive.\n const queryString = String(query.hash);\n if (queryString === prevQueryString) {\n return;\n }\n prevQueryString = queryString;\n\n // New query identity → supersede the previous subscription and start fresh.\n const myRun = ++runId;\n teardownActive();\n setIsFetched(false);\n initQuery(query, myRun);\n });\n\n // Tear down the live subscription when the hook's owner is disposed. Registered\n // on the hook (component) scope rather than inside the effect, so an effect\n // re-run that early-returns (unchanged query) doesn't clean up the still-valid\n // subscription. Bumping runId also invalidates any in-flight initQuery.\n onCleanup(() => {\n runId++;\n teardownActive();\n // Opt-in: cancel the query once this hook (its last subscriber) is gone.\n // teardownActive() above already removed this hook's callback, so\n // deregisterQuery's refcount guard sees the true remaining-subscriber count.\n if (options?.deregisterOnCleanup && activeHash) {\n sp00ky.deregisterQuery(activeHash);\n }\n });\n\n const isLoading = () => {\n return !isFetched() && error() === undefined;\n };\n\n // True once the query has delivered a result AND no fetch cycle is in flight\n // (registration + initial sync included — the core holds `fetching` across\n // the whole registration and flushes debounced results before flipping back\n // to idle). While settled, the results are authoritative: a windowed query\n // returning fewer rows than its LIMIT really is the end of the list, so\n // virtualized lists may size themselves to it without the scrollbar jumping\n // when a still-syncing window transiently reports short. Resets to false\n // whenever the query identity changes.\n const isSettled = () => isFetched() && !isFetching();\n\n return {\n data,\n error,\n isLoading,\n isFetching,\n isSettled,\n };\n}\n","import type {\n ColumnSchema,\n FinalQuery,\n SchemaStructure,\n TableNames,\n} from '@spooky-sync/query-builder';\nimport { createEffect, useContext } from 'solid-js';\nimport { SyncedDb } from '..';\nimport type { Sp00kyQueryResultPromise, PreloadOptions as CorePreloadOptions } from '@spooky-sync/core';\nimport { Sp00kyContext } from './context';\n\ntype PreloadArg<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n> =\n | FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>\n | (() =>\n | FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>\n | null\n | undefined);\n\ntype PreloadOptions = CorePreloadOptions & {\n /** Only preload while this returns true (defaults to always). */\n enabled?: () => boolean;\n};\n\n// Overload: context-based (no explicit db)\nexport function createPreload<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n>(\n finalQuery: PreloadArg<S, TableName, T, RelatedFields, IsOne>,\n options?: PreloadOptions,\n): void;\n\n// Overload: explicit db\nexport function createPreload<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n>(\n db: SyncedDb<S>,\n finalQuery: PreloadArg<S, TableName, T, RelatedFields, IsOne>,\n options?: PreloadOptions,\n): void;\n\n/**\n * Reactive, fire-and-forget prewarm. Resolves the query (calling it if it's a\n * function so it tracks reactive deps), dedupes on the query's stable identity\n * hash, and warms it into the local cache via `db.preload`. No subscription and\n * no cleanup: preload registers nothing that needs tearing down.\n *\n * Typical use: inside a list row, preload the detail query the user is likely\n * to open next, so navigation paints from cache instead of the network.\n */\nexport function createPreload<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n>(\n dbOrQuery: SyncedDb<S> | PreloadArg<S, TableName, T, RelatedFields, IsOne>,\n queryOrOptions?: PreloadArg<S, TableName, T, RelatedFields, IsOne> | PreloadOptions,\n maybeOptions?: PreloadOptions,\n): void {\n let db: SyncedDb<S>;\n let finalQuery: PreloadArg<S, TableName, T, RelatedFields, IsOne>;\n let options: PreloadOptions | undefined;\n\n if (dbOrQuery instanceof SyncedDb) {\n db = dbOrQuery;\n finalQuery = queryOrOptions as PreloadArg<S, TableName, T, RelatedFields, IsOne>;\n options = maybeOptions;\n } else {\n const contextDb = useContext(Sp00kyContext);\n if (!contextDb) {\n throw new Error(\n 'createPreload: No db argument provided and no Sp00kyContext found. ' +\n 'Either pass a SyncedDb instance or wrap your app in <Sp00kyProvider>.',\n );\n }\n db = contextDb as SyncedDb<S>;\n finalQuery = dbOrQuery;\n options = queryOrOptions as PreloadOptions | undefined;\n }\n\n let prevHash: number | undefined;\n\n createEffect(() => {\n if (!(options?.enabled?.() ?? true)) return;\n\n const query = typeof finalQuery === 'function' ? finalQuery() : finalQuery;\n if (!query) return;\n\n // Dedupe on the query's stable identity hash so a reactive re-run with an\n // unchanged query doesn't refetch (the core also dedupes per session).\n if (query.hash === prevHash) return;\n prevHash = query.hash;\n\n void db.getSp00ky().preload(query, { refresh: options?.refresh, staleTime: options?.staleTime });\n });\n}\n","import { createSignal, onCleanup, type Accessor } from 'solid-js';\nimport { useDb } from './context';\nimport type { SyncHealth, SyncHealthStatus } from '@spooky-sync/core';\n\nexport interface UseSyncStatus {\n /** Full health snapshot; updates reactively on every transition. */\n health: Accessor<SyncHealth>;\n /** `'healthy'` | `'degraded'`. */\n status: Accessor<SyncHealthStatus>;\n isHealthy: Accessor<boolean>;\n /** `true` once sync has failed for a sustained run — drive a banner off this. */\n isDegraded: Accessor<boolean>;\n /** `true` once at least one sync round has succeeded this session. */\n everConnected: Accessor<boolean>;\n /**\n * `true` only for a real lost connection: degraded AFTER a first successful\n * sync. Stays `false` during the initial \"connecting\" phase (degraded but\n * never reached the server yet), so an indicator can show nothing until the\n * app has actually connected once.\n */\n isOffline: Accessor<boolean>;\n}\n\n/**\n * Observe sync health for a \"can't reach the server\" banner / indicator.\n *\n * Backed by `db.subscribeToSyncHealth`. Individual sync failures (a transient\n * remote 500 on query registration, a dropped socket) are absorbed by the\n * retry and never flip this; `isDegraded()` only goes true once failures\n * persist for the configured number of consecutive rounds (sp00ky core config\n * `syncHealth.degradeAfterConsecutiveFailures`, default 3), and flips back on\n * the next successful round. Must be used within a `<Sp00kyProvider>`.\n */\nexport function useSyncStatus(): UseSyncStatus {\n const db = useDb();\n // subscribeToSyncHealth fires synchronously with the current status, so the\n // signal is correct from first read; the initial value just avoids a flash.\n const [health, setHealth] = createSignal<SyncHealth>(db.syncHealth);\n const unsub = db.subscribeToSyncHealth(setHealth);\n onCleanup(unsub);\n\n return {\n health,\n status: () => health().status,\n isHealthy: () => health().status === 'healthy',\n isDegraded: () => health().status === 'degraded',\n everConnected: () => health().everConnected,\n isOffline: () => health().status === 'degraded' && health().everConnected,\n };\n}\n","import { createSignal, onCleanup, type Accessor } from 'solid-js';\nimport { useDb } from './context';\nimport type { StorageHealth, StorageHealthStatus } from '@spooky-sync/core';\n\nexport interface UseStorageStatus {\n /** Full durability snapshot; updates reactively. */\n health: Accessor<StorageHealth>;\n /** `'unknown'` | `'persistent'` | `'memory'`. */\n status: Accessor<StorageHealthStatus>;\n /** `true` when the local store survives a reload. */\n isPersistent: Accessor<boolean>;\n /**\n * `true` only when durable storage was requested and could NOT be opened, so\n * the dataset is sitting in RAM and local writes die on reload. Drive a\n * warning off this, not off `status`: a store configured as in-memory reports\n * `'memory'` too, and that is a choice rather than a problem.\n */\n isMemoryFallback: Accessor<boolean>;\n}\n\n/**\n * Observe how durable the LOCAL cache is, for a \"no local storage\" warning.\n *\n * Under `localEngine: 'sqlite'` with `store: 'indexeddb'` the durable store is\n * the OPFS SAHPool VFS, and only ONE client per bucket can hold it open: a\n * second tab of the same app cannot get it and runs in memory instead (the\n * engine retries first, so a closing tab's lock is usually waited out). Must be\n * used within a `<Sp00kyProvider>`.\n */\nexport function useStorageStatus(): UseStorageStatus {\n const db = useDb();\n // subscribeToStorageHealth fires synchronously with the current snapshot, so\n // the signal is correct from the first read; the initial value avoids a flash.\n const [health, setHealth] = createSignal<StorageHealth>(db.storageHealth);\n const unsub = db.subscribeToStorageHealth(setHealth);\n onCleanup(unsub);\n\n return {\n health,\n status: () => health().status,\n isPersistent: () => health().status === 'persistent',\n isMemoryFallback: () => health().fallback,\n };\n}\n","import { createEffect, createSignal, onCleanup, useContext, type Accessor } from 'solid-js';\nimport { Sp00kyContext } from './context';\nimport type { CrdtField } from '@spooky-sync/core';\n\nexport function useCrdtField(\n table: string,\n recordId: () => string | undefined,\n field: string,\n fallbackText?: () => string | undefined,\n): Accessor<CrdtField | null> {\n const db = useContext(Sp00kyContext);\n if (!db) {\n throw new Error('useCrdtField must be used within a <Sp00kyProvider>');\n }\n\n const [crdtField, setCrdtField] = createSignal<CrdtField | null>(null);\n let currentId: string | undefined;\n let initialized = false;\n\n createEffect(() => {\n const id = recordId();\n\n // Skip if the ID hasn't changed (but allow the first non-undefined value through)\n if (initialized && id === currentId) return;\n\n // Close previous field\n if (currentId && crdtField()) {\n db.getSp00ky().closeCrdtField(table, currentId, field);\n setCrdtField(null);\n }\n\n currentId = id;\n initialized = true;\n\n if (!id) return;\n\n const sp00ky = db.getSp00ky();\n const text = fallbackText?.();\n sp00ky\n .openCrdtField(table, id, field, text)\n .then((cf) => {\n if (currentId === id) {\n setCrdtField(cf);\n }\n })\n .catch((err) => {\n // Silent rejections here leave the consumer's `Show when={field()}`\n // permanently stuck on its fallback (typically a static `<p>` with\n // no editing UI), with no error trail. Surface the failure so the\n // root cause (missing `@crdt` annotation, schema codegen drift,\n // local DB query failure, etc.) is visible in the console instead\n // of silently breaking collaborative fields.\n console.error(\n `[useCrdtField] Failed to open CRDT field ${table}.${field} on ${id}:`,\n err,\n );\n });\n });\n\n onCleanup(() => {\n if (currentId && crdtField()) {\n db.getSp00ky().closeCrdtField(table, currentId, field);\n setCrdtField(null);\n }\n });\n\n return crdtField;\n}\n","import { createSignal, onCleanup, type Accessor } from 'solid-js';\nimport { useDb } from './context';\nimport type { FeatureFlagOptions } from '@spooky-sync/core';\n\nexport interface UseFeatureFlag {\n variant: Accessor<string | undefined>;\n payload: Accessor<unknown | undefined>;\n enabled: Accessor<boolean>;\n}\n\n/**\n * Subscribe to a feature flag for the currently authenticated user.\n *\n * Returns three Solid accessors that update reactively whenever the\n * server-materialized assignment in `_00_user_feature` changes. Backed by\n * the same SSP + sync pipeline that powers `useQuery`, so toggling a flag\n * via `spky flag enable <key>` propagates to the UI without a refresh.\n *\n * `enabled()` is `true` when the resolved variant exists and is not 'off'.\n * For multi-variant flags, prefer `variant()` directly.\n */\nexport function useFeatureFlag(\n key: string,\n options?: FeatureFlagOptions,\n): UseFeatureFlag {\n const db = useDb();\n const handle = db.getSp00ky().feature(key, options);\n\n const [variant, setVariant] = createSignal<string | undefined>(handle.variant());\n const [payload, setPayload] = createSignal<unknown | undefined>(handle.payload());\n\n const unsub = handle.subscribe((s) => {\n setVariant(s.variant ?? options?.fallback);\n setPayload(s.payload);\n });\n\n onCleanup(() => {\n unsub();\n handle.close();\n });\n\n return {\n variant,\n payload,\n enabled: () => {\n const v = variant();\n return v !== undefined && v !== 'off';\n },\n };\n}\n","import { createSignal, onCleanup, type Accessor } from 'solid-js';\nimport { useDb } from './context';\nimport { semverGt, type AppReleaseOptions, type AppReleaseSnapshot } from '@spooky-sync/core';\n\nexport interface UseAppReleaseOptions extends AppReleaseOptions {\n /** App name from sp00ky.yml, e.g. `web`. */\n app: string;\n /**\n * The running build's version (X.Y.Z), typically baked in at build time\n * (e.g. a vite `define` from package.json). `updateAvailable()` is true when\n * the announced release is semver-newer than this.\n */\n currentVersion: string;\n}\n\nexport interface UseAppRelease {\n /** Latest announced version for the app, or undefined when no row exists. */\n latestVersion: Accessor<string | undefined>;\n /** Announced version is semver-newer than the running build. */\n updateAvailable: Accessor<boolean>;\n /** The newer release asks clients to update/reload without prompting. */\n mandatory: Accessor<boolean>;\n /** The newer release asks reloads to clear service-worker caches first. */\n cacheBust: Accessor<boolean>;\n /**\n * Reload onto the announced release. Plain `location.reload()` normally;\n * when the release is flagged cache-bust, CacheStorage is cleared, the\n * service-worker registration is nudged to update, and navigation carries a\n * `?cb=` token to punch through intermediary caches. The service worker is\n * deliberately NOT unregistered: navigating while still controlled by a\n * just-unregistered worker strands subresource fetches on the dead worker\n * and the page hangs until a manual reload.\n */\n reload: () => Promise<void>;\n}\n\nasync function reloadForSnapshot(snapshot: AppReleaseSnapshot): Promise<void> {\n if (typeof window === 'undefined') return;\n if (snapshot.cacheBust) {\n try {\n if (window.caches) {\n const keys = await window.caches.keys();\n await Promise.all(keys.map((k) => window.caches.delete(k)));\n }\n if (navigator.serviceWorker) {\n const regs = await navigator.serviceWorker.getRegistrations();\n for (const r of regs) r.update().catch(() => {});\n }\n window.location.href = window.location.pathname + '?cb=' + Date.now();\n return;\n } catch {\n /* fall through to a plain reload */\n }\n }\n window.location.reload();\n}\n\n/**\n * Observe the app's announced release (`_00_app_release:<app>`, written by\n * `spky deploy` / `spky release`) and compare it against the running build.\n *\n * Typical use: mount a small \"new version available — Reload\" notification\n * gated on `updateAvailable()`, auto-invoking `reload()` when `mandatory()`\n * (guard the auto path against reload loops with a per-version marker, since\n * a client can reload while the deploy is still rolling out and land on the\n * old bundle again).\n */\nexport function useAppRelease(options: UseAppReleaseOptions): UseAppRelease {\n const db = useDb();\n const handle = db.getSp00ky().appRelease(options.app, { ttl: options.ttl });\n\n const [snapshot, setSnapshot] = createSignal<AppReleaseSnapshot>(handle.snapshot());\n const unsub = handle.subscribe(setSnapshot);\n\n onCleanup(() => {\n unsub();\n handle.close();\n });\n\n const updateAvailable = () => semverGt(snapshot().version, options.currentVersion);\n\n return {\n latestVersion: () => snapshot().version,\n updateAvailable,\n mandatory: () => updateAvailable() && snapshot().mandatory,\n cacheBust: () => snapshot().cacheBust,\n reload: () => reloadForSnapshot(snapshot()),\n };\n}\n","import { createSignal, onCleanup } from 'solid-js';\nimport type { SchemaStructure, BucketNames } from '@spooky-sync/query-builder';\nimport { fileToUint8Array } from '@spooky-sync/core';\nimport type { SyncedDb } from '../index';\nimport { useDb } from './context';\n\nexport interface FileUploadResult {\n isUploading: () => boolean;\n error: () => Error | null;\n clearError: () => void;\n upload: (path: string, file: File | Blob) => Promise<void>;\n download: (path: string) => Promise<string | null>;\n remove: (path: string) => Promise<void>;\n exists: (path: string) => Promise<boolean>;\n}\n\nexport function useFileUpload<S extends SchemaStructure>(\n bucketName: BucketNames<S>,\n): FileUploadResult;\nexport function useFileUpload<S extends SchemaStructure>(\n db: SyncedDb<S>,\n bucketName: BucketNames<S>,\n): FileUploadResult;\nexport function useFileUpload<S extends SchemaStructure>(\n dbOrBucketName: SyncedDb<S> | BucketNames<S>,\n maybeBucketName?: BucketNames<S>,\n): FileUploadResult {\n let db: SyncedDb<S>;\n let bucketName: BucketNames<S>;\n\n if (typeof dbOrBucketName === 'string') {\n db = useDb<S>();\n bucketName = dbOrBucketName as BucketNames<S>;\n } else {\n db = dbOrBucketName as SyncedDb<S>;\n // oxlint-disable-next-line no-non-null-assertion\n bucketName = maybeBucketName!;\n }\n\n const [isUploading, setIsUploading] = createSignal(false);\n const [error, setError] = createSignal<Error | null>(null);\n\n const objectUrls: string[] = [];\n onCleanup(() => {\n for (const url of objectUrls) {\n URL.revokeObjectURL(url);\n }\n });\n\n const clearError = () => setError(null);\n\n const validate = (file: File | Blob): void => {\n const config = db.getBucketConfig(bucketName as string);\n if (!config) return;\n\n if (config.maxSize !== null && config.maxSize !== undefined && file.size > config.maxSize) {\n const maxMB = (config.maxSize / (1024 * 1024)).toFixed(1);\n throw new Error(`File exceeds maximum size of ${maxMB} MB.`);\n }\n\n if (config.allowedExtensions && config.allowedExtensions.length > 0) {\n const fileName = (file as File).name;\n if (fileName) {\n const ext = fileName.split('.').pop()?.toLowerCase();\n if (!ext || !config.allowedExtensions.includes(ext)) {\n throw new Error(\n `File type not allowed. Accepted: ${config.allowedExtensions.join(', ')}.`\n );\n }\n }\n }\n };\n\n const upload = async (path: string, file: File | Blob): Promise<void> => {\n setError(null);\n try {\n validate(file);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return;\n }\n\n setIsUploading(true);\n try {\n const bytes = await fileToUint8Array(file);\n await db.bucket(bucketName).put(path, bytes);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n } finally {\n setIsUploading(false);\n }\n };\n\n const download = async (path: string): Promise<string | null> => {\n setError(null);\n try {\n const content = await db.bucket(bucketName).get(path);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n objectUrls.push(objectUrl);\n return objectUrl;\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return null;\n }\n };\n\n const remove = async (path: string): Promise<void> => {\n setError(null);\n try {\n await db.bucket(bucketName).delete(path);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n }\n };\n\n const exists = async (path: string): Promise<boolean> => {\n setError(null);\n try {\n return await db.bucket(bucketName).exists(path);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return false;\n }\n };\n\n return {\n isUploading,\n error,\n clearError,\n upload,\n download,\n remove,\n exists,\n };\n}\n","import { createSignal, createEffect, onCleanup, type Accessor } from 'solid-js';\nimport type { SchemaStructure, BucketNames } from '@spooky-sync/query-builder';\nimport type { SyncedDb } from '../index';\nimport { useDb } from './context';\n\nexport interface UseDownloadFileOptions {\n cache?: boolean;\n}\n\nexport interface UseDownloadFileResult {\n url: Accessor<string | null>;\n isLoading: Accessor<boolean>;\n error: Accessor<Error | null>;\n refetch: () => void;\n}\n\ninterface CacheEntry {\n url: string;\n refCount: number;\n}\n\nconst downloadCache = new Map<string, CacheEntry>();\nconst inflightRequests = new Map<string, Promise<string | null>>();\n\nfunction cacheKey(bucket: string, path: string): string {\n return `${bucket}:${path}`;\n}\n\nfunction releaseEntry(key: string): void {\n const entry = downloadCache.get(key);\n if (!entry) return;\n entry.refCount--;\n if (entry.refCount <= 0) {\n URL.revokeObjectURL(entry.url);\n downloadCache.delete(key);\n }\n}\n\nexport function useDownloadFile<S extends SchemaStructure>(\n bucketName: BucketNames<S>,\n path: Accessor<string | null | undefined>,\n options?: UseDownloadFileOptions,\n): UseDownloadFileResult;\nexport function useDownloadFile<S extends SchemaStructure>(\n db: SyncedDb<S>,\n bucketName: BucketNames<S>,\n path: Accessor<string | null | undefined>,\n options?: UseDownloadFileOptions,\n): UseDownloadFileResult;\nexport function useDownloadFile<S extends SchemaStructure>(\n dbOrBucketName: SyncedDb<S> | BucketNames<S>,\n bucketNameOrPath?: BucketNames<S> | Accessor<string | null | undefined>,\n pathOrOptions?: Accessor<string | null | undefined> | UseDownloadFileOptions,\n maybeOptions?: UseDownloadFileOptions,\n): UseDownloadFileResult {\n let db: SyncedDb<S>;\n let bucketName: BucketNames<S>;\n let path: Accessor<string | null | undefined>;\n let options: UseDownloadFileOptions;\n\n if (typeof dbOrBucketName === 'string') {\n db = useDb<S>();\n bucketName = dbOrBucketName as BucketNames<S>;\n path = bucketNameOrPath as Accessor<string | null | undefined>;\n options = (pathOrOptions as UseDownloadFileOptions) ?? {};\n } else {\n db = dbOrBucketName as SyncedDb<S>;\n bucketName = bucketNameOrPath as BucketNames<S>;\n path = pathOrOptions as Accessor<string | null | undefined>;\n options = maybeOptions ?? {};\n }\n\n const useCache = options.cache !== false;\n\n const [url, setUrl] = createSignal<string | null>(null);\n const [isLoading, setIsLoading] = createSignal(false);\n const [error, setError] = createSignal<Error | null>(null);\n\n let currentKey: string | null = null;\n let privateUrl: string | null = null;\n const [refetchSignal, setRefetchSignal] = createSignal(0);\n const refetchTrigger = () => setRefetchSignal((n) => n + 1);\n\n async function doDownload(key: string, filePath: string): Promise<string | null> {\n if (useCache) {\n // Check cache\n const cached = downloadCache.get(key);\n if (cached) {\n cached.refCount++;\n currentKey = key;\n return cached.url;\n }\n\n // Check inflight\n const inflight = inflightRequests.get(key);\n if (inflight) {\n const result = await inflight;\n if (result) {\n const entry = downloadCache.get(key);\n if (entry) {\n entry.refCount++;\n currentKey = key;\n }\n }\n return result;\n }\n\n // Start new download\n const promise = (async () => {\n const content = await db.bucket(bucketName).get(filePath);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n downloadCache.set(key, { url: objectUrl, refCount: 1 });\n return objectUrl;\n })();\n\n inflightRequests.set(key, promise);\n try {\n const result = await promise;\n currentKey = key;\n return result;\n } finally {\n inflightRequests.delete(key);\n }\n } else {\n // No caching — private URL per instance\n const content = await db.bucket(bucketName).get(filePath);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n privateUrl = objectUrl;\n return objectUrl;\n }\n }\n\n function releaseCurrentEntry() {\n if (useCache && currentKey) {\n releaseEntry(currentKey);\n currentKey = null;\n }\n if (!useCache && privateUrl) {\n URL.revokeObjectURL(privateUrl);\n privateUrl = null;\n }\n }\n\n createEffect(() => {\n const filePath = path();\n // Subscribe to refetch signal so effect re-runs\n refetchSignal();\n\n // Release previous entry\n releaseCurrentEntry();\n\n if (!filePath) {\n setUrl(null);\n setIsLoading(false);\n setError(null);\n return;\n }\n\n const key = cacheKey(bucketName as string, filePath);\n\n // Synchronous cache hit\n if (useCache) {\n const cached = downloadCache.get(key);\n if (cached) {\n cached.refCount++;\n currentKey = key;\n setUrl(cached.url);\n setIsLoading(false);\n setError(null);\n return;\n }\n }\n\n let cancelled = false;\n setIsLoading(true);\n setError(null);\n\n doDownload(key, filePath).then(\n (result) => {\n if (!cancelled) {\n setUrl(result);\n setIsLoading(false);\n }\n return undefined;\n },\n (err) => {\n if (!cancelled) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setIsLoading(false);\n }\n },\n );\n\n onCleanup(() => {\n cancelled = true;\n });\n });\n\n onCleanup(() => {\n releaseCurrentEntry();\n });\n\n const refetch = () => {\n // Evict current entry from cache before re-triggering\n if (useCache && currentKey) {\n const entry = downloadCache.get(currentKey);\n if (entry) {\n URL.revokeObjectURL(entry.url);\n downloadCache.delete(currentKey);\n }\n currentKey = null;\n }\n refetchTrigger();\n };\n\n return { url, isLoading, error, refetch };\n}\n","import type { JSX} from 'solid-js';\nimport { createSignal, onMount, createComponent, createMemo, mergeProps } from 'solid-js';\nimport type { SchemaStructure } from '@spooky/query-builder';\nimport type { SyncedDbConfig } from '../types';\nimport { SyncedDb } from '../index';\nimport { Sp00kyContext } from './context';\n\nexport interface Sp00kyProviderProps<S extends SchemaStructure> {\n config: SyncedDbConfig<S>;\n fallback?: JSX.Element;\n onError?: (error: Error) => void;\n onReady?: (db: SyncedDb<S>) => void;\n /**\n * Prewarm data into the local cache before revealing the UI. Runs after\n * `init()`; the `fallback` stays visible until it resolves. Use awaitable\n * `db.preload(...)` calls here to gate first-load on essential data (e.g.\n * config). On warm loads preload returns instantly, so there's no perceptible\n * gate after the first run. Best-effort: a rejection is caught and the UI is\n * revealed anyway.\n */\n preload?: (db: SyncedDb<S>) => Promise<void>;\n children: JSX.Element;\n}\n\nexport function Sp00kyProvider<S extends SchemaStructure>(\n props: Sp00kyProviderProps<S>\n): JSX.Element {\n const merged = mergeProps(\n {\n fallback: undefined as JSX.Element | undefined,\n },\n props\n );\n\n const [db, setDb] = createSignal<SyncedDb<S> | undefined>(undefined);\n\n onMount(async () => {\n try {\n const instance = new SyncedDb<S>(merged.config);\n await instance.init();\n // Gate first-load UI on prewarmed data. Best-effort: never let a preload\n // failure keep the app stuck on the fallback.\n if (merged.preload) {\n try {\n await merged.preload(instance);\n } catch (e) {\n // oxlint-disable-next-line no-console\n console.error('Sp00kyProvider: preload failed; revealing UI anyway', e);\n }\n }\n setDb(() => instance);\n merged.onReady?.(instance);\n } catch (e) {\n const error = e instanceof Error ? e : new Error(String(e));\n if (merged.onError) {\n merged.onError(error);\n } else {\n // oxlint-disable-next-line no-console\n console.error('Sp00kyProvider: Failed to initialize database', error);\n }\n }\n });\n\n const content = createMemo(() => {\n const instance = db();\n if (!instance) return merged.fallback;\n return createComponent(Sp00kyContext.Provider, {\n value: instance,\n get children() {\n return merged.children;\n },\n });\n });\n\n return content as unknown as JSX.Element;\n}\n","import type { SyncedDbConfig } from './types';\nimport {\n Sp00kyClient,\n type Sp00kyQueryResultPromise,\n type AuthService,\n type BucketHandle,\n type UpdateOptions,\n type RunOptions,\n type SyncHealth,\n type StorageHealth,\n type PreloadOptions,\n type PreloadRefresh,\n} from '@spooky-sync/core';\n\nimport type {\n GetTable,\n QueryBuilder,\n SchemaStructure,\n TableModel,\n TableNames,\n QueryResult,\n RelatedFieldsMap,\n RelationshipFieldsFromSchema,\n GetRelationship,\n RelatedFieldMapEntry,\n FinalQuery,\n InnerQuery,\n BackendNames,\n BackendRoutes,\n RoutePayload,\n BucketNames,\n BucketDefinitionSchema,\n QueryModifier,\n QueryModifierBuilder,\n QueryInfo,\n RelationshipsMetadata,\n RelationshipDefinition,\n InferRelatedModelFromMetadata,\n GetCardinality,\n} from '@spooky-sync/query-builder';\n\nimport { RecordId, Uuid, type Surreal } from 'surrealdb';\nexport { RecordId, Uuid };\nexport type { Model, GenericModel, GenericSchema, ModelPayload } from './lib/models';\nexport { useQuery } from './lib/use-query';\nexport { createPreload } from './lib/create-preload';\nexport type { PreloadOptions, PreloadRefresh } from '@spooky-sync/core';\nexport { useSyncStatus, type UseSyncStatus } from './lib/use-sync-status';\nexport type { SyncHealth, SyncHealthStatus, SyncHealthConfig } from '@spooky-sync/core';\nexport { useStorageStatus, type UseStorageStatus } from './lib/use-storage-status';\nexport type { StorageHealth, StorageHealthStatus } from '@spooky-sync/core';\nexport { useCrdtField } from './lib/use-crdt-field';\nexport { useFeatureFlag, type UseFeatureFlag } from './lib/use-feature-flag';\nexport {\n useAppRelease,\n type UseAppRelease,\n type UseAppReleaseOptions,\n} from './lib/use-app-release';\nexport { useFileUpload, type FileUploadResult } from './lib/use-file-upload';\nexport { useDownloadFile, type UseDownloadFileOptions, type UseDownloadFileResult } from './lib/use-download-file';\nexport { Sp00kyProvider, type Sp00kyProviderProps } from './lib/Sp00kyProvider';\nexport { useDb } from './lib/context';\n\n// export { AuthEventTypes } from \"@spooky-sync/core\"; // TODO: Verify if AuthEventTypes exists in core\n\n\n// Re-export query builder types for convenience\nexport type {\n QueryModifier,\n QueryModifierBuilder,\n QueryInfo,\n RelationshipsMetadata,\n RelationshipDefinition,\n InferRelatedModelFromMetadata,\n GetCardinality,\n GetTable,\n TableModel,\n TableNames,\n QueryResult,\n};\n\nexport type RelationshipField<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n Field extends RelationshipFieldsFromSchema<Schema, TableName>,\n> = GetRelationship<Schema, TableName, Field>;\n\nexport type RelatedFieldsTableScoped<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n RelatedFields extends RelationshipFieldsFromSchema<Schema, TableName> =\n RelationshipFieldsFromSchema<Schema, TableName>,\n> = {\n [K in RelatedFields]: {\n to: RelationshipField<Schema, TableName, K>['to'];\n relatedFields: RelatedFieldsMap;\n cardinality: RelationshipField<Schema, TableName, K>['cardinality'];\n };\n};\n\nexport type InferModel<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n RelatedFields extends RelatedFieldsTableScoped<Schema, TableName>,\n> = QueryResult<Schema, TableName, RelatedFields, true>;\n\nexport type WithRelated<Field extends string, RelatedFields extends RelatedFieldsMap = {}> = {\n [K in Field]: Omit<RelatedFieldMapEntry, 'relatedFields'> & {\n relatedFields: RelatedFields;\n };\n};\n\nexport type WithRelatedMany<Field extends string, RelatedFields extends RelatedFieldsMap = {}> = {\n [K in Field]: {\n to: Field;\n relatedFields: RelatedFields;\n cardinality: 'many';\n };\n};\n\n/**\n * SyncedDb - A thin wrapper around sp00ky-ts for Solid.js integration\n * Delegates all logic to the underlying sp00ky-ts instance\n */\nexport class SyncedDb<S extends SchemaStructure> {\n private config: SyncedDbConfig<S>;\n private sp00ky: Sp00kyClient<S> | null = null;\n private _initialized = false;\n\n constructor(config: SyncedDbConfig<S>) {\n this.config = config;\n }\n\n public getSp00ky(): Sp00kyClient<S> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky;\n }\n\n /**\n * Initialize the sp00ky-ts instance\n */\n async init(): Promise<void> {\n if (this._initialized) return;\n this.sp00ky = new Sp00kyClient<S>(this.config);\n await this.sp00ky.init();\n this._initialized = true;\n }\n\n /**\n * Create a new record in the database\n */\n async create(id: string, payload: Record<string, unknown>): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.create(id, payload as Record<string, unknown>);\n }\n\n /**\n * Update an existing record in the database\n */\n async update<TName extends TableNames<S>>(\n tableName: TName,\n recordId: string,\n payload: Partial<TableModel<GetTable<S, TName>>>,\n options?: UpdateOptions\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.update(\n tableName as string,\n recordId,\n payload as Record<string, unknown>,\n options\n );\n }\n\n /**\n * Delete an existing record in the database\n */\n async delete<TName extends TableNames<S>>(\n tableName: TName,\n selector: string | RecordId | InnerQuery<GetTable<S, TName>, boolean>\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n // Accept a `\"table:id\"` string OR a RecordId — live-query rows carry their\n // `id` as a RecordId, so callers can pass `db.delete('game', row.id)`\n // directly. Build the canonical string from the raw id part (not\n // `RecordId.toString()`, which escapes special chars) so it round-trips\n // through the engine's `parseRecordIdString`. InnerQuery selectors are not\n // supported yet. (cross-package RecordId instances → match by constructor name.)\n const isRecordId =\n selector instanceof RecordId || (selector as any)?.constructor?.name === 'RecordId';\n let id: string;\n if (typeof selector === 'string') {\n id = selector;\n } else if (isRecordId) {\n id = `${tableName as string}:${(selector as RecordId).id}`;\n } else {\n throw new Error('Only string ID or RecordId selectors are supported currently with core');\n }\n await this.sp00ky.delete(tableName as string, id);\n }\n\n /**\n * Preload/prewarm a built query into the local cache without registering a\n * live view. Fetches once and stores the rows (+ embedded related children)\n * locally so a later `useQuery` for the same data paints instantly. Best-effort.\n */\n public async preload(\n finalQuery: FinalQuery<S, any, any, any, any, Sp00kyQueryResultPromise>,\n options?: PreloadOptions\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.preload(finalQuery, options);\n }\n\n /**\n * Query data from the database\n */\n public query<TName extends TableNames<S>>(\n table: TName\n ): QueryBuilder<S, TName, Sp00kyQueryResultPromise, {}, false> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.query(table, {});\n }\n\n /**\n * Run a backend operation\n */\n public async run<\n B extends BackendNames<S>,\n R extends BackendRoutes<S, B>,\n >(\n backend: B,\n path: R,\n payload: RoutePayload<S, B, R>,\n options?: RunOptions,\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.run(backend, path, payload, options);\n }\n\n /**\n * Authenticate with the database\n */\n public async authenticate(token: string): Promise<RecordId<string>> {\n await this.sp00ky?.authenticate(token);\n // Sp00kyClient.authenticate returns whatever remote.authenticate returns (boolean or token usually?)\n // Wait, checked Sp00kyClient: return this.remote.getClient().authenticate(token);\n // SurrealDB authenticate returns void? or token?\n // Assuming void or token.\n return new RecordId('user', 'me'); // Placeholder or actual?\n }\n\n /**\n * Deauthenticate from the database\n * @deprecated Use signOut() instead\n */\n public async deauthenticate(): Promise<void> {\n await this.signOut();\n }\n\n /**\n * Sign out, clear session and local storage\n */\n public async signOut(): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.auth.signOut();\n }\n\n /**\n * Execute a function with direct access to the remote database connection\n */\n public async useRemote<T>(fn: (db: Surreal) => T | Promise<T>): Promise<T> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return await this.sp00ky.useRemote(fn);\n }\n /**\n * Access the remote database service directly\n */\n get remote(): Sp00kyClient<S>['remoteClient'] {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.remoteClient;\n }\n\n /**\n * Access the local database service directly\n */\n get local(): Sp00kyClient<S>['localClient'] {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.localClient;\n }\n\n /**\n * Access the auth service\n */\n get auth(): AuthService<S> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.auth;\n }\n\n get pendingMutationCount(): number {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.pendingMutationCount;\n }\n\n /** Diagnostic — see `Sp00kyClient.liveRetryCount`. */\n get liveRetryCount(): number {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.liveRetryCount;\n }\n\n subscribeToPendingMutations(cb: (count: number) => void): () => void {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.subscribeToPendingMutations(cb);\n }\n\n /** Current sync-health snapshot. See {@link useSyncStatus}. */\n get syncHealth(): SyncHealth {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.syncHealth;\n }\n\n /**\n * Observe sync health. Fires immediately with the current status and again\n * on every healthy↔degraded transition. Prefer the `useSyncStatus` hook in\n * components; this is the imperative escape hatch.\n */\n subscribeToSyncHealth(cb: (health: SyncHealth) => void): () => void {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.subscribeToSyncHealth(cb);\n }\n\n /** Current local-store durability snapshot. See {@link useStorageStatus}. */\n get storageHealth(): StorageHealth {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.storageHealth;\n }\n\n /**\n * Observe local-store durability. Fires immediately with the current snapshot\n * and again on change. Prefer the `useStorageStatus` hook in components; this\n * is the imperative escape hatch.\n */\n subscribeToStorageHealth(cb: (health: StorageHealth) => void): () => void {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.subscribeToStorageHealth(cb);\n }\n\n bucket<B extends BucketNames<S>>(name: B): BucketHandle {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.bucket(name);\n }\n\n getBucketConfig(name: string): BucketDefinitionSchema | undefined {\n return this.config.schema.buckets?.find((b) => b.name === name);\n }\n}\n\nexport * from './types';\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spooky-sync/client-solid",
3
- "version": "0.0.1-canary.151",
3
+ "version": "0.0.1-canary.153",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "SurrealDB client with local and remote database support for browser applications",
@@ -43,8 +43,8 @@
43
43
  },
44
44
  "packageManager": "pnpm@9.0.0",
45
45
  "dependencies": {
46
- "@spooky-sync/query-builder": "0.0.1-canary.151",
47
- "@spooky-sync/core": "0.0.1-canary.151",
46
+ "@spooky-sync/query-builder": "0.0.1-canary.153",
47
+ "@spooky-sync/core": "0.0.1-canary.153",
48
48
  "@surrealdb/wasm": "^3.0.3",
49
49
  "surrealdb": "2.0.3",
50
50
  "valtio": "^2.1.8"
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  type UpdateOptions,
8
8
  type RunOptions,
9
9
  type SyncHealth,
10
+ type StorageHealth,
10
11
  type PreloadOptions,
11
12
  type PreloadRefresh,
12
13
  } from '@spooky-sync/core';
@@ -46,6 +47,8 @@ export { createPreload } from './lib/create-preload';
46
47
  export type { PreloadOptions, PreloadRefresh } from '@spooky-sync/core';
47
48
  export { useSyncStatus, type UseSyncStatus } from './lib/use-sync-status';
48
49
  export type { SyncHealth, SyncHealthStatus, SyncHealthConfig } from '@spooky-sync/core';
50
+ export { useStorageStatus, type UseStorageStatus } from './lib/use-storage-status';
51
+ export type { StorageHealth, StorageHealthStatus } from '@spooky-sync/core';
49
52
  export { useCrdtField } from './lib/use-crdt-field';
50
53
  export { useFeatureFlag, type UseFeatureFlag } from './lib/use-feature-flag';
51
54
  export {
@@ -326,6 +329,22 @@ export class SyncedDb<S extends SchemaStructure> {
326
329
  return this.sp00ky.subscribeToSyncHealth(cb);
327
330
  }
328
331
 
332
+ /** Current local-store durability snapshot. See {@link useStorageStatus}. */
333
+ get storageHealth(): StorageHealth {
334
+ if (!this.sp00ky) throw new Error('SyncedDb not initialized');
335
+ return this.sp00ky.storageHealth;
336
+ }
337
+
338
+ /**
339
+ * Observe local-store durability. Fires immediately with the current snapshot
340
+ * and again on change. Prefer the `useStorageStatus` hook in components; this
341
+ * is the imperative escape hatch.
342
+ */
343
+ subscribeToStorageHealth(cb: (health: StorageHealth) => void): () => void {
344
+ if (!this.sp00ky) throw new Error('SyncedDb not initialized');
345
+ return this.sp00ky.subscribeToStorageHealth(cb);
346
+ }
347
+
329
348
  bucket<B extends BucketNames<S>>(name: B): BucketHandle {
330
349
  if (!this.sp00ky) throw new Error('SyncedDb not initialized');
331
350
  return this.sp00ky.bucket(name);
@@ -0,0 +1,44 @@
1
+ import { createSignal, onCleanup, type Accessor } from 'solid-js';
2
+ import { useDb } from './context';
3
+ import type { StorageHealth, StorageHealthStatus } from '@spooky-sync/core';
4
+
5
+ export interface UseStorageStatus {
6
+ /** Full durability snapshot; updates reactively. */
7
+ health: Accessor<StorageHealth>;
8
+ /** `'unknown'` | `'persistent'` | `'memory'`. */
9
+ status: Accessor<StorageHealthStatus>;
10
+ /** `true` when the local store survives a reload. */
11
+ isPersistent: Accessor<boolean>;
12
+ /**
13
+ * `true` only when durable storage was requested and could NOT be opened, so
14
+ * the dataset is sitting in RAM and local writes die on reload. Drive a
15
+ * warning off this, not off `status`: a store configured as in-memory reports
16
+ * `'memory'` too, and that is a choice rather than a problem.
17
+ */
18
+ isMemoryFallback: Accessor<boolean>;
19
+ }
20
+
21
+ /**
22
+ * Observe how durable the LOCAL cache is, for a "no local storage" warning.
23
+ *
24
+ * Under `localEngine: 'sqlite'` with `store: 'indexeddb'` the durable store is
25
+ * the OPFS SAHPool VFS, and only ONE client per bucket can hold it open: a
26
+ * second tab of the same app cannot get it and runs in memory instead (the
27
+ * engine retries first, so a closing tab's lock is usually waited out). Must be
28
+ * used within a `<Sp00kyProvider>`.
29
+ */
30
+ export function useStorageStatus(): UseStorageStatus {
31
+ const db = useDb();
32
+ // subscribeToStorageHealth fires synchronously with the current snapshot, so
33
+ // the signal is correct from the first read; the initial value avoids a flash.
34
+ const [health, setHealth] = createSignal<StorageHealth>(db.storageHealth);
35
+ const unsub = db.subscribeToStorageHealth(setHealth);
36
+ onCleanup(unsub);
37
+
38
+ return {
39
+ health,
40
+ status: () => health().status,
41
+ isPersistent: () => health().status === 'persistent',
42
+ isMemoryFallback: () => health().fallback,
43
+ };
44
+ }