@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.
- package/README.md +40 -41
- package/dist/client/index.d.ts +34 -26
- package/dist/client/index.js +904 -732
- package/dist/component/_generated/api.d.ts +2 -2
- package/dist/component/_generated/component.d.ts +84 -27
- package/dist/component/convex.config.d.ts +2 -2
- package/dist/component/mutations.d.ts +131 -0
- package/dist/component/mutations.js +493 -0
- package/dist/component/schema.d.ts +71 -31
- package/dist/component/schema.js +37 -14
- package/dist/server/index.d.ts +58 -47
- package/dist/server/index.js +227 -132
- package/package.json +3 -1
- package/src/client/collection.ts +334 -523
- package/src/client/errors.ts +1 -1
- package/src/client/index.ts +4 -7
- package/src/client/merge.ts +2 -2
- package/src/client/persistence/indexeddb.ts +10 -14
- package/src/client/prose.ts +147 -203
- package/src/client/services/awareness.ts +373 -0
- package/src/client/services/context.ts +114 -0
- package/src/client/services/seq.ts +78 -0
- package/src/client/services/session.ts +20 -0
- package/src/client/services/sync.ts +122 -0
- package/src/client/subdocs.ts +263 -0
- package/src/component/_generated/api.ts +2 -2
- package/src/component/_generated/component.ts +73 -28
- package/src/component/mutations.ts +734 -0
- package/src/component/schema.ts +31 -14
- package/src/server/collection.ts +98 -0
- package/src/server/index.ts +2 -2
- package/src/server/{storage.ts → replicate.ts} +214 -75
- package/dist/component/public.d.ts +0 -83
- package/dist/component/public.js +0 -325
- package/src/client/prose-schema.ts +0 -55
- package/src/client/services/cursor.ts +0 -109
- package/src/component/public.ts +0 -453
- package/src/server/builder.ts +0 -98
- /package/src/client/{replicate.ts → ops.ts} +0 -0
package/README.md
CHANGED
|
@@ -127,16 +127,14 @@ export default defineSchema({
|
|
|
127
127
|
|
|
128
128
|
### Step 3: Create Replication Functions
|
|
129
129
|
|
|
130
|
-
Use `
|
|
130
|
+
Use `collection.create()` to create server-side collection functions:
|
|
131
131
|
|
|
132
132
|
```typescript
|
|
133
133
|
// convex/tasks.ts
|
|
134
|
-
import {
|
|
134
|
+
import { collection } from '@trestleinc/replicate/server';
|
|
135
135
|
import { components } from './_generated/api';
|
|
136
136
|
import type { Task } from '../src/useTasks';
|
|
137
137
|
|
|
138
|
-
const r = replicate(components.replicate);
|
|
139
|
-
|
|
140
138
|
export const {
|
|
141
139
|
stream,
|
|
142
140
|
material,
|
|
@@ -144,18 +142,15 @@ export const {
|
|
|
144
142
|
insert,
|
|
145
143
|
update,
|
|
146
144
|
remove,
|
|
147
|
-
mark,
|
|
148
|
-
compact,
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
peerTimeout: "24h", // Type-safe duration: "30m", "24h", "7d"
|
|
154
|
-
},
|
|
155
|
-
});
|
|
145
|
+
mark,
|
|
146
|
+
compact,
|
|
147
|
+
sessions,
|
|
148
|
+
cursors,
|
|
149
|
+
leave,
|
|
150
|
+
} = collection.create<Task>(components.replicate, 'tasks');
|
|
156
151
|
```
|
|
157
152
|
|
|
158
|
-
**What `
|
|
153
|
+
**What `collection.create()` generates:**
|
|
159
154
|
|
|
160
155
|
- `stream` - Real-time CRDT stream query (cursor-based subscriptions with `seq` numbers)
|
|
161
156
|
- `material` - SSR-friendly query (for server-side rendering)
|
|
@@ -165,6 +160,9 @@ export const {
|
|
|
165
160
|
- `remove` - Dual-storage delete mutation (auto-compacts when threshold exceeded)
|
|
166
161
|
- `mark` - Report sync progress to server (peer tracking for safe compaction)
|
|
167
162
|
- `compact` - Manual compaction trigger (peer-aware, respects active peer sync state)
|
|
163
|
+
- `sessions` - Get connected sessions (presence query)
|
|
164
|
+
- `cursors` - Get cursor positions for collaborative editing
|
|
165
|
+
- `leave` - Explicit disconnect mutation
|
|
168
166
|
|
|
169
167
|
### Step 4: Define Your Collection
|
|
170
168
|
|
|
@@ -296,7 +294,7 @@ For frameworks that support SSR (TanStack Start, Next.js, Remix, SvelteKit), pre
|
|
|
296
294
|
|
|
297
295
|
**Step 1: Prefetch material on the server**
|
|
298
296
|
|
|
299
|
-
Use `ConvexHttpClient` to fetch data during SSR. The `material` query is generated by `
|
|
297
|
+
Use `ConvexHttpClient` to fetch data during SSR. The `material` query is generated by `collection.create()`:
|
|
300
298
|
|
|
301
299
|
```typescript
|
|
302
300
|
// TanStack Start: src/routes/__root.tsx
|
|
@@ -471,12 +469,10 @@ You can customize the behavior of generated functions using optional hooks:
|
|
|
471
469
|
|
|
472
470
|
```typescript
|
|
473
471
|
// convex/tasks.ts
|
|
474
|
-
import {
|
|
472
|
+
import { collection } from '@trestleinc/replicate/server';
|
|
475
473
|
import { components } from './_generated/api';
|
|
476
474
|
import type { Task } from '../src/useTasks';
|
|
477
475
|
|
|
478
|
-
const r = replicate(components.replicate);
|
|
479
|
-
|
|
480
476
|
export const {
|
|
481
477
|
stream,
|
|
482
478
|
material,
|
|
@@ -486,9 +482,10 @@ export const {
|
|
|
486
482
|
remove,
|
|
487
483
|
mark,
|
|
488
484
|
compact,
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
485
|
+
sessions,
|
|
486
|
+
cursors,
|
|
487
|
+
leave,
|
|
488
|
+
} = collection.create<Task>(components.replicate, 'tasks', {
|
|
492
489
|
// Optional hooks for authorization and lifecycle events
|
|
493
490
|
hooks: {
|
|
494
491
|
// Permission checks (eval* hooks validate BEFORE execution, throw to deny)
|
|
@@ -500,7 +497,7 @@ export const {
|
|
|
500
497
|
const userId = await ctx.auth.getUserIdentity();
|
|
501
498
|
if (!userId) throw new Error('Unauthorized');
|
|
502
499
|
},
|
|
503
|
-
evalRemove: async (ctx,
|
|
500
|
+
evalRemove: async (ctx, document) => {
|
|
504
501
|
const userId = await ctx.auth.getUserIdentity();
|
|
505
502
|
if (!userId) throw new Error('Unauthorized');
|
|
506
503
|
},
|
|
@@ -509,7 +506,7 @@ export const {
|
|
|
509
506
|
const userId = await ctx.auth.getUserIdentity();
|
|
510
507
|
if (!userId) throw new Error('Unauthorized');
|
|
511
508
|
},
|
|
512
|
-
evalCompact: async (ctx,
|
|
509
|
+
evalCompact: async (ctx, document) => {
|
|
513
510
|
// Restrict compaction to admin users
|
|
514
511
|
const userId = await ctx.auth.getUserIdentity();
|
|
515
512
|
if (!userId) throw new Error('Unauthorized');
|
|
@@ -519,7 +516,7 @@ export const {
|
|
|
519
516
|
onStream: async (ctx, result) => { /* after stream query */ },
|
|
520
517
|
onInsert: async (ctx, doc) => { /* after insert */ },
|
|
521
518
|
onUpdate: async (ctx, doc) => { /* after update */ },
|
|
522
|
-
onRemove: async (ctx,
|
|
519
|
+
onRemove: async (ctx, document) => { /* after remove */ },
|
|
523
520
|
|
|
524
521
|
// Transform hook (modify documents before returning)
|
|
525
522
|
transform: async (docs) => docs.filter(d => d.isPublic),
|
|
@@ -711,7 +708,7 @@ interface CollectionConfig<T> {
|
|
|
711
708
|
schema: ZodObject; // Required: Zod schema for type inference
|
|
712
709
|
getKey: (item: T) => string | number; // Extract unique key from item
|
|
713
710
|
convexClient: ConvexClient; // Convex client instance
|
|
714
|
-
api: { // API from
|
|
711
|
+
api: { // API from collection.create()
|
|
715
712
|
stream: FunctionReference; // Real-time subscription
|
|
716
713
|
insert: FunctionReference; // Insert mutation
|
|
717
714
|
update: FunctionReference; // Update mutation
|
|
@@ -816,34 +813,33 @@ errors.NonRetriable // Errors that should not be retried (auth, validation)
|
|
|
816
813
|
|
|
817
814
|
### Server-Side (`@trestleinc/replicate/server`)
|
|
818
815
|
|
|
819
|
-
#### `
|
|
816
|
+
#### `collection.create<T>(component, name, options?)`
|
|
820
817
|
|
|
821
|
-
|
|
818
|
+
Creates server-side collection functions that mirror the client-side collection.
|
|
822
819
|
|
|
823
820
|
**Parameters:**
|
|
824
821
|
- `component` - Your Convex component reference (`components.replicate`)
|
|
825
|
-
|
|
826
|
-
|
|
822
|
+
- `name` - Collection name (e.g., `'tasks'`)
|
|
823
|
+
- `options` - Optional configuration for compaction and hooks
|
|
827
824
|
|
|
828
825
|
**Example:**
|
|
829
826
|
```typescript
|
|
830
|
-
import {
|
|
827
|
+
import { collection } from '@trestleinc/replicate/server';
|
|
831
828
|
import { components } from './_generated/api';
|
|
832
829
|
|
|
833
|
-
const
|
|
834
|
-
|
|
830
|
+
export const {
|
|
831
|
+
stream, material, insert, update, remove, recovery, mark, compact, sessions, cursors, leave,
|
|
832
|
+
} = collection.create<Task>(components.replicate, 'tasks');
|
|
835
833
|
```
|
|
836
834
|
|
|
837
|
-
#### `
|
|
835
|
+
#### `CollectionOptions<T>`
|
|
838
836
|
|
|
839
|
-
|
|
837
|
+
Optional configuration for `collection.create()`.
|
|
840
838
|
|
|
841
839
|
**Config:**
|
|
842
840
|
```typescript
|
|
843
|
-
interface
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
// Optional: Compaction settings with type-safe values
|
|
841
|
+
interface CollectionOptions<T> {
|
|
842
|
+
// Optional: Compaction settings
|
|
847
843
|
compaction?: {
|
|
848
844
|
sizeThreshold?: Size; // Size threshold: "100kb", "5mb", "1gb" (default: "5mb")
|
|
849
845
|
peerTimeout?: Duration; // Peer timeout: "30m", "24h", "7d" (default: "24h")
|
|
@@ -854,15 +850,15 @@ interface ReplicateConfig<T> {
|
|
|
854
850
|
// Permission checks (throw to reject)
|
|
855
851
|
evalRead?: (ctx, collection) => Promise<void>;
|
|
856
852
|
evalWrite?: (ctx, doc) => Promise<void>;
|
|
857
|
-
evalRemove?: (ctx,
|
|
853
|
+
evalRemove?: (ctx, document) => Promise<void>;
|
|
858
854
|
evalMark?: (ctx, peerId) => Promise<void>;
|
|
859
|
-
evalCompact?: (ctx,
|
|
855
|
+
evalCompact?: (ctx, document) => Promise<void>;
|
|
860
856
|
|
|
861
857
|
// Lifecycle callbacks (run after operation)
|
|
862
858
|
onStream?: (ctx, result) => Promise<void>;
|
|
863
859
|
onInsert?: (ctx, doc) => Promise<void>;
|
|
864
860
|
onUpdate?: (ctx, doc) => Promise<void>;
|
|
865
|
-
onRemove?: (ctx,
|
|
861
|
+
onRemove?: (ctx, document) => Promise<void>;
|
|
866
862
|
|
|
867
863
|
// Transform hook (modify documents before returning)
|
|
868
864
|
transform?: (docs) => Promise<T[]>;
|
|
@@ -883,6 +879,9 @@ interface ReplicateConfig<T> {
|
|
|
883
879
|
- `remove` - Dual-storage delete mutation (auto-compacts when threshold exceeded)
|
|
884
880
|
- `mark` - Peer sync tracking mutation (reports `syncedSeq` to server)
|
|
885
881
|
- `compact` - Manual compaction mutation (peer-aware, safe for active clients)
|
|
882
|
+
- `sessions` - Get connected sessions (presence query)
|
|
883
|
+
- `cursors` - Get cursor positions for collaborative editing
|
|
884
|
+
- `leave` - Explicit disconnect mutation
|
|
886
885
|
|
|
887
886
|
#### `schema.table(userFields, applyIndexes?)`
|
|
888
887
|
|
package/dist/client/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import * as Y from "yjs";
|
|
|
2
2
|
import { FunctionReference } from "convex/server";
|
|
3
3
|
import { BaseCollectionConfig, Collection, NonSingleResult } from "@tanstack/db";
|
|
4
4
|
import { Context, Effect, Layer } from "effect";
|
|
5
|
+
import { Awareness } from "y-protocols/awareness";
|
|
5
6
|
import { ConvexClient } from "convex/browser";
|
|
6
7
|
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
7
8
|
import * as effect_Types0 from "effect/Types";
|
|
@@ -97,7 +98,7 @@ declare const ProseError_base: new <A extends Record<string, any> = {}>(args: ef
|
|
|
97
98
|
readonly _tag: "ProseError";
|
|
98
99
|
} & Readonly<A>;
|
|
99
100
|
declare class ProseError extends ProseError_base<{
|
|
100
|
-
readonly
|
|
101
|
+
readonly document: string;
|
|
101
102
|
readonly field: string;
|
|
102
103
|
readonly collection: string;
|
|
103
104
|
}> {}
|
|
@@ -113,8 +114,8 @@ declare class NonRetriableError extends Error {
|
|
|
113
114
|
constructor(message: string);
|
|
114
115
|
}
|
|
115
116
|
//#endregion
|
|
116
|
-
//#region src/client/services/
|
|
117
|
-
type
|
|
117
|
+
//#region src/client/services/seq.d.ts
|
|
118
|
+
type Seq = number;
|
|
118
119
|
//#endregion
|
|
119
120
|
//#region src/shared/types.d.ts
|
|
120
121
|
/** ProseMirror-compatible JSON for XmlFragment serialization */
|
|
@@ -152,9 +153,12 @@ type ProseFields<T> = { [K in keyof T]: T[K] extends ProseValue ? K : never }[ke
|
|
|
152
153
|
/** Server-rendered material data for SSR hydration */
|
|
153
154
|
interface Materialized<T> {
|
|
154
155
|
documents: readonly T[];
|
|
155
|
-
cursor?:
|
|
156
|
+
cursor?: Seq;
|
|
156
157
|
count?: number;
|
|
157
|
-
|
|
158
|
+
crdt?: Record<string, {
|
|
159
|
+
bytes: ArrayBuffer;
|
|
160
|
+
seq: number;
|
|
161
|
+
}>;
|
|
158
162
|
}
|
|
159
163
|
/** API object from replicate() */
|
|
160
164
|
interface ConvexCollectionApi {
|
|
@@ -166,6 +170,9 @@ interface ConvexCollectionApi {
|
|
|
166
170
|
mark: FunctionReference<"mutation">;
|
|
167
171
|
compact: FunctionReference<"mutation">;
|
|
168
172
|
material?: FunctionReference<"query">;
|
|
173
|
+
sessions?: FunctionReference<"query">;
|
|
174
|
+
cursors?: FunctionReference<"query">;
|
|
175
|
+
leave?: FunctionReference<"mutation">;
|
|
169
176
|
}
|
|
170
177
|
interface ConvexCollectionConfig<T extends object = object, TSchema extends StandardSchemaV1 = never, TKey extends string | number = string | number> extends BaseCollectionConfig<T, TKey, TSchema> {
|
|
171
178
|
schema: TSchema;
|
|
@@ -173,39 +180,42 @@ interface ConvexCollectionConfig<T extends object = object, TSchema extends Stan
|
|
|
173
180
|
api: ConvexCollectionApi;
|
|
174
181
|
persistence: Persistence;
|
|
175
182
|
material?: Materialized<T>;
|
|
176
|
-
undoCaptureTimeout?: number;
|
|
177
183
|
}
|
|
178
|
-
/**
|
|
184
|
+
/**
|
|
185
|
+
* Binding returned by collection.utils.prose() for collaborative editing.
|
|
186
|
+
*
|
|
187
|
+
* Compatible with TipTap's Collaboration/CollaborationCursor and BlockNote's
|
|
188
|
+
* collaboration config. The editor handles undo/redo internally via y-prosemirror.
|
|
189
|
+
*/
|
|
179
190
|
interface EditorBinding {
|
|
180
|
-
/**
|
|
191
|
+
/** Yjs XmlFragment for content sync */
|
|
181
192
|
readonly fragment: Y.XmlFragment;
|
|
182
|
-
/**
|
|
193
|
+
/**
|
|
194
|
+
* Provider with Yjs Awareness for cursor/presence sync.
|
|
195
|
+
* Pass to CollaborationCursor.configure({ provider: binding.provider })
|
|
196
|
+
* or BlockNote's collaboration.provider
|
|
197
|
+
*/
|
|
183
198
|
readonly provider: {
|
|
184
|
-
readonly awareness:
|
|
199
|
+
readonly awareness: Awareness;
|
|
200
|
+
readonly document: Y.Doc;
|
|
185
201
|
};
|
|
186
|
-
/**
|
|
202
|
+
/** Whether there are unsaved local changes */
|
|
187
203
|
readonly pending: boolean;
|
|
188
|
-
/** Subscribe to pending state changes
|
|
204
|
+
/** Subscribe to pending state changes */
|
|
189
205
|
onPendingChange(callback: (pending: boolean) => void): () => void;
|
|
190
|
-
/**
|
|
191
|
-
|
|
192
|
-
/** Redo the last undone edit */
|
|
193
|
-
redo(): void;
|
|
194
|
-
/** Check if undo is available */
|
|
195
|
-
canUndo(): boolean;
|
|
196
|
-
/** Check if redo is available */
|
|
197
|
-
canRedo(): boolean;
|
|
206
|
+
/** Cleanup - call when unmounting editor */
|
|
207
|
+
destroy(): void;
|
|
198
208
|
}
|
|
199
209
|
/** Utilities exposed on collection.utils */
|
|
200
210
|
interface ConvexCollectionUtils<T extends object> {
|
|
201
211
|
/**
|
|
202
212
|
* Get an editor binding for a prose field.
|
|
203
213
|
* Waits for Y.Doc to be ready (IndexedDB loaded) before returning.
|
|
204
|
-
* @param
|
|
214
|
+
* @param document - The document ID
|
|
205
215
|
* @param field - The prose field name (must be in `prose` config)
|
|
206
216
|
* @returns Promise resolving to EditorBinding
|
|
207
217
|
*/
|
|
208
|
-
prose(
|
|
218
|
+
prose(document: string, field: ProseFields<T>): Promise<EditorBinding>;
|
|
209
219
|
}
|
|
210
220
|
type LazyCollectionConfig<TSchema extends z.ZodObject<z.ZodRawShape>> = Omit<ConvexCollectionConfig<z.infer<TSchema>, TSchema, string>, "persistence" | "material">;
|
|
211
221
|
interface LazyCollection<T extends object> {
|
|
@@ -228,7 +238,7 @@ declare const collection: {
|
|
|
228
238
|
*/
|
|
229
239
|
declare function extract(content: unknown): string;
|
|
230
240
|
//#endregion
|
|
231
|
-
//#region src/client/prose
|
|
241
|
+
//#region src/client/prose.d.ts
|
|
232
242
|
declare function emptyProse(): ProseValue;
|
|
233
243
|
declare function prose$1(): z.ZodType<ProseValue>;
|
|
234
244
|
declare namespace prose$1 {
|
|
@@ -305,10 +315,8 @@ declare const errors: {
|
|
|
305
315
|
readonly CollectionNotReady: typeof CollectionNotReadyError;
|
|
306
316
|
readonly NonRetriable: typeof NonRetriableError;
|
|
307
317
|
};
|
|
308
|
-
declare function empty(): ProseValue;
|
|
309
318
|
declare const prose: typeof prose$1 & {
|
|
310
319
|
extract: typeof extract;
|
|
311
|
-
empty: typeof empty;
|
|
312
320
|
};
|
|
313
321
|
//#endregion
|
|
314
|
-
export { type ConvexCollection, type EditorBinding, type Materialized, type Persistence, type StorageAdapter, collection, errors, persistence, prose };
|
|
322
|
+
export { type ConvexCollection, type EditorBinding, type Materialized, type Persistence, type Seq, type StorageAdapter, collection, errors, persistence, prose };
|