@spooky-sync/client-solid 0.0.1-canary.69 → 0.0.1-canary.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2,6 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  let _spooky_sync_core = require("@spooky-sync/core");
3
3
  let surrealdb = require("surrealdb");
4
4
  let solid_js = require("solid-js");
5
+ let solid_js_store = require("solid-js/store");
5
6
 
6
7
  //#region src/lib/context.ts
7
8
  const Sp00kyContext = (0, solid_js.createContext)();
@@ -28,24 +29,50 @@ function useQuery(dbOrQuery, queryOrOptions, maybeOptions) {
28
29
  finalQuery = dbOrQuery;
29
30
  options = queryOrOptions;
30
31
  }
31
- const [data, setData] = (0, solid_js.createSignal)(void 0);
32
32
  const [error, setError] = (0, solid_js.createSignal)(void 0);
33
33
  const [isFetched, setIsFetched] = (0, solid_js.createSignal)(false);
34
- const [unsubscribe, setUnsubscribe] = (0, solid_js.createSignal)(void 0);
34
+ const [isFetching, setIsFetching] = (0, solid_js.createSignal)(false);
35
+ const [state, setState] = (0, solid_js_store.createStore)({ value: void 0 });
36
+ const [version, setVersion] = (0, solid_js.createSignal)(0);
37
+ const data = () => {
38
+ version();
39
+ return state.value;
40
+ };
35
41
  let prevQueryString;
42
+ let runId = 0;
43
+ let activeUnsub;
44
+ let activeHash;
45
+ const teardownActive = () => {
46
+ activeUnsub?.();
47
+ activeUnsub = void 0;
48
+ };
36
49
  const sp00ky = db.getSp00ky();
37
- const initQuery = async (query) => {
50
+ const initQuery = async (query, myRun) => {
38
51
  const { hash } = await query.run();
52
+ if (myRun !== runId) return;
53
+ activeHash = hash;
39
54
  setError(void 0);
40
55
  let isFirstCall = true;
41
56
  const unsub = await sp00ky.subscribe(hash, (e) => {
42
57
  const queryData = query.isOne ? e[0] : e;
43
- setData(() => queryData);
58
+ const reconcileStart = performance.now();
59
+ setState("value", (0, solid_js_store.reconcile)(queryData, { key: "id" }));
60
+ setVersion((v) => v + 1);
61
+ sp00ky.reportFrontendTiming(hash, performance.now() - reconcileStart);
44
62
  const hasData = query.isOne ? queryData !== null && queryData !== void 0 : e.length > 0;
45
63
  if (!isFirstCall || hasData) setIsFetched(true);
46
64
  isFirstCall = false;
47
65
  }, { immediate: true });
48
- setUnsubscribe(() => unsub);
66
+ const unsubStatus = sp00ky.subscribeQueryStatus(hash, (status) => setIsFetching(status === "fetching"), { immediate: true });
67
+ const teardown = () => {
68
+ unsub();
69
+ unsubStatus();
70
+ };
71
+ if (myRun !== runId) {
72
+ teardown();
73
+ return;
74
+ }
75
+ activeUnsub = teardown;
49
76
  };
50
77
  (0, solid_js.createEffect)(() => {
51
78
  if (!(options?.enabled?.() ?? true)) {
@@ -54,14 +81,18 @@ function useQuery(dbOrQuery, queryOrOptions, maybeOptions) {
54
81
  }
55
82
  const query = typeof finalQuery === "function" ? finalQuery() : finalQuery;
56
83
  if (!query) return;
57
- const queryString = JSON.stringify(query);
84
+ const queryString = String(query.hash);
58
85
  if (queryString === prevQueryString) return;
59
86
  prevQueryString = queryString;
87
+ const myRun = ++runId;
88
+ teardownActive();
60
89
  setIsFetched(false);
61
- initQuery(query);
62
- (0, solid_js.onCleanup)(() => {
63
- unsubscribe()?.();
64
- });
90
+ initQuery(query, myRun);
91
+ });
92
+ (0, solid_js.onCleanup)(() => {
93
+ runId++;
94
+ teardownActive();
95
+ if (options?.deregisterOnCleanup && activeHash) sp00ky.deregisterQuery(activeHash);
65
96
  });
66
97
  const isLoading = () => {
67
98
  return !isFetched() && error() === void 0;
@@ -69,7 +100,8 @@ function useQuery(dbOrQuery, queryOrOptions, maybeOptions) {
69
100
  return {
70
101
  data,
71
102
  error,
72
- isLoading
103
+ isLoading,
104
+ isFetching
73
105
  };
74
106
  }
75
107
 
@@ -432,8 +464,12 @@ var SyncedDb = class {
432
464
  */
433
465
  async delete(tableName, selector) {
434
466
  if (!this.sp00ky) throw new Error("SyncedDb not initialized");
435
- if (typeof selector !== "string") throw new Error("Only string ID selectors are supported currently with core");
436
- await this.sp00ky.delete(tableName, selector);
467
+ const isRecordId = selector instanceof surrealdb.RecordId || selector?.constructor?.name === "RecordId";
468
+ let id;
469
+ if (typeof selector === "string") id = selector;
470
+ else if (isRecordId) id = `${tableName}:${selector.id}`;
471
+ else throw new Error("Only string ID or RecordId selectors are supported currently with core");
472
+ await this.sp00ky.delete(tableName, id);
437
473
  }
438
474
  /**
439
475
  * Query data from the database
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["Sp00kyClient","RecordId"],"sources":["../src/lib/context.ts","../src/lib/use-query.ts","../src/lib/use-crdt-field.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 { 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 = { 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(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 [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 sp00ky = db.getSp00ky();\n\n const initQuery = async (\n query: FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>\n ) => {\n const { hash } = await query.run();\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 setData(() => queryData);\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 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 { 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 } from 'solid-js';\nimport type { SchemaStructure, BucketNames } from '@spooky-sync/query-builder';\nimport { fileToUint8Array } from '@spooky-sync/core';\nimport type { SyncedDb } from '../index';\nimport { useDb } from './context';\n\nexport interface FileUploadResult {\n isUploading: () => boolean;\n error: () => Error | null;\n clearError: () => void;\n upload: (path: string, file: File | Blob) => Promise<void>;\n download: (path: string) => Promise<string | null>;\n remove: (path: string) => Promise<void>;\n exists: (path: string) => Promise<boolean>;\n}\n\nexport function useFileUpload<S extends SchemaStructure>(\n bucketName: BucketNames<S>,\n): FileUploadResult;\nexport function useFileUpload<S extends SchemaStructure>(\n db: SyncedDb<S>,\n bucketName: BucketNames<S>,\n): FileUploadResult;\nexport function useFileUpload<S extends SchemaStructure>(\n dbOrBucketName: SyncedDb<S> | BucketNames<S>,\n maybeBucketName?: BucketNames<S>,\n): FileUploadResult {\n let db: SyncedDb<S>;\n let bucketName: BucketNames<S>;\n\n if (typeof dbOrBucketName === 'string') {\n db = useDb<S>();\n bucketName = dbOrBucketName as BucketNames<S>;\n } else {\n db = dbOrBucketName as SyncedDb<S>;\n // oxlint-disable-next-line no-non-null-assertion\n bucketName = maybeBucketName!;\n }\n\n const [isUploading, setIsUploading] = createSignal(false);\n const [error, setError] = createSignal<Error | null>(null);\n\n const objectUrls: string[] = [];\n onCleanup(() => {\n for (const url of objectUrls) {\n URL.revokeObjectURL(url);\n }\n });\n\n const clearError = () => setError(null);\n\n const validate = (file: File | Blob): void => {\n const config = db.getBucketConfig(bucketName as string);\n if (!config) return;\n\n if (config.maxSize !== null && config.maxSize !== undefined && file.size > config.maxSize) {\n const maxMB = (config.maxSize / (1024 * 1024)).toFixed(1);\n throw new Error(`File exceeds maximum size of ${maxMB} MB.`);\n }\n\n if (config.allowedExtensions && config.allowedExtensions.length > 0) {\n const fileName = (file as File).name;\n if (fileName) {\n const ext = fileName.split('.').pop()?.toLowerCase();\n if (!ext || !config.allowedExtensions.includes(ext)) {\n throw new Error(\n `File type not allowed. Accepted: ${config.allowedExtensions.join(', ')}.`\n );\n }\n }\n }\n };\n\n const upload = async (path: string, file: File | Blob): Promise<void> => {\n setError(null);\n try {\n validate(file);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return;\n }\n\n setIsUploading(true);\n try {\n const bytes = await fileToUint8Array(file);\n await db.bucket(bucketName).put(path, bytes);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n } finally {\n setIsUploading(false);\n }\n };\n\n const download = async (path: string): Promise<string | null> => {\n setError(null);\n try {\n const content = await db.bucket(bucketName).get(path);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n objectUrls.push(objectUrl);\n return objectUrl;\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return null;\n }\n };\n\n const remove = async (path: string): Promise<void> => {\n setError(null);\n try {\n await db.bucket(bucketName).delete(path);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n }\n };\n\n const exists = async (path: string): Promise<boolean> => {\n setError(null);\n try {\n return await db.bucket(bucketName).exists(path);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return false;\n }\n };\n\n return {\n isUploading,\n error,\n clearError,\n upload,\n download,\n remove,\n exists,\n };\n}\n","import { createSignal, createEffect, onCleanup, type Accessor } from 'solid-js';\nimport type { SchemaStructure, BucketNames } from '@spooky-sync/query-builder';\nimport type { SyncedDb } from '../index';\nimport { useDb } from './context';\n\nexport interface UseDownloadFileOptions {\n cache?: boolean;\n}\n\nexport interface UseDownloadFileResult {\n url: Accessor<string | null>;\n isLoading: Accessor<boolean>;\n error: Accessor<Error | null>;\n refetch: () => void;\n}\n\ninterface CacheEntry {\n url: string;\n refCount: number;\n}\n\nconst downloadCache = new Map<string, CacheEntry>();\nconst inflightRequests = new Map<string, Promise<string | null>>();\n\nfunction cacheKey(bucket: string, path: string): string {\n return `${bucket}:${path}`;\n}\n\nfunction releaseEntry(key: string): void {\n const entry = downloadCache.get(key);\n if (!entry) return;\n entry.refCount--;\n if (entry.refCount <= 0) {\n URL.revokeObjectURL(entry.url);\n downloadCache.delete(key);\n }\n}\n\nexport function useDownloadFile<S extends SchemaStructure>(\n bucketName: BucketNames<S>,\n path: Accessor<string | null | undefined>,\n options?: UseDownloadFileOptions,\n): UseDownloadFileResult;\nexport function useDownloadFile<S extends SchemaStructure>(\n db: SyncedDb<S>,\n bucketName: BucketNames<S>,\n path: Accessor<string | null | undefined>,\n options?: UseDownloadFileOptions,\n): UseDownloadFileResult;\nexport function useDownloadFile<S extends SchemaStructure>(\n dbOrBucketName: SyncedDb<S> | BucketNames<S>,\n bucketNameOrPath?: BucketNames<S> | Accessor<string | null | undefined>,\n pathOrOptions?: Accessor<string | null | undefined> | UseDownloadFileOptions,\n maybeOptions?: UseDownloadFileOptions,\n): UseDownloadFileResult {\n let db: SyncedDb<S>;\n let bucketName: BucketNames<S>;\n let path: Accessor<string | null | undefined>;\n let options: UseDownloadFileOptions;\n\n if (typeof dbOrBucketName === 'string') {\n db = useDb<S>();\n bucketName = dbOrBucketName as BucketNames<S>;\n path = bucketNameOrPath as Accessor<string | null | undefined>;\n options = (pathOrOptions as UseDownloadFileOptions) ?? {};\n } else {\n db = dbOrBucketName as SyncedDb<S>;\n bucketName = bucketNameOrPath as BucketNames<S>;\n path = pathOrOptions as Accessor<string | null | undefined>;\n options = maybeOptions ?? {};\n }\n\n const useCache = options.cache !== false;\n\n const [url, setUrl] = createSignal<string | null>(null);\n const [isLoading, setIsLoading] = createSignal(false);\n const [error, setError] = createSignal<Error | null>(null);\n\n let currentKey: string | null = null;\n let privateUrl: string | null = null;\n const [refetchSignal, setRefetchSignal] = createSignal(0);\n const refetchTrigger = () => setRefetchSignal((n) => n + 1);\n\n async function doDownload(key: string, filePath: string): Promise<string | null> {\n if (useCache) {\n // Check cache\n const cached = downloadCache.get(key);\n if (cached) {\n cached.refCount++;\n currentKey = key;\n return cached.url;\n }\n\n // Check inflight\n const inflight = inflightRequests.get(key);\n if (inflight) {\n const result = await inflight;\n if (result) {\n const entry = downloadCache.get(key);\n if (entry) {\n entry.refCount++;\n currentKey = key;\n }\n }\n return result;\n }\n\n // Start new download\n const promise = (async () => {\n const content = await db.bucket(bucketName).get(filePath);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n downloadCache.set(key, { url: objectUrl, refCount: 1 });\n return objectUrl;\n })();\n\n inflightRequests.set(key, promise);\n try {\n const result = await promise;\n currentKey = key;\n return result;\n } finally {\n inflightRequests.delete(key);\n }\n } else {\n // No caching — private URL per instance\n const content = await db.bucket(bucketName).get(filePath);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n privateUrl = objectUrl;\n return objectUrl;\n }\n }\n\n function releaseCurrentEntry() {\n if (useCache && currentKey) {\n releaseEntry(currentKey);\n currentKey = null;\n }\n if (!useCache && privateUrl) {\n URL.revokeObjectURL(privateUrl);\n privateUrl = null;\n }\n }\n\n createEffect(() => {\n const filePath = path();\n // Subscribe to refetch signal so effect re-runs\n refetchSignal();\n\n // Release previous entry\n releaseCurrentEntry();\n\n if (!filePath) {\n setUrl(null);\n setIsLoading(false);\n setError(null);\n return;\n }\n\n const key = cacheKey(bucketName as string, filePath);\n\n // Synchronous cache hit\n if (useCache) {\n const cached = downloadCache.get(key);\n if (cached) {\n cached.refCount++;\n currentKey = key;\n setUrl(cached.url);\n setIsLoading(false);\n setError(null);\n return;\n }\n }\n\n let cancelled = false;\n setIsLoading(true);\n setError(null);\n\n doDownload(key, filePath).then(\n (result) => {\n if (!cancelled) {\n setUrl(result);\n setIsLoading(false);\n }\n return undefined;\n },\n (err) => {\n if (!cancelled) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setIsLoading(false);\n }\n },\n );\n\n onCleanup(() => {\n cancelled = true;\n });\n });\n\n onCleanup(() => {\n releaseCurrentEntry();\n });\n\n const refetch = () => {\n // Evict current entry from cache before re-triggering\n if (useCache && currentKey) {\n const entry = downloadCache.get(currentKey);\n if (entry) {\n URL.revokeObjectURL(entry.url);\n downloadCache.delete(currentKey);\n }\n currentKey = null;\n }\n refetchTrigger();\n };\n\n return { url, isLoading, error, refetch };\n}\n","import type { JSX} from 'solid-js';\nimport { createSignal, onMount, createComponent, createMemo, mergeProps } from 'solid-js';\nimport type { SchemaStructure } from '@spooky/query-builder';\nimport type { SyncedDbConfig } from '../types';\nimport { SyncedDb } from '../index';\nimport { Sp00kyContext } from './context';\n\nexport interface Sp00kyProviderProps<S extends SchemaStructure> {\n config: SyncedDbConfig<S>;\n fallback?: JSX.Element;\n onError?: (error: Error) => void;\n onReady?: (db: SyncedDb<S>) => void;\n children: JSX.Element;\n}\n\nexport function Sp00kyProvider<S extends SchemaStructure>(\n props: Sp00kyProviderProps<S>\n): JSX.Element {\n const merged = mergeProps(\n {\n fallback: undefined as JSX.Element | undefined,\n },\n props\n );\n\n const [db, setDb] = createSignal<SyncedDb<S> | undefined>(undefined);\n\n onMount(async () => {\n try {\n const instance = new SyncedDb<S>(merged.config);\n await instance.init();\n 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} 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 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 { useCrdtField } from './lib/use-crdt-field';\nexport { useFileUpload, type FileUploadResult } from './lib/use-file-upload';\nexport { useDownloadFile, type UseDownloadFileOptions, type UseDownloadFileResult } from './lib/use-download-file';\nexport { Sp00kyProvider, type Sp00kyProviderProps } from './lib/Sp00kyProvider';\nexport { useDb } from './lib/context';\n\n// export { AuthEventTypes } from \"@spooky-sync/core\"; // TODO: Verify if AuthEventTypes exists in core\n\n\n// Re-export query builder types for convenience\nexport type {\n QueryModifier,\n QueryModifierBuilder,\n QueryInfo,\n RelationshipsMetadata,\n RelationshipDefinition,\n InferRelatedModelFromMetadata,\n GetCardinality,\n GetTable,\n TableModel,\n TableNames,\n QueryResult,\n};\n\nexport type RelationshipField<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n Field extends RelationshipFieldsFromSchema<Schema, TableName>,\n> = GetRelationship<Schema, TableName, Field>;\n\nexport type RelatedFieldsTableScoped<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n RelatedFields extends RelationshipFieldsFromSchema<Schema, TableName> =\n RelationshipFieldsFromSchema<Schema, TableName>,\n> = {\n [K in RelatedFields]: {\n to: RelationshipField<Schema, TableName, K>['to'];\n relatedFields: RelatedFieldsMap;\n cardinality: RelationshipField<Schema, TableName, K>['cardinality'];\n };\n};\n\nexport type InferModel<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n RelatedFields extends RelatedFieldsTableScoped<Schema, TableName>,\n> = QueryResult<Schema, TableName, RelatedFields, true>;\n\nexport type WithRelated<Field extends string, RelatedFields extends RelatedFieldsMap = {}> = {\n [K in Field]: Omit<RelatedFieldMapEntry, 'relatedFields'> & {\n relatedFields: RelatedFields;\n };\n};\n\nexport type WithRelatedMany<Field extends string, RelatedFields extends RelatedFieldsMap = {}> = {\n [K in Field]: {\n to: Field;\n relatedFields: RelatedFields;\n cardinality: 'many';\n };\n};\n\n/**\n * SyncedDb - A thin wrapper around sp00ky-ts for Solid.js integration\n * Delegates all logic to the underlying sp00ky-ts instance\n */\nexport class SyncedDb<S extends SchemaStructure> {\n private config: SyncedDbConfig<S>;\n private sp00ky: Sp00kyClient<S> | null = null;\n private _initialized = false;\n\n constructor(config: SyncedDbConfig<S>) {\n this.config = config;\n }\n\n public getSp00ky(): Sp00kyClient<S> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky;\n }\n\n /**\n * Initialize the sp00ky-ts instance\n */\n async init(): Promise<void> {\n if (this._initialized) return;\n this.sp00ky = new Sp00kyClient<S>(this.config);\n await this.sp00ky.init();\n this._initialized = true;\n }\n\n /**\n * Create a new record in the database\n */\n async create(id: string, payload: Record<string, unknown>): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.create(id, payload as Record<string, unknown>);\n }\n\n /**\n * Update an existing record in the database\n */\n async update<TName extends TableNames<S>>(\n tableName: TName,\n recordId: string,\n payload: Partial<TableModel<GetTable<S, TName>>>,\n options?: UpdateOptions\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.update(\n tableName as string,\n recordId,\n payload as Record<string, unknown>,\n options\n );\n }\n\n /**\n * Delete an existing record in the database\n */\n async delete<TName extends TableNames<S>>(\n tableName: TName,\n selector: string | InnerQuery<GetTable<S, TName>, boolean>\n ): Promise<void> {\n if (!this.sp00ky) 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.sp00ky.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, Sp00kyQueryResultPromise, {}, false> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.query(table, {});\n }\n\n /**\n * Run a backend operation\n */\n public async run<\n B extends BackendNames<S>,\n R extends BackendRoutes<S, B>,\n >(\n backend: B,\n path: R,\n payload: RoutePayload<S, B, R>,\n options?: RunOptions,\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.run(backend, path, payload, options);\n }\n\n /**\n * Authenticate with the database\n */\n public async authenticate(token: string): Promise<RecordId<string>> {\n await this.sp00ky?.authenticate(token);\n // Sp00kyClient.authenticate returns whatever remote.authenticate returns (boolean or token usually?)\n // Wait, checked Sp00kyClient: return this.remote.getClient().authenticate(token);\n // SurrealDB authenticate returns void? or token?\n // Assuming void or token.\n return new RecordId('user', 'me'); // Placeholder or actual?\n }\n\n /**\n * Deauthenticate from the database\n * @deprecated Use signOut() instead\n */\n public async deauthenticate(): Promise<void> {\n await this.signOut();\n }\n\n /**\n * Sign out, clear session and local storage\n */\n public async signOut(): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.auth.signOut();\n }\n\n /**\n * Execute a function with direct access to the remote database connection\n */\n public async useRemote<T>(fn: (db: Surreal) => T | Promise<T>): Promise<T> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return await this.sp00ky.useRemote(fn);\n }\n /**\n * Access the remote database service directly\n */\n get remote(): Sp00kyClient<S>['remoteClient'] {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.remoteClient;\n }\n\n /**\n * Access the local database service directly\n */\n get local(): Sp00kyClient<S>['localClient'] {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.localClient;\n }\n\n /**\n * Access the auth service\n */\n get auth(): AuthService<S> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.auth;\n }\n\n get pendingMutationCount(): number {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.pendingMutationCount;\n }\n\n /** Diagnostic — see `Sp00kyClient.liveRetryCount`. */\n get liveRetryCount(): number {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.liveRetryCount;\n }\n\n subscribeToPendingMutations(cb: (count: number) => void): () => void {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.subscribeToPendingMutations(cb);\n }\n\n bucket<B extends BucketNames<S>>(name: B): BucketHandle {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.bucket(name);\n }\n\n getBucketConfig(name: string): BucketDefinitionSchema | undefined {\n return this.config.schema.buckets?.find((b) => b.name === name);\n }\n}\n\nexport * from './types';\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["Sp00kyClient","RecordId"],"sources":["../src/lib/context.ts","../src/lib/use-query.ts","../src/lib/use-crdt-field.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};\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};\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 return {\n data,\n error,\n isLoading,\n isFetching,\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 } from 'solid-js';\nimport type { SchemaStructure, BucketNames } from '@spooky-sync/query-builder';\nimport { fileToUint8Array } from '@spooky-sync/core';\nimport type { SyncedDb } from '../index';\nimport { useDb } from './context';\n\nexport interface FileUploadResult {\n isUploading: () => boolean;\n error: () => Error | null;\n clearError: () => void;\n upload: (path: string, file: File | Blob) => Promise<void>;\n download: (path: string) => Promise<string | null>;\n remove: (path: string) => Promise<void>;\n exists: (path: string) => Promise<boolean>;\n}\n\nexport function useFileUpload<S extends SchemaStructure>(\n bucketName: BucketNames<S>,\n): FileUploadResult;\nexport function useFileUpload<S extends SchemaStructure>(\n db: SyncedDb<S>,\n bucketName: BucketNames<S>,\n): FileUploadResult;\nexport function useFileUpload<S extends SchemaStructure>(\n dbOrBucketName: SyncedDb<S> | BucketNames<S>,\n maybeBucketName?: BucketNames<S>,\n): FileUploadResult {\n let db: SyncedDb<S>;\n let bucketName: BucketNames<S>;\n\n if (typeof dbOrBucketName === 'string') {\n db = useDb<S>();\n bucketName = dbOrBucketName as BucketNames<S>;\n } else {\n db = dbOrBucketName as SyncedDb<S>;\n // oxlint-disable-next-line no-non-null-assertion\n bucketName = maybeBucketName!;\n }\n\n const [isUploading, setIsUploading] = createSignal(false);\n const [error, setError] = createSignal<Error | null>(null);\n\n const objectUrls: string[] = [];\n onCleanup(() => {\n for (const url of objectUrls) {\n URL.revokeObjectURL(url);\n }\n });\n\n const clearError = () => setError(null);\n\n const validate = (file: File | Blob): void => {\n const config = db.getBucketConfig(bucketName as string);\n if (!config) return;\n\n if (config.maxSize !== null && config.maxSize !== undefined && file.size > config.maxSize) {\n const maxMB = (config.maxSize / (1024 * 1024)).toFixed(1);\n throw new Error(`File exceeds maximum size of ${maxMB} MB.`);\n }\n\n if (config.allowedExtensions && config.allowedExtensions.length > 0) {\n const fileName = (file as File).name;\n if (fileName) {\n const ext = fileName.split('.').pop()?.toLowerCase();\n if (!ext || !config.allowedExtensions.includes(ext)) {\n throw new Error(\n `File type not allowed. Accepted: ${config.allowedExtensions.join(', ')}.`\n );\n }\n }\n }\n };\n\n const upload = async (path: string, file: File | Blob): Promise<void> => {\n setError(null);\n try {\n validate(file);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return;\n }\n\n setIsUploading(true);\n try {\n const bytes = await fileToUint8Array(file);\n await db.bucket(bucketName).put(path, bytes);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n } finally {\n setIsUploading(false);\n }\n };\n\n const download = async (path: string): Promise<string | null> => {\n setError(null);\n try {\n const content = await db.bucket(bucketName).get(path);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n objectUrls.push(objectUrl);\n return objectUrl;\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return null;\n }\n };\n\n const remove = async (path: string): Promise<void> => {\n setError(null);\n try {\n await db.bucket(bucketName).delete(path);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n }\n };\n\n const exists = async (path: string): Promise<boolean> => {\n setError(null);\n try {\n return await db.bucket(bucketName).exists(path);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return false;\n }\n };\n\n return {\n isUploading,\n error,\n clearError,\n upload,\n download,\n remove,\n exists,\n };\n}\n","import { createSignal, createEffect, onCleanup, type Accessor } from 'solid-js';\nimport type { SchemaStructure, BucketNames } from '@spooky-sync/query-builder';\nimport type { SyncedDb } from '../index';\nimport { useDb } from './context';\n\nexport interface UseDownloadFileOptions {\n cache?: boolean;\n}\n\nexport interface UseDownloadFileResult {\n url: Accessor<string | null>;\n isLoading: Accessor<boolean>;\n error: Accessor<Error | null>;\n refetch: () => void;\n}\n\ninterface CacheEntry {\n url: string;\n refCount: number;\n}\n\nconst downloadCache = new Map<string, CacheEntry>();\nconst inflightRequests = new Map<string, Promise<string | null>>();\n\nfunction cacheKey(bucket: string, path: string): string {\n return `${bucket}:${path}`;\n}\n\nfunction releaseEntry(key: string): void {\n const entry = downloadCache.get(key);\n if (!entry) return;\n entry.refCount--;\n if (entry.refCount <= 0) {\n URL.revokeObjectURL(entry.url);\n downloadCache.delete(key);\n }\n}\n\nexport function useDownloadFile<S extends SchemaStructure>(\n bucketName: BucketNames<S>,\n path: Accessor<string | null | undefined>,\n options?: UseDownloadFileOptions,\n): UseDownloadFileResult;\nexport function useDownloadFile<S extends SchemaStructure>(\n db: SyncedDb<S>,\n bucketName: BucketNames<S>,\n path: Accessor<string | null | undefined>,\n options?: UseDownloadFileOptions,\n): UseDownloadFileResult;\nexport function useDownloadFile<S extends SchemaStructure>(\n dbOrBucketName: SyncedDb<S> | BucketNames<S>,\n bucketNameOrPath?: BucketNames<S> | Accessor<string | null | undefined>,\n pathOrOptions?: Accessor<string | null | undefined> | UseDownloadFileOptions,\n maybeOptions?: UseDownloadFileOptions,\n): UseDownloadFileResult {\n let db: SyncedDb<S>;\n let bucketName: BucketNames<S>;\n let path: Accessor<string | null | undefined>;\n let options: UseDownloadFileOptions;\n\n if (typeof dbOrBucketName === 'string') {\n db = useDb<S>();\n bucketName = dbOrBucketName as BucketNames<S>;\n path = bucketNameOrPath as Accessor<string | null | undefined>;\n options = (pathOrOptions as UseDownloadFileOptions) ?? {};\n } else {\n db = dbOrBucketName as SyncedDb<S>;\n bucketName = bucketNameOrPath as BucketNames<S>;\n path = pathOrOptions as Accessor<string | null | undefined>;\n options = maybeOptions ?? {};\n }\n\n const useCache = options.cache !== false;\n\n const [url, setUrl] = createSignal<string | null>(null);\n const [isLoading, setIsLoading] = createSignal(false);\n const [error, setError] = createSignal<Error | null>(null);\n\n let currentKey: string | null = null;\n let privateUrl: string | null = null;\n const [refetchSignal, setRefetchSignal] = createSignal(0);\n const refetchTrigger = () => setRefetchSignal((n) => n + 1);\n\n async function doDownload(key: string, filePath: string): Promise<string | null> {\n if (useCache) {\n // Check cache\n const cached = downloadCache.get(key);\n if (cached) {\n cached.refCount++;\n currentKey = key;\n return cached.url;\n }\n\n // Check inflight\n const inflight = inflightRequests.get(key);\n if (inflight) {\n const result = await inflight;\n if (result) {\n const entry = downloadCache.get(key);\n if (entry) {\n entry.refCount++;\n currentKey = key;\n }\n }\n return result;\n }\n\n // Start new download\n const promise = (async () => {\n const content = await db.bucket(bucketName).get(filePath);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n downloadCache.set(key, { url: objectUrl, refCount: 1 });\n return objectUrl;\n })();\n\n inflightRequests.set(key, promise);\n try {\n const result = await promise;\n currentKey = key;\n return result;\n } finally {\n inflightRequests.delete(key);\n }\n } else {\n // No caching — private URL per instance\n const content = await db.bucket(bucketName).get(filePath);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n privateUrl = objectUrl;\n return objectUrl;\n }\n }\n\n function releaseCurrentEntry() {\n if (useCache && currentKey) {\n releaseEntry(currentKey);\n currentKey = null;\n }\n if (!useCache && privateUrl) {\n URL.revokeObjectURL(privateUrl);\n privateUrl = null;\n }\n }\n\n createEffect(() => {\n const filePath = path();\n // Subscribe to refetch signal so effect re-runs\n refetchSignal();\n\n // Release previous entry\n releaseCurrentEntry();\n\n if (!filePath) {\n setUrl(null);\n setIsLoading(false);\n setError(null);\n return;\n }\n\n const key = cacheKey(bucketName as string, filePath);\n\n // Synchronous cache hit\n if (useCache) {\n const cached = downloadCache.get(key);\n if (cached) {\n cached.refCount++;\n currentKey = key;\n setUrl(cached.url);\n setIsLoading(false);\n setError(null);\n return;\n }\n }\n\n let cancelled = false;\n setIsLoading(true);\n setError(null);\n\n doDownload(key, filePath).then(\n (result) => {\n if (!cancelled) {\n setUrl(result);\n setIsLoading(false);\n }\n return undefined;\n },\n (err) => {\n if (!cancelled) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setIsLoading(false);\n }\n },\n );\n\n onCleanup(() => {\n cancelled = true;\n });\n });\n\n onCleanup(() => {\n releaseCurrentEntry();\n });\n\n const refetch = () => {\n // Evict current entry from cache before re-triggering\n if (useCache && currentKey) {\n const entry = downloadCache.get(currentKey);\n if (entry) {\n URL.revokeObjectURL(entry.url);\n downloadCache.delete(currentKey);\n }\n currentKey = null;\n }\n refetchTrigger();\n };\n\n return { url, isLoading, error, refetch };\n}\n","import type { JSX} from 'solid-js';\nimport { createSignal, onMount, createComponent, createMemo, mergeProps } from 'solid-js';\nimport type { SchemaStructure } from '@spooky/query-builder';\nimport type { SyncedDbConfig } from '../types';\nimport { SyncedDb } from '../index';\nimport { Sp00kyContext } from './context';\n\nexport interface Sp00kyProviderProps<S extends SchemaStructure> {\n config: SyncedDbConfig<S>;\n fallback?: JSX.Element;\n onError?: (error: Error) => void;\n onReady?: (db: SyncedDb<S>) => void;\n children: JSX.Element;\n}\n\nexport function Sp00kyProvider<S extends SchemaStructure>(\n props: Sp00kyProviderProps<S>\n): JSX.Element {\n const merged = mergeProps(\n {\n fallback: undefined as JSX.Element | undefined,\n },\n props\n );\n\n const [db, setDb] = createSignal<SyncedDb<S> | undefined>(undefined);\n\n onMount(async () => {\n try {\n const instance = new SyncedDb<S>(merged.config);\n await instance.init();\n 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} 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 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 { useCrdtField } from './lib/use-crdt-field';\nexport { useFileUpload, type FileUploadResult } from './lib/use-file-upload';\nexport { useDownloadFile, type UseDownloadFileOptions, type UseDownloadFileResult } from './lib/use-download-file';\nexport { Sp00kyProvider, type Sp00kyProviderProps } from './lib/Sp00kyProvider';\nexport { useDb } from './lib/context';\n\n// export { AuthEventTypes } from \"@spooky-sync/core\"; // TODO: Verify if AuthEventTypes exists in core\n\n\n// Re-export query builder types for convenience\nexport type {\n QueryModifier,\n QueryModifierBuilder,\n QueryInfo,\n RelationshipsMetadata,\n RelationshipDefinition,\n InferRelatedModelFromMetadata,\n GetCardinality,\n GetTable,\n TableModel,\n TableNames,\n QueryResult,\n};\n\nexport type RelationshipField<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n Field extends RelationshipFieldsFromSchema<Schema, TableName>,\n> = GetRelationship<Schema, TableName, Field>;\n\nexport type RelatedFieldsTableScoped<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n RelatedFields extends RelationshipFieldsFromSchema<Schema, TableName> =\n RelationshipFieldsFromSchema<Schema, TableName>,\n> = {\n [K in RelatedFields]: {\n to: RelationshipField<Schema, TableName, K>['to'];\n relatedFields: RelatedFieldsMap;\n cardinality: RelationshipField<Schema, TableName, K>['cardinality'];\n };\n};\n\nexport type InferModel<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n RelatedFields extends RelatedFieldsTableScoped<Schema, TableName>,\n> = QueryResult<Schema, TableName, RelatedFields, true>;\n\nexport type WithRelated<Field extends string, RelatedFields extends RelatedFieldsMap = {}> = {\n [K in Field]: Omit<RelatedFieldMapEntry, 'relatedFields'> & {\n relatedFields: RelatedFields;\n };\n};\n\nexport type WithRelatedMany<Field extends string, RelatedFields extends RelatedFieldsMap = {}> = {\n [K in Field]: {\n to: Field;\n relatedFields: RelatedFields;\n cardinality: 'many';\n };\n};\n\n/**\n * SyncedDb - A thin wrapper around sp00ky-ts for Solid.js integration\n * Delegates all logic to the underlying sp00ky-ts instance\n */\nexport class SyncedDb<S extends SchemaStructure> {\n private config: SyncedDbConfig<S>;\n private sp00ky: Sp00kyClient<S> | null = null;\n private _initialized = false;\n\n constructor(config: SyncedDbConfig<S>) {\n this.config = config;\n }\n\n public getSp00ky(): Sp00kyClient<S> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky;\n }\n\n /**\n * Initialize the sp00ky-ts instance\n */\n async init(): Promise<void> {\n if (this._initialized) return;\n this.sp00ky = new Sp00kyClient<S>(this.config);\n await this.sp00ky.init();\n this._initialized = true;\n }\n\n /**\n * Create a new record in the database\n */\n async create(id: string, payload: Record<string, unknown>): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.create(id, payload as Record<string, unknown>);\n }\n\n /**\n * Update an existing record in the database\n */\n async update<TName extends TableNames<S>>(\n tableName: TName,\n recordId: string,\n payload: Partial<TableModel<GetTable<S, TName>>>,\n options?: UpdateOptions\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.update(\n tableName as string,\n recordId,\n payload as Record<string, unknown>,\n options\n );\n }\n\n /**\n * Delete an existing record in the database\n */\n async delete<TName extends TableNames<S>>(\n tableName: TName,\n selector: string | RecordId | InnerQuery<GetTable<S, TName>, boolean>\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n // Accept a `\"table:id\"` string OR a RecordId — live-query rows carry their\n // `id` as a RecordId, so callers can pass `db.delete('game', row.id)`\n // directly. Build the canonical string from the raw id part (not\n // `RecordId.toString()`, which escapes special chars) so it round-trips\n // through the engine's `parseRecordIdString`. InnerQuery selectors are not\n // supported yet. (cross-package RecordId instances → match by constructor name.)\n const isRecordId =\n selector instanceof RecordId || (selector as any)?.constructor?.name === 'RecordId';\n let id: string;\n if (typeof selector === 'string') {\n id = selector;\n } else if (isRecordId) {\n id = `${tableName as string}:${(selector as RecordId).id}`;\n } else {\n throw new Error('Only string ID or RecordId selectors are supported currently with core');\n }\n await this.sp00ky.delete(tableName as string, id);\n }\n\n /**\n * Query data from the database\n */\n public query<TName extends TableNames<S>>(\n table: TName\n ): QueryBuilder<S, TName, Sp00kyQueryResultPromise, {}, false> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.query(table, {});\n }\n\n /**\n * Run a backend operation\n */\n public async run<\n B extends BackendNames<S>,\n R extends BackendRoutes<S, B>,\n >(\n backend: B,\n path: R,\n payload: RoutePayload<S, B, R>,\n options?: RunOptions,\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.run(backend, path, payload, options);\n }\n\n /**\n * Authenticate with the database\n */\n public async authenticate(token: string): Promise<RecordId<string>> {\n await this.sp00ky?.authenticate(token);\n // Sp00kyClient.authenticate returns whatever remote.authenticate returns (boolean or token usually?)\n // Wait, checked Sp00kyClient: return this.remote.getClient().authenticate(token);\n // SurrealDB authenticate returns void? or token?\n // Assuming void or token.\n return new RecordId('user', 'me'); // Placeholder or actual?\n }\n\n /**\n * Deauthenticate from the database\n * @deprecated Use signOut() instead\n */\n public async deauthenticate(): Promise<void> {\n await this.signOut();\n }\n\n /**\n * Sign out, clear session and local storage\n */\n public async signOut(): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.auth.signOut();\n }\n\n /**\n * Execute a function with direct access to the remote database connection\n */\n public async useRemote<T>(fn: (db: Surreal) => T | Promise<T>): Promise<T> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return await this.sp00ky.useRemote(fn);\n }\n /**\n * Access the remote database service directly\n */\n get remote(): Sp00kyClient<S>['remoteClient'] {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.remoteClient;\n }\n\n /**\n * Access the local database service directly\n */\n get local(): Sp00kyClient<S>['localClient'] {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.localClient;\n }\n\n /**\n * Access the auth service\n */\n get auth(): AuthService<S> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.auth;\n }\n\n get pendingMutationCount(): number {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.pendingMutationCount;\n }\n\n /** Diagnostic — see `Sp00kyClient.liveRetryCount`. */\n get liveRetryCount(): number {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.liveRetryCount;\n }\n\n subscribeToPendingMutations(cb: (count: number) => void): () => void {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.subscribeToPendingMutations(cb);\n }\n\n 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
@@ -45,6 +45,14 @@ type QueryArg<S extends SchemaStructure$1, TableName extends TableNames$1<S>, T
45
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,7 @@ 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;
55
64
  };
56
65
  declare function useQuery<S extends SchemaStructure$1, TableName extends TableNames$1<S>, T extends {
57
66
  columns: Record<string, ColumnSchema>;
@@ -59,6 +68,7 @@ declare function useQuery<S extends SchemaStructure$1, TableName extends TableNa
59
68
  data: () => TData | undefined;
60
69
  error: () => Error | undefined;
61
70
  isLoading: () => boolean;
71
+ isFetching: () => boolean;
62
72
  };
63
73
  //#endregion
64
74
  //#region src/lib/use-crdt-field.d.ts
@@ -150,7 +160,7 @@ declare class SyncedDb<S extends SchemaStructure$1> {
150
160
  /**
151
161
  * Delete an existing record in the database
152
162
  */
153
- delete<TName extends TableNames<S>>(tableName: TName, selector: string | InnerQuery<GetTable<S, TName>, boolean>): Promise<void>;
163
+ delete<TName extends TableNames<S>>(tableName: TName, selector: string | RecordId | InnerQuery<GetTable<S, TName>, boolean>): Promise<void>;
154
164
  /**
155
165
  * Query data from the database
156
166
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../../src/lib/models.ts","../../../src/types/index.ts","../../../src/lib/use-query.ts","../../../src/lib/use-crdt-field.ts","../../../src/lib/use-file-upload.ts","../../../src/lib/use-download-file.ts","../../../src/lib/Sp00kyProvider.ts","../../../src/lib/context.ts","../../../src/index.ts"],"sourcesContent":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../../src/lib/models.ts","../../../src/types/index.ts","../../../src/lib/use-query.ts","../../../src/lib/use-crdt-field.ts","../../../src/lib/use-file-upload.ts","../../../src/lib/use-download-file.ts","../../../src/lib/Sp00kyProvider.ts","../../../src/lib/context.ts","../../../src/index.ts"],"sourcesContent":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -45,6 +45,14 @@ type QueryArg<S extends SchemaStructure$1, TableName extends TableNames$1<S>, T
45
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,7 @@ 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;
55
64
  };
56
65
  declare function useQuery<S extends SchemaStructure$1, TableName extends TableNames$1<S>, T extends {
57
66
  columns: Record<string, ColumnSchema>;
@@ -59,6 +68,7 @@ declare function useQuery<S extends SchemaStructure$1, TableName extends TableNa
59
68
  data: () => TData | undefined;
60
69
  error: () => Error | undefined;
61
70
  isLoading: () => boolean;
71
+ isFetching: () => boolean;
62
72
  };
63
73
  //#endregion
64
74
  //#region src/lib/use-crdt-field.d.ts
@@ -150,7 +160,7 @@ declare class SyncedDb<S extends SchemaStructure$1> {
150
160
  /**
151
161
  * Delete an existing record in the database
152
162
  */
153
- delete<TName extends TableNames<S>>(tableName: TName, selector: string | InnerQuery<GetTable<S, TName>, boolean>): Promise<void>;
163
+ delete<TName extends TableNames<S>>(tableName: TName, selector: string | RecordId | InnerQuery<GetTable<S, TName>, boolean>): Promise<void>;
154
164
  /**
155
165
  * Query data from the database
156
166
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/lib/models.ts","../../../src/types/index.ts","../../../src/lib/use-query.ts","../../../src/lib/use-crdt-field.ts","../../../src/lib/use-file-upload.ts","../../../src/lib/use-download-file.ts","../../../src/lib/Sp00kyProvider.ts","../../../src/lib/context.ts","../../../src/index.ts"],"sourcesContent":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/lib/models.ts","../../../src/types/index.ts","../../../src/lib/use-query.ts","../../../src/lib/use-crdt-field.ts","../../../src/lib/use-file-upload.ts","../../../src/lib/use-download-file.ts","../../../src/lib/Sp00kyProvider.ts","../../../src/lib/context.ts","../../../src/index.ts"],"sourcesContent":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Sp00kyClient, fileToUint8Array } from "@spooky-sync/core";
2
2
  import { RecordId, Uuid } from "surrealdb";
3
3
  import { createComponent, createContext, createEffect, createMemo, createSignal, mergeProps, onCleanup, onMount, useContext } from "solid-js";
4
+ import { createStore, reconcile } from "solid-js/store";
4
5
 
5
6
  //#region src/lib/context.ts
6
7
  const Sp00kyContext = createContext();
@@ -27,24 +28,50 @@ function useQuery(dbOrQuery, queryOrOptions, maybeOptions) {
27
28
  finalQuery = dbOrQuery;
28
29
  options = queryOrOptions;
29
30
  }
30
- const [data, setData] = createSignal(void 0);
31
31
  const [error, setError] = createSignal(void 0);
32
32
  const [isFetched, setIsFetched] = createSignal(false);
33
- const [unsubscribe, setUnsubscribe] = createSignal(void 0);
33
+ const [isFetching, setIsFetching] = createSignal(false);
34
+ const [state, setState] = createStore({ value: void 0 });
35
+ const [version, setVersion] = createSignal(0);
36
+ const data = () => {
37
+ version();
38
+ return state.value;
39
+ };
34
40
  let prevQueryString;
41
+ let runId = 0;
42
+ let activeUnsub;
43
+ let activeHash;
44
+ const teardownActive = () => {
45
+ activeUnsub?.();
46
+ activeUnsub = void 0;
47
+ };
35
48
  const sp00ky = db.getSp00ky();
36
- const initQuery = async (query) => {
49
+ const initQuery = async (query, myRun) => {
37
50
  const { hash } = await query.run();
51
+ if (myRun !== runId) return;
52
+ activeHash = hash;
38
53
  setError(void 0);
39
54
  let isFirstCall = true;
40
55
  const unsub = await sp00ky.subscribe(hash, (e) => {
41
56
  const queryData = query.isOne ? e[0] : e;
42
- setData(() => queryData);
57
+ const reconcileStart = performance.now();
58
+ setState("value", reconcile(queryData, { key: "id" }));
59
+ setVersion((v) => v + 1);
60
+ sp00ky.reportFrontendTiming(hash, performance.now() - reconcileStart);
43
61
  const hasData = query.isOne ? queryData !== null && queryData !== void 0 : e.length > 0;
44
62
  if (!isFirstCall || hasData) setIsFetched(true);
45
63
  isFirstCall = false;
46
64
  }, { immediate: true });
47
- setUnsubscribe(() => unsub);
65
+ const unsubStatus = sp00ky.subscribeQueryStatus(hash, (status) => setIsFetching(status === "fetching"), { immediate: true });
66
+ const teardown = () => {
67
+ unsub();
68
+ unsubStatus();
69
+ };
70
+ if (myRun !== runId) {
71
+ teardown();
72
+ return;
73
+ }
74
+ activeUnsub = teardown;
48
75
  };
49
76
  createEffect(() => {
50
77
  if (!(options?.enabled?.() ?? true)) {
@@ -53,14 +80,18 @@ function useQuery(dbOrQuery, queryOrOptions, maybeOptions) {
53
80
  }
54
81
  const query = typeof finalQuery === "function" ? finalQuery() : finalQuery;
55
82
  if (!query) return;
56
- const queryString = JSON.stringify(query);
83
+ const queryString = String(query.hash);
57
84
  if (queryString === prevQueryString) return;
58
85
  prevQueryString = queryString;
86
+ const myRun = ++runId;
87
+ teardownActive();
59
88
  setIsFetched(false);
60
- initQuery(query);
61
- onCleanup(() => {
62
- unsubscribe()?.();
63
- });
89
+ initQuery(query, myRun);
90
+ });
91
+ onCleanup(() => {
92
+ runId++;
93
+ teardownActive();
94
+ if (options?.deregisterOnCleanup && activeHash) sp00ky.deregisterQuery(activeHash);
64
95
  });
65
96
  const isLoading = () => {
66
97
  return !isFetched() && error() === void 0;
@@ -68,7 +99,8 @@ function useQuery(dbOrQuery, queryOrOptions, maybeOptions) {
68
99
  return {
69
100
  data,
70
101
  error,
71
- isLoading
102
+ isLoading,
103
+ isFetching
72
104
  };
73
105
  }
74
106
 
@@ -431,8 +463,12 @@ var SyncedDb = class {
431
463
  */
432
464
  async delete(tableName, selector) {
433
465
  if (!this.sp00ky) throw new Error("SyncedDb not initialized");
434
- if (typeof selector !== "string") throw new Error("Only string ID selectors are supported currently with core");
435
- await this.sp00ky.delete(tableName, selector);
466
+ const isRecordId = selector instanceof RecordId || selector?.constructor?.name === "RecordId";
467
+ let id;
468
+ if (typeof selector === "string") id = selector;
469
+ else if (isRecordId) id = `${tableName}:${selector.id}`;
470
+ else throw new Error("Only string ID or RecordId selectors are supported currently with core");
471
+ await this.sp00ky.delete(tableName, id);
436
472
  }
437
473
  /**
438
474
  * Query data from the database
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/lib/context.ts","../src/lib/use-query.ts","../src/lib/use-crdt-field.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 { 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 = { 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(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 [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 sp00ky = db.getSp00ky();\n\n const initQuery = async (\n query: FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>\n ) => {\n const { hash } = await query.run();\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 setData(() => queryData);\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 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 { 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 } from 'solid-js';\nimport type { SchemaStructure, BucketNames } from '@spooky-sync/query-builder';\nimport { fileToUint8Array } from '@spooky-sync/core';\nimport type { SyncedDb } from '../index';\nimport { useDb } from './context';\n\nexport interface FileUploadResult {\n isUploading: () => boolean;\n error: () => Error | null;\n clearError: () => void;\n upload: (path: string, file: File | Blob) => Promise<void>;\n download: (path: string) => Promise<string | null>;\n remove: (path: string) => Promise<void>;\n exists: (path: string) => Promise<boolean>;\n}\n\nexport function useFileUpload<S extends SchemaStructure>(\n bucketName: BucketNames<S>,\n): FileUploadResult;\nexport function useFileUpload<S extends SchemaStructure>(\n db: SyncedDb<S>,\n bucketName: BucketNames<S>,\n): FileUploadResult;\nexport function useFileUpload<S extends SchemaStructure>(\n dbOrBucketName: SyncedDb<S> | BucketNames<S>,\n maybeBucketName?: BucketNames<S>,\n): FileUploadResult {\n let db: SyncedDb<S>;\n let bucketName: BucketNames<S>;\n\n if (typeof dbOrBucketName === 'string') {\n db = useDb<S>();\n bucketName = dbOrBucketName as BucketNames<S>;\n } else {\n db = dbOrBucketName as SyncedDb<S>;\n // oxlint-disable-next-line no-non-null-assertion\n bucketName = maybeBucketName!;\n }\n\n const [isUploading, setIsUploading] = createSignal(false);\n const [error, setError] = createSignal<Error | null>(null);\n\n const objectUrls: string[] = [];\n onCleanup(() => {\n for (const url of objectUrls) {\n URL.revokeObjectURL(url);\n }\n });\n\n const clearError = () => setError(null);\n\n const validate = (file: File | Blob): void => {\n const config = db.getBucketConfig(bucketName as string);\n if (!config) return;\n\n if (config.maxSize !== null && config.maxSize !== undefined && file.size > config.maxSize) {\n const maxMB = (config.maxSize / (1024 * 1024)).toFixed(1);\n throw new Error(`File exceeds maximum size of ${maxMB} MB.`);\n }\n\n if (config.allowedExtensions && config.allowedExtensions.length > 0) {\n const fileName = (file as File).name;\n if (fileName) {\n const ext = fileName.split('.').pop()?.toLowerCase();\n if (!ext || !config.allowedExtensions.includes(ext)) {\n throw new Error(\n `File type not allowed. Accepted: ${config.allowedExtensions.join(', ')}.`\n );\n }\n }\n }\n };\n\n const upload = async (path: string, file: File | Blob): Promise<void> => {\n setError(null);\n try {\n validate(file);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return;\n }\n\n setIsUploading(true);\n try {\n const bytes = await fileToUint8Array(file);\n await db.bucket(bucketName).put(path, bytes);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n } finally {\n setIsUploading(false);\n }\n };\n\n const download = async (path: string): Promise<string | null> => {\n setError(null);\n try {\n const content = await db.bucket(bucketName).get(path);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n objectUrls.push(objectUrl);\n return objectUrl;\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return null;\n }\n };\n\n const remove = async (path: string): Promise<void> => {\n setError(null);\n try {\n await db.bucket(bucketName).delete(path);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n }\n };\n\n const exists = async (path: string): Promise<boolean> => {\n setError(null);\n try {\n return await db.bucket(bucketName).exists(path);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return false;\n }\n };\n\n return {\n isUploading,\n error,\n clearError,\n upload,\n download,\n remove,\n exists,\n };\n}\n","import { createSignal, createEffect, onCleanup, type Accessor } from 'solid-js';\nimport type { SchemaStructure, BucketNames } from '@spooky-sync/query-builder';\nimport type { SyncedDb } from '../index';\nimport { useDb } from './context';\n\nexport interface UseDownloadFileOptions {\n cache?: boolean;\n}\n\nexport interface UseDownloadFileResult {\n url: Accessor<string | null>;\n isLoading: Accessor<boolean>;\n error: Accessor<Error | null>;\n refetch: () => void;\n}\n\ninterface CacheEntry {\n url: string;\n refCount: number;\n}\n\nconst downloadCache = new Map<string, CacheEntry>();\nconst inflightRequests = new Map<string, Promise<string | null>>();\n\nfunction cacheKey(bucket: string, path: string): string {\n return `${bucket}:${path}`;\n}\n\nfunction releaseEntry(key: string): void {\n const entry = downloadCache.get(key);\n if (!entry) return;\n entry.refCount--;\n if (entry.refCount <= 0) {\n URL.revokeObjectURL(entry.url);\n downloadCache.delete(key);\n }\n}\n\nexport function useDownloadFile<S extends SchemaStructure>(\n bucketName: BucketNames<S>,\n path: Accessor<string | null | undefined>,\n options?: UseDownloadFileOptions,\n): UseDownloadFileResult;\nexport function useDownloadFile<S extends SchemaStructure>(\n db: SyncedDb<S>,\n bucketName: BucketNames<S>,\n path: Accessor<string | null | undefined>,\n options?: UseDownloadFileOptions,\n): UseDownloadFileResult;\nexport function useDownloadFile<S extends SchemaStructure>(\n dbOrBucketName: SyncedDb<S> | BucketNames<S>,\n bucketNameOrPath?: BucketNames<S> | Accessor<string | null | undefined>,\n pathOrOptions?: Accessor<string | null | undefined> | UseDownloadFileOptions,\n maybeOptions?: UseDownloadFileOptions,\n): UseDownloadFileResult {\n let db: SyncedDb<S>;\n let bucketName: BucketNames<S>;\n let path: Accessor<string | null | undefined>;\n let options: UseDownloadFileOptions;\n\n if (typeof dbOrBucketName === 'string') {\n db = useDb<S>();\n bucketName = dbOrBucketName as BucketNames<S>;\n path = bucketNameOrPath as Accessor<string | null | undefined>;\n options = (pathOrOptions as UseDownloadFileOptions) ?? {};\n } else {\n db = dbOrBucketName as SyncedDb<S>;\n bucketName = bucketNameOrPath as BucketNames<S>;\n path = pathOrOptions as Accessor<string | null | undefined>;\n options = maybeOptions ?? {};\n }\n\n const useCache = options.cache !== false;\n\n const [url, setUrl] = createSignal<string | null>(null);\n const [isLoading, setIsLoading] = createSignal(false);\n const [error, setError] = createSignal<Error | null>(null);\n\n let currentKey: string | null = null;\n let privateUrl: string | null = null;\n const [refetchSignal, setRefetchSignal] = createSignal(0);\n const refetchTrigger = () => setRefetchSignal((n) => n + 1);\n\n async function doDownload(key: string, filePath: string): Promise<string | null> {\n if (useCache) {\n // Check cache\n const cached = downloadCache.get(key);\n if (cached) {\n cached.refCount++;\n currentKey = key;\n return cached.url;\n }\n\n // Check inflight\n const inflight = inflightRequests.get(key);\n if (inflight) {\n const result = await inflight;\n if (result) {\n const entry = downloadCache.get(key);\n if (entry) {\n entry.refCount++;\n currentKey = key;\n }\n }\n return result;\n }\n\n // Start new download\n const promise = (async () => {\n const content = await db.bucket(bucketName).get(filePath);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n downloadCache.set(key, { url: objectUrl, refCount: 1 });\n return objectUrl;\n })();\n\n inflightRequests.set(key, promise);\n try {\n const result = await promise;\n currentKey = key;\n return result;\n } finally {\n inflightRequests.delete(key);\n }\n } else {\n // No caching — private URL per instance\n const content = await db.bucket(bucketName).get(filePath);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n privateUrl = objectUrl;\n return objectUrl;\n }\n }\n\n function releaseCurrentEntry() {\n if (useCache && currentKey) {\n releaseEntry(currentKey);\n currentKey = null;\n }\n if (!useCache && privateUrl) {\n URL.revokeObjectURL(privateUrl);\n privateUrl = null;\n }\n }\n\n createEffect(() => {\n const filePath = path();\n // Subscribe to refetch signal so effect re-runs\n refetchSignal();\n\n // Release previous entry\n releaseCurrentEntry();\n\n if (!filePath) {\n setUrl(null);\n setIsLoading(false);\n setError(null);\n return;\n }\n\n const key = cacheKey(bucketName as string, filePath);\n\n // Synchronous cache hit\n if (useCache) {\n const cached = downloadCache.get(key);\n if (cached) {\n cached.refCount++;\n currentKey = key;\n setUrl(cached.url);\n setIsLoading(false);\n setError(null);\n return;\n }\n }\n\n let cancelled = false;\n setIsLoading(true);\n setError(null);\n\n doDownload(key, filePath).then(\n (result) => {\n if (!cancelled) {\n setUrl(result);\n setIsLoading(false);\n }\n return undefined;\n },\n (err) => {\n if (!cancelled) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setIsLoading(false);\n }\n },\n );\n\n onCleanup(() => {\n cancelled = true;\n });\n });\n\n onCleanup(() => {\n releaseCurrentEntry();\n });\n\n const refetch = () => {\n // Evict current entry from cache before re-triggering\n if (useCache && currentKey) {\n const entry = downloadCache.get(currentKey);\n if (entry) {\n URL.revokeObjectURL(entry.url);\n downloadCache.delete(currentKey);\n }\n currentKey = null;\n }\n refetchTrigger();\n };\n\n return { url, isLoading, error, refetch };\n}\n","import type { JSX} from 'solid-js';\nimport { createSignal, onMount, createComponent, createMemo, mergeProps } from 'solid-js';\nimport type { SchemaStructure } from '@spooky/query-builder';\nimport type { SyncedDbConfig } from '../types';\nimport { SyncedDb } from '../index';\nimport { Sp00kyContext } from './context';\n\nexport interface Sp00kyProviderProps<S extends SchemaStructure> {\n config: SyncedDbConfig<S>;\n fallback?: JSX.Element;\n onError?: (error: Error) => void;\n onReady?: (db: SyncedDb<S>) => void;\n children: JSX.Element;\n}\n\nexport function Sp00kyProvider<S extends SchemaStructure>(\n props: Sp00kyProviderProps<S>\n): JSX.Element {\n const merged = mergeProps(\n {\n fallback: undefined as JSX.Element | undefined,\n },\n props\n );\n\n const [db, setDb] = createSignal<SyncedDb<S> | undefined>(undefined);\n\n onMount(async () => {\n try {\n const instance = new SyncedDb<S>(merged.config);\n await instance.init();\n 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} 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 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 { useCrdtField } from './lib/use-crdt-field';\nexport { useFileUpload, type FileUploadResult } from './lib/use-file-upload';\nexport { useDownloadFile, type UseDownloadFileOptions, type UseDownloadFileResult } from './lib/use-download-file';\nexport { Sp00kyProvider, type Sp00kyProviderProps } from './lib/Sp00kyProvider';\nexport { useDb } from './lib/context';\n\n// export { AuthEventTypes } from \"@spooky-sync/core\"; // TODO: Verify if AuthEventTypes exists in core\n\n\n// Re-export query builder types for convenience\nexport type {\n QueryModifier,\n QueryModifierBuilder,\n QueryInfo,\n RelationshipsMetadata,\n RelationshipDefinition,\n InferRelatedModelFromMetadata,\n GetCardinality,\n GetTable,\n TableModel,\n TableNames,\n QueryResult,\n};\n\nexport type RelationshipField<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n Field extends RelationshipFieldsFromSchema<Schema, TableName>,\n> = GetRelationship<Schema, TableName, Field>;\n\nexport type RelatedFieldsTableScoped<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n RelatedFields extends RelationshipFieldsFromSchema<Schema, TableName> =\n RelationshipFieldsFromSchema<Schema, TableName>,\n> = {\n [K in RelatedFields]: {\n to: RelationshipField<Schema, TableName, K>['to'];\n relatedFields: RelatedFieldsMap;\n cardinality: RelationshipField<Schema, TableName, K>['cardinality'];\n };\n};\n\nexport type InferModel<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n RelatedFields extends RelatedFieldsTableScoped<Schema, TableName>,\n> = QueryResult<Schema, TableName, RelatedFields, true>;\n\nexport type WithRelated<Field extends string, RelatedFields extends RelatedFieldsMap = {}> = {\n [K in Field]: Omit<RelatedFieldMapEntry, 'relatedFields'> & {\n relatedFields: RelatedFields;\n };\n};\n\nexport type WithRelatedMany<Field extends string, RelatedFields extends RelatedFieldsMap = {}> = {\n [K in Field]: {\n to: Field;\n relatedFields: RelatedFields;\n cardinality: 'many';\n };\n};\n\n/**\n * SyncedDb - A thin wrapper around sp00ky-ts for Solid.js integration\n * Delegates all logic to the underlying sp00ky-ts instance\n */\nexport class SyncedDb<S extends SchemaStructure> {\n private config: SyncedDbConfig<S>;\n private sp00ky: Sp00kyClient<S> | null = null;\n private _initialized = false;\n\n constructor(config: SyncedDbConfig<S>) {\n this.config = config;\n }\n\n public getSp00ky(): Sp00kyClient<S> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky;\n }\n\n /**\n * Initialize the sp00ky-ts instance\n */\n async init(): Promise<void> {\n if (this._initialized) return;\n this.sp00ky = new Sp00kyClient<S>(this.config);\n await this.sp00ky.init();\n this._initialized = true;\n }\n\n /**\n * Create a new record in the database\n */\n async create(id: string, payload: Record<string, unknown>): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.create(id, payload as Record<string, unknown>);\n }\n\n /**\n * Update an existing record in the database\n */\n async update<TName extends TableNames<S>>(\n tableName: TName,\n recordId: string,\n payload: Partial<TableModel<GetTable<S, TName>>>,\n options?: UpdateOptions\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.update(\n tableName as string,\n recordId,\n payload as Record<string, unknown>,\n options\n );\n }\n\n /**\n * Delete an existing record in the database\n */\n async delete<TName extends TableNames<S>>(\n tableName: TName,\n selector: string | InnerQuery<GetTable<S, TName>, boolean>\n ): Promise<void> {\n if (!this.sp00ky) 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.sp00ky.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, Sp00kyQueryResultPromise, {}, false> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.query(table, {});\n }\n\n /**\n * Run a backend operation\n */\n public async run<\n B extends BackendNames<S>,\n R extends BackendRoutes<S, B>,\n >(\n backend: B,\n path: R,\n payload: RoutePayload<S, B, R>,\n options?: RunOptions,\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.run(backend, path, payload, options);\n }\n\n /**\n * Authenticate with the database\n */\n public async authenticate(token: string): Promise<RecordId<string>> {\n await this.sp00ky?.authenticate(token);\n // Sp00kyClient.authenticate returns whatever remote.authenticate returns (boolean or token usually?)\n // Wait, checked Sp00kyClient: return this.remote.getClient().authenticate(token);\n // SurrealDB authenticate returns void? or token?\n // Assuming void or token.\n return new RecordId('user', 'me'); // Placeholder or actual?\n }\n\n /**\n * Deauthenticate from the database\n * @deprecated Use signOut() instead\n */\n public async deauthenticate(): Promise<void> {\n await this.signOut();\n }\n\n /**\n * Sign out, clear session and local storage\n */\n public async signOut(): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.auth.signOut();\n }\n\n /**\n * Execute a function with direct access to the remote database connection\n */\n public async useRemote<T>(fn: (db: Surreal) => T | Promise<T>): Promise<T> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return await this.sp00ky.useRemote(fn);\n }\n /**\n * Access the remote database service directly\n */\n get remote(): Sp00kyClient<S>['remoteClient'] {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.remoteClient;\n }\n\n /**\n * Access the local database service directly\n */\n get local(): Sp00kyClient<S>['localClient'] {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.localClient;\n }\n\n /**\n * Access the auth service\n */\n get auth(): AuthService<S> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.auth;\n }\n\n get pendingMutationCount(): number {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.pendingMutationCount;\n }\n\n /** Diagnostic — see `Sp00kyClient.liveRetryCount`. */\n get liveRetryCount(): number {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.liveRetryCount;\n }\n\n subscribeToPendingMutations(cb: (count: number) => void): () => void {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.subscribeToPendingMutations(cb);\n }\n\n bucket<B extends BucketNames<S>>(name: B): BucketHandle {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.bucket(name);\n }\n\n getBucketConfig(name: string): BucketDefinitionSchema | undefined {\n return this.config.schema.buckets?.find((b) => b.name === name);\n }\n}\n\nexport * from './types';\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/lib/context.ts","../src/lib/use-query.ts","../src/lib/use-crdt-field.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};\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};\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 return {\n data,\n error,\n isLoading,\n isFetching,\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 } from 'solid-js';\nimport type { SchemaStructure, BucketNames } from '@spooky-sync/query-builder';\nimport { fileToUint8Array } from '@spooky-sync/core';\nimport type { SyncedDb } from '../index';\nimport { useDb } from './context';\n\nexport interface FileUploadResult {\n isUploading: () => boolean;\n error: () => Error | null;\n clearError: () => void;\n upload: (path: string, file: File | Blob) => Promise<void>;\n download: (path: string) => Promise<string | null>;\n remove: (path: string) => Promise<void>;\n exists: (path: string) => Promise<boolean>;\n}\n\nexport function useFileUpload<S extends SchemaStructure>(\n bucketName: BucketNames<S>,\n): FileUploadResult;\nexport function useFileUpload<S extends SchemaStructure>(\n db: SyncedDb<S>,\n bucketName: BucketNames<S>,\n): FileUploadResult;\nexport function useFileUpload<S extends SchemaStructure>(\n dbOrBucketName: SyncedDb<S> | BucketNames<S>,\n maybeBucketName?: BucketNames<S>,\n): FileUploadResult {\n let db: SyncedDb<S>;\n let bucketName: BucketNames<S>;\n\n if (typeof dbOrBucketName === 'string') {\n db = useDb<S>();\n bucketName = dbOrBucketName as BucketNames<S>;\n } else {\n db = dbOrBucketName as SyncedDb<S>;\n // oxlint-disable-next-line no-non-null-assertion\n bucketName = maybeBucketName!;\n }\n\n const [isUploading, setIsUploading] = createSignal(false);\n const [error, setError] = createSignal<Error | null>(null);\n\n const objectUrls: string[] = [];\n onCleanup(() => {\n for (const url of objectUrls) {\n URL.revokeObjectURL(url);\n }\n });\n\n const clearError = () => setError(null);\n\n const validate = (file: File | Blob): void => {\n const config = db.getBucketConfig(bucketName as string);\n if (!config) return;\n\n if (config.maxSize !== null && config.maxSize !== undefined && file.size > config.maxSize) {\n const maxMB = (config.maxSize / (1024 * 1024)).toFixed(1);\n throw new Error(`File exceeds maximum size of ${maxMB} MB.`);\n }\n\n if (config.allowedExtensions && config.allowedExtensions.length > 0) {\n const fileName = (file as File).name;\n if (fileName) {\n const ext = fileName.split('.').pop()?.toLowerCase();\n if (!ext || !config.allowedExtensions.includes(ext)) {\n throw new Error(\n `File type not allowed. Accepted: ${config.allowedExtensions.join(', ')}.`\n );\n }\n }\n }\n };\n\n const upload = async (path: string, file: File | Blob): Promise<void> => {\n setError(null);\n try {\n validate(file);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return;\n }\n\n setIsUploading(true);\n try {\n const bytes = await fileToUint8Array(file);\n await db.bucket(bucketName).put(path, bytes);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n } finally {\n setIsUploading(false);\n }\n };\n\n const download = async (path: string): Promise<string | null> => {\n setError(null);\n try {\n const content = await db.bucket(bucketName).get(path);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n objectUrls.push(objectUrl);\n return objectUrl;\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return null;\n }\n };\n\n const remove = async (path: string): Promise<void> => {\n setError(null);\n try {\n await db.bucket(bucketName).delete(path);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n }\n };\n\n const exists = async (path: string): Promise<boolean> => {\n setError(null);\n try {\n return await db.bucket(bucketName).exists(path);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return false;\n }\n };\n\n return {\n isUploading,\n error,\n clearError,\n upload,\n download,\n remove,\n exists,\n };\n}\n","import { createSignal, createEffect, onCleanup, type Accessor } from 'solid-js';\nimport type { SchemaStructure, BucketNames } from '@spooky-sync/query-builder';\nimport type { SyncedDb } from '../index';\nimport { useDb } from './context';\n\nexport interface UseDownloadFileOptions {\n cache?: boolean;\n}\n\nexport interface UseDownloadFileResult {\n url: Accessor<string | null>;\n isLoading: Accessor<boolean>;\n error: Accessor<Error | null>;\n refetch: () => void;\n}\n\ninterface CacheEntry {\n url: string;\n refCount: number;\n}\n\nconst downloadCache = new Map<string, CacheEntry>();\nconst inflightRequests = new Map<string, Promise<string | null>>();\n\nfunction cacheKey(bucket: string, path: string): string {\n return `${bucket}:${path}`;\n}\n\nfunction releaseEntry(key: string): void {\n const entry = downloadCache.get(key);\n if (!entry) return;\n entry.refCount--;\n if (entry.refCount <= 0) {\n URL.revokeObjectURL(entry.url);\n downloadCache.delete(key);\n }\n}\n\nexport function useDownloadFile<S extends SchemaStructure>(\n bucketName: BucketNames<S>,\n path: Accessor<string | null | undefined>,\n options?: UseDownloadFileOptions,\n): UseDownloadFileResult;\nexport function useDownloadFile<S extends SchemaStructure>(\n db: SyncedDb<S>,\n bucketName: BucketNames<S>,\n path: Accessor<string | null | undefined>,\n options?: UseDownloadFileOptions,\n): UseDownloadFileResult;\nexport function useDownloadFile<S extends SchemaStructure>(\n dbOrBucketName: SyncedDb<S> | BucketNames<S>,\n bucketNameOrPath?: BucketNames<S> | Accessor<string | null | undefined>,\n pathOrOptions?: Accessor<string | null | undefined> | UseDownloadFileOptions,\n maybeOptions?: UseDownloadFileOptions,\n): UseDownloadFileResult {\n let db: SyncedDb<S>;\n let bucketName: BucketNames<S>;\n let path: Accessor<string | null | undefined>;\n let options: UseDownloadFileOptions;\n\n if (typeof dbOrBucketName === 'string') {\n db = useDb<S>();\n bucketName = dbOrBucketName as BucketNames<S>;\n path = bucketNameOrPath as Accessor<string | null | undefined>;\n options = (pathOrOptions as UseDownloadFileOptions) ?? {};\n } else {\n db = dbOrBucketName as SyncedDb<S>;\n bucketName = bucketNameOrPath as BucketNames<S>;\n path = pathOrOptions as Accessor<string | null | undefined>;\n options = maybeOptions ?? {};\n }\n\n const useCache = options.cache !== false;\n\n const [url, setUrl] = createSignal<string | null>(null);\n const [isLoading, setIsLoading] = createSignal(false);\n const [error, setError] = createSignal<Error | null>(null);\n\n let currentKey: string | null = null;\n let privateUrl: string | null = null;\n const [refetchSignal, setRefetchSignal] = createSignal(0);\n const refetchTrigger = () => setRefetchSignal((n) => n + 1);\n\n async function doDownload(key: string, filePath: string): Promise<string | null> {\n if (useCache) {\n // Check cache\n const cached = downloadCache.get(key);\n if (cached) {\n cached.refCount++;\n currentKey = key;\n return cached.url;\n }\n\n // Check inflight\n const inflight = inflightRequests.get(key);\n if (inflight) {\n const result = await inflight;\n if (result) {\n const entry = downloadCache.get(key);\n if (entry) {\n entry.refCount++;\n currentKey = key;\n }\n }\n return result;\n }\n\n // Start new download\n const promise = (async () => {\n const content = await db.bucket(bucketName).get(filePath);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n downloadCache.set(key, { url: objectUrl, refCount: 1 });\n return objectUrl;\n })();\n\n inflightRequests.set(key, promise);\n try {\n const result = await promise;\n currentKey = key;\n return result;\n } finally {\n inflightRequests.delete(key);\n }\n } else {\n // No caching — private URL per instance\n const content = await db.bucket(bucketName).get(filePath);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n privateUrl = objectUrl;\n return objectUrl;\n }\n }\n\n function releaseCurrentEntry() {\n if (useCache && currentKey) {\n releaseEntry(currentKey);\n currentKey = null;\n }\n if (!useCache && privateUrl) {\n URL.revokeObjectURL(privateUrl);\n privateUrl = null;\n }\n }\n\n createEffect(() => {\n const filePath = path();\n // Subscribe to refetch signal so effect re-runs\n refetchSignal();\n\n // Release previous entry\n releaseCurrentEntry();\n\n if (!filePath) {\n setUrl(null);\n setIsLoading(false);\n setError(null);\n return;\n }\n\n const key = cacheKey(bucketName as string, filePath);\n\n // Synchronous cache hit\n if (useCache) {\n const cached = downloadCache.get(key);\n if (cached) {\n cached.refCount++;\n currentKey = key;\n setUrl(cached.url);\n setIsLoading(false);\n setError(null);\n return;\n }\n }\n\n let cancelled = false;\n setIsLoading(true);\n setError(null);\n\n doDownload(key, filePath).then(\n (result) => {\n if (!cancelled) {\n setUrl(result);\n setIsLoading(false);\n }\n return undefined;\n },\n (err) => {\n if (!cancelled) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setIsLoading(false);\n }\n },\n );\n\n onCleanup(() => {\n cancelled = true;\n });\n });\n\n onCleanup(() => {\n releaseCurrentEntry();\n });\n\n const refetch = () => {\n // Evict current entry from cache before re-triggering\n if (useCache && currentKey) {\n const entry = downloadCache.get(currentKey);\n if (entry) {\n URL.revokeObjectURL(entry.url);\n downloadCache.delete(currentKey);\n }\n currentKey = null;\n }\n refetchTrigger();\n };\n\n return { url, isLoading, error, refetch };\n}\n","import type { JSX} from 'solid-js';\nimport { createSignal, onMount, createComponent, createMemo, mergeProps } from 'solid-js';\nimport type { SchemaStructure } from '@spooky/query-builder';\nimport type { SyncedDbConfig } from '../types';\nimport { SyncedDb } from '../index';\nimport { Sp00kyContext } from './context';\n\nexport interface Sp00kyProviderProps<S extends SchemaStructure> {\n config: SyncedDbConfig<S>;\n fallback?: JSX.Element;\n onError?: (error: Error) => void;\n onReady?: (db: SyncedDb<S>) => void;\n children: JSX.Element;\n}\n\nexport function Sp00kyProvider<S extends SchemaStructure>(\n props: Sp00kyProviderProps<S>\n): JSX.Element {\n const merged = mergeProps(\n {\n fallback: undefined as JSX.Element | undefined,\n },\n props\n );\n\n const [db, setDb] = createSignal<SyncedDb<S> | undefined>(undefined);\n\n onMount(async () => {\n try {\n const instance = new SyncedDb<S>(merged.config);\n await instance.init();\n 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} 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 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 { useCrdtField } from './lib/use-crdt-field';\nexport { useFileUpload, type FileUploadResult } from './lib/use-file-upload';\nexport { useDownloadFile, type UseDownloadFileOptions, type UseDownloadFileResult } from './lib/use-download-file';\nexport { Sp00kyProvider, type Sp00kyProviderProps } from './lib/Sp00kyProvider';\nexport { useDb } from './lib/context';\n\n// export { AuthEventTypes } from \"@spooky-sync/core\"; // TODO: Verify if AuthEventTypes exists in core\n\n\n// Re-export query builder types for convenience\nexport type {\n QueryModifier,\n QueryModifierBuilder,\n QueryInfo,\n RelationshipsMetadata,\n RelationshipDefinition,\n InferRelatedModelFromMetadata,\n GetCardinality,\n GetTable,\n TableModel,\n TableNames,\n QueryResult,\n};\n\nexport type RelationshipField<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n Field extends RelationshipFieldsFromSchema<Schema, TableName>,\n> = GetRelationship<Schema, TableName, Field>;\n\nexport type RelatedFieldsTableScoped<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n RelatedFields extends RelationshipFieldsFromSchema<Schema, TableName> =\n RelationshipFieldsFromSchema<Schema, TableName>,\n> = {\n [K in RelatedFields]: {\n to: RelationshipField<Schema, TableName, K>['to'];\n relatedFields: RelatedFieldsMap;\n cardinality: RelationshipField<Schema, TableName, K>['cardinality'];\n };\n};\n\nexport type InferModel<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n RelatedFields extends RelatedFieldsTableScoped<Schema, TableName>,\n> = QueryResult<Schema, TableName, RelatedFields, true>;\n\nexport type WithRelated<Field extends string, RelatedFields extends RelatedFieldsMap = {}> = {\n [K in Field]: Omit<RelatedFieldMapEntry, 'relatedFields'> & {\n relatedFields: RelatedFields;\n };\n};\n\nexport type WithRelatedMany<Field extends string, RelatedFields extends RelatedFieldsMap = {}> = {\n [K in Field]: {\n to: Field;\n relatedFields: RelatedFields;\n cardinality: 'many';\n };\n};\n\n/**\n * SyncedDb - A thin wrapper around sp00ky-ts for Solid.js integration\n * Delegates all logic to the underlying sp00ky-ts instance\n */\nexport class SyncedDb<S extends SchemaStructure> {\n private config: SyncedDbConfig<S>;\n private sp00ky: Sp00kyClient<S> | null = null;\n private _initialized = false;\n\n constructor(config: SyncedDbConfig<S>) {\n this.config = config;\n }\n\n public getSp00ky(): Sp00kyClient<S> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky;\n }\n\n /**\n * Initialize the sp00ky-ts instance\n */\n async init(): Promise<void> {\n if (this._initialized) return;\n this.sp00ky = new Sp00kyClient<S>(this.config);\n await this.sp00ky.init();\n this._initialized = true;\n }\n\n /**\n * Create a new record in the database\n */\n async create(id: string, payload: Record<string, unknown>): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.create(id, payload as Record<string, unknown>);\n }\n\n /**\n * Update an existing record in the database\n */\n async update<TName extends TableNames<S>>(\n tableName: TName,\n recordId: string,\n payload: Partial<TableModel<GetTable<S, TName>>>,\n options?: UpdateOptions\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.update(\n tableName as string,\n recordId,\n payload as Record<string, unknown>,\n options\n );\n }\n\n /**\n * Delete an existing record in the database\n */\n async delete<TName extends TableNames<S>>(\n tableName: TName,\n selector: string | RecordId | InnerQuery<GetTable<S, TName>, boolean>\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n // Accept a `\"table:id\"` string OR a RecordId — live-query rows carry their\n // `id` as a RecordId, so callers can pass `db.delete('game', row.id)`\n // directly. Build the canonical string from the raw id part (not\n // `RecordId.toString()`, which escapes special chars) so it round-trips\n // through the engine's `parseRecordIdString`. InnerQuery selectors are not\n // supported yet. (cross-package RecordId instances → match by constructor name.)\n const isRecordId =\n selector instanceof RecordId || (selector as any)?.constructor?.name === 'RecordId';\n let id: string;\n if (typeof selector === 'string') {\n id = selector;\n } else if (isRecordId) {\n id = `${tableName as string}:${(selector as RecordId).id}`;\n } else {\n throw new Error('Only string ID or RecordId selectors are supported currently with core');\n }\n await this.sp00ky.delete(tableName as string, id);\n }\n\n /**\n * Query data from the database\n */\n public query<TName extends TableNames<S>>(\n table: TName\n ): QueryBuilder<S, TName, Sp00kyQueryResultPromise, {}, false> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.query(table, {});\n }\n\n /**\n * Run a backend operation\n */\n public async run<\n B extends BackendNames<S>,\n R extends BackendRoutes<S, B>,\n >(\n backend: B,\n path: R,\n payload: RoutePayload<S, B, R>,\n options?: RunOptions,\n ): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.run(backend, path, payload, options);\n }\n\n /**\n * Authenticate with the database\n */\n public async authenticate(token: string): Promise<RecordId<string>> {\n await this.sp00ky?.authenticate(token);\n // Sp00kyClient.authenticate returns whatever remote.authenticate returns (boolean or token usually?)\n // Wait, checked Sp00kyClient: return this.remote.getClient().authenticate(token);\n // SurrealDB authenticate returns void? or token?\n // Assuming void or token.\n return new RecordId('user', 'me'); // Placeholder or actual?\n }\n\n /**\n * Deauthenticate from the database\n * @deprecated Use signOut() instead\n */\n public async deauthenticate(): Promise<void> {\n await this.signOut();\n }\n\n /**\n * Sign out, clear session and local storage\n */\n public async signOut(): Promise<void> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n await this.sp00ky.auth.signOut();\n }\n\n /**\n * Execute a function with direct access to the remote database connection\n */\n public async useRemote<T>(fn: (db: Surreal) => T | Promise<T>): Promise<T> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return await this.sp00ky.useRemote(fn);\n }\n /**\n * Access the remote database service directly\n */\n get remote(): Sp00kyClient<S>['remoteClient'] {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.remoteClient;\n }\n\n /**\n * Access the local database service directly\n */\n get local(): Sp00kyClient<S>['localClient'] {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.localClient;\n }\n\n /**\n * Access the auth service\n */\n get auth(): AuthService<S> {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.auth;\n }\n\n get pendingMutationCount(): number {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.pendingMutationCount;\n }\n\n /** Diagnostic — see `Sp00kyClient.liveRetryCount`. */\n get liveRetryCount(): number {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.liveRetryCount;\n }\n\n subscribeToPendingMutations(cb: (count: number) => void): () => void {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.subscribeToPendingMutations(cb);\n }\n\n bucket<B extends BucketNames<S>>(name: B): BucketHandle {\n if (!this.sp00ky) throw new Error('SyncedDb not initialized');\n return this.sp00ky.bucket(name);\n }\n\n getBucketConfig(name: string): BucketDefinitionSchema | undefined {\n return this.config.schema.buckets?.find((b) => b.name === name);\n }\n}\n\nexport * from './types';\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spooky-sync/client-solid",
3
- "version": "0.0.1-canary.69",
3
+ "version": "0.0.1-canary.70",
4
4
  "type": "module",
5
5
  "description": "SurrealDB client with local and remote database support for browser applications",
6
6
  "main": "./dist/index.cjs",
@@ -44,7 +44,7 @@
44
44
  "dependencies": {
45
45
  "@spooky-sync/query-builder": "workspace:*",
46
46
  "@spooky-sync/core": "workspace:*",
47
- "@surrealdb/wasm": "^3.0.0",
47
+ "@surrealdb/wasm": "^3.0.3",
48
48
  "surrealdb": "2.0.3",
49
49
  "valtio": "^2.1.8"
50
50
  },
package/src/index.ts CHANGED
@@ -160,12 +160,26 @@ export class SyncedDb<S extends SchemaStructure> {
160
160
  */
161
161
  async delete<TName extends TableNames<S>>(
162
162
  tableName: TName,
163
- selector: string | InnerQuery<GetTable<S, TName>, boolean>
163
+ selector: string | RecordId | InnerQuery<GetTable<S, TName>, boolean>
164
164
  ): Promise<void> {
165
165
  if (!this.sp00ky) throw new Error('SyncedDb not initialized');
166
- if (typeof selector !== 'string')
167
- throw new Error('Only string ID selectors are supported currently with core');
168
- await this.sp00ky.delete(tableName as string, selector);
166
+ // Accept a `"table:id"` string OR a RecordId — live-query rows carry their
167
+ // `id` as a RecordId, so callers can pass `db.delete('game', row.id)`
168
+ // directly. Build the canonical string from the raw id part (not
169
+ // `RecordId.toString()`, which escapes special chars) so it round-trips
170
+ // through the engine's `parseRecordIdString`. InnerQuery selectors are not
171
+ // supported yet. (cross-package RecordId instances → match by constructor name.)
172
+ const isRecordId =
173
+ selector instanceof RecordId || (selector as any)?.constructor?.name === 'RecordId';
174
+ let id: string;
175
+ if (typeof selector === 'string') {
176
+ id = selector;
177
+ } else if (isRecordId) {
178
+ id = `${tableName as string}:${(selector as RecordId).id}`;
179
+ } else {
180
+ throw new Error('Only string ID or RecordId selectors are supported currently with core');
181
+ }
182
+ await this.sp00ky.delete(tableName as string, id);
169
183
  }
170
184
 
171
185
  /**
@@ -6,6 +6,7 @@ import type {
6
6
  QueryResult,
7
7
  } from '@spooky-sync/query-builder';
8
8
  import { createEffect, createSignal, onCleanup, useContext } from 'solid-js';
9
+ import { createStore, reconcile } from 'solid-js/store';
9
10
  import { SyncedDb } from '..';
10
11
  import type { Sp00kyQueryResultPromise } from '@spooky-sync/core';
11
12
  import { Sp00kyContext } from './context';
@@ -23,7 +24,17 @@ type QueryArg<
23
24
  | null
24
25
  | undefined);
25
26
 
26
- type QueryOptions = { enabled?: () => boolean };
27
+ type QueryOptions = {
28
+ enabled?: () => boolean;
29
+ /**
30
+ * Tear down the query (remote `_00_query` view + local WASM view) when this
31
+ * hook is disposed and no other subscriber remains, instead of keeping it
32
+ * resident for cheap re-subscription. Use for viewport-windowed lists that
33
+ * mount/unmount a query per scroll window and want off-screen windows
34
+ * cancelled. Trade-off: scrolling back to a torn-down window re-registers it.
35
+ */
36
+ deregisterOnCleanup?: boolean;
37
+ };
27
38
 
28
39
  // Overload: context-based (no explicit db)
29
40
  export function useQuery<
@@ -36,7 +47,12 @@ export function useQuery<
36
47
  >(
37
48
  finalQuery: QueryArg<S, TableName, T, RelatedFields, IsOne>,
38
49
  options?: QueryOptions,
39
- ): { data: () => TData | undefined; error: () => Error | undefined; isLoading: () => boolean };
50
+ ): {
51
+ data: () => TData | undefined;
52
+ error: () => Error | undefined;
53
+ isLoading: () => boolean;
54
+ isFetching: () => boolean;
55
+ };
40
56
 
41
57
  // Overload: explicit db (backward-compatible)
42
58
  export function useQuery<
@@ -50,7 +66,12 @@ export function useQuery<
50
66
  db: SyncedDb<S>,
51
67
  finalQuery: QueryArg<S, TableName, T, RelatedFields, IsOne>,
52
68
  options?: QueryOptions,
53
- ): { data: () => TData | undefined; error: () => Error | undefined; isLoading: () => boolean };
69
+ ): {
70
+ data: () => TData | undefined;
71
+ error: () => Error | undefined;
72
+ isLoading: () => boolean;
73
+ isFetching: () => boolean;
74
+ };
54
75
 
55
76
  // Implementation
56
77
  export function useQuery<
@@ -94,18 +115,56 @@ export function useQuery<
94
115
  options = queryOrOptions as QueryOptions | undefined;
95
116
  }
96
117
 
97
- const [data, setData] = createSignal<TData | undefined>(undefined);
98
118
  const [error, setError] = createSignal<Error | undefined>(undefined);
99
119
  const [isFetched, setIsFetched] = createSignal(false);
100
- const [unsubscribe, setUnsubscribe] = createSignal<(() => void) | undefined>(undefined);
120
+ const [isFetching, setIsFetching] = createSignal(false);
121
+ // Results live in a store (not a signal) so consecutive live-query emissions
122
+ // are merged with `reconcile`: unchanged rows keep their object identity and
123
+ // changed rows are mutated in place. That keeps Solid's reference-keyed `<For>`
124
+ // rows — and any `useQuery` subscriptions mounted inside them — alive across
125
+ // updates, instead of tearing every row down and re-registering its queries.
126
+ const [state, setState] = createStore<{ value: TData | undefined }>({ value: undefined });
127
+ // `reconcile` (below) merges each emission into `state.value` IN PLACE, keeping
128
+ // the array reference stable. That's ideal for granular per-row reactivity, but
129
+ // it means a *coarse* reader of `data()` — `<For each={data()}>`, or an effect
130
+ // that copies the whole array elsewhere (e.g. GameList's windowed store) — is
131
+ // NOT re-run when rows are added/removed/reordered within a same-length result
132
+ // (the classic case: deleting a row in a windowed list shifts the next one in,
133
+ // so length stays 50 and the array ref never changes). Bump a version on every
134
+ // emission and read it in `data()` so every consumer re-runs on any change while
135
+ // reconcile still preserves row identity underneath.
136
+ const [version, setVersion] = createSignal(0);
137
+ const data = () => {
138
+ version();
139
+ return state.value;
140
+ };
141
+
101
142
  let prevQueryString: string | undefined;
143
+ // Monotonic token for each subscription generation. Bumped whenever the query
144
+ // identity changes or the hook is disposed, so a slow async `initQuery`
145
+ // continuation can detect it was superseded and avoid installing a stale (and
146
+ // leaked) subscription.
147
+ let runId = 0;
148
+ let activeUnsub: (() => void) | undefined;
149
+ // The hash of the currently-installed subscription, for opt-in deregister on
150
+ // dispose (see `deregisterOnCleanup`).
151
+ let activeHash: string | undefined;
152
+
153
+ const teardownActive = () => {
154
+ activeUnsub?.();
155
+ activeUnsub = undefined;
156
+ };
102
157
 
103
158
  const sp00ky = db.getSp00ky();
104
159
 
105
160
  const initQuery = async (
106
- query: FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>
161
+ query: FinalQuery<S, TableName, T, RelatedFields, IsOne, Sp00kyQueryResultPromise>,
162
+ myRun: number
107
163
  ) => {
108
164
  const { hash } = await query.run();
165
+ // A newer query identity (or disposal) won the race while we awaited run().
166
+ if (myRun !== runId) return;
167
+ activeHash = hash;
109
168
  setError(undefined);
110
169
 
111
170
  let isFirstCall = true;
@@ -113,7 +172,16 @@ export function useQuery<
113
172
  hash,
114
173
  (e) => {
115
174
  const queryData = (query.isOne ? e[0] : e) as TData;
116
- setData(() => queryData);
175
+ // Merge into the store by record id: unchanged rows keep their identity,
176
+ // changed rows update in place. Replaces wholesale for `one()`/null.
177
+ // Time the reconcile → report as the "frontend" phase for DevTools/MCP.
178
+ const reconcileStart = performance.now();
179
+ setState('value', reconcile(queryData as any, { key: 'id' }));
180
+ // Notify coarse `data()` readers (see the `version` note above): reconcile
181
+ // keeps the array ref stable, so this is what re-runs `<For>`/copy-effects
182
+ // on add/remove/reorder.
183
+ setVersion((v) => v + 1);
184
+ sp00ky.reportFrontendTiming(hash, performance.now() - reconcileStart);
117
185
  // The first (immediate) callback with no data likely means the local DB
118
186
  // hasn't synced yet — don't mark as fetched so UI shows loading state
119
187
  const hasData = query.isOne ? queryData !== null && queryData !== undefined : (e as any[]).length > 0;
@@ -125,7 +193,25 @@ export function useQuery<
125
193
  { immediate: true }
126
194
  );
127
195
 
128
- setUnsubscribe(() => unsub);
196
+ // Mirror the query's fetch status so the UI can show a "loading more"
197
+ // state while the sync engine pulls missing records in the background.
198
+ const unsubStatus = sp00ky.subscribeQueryStatus(
199
+ hash,
200
+ (status) => setIsFetching(status === 'fetching'),
201
+ { immediate: true }
202
+ );
203
+
204
+ const teardown = () => {
205
+ unsub();
206
+ unsubStatus();
207
+ };
208
+
209
+ // Superseded while awaiting subscribe()? Don't leak — tear down immediately.
210
+ if (myRun !== runId) {
211
+ teardown();
212
+ return;
213
+ }
214
+ activeUnsub = teardown;
129
215
  };
130
216
 
131
217
  createEffect(() => {
@@ -143,21 +229,36 @@ export function useQuery<
143
229
  return;
144
230
  }
145
231
 
146
- // Prevent re-running if query hasn't changed
147
- const queryString = JSON.stringify(query);
232
+ // Dedup on the query's stable identity hash (cyrb53 of surql + vars), not a
233
+ // full `JSON.stringify` of the FinalQuery (which walks the whole schema +
234
+ // inner query on every reactive tick and isn't guaranteed stable). When the
235
+ // identity is unchanged we keep the existing subscription alive.
236
+ const queryString = String(query.hash);
148
237
  if (queryString === prevQueryString) {
149
238
  return;
150
239
  }
151
240
  prevQueryString = queryString;
152
241
 
153
- // Reset fetched state when query changes
242
+ // New query identity supersede the previous subscription and start fresh.
243
+ const myRun = ++runId;
244
+ teardownActive();
154
245
  setIsFetched(false);
155
- initQuery(query);
246
+ initQuery(query, myRun);
247
+ });
156
248
 
157
- // Cleanup
158
- onCleanup(() => {
159
- unsubscribe()?.();
160
- });
249
+ // Tear down the live subscription when the hook's owner is disposed. Registered
250
+ // on the hook (component) scope rather than inside the effect, so an effect
251
+ // re-run that early-returns (unchanged query) doesn't clean up the still-valid
252
+ // subscription. Bumping runId also invalidates any in-flight initQuery.
253
+ onCleanup(() => {
254
+ runId++;
255
+ teardownActive();
256
+ // Opt-in: cancel the query once this hook (its last subscriber) is gone.
257
+ // teardownActive() above already removed this hook's callback, so
258
+ // deregisterQuery's refcount guard sees the true remaining-subscriber count.
259
+ if (options?.deregisterOnCleanup && activeHash) {
260
+ sp00ky.deregisterQuery(activeHash);
261
+ }
161
262
  });
162
263
 
163
264
  const isLoading = () => {
@@ -168,5 +269,6 @@ export function useQuery<
168
269
  data,
169
270
  error,
170
271
  isLoading,
272
+ isFetching,
171
273
  };
172
274
  }