@electric-sql/y-electric 0.1.21 → 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 +43 -22
- 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 +43 -22
- 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/dist/index.legacy-esm.js
CHANGED
|
@@ -51,6 +51,7 @@ var ElectricProvider = class extends ObservableV2 {
|
|
|
51
51
|
* @param {ResumeState} [options.resumeState] - Resume state for the provider
|
|
52
52
|
* @param {boolean} [options.connect=true] - Whether to automatically connect upon initialization
|
|
53
53
|
* @param {typeof fetch} [options.fetchClient] - Custom fetch implementation to use for HTTP requests
|
|
54
|
+
* @param {number} [options.debounceMs] - Debounce window in milliseconds for sending document updates. If 0 or undefined, debouncing is disabled.
|
|
54
55
|
*/
|
|
55
56
|
constructor({
|
|
56
57
|
doc,
|
|
@@ -58,7 +59,8 @@ var ElectricProvider = class extends ObservableV2 {
|
|
|
58
59
|
awarenessUpdates: awarenessUpdatesConfig,
|
|
59
60
|
resumeState,
|
|
60
61
|
connect = true,
|
|
61
|
-
fetchClient
|
|
62
|
+
fetchClient,
|
|
63
|
+
debounceMs
|
|
62
64
|
}) {
|
|
63
65
|
var _a;
|
|
64
66
|
super();
|
|
@@ -68,10 +70,12 @@ var ElectricProvider = class extends ObservableV2 {
|
|
|
68
70
|
this.pendingChanges = null;
|
|
69
71
|
this.sendingAwarenessState = false;
|
|
70
72
|
this.pendingAwarenessUpdate = null;
|
|
73
|
+
this.debounceTimer = null;
|
|
71
74
|
this.doc = doc;
|
|
72
75
|
this.documentUpdates = documentUpdatesConfig;
|
|
73
76
|
this.awarenessUpdates = awarenessUpdatesConfig;
|
|
74
77
|
this.resumeState = resumeState != null ? resumeState : {};
|
|
78
|
+
this.debounceMs = debounceMs != null ? debounceMs : 0;
|
|
75
79
|
this.fetchClient = fetchClient;
|
|
76
80
|
this.exitHandler = () => {
|
|
77
81
|
if (env.isNode && typeof process !== `undefined`) {
|
|
@@ -125,8 +129,30 @@ var ElectricProvider = class extends ObservableV2 {
|
|
|
125
129
|
this.pendingChanges = update;
|
|
126
130
|
}
|
|
127
131
|
}
|
|
132
|
+
clearDebounceTimer() {
|
|
133
|
+
if (this.debounceTimer !== null) {
|
|
134
|
+
clearTimeout(this.debounceTimer);
|
|
135
|
+
this.debounceTimer = null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
scheduleSendOperations() {
|
|
139
|
+
if (this.debounceMs > 0) {
|
|
140
|
+
if (this.debounceTimer === null) {
|
|
141
|
+
this.debounceTimer = setTimeout(async () => {
|
|
142
|
+
this.debounceTimer = null;
|
|
143
|
+
await this.sendOperations();
|
|
144
|
+
if (this.pendingChanges && this.connected && !this.sendingPendingChanges) {
|
|
145
|
+
this.scheduleSendOperations();
|
|
146
|
+
}
|
|
147
|
+
}, this.debounceMs);
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
this.sendOperations();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
128
153
|
destroy() {
|
|
129
154
|
var _a;
|
|
155
|
+
this.clearDebounceTimer();
|
|
130
156
|
this.disconnect();
|
|
131
157
|
this.doc.off(`update`, this.documentUpdateHandler);
|
|
132
158
|
(_a = this.awarenessUpdates) == null ? void 0 : _a.protocol.off(`update`, this.awarenessUpdateHandler);
|
|
@@ -137,6 +163,10 @@ var ElectricProvider = class extends ObservableV2 {
|
|
|
137
163
|
}
|
|
138
164
|
disconnect() {
|
|
139
165
|
var _a;
|
|
166
|
+
this.clearDebounceTimer();
|
|
167
|
+
if (this.pendingChanges && this.connected) {
|
|
168
|
+
this.sendOperations();
|
|
169
|
+
}
|
|
140
170
|
(_a = this.unsubscribeShapes) == null ? void 0 : _a.call(this);
|
|
141
171
|
if (!this.connected) {
|
|
142
172
|
return;
|
|
@@ -180,16 +210,15 @@ var ElectricProvider = class extends ObservableV2 {
|
|
|
180
210
|
);
|
|
181
211
|
let awarenessShapeUnsubscribe;
|
|
182
212
|
if (this.awarenessUpdates) {
|
|
183
|
-
const awarenessStream = new ShapeStream(__spreadProps(__spreadValues(
|
|
184
|
-
signal: abortController.signal
|
|
213
|
+
const awarenessStream = new ShapeStream(__spreadProps(__spreadValues({}, this.awarenessUpdates.shape), {
|
|
214
|
+
signal: abortController.signal,
|
|
215
|
+
offset: `now`
|
|
185
216
|
}));
|
|
186
|
-
awarenessShapeUnsubscribe = awarenessStream.subscribe(
|
|
187
|
-
|
|
188
|
-
messages
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
);
|
|
192
|
-
});
|
|
217
|
+
awarenessShapeUnsubscribe = awarenessStream.subscribe(
|
|
218
|
+
(messages) => {
|
|
219
|
+
this.awarenessShapeHandler(messages);
|
|
220
|
+
}
|
|
221
|
+
);
|
|
193
222
|
}
|
|
194
223
|
this.unsubscribeShapes = () => {
|
|
195
224
|
abortController.abort();
|
|
@@ -221,17 +250,16 @@ var ElectricProvider = class extends ObservableV2 {
|
|
|
221
250
|
}
|
|
222
251
|
}
|
|
223
252
|
}
|
|
224
|
-
// TODO: add an optional throttler that batches updates
|
|
225
|
-
// before pushing to the server
|
|
226
253
|
async applyDocumentUpdate(update, origin) {
|
|
227
254
|
if (origin === `server`) {
|
|
228
255
|
return;
|
|
229
256
|
}
|
|
230
257
|
this.batch(update);
|
|
231
|
-
this.
|
|
258
|
+
this.scheduleSendOperations();
|
|
232
259
|
}
|
|
233
260
|
async sendOperations() {
|
|
234
261
|
var _a;
|
|
262
|
+
this.clearDebounceTimer();
|
|
235
263
|
if (!this.connected || this.sendingPendingChanges) {
|
|
236
264
|
return;
|
|
237
265
|
}
|
|
@@ -297,7 +325,7 @@ var ElectricProvider = class extends ObservableV2 {
|
|
|
297
325
|
this.sendingAwarenessState = false;
|
|
298
326
|
}
|
|
299
327
|
}
|
|
300
|
-
awarenessShapeHandler(messages
|
|
328
|
+
awarenessShapeHandler(messages) {
|
|
301
329
|
for (const message of messages) {
|
|
302
330
|
if (isChangeMessage(message)) {
|
|
303
331
|
if (message.headers.operation === `delete`) {
|
|
@@ -314,12 +342,6 @@ var ElectricProvider = class extends ObservableV2 {
|
|
|
314
342
|
this
|
|
315
343
|
);
|
|
316
344
|
}
|
|
317
|
-
} else if (isControlMessage(message) && message.headers.control === `up-to-date`) {
|
|
318
|
-
this.resumeState.awareness = {
|
|
319
|
-
offset,
|
|
320
|
-
handle
|
|
321
|
-
};
|
|
322
|
-
this.emit(`resumeState`, [this.resumeState]);
|
|
323
345
|
}
|
|
324
346
|
}
|
|
325
347
|
}
|
|
@@ -363,8 +385,7 @@ var LocalStorageResumeStateProvider = class extends ObservableV22 {
|
|
|
363
385
|
}
|
|
364
386
|
save(resumeState) {
|
|
365
387
|
const jsonPart = JSON.stringify({
|
|
366
|
-
operations: resumeState.document
|
|
367
|
-
awareness: resumeState.awareness
|
|
388
|
+
operations: resumeState.document
|
|
368
389
|
});
|
|
369
390
|
localStorage.setItem(this.key, jsonPart);
|
|
370
391
|
if (resumeState.stableStateVector) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/y-electric.ts","../src/local-storage-resume-state.ts","../src/utils.ts"],"sourcesContent":["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":["ObservableV2","decoding"]}
|
|
1
|
+
{"version":3,"sources":["../src/y-electric.ts","../src/local-storage-resume-state.ts","../src/utils.ts"],"sourcesContent":["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":["ObservableV2","decoding"]}
|
package/dist/index.mjs
CHANGED
|
@@ -51,6 +51,7 @@ var ElectricProvider = class extends ObservableV2 {
|
|
|
51
51
|
* @param {ResumeState} [options.resumeState] - Resume state for the provider
|
|
52
52
|
* @param {boolean} [options.connect=true] - Whether to automatically connect upon initialization
|
|
53
53
|
* @param {typeof fetch} [options.fetchClient] - Custom fetch implementation to use for HTTP requests
|
|
54
|
+
* @param {number} [options.debounceMs] - Debounce window in milliseconds for sending document updates. If 0 or undefined, debouncing is disabled.
|
|
54
55
|
*/
|
|
55
56
|
constructor({
|
|
56
57
|
doc,
|
|
@@ -58,7 +59,8 @@ var ElectricProvider = class extends ObservableV2 {
|
|
|
58
59
|
awarenessUpdates: awarenessUpdatesConfig,
|
|
59
60
|
resumeState,
|
|
60
61
|
connect = true,
|
|
61
|
-
fetchClient
|
|
62
|
+
fetchClient,
|
|
63
|
+
debounceMs
|
|
62
64
|
}) {
|
|
63
65
|
var _a;
|
|
64
66
|
super();
|
|
@@ -68,10 +70,12 @@ var ElectricProvider = class extends ObservableV2 {
|
|
|
68
70
|
this.pendingChanges = null;
|
|
69
71
|
this.sendingAwarenessState = false;
|
|
70
72
|
this.pendingAwarenessUpdate = null;
|
|
73
|
+
this.debounceTimer = null;
|
|
71
74
|
this.doc = doc;
|
|
72
75
|
this.documentUpdates = documentUpdatesConfig;
|
|
73
76
|
this.awarenessUpdates = awarenessUpdatesConfig;
|
|
74
77
|
this.resumeState = resumeState != null ? resumeState : {};
|
|
78
|
+
this.debounceMs = debounceMs != null ? debounceMs : 0;
|
|
75
79
|
this.fetchClient = fetchClient;
|
|
76
80
|
this.exitHandler = () => {
|
|
77
81
|
if (env.isNode && typeof process !== `undefined`) {
|
|
@@ -125,8 +129,30 @@ var ElectricProvider = class extends ObservableV2 {
|
|
|
125
129
|
this.pendingChanges = update;
|
|
126
130
|
}
|
|
127
131
|
}
|
|
132
|
+
clearDebounceTimer() {
|
|
133
|
+
if (this.debounceTimer !== null) {
|
|
134
|
+
clearTimeout(this.debounceTimer);
|
|
135
|
+
this.debounceTimer = null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
scheduleSendOperations() {
|
|
139
|
+
if (this.debounceMs > 0) {
|
|
140
|
+
if (this.debounceTimer === null) {
|
|
141
|
+
this.debounceTimer = setTimeout(async () => {
|
|
142
|
+
this.debounceTimer = null;
|
|
143
|
+
await this.sendOperations();
|
|
144
|
+
if (this.pendingChanges && this.connected && !this.sendingPendingChanges) {
|
|
145
|
+
this.scheduleSendOperations();
|
|
146
|
+
}
|
|
147
|
+
}, this.debounceMs);
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
this.sendOperations();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
128
153
|
destroy() {
|
|
129
154
|
var _a;
|
|
155
|
+
this.clearDebounceTimer();
|
|
130
156
|
this.disconnect();
|
|
131
157
|
this.doc.off(`update`, this.documentUpdateHandler);
|
|
132
158
|
(_a = this.awarenessUpdates) == null ? void 0 : _a.protocol.off(`update`, this.awarenessUpdateHandler);
|
|
@@ -137,6 +163,10 @@ var ElectricProvider = class extends ObservableV2 {
|
|
|
137
163
|
}
|
|
138
164
|
disconnect() {
|
|
139
165
|
var _a;
|
|
166
|
+
this.clearDebounceTimer();
|
|
167
|
+
if (this.pendingChanges && this.connected) {
|
|
168
|
+
this.sendOperations();
|
|
169
|
+
}
|
|
140
170
|
(_a = this.unsubscribeShapes) == null ? void 0 : _a.call(this);
|
|
141
171
|
if (!this.connected) {
|
|
142
172
|
return;
|
|
@@ -180,16 +210,15 @@ var ElectricProvider = class extends ObservableV2 {
|
|
|
180
210
|
);
|
|
181
211
|
let awarenessShapeUnsubscribe;
|
|
182
212
|
if (this.awarenessUpdates) {
|
|
183
|
-
const awarenessStream = new ShapeStream(__spreadProps(__spreadValues(
|
|
184
|
-
signal: abortController.signal
|
|
213
|
+
const awarenessStream = new ShapeStream(__spreadProps(__spreadValues({}, this.awarenessUpdates.shape), {
|
|
214
|
+
signal: abortController.signal,
|
|
215
|
+
offset: `now`
|
|
185
216
|
}));
|
|
186
|
-
awarenessShapeUnsubscribe = awarenessStream.subscribe(
|
|
187
|
-
|
|
188
|
-
messages
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
);
|
|
192
|
-
});
|
|
217
|
+
awarenessShapeUnsubscribe = awarenessStream.subscribe(
|
|
218
|
+
(messages) => {
|
|
219
|
+
this.awarenessShapeHandler(messages);
|
|
220
|
+
}
|
|
221
|
+
);
|
|
193
222
|
}
|
|
194
223
|
this.unsubscribeShapes = () => {
|
|
195
224
|
abortController.abort();
|
|
@@ -221,17 +250,16 @@ var ElectricProvider = class extends ObservableV2 {
|
|
|
221
250
|
}
|
|
222
251
|
}
|
|
223
252
|
}
|
|
224
|
-
// TODO: add an optional throttler that batches updates
|
|
225
|
-
// before pushing to the server
|
|
226
253
|
async applyDocumentUpdate(update, origin) {
|
|
227
254
|
if (origin === `server`) {
|
|
228
255
|
return;
|
|
229
256
|
}
|
|
230
257
|
this.batch(update);
|
|
231
|
-
this.
|
|
258
|
+
this.scheduleSendOperations();
|
|
232
259
|
}
|
|
233
260
|
async sendOperations() {
|
|
234
261
|
var _a;
|
|
262
|
+
this.clearDebounceTimer();
|
|
235
263
|
if (!this.connected || this.sendingPendingChanges) {
|
|
236
264
|
return;
|
|
237
265
|
}
|
|
@@ -297,7 +325,7 @@ var ElectricProvider = class extends ObservableV2 {
|
|
|
297
325
|
this.sendingAwarenessState = false;
|
|
298
326
|
}
|
|
299
327
|
}
|
|
300
|
-
awarenessShapeHandler(messages
|
|
328
|
+
awarenessShapeHandler(messages) {
|
|
301
329
|
for (const message of messages) {
|
|
302
330
|
if (isChangeMessage(message)) {
|
|
303
331
|
if (message.headers.operation === `delete`) {
|
|
@@ -314,12 +342,6 @@ var ElectricProvider = class extends ObservableV2 {
|
|
|
314
342
|
this
|
|
315
343
|
);
|
|
316
344
|
}
|
|
317
|
-
} else if (isControlMessage(message) && message.headers.control === `up-to-date`) {
|
|
318
|
-
this.resumeState.awareness = {
|
|
319
|
-
offset,
|
|
320
|
-
handle
|
|
321
|
-
};
|
|
322
|
-
this.emit(`resumeState`, [this.resumeState]);
|
|
323
345
|
}
|
|
324
346
|
}
|
|
325
347
|
}
|
|
@@ -363,8 +385,7 @@ var LocalStorageResumeStateProvider = class extends ObservableV22 {
|
|
|
363
385
|
}
|
|
364
386
|
save(resumeState) {
|
|
365
387
|
const jsonPart = JSON.stringify({
|
|
366
|
-
operations: resumeState.document
|
|
367
|
-
awareness: resumeState.awareness
|
|
388
|
+
operations: resumeState.document
|
|
368
389
|
});
|
|
369
390
|
localStorage.setItem(this.key, jsonPart);
|
|
370
391
|
if (resumeState.stableStateVector) {
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/y-electric.ts","../src/local-storage-resume-state.ts","../src/utils.ts"],"sourcesContent":["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":["ObservableV2","decoding"]}
|
|
1
|
+
{"version":3,"sources":["../src/y-electric.ts","../src/local-storage-resume-state.ts","../src/utils.ts"],"sourcesContent":["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":";;;;;;;;;;;;;;;;;;;;;AAAA,YAAY,cAAc;AAC1B,YAAY,cAAc;AAC1B,YAAY,uBAAuB;AACnC,SAAS,oBAAoB;AAC7B,YAAY,SAAS;AACrB,YAAY,OAAO;AACnB;AAAA,EAEE;AAAA,EACA;AAAA,EAIA;AAAA,OAEK;AAcA,IAAM,mBAAN,cAGG,aAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmEhC,YAAY;AAAA,IACV;AAAA,IACA,iBAAiB;AAAA,IACjB,kBAAkB;AAAA,IAClB;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,EACF,GAA2E;AA3G7E;AA4GI,UAAM;AA1DR,SAAQ,aAAsB;AAC9B,SAAQ,UAAmB;AAG3B,SAAQ,wBAAiC;AACzC,SAAQ,iBAAoC;AAC5C,SAAQ,wBAAiC;AACzC,SAAQ,yBAAiD;AAEzD,SAAQ,gBAAsD;AAmD5D,SAAK,MAAM;AACX,SAAK,kBAAkB;AACvB,SAAK,mBAAmB;AACxB,SAAK,cAAc,oCAAe,CAAC;AACnC,SAAK,aAAa,kCAAc;AAEhC,SAAK,cAAc;AAEnB,SAAK,cAAc,MAAM;AACvB,UAAQ,cAAU,OAAO,YAAY,aAAa;AAChD,gBAAQ,GAAG,QAAQ,KAAK,QAAQ,KAAK,IAAI,CAAC;AAAA,MAC5C;AAAA,IACF;AAEA,SAAK,wBAAwB,KAAK,IAAI;AAAA,MACpC;AAAA,MACA,KAAK,oBAAoB,KAAK,IAAI;AAAA,IACpC;AACA,QAAI,KAAK,kBAAkB;AACzB,WAAK,yBAAyB,KAAK,qBAAqB,KAAK,IAAI;AACjE,WAAK,iBAAiB,SAAS,GAAG,UAAU,KAAK,sBAAuB;AAAA,IAC1E;AAIA,SAAI,UAAK,gBAAL,mBAAkB,mBAAmB;AACvC,WAAK,iBAAmB;AAAA,QACtB,KAAK;AAAA,QACL,KAAK,YAAY;AAAA,MACnB;AAAA,IACF;AAEA,QAAI,SAAS;AACX,WAAK,QAAQ;AAAA,IACf;AAAA,EACF;AAAA,EAEA,IAAI,SAAS;AACX,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,OAAO,OAAO;AAChB,QAAI,KAAK,YAAY,OAAO;AAC1B,WAAK,UAAU;AACf,WAAK,KAAK,UAAU,CAAC,KAAK,CAAC;AAC3B,WAAK,KAAK,QAAQ,CAAC,KAAK,CAAC;AAAA,IAC3B;AAAA,EACF;AAAA,EAEA,IAAI,UAAU,OAAO;AACnB,QAAI,KAAK,eAAe,OAAO;AAC7B,WAAK,aAAa;AAClB,UAAI,OAAO;AACT,aAAK,eAAe;AAAA,MACtB;AACA,WAAK,KAAK,UAAU,CAAC,EAAE,QAAQ,QAAQ,cAAc,eAAe,CAAC,CAAC;AAAA,IACxE;AAAA,EACF;AAAA,EAEA,IAAI,YAAY;AACd,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,MAAM,QAAoB;AAChC,QAAI,KAAK,gBAAgB;AACvB,WAAK,iBAAmB,eAAa,CAAC,KAAK,gBAAgB,MAAM,CAAC;AAAA,IACpE,OAAO;AACL,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AAAA,EAEQ,qBAAqB;AAC3B,QAAI,KAAK,kBAAkB,MAAM;AAC/B,mBAAa,KAAK,aAAa;AAC/B,WAAK,gBAAgB;AAAA,IACvB;AAAA,EACF;AAAA,EAEQ,yBAAyB;AAC/B,QAAI,KAAK,aAAa,GAAG;AACvB,UAAI,KAAK,kBAAkB,MAAM;AAC/B,aAAK,gBAAgB,WAAW,YAAY;AAC1C,eAAK,gBAAgB;AACrB,gBAAM,KAAK,eAAe;AAC1B,cACE,KAAK,kBACL,KAAK,aACL,CAAC,KAAK,uBACN;AACA,iBAAK,uBAAuB;AAAA,UAC9B;AAAA,QACF,GAAG,KAAK,UAAU;AAAA,MACpB;AAAA,IACF,OAAO;AACL,WAAK,eAAe;AAAA,IACtB;AAAA,EACF;AAAA,EAEA,UAAU;AAhNZ;AAiNI,SAAK,mBAAmB;AACxB,SAAK,WAAW;AAEhB,SAAK,IAAI,IAAI,UAAU,KAAK,qBAAqB;AACjD,eAAK,qBAAL,mBAAuB,SAAS,IAAI,UAAU,KAAK;AAEnD,QAAQ,cAAU,OAAO,YAAY,aAAa;AAChD,cAAQ,IAAI,QAAQ,KAAK,WAAY;AAAA,IACvC;AACA,UAAM,QAAQ;AAAA,EAChB;AAAA,EAEA,aAAa;AA7Nf;AA+NI,SAAK,mBAAmB;AACxB,QAAI,KAAK,kBAAkB,KAAK,WAAW;AACzC,WAAK,eAAe;AAAA,IACtB;AAEA,eAAK,sBAAL;AAEA,QAAI,CAAC,KAAK,WAAW;AACnB;AAAA,IACF;AAEA,QAAI,KAAK,kBAAkB;AACzB,MAAkB;AAAA,QAChB,KAAK,iBAAiB;AAAA,QACtB,MAAM,KAAK,KAAK,iBAAiB,SAAS,UAAU,EAAE,KAAK,CAAC,EAAE;AAAA,UAC5D,CAAC,WAAW,WAAW,KAAK,iBAAkB,SAAS;AAAA,QACzD;AAAA,QACA;AAAA,MACF;AAGA,MAAkB;AAAA,QAChB,KAAK,iBAAiB;AAAA,QACtB,CAAC,KAAK,iBAAiB,SAAS,QAAQ;AAAA,QACxC;AAAA,MACF;AAEA,WAAK,iBAAiB,SAAS,cAAc,CAAC,CAAC;AAAA,IACjD;AAGA,SAAK,KAAK,oBAAoB,CAAC,CAAC;AAEhC,SAAK,yBAAyB;AAE9B,SAAK,YAAY;AACjB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,UAAU;AACR,QAAI,KAAK,WAAW;AAClB;AAAA,IACF;AACA,UAAM,kBAAkB,IAAI,gBAAgB;AAE5C,UAAM,mBAAmB,IAAI,YAAmC,gDAC3D,KAAK,gBAAgB,QACrB,KAAK,YAAY,WAF0C;AAAA,MAG9D,QAAQ,gBAAgB;AAAA,IAC1B,EAAC;AAED,UAAM,6BAA6B,iBAAiB;AAAA,MAClD,CAAC,aAA+C;AAC9C,aAAK;AAAA,UACH;AAAA,UACA,iBAAiB;AAAA,UACjB,iBAAiB;AAAA,QACnB;AAAA,MACF;AAAA,IACF;AAEA,QAAI;AACJ,QAAI,KAAK,kBAAkB;AACzB,YAAM,kBAAkB,IAAI,YAAoC,iCAC3D,KAAK,iBAAiB,QADqC;AAAA,QAE9D,QAAQ,gBAAgB;AAAA,QACxB,QAAQ;AAAA,MACV,EAAC;AAED,kCAA4B,gBAAgB;AAAA,QAC1C,CAAC,aAAgD;AAC/C,eAAK,sBAAsB,QAAQ;AAAA,QACrC;AAAA,MACF;AAAA,IACF;AAEA,SAAK,oBAAoB,MAAM;AAC7B,sBAAgB,MAAM;AACtB,iCAA2B;AAC3B;AACA,WAAK,oBAAoB;AAAA,IAC3B;AAEA,SAAK,KAAK,UAAU,CAAC,EAAE,QAAQ,aAAa,CAAC,CAAC;AAAA,EAChD;AAAA,EAEQ,uBACN,UACA,QACA,QACA;AACA,eAAW,WAAW,UAAU;AAC9B,UAAI,gBAAgB,OAAO,GAAG;AAC5B,cAAM,UAAU,KAAK,gBAAgB,iBAAiB,QAAQ,KAAK;AACnE,eAAO,QAAQ,QAAQ,QAAQ,IAAI,QAAQ;AACzC,gBAAM,YAAqB,2BAAkB,OAAO;AACpD,UAAE,cAAY,KAAK,KAAK,WAAW,QAAQ;AAAA,QAC7C;AAAA,MACF,WACE,iBAAiB,OAAO,KACxB,QAAQ,QAAQ,YAAY,cAC5B;AACA,aAAK,YAAY,WAAW;AAAA,UAC1B;AAAA,UACA;AAAA,QACF;AAEA,YAAI,CAAC,KAAK,uBAAuB;AAC/B,eAAK,SAAS;AACd,eAAK,YAAY,oBAAsB,oBAAkB,KAAK,GAAG;AAAA,QACnE;AACA,aAAK,KAAK,eAAe,CAAC,KAAK,WAAW,CAAC;AAC3C,aAAK,YAAY;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,oBAAoB,QAAoB,QAAiB;AAErE,QAAI,WAAW,UAAU;AACvB;AAAA,IACF;AAEA,SAAK,MAAM,MAAM;AACjB,SAAK,uBAAuB;AAAA,EAC9B;AAAA,EAEA,MAAc,iBAAiB;AA9VjC;AA+VI,SAAK,mBAAmB;AAExB,QAAI,CAAC,KAAK,aAAa,KAAK,uBAAuB;AACjD;AAAA,IACF;AAEA,QAAI;AACF,WAAK,wBAAwB;AAC7B,aACE,KAAK,kBACL,KAAK,eAAe,SAAS,KAC7B,KAAK,WACL;AACA,cAAM,UAAU,KAAK;AACrB,aAAK,iBAAiB;AAEtB,cAAM,UAAmB,uBAAc;AACvC,QAAS,4BAAmB,SAAS,OAAO;AAE5C,cAAM,UAAU,MAAM;AAAA,UACpB;AAAA,UACA,KAAK,gBAAgB;AAAA,WACrB,UAAK,gBAAL,YAAoB;AAAA,UACpB,KAAK,gBAAgB;AAAA,QACvB;AACA,YAAI,CAAC,SAAS;AACZ,eAAK,MAAM,OAAO;AAClB,eAAK,WAAW;AAAA,QAClB;AAAA,MACF;AAEA,WAAK,YAAY,oBAAsB,oBAAkB,KAAK,GAAG;AACjE,WAAK,KAAK,eAAe,CAAC,KAAK,WAAW,CAAC;AAAA,IAC7C,UAAE;AACA,WAAK,wBAAwB;AAAA,IAC/B;AAAA,EACF;AAAA,EAEA,MAAc,qBACZ,iBACA,QACA;AAxYJ;AAyYI,QAAI,WAAW,WAAW,CAAC,KAAK,WAAW;AACzC;AAAA,IACF;AAEA,SAAK,yBAAyB;AAE9B,QAAI,KAAK,uBAAuB;AAC9B;AAAA,IACF;AAEA,SAAK,wBAAwB;AAE7B,QAAI;AACF,aAAO,KAAK,0BAA0B,KAAK,WAAW;AACpD,cAAM,SAAS,KAAK;AACpB,aAAK,yBAAyB;AAE9B,cAAM,EAAE,OAAO,SAAS,QAAQ,IAAI;AACpC,cAAM,iBAAiB,MAAM,OAAO,OAAO,EAAE,OAAO,OAAO;AAC3D,cAAM,UAAmB,uBAAc;AAEvC,QAAS;AAAA,UACP;AAAA,UACkB;AAAA,YAChB,KAAK,iBAAkB;AAAA,YACvB;AAAA,UACF;AAAA,QACF;AACA,cAAM,UAAU,MAAM;AAAA,UACpB;AAAA,UACA,KAAK,iBAAkB;AAAA,WACvB,UAAK,gBAAL,YAAoB;AAAA,UACpB,KAAK,iBAAkB;AAAA,QACzB;AACA,YAAI,CAAC,SAAS;AACZ,eAAK,WAAW;AAAA,QAClB;AAAA,MACF;AAAA,IACF,UAAE;AACA,WAAK,wBAAwB;AAAA,IAC/B;AAAA,EACF;AAAA,EAEQ,sBAAsB,UAA6C;AACzE,eAAW,WAAW,UAAU;AAC9B,UAAI,gBAAgB,OAAO,GAAG;AAC5B,YAAI,QAAQ,QAAQ,cAAc,UAAU;AAC1C,UAAkB;AAAA,YAChB,KAAK,iBAAkB;AAAA,YACvB,CAAC,OAAO,QAAQ,MAAM,SAAS,CAAC;AAAA,YAChC;AAAA,UACF;AAAA,QACF,OAAO;AACL,gBAAM,UAAU,KAAK,iBAAkB,iBAAiB,QAAQ,KAAK;AACrE,UAAkB;AAAA,YAChB,KAAK,iBAAkB;AAAA,YACd,2BAAkB,OAAO;AAAA,YAClC;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,eAAe,KACb,SACA,UACA,aACA,cACkB;AA/cpB;AAgdE,MAAI;AACJ,QAAM,KAAc,sBAAa,OAAO;AAExC,MAAI;AACF,eAAW,MAAM,YAAY,UAAW;AAAA,MACtC,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM;AAAA,IACR,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,2BAA2B;AAAA,IAC7C;AAEA,WAAO;AAAA,EACT,SAAS,OAAO;AACd,UAAM,cAAc,QAAO,kDAAe;AAAA,MACxC;AAAA,MACA;AAAA,IACF,OAH2B,YAGrB;AACN,WAAO;AAAA,EACT;AACF;;;ACveA,SAAS,gBAAAA,qBAAoB;AAE7B,YAAY,YAAY;AAOjB,IAAM,kCAAN,cAA8CA,cAA0C;AAAA,EAI7F,YAAY,KAAa;AACvB,UAAM;AACN,SAAK,MAAM;AAAA,EACb;AAAA,EAEA,uBAAuB,UAAwC;AAC7D,UAAM,qBAAqB,SAAS,GAAG,eAAe,KAAK,KAAK,KAAK,IAAI,CAAC;AAC1E,WAAO,MAAM,SAAS,IAAI,eAAe,kBAAkB;AAAA,EAC7D;AAAA,EAEA,KAAK,aAA0B;AAC7B,UAAM,WAAW,KAAK,UAAU;AAAA,MAC9B,YAAY,YAAY;AAAA,IAC1B,CAAC;AACD,iBAAa,QAAQ,KAAK,KAAK,QAAQ;AAEvC,QAAI,YAAY,mBAAmB;AACjC,YAAM,eAAsB,gBAAS,YAAY,iBAAiB;AAClE,mBAAa,QAAQ,GAAG,KAAK,GAAG,WAAW,YAAY;AAAA,IACzD,OAAO;AAEL,mBAAa,WAAW,GAAG,KAAK,GAAG,SAAS;AAAA,IAC9C;AAAA,EACF;AAAA,EAEA,OAAoB;AAClB,QAAI,KAAK,aAAa;AACpB,aAAO,KAAK;AAAA,IACd;AAEA,UAAM,WAAW,aAAa,QAAQ,KAAK,GAAG;AAC9C,QAAI,CAAC,UAAU;AACb,WAAK,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC;AAAA,IAC1B,OAAO;AACL,WAAK,cAAc,KAAK,MAAM,QAAQ;AAEtC,YAAM,aAAa,aAAa,QAAQ,GAAG,KAAK,GAAG,SAAS;AAC5D,UAAI,YAAY;AACd,aAAK,YAAa,oBAA2B,kBAAW,UAAU;AAAA,MACpE;AAEA,WAAK,KAAK,UAAU,CAAC,KAAK,WAAY,CAAC;AAAA,IACzC;AAEA,WAAO,KAAK;AAAA,EACd;AACF;;;AC5DA,YAAYC,eAAc;AAK1B,IAAM,wBAAwB,CAAC,cAAsB;AACnD,QAAM,iBAAiB,UAAU,WAAW,KAAK,IAC7C,UAAU,MAAM,CAAC,IACjB;AACJ,SAAO,IAAI;AAAA,IACT,eAAe,MAAM,SAAS,EAAG,IAAI,CAAC,SAAS,SAAS,MAAM,EAAE,CAAC;AAAA,EACnE;AACF;AAKO,IAAM,iBAAiB;AAAA,EAC5B,OAAO,CAAC,cAAsB;AAC5B,UAAM,aAAa,sBAAsB,SAAS;AAClD,WAAgB,wBAAc,UAAU;AAAA,EAC1C;AACF;","names":["ObservableV2","decoding"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@electric-sql/y-electric",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
4
4
|
"description": "YJS network provider for ElectricSQL",
|
|
5
5
|
"author": "ElectricSQL team and contributors.",
|
|
6
6
|
"bugs": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"lib0": "^0.2.65",
|
|
12
12
|
"y-protocols": "^1.0.5",
|
|
13
13
|
"yjs": "^13.6.6",
|
|
14
|
-
"@electric-sql/client": "1.3.
|
|
14
|
+
"@electric-sql/client": "1.3.1"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@types/node": "^22.0.0",
|
|
@@ -25,7 +25,6 @@ export class LocalStorageResumeStateProvider extends ObservableV2<ElectricResume
|
|
|
25
25
|
save(resumeState: ResumeState) {
|
|
26
26
|
const jsonPart = JSON.stringify({
|
|
27
27
|
operations: resumeState.document,
|
|
28
|
-
awareness: resumeState.awareness,
|
|
29
28
|
})
|
|
30
29
|
localStorage.setItem(this.key, jsonPart)
|
|
31
30
|
|
package/src/types.ts
CHANGED
|
@@ -49,7 +49,6 @@ export type YProvider = {
|
|
|
49
49
|
* A resume state provider is used to persist the sync state of a document
|
|
50
50
|
* This is composed of:
|
|
51
51
|
* - The document shape offset and handle
|
|
52
|
-
* - The awareness shape offset and handle (optional)
|
|
53
52
|
* - The state vector of the document synced to the server (optional)
|
|
54
53
|
*/
|
|
55
54
|
export type ElectricResumeStateProvider = {
|
|
@@ -74,6 +73,7 @@ export type ElectricResumeStateProvider = {
|
|
|
74
73
|
* @param resumeState (optional) The resume state to use for the provider. If no resume state the provider will fetch the entire shape.
|
|
75
74
|
* @param connect (optional) Whether to automatically connect upon initialization.
|
|
76
75
|
* @param fetchClient (optional) Custom fetch implementation to use for send requests.
|
|
76
|
+
* @param debounceMs (optional) Debounce window in milliseconds for sending document updates. If 0 or undefined, debouncing is disabled and updates are sent immediately.
|
|
77
77
|
*/
|
|
78
78
|
export type ElectricProviderOptions<
|
|
79
79
|
RowWithDocumentUpdate extends Row<decoding.Decoder>,
|
|
@@ -96,6 +96,7 @@ export type ElectricProviderOptions<
|
|
|
96
96
|
resumeState?: ResumeState
|
|
97
97
|
connect?: boolean
|
|
98
98
|
fetchClient?: typeof fetch
|
|
99
|
+
debounceMs?: number
|
|
99
100
|
}
|
|
100
101
|
|
|
101
102
|
export type ResumeState = {
|
|
@@ -103,10 +104,6 @@ export type ResumeState = {
|
|
|
103
104
|
offset: Offset
|
|
104
105
|
handle: string
|
|
105
106
|
}
|
|
106
|
-
awareness?: {
|
|
107
|
-
offset: Offset
|
|
108
|
-
handle: string
|
|
109
|
-
}
|
|
110
107
|
|
|
111
108
|
// The vector of the document at the time of the last sync.
|
|
112
109
|
// When the provider starts, it batches the diff between this
|