@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 +49 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +49 -13
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +18 -4
- package/src/lib/use-query.ts +118 -16
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 [
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
63
|
-
|
|
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
|
-
|
|
436
|
-
|
|
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
|
package/dist/index.cjs.map
CHANGED
|
@@ -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
|
*/
|
package/dist/index.d.cts.map
CHANGED
|
@@ -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":";;;;;;;KAMY,WAAW;KACX,kBAAkB;MAAU;;;;;;;;AAD5B,UCEK,gBAAA,CDFO;EACZ;EAAY,KAAA,CAAA,EAAA,OAAA;;ACQP,UAAA,CAAA,CAAA;AAPA,KAWL,aAAA,GAXqB,QAAA,GAAA,WAAA;AAOhB;AAIjB;AAKA;AAAgC,KAApB,oBAAoB,CAAA,UAAW,iBAAX,CAAA,GAAA,QACxB,YADmC,CACxB,CADwB,CAAA,GACnB,YADmB,CACR,UADQ,CACC,CADD,EACI,CADJ,CAAA,CAAA;;;;;AACnB,KAOZ,2BAPY,CAAA,UAO0B,iBAP1B,EAAA,eAO0D,aAP1D,CAAA,GAAA,gBAQR,YARkB,CAQP,CARO,CAAA,GAAA,UAStB,OAFA,CAEQ,CAFR,CAAA,eAA2B,CAAA,CAAA,MAAA,CAAA,EAAA;EAAA,IAAA,EAEiB,SAFjB;AAAW,CAAA,CAAA,IAEsB,GAFtB,CAAA,OAAA,CAAA,GAAA;EAAgC,KAAA,EAGrE,GAHqE,CAAA,IAAA,CAAA,SAAA,MAG7C,MAH6C,GAGpC,MAHoC,CAG7B,GAH6B,CAAA,IAAA,CAAA,CAAA,GAAA,GAAA;EACvD,KAAA,EAGd,GAHc,CAAA,IAAA,CAAA;EAAX,WAAA,EAIG,GAJH,CAAA,aAAA,CAAA;AACI,CAAA;KASf,QATmE,CAAA,CAAA,CAAA,GAAA,QAC3D,MAQoB,CARpB,GAQwB,CARxB,CAQ0B,CAR1B,CAAA;AAAiC,KAUlC,cAVkC,CAAA,UAUT,iBAVS,CAAA,GAUU,QAVV,CAUmB,YAVnB,CAUgC,CAVhC,CAAA,CAAA;;;
|
|
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
|
*/
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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":";;;;;;;KAMY,WAAW;KACX,kBAAkB;MAAU;;;;;;;;AAD5B,UCEK,gBAAA,CDFO;EACZ;EAAY,KAAA,CAAA,EAAA,OAAA;;ACQP,UAAA,CAAA,CAAA;AAPA,KAWL,aAAA,GAXqB,QAAA,GAAA,WAAA;AAOhB;AAIjB;AAKA;AAAgC,KAApB,oBAAoB,CAAA,UAAW,iBAAX,CAAA,GAAA,QACxB,YADmC,CACxB,CADwB,CAAA,GACnB,YADmB,CACR,UADQ,CACC,CADD,EACI,CADJ,CAAA,CAAA;;;;;AACnB,KAOZ,2BAPY,CAAA,UAO0B,iBAP1B,EAAA,eAO0D,aAP1D,CAAA,GAAA,gBAQR,YARkB,CAQP,CARO,CAAA,GAAA,UAStB,OAFA,CAEQ,CAFR,CAAA,eAA2B,CAAA,CAAA,MAAA,CAAA,EAAA;EAAA,IAAA,EAEiB,SAFjB;AAAW,CAAA,CAAA,IAEsB,GAFtB,CAAA,OAAA,CAAA,GAAA;EAAgC,KAAA,EAGrE,GAHqE,CAAA,IAAA,CAAA,SAAA,MAG7C,MAH6C,GAGpC,MAHoC,CAG7B,GAH6B,CAAA,IAAA,CAAA,CAAA,GAAA,GAAA;EACvD,KAAA,EAGd,GAHc,CAAA,IAAA,CAAA;EAAX,WAAA,EAIG,GAJH,CAAA,aAAA,CAAA;AACI,CAAA;KASf,QATmE,CAAA,CAAA,CAAA,GAAA,QAC3D,MAQoB,CARpB,GAQwB,CARxB,CAQ0B,CAR1B,CAAA;AAAiC,KAUlC,cAVkC,CAAA,UAUT,iBAVS,CAAA,GAUU,QAVV,CAUmB,YAVnB,CAUgC,CAVhC,CAAA,CAAA;;;
|
|
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 [
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
62
|
-
|
|
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
|
-
|
|
435
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
/**
|
package/src/lib/use-query.ts
CHANGED
|
@@ -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 = {
|
|
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
|
-
): {
|
|
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
|
-
): {
|
|
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 [
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
147
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
}
|