@foretag/tanstack-db-surrealdb 0.5.8 → 0.6.1
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/README.md +313 -97
- package/dist/index.d.mts +135 -20
- package/dist/index.d.ts +135 -20
- package/dist/index.js +936 -417
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +933 -419
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -11
package/README.md
CHANGED
|
@@ -1,137 +1,353 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @foretag/tanstack-db-surrealdb
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TanStack DB collection adapter for SurrealDB JS with:
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
5
|
+
- Realtime replication (`LIVE`)
|
|
6
|
+
- Local-first writes
|
|
7
|
+
- Optional E2EE envelopes (`version/algorithm/key_id/nonce/ciphertext`)
|
|
8
|
+
- Optional Loro CRDT replication (`json`, `richtext`)
|
|
9
|
+
- Query-driven sync modes (`eager`, `on-demand`, `progressive`)
|
|
9
10
|
|
|
10
|
-
##
|
|
11
|
+
## Install
|
|
11
12
|
|
|
12
|
-
### NPM
|
|
13
13
|
```sh
|
|
14
|
-
# NPM
|
|
15
14
|
npm install @foretag/tanstack-db-surrealdb
|
|
16
|
-
#
|
|
17
|
-
bun
|
|
15
|
+
# or
|
|
16
|
+
bun add @foretag/tanstack-db-surrealdb
|
|
18
17
|
```
|
|
19
18
|
|
|
20
|
-
##
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
21
|
```ts
|
|
22
|
-
|
|
23
|
-
import { QueryClient } from '@tanstack/
|
|
22
|
+
import { createCollection } from '@tanstack/db';
|
|
23
|
+
import { QueryClient } from '@tanstack/query-core';
|
|
24
24
|
import { Surreal } from 'surrealdb';
|
|
25
|
+
import { surrealCollectionOptions } from '@foretag/tanstack-db-surrealdb';
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
await db.use({ ns: 'ns', db: 'db' });
|
|
27
|
+
const db = new Surreal();
|
|
28
|
+
const queryClient = new QueryClient();
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
type Product = { id: string; name: string; price: number };
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
32
|
+
export const products = createCollection(
|
|
33
|
+
surrealCollectionOptions<Product>({
|
|
34
|
+
db,
|
|
35
|
+
table: { name: 'product' },
|
|
36
|
+
queryClient,
|
|
37
|
+
queryKey: ['product'],
|
|
38
|
+
syncMode: 'eager',
|
|
39
|
+
}),
|
|
40
|
+
);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Adapter API
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
type SurrealCollectionOptions<T> = {
|
|
47
|
+
db: Surreal;
|
|
48
|
+
table: Table | { name: string; relation?: boolean } | string;
|
|
49
|
+
queryClient: QueryClient;
|
|
50
|
+
queryKey: readonly unknown[];
|
|
51
|
+
syncMode?: 'eager' | 'on-demand' | 'progressive';
|
|
52
|
+
e2ee?: {
|
|
53
|
+
enabled: boolean;
|
|
54
|
+
crypto: CryptoProvider;
|
|
55
|
+
aad?: (ctx: { table: string; id: string; kind: 'base'|'update'|'snapshot'; baseTable?: string }) => Uint8Array;
|
|
56
|
+
};
|
|
57
|
+
crdt?: {
|
|
58
|
+
enabled: boolean;
|
|
59
|
+
profile: 'json' | 'richtext';
|
|
60
|
+
updatesTable: Table | { name: string } | string;
|
|
61
|
+
snapshotsTable?: Table | { name: string } | string;
|
|
62
|
+
// Optional overrides. If omitted, adapter uses built-in handlers for `profile`.
|
|
63
|
+
materialize?: (doc: LoroDoc, id: string) => T;
|
|
64
|
+
applyLocalChange?: (doc: LoroDoc, change: { type: 'insert'|'update'|'delete'; value: T }) => void;
|
|
65
|
+
persistMaterializedView?: boolean;
|
|
66
|
+
actor?: string | ((ctx: { id: string; change?: { type: 'insert'|'update'|'delete'; value: T } }) => string | undefined);
|
|
67
|
+
localActorId?: string; // deprecated
|
|
68
|
+
};
|
|
45
69
|
};
|
|
70
|
+
```
|
|
46
71
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
.from({ products })
|
|
60
|
-
.where(({ products }) => eq(products.store, '123'))
|
|
61
|
-
)
|
|
72
|
+
## E2EE
|
|
73
|
+
|
|
74
|
+
Envelope fields stored in Surreal records:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
type EncryptedEnvelope = {
|
|
78
|
+
version: number;
|
|
79
|
+
algorithm: string;
|
|
80
|
+
key_id: string;
|
|
81
|
+
nonce: string;
|
|
82
|
+
ciphertext: string;
|
|
83
|
+
};
|
|
62
84
|
```
|
|
63
85
|
|
|
64
|
-
|
|
86
|
+
Default AAD:
|
|
65
87
|
|
|
66
|
-
|
|
88
|
+
- Base records: `<table>:<record_id>`
|
|
89
|
+
- CRDT updates/snapshots: `<updates_or_snapshots_table>:<base_table>:<doc_id>`
|
|
67
90
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
91
|
+
Included provider:
|
|
92
|
+
|
|
93
|
+
- `WebCryptoAESGCM` (`AES-256-GCM`, versioned envelope)
|
|
94
|
+
|
|
95
|
+
## CRDT Profiles
|
|
96
|
+
|
|
97
|
+
CRDT is managed by profile by default:
|
|
98
|
+
|
|
99
|
+
- `profile: 'json'` uses built-in JSON handlers
|
|
100
|
+
- `profile: 'richtext'` uses built-in richtext handlers
|
|
101
|
+
|
|
102
|
+
Advanced overrides are still available:
|
|
103
|
+
|
|
104
|
+
- `createLoroProfile('json' | 'richtext')`
|
|
105
|
+
- `materialize` and `applyLocalChange` in `crdt` options
|
|
106
|
+
|
|
107
|
+
For CRDT loop-prevention metadata, prefer `crdt.actor` so actor identity can be resolved per doc/write. `localActorId` remains only for backwards compatibility.
|
|
108
|
+
|
|
109
|
+
## CRDT Table Requirements
|
|
110
|
+
|
|
111
|
+
For `crdt.enabled: true`, users must provide:
|
|
112
|
+
|
|
113
|
+
- Base table (`table`) for record identity and optional materialized metadata.
|
|
114
|
+
- Updates table (`crdt.updatesTable`) as append-only CRDT log.
|
|
115
|
+
|
|
116
|
+
Optional:
|
|
117
|
+
|
|
118
|
+
- Snapshots table (`crdt.snapshotsTable`) for compaction and faster hydration.
|
|
119
|
+
|
|
120
|
+
If `crdt.updatesTable` is missing, CRDT mode cannot function.
|
|
121
|
+
|
|
122
|
+
## SQL Templates
|
|
123
|
+
|
|
124
|
+
### Plain
|
|
125
|
+
|
|
126
|
+
```sql
|
|
127
|
+
DEFINE TABLE note SCHEMAFULL;
|
|
128
|
+
DEFINE FIELD title ON note TYPE string;
|
|
129
|
+
DEFINE FIELD body ON note TYPE string;
|
|
130
|
+
DEFINE FIELD updated_at ON note TYPE datetime VALUE time::now();
|
|
131
|
+
DEFINE INDEX note_updated ON note FIELDS updated_at;
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### E2EE-only
|
|
135
|
+
|
|
136
|
+
```sql
|
|
137
|
+
DEFINE TABLE secret_note SCHEMAFULL;
|
|
138
|
+
DEFINE FIELD owner ON secret_note TYPE record<account>;
|
|
139
|
+
DEFINE FIELD updated_at ON secret_note TYPE datetime;
|
|
140
|
+
DEFINE FIELD version ON secret_note TYPE int;
|
|
141
|
+
DEFINE FIELD algorithm ON secret_note TYPE string;
|
|
142
|
+
DEFINE FIELD key_id ON secret_note TYPE string;
|
|
143
|
+
DEFINE FIELD nonce ON secret_note TYPE string;
|
|
144
|
+
DEFINE FIELD ciphertext ON secret_note TYPE string;
|
|
145
|
+
DEFINE INDEX secret_note_owner_updated ON secret_note FIELDS owner, updated_at;
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### CRDT-only
|
|
149
|
+
|
|
150
|
+
```sql
|
|
151
|
+
DEFINE TABLE doc SCHEMAFULL;
|
|
152
|
+
DEFINE FIELD owner ON doc TYPE record<account>;
|
|
153
|
+
DEFINE FIELD updated_at ON doc TYPE datetime;
|
|
154
|
+
DEFINE INDEX doc_owner_updated ON doc FIELDS owner, updated_at;
|
|
155
|
+
|
|
156
|
+
-- Necessary for CRDT updates
|
|
157
|
+
DEFINE TABLE crdt_update SCHEMAFULL;
|
|
158
|
+
DEFINE FIELD doc ON crdt_update TYPE record<doc>;
|
|
159
|
+
DEFINE FIELD ts ON crdt_update TYPE datetime;
|
|
160
|
+
DEFINE FIELD update_bytes ON crdt_update TYPE string;
|
|
161
|
+
DEFINE FIELD actor ON crdt_update TYPE string;
|
|
162
|
+
DEFINE INDEX crdt_doc_ts ON crdt_update FIELDS doc, ts;
|
|
163
|
+
|
|
164
|
+
DEFINE TABLE crdt_snapshot SCHEMAFULL;
|
|
165
|
+
DEFINE FIELD doc ON crdt_snapshot TYPE record<doc>;
|
|
166
|
+
DEFINE FIELD ts ON crdt_snapshot TYPE datetime;
|
|
167
|
+
DEFINE FIELD snapshot_bytes ON crdt_snapshot TYPE string;
|
|
168
|
+
DEFINE INDEX snap_doc_ts ON crdt_snapshot FIELDS doc, ts;
|
|
71
169
|
```
|
|
72
170
|
|
|
73
|
-
|
|
171
|
+
### CRDT + E2EE
|
|
172
|
+
|
|
173
|
+
```sql
|
|
174
|
+
DEFINE TABLE secure_doc SCHEMAFULL;
|
|
175
|
+
DEFINE FIELD owner ON secure_doc TYPE record<account>;
|
|
176
|
+
DEFINE FIELD updated_at ON secure_doc TYPE datetime;
|
|
177
|
+
DEFINE INDEX secure_doc_owner_updated ON secure_doc FIELDS owner, updated_at;
|
|
178
|
+
|
|
179
|
+
DEFINE TABLE crdt_update SCHEMAFULL;
|
|
180
|
+
DEFINE FIELD doc ON crdt_update TYPE record<secure_doc>;
|
|
181
|
+
DEFINE FIELD ts ON crdt_update TYPE datetime;
|
|
182
|
+
DEFINE FIELD actor ON crdt_update TYPE string;
|
|
183
|
+
DEFINE FIELD version ON crdt_update TYPE int;
|
|
184
|
+
DEFINE FIELD algorithm ON crdt_update TYPE string;
|
|
185
|
+
DEFINE FIELD key_id ON crdt_update TYPE string;
|
|
186
|
+
DEFINE FIELD nonce ON crdt_update TYPE string;
|
|
187
|
+
DEFINE FIELD ciphertext ON crdt_update TYPE string;
|
|
188
|
+
DEFINE INDEX crdt_doc_ts ON crdt_update FIELDS doc, ts;
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
If a single `crdt_update` table is shared across multiple base tables, use a union type such as `record<doc> | record<sheet>`.
|
|
192
|
+
|
|
193
|
+
## Permissions Templates
|
|
194
|
+
|
|
195
|
+
The adapter does not manage Surreal table permissions. Define them in schema.
|
|
196
|
+
|
|
197
|
+
### E2EE-only table permissions
|
|
198
|
+
|
|
199
|
+
```sql
|
|
200
|
+
DEFINE TABLE secret_note SCHEMAFULL
|
|
201
|
+
PERMISSIONS
|
|
202
|
+
FOR select, create, update, delete WHERE owner = $auth.id;
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### CRDT updates table permissions (append-only)
|
|
206
|
+
|
|
207
|
+
```sql
|
|
208
|
+
DEFINE TABLE crdt_update SCHEMAFULL
|
|
209
|
+
PERMISSIONS
|
|
210
|
+
FOR select, create WHERE owner = $auth.id
|
|
211
|
+
FOR update, delete NONE;
|
|
212
|
+
|
|
213
|
+
-- Add owner metadata on update rows for simple ACL checks
|
|
214
|
+
DEFINE FIELD owner ON crdt_update TYPE record<account>;
|
|
215
|
+
DEFINE INDEX crdt_owner_doc_ts ON crdt_update FIELDS owner, doc, ts;
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### CRDT snapshots table permissions
|
|
219
|
+
|
|
220
|
+
```sql
|
|
221
|
+
DEFINE TABLE crdt_snapshot SCHEMAFULL
|
|
222
|
+
PERMISSIONS
|
|
223
|
+
FOR select WHERE owner = $auth.id
|
|
224
|
+
FOR create, update, delete NONE;
|
|
225
|
+
|
|
226
|
+
-- Common pattern: clients read snapshots; only trusted backend writes/prunes them
|
|
227
|
+
DEFINE FIELD owner ON crdt_snapshot TYPE record<account>;
|
|
228
|
+
DEFINE INDEX snap_owner_doc_ts ON crdt_snapshot FIELDS owner, doc, ts;
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
If you run snapshot compaction from a trusted backend/service account, grant create/delete to that account only.
|
|
232
|
+
|
|
233
|
+
## Usage Snippets
|
|
234
|
+
|
|
235
|
+
### E2EE-only secret table
|
|
74
236
|
|
|
75
237
|
```ts
|
|
76
|
-
|
|
77
|
-
import wasm from 'vite-plugin-wasm';
|
|
78
|
-
import topLevelAwait from 'vite-plugin-top-level-await';
|
|
238
|
+
const provider = await WebCryptoAESGCM.fromRawKey(rawKey, { kid: 'org-key-2026-01' });
|
|
79
239
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
240
|
+
const secrets = createCollection(
|
|
241
|
+
surrealCollectionOptions<{ id: string; title: string; body: string }>({
|
|
242
|
+
db,
|
|
243
|
+
table: { name: 'secret_note' },
|
|
244
|
+
queryClient,
|
|
245
|
+
queryKey: ['secret-note'],
|
|
246
|
+
syncMode: 'eager',
|
|
247
|
+
e2ee: { enabled: true, crypto: provider },
|
|
248
|
+
}),
|
|
249
|
+
);
|
|
83
250
|
```
|
|
84
251
|
|
|
85
|
-
###
|
|
86
|
-
`next.config.js`
|
|
252
|
+
### CRDT richtext docs
|
|
87
253
|
|
|
88
254
|
```ts
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
255
|
+
const docs = createCollection(
|
|
256
|
+
surrealCollectionOptions<{ id: string; content: string; title?: string }>({
|
|
257
|
+
db,
|
|
258
|
+
table: { name: 'doc' },
|
|
259
|
+
queryClient,
|
|
260
|
+
queryKey: ['doc'],
|
|
261
|
+
syncMode: 'on-demand',
|
|
262
|
+
crdt: {
|
|
263
|
+
enabled: true,
|
|
264
|
+
profile: 'richtext',
|
|
265
|
+
updatesTable: { name: 'crdt_update' },
|
|
266
|
+
snapshotsTable: { name: 'crdt_snapshot' },
|
|
267
|
+
actor: ({ id }) => id.startsWith('team-a') ? 'device:team-a:abc' : 'device:team-b:abc',
|
|
268
|
+
},
|
|
269
|
+
}),
|
|
270
|
+
);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### RecordId model example
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
import { RecordId } from 'surrealdb';
|
|
277
|
+
|
|
278
|
+
type CalendarEvent = {
|
|
279
|
+
id: RecordId<'calendar_event'>;
|
|
280
|
+
owner: RecordId<'account'>;
|
|
281
|
+
title: string;
|
|
282
|
+
start_at: string;
|
|
97
283
|
};
|
|
284
|
+
|
|
285
|
+
await calendarEvents.insert({
|
|
286
|
+
id: new RecordId('calendar_event', 'evt-001'),
|
|
287
|
+
owner: new RecordId('account', 'user-123'),
|
|
288
|
+
title: 'Planning',
|
|
289
|
+
start_at: '2026-02-23T10:00:00.000Z',
|
|
290
|
+
});
|
|
98
291
|
```
|
|
99
292
|
|
|
100
|
-
|
|
293
|
+
Full runnable example: `examples/record-id.ts`.
|
|
101
294
|
|
|
102
|
-
|
|
295
|
+
### On-demand drive listing (query-driven)
|
|
103
296
|
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
297
|
+
```ts
|
|
298
|
+
import { createLiveQueryCollection, eq } from '@tanstack/db';
|
|
299
|
+
|
|
300
|
+
const files = createCollection(
|
|
301
|
+
surrealCollectionOptions<{ id: string; owner: string; updated_at: string; name: string }>({
|
|
302
|
+
db,
|
|
303
|
+
table: { name: 'file' },
|
|
304
|
+
queryClient,
|
|
305
|
+
queryKey: ['file'],
|
|
306
|
+
syncMode: 'on-demand',
|
|
307
|
+
}),
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
const ownerFiles = createLiveQueryCollection((q) =>
|
|
311
|
+
q
|
|
312
|
+
.from({ files })
|
|
313
|
+
.where(({ files }) => eq(files.owner, 'account:abc'))
|
|
314
|
+
.select(({ files }) => files),
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
await ownerFiles.preload();
|
|
113
318
|
```
|
|
114
319
|
|
|
115
|
-
|
|
320
|
+
## Key Wrapping / Multi-Principal Access
|
|
321
|
+
|
|
322
|
+
This adapter expects key management to be provided by your app or KMS. For production shared access (users, teams, orgs), keep using wrapped keys:
|
|
323
|
+
|
|
324
|
+
- Encrypt entity data with a data key.
|
|
325
|
+
- Wrap that data key for each authorized principal (user/team/service/device).
|
|
326
|
+
- Resolve the active key by `kid` at decrypt time.
|
|
327
|
+
- Rotate by issuing a new `kid` and re-wrapping/re-encrypting progressively.
|
|
328
|
+
|
|
329
|
+
The adapter consumes derived keys through `CryptoProvider`; it does not manage wrapping policy for you.
|
|
330
|
+
|
|
331
|
+
## Testing
|
|
332
|
+
|
|
333
|
+
Unit tests (`bun test`) cover:
|
|
334
|
+
|
|
335
|
+
- id/query translation behavior
|
|
336
|
+
- modern eager + on-demand sync controls
|
|
337
|
+
- E2EE envelope/AAD behavior
|
|
338
|
+
- CRDT update append, snapshot hydration, and actor loop prevention
|
|
116
339
|
|
|
117
|
-
|
|
340
|
+
Real SurrealDB integration tests are available and run against a live instance:
|
|
118
341
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
<p>In most cases Tanstack DB is sufficient to handle CRUD operations. However, if you need to implement a distributed system that is offline first, CRDTs are the way to go. Think: Google Docs, Figma Pages, Notion Blocks etc. We recommend you check out <a href='https://www.loro.dev/' target='_blank'>Loro</a> for a deeper understanding.</p>
|
|
122
|
-
</details>
|
|
342
|
+
1. Copy `.env.example` to `.env` and fill connection/auth values.
|
|
343
|
+
2. Run `bun run test:integration`.
|
|
123
344
|
|
|
124
|
-
|
|
125
|
-
<summary><strong>How do I achieve type safety?</strong></summary>
|
|
126
|
-
<p>Using Codegen tools that generate types from your SurrealDB Schema, this means you don't have to manually maintain types for each Collection.</p>
|
|
127
|
-
</details>
|
|
345
|
+
Required env:
|
|
128
346
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
347
|
+
- `SURREAL_URL`
|
|
348
|
+
- `SURREAL_NAMESPACE`
|
|
349
|
+
- `SURREAL_DATABASE`
|
|
350
|
+
- `SURREAL_USERNAME`
|
|
351
|
+
- `SURREAL_PASSWORD`
|
|
133
352
|
|
|
134
|
-
|
|
135
|
-
<summary><strong>Can I reduce the package sizes?</strong></summary>
|
|
136
|
-
<p>They can be reduced, but these steps are very unique based on use-case. Loro ships a WASM binary thats 1 MB Gzipped in size, it's one of the tradeoffs of using this approach. The maintainers up-stream are working on reducing the size of the WASM binary.</p>
|
|
137
|
-
</details>
|
|
353
|
+
`SURREAL_REQUIRE_LIVE=true` (default) enforces LIVE query assertions; set it to `false` if you intentionally use a connection without LIVE support.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,12 +1,67 @@
|
|
|
1
1
|
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { RecordId, Surreal } from 'surrealdb';
|
|
2
|
+
import { CollectionConfig, UtilsRecord, LoadSubsetOptions, OperationConfig, Transaction } from '@tanstack/db';
|
|
3
|
+
import { Table, Surreal, RecordId } from 'surrealdb';
|
|
5
4
|
import { QueryClient } from '@tanstack/query-core';
|
|
5
|
+
import { LoroDoc } from 'loro-crdt';
|
|
6
6
|
|
|
7
|
-
type
|
|
8
|
-
|
|
7
|
+
type EncryptInput = {
|
|
8
|
+
plaintext: Bytes;
|
|
9
|
+
aad?: Bytes;
|
|
10
|
+
v?: number;
|
|
11
|
+
alg?: string;
|
|
12
|
+
kid?: string;
|
|
13
|
+
};
|
|
14
|
+
type DecryptInput = {
|
|
15
|
+
envelope: EncryptedEnvelope;
|
|
16
|
+
aad?: Bytes;
|
|
17
|
+
};
|
|
18
|
+
interface CryptoProvider {
|
|
19
|
+
encrypt(input: EncryptInput): Promise<EncryptedEnvelope>;
|
|
20
|
+
decrypt(input: DecryptInput): Promise<Bytes>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @deprecated Use SurrealE2EEOptions from ../types.
|
|
25
|
+
*/
|
|
26
|
+
interface E2EEConfig<TItem extends object> {
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
serialize?: (item: TItem) => Bytes;
|
|
29
|
+
deserialize?: (bytes: Bytes) => TItem;
|
|
30
|
+
crypto: CryptoProvider;
|
|
31
|
+
fields?: {
|
|
32
|
+
ciphertext: string;
|
|
33
|
+
nonce: string;
|
|
34
|
+
version: string;
|
|
35
|
+
};
|
|
36
|
+
aad?: (ctx: {
|
|
37
|
+
table: string;
|
|
38
|
+
id: string;
|
|
39
|
+
}) => Bytes;
|
|
40
|
+
}
|
|
41
|
+
type EncryptionEnvelope = EncryptedEnvelope;
|
|
42
|
+
type EncryptionAADContext = AADContext;
|
|
43
|
+
type AdapterE2EEOptions = SurrealE2EEOptions;
|
|
44
|
+
|
|
45
|
+
type KeyResolver = (kid: string) => Promise<CryptoKey> | CryptoKey;
|
|
46
|
+
type WebCryptoAESGCMOptions = {
|
|
47
|
+
alg?: string;
|
|
48
|
+
version?: number;
|
|
49
|
+
kid?: string;
|
|
50
|
+
resolveKey?: KeyResolver;
|
|
51
|
+
};
|
|
52
|
+
declare class WebCryptoAESGCM implements CryptoProvider {
|
|
53
|
+
private readonly alg;
|
|
54
|
+
private readonly version;
|
|
55
|
+
private readonly kid;
|
|
56
|
+
private readonly resolveKey;
|
|
57
|
+
constructor(key: CryptoKey, options?: WebCryptoAESGCMOptions);
|
|
58
|
+
static fromRawKey(rawKey: Bytes, options?: Omit<WebCryptoAESGCMOptions, 'resolveKey'>): Promise<WebCryptoAESGCM>;
|
|
59
|
+
private keyFor;
|
|
60
|
+
encrypt(input: EncryptInput): Promise<EncryptedEnvelope>;
|
|
61
|
+
decrypt({ envelope, aad }: DecryptInput): Promise<Bytes>;
|
|
62
|
+
}
|
|
9
63
|
|
|
64
|
+
type Bytes = Uint8Array;
|
|
10
65
|
type WithId<T> = T & {
|
|
11
66
|
id: string | RecordId;
|
|
12
67
|
};
|
|
@@ -18,17 +73,80 @@ type TableOptions = {
|
|
|
18
73
|
name: string;
|
|
19
74
|
relation?: boolean;
|
|
20
75
|
};
|
|
21
|
-
type
|
|
22
|
-
type
|
|
23
|
-
|
|
76
|
+
type TableLike = Table | TableOptions | string;
|
|
77
|
+
type SurrealSubset = LoadSubsetOptions;
|
|
78
|
+
type EncryptedEnvelope = {
|
|
79
|
+
v: number;
|
|
80
|
+
alg: string;
|
|
81
|
+
kid: string;
|
|
82
|
+
n: string;
|
|
83
|
+
ct: string;
|
|
84
|
+
};
|
|
85
|
+
type EnvelopeKind = 'base' | 'update' | 'snapshot';
|
|
86
|
+
type AADContext = {
|
|
87
|
+
table: string;
|
|
88
|
+
id: string;
|
|
89
|
+
kind: EnvelopeKind;
|
|
90
|
+
baseTable?: string;
|
|
91
|
+
};
|
|
92
|
+
type SurrealE2EEOptions = {
|
|
93
|
+
enabled: boolean;
|
|
94
|
+
crypto: CryptoProvider;
|
|
95
|
+
aad?: (ctx: AADContext) => Bytes;
|
|
96
|
+
};
|
|
97
|
+
type LocalChange<T> = {
|
|
98
|
+
type: 'insert' | 'update' | 'delete';
|
|
99
|
+
value: T;
|
|
100
|
+
};
|
|
101
|
+
type CRDTActorContext<T> = {
|
|
102
|
+
id: string;
|
|
103
|
+
change?: LocalChange<T>;
|
|
104
|
+
};
|
|
105
|
+
type SurrealCRDTOptions<T extends object> = {
|
|
106
|
+
enabled: boolean;
|
|
107
|
+
profile: 'json' | 'richtext';
|
|
108
|
+
updatesTable: TableLike;
|
|
109
|
+
snapshotsTable?: TableLike;
|
|
110
|
+
materialize?: (doc: LoroDoc, id: string) => T;
|
|
111
|
+
applyLocalChange?: (doc: LoroDoc, change: LocalChange<T>) => void;
|
|
112
|
+
persistMaterializedView?: boolean;
|
|
113
|
+
actor?: string | ((ctx: CRDTActorContext<T>) => string | undefined);
|
|
114
|
+
/** @deprecated Use `actor` instead. */
|
|
115
|
+
localActorId?: string;
|
|
116
|
+
};
|
|
117
|
+
type AdapterSyncMode = 'eager' | 'on-demand' | 'progressive';
|
|
118
|
+
type SurrealCollectionOptions<T extends object> = Omit<CollectionConfig<T>, 'onInsert' | 'onUpdate' | 'onDelete' | 'sync' | 'getKey' | 'syncMode'> & {
|
|
24
119
|
db: Surreal;
|
|
25
|
-
table:
|
|
26
|
-
syncMode?: SyncMode;
|
|
120
|
+
table: TableLike;
|
|
27
121
|
queryKey: readonly unknown[];
|
|
28
122
|
queryClient: QueryClient;
|
|
29
|
-
|
|
30
|
-
|
|
123
|
+
syncMode?: AdapterSyncMode;
|
|
124
|
+
e2ee?: SurrealE2EEOptions;
|
|
125
|
+
crdt?: SurrealCRDTOptions<T>;
|
|
126
|
+
onError?: (error: unknown) => void;
|
|
31
127
|
};
|
|
128
|
+
type SurrealCollectionOptionsReturn<T extends {
|
|
129
|
+
id: string | RecordId;
|
|
130
|
+
}> = CollectionConfig<T, string, StandardSchemaV1<Omit<T, 'id'> & {
|
|
131
|
+
id?: T['id'];
|
|
132
|
+
}, T>, UtilsRecord> & {
|
|
133
|
+
schema: StandardSchemaV1<Omit<T, 'id'> & {
|
|
134
|
+
id?: T['id'];
|
|
135
|
+
}, T>;
|
|
136
|
+
utils: UtilsRecord;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
interface CRDTProfileAdapter<T extends object> {
|
|
140
|
+
materialize: (doc: LoroDoc, id: string) => T;
|
|
141
|
+
applyLocalChange: (doc: LoroDoc, change: LocalChange<T>) => void;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
type LoroProfile = 'json' | 'richtext';
|
|
145
|
+
declare const materializeLoroJson: <T extends object>(doc: LoroDoc, id: string) => T;
|
|
146
|
+
declare const applyLoroJsonChange: <T extends object>(doc: LoroDoc, change: LocalChange<T>) => void;
|
|
147
|
+
declare const materializeLoroRichtext: <T extends object>(doc: LoroDoc, id: string) => T;
|
|
148
|
+
declare const applyLoroRichtextChange: <T extends object>(doc: LoroDoc, change: LocalChange<T>) => void;
|
|
149
|
+
declare const createLoroProfile: <T extends object = Record<string, unknown>>(profile: LoroProfile) => CRDTProfileAdapter<T>;
|
|
32
150
|
|
|
33
151
|
declare const toRecordKeyString: (rid: RecordId | string) => string;
|
|
34
152
|
|
|
@@ -37,17 +155,14 @@ type MutationInput<T extends {
|
|
|
37
155
|
}> = Omit<T, 'id'> & {
|
|
38
156
|
id?: T['id'];
|
|
39
157
|
};
|
|
40
|
-
|
|
158
|
+
declare function surrealCollectionOptions<T extends SyncedTable<object>>(config: SurrealCollectionOptions<T>): CollectionConfig<T, string, StandardSchemaV1<MutationInput<T>, T>, UtilsRecord> & {
|
|
159
|
+
schema: StandardSchemaV1<MutationInput<T>, T>;
|
|
160
|
+
utils: UtilsRecord;
|
|
161
|
+
};
|
|
41
162
|
declare module '@tanstack/db' {
|
|
42
163
|
interface Collection<T extends object = Record<string, unknown>, TKey extends string | number = string | number, TUtils extends UtilsRecord = UtilsRecord, TSchema extends StandardSchemaV1 = StandardSchemaV1, TInsertInput extends object = T> {
|
|
43
164
|
delete(keys: Array<TKey | RecordId | string> | TKey | RecordId | string, config?: OperationConfig): Transaction<any>;
|
|
44
165
|
}
|
|
45
166
|
}
|
|
46
|
-
declare function surrealCollectionOptions<T extends SyncedTable<object>, S extends Record<string, Container> = {
|
|
47
|
-
[k: string]: never;
|
|
48
|
-
}>({ id, useLoro, onError, db, queryClient, queryKey, syncMode, ...config }: SurrealCollectionConfig): CollectionConfig<T, string, StandardSchemaV1<MutationInput<T>, T>, UtilsRecord> & {
|
|
49
|
-
schema: StandardSchemaV1<MutationInput<T>, T>;
|
|
50
|
-
utils: UtilsRecord;
|
|
51
|
-
};
|
|
52
167
|
|
|
53
|
-
export {
|
|
168
|
+
export { type AADContext, type AdapterE2EEOptions, type AdapterSyncMode, type Bytes, type CRDTActorContext, type CryptoProvider, type DecryptInput, type E2EEConfig, type EncryptInput, type EncryptedEnvelope, type EncryptionAADContext, type EncryptionEnvelope, type EnvelopeKind, type LocalChange, type LoroProfile, type SurrealCRDTOptions, type SurrealCollectionOptions, type SurrealCollectionOptionsReturn, type SurrealE2EEOptions, type SurrealSubset, type SyncedTable, type TableLike, type TableOptions, WebCryptoAESGCM, type WithId, applyLoroJsonChange, applyLoroRichtextChange, createLoroProfile, materializeLoroJson, materializeLoroRichtext, surrealCollectionOptions, toRecordKeyString };
|