-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Expand file tree
/
Copy pathutils.ts
More file actions
106 lines (93 loc) · 3.2 KB
/
utils.ts
File metadata and controls
106 lines (93 loc) · 3.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/**
* External dependencies
*/
import * as Y from 'yjs';
import * as buffer from 'lib0/buffer';
/**
* Internal dependencies
*/
import {
CRDT_DOC_META_PERSISTENCE_KEY,
CRDT_DOC_VERSION,
CRDT_STATE_MAP_KEY,
CRDT_STATE_MAP_SAVED_AT_KEY as SAVED_AT_KEY,
CRDT_STATE_MAP_SAVED_BY_KEY as SAVED_BY_KEY,
CRDT_STATE_MAP_VERSION_KEY as VERSION_KEY,
} from './config';
import type { CRDTDoc } from './types';
// An object representation of CRDT document metadata.
type DocumentMeta = Record< string, DocumentMetaValue >;
type DocumentMetaValue = boolean | number | string;
/**
* Creates a new Y.Doc instance with the given document metadata.
*
* @param {DocumentMeta} documentMeta Optional metadata to associate with the
* document. Metadata is not persisted.
*/
export function createYjsDoc( documentMeta: DocumentMeta = {} ): CRDTDoc {
// Convert the object representation of CRDT document metadata to a map.
// Document metadata is passed to the Y.Doc constructor and stored in its
// `meta` property. It is not synced to peers or persisted with the document.
// It is just a place to store transient information about this doc instance.
const metaMap = new Map< string, DocumentMetaValue >(
Object.entries( documentMeta )
);
// IMPORTANT: Do not add update the document itself to avoid generating updates
// before observers are attached. Add initial updates in `initializeYjsDoc`.
return new Y.Doc( { meta: metaMap } );
}
/**
* Initializes a Y.Doc instance with the necessary CRDT state for our use case.
*
* @param {Y.Doc} ydoc Y.Doc instance to initialize.
*/
export function initializeYjsDoc( ydoc: CRDTDoc ): void {
const stateMap = ydoc.getMap( CRDT_STATE_MAP_KEY );
stateMap.set( VERSION_KEY, CRDT_DOC_VERSION );
}
/**
* Record that the entity was saved (persisted to the database) in the CRDT
* document record metadata.
*
* @param {CRDTDoc} ydoc CRDT document.
*/
export function markEntityAsSaved( ydoc: CRDTDoc ): void {
const recordMeta = ydoc.getMap( CRDT_STATE_MAP_KEY );
recordMeta.set( SAVED_AT_KEY, Date.now() );
recordMeta.set( SAVED_BY_KEY, ydoc.clientID );
}
function pseudoRandomID(): number {
return Math.floor( Math.random() * 1000000000 );
}
export function serializeCrdtDoc(
crdtDoc: CRDTDoc,
baseVersion: number = 0
): string {
return JSON.stringify( {
document: buffer.toBase64( Y.encodeStateAsUpdateV2( crdtDoc ) ),
baseVersion,
updateId: pseudoRandomID(), // helps with debugging
} );
}
export function deserializeCrdtDoc(
serializedCrdtDoc: string
): CRDTDoc | null {
try {
const { document } = JSON.parse( serializedCrdtDoc );
// Mark this document as from persistence.
const docMeta: DocumentMeta = {
[ CRDT_DOC_META_PERSISTENCE_KEY ]: true,
};
// Apply the document as an update against a new (temporary) Y.Doc.
const ydoc = createYjsDoc( docMeta );
const yupdate = buffer.fromBase64( document );
Y.applyUpdateV2( ydoc, yupdate );
// Overwrite the client ID (which is from a previous session) with a random
// client ID. Deserialized documents should not be used directly. Instead,
// their state should be applied to another in-use document.
ydoc.clientID = pseudoRandomID();
return ydoc;
} catch {
return null;
}
}