0
# Snapshot System
1
2
Time-travel functionality for document state at specific points with diff capabilities. Yjs snapshots provide immutable views of document state that enable version control, branching, and historical analysis.
3
4
## Capabilities
5
6
### Snapshot Class
7
8
Immutable representation of document state at a specific point in time.
9
10
```typescript { .api }
11
/**
12
* Immutable snapshot of document state
13
*/
14
class Snapshot {
15
/** Delete set at snapshot time */
16
readonly ds: DeleteSet;
17
18
/** State vector at snapshot time */
19
readonly sv: Map<number, number>;
20
}
21
```
22
23
### Creating Snapshots
24
25
Functions for creating snapshots from documents and components.
26
27
```typescript { .api }
28
/**
29
* Create snapshot of current document state
30
* @param doc - Document to snapshot
31
* @returns Snapshot of current state
32
*/
33
function snapshot(doc: Doc): Snapshot;
34
35
/**
36
* Create snapshot from delete set and state vector
37
* @param ds - Delete set at snapshot time
38
* @param sv - State vector at snapshot time
39
* @returns Constructed snapshot
40
*/
41
function createSnapshot(ds: DeleteSet, sv: Map<number, number>): Snapshot;
42
43
/**
44
* Empty snapshot constant representing initial state
45
*/
46
const emptySnapshot: Snapshot;
47
```
48
49
**Usage Examples:**
50
51
```typescript
52
import * as Y from "yjs";
53
54
const doc = new Y.Doc();
55
const yarray = doc.getArray("items");
56
57
// Take initial snapshot
58
const initialSnapshot = Y.snapshot(doc);
59
60
// Make changes
61
yarray.push(["item1", "item2"]);
62
63
// Take snapshot after changes
64
const afterChangesSnapshot = Y.snapshot(doc);
65
66
// Take another snapshot after more changes
67
yarray.delete(0, 1);
68
const finalSnapshot = Y.snapshot(doc);
69
70
console.log("Snapshots are different:",
71
!Y.equalSnapshots(initialSnapshot, afterChangesSnapshot));
72
```
73
74
### Snapshot Comparison
75
76
Functions for comparing snapshots and checking relationships.
77
78
```typescript { .api }
79
/**
80
* Compare two snapshots for equality
81
* @param snap1 - First snapshot
82
* @param snap2 - Second snapshot
83
* @returns True if snapshots represent the same state
84
*/
85
function equalSnapshots(snap1: Snapshot, snap2: Snapshot): boolean;
86
87
/**
88
* Check if snapshot contains a specific update
89
* @param snapshot - Snapshot to check
90
* @param update - Binary update to check for
91
* @returns True if update is contained in snapshot
92
*/
93
function snapshotContainsUpdate(snapshot: Snapshot, update: Uint8Array): boolean;
94
```
95
96
**Usage Examples:**
97
98
```typescript
99
import * as Y from "yjs";
100
101
const doc = new Y.Doc();
102
const yarray = doc.getArray("items");
103
104
// Create snapshots at different states
105
const snap1 = Y.snapshot(doc);
106
107
yarray.push(["item1"]);
108
const snap2 = Y.snapshot(doc);
109
110
yarray.push(["item2"]);
111
const snap3 = Y.snapshot(doc);
112
113
// Compare snapshots
114
console.log("snap1 equals snap2:", Y.equalSnapshots(snap1, snap2)); // false
115
console.log("snap2 equals snap3:", Y.equalSnapshots(snap2, snap3)); // false
116
117
// Check if update is contained in snapshot
118
const update = Y.encodeStateAsUpdate(doc);
119
console.log("Current update in snap3:", Y.snapshotContainsUpdate(snap3, update)); // true
120
console.log("Current update in snap1:", Y.snapshotContainsUpdate(snap1, update)); // false
121
```
122
123
### Document Creation from Snapshots
124
125
Functions for creating new documents from snapshot states.
126
127
```typescript { .api }
128
/**
129
* Create document from snapshot state
130
* @param originDoc - Original document the snapshot was taken from
131
* @param snapshot - Snapshot to recreate state from
132
* @param newDoc - Optional existing document to apply state to
133
* @returns Document in the snapshot state
134
*/
135
function createDocFromSnapshot(originDoc: Doc, snapshot: Snapshot, newDoc?: Doc): Doc;
136
```
137
138
**Usage Examples:**
139
140
```typescript
141
import * as Y from "yjs";
142
143
const doc = new Y.Doc();
144
const yarray = doc.getArray("items");
145
146
// Build up document state
147
yarray.push(["item1", "item2", "item3"]);
148
const snapshot1 = Y.snapshot(doc);
149
150
yarray.delete(1, 1); // Remove "item2"
151
const snapshot2 = Y.snapshot(doc);
152
153
yarray.push(["item4"]);
154
const finalSnapshot = Y.snapshot(doc);
155
156
// Create documents from different snapshots
157
const docFromSnap1 = Y.createDocFromSnapshot(doc, snapshot1);
158
const docFromSnap2 = Y.createDocFromSnapshot(doc, snapshot2);
159
160
console.log("Snap1 array:", docFromSnap1.getArray("items").toArray()); // ["item1", "item2", "item3"]
161
console.log("Snap2 array:", docFromSnap2.getArray("items").toArray()); // ["item1", "item3"]
162
console.log("Current array:", doc.getArray("items").toArray()); // ["item1", "item3", "item4"]
163
164
// Create document with specific client ID
165
const newDoc = new Y.Doc({ clientID: 999 });
166
const restoredDoc = Y.createDocFromSnapshot(doc, snapshot1, newDoc);
167
console.log("Restored doc client ID:", restoredDoc.clientID); // 999
168
```
169
170
### Snapshot Serialization
171
172
Functions for encoding and decoding snapshots for storage or transmission.
173
174
```typescript { .api }
175
/**
176
* Encode snapshot to binary format (V1)
177
* @param snapshot - Snapshot to encode
178
* @returns Binary representation
179
*/
180
function encodeSnapshot(snapshot: Snapshot): Uint8Array;
181
182
/**
183
* Decode snapshot from binary format (V1)
184
* @param buf - Binary snapshot data
185
* @returns Decoded snapshot
186
*/
187
function decodeSnapshot(buf: Uint8Array): Snapshot;
188
189
/**
190
* Encode snapshot to binary format (V2, optimized)
191
* @param snapshot - Snapshot to encode
192
* @returns Binary representation in V2 format
193
*/
194
function encodeSnapshotV2(snapshot: Snapshot): Uint8Array;
195
196
/**
197
* Decode snapshot from binary format (V2)
198
* @param buf - Binary snapshot data in V2 format
199
* @returns Decoded snapshot
200
*/
201
function decodeSnapshotV2(buf: Uint8Array): Snapshot;
202
```
203
204
**Usage Examples:**
205
206
```typescript
207
import * as Y from "yjs";
208
209
const doc = new Y.Doc();
210
const yarray = doc.getArray("items");
211
yarray.push(["item1", "item2", "item3"]);
212
213
const snapshot = Y.snapshot(doc);
214
215
// Encode snapshot (V1)
216
const encodedV1 = Y.encodeSnapshot(snapshot);
217
console.log("V1 encoded size:", encodedV1.length);
218
219
// Encode snapshot (V2, typically smaller)
220
const encodedV2 = Y.encodeSnapshotV2(snapshot);
221
console.log("V2 encoded size:", encodedV2.length);
222
223
// Decode snapshots
224
const decodedV1 = Y.decodeSnapshot(encodedV1);
225
const decodedV2 = Y.decodeSnapshotV2(encodedV2);
226
227
// Verify equality
228
console.log("V1 decoded equals original:", Y.equalSnapshots(snapshot, decodedV1));
229
console.log("V2 decoded equals original:", Y.equalSnapshots(snapshot, decodedV2));
230
231
// Store/retrieve from storage
232
localStorage.setItem("document-snapshot", JSON.stringify(Array.from(encodedV2)));
233
const stored = new Uint8Array(JSON.parse(localStorage.getItem("document-snapshot")!));
234
const restoredSnapshot = Y.decodeSnapshotV2(stored);
235
```
236
237
### Snapshot-based Type Operations
238
239
Functions for getting type values at specific snapshot states.
240
241
```typescript { .api }
242
/**
243
* Convert YArray to JavaScript array at snapshot state
244
* @param type - YArray to convert
245
* @param snapshot - Snapshot state to use
246
* @returns Array content at snapshot time
247
*/
248
function typeListToArraySnapshot(type: YArray<any>, snapshot: Snapshot): Array<any>;
249
250
/**
251
* Get YMap value at snapshot state
252
* @param type - YMap to query
253
* @param key - Key to get value for
254
* @param snapshot - Snapshot state to use
255
* @returns Value at snapshot time or undefined
256
*/
257
function typeMapGetSnapshot(type: YMap<any>, key: string, snapshot: Snapshot): any;
258
259
/**
260
* Get all YMap entries at snapshot state
261
* @param type - YMap to query
262
* @param snapshot - Snapshot state to use
263
* @returns Object with all key-value pairs at snapshot time
264
*/
265
function typeMapGetAllSnapshot(type: YMap<any>, snapshot: Snapshot): { [key: string]: any };
266
```
267
268
**Usage Examples:**
269
270
```typescript
271
import * as Y from "yjs";
272
273
const doc = new Y.Doc();
274
const yarray = doc.getArray("items");
275
const ymap = doc.getMap("metadata");
276
277
// Build initial state
278
yarray.push(["item1", "item2"]);
279
ymap.set("count", 2);
280
ymap.set("created", "2023-01-01");
281
const snapshot1 = Y.snapshot(doc);
282
283
// Make changes
284
yarray.push(["item3"]);
285
ymap.set("count", 3);
286
ymap.set("modified", "2023-01-02");
287
ymap.delete("created");
288
const snapshot2 = Y.snapshot(doc);
289
290
// Query historical values
291
const arrayAtSnap1 = Y.typeListToArraySnapshot(yarray, snapshot1);
292
const arrayAtSnap2 = Y.typeListToArraySnapshot(yarray, snapshot2);
293
294
console.log("Array at snapshot1:", arrayAtSnap1); // ["item1", "item2"]
295
console.log("Array at snapshot2:", arrayAtSnap2); // ["item1", "item2", "item3"]
296
297
const countAtSnap1 = Y.typeMapGetSnapshot(ymap, "count", snapshot1);
298
const countAtSnap2 = Y.typeMapGetSnapshot(ymap, "count", snapshot2);
299
300
console.log("Count at snapshot1:", countAtSnap1); // 2
301
console.log("Count at snapshot2:", countAtSnap2); // 3
302
303
const allMetaAtSnap1 = Y.typeMapGetAllSnapshot(ymap, snapshot1);
304
const allMetaAtSnap2 = Y.typeMapGetAllSnapshot(ymap, snapshot2);
305
306
console.log("Metadata at snapshot1:", allMetaAtSnap1); // {count: 2, created: "2023-01-01"}
307
console.log("Metadata at snapshot2:", allMetaAtSnap2); // {count: 3, modified: "2023-01-02"}
308
```
309
310
### Advanced Snapshot Patterns
311
312
**Version Control System:**
313
314
```typescript
315
import * as Y from "yjs";
316
317
interface Version {
318
id: string;
319
timestamp: number;
320
message: string;
321
snapshot: Y.Snapshot;
322
}
323
324
class VersionControl {
325
private doc: Y.Doc;
326
private versions: Version[] = [];
327
328
constructor(doc: Y.Doc) {
329
this.doc = doc;
330
// Save initial version
331
this.saveVersion("Initial version");
332
}
333
334
saveVersion(message: string): string {
335
const versionId = `v${Date.now()}`;
336
const version: Version = {
337
id: versionId,
338
timestamp: Date.now(),
339
message,
340
snapshot: Y.snapshot(this.doc)
341
};
342
343
this.versions.push(version);
344
return versionId;
345
}
346
347
getVersions(): Version[] {
348
return [...this.versions];
349
}
350
351
restoreVersion(versionId: string): Y.Doc | null {
352
const version = this.versions.find(v => v.id === versionId);
353
if (!version) return null;
354
355
return Y.createDocFromSnapshot(this.doc, version.snapshot);
356
}
357
358
compareVersions(versionId1: string, versionId2: string): boolean {
359
const v1 = this.versions.find(v => v.id === versionId1);
360
const v2 = this.versions.find(v => v.id === versionId2);
361
362
if (!v1 || !v2) return false;
363
return Y.equalSnapshots(v1.snapshot, v2.snapshot);
364
}
365
}
366
367
// Usage
368
const doc = new Y.Doc();
369
const vc = new VersionControl(doc);
370
371
const yarray = doc.getArray("items");
372
yarray.push(["item1"]);
373
const v1 = vc.saveVersion("Added item1");
374
375
yarray.push(["item2"]);
376
const v2 = vc.saveVersion("Added item2");
377
378
// Restore to previous version
379
const restoredDoc = vc.restoreVersion(v1);
380
console.log("Restored array:", restoredDoc?.getArray("items").toArray()); // ["item1"]
381
```
382
383
**Branching System:**
384
385
```typescript
386
import * as Y from "yjs";
387
388
interface Branch {
389
name: string;
390
baseSnapshot: Y.Snapshot;
391
doc: Y.Doc;
392
}
393
394
class BranchManager {
395
private mainDoc: Y.Doc;
396
private branches: Map<string, Branch> = new Map();
397
398
constructor(doc: Y.Doc) {
399
this.mainDoc = doc;
400
}
401
402
createBranch(name: string, fromSnapshot?: Y.Snapshot): Y.Doc {
403
const baseSnapshot = fromSnapshot || Y.snapshot(this.mainDoc);
404
const branchDoc = Y.createDocFromSnapshot(this.mainDoc, baseSnapshot);
405
406
this.branches.set(name, {
407
name,
408
baseSnapshot,
409
doc: branchDoc
410
});
411
412
return branchDoc;
413
}
414
415
getBranch(name: string): Y.Doc | null {
416
return this.branches.get(name)?.doc || null;
417
}
418
419
mergeBranch(branchName: string): boolean {
420
const branch = this.branches.get(branchName);
421
if (!branch) return false;
422
423
// Get changes made in branch
424
const branchStateVector = Y.encodeStateVector(branch.baseSnapshot.sv);
425
const branchChanges = Y.encodeStateAsUpdate(branch.doc, branchStateVector);
426
427
// Apply to main document
428
Y.applyUpdate(this.mainDoc, branchChanges);
429
430
return true;
431
}
432
433
deleteBranch(name: string): boolean {
434
return this.branches.delete(name);
435
}
436
}
437
438
// Usage
439
const mainDoc = new Y.Doc();
440
const bm = new BranchManager(mainDoc);
441
442
// Work on main
443
mainDoc.getArray("items").push(["main-item"]);
444
445
// Create feature branch
446
const featureBranch = bm.createBranch("feature-xyz");
447
featureBranch.getArray("items").push(["feature-item"]);
448
449
// Merge back to main
450
bm.mergeBranch("feature-xyz");
451
452
console.log("Main after merge:", mainDoc.getArray("items").toArray());
453
// ["main-item", "feature-item"]
454
```
455
456
**Snapshot-based Diff:**
457
458
```typescript
459
import * as Y from "yjs";
460
461
function diffSnapshots(doc: Y.Doc, snap1: Y.Snapshot, snap2: Y.Snapshot): any {
462
const doc1 = Y.createDocFromSnapshot(doc, snap1);
463
const doc2 = Y.createDocFromSnapshot(doc, snap2);
464
465
const diff: any = {};
466
467
// Compare shared types
468
doc1.share.forEach((type1, name) => {
469
const type2 = doc2.share.get(name);
470
471
if (type1 instanceof Y.YArray && type2 instanceof Y.YArray) {
472
const arr1 = type1.toArray();
473
const arr2 = type2.toArray();
474
if (JSON.stringify(arr1) !== JSON.stringify(arr2)) {
475
diff[name] = { before: arr1, after: arr2 };
476
}
477
} else if (type1 instanceof Y.YMap && type2 instanceof Y.YMap) {
478
const obj1 = type1.toJSON();
479
const obj2 = type2.toJSON();
480
if (JSON.stringify(obj1) !== JSON.stringify(obj2)) {
481
diff[name] = { before: obj1, after: obj2 };
482
}
483
}
484
});
485
486
return diff;
487
}
488
489
// Usage
490
const doc = new Y.Doc();
491
const yarray = doc.getArray("items");
492
493
yarray.push(["item1", "item2"]);
494
const snap1 = Y.snapshot(doc);
495
496
yarray.delete(0, 1);
497
yarray.push(["item3"]);
498
const snap2 = Y.snapshot(doc);
499
500
const diff = diffSnapshots(doc, snap1, snap2);
501
console.log("Diff:", diff);
502
// { items: { before: ["item1", "item2"], after: ["item2", "item3"] } }
503
```