@sqlrooms/crdt 0.29.0-rc.0 → 0.29.0-rc.2
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 +10 -3
- package/dist/sync/webSocketConnector.d.ts +27 -0
- package/dist/sync/webSocketConnector.d.ts.map +1 -0
- package/dist/sync/webSocketConnector.js +390 -0
- package/dist/sync/webSocketConnector.js.map +1 -0
- package/dist/types.d.ts +28 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -33,8 +33,16 @@ const {roomStore, useRoomStore} = createRoomStore(
|
|
|
33
33
|
...createCrdtSlice({
|
|
34
34
|
schema: mirrorSchema,
|
|
35
35
|
bindings: [
|
|
36
|
-
{
|
|
37
|
-
|
|
36
|
+
{
|
|
37
|
+
key: 'room',
|
|
38
|
+
select: (s) => s.room?.config,
|
|
39
|
+
apply: (value) => set({room: {...get().room, config: value}}),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
key: 'layout',
|
|
43
|
+
select: (s) => s.layout?.config,
|
|
44
|
+
apply: (value) => set({layout: {...get().layout, config: value}}),
|
|
45
|
+
},
|
|
38
46
|
],
|
|
39
47
|
storage: createLocalStorageDocStorage('sqlrooms-sync-demo'),
|
|
40
48
|
sync: createWebSocketSyncConnector({
|
|
@@ -69,4 +77,3 @@ See `examples/sync` for an end-to-end demonstration with the Python sync server.
|
|
|
69
77
|
- Mirror emits `tags` metadata; we tag store-origin writes as `from-store` to avoid loops. Log `mirror.subscribe` in your app if you need deeper inspection.
|
|
70
78
|
- Use the `storage` hook to inject an in-memory or temp store in tests; pass a fake `sync` connector that captures `subscribeLocalUpdates` traffic for assertions.
|
|
71
79
|
- The websocket connector exposes `onStatus` for basic connection logging; attach console logs or telemetry there during development.
|
|
72
|
-
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { CrdtSyncConnector } from '../createCrdtSlice';
|
|
2
|
+
type WebSocketLike = {
|
|
3
|
+
readyState: number;
|
|
4
|
+
send: (data: string | ArrayBufferLike | Blob | ArrayBufferView) => void;
|
|
5
|
+
close: () => void;
|
|
6
|
+
addEventListener: (type: string, listener: (...args: any[]) => void) => void;
|
|
7
|
+
removeEventListener: (type: string, listener: (...args: any[]) => void) => void;
|
|
8
|
+
};
|
|
9
|
+
export type WebSocketSyncOptions = {
|
|
10
|
+
url: string;
|
|
11
|
+
roomId: string;
|
|
12
|
+
token?: string;
|
|
13
|
+
params?: Record<string, string>;
|
|
14
|
+
protocols?: string | string[];
|
|
15
|
+
onStatus?: (status: 'connecting' | 'open' | 'closed' | 'error') => void;
|
|
16
|
+
createSocket?: (url: string, protocols?: string | string[]) => WebSocketLike;
|
|
17
|
+
sendSnapshotOnConnect?: boolean;
|
|
18
|
+
maxRetries?: number;
|
|
19
|
+
initialDelayMs?: number;
|
|
20
|
+
maxDelayMs?: number;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Creates a CRDT sync connector that exchanges Loro updates over WebSocket.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createWebSocketSyncConnector(options: WebSocketSyncOptions): CrdtSyncConnector;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=webSocketConnector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webSocketConnector.d.ts","sourceRoot":"","sources":["../../src/sync/webSocketConnector.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,iBAAiB,EAAC,MAAM,oBAAoB,CAAC;AAErD,KAAK,aAAa,GAAG;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,GAAG,eAAe,KAAK,IAAI,CAAC;IACxE,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,KAAK,IAAI,CAAC;IAC7E,mBAAmB,EAAE,CACnB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,KAC/B,IAAI,CAAC;CACX,CAAC;AAgCF,MAAM,MAAM,oBAAoB,GAAG;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC9B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,KAAK,IAAI,CAAC;IACxE,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,aAAa,CAAC;IAC7E,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;GAEG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,oBAAoB,GAC5B,iBAAiB,CAyYnB"}
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
const WS_OPEN = 1;
|
|
2
|
+
const WS_CONNECTING = 0;
|
|
3
|
+
const toBase64 = (bytes) => {
|
|
4
|
+
const buf = globalThis.Buffer;
|
|
5
|
+
if (buf) {
|
|
6
|
+
return buf.from(bytes).toString('base64');
|
|
7
|
+
}
|
|
8
|
+
let binary = '';
|
|
9
|
+
bytes.forEach((b) => {
|
|
10
|
+
binary += String.fromCharCode(b);
|
|
11
|
+
});
|
|
12
|
+
return btoa(binary);
|
|
13
|
+
};
|
|
14
|
+
const fromBase64 = (value) => {
|
|
15
|
+
const buf = globalThis.Buffer;
|
|
16
|
+
if (buf) {
|
|
17
|
+
return Uint8Array.from(buf.from(value, 'base64'));
|
|
18
|
+
}
|
|
19
|
+
const binary = atob(value);
|
|
20
|
+
const out = new Uint8Array(binary.length);
|
|
21
|
+
for (let i = 0; i < binary.length; i += 1)
|
|
22
|
+
out[i] = binary.charCodeAt(i);
|
|
23
|
+
return out;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Creates a CRDT sync connector that exchanges Loro updates over WebSocket.
|
|
27
|
+
*/
|
|
28
|
+
export function createWebSocketSyncConnector(options) {
|
|
29
|
+
let socket;
|
|
30
|
+
let unsubscribeLocal;
|
|
31
|
+
let subscribedDoc;
|
|
32
|
+
let attempt = 0;
|
|
33
|
+
let stopped = false;
|
|
34
|
+
let reconnectTimer;
|
|
35
|
+
let joined = false;
|
|
36
|
+
let connecting = false;
|
|
37
|
+
const pending = [];
|
|
38
|
+
let localSubscribed = false;
|
|
39
|
+
let listeningSocket;
|
|
40
|
+
let detachSocketListeners;
|
|
41
|
+
const maxRetries = options.maxRetries ?? Infinity;
|
|
42
|
+
const initialDelay = options.initialDelayMs ?? 500;
|
|
43
|
+
const maxDelay = options.maxDelayMs ?? 5000;
|
|
44
|
+
const maybeSendUpdate = (update) => {
|
|
45
|
+
console.debug('[crdt] local update observed', update.byteLength, 'bytes', {
|
|
46
|
+
joined,
|
|
47
|
+
readyState: socket?.readyState,
|
|
48
|
+
});
|
|
49
|
+
if (!socket || socket.readyState !== WS_OPEN) {
|
|
50
|
+
console.debug('[crdt] skip send: socket not open, buffering');
|
|
51
|
+
pending.push(update);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (!joined) {
|
|
55
|
+
console.debug('[crdt] buffering local update before join', update.byteLength, 'bytes');
|
|
56
|
+
pending.push(update);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
console.debug('[crdt] sending local update', update.byteLength, 'bytes');
|
|
60
|
+
socket.send(update);
|
|
61
|
+
};
|
|
62
|
+
const attachLocalSubscription = (doc) => {
|
|
63
|
+
try {
|
|
64
|
+
const unsub = doc.subscribeLocalUpdates((update) => {
|
|
65
|
+
// Pass through to shared handler so we keep centralized buffering/sending logic.
|
|
66
|
+
maybeSendUpdate(update);
|
|
67
|
+
});
|
|
68
|
+
unsubscribeLocal = () => {
|
|
69
|
+
try {
|
|
70
|
+
unsub();
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
console.warn('[crdt] failed to unsubscribe local updates', error);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
localSubscribed = true;
|
|
77
|
+
console.debug('[crdt] attached local update subscription');
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
console.warn('[crdt] failed to attach local subscription', error);
|
|
81
|
+
localSubscribed = false;
|
|
82
|
+
unsubscribeLocal = undefined;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Ensures we are subscribed to local updates on the *current* doc.
|
|
87
|
+
*
|
|
88
|
+
* This matters because this connector can be reused across reconnects and
|
|
89
|
+
* `connect(doc)` calls; we must not keep a stale subscription to a prior doc.
|
|
90
|
+
*/
|
|
91
|
+
const ensureLocalSubscription = (doc) => {
|
|
92
|
+
if (subscribedDoc && subscribedDoc !== doc) {
|
|
93
|
+
console.info('[crdt] switching local update subscription to new doc');
|
|
94
|
+
unsubscribeLocal?.();
|
|
95
|
+
unsubscribeLocal = undefined;
|
|
96
|
+
localSubscribed = false;
|
|
97
|
+
subscribedDoc = undefined;
|
|
98
|
+
}
|
|
99
|
+
if (!localSubscribed || !unsubscribeLocal || subscribedDoc !== doc) {
|
|
100
|
+
attachLocalSubscription(doc);
|
|
101
|
+
subscribedDoc = doc;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
const buildUrl = () => {
|
|
105
|
+
const url = new URL(options.url);
|
|
106
|
+
url.searchParams.set('roomId', options.roomId);
|
|
107
|
+
if (options.token)
|
|
108
|
+
url.searchParams.set('token', options.token);
|
|
109
|
+
if (options.params) {
|
|
110
|
+
Object.entries(options.params).forEach(([k, v]) => url.searchParams.set(k, v));
|
|
111
|
+
}
|
|
112
|
+
return url.toString();
|
|
113
|
+
};
|
|
114
|
+
const sendStatus = (status) => {
|
|
115
|
+
console.info('[crdt] ws status ->', status);
|
|
116
|
+
options.onStatus?.(status);
|
|
117
|
+
};
|
|
118
|
+
const sendJoin = () => {
|
|
119
|
+
if (!socket || socket.readyState !== WS_OPEN)
|
|
120
|
+
return;
|
|
121
|
+
const payload = JSON.stringify({ type: 'crdt-join', roomId: options.roomId });
|
|
122
|
+
console.debug('[crdt] sending join', payload);
|
|
123
|
+
socket.send(payload);
|
|
124
|
+
};
|
|
125
|
+
const sendSnapshot = (doc) => {
|
|
126
|
+
if (!socket || socket.readyState !== WS_OPEN)
|
|
127
|
+
return;
|
|
128
|
+
try {
|
|
129
|
+
const snapshot = doc.export({ mode: 'snapshot' });
|
|
130
|
+
console.info('[crdt] sending snapshot bytes', snapshot?.byteLength ?? snapshot?.length ?? 0);
|
|
131
|
+
const payload = JSON.stringify({
|
|
132
|
+
type: 'crdt-snapshot',
|
|
133
|
+
roomId: options.roomId,
|
|
134
|
+
data: toBase64(snapshot),
|
|
135
|
+
});
|
|
136
|
+
socket.send(payload);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
console.warn('Failed to send CRDT snapshot', error);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const scheduleReconnect = (doc) => {
|
|
143
|
+
if (stopped)
|
|
144
|
+
return;
|
|
145
|
+
if (reconnectTimer)
|
|
146
|
+
return;
|
|
147
|
+
if (connecting)
|
|
148
|
+
return;
|
|
149
|
+
if (attempt >= maxRetries) {
|
|
150
|
+
sendStatus('closed');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const delay = Math.min(maxDelay, initialDelay * 2 ** attempt);
|
|
154
|
+
attempt += 1;
|
|
155
|
+
reconnectTimer = setTimeout(() => {
|
|
156
|
+
reconnectTimer = undefined;
|
|
157
|
+
void connect(doc);
|
|
158
|
+
}, delay);
|
|
159
|
+
};
|
|
160
|
+
const connect = async (doc) => {
|
|
161
|
+
if (stopped)
|
|
162
|
+
return;
|
|
163
|
+
if (connecting)
|
|
164
|
+
return;
|
|
165
|
+
// Always ensure we are subscribed to the *current* doc.
|
|
166
|
+
ensureLocalSubscription(doc);
|
|
167
|
+
console.info('[crdt] connect start');
|
|
168
|
+
const attachSocketListenersFor = (ws) => {
|
|
169
|
+
// Ensure browser websockets deliver binary frames as ArrayBuffer (not Blob)
|
|
170
|
+
if ('binaryType' in ws) {
|
|
171
|
+
try {
|
|
172
|
+
ws.binaryType = 'arraybuffer';
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
console.warn('Failed to set binaryType on CRDT websocket', error);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const handleMessage = (event) => {
|
|
179
|
+
// Use the currently connected doc reference (not the doc that created the socket),
|
|
180
|
+
// so a later connect(doc) call can rebind without needing a new WebSocket.
|
|
181
|
+
const activeDoc = subscribedDoc ?? doc;
|
|
182
|
+
if (!activeDoc)
|
|
183
|
+
return;
|
|
184
|
+
// Binary updates flow directly
|
|
185
|
+
if (event.data instanceof ArrayBuffer ||
|
|
186
|
+
ArrayBuffer.isView(event.data)) {
|
|
187
|
+
const bytes = event.data instanceof ArrayBuffer
|
|
188
|
+
? new Uint8Array(event.data)
|
|
189
|
+
: new Uint8Array(event.data.buffer, event.data.byteOffset, event.data.byteLength);
|
|
190
|
+
activeDoc.import(bytes);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (typeof Blob !== 'undefined' && event.data instanceof Blob) {
|
|
194
|
+
void event.data
|
|
195
|
+
.arrayBuffer()
|
|
196
|
+
.then((buf) => activeDoc.import(new Uint8Array(buf)))
|
|
197
|
+
.catch((error) => console.warn('Failed to decode CRDT binary message', error));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (typeof event.data === 'string') {
|
|
201
|
+
try {
|
|
202
|
+
const parsed = JSON.parse(event.data);
|
|
203
|
+
console.debug('[crdt] ws message', parsed?.type);
|
|
204
|
+
if (parsed?.type === 'crdt-joined') {
|
|
205
|
+
joined = true;
|
|
206
|
+
console.debug('[crdt] joined room, flushing', pending.length, 'pending');
|
|
207
|
+
// flush pending updates
|
|
208
|
+
while (pending.length) {
|
|
209
|
+
const update = pending.shift();
|
|
210
|
+
if (update && ws && ws.readyState === WS_OPEN) {
|
|
211
|
+
// Debug visibility for diagnosing silent sync issues.
|
|
212
|
+
console.debug('[crdt] sending pending update after join', update.byteLength, 'bytes');
|
|
213
|
+
ws.send(update);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (parsed?.type === 'crdt-snapshot' && parsed.data) {
|
|
219
|
+
const bytes = fromBase64(parsed.data);
|
|
220
|
+
activeDoc.import(bytes);
|
|
221
|
+
}
|
|
222
|
+
// Ignore other messages (errors, acks) for now
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
console.warn('Failed to parse CRDT message', error);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
const handleOpen = () => {
|
|
230
|
+
attempt = 0;
|
|
231
|
+
joined = false;
|
|
232
|
+
connecting = false;
|
|
233
|
+
sendStatus('open');
|
|
234
|
+
sendJoin();
|
|
235
|
+
if (options.sendSnapshotOnConnect) {
|
|
236
|
+
sendSnapshot(doc);
|
|
237
|
+
}
|
|
238
|
+
ensureLocalSubscription(doc);
|
|
239
|
+
};
|
|
240
|
+
const handleClose = (event) => {
|
|
241
|
+
console.warn('CRDT WS closed', {
|
|
242
|
+
code: event.code,
|
|
243
|
+
reason: event.reason,
|
|
244
|
+
pending: pending.length,
|
|
245
|
+
joined,
|
|
246
|
+
});
|
|
247
|
+
connecting = false;
|
|
248
|
+
sendStatus('closed');
|
|
249
|
+
unsubscribeLocal?.();
|
|
250
|
+
unsubscribeLocal = undefined;
|
|
251
|
+
localSubscribed = false;
|
|
252
|
+
subscribedDoc = undefined;
|
|
253
|
+
joined = false;
|
|
254
|
+
pending.length = 0;
|
|
255
|
+
detachSocketListeners?.();
|
|
256
|
+
detachSocketListeners = undefined;
|
|
257
|
+
listeningSocket = undefined;
|
|
258
|
+
scheduleReconnect(doc);
|
|
259
|
+
};
|
|
260
|
+
const handleError = (event) => {
|
|
261
|
+
console.warn('CRDT WS error', event);
|
|
262
|
+
connecting = false;
|
|
263
|
+
sendStatus('error');
|
|
264
|
+
joined = false;
|
|
265
|
+
unsubscribeLocal?.();
|
|
266
|
+
unsubscribeLocal = undefined;
|
|
267
|
+
localSubscribed = false;
|
|
268
|
+
subscribedDoc = undefined;
|
|
269
|
+
detachSocketListeners?.();
|
|
270
|
+
detachSocketListeners = undefined;
|
|
271
|
+
listeningSocket = undefined;
|
|
272
|
+
scheduleReconnect(doc);
|
|
273
|
+
};
|
|
274
|
+
// Some WebSocket implementations expose either addEventListener or on*
|
|
275
|
+
// handlers. Use one style only to avoid duplicate events.
|
|
276
|
+
if (typeof ws.addEventListener === 'function') {
|
|
277
|
+
ws.addEventListener('message', handleMessage);
|
|
278
|
+
ws.addEventListener('open', handleOpen);
|
|
279
|
+
ws.addEventListener('close', handleClose);
|
|
280
|
+
ws.addEventListener('error', handleError);
|
|
281
|
+
detachSocketListeners = () => {
|
|
282
|
+
try {
|
|
283
|
+
ws.removeEventListener('message', handleMessage);
|
|
284
|
+
ws.removeEventListener('open', handleOpen);
|
|
285
|
+
ws.removeEventListener('close', handleClose);
|
|
286
|
+
ws.removeEventListener('error', handleError);
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
console.warn('[crdt] failed to detach socket listeners', error);
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
ws.onmessage = handleMessage;
|
|
295
|
+
ws.onopen = handleOpen;
|
|
296
|
+
ws.onclose = handleClose;
|
|
297
|
+
ws.onerror = handleError;
|
|
298
|
+
detachSocketListeners = () => {
|
|
299
|
+
try {
|
|
300
|
+
ws.onmessage = null;
|
|
301
|
+
ws.onopen = null;
|
|
302
|
+
ws.onclose = null;
|
|
303
|
+
ws.onerror = null;
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
console.warn('[crdt] failed to clear socket handlers', error);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
listeningSocket = ws;
|
|
311
|
+
// If we stay in CONNECTING too long, force a reconnect to avoid being stuck.
|
|
312
|
+
setTimeout(() => {
|
|
313
|
+
if (ws && ws.readyState === WS_CONNECTING && !joined) {
|
|
314
|
+
console.warn('[crdt] ws still connecting after timeout; retrying');
|
|
315
|
+
try {
|
|
316
|
+
ws.close();
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
console.warn('[crdt] error closing stuck socket', error);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}, 3000);
|
|
323
|
+
};
|
|
324
|
+
if (socket &&
|
|
325
|
+
(socket.readyState === WS_OPEN || socket.readyState === WS_CONNECTING)) {
|
|
326
|
+
console.info('[crdt] reuse existing socket, readyState:', socket.readyState);
|
|
327
|
+
if (listeningSocket !== socket) {
|
|
328
|
+
console.warn('[crdt] existing socket had no listeners; attaching now');
|
|
329
|
+
attachSocketListenersFor(socket);
|
|
330
|
+
// If the socket is already open, we won't get an 'open' event, so act as if
|
|
331
|
+
// we just opened: (re)join and optionally send snapshot.
|
|
332
|
+
if (socket.readyState === WS_OPEN) {
|
|
333
|
+
attempt = 0;
|
|
334
|
+
joined = false;
|
|
335
|
+
sendStatus('open');
|
|
336
|
+
sendJoin();
|
|
337
|
+
if (options.sendSnapshotOnConnect)
|
|
338
|
+
sendSnapshot(doc);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (reconnectTimer) {
|
|
344
|
+
clearTimeout(reconnectTimer);
|
|
345
|
+
reconnectTimer = undefined;
|
|
346
|
+
}
|
|
347
|
+
connecting = true;
|
|
348
|
+
sendStatus('connecting');
|
|
349
|
+
const wsCreator = options.createSocket ??
|
|
350
|
+
((url, protocols) => new WebSocket(url, protocols));
|
|
351
|
+
const url = buildUrl();
|
|
352
|
+
try {
|
|
353
|
+
socket = wsCreator(url, options.protocols);
|
|
354
|
+
console.info('[crdt] ws created', url);
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
connecting = false;
|
|
358
|
+
sendStatus('error');
|
|
359
|
+
scheduleReconnect(doc);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
attachSocketListenersFor(socket);
|
|
363
|
+
};
|
|
364
|
+
const disconnect = async () => {
|
|
365
|
+
stopped = true;
|
|
366
|
+
if (reconnectTimer) {
|
|
367
|
+
clearTimeout(reconnectTimer);
|
|
368
|
+
reconnectTimer = undefined;
|
|
369
|
+
}
|
|
370
|
+
unsubscribeLocal?.();
|
|
371
|
+
unsubscribeLocal = undefined;
|
|
372
|
+
localSubscribed = false;
|
|
373
|
+
subscribedDoc = undefined;
|
|
374
|
+
detachSocketListeners?.();
|
|
375
|
+
detachSocketListeners = undefined;
|
|
376
|
+
listeningSocket = undefined;
|
|
377
|
+
if (socket) {
|
|
378
|
+
try {
|
|
379
|
+
socket.close();
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
console.warn('Error closing CRDT websocket', error);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
socket = undefined;
|
|
386
|
+
sendStatus('closed');
|
|
387
|
+
};
|
|
388
|
+
return { connect, disconnect };
|
|
389
|
+
}
|
|
390
|
+
//# sourceMappingURL=webSocketConnector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webSocketConnector.js","sourceRoot":"","sources":["../../src/sync/webSocketConnector.ts"],"names":[],"mappings":"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","sourcesContent":["import {LoroDoc} from 'loro-crdt';\nimport {CrdtSyncConnector} from '../createCrdtSlice';\n\ntype WebSocketLike = {\n readyState: number;\n send: (data: string | ArrayBufferLike | Blob | ArrayBufferView) => void;\n close: () => void;\n addEventListener: (type: string, listener: (...args: any[]) => void) => void;\n removeEventListener: (\n type: string,\n listener: (...args: any[]) => void,\n ) => void;\n};\n\nconst WS_OPEN = 1;\nconst WS_CONNECTING = 0;\n\nconst toBase64 = (bytes: Uint8Array) => {\n const buf = (globalThis as any).Buffer as\n | {from: (input: Uint8Array) => {toString: (enc: string) => string}}\n | undefined;\n if (buf) {\n return buf.from(bytes).toString('base64');\n }\n let binary = '';\n bytes.forEach((b) => {\n binary += String.fromCharCode(b);\n });\n return btoa(binary);\n};\n\nconst fromBase64 = (value: string) => {\n const buf = (globalThis as any).Buffer as\n | {from: (input: string, encoding: string) => Uint8Array}\n | undefined;\n if (buf) {\n return Uint8Array.from(buf.from(value, 'base64'));\n }\n const binary = atob(value);\n const out = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) out[i] = binary.charCodeAt(i);\n return out;\n};\n\nexport type WebSocketSyncOptions = {\n url: string;\n roomId: string;\n token?: string;\n params?: Record<string, string>;\n protocols?: string | string[];\n onStatus?: (status: 'connecting' | 'open' | 'closed' | 'error') => void;\n createSocket?: (url: string, protocols?: string | string[]) => WebSocketLike;\n sendSnapshotOnConnect?: boolean;\n maxRetries?: number;\n initialDelayMs?: number;\n maxDelayMs?: number;\n};\n\n/**\n * Creates a CRDT sync connector that exchanges Loro updates over WebSocket.\n */\nexport function createWebSocketSyncConnector(\n options: WebSocketSyncOptions,\n): CrdtSyncConnector {\n let socket: WebSocketLike | undefined;\n let unsubscribeLocal: (() => void) | undefined;\n let subscribedDoc: LoroDoc | undefined;\n let attempt = 0;\n let stopped = false;\n let reconnectTimer: ReturnType<typeof setTimeout> | undefined;\n let joined = false;\n let connecting = false;\n const pending: Uint8Array[] = [];\n let localSubscribed = false;\n let listeningSocket: WebSocketLike | undefined;\n let detachSocketListeners: (() => void) | undefined;\n\n const maxRetries = options.maxRetries ?? Infinity;\n const initialDelay = options.initialDelayMs ?? 500;\n const maxDelay = options.maxDelayMs ?? 5000;\n\n const maybeSendUpdate = (update: Uint8Array) => {\n console.debug('[crdt] local update observed', update.byteLength, 'bytes', {\n joined,\n readyState: socket?.readyState,\n });\n if (!socket || socket.readyState !== WS_OPEN) {\n console.debug('[crdt] skip send: socket not open, buffering');\n pending.push(update);\n return;\n }\n if (!joined) {\n console.debug(\n '[crdt] buffering local update before join',\n update.byteLength,\n 'bytes',\n );\n pending.push(update);\n return;\n }\n console.debug('[crdt] sending local update', update.byteLength, 'bytes');\n socket.send(update);\n };\n\n const attachLocalSubscription = (doc: LoroDoc) => {\n try {\n const unsub = doc.subscribeLocalUpdates((update: Uint8Array) => {\n // Pass through to shared handler so we keep centralized buffering/sending logic.\n maybeSendUpdate(update);\n });\n unsubscribeLocal = () => {\n try {\n unsub();\n } catch (error) {\n console.warn('[crdt] failed to unsubscribe local updates', error);\n }\n };\n localSubscribed = true;\n console.debug('[crdt] attached local update subscription');\n } catch (error) {\n console.warn('[crdt] failed to attach local subscription', error);\n localSubscribed = false;\n unsubscribeLocal = undefined;\n }\n };\n\n /**\n * Ensures we are subscribed to local updates on the *current* doc.\n *\n * This matters because this connector can be reused across reconnects and\n * `connect(doc)` calls; we must not keep a stale subscription to a prior doc.\n */\n const ensureLocalSubscription = (doc: LoroDoc) => {\n if (subscribedDoc && subscribedDoc !== doc) {\n console.info('[crdt] switching local update subscription to new doc');\n unsubscribeLocal?.();\n unsubscribeLocal = undefined;\n localSubscribed = false;\n subscribedDoc = undefined;\n }\n if (!localSubscribed || !unsubscribeLocal || subscribedDoc !== doc) {\n attachLocalSubscription(doc);\n subscribedDoc = doc;\n }\n };\n\n const buildUrl = () => {\n const url = new URL(options.url);\n url.searchParams.set('roomId', options.roomId);\n if (options.token) url.searchParams.set('token', options.token);\n if (options.params) {\n Object.entries(options.params).forEach(([k, v]) =>\n url.searchParams.set(k, v),\n );\n }\n return url.toString();\n };\n\n const sendStatus = (status: 'connecting' | 'open' | 'closed' | 'error') => {\n console.info('[crdt] ws status ->', status);\n options.onStatus?.(status);\n };\n\n const sendJoin = () => {\n if (!socket || socket.readyState !== WS_OPEN) return;\n const payload = JSON.stringify({type: 'crdt-join', roomId: options.roomId});\n console.debug('[crdt] sending join', payload);\n socket.send(payload);\n };\n\n const sendSnapshot = (doc: LoroDoc) => {\n if (!socket || socket.readyState !== WS_OPEN) return;\n try {\n const snapshot = doc.export({mode: 'snapshot'});\n console.info(\n '[crdt] sending snapshot bytes',\n snapshot?.byteLength ?? snapshot?.length ?? 0,\n );\n const payload = JSON.stringify({\n type: 'crdt-snapshot',\n roomId: options.roomId,\n data: toBase64(snapshot),\n });\n socket.send(payload);\n } catch (error) {\n console.warn('Failed to send CRDT snapshot', error);\n }\n };\n\n const scheduleReconnect = (doc: LoroDoc) => {\n if (stopped) return;\n if (reconnectTimer) return;\n if (connecting) return;\n if (attempt >= maxRetries) {\n sendStatus('closed');\n return;\n }\n const delay = Math.min(maxDelay, initialDelay * 2 ** attempt);\n attempt += 1;\n reconnectTimer = setTimeout(() => {\n reconnectTimer = undefined;\n void connect(doc);\n }, delay);\n };\n\n const connect = async (doc: LoroDoc) => {\n if (stopped) return;\n if (connecting) return;\n // Always ensure we are subscribed to the *current* doc.\n ensureLocalSubscription(doc);\n console.info('[crdt] connect start');\n\n const attachSocketListenersFor = (ws: WebSocketLike) => {\n // Ensure browser websockets deliver binary frames as ArrayBuffer (not Blob)\n if ('binaryType' in ws) {\n try {\n (ws as any).binaryType = 'arraybuffer';\n } catch (error) {\n console.warn('Failed to set binaryType on CRDT websocket', error);\n }\n }\n\n const handleMessage = (event: any) => {\n // Use the currently connected doc reference (not the doc that created the socket),\n // so a later connect(doc) call can rebind without needing a new WebSocket.\n const activeDoc = subscribedDoc ?? doc;\n if (!activeDoc) return;\n // Binary updates flow directly\n if (\n event.data instanceof ArrayBuffer ||\n ArrayBuffer.isView(event.data)\n ) {\n const bytes =\n event.data instanceof ArrayBuffer\n ? new Uint8Array(event.data)\n : new Uint8Array(\n event.data.buffer,\n event.data.byteOffset,\n event.data.byteLength,\n );\n activeDoc.import(bytes);\n return;\n }\n if (typeof Blob !== 'undefined' && event.data instanceof Blob) {\n void event.data\n .arrayBuffer()\n .then((buf: ArrayBuffer) => activeDoc.import(new Uint8Array(buf)))\n .catch((error: unknown) =>\n console.warn('Failed to decode CRDT binary message', error),\n );\n return;\n }\n\n if (typeof event.data === 'string') {\n try {\n const parsed = JSON.parse(event.data);\n console.debug('[crdt] ws message', parsed?.type);\n if (parsed?.type === 'crdt-joined') {\n joined = true;\n console.debug(\n '[crdt] joined room, flushing',\n pending.length,\n 'pending',\n );\n // flush pending updates\n while (pending.length) {\n const update = pending.shift();\n if (update && ws && ws.readyState === WS_OPEN) {\n // Debug visibility for diagnosing silent sync issues.\n console.debug(\n '[crdt] sending pending update after join',\n update.byteLength,\n 'bytes',\n );\n ws.send(update);\n }\n }\n return;\n }\n if (parsed?.type === 'crdt-snapshot' && parsed.data) {\n const bytes = fromBase64(parsed.data);\n activeDoc.import(bytes);\n }\n // Ignore other messages (errors, acks) for now\n } catch (error) {\n console.warn('Failed to parse CRDT message', error);\n }\n }\n };\n\n const handleOpen = () => {\n attempt = 0;\n joined = false;\n connecting = false;\n sendStatus('open');\n sendJoin();\n if (options.sendSnapshotOnConnect) {\n sendSnapshot(doc);\n }\n ensureLocalSubscription(doc);\n };\n\n const handleClose = (event: CloseEvent) => {\n console.warn('CRDT WS closed', {\n code: event.code,\n reason: event.reason,\n pending: pending.length,\n joined,\n });\n connecting = false;\n sendStatus('closed');\n unsubscribeLocal?.();\n unsubscribeLocal = undefined;\n localSubscribed = false;\n subscribedDoc = undefined;\n joined = false;\n pending.length = 0;\n detachSocketListeners?.();\n detachSocketListeners = undefined;\n listeningSocket = undefined;\n scheduleReconnect(doc);\n };\n\n const handleError = (event: Event) => {\n console.warn('CRDT WS error', event);\n connecting = false;\n sendStatus('error');\n joined = false;\n unsubscribeLocal?.();\n unsubscribeLocal = undefined;\n localSubscribed = false;\n subscribedDoc = undefined;\n detachSocketListeners?.();\n detachSocketListeners = undefined;\n listeningSocket = undefined;\n scheduleReconnect(doc);\n };\n\n // Some WebSocket implementations expose either addEventListener or on*\n // handlers. Use one style only to avoid duplicate events.\n if (typeof ws.addEventListener === 'function') {\n ws.addEventListener('message', handleMessage);\n ws.addEventListener('open', handleOpen);\n ws.addEventListener('close', handleClose);\n ws.addEventListener('error', handleError);\n detachSocketListeners = () => {\n try {\n ws.removeEventListener('message', handleMessage);\n ws.removeEventListener('open', handleOpen);\n ws.removeEventListener('close', handleClose);\n ws.removeEventListener('error', handleError);\n } catch (error) {\n console.warn('[crdt] failed to detach socket listeners', error);\n }\n };\n } else {\n (ws as any).onmessage = handleMessage;\n (ws as any).onopen = handleOpen;\n (ws as any).onclose = handleClose;\n (ws as any).onerror = handleError;\n detachSocketListeners = () => {\n try {\n (ws as any).onmessage = null;\n (ws as any).onopen = null;\n (ws as any).onclose = null;\n (ws as any).onerror = null;\n } catch (error) {\n console.warn('[crdt] failed to clear socket handlers', error);\n }\n };\n }\n listeningSocket = ws;\n\n // If we stay in CONNECTING too long, force a reconnect to avoid being stuck.\n setTimeout(() => {\n if (ws && ws.readyState === WS_CONNECTING && !joined) {\n console.warn('[crdt] ws still connecting after timeout; retrying');\n try {\n ws.close();\n } catch (error) {\n console.warn('[crdt] error closing stuck socket', error);\n }\n }\n }, 3000);\n };\n\n if (\n socket &&\n (socket.readyState === WS_OPEN || socket.readyState === WS_CONNECTING)\n ) {\n console.info(\n '[crdt] reuse existing socket, readyState:',\n socket.readyState,\n );\n if (listeningSocket !== socket) {\n console.warn('[crdt] existing socket had no listeners; attaching now');\n attachSocketListenersFor(socket);\n // If the socket is already open, we won't get an 'open' event, so act as if\n // we just opened: (re)join and optionally send snapshot.\n if (socket.readyState === WS_OPEN) {\n attempt = 0;\n joined = false;\n sendStatus('open');\n sendJoin();\n if (options.sendSnapshotOnConnect) sendSnapshot(doc);\n }\n }\n return;\n }\n if (reconnectTimer) {\n clearTimeout(reconnectTimer);\n reconnectTimer = undefined;\n }\n connecting = true;\n sendStatus('connecting');\n const wsCreator =\n options.createSocket ??\n ((url, protocols) => new WebSocket(url, protocols));\n const url = buildUrl();\n try {\n socket = wsCreator(url, options.protocols);\n console.info('[crdt] ws created', url);\n } catch (error) {\n connecting = false;\n sendStatus('error');\n scheduleReconnect(doc);\n return;\n }\n attachSocketListenersFor(socket);\n };\n\n const disconnect = async () => {\n stopped = true;\n if (reconnectTimer) {\n clearTimeout(reconnectTimer);\n reconnectTimer = undefined;\n }\n unsubscribeLocal?.();\n unsubscribeLocal = undefined;\n localSubscribed = false;\n subscribedDoc = undefined;\n detachSocketListeners?.();\n detachSocketListeners = undefined;\n listeningSocket = undefined;\n if (socket) {\n try {\n socket.close();\n } catch (error) {\n console.warn('Error closing CRDT websocket', error);\n }\n }\n socket = undefined;\n sendStatus('closed');\n };\n\n return {connect, disconnect};\n}\n"]}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { SchemaType, InferType } from 'loro-mirror';
|
|
2
|
+
import { StateCreator } from 'zustand';
|
|
3
|
+
export type StoreSet<S> = Parameters<StateCreator<S>>[0];
|
|
4
|
+
export type StoreGet<S> = Parameters<StateCreator<S>>[1];
|
|
5
|
+
export type MirrorSchema<T extends SchemaType = SchemaType> = T;
|
|
6
|
+
/**
|
|
7
|
+
* `loro-mirror` injects internal `$cid` metadata into mirrored map objects.
|
|
8
|
+
*
|
|
9
|
+
* Store state typically doesn't include `$cid`, so binding `select()` functions
|
|
10
|
+
* naturally return the same shape *minus* `$cid`. This helper strips `$cid`
|
|
11
|
+
* recursively so `select()` can be type-safe without forcing `any` casts.
|
|
12
|
+
*/
|
|
13
|
+
export type StripCidDeep<T> = T extends (infer U)[] ? StripCidDeep<U>[] : T extends Record<string, any> ? T extends {
|
|
14
|
+
$cid: any;
|
|
15
|
+
} ? Omit<{
|
|
16
|
+
[K in keyof T]: StripCidDeep<T[K]>;
|
|
17
|
+
}, '$cid'> : {
|
|
18
|
+
[K in keyof T]: StripCidDeep<T[K]>;
|
|
19
|
+
} : T;
|
|
20
|
+
export type InferredState<TSchema extends SchemaType> = InferType<TSchema> extends Record<string, any> ? InferType<TSchema> : Record<string, never>;
|
|
21
|
+
/**
|
|
22
|
+
* Local equivalent of `createSlice` from `@sqlrooms/room-store`.
|
|
23
|
+
*
|
|
24
|
+
* Kept inline so `@sqlrooms/crdt` stays dependency-light (no need to depend on
|
|
25
|
+
* `@sqlrooms/room-store` just for a typing helper).
|
|
26
|
+
*/
|
|
27
|
+
export declare function createSlice<SliceState, StoreState extends SliceState = SliceState>(sliceCreator: (...args: Parameters<StateCreator<StoreState>>) => SliceState): StateCreator<SliceState>;
|
|
28
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAE,SAAS,EAAC,MAAM,aAAa,CAAC;AAClD,OAAO,EAAC,YAAY,EAAW,MAAM,SAAS,CAAC;AAE/C,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACzD,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAEzD,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,UAAU,GAAG,UAAU,IAAI,CAAC,CAAC;AAEhE;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,GAC/C,YAAY,CAAC,CAAC,CAAC,EAAE,GACjB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC3B,CAAC,SAAS;IAAC,IAAI,EAAE,GAAG,CAAA;CAAC,GACnB,IAAI,CAAC;KAAE,CAAC,IAAI,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAC,EAAE,MAAM,CAAC,GAClD;KAAE,CAAC,IAAI,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAC,GACtC,CAAC,CAAC;AAER,MAAM,MAAM,aAAa,CAAC,OAAO,SAAS,UAAU,IAClD,SAAS,CAAC,OAAO,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1C,SAAS,CAAC,OAAO,CAAC,GAClB,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAE5B;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,UAAU,EACV,UAAU,SAAS,UAAU,GAAG,UAAU,EAE1C,YAAY,EAAE,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,KAAK,UAAU,GAC1E,YAAY,CAAC,UAAU,CAAC,CAG1B"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local equivalent of `createSlice` from `@sqlrooms/room-store`.
|
|
3
|
+
*
|
|
4
|
+
* Kept inline so `@sqlrooms/crdt` stays dependency-light (no need to depend on
|
|
5
|
+
* `@sqlrooms/room-store` just for a typing helper).
|
|
6
|
+
*/
|
|
7
|
+
export function createSlice(sliceCreator) {
|
|
8
|
+
return (set, get, store) => sliceCreator(set, get, store);
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA4BA;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAIzB,YAA2E;IAE3E,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,CACzB,YAAY,CAAC,GAAG,EAAE,GAAuB,EAAE,KAA6B,CAAC,CAAC;AAC9E,CAAC","sourcesContent":["import {SchemaType, InferType} from 'loro-mirror';\nimport {StateCreator, StoreApi} from 'zustand';\n\nexport type StoreSet<S> = Parameters<StateCreator<S>>[0];\nexport type StoreGet<S> = Parameters<StateCreator<S>>[1];\n\nexport type MirrorSchema<T extends SchemaType = SchemaType> = T;\n\n/**\n * `loro-mirror` injects internal `$cid` metadata into mirrored map objects.\n *\n * Store state typically doesn't include `$cid`, so binding `select()` functions\n * naturally return the same shape *minus* `$cid`. This helper strips `$cid`\n * recursively so `select()` can be type-safe without forcing `any` casts.\n */\nexport type StripCidDeep<T> = T extends (infer U)[]\n ? StripCidDeep<U>[]\n : T extends Record<string, any>\n ? T extends {$cid: any}\n ? Omit<{[K in keyof T]: StripCidDeep<T[K]>}, '$cid'>\n : {[K in keyof T]: StripCidDeep<T[K]>}\n : T;\n\nexport type InferredState<TSchema extends SchemaType> =\n InferType<TSchema> extends Record<string, any>\n ? InferType<TSchema>\n : Record<string, never>;\n\n/**\n * Local equivalent of `createSlice` from `@sqlrooms/room-store`.\n *\n * Kept inline so `@sqlrooms/crdt` stays dependency-light (no need to depend on\n * `@sqlrooms/room-store` just for a typing helper).\n */\nexport function createSlice<\n SliceState,\n StoreState extends SliceState = SliceState,\n>(\n sliceCreator: (...args: Parameters<StateCreator<StoreState>>) => SliceState,\n): StateCreator<SliceState> {\n return (set, get, store) =>\n sliceCreator(set, get as () => StoreState, store as StoreApi<StoreState>);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sqlrooms/crdt",
|
|
3
|
-
"version": "0.29.0-rc.
|
|
3
|
+
"version": "0.29.0-rc.2",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"repository": {
|
|
12
12
|
"type": "git",
|
|
13
|
-
"url": "https://github.com/sqlrooms/sqlrooms.git"
|
|
13
|
+
"url": "git+https://github.com/sqlrooms/sqlrooms.git"
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
16
|
"dist"
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"zustand": "^5.0.8"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@jest/globals": "^30.
|
|
28
|
+
"@jest/globals": "^30.3.0",
|
|
29
29
|
"@types/jest": "^30.0.0",
|
|
30
30
|
"jest": "^30.1.3",
|
|
31
31
|
"ts-jest": "^29.4.4"
|
|
@@ -41,5 +41,5 @@
|
|
|
41
41
|
"typecheck": "tsc --noEmit",
|
|
42
42
|
"typedoc": "typedoc"
|
|
43
43
|
},
|
|
44
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "5d511631992c1af8852ea79ced488867aad4a555"
|
|
45
45
|
}
|