@spooky-sync/client-solid 0.0.1-canary.17 → 0.0.1-canary.170

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["SpookyClient","RecordId"],"sources":["../src/lib/context.ts","../src/lib/use-query.ts","../src/lib/use-file-upload.ts","../src/lib/use-download-file.ts","../src/lib/SpookyProvider.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 SpookyContext = createContext<SyncedDb<any> | undefined>();\n\nexport function useDb<S extends SchemaStructure>(): SyncedDb<S> {\n const db = useContext(SpookyContext);\n if (!db) {\n throw new Error('useDb must be used within a <SpookyProvider>. Wrap your app in <SpookyProvider config={...}>.');\n }\n return db as SyncedDb<S>;\n}\n","import {\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 { SyncedDb } from '..';\nimport { SpookyQueryResultPromise } from '@spooky-sync/core';\nimport { SpookyContext } 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, SpookyQueryResultPromise>\n | (() =>\n | FinalQuery<S, TableName, T, RelatedFields, IsOne, SpookyQueryResultPromise>\n | null\n | undefined);\n\ntype QueryOptions = { enabled?: () => boolean };\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): { data: () => TData | undefined; error: () => Error | undefined; isLoading: () => boolean };\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): { data: () => TData | undefined; error: () => Error | undefined; isLoading: () => boolean };\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(SpookyContext);\n if (!contextDb) {\n throw new Error(\n 'useQuery: No db argument provided and no SpookyContext found. ' +\n 'Either pass a SyncedDb instance or wrap your app in <SpookyProvider>.'\n );\n }\n db = contextDb as SyncedDb<S>;\n finalQuery = dbOrQuery;\n options = queryOrOptions as QueryOptions | undefined;\n }\n\n const [data, setData] = createSignal<TData | undefined>(undefined);\n const [error, setError] = createSignal<Error | undefined>(undefined);\n const [isFetched, setIsFetched] = createSignal(false);\n const [unsubscribe, setUnsubscribe] = createSignal<(() => void) | undefined>(undefined);\n let prevQueryString: string | undefined;\n\n const spooky = db.getSpooky();\n\n const initQuery = async (\n query: FinalQuery<S, TableName, T, RelatedFields, IsOne, SpookyQueryResultPromise>\n ) => {\n const { hash } = await query.run();\n setError(undefined);\n\n let isFirstCall = true;\n const unsub = await spooky.subscribe(\n hash,\n (e) => {\n const data = (query.isOne ? e[0] : e) as TData;\n setData(() => data);\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 ? data != null : (e as any[]).length > 0;\n if (!isFirstCall || hasData) {\n setIsFetched(true);\n }\n isFirstCall = false;\n },\n { immediate: true }\n );\n\n setUnsubscribe(() => unsub);\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 // Prevent re-running if query hasn't changed\n const queryString = JSON.stringify(query);\n if (queryString === prevQueryString) {\n return;\n }\n prevQueryString = queryString;\n\n // Reset fetched state when query changes\n setIsFetched(false);\n initQuery(query);\n\n // Cleanup\n onCleanup(() => {\n unsubscribe()?.();\n });\n });\n\n const isLoading = () => {\n return !isFetched() && error() === undefined;\n };\n\n return {\n data,\n error,\n isLoading,\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 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 && 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 let refetchTrigger: () => void;\n const [refetchSignal, setRefetchSignal] = createSignal(0);\n 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 },\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 { createSignal, onMount, createComponent, createMemo, JSX, mergeProps } from 'solid-js';\nimport type { SchemaStructure } from '@spooky/query-builder';\nimport type { SyncedDbConfig } from '../types';\nimport { SyncedDb } from '../index';\nimport { SpookyContext } from './context';\n\nexport interface SpookyProviderProps<S extends SchemaStructure> {\n config: SyncedDbConfig<S>;\n fallback?: JSX.Element;\n onError?: (error: Error) => void;\n onReady?: (db: SyncedDb<S>) => void;\n children: JSX.Element;\n}\n\nexport function SpookyProvider<S extends SchemaStructure>(\n props: SpookyProviderProps<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 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 console.error('SpookyProvider: 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(SpookyContext.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 SpookyClient,\n AuthService,\n BucketHandle,\n type SpookyQueryResultPromise,\n UpdateOptions,\n RunOptions,\n} from '@spooky-sync/core';\n\nimport {\n GetTable,\n QueryBuilder,\n SchemaStructure,\n TableModel,\n TableNames,\n QueryResult,\n RelatedFieldsMap,\n RelationshipFieldsFromSchema,\n GetRelationship,\n RelatedFieldMapEntry,\n InnerQuery,\n BackendNames,\n BackendRoutes,\n RoutePayload,\n BucketNames,\n BucketDefinitionSchema,\n} from '@spooky-sync/query-builder';\n\nimport { RecordId, Uuid, Surreal } from 'surrealdb';\nexport { RecordId, Uuid };\nexport type { Model, GenericModel, GenericSchema, ModelPayload } from './lib/models';\nexport { useQuery } from './lib/use-query';\nexport { useFileUpload, type FileUploadResult } from './lib/use-file-upload';\nexport { useDownloadFile, type UseDownloadFileOptions, type UseDownloadFileResult } from './lib/use-download-file';\nexport { SpookyProvider, type SpookyProviderProps } from './lib/SpookyProvider';\nexport { useDb } from './lib/context';\n\n// export { AuthEventTypes } from \"@spooky-sync/core\"; // TODO: Verify if AuthEventTypes exists in core\nexport type {};\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} from '@spooky-sync/query-builder';\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 spooky-ts for Solid.js integration\n * Delegates all logic to the underlying spooky-ts instance\n */\nexport class SyncedDb<S extends SchemaStructure> {\n private config: SyncedDbConfig<S>;\n private spooky: SpookyClient<S> | null = null;\n private _initialized = false;\n\n constructor(config: SyncedDbConfig<S>) {\n this.config = config;\n }\n\n public getSpooky(): SpookyClient<S> {\n if (!this.spooky) throw new Error('SyncedDb not initialized');\n return this.spooky;\n }\n\n /**\n * Initialize the spooky-ts instance\n */\n async init(): Promise<void> {\n if (this._initialized) return;\n this.spooky = new SpookyClient<S>(this.config);\n await this.spooky.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.spooky) throw new Error('SyncedDb not initialized');\n await this.spooky.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.spooky) throw new Error('SyncedDb not initialized');\n await this.spooky.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 | InnerQuery<GetTable<S, TName>, boolean>\n ): Promise<void> {\n if (!this.spooky) throw new Error('SyncedDb not initialized');\n if (typeof selector !== 'string')\n throw new Error('Only string ID selectors are supported currently with core');\n await this.spooky.delete(tableName as string, selector);\n }\n\n /**\n * Query data from the database\n */\n public query<TName extends TableNames<S>>(\n table: TName\n ): QueryBuilder<S, TName, SpookyQueryResultPromise, {}, false> {\n if (!this.spooky) throw new Error('SyncedDb not initialized');\n return this.spooky.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.spooky) throw new Error('SyncedDb not initialized');\n await this.spooky.run(backend, path, payload, options);\n }\n\n /**\n * Authenticate with the database\n */\n public async authenticate(token: string): Promise<RecordId<string>> {\n const result = await this.spooky?.authenticate(token);\n // SpookyClient.authenticate returns whatever remote.authenticate returns (boolean or token usually?)\n // Wait, checked SpookyClient: 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.spooky) throw new Error('SyncedDb not initialized');\n await this.spooky.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.spooky) throw new Error('SyncedDb not initialized');\n return await this.spooky.useRemote(fn);\n }\n /**\n * Access the remote database service directly\n */\n get remote(): SpookyClient<S>['remoteClient'] {\n if (!this.spooky) throw new Error('SyncedDb not initialized');\n return this.spooky.remoteClient;\n }\n\n /**\n * Access the local database service directly\n */\n get local(): SpookyClient<S>['localClient'] {\n if (!this.spooky) throw new Error('SyncedDb not initialized');\n return this.spooky.localClient;\n }\n\n /**\n * Access the auth service\n */\n get auth(): AuthService<S> {\n if (!this.spooky) throw new Error('SyncedDb not initialized');\n return this.spooky.auth;\n }\n\n get pendingMutationCount(): number {\n if (!this.spooky) throw new Error('SyncedDb not initialized');\n return this.spooky.pendingMutationCount;\n }\n\n subscribeToPendingMutations(cb: (count: number) => void): () => void {\n if (!this.spooky) throw new Error('SyncedDb not initialized');\n return this.spooky.subscribeToPendingMutations(cb);\n }\n\n bucket<B extends BucketNames<S>>(name: B): BucketHandle {\n if (!this.spooky) throw new Error('SyncedDb not initialized');\n return this.spooky.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 { 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 // 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 connection: () => health().connection,\n isReconnecting: () => health().connection === 'reconnecting',\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 { 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 const [url, setUrl] = createSignal<string | null>(null);\n const [isLoading, setIsLoading] = createSignal(false);\n const [error, setError] = createSignal<Error | null>(null);\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 createEffect(() => {\n const filePath = path();\n // Subscribe to refetch signal so effect re-runs\n refetchSignal();\n\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 onCleanup(() => {\n cancelled = true;\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 { JSX } from 'solid-js';\nimport {\n createSignal,\n onMount,\n onCleanup,\n createComponent,\n createMemo,\n mergeProps,\n} 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` 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 already\n // 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 onMount(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 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 {\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 } from './lib/context';\n\n// export { AuthEventTypes } from \"@spooky-sync/core\"; // TODO: Verify if AuthEventTypes exists in core\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 * 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 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<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 * 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 { AuthService, BucketHandle, RunOptions, SpookyClient, SpookyConfig, SpookyQueryResultPromise, UpdateOptions } from "@spooky-sync/core";
3
+ import { AppReleaseOptions, AuthService, BucketHandle, ConnectionState, ConnectionState as ConnectionState$1, CrdtField, FeatureFlagOptions, PreloadOptions, PreloadOptions as PreloadOptions$1, PreloadRefresh, ReconnectConfig, 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
 
@@ -37,14 +37,22 @@ type InferRelationshipsFromConst<S extends SchemaStructure$1, Schema extends Gen
37
37
  cardinality: Rel['cardinality'];
38
38
  } } };
39
39
  type Prettify<T> = { [K in keyof T]: T[K] } & {};
40
- type SyncedDbConfig<S extends SchemaStructure$1> = Prettify<SpookyConfig<S>>;
40
+ type SyncedDbConfig<S extends SchemaStructure$1> = Prettify<Sp00kyConfig<S>>;
41
41
  //#endregion
42
42
  //#region src/lib/use-query.d.ts
43
43
  type QueryArg<S extends SchemaStructure$1, TableName extends TableNames$1<S>, T extends {
44
44
  columns: Record<string, ColumnSchema>;
45
- }, RelatedFields extends Record<string, any>, IsOne extends boolean> = FinalQuery<S, TableName, T, RelatedFields, IsOne, SpookyQueryResultPromise> | (() => FinalQuery<S, TableName, T, RelatedFields, IsOne, SpookyQueryResultPromise> | null | undefined);
45
+ }, RelatedFields extends Record<string, any>, IsOne extends boolean> = FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise> | (() => FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise> | null | undefined);
46
46
  type QueryOptions = {
47
47
  enabled?: () => boolean;
48
+ /**
49
+ * Tear down the query (remote `_00_query` view + local WASM view) when this
50
+ * hook is disposed and no other subscriber remains, instead of keeping it
51
+ * resident for cheap re-subscription. Use for viewport-windowed lists that
52
+ * mount/unmount a query per scroll window and want off-screen windows
53
+ * cancelled. Trade-off: scrolling back to a torn-down window re-registers it.
54
+ */
55
+ deregisterOnCleanup?: boolean;
48
56
  };
49
57
  declare function useQuery<S extends SchemaStructure$1, TableName extends TableNames$1<S>, T extends {
50
58
  columns: Record<string, ColumnSchema>;
@@ -52,6 +60,8 @@ declare function useQuery<S extends SchemaStructure$1, TableName extends TableNa
52
60
  data: () => TData | undefined;
53
61
  error: () => Error | undefined;
54
62
  isLoading: () => boolean;
63
+ isFetching: () => boolean;
64
+ isSettled: () => boolean;
55
65
  };
56
66
  declare function useQuery<S extends SchemaStructure$1, TableName extends TableNames$1<S>, T extends {
57
67
  columns: Record<string, ColumnSchema>;
@@ -59,8 +69,165 @@ declare function useQuery<S extends SchemaStructure$1, TableName extends TableNa
59
69
  data: () => TData | undefined;
60
70
  error: () => Error | undefined;
61
71
  isLoading: () => boolean;
72
+ isFetching: () => boolean;
73
+ isSettled: () => boolean;
62
74
  };
63
75
  //#endregion
76
+ //#region src/lib/create-preload.d.ts
77
+ type PreloadArg<S extends SchemaStructure$1, TableName extends TableNames$1<S>, T extends {
78
+ columns: Record<string, ColumnSchema>;
79
+ }, RelatedFields extends Record<string, any>, IsOne extends boolean> = FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise> | (() => FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise> | null | undefined);
80
+ type PreloadOptions$2 = PreloadOptions$1 & {
81
+ /** Only preload while this returns true (defaults to always). */
82
+ enabled?: () => boolean;
83
+ };
84
+ declare function createPreload<S extends SchemaStructure$1, TableName extends TableNames$1<S>, T extends {
85
+ columns: Record<string, ColumnSchema>;
86
+ }, RelatedFields extends Record<string, any>, IsOne extends boolean>(finalQuery: PreloadArg<S, TableName, T, RelatedFields, IsOne>, options?: PreloadOptions$2): void;
87
+ declare function createPreload<S extends SchemaStructure$1, TableName extends TableNames$1<S>, T extends {
88
+ columns: Record<string, ColumnSchema>;
89
+ }, RelatedFields extends Record<string, any>, IsOne extends boolean>(db: SyncedDb<S>, finalQuery: PreloadArg<S, TableName, T, RelatedFields, IsOne>, options?: PreloadOptions$2): void;
90
+ //#endregion
91
+ //#region src/lib/use-sync-status.d.ts
92
+ interface UseSyncStatus {
93
+ /** Full health snapshot; updates reactively on every transition. */
94
+ health: Accessor<SyncHealth$1>;
95
+ /** `'healthy'` | `'degraded'`. */
96
+ status: Accessor<SyncHealthStatus$1>;
97
+ isHealthy: Accessor<boolean>;
98
+ /** `true` once sync has failed for a sustained run — drive a banner off this. */
99
+ isDegraded: Accessor<boolean>;
100
+ /** `true` once at least one sync round has succeeded this session. */
101
+ everConnected: Accessor<boolean>;
102
+ /**
103
+ * `true` only for a real lost connection: degraded AFTER a first successful
104
+ * sync. Stays `false` during the initial "connecting" phase (degraded but
105
+ * never reached the server yet), so an indicator can show nothing until the
106
+ * app has actually connected once.
107
+ */
108
+ isOffline: Accessor<boolean>;
109
+ /**
110
+ * Transport state of the remote WebSocket. Flips the instant the socket
111
+ * drops, unlike `status`, which only degrades after a sustained run of failed
112
+ * sync rounds — so this is what to drive a "reconnecting…" affordance off.
113
+ */
114
+ connection: Accessor<ConnectionState$1>;
115
+ /**
116
+ * `true` while the connection is being re-established. Usually still
117
+ * `isHealthy()`: a short reconnect is invisible to sync, and writes made
118
+ * during it are queued locally and pushed once the socket is back.
119
+ */
120
+ isReconnecting: Accessor<boolean>;
121
+ }
122
+ /**
123
+ * Observe sync health for a "can't reach the server" banner / indicator.
124
+ *
125
+ * Backed by `db.subscribeToSyncHealth`. Individual sync failures (a transient
126
+ * remote 500 on query registration, a dropped socket) are absorbed by the
127
+ * retry and never flip this; `isDegraded()` only goes true once failures
128
+ * persist for the configured number of consecutive rounds (sp00ky core config
129
+ * `syncHealth.degradeAfterConsecutiveFailures`, default 3), and flips back on
130
+ * the next successful round. Must be used within a `<Sp00kyProvider>`.
131
+ */
132
+ declare function useSyncStatus(): UseSyncStatus;
133
+ //# sourceMappingURL=use-sync-status.d.ts.map
134
+ //#endregion
135
+ //#region src/lib/use-storage-status.d.ts
136
+ interface UseStorageStatus {
137
+ /** Full durability snapshot; updates reactively. */
138
+ health: Accessor<StorageHealth$1>;
139
+ /** `'unknown'` | `'persistent'` | `'memory'`. */
140
+ status: Accessor<StorageHealthStatus$1>;
141
+ /** `true` when the local store survives a reload. */
142
+ isPersistent: Accessor<boolean>;
143
+ /**
144
+ * `true` only when durable storage was requested and could NOT be opened, so
145
+ * the dataset is sitting in RAM and local writes die on reload. Drive a
146
+ * warning off this, not off `status`: a store configured as in-memory reports
147
+ * `'memory'` too, and that is a choice rather than a problem.
148
+ */
149
+ isMemoryFallback: Accessor<boolean>;
150
+ }
151
+ /**
152
+ * Observe how durable the LOCAL cache is, for a "no local storage" warning.
153
+ *
154
+ * Under `localEngine: 'sqlite'` with `store: 'indexeddb'` the durable store is
155
+ * the OPFS SAHPool VFS, and only ONE client per bucket can hold it open: a
156
+ * second tab of the same app cannot get it and runs in memory instead (the
157
+ * engine retries first, so a closing tab's lock is usually waited out). Must be
158
+ * used within a `<Sp00kyProvider>`.
159
+ */
160
+ declare function useStorageStatus(): UseStorageStatus;
161
+ //# sourceMappingURL=use-storage-status.d.ts.map
162
+ //#endregion
163
+ //#region src/lib/use-crdt-field.d.ts
164
+ declare function useCrdtField(table: string, recordId: () => string | undefined, field: string, fallbackText?: () => string | undefined): Accessor<CrdtField | null>;
165
+ //# sourceMappingURL=use-crdt-field.d.ts.map
166
+ //#endregion
167
+ //#region src/lib/use-feature-flag.d.ts
168
+ interface UseFeatureFlag {
169
+ variant: Accessor<string | undefined>;
170
+ payload: Accessor<unknown | undefined>;
171
+ enabled: Accessor<boolean>;
172
+ }
173
+ /**
174
+ * Subscribe to a feature flag for the currently authenticated user.
175
+ *
176
+ * Returns three Solid accessors that update reactively whenever the
177
+ * server-materialized assignment in `_00_user_feature` changes. Backed by
178
+ * the same SSP + sync pipeline that powers `useQuery`, so toggling a flag
179
+ * via `spky flag enable <key>` propagates to the UI without a refresh.
180
+ *
181
+ * `enabled()` is `true` when the resolved variant exists and is not 'off'.
182
+ * For multi-variant flags, prefer `variant()` directly.
183
+ */
184
+ declare function useFeatureFlag(key: string, options?: FeatureFlagOptions): UseFeatureFlag;
185
+ //# sourceMappingURL=use-feature-flag.d.ts.map
186
+ //#endregion
187
+ //#region src/lib/use-app-release.d.ts
188
+ interface UseAppReleaseOptions extends AppReleaseOptions {
189
+ /** App name from sp00ky.yml, e.g. `web`. */
190
+ app: string;
191
+ /**
192
+ * The running build's version (X.Y.Z), typically baked in at build time
193
+ * (e.g. a vite `define` from package.json). `updateAvailable()` is true when
194
+ * the announced release is semver-newer than this.
195
+ */
196
+ currentVersion: string;
197
+ }
198
+ interface UseAppRelease {
199
+ /** Latest announced version for the app, or undefined when no row exists. */
200
+ latestVersion: Accessor<string | undefined>;
201
+ /** Announced version is semver-newer than the running build. */
202
+ updateAvailable: Accessor<boolean>;
203
+ /** The newer release asks clients to update/reload without prompting. */
204
+ mandatory: Accessor<boolean>;
205
+ /** The newer release asks reloads to clear service-worker caches first. */
206
+ cacheBust: Accessor<boolean>;
207
+ /**
208
+ * Reload onto the announced release. Plain `location.reload()` normally;
209
+ * when the release is flagged cache-bust, CacheStorage is cleared, the
210
+ * service-worker registration is nudged to update, and navigation carries a
211
+ * `?cb=` token to punch through intermediary caches. The service worker is
212
+ * deliberately NOT unregistered: navigating while still controlled by a
213
+ * just-unregistered worker strands subresource fetches on the dead worker
214
+ * and the page hangs until a manual reload.
215
+ */
216
+ reload: () => Promise<void>;
217
+ }
218
+ /**
219
+ * Observe the app's announced release (`_00_app_release:<app>`, written by
220
+ * `spky deploy` / `spky release`) and compare it against the running build.
221
+ *
222
+ * Typical use: mount a small "new version available — Reload" notification
223
+ * gated on `updateAvailable()`, auto-invoking `reload()` when `mandatory()`
224
+ * (guard the auto path against reload loops with a per-version marker, since
225
+ * a client can reload while the deploy is still rolling out and land on the
226
+ * old bundle again).
227
+ */
228
+ declare function useAppRelease(options: UseAppReleaseOptions): UseAppRelease;
229
+ //# sourceMappingURL=use-app-release.d.ts.map
230
+ //#endregion
64
231
  //#region src/lib/use-file-upload.d.ts
65
232
  interface FileUploadResult {
66
233
  isUploading: () => boolean;
@@ -77,7 +244,27 @@ declare function useFileUpload<S extends SchemaStructure$1>(db: SyncedDb<S>, buc
77
244
  //#endregion
78
245
  //#region src/lib/use-download-file.d.ts
79
246
  interface UseDownloadFileOptions {
247
+ /**
248
+ * Master switch, default `true`. `false` gives every hook instance its own
249
+ * private object URL fetched fresh from the bucket and revoked on unmount —
250
+ * no sharing, no persistence, no reuse.
251
+ */
80
252
  cache?: boolean;
253
+ /**
254
+ * Keep the bytes in OPFS so they survive a reload and are available offline.
255
+ * Default `true`. Turn off for one-shot or sensitive files; the in-tab object
256
+ * URL is still shared between components rendering the same path.
257
+ */
258
+ persist?: boolean;
259
+ /** Exempt this file from pressure eviction. Pinned bytes never expire. */
260
+ pin?: boolean;
261
+ /**
262
+ * `'never'` (default) treats a bucket path as immutable, which is how paths
263
+ * are written (`crypto.randomUUID() + ext`). `'head'` spends a remote `head()`
264
+ * to compare sizes before trusting the cached copy — for paths the app
265
+ * overwrites in place.
266
+ */
267
+ revalidate?: 'never' | 'head';
81
268
  }
82
269
  interface UseDownloadFileResult {
83
270
  url: Accessor<string | null>;
@@ -89,16 +276,25 @@ declare function useDownloadFile<S extends SchemaStructure$1>(bucketName: Bucket
89
276
  declare function useDownloadFile<S extends SchemaStructure$1>(db: SyncedDb<S>, bucketName: BucketNames<S>, path: Accessor<string | null | undefined>, options?: UseDownloadFileOptions): UseDownloadFileResult;
90
277
  //# sourceMappingURL=use-download-file.d.ts.map
91
278
  //#endregion
92
- //#region src/lib/SpookyProvider.d.ts
93
- interface SpookyProviderProps<S extends SchemaStructure> {
279
+ //#region src/lib/Sp00kyProvider.d.ts
280
+ interface Sp00kyProviderProps<S extends SchemaStructure> {
94
281
  config: SyncedDbConfig<S>;
95
282
  fallback?: JSX.Element;
96
283
  onError?: (error: Error) => void;
97
284
  onReady?: (db: SyncedDb<S>) => void;
285
+ /**
286
+ * Prewarm data into the local cache before revealing the UI. Runs after
287
+ * `init()`; the `fallback` stays visible until it resolves. Use awaitable
288
+ * `db.preload(...)` calls here to gate first-load on essential data (e.g.
289
+ * config). On warm loads preload returns instantly, so there's no perceptible
290
+ * gate after the first run. Best-effort: a rejection is caught and the UI is
291
+ * revealed anyway.
292
+ */
293
+ preload?: (db: SyncedDb<S>) => Promise<void>;
98
294
  children: JSX.Element;
99
295
  }
100
- declare function SpookyProvider<S extends SchemaStructure>(props: SpookyProviderProps<S>): JSX.Element;
101
- //# sourceMappingURL=SpookyProvider.d.ts.map
296
+ declare function Sp00kyProvider<S extends SchemaStructure>(props: Sp00kyProviderProps<S>): JSX.Element;
297
+ //# sourceMappingURL=Sp00kyProvider.d.ts.map
102
298
  //#endregion
103
299
  //#region src/lib/context.d.ts
104
300
  declare function useDb<S extends SchemaStructure>(): SyncedDb<S>;
@@ -106,13 +302,13 @@ declare function useDb<S extends SchemaStructure>(): SyncedDb<S>;
106
302
 
107
303
  //#endregion
108
304
  //#region src/index.d.ts
109
- type RelationshipField<Schema extends SchemaStructure$1, TableName extends TableNames$1<Schema>, Field extends RelationshipFieldsFromSchema<Schema, TableName>> = GetRelationship<Schema, TableName, Field>;
110
- type RelatedFieldsTableScoped<Schema extends SchemaStructure$1, TableName extends TableNames$1<Schema>, RelatedFields extends RelationshipFieldsFromSchema<Schema, TableName> = RelationshipFieldsFromSchema<Schema, TableName>> = { [K in RelatedFields]: {
305
+ type RelationshipField<Schema extends SchemaStructure$1, TableName extends TableNames<Schema>, Field extends RelationshipFieldsFromSchema<Schema, TableName>> = GetRelationship<Schema, TableName, Field>;
306
+ type RelatedFieldsTableScoped<Schema extends SchemaStructure$1, TableName extends TableNames<Schema>, RelatedFields extends RelationshipFieldsFromSchema<Schema, TableName> = RelationshipFieldsFromSchema<Schema, TableName>> = { [K in RelatedFields]: {
111
307
  to: RelationshipField<Schema, TableName, K>['to'];
112
308
  relatedFields: RelatedFieldsMap;
113
309
  cardinality: RelationshipField<Schema, TableName, K>['cardinality'];
114
310
  } };
115
- type InferModel<Schema extends SchemaStructure$1, TableName extends TableNames$1<Schema>, RelatedFields extends RelatedFieldsTableScoped<Schema, TableName>> = QueryResult$1<Schema, TableName, RelatedFields, true>;
311
+ type InferModel<Schema extends SchemaStructure$1, TableName extends TableNames<Schema>, RelatedFields extends RelatedFieldsTableScoped<Schema, TableName>> = QueryResult<Schema, TableName, RelatedFields, true>;
116
312
  type WithRelated<Field extends string, RelatedFields extends RelatedFieldsMap = {}> = { [K in Field]: Omit<RelatedFieldMapEntry, 'relatedFields'> & {
117
313
  relatedFields: RelatedFields;
118
314
  } };
@@ -122,19 +318,26 @@ type WithRelatedMany<Field extends string, RelatedFields extends RelatedFieldsMa
122
318
  cardinality: 'many';
123
319
  } };
124
320
  /**
125
- * SyncedDb - A thin wrapper around spooky-ts for Solid.js integration
126
- * Delegates all logic to the underlying spooky-ts instance
321
+ * SyncedDb - A thin wrapper around sp00ky-ts for Solid.js integration
322
+ * Delegates all logic to the underlying sp00ky-ts instance
127
323
  */
128
324
  declare class SyncedDb<S extends SchemaStructure$1> {
129
325
  private config;
130
- private spooky;
326
+ private sp00ky;
131
327
  private _initialized;
132
328
  constructor(config: SyncedDbConfig<S>);
133
- getSpooky(): SpookyClient<S>;
329
+ getSp00ky(): Sp00kyClient<S>;
134
330
  /**
135
- * Initialize the spooky-ts instance
331
+ * Initialize the sp00ky-ts instance
136
332
  */
137
333
  init(): Promise<void>;
334
+ /**
335
+ * Tear down the client: leaves the tabs broker, closes the local store and
336
+ * remote socket, and frees the wasm circuit. Without this a remounted provider
337
+ * (or an HMR reload) strands a whole client, and the abandoned wasm heaps stay
338
+ * resident because V8 cannot see how much wasm memory a dropped wrapper holds.
339
+ */
340
+ close(): Promise<void>;
138
341
  /**
139
342
  * Create a new record in the database
140
343
  */
@@ -142,15 +345,21 @@ declare class SyncedDb<S extends SchemaStructure$1> {
142
345
  /**
143
346
  * Update an existing record in the database
144
347
  */
145
- update<TName extends TableNames$1<S>>(tableName: TName, recordId: string, payload: Partial<TableModel$1<GetTable$1<S, TName>>>, options?: UpdateOptions): Promise<void>;
348
+ update<TName extends TableNames<S>>(tableName: TName, recordId: string, payload: Partial<TableModel<GetTable<S, TName>>>, options?: UpdateOptions): Promise<void>;
146
349
  /**
147
350
  * Delete an existing record in the database
148
351
  */
149
- delete<TName extends TableNames$1<S>>(tableName: TName, selector: string | InnerQuery<GetTable$1<S, TName>, boolean>): Promise<void>;
352
+ delete<TName extends TableNames<S>>(tableName: TName, selector: string | RecordId | InnerQuery<GetTable<S, TName>, boolean>): Promise<void>;
353
+ /**
354
+ * Preload/prewarm a built query into the local cache without registering a
355
+ * live view. Fetches once and stores the rows (+ embedded related children)
356
+ * locally so a later `useQuery` for the same data paints instantly. Best-effort.
357
+ */
358
+ preload(finalQuery: FinalQuery<S, any, any, any, any, Sp00kyQueryResultPromise>, options?: PreloadOptions$1): Promise<void>;
150
359
  /**
151
360
  * Query data from the database
152
361
  */
153
- query<TName extends TableNames$1<S>>(table: TName): QueryBuilder<S, TName, SpookyQueryResultPromise, {}, false>;
362
+ query<TName extends TableNames<S>>(table: TName): QueryBuilder<S, TName, Sp00kyQueryResultPromise, {}, false>;
154
363
  /**
155
364
  * Run a backend operation
156
365
  */
@@ -175,20 +384,38 @@ declare class SyncedDb<S extends SchemaStructure$1> {
175
384
  /**
176
385
  * Access the remote database service directly
177
386
  */
178
- get remote(): SpookyClient<S>['remoteClient'];
387
+ get remote(): Sp00kyClient<S>['remoteClient'];
179
388
  /**
180
389
  * Access the local database service directly
181
390
  */
182
- get local(): SpookyClient<S>['localClient'];
391
+ get local(): Sp00kyClient<S>['localClient'];
183
392
  /**
184
393
  * Access the auth service
185
394
  */
186
395
  get auth(): AuthService<S>;
187
396
  get pendingMutationCount(): number;
397
+ /** Diagnostic — see `Sp00kyClient.liveRetryCount`. */
398
+ get liveRetryCount(): number;
188
399
  subscribeToPendingMutations(cb: (count: number) => void): () => void;
400
+ /** Current sync-health snapshot. See {@link useSyncStatus}. */
401
+ get syncHealth(): SyncHealth$1;
402
+ /**
403
+ * Observe sync health. Fires immediately with the current status and again
404
+ * on every healthy↔degraded transition. Prefer the `useSyncStatus` hook in
405
+ * components; this is the imperative escape hatch.
406
+ */
407
+ subscribeToSyncHealth(cb: (health: SyncHealth$1) => void): () => void;
408
+ /** Current local-store durability snapshot. See {@link useStorageStatus}. */
409
+ get storageHealth(): StorageHealth$1;
410
+ /**
411
+ * Observe local-store durability. Fires immediately with the current snapshot
412
+ * and again on change. Prefer the `useStorageStatus` hook in components; this
413
+ * is the imperative escape hatch.
414
+ */
415
+ subscribeToStorageHealth(cb: (health: StorageHealth$1) => void): () => void;
189
416
  bucket<B extends BucketNames<S>>(name: B): BucketHandle;
190
417
  getBucketConfig(name: string): BucketDefinitionSchema | undefined;
191
418
  }
192
419
  //#endregion
193
- export { CacheStrategy, type FileUploadResult, type GenericModel, type GenericSchema, type GetCardinality, type GetTable, InferModel, type InferRelatedModelFromMetadata, InferRelationshipsFromConst, InferSchemaFromConst, type Model, type ModelPayload, ProvisionOptions, type QueryInfo, type QueryModifier, type QueryModifierBuilder, type QueryResult, RecordId, RelatedFieldsTableScoped, type RelationshipDefinition, RelationshipField, type RelationshipsMetadata, SpookyProvider, type SpookyProviderProps, SyncedDb, SyncedDbConfig, type TableModel, type TableNames, type UseDownloadFileOptions, type UseDownloadFileResult, Uuid, WithRelated, WithRelatedMany, useDb, useDownloadFile, useFileUpload, useQuery };
420
+ export { CacheStrategy, type ConnectionState, 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, type ReconnectConfig, 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 };
194
421
  //# sourceMappingURL=index.d.cts.map