@trestleinc/replicate 1.1.2 → 1.2.0-preview.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.
Files changed (39) hide show
  1. package/README.md +40 -41
  2. package/dist/client/index.d.ts +34 -26
  3. package/dist/client/index.js +904 -732
  4. package/dist/component/_generated/api.d.ts +2 -2
  5. package/dist/component/_generated/component.d.ts +84 -27
  6. package/dist/component/convex.config.d.ts +2 -2
  7. package/dist/component/mutations.d.ts +131 -0
  8. package/dist/component/mutations.js +493 -0
  9. package/dist/component/schema.d.ts +71 -31
  10. package/dist/component/schema.js +37 -14
  11. package/dist/server/index.d.ts +58 -47
  12. package/dist/server/index.js +227 -132
  13. package/package.json +3 -1
  14. package/src/client/collection.ts +334 -523
  15. package/src/client/errors.ts +1 -1
  16. package/src/client/index.ts +4 -7
  17. package/src/client/merge.ts +2 -2
  18. package/src/client/persistence/indexeddb.ts +10 -14
  19. package/src/client/prose.ts +147 -203
  20. package/src/client/services/awareness.ts +373 -0
  21. package/src/client/services/context.ts +114 -0
  22. package/src/client/services/seq.ts +78 -0
  23. package/src/client/services/session.ts +20 -0
  24. package/src/client/services/sync.ts +122 -0
  25. package/src/client/subdocs.ts +263 -0
  26. package/src/component/_generated/api.ts +2 -2
  27. package/src/component/_generated/component.ts +73 -28
  28. package/src/component/mutations.ts +734 -0
  29. package/src/component/schema.ts +31 -14
  30. package/src/server/collection.ts +98 -0
  31. package/src/server/index.ts +2 -2
  32. package/src/server/{storage.ts → replicate.ts} +214 -75
  33. package/dist/component/public.d.ts +0 -83
  34. package/dist/component/public.js +0 -325
  35. package/src/client/prose-schema.ts +0 -55
  36. package/src/client/services/cursor.ts +0 -109
  37. package/src/component/public.ts +0 -453
  38. package/src/server/builder.ts +0 -98
  39. /package/src/client/{replicate.ts → ops.ts} +0 -0
@@ -4,29 +4,46 @@ import { v } from "convex/values";
4
4
  export default defineSchema({
5
5
  documents: defineTable({
6
6
  collection: v.string(),
7
- documentId: v.string(),
8
- crdtBytes: v.bytes(),
7
+ document: v.string(),
8
+ bytes: v.bytes(),
9
9
  seq: v.number(),
10
10
  })
11
11
  .index("by_collection", ["collection"])
12
- .index("by_collection_document", ["collection", "documentId"])
12
+ .index("by_document", ["collection", "document"])
13
13
  .index("by_seq", ["collection", "seq"]),
14
14
 
15
15
  snapshots: defineTable({
16
16
  collection: v.string(),
17
- documentId: v.string(),
18
- snapshotBytes: v.bytes(),
19
- stateVector: v.bytes(),
20
- snapshotSeq: v.number(),
21
- createdAt: v.number(),
22
- }).index("by_document", ["collection", "documentId"]),
17
+ document: v.string(),
18
+ bytes: v.bytes(),
19
+ vector: v.bytes(),
20
+ seq: v.number(),
21
+ created: v.number(),
22
+ }).index("by_document", ["collection", "document"]),
23
23
 
24
- peers: defineTable({
24
+ sessions: defineTable({
25
25
  collection: v.string(),
26
- peerId: v.string(),
27
- lastSyncedSeq: v.number(),
28
- lastSeenAt: v.number(),
26
+ document: v.string(),
27
+ client: v.string(),
28
+ vector: v.optional(v.bytes()),
29
+ connected: v.boolean(),
30
+ seq: v.number(),
31
+ seen: v.number(),
32
+ user: v.optional(v.string()),
33
+ profile: v.optional(v.object({
34
+ name: v.optional(v.string()),
35
+ color: v.optional(v.string()),
36
+ avatar: v.optional(v.string()),
37
+ })),
38
+ cursor: v.optional(v.object({
39
+ anchor: v.any(),
40
+ head: v.any(),
41
+ field: v.optional(v.string()),
42
+ })),
43
+ timeout: v.optional(v.id("_scheduled_functions")),
29
44
  })
30
45
  .index("by_collection", ["collection"])
31
- .index("by_collection_peer", ["collection", "peerId"]),
46
+ .index("by_document", ["collection", "document"])
47
+ .index("by_client", ["collection", "document", "client"])
48
+ .index("by_connected", ["collection", "document", "connected"]),
32
49
  });
@@ -0,0 +1,98 @@
1
+ import type { GenericMutationCtx, GenericQueryCtx, GenericDataModel } from "convex/server";
2
+ import { Replicate } from "$/server/replicate";
3
+ import type { CompactionConfig } from "$/shared/types";
4
+
5
+ export interface CollectionOptions<T extends object> {
6
+ compaction?: Partial<CompactionConfig>;
7
+ hooks?: {
8
+ evalRead?: (ctx: GenericQueryCtx<GenericDataModel>, collection: string) => void | Promise<void>;
9
+ evalWrite?: (ctx: GenericMutationCtx<GenericDataModel>, doc: T) => void | Promise<void>;
10
+ evalRemove?: (ctx: GenericMutationCtx<GenericDataModel>, docId: string) => void | Promise<void>;
11
+ evalMark?: (ctx: GenericMutationCtx<GenericDataModel>, client: string) => void | Promise<void>;
12
+ evalLeave?: (ctx: GenericMutationCtx<GenericDataModel>, client: string) => void | Promise<void>;
13
+ evalCompact?: (
14
+ ctx: GenericMutationCtx<GenericDataModel>,
15
+ document: string,
16
+ ) => void | Promise<void>;
17
+ onStream?: (ctx: GenericQueryCtx<GenericDataModel>, result: any) => void | Promise<void>;
18
+ onInsert?: (ctx: GenericMutationCtx<GenericDataModel>, doc: T) => void | Promise<void>;
19
+ onUpdate?: (ctx: GenericMutationCtx<GenericDataModel>, doc: T) => void | Promise<void>;
20
+ onRemove?: (ctx: GenericMutationCtx<GenericDataModel>, docId: string) => void | Promise<void>;
21
+ transform?: (docs: T[]) => T[] | Promise<T[]>;
22
+ };
23
+ }
24
+
25
+ function createCollection<T extends object>(
26
+ component: any,
27
+ name: string,
28
+ options?: CollectionOptions<T>,
29
+ ) {
30
+ return createCollectionInternal<T>(component, name, options);
31
+ }
32
+
33
+ export const collection = {
34
+ create: createCollection,
35
+ } as const;
36
+
37
+ function createCollectionInternal<T extends object>(
38
+ component: any,
39
+ name: string,
40
+ options?: CollectionOptions<T>,
41
+ ) {
42
+ const storage = new Replicate<T>(component, name, options?.compaction);
43
+
44
+ const hooks = options?.hooks;
45
+
46
+ return {
47
+ __collection: name,
48
+
49
+ stream: storage.createStreamQuery({
50
+ evalRead: hooks?.evalRead,
51
+ onStream: hooks?.onStream,
52
+ }),
53
+
54
+ material: storage.createSSRQuery({
55
+ evalRead: hooks?.evalRead,
56
+ transform: hooks?.transform,
57
+ }),
58
+
59
+ recovery: storage.createRecoveryQuery({
60
+ evalRead: hooks?.evalRead,
61
+ }),
62
+
63
+ insert: storage.createInsertMutation({
64
+ evalWrite: hooks?.evalWrite,
65
+ onInsert: hooks?.onInsert,
66
+ }),
67
+
68
+ update: storage.createUpdateMutation({
69
+ evalWrite: hooks?.evalWrite,
70
+ onUpdate: hooks?.onUpdate,
71
+ }),
72
+
73
+ remove: storage.createRemoveMutation({
74
+ evalRemove: hooks?.evalRemove,
75
+ onRemove: hooks?.onRemove,
76
+ }),
77
+
78
+ mark: storage.createMarkMutation({
79
+ evalWrite: hooks?.evalMark,
80
+ }),
81
+
82
+ compact: storage.createCompactMutation({
83
+ evalWrite: hooks?.evalCompact,
84
+ }),
85
+
86
+ sessions: storage.createSessionsQuery({
87
+ evalRead: hooks?.evalRead,
88
+ }),
89
+
90
+ cursors: storage.createCursorsQuery({
91
+ evalRead: hooks?.evalRead,
92
+ }),
93
+
94
+ leave: storage.createLeaveMutation({
95
+ evalWrite: hooks?.evalLeave,
96
+ }),
97
+ };
98
+ }
@@ -1,5 +1,5 @@
1
- export { replicate } from "$/server/builder";
2
- export type { ReplicateConfig } from "$/server/builder";
1
+ export { collection } from "$/server/collection";
2
+ export type { CollectionOptions } from "$/server/collection";
3
3
 
4
4
  import { table, prose } from "$/server/schema";
5
5
 
@@ -34,32 +34,34 @@ export class Replicate<T extends object> {
34
34
 
35
35
  return queryGeneric({
36
36
  args: {
37
- cursor: v.number(),
37
+ seq: v.number(),
38
38
  limit: v.optional(v.number()),
39
- sizeThreshold: v.optional(v.number()),
39
+ threshold: v.optional(v.number()),
40
40
  },
41
41
  returns: v.object({
42
42
  changes: v.array(
43
43
  v.object({
44
- documentId: v.string(),
45
- crdtBytes: v.bytes(),
44
+ document: v.string(),
45
+ bytes: v.bytes(),
46
46
  seq: v.number(),
47
- operationType: v.string(),
47
+ type: v.string(),
48
48
  }),
49
49
  ),
50
- cursor: v.number(),
51
- hasMore: v.boolean(),
52
- compact: v.optional(v.string()),
50
+ seq: v.number(),
51
+ more: v.boolean(),
52
+ compact: v.optional(v.object({
53
+ documents: v.array(v.string()),
54
+ })),
53
55
  }),
54
56
  handler: async (ctx, args) => {
55
57
  if (opts?.evalRead) {
56
58
  await opts.evalRead(ctx, collection);
57
59
  }
58
- const result = await ctx.runQuery(component.public.stream, {
60
+ const result = await ctx.runQuery(component.mutations.stream, {
59
61
  collection,
60
- cursor: args.cursor,
62
+ seq: args.seq,
61
63
  limit: args.limit,
62
- sizeThreshold: args.sizeThreshold,
64
+ threshold: args.threshold,
63
65
  });
64
66
 
65
67
  if (opts?.onStream) {
@@ -83,9 +85,12 @@ export class Replicate<T extends object> {
83
85
  args: {},
84
86
  returns: v.object({
85
87
  documents: v.any(),
86
- cursor: v.optional(v.number()),
87
88
  count: v.number(),
88
- crdtBytes: v.optional(v.bytes()),
89
+ crdt: v.optional(v.record(v.string(), v.object({
90
+ bytes: v.bytes(),
91
+ seq: v.number(),
92
+ }))),
93
+ cursor: v.optional(v.number()),
89
94
  }),
90
95
  handler: async (ctx) => {
91
96
  if (opts?.evalRead) {
@@ -98,24 +103,35 @@ export class Replicate<T extends object> {
98
103
 
99
104
  const response: {
100
105
  documents: T[];
101
- cursor?: number;
102
106
  count: number;
103
- crdtBytes?: ArrayBuffer;
107
+ crdt?: Record<string, { bytes: ArrayBuffer; seq: number }>;
108
+ cursor?: number;
104
109
  } = {
105
110
  documents: docs,
106
111
  count: docs.length,
107
112
  };
108
113
 
109
- if (opts?.includeCRDTState) {
110
- const crdtState = await ctx.runQuery(component.public.getInitialState, {
111
- collection,
112
- });
113
-
114
- if (crdtState) {
115
- response.crdtBytes = crdtState.crdtBytes;
116
- response.cursor = crdtState.cursor;
114
+ if (opts?.includeCRDTState && docs.length > 0) {
115
+ const crdt: Record<string, { bytes: ArrayBuffer; seq: number }> = {};
116
+ let maxSeq = 0;
117
+
118
+ for (const doc of docs) {
119
+ const docId = (doc as { id: string }).id;
120
+ const state = await ctx.runQuery(component.mutations.getDocumentState, {
121
+ collection,
122
+ document: docId,
123
+ });
124
+
125
+ if (state) {
126
+ crdt[docId] = { bytes: state.bytes, seq: state.seq };
127
+ maxSeq = Math.max(maxSeq, state.seq);
128
+ }
117
129
  }
130
+
131
+ response.crdt = crdt;
132
+ response.cursor = maxSeq;
118
133
  }
134
+
119
135
  return response;
120
136
  },
121
137
  });
@@ -130,30 +146,30 @@ export class Replicate<T extends object> {
130
146
 
131
147
  return mutationGeneric({
132
148
  args: {
133
- documentId: v.string(),
134
- crdtBytes: v.bytes(),
135
- materializedDoc: v.any(),
149
+ document: v.string(),
150
+ bytes: v.bytes(),
151
+ material: v.any(),
136
152
  },
137
153
  returns: v.object({
138
154
  success: v.boolean(),
139
155
  seq: v.number(),
140
156
  }),
141
157
  handler: async (ctx, args) => {
142
- const doc = args.materializedDoc as T;
158
+ const doc = args.material as T;
143
159
 
144
160
  if (opts?.evalWrite) {
145
161
  await opts.evalWrite(ctx, doc);
146
162
  }
147
163
 
148
- const result = await ctx.runMutation(component.public.insertDocument, {
164
+ const result = await ctx.runMutation(component.mutations.insertDocument, {
149
165
  collection,
150
- documentId: args.documentId,
151
- crdtBytes: args.crdtBytes,
166
+ document: args.document,
167
+ bytes: args.bytes,
152
168
  });
153
169
 
154
170
  await ctx.db.insert(collection, {
155
- id: args.documentId,
156
- ...(args.materializedDoc as object),
171
+ id: args.document,
172
+ ...(args.material as object),
157
173
  timestamp: Date.now(),
158
174
  });
159
175
 
@@ -178,35 +194,35 @@ export class Replicate<T extends object> {
178
194
 
179
195
  return mutationGeneric({
180
196
  args: {
181
- documentId: v.string(),
182
- crdtBytes: v.bytes(),
183
- materializedDoc: v.any(),
197
+ document: v.string(),
198
+ bytes: v.bytes(),
199
+ material: v.any(),
184
200
  },
185
201
  returns: v.object({
186
202
  success: v.boolean(),
187
203
  seq: v.number(),
188
204
  }),
189
205
  handler: async (ctx, args) => {
190
- const doc = args.materializedDoc as T;
206
+ const doc = args.material as T;
191
207
 
192
208
  if (opts?.evalWrite) {
193
209
  await opts.evalWrite(ctx, doc);
194
210
  }
195
211
 
196
- const result = await ctx.runMutation(component.public.updateDocument, {
212
+ const result = await ctx.runMutation(component.mutations.updateDocument, {
197
213
  collection,
198
- documentId: args.documentId,
199
- crdtBytes: args.crdtBytes,
214
+ document: args.document,
215
+ bytes: args.bytes,
200
216
  });
201
217
 
202
218
  const existing = await ctx.db
203
219
  .query(collection)
204
- .withIndex("by_doc_id", q => q.eq("id", args.documentId))
220
+ .withIndex("by_doc_id", q => q.eq("id", args.document))
205
221
  .first();
206
222
 
207
223
  if (existing) {
208
224
  await ctx.db.patch(existing._id, {
209
- ...(args.materializedDoc as object),
225
+ ...(args.material as object),
210
226
  timestamp: Date.now(),
211
227
  });
212
228
  }
@@ -232,28 +248,27 @@ export class Replicate<T extends object> {
232
248
 
233
249
  return mutationGeneric({
234
250
  args: {
235
- documentId: v.string(),
236
- crdtBytes: v.bytes(),
251
+ document: v.string(),
252
+ bytes: v.bytes(),
237
253
  },
238
254
  returns: v.object({
239
255
  success: v.boolean(),
240
256
  seq: v.number(),
241
257
  }),
242
258
  handler: async (ctx, args) => {
243
- const documentId = args.documentId;
244
259
  if (opts?.evalRemove) {
245
- await opts.evalRemove(ctx, documentId);
260
+ await opts.evalRemove(ctx, args.document);
246
261
  }
247
262
 
248
- const result = await ctx.runMutation(component.public.deleteDocument, {
263
+ const result = await ctx.runMutation(component.mutations.deleteDocument, {
249
264
  collection,
250
- documentId: documentId,
251
- crdtBytes: args.crdtBytes,
265
+ document: args.document,
266
+ bytes: args.bytes,
252
267
  });
253
268
 
254
269
  const existing = await ctx.db
255
270
  .query(collection)
256
- .withIndex("by_doc_id", q => q.eq("id", documentId))
271
+ .withIndex("by_doc_id", q => q.eq("id", args.document))
257
272
  .first();
258
273
 
259
274
  if (existing) {
@@ -261,7 +276,7 @@ export class Replicate<T extends object> {
261
276
  }
262
277
 
263
278
  if (opts?.onRemove) {
264
- await opts.onRemove(ctx, documentId);
279
+ await opts.onRemove(ctx, args.document);
265
280
  }
266
281
 
267
282
  return {
@@ -273,26 +288,150 @@ export class Replicate<T extends object> {
273
288
  }
274
289
 
275
290
  createMarkMutation(opts?: {
276
- evalWrite?: (ctx: GenericMutationCtx<GenericDataModel>, peerId: string) => void | Promise<void>;
291
+ evalWrite?: (ctx: GenericMutationCtx<GenericDataModel>, client: string) => void | Promise<void>;
277
292
  }) {
278
293
  const component = this.component;
279
294
  const collection = this.collectionName;
280
295
 
281
296
  return mutationGeneric({
282
297
  args: {
283
- peerId: v.string(),
284
- syncedSeq: v.number(),
298
+ document: v.string(),
299
+ client: v.string(),
300
+ seq: v.optional(v.number()),
301
+ vector: v.optional(v.bytes()),
302
+ user: v.optional(v.string()),
303
+ profile: v.optional(v.object({
304
+ name: v.optional(v.string()),
305
+ color: v.optional(v.string()),
306
+ avatar: v.optional(v.string()),
307
+ })),
308
+ cursor: v.optional(v.object({
309
+ anchor: v.any(),
310
+ head: v.any(),
311
+ field: v.optional(v.string()),
312
+ })),
313
+ interval: v.optional(v.number()),
285
314
  },
286
315
  returns: v.null(),
287
316
  handler: async (ctx, args) => {
288
317
  if (opts?.evalWrite) {
289
- await opts.evalWrite(ctx, args.peerId);
318
+ await opts.evalWrite(ctx, args.client);
290
319
  }
291
320
 
292
- await ctx.runMutation(component.public.mark, {
321
+ await ctx.runMutation(component.mutations.mark, {
293
322
  collection,
294
- peerId: args.peerId,
295
- syncedSeq: args.syncedSeq,
323
+ document: args.document,
324
+ client: args.client,
325
+ seq: args.seq,
326
+ vector: args.vector,
327
+ user: args.user,
328
+ profile: args.profile,
329
+ cursor: args.cursor,
330
+ interval: args.interval,
331
+ });
332
+
333
+ return null;
334
+ },
335
+ });
336
+ }
337
+
338
+ createSessionsQuery(opts?: {
339
+ evalRead?: (ctx: GenericQueryCtx<GenericDataModel>, collection: string) => void | Promise<void>;
340
+ }) {
341
+ const component = this.component;
342
+ const collection = this.collectionName;
343
+
344
+ return queryGeneric({
345
+ args: {
346
+ document: v.string(),
347
+ connected: v.optional(v.boolean()),
348
+ exclude: v.optional(v.string()),
349
+ group: v.optional(v.boolean()),
350
+ },
351
+ returns: v.array(v.object({
352
+ client: v.string(),
353
+ document: v.string(),
354
+ user: v.optional(v.string()),
355
+ profile: v.optional(v.any()),
356
+ cursor: v.optional(v.object({
357
+ anchor: v.any(),
358
+ head: v.any(),
359
+ field: v.optional(v.string()),
360
+ })),
361
+ seen: v.number(),
362
+ })),
363
+ handler: async (ctx, args) => {
364
+ if (opts?.evalRead) {
365
+ await opts.evalRead(ctx, collection);
366
+ }
367
+
368
+ return await ctx.runQuery(component.mutations.sessions, {
369
+ collection,
370
+ document: args.document,
371
+ connected: args.connected,
372
+ exclude: args.exclude,
373
+ group: args.group,
374
+ });
375
+ },
376
+ });
377
+ }
378
+
379
+ createCursorsQuery(opts?: {
380
+ evalRead?: (ctx: GenericQueryCtx<GenericDataModel>, collection: string) => void | Promise<void>;
381
+ }) {
382
+ const component = this.component;
383
+ const collection = this.collectionName;
384
+
385
+ return queryGeneric({
386
+ args: {
387
+ document: v.string(),
388
+ exclude: v.optional(v.string()),
389
+ },
390
+ returns: v.array(v.object({
391
+ client: v.string(),
392
+ user: v.optional(v.string()),
393
+ profile: v.optional(v.any()),
394
+ cursor: v.object({
395
+ anchor: v.any(),
396
+ head: v.any(),
397
+ field: v.optional(v.string()),
398
+ }),
399
+ })),
400
+ handler: async (ctx, args) => {
401
+ if (opts?.evalRead) {
402
+ await opts.evalRead(ctx, collection);
403
+ }
404
+
405
+ return await ctx.runQuery(component.mutations.cursors, {
406
+ collection,
407
+ document: args.document,
408
+ exclude: args.exclude,
409
+ });
410
+ },
411
+ });
412
+ }
413
+
414
+ createLeaveMutation(opts?: {
415
+ evalWrite?: (ctx: GenericMutationCtx<GenericDataModel>, client: string) => void | Promise<void>;
416
+ }) {
417
+ const component = this.component;
418
+ const collection = this.collectionName;
419
+
420
+ return mutationGeneric({
421
+ args: {
422
+ document: v.string(),
423
+ client: v.string(),
424
+ },
425
+ returns: v.null(),
426
+ handler: async (ctx, args) => {
427
+ if (opts?.evalWrite) {
428
+ await opts.evalWrite(ctx, args.client);
429
+ }
430
+
431
+ await ctx.runMutation(component.mutations.leave, {
432
+ collection,
433
+ document: args.document,
434
+ client: args.client,
296
435
  });
297
436
 
298
437
  return null;
@@ -303,7 +442,7 @@ export class Replicate<T extends object> {
303
442
  createCompactMutation(opts?: {
304
443
  evalWrite?: (
305
444
  ctx: GenericMutationCtx<GenericDataModel>,
306
- documentId: string,
445
+ document: string,
307
446
  ) => void | Promise<void>;
308
447
  }) {
309
448
  const component = this.component;
@@ -311,55 +450,55 @@ export class Replicate<T extends object> {
311
450
 
312
451
  return mutationGeneric({
313
452
  args: {
314
- documentId: v.string(),
315
- snapshotBytes: v.bytes(),
316
- stateVector: v.bytes(),
317
- peerTimeout: v.optional(v.number()),
453
+ document: v.string(),
318
454
  },
319
455
  returns: v.object({
320
456
  success: v.boolean(),
321
457
  removed: v.number(),
322
458
  retained: v.number(),
459
+ size: v.number(),
323
460
  }),
324
461
  handler: async (ctx, args) => {
325
462
  if (opts?.evalWrite) {
326
- await opts.evalWrite(ctx, args.documentId);
463
+ await opts.evalWrite(ctx, args.document);
327
464
  }
328
465
 
329
- return await ctx.runMutation(component.public.compact, {
466
+ return await ctx.runMutation(component.mutations.compact, {
330
467
  collection,
331
- documentId: args.documentId,
332
- snapshotBytes: args.snapshotBytes,
333
- stateVector: args.stateVector,
334
- peerTimeout: args.peerTimeout,
468
+ document: args.document,
335
469
  });
336
470
  },
337
471
  });
338
472
  }
339
473
 
340
474
  createRecoveryQuery(opts?: {
341
- evalRead?: (ctx: GenericQueryCtx<GenericDataModel>, collection: string) => void | Promise<void>;
475
+ evalRead?: (
476
+ ctx: GenericQueryCtx<GenericDataModel>,
477
+ collection: string,
478
+ document: string,
479
+ ) => void | Promise<void>;
342
480
  }) {
343
481
  const component = this.component;
344
482
  const collection = this.collectionName;
345
483
 
346
484
  return queryGeneric({
347
485
  args: {
348
- clientStateVector: v.bytes(),
486
+ document: v.string(),
487
+ vector: v.bytes(),
349
488
  },
350
489
  returns: v.object({
351
490
  diff: v.optional(v.bytes()),
352
- serverStateVector: v.bytes(),
353
- cursor: v.number(),
491
+ vector: v.bytes(),
354
492
  }),
355
493
  handler: async (ctx, args) => {
356
494
  if (opts?.evalRead) {
357
- await opts.evalRead(ctx, collection);
495
+ await opts.evalRead(ctx, collection, args.document);
358
496
  }
359
497
 
360
- return await ctx.runQuery(component.public.recovery, {
498
+ return await ctx.runQuery(component.mutations.recovery, {
361
499
  collection,
362
- clientStateVector: args.clientStateVector,
500
+ document: args.document,
501
+ vector: args.vector,
363
502
  });
364
503
  },
365
504
  });