@spooky-sync/client-solid 0.0.1-canary.11 → 0.0.1-canary.110
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 +220 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +99 -21
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +99 -21
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +218 -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 +143 -28
- package/src/lib/use-sync-status.ts +50 -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,22 +81,125 @@ 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;
|
|
68
99
|
};
|
|
100
|
+
const isSettled = () => isFetched() && !isFetching();
|
|
69
101
|
return {
|
|
70
102
|
data,
|
|
71
103
|
error,
|
|
72
|
-
isLoading
|
|
104
|
+
isLoading,
|
|
105
|
+
isFetching,
|
|
106
|
+
isSettled
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
//#endregion
|
|
111
|
+
//#region src/lib/use-sync-status.ts
|
|
112
|
+
/**
|
|
113
|
+
* Observe sync health for a "can't reach the server" banner / indicator.
|
|
114
|
+
*
|
|
115
|
+
* Backed by `db.subscribeToSyncHealth`. Individual sync failures (a transient
|
|
116
|
+
* remote 500 on query registration, a dropped socket) are absorbed by the
|
|
117
|
+
* retry and never flip this; `isDegraded()` only goes true once failures
|
|
118
|
+
* persist for the configured number of consecutive rounds (sp00ky core config
|
|
119
|
+
* `syncHealth.degradeAfterConsecutiveFailures`, default 3), and flips back on
|
|
120
|
+
* the next successful round. Must be used within a `<Sp00kyProvider>`.
|
|
121
|
+
*/
|
|
122
|
+
function useSyncStatus() {
|
|
123
|
+
const db = useDb();
|
|
124
|
+
const [health, setHealth] = (0, solid_js.createSignal)(db.syncHealth);
|
|
125
|
+
(0, solid_js.onCleanup)(db.subscribeToSyncHealth(setHealth));
|
|
126
|
+
return {
|
|
127
|
+
health,
|
|
128
|
+
status: () => health().status,
|
|
129
|
+
isHealthy: () => health().status === "healthy",
|
|
130
|
+
isDegraded: () => health().status === "degraded",
|
|
131
|
+
everConnected: () => health().everConnected,
|
|
132
|
+
isOffline: () => health().status === "degraded" && health().everConnected
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region src/lib/use-crdt-field.ts
|
|
138
|
+
function useCrdtField(table, recordId, field, fallbackText) {
|
|
139
|
+
const db = (0, solid_js.useContext)(Sp00kyContext);
|
|
140
|
+
if (!db) throw new Error("useCrdtField must be used within a <Sp00kyProvider>");
|
|
141
|
+
const [crdtField, setCrdtField] = (0, solid_js.createSignal)(null);
|
|
142
|
+
let currentId;
|
|
143
|
+
let initialized = false;
|
|
144
|
+
(0, solid_js.createEffect)(() => {
|
|
145
|
+
const id = recordId();
|
|
146
|
+
if (initialized && id === currentId) return;
|
|
147
|
+
if (currentId && crdtField()) {
|
|
148
|
+
db.getSp00ky().closeCrdtField(table, currentId, field);
|
|
149
|
+
setCrdtField(null);
|
|
150
|
+
}
|
|
151
|
+
currentId = id;
|
|
152
|
+
initialized = true;
|
|
153
|
+
if (!id) return;
|
|
154
|
+
const sp00ky = db.getSp00ky();
|
|
155
|
+
const text = fallbackText?.();
|
|
156
|
+
sp00ky.openCrdtField(table, id, field, text).then((cf) => {
|
|
157
|
+
if (currentId === id) setCrdtField(cf);
|
|
158
|
+
}).catch((err) => {
|
|
159
|
+
console.error(`[useCrdtField] Failed to open CRDT field ${table}.${field} on ${id}:`, err);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
(0, solid_js.onCleanup)(() => {
|
|
163
|
+
if (currentId && crdtField()) {
|
|
164
|
+
db.getSp00ky().closeCrdtField(table, currentId, field);
|
|
165
|
+
setCrdtField(null);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
return crdtField;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/lib/use-feature-flag.ts
|
|
173
|
+
/**
|
|
174
|
+
* Subscribe to a feature flag for the currently authenticated user.
|
|
175
|
+
*
|
|
176
|
+
* Returns three Solid accessors that update reactively whenever the
|
|
177
|
+
* server-materialized assignment in `_00_user_feature` changes. Backed by
|
|
178
|
+
* the same SSP + sync pipeline that powers `useQuery`, so toggling a flag
|
|
179
|
+
* via `spky flag enable <key>` propagates to the UI without a refresh.
|
|
180
|
+
*
|
|
181
|
+
* `enabled()` is `true` when the resolved variant exists and is not 'off'.
|
|
182
|
+
* For multi-variant flags, prefer `variant()` directly.
|
|
183
|
+
*/
|
|
184
|
+
function useFeatureFlag(key, options) {
|
|
185
|
+
const handle = useDb().getSp00ky().feature(key, options);
|
|
186
|
+
const [variant, setVariant] = (0, solid_js.createSignal)(handle.variant());
|
|
187
|
+
const [payload, setPayload] = (0, solid_js.createSignal)(handle.payload());
|
|
188
|
+
const unsub = handle.subscribe((s) => {
|
|
189
|
+
setVariant(s.variant ?? options?.fallback);
|
|
190
|
+
setPayload(s.payload);
|
|
191
|
+
});
|
|
192
|
+
(0, solid_js.onCleanup)(() => {
|
|
193
|
+
unsub();
|
|
194
|
+
handle.close();
|
|
195
|
+
});
|
|
196
|
+
return {
|
|
197
|
+
variant,
|
|
198
|
+
payload,
|
|
199
|
+
enabled: () => {
|
|
200
|
+
const v = variant();
|
|
201
|
+
return v !== void 0 && v !== "off";
|
|
202
|
+
}
|
|
73
203
|
};
|
|
74
204
|
}
|
|
75
205
|
|
|
@@ -95,7 +225,7 @@ function useFileUpload(dbOrBucketName, maybeBucketName) {
|
|
|
95
225
|
const validate = (file) => {
|
|
96
226
|
const config = db.getBucketConfig(bucketName);
|
|
97
227
|
if (!config) return;
|
|
98
|
-
if (config.maxSize
|
|
228
|
+
if (config.maxSize !== null && config.maxSize !== void 0 && file.size > config.maxSize) {
|
|
99
229
|
const maxMB = (config.maxSize / (1024 * 1024)).toFixed(1);
|
|
100
230
|
throw new Error(`File exceeds maximum size of ${maxMB} MB.`);
|
|
101
231
|
}
|
|
@@ -204,9 +334,8 @@ function useDownloadFile(dbOrBucketName, bucketNameOrPath, pathOrOptions, maybeO
|
|
|
204
334
|
const [error, setError] = (0, solid_js.createSignal)(null);
|
|
205
335
|
let currentKey = null;
|
|
206
336
|
let privateUrl = null;
|
|
207
|
-
let refetchTrigger;
|
|
208
337
|
const [refetchSignal, setRefetchSignal] = (0, solid_js.createSignal)(0);
|
|
209
|
-
refetchTrigger = () => setRefetchSignal((n) => n + 1);
|
|
338
|
+
const refetchTrigger = () => setRefetchSignal((n) => n + 1);
|
|
210
339
|
async function doDownload(key, filePath) {
|
|
211
340
|
if (useCache) {
|
|
212
341
|
const cached = downloadCache.get(key);
|
|
@@ -326,8 +455,8 @@ function useDownloadFile(dbOrBucketName, bucketNameOrPath, pathOrOptions, maybeO
|
|
|
326
455
|
}
|
|
327
456
|
|
|
328
457
|
//#endregion
|
|
329
|
-
//#region src/lib/
|
|
330
|
-
function
|
|
458
|
+
//#region src/lib/Sp00kyProvider.ts
|
|
459
|
+
function Sp00kyProvider(props) {
|
|
331
460
|
const merged = (0, solid_js.mergeProps)({ fallback: void 0 }, props);
|
|
332
461
|
const [db, setDb] = (0, solid_js.createSignal)(void 0);
|
|
333
462
|
(0, solid_js.onMount)(async () => {
|
|
@@ -339,13 +468,13 @@ function SpookyProvider(props) {
|
|
|
339
468
|
} catch (e) {
|
|
340
469
|
const error = e instanceof Error ? e : new Error(String(e));
|
|
341
470
|
if (merged.onError) merged.onError(error);
|
|
342
|
-
else console.error("
|
|
471
|
+
else console.error("Sp00kyProvider: Failed to initialize database", error);
|
|
343
472
|
}
|
|
344
473
|
});
|
|
345
474
|
return (0, solid_js.createMemo)(() => {
|
|
346
475
|
const instance = db();
|
|
347
476
|
if (!instance) return merged.fallback;
|
|
348
|
-
return (0, solid_js.createComponent)(
|
|
477
|
+
return (0, solid_js.createComponent)(Sp00kyContext.Provider, {
|
|
349
478
|
value: instance,
|
|
350
479
|
get children() {
|
|
351
480
|
return merged.children;
|
|
@@ -357,69 +486,73 @@ function SpookyProvider(props) {
|
|
|
357
486
|
//#endregion
|
|
358
487
|
//#region src/index.ts
|
|
359
488
|
/**
|
|
360
|
-
* SyncedDb - A thin wrapper around
|
|
361
|
-
* Delegates all logic to the underlying
|
|
489
|
+
* SyncedDb - A thin wrapper around sp00ky-ts for Solid.js integration
|
|
490
|
+
* Delegates all logic to the underlying sp00ky-ts instance
|
|
362
491
|
*/
|
|
363
492
|
var SyncedDb = class {
|
|
364
493
|
constructor(config) {
|
|
365
|
-
this.
|
|
494
|
+
this.sp00ky = null;
|
|
366
495
|
this._initialized = false;
|
|
367
496
|
this.config = config;
|
|
368
497
|
}
|
|
369
|
-
|
|
370
|
-
if (!this.
|
|
371
|
-
return this.
|
|
498
|
+
getSp00ky() {
|
|
499
|
+
if (!this.sp00ky) throw new Error("SyncedDb not initialized");
|
|
500
|
+
return this.sp00ky;
|
|
372
501
|
}
|
|
373
502
|
/**
|
|
374
|
-
* Initialize the
|
|
503
|
+
* Initialize the sp00ky-ts instance
|
|
375
504
|
*/
|
|
376
505
|
async init() {
|
|
377
506
|
if (this._initialized) return;
|
|
378
|
-
this.
|
|
379
|
-
await this.
|
|
507
|
+
this.sp00ky = new _spooky_sync_core.Sp00kyClient(this.config);
|
|
508
|
+
await this.sp00ky.init();
|
|
380
509
|
this._initialized = true;
|
|
381
510
|
}
|
|
382
511
|
/**
|
|
383
512
|
* Create a new record in the database
|
|
384
513
|
*/
|
|
385
514
|
async create(id, payload) {
|
|
386
|
-
if (!this.
|
|
387
|
-
await this.
|
|
515
|
+
if (!this.sp00ky) throw new Error("SyncedDb not initialized");
|
|
516
|
+
await this.sp00ky.create(id, payload);
|
|
388
517
|
}
|
|
389
518
|
/**
|
|
390
519
|
* Update an existing record in the database
|
|
391
520
|
*/
|
|
392
521
|
async update(tableName, recordId, payload, options) {
|
|
393
|
-
if (!this.
|
|
394
|
-
await this.
|
|
522
|
+
if (!this.sp00ky) throw new Error("SyncedDb not initialized");
|
|
523
|
+
await this.sp00ky.update(tableName, recordId, payload, options);
|
|
395
524
|
}
|
|
396
525
|
/**
|
|
397
526
|
* Delete an existing record in the database
|
|
398
527
|
*/
|
|
399
528
|
async delete(tableName, selector) {
|
|
400
|
-
if (!this.
|
|
401
|
-
|
|
402
|
-
|
|
529
|
+
if (!this.sp00ky) throw new Error("SyncedDb not initialized");
|
|
530
|
+
const isRecordId = selector instanceof surrealdb.RecordId || selector?.constructor?.name === "RecordId";
|
|
531
|
+
let id;
|
|
532
|
+
if (typeof selector === "string") id = selector;
|
|
533
|
+
else if (isRecordId) id = `${tableName}:${selector.id}`;
|
|
534
|
+
else throw new Error("Only string ID or RecordId selectors are supported currently with core");
|
|
535
|
+
await this.sp00ky.delete(tableName, id);
|
|
403
536
|
}
|
|
404
537
|
/**
|
|
405
538
|
* Query data from the database
|
|
406
539
|
*/
|
|
407
540
|
query(table) {
|
|
408
|
-
if (!this.
|
|
409
|
-
return this.
|
|
541
|
+
if (!this.sp00ky) throw new Error("SyncedDb not initialized");
|
|
542
|
+
return this.sp00ky.query(table, {});
|
|
410
543
|
}
|
|
411
544
|
/**
|
|
412
545
|
* Run a backend operation
|
|
413
546
|
*/
|
|
414
547
|
async run(backend, path, payload, options) {
|
|
415
|
-
if (!this.
|
|
416
|
-
await this.
|
|
548
|
+
if (!this.sp00ky) throw new Error("SyncedDb not initialized");
|
|
549
|
+
await this.sp00ky.run(backend, path, payload, options);
|
|
417
550
|
}
|
|
418
551
|
/**
|
|
419
552
|
* Authenticate with the database
|
|
420
553
|
*/
|
|
421
554
|
async authenticate(token) {
|
|
422
|
-
await this.
|
|
555
|
+
await this.sp00ky?.authenticate(token);
|
|
423
556
|
return new surrealdb.RecordId("user", "me");
|
|
424
557
|
}
|
|
425
558
|
/**
|
|
@@ -433,48 +566,67 @@ var SyncedDb = class {
|
|
|
433
566
|
* Sign out, clear session and local storage
|
|
434
567
|
*/
|
|
435
568
|
async signOut() {
|
|
436
|
-
if (!this.
|
|
437
|
-
await this.
|
|
569
|
+
if (!this.sp00ky) throw new Error("SyncedDb not initialized");
|
|
570
|
+
await this.sp00ky.auth.signOut();
|
|
438
571
|
}
|
|
439
572
|
/**
|
|
440
573
|
* Execute a function with direct access to the remote database connection
|
|
441
574
|
*/
|
|
442
575
|
async useRemote(fn) {
|
|
443
|
-
if (!this.
|
|
444
|
-
return await this.
|
|
576
|
+
if (!this.sp00ky) throw new Error("SyncedDb not initialized");
|
|
577
|
+
return await this.sp00ky.useRemote(fn);
|
|
445
578
|
}
|
|
446
579
|
/**
|
|
447
580
|
* Access the remote database service directly
|
|
448
581
|
*/
|
|
449
582
|
get remote() {
|
|
450
|
-
if (!this.
|
|
451
|
-
return this.
|
|
583
|
+
if (!this.sp00ky) throw new Error("SyncedDb not initialized");
|
|
584
|
+
return this.sp00ky.remoteClient;
|
|
452
585
|
}
|
|
453
586
|
/**
|
|
454
587
|
* Access the local database service directly
|
|
455
588
|
*/
|
|
456
589
|
get local() {
|
|
457
|
-
if (!this.
|
|
458
|
-
return this.
|
|
590
|
+
if (!this.sp00ky) throw new Error("SyncedDb not initialized");
|
|
591
|
+
return this.sp00ky.localClient;
|
|
459
592
|
}
|
|
460
593
|
/**
|
|
461
594
|
* Access the auth service
|
|
462
595
|
*/
|
|
463
596
|
get auth() {
|
|
464
|
-
if (!this.
|
|
465
|
-
return this.
|
|
597
|
+
if (!this.sp00ky) throw new Error("SyncedDb not initialized");
|
|
598
|
+
return this.sp00ky.auth;
|
|
466
599
|
}
|
|
467
600
|
get pendingMutationCount() {
|
|
468
|
-
if (!this.
|
|
469
|
-
return this.
|
|
601
|
+
if (!this.sp00ky) throw new Error("SyncedDb not initialized");
|
|
602
|
+
return this.sp00ky.pendingMutationCount;
|
|
603
|
+
}
|
|
604
|
+
/** Diagnostic — see `Sp00kyClient.liveRetryCount`. */
|
|
605
|
+
get liveRetryCount() {
|
|
606
|
+
if (!this.sp00ky) throw new Error("SyncedDb not initialized");
|
|
607
|
+
return this.sp00ky.liveRetryCount;
|
|
470
608
|
}
|
|
471
609
|
subscribeToPendingMutations(cb) {
|
|
472
|
-
if (!this.
|
|
473
|
-
return this.
|
|
610
|
+
if (!this.sp00ky) throw new Error("SyncedDb not initialized");
|
|
611
|
+
return this.sp00ky.subscribeToPendingMutations(cb);
|
|
612
|
+
}
|
|
613
|
+
/** Current sync-health snapshot. See {@link useSyncStatus}. */
|
|
614
|
+
get syncHealth() {
|
|
615
|
+
if (!this.sp00ky) throw new Error("SyncedDb not initialized");
|
|
616
|
+
return this.sp00ky.syncHealth;
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Observe sync health. Fires immediately with the current status and again
|
|
620
|
+
* on every healthy↔degraded transition. Prefer the `useSyncStatus` hook in
|
|
621
|
+
* components; this is the imperative escape hatch.
|
|
622
|
+
*/
|
|
623
|
+
subscribeToSyncHealth(cb) {
|
|
624
|
+
if (!this.sp00ky) throw new Error("SyncedDb not initialized");
|
|
625
|
+
return this.sp00ky.subscribeToSyncHealth(cb);
|
|
474
626
|
}
|
|
475
627
|
bucket(name) {
|
|
476
|
-
if (!this.
|
|
477
|
-
return this.
|
|
628
|
+
if (!this.sp00ky) throw new Error("SyncedDb not initialized");
|
|
629
|
+
return this.sp00ky.bucket(name);
|
|
478
630
|
}
|
|
479
631
|
getBucketConfig(name) {
|
|
480
632
|
return this.config.schema.buckets?.find((b) => b.name === name);
|
|
@@ -483,11 +635,14 @@ var SyncedDb = class {
|
|
|
483
635
|
|
|
484
636
|
//#endregion
|
|
485
637
|
exports.RecordId = surrealdb.RecordId;
|
|
486
|
-
exports.
|
|
638
|
+
exports.Sp00kyProvider = Sp00kyProvider;
|
|
487
639
|
exports.SyncedDb = SyncedDb;
|
|
488
640
|
exports.Uuid = surrealdb.Uuid;
|
|
641
|
+
exports.useCrdtField = useCrdtField;
|
|
489
642
|
exports.useDb = useDb;
|
|
490
643
|
exports.useDownloadFile = useDownloadFile;
|
|
644
|
+
exports.useFeatureFlag = useFeatureFlag;
|
|
491
645
|
exports.useFileUpload = useFileUpload;
|
|
492
646
|
exports.useQuery = useQuery;
|
|
647
|
+
exports.useSyncStatus = useSyncStatus;
|
|
493
648
|
//# sourceMappingURL=index.cjs.map
|