@spooky-sync/client-solid2 0.0.1-canary.200

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["Sp00kyClient","RecordId"],"sources":["../src/lib/conflate.ts","../src/lib/from-subscription.ts","../src/lib/context.ts","../src/lib/create-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/create-submission.ts","../src/index.ts"],"sourcesContent":["/**\n * Latest-wins async iterable over a subscribe-callback source.\n *\n * Bridges spooky's push-callback subscriptions into the AsyncIterable shape\n * Solid 2 computations consume natively. Each spooky emission is a full result\n * set, so intermediate values are droppable: only the newest unconsumed value\n * is buffered, and a pending pull resolves with it immediately.\n *\n * Teardown contract (probed in rc-semantics.test.ts): Solid 2 does NOT\n * terminate a superseded/disposed computation's async generator — no\n * `return()`, no `finally`. Consumers MUST call `it.return()` themselves from\n * an `onCleanup` registered synchronously in the compute scope. `return()`\n * unsubscribes (awaiting the unsubscribe if the subscribe returned a promise,\n * as `sp00ky.subscribe` does) and resolves any parked pull as done.\n */\nexport function conflate<T>(\n subscribe: (cb: (v: T) => void) => (() => void) | Promise<() => void>\n): AsyncIterable<T> {\n return {\n [Symbol.asyncIterator](): AsyncIterator<T> {\n let buffered: { v: T } | undefined;\n let resolveNext: ((r: IteratorResult<T>) => void) | undefined;\n let done = false;\n\n const unsubMaybe = subscribe((v) => {\n if (done) return;\n if (resolveNext) {\n const r = resolveNext;\n resolveNext = undefined;\n r({ value: v, done: false });\n } else {\n buffered = { v };\n }\n });\n\n const finish = () => {\n if (done) return;\n done = true;\n buffered = undefined;\n // Unsubscribe may still be in flight (async registration); chain it.\n Promise.resolve(unsubMaybe)\n .then((unsub) => unsub())\n .catch(() => {\n // Registration failed — there is nothing to unsubscribe.\n });\n if (resolveNext) {\n const r = resolveNext;\n resolveNext = undefined;\n r({ value: undefined as never, done: true });\n }\n };\n\n return {\n next(): Promise<IteratorResult<T>> {\n if (done) return Promise.resolve({ value: undefined as never, done: true });\n if (buffered) {\n const v = buffered.v;\n buffered = undefined;\n return Promise.resolve({ value: v, done: false });\n }\n return new Promise<IteratorResult<T>>((r) => (resolveNext = r));\n },\n return(): Promise<IteratorResult<T>> {\n finish();\n return Promise.resolve({ value: undefined as never, done: true });\n },\n throw(e: unknown): Promise<IteratorResult<T>> {\n finish();\n return Promise.reject(e);\n },\n };\n },\n };\n}\n","import { createMemo, onCleanup, type Accessor } from 'solid-js';\nimport { conflate } from './conflate';\n\n/**\n * Reactive view over a spooky subscribe-callback API.\n *\n * The memo's async generator pulls from a conflated (latest-wins) iterator;\n * `initial` is committed as the memo's `loadingValue`, so the accessor is\n * readable synchronously from birth and never suspends. Spooky's subscribe\n * APIs fire immediately with the current value, so the real value lands within\n * a tick of the first read.\n *\n * Teardown is manual by contract (see conflate.ts): onCleanup terminates the\n * iterator, which unsubscribes.\n */\nexport function fromSubscription<T>(\n subscribe: (cb: (v: T) => void) => (() => void) | Promise<() => void>,\n initial: T\n): Accessor<T> {\n return createMemo(\n async function* (): AsyncGenerator<T> {\n const it = conflate(subscribe)[Symbol.asyncIterator]();\n onCleanup(() => void it.return?.());\n while (true) {\n const r = await it.next();\n if (r.done) break;\n yield r.value;\n }\n },\n { loadingValue: initial }\n );\n}\n","import { createContext, useContext, type Accessor } from 'solid-js';\nimport type { SchemaStructure } from '@spooky-sync/query-builder';\nimport type { SyncedDb } from '../index';\nimport { fromSubscription } from './from-subscription';\n\n// Solid 2: the context object doubles as its provider component —\n// <Sp00kyContext value={db}>{children}</Sp00kyContext>.\nexport const Sp00kyContext = createContext<SyncedDb<any>>();\n\nexport function useDb<S extends SchemaStructure>(): SyncedDb<S> {\n try {\n return useContext(Sp00kyContext) as SyncedDb<S>;\n } catch {\n // Solid 2 throws ContextNotFoundError; rethrow with actionable guidance.\n throw new Error(\n 'useDb must be used within a <Sp00kyProvider>. Wrap your app in <Sp00kyProvider config={...}>.'\n );\n }\n}\n\n/**\n * Count of locally-committed mutations not yet acknowledged by the server.\n * Drive an \"unsaved changes\" indicator off this.\n */\nexport function usePendingMutations(): Accessor<number> {\n const db = useDb();\n return fromSubscription((cb) => db.subscribeToPendingMutations(cb), db.pendingMutationCount);\n}\n","import type {\n ColumnSchema,\n FinalQuery,\n SchemaStructure,\n TableNames,\n QueryResult,\n} from '@spooky-sync/query-builder';\nimport {\n createMemo,\n createProjection,\n createSignal,\n onCleanup,\n type Accessor,\n} from 'solid-js';\nimport { SyncedDb } from '..';\nimport type { Sp00kyQueryResultPromise } from '@spooky-sync/core';\nimport { useDb } from './context';\nimport { conflate } from './conflate';\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\nexport type 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\nexport type CreateQueryResult<TData> = {\n /**\n * Reactive result. Never suspends and never throws: born as an empty\n * committed value (`[]` / `null`) and reconciled in place (keyed by `id`) on\n * every live emission — unchanged rows keep identity, and coarse readers\n * (`<For>`) are notified on add/remove/reorder.\n */\n data: Accessor<TData>;\n /**\n * Suspending read of the same result for `<Loading>` users: throws Solid's\n * not-ready protocol until the query has delivered its first real result\n * (or errored, in which case it returns the empty value and `error()` is\n * set). Read this inside a `<Loading>` boundary.\n */\n ready: Accessor<TData>;\n error: Accessor<Error | undefined>;\n isLoading: Accessor<boolean>;\n isFetching: Accessor<boolean>;\n /**\n * True once the query has delivered a result AND no fetch cycle is in\n * flight (registration + initial sync included). While settled, results are\n * authoritative: a windowed query returning fewer rows than its LIMIT\n * really is the end of the list. Resets when the query identity changes.\n */\n isSettled: Accessor<boolean>;\n};\n\n// Overload: context-based (no explicit db)\nexport function createQuery<\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): CreateQueryResult<TData>;\n\n// Overload: explicit db\nexport function createQuery<\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): CreateQueryResult<TData>;\n\n// Implementation\nexport function createQuery<\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: SyncedDb<S> | QueryArg<S, TableName, T, RelatedFields, IsOne>,\n queryOrOptions?: QueryArg<S, TableName, T, RelatedFields, IsOne> | QueryOptions,\n maybeOptions?: QueryOptions\n): CreateQueryResult<TData> {\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 db = dbOrQuery;\n finalQuery = queryOrOptions as QueryArg<S, TableName, T, RelatedFields, IsOne>;\n options = maybeOptions;\n } else {\n db = useDb<S>();\n finalQuery = dbOrQuery;\n options = queryOrOptions as QueryOptions | undefined;\n }\n\n const sp00ky = db.getSp00ky();\n\n // Status channel. Written from subscription callbacks and generator\n // continuations, which run outside any tracking scope — `ownedWrite` opts\n // these signals out of Solid 2's owned-scope write guard.\n const [error, setError] = createSignal<Error | undefined>(undefined, { ownedWrite: true });\n const [isFetched, setIsFetched] = createSignal(false, { ownedWrite: true });\n const [isFetching, setIsFetching] = createSignal(false, { ownedWrite: true });\n\n // The hash of the currently-installed subscription, for opt-in deregister on\n // dispose (see `deregisterOnCleanup`).\n let activeHash: string | undefined;\n\n // Results live in a projection: each yielded emission is reconciled in place\n // keyed by `id` (unchanged rows keep identity; coarse readers are notified\n // on add/remove/reorder — probed in rc-semantics.test.ts, replacing the\n // Solid 1 reconcile + version-signal hack). `seedLoadingValue` births the\n // store committed, so `data()` reads never suspend.\n //\n // The compute's tracked reads (enabled, query thunk) all happen before the\n // first await — Solid 2 only creates dependency edges for pre-await reads. A\n // dep change restarts the generator; the superseded one is ABANDONED by\n // Solid (no return()/finally — probed), so the onCleanup registered\n // synchronously below is what tears down its subscriptions. This also\n // replaces the Solid 1 hook's runId/prevQueryString supersede machinery:\n // Solid dedupes re-runs whose tracked reads are unchanged, and identical\n // query identity means an identical hash means the same compute inputs.\n const store = createProjection(\n async function* (): AsyncGenerator<{ value: TData }> {\n const enabled = options?.enabled?.() ?? true;\n const query = typeof finalQuery === 'function' ? finalQuery() : finalQuery;\n\n if (!enabled || !query) {\n setIsFetched(false);\n setError(undefined);\n return;\n }\n\n // A new identity starts clean: a previous identity's failure must not\n // keep this one out of its loading state.\n setIsFetched(false);\n setError(undefined);\n\n const iterators: AsyncIterator<any>[] = [];\n const cleanups: (() => void)[] = [];\n onCleanup(() => {\n for (const it of iterators) void it.return?.();\n for (const c of cleanups) c();\n });\n\n try {\n /**\n * Registration can fail — the canonical case is the SSP answering 503\n * NOT_READY while it bootstraps. Surface it as `error()` instead of\n * throwing into the graph: the sync scheduler retries the\n * registration underneath, so a transient failure still recovers, and\n * a spinner driven by `isLoading()` resolves via `error()`.\n */\n const { hash } = await query.run();\n activeHash = hash;\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 cleanups.push(\n sp00ky.subscribeQueryStatus(hash, (status) => setIsFetching(status === 'fetching'), {\n immediate: true,\n })\n );\n\n const it = conflate<Record<string, any>[]>((cb) =>\n sp00ky.subscribe(hash, cb, { immediate: true })\n )[Symbol.asyncIterator]();\n iterators.push(it);\n\n let isFirstCall = true;\n while (true) {\n const r = await it.next();\n if (r.done) break;\n const e = r.value;\n const queryData = (query.isOne ? (e[0] ?? null) : e) as TData;\n // The first (immediate) callback with no data likely means the local\n // DB hasn't synced yet — don't mark as fetched so UI shows loading.\n const hasData = query.isOne\n ? queryData !== null && queryData !== undefined\n : e.length > 0;\n if (!isFirstCall || hasData) setIsFetched(true);\n isFirstCall = false;\n\n // Time the store commit (yield → resume) and report it as the\n // \"frontend\" phase for DevTools/MCP. Approximate: Solid reconciles\n // the yielded value before resuming the generator.\n const t0 = performance.now();\n yield { value: queryData };\n sp00ky.reportFrontendTiming(hash, performance.now() - t0);\n }\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n }\n },\n // Wrapped in an object so `one()` queries (row object or null) and list\n // queries share one store shape; `key` reconciles `value`'s contents.\n { value: null as TData },\n { key: 'id', seedLoadingValue: true }\n );\n\n // Fallback empty value served before the first emission of a list query.\n const emptyList = [] as unknown as TData;\n\n const data: Accessor<TData> = () => {\n const v = store.value;\n if (v === null || v === undefined) {\n const query = typeof finalQuery === 'function' ? finalQuery() : finalQuery;\n if (query && !query.isOne) return emptyList;\n }\n return v as TData;\n };\n\n // Suspending read: pends until the first real result (or error) via an async\n // memo that resolves when isFetched/error flips. Reading it inside <Loading>\n // integrates with Solid 2's boundary protocol; `data` stays non-throwing.\n const readyGate = createMemo(async (): Promise<true> => {\n if (isFetched() || error()) return true;\n // Tracked reads above registered the deps; park until one flips.\n await new Promise<void>(() => {});\n return true;\n });\n const ready: Accessor<TData> = () => {\n readyGate();\n return data();\n };\n\n // Tear down the live subscription when the hook's owner is disposed. The\n // projection's own onCleanup (inside the compute) already unsubscribes; this\n // hook-scope cleanup only handles the opt-in query deregistration.\n onCleanup(() => {\n // Opt-in: cancel the query once this hook (its last subscriber) is gone.\n // The compute's cleanup removed this hook's callback, so deregisterQuery's\n // refcount guard sees the true remaining-subscriber count.\n if (options?.deregisterOnCleanup && activeHash) {\n sp00ky.deregisterQuery(activeHash);\n }\n });\n\n const isLoading = () => !isFetched() && error() === undefined;\n const isSettled = () => isFetched() && !isFetching();\n\n return {\n data,\n ready,\n error,\n isLoading,\n isFetching,\n isSettled,\n };\n}\n\n/** @deprecated Renamed `createQuery` in the Solid 2 binding. */\nexport const useQuery = createQuery;\n","import type {\n ColumnSchema,\n FinalQuery,\n SchemaStructure,\n TableNames,\n} from '@spooky-sync/query-builder';\nimport { createEffect } from 'solid-js';\nimport { SyncedDb } from '..';\nimport type {\n Sp00kyQueryResultPromise,\n PreloadOptions as CorePreloadOptions,\n} from '@spooky-sync/core';\nimport { useDb } 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 db = useDb<S>();\n finalQuery = dbOrQuery;\n options = queryOrOptions as PreloadOptions | undefined;\n }\n\n let prevHash: number | undefined;\n\n // Two-arg Solid 2 effect: compute resolves the query (tracking its reactive\n // deps) and dedupes on the identity hash; the untracked apply fires the\n // preload. Returning `undefined` from compute skips nothing — apply guards.\n createEffect(\n () => {\n if (!(options?.enabled?.() ?? true)) return undefined;\n const query = typeof finalQuery === 'function' ? finalQuery() : finalQuery;\n if (!query) return undefined;\n // Dedupe on the query's stable identity hash so a reactive re-run with\n // an unchanged query doesn't refetch (the core also dedupes per session).\n if (query.hash === prevHash) return undefined;\n prevHash = query.hash;\n return query;\n },\n (query) => {\n if (!query) return;\n void db\n .getSp00ky()\n .preload(query, { refresh: options?.refresh, staleTime: options?.staleTime });\n }\n );\n}\n","import type { Accessor } from 'solid-js';\nimport { useDb } from './context';\nimport { fromSubscription } from './from-subscription';\nimport type { ConnectionState, 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 * Transport state of the remote WebSocket. Flips the instant the socket\n * drops, unlike `status`, which only degrades after a sustained run of failed\n * sync rounds — so this is what to drive a \"reconnecting…\" affordance off.\n */\n connection: Accessor<ConnectionState>;\n /**\n * `true` while the connection is being re-established. Usually still\n * `isHealthy()`: a short reconnect is invisible to sync, and writes made\n * during it are queued locally and pushed once the socket is back.\n */\n isReconnecting: 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 // The subscription fires synchronously with the current status; the initial\n // value (loadingValue) just avoids a flash before it lands.\n const health = fromSubscription<SyncHealth>((cb) => db.subscribeToSyncHealth(cb), db.syncHealth);\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 connection: () => health().connection,\n isReconnecting: () => health().connection === 'reconnecting',\n };\n}\n","import type { Accessor } from 'solid-js';\nimport { useDb } from './context';\nimport { fromSubscription } from './from-subscription';\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 const health = fromSubscription<StorageHealth>(\n (cb) => db.subscribeToStorageHealth(cb),\n db.storageHealth\n );\n\n return {\n health,\n status: () => health().status,\n isPersistent: () => health().status === 'persistent',\n isMemoryFallback: () => health().fallback,\n };\n}\n","import { createEffect, createSignal, type Accessor } from 'solid-js';\nimport { useDb } 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 = useDb();\n const [crdtField, setCrdtField] = createSignal<CrdtField | null>(null, { ownedWrite: true });\n\n // Two-arg Solid 2 effect: the compute tracks `recordId`, the apply owns the\n // open/close lifecycle and returns the cleanup — which runs both when the id\n // changes (before the next apply) and on unmount. That replaces the Solid 1\n // version's manual currentId/initialized bookkeeping.\n createEffect(\n () => recordId(),\n (id) => {\n if (!id) {\n setCrdtField(null);\n return;\n }\n const sp00ky = db.getSp00ky();\n let superseded = false;\n const text = fallbackText?.();\n sp00ky\n .openCrdtField(table, id, field, text)\n .then((cf) => {\n if (!superseded) {\n setCrdtField(cf);\n } else {\n sp00ky.closeCrdtField(table, id, field);\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(`[useCrdtField] Failed to open CRDT field ${table}.${field} on ${id}:`, err);\n });\n return () => {\n superseded = true;\n if (crdtField()) {\n sp00ky.closeCrdtField(table, id, field);\n setCrdtField(null);\n }\n };\n }\n );\n\n return crdtField;\n}\n","import type { Accessor } from 'solid-js';\nimport { onCleanup } from 'solid-js';\nimport { useDb } from './context';\nimport { fromSubscription } from './from-subscription';\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 `createQuery`, 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(key: string, options?: FeatureFlagOptions): UseFeatureFlag {\n const db = useDb();\n const handle = db.getSp00ky().feature(key, options);\n onCleanup(() => handle.close());\n\n const state = fromSubscription<{ variant: string | undefined; payload: unknown }>(\n (cb) =>\n handle.subscribe((s) => cb({ variant: s.variant ?? options?.fallback, payload: s.payload })),\n { variant: handle.variant(), payload: handle.payload() }\n );\n\n return {\n variant: () => state().variant,\n payload: () => state().payload,\n enabled: () => {\n const v = state().variant;\n return v !== undefined && v !== 'off';\n },\n };\n}\n","import type { Accessor } from 'solid-js';\nimport { onCleanup } from 'solid-js';\nimport { useDb } from './context';\nimport { fromSubscription } from './from-subscription';\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 onCleanup(() => handle.close());\n\n const snapshot = fromSubscription<AppReleaseSnapshot>(\n (cb) => handle.subscribe(cb),\n handle.snapshot()\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 { BucketPutOptions, BucketPutResult } 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: (\n path: string,\n file: File | Blob,\n options?: BucketPutOptions\n ) => Promise<BucketPutResult | 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 // Written from async continuations — outside any tracking scope.\n const [isUploading, setIsUploading] = createSignal(false, { ownedWrite: true });\n const [error, setError] = createSignal<Error | null>(null, { ownedWrite: true });\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 (\n path: string,\n file: File | Blob,\n options?: BucketPutOptions\n ): Promise<BucketPutResult | 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 return await db.bucket(bucketName).put(path, bytes, options);\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 { BlobUrlLease } from '@spooky-sync/core';\nimport type { SyncedDb } from '../index';\nimport { useDb } from './context';\n\nexport interface UseDownloadFileOptions {\n /**\n * Master switch, default `true`. `false` gives every hook instance its own\n * private object URL fetched fresh from the bucket and revoked on unmount —\n * no sharing, no persistence, no reuse.\n */\n cache?: boolean;\n /**\n * Keep the bytes in OPFS so they survive a reload and are available offline.\n * Default `true`. Turn off for one-shot or sensitive files; the in-tab object\n * URL is still shared between components rendering the same path.\n */\n persist?: boolean;\n /** Exempt this file from pressure eviction. Pinned bytes never expire. */\n pin?: boolean;\n /**\n * `'never'` (default) treats a bucket path as immutable, which is how paths\n * are written (`crypto.randomUUID() + ext`). `'head'` spends a remote `head()`\n * to compare sizes before trusting the cached copy — for paths the app\n * overwrites in place.\n */\n revalidate?: 'never' | 'head';\n}\n\nexport interface UseDownloadFileResult {\n url: Accessor<string | null>;\n isLoading: Accessor<boolean>;\n error: Accessor<Error | null>;\n refetch: () => void;\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 // Written from fetch continuations — outside any tracking scope.\n const [url, setUrl] = createSignal<string | null>(null, { ownedWrite: true });\n const [isLoading, setIsLoading] = createSignal(false, { ownedWrite: true });\n const [error, setError] = createSignal<Error | null>(null, { ownedWrite: true });\n\n // Exactly one of these is held at a time: a refcounted lease on the shared\n // cache entry, or a private URL this instance minted and must revoke itself.\n let lease: BlobUrlLease | null = null;\n let privateUrl: string | null = null;\n\n const [refetchSignal, setRefetchSignal] = createSignal(0);\n /** Consumed by the next effect run, so `refetch()` bypasses every layer once. */\n let reloadOnce = false;\n\n function releaseCurrent() {\n lease?.release();\n lease = null;\n if (privateUrl) {\n URL.revokeObjectURL(privateUrl);\n privateUrl = null;\n }\n }\n\n // Two-arg Solid 2 effect: compute tracks path + refetch tick; apply runs the\n // fetch and returns the cancel/release cleanup, which runs before the next\n // apply and on unmount.\n createEffect(\n () => {\n refetchSignal();\n return path();\n },\n (filePath) => {\n releaseCurrent();\n\n if (!filePath) {\n setUrl(null);\n setIsLoading(false);\n setError(null);\n return;\n }\n\n const reload = reloadOnce;\n reloadOnce = false;\n\n let cancelled = false;\n setIsLoading(true);\n setError(null);\n\n const bucket = db.bucket(bucketName);\n const resolve = useCache\n ? bucket\n .url(filePath, {\n persist: options.persist !== false,\n pin: options.pin,\n revalidate: options.revalidate,\n reload,\n })\n .then((acquired) => {\n if (!acquired) return null;\n if (cancelled) {\n // Unmounted or the path changed mid-flight — hand the reference\n // straight back, or the entry never drops to zero and its object\n // URL leaks for the life of the tab.\n acquired.release();\n return null;\n }\n lease = acquired;\n return acquired.url;\n })\n : bucket.read(filePath, { persist: false, reload: true }).then((blob) => {\n if (!blob || cancelled) return null;\n privateUrl = URL.createObjectURL(blob);\n return privateUrl;\n });\n\n resolve.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 return () => {\n cancelled = true;\n };\n }\n );\n\n onCleanup(() => {\n releaseCurrent();\n });\n\n const refetch = () => {\n reloadOnce = true;\n setRefetchSignal((n) => n + 1);\n };\n\n return { url, isLoading, error, refetch };\n}\n","import type { Element } from 'solid-js';\nimport {\n createSignal,\n onSettled,\n onCleanup,\n createComponent,\n createMemo,\n merge,\n} from 'solid-js';\nimport type { SchemaStructure } from '@spooky-sync/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?: 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: Element;\n}\n\nexport function Sp00kyProvider<S extends SchemaStructure>(\n props: Sp00kyProviderProps<S>\n): Element {\n const merged = merge({ fallback: undefined as Element | undefined }, props);\n\n // Written from the async init continuation — outside any tracking scope.\n const [db, setDb] = createSignal<SyncedDb<S> | undefined>(undefined, { ownedWrite: true });\n\n // Init is async, so a dispose can land mid-init. Only that narrow race is\n // handled here: an instance whose init finished AFTER the provider was\n // already gone is closed, because nothing will ever reference it.\n //\n // A live, mounted client is deliberately NOT closed on cleanup. Doing that\n // nulls `SyncedDb.sp00ky`, so every later `create`/`update`/`delete` throws\n // \"SyncedDb not initialized\" while reads keep rendering from state that is\n // already subscribed — i.e. mutations die silently and the app looks fine. In\n // a host app the provider wraps the whole tree and only unmounts with the\n // page, where the browser reclaims the worker anyway, so the leak this was\n // meant to fix is worth far less than that risk.\n let disposed = false;\n\n onCleanup(() => {\n disposed = true;\n });\n\n // `onSettled` replaces Solid 1's `onMount`.\n onSettled(() => {\n void (async () => {\n try {\n const instance = new SyncedDb<S>(merged.config);\n await instance.init();\n if (disposed) {\n await instance.close();\n return;\n }\n // Gate first-load UI on prewarmed data. Best-effort: never let a\n // preload 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\n const content = createMemo(() => {\n const instance = db();\n if (!instance) return merged.fallback;\n // Solid 2: the context object IS the provider component.\n return createComponent(Sp00kyContext, {\n value: instance,\n get children() {\n return merged.children;\n },\n });\n });\n\n return content as unknown as Element;\n}\n","import { createSignal, type Accessor } from 'solid-js';\n\nexport interface Submission<Args extends unknown[], R> {\n /** Run the wrapped async fn. Concurrent submits share the pending flag. */\n submit: (...args: Args) => Promise<R | undefined>;\n /** True while at least one submit is in flight. */\n pending: Accessor<boolean>;\n /** Error from the most recent settled submit, cleared on the next submit. */\n error: Accessor<Error | undefined>;\n /** Result of the most recent successful submit. */\n result: Accessor<R | undefined>;\n clearError: () => void;\n}\n\n/**\n * Thin submission-state wrapper for mutations — button spinner/disable state\n * around `db.create/update/delete/run` calls.\n *\n * Deliberately NOT built on Solid 2's `action()`/`createOptimisticStore`: the\n * spooky engine is already optimistic local-first (writes commit to the local\n * DB and re-render through live queries before sync; `run()` is an outbox\n * CREATE), so a transaction/revert layer on top buys nothing and `action()`'s\n * await-vs-yield transaction escape is a real footgun. Errors here mean the\n * LOCAL commit failed — sync/push failures surface through `useSyncStatus`\n * and `usePendingMutations` instead.\n */\nexport function createSubmission<Args extends unknown[], R>(\n fn: (...args: Args) => Promise<R>\n): Submission<Args, R> {\n // Written from promise continuations — outside any tracking scope.\n const [inFlight, setInFlight] = createSignal(0, { ownedWrite: true });\n const [error, setError] = createSignal<Error | undefined>(undefined, { ownedWrite: true });\n const [result, setResult] = createSignal<R | undefined>(undefined, { ownedWrite: true });\n\n const submit = async (...args: Args): Promise<R | undefined> => {\n setError(undefined);\n setInFlight((n) => n + 1);\n try {\n const r = await fn(...args);\n setResult(() => r);\n return r;\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return undefined;\n } finally {\n setInFlight((n) => n - 1);\n }\n };\n\n return {\n submit,\n pending: () => inFlight() > 0,\n error,\n result,\n clearError: () => setError(undefined),\n };\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 { createQuery, useQuery, type CreateQueryResult, type QueryOptions } from './lib/create-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 {\n SyncHealth,\n SyncHealthStatus,\n SyncHealthConfig,\n ConnectionState,\n ReconnectConfig,\n} 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 {\n useDownloadFile,\n type UseDownloadFileOptions,\n type UseDownloadFileResult,\n} from './lib/use-download-file';\nexport { Sp00kyProvider, type Sp00kyProviderProps } from './lib/Sp00kyProvider';\nexport { useDb, usePendingMutations } from './lib/context';\nexport { createSubmission, type Submission } from './lib/create-submission';\nexport { conflate } from './lib/conflate';\nexport { fromSubscription } from './lib/from-subscription';\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 *\n * NOTE: keep in sync with packages/client-solid/src/index.ts (SyncedDb).\n * Copied rather than shared so this package's dependency graph never pulls\n * in solid-js 1.x; fold the two together once client-solid moves to Solid 2.\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 * Tear down the client: leaves the tabs broker, closes the local store and\n * remote socket, and frees the wasm circuit. Without this a remounted provider\n * (or an HMR reload) strands a whole client, and the abandoned wasm heaps stay\n * resident because V8 cannot see how much wasm memory a dropped wrapper holds.\n */\n async close(): Promise<void> {\n const instance = this.sp00ky;\n this.sp00ky = null;\n this._initialized = false;\n if (instance) await instance.close();\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\n // name; Solid 2 store proxies serve methods BOUND, so a RecordId read out\n // of a query row reports 'bound RecordId' — accept both.)\n const ctorName = (selector as any)?.constructor?.name;\n const isRecordId =\n selector instanceof RecordId || ctorName === 'RecordId' || ctorName === 'bound 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 `createQuery` 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<B extends BackendNames<S>, R extends BackendRoutes<S, B>>(\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 * 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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"}