@electric-sql/y-electric 0.1.20 → 0.1.22
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 +13 -6
- package/dist/cjs/index.cjs +131 -138
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +8 -6
- package/dist/index.browser.mjs +1 -1
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.d.ts +8 -6
- package/dist/index.legacy-esm.js +43 -22
- package/dist/index.legacy-esm.js.map +1 -1
- package/dist/index.mjs +131 -138
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/local-storage-resume-state.ts +0 -1
- package/src/types.ts +2 -5
- package/src/y-electric.ts +50 -26
package/README.md
CHANGED
|
@@ -9,20 +9,26 @@ The typical flow for syncing shared documents using Yjs and Electric is the foll
|
|
|
9
9
|
1. Developer exposes a shape proxy for [authorizing](https://electric-sql.com/docs/guides/auth) shape requests
|
|
10
10
|
2. Clients define a [shape](https://electric-sql.com/docs/guides/shapes) for syncing changes for a [Y.Doc](https://docs.yjs.dev/api/y.doc)
|
|
11
11
|
3. Developer exposes a [write API](#Handling Writes) for handling Yjs updates
|
|
12
|
-
4.
|
|
12
|
+
4. Voilà! Y-Electric automatically shares updates across all connected clients
|
|
13
|
+
|
|
14
|
+
### Key Features
|
|
13
15
|
|
|
14
16
|
### Basic Setup
|
|
15
17
|
|
|
16
18
|
```typescript
|
|
17
19
|
import * as Y from 'yjs'
|
|
18
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
ElectricProvider,
|
|
22
|
+
LocalStorageResumeStateProvider,
|
|
23
|
+
} from '@electric-sql/y-electric'
|
|
19
24
|
import { Awareness } from 'y-protocols/awareness'
|
|
20
25
|
import { parseToDecoder } from '@electric-sql/y-electric/utils'
|
|
21
26
|
|
|
22
27
|
const ydoc = new Y.Doc()
|
|
23
28
|
const awareness = new Awareness(ydoc)
|
|
29
|
+
const resumeStateProvider = new LocalStorageResumeStateProvider('my-doc')
|
|
24
30
|
|
|
25
|
-
new ElectricProvider({
|
|
31
|
+
const provider = new ElectricProvider({
|
|
26
32
|
doc: ydoc,
|
|
27
33
|
documentUpdates: {
|
|
28
34
|
shape: {
|
|
@@ -51,6 +57,9 @@ new ElectricProvider({
|
|
|
51
57
|
},
|
|
52
58
|
resumeState: resumeStateProvider.load(),
|
|
53
59
|
})
|
|
60
|
+
|
|
61
|
+
// Subscribe to resume state changes to persist them
|
|
62
|
+
resumeStateProvider.subscribeToResumeState(provider)
|
|
54
63
|
```
|
|
55
64
|
|
|
56
65
|
### Handling Writes
|
|
@@ -116,6 +125,4 @@ In the client, you need to pass a `getUpdateFromRow` to extract the column with
|
|
|
116
125
|
|
|
117
126
|
### Storage providers
|
|
118
127
|
|
|
119
|
-
Y-Electric
|
|
120
|
-
|
|
121
|
-
The ElectricStorageProvider also keeps track of the document state vector to handle offline updates.
|
|
128
|
+
Y-Electric works with existing [database providers](https://docs.yjs.dev/ecosystem/database-provider) to store documents locally. When saving documents locally, we recommend providing a `ElectricResumeStateProvider` to save a resume point for the document, otherwise the entire document will be retransmitted when a new client session starts.
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -43,26 +43,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
43
43
|
mod
|
|
44
44
|
));
|
|
45
45
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
46
|
-
var __async = (__this, __arguments, generator) => {
|
|
47
|
-
return new Promise((resolve, reject) => {
|
|
48
|
-
var fulfilled = (value) => {
|
|
49
|
-
try {
|
|
50
|
-
step(generator.next(value));
|
|
51
|
-
} catch (e) {
|
|
52
|
-
reject(e);
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
var rejected = (value) => {
|
|
56
|
-
try {
|
|
57
|
-
step(generator.throw(value));
|
|
58
|
-
} catch (e) {
|
|
59
|
-
reject(e);
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
63
|
-
step((generator = generator.apply(__this, __arguments)).next());
|
|
64
|
-
});
|
|
65
|
-
};
|
|
66
46
|
|
|
67
47
|
// src/index.ts
|
|
68
48
|
var src_exports = {};
|
|
@@ -102,6 +82,7 @@ var ElectricProvider = class extends import_observable.ObservableV2 {
|
|
|
102
82
|
* @param {ResumeState} [options.resumeState] - Resume state for the provider
|
|
103
83
|
* @param {boolean} [options.connect=true] - Whether to automatically connect upon initialization
|
|
104
84
|
* @param {typeof fetch} [options.fetchClient] - Custom fetch implementation to use for HTTP requests
|
|
85
|
+
* @param {number} [options.debounceMs] - Debounce window in milliseconds for sending document updates. If 0 or undefined, debouncing is disabled.
|
|
105
86
|
*/
|
|
106
87
|
constructor({
|
|
107
88
|
doc,
|
|
@@ -109,7 +90,8 @@ var ElectricProvider = class extends import_observable.ObservableV2 {
|
|
|
109
90
|
awarenessUpdates: awarenessUpdatesConfig,
|
|
110
91
|
resumeState,
|
|
111
92
|
connect = true,
|
|
112
|
-
fetchClient
|
|
93
|
+
fetchClient,
|
|
94
|
+
debounceMs
|
|
113
95
|
}) {
|
|
114
96
|
var _a;
|
|
115
97
|
super();
|
|
@@ -119,10 +101,12 @@ var ElectricProvider = class extends import_observable.ObservableV2 {
|
|
|
119
101
|
this.pendingChanges = null;
|
|
120
102
|
this.sendingAwarenessState = false;
|
|
121
103
|
this.pendingAwarenessUpdate = null;
|
|
104
|
+
this.debounceTimer = null;
|
|
122
105
|
this.doc = doc;
|
|
123
106
|
this.documentUpdates = documentUpdatesConfig;
|
|
124
107
|
this.awarenessUpdates = awarenessUpdatesConfig;
|
|
125
108
|
this.resumeState = resumeState != null ? resumeState : {};
|
|
109
|
+
this.debounceMs = debounceMs != null ? debounceMs : 0;
|
|
126
110
|
this.fetchClient = fetchClient;
|
|
127
111
|
this.exitHandler = () => {
|
|
128
112
|
if (env.isNode && typeof process !== `undefined`) {
|
|
@@ -176,8 +160,30 @@ var ElectricProvider = class extends import_observable.ObservableV2 {
|
|
|
176
160
|
this.pendingChanges = update;
|
|
177
161
|
}
|
|
178
162
|
}
|
|
163
|
+
clearDebounceTimer() {
|
|
164
|
+
if (this.debounceTimer !== null) {
|
|
165
|
+
clearTimeout(this.debounceTimer);
|
|
166
|
+
this.debounceTimer = null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
scheduleSendOperations() {
|
|
170
|
+
if (this.debounceMs > 0) {
|
|
171
|
+
if (this.debounceTimer === null) {
|
|
172
|
+
this.debounceTimer = setTimeout(async () => {
|
|
173
|
+
this.debounceTimer = null;
|
|
174
|
+
await this.sendOperations();
|
|
175
|
+
if (this.pendingChanges && this.connected && !this.sendingPendingChanges) {
|
|
176
|
+
this.scheduleSendOperations();
|
|
177
|
+
}
|
|
178
|
+
}, this.debounceMs);
|
|
179
|
+
}
|
|
180
|
+
} else {
|
|
181
|
+
this.sendOperations();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
179
184
|
destroy() {
|
|
180
185
|
var _a;
|
|
186
|
+
this.clearDebounceTimer();
|
|
181
187
|
this.disconnect();
|
|
182
188
|
this.doc.off(`update`, this.documentUpdateHandler);
|
|
183
189
|
(_a = this.awarenessUpdates) == null ? void 0 : _a.protocol.off(`update`, this.awarenessUpdateHandler);
|
|
@@ -188,6 +194,10 @@ var ElectricProvider = class extends import_observable.ObservableV2 {
|
|
|
188
194
|
}
|
|
189
195
|
disconnect() {
|
|
190
196
|
var _a;
|
|
197
|
+
this.clearDebounceTimer();
|
|
198
|
+
if (this.pendingChanges && this.connected) {
|
|
199
|
+
this.sendOperations();
|
|
200
|
+
}
|
|
191
201
|
(_a = this.unsubscribeShapes) == null ? void 0 : _a.call(this);
|
|
192
202
|
if (!this.connected) {
|
|
193
203
|
return;
|
|
@@ -231,16 +241,15 @@ var ElectricProvider = class extends import_observable.ObservableV2 {
|
|
|
231
241
|
);
|
|
232
242
|
let awarenessShapeUnsubscribe;
|
|
233
243
|
if (this.awarenessUpdates) {
|
|
234
|
-
const awarenessStream = new import_client.ShapeStream(__spreadProps(__spreadValues(
|
|
235
|
-
signal: abortController.signal
|
|
244
|
+
const awarenessStream = new import_client.ShapeStream(__spreadProps(__spreadValues({}, this.awarenessUpdates.shape), {
|
|
245
|
+
signal: abortController.signal,
|
|
246
|
+
offset: `now`
|
|
236
247
|
}));
|
|
237
|
-
awarenessShapeUnsubscribe = awarenessStream.subscribe(
|
|
238
|
-
|
|
239
|
-
messages
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
);
|
|
243
|
-
});
|
|
248
|
+
awarenessShapeUnsubscribe = awarenessStream.subscribe(
|
|
249
|
+
(messages) => {
|
|
250
|
+
this.awarenessShapeHandler(messages);
|
|
251
|
+
}
|
|
252
|
+
);
|
|
244
253
|
}
|
|
245
254
|
this.unsubscribeShapes = () => {
|
|
246
255
|
abortController.abort();
|
|
@@ -272,89 +281,82 @@ var ElectricProvider = class extends import_observable.ObservableV2 {
|
|
|
272
281
|
}
|
|
273
282
|
}
|
|
274
283
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
282
|
-
this.batch(update);
|
|
283
|
-
this.sendOperations();
|
|
284
|
-
});
|
|
284
|
+
async applyDocumentUpdate(update, origin) {
|
|
285
|
+
if (origin === `server`) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
this.batch(update);
|
|
289
|
+
this.scheduleSendOperations();
|
|
285
290
|
}
|
|
286
|
-
sendOperations() {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
291
|
+
async sendOperations() {
|
|
292
|
+
var _a;
|
|
293
|
+
this.clearDebounceTimer();
|
|
294
|
+
if (!this.connected || this.sendingPendingChanges) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
try {
|
|
298
|
+
this.sendingPendingChanges = true;
|
|
299
|
+
while (this.pendingChanges && this.pendingChanges.length > 2 && this.connected) {
|
|
300
|
+
const sending = this.pendingChanges;
|
|
301
|
+
this.pendingChanges = null;
|
|
302
|
+
const encoder = encoding.createEncoder();
|
|
303
|
+
encoding.writeVarUint8Array(encoder, sending);
|
|
304
|
+
const success = await send(
|
|
305
|
+
encoder,
|
|
306
|
+
this.documentUpdates.sendUrl,
|
|
307
|
+
(_a = this.fetchClient) != null ? _a : fetch,
|
|
308
|
+
this.documentUpdates.sendErrorRetryHandler
|
|
309
|
+
);
|
|
310
|
+
if (!success) {
|
|
311
|
+
this.batch(sending);
|
|
312
|
+
this.disconnect();
|
|
309
313
|
}
|
|
310
|
-
this.resumeState.stableStateVector = Y.encodeStateVector(this.doc);
|
|
311
|
-
this.emit(`resumeState`, [this.resumeState]);
|
|
312
|
-
} finally {
|
|
313
|
-
this.sendingPendingChanges = false;
|
|
314
314
|
}
|
|
315
|
-
|
|
315
|
+
this.resumeState.stableStateVector = Y.encodeStateVector(this.doc);
|
|
316
|
+
this.emit(`resumeState`, [this.resumeState]);
|
|
317
|
+
} finally {
|
|
318
|
+
this.sendingPendingChanges = false;
|
|
319
|
+
}
|
|
316
320
|
}
|
|
317
|
-
applyAwarenessUpdate(awarenessUpdate, origin) {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
this.disconnect();
|
|
350
|
-
}
|
|
321
|
+
async applyAwarenessUpdate(awarenessUpdate, origin) {
|
|
322
|
+
var _a;
|
|
323
|
+
if (origin !== `local` || !this.connected) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
this.pendingAwarenessUpdate = awarenessUpdate;
|
|
327
|
+
if (this.sendingAwarenessState) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
this.sendingAwarenessState = true;
|
|
331
|
+
try {
|
|
332
|
+
while (this.pendingAwarenessUpdate && this.connected) {
|
|
333
|
+
const update = this.pendingAwarenessUpdate;
|
|
334
|
+
this.pendingAwarenessUpdate = null;
|
|
335
|
+
const { added, updated, removed } = update;
|
|
336
|
+
const changedClients = added.concat(updated).concat(removed);
|
|
337
|
+
const encoder = encoding.createEncoder();
|
|
338
|
+
encoding.writeVarUint8Array(
|
|
339
|
+
encoder,
|
|
340
|
+
awarenessProtocol.encodeAwarenessUpdate(
|
|
341
|
+
this.awarenessUpdates.protocol,
|
|
342
|
+
changedClients
|
|
343
|
+
)
|
|
344
|
+
);
|
|
345
|
+
const success = await send(
|
|
346
|
+
encoder,
|
|
347
|
+
this.awarenessUpdates.sendUrl,
|
|
348
|
+
(_a = this.fetchClient) != null ? _a : fetch,
|
|
349
|
+
this.awarenessUpdates.sendErrorRetryHandler
|
|
350
|
+
);
|
|
351
|
+
if (!success) {
|
|
352
|
+
this.disconnect();
|
|
351
353
|
}
|
|
352
|
-
} finally {
|
|
353
|
-
this.sendingAwarenessState = false;
|
|
354
354
|
}
|
|
355
|
-
}
|
|
355
|
+
} finally {
|
|
356
|
+
this.sendingAwarenessState = false;
|
|
357
|
+
}
|
|
356
358
|
}
|
|
357
|
-
awarenessShapeHandler(messages
|
|
359
|
+
awarenessShapeHandler(messages) {
|
|
358
360
|
for (const message of messages) {
|
|
359
361
|
if ((0, import_client.isChangeMessage)(message)) {
|
|
360
362
|
if (message.headers.operation === `delete`) {
|
|
@@ -371,41 +373,33 @@ var ElectricProvider = class extends import_observable.ObservableV2 {
|
|
|
371
373
|
this
|
|
372
374
|
);
|
|
373
375
|
}
|
|
374
|
-
} else if ((0, import_client.isControlMessage)(message) && message.headers.control === `up-to-date`) {
|
|
375
|
-
this.resumeState.awareness = {
|
|
376
|
-
offset,
|
|
377
|
-
handle
|
|
378
|
-
};
|
|
379
|
-
this.emit(`resumeState`, [this.resumeState]);
|
|
380
376
|
}
|
|
381
377
|
}
|
|
382
378
|
}
|
|
383
379
|
};
|
|
384
|
-
function send(encoder, endpoint, fetchClient, retryHandler) {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
throw new Error(`Server did not return 2xx`);
|
|
399
|
-
}
|
|
400
|
-
return true;
|
|
401
|
-
} catch (error) {
|
|
402
|
-
const shouldRetry = yield (_a = retryHandler == null ? void 0 : retryHandler({
|
|
403
|
-
response,
|
|
404
|
-
error
|
|
405
|
-
})) != null ? _a : false;
|
|
406
|
-
return shouldRetry;
|
|
380
|
+
async function send(encoder, endpoint, fetchClient, retryHandler) {
|
|
381
|
+
var _a;
|
|
382
|
+
let response;
|
|
383
|
+
const op = encoding.toUint8Array(encoder);
|
|
384
|
+
try {
|
|
385
|
+
response = await fetchClient(endpoint, {
|
|
386
|
+
method: `PUT`,
|
|
387
|
+
headers: {
|
|
388
|
+
"Content-Type": `application/octet-stream`
|
|
389
|
+
},
|
|
390
|
+
body: op
|
|
391
|
+
});
|
|
392
|
+
if (!response.ok) {
|
|
393
|
+
throw new Error(`Server did not return 2xx`);
|
|
407
394
|
}
|
|
408
|
-
|
|
395
|
+
return true;
|
|
396
|
+
} catch (error) {
|
|
397
|
+
const shouldRetry = await ((_a = retryHandler == null ? void 0 : retryHandler({
|
|
398
|
+
response,
|
|
399
|
+
error
|
|
400
|
+
})) != null ? _a : false);
|
|
401
|
+
return shouldRetry;
|
|
402
|
+
}
|
|
409
403
|
}
|
|
410
404
|
|
|
411
405
|
// src/local-storage-resume-state.ts
|
|
@@ -422,8 +416,7 @@ var LocalStorageResumeStateProvider = class extends import_observable2.Observabl
|
|
|
422
416
|
}
|
|
423
417
|
save(resumeState) {
|
|
424
418
|
const jsonPart = JSON.stringify({
|
|
425
|
-
operations: resumeState.document
|
|
426
|
-
awareness: resumeState.awareness
|
|
419
|
+
operations: resumeState.document
|
|
427
420
|
});
|
|
428
421
|
localStorage.setItem(this.key, jsonPart);
|
|
429
422
|
if (resumeState.stableStateVector) {
|
package/dist/cjs/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/index.ts","../../src/y-electric.ts","../../src/local-storage-resume-state.ts","../../src/utils.ts"],"sourcesContent":["export * from './y-electric'\nexport * from './local-storage-resume-state'\nexport * from './utils'\nexport * from './types'\n","import * as encoding from 'lib0/encoding'\nimport * as decoding from 'lib0/decoding'\nimport * as awarenessProtocol from 'y-protocols/awareness'\nimport { ObservableV2 } from 'lib0/observable'\nimport * as env from 'lib0/environment'\nimport * as Y from 'yjs'\nimport {\n GetExtensions,\n isChangeMessage,\n isControlMessage,\n Message,\n Offset,\n Row,\n ShapeStream,\n ShapeStreamOptions,\n} from '@electric-sql/client'\nimport {\n YProvider,\n ResumeState,\n SendErrorRetryHandler,\n ElectricProviderOptions,\n} from './types'\n\ntype AwarenessUpdate = {\n added: number[]\n updated: number[]\n removed: number[]\n}\n\nexport class ElectricProvider<\n RowWithDocumentUpdate extends Row<decoding.Decoder> = never,\n RowWithAwarenessUpdate extends Row<decoding.Decoder> = never,\n> extends ObservableV2<YProvider> {\n private doc: Y.Doc\n\n private documentUpdates: {\n shape: ShapeStreamOptions<GetExtensions<RowWithDocumentUpdate>>\n sendUrl: string | URL\n getUpdateFromRow: (row: RowWithDocumentUpdate) => decoding.Decoder\n sendErrorRetryHandler?: SendErrorRetryHandler\n }\n\n private awarenessUpdates?: {\n shape: ShapeStreamOptions<GetExtensions<RowWithAwarenessUpdate>>\n sendUrl: string | URL\n protocol: awarenessProtocol.Awareness\n getUpdateFromRow: (row: RowWithAwarenessUpdate) => decoding.Decoder\n sendErrorRetryHandler?: SendErrorRetryHandler\n }\n\n private _connected: boolean = false\n private _synced: boolean = false\n\n private resumeState: ResumeState\n private sendingPendingChanges: boolean = false\n private pendingChanges: Uint8Array | null = null\n private sendingAwarenessState: boolean = false\n private pendingAwarenessUpdate: AwarenessUpdate | null = null\n\n private documentUpdateHandler: (\n update: Uint8Array,\n origin: unknown,\n doc: Y.Doc,\n transaction: Y.Transaction\n ) => void\n private awarenessUpdateHandler?: (\n update: AwarenessUpdate,\n origin: unknown\n ) => void\n\n private exitHandler: () => void\n private unsubscribeShapes?: () => void\n\n private fetchClient?: typeof fetch\n\n /**\n * Creates a new ElectricProvider instance that connects YJS documents to Electric SQL.\n *\n * @constructor\n * @param {ElectricProviderOptions} options - Configuration options for the provider\n * @param {Y.Doc} options.doc - The YJS document to be synchronized\n * @param {Object} options.documentUpdates - Document updates configuration\n * @param {ShapeStreamOptions} options.documentUpdates.shape - Options for the document updates shape stream\n * @param {string|URL} options.documentUpdates.sendUrl - URL endpoint for sending document updates\n * @param {Function} options.documentUpdates.getUpdateFromRow - Function to extract document update from row\n * @param {SendErrorRetryHandler} [options.documentUpdates.sendErrorRetryHandler] - Error handler for retrying document updates\n * @param {Object} [options.awarenessUpdates] - Awareness updates configuration (optional)\n * @param {ShapeStreamOptions} options.awarenessUpdates.shape - Options for the awareness updates shape stream\n * @param {string|URL} options.awarenessUpdates.sendUrl - URL endpoint for sending awareness updates\n * @param {awarenessProtocol.Awareness} options.awarenessUpdates.protocol - Awareness protocol instance\n * @param {Function} options.awarenessUpdates.getUpdateFromRow - Function to extract awareness update from row\n * @param {SendErrorRetryHandler} [options.awarenessUpdates.sendErrorRetryHandler] - Error handler for retrying awareness updates\n * @param {ResumeState} [options.resumeState] - Resume state for the provider\n * @param {boolean} [options.connect=true] - Whether to automatically connect upon initialization\n * @param {typeof fetch} [options.fetchClient] - Custom fetch implementation to use for HTTP requests\n */\n constructor({\n doc,\n documentUpdates: documentUpdatesConfig,\n awarenessUpdates: awarenessUpdatesConfig,\n resumeState,\n connect = true,\n fetchClient,\n }: ElectricProviderOptions<RowWithDocumentUpdate, RowWithAwarenessUpdate>) {\n super()\n\n this.doc = doc\n this.documentUpdates = documentUpdatesConfig\n this.awarenessUpdates = awarenessUpdatesConfig\n this.resumeState = resumeState ?? {}\n\n this.fetchClient = fetchClient\n\n this.exitHandler = () => {\n if (env.isNode && typeof process !== `undefined`) {\n process.on(`exit`, this.destroy.bind(this))\n }\n }\n\n this.documentUpdateHandler = this.doc.on(\n `update`,\n this.applyDocumentUpdate.bind(this)\n )\n if (this.awarenessUpdates) {\n this.awarenessUpdateHandler = this.applyAwarenessUpdate.bind(this)\n this.awarenessUpdates.protocol.on(`update`, this.awarenessUpdateHandler!)\n }\n\n // enqueue unsynced changes from document if the\n // resume state provides the document state vector\n if (this.resumeState?.stableStateVector) {\n this.pendingChanges = Y.encodeStateAsUpdate(\n this.doc,\n this.resumeState.stableStateVector\n )\n }\n\n if (connect) {\n this.connect()\n }\n }\n\n get synced() {\n return this._synced\n }\n\n set synced(state) {\n if (this._synced !== state) {\n this._synced = state\n this.emit(`synced`, [state])\n this.emit(`sync`, [state])\n }\n }\n\n set connected(state) {\n if (this._connected !== state) {\n this._connected = state\n if (state) {\n this.sendOperations()\n }\n this.emit(`status`, [{ status: state ? `connected` : `disconnected` }])\n }\n }\n\n get connected() {\n return this._connected\n }\n\n private batch(update: Uint8Array) {\n if (this.pendingChanges) {\n this.pendingChanges = Y.mergeUpdates([this.pendingChanges, update])\n } else {\n this.pendingChanges = update\n }\n }\n\n destroy() {\n this.disconnect()\n\n this.doc.off(`update`, this.documentUpdateHandler)\n this.awarenessUpdates?.protocol.off(`update`, this.awarenessUpdateHandler!)\n\n if (env.isNode && typeof process !== `undefined`) {\n process.off(`exit`, this.exitHandler!)\n }\n super.destroy()\n }\n\n disconnect() {\n this.unsubscribeShapes?.()\n\n if (!this.connected) {\n return\n }\n\n if (this.awarenessUpdates) {\n awarenessProtocol.removeAwarenessStates(\n this.awarenessUpdates.protocol,\n Array.from(this.awarenessUpdates.protocol.getStates().keys()).filter(\n (client) => client !== this.awarenessUpdates!.protocol.clientID\n ),\n this\n )\n\n // try to notifying other clients that we are disconnecting\n awarenessProtocol.removeAwarenessStates(\n this.awarenessUpdates.protocol,\n [this.awarenessUpdates.protocol.clientID],\n `local`\n )\n\n this.awarenessUpdates.protocol.setLocalState({})\n }\n\n // TODO: await for events before closing\n this.emit(`connection-close`, [])\n\n this.pendingAwarenessUpdate = null\n\n this.connected = false\n this.synced = false\n }\n\n connect() {\n if (this.connected) {\n return\n }\n const abortController = new AbortController()\n\n const operationsStream = new ShapeStream<RowWithDocumentUpdate>({\n ...this.documentUpdates.shape,\n ...this.resumeState.document,\n signal: abortController.signal,\n })\n\n const operationsShapeUnsubscribe = operationsStream.subscribe(\n (messages) => {\n this.operationsShapeHandler(\n messages,\n operationsStream.lastOffset,\n operationsStream.shapeHandle!\n )\n }\n )\n\n let awarenessShapeUnsubscribe: () => void | undefined\n if (this.awarenessUpdates) {\n const awarenessStream = new ShapeStream<RowWithAwarenessUpdate>({\n ...this.awarenessUpdates.shape,\n ...this.resumeState.awareness,\n signal: abortController.signal,\n })\n\n awarenessShapeUnsubscribe = awarenessStream.subscribe((messages) => {\n this.awarenessShapeHandler(\n messages,\n awarenessStream.lastOffset,\n awarenessStream.shapeHandle!\n )\n })\n }\n\n this.unsubscribeShapes = () => {\n abortController.abort()\n operationsShapeUnsubscribe()\n awarenessShapeUnsubscribe?.()\n this.unsubscribeShapes = undefined\n }\n\n this.emit(`status`, [{ status: `connecting` }])\n }\n\n private operationsShapeHandler(\n messages: Message<RowWithDocumentUpdate>[],\n offset: Offset,\n handle: string\n ) {\n for (const message of messages) {\n if (isChangeMessage(message)) {\n const decoder = this.documentUpdates.getUpdateFromRow(message.value)\n while (decoder.pos !== decoder.arr.length) {\n const operation = decoding.readVarUint8Array(decoder)\n Y.applyUpdate(this.doc, operation, `server`)\n }\n } else if (\n isControlMessage(message) &&\n message.headers.control === `up-to-date`\n ) {\n this.resumeState.document = {\n offset,\n handle,\n }\n\n if (!this.sendingPendingChanges) {\n this.synced = true\n this.resumeState.stableStateVector = Y.encodeStateVector(this.doc)\n }\n this.emit(`resumeState`, [this.resumeState])\n this.connected = true\n }\n }\n }\n\n // TODO: add an optional throttler that batches updates\n // before pushing to the server\n private async applyDocumentUpdate(update: Uint8Array, origin: unknown) {\n // don't re-send updates from electric\n if (origin === `server`) {\n return\n }\n\n this.batch(update)\n this.sendOperations()\n }\n\n private async sendOperations() {\n if (!this.connected || this.sendingPendingChanges) {\n return\n }\n\n try {\n this.sendingPendingChanges = true\n while (\n this.pendingChanges &&\n this.pendingChanges.length > 2 &&\n this.connected\n ) {\n const sending = this.pendingChanges\n this.pendingChanges = null\n\n const encoder = encoding.createEncoder()\n encoding.writeVarUint8Array(encoder, sending)\n\n const success = await send(\n encoder,\n this.documentUpdates.sendUrl,\n this.fetchClient ?? fetch,\n this.documentUpdates.sendErrorRetryHandler\n )\n if (!success) {\n this.batch(sending)\n this.disconnect()\n }\n }\n // no more pending changes, move stableStateVector forward\n this.resumeState.stableStateVector = Y.encodeStateVector(this.doc)\n this.emit(`resumeState`, [this.resumeState])\n } finally {\n this.sendingPendingChanges = false\n }\n }\n\n private async applyAwarenessUpdate(\n awarenessUpdate: AwarenessUpdate,\n origin: unknown\n ) {\n if (origin !== `local` || !this.connected) {\n return\n }\n\n this.pendingAwarenessUpdate = awarenessUpdate\n\n if (this.sendingAwarenessState) {\n return\n }\n\n this.sendingAwarenessState = true\n\n try {\n while (this.pendingAwarenessUpdate && this.connected) {\n const update = this.pendingAwarenessUpdate\n this.pendingAwarenessUpdate = null\n\n const { added, updated, removed } = update\n const changedClients = added.concat(updated).concat(removed)\n const encoder = encoding.createEncoder()\n\n encoding.writeVarUint8Array(\n encoder,\n awarenessProtocol.encodeAwarenessUpdate(\n this.awarenessUpdates!.protocol,\n changedClients\n )\n )\n const success = await send(\n encoder,\n this.awarenessUpdates!.sendUrl,\n this.fetchClient ?? fetch,\n this.awarenessUpdates!.sendErrorRetryHandler\n )\n if (!success) {\n this.disconnect()\n }\n }\n } finally {\n this.sendingAwarenessState = false\n }\n }\n\n private awarenessShapeHandler(\n messages: Message<RowWithAwarenessUpdate>[],\n offset: Offset,\n handle: string\n ) {\n for (const message of messages) {\n if (isChangeMessage(message)) {\n if (message.headers.operation === `delete`) {\n awarenessProtocol.removeAwarenessStates(\n this.awarenessUpdates!.protocol,\n [Number(message.value.client_id)],\n `remote`\n )\n } else {\n const decoder = this.awarenessUpdates!.getUpdateFromRow(message.value)\n awarenessProtocol.applyAwarenessUpdate(\n this.awarenessUpdates!.protocol,\n decoding.readVarUint8Array(decoder),\n this\n )\n }\n } else if (\n isControlMessage(message) &&\n message.headers.control === `up-to-date`\n ) {\n this.resumeState.awareness = {\n offset: offset,\n handle: handle,\n }\n this.emit(`resumeState`, [this.resumeState])\n }\n }\n }\n}\n\nasync function send(\n encoder: encoding.Encoder,\n endpoint: string | URL,\n fetchClient: typeof fetch,\n retryHandler?: SendErrorRetryHandler\n): Promise<boolean> {\n let response: Response | undefined\n const op = encoding.toUint8Array(encoder)\n\n try {\n response = await fetchClient(endpoint!, {\n method: `PUT`,\n headers: {\n 'Content-Type': `application/octet-stream`,\n },\n body: op as BodyInit,\n })\n\n if (!response.ok) {\n throw new Error(`Server did not return 2xx`)\n }\n\n return true\n } catch (error) {\n const shouldRetry = await (retryHandler?.({\n response,\n error,\n }) ?? false)\n return shouldRetry\n }\n}\n","import { ResumeState, ElectricResumeStateProvider } from './types'\nimport { ObservableV2 } from 'lib0/observable.js'\nimport { ElectricProvider } from './y-electric'\nimport * as buffer from 'lib0/buffer'\n\n/**\n * A ResumeStateProvider implementation using LocalStorage.\n * This is a reference implementation that can be used as a starting point\n * for implementing other ResumeStateProviders.\n */\nexport class LocalStorageResumeStateProvider extends ObservableV2<ElectricResumeStateProvider> {\n private key: string\n private resumeState?: ResumeState\n\n constructor(key: string) {\n super()\n this.key = key\n }\n\n subscribeToResumeState(provider: ElectricProvider): () => void {\n const resumeStateHandler = provider.on(`resumeState`, this.save.bind(this))\n return () => provider.off(`resumeState`, resumeStateHandler)\n }\n\n save(resumeState: ResumeState) {\n const jsonPart = JSON.stringify({\n operations: resumeState.document,\n awareness: resumeState.awareness,\n })\n localStorage.setItem(this.key, jsonPart)\n\n if (resumeState.stableStateVector) {\n const vectorBase64 = buffer.toBase64(resumeState.stableStateVector)\n localStorage.setItem(`${this.key}_vector`, vectorBase64)\n } else {\n // ensure vector is removed\n localStorage.removeItem(`${this.key}_vector`)\n }\n }\n\n load(): ResumeState {\n if (this.resumeState) {\n return this.resumeState\n }\n\n const jsonData = localStorage.getItem(this.key)\n if (!jsonData) {\n this.emit(`synced`, [{}])\n } else {\n this.resumeState = JSON.parse(jsonData)\n\n const vectorData = localStorage.getItem(`${this.key}_vector`)\n if (vectorData) {\n this.resumeState!.stableStateVector = buffer.fromBase64(vectorData)\n }\n\n this.emit(`synced`, [this.resumeState!])\n }\n\n return this.resumeState!\n }\n}\n","import * as decoding from 'lib0/decoding'\n\n/**\n * Convert a hex string from PostgreSQL's bytea format to a Uint8Array\n */\nconst hexStringToUint8Array = (hexString: string) => {\n const cleanHexString = hexString.startsWith(`\\\\x`)\n ? hexString.slice(2)\n : hexString\n return new Uint8Array(\n cleanHexString.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16))\n )\n}\n\n/**\n * Utility to parse hex string bytea data to a decoder for YJS operations\n */\nexport const parseToDecoder = {\n bytea: (hexString: string) => {\n const uint8Array = hexStringToUint8Array(hexString)\n return decoding.createDecoder(uint8Array)\n },\n}\n"],"mappings":"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","names":["import_observable","decoding"]}
|
|
1
|
+
{"version":3,"sources":["../../src/index.ts","../../src/y-electric.ts","../../src/local-storage-resume-state.ts","../../src/utils.ts"],"sourcesContent":["export * from './y-electric'\nexport * from './local-storage-resume-state'\nexport * from './utils'\nexport * from './types'\n","import * as encoding from 'lib0/encoding'\nimport * as decoding from 'lib0/decoding'\nimport * as awarenessProtocol from 'y-protocols/awareness'\nimport { ObservableV2 } from 'lib0/observable'\nimport * as env from 'lib0/environment'\nimport * as Y from 'yjs'\nimport {\n GetExtensions,\n isChangeMessage,\n isControlMessage,\n Message,\n Offset,\n Row,\n ShapeStream,\n ShapeStreamOptions,\n} from '@electric-sql/client'\nimport {\n YProvider,\n ResumeState,\n SendErrorRetryHandler,\n ElectricProviderOptions,\n} from './types'\n\ntype AwarenessUpdate = {\n added: number[]\n updated: number[]\n removed: number[]\n}\n\nexport class ElectricProvider<\n RowWithDocumentUpdate extends Row<decoding.Decoder> = never,\n RowWithAwarenessUpdate extends Row<decoding.Decoder> = never,\n> extends ObservableV2<YProvider> {\n private doc: Y.Doc\n\n private documentUpdates: {\n shape: ShapeStreamOptions<GetExtensions<RowWithDocumentUpdate>>\n sendUrl: string | URL\n getUpdateFromRow: (row: RowWithDocumentUpdate) => decoding.Decoder\n sendErrorRetryHandler?: SendErrorRetryHandler\n }\n\n private awarenessUpdates?: {\n shape: ShapeStreamOptions<GetExtensions<RowWithAwarenessUpdate>>\n sendUrl: string | URL\n protocol: awarenessProtocol.Awareness\n getUpdateFromRow: (row: RowWithAwarenessUpdate) => decoding.Decoder\n sendErrorRetryHandler?: SendErrorRetryHandler\n }\n\n private _connected: boolean = false\n private _synced: boolean = false\n\n private resumeState: ResumeState\n private sendingPendingChanges: boolean = false\n private pendingChanges: Uint8Array | null = null\n private sendingAwarenessState: boolean = false\n private pendingAwarenessUpdate: AwarenessUpdate | null = null\n private debounceMs: number\n private debounceTimer: ReturnType<typeof setTimeout> | null = null\n\n private documentUpdateHandler: (\n update: Uint8Array,\n origin: unknown,\n doc: Y.Doc,\n transaction: Y.Transaction\n ) => void\n private awarenessUpdateHandler?: (\n update: AwarenessUpdate,\n origin: unknown\n ) => void\n\n private exitHandler: () => void\n private unsubscribeShapes?: () => void\n\n private fetchClient?: typeof fetch\n\n /**\n * Creates a new ElectricProvider instance that connects YJS documents to Electric SQL.\n *\n * @constructor\n * @param {ElectricProviderOptions} options - Configuration options for the provider\n * @param {Y.Doc} options.doc - The YJS document to be synchronized\n * @param {Object} options.documentUpdates - Document updates configuration\n * @param {ShapeStreamOptions} options.documentUpdates.shape - Options for the document updates shape stream\n * @param {string|URL} options.documentUpdates.sendUrl - URL endpoint for sending document updates\n * @param {Function} options.documentUpdates.getUpdateFromRow - Function to extract document update from row\n * @param {SendErrorRetryHandler} [options.documentUpdates.sendErrorRetryHandler] - Error handler for retrying document updates\n * @param {Object} [options.awarenessUpdates] - Awareness updates configuration (optional)\n * @param {ShapeStreamOptions} options.awarenessUpdates.shape - Options for the awareness updates shape stream\n * @param {string|URL} options.awarenessUpdates.sendUrl - URL endpoint for sending awareness updates\n * @param {awarenessProtocol.Awareness} options.awarenessUpdates.protocol - Awareness protocol instance\n * @param {Function} options.awarenessUpdates.getUpdateFromRow - Function to extract awareness update from row\n * @param {SendErrorRetryHandler} [options.awarenessUpdates.sendErrorRetryHandler] - Error handler for retrying awareness updates\n * @param {ResumeState} [options.resumeState] - Resume state for the provider\n * @param {boolean} [options.connect=true] - Whether to automatically connect upon initialization\n * @param {typeof fetch} [options.fetchClient] - Custom fetch implementation to use for HTTP requests\n * @param {number} [options.debounceMs] - Debounce window in milliseconds for sending document updates. If 0 or undefined, debouncing is disabled.\n */\n constructor({\n doc,\n documentUpdates: documentUpdatesConfig,\n awarenessUpdates: awarenessUpdatesConfig,\n resumeState,\n connect = true,\n fetchClient,\n debounceMs,\n }: ElectricProviderOptions<RowWithDocumentUpdate, RowWithAwarenessUpdate>) {\n super()\n\n this.doc = doc\n this.documentUpdates = documentUpdatesConfig\n this.awarenessUpdates = awarenessUpdatesConfig\n this.resumeState = resumeState ?? {}\n this.debounceMs = debounceMs ?? 0\n\n this.fetchClient = fetchClient\n\n this.exitHandler = () => {\n if (env.isNode && typeof process !== `undefined`) {\n process.on(`exit`, this.destroy.bind(this))\n }\n }\n\n this.documentUpdateHandler = this.doc.on(\n `update`,\n this.applyDocumentUpdate.bind(this)\n )\n if (this.awarenessUpdates) {\n this.awarenessUpdateHandler = this.applyAwarenessUpdate.bind(this)\n this.awarenessUpdates.protocol.on(`update`, this.awarenessUpdateHandler!)\n }\n\n // enqueue unsynced changes from document if the\n // resume state provides the document state vector\n if (this.resumeState?.stableStateVector) {\n this.pendingChanges = Y.encodeStateAsUpdate(\n this.doc,\n this.resumeState.stableStateVector\n )\n }\n\n if (connect) {\n this.connect()\n }\n }\n\n get synced() {\n return this._synced\n }\n\n set synced(state) {\n if (this._synced !== state) {\n this._synced = state\n this.emit(`synced`, [state])\n this.emit(`sync`, [state])\n }\n }\n\n set connected(state) {\n if (this._connected !== state) {\n this._connected = state\n if (state) {\n this.sendOperations()\n }\n this.emit(`status`, [{ status: state ? `connected` : `disconnected` }])\n }\n }\n\n get connected() {\n return this._connected\n }\n\n private batch(update: Uint8Array) {\n if (this.pendingChanges) {\n this.pendingChanges = Y.mergeUpdates([this.pendingChanges, update])\n } else {\n this.pendingChanges = update\n }\n }\n\n private clearDebounceTimer() {\n if (this.debounceTimer !== null) {\n clearTimeout(this.debounceTimer)\n this.debounceTimer = null\n }\n }\n\n private scheduleSendOperations() {\n if (this.debounceMs > 0) {\n if (this.debounceTimer === null) {\n this.debounceTimer = setTimeout(async () => {\n this.debounceTimer = null\n await this.sendOperations()\n if (\n this.pendingChanges &&\n this.connected &&\n !this.sendingPendingChanges\n ) {\n this.scheduleSendOperations()\n }\n }, this.debounceMs)\n }\n } else {\n this.sendOperations()\n }\n }\n\n destroy() {\n this.clearDebounceTimer()\n this.disconnect()\n\n this.doc.off(`update`, this.documentUpdateHandler)\n this.awarenessUpdates?.protocol.off(`update`, this.awarenessUpdateHandler!)\n\n if (env.isNode && typeof process !== `undefined`) {\n process.off(`exit`, this.exitHandler!)\n }\n super.destroy()\n }\n\n disconnect() {\n // Flush any pending changes before disconnecting\n this.clearDebounceTimer()\n if (this.pendingChanges && this.connected) {\n this.sendOperations()\n }\n\n this.unsubscribeShapes?.()\n\n if (!this.connected) {\n return\n }\n\n if (this.awarenessUpdates) {\n awarenessProtocol.removeAwarenessStates(\n this.awarenessUpdates.protocol,\n Array.from(this.awarenessUpdates.protocol.getStates().keys()).filter(\n (client) => client !== this.awarenessUpdates!.protocol.clientID\n ),\n this\n )\n\n // try to notifying other clients that we are disconnecting\n awarenessProtocol.removeAwarenessStates(\n this.awarenessUpdates.protocol,\n [this.awarenessUpdates.protocol.clientID],\n `local`\n )\n\n this.awarenessUpdates.protocol.setLocalState({})\n }\n\n // TODO: await for events before closing\n this.emit(`connection-close`, [])\n\n this.pendingAwarenessUpdate = null\n\n this.connected = false\n this.synced = false\n }\n\n connect() {\n if (this.connected) {\n return\n }\n const abortController = new AbortController()\n\n const operationsStream = new ShapeStream<RowWithDocumentUpdate>({\n ...this.documentUpdates.shape,\n ...this.resumeState.document,\n signal: abortController.signal,\n })\n\n const operationsShapeUnsubscribe = operationsStream.subscribe(\n (messages: Message<RowWithDocumentUpdate>[]) => {\n this.operationsShapeHandler(\n messages,\n operationsStream.lastOffset,\n operationsStream.shapeHandle!\n )\n }\n )\n\n let awarenessShapeUnsubscribe: () => void | undefined\n if (this.awarenessUpdates) {\n const awarenessStream = new ShapeStream<RowWithAwarenessUpdate>({\n ...this.awarenessUpdates.shape,\n signal: abortController.signal,\n offset: `now`,\n })\n\n awarenessShapeUnsubscribe = awarenessStream.subscribe(\n (messages: Message<RowWithAwarenessUpdate>[]) => {\n this.awarenessShapeHandler(messages)\n }\n )\n }\n\n this.unsubscribeShapes = () => {\n abortController.abort()\n operationsShapeUnsubscribe()\n awarenessShapeUnsubscribe?.()\n this.unsubscribeShapes = undefined\n }\n\n this.emit(`status`, [{ status: `connecting` }])\n }\n\n private operationsShapeHandler(\n messages: Message<RowWithDocumentUpdate>[],\n offset: Offset,\n handle: string\n ) {\n for (const message of messages) {\n if (isChangeMessage(message)) {\n const decoder = this.documentUpdates.getUpdateFromRow(message.value)\n while (decoder.pos !== decoder.arr.length) {\n const operation = decoding.readVarUint8Array(decoder)\n Y.applyUpdate(this.doc, operation, `server`)\n }\n } else if (\n isControlMessage(message) &&\n message.headers.control === `up-to-date`\n ) {\n this.resumeState.document = {\n offset,\n handle,\n }\n\n if (!this.sendingPendingChanges) {\n this.synced = true\n this.resumeState.stableStateVector = Y.encodeStateVector(this.doc)\n }\n this.emit(`resumeState`, [this.resumeState])\n this.connected = true\n }\n }\n }\n\n private async applyDocumentUpdate(update: Uint8Array, origin: unknown) {\n // don't re-send updates from electric\n if (origin === `server`) {\n return\n }\n\n this.batch(update)\n this.scheduleSendOperations()\n }\n\n private async sendOperations() {\n this.clearDebounceTimer()\n\n if (!this.connected || this.sendingPendingChanges) {\n return\n }\n\n try {\n this.sendingPendingChanges = true\n while (\n this.pendingChanges &&\n this.pendingChanges.length > 2 &&\n this.connected\n ) {\n const sending = this.pendingChanges\n this.pendingChanges = null\n\n const encoder = encoding.createEncoder()\n encoding.writeVarUint8Array(encoder, sending)\n\n const success = await send(\n encoder,\n this.documentUpdates.sendUrl,\n this.fetchClient ?? fetch,\n this.documentUpdates.sendErrorRetryHandler\n )\n if (!success) {\n this.batch(sending)\n this.disconnect()\n }\n }\n // no more pending changes, move stableStateVector forward\n this.resumeState.stableStateVector = Y.encodeStateVector(this.doc)\n this.emit(`resumeState`, [this.resumeState])\n } finally {\n this.sendingPendingChanges = false\n }\n }\n\n private async applyAwarenessUpdate(\n awarenessUpdate: AwarenessUpdate,\n origin: unknown\n ) {\n if (origin !== `local` || !this.connected) {\n return\n }\n\n this.pendingAwarenessUpdate = awarenessUpdate\n\n if (this.sendingAwarenessState) {\n return\n }\n\n this.sendingAwarenessState = true\n\n try {\n while (this.pendingAwarenessUpdate && this.connected) {\n const update = this.pendingAwarenessUpdate\n this.pendingAwarenessUpdate = null\n\n const { added, updated, removed } = update\n const changedClients = added.concat(updated).concat(removed)\n const encoder = encoding.createEncoder()\n\n encoding.writeVarUint8Array(\n encoder,\n awarenessProtocol.encodeAwarenessUpdate(\n this.awarenessUpdates!.protocol,\n changedClients\n )\n )\n const success = await send(\n encoder,\n this.awarenessUpdates!.sendUrl,\n this.fetchClient ?? fetch,\n this.awarenessUpdates!.sendErrorRetryHandler\n )\n if (!success) {\n this.disconnect()\n }\n }\n } finally {\n this.sendingAwarenessState = false\n }\n }\n\n private awarenessShapeHandler(messages: Message<RowWithAwarenessUpdate>[]) {\n for (const message of messages) {\n if (isChangeMessage(message)) {\n if (message.headers.operation === `delete`) {\n awarenessProtocol.removeAwarenessStates(\n this.awarenessUpdates!.protocol,\n [Number(message.value.client_id)],\n `remote`\n )\n } else {\n const decoder = this.awarenessUpdates!.getUpdateFromRow(message.value)\n awarenessProtocol.applyAwarenessUpdate(\n this.awarenessUpdates!.protocol,\n decoding.readVarUint8Array(decoder),\n this\n )\n }\n }\n }\n }\n}\n\nasync function send(\n encoder: encoding.Encoder,\n endpoint: string | URL,\n fetchClient: typeof fetch,\n retryHandler?: SendErrorRetryHandler\n): Promise<boolean> {\n let response: Response | undefined\n const op = encoding.toUint8Array(encoder)\n\n try {\n response = await fetchClient(endpoint!, {\n method: `PUT`,\n headers: {\n 'Content-Type': `application/octet-stream`,\n },\n body: op as BodyInit,\n })\n\n if (!response.ok) {\n throw new Error(`Server did not return 2xx`)\n }\n\n return true\n } catch (error) {\n const shouldRetry = await (retryHandler?.({\n response,\n error,\n }) ?? false)\n return shouldRetry\n }\n}\n","import { ResumeState, ElectricResumeStateProvider } from './types'\nimport { ObservableV2 } from 'lib0/observable.js'\nimport { ElectricProvider } from './y-electric'\nimport * as buffer from 'lib0/buffer'\n\n/**\n * A ResumeStateProvider implementation using LocalStorage.\n * This is a reference implementation that can be used as a starting point\n * for implementing other ResumeStateProviders.\n */\nexport class LocalStorageResumeStateProvider extends ObservableV2<ElectricResumeStateProvider> {\n private key: string\n private resumeState?: ResumeState\n\n constructor(key: string) {\n super()\n this.key = key\n }\n\n subscribeToResumeState(provider: ElectricProvider): () => void {\n const resumeStateHandler = provider.on(`resumeState`, this.save.bind(this))\n return () => provider.off(`resumeState`, resumeStateHandler)\n }\n\n save(resumeState: ResumeState) {\n const jsonPart = JSON.stringify({\n operations: resumeState.document,\n })\n localStorage.setItem(this.key, jsonPart)\n\n if (resumeState.stableStateVector) {\n const vectorBase64 = buffer.toBase64(resumeState.stableStateVector)\n localStorage.setItem(`${this.key}_vector`, vectorBase64)\n } else {\n // ensure vector is removed\n localStorage.removeItem(`${this.key}_vector`)\n }\n }\n\n load(): ResumeState {\n if (this.resumeState) {\n return this.resumeState\n }\n\n const jsonData = localStorage.getItem(this.key)\n if (!jsonData) {\n this.emit(`synced`, [{}])\n } else {\n this.resumeState = JSON.parse(jsonData)\n\n const vectorData = localStorage.getItem(`${this.key}_vector`)\n if (vectorData) {\n this.resumeState!.stableStateVector = buffer.fromBase64(vectorData)\n }\n\n this.emit(`synced`, [this.resumeState!])\n }\n\n return this.resumeState!\n }\n}\n","import * as decoding from 'lib0/decoding'\n\n/**\n * Convert a hex string from PostgreSQL's bytea format to a Uint8Array\n */\nconst hexStringToUint8Array = (hexString: string) => {\n const cleanHexString = hexString.startsWith(`\\\\x`)\n ? hexString.slice(2)\n : hexString\n return new Uint8Array(\n cleanHexString.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16))\n )\n}\n\n/**\n * Utility to parse hex string bytea data to a decoder for YJS operations\n */\nexport const parseToDecoder = {\n bytea: (hexString: string) => {\n const uint8Array = hexStringToUint8Array(hexString)\n return decoding.createDecoder(uint8Array)\n },\n}\n"],"mappings":"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","names":["import_observable","decoding"]}
|
package/dist/cjs/index.d.cts
CHANGED
|
@@ -39,7 +39,6 @@ type YProvider = {
|
|
|
39
39
|
* A resume state provider is used to persist the sync state of a document
|
|
40
40
|
* This is composed of:
|
|
41
41
|
* - The document shape offset and handle
|
|
42
|
-
* - The awareness shape offset and handle (optional)
|
|
43
42
|
* - The state vector of the document synced to the server (optional)
|
|
44
43
|
*/
|
|
45
44
|
type ElectricResumeStateProvider = {
|
|
@@ -63,6 +62,7 @@ type ElectricResumeStateProvider = {
|
|
|
63
62
|
* @param resumeState (optional) The resume state to use for the provider. If no resume state the provider will fetch the entire shape.
|
|
64
63
|
* @param connect (optional) Whether to automatically connect upon initialization.
|
|
65
64
|
* @param fetchClient (optional) Custom fetch implementation to use for send requests.
|
|
65
|
+
* @param debounceMs (optional) Debounce window in milliseconds for sending document updates. If 0 or undefined, debouncing is disabled and updates are sent immediately.
|
|
66
66
|
*/
|
|
67
67
|
type ElectricProviderOptions<RowWithDocumentUpdate extends Row<decoding.Decoder>, RowWithAwarenessUpdate extends Row<decoding.Decoder> = never> = {
|
|
68
68
|
doc: Y.Doc;
|
|
@@ -82,16 +82,13 @@ type ElectricProviderOptions<RowWithDocumentUpdate extends Row<decoding.Decoder>
|
|
|
82
82
|
resumeState?: ResumeState;
|
|
83
83
|
connect?: boolean;
|
|
84
84
|
fetchClient?: typeof fetch;
|
|
85
|
+
debounceMs?: number;
|
|
85
86
|
};
|
|
86
87
|
type ResumeState = {
|
|
87
88
|
document?: {
|
|
88
89
|
offset: Offset;
|
|
89
90
|
handle: string;
|
|
90
91
|
};
|
|
91
|
-
awareness?: {
|
|
92
|
-
offset: Offset;
|
|
93
|
-
handle: string;
|
|
94
|
-
};
|
|
95
92
|
stableStateVector?: Uint8Array;
|
|
96
93
|
};
|
|
97
94
|
|
|
@@ -106,6 +103,8 @@ declare class ElectricProvider<RowWithDocumentUpdate extends Row<decoding.Decode
|
|
|
106
103
|
private pendingChanges;
|
|
107
104
|
private sendingAwarenessState;
|
|
108
105
|
private pendingAwarenessUpdate;
|
|
106
|
+
private debounceMs;
|
|
107
|
+
private debounceTimer;
|
|
109
108
|
private documentUpdateHandler;
|
|
110
109
|
private awarenessUpdateHandler?;
|
|
111
110
|
private exitHandler;
|
|
@@ -131,13 +130,16 @@ declare class ElectricProvider<RowWithDocumentUpdate extends Row<decoding.Decode
|
|
|
131
130
|
* @param {ResumeState} [options.resumeState] - Resume state for the provider
|
|
132
131
|
* @param {boolean} [options.connect=true] - Whether to automatically connect upon initialization
|
|
133
132
|
* @param {typeof fetch} [options.fetchClient] - Custom fetch implementation to use for HTTP requests
|
|
133
|
+
* @param {number} [options.debounceMs] - Debounce window in milliseconds for sending document updates. If 0 or undefined, debouncing is disabled.
|
|
134
134
|
*/
|
|
135
|
-
constructor({ doc, documentUpdates: documentUpdatesConfig, awarenessUpdates: awarenessUpdatesConfig, resumeState, connect, fetchClient, }: ElectricProviderOptions<RowWithDocumentUpdate, RowWithAwarenessUpdate>);
|
|
135
|
+
constructor({ doc, documentUpdates: documentUpdatesConfig, awarenessUpdates: awarenessUpdatesConfig, resumeState, connect, fetchClient, debounceMs, }: ElectricProviderOptions<RowWithDocumentUpdate, RowWithAwarenessUpdate>);
|
|
136
136
|
get synced(): boolean;
|
|
137
137
|
set synced(state: boolean);
|
|
138
138
|
set connected(state: boolean);
|
|
139
139
|
get connected(): boolean;
|
|
140
140
|
private batch;
|
|
141
|
+
private clearDebounceTimer;
|
|
142
|
+
private scheduleSendOperations;
|
|
141
143
|
destroy(): void;
|
|
142
144
|
disconnect(): void;
|
|
143
145
|
connect(): void;
|
package/dist/index.browser.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var
|
|
1
|
+
var x=Object.defineProperty,P=Object.defineProperties;var H=Object.getOwnPropertyDescriptors;var S=Object.getOwnPropertySymbols;var T=Object.prototype.hasOwnProperty,E=Object.prototype.propertyIsEnumerable;var y=(n,t,e)=>t in n?x(n,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):n[t]=e,u=(n,t)=>{for(var e in t||(t={}))T.call(t,e)&&y(n,e,t[e]);if(S)for(var e of S(t))E.call(t,e)&&y(n,e,t[e]);return n},g=(n,t)=>P(n,H(t));import*as h from"lib0/encoding";import*as f from"lib0/decoding";import*as c from"y-protocols/awareness";import{ObservableV2 as O}from"lib0/observable";import*as w from"lib0/environment";import*as d from"yjs";import{isChangeMessage as v,isControlMessage as V,ShapeStream as b}from"@electric-sql/client";var A=class extends O{constructor({doc:e,documentUpdates:s,awarenessUpdates:r,resumeState:a,connect:i=!0,fetchClient:o,debounceMs:p}){var l;super();this._connected=!1;this._synced=!1;this.sendingPendingChanges=!1;this.pendingChanges=null;this.sendingAwarenessState=!1;this.pendingAwarenessUpdate=null;this.debounceTimer=null;this.doc=e,this.documentUpdates=s,this.awarenessUpdates=r,this.resumeState=a!=null?a:{},this.debounceMs=p!=null?p:0,this.fetchClient=o,this.exitHandler=()=>{w.isNode&&typeof process!="undefined"&&process.on("exit",this.destroy.bind(this))},this.documentUpdateHandler=this.doc.on("update",this.applyDocumentUpdate.bind(this)),this.awarenessUpdates&&(this.awarenessUpdateHandler=this.applyAwarenessUpdate.bind(this),this.awarenessUpdates.protocol.on("update",this.awarenessUpdateHandler)),(l=this.resumeState)!=null&&l.stableStateVector&&(this.pendingChanges=d.encodeStateAsUpdate(this.doc,this.resumeState.stableStateVector)),i&&this.connect()}get synced(){return this._synced}set synced(e){this._synced!==e&&(this._synced=e,this.emit("synced",[e]),this.emit("sync",[e]))}set connected(e){this._connected!==e&&(this._connected=e,e&&this.sendOperations(),this.emit("status",[{status:e?"connected":"disconnected"}]))}get connected(){return this._connected}batch(e){this.pendingChanges?this.pendingChanges=d.mergeUpdates([this.pendingChanges,e]):this.pendingChanges=e}clearDebounceTimer(){this.debounceTimer!==null&&(clearTimeout(this.debounceTimer),this.debounceTimer=null)}scheduleSendOperations(){this.debounceMs>0?this.debounceTimer===null&&(this.debounceTimer=setTimeout(async()=>{this.debounceTimer=null,await this.sendOperations(),this.pendingChanges&&this.connected&&!this.sendingPendingChanges&&this.scheduleSendOperations()},this.debounceMs)):this.sendOperations()}destroy(){var e;this.clearDebounceTimer(),this.disconnect(),this.doc.off("update",this.documentUpdateHandler),(e=this.awarenessUpdates)==null||e.protocol.off("update",this.awarenessUpdateHandler),w.isNode&&typeof process!="undefined"&&process.off("exit",this.exitHandler),super.destroy()}disconnect(){var e;this.clearDebounceTimer(),this.pendingChanges&&this.connected&&this.sendOperations(),(e=this.unsubscribeShapes)==null||e.call(this),this.connected&&(this.awarenessUpdates&&(c.removeAwarenessStates(this.awarenessUpdates.protocol,Array.from(this.awarenessUpdates.protocol.getStates().keys()).filter(s=>s!==this.awarenessUpdates.protocol.clientID),this),c.removeAwarenessStates(this.awarenessUpdates.protocol,[this.awarenessUpdates.protocol.clientID],"local"),this.awarenessUpdates.protocol.setLocalState({})),this.emit("connection-close",[]),this.pendingAwarenessUpdate=null,this.connected=!1,this.synced=!1)}connect(){if(this.connected)return;let e=new AbortController,s=new b(g(u(u({},this.documentUpdates.shape),this.resumeState.document),{signal:e.signal})),r=s.subscribe(i=>{this.operationsShapeHandler(i,s.lastOffset,s.shapeHandle)}),a;this.awarenessUpdates&&(a=new b(g(u({},this.awarenessUpdates.shape),{signal:e.signal,offset:"now"})).subscribe(o=>{this.awarenessShapeHandler(o)})),this.unsubscribeShapes=()=>{e.abort(),r(),a==null||a(),this.unsubscribeShapes=void 0},this.emit("status",[{status:"connecting"}])}operationsShapeHandler(e,s,r){for(let a of e)if(v(a)){let i=this.documentUpdates.getUpdateFromRow(a.value);for(;i.pos!==i.arr.length;){let o=f.readVarUint8Array(i);d.applyUpdate(this.doc,o,"server")}}else V(a)&&a.headers.control==="up-to-date"&&(this.resumeState.document={offset:s,handle:r},this.sendingPendingChanges||(this.synced=!0,this.resumeState.stableStateVector=d.encodeStateVector(this.doc)),this.emit("resumeState",[this.resumeState]),this.connected=!0)}async applyDocumentUpdate(e,s){s!=="server"&&(this.batch(e),this.scheduleSendOperations())}async sendOperations(){var e;if(this.clearDebounceTimer(),!(!this.connected||this.sendingPendingChanges))try{for(this.sendingPendingChanges=!0;this.pendingChanges&&this.pendingChanges.length>2&&this.connected;){let s=this.pendingChanges;this.pendingChanges=null;let r=h.createEncoder();h.writeVarUint8Array(r,s),await R(r,this.documentUpdates.sendUrl,(e=this.fetchClient)!=null?e:fetch,this.documentUpdates.sendErrorRetryHandler)||(this.batch(s),this.disconnect())}this.resumeState.stableStateVector=d.encodeStateVector(this.doc),this.emit("resumeState",[this.resumeState])}finally{this.sendingPendingChanges=!1}}async applyAwarenessUpdate(e,s){var r;if(!(s!=="local"||!this.connected)&&(this.pendingAwarenessUpdate=e,!this.sendingAwarenessState)){this.sendingAwarenessState=!0;try{for(;this.pendingAwarenessUpdate&&this.connected;){let a=this.pendingAwarenessUpdate;this.pendingAwarenessUpdate=null;let{added:i,updated:o,removed:p}=a,l=i.concat(o).concat(p),U=h.createEncoder();h.writeVarUint8Array(U,c.encodeAwarenessUpdate(this.awarenessUpdates.protocol,l)),await R(U,this.awarenessUpdates.sendUrl,(r=this.fetchClient)!=null?r:fetch,this.awarenessUpdates.sendErrorRetryHandler)||this.disconnect()}}finally{this.sendingAwarenessState=!1}}}awarenessShapeHandler(e){for(let s of e)if(v(s))if(s.headers.operation==="delete")c.removeAwarenessStates(this.awarenessUpdates.protocol,[Number(s.value.client_id)],"remote");else{let r=this.awarenessUpdates.getUpdateFromRow(s.value);c.applyAwarenessUpdate(this.awarenessUpdates.protocol,f.readVarUint8Array(r),this)}}};async function R(n,t,e,s){var i;let r,a=h.toUint8Array(n);try{if(r=await e(t,{method:"PUT",headers:{"Content-Type":"application/octet-stream"},body:a}),!r.ok)throw new Error("Server did not return 2xx");return!0}catch(o){return await((i=s==null?void 0:s({response:r,error:o}))!=null?i:!1)}}import{ObservableV2 as W}from"lib0/observable.js";import*as m from"lib0/buffer";var C=class extends W{constructor(t){super(),this.key=t}subscribeToResumeState(t){let e=t.on("resumeState",this.save.bind(this));return()=>t.off("resumeState",e)}save(t){let e=JSON.stringify({operations:t.document});if(localStorage.setItem(this.key,e),t.stableStateVector){let s=m.toBase64(t.stableStateVector);localStorage.setItem(`${this.key}_vector`,s)}else localStorage.removeItem(`${this.key}_vector`)}load(){if(this.resumeState)return this.resumeState;let t=localStorage.getItem(this.key);if(!t)this.emit("synced",[{}]);else{this.resumeState=JSON.parse(t);let e=localStorage.getItem(`${this.key}_vector`);e&&(this.resumeState.stableStateVector=m.fromBase64(e)),this.emit("synced",[this.resumeState])}return this.resumeState}};import*as D from"lib0/decoding";var k=n=>{let t=n.startsWith("\\x")?n.slice(2):n;return new Uint8Array(t.match(/.{1,2}/g).map(e=>parseInt(e,16)))},z={bytea:n=>{let t=k(n);return D.createDecoder(t)}};export{A as ElectricProvider,C as LocalStorageResumeStateProvider,z as parseToDecoder};
|
|
2
2
|
//# sourceMappingURL=index.browser.mjs.map
|