@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.
Files changed (90) hide show
  1. package/dist/event-signal.js +3 -3
  2. package/dist/index.d.ts +2 -2
  3. package/dist/index.js +2 -0
  4. package/package.json +1 -1
  5. package/dist/json-patch/patchProxy.d.ts +0 -41
  6. package/dist/json-patch/patchProxy.js +0 -125
  7. package/dist/json-patch/state.d.ts +0 -2
  8. package/dist/json-patch/state.js +0 -8
  9. package/dist/json-patch/transformPatch.d.ts +0 -19
  10. package/dist/json-patch/transformPatch.js +0 -37
  11. package/dist/json-patch/types.d.ts +0 -52
  12. package/dist/json-patch/types.js +0 -1
  13. package/dist/json-patch/utils/deepEqual.d.ts +0 -1
  14. package/dist/json-patch/utils/deepEqual.js +0 -33
  15. package/dist/json-patch/utils/exit.d.ts +0 -2
  16. package/dist/json-patch/utils/exit.js +0 -4
  17. package/dist/json-patch/utils/get.d.ts +0 -2
  18. package/dist/json-patch/utils/get.js +0 -6
  19. package/dist/json-patch/utils/getOpData.d.ts +0 -2
  20. package/dist/json-patch/utils/getOpData.js +0 -10
  21. package/dist/json-patch/utils/getType.d.ts +0 -3
  22. package/dist/json-patch/utils/getType.js +0 -6
  23. package/dist/json-patch/utils/index.d.ts +0 -14
  24. package/dist/json-patch/utils/index.js +0 -14
  25. package/dist/json-patch/utils/log.d.ts +0 -2
  26. package/dist/json-patch/utils/log.js +0 -7
  27. package/dist/json-patch/utils/ops.d.ts +0 -14
  28. package/dist/json-patch/utils/ops.js +0 -103
  29. package/dist/json-patch/utils/paths.d.ts +0 -9
  30. package/dist/json-patch/utils/paths.js +0 -53
  31. package/dist/json-patch/utils/pluck.d.ts +0 -5
  32. package/dist/json-patch/utils/pluck.js +0 -30
  33. package/dist/json-patch/utils/shallowCopy.d.ts +0 -1
  34. package/dist/json-patch/utils/shallowCopy.js +0 -20
  35. package/dist/json-patch/utils/softWrites.d.ts +0 -7
  36. package/dist/json-patch/utils/softWrites.js +0 -18
  37. package/dist/json-patch/utils/toArrayIndex.d.ts +0 -1
  38. package/dist/json-patch/utils/toArrayIndex.js +0 -12
  39. package/dist/json-patch/utils/toKeys.d.ts +0 -1
  40. package/dist/json-patch/utils/toKeys.js +0 -15
  41. package/dist/json-patch/utils/updateArrayIndexes.d.ts +0 -5
  42. package/dist/json-patch/utils/updateArrayIndexes.js +0 -38
  43. package/dist/json-patch/utils/updateArrayPath.d.ts +0 -5
  44. package/dist/json-patch/utils/updateArrayPath.js +0 -45
  45. package/dist/net/AbstractTransport.d.ts +0 -47
  46. package/dist/net/AbstractTransport.js +0 -39
  47. package/dist/net/PatchesSync.d.ts +0 -47
  48. package/dist/net/PatchesSync.js +0 -289
  49. package/dist/net/index.d.ts +0 -9
  50. package/dist/net/index.js +0 -7
  51. package/dist/net/protocol/JSONRPCClient.d.ts +0 -55
  52. package/dist/net/protocol/JSONRPCClient.js +0 -106
  53. package/dist/net/protocol/types.d.ts +0 -142
  54. package/dist/net/protocol/types.js +0 -1
  55. package/dist/net/types.d.ts +0 -6
  56. package/dist/net/types.js +0 -1
  57. package/dist/net/webrtc/WebRTCAwareness.d.ts +0 -81
  58. package/dist/net/webrtc/WebRTCAwareness.js +0 -119
  59. package/dist/net/webrtc/WebRTCTransport.d.ts +0 -80
  60. package/dist/net/webrtc/WebRTCTransport.js +0 -157
  61. package/dist/net/websocket/PatchesWebSocket.d.ts +0 -107
  62. package/dist/net/websocket/PatchesWebSocket.js +0 -144
  63. package/dist/net/websocket/SignalingService.d.ts +0 -91
  64. package/dist/net/websocket/SignalingService.js +0 -140
  65. package/dist/net/websocket/WebSocketTransport.d.ts +0 -58
  66. package/dist/net/websocket/WebSocketTransport.js +0 -190
  67. package/dist/net/websocket/onlineState.d.ts +0 -9
  68. package/dist/net/websocket/onlineState.js +0 -18
  69. package/dist/persist/InMemoryStore.d.ts +0 -23
  70. package/dist/persist/InMemoryStore.js +0 -103
  71. package/dist/persist/IndexedDBStore.d.ts +0 -81
  72. package/dist/persist/IndexedDBStore.js +0 -377
  73. package/dist/persist/PatchesStore.d.ts +0 -38
  74. package/dist/persist/PatchesStore.js +0 -1
  75. package/dist/persist/index.d.ts +0 -3
  76. package/dist/persist/index.js +0 -3
  77. package/dist/server/PatchesBranchManager.d.ts +0 -40
  78. package/dist/server/PatchesBranchManager.js +0 -138
  79. package/dist/server/PatchesHistoryManager.d.ts +0 -43
  80. package/dist/server/PatchesHistoryManager.js +0 -59
  81. package/dist/server/PatchesServer.d.ts +0 -129
  82. package/dist/server/PatchesServer.js +0 -358
  83. package/dist/server/index.d.ts +0 -3
  84. package/dist/server/index.js +0 -3
  85. package/dist/types.d.ts +0 -164
  86. package/dist/types.js +0 -1
  87. package/dist/utils/batching.d.ts +0 -5
  88. package/dist/utils/batching.js +0 -38
  89. package/dist/utils.d.ts +0 -36
  90. 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,12 +0,0 @@
1
- export function toArrayIndex(array, str) {
2
- if (str === '-') {
3
- return array.length;
4
- }
5
- for (let i = 0, imax = str.length; i < imax; i++) {
6
- const ch = str.charCodeAt(i);
7
- if (57 < ch || ch < 48) {
8
- return Infinity;
9
- }
10
- }
11
- return +str;
12
- }
@@ -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
- }
@@ -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
- }
@@ -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';