@spooky-sync/client-solid 0.0.1-canary.16 → 0.0.1-canary.160

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":";;;;;;AAIA,MAAa,6CAA0D;AAEvE,SAAgB,QAAgD;CAC9D,MAAM,8BAAgB,cAAc;AACpC,KAAI,CAAC,GACH,OAAM,IAAI,MAAM,gGAAgG;AAElH,QAAO;;;;;AC4CT,SAAgB,SAUd,WAGA,gBAGA,cACA;CACA,IAAI;CACJ,IAAI;CACJ,IAAI;AAEJ,KAAI,qBAAqB,UAAU;AAEjC,OAAK;AACL,eAAa;AACb,YAAU;QACL;EAEL,MAAM,qCAAuB,cAAc;AAC3C,MAAI,CAAC,UACH,OAAM,IAAI,MACR,sIAED;AAEH,OAAK;AACL,eAAa;AACb,YAAU;;CAGZ,MAAM,CAAC,MAAM,sCAA2C,OAAU;CAClE,MAAM,CAAC,OAAO,uCAA4C,OAAU;CACpE,MAAM,CAAC,WAAW,2CAA6B,MAAM;CACrD,MAAM,CAAC,aAAa,6CAAyD,OAAU;CACvF,IAAI;CAEJ,MAAM,SAAS,GAAG,WAAW;CAE7B,MAAM,YAAY,OAChB,UACG;EACH,MAAM,EAAE,SAAS,MAAM,MAAM,KAAK;AAClC,WAAS,OAAU;EAEnB,IAAI,cAAc;EAClB,MAAM,QAAQ,MAAM,OAAO,UACzB,OACC,MAAM;GACL,MAAM,OAAQ,MAAM,QAAQ,EAAE,KAAK;AACnC,iBAAc,KAAK;GAGnB,MAAM,UAAU,MAAM,QAAQ,QAAQ,OAAQ,EAAY,SAAS;AACnE,OAAI,CAAC,eAAe,QAClB,cAAa,KAAK;AAEpB,iBAAc;KAEhB,EAAE,WAAW,MAAM,CACpB;AAED,uBAAqB,MAAM;;AAG7B,kCAAmB;AAIjB,MAAI,EAHY,SAAS,WAAW,IAAI,OAG1B;AACZ,YAAS,OAAU;AACnB;;EAIF,MAAM,QAAQ,OAAO,eAAe,aAAa,YAAY,GAAG;AAChE,MAAI,CAAC,MACH;EAIF,MAAM,cAAc,KAAK,UAAU,MAAM;AACzC,MAAI,gBAAgB,gBAClB;AAEF,oBAAkB;AAGlB,eAAa,MAAM;AACnB,YAAU,MAAM;AAGhB,gCAAgB;AACd,gBAAa,IAAI;IACjB;GACF;CAEF,MAAM,kBAAkB;AACtB,SAAO,CAAC,WAAW,IAAI,OAAO,KAAK;;AAGrC,QAAO;EACL;EACA;EACA;EACD;;;;;ACnJH,SAAgB,cACd,gBACA,iBACkB;CAClB,IAAI;CACJ,IAAI;AAEJ,KAAI,OAAO,mBAAmB,UAAU;AACtC,OAAK,OAAU;AACf,eAAa;QACR;AACL,OAAK;AACL,eAAa;;CAGf,MAAM,CAAC,aAAa,6CAA+B,MAAM;CACzD,MAAM,CAAC,OAAO,uCAAuC,KAAK;CAE1D,MAAM,aAAuB,EAAE;AAC/B,+BAAgB;AACd,OAAK,MAAM,OAAO,WAChB,KAAI,gBAAgB,IAAI;GAE1B;CAEF,MAAM,mBAAmB,SAAS,KAAK;CAEvC,MAAM,YAAY,SAA4B;EAC5C,MAAM,SAAS,GAAG,gBAAgB,WAAqB;AACvD,MAAI,CAAC,OAAQ;AAEb,MAAI,OAAO,WAAW,QAAQ,KAAK,OAAO,OAAO,SAAS;GACxD,MAAM,SAAS,OAAO,WAAW,OAAO,OAAO,QAAQ,EAAE;AACzD,SAAM,IAAI,MAAM,gCAAgC,MAAM,MAAM;;AAG9D,MAAI,OAAO,qBAAqB,OAAO,kBAAkB,SAAS,GAAG;GACnE,MAAM,WAAY,KAAc;AAChC,OAAI,UAAU;IACZ,MAAM,MAAM,SAAS,MAAM,IAAI,CAAC,KAAK,EAAE,aAAa;AACpD,QAAI,CAAC,OAAO,CAAC,OAAO,kBAAkB,SAAS,IAAI,CACjD,OAAM,IAAI,MACR,oCAAoC,OAAO,kBAAkB,KAAK,KAAK,CAAC,GACzE;;;;CAMT,MAAM,SAAS,OAAO,MAAc,SAAqC;AACvE,WAAS,KAAK;AACd,MAAI;AACF,YAAS,KAAK;WACP,GAAG;AACV,YAAS,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,EAAE,CAAC,CAAC;AACvD;;AAGF,iBAAe,KAAK;AACpB,MAAI;GACF,MAAM,QAAQ,8CAAuB,KAAK;AAC1C,SAAM,GAAG,OAAO,WAAW,CAAC,IAAI,MAAM,MAAM;WACrC,GAAG;AACV,YAAS,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,EAAE,CAAC,CAAC;YAC/C;AACR,kBAAe,MAAM;;;CAIzB,MAAM,WAAW,OAAO,SAAyC;AAC/D,WAAS,KAAK;AACd,MAAI;GACF,MAAM,UAAU,MAAM,GAAG,OAAO,WAAW,CAAC,IAAI,KAAK;AACrD,OAAI,CAAC,QAAS,QAAO;GACrB,MAAM,YAAY,IAAI,gBAAgB,IAAI,KAAK,CAAC,QAAoB,CAAC,CAAC;AACtE,cAAW,KAAK,UAAU;AAC1B,UAAO;WACA,GAAG;AACV,YAAS,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,EAAE,CAAC,CAAC;AACvD,UAAO;;;CAIX,MAAM,SAAS,OAAO,SAAgC;AACpD,WAAS,KAAK;AACd,MAAI;AACF,SAAM,GAAG,OAAO,WAAW,CAAC,OAAO,KAAK;WACjC,GAAG;AACV,YAAS,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,EAAE,CAAC,CAAC;;;CAI3D,MAAM,SAAS,OAAO,SAAmC;AACvD,WAAS,KAAK;AACd,MAAI;AACF,UAAO,MAAM,GAAG,OAAO,WAAW,CAAC,OAAO,KAAK;WACxC,GAAG;AACV,YAAS,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,EAAE,CAAC,CAAC;AACvD,UAAO;;;AAIX,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACD;;;;;AChHH,MAAM,gCAAgB,IAAI,KAAyB;AACnD,MAAM,mCAAmB,IAAI,KAAqC;AAElE,SAAS,SAAS,QAAgB,MAAsB;AACtD,QAAO,GAAG,OAAO,GAAG;;AAGtB,SAAS,aAAa,KAAmB;CACvC,MAAM,QAAQ,cAAc,IAAI,IAAI;AACpC,KAAI,CAAC,MAAO;AACZ,OAAM;AACN,KAAI,MAAM,YAAY,GAAG;AACvB,MAAI,gBAAgB,MAAM,IAAI;AAC9B,gBAAc,OAAO,IAAI;;;AAe7B,SAAgB,gBACd,gBACA,kBACA,eACA,cACuB;CACvB,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;AAEJ,KAAI,OAAO,mBAAmB,UAAU;AACtC,OAAK,OAAU;AACf,eAAa;AACb,SAAO;AACP,YAAW,iBAA4C,EAAE;QACpD;AACL,OAAK;AACL,eAAa;AACb,SAAO;AACP,YAAU,gBAAgB,EAAE;;CAG9B,MAAM,WAAW,QAAQ,UAAU;CAEnC,MAAM,CAAC,KAAK,qCAAsC,KAAK;CACvD,MAAM,CAAC,WAAW,2CAA6B,MAAM;CACrD,MAAM,CAAC,OAAO,uCAAuC,KAAK;CAE1D,IAAI,aAA4B;CAChC,IAAI,aAA4B;CAChC,IAAI;CACJ,MAAM,CAAC,eAAe,+CAAiC,EAAE;AACzD,wBAAuB,kBAAkB,MAAM,IAAI,EAAE;CAErD,eAAe,WAAW,KAAa,UAA0C;AAC/E,MAAI,UAAU;GAEZ,MAAM,SAAS,cAAc,IAAI,IAAI;AACrC,OAAI,QAAQ;AACV,WAAO;AACP,iBAAa;AACb,WAAO,OAAO;;GAIhB,MAAM,WAAW,iBAAiB,IAAI,IAAI;AAC1C,OAAI,UAAU;IACZ,MAAM,SAAS,MAAM;AACrB,QAAI,QAAQ;KACV,MAAM,QAAQ,cAAc,IAAI,IAAI;AACpC,SAAI,OAAO;AACT,YAAM;AACN,mBAAa;;;AAGjB,WAAO;;GAIT,MAAM,WAAW,YAAY;IAC3B,MAAM,UAAU,MAAM,GAAG,OAAO,WAAW,CAAC,IAAI,SAAS;AACzD,QAAI,CAAC,QAAS,QAAO;IACrB,MAAM,YAAY,IAAI,gBAAgB,IAAI,KAAK,CAAC,QAAoB,CAAC,CAAC;AACtE,kBAAc,IAAI,KAAK;KAAE,KAAK;KAAW,UAAU;KAAG,CAAC;AACvD,WAAO;OACL;AAEJ,oBAAiB,IAAI,KAAK,QAAQ;AAClC,OAAI;IACF,MAAM,SAAS,MAAM;AACrB,iBAAa;AACb,WAAO;aACC;AACR,qBAAiB,OAAO,IAAI;;SAEzB;GAEL,MAAM,UAAU,MAAM,GAAG,OAAO,WAAW,CAAC,IAAI,SAAS;AACzD,OAAI,CAAC,QAAS,QAAO;GACrB,MAAM,YAAY,IAAI,gBAAgB,IAAI,KAAK,CAAC,QAAoB,CAAC,CAAC;AACtE,gBAAa;AACb,UAAO;;;CAIX,SAAS,sBAAsB;AAC7B,MAAI,YAAY,YAAY;AAC1B,gBAAa,WAAW;AACxB,gBAAa;;AAEf,MAAI,CAAC,YAAY,YAAY;AAC3B,OAAI,gBAAgB,WAAW;AAC/B,gBAAa;;;AAIjB,kCAAmB;EACjB,MAAM,WAAW,MAAM;AAEvB,iBAAe;AAGf,uBAAqB;AAErB,MAAI,CAAC,UAAU;AACb,UAAO,KAAK;AACZ,gBAAa,MAAM;AACnB,YAAS,KAAK;AACd;;EAGF,MAAM,MAAM,SAAS,YAAsB,SAAS;AAGpD,MAAI,UAAU;GACZ,MAAM,SAAS,cAAc,IAAI,IAAI;AACrC,OAAI,QAAQ;AACV,WAAO;AACP,iBAAa;AACb,WAAO,OAAO,IAAI;AAClB,iBAAa,MAAM;AACnB,aAAS,KAAK;AACd;;;EAIJ,IAAI,YAAY;AAChB,eAAa,KAAK;AAClB,WAAS,KAAK;AAEd,aAAW,KAAK,SAAS,CAAC,MACvB,WAAW;AACV,OAAI,CAAC,WAAW;AACd,WAAO,OAAO;AACd,iBAAa,MAAM;;MAGtB,QAAQ;AACP,OAAI,CAAC,WAAW;AACd,aAAS,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,CAAC;AAC7D,iBAAa,MAAM;;IAGxB;AAED,gCAAgB;AACd,eAAY;IACZ;GACF;AAEF,+BAAgB;AACd,uBAAqB;GACrB;CAEF,MAAM,gBAAgB;AAEpB,MAAI,YAAY,YAAY;GAC1B,MAAM,QAAQ,cAAc,IAAI,WAAW;AAC3C,OAAI,OAAO;AACT,QAAI,gBAAgB,MAAM,IAAI;AAC9B,kBAAc,OAAO,WAAW;;AAElC,gBAAa;;AAEf,kBAAgB;;AAGlB,QAAO;EAAE;EAAK;EAAW;EAAO;EAAS;;;;;AC3M3C,SAAgB,eACd,OACa;CACb,MAAM,kCACJ,EACE,UAAU,QACX,EACD,MACD;CAED,MAAM,CAAC,IAAI,oCAA+C,OAAU;AAEpE,uBAAQ,YAAY;AAClB,MAAI;GACF,MAAM,WAAW,IAAI,SAAY,OAAO,OAAO;AAC/C,SAAM,SAAS,MAAM;AACrB,eAAY,SAAS;AACrB,UAAO,UAAU,SAAS;WACnB,GAAG;GACV,MAAM,QAAQ,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,EAAE,CAAC;AAC3D,OAAI,OAAO,QACT,QAAO,QAAQ,MAAM;OAErB,SAAQ,MAAM,iDAAiD,MAAM;;GAGzE;AAaF,uCAXiC;EAC/B,MAAM,WAAW,IAAI;AACrB,MAAI,CAAC,SAAU,QAAO,OAAO;AAC7B,uCAAuB,cAAc,UAAU;GAC7C,OAAO;GACP,IAAI,WAAW;AACb,WAAO,OAAO;;GAEjB,CAAC;GACF;;;;;;;;;ACgDJ,IAAa,WAAb,MAAiD;CAK/C,YAAY,QAA2B;OAH/B,SAAiC;OACjC,eAAe;AAGrB,OAAK,SAAS;;CAGhB,AAAO,YAA6B;AAClC,MAAI,CAAC,KAAK,OAAQ,OAAM,IAAI,MAAM,2BAA2B;AAC7D,SAAO,KAAK;;;;;CAMd,MAAM,OAAsB;AAC1B,MAAI,KAAK,aAAc;AACvB,OAAK,SAAS,IAAIA,+BAAgB,KAAK,OAAO;AAC9C,QAAM,KAAK,OAAO,MAAM;AACxB,OAAK,eAAe;;;;;CAMtB,MAAM,OAAO,IAAY,SAAiD;AACxE,MAAI,CAAC,KAAK,OAAQ,OAAM,IAAI,MAAM,2BAA2B;AAC7D,QAAM,KAAK,OAAO,OAAO,IAAI,QAAmC;;;;;CAMlE,MAAM,OACJ,WACA,UACA,SACA,SACe;AACf,MAAI,CAAC,KAAK,OAAQ,OAAM,IAAI,MAAM,2BAA2B;AAC7D,QAAM,KAAK,OAAO,OAChB,WACA,UACA,SACA,QACD;;;;;CAMH,MAAM,OACJ,WACA,UACe;AACf,MAAI,CAAC,KAAK,OAAQ,OAAM,IAAI,MAAM,2BAA2B;AAC7D,MAAI,OAAO,aAAa,SACtB,OAAM,IAAI,MAAM,6DAA6D;AAC/E,QAAM,KAAK,OAAO,OAAO,WAAqB,SAAS;;;;;CAMzD,AAAO,MACL,OAC6D;AAC7D,MAAI,CAAC,KAAK,OAAQ,OAAM,IAAI,MAAM,2BAA2B;AAC7D,SAAO,KAAK,OAAO,MAAM,OAAO,EAAE,CAAC;;;;;CAMrC,MAAa,IAIX,SACA,MACA,SACA,SACe;AACf,MAAI,CAAC,KAAK,OAAQ,OAAM,IAAI,MAAM,2BAA2B;AAC7D,QAAM,KAAK,OAAO,IAAI,SAAS,MAAM,SAAS,QAAQ;;;;;CAMxD,MAAa,aAAa,OAA0C;AACnD,QAAM,KAAK,QAAQ,aAAa,MAAM;AAKrD,SAAO,IAAIC,mBAAS,QAAQ,KAAK;;;;;;CAOnC,MAAa,iBAAgC;AAC3C,QAAM,KAAK,SAAS;;;;;CAMtB,MAAa,UAAyB;AACpC,MAAI,CAAC,KAAK,OAAQ,OAAM,IAAI,MAAM,2BAA2B;AAC7D,QAAM,KAAK,OAAO,KAAK,SAAS;;;;;CAMlC,MAAa,UAAa,IAAiD;AACzE,MAAI,CAAC,KAAK,OAAQ,OAAM,IAAI,MAAM,2BAA2B;AAC7D,SAAO,MAAM,KAAK,OAAO,UAAU,GAAG;;;;;CAKxC,IAAI,SAA0C;AAC5C,MAAI,CAAC,KAAK,OAAQ,OAAM,IAAI,MAAM,2BAA2B;AAC7D,SAAO,KAAK,OAAO;;;;;CAMrB,IAAI,QAAwC;AAC1C,MAAI,CAAC,KAAK,OAAQ,OAAM,IAAI,MAAM,2BAA2B;AAC7D,SAAO,KAAK,OAAO;;;;;CAMrB,IAAI,OAAuB;AACzB,MAAI,CAAC,KAAK,OAAQ,OAAM,IAAI,MAAM,2BAA2B;AAC7D,SAAO,KAAK,OAAO;;CAGrB,IAAI,uBAA+B;AACjC,MAAI,CAAC,KAAK,OAAQ,OAAM,IAAI,MAAM,2BAA2B;AAC7D,SAAO,KAAK,OAAO;;CAGrB,4BAA4B,IAAyC;AACnE,MAAI,CAAC,KAAK,OAAQ,OAAM,IAAI,MAAM,2BAA2B;AAC7D,SAAO,KAAK,OAAO,4BAA4B,GAAG;;CAGpD,OAAiC,MAAuB;AACtD,MAAI,CAAC,KAAK,OAAQ,OAAM,IAAI,MAAM,2BAA2B;AAC7D,SAAO,KAAK,OAAO,OAAO,KAAK;;CAGjC,gBAAgB,MAAkD;AAChE,SAAO,KAAK,OAAO,OAAO,SAAS,MAAM,MAAM,EAAE,SAAS,KAAK"}
1
+ {"version":3,"file":"index.cjs","names":["Sp00kyClient","RecordId"],"sources":["../src/lib/context.ts","../src/lib/use-query.ts","../src/lib/create-preload.ts","../src/lib/use-sync-status.ts","../src/lib/use-storage-status.ts","../src/lib/use-crdt-field.ts","../src/lib/use-feature-flag.ts","../src/lib/use-app-release.ts","../src/lib/use-file-upload.ts","../src/lib/use-download-file.ts","../src/lib/Sp00kyProvider.ts","../src/index.ts"],"sourcesContent":["import { createContext, useContext } from 'solid-js';\nimport type { SchemaStructure } from '@spooky/query-builder';\nimport type { SyncedDb } from '../index';\n\nexport const Sp00kyContext = createContext<SyncedDb<any> | undefined>();\n\nexport function useDb<S extends SchemaStructure>(): SyncedDb<S> {\n const db = useContext(Sp00kyContext);\n if (!db) {\n throw new Error('useDb must be used within a <Sp00kyProvider>. Wrap your app in <Sp00kyProvider config={...}>.');\n }\n return db as SyncedDb<S>;\n}\n","import type {\n ColumnSchema,\n FinalQuery,\n SchemaStructure,\n TableNames,\n QueryResult,\n} from '@spooky-sync/query-builder';\nimport { createEffect, createSignal, onCleanup, useContext } from 'solid-js';\nimport { createStore, reconcile } from 'solid-js/store';\nimport { SyncedDb } from '..';\nimport type { Sp00kyQueryResultPromise } from '@spooky-sync/core';\nimport { Sp00kyContext } from './context';\n\ntype QueryArg<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n> =\n | FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>\n | (() =>\n | FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>\n | null\n | undefined);\n\ntype QueryOptions = {\n enabled?: () => boolean;\n /**\n * Tear down the query (remote `_00_query` view + local WASM view) when this\n * hook is disposed and no other subscriber remains, instead of keeping it\n * resident for cheap re-subscription. Use for viewport-windowed lists that\n * mount/unmount a query per scroll window and want off-screen windows\n * cancelled. Trade-off: scrolling back to a torn-down window re-registers it.\n */\n deregisterOnCleanup?: boolean;\n};\n\n// Overload: context-based (no explicit db)\nexport function useQuery<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n TData = QueryResult<S, TableName, RelatedFields, IsOne> | null,\n>(\n finalQuery: QueryArg<S, TableName, T, RelatedFields, IsOne>,\n options?: QueryOptions,\n): {\n data: () => TData | undefined;\n error: () => Error | undefined;\n isLoading: () => boolean;\n isFetching: () => boolean;\n isSettled: () => boolean;\n};\n\n// Overload: explicit db (backward-compatible)\nexport function useQuery<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n TData = QueryResult<S, TableName, RelatedFields, IsOne> | null,\n>(\n db: SyncedDb<S>,\n finalQuery: QueryArg<S, TableName, T, RelatedFields, IsOne>,\n options?: QueryOptions,\n): {\n data: () => TData | undefined;\n error: () => Error | undefined;\n isLoading: () => boolean;\n isFetching: () => boolean;\n isSettled: () => boolean;\n};\n\n// Implementation\nexport function useQuery<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends {\n columns: Record<string, ColumnSchema>;\n },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n TData = QueryResult<S, TableName, RelatedFields, IsOne> | null,\n>(\n dbOrQuery:\n | SyncedDb<S>\n | QueryArg<S, TableName, T, RelatedFields, IsOne>,\n queryOrOptions?:\n | QueryArg<S, TableName, T, RelatedFields, IsOne>\n | QueryOptions,\n maybeOptions?: QueryOptions,\n) {\n let db: SyncedDb<S>;\n let finalQuery: QueryArg<S, TableName, T, RelatedFields, IsOne>;\n let options: QueryOptions | undefined;\n\n if (dbOrQuery instanceof SyncedDb) {\n // Explicit db overload: useQuery(db, query, options?)\n db = dbOrQuery;\n finalQuery = queryOrOptions as QueryArg<S, TableName, T, RelatedFields, IsOne>;\n options = maybeOptions;\n } else {\n // Context-based overload: useQuery(query, options?)\n const contextDb = useContext(Sp00kyContext);\n if (!contextDb) {\n throw new Error(\n 'useQuery: No db argument provided and no Sp00kyContext found. ' +\n 'Either pass a SyncedDb instance or wrap your app in <Sp00kyProvider>.'\n );\n }\n db = contextDb as SyncedDb<S>;\n finalQuery = dbOrQuery;\n options = queryOrOptions as QueryOptions | undefined;\n }\n\n const [error, setError] = createSignal<Error | undefined>(undefined);\n const [isFetched, setIsFetched] = createSignal(false);\n const [isFetching, setIsFetching] = createSignal(false);\n // Results live in a store (not a signal) so consecutive live-query emissions\n // are merged with `reconcile`: unchanged rows keep their object identity and\n // changed rows are mutated in place. That keeps Solid's reference-keyed `<For>`\n // rows — and any `useQuery` subscriptions mounted inside them — alive across\n // updates, instead of tearing every row down and re-registering its queries.\n const [state, setState] = createStore<{ value: TData | undefined }>({ value: undefined });\n // `reconcile` (below) merges each emission into `state.value` IN PLACE, keeping\n // the array reference stable. That's ideal for granular per-row reactivity, but\n // it means a *coarse* reader of `data()` — `<For each={data()}>`, or an effect\n // that copies the whole array elsewhere (e.g. GameList's windowed store) — is\n // NOT re-run when rows are added/removed/reordered within a same-length result\n // (the classic case: deleting a row in a windowed list shifts the next one in,\n // so length stays 50 and the array ref never changes). Bump a version on every\n // emission and read it in `data()` so every consumer re-runs on any change while\n // reconcile still preserves row identity underneath.\n const [version, setVersion] = createSignal(0);\n const data = () => {\n version();\n return state.value;\n };\n\n let prevQueryString: string | undefined;\n // Monotonic token for each subscription generation. Bumped whenever the query\n // identity changes or the hook is disposed, so a slow async `initQuery`\n // continuation can detect it was superseded and avoid installing a stale (and\n // leaked) subscription.\n let runId = 0;\n let activeUnsub: (() => void) | undefined;\n // The hash of the currently-installed subscription, for opt-in deregister on\n // dispose (see `deregisterOnCleanup`).\n let activeHash: string | undefined;\n\n const teardownActive = () => {\n activeUnsub?.();\n activeUnsub = undefined;\n };\n\n const sp00ky = db.getSp00ky();\n\n const initQuery = async (\n query: FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>,\n myRun: number\n ) => {\n const { hash } = await query.run();\n // A newer query identity (or disposal) won the race while we awaited run().\n if (myRun !== runId) return;\n activeHash = hash;\n setError(undefined);\n\n let isFirstCall = true;\n const unsub = await sp00ky.subscribe(\n hash,\n (e) => {\n const queryData = (query.isOne ? e[0] : e) as TData;\n // Merge into the store by record id: unchanged rows keep their identity,\n // changed rows update in place. Replaces wholesale for `one()`/null.\n // Time the reconcile → report as the \"frontend\" phase for DevTools/MCP.\n const reconcileStart = performance.now();\n setState('value', reconcile(queryData as any, { key: 'id' }));\n // Notify coarse `data()` readers (see the `version` note above): reconcile\n // keeps the array ref stable, so this is what re-runs `<For>`/copy-effects\n // on add/remove/reorder.\n setVersion((v) => v + 1);\n sp00ky.reportFrontendTiming(hash, performance.now() - reconcileStart);\n // The first (immediate) callback with no data likely means the local DB\n // hasn't synced yet — don't mark as fetched so UI shows loading state\n const hasData = query.isOne ? queryData !== null && queryData !== undefined : (e as any[]).length > 0;\n if (!isFirstCall || hasData) {\n setIsFetched(true);\n }\n isFirstCall = false;\n },\n { immediate: true }\n );\n\n // Mirror the query's fetch status so the UI can show a \"loading more\"\n // state while the sync engine pulls missing records in the background.\n const unsubStatus = sp00ky.subscribeQueryStatus(\n hash,\n (status) => setIsFetching(status === 'fetching'),\n { immediate: true }\n );\n\n const teardown = () => {\n unsub();\n unsubStatus();\n };\n\n // Superseded while awaiting subscribe()? Don't leak — tear down immediately.\n if (myRun !== runId) {\n teardown();\n return;\n }\n activeUnsub = teardown;\n };\n\n createEffect(() => {\n const enabled = options?.enabled?.() ?? true;\n\n // If disabled, clear error and don't run query\n if (!enabled) {\n setError(undefined);\n return;\n }\n\n // Init Query\n const query = typeof finalQuery === 'function' ? finalQuery() : finalQuery;\n if (!query) {\n return;\n }\n\n // Dedup on the query's stable identity hash (cyrb53 of surql + vars), not a\n // full `JSON.stringify` of the FinalQuery (which walks the whole schema +\n // inner query on every reactive tick and isn't guaranteed stable). When the\n // identity is unchanged we keep the existing subscription alive.\n const queryString = String(query.hash);\n if (queryString === prevQueryString) {\n return;\n }\n prevQueryString = queryString;\n\n // New query identity → supersede the previous subscription and start fresh.\n const myRun = ++runId;\n teardownActive();\n setIsFetched(false);\n initQuery(query, myRun);\n });\n\n // Tear down the live subscription when the hook's owner is disposed. Registered\n // on the hook (component) scope rather than inside the effect, so an effect\n // re-run that early-returns (unchanged query) doesn't clean up the still-valid\n // subscription. Bumping runId also invalidates any in-flight initQuery.\n onCleanup(() => {\n runId++;\n teardownActive();\n // Opt-in: cancel the query once this hook (its last subscriber) is gone.\n // teardownActive() above already removed this hook's callback, so\n // deregisterQuery's refcount guard sees the true remaining-subscriber count.\n if (options?.deregisterOnCleanup && activeHash) {\n sp00ky.deregisterQuery(activeHash);\n }\n });\n\n const isLoading = () => {\n return !isFetched() && error() === undefined;\n };\n\n // True once the query has delivered a result AND no fetch cycle is in flight\n // (registration + initial sync included — the core holds `fetching` across\n // the whole registration and flushes debounced results before flipping back\n // to idle). While settled, the results are authoritative: a windowed query\n // returning fewer rows than its LIMIT really is the end of the list, so\n // virtualized lists may size themselves to it without the scrollbar jumping\n // when a still-syncing window transiently reports short. Resets to false\n // whenever the query identity changes.\n const isSettled = () => isFetched() && !isFetching();\n\n return {\n data,\n error,\n isLoading,\n isFetching,\n isSettled,\n };\n}\n","import type {\n ColumnSchema,\n FinalQuery,\n SchemaStructure,\n TableNames,\n} from '@spooky-sync/query-builder';\nimport { createEffect, useContext } from 'solid-js';\nimport { SyncedDb } from '..';\nimport type { Sp00kyQueryResultPromise, PreloadOptions as CorePreloadOptions } from '@spooky-sync/core';\nimport { Sp00kyContext } from './context';\n\ntype PreloadArg<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n> =\n | FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>\n | (() =>\n | FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>\n | null\n | undefined);\n\ntype PreloadOptions = CorePreloadOptions & {\n /** Only preload while this returns true (defaults to always). */\n enabled?: () => boolean;\n};\n\n// Overload: context-based (no explicit db)\nexport function createPreload<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n>(\n finalQuery: PreloadArg<S, TableName, T, RelatedFields, IsOne>,\n options?: PreloadOptions,\n): void;\n\n// Overload: explicit db\nexport function createPreload<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n>(\n db: SyncedDb<S>,\n finalQuery: PreloadArg<S, TableName, T, RelatedFields, IsOne>,\n options?: PreloadOptions,\n): void;\n\n/**\n * Reactive, fire-and-forget prewarm. Resolves the query (calling it if it's a\n * function so it tracks reactive deps), dedupes on the query's stable identity\n * hash, and warms it into the local cache via `db.preload`. No subscription and\n * no cleanup: preload registers nothing that needs tearing down.\n *\n * Typical use: inside a list row, preload the detail query the user is likely\n * to open next, so navigation paints from cache instead of the network.\n */\nexport function createPreload<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n>(\n dbOrQuery: SyncedDb<S> | PreloadArg<S, TableName, T, RelatedFields, IsOne>,\n queryOrOptions?: PreloadArg<S, TableName, T, RelatedFields, IsOne> | PreloadOptions,\n maybeOptions?: PreloadOptions,\n): void {\n let db: SyncedDb<S>;\n let finalQuery: PreloadArg<S, TableName, T, RelatedFields, IsOne>;\n let options: PreloadOptions | undefined;\n\n if (dbOrQuery instanceof SyncedDb) {\n db = dbOrQuery;\n finalQuery = queryOrOptions as PreloadArg<S, TableName, T, RelatedFields, IsOne>;\n options = maybeOptions;\n } else {\n const contextDb = useContext(Sp00kyContext);\n if (!contextDb) {\n throw new Error(\n 'createPreload: No db argument provided and no Sp00kyContext found. ' +\n 'Either pass a SyncedDb instance or wrap your app in <Sp00kyProvider>.',\n );\n }\n db = contextDb as SyncedDb<S>;\n finalQuery = dbOrQuery;\n options = queryOrOptions as PreloadOptions | undefined;\n }\n\n let prevHash: number | undefined;\n\n createEffect(() => {\n if (!(options?.enabled?.() ?? true)) return;\n\n const query = typeof finalQuery === 'function' ? finalQuery() : finalQuery;\n if (!query) return;\n\n // Dedupe on the query's stable identity hash so a reactive re-run with an\n // unchanged query doesn't refetch (the core also dedupes per session).\n if (query.hash === prevHash) return;\n prevHash = query.hash;\n\n void db.getSp00ky().preload(query, { refresh: options?.refresh, staleTime: options?.staleTime });\n });\n}\n","import { createSignal, onCleanup, type Accessor } from 'solid-js';\nimport { useDb } from './context';\nimport type { SyncHealth, SyncHealthStatus } from '@spooky-sync/core';\n\nexport interface UseSyncStatus {\n /** Full health snapshot; updates reactively on every transition. */\n health: Accessor<SyncHealth>;\n /** `'healthy'` | `'degraded'`. */\n status: Accessor<SyncHealthStatus>;\n isHealthy: Accessor<boolean>;\n /** `true` once sync has failed for a sustained run — drive a banner off this. */\n isDegraded: Accessor<boolean>;\n /** `true` once at least one sync round has succeeded this session. */\n everConnected: Accessor<boolean>;\n /**\n * `true` only for a real lost connection: degraded AFTER a first successful\n * sync. Stays `false` during the initial \"connecting\" phase (degraded but\n * never reached the server yet), so an indicator can show nothing until the\n * app has actually connected once.\n */\n isOffline: Accessor<boolean>;\n}\n\n/**\n * Observe sync health for a \"can't reach the server\" banner / indicator.\n *\n * Backed by `db.subscribeToSyncHealth`. Individual sync failures (a transient\n * remote 500 on query registration, a dropped socket) are absorbed by the\n * retry and never flip this; `isDegraded()` only goes true once failures\n * persist for the configured number of consecutive rounds (sp00ky core config\n * `syncHealth.degradeAfterConsecutiveFailures`, default 3), and flips back on\n * the next successful round. Must be used within a `<Sp00kyProvider>`.\n */\nexport function useSyncStatus(): UseSyncStatus {\n const db = useDb();\n // subscribeToSyncHealth fires synchronously with the current status, so the\n // signal is correct from first read; the initial value just avoids a flash.\n const [health, setHealth] = createSignal<SyncHealth>(db.syncHealth);\n const unsub = db.subscribeToSyncHealth(setHealth);\n onCleanup(unsub);\n\n return {\n health,\n status: () => health().status,\n isHealthy: () => health().status === 'healthy',\n isDegraded: () => health().status === 'degraded',\n everConnected: () => health().everConnected,\n isOffline: () => health().status === 'degraded' && health().everConnected,\n };\n}\n","import { createSignal, onCleanup, type Accessor } from 'solid-js';\nimport { useDb } from './context';\nimport type { StorageHealth, StorageHealthStatus } from '@spooky-sync/core';\n\nexport interface UseStorageStatus {\n /** Full durability snapshot; updates reactively. */\n health: Accessor<StorageHealth>;\n /** `'unknown'` | `'persistent'` | `'memory'`. */\n status: Accessor<StorageHealthStatus>;\n /** `true` when the local store survives a reload. */\n isPersistent: Accessor<boolean>;\n /**\n * `true` only when durable storage was requested and could NOT be opened, so\n * the dataset is sitting in RAM and local writes die on reload. Drive a\n * warning off this, not off `status`: a store configured as in-memory reports\n * `'memory'` too, and that is a choice rather than a problem.\n */\n isMemoryFallback: Accessor<boolean>;\n}\n\n/**\n * Observe how durable the LOCAL cache is, for a \"no local storage\" warning.\n *\n * Under `localEngine: 'sqlite'` with `store: 'indexeddb'` the durable store is\n * the OPFS SAHPool VFS, and only ONE client per bucket can hold it open: a\n * second tab of the same app cannot get it and runs in memory instead (the\n * engine retries first, so a closing tab's lock is usually waited out). Must be\n * used within a `<Sp00kyProvider>`.\n */\nexport function useStorageStatus(): UseStorageStatus {\n const db = useDb();\n // subscribeToStorageHealth fires synchronously with the current snapshot, so\n // the signal is correct from the first read; the initial value avoids a flash.\n const [health, setHealth] = createSignal<StorageHealth>(db.storageHealth);\n const unsub = db.subscribeToStorageHealth(setHealth);\n onCleanup(unsub);\n\n return {\n health,\n status: () => health().status,\n isPersistent: () => health().status === 'persistent',\n isMemoryFallback: () => health().fallback,\n };\n}\n","import { createEffect, createSignal, onCleanup, useContext, type Accessor } from 'solid-js';\nimport { Sp00kyContext } from './context';\nimport type { CrdtField } from '@spooky-sync/core';\n\nexport function useCrdtField(\n table: string,\n recordId: () => string | undefined,\n field: string,\n fallbackText?: () => string | undefined,\n): Accessor<CrdtField | null> {\n const db = useContext(Sp00kyContext);\n if (!db) {\n throw new Error('useCrdtField must be used within a <Sp00kyProvider>');\n }\n\n const [crdtField, setCrdtField] = createSignal<CrdtField | null>(null);\n let currentId: string | undefined;\n let initialized = false;\n\n createEffect(() => {\n const id = recordId();\n\n // Skip if the ID hasn't changed (but allow the first non-undefined value through)\n if (initialized && id === currentId) return;\n\n // Close previous field\n if (currentId && crdtField()) {\n db.getSp00ky().closeCrdtField(table, currentId, field);\n setCrdtField(null);\n }\n\n currentId = id;\n initialized = true;\n\n if (!id) return;\n\n const sp00ky = db.getSp00ky();\n const text = fallbackText?.();\n sp00ky\n .openCrdtField(table, id, field, text)\n .then((cf) => {\n if (currentId === id) {\n setCrdtField(cf);\n }\n })\n .catch((err) => {\n // Silent rejections here leave the consumer's `Show when={field()}`\n // permanently stuck on its fallback (typically a static `<p>` with\n // no editing UI), with no error trail. Surface the failure so the\n // root cause (missing `@crdt` annotation, schema codegen drift,\n // local DB query failure, etc.) is visible in the console instead\n // of silently breaking collaborative fields.\n console.error(\n `[useCrdtField] Failed to open CRDT field ${table}.${field} on ${id}:`,\n err,\n );\n });\n });\n\n onCleanup(() => {\n if (currentId && crdtField()) {\n db.getSp00ky().closeCrdtField(table, currentId, field);\n setCrdtField(null);\n }\n });\n\n return crdtField;\n}\n","import { createSignal, onCleanup, type Accessor } from 'solid-js';\nimport { useDb } from './context';\nimport type { FeatureFlagOptions } from '@spooky-sync/core';\n\nexport interface UseFeatureFlag {\n variant: Accessor<string | undefined>;\n payload: Accessor<unknown | undefined>;\n enabled: Accessor<boolean>;\n}\n\n/**\n * Subscribe to a feature flag for the currently authenticated user.\n *\n * Returns three Solid accessors that update reactively whenever the\n * server-materialized assignment in `_00_user_feature` changes. Backed by\n * the same SSP + sync pipeline that powers `useQuery`, so toggling a flag\n * via `spky flag enable <key>` propagates to the UI without a refresh.\n *\n * `enabled()` is `true` when the resolved variant exists and is not 'off'.\n * For multi-variant flags, prefer `variant()` directly.\n */\nexport function useFeatureFlag(\n key: string,\n options?: FeatureFlagOptions,\n): UseFeatureFlag {\n const db = useDb();\n const handle = db.getSp00ky().feature(key, options);\n\n const [variant, setVariant] = createSignal<string | undefined>(handle.variant());\n const [payload, setPayload] = createSignal<unknown | undefined>(handle.payload());\n\n const unsub = handle.subscribe((s) => {\n setVariant(s.variant ?? options?.fallback);\n setPayload(s.payload);\n });\n\n onCleanup(() => {\n unsub();\n handle.close();\n });\n\n return {\n variant,\n payload,\n enabled: () => {\n const v = variant();\n return v !== undefined && v !== 'off';\n },\n };\n}\n","import { createSignal, onCleanup, type Accessor } from 'solid-js';\nimport { useDb } from './context';\nimport { semverGt, type AppReleaseOptions, type AppReleaseSnapshot } from '@spooky-sync/core';\n\nexport interface UseAppReleaseOptions extends AppReleaseOptions {\n /** App name from sp00ky.yml, e.g. `web`. */\n app: string;\n /**\n * The running build's version (X.Y.Z), typically baked in at build time\n * (e.g. a vite `define` from package.json). `updateAvailable()` is true when\n * the announced release is semver-newer than this.\n */\n currentVersion: string;\n}\n\nexport interface UseAppRelease {\n /** Latest announced version for the app, or undefined when no row exists. */\n latestVersion: Accessor<string | undefined>;\n /** Announced version is semver-newer than the running build. */\n updateAvailable: Accessor<boolean>;\n /** The newer release asks clients to update/reload without prompting. */\n mandatory: Accessor<boolean>;\n /** The newer release asks reloads to clear service-worker caches first. */\n cacheBust: Accessor<boolean>;\n /**\n * Reload onto the announced release. Plain `location.reload()` normally;\n * when the release is flagged cache-bust, CacheStorage is cleared, the\n * service-worker registration is nudged to update, and navigation carries a\n * `?cb=` token to punch through intermediary caches. The service worker is\n * deliberately NOT unregistered: navigating while still controlled by a\n * just-unregistered worker strands subresource fetches on the dead worker\n * and the page hangs until a manual reload.\n */\n reload: () => Promise<void>;\n}\n\nasync function reloadForSnapshot(snapshot: AppReleaseSnapshot): Promise<void> {\n if (typeof window === 'undefined') return;\n if (snapshot.cacheBust) {\n try {\n if (window.caches) {\n const keys = await window.caches.keys();\n await Promise.all(keys.map((k) => window.caches.delete(k)));\n }\n if (navigator.serviceWorker) {\n const regs = await navigator.serviceWorker.getRegistrations();\n for (const r of regs) r.update().catch(() => {});\n }\n window.location.href = window.location.pathname + '?cb=' + Date.now();\n return;\n } catch {\n /* fall through to a plain reload */\n }\n }\n window.location.reload();\n}\n\n/**\n * Observe the app's announced release (`_00_app_release:<app>`, written by\n * `spky deploy` / `spky release`) and compare it against the running build.\n *\n * Typical use: mount a small \"new version available — Reload\" notification\n * gated on `updateAvailable()`, auto-invoking `reload()` when `mandatory()`\n * (guard the auto path against reload loops with a per-version marker, since\n * a client can reload while the deploy is still rolling out and land on the\n * old bundle again).\n */\nexport function useAppRelease(options: UseAppReleaseOptions): UseAppRelease {\n const db = useDb();\n const handle = db.getSp00ky().appRelease(options.app, { ttl: options.ttl });\n\n const [snapshot, setSnapshot] = createSignal<AppReleaseSnapshot>(handle.snapshot());\n const unsub = handle.subscribe(setSnapshot);\n\n onCleanup(() => {\n unsub();\n handle.close();\n });\n\n const updateAvailable = () => semverGt(snapshot().version, options.currentVersion);\n\n return {\n latestVersion: () => snapshot().version,\n updateAvailable,\n mandatory: () => updateAvailable() && snapshot().mandatory,\n cacheBust: () => snapshot().cacheBust,\n reload: () => reloadForSnapshot(snapshot()),\n };\n}\n","import { createSignal, onCleanup } from 'solid-js';\nimport type { SchemaStructure, BucketNames } from '@spooky-sync/query-builder';\nimport { fileToUint8Array } from '@spooky-sync/core';\nimport type { SyncedDb } from '../index';\nimport { useDb } from './context';\n\nexport interface FileUploadResult {\n isUploading: () => boolean;\n error: () => Error | null;\n clearError: () => void;\n upload: (path: string, file: File | Blob) => Promise<void>;\n download: (path: string) => Promise<string | null>;\n remove: (path: string) => Promise<void>;\n exists: (path: string) => Promise<boolean>;\n}\n\nexport function useFileUpload<S extends SchemaStructure>(\n bucketName: BucketNames<S>,\n): FileUploadResult;\nexport function useFileUpload<S extends SchemaStructure>(\n db: SyncedDb<S>,\n bucketName: BucketNames<S>,\n): FileUploadResult;\nexport function useFileUpload<S extends SchemaStructure>(\n dbOrBucketName: SyncedDb<S> | BucketNames<S>,\n maybeBucketName?: BucketNames<S>,\n): FileUploadResult {\n let db: SyncedDb<S>;\n let bucketName: BucketNames<S>;\n\n if (typeof dbOrBucketName === 'string') {\n db = useDb<S>();\n bucketName = dbOrBucketName as BucketNames<S>;\n } else {\n db = dbOrBucketName as SyncedDb<S>;\n // oxlint-disable-next-line no-non-null-assertion\n bucketName = maybeBucketName!;\n }\n\n const [isUploading, setIsUploading] = createSignal(false);\n const [error, setError] = createSignal<Error | null>(null);\n\n const objectUrls: string[] = [];\n onCleanup(() => {\n for (const url of objectUrls) {\n URL.revokeObjectURL(url);\n }\n });\n\n const clearError = () => setError(null);\n\n const validate = (file: File | Blob): void => {\n const config = db.getBucketConfig(bucketName as string);\n if (!config) return;\n\n if (config.maxSize !== null && config.maxSize !== undefined && file.size > config.maxSize) {\n const maxMB = (config.maxSize / (1024 * 1024)).toFixed(1);\n throw new Error(`File exceeds maximum size of ${maxMB} MB.`);\n }\n\n if (config.allowedExtensions && config.allowedExtensions.length > 0) {\n const fileName = (file as File).name;\n if (fileName) {\n const ext = fileName.split('.').pop()?.toLowerCase();\n if (!ext || !config.allowedExtensions.includes(ext)) {\n throw new Error(\n `File type not allowed. Accepted: ${config.allowedExtensions.join(', ')}.`\n );\n }\n }\n }\n };\n\n const upload = async (path: string, file: File | Blob): Promise<void> => {\n setError(null);\n try {\n validate(file);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return;\n }\n\n setIsUploading(true);\n try {\n const bytes = await fileToUint8Array(file);\n await db.bucket(bucketName).put(path, bytes);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n } finally {\n setIsUploading(false);\n }\n };\n\n const download = async (path: string): Promise<string | null> => {\n setError(null);\n try {\n const content = await db.bucket(bucketName).get(path);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n objectUrls.push(objectUrl);\n return objectUrl;\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return null;\n }\n };\n\n const remove = async (path: string): Promise<void> => {\n setError(null);\n try {\n await db.bucket(bucketName).delete(path);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n }\n };\n\n const exists = async (path: string): Promise<boolean> => {\n setError(null);\n try {\n return await db.bucket(bucketName).exists(path);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return false;\n }\n };\n\n return {\n isUploading,\n error,\n clearError,\n upload,\n download,\n remove,\n exists,\n };\n}\n","import { createSignal, createEffect, onCleanup, type Accessor } from 'solid-js';\nimport type { SchemaStructure, BucketNames } from '@spooky-sync/query-builder';\nimport type { SyncedDb } from '../index';\nimport { useDb } from './context';\n\nexport interface UseDownloadFileOptions {\n cache?: boolean;\n}\n\nexport interface UseDownloadFileResult {\n url: Accessor<string | null>;\n isLoading: Accessor<boolean>;\n error: Accessor<Error | null>;\n refetch: () => void;\n}\n\ninterface CacheEntry {\n url: string;\n refCount: number;\n}\n\nconst downloadCache = new Map<string, CacheEntry>();\nconst inflightRequests = new Map<string, Promise<string | null>>();\n\nfunction cacheKey(bucket: string, path: string): string {\n return `${bucket}:${path}`;\n}\n\nfunction releaseEntry(key: string): void {\n const entry = downloadCache.get(key);\n if (!entry) return;\n entry.refCount--;\n if (entry.refCount <= 0) {\n URL.revokeObjectURL(entry.url);\n downloadCache.delete(key);\n }\n}\n\nexport function useDownloadFile<S extends SchemaStructure>(\n bucketName: BucketNames<S>,\n path: Accessor<string | null | undefined>,\n options?: UseDownloadFileOptions,\n): UseDownloadFileResult;\nexport function useDownloadFile<S extends SchemaStructure>(\n db: SyncedDb<S>,\n bucketName: BucketNames<S>,\n path: Accessor<string | null | undefined>,\n options?: UseDownloadFileOptions,\n): UseDownloadFileResult;\nexport function useDownloadFile<S extends SchemaStructure>(\n dbOrBucketName: SyncedDb<S> | BucketNames<S>,\n bucketNameOrPath?: BucketNames<S> | Accessor<string | null | undefined>,\n pathOrOptions?: Accessor<string | null | undefined> | UseDownloadFileOptions,\n maybeOptions?: UseDownloadFileOptions,\n): UseDownloadFileResult {\n let db: SyncedDb<S>;\n let bucketName: BucketNames<S>;\n let path: Accessor<string | null | undefined>;\n let options: UseDownloadFileOptions;\n\n if (typeof dbOrBucketName === 'string') {\n db = useDb<S>();\n bucketName = dbOrBucketName as BucketNames<S>;\n path = bucketNameOrPath as Accessor<string | null | undefined>;\n options = (pathOrOptions as UseDownloadFileOptions) ?? {};\n } else {\n db = dbOrBucketName as SyncedDb<S>;\n bucketName = bucketNameOrPath as BucketNames<S>;\n path = pathOrOptions as Accessor<string | null | undefined>;\n options = maybeOptions ?? {};\n }\n\n const useCache = options.cache !== false;\n\n const [url, setUrl] = createSignal<string | null>(null);\n const [isLoading, setIsLoading] = createSignal(false);\n const [error, setError] = createSignal<Error | null>(null);\n\n let currentKey: string | null = null;\n let privateUrl: string | null = null;\n const [refetchSignal, setRefetchSignal] = createSignal(0);\n const refetchTrigger = () => setRefetchSignal((n) => n + 1);\n\n async function doDownload(key: string, filePath: string): Promise<string | null> {\n if (useCache) {\n // Check cache\n const cached = downloadCache.get(key);\n if (cached) {\n cached.refCount++;\n currentKey = key;\n return cached.url;\n }\n\n // Check inflight\n const inflight = inflightRequests.get(key);\n if (inflight) {\n const result = await inflight;\n if (result) {\n const entry = downloadCache.get(key);\n if (entry) {\n entry.refCount++;\n currentKey = key;\n }\n }\n return result;\n }\n\n // Start new download\n const promise = (async () => {\n const content = await db.bucket(bucketName).get(filePath);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n downloadCache.set(key, { url: objectUrl, refCount: 1 });\n return objectUrl;\n })();\n\n inflightRequests.set(key, promise);\n try {\n const result = await promise;\n currentKey = key;\n return result;\n } finally {\n inflightRequests.delete(key);\n }\n } else {\n // No caching — private URL per instance\n const content = await db.bucket(bucketName).get(filePath);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n privateUrl = objectUrl;\n return objectUrl;\n }\n }\n\n function releaseCurrentEntry() {\n if (useCache && currentKey) {\n releaseEntry(currentKey);\n currentKey = null;\n }\n if (!useCache && privateUrl) {\n URL.revokeObjectURL(privateUrl);\n privateUrl = null;\n }\n }\n\n createEffect(() => {\n const filePath = path();\n // Subscribe to refetch signal so effect re-runs\n refetchSignal();\n\n // Release previous entry\n releaseCurrentEntry();\n\n if (!filePath) {\n setUrl(null);\n setIsLoading(false);\n setError(null);\n return;\n }\n\n const key = cacheKey(bucketName as string, filePath);\n\n // Synchronous cache hit\n if (useCache) {\n const cached = downloadCache.get(key);\n if (cached) {\n cached.refCount++;\n currentKey = key;\n setUrl(cached.url);\n setIsLoading(false);\n setError(null);\n return;\n }\n }\n\n let cancelled = false;\n setIsLoading(true);\n setError(null);\n\n doDownload(key, filePath).then(\n (result) => {\n if (!cancelled) {\n setUrl(result);\n setIsLoading(false);\n }\n return undefined;\n },\n (err) => {\n if (!cancelled) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setIsLoading(false);\n }\n },\n );\n\n onCleanup(() => {\n cancelled = true;\n });\n });\n\n onCleanup(() => {\n releaseCurrentEntry();\n });\n\n const refetch = () => {\n // Evict current entry from cache before re-triggering\n if (useCache && currentKey) {\n const entry = downloadCache.get(currentKey);\n if (entry) {\n URL.revokeObjectURL(entry.url);\n downloadCache.delete(currentKey);\n }\n currentKey = null;\n }\n refetchTrigger();\n };\n\n return { url, isLoading, error, refetch };\n}\n","import type { JSX } from 'solid-js';\nimport {\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 // Set once the provider is disposed. `onMount` is async, so a remount (or a\n // fast unmount) can land mid-init; without this the abandoned instance keeps\n // its SharedWorker leadership, its OPFS worker and its wasm circuit alive for\n // the life of the page.\n let disposed = false;\n let live: SyncedDb<S> | undefined;\n\n onCleanup(() => {\n disposed = true;\n const instance = live;\n live = undefined;\n void instance?.close();\n });\n\n onMount(async () => {\n try {\n const instance = new SyncedDb<S>(merged.config);\n live = instance;\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 { SyncHealth, SyncHealthStatus, SyncHealthConfig } from '@spooky-sync/core';\nexport { useStorageStatus, type UseStorageStatus } from './lib/use-storage-status';\nexport type { StorageHealth, StorageHealthStatus } from '@spooky-sync/core';\nexport { useCrdtField } from './lib/use-crdt-field';\nexport { useFeatureFlag, type UseFeatureFlag } from './lib/use-feature-flag';\nexport {\n useAppRelease,\n type UseAppRelease,\n type UseAppReleaseOptions,\n} from './lib/use-app-release';\nexport { useFileUpload, type FileUploadResult } from './lib/use-file-upload';\nexport {\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, CrdtField, FeatureFlagOptions, PreloadOptions, PreloadOptions as PreloadOptions$1, PreloadRefresh, RunOptions, Sp00kyClient, Sp00kyConfig, Sp00kyQueryResultPromise, StorageHealth, StorageHealth as StorageHealth$1, StorageHealthStatus, StorageHealthStatus as StorageHealthStatus$1, SyncHealth, SyncHealth as SyncHealth$1, SyncHealthConfig, SyncHealthStatus, SyncHealthStatus as SyncHealthStatus$1, UpdateOptions } from "@spooky-sync/core";
4
4
  import { BackendNames, BackendRoutes, BucketDefinitionSchema, BucketNames, ColumnSchema, FinalQuery, GetCardinality, GetRelationship, GetTable, GetTable as GetTable$1, InferRelatedModelFromMetadata, InnerQuery, QueryBuilder, QueryInfo, QueryModifier, QueryModifierBuilder, QueryResult, QueryResult as QueryResult$1, RelatedFieldMapEntry, RelatedFieldsMap, RelationshipDefinition, RelationshipFieldsFromSchema, RelationshipsMetadata, RoutePayload, SchemaStructure as SchemaStructure$1, TableModel, TableModel as TableModel$1, TableNames, TableNames as TableNames$1 } from "@spooky-sync/query-builder";
5
5
  import { Accessor, JSX } from "solid-js";
6
6
 
@@ -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,7 +69,152 @@ 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;
74
+ };
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;
62
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
+ /**
111
+ * Observe sync health for a "can't reach the server" banner / indicator.
112
+ *
113
+ * Backed by `db.subscribeToSyncHealth`. Individual sync failures (a transient
114
+ * remote 500 on query registration, a dropped socket) are absorbed by the
115
+ * retry and never flip this; `isDegraded()` only goes true once failures
116
+ * persist for the configured number of consecutive rounds (sp00ky core config
117
+ * `syncHealth.degradeAfterConsecutiveFailures`, default 3), and flips back on
118
+ * the next successful round. Must be used within a `<Sp00kyProvider>`.
119
+ */
120
+ declare function useSyncStatus(): UseSyncStatus;
121
+ //# sourceMappingURL=use-sync-status.d.ts.map
122
+ //#endregion
123
+ //#region src/lib/use-storage-status.d.ts
124
+ interface UseStorageStatus {
125
+ /** Full durability snapshot; updates reactively. */
126
+ health: Accessor<StorageHealth$1>;
127
+ /** `'unknown'` | `'persistent'` | `'memory'`. */
128
+ status: Accessor<StorageHealthStatus$1>;
129
+ /** `true` when the local store survives a reload. */
130
+ isPersistent: Accessor<boolean>;
131
+ /**
132
+ * `true` only when durable storage was requested and could NOT be opened, so
133
+ * the dataset is sitting in RAM and local writes die on reload. Drive a
134
+ * warning off this, not off `status`: a store configured as in-memory reports
135
+ * `'memory'` too, and that is a choice rather than a problem.
136
+ */
137
+ isMemoryFallback: Accessor<boolean>;
138
+ }
139
+ /**
140
+ * Observe how durable the LOCAL cache is, for a "no local storage" warning.
141
+ *
142
+ * Under `localEngine: 'sqlite'` with `store: 'indexeddb'` the durable store is
143
+ * the OPFS SAHPool VFS, and only ONE client per bucket can hold it open: a
144
+ * second tab of the same app cannot get it and runs in memory instead (the
145
+ * engine retries first, so a closing tab's lock is usually waited out). Must be
146
+ * used within a `<Sp00kyProvider>`.
147
+ */
148
+ declare function useStorageStatus(): UseStorageStatus;
149
+ //# sourceMappingURL=use-storage-status.d.ts.map
150
+ //#endregion
151
+ //#region src/lib/use-crdt-field.d.ts
152
+ declare function useCrdtField(table: string, recordId: () => string | undefined, field: string, fallbackText?: () => string | undefined): Accessor<CrdtField | null>;
153
+ //# sourceMappingURL=use-crdt-field.d.ts.map
154
+ //#endregion
155
+ //#region src/lib/use-feature-flag.d.ts
156
+ interface UseFeatureFlag {
157
+ variant: Accessor<string | undefined>;
158
+ payload: Accessor<unknown | undefined>;
159
+ enabled: Accessor<boolean>;
160
+ }
161
+ /**
162
+ * Subscribe to a feature flag for the currently authenticated user.
163
+ *
164
+ * Returns three Solid accessors that update reactively whenever the
165
+ * server-materialized assignment in `_00_user_feature` changes. Backed by
166
+ * the same SSP + sync pipeline that powers `useQuery`, so toggling a flag
167
+ * via `spky flag enable <key>` propagates to the UI without a refresh.
168
+ *
169
+ * `enabled()` is `true` when the resolved variant exists and is not 'off'.
170
+ * For multi-variant flags, prefer `variant()` directly.
171
+ */
172
+ declare function useFeatureFlag(key: string, options?: FeatureFlagOptions): UseFeatureFlag;
173
+ //# sourceMappingURL=use-feature-flag.d.ts.map
174
+ //#endregion
175
+ //#region src/lib/use-app-release.d.ts
176
+ interface UseAppReleaseOptions extends AppReleaseOptions {
177
+ /** App name from sp00ky.yml, e.g. `web`. */
178
+ app: string;
179
+ /**
180
+ * The running build's version (X.Y.Z), typically baked in at build time
181
+ * (e.g. a vite `define` from package.json). `updateAvailable()` is true when
182
+ * the announced release is semver-newer than this.
183
+ */
184
+ currentVersion: string;
185
+ }
186
+ interface UseAppRelease {
187
+ /** Latest announced version for the app, or undefined when no row exists. */
188
+ latestVersion: Accessor<string | undefined>;
189
+ /** Announced version is semver-newer than the running build. */
190
+ updateAvailable: Accessor<boolean>;
191
+ /** The newer release asks clients to update/reload without prompting. */
192
+ mandatory: Accessor<boolean>;
193
+ /** The newer release asks reloads to clear service-worker caches first. */
194
+ cacheBust: Accessor<boolean>;
195
+ /**
196
+ * Reload onto the announced release. Plain `location.reload()` normally;
197
+ * when the release is flagged cache-bust, CacheStorage is cleared, the
198
+ * service-worker registration is nudged to update, and navigation carries a
199
+ * `?cb=` token to punch through intermediary caches. The service worker is
200
+ * deliberately NOT unregistered: navigating while still controlled by a
201
+ * just-unregistered worker strands subresource fetches on the dead worker
202
+ * and the page hangs until a manual reload.
203
+ */
204
+ reload: () => Promise<void>;
205
+ }
206
+ /**
207
+ * Observe the app's announced release (`_00_app_release:<app>`, written by
208
+ * `spky deploy` / `spky release`) and compare it against the running build.
209
+ *
210
+ * Typical use: mount a small "new version available — Reload" notification
211
+ * gated on `updateAvailable()`, auto-invoking `reload()` when `mandatory()`
212
+ * (guard the auto path against reload loops with a per-version marker, since
213
+ * a client can reload while the deploy is still rolling out and land on the
214
+ * old bundle again).
215
+ */
216
+ declare function useAppRelease(options: UseAppReleaseOptions): UseAppRelease;
217
+ //# sourceMappingURL=use-app-release.d.ts.map
63
218
  //#endregion
64
219
  //#region src/lib/use-file-upload.d.ts
65
220
  interface FileUploadResult {
@@ -89,16 +244,25 @@ declare function useDownloadFile<S extends SchemaStructure$1>(bucketName: Bucket
89
244
  declare function useDownloadFile<S extends SchemaStructure$1>(db: SyncedDb<S>, bucketName: BucketNames<S>, path: Accessor<string | null | undefined>, options?: UseDownloadFileOptions): UseDownloadFileResult;
90
245
  //# sourceMappingURL=use-download-file.d.ts.map
91
246
  //#endregion
92
- //#region src/lib/SpookyProvider.d.ts
93
- interface SpookyProviderProps<S extends SchemaStructure> {
247
+ //#region src/lib/Sp00kyProvider.d.ts
248
+ interface Sp00kyProviderProps<S extends SchemaStructure> {
94
249
  config: SyncedDbConfig<S>;
95
250
  fallback?: JSX.Element;
96
251
  onError?: (error: Error) => void;
97
252
  onReady?: (db: SyncedDb<S>) => void;
253
+ /**
254
+ * Prewarm data into the local cache before revealing the UI. Runs after
255
+ * `init()`; the `fallback` stays visible until it resolves. Use awaitable
256
+ * `db.preload(...)` calls here to gate first-load on essential data (e.g.
257
+ * config). On warm loads preload returns instantly, so there's no perceptible
258
+ * gate after the first run. Best-effort: a rejection is caught and the UI is
259
+ * revealed anyway.
260
+ */
261
+ preload?: (db: SyncedDb<S>) => Promise<void>;
98
262
  children: JSX.Element;
99
263
  }
100
- declare function SpookyProvider<S extends SchemaStructure>(props: SpookyProviderProps<S>): JSX.Element;
101
- //# sourceMappingURL=SpookyProvider.d.ts.map
264
+ declare function Sp00kyProvider<S extends SchemaStructure>(props: Sp00kyProviderProps<S>): JSX.Element;
265
+ //# sourceMappingURL=Sp00kyProvider.d.ts.map
102
266
  //#endregion
103
267
  //#region src/lib/context.d.ts
104
268
  declare function useDb<S extends SchemaStructure>(): SyncedDb<S>;
@@ -106,13 +270,13 @@ declare function useDb<S extends SchemaStructure>(): SyncedDb<S>;
106
270
 
107
271
  //#endregion
108
272
  //#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]: {
273
+ type RelationshipField<Schema extends SchemaStructure$1, TableName extends TableNames<Schema>, Field extends RelationshipFieldsFromSchema<Schema, TableName>> = GetRelationship<Schema, TableName, Field>;
274
+ type RelatedFieldsTableScoped<Schema extends SchemaStructure$1, TableName extends TableNames<Schema>, RelatedFields extends RelationshipFieldsFromSchema<Schema, TableName> = RelationshipFieldsFromSchema<Schema, TableName>> = { [K in RelatedFields]: {
111
275
  to: RelationshipField<Schema, TableName, K>['to'];
112
276
  relatedFields: RelatedFieldsMap;
113
277
  cardinality: RelationshipField<Schema, TableName, K>['cardinality'];
114
278
  } };
115
- type InferModel<Schema extends SchemaStructure$1, TableName extends TableNames$1<Schema>, RelatedFields extends RelatedFieldsTableScoped<Schema, TableName>> = QueryResult$1<Schema, TableName, RelatedFields, true>;
279
+ type InferModel<Schema extends SchemaStructure$1, TableName extends TableNames<Schema>, RelatedFields extends RelatedFieldsTableScoped<Schema, TableName>> = QueryResult<Schema, TableName, RelatedFields, true>;
116
280
  type WithRelated<Field extends string, RelatedFields extends RelatedFieldsMap = {}> = { [K in Field]: Omit<RelatedFieldMapEntry, 'relatedFields'> & {
117
281
  relatedFields: RelatedFields;
118
282
  } };
@@ -122,19 +286,26 @@ type WithRelatedMany<Field extends string, RelatedFields extends RelatedFieldsMa
122
286
  cardinality: 'many';
123
287
  } };
124
288
  /**
125
- * SyncedDb - A thin wrapper around spooky-ts for Solid.js integration
126
- * Delegates all logic to the underlying spooky-ts instance
289
+ * SyncedDb - A thin wrapper around sp00ky-ts for Solid.js integration
290
+ * Delegates all logic to the underlying sp00ky-ts instance
127
291
  */
128
292
  declare class SyncedDb<S extends SchemaStructure$1> {
129
293
  private config;
130
- private spooky;
294
+ private sp00ky;
131
295
  private _initialized;
132
296
  constructor(config: SyncedDbConfig<S>);
133
- getSpooky(): SpookyClient<S>;
297
+ getSp00ky(): Sp00kyClient<S>;
134
298
  /**
135
- * Initialize the spooky-ts instance
299
+ * Initialize the sp00ky-ts instance
136
300
  */
137
301
  init(): Promise<void>;
302
+ /**
303
+ * Tear down the client: leaves the tabs broker, closes the local store and
304
+ * remote socket, and frees the wasm circuit. Without this a remounted provider
305
+ * (or an HMR reload) strands a whole client, and the abandoned wasm heaps stay
306
+ * resident because V8 cannot see how much wasm memory a dropped wrapper holds.
307
+ */
308
+ close(): Promise<void>;
138
309
  /**
139
310
  * Create a new record in the database
140
311
  */
@@ -142,15 +313,21 @@ declare class SyncedDb<S extends SchemaStructure$1> {
142
313
  /**
143
314
  * Update an existing record in the database
144
315
  */
145
- update<TName extends TableNames$1<S>>(tableName: TName, recordId: string, payload: Partial<TableModel$1<GetTable$1<S, TName>>>, options?: UpdateOptions): Promise<void>;
316
+ update<TName extends TableNames<S>>(tableName: TName, recordId: string, payload: Partial<TableModel<GetTable<S, TName>>>, options?: UpdateOptions): Promise<void>;
146
317
  /**
147
318
  * Delete an existing record in the database
148
319
  */
149
- delete<TName extends TableNames$1<S>>(tableName: TName, selector: string | InnerQuery<GetTable$1<S, TName>, boolean>): Promise<void>;
320
+ delete<TName extends TableNames<S>>(tableName: TName, selector: string | RecordId | InnerQuery<GetTable<S, TName>, boolean>): Promise<void>;
321
+ /**
322
+ * Preload/prewarm a built query into the local cache without registering a
323
+ * live view. Fetches once and stores the rows (+ embedded related children)
324
+ * locally so a later `useQuery` for the same data paints instantly. Best-effort.
325
+ */
326
+ preload(finalQuery: FinalQuery<S, any, any, any, any, Sp00kyQueryResultPromise>, options?: PreloadOptions$1): Promise<void>;
150
327
  /**
151
328
  * Query data from the database
152
329
  */
153
- query<TName extends TableNames$1<S>>(table: TName): QueryBuilder<S, TName, SpookyQueryResultPromise, {}, false>;
330
+ query<TName extends TableNames<S>>(table: TName): QueryBuilder<S, TName, Sp00kyQueryResultPromise, {}, false>;
154
331
  /**
155
332
  * Run a backend operation
156
333
  */
@@ -175,20 +352,38 @@ declare class SyncedDb<S extends SchemaStructure$1> {
175
352
  /**
176
353
  * Access the remote database service directly
177
354
  */
178
- get remote(): SpookyClient<S>['remoteClient'];
355
+ get remote(): Sp00kyClient<S>['remoteClient'];
179
356
  /**
180
357
  * Access the local database service directly
181
358
  */
182
- get local(): SpookyClient<S>['localClient'];
359
+ get local(): Sp00kyClient<S>['localClient'];
183
360
  /**
184
361
  * Access the auth service
185
362
  */
186
363
  get auth(): AuthService<S>;
187
364
  get pendingMutationCount(): number;
365
+ /** Diagnostic — see `Sp00kyClient.liveRetryCount`. */
366
+ get liveRetryCount(): number;
188
367
  subscribeToPendingMutations(cb: (count: number) => void): () => void;
368
+ /** Current sync-health snapshot. See {@link useSyncStatus}. */
369
+ get syncHealth(): SyncHealth$1;
370
+ /**
371
+ * Observe sync health. Fires immediately with the current status and again
372
+ * on every healthy↔degraded transition. Prefer the `useSyncStatus` hook in
373
+ * components; this is the imperative escape hatch.
374
+ */
375
+ subscribeToSyncHealth(cb: (health: SyncHealth$1) => void): () => void;
376
+ /** Current local-store durability snapshot. See {@link useStorageStatus}. */
377
+ get storageHealth(): StorageHealth$1;
378
+ /**
379
+ * Observe local-store durability. Fires immediately with the current snapshot
380
+ * and again on change. Prefer the `useStorageStatus` hook in components; this
381
+ * is the imperative escape hatch.
382
+ */
383
+ subscribeToStorageHealth(cb: (health: StorageHealth$1) => void): () => void;
189
384
  bucket<B extends BucketNames<S>>(name: B): BucketHandle;
190
385
  getBucketConfig(name: string): BucketDefinitionSchema | undefined;
191
386
  }
192
387
  //#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 };
388
+ export { CacheStrategy, type FileUploadResult, type GenericModel, type GenericSchema, type GetCardinality, type GetTable, InferModel, type InferRelatedModelFromMetadata, InferRelationshipsFromConst, InferSchemaFromConst, type Model, type ModelPayload, type PreloadOptions, type PreloadRefresh, ProvisionOptions, type QueryInfo, type QueryModifier, type QueryModifierBuilder, type QueryResult, RecordId, RelatedFieldsTableScoped, type RelationshipDefinition, RelationshipField, type RelationshipsMetadata, Sp00kyProvider, type Sp00kyProviderProps, type StorageHealth, type StorageHealthStatus, type SyncHealth, type SyncHealthConfig, type SyncHealthStatus, SyncedDb, SyncedDbConfig, type TableModel, type TableNames, type UseAppRelease, type UseAppReleaseOptions, type UseDownloadFileOptions, type UseDownloadFileResult, type UseFeatureFlag, type UseStorageStatus, type UseSyncStatus, Uuid, WithRelated, WithRelatedMany, createPreload, useAppRelease, useCrdtField, useDb, useDownloadFile, useFeatureFlag, useFileUpload, useQuery, useStorageStatus, useSyncStatus };
194
389
  //# sourceMappingURL=index.d.cts.map