@spooky-sync/client-solid 0.0.1-canary.7 → 0.0.1-canary.71

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.js CHANGED
@@ -1,12 +1,13 @@
1
- import { SpookyClient, fileToUint8Array } from "@spooky-sync/core";
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
- const SpookyContext = createContext();
7
+ const Sp00kyContext = createContext();
7
8
  function useDb() {
8
- const db = useContext(SpookyContext);
9
- if (!db) throw new Error("useDb must be used within a <SpookyProvider>. Wrap your app in <SpookyProvider config={...}>.");
9
+ const db = useContext(Sp00kyContext);
10
+ if (!db) throw new Error("useDb must be used within a <Sp00kyProvider>. Wrap your app in <Sp00kyProvider config={...}>.");
10
11
  return db;
11
12
  }
12
13
 
@@ -21,30 +22,56 @@ function useQuery(dbOrQuery, queryOrOptions, maybeOptions) {
21
22
  finalQuery = queryOrOptions;
22
23
  options = maybeOptions;
23
24
  } else {
24
- const contextDb = useContext(SpookyContext);
25
- if (!contextDb) throw new Error("useQuery: No db argument provided and no SpookyContext found. Either pass a SyncedDb instance or wrap your app in <SpookyProvider>.");
25
+ const contextDb = useContext(Sp00kyContext);
26
+ if (!contextDb) throw new Error("useQuery: No db argument provided and no Sp00kyContext found. Either pass a SyncedDb instance or wrap your app in <Sp00kyProvider>.");
26
27
  db = contextDb;
27
28
  finalQuery = dbOrQuery;
28
29
  options = queryOrOptions;
29
30
  }
30
- const [data, setData] = createSignal(void 0);
31
31
  const [error, setError] = createSignal(void 0);
32
32
  const [isFetched, setIsFetched] = createSignal(false);
33
- const [unsubscribe, setUnsubscribe] = createSignal(void 0);
33
+ const [isFetching, setIsFetching] = createSignal(false);
34
+ const [state, setState] = createStore({ value: void 0 });
35
+ const [version, setVersion] = createSignal(0);
36
+ const data = () => {
37
+ version();
38
+ return state.value;
39
+ };
34
40
  let prevQueryString;
35
- const spooky = db.getSpooky();
36
- const initQuery = async (query) => {
41
+ let runId = 0;
42
+ let activeUnsub;
43
+ let activeHash;
44
+ const teardownActive = () => {
45
+ activeUnsub?.();
46
+ activeUnsub = void 0;
47
+ };
48
+ const sp00ky = db.getSp00ky();
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
- const unsub = await spooky.subscribe(hash, (e) => {
41
- const data = query.isOne ? e[0] : e;
42
- setData(() => data);
43
- const hasData = query.isOne ? data != null : e.length > 0;
55
+ const unsub = await sp00ky.subscribe(hash, (e) => {
56
+ const queryData = query.isOne ? e[0] : e;
57
+ const reconcileStart = performance.now();
58
+ setState("value", reconcile(queryData, { key: "id" }));
59
+ setVersion((v) => v + 1);
60
+ sp00ky.reportFrontendTiming(hash, performance.now() - reconcileStart);
61
+ const hasData = query.isOne ? queryData !== null && queryData !== void 0 : e.length > 0;
44
62
  if (!isFirstCall || hasData) setIsFetched(true);
45
63
  isFirstCall = false;
46
64
  }, { immediate: true });
47
- setUnsubscribe(() => unsub);
65
+ const unsubStatus = sp00ky.subscribeQueryStatus(hash, (status) => setIsFetching(status === "fetching"), { immediate: true });
66
+ const teardown = () => {
67
+ unsub();
68
+ unsubStatus();
69
+ };
70
+ if (myRun !== runId) {
71
+ teardown();
72
+ return;
73
+ }
74
+ activeUnsub = teardown;
48
75
  };
49
76
  createEffect(() => {
50
77
  if (!(options?.enabled?.() ?? true)) {
@@ -53,14 +80,18 @@ function useQuery(dbOrQuery, queryOrOptions, maybeOptions) {
53
80
  }
54
81
  const query = typeof finalQuery === "function" ? finalQuery() : finalQuery;
55
82
  if (!query) return;
56
- const queryString = JSON.stringify(query);
83
+ const queryString = String(query.hash);
57
84
  if (queryString === prevQueryString) return;
58
85
  prevQueryString = queryString;
86
+ const myRun = ++runId;
87
+ teardownActive();
59
88
  setIsFetched(false);
60
- initQuery(query);
61
- onCleanup(() => {
62
- unsubscribe()?.();
63
- });
89
+ initQuery(query, myRun);
90
+ });
91
+ onCleanup(() => {
92
+ runId++;
93
+ teardownActive();
94
+ if (options?.deregisterOnCleanup && activeHash) sp00ky.deregisterQuery(activeHash);
64
95
  });
65
96
  const isLoading = () => {
66
97
  return !isFetched() && error() === void 0;
@@ -68,10 +99,46 @@ 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
 
107
+ //#endregion
108
+ //#region src/lib/use-crdt-field.ts
109
+ function useCrdtField(table, recordId, field, fallbackText) {
110
+ const db = useContext(Sp00kyContext);
111
+ if (!db) throw new Error("useCrdtField must be used within a <Sp00kyProvider>");
112
+ const [crdtField, setCrdtField] = createSignal(null);
113
+ let currentId;
114
+ let initialized = false;
115
+ createEffect(() => {
116
+ const id = recordId();
117
+ if (initialized && id === currentId) return;
118
+ if (currentId && crdtField()) {
119
+ db.getSp00ky().closeCrdtField(table, currentId, field);
120
+ setCrdtField(null);
121
+ }
122
+ currentId = id;
123
+ initialized = true;
124
+ if (!id) return;
125
+ const sp00ky = db.getSp00ky();
126
+ const text = fallbackText?.();
127
+ sp00ky.openCrdtField(table, id, field, text).then((cf) => {
128
+ if (currentId === id) setCrdtField(cf);
129
+ }).catch((err) => {
130
+ console.error(`[useCrdtField] Failed to open CRDT field ${table}.${field} on ${id}:`, err);
131
+ });
132
+ });
133
+ onCleanup(() => {
134
+ if (currentId && crdtField()) {
135
+ db.getSp00ky().closeCrdtField(table, currentId, field);
136
+ setCrdtField(null);
137
+ }
138
+ });
139
+ return crdtField;
140
+ }
141
+
75
142
  //#endregion
76
143
  //#region src/lib/use-file-upload.ts
77
144
  function useFileUpload(dbOrBucketName, maybeBucketName) {
@@ -94,7 +161,7 @@ function useFileUpload(dbOrBucketName, maybeBucketName) {
94
161
  const validate = (file) => {
95
162
  const config = db.getBucketConfig(bucketName);
96
163
  if (!config) return;
97
- if (config.maxSize != null && file.size > config.maxSize) {
164
+ if (config.maxSize !== null && config.maxSize !== void 0 && file.size > config.maxSize) {
98
165
  const maxMB = (config.maxSize / (1024 * 1024)).toFixed(1);
99
166
  throw new Error(`File exceeds maximum size of ${maxMB} MB.`);
100
167
  }
@@ -203,9 +270,8 @@ function useDownloadFile(dbOrBucketName, bucketNameOrPath, pathOrOptions, maybeO
203
270
  const [error, setError] = createSignal(null);
204
271
  let currentKey = null;
205
272
  let privateUrl = null;
206
- let refetchTrigger;
207
273
  const [refetchSignal, setRefetchSignal] = createSignal(0);
208
- refetchTrigger = () => setRefetchSignal((n) => n + 1);
274
+ const refetchTrigger = () => setRefetchSignal((n) => n + 1);
209
275
  async function doDownload(key, filePath) {
210
276
  if (useCache) {
211
277
  const cached = downloadCache.get(key);
@@ -325,8 +391,8 @@ function useDownloadFile(dbOrBucketName, bucketNameOrPath, pathOrOptions, maybeO
325
391
  }
326
392
 
327
393
  //#endregion
328
- //#region src/lib/SpookyProvider.ts
329
- function SpookyProvider(props) {
394
+ //#region src/lib/Sp00kyProvider.ts
395
+ function Sp00kyProvider(props) {
330
396
  const merged = mergeProps({ fallback: void 0 }, props);
331
397
  const [db, setDb] = createSignal(void 0);
332
398
  onMount(async () => {
@@ -338,13 +404,13 @@ function SpookyProvider(props) {
338
404
  } catch (e) {
339
405
  const error = e instanceof Error ? e : new Error(String(e));
340
406
  if (merged.onError) merged.onError(error);
341
- else console.error("SpookyProvider: Failed to initialize database", error);
407
+ else console.error("Sp00kyProvider: Failed to initialize database", error);
342
408
  }
343
409
  });
344
410
  return createMemo(() => {
345
411
  const instance = db();
346
412
  if (!instance) return merged.fallback;
347
- return createComponent(SpookyContext.Provider, {
413
+ return createComponent(Sp00kyContext.Provider, {
348
414
  value: instance,
349
415
  get children() {
350
416
  return merged.children;
@@ -356,69 +422,73 @@ function SpookyProvider(props) {
356
422
  //#endregion
357
423
  //#region src/index.ts
358
424
  /**
359
- * SyncedDb - A thin wrapper around spooky-ts for Solid.js integration
360
- * Delegates all logic to the underlying spooky-ts instance
425
+ * SyncedDb - A thin wrapper around sp00ky-ts for Solid.js integration
426
+ * Delegates all logic to the underlying sp00ky-ts instance
361
427
  */
362
428
  var SyncedDb = class {
363
429
  constructor(config) {
364
- this.spooky = null;
430
+ this.sp00ky = null;
365
431
  this._initialized = false;
366
432
  this.config = config;
367
433
  }
368
- getSpooky() {
369
- if (!this.spooky) throw new Error("SyncedDb not initialized");
370
- return this.spooky;
434
+ getSp00ky() {
435
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
436
+ return this.sp00ky;
371
437
  }
372
438
  /**
373
- * Initialize the spooky-ts instance
439
+ * Initialize the sp00ky-ts instance
374
440
  */
375
441
  async init() {
376
442
  if (this._initialized) return;
377
- this.spooky = new SpookyClient(this.config);
378
- await this.spooky.init();
443
+ this.sp00ky = new Sp00kyClient(this.config);
444
+ await this.sp00ky.init();
379
445
  this._initialized = true;
380
446
  }
381
447
  /**
382
448
  * Create a new record in the database
383
449
  */
384
450
  async create(id, payload) {
385
- if (!this.spooky) throw new Error("SyncedDb not initialized");
386
- await this.spooky.create(id, payload);
451
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
452
+ await this.sp00ky.create(id, payload);
387
453
  }
388
454
  /**
389
455
  * Update an existing record in the database
390
456
  */
391
457
  async update(tableName, recordId, payload, options) {
392
- if (!this.spooky) throw new Error("SyncedDb not initialized");
393
- await this.spooky.update(tableName, recordId, payload, options);
458
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
459
+ await this.sp00ky.update(tableName, recordId, payload, options);
394
460
  }
395
461
  /**
396
462
  * Delete an existing record in the database
397
463
  */
398
464
  async delete(tableName, selector) {
399
- if (!this.spooky) throw new Error("SyncedDb not initialized");
400
- if (typeof selector !== "string") throw new Error("Only string ID selectors are supported currently with core");
401
- await this.spooky.delete(tableName, selector);
465
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
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);
402
472
  }
403
473
  /**
404
474
  * Query data from the database
405
475
  */
406
476
  query(table) {
407
- if (!this.spooky) throw new Error("SyncedDb not initialized");
408
- return this.spooky.query(table, {});
477
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
478
+ return this.sp00ky.query(table, {});
409
479
  }
410
480
  /**
411
481
  * Run a backend operation
412
482
  */
413
483
  async run(backend, path, payload, options) {
414
- if (!this.spooky) throw new Error("SyncedDb not initialized");
415
- await this.spooky.run(backend, path, payload, options);
484
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
485
+ await this.sp00ky.run(backend, path, payload, options);
416
486
  }
417
487
  /**
418
488
  * Authenticate with the database
419
489
  */
420
490
  async authenticate(token) {
421
- await this.spooky?.authenticate(token);
491
+ await this.sp00ky?.authenticate(token);
422
492
  return new RecordId("user", "me");
423
493
  }
424
494
  /**
@@ -432,48 +502,53 @@ var SyncedDb = class {
432
502
  * Sign out, clear session and local storage
433
503
  */
434
504
  async signOut() {
435
- if (!this.spooky) throw new Error("SyncedDb not initialized");
436
- await this.spooky.auth.signOut();
505
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
506
+ await this.sp00ky.auth.signOut();
437
507
  }
438
508
  /**
439
509
  * Execute a function with direct access to the remote database connection
440
510
  */
441
511
  async useRemote(fn) {
442
- if (!this.spooky) throw new Error("SyncedDb not initialized");
443
- return await this.spooky.useRemote(fn);
512
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
513
+ return await this.sp00ky.useRemote(fn);
444
514
  }
445
515
  /**
446
516
  * Access the remote database service directly
447
517
  */
448
518
  get remote() {
449
- if (!this.spooky) throw new Error("SyncedDb not initialized");
450
- return this.spooky.remoteClient;
519
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
520
+ return this.sp00ky.remoteClient;
451
521
  }
452
522
  /**
453
523
  * Access the local database service directly
454
524
  */
455
525
  get local() {
456
- if (!this.spooky) throw new Error("SyncedDb not initialized");
457
- return this.spooky.localClient;
526
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
527
+ return this.sp00ky.localClient;
458
528
  }
459
529
  /**
460
530
  * Access the auth service
461
531
  */
462
532
  get auth() {
463
- if (!this.spooky) throw new Error("SyncedDb not initialized");
464
- return this.spooky.auth;
533
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
534
+ return this.sp00ky.auth;
465
535
  }
466
536
  get pendingMutationCount() {
467
- if (!this.spooky) throw new Error("SyncedDb not initialized");
468
- return this.spooky.pendingMutationCount;
537
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
538
+ return this.sp00ky.pendingMutationCount;
539
+ }
540
+ /** Diagnostic — see `Sp00kyClient.liveRetryCount`. */
541
+ get liveRetryCount() {
542
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
543
+ return this.sp00ky.liveRetryCount;
469
544
  }
470
545
  subscribeToPendingMutations(cb) {
471
- if (!this.spooky) throw new Error("SyncedDb not initialized");
472
- return this.spooky.subscribeToPendingMutations(cb);
546
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
547
+ return this.sp00ky.subscribeToPendingMutations(cb);
473
548
  }
474
549
  bucket(name) {
475
- if (!this.spooky) throw new Error("SyncedDb not initialized");
476
- return this.spooky.bucket(name);
550
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
551
+ return this.sp00ky.bucket(name);
477
552
  }
478
553
  getBucketConfig(name) {
479
554
  return this.config.schema.buckets?.find((b) => b.name === name);
@@ -481,5 +556,5 @@ var SyncedDb = class {
481
556
  };
482
557
 
483
558
  //#endregion
484
- export { RecordId, SpookyProvider, SyncedDb, Uuid, useDb, useDownloadFile, useFileUpload, useQuery };
559
+ export { RecordId, Sp00kyProvider, SyncedDb, Uuid, useCrdtField, useDb, useDownloadFile, useFileUpload, useQuery };
485
560
  //# sourceMappingURL=index.js.map
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-file-upload.ts","../src/lib/use-download-file.ts","../src/lib/SpookyProvider.ts","../src/index.ts"],"sourcesContent":["import { createContext, useContext } from 'solid-js';\nimport type { SchemaStructure } from '@spooky/query-builder';\nimport type { SyncedDb } from '../index';\n\nexport const SpookyContext = createContext<SyncedDb<any> | undefined>();\n\nexport function useDb<S extends SchemaStructure>(): SyncedDb<S> {\n const db = useContext(SpookyContext);\n if (!db) {\n throw new Error('useDb must be used within a <SpookyProvider>. Wrap your app in <SpookyProvider config={...}>.');\n }\n return db as SyncedDb<S>;\n}\n","import {\n ColumnSchema,\n FinalQuery,\n SchemaStructure,\n TableNames,\n QueryResult,\n} from '@spooky-sync/query-builder';\nimport { createEffect, createSignal, onCleanup, useContext } from 'solid-js';\nimport { SyncedDb } from '..';\nimport { SpookyQueryResultPromise } from '@spooky-sync/core';\nimport { SpookyContext } from './context';\n\ntype QueryArg<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n> =\n | FinalQuery<S, TableName, T, RelatedFields, IsOne, SpookyQueryResultPromise>\n | (() =>\n | FinalQuery<S, TableName, T, RelatedFields, IsOne, SpookyQueryResultPromise>\n | null\n | undefined);\n\ntype QueryOptions = { enabled?: () => boolean };\n\n// Overload: context-based (no explicit db)\nexport function useQuery<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n TData = QueryResult<S, TableName, RelatedFields, IsOne> | null,\n>(\n finalQuery: QueryArg<S, TableName, T, RelatedFields, IsOne>,\n options?: QueryOptions,\n): { data: () => TData | undefined; error: () => Error | undefined; isLoading: () => boolean };\n\n// Overload: explicit db (backward-compatible)\nexport function useQuery<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n TData = QueryResult<S, TableName, RelatedFields, IsOne> | null,\n>(\n db: SyncedDb<S>,\n finalQuery: QueryArg<S, TableName, T, RelatedFields, IsOne>,\n options?: QueryOptions,\n): { data: () => TData | undefined; error: () => Error | undefined; isLoading: () => boolean };\n\n// Implementation\nexport function useQuery<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends {\n columns: Record<string, ColumnSchema>;\n },\n RelatedFields extends Record<string, any>,\n IsOne extends boolean,\n TData = QueryResult<S, TableName, RelatedFields, IsOne> | null,\n>(\n dbOrQuery:\n | SyncedDb<S>\n | QueryArg<S, TableName, T, RelatedFields, IsOne>,\n queryOrOptions?:\n | QueryArg<S, TableName, T, RelatedFields, IsOne>\n | QueryOptions,\n maybeOptions?: QueryOptions,\n) {\n let db: SyncedDb<S>;\n let finalQuery: QueryArg<S, TableName, T, RelatedFields, IsOne>;\n let options: QueryOptions | undefined;\n\n if (dbOrQuery instanceof SyncedDb) {\n // Explicit db overload: useQuery(db, query, options?)\n db = dbOrQuery;\n finalQuery = queryOrOptions as QueryArg<S, TableName, T, RelatedFields, IsOne>;\n options = maybeOptions;\n } else {\n // Context-based overload: useQuery(query, options?)\n const contextDb = useContext(SpookyContext);\n if (!contextDb) {\n throw new Error(\n 'useQuery: No db argument provided and no SpookyContext found. ' +\n 'Either pass a SyncedDb instance or wrap your app in <SpookyProvider>.'\n );\n }\n db = contextDb as SyncedDb<S>;\n finalQuery = dbOrQuery;\n options = queryOrOptions as QueryOptions | undefined;\n }\n\n const [data, setData] = createSignal<TData | undefined>(undefined);\n const [error, setError] = createSignal<Error | undefined>(undefined);\n const [isFetched, setIsFetched] = createSignal(false);\n const [unsubscribe, setUnsubscribe] = createSignal<(() => void) | undefined>(undefined);\n let prevQueryString: string | undefined;\n\n const spooky = db.getSpooky();\n\n const initQuery = async (\n query: FinalQuery<S, TableName, T, RelatedFields, IsOne, SpookyQueryResultPromise>\n ) => {\n const { hash } = await query.run();\n setError(undefined);\n\n let isFirstCall = true;\n const unsub = await spooky.subscribe(\n hash,\n (e) => {\n const data = (query.isOne ? e[0] : e) as TData;\n setData(() => data);\n // The first (immediate) callback with no data likely means the local DB\n // hasn't synced yet — don't mark as fetched so UI shows loading state\n const hasData = query.isOne ? data != null : (e as any[]).length > 0;\n if (!isFirstCall || hasData) {\n setIsFetched(true);\n }\n isFirstCall = false;\n },\n { immediate: true }\n );\n\n setUnsubscribe(() => unsub);\n };\n\n createEffect(() => {\n const enabled = options?.enabled?.() ?? true;\n\n // If disabled, clear error and don't run query\n if (!enabled) {\n setError(undefined);\n return;\n }\n\n // Init Query\n const query = typeof finalQuery === 'function' ? finalQuery() : finalQuery;\n if (!query) {\n return;\n }\n\n // Prevent re-running if query hasn't changed\n const queryString = JSON.stringify(query);\n if (queryString === prevQueryString) {\n return;\n }\n prevQueryString = queryString;\n\n // Reset fetched state when query changes\n setIsFetched(false);\n initQuery(query);\n\n // Cleanup\n onCleanup(() => {\n unsubscribe()?.();\n });\n });\n\n const isLoading = () => {\n return !isFetched() && error() === undefined;\n };\n\n return {\n data,\n error,\n isLoading,\n };\n}\n","import { createSignal, onCleanup } from 'solid-js';\nimport type { SchemaStructure, BucketNames } from '@spooky-sync/query-builder';\nimport { fileToUint8Array } from '@spooky-sync/core';\nimport type { SyncedDb } from '../index';\nimport { useDb } from './context';\n\nexport interface FileUploadResult {\n isUploading: () => boolean;\n error: () => Error | null;\n clearError: () => void;\n upload: (path: string, file: File | Blob) => Promise<void>;\n download: (path: string) => Promise<string | null>;\n remove: (path: string) => Promise<void>;\n exists: (path: string) => Promise<boolean>;\n}\n\nexport function useFileUpload<S extends SchemaStructure>(\n bucketName: BucketNames<S>,\n): FileUploadResult;\nexport function useFileUpload<S extends SchemaStructure>(\n db: SyncedDb<S>,\n bucketName: BucketNames<S>,\n): FileUploadResult;\nexport function useFileUpload<S extends SchemaStructure>(\n dbOrBucketName: SyncedDb<S> | BucketNames<S>,\n maybeBucketName?: BucketNames<S>,\n): FileUploadResult {\n let db: SyncedDb<S>;\n let bucketName: BucketNames<S>;\n\n if (typeof dbOrBucketName === 'string') {\n db = useDb<S>();\n bucketName = dbOrBucketName as BucketNames<S>;\n } else {\n db = dbOrBucketName as SyncedDb<S>;\n bucketName = maybeBucketName!;\n }\n\n const [isUploading, setIsUploading] = createSignal(false);\n const [error, setError] = createSignal<Error | null>(null);\n\n const objectUrls: string[] = [];\n onCleanup(() => {\n for (const url of objectUrls) {\n URL.revokeObjectURL(url);\n }\n });\n\n const clearError = () => setError(null);\n\n const validate = (file: File | Blob): void => {\n const config = db.getBucketConfig(bucketName as string);\n if (!config) return;\n\n if (config.maxSize != null && file.size > config.maxSize) {\n const maxMB = (config.maxSize / (1024 * 1024)).toFixed(1);\n throw new Error(`File exceeds maximum size of ${maxMB} MB.`);\n }\n\n if (config.allowedExtensions && config.allowedExtensions.length > 0) {\n const fileName = (file as File).name;\n if (fileName) {\n const ext = fileName.split('.').pop()?.toLowerCase();\n if (!ext || !config.allowedExtensions.includes(ext)) {\n throw new Error(\n `File type not allowed. Accepted: ${config.allowedExtensions.join(', ')}.`\n );\n }\n }\n }\n };\n\n const upload = async (path: string, file: File | Blob): Promise<void> => {\n setError(null);\n try {\n validate(file);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return;\n }\n\n setIsUploading(true);\n try {\n const bytes = await fileToUint8Array(file);\n await db.bucket(bucketName).put(path, bytes);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n } finally {\n setIsUploading(false);\n }\n };\n\n const download = async (path: string): Promise<string | null> => {\n setError(null);\n try {\n const content = await db.bucket(bucketName).get(path);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n objectUrls.push(objectUrl);\n return objectUrl;\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return null;\n }\n };\n\n const remove = async (path: string): Promise<void> => {\n setError(null);\n try {\n await db.bucket(bucketName).delete(path);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n }\n };\n\n const exists = async (path: string): Promise<boolean> => {\n setError(null);\n try {\n return await db.bucket(bucketName).exists(path);\n } catch (e) {\n setError(e instanceof Error ? e : new Error(String(e)));\n return false;\n }\n };\n\n return {\n isUploading,\n error,\n clearError,\n upload,\n download,\n remove,\n exists,\n };\n}\n","import { createSignal, createEffect, onCleanup, type Accessor } from 'solid-js';\nimport type { SchemaStructure, BucketNames } from '@spooky-sync/query-builder';\nimport type { SyncedDb } from '../index';\nimport { useDb } from './context';\n\nexport interface UseDownloadFileOptions {\n cache?: boolean;\n}\n\nexport interface UseDownloadFileResult {\n url: Accessor<string | null>;\n isLoading: Accessor<boolean>;\n error: Accessor<Error | null>;\n refetch: () => void;\n}\n\ninterface CacheEntry {\n url: string;\n refCount: number;\n}\n\nconst downloadCache = new Map<string, CacheEntry>();\nconst inflightRequests = new Map<string, Promise<string | null>>();\n\nfunction cacheKey(bucket: string, path: string): string {\n return `${bucket}:${path}`;\n}\n\nfunction releaseEntry(key: string): void {\n const entry = downloadCache.get(key);\n if (!entry) return;\n entry.refCount--;\n if (entry.refCount <= 0) {\n URL.revokeObjectURL(entry.url);\n downloadCache.delete(key);\n }\n}\n\nexport function useDownloadFile<S extends SchemaStructure>(\n bucketName: BucketNames<S>,\n path: Accessor<string | null | undefined>,\n options?: UseDownloadFileOptions,\n): UseDownloadFileResult;\nexport function useDownloadFile<S extends SchemaStructure>(\n db: SyncedDb<S>,\n bucketName: BucketNames<S>,\n path: Accessor<string | null | undefined>,\n options?: UseDownloadFileOptions,\n): UseDownloadFileResult;\nexport function useDownloadFile<S extends SchemaStructure>(\n dbOrBucketName: SyncedDb<S> | BucketNames<S>,\n bucketNameOrPath?: BucketNames<S> | Accessor<string | null | undefined>,\n pathOrOptions?: Accessor<string | null | undefined> | UseDownloadFileOptions,\n maybeOptions?: UseDownloadFileOptions,\n): UseDownloadFileResult {\n let db: SyncedDb<S>;\n let bucketName: BucketNames<S>;\n let path: Accessor<string | null | undefined>;\n let options: UseDownloadFileOptions;\n\n if (typeof dbOrBucketName === 'string') {\n db = useDb<S>();\n bucketName = dbOrBucketName as BucketNames<S>;\n path = bucketNameOrPath as Accessor<string | null | undefined>;\n options = (pathOrOptions as UseDownloadFileOptions) ?? {};\n } else {\n db = dbOrBucketName as SyncedDb<S>;\n bucketName = bucketNameOrPath as BucketNames<S>;\n path = pathOrOptions as Accessor<string | null | undefined>;\n options = maybeOptions ?? {};\n }\n\n const useCache = options.cache !== false;\n\n const [url, setUrl] = createSignal<string | null>(null);\n const [isLoading, setIsLoading] = createSignal(false);\n const [error, setError] = createSignal<Error | null>(null);\n\n let currentKey: string | null = null;\n let privateUrl: string | null = null;\n let refetchTrigger: () => void;\n const [refetchSignal, setRefetchSignal] = createSignal(0);\n refetchTrigger = () => setRefetchSignal((n) => n + 1);\n\n async function doDownload(key: string, filePath: string): Promise<string | null> {\n if (useCache) {\n // Check cache\n const cached = downloadCache.get(key);\n if (cached) {\n cached.refCount++;\n currentKey = key;\n return cached.url;\n }\n\n // Check inflight\n const inflight = inflightRequests.get(key);\n if (inflight) {\n const result = await inflight;\n if (result) {\n const entry = downloadCache.get(key);\n if (entry) {\n entry.refCount++;\n currentKey = key;\n }\n }\n return result;\n }\n\n // Start new download\n const promise = (async () => {\n const content = await db.bucket(bucketName).get(filePath);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n downloadCache.set(key, { url: objectUrl, refCount: 1 });\n return objectUrl;\n })();\n\n inflightRequests.set(key, promise);\n try {\n const result = await promise;\n currentKey = key;\n return result;\n } finally {\n inflightRequests.delete(key);\n }\n } else {\n // No caching — private URL per instance\n const content = await db.bucket(bucketName).get(filePath);\n if (!content) return null;\n const objectUrl = URL.createObjectURL(new Blob([content as BlobPart]));\n privateUrl = objectUrl;\n return objectUrl;\n }\n }\n\n function releaseCurrentEntry() {\n if (useCache && currentKey) {\n releaseEntry(currentKey);\n currentKey = null;\n }\n if (!useCache && privateUrl) {\n URL.revokeObjectURL(privateUrl);\n privateUrl = null;\n }\n }\n\n createEffect(() => {\n const filePath = path();\n // Subscribe to refetch signal so effect re-runs\n refetchSignal();\n\n // Release previous entry\n releaseCurrentEntry();\n\n if (!filePath) {\n setUrl(null);\n setIsLoading(false);\n setError(null);\n return;\n }\n\n const key = cacheKey(bucketName as string, filePath);\n\n // Synchronous cache hit\n if (useCache) {\n const cached = downloadCache.get(key);\n if (cached) {\n cached.refCount++;\n currentKey = key;\n setUrl(cached.url);\n setIsLoading(false);\n setError(null);\n return;\n }\n }\n\n let cancelled = false;\n setIsLoading(true);\n setError(null);\n\n doDownload(key, filePath).then(\n (result) => {\n if (!cancelled) {\n setUrl(result);\n setIsLoading(false);\n }\n },\n (err) => {\n if (!cancelled) {\n setError(err instanceof Error ? err : new Error(String(err)));\n setIsLoading(false);\n }\n },\n );\n\n onCleanup(() => {\n cancelled = true;\n });\n });\n\n onCleanup(() => {\n releaseCurrentEntry();\n });\n\n const refetch = () => {\n // Evict current entry from cache before re-triggering\n if (useCache && currentKey) {\n const entry = downloadCache.get(currentKey);\n if (entry) {\n URL.revokeObjectURL(entry.url);\n downloadCache.delete(currentKey);\n }\n currentKey = null;\n }\n refetchTrigger();\n };\n\n return { url, isLoading, error, refetch };\n}\n","import { createSignal, onMount, createComponent, createMemo, JSX, mergeProps } from 'solid-js';\nimport type { SchemaStructure } from '@spooky/query-builder';\nimport type { SyncedDbConfig } from '../types';\nimport { SyncedDb } from '../index';\nimport { SpookyContext } from './context';\n\nexport interface SpookyProviderProps<S extends SchemaStructure> {\n config: SyncedDbConfig<S>;\n fallback?: JSX.Element;\n onError?: (error: Error) => void;\n onReady?: (db: SyncedDb<S>) => void;\n children: JSX.Element;\n}\n\nexport function SpookyProvider<S extends SchemaStructure>(\n props: SpookyProviderProps<S>\n): JSX.Element {\n const merged = mergeProps(\n {\n fallback: undefined as JSX.Element | undefined,\n },\n props\n );\n\n const [db, setDb] = createSignal<SyncedDb<S> | undefined>(undefined);\n\n onMount(async () => {\n try {\n const instance = new SyncedDb<S>(merged.config);\n await instance.init();\n setDb(() => instance);\n merged.onReady?.(instance);\n } catch (e) {\n const error = e instanceof Error ? e : new Error(String(e));\n if (merged.onError) {\n merged.onError(error);\n } else {\n console.error('SpookyProvider: Failed to initialize database', error);\n }\n }\n });\n\n const content = createMemo(() => {\n const instance = db();\n if (!instance) return merged.fallback;\n return createComponent(SpookyContext.Provider, {\n value: instance,\n get children() {\n return merged.children;\n },\n });\n });\n\n return content as unknown as JSX.Element;\n}\n","import type { SyncedDbConfig } from './types';\nimport {\n SpookyClient,\n AuthService,\n BucketHandle,\n type SpookyQueryResultPromise,\n UpdateOptions,\n RunOptions,\n} from '@spooky-sync/core';\n\nimport {\n GetTable,\n QueryBuilder,\n SchemaStructure,\n TableModel,\n TableNames,\n QueryResult,\n RelatedFieldsMap,\n RelationshipFieldsFromSchema,\n GetRelationship,\n RelatedFieldMapEntry,\n InnerQuery,\n BackendNames,\n BackendRoutes,\n RoutePayload,\n BucketNames,\n BucketDefinitionSchema,\n} from '@spooky-sync/query-builder';\n\nimport { RecordId, Uuid, Surreal } from 'surrealdb';\nexport { RecordId, Uuid };\nexport type { Model, GenericModel, GenericSchema, ModelPayload } from './lib/models';\nexport { useQuery } from './lib/use-query';\nexport { useFileUpload, type FileUploadResult } from './lib/use-file-upload';\nexport { useDownloadFile, type UseDownloadFileOptions, type UseDownloadFileResult } from './lib/use-download-file';\nexport { SpookyProvider, type SpookyProviderProps } from './lib/SpookyProvider';\nexport { useDb } from './lib/context';\n\n// export { AuthEventTypes } from \"@spooky-sync/core\"; // TODO: Verify if AuthEventTypes exists in core\nexport type {};\n\n// Re-export query builder types for convenience\nexport type {\n QueryModifier,\n QueryModifierBuilder,\n QueryInfo,\n RelationshipsMetadata,\n RelationshipDefinition,\n InferRelatedModelFromMetadata,\n GetCardinality,\n GetTable,\n TableModel,\n TableNames,\n QueryResult,\n} from '@spooky-sync/query-builder';\n\nexport type RelationshipField<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n Field extends RelationshipFieldsFromSchema<Schema, TableName>,\n> = GetRelationship<Schema, TableName, Field>;\n\nexport type RelatedFieldsTableScoped<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n RelatedFields extends RelationshipFieldsFromSchema<Schema, TableName> =\n RelationshipFieldsFromSchema<Schema, TableName>,\n> = {\n [K in RelatedFields]: {\n to: RelationshipField<Schema, TableName, K>['to'];\n relatedFields: RelatedFieldsMap;\n cardinality: RelationshipField<Schema, TableName, K>['cardinality'];\n };\n};\n\nexport type InferModel<\n Schema extends SchemaStructure,\n TableName extends TableNames<Schema>,\n RelatedFields extends RelatedFieldsTableScoped<Schema, TableName>,\n> = QueryResult<Schema, TableName, RelatedFields, true>;\n\nexport type WithRelated<Field extends string, RelatedFields extends RelatedFieldsMap = {}> = {\n [K in Field]: Omit<RelatedFieldMapEntry, 'relatedFields'> & {\n relatedFields: RelatedFields;\n };\n};\n\nexport type WithRelatedMany<Field extends string, RelatedFields extends RelatedFieldsMap = {}> = {\n [K in Field]: {\n to: Field;\n relatedFields: RelatedFields;\n cardinality: 'many';\n };\n};\n\n/**\n * SyncedDb - A thin wrapper around spooky-ts for Solid.js integration\n * Delegates all logic to the underlying spooky-ts instance\n */\nexport class SyncedDb<S extends SchemaStructure> {\n private config: SyncedDbConfig<S>;\n private spooky: SpookyClient<S> | null = null;\n private _initialized = false;\n\n constructor(config: SyncedDbConfig<S>) {\n this.config = config;\n }\n\n public getSpooky(): SpookyClient<S> {\n if (!this.spooky) throw new Error('SyncedDb not initialized');\n return this.spooky;\n }\n\n /**\n * Initialize the spooky-ts instance\n */\n async init(): Promise<void> {\n if (this._initialized) return;\n this.spooky = new SpookyClient<S>(this.config);\n await this.spooky.init();\n this._initialized = true;\n }\n\n /**\n * Create a new record in the database\n */\n async create(id: string, payload: Record<string, unknown>): Promise<void> {\n if (!this.spooky) throw new Error('SyncedDb not initialized');\n await this.spooky.create(id, payload as Record<string, unknown>);\n }\n\n /**\n * Update an existing record in the database\n */\n async update<TName extends TableNames<S>>(\n tableName: TName,\n recordId: string,\n payload: Partial<TableModel<GetTable<S, TName>>>,\n options?: UpdateOptions\n ): Promise<void> {\n if (!this.spooky) throw new Error('SyncedDb not initialized');\n await this.spooky.update(\n tableName as string,\n recordId,\n payload as Record<string, unknown>,\n options\n );\n }\n\n /**\n * Delete an existing record in the database\n */\n async delete<TName extends TableNames<S>>(\n tableName: TName,\n selector: string | InnerQuery<GetTable<S, TName>, boolean>\n ): Promise<void> {\n if (!this.spooky) throw new Error('SyncedDb not initialized');\n if (typeof selector !== 'string')\n throw new Error('Only string ID selectors are supported currently with core');\n await this.spooky.delete(tableName as string, selector);\n }\n\n /**\n * Query data from the database\n */\n public query<TName extends TableNames<S>>(\n table: TName\n ): QueryBuilder<S, TName, SpookyQueryResultPromise, {}, false> {\n if (!this.spooky) throw new Error('SyncedDb not initialized');\n return this.spooky.query(table, {});\n }\n\n /**\n * Run a backend operation\n */\n public async run<\n B extends BackendNames<S>,\n R extends BackendRoutes<S, B>,\n >(\n backend: B,\n path: R,\n payload: RoutePayload<S, B, R>,\n options?: RunOptions,\n ): Promise<void> {\n if (!this.spooky) throw new Error('SyncedDb not initialized');\n await this.spooky.run(backend, path, payload, options);\n }\n\n /**\n * Authenticate with the database\n */\n public async authenticate(token: string): Promise<RecordId<string>> {\n const result = await this.spooky?.authenticate(token);\n // SpookyClient.authenticate returns whatever remote.authenticate returns (boolean or token usually?)\n // Wait, checked SpookyClient: return this.remote.getClient().authenticate(token);\n // SurrealDB authenticate returns void? or token?\n // Assuming void or token.\n return new RecordId('user', 'me'); // Placeholder or actual?\n }\n\n /**\n * Deauthenticate from the database\n * @deprecated Use signOut() instead\n */\n public async deauthenticate(): Promise<void> {\n await this.signOut();\n }\n\n /**\n * Sign out, clear session and local storage\n */\n public async signOut(): Promise<void> {\n if (!this.spooky) throw new Error('SyncedDb not initialized');\n await this.spooky.auth.signOut();\n }\n\n /**\n * Execute a function with direct access to the remote database connection\n */\n public async useRemote<T>(fn: (db: Surreal) => T | Promise<T>): Promise<T> {\n if (!this.spooky) throw new Error('SyncedDb not initialized');\n return await this.spooky.useRemote(fn);\n }\n /**\n * Access the remote database service directly\n */\n get remote(): SpookyClient<S>['remoteClient'] {\n if (!this.spooky) throw new Error('SyncedDb not initialized');\n return this.spooky.remoteClient;\n }\n\n /**\n * Access the local database service directly\n */\n get local(): SpookyClient<S>['localClient'] {\n if (!this.spooky) throw new Error('SyncedDb not initialized');\n return this.spooky.localClient;\n }\n\n /**\n * Access the auth service\n */\n get auth(): AuthService<S> {\n if (!this.spooky) throw new Error('SyncedDb not initialized');\n return this.spooky.auth;\n }\n\n get pendingMutationCount(): number {\n if (!this.spooky) throw new Error('SyncedDb not initialized');\n return this.spooky.pendingMutationCount;\n }\n\n subscribeToPendingMutations(cb: (count: number) => void): () => void {\n if (!this.spooky) throw new Error('SyncedDb not initialized');\n return this.spooky.subscribeToPendingMutations(cb);\n }\n\n bucket<B extends BucketNames<S>>(name: B): BucketHandle {\n if (!this.spooky) throw new Error('SyncedDb not initialized');\n return this.spooky.bucket(name);\n }\n\n getBucketConfig(name: string): BucketDefinitionSchema | undefined {\n return this.config.schema.buckets?.find((b) => b.name === name);\n }\n}\n\nexport * from './types';\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.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.7",
3
+ "version": "0.0.1-canary.71",
4
4
  "type": "module",
5
5
  "description": "SurrealDB client with local and remote database support for browser applications",
6
6
  "main": "./dist/index.cjs",
@@ -27,13 +27,14 @@
27
27
  "database",
28
28
  "browser",
29
29
  "offline",
30
- "sync"
30
+ "sync",
31
+ "tanstack-intent"
31
32
  ],
32
33
  "author": "",
33
34
  "license": "MIT",
34
35
  "repository": {
35
36
  "type": "git",
36
- "url": "https://github.com/mono424/spooky.git",
37
+ "url": "https://github.com/mono424/sp00ky.git",
37
38
  "directory": "packages/client-solid"
38
39
  },
39
40
  "publishConfig": {
@@ -43,8 +44,8 @@
43
44
  "dependencies": {
44
45
  "@spooky-sync/query-builder": "workspace:*",
45
46
  "@spooky-sync/core": "workspace:*",
46
- "@surrealdb/wasm": "^3.0.0",
47
- "surrealdb": "2.0.0",
47
+ "@surrealdb/wasm": "^3.0.3",
48
+ "surrealdb": "2.0.3",
48
49
  "valtio": "^2.1.8"
49
50
  },
50
51
  "peerDependencies": {