@spooky-sync/client-solid 0.0.1-canary.9 → 0.0.1-canary.91

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/AGENTS.md ADDED
@@ -0,0 +1,66 @@
1
+ # `@spooky-sync/client-solid` — agent guide
2
+
3
+ ## What this package is
4
+
5
+ The SolidJS binding for sp00ky. Exposes a `Sp00kyProvider` that initializes a `Sp00kyClient` and a set of reactive hooks (`useDb`, `useQuery`, `useCrdtField`, `useFileUpload`, `useDownloadFile`). All hooks expect to be called inside a `<Sp00kyProvider>` boundary.
6
+
7
+ ## Setup pattern
8
+
9
+ ```ts
10
+ // db.ts
11
+ import type { SyncedDbConfig } from '@spooky-sync/client-solid';
12
+ import { schema, SURQL_SCHEMA } from './schema.gen'; // generated by `spky generate`
13
+
14
+ export const dbConfig: SyncedDbConfig<typeof schema> = {
15
+ schema,
16
+ schemaSurql: SURQL_SCHEMA,
17
+ database: {
18
+ namespace: 'main',
19
+ database: 'app',
20
+ endpoint: 'ws://localhost:8666/rpc',
21
+ store: 'memory', // or 'indexeddb' for persistence
22
+ persistenceClient: 'localstorage',
23
+ },
24
+ };
25
+ ```
26
+
27
+ ```tsx
28
+ // App.tsx
29
+ <Sp00kyProvider config={dbConfig}>{/* app */}</Sp00kyProvider>
30
+ ```
31
+
32
+ ## Key hooks
33
+
34
+ - **`useDb<typeof schema>()`** — returns the `SyncedDb<S>` instance. Methods:
35
+ - `db.create(id, payload)` — `id` is a full record ID like `'thread:abc'`.
36
+ - `db.update(table, id, payload, options?)` — `options.debounced` coalesces updates.
37
+ - `db.delete(table, idOrSelector)`.
38
+ - `db.query(table)` — returns a `QueryBuilder`. Chain `.related()`, `.orderBy()`, `.limit()`, etc., end with `.build()`.
39
+ - `db.run(backend, route, payload)` — call a backend RPC route.
40
+ - `db.bucket(name)` — get a `BucketHandle` for file storage.
41
+ - `db.useRemote(fn)` — escape hatch to the raw `Surreal` client (skips cache).
42
+ - `db.authenticate(token)`, `db.signOut()`, `db.auth`.
43
+ - `db.pendingMutationCount`, `db.subscribeToPendingMutations(cb)`.
44
+ - **`useQuery(() => db.query(...).build())`** — reactive query. Returns `{ data, status, error, ... }` accessors. The factory function is tracked, so passing reactive params (signals) re-runs the query.
45
+ - **`useCrdtField(table, () => recordId, field, () => valueAccessor)`** — wires a CRDT text field to a Loro doc. Pair with `db.update(table, id, { [field]: newValue }, { debounced: true })` so rapid keystrokes don't flood the queue. *All four arguments take accessor functions where reactive — that's deliberate, for SolidJS tracking.*
46
+ - **`useFileUpload()`** / **`useDownloadFile()`** — bucket helpers; the upload result includes the storage path you write into a record column.
47
+
48
+ ## Re-exports for convenience
49
+
50
+ - `RecordId`, `Uuid` from `surrealdb`.
51
+ - Query-builder types: `TableModel`, `TableNames`, `GetTable`, `QueryResult`, etc. (see `@spooky-sync/query-builder/AGENTS.md`).
52
+ - `Model<S, T>`, `GenericModel`, `ModelPayload` — typed row shapes.
53
+
54
+ ## Common gotchas
55
+
56
+ - **`useDb()` requires `<typeof schema>`.** Without the generic, all calls fall back to `unknown` and you lose type safety.
57
+ - **CRDT fields are not regular columns.** Don't read or write them via `useQuery` — read with `useCrdtField`, write via `db.update` with `{ debounced: true }`.
58
+ - **Generate IDs explicitly.** `const id = new Uuid().toString()`, then `db.create(\`thread:\${id}\`, ...)`. SurrealDB's auto-id only fires on direct DB writes, not through the sync queue.
59
+ - **`useQuery` factories must call `.build()` (or `.all()`, `.first()`, etc.).** A bare `db.query('thread')` is a builder, not a query — `useQuery` will throw or return forever-loading.
60
+ - **Provider is mandatory.** Calling any hook outside `<Sp00kyProvider>` throws.
61
+
62
+ ## Pointers
63
+
64
+ - Sync engine: `node_modules/@spooky-sync/core/AGENTS.md`
65
+ - Query builder DSL: `node_modules/@spooky-sync/query-builder/AGENTS.md`
66
+ - Schema authoring + codegen: `node_modules/@spooky-sync/cli/AGENTS.md`
package/dist/index.cjs CHANGED
@@ -2,12 +2,13 @@ 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
- const SpookyContext = (0, solid_js.createContext)();
8
+ const Sp00kyContext = (0, solid_js.createContext)();
8
9
  function useDb() {
9
- const db = (0, solid_js.useContext)(SpookyContext);
10
- if (!db) throw new Error("useDb must be used within a <SpookyProvider>. Wrap your app in <SpookyProvider config={...}>.");
10
+ const db = (0, solid_js.useContext)(Sp00kyContext);
11
+ if (!db) throw new Error("useDb must be used within a <Sp00kyProvider>. Wrap your app in <Sp00kyProvider config={...}>.");
11
12
  return db;
12
13
  }
13
14
 
@@ -22,30 +23,56 @@ function useQuery(dbOrQuery, queryOrOptions, maybeOptions) {
22
23
  finalQuery = queryOrOptions;
23
24
  options = maybeOptions;
24
25
  } else {
25
- const contextDb = (0, solid_js.useContext)(SpookyContext);
26
- 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>.");
26
+ const contextDb = (0, solid_js.useContext)(Sp00kyContext);
27
+ 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>.");
27
28
  db = contextDb;
28
29
  finalQuery = dbOrQuery;
29
30
  options = queryOrOptions;
30
31
  }
31
- const [data, setData] = (0, solid_js.createSignal)(void 0);
32
32
  const [error, setError] = (0, solid_js.createSignal)(void 0);
33
33
  const [isFetched, setIsFetched] = (0, solid_js.createSignal)(false);
34
- const [unsubscribe, setUnsubscribe] = (0, solid_js.createSignal)(void 0);
34
+ const [isFetching, setIsFetching] = (0, solid_js.createSignal)(false);
35
+ const [state, setState] = (0, solid_js_store.createStore)({ value: void 0 });
36
+ const [version, setVersion] = (0, solid_js.createSignal)(0);
37
+ const data = () => {
38
+ version();
39
+ return state.value;
40
+ };
35
41
  let prevQueryString;
36
- const spooky = db.getSpooky();
37
- const initQuery = async (query) => {
42
+ let runId = 0;
43
+ let activeUnsub;
44
+ let activeHash;
45
+ const teardownActive = () => {
46
+ activeUnsub?.();
47
+ activeUnsub = void 0;
48
+ };
49
+ const sp00ky = db.getSp00ky();
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
- const unsub = await spooky.subscribe(hash, (e) => {
42
- const data = query.isOne ? e[0] : e;
43
- setData(() => data);
44
- const hasData = query.isOne ? data != null : e.length > 0;
56
+ const unsub = await sp00ky.subscribe(hash, (e) => {
57
+ const queryData = query.isOne ? e[0] : e;
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);
62
+ const hasData = query.isOne ? queryData !== null && queryData !== void 0 : e.length > 0;
45
63
  if (!isFirstCall || hasData) setIsFetched(true);
46
64
  isFirstCall = false;
47
65
  }, { immediate: true });
48
- setUnsubscribe(() => unsub);
66
+ const unsubStatus = sp00ky.subscribeQueryStatus(hash, (status) => setIsFetching(status === "fetching"), { immediate: true });
67
+ const teardown = () => {
68
+ unsub();
69
+ unsubStatus();
70
+ };
71
+ if (myRun !== runId) {
72
+ teardown();
73
+ return;
74
+ }
75
+ activeUnsub = teardown;
49
76
  };
50
77
  (0, solid_js.createEffect)(() => {
51
78
  if (!(options?.enabled?.() ?? true)) {
@@ -54,14 +81,18 @@ function useQuery(dbOrQuery, queryOrOptions, maybeOptions) {
54
81
  }
55
82
  const query = typeof finalQuery === "function" ? finalQuery() : finalQuery;
56
83
  if (!query) return;
57
- const queryString = JSON.stringify(query);
84
+ const queryString = String(query.hash);
58
85
  if (queryString === prevQueryString) return;
59
86
  prevQueryString = queryString;
87
+ const myRun = ++runId;
88
+ teardownActive();
60
89
  setIsFetched(false);
61
- initQuery(query);
62
- (0, solid_js.onCleanup)(() => {
63
- unsubscribe()?.();
64
- });
90
+ initQuery(query, myRun);
91
+ });
92
+ (0, solid_js.onCleanup)(() => {
93
+ runId++;
94
+ teardownActive();
95
+ if (options?.deregisterOnCleanup && activeHash) sp00ky.deregisterQuery(activeHash);
65
96
  });
66
97
  const isLoading = () => {
67
98
  return !isFetched() && error() === void 0;
@@ -69,7 +100,102 @@ function useQuery(dbOrQuery, queryOrOptions, maybeOptions) {
69
100
  return {
70
101
  data,
71
102
  error,
72
- isLoading
103
+ isLoading,
104
+ isFetching
105
+ };
106
+ }
107
+
108
+ //#endregion
109
+ //#region src/lib/use-sync-status.ts
110
+ /**
111
+ * Observe sync health for a "can't reach the server" banner / indicator.
112
+ *
113
+ * Backed by `db.subscribeToSyncHealth`. Individual sync failures (a transient
114
+ * remote 500 on query registration, a dropped socket) are absorbed by the
115
+ * retry and never flip this; `isDegraded()` only goes true once failures
116
+ * persist for the configured number of consecutive rounds (sp00ky core config
117
+ * `syncHealth.degradeAfterConsecutiveFailures`, default 3), and flips back on
118
+ * the next successful round. Must be used within a `<Sp00kyProvider>`.
119
+ */
120
+ function useSyncStatus() {
121
+ const db = useDb();
122
+ const [health, setHealth] = (0, solid_js.createSignal)(db.syncHealth);
123
+ (0, solid_js.onCleanup)(db.subscribeToSyncHealth(setHealth));
124
+ return {
125
+ health,
126
+ status: () => health().status,
127
+ isHealthy: () => health().status === "healthy",
128
+ isDegraded: () => health().status === "degraded"
129
+ };
130
+ }
131
+
132
+ //#endregion
133
+ //#region src/lib/use-crdt-field.ts
134
+ function useCrdtField(table, recordId, field, fallbackText) {
135
+ const db = (0, solid_js.useContext)(Sp00kyContext);
136
+ if (!db) throw new Error("useCrdtField must be used within a <Sp00kyProvider>");
137
+ const [crdtField, setCrdtField] = (0, solid_js.createSignal)(null);
138
+ let currentId;
139
+ let initialized = false;
140
+ (0, solid_js.createEffect)(() => {
141
+ const id = recordId();
142
+ if (initialized && id === currentId) return;
143
+ if (currentId && crdtField()) {
144
+ db.getSp00ky().closeCrdtField(table, currentId, field);
145
+ setCrdtField(null);
146
+ }
147
+ currentId = id;
148
+ initialized = true;
149
+ if (!id) return;
150
+ const sp00ky = db.getSp00ky();
151
+ const text = fallbackText?.();
152
+ sp00ky.openCrdtField(table, id, field, text).then((cf) => {
153
+ if (currentId === id) setCrdtField(cf);
154
+ }).catch((err) => {
155
+ console.error(`[useCrdtField] Failed to open CRDT field ${table}.${field} on ${id}:`, err);
156
+ });
157
+ });
158
+ (0, solid_js.onCleanup)(() => {
159
+ if (currentId && crdtField()) {
160
+ db.getSp00ky().closeCrdtField(table, currentId, field);
161
+ setCrdtField(null);
162
+ }
163
+ });
164
+ return crdtField;
165
+ }
166
+
167
+ //#endregion
168
+ //#region src/lib/use-feature-flag.ts
169
+ /**
170
+ * Subscribe to a feature flag for the currently authenticated user.
171
+ *
172
+ * Returns three Solid accessors that update reactively whenever the
173
+ * server-materialized assignment in `_00_user_feature` changes. Backed by
174
+ * the same SSP + sync pipeline that powers `useQuery`, so toggling a flag
175
+ * via `spky flag enable <key>` propagates to the UI without a refresh.
176
+ *
177
+ * `enabled()` is `true` when the resolved variant exists and is not 'off'.
178
+ * For multi-variant flags, prefer `variant()` directly.
179
+ */
180
+ function useFeatureFlag(key, options) {
181
+ const handle = useDb().getSp00ky().feature(key, options);
182
+ const [variant, setVariant] = (0, solid_js.createSignal)(handle.variant());
183
+ const [payload, setPayload] = (0, solid_js.createSignal)(handle.payload());
184
+ const unsub = handle.subscribe((s) => {
185
+ setVariant(s.variant ?? options?.fallback);
186
+ setPayload(s.payload);
187
+ });
188
+ (0, solid_js.onCleanup)(() => {
189
+ unsub();
190
+ handle.close();
191
+ });
192
+ return {
193
+ variant,
194
+ payload,
195
+ enabled: () => {
196
+ const v = variant();
197
+ return v !== void 0 && v !== "off";
198
+ }
73
199
  };
74
200
  }
75
201
 
@@ -95,7 +221,7 @@ function useFileUpload(dbOrBucketName, maybeBucketName) {
95
221
  const validate = (file) => {
96
222
  const config = db.getBucketConfig(bucketName);
97
223
  if (!config) return;
98
- if (config.maxSize != null && file.size > config.maxSize) {
224
+ if (config.maxSize !== null && config.maxSize !== void 0 && file.size > config.maxSize) {
99
225
  const maxMB = (config.maxSize / (1024 * 1024)).toFixed(1);
100
226
  throw new Error(`File exceeds maximum size of ${maxMB} MB.`);
101
227
  }
@@ -204,9 +330,8 @@ function useDownloadFile(dbOrBucketName, bucketNameOrPath, pathOrOptions, maybeO
204
330
  const [error, setError] = (0, solid_js.createSignal)(null);
205
331
  let currentKey = null;
206
332
  let privateUrl = null;
207
- let refetchTrigger;
208
333
  const [refetchSignal, setRefetchSignal] = (0, solid_js.createSignal)(0);
209
- refetchTrigger = () => setRefetchSignal((n) => n + 1);
334
+ const refetchTrigger = () => setRefetchSignal((n) => n + 1);
210
335
  async function doDownload(key, filePath) {
211
336
  if (useCache) {
212
337
  const cached = downloadCache.get(key);
@@ -326,8 +451,8 @@ function useDownloadFile(dbOrBucketName, bucketNameOrPath, pathOrOptions, maybeO
326
451
  }
327
452
 
328
453
  //#endregion
329
- //#region src/lib/SpookyProvider.ts
330
- function SpookyProvider(props) {
454
+ //#region src/lib/Sp00kyProvider.ts
455
+ function Sp00kyProvider(props) {
331
456
  const merged = (0, solid_js.mergeProps)({ fallback: void 0 }, props);
332
457
  const [db, setDb] = (0, solid_js.createSignal)(void 0);
333
458
  (0, solid_js.onMount)(async () => {
@@ -339,13 +464,13 @@ function SpookyProvider(props) {
339
464
  } catch (e) {
340
465
  const error = e instanceof Error ? e : new Error(String(e));
341
466
  if (merged.onError) merged.onError(error);
342
- else console.error("SpookyProvider: Failed to initialize database", error);
467
+ else console.error("Sp00kyProvider: Failed to initialize database", error);
343
468
  }
344
469
  });
345
470
  return (0, solid_js.createMemo)(() => {
346
471
  const instance = db();
347
472
  if (!instance) return merged.fallback;
348
- return (0, solid_js.createComponent)(SpookyContext.Provider, {
473
+ return (0, solid_js.createComponent)(Sp00kyContext.Provider, {
349
474
  value: instance,
350
475
  get children() {
351
476
  return merged.children;
@@ -357,69 +482,73 @@ function SpookyProvider(props) {
357
482
  //#endregion
358
483
  //#region src/index.ts
359
484
  /**
360
- * SyncedDb - A thin wrapper around spooky-ts for Solid.js integration
361
- * Delegates all logic to the underlying spooky-ts instance
485
+ * SyncedDb - A thin wrapper around sp00ky-ts for Solid.js integration
486
+ * Delegates all logic to the underlying sp00ky-ts instance
362
487
  */
363
488
  var SyncedDb = class {
364
489
  constructor(config) {
365
- this.spooky = null;
490
+ this.sp00ky = null;
366
491
  this._initialized = false;
367
492
  this.config = config;
368
493
  }
369
- getSpooky() {
370
- if (!this.spooky) throw new Error("SyncedDb not initialized");
371
- return this.spooky;
494
+ getSp00ky() {
495
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
496
+ return this.sp00ky;
372
497
  }
373
498
  /**
374
- * Initialize the spooky-ts instance
499
+ * Initialize the sp00ky-ts instance
375
500
  */
376
501
  async init() {
377
502
  if (this._initialized) return;
378
- this.spooky = new _spooky_sync_core.SpookyClient(this.config);
379
- await this.spooky.init();
503
+ this.sp00ky = new _spooky_sync_core.Sp00kyClient(this.config);
504
+ await this.sp00ky.init();
380
505
  this._initialized = true;
381
506
  }
382
507
  /**
383
508
  * Create a new record in the database
384
509
  */
385
510
  async create(id, payload) {
386
- if (!this.spooky) throw new Error("SyncedDb not initialized");
387
- await this.spooky.create(id, payload);
511
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
512
+ await this.sp00ky.create(id, payload);
388
513
  }
389
514
  /**
390
515
  * Update an existing record in the database
391
516
  */
392
517
  async update(tableName, recordId, payload, options) {
393
- if (!this.spooky) throw new Error("SyncedDb not initialized");
394
- await this.spooky.update(tableName, recordId, payload, options);
518
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
519
+ await this.sp00ky.update(tableName, recordId, payload, options);
395
520
  }
396
521
  /**
397
522
  * Delete an existing record in the database
398
523
  */
399
524
  async delete(tableName, selector) {
400
- if (!this.spooky) throw new Error("SyncedDb not initialized");
401
- if (typeof selector !== "string") throw new Error("Only string ID selectors are supported currently with core");
402
- await this.spooky.delete(tableName, selector);
525
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
526
+ const isRecordId = selector instanceof surrealdb.RecordId || selector?.constructor?.name === "RecordId";
527
+ let id;
528
+ if (typeof selector === "string") id = selector;
529
+ else if (isRecordId) id = `${tableName}:${selector.id}`;
530
+ else throw new Error("Only string ID or RecordId selectors are supported currently with core");
531
+ await this.sp00ky.delete(tableName, id);
403
532
  }
404
533
  /**
405
534
  * Query data from the database
406
535
  */
407
536
  query(table) {
408
- if (!this.spooky) throw new Error("SyncedDb not initialized");
409
- return this.spooky.query(table, {});
537
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
538
+ return this.sp00ky.query(table, {});
410
539
  }
411
540
  /**
412
541
  * Run a backend operation
413
542
  */
414
543
  async run(backend, path, payload, options) {
415
- if (!this.spooky) throw new Error("SyncedDb not initialized");
416
- await this.spooky.run(backend, path, payload, options);
544
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
545
+ await this.sp00ky.run(backend, path, payload, options);
417
546
  }
418
547
  /**
419
548
  * Authenticate with the database
420
549
  */
421
550
  async authenticate(token) {
422
- await this.spooky?.authenticate(token);
551
+ await this.sp00ky?.authenticate(token);
423
552
  return new surrealdb.RecordId("user", "me");
424
553
  }
425
554
  /**
@@ -433,48 +562,67 @@ var SyncedDb = class {
433
562
  * Sign out, clear session and local storage
434
563
  */
435
564
  async signOut() {
436
- if (!this.spooky) throw new Error("SyncedDb not initialized");
437
- await this.spooky.auth.signOut();
565
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
566
+ await this.sp00ky.auth.signOut();
438
567
  }
439
568
  /**
440
569
  * Execute a function with direct access to the remote database connection
441
570
  */
442
571
  async useRemote(fn) {
443
- if (!this.spooky) throw new Error("SyncedDb not initialized");
444
- return await this.spooky.useRemote(fn);
572
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
573
+ return await this.sp00ky.useRemote(fn);
445
574
  }
446
575
  /**
447
576
  * Access the remote database service directly
448
577
  */
449
578
  get remote() {
450
- if (!this.spooky) throw new Error("SyncedDb not initialized");
451
- return this.spooky.remoteClient;
579
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
580
+ return this.sp00ky.remoteClient;
452
581
  }
453
582
  /**
454
583
  * Access the local database service directly
455
584
  */
456
585
  get local() {
457
- if (!this.spooky) throw new Error("SyncedDb not initialized");
458
- return this.spooky.localClient;
586
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
587
+ return this.sp00ky.localClient;
459
588
  }
460
589
  /**
461
590
  * Access the auth service
462
591
  */
463
592
  get auth() {
464
- if (!this.spooky) throw new Error("SyncedDb not initialized");
465
- return this.spooky.auth;
593
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
594
+ return this.sp00ky.auth;
466
595
  }
467
596
  get pendingMutationCount() {
468
- if (!this.spooky) throw new Error("SyncedDb not initialized");
469
- return this.spooky.pendingMutationCount;
597
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
598
+ return this.sp00ky.pendingMutationCount;
599
+ }
600
+ /** Diagnostic — see `Sp00kyClient.liveRetryCount`. */
601
+ get liveRetryCount() {
602
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
603
+ return this.sp00ky.liveRetryCount;
470
604
  }
471
605
  subscribeToPendingMutations(cb) {
472
- if (!this.spooky) throw new Error("SyncedDb not initialized");
473
- return this.spooky.subscribeToPendingMutations(cb);
606
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
607
+ return this.sp00ky.subscribeToPendingMutations(cb);
608
+ }
609
+ /** Current sync-health snapshot. See {@link useSyncStatus}. */
610
+ get syncHealth() {
611
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
612
+ return this.sp00ky.syncHealth;
613
+ }
614
+ /**
615
+ * Observe sync health. Fires immediately with the current status and again
616
+ * on every healthy↔degraded transition. Prefer the `useSyncStatus` hook in
617
+ * components; this is the imperative escape hatch.
618
+ */
619
+ subscribeToSyncHealth(cb) {
620
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
621
+ return this.sp00ky.subscribeToSyncHealth(cb);
474
622
  }
475
623
  bucket(name) {
476
- if (!this.spooky) throw new Error("SyncedDb not initialized");
477
- return this.spooky.bucket(name);
624
+ if (!this.sp00ky) throw new Error("SyncedDb not initialized");
625
+ return this.sp00ky.bucket(name);
478
626
  }
479
627
  getBucketConfig(name) {
480
628
  return this.config.schema.buckets?.find((b) => b.name === name);
@@ -483,11 +631,14 @@ var SyncedDb = class {
483
631
 
484
632
  //#endregion
485
633
  exports.RecordId = surrealdb.RecordId;
486
- exports.SpookyProvider = SpookyProvider;
634
+ exports.Sp00kyProvider = Sp00kyProvider;
487
635
  exports.SyncedDb = SyncedDb;
488
636
  exports.Uuid = surrealdb.Uuid;
637
+ exports.useCrdtField = useCrdtField;
489
638
  exports.useDb = useDb;
490
639
  exports.useDownloadFile = useDownloadFile;
640
+ exports.useFeatureFlag = useFeatureFlag;
491
641
  exports.useFileUpload = useFileUpload;
492
642
  exports.useQuery = useQuery;
643
+ exports.useSyncStatus = useSyncStatus;
493
644
  //# sourceMappingURL=index.cjs.map