@spooky-sync/client-solid 0.0.1-canary.9 → 0.0.1-canary.90
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 +66 -0
- package/dist/index.cjs +216 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +88 -21
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +88 -21
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +214 -66
- package/dist/index.js.map +1 -1
- package/package.json +8 -7
- package/skills/sp00ky-solid/SKILL.md +264 -0
- package/skills/sp00ky-solid/references/file-hooks.md +112 -0
- package/src/cache/index.ts +1 -1
- package/src/cache/surrealdb-wasm-factory.ts +4 -1
- package/src/index.ts +103 -55
- package/src/lib/{SpookyProvider.ts → Sp00kyProvider.ts} +9 -7
- package/src/lib/context.ts +3 -3
- package/src/lib/models.ts +1 -1
- package/src/lib/use-crdt-field.ts +68 -0
- package/src/lib/use-download-file.ts +2 -2
- package/src/lib/use-feature-flag.ts +50 -0
- package/src/lib/use-file-upload.ts +2 -1
- package/src/lib/use-query.ts +130 -28
- package/src/lib/use-sync-status.ts +39 -0
- package/src/types/index.ts +3 -4
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
|
|
8
|
+
const Sp00kyContext = (0, solid_js.createContext)();
|
|
8
9
|
function useDb() {
|
|
9
|
-
const db = (0, solid_js.useContext)(
|
|
10
|
-
if (!db) throw new Error("useDb must be used within a <
|
|
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)(
|
|
26
|
-
if (!contextDb) throw new Error("useQuery: No db argument provided and no
|
|
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 [
|
|
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
|
-
|
|
37
|
-
|
|
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
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
66
|
+
const unsubStatus = sp00ky.subscribeQueryStatus(hash, (status) => setIsFetching(status === "fetching"), { immediate: true });
|
|
67
|
+
const teardown = () => {
|
|
68
|
+
unsub();
|
|
69
|
+
unsubStatus();
|
|
70
|
+
};
|
|
71
|
+
if (myRun !== runId) {
|
|
72
|
+
teardown();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
activeUnsub = teardown;
|
|
49
76
|
};
|
|
50
77
|
(0, solid_js.createEffect)(() => {
|
|
51
78
|
if (!(options?.enabled?.() ?? true)) {
|
|
@@ -54,14 +81,18 @@ function useQuery(dbOrQuery, queryOrOptions, maybeOptions) {
|
|
|
54
81
|
}
|
|
55
82
|
const query = typeof finalQuery === "function" ? finalQuery() : finalQuery;
|
|
56
83
|
if (!query) return;
|
|
57
|
-
const queryString =
|
|
84
|
+
const queryString = String(query.hash);
|
|
58
85
|
if (queryString === prevQueryString) return;
|
|
59
86
|
prevQueryString = queryString;
|
|
87
|
+
const myRun = ++runId;
|
|
88
|
+
teardownActive();
|
|
60
89
|
setIsFetched(false);
|
|
61
|
-
initQuery(query);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
90
|
+
initQuery(query, myRun);
|
|
91
|
+
});
|
|
92
|
+
(0, solid_js.onCleanup)(() => {
|
|
93
|
+
runId++;
|
|
94
|
+
teardownActive();
|
|
95
|
+
if (options?.deregisterOnCleanup && activeHash) sp00ky.deregisterQuery(activeHash);
|
|
65
96
|
});
|
|
66
97
|
const isLoading = () => {
|
|
67
98
|
return !isFetched() && error() === void 0;
|
|
@@ -69,7 +100,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
|
|
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/
|
|
330
|
-
function
|
|
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("
|
|
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)(
|
|
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
|
|
361
|
-
* Delegates all logic to the underlying
|
|
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.
|
|
490
|
+
this.sp00ky = null;
|
|
366
491
|
this._initialized = false;
|
|
367
492
|
this.config = config;
|
|
368
493
|
}
|
|
369
|
-
|
|
370
|
-
if (!this.
|
|
371
|
-
return this.
|
|
494
|
+
getSp00ky() {
|
|
495
|
+
if (!this.sp00ky) throw new Error("SyncedDb not initialized");
|
|
496
|
+
return this.sp00ky;
|
|
372
497
|
}
|
|
373
498
|
/**
|
|
374
|
-
* Initialize the
|
|
499
|
+
* Initialize the sp00ky-ts instance
|
|
375
500
|
*/
|
|
376
501
|
async init() {
|
|
377
502
|
if (this._initialized) return;
|
|
378
|
-
this.
|
|
379
|
-
await this.
|
|
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.
|
|
387
|
-
await this.
|
|
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.
|
|
394
|
-
await this.
|
|
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.
|
|
401
|
-
|
|
402
|
-
|
|
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.
|
|
409
|
-
return this.
|
|
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.
|
|
416
|
-
await this.
|
|
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.
|
|
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.
|
|
437
|
-
await this.
|
|
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.
|
|
444
|
-
return await this.
|
|
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.
|
|
451
|
-
return this.
|
|
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.
|
|
458
|
-
return this.
|
|
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.
|
|
465
|
-
return this.
|
|
593
|
+
if (!this.sp00ky) throw new Error("SyncedDb not initialized");
|
|
594
|
+
return this.sp00ky.auth;
|
|
466
595
|
}
|
|
467
596
|
get pendingMutationCount() {
|
|
468
|
-
if (!this.
|
|
469
|
-
return this.
|
|
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.
|
|
473
|
-
return this.
|
|
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.
|
|
477
|
-
return this.
|
|
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.
|
|
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
|