@electric-sql/y-electric 0.1.0
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/LICENSE +177 -0
- package/README.md +121 -0
- package/dist/cjs/index.cjs +475 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/index.d.cts +172 -0
- package/dist/index.browser.mjs +2 -0
- package/dist/index.browser.mjs.map +1 -0
- package/dist/index.d.ts +172 -0
- package/dist/index.legacy-esm.js +415 -0
- package/dist/index.legacy-esm.js.map +1 -0
- package/dist/index.mjs +443 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +77 -0
- package/src/index.ts +4 -0
- package/src/local-storage-resume-state.ts +62 -0
- package/src/types.ts +115 -0
- package/src/utils.ts +23 -0
- package/src/y-electric.ts +465 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import * as decoding from 'lib0/decoding';
|
|
2
|
+
import { ObservableV2 } from 'lib0/observable';
|
|
3
|
+
import { Row, ShapeStreamOptions, GetExtensions, Offset } from '@electric-sql/client';
|
|
4
|
+
import * as awarenessProtocol from 'y-protocols/awareness';
|
|
5
|
+
import * as Y from 'yjs';
|
|
6
|
+
import { ObservableV2 as ObservableV2$1 } from 'lib0/observable.js';
|
|
7
|
+
|
|
8
|
+
type ConnectivityStatus = `connected` | `disconnected` | `connecting`;
|
|
9
|
+
/**
|
|
10
|
+
* A function that handles send errors.
|
|
11
|
+
* @param response The http response from the server if the server returned a response.
|
|
12
|
+
* @param error An exception raised by the fetch client if the server did not return a response.
|
|
13
|
+
* @returns A promise that resolves to true if the send request should be retried.
|
|
14
|
+
*/
|
|
15
|
+
type SendErrorRetryHandler = ({ response, error, }: {
|
|
16
|
+
response?: Response;
|
|
17
|
+
error?: unknown;
|
|
18
|
+
}) => Promise<boolean>;
|
|
19
|
+
/**
|
|
20
|
+
* The Observable interface for the YElectric provider.
|
|
21
|
+
*
|
|
22
|
+
* @event resumeState emitted when the provider sends or receives an update. This is mainly consumed by ResumeStateProvider to persist the resume state.
|
|
23
|
+
* @event sync Emitted when the provider receives an up-to-date control message from the server, meaning that the client caught up with latest changes from the server.
|
|
24
|
+
* @event synced same as @event sync.
|
|
25
|
+
* @event status Emitted when the provider's connectivity status changes.
|
|
26
|
+
* @event "connection-close" Emitted when the client disconnects from the server, by unsubscribing from shapes.
|
|
27
|
+
*/
|
|
28
|
+
type YProvider = {
|
|
29
|
+
resumeState: (resumeState: ResumeState) => void;
|
|
30
|
+
sync: (state: boolean) => void;
|
|
31
|
+
synced: (state: boolean) => void;
|
|
32
|
+
status: (status: {
|
|
33
|
+
status: `connecting` | `connected` | `disconnected`;
|
|
34
|
+
}) => void;
|
|
35
|
+
'connection-close': () => void;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The Observable interface for a ResumeStateProvider
|
|
39
|
+
* A resume state provider is used to persist the sync state of a document
|
|
40
|
+
* This is composed of:
|
|
41
|
+
* - The document shape offset and handle
|
|
42
|
+
* - The awareness shape offset and handle (optional)
|
|
43
|
+
* - The state vector of the document synced to the server (optional)
|
|
44
|
+
*/
|
|
45
|
+
type ElectricResumeStateProvider = {
|
|
46
|
+
synced: (state: ResumeState) => void;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Options for the ElectricProvider.
|
|
50
|
+
*
|
|
51
|
+
* @template RowWithDocumentUpdate The type of the row that contains the document update.
|
|
52
|
+
* @template RowWithAwarenessUpdate (optional) The type of the row that contains the awareness update.
|
|
53
|
+
* @param documentUpdates Options for the document updates.
|
|
54
|
+
* @param documentUpdates.shape Options for the document updates shape.
|
|
55
|
+
* @param documentUpdates.sendUrl The URL to send the document updates to.
|
|
56
|
+
* @param documentUpdates.getUpdateFromRow A function that returns the update column from the row.
|
|
57
|
+
* @param documentUpdates.sendErrorRetryHandler (optional) A function that handles send errors.
|
|
58
|
+
* @param awarenessUpdates (optional) Options for the awareness updates.
|
|
59
|
+
* @param awarenessUpdates.shape Options for the awareness updates shape.
|
|
60
|
+
* @param awarenessUpdates.sendUrl The URL to send the awareness updates to.
|
|
61
|
+
* @param awarenessUpdates.getUpdateFromRow A function that returns the update column from the row.
|
|
62
|
+
* @param awarenessUpdates.sendErrorRetryHandler (optional) A function that handles send errors.
|
|
63
|
+
* @param resumeState (optional) The resume state to use for the provider. If no resume state the provider will fetch the entire shape.
|
|
64
|
+
* @param connect (optional) Whether to automatically connect upon initialization.
|
|
65
|
+
* @param fetchClient (optional) Custom fetch implementation to use for send requests.
|
|
66
|
+
*/
|
|
67
|
+
type ElectricProviderOptions<RowWithDocumentUpdate extends Row<decoding.Decoder>, RowWithAwarenessUpdate extends Row<decoding.Decoder> = never> = {
|
|
68
|
+
doc: Y.Doc;
|
|
69
|
+
documentUpdates: {
|
|
70
|
+
shape: ShapeStreamOptions<GetExtensions<RowWithDocumentUpdate>>;
|
|
71
|
+
sendUrl: string | URL;
|
|
72
|
+
getUpdateFromRow: (row: RowWithDocumentUpdate) => decoding.Decoder;
|
|
73
|
+
sendErrorRetryHandler?: SendErrorRetryHandler;
|
|
74
|
+
};
|
|
75
|
+
awarenessUpdates?: {
|
|
76
|
+
shape: ShapeStreamOptions<GetExtensions<RowWithAwarenessUpdate>>;
|
|
77
|
+
sendUrl: string | URL;
|
|
78
|
+
protocol: awarenessProtocol.Awareness;
|
|
79
|
+
getUpdateFromRow: (row: RowWithAwarenessUpdate) => decoding.Decoder;
|
|
80
|
+
sendErrorRetryHandler?: SendErrorRetryHandler;
|
|
81
|
+
};
|
|
82
|
+
resumeState?: ResumeState;
|
|
83
|
+
connect?: boolean;
|
|
84
|
+
fetchClient?: typeof fetch;
|
|
85
|
+
};
|
|
86
|
+
type ResumeState = {
|
|
87
|
+
document?: {
|
|
88
|
+
offset: Offset;
|
|
89
|
+
handle: string;
|
|
90
|
+
};
|
|
91
|
+
awareness?: {
|
|
92
|
+
offset: Offset;
|
|
93
|
+
handle: string;
|
|
94
|
+
};
|
|
95
|
+
stableStateVector?: Uint8Array;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
declare class ElectricProvider<RowWithDocumentUpdate extends Row<decoding.Decoder> = never, RowWithAwarenessUpdate extends Row<decoding.Decoder> = never> extends ObservableV2<YProvider> {
|
|
99
|
+
private doc;
|
|
100
|
+
private documentUpdates;
|
|
101
|
+
private awarenessUpdates?;
|
|
102
|
+
private _connected;
|
|
103
|
+
private _synced;
|
|
104
|
+
private resumeState;
|
|
105
|
+
private sendingPendingChanges;
|
|
106
|
+
private pendingChanges;
|
|
107
|
+
private sendingAwarenessState;
|
|
108
|
+
private pendingAwarenessUpdate;
|
|
109
|
+
private documentUpdateHandler;
|
|
110
|
+
private awarenessUpdateHandler?;
|
|
111
|
+
private exitHandler;
|
|
112
|
+
private unsubscribeShapes?;
|
|
113
|
+
private fetchClient?;
|
|
114
|
+
/**
|
|
115
|
+
* Creates a new ElectricProvider instance that connects YJS documents to Electric SQL.
|
|
116
|
+
*
|
|
117
|
+
* @constructor
|
|
118
|
+
* @param {ElectricProviderOptions} options - Configuration options for the provider
|
|
119
|
+
* @param {Y.Doc} options.doc - The YJS document to be synchronized
|
|
120
|
+
* @param {Object} options.documentUpdates - Document updates configuration
|
|
121
|
+
* @param {ShapeStreamOptions} options.documentUpdates.shape - Options for the document updates shape stream
|
|
122
|
+
* @param {string|URL} options.documentUpdates.sendUrl - URL endpoint for sending document updates
|
|
123
|
+
* @param {Function} options.documentUpdates.getUpdateFromRow - Function to extract document update from row
|
|
124
|
+
* @param {SendErrorRetryHandler} [options.documentUpdates.sendErrorRetryHandler] - Error handler for retrying document updates
|
|
125
|
+
* @param {Object} [options.awarenessUpdates] - Awareness updates configuration (optional)
|
|
126
|
+
* @param {ShapeStreamOptions} options.awarenessUpdates.shape - Options for the awareness updates shape stream
|
|
127
|
+
* @param {string|URL} options.awarenessUpdates.sendUrl - URL endpoint for sending awareness updates
|
|
128
|
+
* @param {awarenessProtocol.Awareness} options.awarenessUpdates.protocol - Awareness protocol instance
|
|
129
|
+
* @param {Function} options.awarenessUpdates.getUpdateFromRow - Function to extract awareness update from row
|
|
130
|
+
* @param {SendErrorRetryHandler} [options.awarenessUpdates.sendErrorRetryHandler] - Error handler for retrying awareness updates
|
|
131
|
+
* @param {ResumeState} [options.resumeState] - Resume state for the provider
|
|
132
|
+
* @param {boolean} [options.connect=true] - Whether to automatically connect upon initialization
|
|
133
|
+
* @param {typeof fetch} [options.fetchClient] - Custom fetch implementation to use for HTTP requests
|
|
134
|
+
*/
|
|
135
|
+
constructor({ doc, documentUpdates: documentUpdatesConfig, awarenessUpdates: awarenessUpdatesConfig, resumeState, connect, fetchClient, }: ElectricProviderOptions<RowWithDocumentUpdate, RowWithAwarenessUpdate>);
|
|
136
|
+
get synced(): boolean;
|
|
137
|
+
set synced(state: boolean);
|
|
138
|
+
set connected(state: boolean);
|
|
139
|
+
get connected(): boolean;
|
|
140
|
+
private batch;
|
|
141
|
+
destroy(): void;
|
|
142
|
+
disconnect(): void;
|
|
143
|
+
connect(): void;
|
|
144
|
+
private operationsShapeHandler;
|
|
145
|
+
private applyDocumentUpdate;
|
|
146
|
+
private sendOperations;
|
|
147
|
+
private applyAwarenessUpdate;
|
|
148
|
+
private awarenessShapeHandler;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* A ResumeStateProvider implementation using LocalStorage.
|
|
153
|
+
* This is a reference implementation that can be used as a starting point
|
|
154
|
+
* for implementing other ResumeStateProviders.
|
|
155
|
+
*/
|
|
156
|
+
declare class LocalStorageResumeStateProvider extends ObservableV2$1<ElectricResumeStateProvider> {
|
|
157
|
+
private key;
|
|
158
|
+
private resumeState?;
|
|
159
|
+
constructor(key: string);
|
|
160
|
+
subscribeToResumeState(provider: ElectricProvider): () => void;
|
|
161
|
+
save(resumeState: ResumeState): void;
|
|
162
|
+
load(): ResumeState;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Utility to parse hex string bytea data to a decoder for YJS operations
|
|
167
|
+
*/
|
|
168
|
+
declare const parseToDecoder: {
|
|
169
|
+
bytea: (hexString: string) => decoding.Decoder;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export { type ConnectivityStatus, ElectricProvider, type ElectricProviderOptions, type ElectricResumeStateProvider, LocalStorageResumeStateProvider, type ResumeState, type SendErrorRetryHandler, type YProvider, parseToDecoder };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var D=Object.defineProperty,E=Object.defineProperties;var O=Object.getOwnPropertyDescriptors;var y=Object.getOwnPropertySymbols;var V=Object.prototype.hasOwnProperty,k=Object.prototype.propertyIsEnumerable;var v=(a,t,e)=>t in a?D(a,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):a[t]=e,u=(a,t)=>{for(var e in t||(t={}))V.call(t,e)&&v(a,e,t[e]);if(y)for(var e of y(t))k.call(t,e)&&v(a,e,t[e]);return a},w=(a,t)=>E(a,O(t));var m=(a,t,e)=>new Promise((n,r)=>{var s=d=>{try{i(e.next(d))}catch(l){r(l)}},o=d=>{try{i(e.throw(d))}catch(l){r(l)}},i=d=>d.done?n(d.value):Promise.resolve(d.value).then(s,o);i((e=e.apply(a,t)).next())});import*as h from"lib0/encoding";import*as g from"lib0/decoding";import*as p from"y-protocols/awareness";import{ObservableV2 as W}from"lib0/observable";import*as U from"lib0/environment";import*as c from"yjs";import{isChangeMessage as b,isControlMessage as A,ShapeStream as R}from"@electric-sql/client";var C=class extends W{constructor({doc:e,documentUpdates:n,awarenessUpdates:r,resumeState:s,connect:o=!0,fetchClient:i}){var d;super();this._connected=!1;this._synced=!1;this.sendingPendingChanges=!1;this.pendingChanges=null;this.sendingAwarenessState=!1;this.pendingAwarenessUpdate=null;this.doc=e,this.documentUpdates=n,this.awarenessUpdates=r,this.resumeState=s!=null?s:{},this.fetchClient=i,this.exitHandler=()=>{U.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)),(d=this.resumeState)!=null&&d.stableStateVector&&(this.pendingChanges=c.encodeStateAsUpdate(this.doc,this.resumeState.stableStateVector)),o&&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=c.mergeUpdates([this.pendingChanges,e]):this.pendingChanges=e}destroy(){var e;this.disconnect(),this.doc.off("update",this.documentUpdateHandler),(e=this.awarenessUpdates)==null||e.protocol.off("update",this.awarenessUpdateHandler),U.isNode&&typeof process!="undefined"&&process.off("exit",this.exitHandler),super.destroy()}disconnect(){var e;(e=this.unsubscribeShapes)==null||e.call(this),this.connected&&(this.awarenessUpdates&&(p.removeAwarenessStates(this.awarenessUpdates.protocol,Array.from(this.awarenessUpdates.protocol.getStates().keys()).filter(n=>n!==this.awarenessUpdates.protocol.clientID),this),p.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,n=new R(w(u(u({},this.documentUpdates.shape),this.resumeState.document),{signal:e.signal})),r=n.subscribe(o=>{this.operationsShapeHandler(o,n.lastOffset,n.shapeHandle)}),s;if(this.awarenessUpdates){let o=new R(w(u(u({},this.awarenessUpdates.shape),this.resumeState.awareness),{signal:e.signal}));s=o.subscribe(i=>{this.awarenessShapeHandler(i,o.lastOffset,o.shapeHandle)})}this.unsubscribeShapes=()=>{e.abort(),r(),s==null||s(),this.unsubscribeShapes=void 0},this.emit("status",[{status:"connecting"}])}operationsShapeHandler(e,n,r){for(let s of e)if(b(s)){let o=this.documentUpdates.getUpdateFromRow(s.value);for(;o.pos!==o.arr.length;){let i=g.readVarUint8Array(o);c.applyUpdate(this.doc,i,"server")}}else A(s)&&s.headers.control==="up-to-date"&&(this.resumeState.document={offset:n,handle:r},this.sendingPendingChanges||(this.synced=!0,this.resumeState.stableStateVector=c.encodeStateVector(this.doc)),this.emit("resumeState",[this.resumeState]),this.connected=!0)}applyDocumentUpdate(e,n){return m(this,null,function*(){n!=="server"&&(this.batch(e),this.sendOperations())})}sendOperations(){return m(this,null,function*(){var e;if(!(!this.connected||this.sendingPendingChanges))try{for(this.sendingPendingChanges=!0;this.pendingChanges&&this.pendingChanges.length>2&&this.connected;){let n=this.pendingChanges;this.pendingChanges=null;let r=h.createEncoder();h.writeVarUint8Array(r,n),(yield x(r,this.documentUpdates.sendUrl,(e=this.fetchClient)!=null?e:fetch,this.documentUpdates.sendErrorRetryHandler))||(this.batch(n),this.disconnect())}this.resumeState.stableStateVector=c.encodeStateVector(this.doc),this.emit("resumeState",[this.resumeState])}finally{this.sendingPendingChanges=!1}})}applyAwarenessUpdate(e,n){return m(this,null,function*(){var r;if(!(n!=="local"||!this.connected)&&(this.pendingAwarenessUpdate=e,!this.sendingAwarenessState)){this.sendingAwarenessState=!0;try{for(;this.pendingAwarenessUpdate&&this.connected;){let s=this.pendingAwarenessUpdate;this.pendingAwarenessUpdate=null;let{added:o,updated:i,removed:d}=s,l=o.concat(i).concat(d),S=h.createEncoder();h.writeVarUint8Array(S,p.encodeAwarenessUpdate(this.awarenessUpdates.protocol,l)),(yield x(S,this.awarenessUpdates.sendUrl,(r=this.fetchClient)!=null?r:fetch,this.awarenessUpdates.sendErrorRetryHandler))||this.disconnect()}}finally{this.sendingAwarenessState=!1}}})}awarenessShapeHandler(e,n,r){for(let s of e)if(b(s))if(s.headers.operation==="delete")p.removeAwarenessStates(this.awarenessUpdates.protocol,[Number(s.value.client_id)],"remote");else{let o=this.awarenessUpdates.getUpdateFromRow(s.value);p.applyAwarenessUpdate(this.awarenessUpdates.protocol,g.readVarUint8Array(o),this)}else A(s)&&s.headers.control==="up-to-date"&&(this.resumeState.awareness={offset:n,handle:r},this.emit("resumeState",[this.resumeState]))}};function x(a,t,e,n){return m(this,null,function*(){var o;let r,s=h.toUint8Array(a);try{if(r=yield e(t,{method:"PUT",headers:{"Content-Type":"application/octet-stream"},body:s}),!r.ok)throw new Error("Server did not return 2xx");return!0}catch(i){return yield(o=n==null?void 0:n({response:r,error:i}))!=null?o:!1}})}import{ObservableV2 as _}from"lib0/observable.js";import*as f from"lib0/buffer";var H=class extends _{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,awareness:t.awareness});if(localStorage.setItem(this.key,e),t.stableStateVector){let n=f.toBase64(t.stableStateVector);localStorage.setItem(`${this.key}_vector`,n)}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=f.fromBase64(e)),this.emit("synced",[this.resumeState])}return this.resumeState}};import*as P from"lib0/decoding";var Y=a=>{let t=a.startsWith("\\x")?a.slice(2):a;return new Uint8Array(t.match(/.{1,2}/g).map(e=>parseInt(e,16)))},K={bytea:a=>{let t=Y(a);return P.createDecoder(t)}};export{C as ElectricProvider,H as LocalStorageResumeStateProvider,K as parseToDecoder};
|
|
2
|
+
//# sourceMappingURL=index.browser.mjs.map
|
|
@@ -0,0 +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,\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":["encoding","decoding","awarenessProtocol","ObservableV2","env","Y","isChangeMessage","isControlMessage","ShapeStream","ElectricProvider","ObservableV2","doc","documentUpdatesConfig","awarenessUpdatesConfig","resumeState","connect","fetchClient","_a","state","update","client","abortController","operationsStream","ShapeStream","__spreadProps","__spreadValues","operationsShapeUnsubscribe","messages","awarenessShapeUnsubscribe","awarenessStream","offset","handle","message","isChangeMessage","decoder","operation","isControlMessage","origin","__async","sending","encoder","send","awarenessUpdate","added","updated","removed","changedClients","endpoint","retryHandler","response","op","error","ObservableV2","buffer","LocalStorageResumeStateProvider","key","provider","resumeStateHandler","resumeState","jsonPart","vectorBase64","jsonData","vectorData","decoding","hexStringToUint8Array","hexString","cleanHexString","byte","parseToDecoder","uint8Array"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import * as decoding from 'lib0/decoding';
|
|
2
|
+
import { ObservableV2 } from 'lib0/observable';
|
|
3
|
+
import { Row, ShapeStreamOptions, GetExtensions, Offset } from '@electric-sql/client';
|
|
4
|
+
import * as awarenessProtocol from 'y-protocols/awareness';
|
|
5
|
+
import * as Y from 'yjs';
|
|
6
|
+
import { ObservableV2 as ObservableV2$1 } from 'lib0/observable.js';
|
|
7
|
+
|
|
8
|
+
type ConnectivityStatus = `connected` | `disconnected` | `connecting`;
|
|
9
|
+
/**
|
|
10
|
+
* A function that handles send errors.
|
|
11
|
+
* @param response The http response from the server if the server returned a response.
|
|
12
|
+
* @param error An exception raised by the fetch client if the server did not return a response.
|
|
13
|
+
* @returns A promise that resolves to true if the send request should be retried.
|
|
14
|
+
*/
|
|
15
|
+
type SendErrorRetryHandler = ({ response, error, }: {
|
|
16
|
+
response?: Response;
|
|
17
|
+
error?: unknown;
|
|
18
|
+
}) => Promise<boolean>;
|
|
19
|
+
/**
|
|
20
|
+
* The Observable interface for the YElectric provider.
|
|
21
|
+
*
|
|
22
|
+
* @event resumeState emitted when the provider sends or receives an update. This is mainly consumed by ResumeStateProvider to persist the resume state.
|
|
23
|
+
* @event sync Emitted when the provider receives an up-to-date control message from the server, meaning that the client caught up with latest changes from the server.
|
|
24
|
+
* @event synced same as @event sync.
|
|
25
|
+
* @event status Emitted when the provider's connectivity status changes.
|
|
26
|
+
* @event "connection-close" Emitted when the client disconnects from the server, by unsubscribing from shapes.
|
|
27
|
+
*/
|
|
28
|
+
type YProvider = {
|
|
29
|
+
resumeState: (resumeState: ResumeState) => void;
|
|
30
|
+
sync: (state: boolean) => void;
|
|
31
|
+
synced: (state: boolean) => void;
|
|
32
|
+
status: (status: {
|
|
33
|
+
status: `connecting` | `connected` | `disconnected`;
|
|
34
|
+
}) => void;
|
|
35
|
+
'connection-close': () => void;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The Observable interface for a ResumeStateProvider
|
|
39
|
+
* A resume state provider is used to persist the sync state of a document
|
|
40
|
+
* This is composed of:
|
|
41
|
+
* - The document shape offset and handle
|
|
42
|
+
* - The awareness shape offset and handle (optional)
|
|
43
|
+
* - The state vector of the document synced to the server (optional)
|
|
44
|
+
*/
|
|
45
|
+
type ElectricResumeStateProvider = {
|
|
46
|
+
synced: (state: ResumeState) => void;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Options for the ElectricProvider.
|
|
50
|
+
*
|
|
51
|
+
* @template RowWithDocumentUpdate The type of the row that contains the document update.
|
|
52
|
+
* @template RowWithAwarenessUpdate (optional) The type of the row that contains the awareness update.
|
|
53
|
+
* @param documentUpdates Options for the document updates.
|
|
54
|
+
* @param documentUpdates.shape Options for the document updates shape.
|
|
55
|
+
* @param documentUpdates.sendUrl The URL to send the document updates to.
|
|
56
|
+
* @param documentUpdates.getUpdateFromRow A function that returns the update column from the row.
|
|
57
|
+
* @param documentUpdates.sendErrorRetryHandler (optional) A function that handles send errors.
|
|
58
|
+
* @param awarenessUpdates (optional) Options for the awareness updates.
|
|
59
|
+
* @param awarenessUpdates.shape Options for the awareness updates shape.
|
|
60
|
+
* @param awarenessUpdates.sendUrl The URL to send the awareness updates to.
|
|
61
|
+
* @param awarenessUpdates.getUpdateFromRow A function that returns the update column from the row.
|
|
62
|
+
* @param awarenessUpdates.sendErrorRetryHandler (optional) A function that handles send errors.
|
|
63
|
+
* @param resumeState (optional) The resume state to use for the provider. If no resume state the provider will fetch the entire shape.
|
|
64
|
+
* @param connect (optional) Whether to automatically connect upon initialization.
|
|
65
|
+
* @param fetchClient (optional) Custom fetch implementation to use for send requests.
|
|
66
|
+
*/
|
|
67
|
+
type ElectricProviderOptions<RowWithDocumentUpdate extends Row<decoding.Decoder>, RowWithAwarenessUpdate extends Row<decoding.Decoder> = never> = {
|
|
68
|
+
doc: Y.Doc;
|
|
69
|
+
documentUpdates: {
|
|
70
|
+
shape: ShapeStreamOptions<GetExtensions<RowWithDocumentUpdate>>;
|
|
71
|
+
sendUrl: string | URL;
|
|
72
|
+
getUpdateFromRow: (row: RowWithDocumentUpdate) => decoding.Decoder;
|
|
73
|
+
sendErrorRetryHandler?: SendErrorRetryHandler;
|
|
74
|
+
};
|
|
75
|
+
awarenessUpdates?: {
|
|
76
|
+
shape: ShapeStreamOptions<GetExtensions<RowWithAwarenessUpdate>>;
|
|
77
|
+
sendUrl: string | URL;
|
|
78
|
+
protocol: awarenessProtocol.Awareness;
|
|
79
|
+
getUpdateFromRow: (row: RowWithAwarenessUpdate) => decoding.Decoder;
|
|
80
|
+
sendErrorRetryHandler?: SendErrorRetryHandler;
|
|
81
|
+
};
|
|
82
|
+
resumeState?: ResumeState;
|
|
83
|
+
connect?: boolean;
|
|
84
|
+
fetchClient?: typeof fetch;
|
|
85
|
+
};
|
|
86
|
+
type ResumeState = {
|
|
87
|
+
document?: {
|
|
88
|
+
offset: Offset;
|
|
89
|
+
handle: string;
|
|
90
|
+
};
|
|
91
|
+
awareness?: {
|
|
92
|
+
offset: Offset;
|
|
93
|
+
handle: string;
|
|
94
|
+
};
|
|
95
|
+
stableStateVector?: Uint8Array;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
declare class ElectricProvider<RowWithDocumentUpdate extends Row<decoding.Decoder> = never, RowWithAwarenessUpdate extends Row<decoding.Decoder> = never> extends ObservableV2<YProvider> {
|
|
99
|
+
private doc;
|
|
100
|
+
private documentUpdates;
|
|
101
|
+
private awarenessUpdates?;
|
|
102
|
+
private _connected;
|
|
103
|
+
private _synced;
|
|
104
|
+
private resumeState;
|
|
105
|
+
private sendingPendingChanges;
|
|
106
|
+
private pendingChanges;
|
|
107
|
+
private sendingAwarenessState;
|
|
108
|
+
private pendingAwarenessUpdate;
|
|
109
|
+
private documentUpdateHandler;
|
|
110
|
+
private awarenessUpdateHandler?;
|
|
111
|
+
private exitHandler;
|
|
112
|
+
private unsubscribeShapes?;
|
|
113
|
+
private fetchClient?;
|
|
114
|
+
/**
|
|
115
|
+
* Creates a new ElectricProvider instance that connects YJS documents to Electric SQL.
|
|
116
|
+
*
|
|
117
|
+
* @constructor
|
|
118
|
+
* @param {ElectricProviderOptions} options - Configuration options for the provider
|
|
119
|
+
* @param {Y.Doc} options.doc - The YJS document to be synchronized
|
|
120
|
+
* @param {Object} options.documentUpdates - Document updates configuration
|
|
121
|
+
* @param {ShapeStreamOptions} options.documentUpdates.shape - Options for the document updates shape stream
|
|
122
|
+
* @param {string|URL} options.documentUpdates.sendUrl - URL endpoint for sending document updates
|
|
123
|
+
* @param {Function} options.documentUpdates.getUpdateFromRow - Function to extract document update from row
|
|
124
|
+
* @param {SendErrorRetryHandler} [options.documentUpdates.sendErrorRetryHandler] - Error handler for retrying document updates
|
|
125
|
+
* @param {Object} [options.awarenessUpdates] - Awareness updates configuration (optional)
|
|
126
|
+
* @param {ShapeStreamOptions} options.awarenessUpdates.shape - Options for the awareness updates shape stream
|
|
127
|
+
* @param {string|URL} options.awarenessUpdates.sendUrl - URL endpoint for sending awareness updates
|
|
128
|
+
* @param {awarenessProtocol.Awareness} options.awarenessUpdates.protocol - Awareness protocol instance
|
|
129
|
+
* @param {Function} options.awarenessUpdates.getUpdateFromRow - Function to extract awareness update from row
|
|
130
|
+
* @param {SendErrorRetryHandler} [options.awarenessUpdates.sendErrorRetryHandler] - Error handler for retrying awareness updates
|
|
131
|
+
* @param {ResumeState} [options.resumeState] - Resume state for the provider
|
|
132
|
+
* @param {boolean} [options.connect=true] - Whether to automatically connect upon initialization
|
|
133
|
+
* @param {typeof fetch} [options.fetchClient] - Custom fetch implementation to use for HTTP requests
|
|
134
|
+
*/
|
|
135
|
+
constructor({ doc, documentUpdates: documentUpdatesConfig, awarenessUpdates: awarenessUpdatesConfig, resumeState, connect, fetchClient, }: ElectricProviderOptions<RowWithDocumentUpdate, RowWithAwarenessUpdate>);
|
|
136
|
+
get synced(): boolean;
|
|
137
|
+
set synced(state: boolean);
|
|
138
|
+
set connected(state: boolean);
|
|
139
|
+
get connected(): boolean;
|
|
140
|
+
private batch;
|
|
141
|
+
destroy(): void;
|
|
142
|
+
disconnect(): void;
|
|
143
|
+
connect(): void;
|
|
144
|
+
private operationsShapeHandler;
|
|
145
|
+
private applyDocumentUpdate;
|
|
146
|
+
private sendOperations;
|
|
147
|
+
private applyAwarenessUpdate;
|
|
148
|
+
private awarenessShapeHandler;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* A ResumeStateProvider implementation using LocalStorage.
|
|
153
|
+
* This is a reference implementation that can be used as a starting point
|
|
154
|
+
* for implementing other ResumeStateProviders.
|
|
155
|
+
*/
|
|
156
|
+
declare class LocalStorageResumeStateProvider extends ObservableV2$1<ElectricResumeStateProvider> {
|
|
157
|
+
private key;
|
|
158
|
+
private resumeState?;
|
|
159
|
+
constructor(key: string);
|
|
160
|
+
subscribeToResumeState(provider: ElectricProvider): () => void;
|
|
161
|
+
save(resumeState: ResumeState): void;
|
|
162
|
+
load(): ResumeState;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Utility to parse hex string bytea data to a decoder for YJS operations
|
|
167
|
+
*/
|
|
168
|
+
declare const parseToDecoder: {
|
|
169
|
+
bytea: (hexString: string) => decoding.Decoder;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export { type ConnectivityStatus, ElectricProvider, type ElectricProviderOptions, type ElectricResumeStateProvider, LocalStorageResumeStateProvider, type ResumeState, type SendErrorRetryHandler, type YProvider, parseToDecoder };
|