@dabble/patches 0.2.8 → 0.2.10
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/dist/event-signal.js +3 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -0
- package/package.json +1 -1
- package/dist/json-patch/patchProxy.d.ts +0 -41
- package/dist/json-patch/patchProxy.js +0 -125
- package/dist/json-patch/state.d.ts +0 -2
- package/dist/json-patch/state.js +0 -8
- package/dist/json-patch/transformPatch.d.ts +0 -19
- package/dist/json-patch/transformPatch.js +0 -37
- package/dist/json-patch/types.d.ts +0 -52
- package/dist/json-patch/types.js +0 -1
- package/dist/json-patch/utils/deepEqual.d.ts +0 -1
- package/dist/json-patch/utils/deepEqual.js +0 -33
- package/dist/json-patch/utils/exit.d.ts +0 -2
- package/dist/json-patch/utils/exit.js +0 -4
- package/dist/json-patch/utils/get.d.ts +0 -2
- package/dist/json-patch/utils/get.js +0 -6
- package/dist/json-patch/utils/getOpData.d.ts +0 -2
- package/dist/json-patch/utils/getOpData.js +0 -10
- package/dist/json-patch/utils/getType.d.ts +0 -3
- package/dist/json-patch/utils/getType.js +0 -6
- package/dist/json-patch/utils/index.d.ts +0 -14
- package/dist/json-patch/utils/index.js +0 -14
- package/dist/json-patch/utils/log.d.ts +0 -2
- package/dist/json-patch/utils/log.js +0 -7
- package/dist/json-patch/utils/ops.d.ts +0 -14
- package/dist/json-patch/utils/ops.js +0 -103
- package/dist/json-patch/utils/paths.d.ts +0 -9
- package/dist/json-patch/utils/paths.js +0 -53
- package/dist/json-patch/utils/pluck.d.ts +0 -5
- package/dist/json-patch/utils/pluck.js +0 -30
- package/dist/json-patch/utils/shallowCopy.d.ts +0 -1
- package/dist/json-patch/utils/shallowCopy.js +0 -20
- package/dist/json-patch/utils/softWrites.d.ts +0 -7
- package/dist/json-patch/utils/softWrites.js +0 -18
- package/dist/json-patch/utils/toArrayIndex.d.ts +0 -1
- package/dist/json-patch/utils/toArrayIndex.js +0 -12
- package/dist/json-patch/utils/toKeys.d.ts +0 -1
- package/dist/json-patch/utils/toKeys.js +0 -15
- package/dist/json-patch/utils/updateArrayIndexes.d.ts +0 -5
- package/dist/json-patch/utils/updateArrayIndexes.js +0 -38
- package/dist/json-patch/utils/updateArrayPath.d.ts +0 -5
- package/dist/json-patch/utils/updateArrayPath.js +0 -45
- package/dist/net/AbstractTransport.d.ts +0 -47
- package/dist/net/AbstractTransport.js +0 -39
- package/dist/net/PatchesSync.d.ts +0 -47
- package/dist/net/PatchesSync.js +0 -289
- package/dist/net/index.d.ts +0 -9
- package/dist/net/index.js +0 -7
- package/dist/net/protocol/JSONRPCClient.d.ts +0 -55
- package/dist/net/protocol/JSONRPCClient.js +0 -106
- package/dist/net/protocol/types.d.ts +0 -142
- package/dist/net/protocol/types.js +0 -1
- package/dist/net/types.d.ts +0 -6
- package/dist/net/types.js +0 -1
- package/dist/net/webrtc/WebRTCAwareness.d.ts +0 -81
- package/dist/net/webrtc/WebRTCAwareness.js +0 -119
- package/dist/net/webrtc/WebRTCTransport.d.ts +0 -80
- package/dist/net/webrtc/WebRTCTransport.js +0 -157
- package/dist/net/websocket/PatchesWebSocket.d.ts +0 -107
- package/dist/net/websocket/PatchesWebSocket.js +0 -144
- package/dist/net/websocket/SignalingService.d.ts +0 -91
- package/dist/net/websocket/SignalingService.js +0 -140
- package/dist/net/websocket/WebSocketTransport.d.ts +0 -58
- package/dist/net/websocket/WebSocketTransport.js +0 -190
- package/dist/net/websocket/onlineState.d.ts +0 -9
- package/dist/net/websocket/onlineState.js +0 -18
- package/dist/persist/InMemoryStore.d.ts +0 -23
- package/dist/persist/InMemoryStore.js +0 -103
- package/dist/persist/IndexedDBStore.d.ts +0 -81
- package/dist/persist/IndexedDBStore.js +0 -377
- package/dist/persist/PatchesStore.d.ts +0 -38
- package/dist/persist/PatchesStore.js +0 -1
- package/dist/persist/index.d.ts +0 -3
- package/dist/persist/index.js +0 -3
- package/dist/server/PatchesBranchManager.d.ts +0 -40
- package/dist/server/PatchesBranchManager.js +0 -138
- package/dist/server/PatchesHistoryManager.d.ts +0 -43
- package/dist/server/PatchesHistoryManager.js +0 -59
- package/dist/server/PatchesServer.d.ts +0 -129
- package/dist/server/PatchesServer.js +0 -358
- package/dist/server/index.d.ts +0 -3
- package/dist/server/index.js +0 -3
- package/dist/types.d.ts +0 -164
- package/dist/types.js +0 -1
- package/dist/utils/batching.d.ts +0 -5
- package/dist/utils/batching.js +0 -38
- package/dist/utils.d.ts +0 -36
- package/dist/utils.js +0 -103
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { State } from '../types.js';
|
|
2
|
-
export declare const EMPTY: {};
|
|
3
|
-
export declare function pluck(state: State, keys: string[]): any;
|
|
4
|
-
export declare function pluckWithShallowCopy(state: State, keys: string[], createMissingObjects?: boolean): any;
|
|
5
|
-
export declare function getValue(state: State, value: any, addKey?: string, addValue?: any): any;
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { shallowCopy } from './shallowCopy.js';
|
|
2
|
-
export const EMPTY = {};
|
|
3
|
-
export function pluck(state, keys) {
|
|
4
|
-
let object = state.root;
|
|
5
|
-
for (let i = 0, imax = keys.length - 1; i < imax; i++) {
|
|
6
|
-
const key = keys[i];
|
|
7
|
-
if (!object[key]) {
|
|
8
|
-
return null;
|
|
9
|
-
}
|
|
10
|
-
object = object[key];
|
|
11
|
-
}
|
|
12
|
-
return object;
|
|
13
|
-
}
|
|
14
|
-
export function pluckWithShallowCopy(state, keys, createMissingObjects) {
|
|
15
|
-
let object = state.root;
|
|
16
|
-
for (let i = 0, imax = keys.length - 1; i < imax; i++) {
|
|
17
|
-
const key = keys[i];
|
|
18
|
-
object = object[key] = createMissingObjects && !object[key] ? getValue(state, EMPTY) : getValue(state, object[key]);
|
|
19
|
-
}
|
|
20
|
-
return object;
|
|
21
|
-
}
|
|
22
|
-
export function getValue(state, value, addKey, addValue) {
|
|
23
|
-
if (!state.cache?.has(value)) {
|
|
24
|
-
value = shallowCopy(value);
|
|
25
|
-
state.cache?.add(value);
|
|
26
|
-
}
|
|
27
|
-
if (addKey)
|
|
28
|
-
value[addKey] = addValue;
|
|
29
|
-
return value;
|
|
30
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function shallowCopy(obj: any): any;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export function shallowCopy(obj) {
|
|
2
|
-
if (!obj || typeof obj !== 'object') {
|
|
3
|
-
return obj;
|
|
4
|
-
}
|
|
5
|
-
if (Array.isArray(obj)) {
|
|
6
|
-
const len = obj.length;
|
|
7
|
-
const ary = new Array(len);
|
|
8
|
-
for (let i = 0; i < len; i++) {
|
|
9
|
-
ary[i] = obj[i];
|
|
10
|
-
}
|
|
11
|
-
return ary;
|
|
12
|
-
}
|
|
13
|
-
const keys = Object.keys(obj);
|
|
14
|
-
const copy = {};
|
|
15
|
-
for (let j = 0, jmax = keys.length; j < jmax; j++) {
|
|
16
|
-
const key = keys[j];
|
|
17
|
-
copy[key] = obj[key];
|
|
18
|
-
}
|
|
19
|
-
return copy;
|
|
20
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { JSONPatchOp } from '../types.js';
|
|
2
|
-
export declare function isEmptyObject(value: any): boolean;
|
|
3
|
-
/**
|
|
4
|
-
* If other objects were added to this same path, assume they are maps/hashes/lookups and don't overwrite, allow
|
|
5
|
-
* subsequent ops to merge onto the first map created. `soft` will also do this for any value that already exists.
|
|
6
|
-
*/
|
|
7
|
-
export declare function updateSoftWrites(overPath: string, ops: JSONPatchOp[]): JSONPatchOp[];
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { log } from './log.js';
|
|
2
|
-
import { mapAndFilterOps } from './ops.js';
|
|
3
|
-
export function isEmptyObject(value) {
|
|
4
|
-
return Boolean(value && typeof value === 'object' && Object.keys(value).length === 0);
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* If other objects were added to this same path, assume they are maps/hashes/lookups and don't overwrite, allow
|
|
8
|
-
* subsequent ops to merge onto the first map created. `soft` will also do this for any value that already exists.
|
|
9
|
-
*/
|
|
10
|
-
export function updateSoftWrites(overPath, ops) {
|
|
11
|
-
return mapAndFilterOps(ops, op => {
|
|
12
|
-
if (op.op === 'add' && op.path === overPath && isEmptyObject(op.value)) {
|
|
13
|
-
log('Removing empty object', op);
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
16
|
-
return op;
|
|
17
|
-
});
|
|
18
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function toArrayIndex(array: any[], str: string): number;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function toKeys(path: string): string[];
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
function esc(m) {
|
|
2
|
-
return m === '~0' ? '~' : '/';
|
|
3
|
-
}
|
|
4
|
-
export function toKeys(path) {
|
|
5
|
-
const keys = path.split('/');
|
|
6
|
-
if (!path.includes('~')) {
|
|
7
|
-
return keys;
|
|
8
|
-
}
|
|
9
|
-
for (let i = 0, imax = keys.length; i < imax; i++) {
|
|
10
|
-
if (keys[i].includes('~')) {
|
|
11
|
-
keys[i] = keys[i].replace(/~[01]/g, esc);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
return keys;
|
|
15
|
-
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { JSONPatchOp, State } from '../types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Update array indexes to account for values being added or removed from an array.
|
|
4
|
-
*/
|
|
5
|
-
export declare function updateArrayIndexes(state: State, thisPath: string, otherOps: JSONPatchOp[], modifier: 1 | -1, isRemove?: boolean): JSONPatchOp[];
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { getTypeLike } from './getType.js';
|
|
2
|
-
import { log } from './log.js';
|
|
3
|
-
import { isAdd, mapAndFilterOps, transformRemove } from './ops.js';
|
|
4
|
-
import { getPrefixAndProp } from './paths.js';
|
|
5
|
-
import { updateArrayPath } from './updateArrayPath.js';
|
|
6
|
-
/**
|
|
7
|
-
* Update array indexes to account for values being added or removed from an array.
|
|
8
|
-
*/
|
|
9
|
-
export function updateArrayIndexes(state, thisPath, otherOps, modifier, isRemove) {
|
|
10
|
-
const [arrayPrefix, indexStr] = getPrefixAndProp(thisPath);
|
|
11
|
-
const index = parseInt(indexStr);
|
|
12
|
-
log('Shifting array indexes', thisPath, modifier);
|
|
13
|
-
// Check ops for any that need to be replaced
|
|
14
|
-
return mapAndFilterOps(otherOps, (op, i, breakAfter) => {
|
|
15
|
-
if (isRemove && thisPath === op.from) {
|
|
16
|
-
const opLike = getTypeLike(state, op);
|
|
17
|
-
if (opLike === 'move') {
|
|
18
|
-
// We need the rest of the otherOps to be adjusted against this "move"
|
|
19
|
-
breakAfter();
|
|
20
|
-
return transformRemove(state, op.path, otherOps.slice(i + 1));
|
|
21
|
-
}
|
|
22
|
-
else if (opLike === 'copy') {
|
|
23
|
-
// We need future ops on the copied object to be removed
|
|
24
|
-
breakAfter();
|
|
25
|
-
let rest = transformRemove(state, thisPath, otherOps.slice(i + 1));
|
|
26
|
-
rest = transformRemove(state, op.path, rest);
|
|
27
|
-
return rest;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
if (op.soft && isAdd(state, op, 'path') && op.path === thisPath) {
|
|
31
|
-
breakAfter(true);
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
// check for items from the same array that will be affected
|
|
35
|
-
op = updateArrayPath(state, op, 'from', arrayPrefix, index, modifier);
|
|
36
|
-
return op && updateArrayPath(state, op, 'path', arrayPrefix, index, modifier);
|
|
37
|
-
});
|
|
38
|
-
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { JSONPatchOp, State } from '../types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Adjust ops within an array
|
|
4
|
-
*/
|
|
5
|
-
export declare function updateArrayPath(state: State, otherOp: JSONPatchOp, pathName: 'from' | 'path', thisPrefix: string, thisIndex: number, modifier: 1 | -1): JSONPatchOp | [JSONPatchOp, JSONPatchOp] | null;
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { getTypeLike } from './getType.js';
|
|
2
|
-
import { isAdd } from './ops.js';
|
|
3
|
-
import { getIndexAndEnd } from './paths.js';
|
|
4
|
-
import { getValue } from './pluck.js';
|
|
5
|
-
/**
|
|
6
|
-
* Adjust ops within an array
|
|
7
|
-
*/
|
|
8
|
-
export function updateArrayPath(state, otherOp, pathName, thisPrefix, thisIndex, modifier) {
|
|
9
|
-
const path = otherOp[pathName];
|
|
10
|
-
if (!path || !path.startsWith(thisPrefix))
|
|
11
|
-
return otherOp;
|
|
12
|
-
const [otherIndex, end] = getIndexAndEnd(state, path, thisPrefix.length);
|
|
13
|
-
const opLike = getTypeLike(state, otherOp);
|
|
14
|
-
// A bit of complex logic to handle moves upwards in an array. Since an item is removed earier in the array and added later, the other index is like it was one less (or this index was one more), so we correct it
|
|
15
|
-
if (opLike === 'move' &&
|
|
16
|
-
pathName === 'path' &&
|
|
17
|
-
otherOp.from?.startsWith(thisPrefix) &&
|
|
18
|
-
getIndexAndEnd(state, otherOp.from, thisPrefix.length)[0] < otherIndex) {
|
|
19
|
-
thisIndex -= 1;
|
|
20
|
-
}
|
|
21
|
-
if (otherIndex < thisIndex)
|
|
22
|
-
return otherOp;
|
|
23
|
-
// When this is a removed item and the op is a subpath or a non-add, remove it.
|
|
24
|
-
if (otherIndex === thisIndex && modifier === -1) {
|
|
25
|
-
if (end === path.length) {
|
|
26
|
-
// If we are adding to the location something got removed, continue adding it.
|
|
27
|
-
if (isAdd(state, otherOp, pathName))
|
|
28
|
-
return otherOp;
|
|
29
|
-
if (otherOp.op === 'replace')
|
|
30
|
-
return getValue(state, otherOp, 'op', 'add');
|
|
31
|
-
// If we are replacing an item which was removed, add it (don't replace something else in the array)
|
|
32
|
-
if (opLike === 'replace')
|
|
33
|
-
return [{ op: 'add', path: otherOp.path, value: null }, otherOp];
|
|
34
|
-
}
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
else if (isAdd(state, otherOp, pathName) && otherIndex === thisIndex && end === path.length) {
|
|
38
|
-
if (otherOp.soft)
|
|
39
|
-
return null;
|
|
40
|
-
return otherOp;
|
|
41
|
-
}
|
|
42
|
-
const newPath = thisPrefix + (otherIndex + modifier) + path.slice(end);
|
|
43
|
-
otherOp = getValue(state, otherOp, pathName, newPath);
|
|
44
|
-
return otherOp;
|
|
45
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import type { ConnectionState, Transport } from '../net/protocol/types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Abstract base class that implements common functionality for various transport implementations.
|
|
4
|
-
* Provides state management and event signaling for connection state changes and message reception.
|
|
5
|
-
* Concrete transport implementations must extend this class and implement the abstract methods.
|
|
6
|
-
*/
|
|
7
|
-
export declare abstract class AbstractTransport implements Transport {
|
|
8
|
-
private _state;
|
|
9
|
-
/**
|
|
10
|
-
* Signal that emits when the connection state changes.
|
|
11
|
-
* Subscribers will receive the new connection state as an argument.
|
|
12
|
-
*/
|
|
13
|
-
readonly onStateChange: import("../event-signal.js").Signal<(state: ConnectionState) => void>;
|
|
14
|
-
/**
|
|
15
|
-
* Signal that emits when a message is received from the transport.
|
|
16
|
-
* Subscribers will receive the message data as a string.
|
|
17
|
-
*/
|
|
18
|
-
readonly onMessage: import("../event-signal.js").Signal<(data: string) => void>;
|
|
19
|
-
/**
|
|
20
|
-
* Gets the current connection state of the transport.
|
|
21
|
-
* @returns The current connection state ('connecting', 'connected', 'disconnected', or 'error')
|
|
22
|
-
*/
|
|
23
|
-
get state(): ConnectionState;
|
|
24
|
-
/**
|
|
25
|
-
* Sets the connection state and emits a state change event.
|
|
26
|
-
* This method is protected and should only be called by subclasses.
|
|
27
|
-
* @param state - The new connection state
|
|
28
|
-
*/
|
|
29
|
-
protected set state(state: ConnectionState);
|
|
30
|
-
/**
|
|
31
|
-
* Establishes the connection for this transport.
|
|
32
|
-
* Must be implemented by concrete subclasses.
|
|
33
|
-
* @returns A promise that resolves when the connection is established
|
|
34
|
-
*/
|
|
35
|
-
abstract connect(): Promise<void>;
|
|
36
|
-
/**
|
|
37
|
-
* Terminates the connection for this transport.
|
|
38
|
-
* Must be implemented by concrete subclasses.
|
|
39
|
-
*/
|
|
40
|
-
abstract disconnect(): void;
|
|
41
|
-
/**
|
|
42
|
-
* Sends data through this transport.
|
|
43
|
-
* Must be implemented by concrete subclasses.
|
|
44
|
-
* @param data - The string data to send
|
|
45
|
-
*/
|
|
46
|
-
abstract send(data: string): void;
|
|
47
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { signal } from '../event-signal.js';
|
|
2
|
-
/**
|
|
3
|
-
* Abstract base class that implements common functionality for various transport implementations.
|
|
4
|
-
* Provides state management and event signaling for connection state changes and message reception.
|
|
5
|
-
* Concrete transport implementations must extend this class and implement the abstract methods.
|
|
6
|
-
*/
|
|
7
|
-
export class AbstractTransport {
|
|
8
|
-
constructor() {
|
|
9
|
-
this._state = 'disconnected';
|
|
10
|
-
/**
|
|
11
|
-
* Signal that emits when the connection state changes.
|
|
12
|
-
* Subscribers will receive the new connection state as an argument.
|
|
13
|
-
*/
|
|
14
|
-
this.onStateChange = signal();
|
|
15
|
-
/**
|
|
16
|
-
* Signal that emits when a message is received from the transport.
|
|
17
|
-
* Subscribers will receive the message data as a string.
|
|
18
|
-
*/
|
|
19
|
-
this.onMessage = signal();
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Gets the current connection state of the transport.
|
|
23
|
-
* @returns The current connection state ('connecting', 'connected', 'disconnected', or 'error')
|
|
24
|
-
*/
|
|
25
|
-
get state() {
|
|
26
|
-
return this._state;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Sets the connection state and emits a state change event.
|
|
30
|
-
* This method is protected and should only be called by subclasses.
|
|
31
|
-
* @param state - The new connection state
|
|
32
|
-
*/
|
|
33
|
-
set state(state) {
|
|
34
|
-
if (state === this._state)
|
|
35
|
-
return;
|
|
36
|
-
this._state = state;
|
|
37
|
-
this.onStateChange.emit(state);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { Patches } from '../client/Patches.js';
|
|
2
|
-
import type { WebSocketOptions } from './websocket/WebSocketTransport.js';
|
|
3
|
-
export interface PatchesSyncOptions {
|
|
4
|
-
wsOptions?: WebSocketOptions;
|
|
5
|
-
maxBatchSize?: number;
|
|
6
|
-
}
|
|
7
|
-
export interface PatchesSyncState {
|
|
8
|
-
online: boolean;
|
|
9
|
-
connected: boolean;
|
|
10
|
-
syncing: 'initial' | 'updating' | null | Error;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Handles WebSocket connection, document subscriptions, and syncing logic between
|
|
14
|
-
* the Patches instance and the server.
|
|
15
|
-
*/
|
|
16
|
-
export declare class PatchesSync {
|
|
17
|
-
private ws;
|
|
18
|
-
private patches;
|
|
19
|
-
private store;
|
|
20
|
-
private options;
|
|
21
|
-
private trackedDocs;
|
|
22
|
-
private isFlushing;
|
|
23
|
-
private globalSyncTimeout;
|
|
24
|
-
private _state;
|
|
25
|
-
readonly onStateChange: import("../event-signal.js").Signal<(state: PatchesSyncState) => void>;
|
|
26
|
-
readonly onError: import("../event-signal.js").Signal<(error: Error, context?: {
|
|
27
|
-
docId?: string;
|
|
28
|
-
}) => void>;
|
|
29
|
-
constructor(url: string, patches: Patches, options?: PatchesSyncOptions);
|
|
30
|
-
get state(): PatchesSyncState;
|
|
31
|
-
private setState;
|
|
32
|
-
connect(): Promise<void>;
|
|
33
|
-
disconnect(): void;
|
|
34
|
-
private scheduleGlobalSync;
|
|
35
|
-
syncAllKnownDocs(): Promise<void>;
|
|
36
|
-
syncDoc(docId: string): Promise<void>;
|
|
37
|
-
flushDoc(docId: string): Promise<void>;
|
|
38
|
-
/**
|
|
39
|
-
* Initiates the deletion process for a document both locally and on the server.
|
|
40
|
-
* This now delegates the local tombstone marking to Patches.
|
|
41
|
-
*/
|
|
42
|
-
_handleDocDeleted(docId: string): Promise<void>;
|
|
43
|
-
private _handleOnlineChange;
|
|
44
|
-
private _handleConnectionChange;
|
|
45
|
-
private _handleDocsTracked;
|
|
46
|
-
private _handleDocsUntracked;
|
|
47
|
-
}
|
package/dist/net/PatchesSync.js
DELETED
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
import { signal } from '../event-signal.js';
|
|
2
|
-
import { breakIntoBatches } from '../utils/batching.js';
|
|
3
|
-
import { PatchesWebSocket } from './websocket/PatchesWebSocket.js';
|
|
4
|
-
import { onlineState } from './websocket/onlineState.js';
|
|
5
|
-
/**
|
|
6
|
-
* Handles WebSocket connection, document subscriptions, and syncing logic between
|
|
7
|
-
* the Patches instance and the server.
|
|
8
|
-
*/
|
|
9
|
-
export class PatchesSync {
|
|
10
|
-
constructor(url, patches, options = {}) {
|
|
11
|
-
this.isFlushing = new Set();
|
|
12
|
-
this.globalSyncTimeout = null;
|
|
13
|
-
this._state = { online: false, connected: false, syncing: null };
|
|
14
|
-
// Signals
|
|
15
|
-
this.onStateChange = signal();
|
|
16
|
-
this.onError = signal();
|
|
17
|
-
// --- Event Handlers ---
|
|
18
|
-
this._handleOnlineChange = (isOnline) => {
|
|
19
|
-
this.setState({ online: isOnline });
|
|
20
|
-
if (isOnline && this.state.connected) {
|
|
21
|
-
this.scheduleGlobalSync();
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
this._handleConnectionChange = (connectionState) => {
|
|
25
|
-
const isConnected = connectionState === 'connected';
|
|
26
|
-
const isConnecting = connectionState === 'connecting';
|
|
27
|
-
// Preserve syncing state if moving from connecting -> connected
|
|
28
|
-
// Reset syncing if disconnected or errored
|
|
29
|
-
const newSyncingState = isConnected
|
|
30
|
-
? this._state.syncing // Preserve
|
|
31
|
-
: isConnecting
|
|
32
|
-
? this._state.syncing // Preserve during connecting phase too
|
|
33
|
-
: null; // Reset
|
|
34
|
-
this.setState({ connected: isConnected, syncing: newSyncingState });
|
|
35
|
-
if (isConnected) {
|
|
36
|
-
// Sync everything on connect/reconnect
|
|
37
|
-
void this.syncAllKnownDocs();
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
this._handleDocsTracked = async (docIds) => {
|
|
41
|
-
const newIds = docIds.filter(id => !this.trackedDocs.has(id));
|
|
42
|
-
if (!newIds.length)
|
|
43
|
-
return;
|
|
44
|
-
newIds.forEach(id => this.trackedDocs.add(id));
|
|
45
|
-
if (this.state.connected) {
|
|
46
|
-
try {
|
|
47
|
-
await this.ws.subscribe(newIds);
|
|
48
|
-
// Trigger sync for newly tracked docs immediately
|
|
49
|
-
await Promise.all(newIds.map(id => this.syncDoc(id)));
|
|
50
|
-
}
|
|
51
|
-
catch (err) {
|
|
52
|
-
console.warn(`Failed to subscribe/sync newly tracked docs: ${newIds.join(', ')}`, err);
|
|
53
|
-
this.onError.emit(err);
|
|
54
|
-
// State remains tracked locally, will retry on next sync
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
this._handleDocsUntracked = async (docIds) => {
|
|
59
|
-
const existingIds = docIds.filter(id => this.trackedDocs.has(id));
|
|
60
|
-
if (!existingIds.length)
|
|
61
|
-
return;
|
|
62
|
-
existingIds.forEach(id => this.trackedDocs.delete(id));
|
|
63
|
-
if (this.state.connected) {
|
|
64
|
-
try {
|
|
65
|
-
await this.ws.unsubscribe(existingIds);
|
|
66
|
-
}
|
|
67
|
-
catch (err) {
|
|
68
|
-
console.warn(`Failed to unsubscribe docs: ${existingIds.join(', ')}`, err);
|
|
69
|
-
// Continue with local untrack
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
this.patches = patches;
|
|
74
|
-
this.store = patches.store;
|
|
75
|
-
this.options = options;
|
|
76
|
-
this.ws = new PatchesWebSocket(url, options.wsOptions);
|
|
77
|
-
this._state.online = onlineState.isOnline;
|
|
78
|
-
this.trackedDocs = new Set(patches.trackedDocs);
|
|
79
|
-
// --- Event Listeners ---
|
|
80
|
-
onlineState.onOnlineChange(this._handleOnlineChange);
|
|
81
|
-
this.ws.onStateChange(this._handleConnectionChange);
|
|
82
|
-
this.ws.onChangesCommitted(({ docId, changes }) => {
|
|
83
|
-
// Persist first, then notify Patches instance to update PatchesDoc
|
|
84
|
-
this.store
|
|
85
|
-
.saveCommittedChanges(docId, changes)
|
|
86
|
-
.then(() => this.patches.applyServerChanges(docId, changes))
|
|
87
|
-
.catch((err) => this.onError.emit(err, { docId }));
|
|
88
|
-
});
|
|
89
|
-
// Forward errors to Patches error signal
|
|
90
|
-
this.onError((err, context) => {
|
|
91
|
-
patches.onError.emit(err, context);
|
|
92
|
-
});
|
|
93
|
-
// Listen to Patches for tracking changes
|
|
94
|
-
patches.onTrackDocs(this._handleDocsTracked);
|
|
95
|
-
patches.onUntrackDocs(this._handleDocsUntracked);
|
|
96
|
-
patches.onDeleteDoc(this._handleDocDeleted);
|
|
97
|
-
}
|
|
98
|
-
get state() {
|
|
99
|
-
return this._state;
|
|
100
|
-
}
|
|
101
|
-
setState(update) {
|
|
102
|
-
const newState = { ...this._state, ...update };
|
|
103
|
-
if (JSON.stringify(this._state) !== JSON.stringify(newState)) {
|
|
104
|
-
this._state = newState;
|
|
105
|
-
this.onStateChange.emit(this._state);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
// --- Connection & Lifecycle ---
|
|
109
|
-
async connect() {
|
|
110
|
-
try {
|
|
111
|
-
await this.ws.connect();
|
|
112
|
-
// _handleConnectionChange handles state update and sync trigger
|
|
113
|
-
}
|
|
114
|
-
catch (err) {
|
|
115
|
-
console.error('PatchesSync connection failed:', err);
|
|
116
|
-
this.setState({ connected: false, syncing: err instanceof Error ? err : new Error(String(err)) });
|
|
117
|
-
this.onError.emit(err);
|
|
118
|
-
throw err;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
disconnect() {
|
|
122
|
-
if (this.globalSyncTimeout)
|
|
123
|
-
clearTimeout(this.globalSyncTimeout);
|
|
124
|
-
this.ws.disconnect();
|
|
125
|
-
this.setState({ connected: false, syncing: null });
|
|
126
|
-
}
|
|
127
|
-
// --- Doc Tracking & Subscription (Now handled via signals) ---
|
|
128
|
-
// --- Syncing Logic ---
|
|
129
|
-
scheduleGlobalSync() {
|
|
130
|
-
if (this.globalSyncTimeout)
|
|
131
|
-
clearTimeout(this.globalSyncTimeout);
|
|
132
|
-
this.globalSyncTimeout = setTimeout(() => {
|
|
133
|
-
this.globalSyncTimeout = null;
|
|
134
|
-
void this.syncAllKnownDocs();
|
|
135
|
-
}, 300);
|
|
136
|
-
}
|
|
137
|
-
async syncAllKnownDocs() {
|
|
138
|
-
if (!this.state.connected)
|
|
139
|
-
return;
|
|
140
|
-
this.setState({ syncing: 'updating' });
|
|
141
|
-
try {
|
|
142
|
-
const tracked = await this.store.listDocs(true); // Include deleted docs
|
|
143
|
-
const activeDocs = tracked.filter(t => !t.deleted);
|
|
144
|
-
const deletedDocs = tracked.filter(t => t.deleted);
|
|
145
|
-
const activeDocIds = activeDocs.map((t) => t.docId);
|
|
146
|
-
// Ensure tracked set reflects only active docs for subscription purposes
|
|
147
|
-
this.trackedDocs = new Set(activeDocIds);
|
|
148
|
-
// Subscribe to active docs
|
|
149
|
-
if (activeDocIds.length > 0) {
|
|
150
|
-
try {
|
|
151
|
-
await this.ws.subscribe(activeDocIds);
|
|
152
|
-
}
|
|
153
|
-
catch (err) {
|
|
154
|
-
console.warn('Error subscribing to active docs during sync:', err);
|
|
155
|
-
this.onError.emit(err);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
// Sync each active doc
|
|
159
|
-
const activeSyncPromises = activeDocIds.map((id) => this.syncDoc(id));
|
|
160
|
-
// Attempt to delete docs marked with tombstones
|
|
161
|
-
const deletePromises = deletedDocs.map(async ({ docId }) => {
|
|
162
|
-
try {
|
|
163
|
-
console.info(`Attempting server delete for tombstoned doc: ${docId}`);
|
|
164
|
-
await this.ws.deleteDoc(docId);
|
|
165
|
-
// If server delete succeeds, remove tombstone and all data locally
|
|
166
|
-
await this.store.confirmDeleteDoc(docId);
|
|
167
|
-
console.info(`Successfully deleted and untracked doc: ${docId}`);
|
|
168
|
-
}
|
|
169
|
-
catch (err) {
|
|
170
|
-
// If server delete fails (e.g., offline, already deleted), keep tombstone for retry
|
|
171
|
-
console.warn(`Server delete failed for ${docId}, keeping tombstone:`, err);
|
|
172
|
-
this.onError.emit(err, { docId });
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
// Wait for all sync and delete operations
|
|
176
|
-
await Promise.all([...activeSyncPromises, ...deletePromises]);
|
|
177
|
-
this.setState({ syncing: null });
|
|
178
|
-
}
|
|
179
|
-
catch (error) {
|
|
180
|
-
console.error('Error during global sync:', error);
|
|
181
|
-
this.setState({ syncing: error instanceof Error ? error : new Error(String(error)) });
|
|
182
|
-
this.onError.emit(error);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
async syncDoc(docId) {
|
|
186
|
-
if (this.isFlushing.has(docId))
|
|
187
|
-
return; // Already flushing
|
|
188
|
-
if (!this.state.connected)
|
|
189
|
-
return;
|
|
190
|
-
try {
|
|
191
|
-
const pending = await this.store.getPendingChanges(docId);
|
|
192
|
-
if (pending.length > 0) {
|
|
193
|
-
await this.flushDoc(docId); // flushDoc handles setting flushing state
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
// No pending, just check for server changes
|
|
197
|
-
const [committedRev] = await this.store.getLastRevs(docId);
|
|
198
|
-
const serverChanges = await this.ws.getChangesSince(docId, committedRev);
|
|
199
|
-
if (serverChanges.length > 0) {
|
|
200
|
-
await this.store.saveCommittedChanges(docId, serverChanges);
|
|
201
|
-
this.patches.applyServerChanges(docId, serverChanges);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
catch (err) {
|
|
206
|
-
console.error(`Error syncing doc ${docId}:`, err);
|
|
207
|
-
this.onError.emit(err, { docId });
|
|
208
|
-
// Don't let one doc failure stop others in global sync
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
async flushDoc(docId) {
|
|
212
|
-
if (!this.trackedDocs.has(docId)) {
|
|
213
|
-
throw new Error(`Document ${docId} is not tracked`);
|
|
214
|
-
}
|
|
215
|
-
if (this.isFlushing.has(docId)) {
|
|
216
|
-
throw new Error(`Document ${docId} is already being flushed`);
|
|
217
|
-
}
|
|
218
|
-
if (!this.state.connected) {
|
|
219
|
-
throw new Error('Not connected to server');
|
|
220
|
-
}
|
|
221
|
-
this.isFlushing.add(docId);
|
|
222
|
-
if (this.state.syncing !== 'updating') {
|
|
223
|
-
this.setState({ syncing: 'updating' });
|
|
224
|
-
}
|
|
225
|
-
try {
|
|
226
|
-
// Get changes from Patches for this doc
|
|
227
|
-
let pending = await this.store.getPendingChanges(docId);
|
|
228
|
-
if (!pending.length) {
|
|
229
|
-
// Try to get from memory if available
|
|
230
|
-
pending = this.patches.getDocChanges(docId);
|
|
231
|
-
if (!pending.length) {
|
|
232
|
-
this.isFlushing.delete(docId);
|
|
233
|
-
return; // Nothing to flush
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
const batches = breakIntoBatches(pending, this.options.maxBatchSize);
|
|
237
|
-
for (const batch of batches) {
|
|
238
|
-
if (!this.state.connected) {
|
|
239
|
-
throw new Error('Disconnected during flush');
|
|
240
|
-
}
|
|
241
|
-
const range = [batch[0].rev, batch[batch.length - 1].rev];
|
|
242
|
-
const committed = await this.ws.commitChanges(docId, batch);
|
|
243
|
-
// Persist committed + remove pending in store
|
|
244
|
-
await this.store.saveCommittedChanges(docId, committed, range);
|
|
245
|
-
// Notify Patches to update PatchesDoc
|
|
246
|
-
this.patches.applyServerChanges(docId, committed);
|
|
247
|
-
// Fetch remaining pending for next batch or check completion
|
|
248
|
-
pending = await this.store.getPendingChanges(docId);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
catch (err) {
|
|
252
|
-
console.error(`Flush failed for doc ${docId}:`, err);
|
|
253
|
-
this.onError.emit(err, { docId });
|
|
254
|
-
// Let Patches know about the failure
|
|
255
|
-
this.patches.handleSendFailure(docId);
|
|
256
|
-
// Don't clear flushing flag, let next sync attempt retry
|
|
257
|
-
throw err; // Re-throw so caller (like syncAll) knows it failed
|
|
258
|
-
}
|
|
259
|
-
finally {
|
|
260
|
-
this.isFlushing.delete(docId);
|
|
261
|
-
// Update global sync state if nothing else is flushing
|
|
262
|
-
if (this.isFlushing.size === 0 && this.state.syncing === 'updating') {
|
|
263
|
-
this.setState({ syncing: null });
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
// --- Server Operations ---
|
|
268
|
-
/**
|
|
269
|
-
* Initiates the deletion process for a document both locally and on the server.
|
|
270
|
-
* This now delegates the local tombstone marking to Patches.
|
|
271
|
-
*/
|
|
272
|
-
async _handleDocDeleted(docId) {
|
|
273
|
-
// Attempt server delete if online
|
|
274
|
-
if (this.state.connected) {
|
|
275
|
-
try {
|
|
276
|
-
await this.ws.deleteDoc(docId);
|
|
277
|
-
await this.store.confirmDeleteDoc(docId);
|
|
278
|
-
}
|
|
279
|
-
catch (err) {
|
|
280
|
-
console.error(`Server delete failed for doc ${docId}, will retry on reconnect/resync.`, err);
|
|
281
|
-
this.onError.emit(err, { docId });
|
|
282
|
-
throw err;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
else {
|
|
286
|
-
console.warn(`Offline: Server delete for doc ${docId} deferred.`);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
package/dist/net/index.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export * from './AbstractTransport.js';
|
|
2
|
-
export * from './PatchesSync.js';
|
|
3
|
-
export * from './protocol/JSONRPCClient.js';
|
|
4
|
-
export type * from './protocol/types';
|
|
5
|
-
export type * from './types';
|
|
6
|
-
export * from './webrtc/WebRTCAwareness.js';
|
|
7
|
-
export * from './webrtc/WebRTCTransport.js';
|
|
8
|
-
export * from './websocket/PatchesWebSocket.js';
|
|
9
|
-
export * from './websocket/WebSocketTransport.js';
|
package/dist/net/index.js
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export * from './AbstractTransport.js';
|
|
2
|
-
export * from './PatchesSync.js';
|
|
3
|
-
export * from './protocol/JSONRPCClient.js';
|
|
4
|
-
export * from './webrtc/WebRTCAwareness.js';
|
|
5
|
-
export * from './webrtc/WebRTCTransport.js';
|
|
6
|
-
export * from './websocket/PatchesWebSocket.js';
|
|
7
|
-
export * from './websocket/WebSocketTransport.js';
|