@dabble/patches 0.2.10 → 0.2.12

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 (97) hide show
  1. package/dist/client/Patches.d.ts +9 -1
  2. package/dist/client/Patches.js +9 -1
  3. package/dist/client/PatchesDoc.d.ts +13 -1
  4. package/dist/client/PatchesDoc.js +17 -3
  5. package/dist/json-patch/createJSONPatch.d.ts +1 -1
  6. package/dist/json-patch/createJSONPatch.js +2 -2
  7. package/dist/json-patch/patchProxy.d.ts +41 -0
  8. package/dist/json-patch/patchProxy.js +125 -0
  9. package/dist/json-patch/state.d.ts +2 -0
  10. package/dist/json-patch/state.js +8 -0
  11. package/dist/json-patch/transformPatch.d.ts +19 -0
  12. package/dist/json-patch/transformPatch.js +37 -0
  13. package/dist/json-patch/types.d.ts +52 -0
  14. package/dist/json-patch/types.js +1 -0
  15. package/dist/json-patch/utils/deepEqual.d.ts +1 -0
  16. package/dist/json-patch/utils/deepEqual.js +33 -0
  17. package/dist/json-patch/utils/exit.d.ts +2 -0
  18. package/dist/json-patch/utils/exit.js +4 -0
  19. package/dist/json-patch/utils/get.d.ts +2 -0
  20. package/dist/json-patch/utils/get.js +6 -0
  21. package/dist/json-patch/utils/getOpData.d.ts +2 -0
  22. package/dist/json-patch/utils/getOpData.js +10 -0
  23. package/dist/json-patch/utils/getType.d.ts +3 -0
  24. package/dist/json-patch/utils/getType.js +6 -0
  25. package/dist/json-patch/utils/index.d.ts +14 -0
  26. package/dist/json-patch/utils/index.js +14 -0
  27. package/dist/json-patch/utils/log.d.ts +2 -0
  28. package/dist/json-patch/utils/log.js +7 -0
  29. package/dist/json-patch/utils/ops.d.ts +14 -0
  30. package/dist/json-patch/utils/ops.js +103 -0
  31. package/dist/json-patch/utils/paths.d.ts +9 -0
  32. package/dist/json-patch/utils/paths.js +53 -0
  33. package/dist/json-patch/utils/pluck.d.ts +5 -0
  34. package/dist/json-patch/utils/pluck.js +30 -0
  35. package/dist/json-patch/utils/shallowCopy.d.ts +1 -0
  36. package/dist/json-patch/utils/shallowCopy.js +20 -0
  37. package/dist/json-patch/utils/softWrites.d.ts +7 -0
  38. package/dist/json-patch/utils/softWrites.js +18 -0
  39. package/dist/json-patch/utils/toArrayIndex.d.ts +1 -0
  40. package/dist/json-patch/utils/toArrayIndex.js +12 -0
  41. package/dist/json-patch/utils/toKeys.d.ts +1 -0
  42. package/dist/json-patch/utils/toKeys.js +15 -0
  43. package/dist/json-patch/utils/updateArrayIndexes.d.ts +5 -0
  44. package/dist/json-patch/utils/updateArrayIndexes.js +38 -0
  45. package/dist/json-patch/utils/updateArrayPath.d.ts +5 -0
  46. package/dist/json-patch/utils/updateArrayPath.js +45 -0
  47. package/dist/net/AbstractTransport.d.ts +47 -0
  48. package/dist/net/AbstractTransport.js +39 -0
  49. package/dist/net/PatchesSync.d.ts +52 -0
  50. package/dist/net/PatchesSync.js +293 -0
  51. package/dist/net/index.d.ts +9 -0
  52. package/dist/net/index.js +7 -0
  53. package/dist/net/protocol/JSONRPCClient.d.ts +55 -0
  54. package/dist/net/protocol/JSONRPCClient.js +106 -0
  55. package/dist/net/protocol/types.d.ts +142 -0
  56. package/dist/net/protocol/types.js +1 -0
  57. package/dist/net/types.d.ts +6 -0
  58. package/dist/net/types.js +1 -0
  59. package/dist/net/webrtc/WebRTCAwareness.d.ts +81 -0
  60. package/dist/net/webrtc/WebRTCAwareness.js +119 -0
  61. package/dist/net/webrtc/WebRTCTransport.d.ts +80 -0
  62. package/dist/net/webrtc/WebRTCTransport.js +157 -0
  63. package/dist/net/websocket/PatchesWebSocket.d.ts +107 -0
  64. package/dist/net/websocket/PatchesWebSocket.js +144 -0
  65. package/dist/net/websocket/SignalingService.d.ts +91 -0
  66. package/dist/net/websocket/SignalingService.js +140 -0
  67. package/dist/net/websocket/WebSocketTransport.d.ts +58 -0
  68. package/dist/net/websocket/WebSocketTransport.js +190 -0
  69. package/dist/net/websocket/onlineState.d.ts +9 -0
  70. package/dist/net/websocket/onlineState.js +18 -0
  71. package/dist/persist/InMemoryStore.d.ts +23 -0
  72. package/dist/persist/InMemoryStore.js +103 -0
  73. package/dist/persist/IndexedDBStore.d.ts +81 -0
  74. package/dist/persist/IndexedDBStore.js +377 -0
  75. package/dist/persist/PatchesStore.d.ts +38 -0
  76. package/dist/persist/PatchesStore.js +1 -0
  77. package/dist/persist/index.d.ts +3 -0
  78. package/dist/persist/index.js +3 -0
  79. package/dist/server/PatchesBranchManager.d.ts +40 -0
  80. package/dist/server/PatchesBranchManager.js +138 -0
  81. package/dist/server/PatchesHistoryManager.d.ts +43 -0
  82. package/dist/server/PatchesHistoryManager.js +59 -0
  83. package/dist/server/PatchesServer.d.ts +129 -0
  84. package/dist/server/PatchesServer.js +358 -0
  85. package/dist/server/index.d.ts +3 -0
  86. package/dist/server/index.js +3 -0
  87. package/dist/types.d.ts +166 -0
  88. package/dist/types.js +1 -0
  89. package/dist/utils/batching.d.ts +3 -0
  90. package/dist/utils/batching.js +41 -0
  91. package/dist/utils/breakChange.d.ts +10 -0
  92. package/dist/utils/breakChange.js +303 -0
  93. package/dist/utils/getJSONByteSize.d.ts +2 -0
  94. package/dist/utils/getJSONByteSize.js +13 -0
  95. package/dist/utils.d.ts +36 -0
  96. package/dist/utils.js +103 -0
  97. package/package.json +1 -1
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Represents a JSON-RPC 2.0 request object.
3
+ */
4
+ export interface JsonRpcRequest {
5
+ /** JSON-RPC protocol version, always "2.0" */
6
+ jsonrpc: '2.0';
7
+ /** Name of the remote procedure to call */
8
+ method: string;
9
+ /** Parameters to pass to the remote procedure */
10
+ params?: any;
11
+ /** Request identifier, used to match responses to requests */
12
+ id?: number | string;
13
+ }
14
+ /**
15
+ * Represents a JSON-RPC 2.0 response object.
16
+ */
17
+ export interface JsonRpcResponse {
18
+ /** JSON-RPC protocol version, always "2.0" */
19
+ jsonrpc: '2.0';
20
+ /** Result of the successful procedure call */
21
+ result?: any;
22
+ /** Error information if the procedure call failed */
23
+ error?: {
24
+ code: number;
25
+ message: string;
26
+ };
27
+ /** Response identifier, matches the id of the corresponding request */
28
+ id: number | string;
29
+ }
30
+ /** Union type for all possible JSON-RPC message types */
31
+ export type JsonRpcMessage = JsonRpcRequest | JsonRpcResponse;
32
+ /** Function type for sending JSON-RPC messages */
33
+ export type SendFn = (message: JsonRpcMessage) => void;
34
+ /**
35
+ * Service that facilitates WebRTC connection establishment by relaying signaling messages.
36
+ * Acts as a central hub for WebRTC peers to exchange connection information.
37
+ */
38
+ export declare class SignalingService {
39
+ private clients;
40
+ /**
41
+ * Registers a new client connection with the signaling service.
42
+ * Assigns a unique ID to the client and informs them of other connected peers.
43
+ *
44
+ * @param send - Function to send messages to this client
45
+ * @param id - Optional client ID (generated if not provided)
46
+ * @returns The client's assigned ID
47
+ */
48
+ onClientConnected(send: SendFn, id?: string): string;
49
+ /**
50
+ * Handles a client disconnection by removing them from the registry
51
+ * and notifying all other connected clients.
52
+ *
53
+ * @param id - ID of the disconnected client
54
+ */
55
+ onClientDisconnected(id: string): void;
56
+ /**
57
+ * Handles a signaling message from a client, relaying WebRTC session data
58
+ * between peers to facilitate connection establishment.
59
+ *
60
+ * @param fromId - ID of the client sending the message
61
+ * @param message - The JSON-RPC message or its string representation
62
+ * @returns True if the message was a valid signaling message and was handled, false otherwise
63
+ */
64
+ handleClientMessage(fromId: string, message: string | JsonRpcRequest): boolean;
65
+ /**
66
+ * Sends a successful JSON-RPC response to a client.
67
+ *
68
+ * @private
69
+ * @param toId - ID of the client to send the response to
70
+ * @param id - Request ID to match in the response
71
+ * @param result - Result data to include in the response
72
+ */
73
+ private respond;
74
+ /**
75
+ * Sends an error JSON-RPC response to a client.
76
+ *
77
+ * @private
78
+ * @param toId - ID of the client to send the error response to
79
+ * @param id - Request ID to match in the response, or undefined for notifications
80
+ * @param message - Error message to include
81
+ */
82
+ private respondError;
83
+ /**
84
+ * Broadcasts a message to all connected clients, optionally excluding one.
85
+ *
86
+ * @private
87
+ * @param message - The message to broadcast
88
+ * @param excludeId - Optional ID of a client to exclude from the broadcast
89
+ */
90
+ private broadcast;
91
+ }
@@ -0,0 +1,140 @@
1
+ import { createId } from 'crypto-id';
2
+ /**
3
+ * Service that facilitates WebRTC connection establishment by relaying signaling messages.
4
+ * Acts as a central hub for WebRTC peers to exchange connection information.
5
+ */
6
+ export class SignalingService {
7
+ constructor() {
8
+ this.clients = new Map();
9
+ }
10
+ /**
11
+ * Registers a new client connection with the signaling service.
12
+ * Assigns a unique ID to the client and informs them of other connected peers.
13
+ *
14
+ * @param send - Function to send messages to this client
15
+ * @param id - Optional client ID (generated if not provided)
16
+ * @returns The client's assigned ID
17
+ */
18
+ onClientConnected(send, id = createId(14)) {
19
+ this.clients.set(id, { send });
20
+ const welcome = {
21
+ jsonrpc: '2.0',
22
+ method: 'peer-welcome',
23
+ params: {
24
+ id,
25
+ peers: Array.from(this.clients.keys()).filter(pid => pid !== id),
26
+ },
27
+ };
28
+ send(welcome);
29
+ return id;
30
+ }
31
+ /**
32
+ * Handles a client disconnection by removing them from the registry
33
+ * and notifying all other connected clients.
34
+ *
35
+ * @param id - ID of the disconnected client
36
+ */
37
+ onClientDisconnected(id) {
38
+ this.clients.delete(id);
39
+ // Broadcast to all others
40
+ this.broadcast({
41
+ jsonrpc: '2.0',
42
+ method: 'peer-disconnected',
43
+ params: { id },
44
+ });
45
+ }
46
+ /**
47
+ * Handles a signaling message from a client, relaying WebRTC session data
48
+ * between peers to facilitate connection establishment.
49
+ *
50
+ * @param fromId - ID of the client sending the message
51
+ * @param message - The JSON-RPC message or its string representation
52
+ * @returns True if the message was a valid signaling message and was handled, false otherwise
53
+ */
54
+ handleClientMessage(fromId, message) {
55
+ let parsed;
56
+ try {
57
+ parsed = typeof message === 'string' ? JSON.parse(message) : message;
58
+ }
59
+ catch (err) {
60
+ return false;
61
+ }
62
+ if (parsed.jsonrpc !== '2.0' || parsed.method !== 'peer-signal' || !parsed.params?.to)
63
+ return false;
64
+ const { params, id } = parsed;
65
+ const { to, data } = params;
66
+ const target = this.clients.get(to);
67
+ if (!target) {
68
+ this.respondError(fromId, id, 'Target not connected');
69
+ // Was a signaling message, even if the target is not connected
70
+ return true;
71
+ }
72
+ const outbound = {
73
+ jsonrpc: '2.0',
74
+ method: 'signal',
75
+ params: {
76
+ from: fromId,
77
+ data,
78
+ },
79
+ };
80
+ target.send(outbound);
81
+ if (id !== undefined) {
82
+ this.respond(fromId, id, 'ok');
83
+ }
84
+ return true;
85
+ }
86
+ /**
87
+ * Sends a successful JSON-RPC response to a client.
88
+ *
89
+ * @private
90
+ * @param toId - ID of the client to send the response to
91
+ * @param id - Request ID to match in the response
92
+ * @param result - Result data to include in the response
93
+ */
94
+ respond(toId, id, result) {
95
+ const client = this.clients.get(toId);
96
+ if (!client)
97
+ return;
98
+ const response = {
99
+ jsonrpc: '2.0',
100
+ result,
101
+ id,
102
+ };
103
+ client.send(response);
104
+ }
105
+ /**
106
+ * Sends an error JSON-RPC response to a client.
107
+ *
108
+ * @private
109
+ * @param toId - ID of the client to send the error response to
110
+ * @param id - Request ID to match in the response, or undefined for notifications
111
+ * @param message - Error message to include
112
+ */
113
+ respondError(toId, id, message) {
114
+ if (id === undefined)
115
+ return;
116
+ const client = this.clients.get(toId);
117
+ if (!client)
118
+ return;
119
+ const response = {
120
+ jsonrpc: '2.0',
121
+ error: { code: -32000, message },
122
+ id,
123
+ };
124
+ client.send(response);
125
+ }
126
+ /**
127
+ * Broadcasts a message to all connected clients, optionally excluding one.
128
+ *
129
+ * @private
130
+ * @param message - The message to broadcast
131
+ * @param excludeId - Optional ID of a client to exclude from the broadcast
132
+ */
133
+ broadcast(message, excludeId) {
134
+ for (const [id, client] of this.clients.entries()) {
135
+ if (id !== excludeId) {
136
+ client.send(message);
137
+ }
138
+ }
139
+ }
140
+ }
@@ -0,0 +1,58 @@
1
+ import { AbstractTransport } from '../AbstractTransport.js';
2
+ /** WebSocket constructor options (subset) */
3
+ export interface WebSocketOptions {
4
+ protocol?: string | string[];
5
+ }
6
+ /**
7
+ * WebSocket-based transport implementation that provides communication over the WebSocket protocol.
8
+ * Includes automatic reconnection with exponential backoff.
9
+ */
10
+ export declare class WebSocketTransport extends AbstractTransport {
11
+ private url;
12
+ private wsOptions?;
13
+ private ws;
14
+ private reconnectTimer;
15
+ private backoff;
16
+ private connecting;
17
+ private connectionDeferred;
18
+ private onlineUnsubscriber;
19
+ /** Flag representing the *intent* to be connected. It is set by `connect()` and cleared by `disconnect()`. */
20
+ private shouldBeConnected;
21
+ /**
22
+ * Creates a new WebSocket transport instance.
23
+ * @param url - The WebSocket server URL to connect to
24
+ * @param wsOptions - Optional configuration for the WebSocket connection
25
+ */
26
+ constructor(url: string, wsOptions?: WebSocketOptions | undefined);
27
+ /**
28
+ * Establishes a connection to the WebSocket server.
29
+ * If a connection is already open or in progress, this method returns immediately.
30
+ * On connection failure, an automatic reconnection attempt will be scheduled.
31
+ * @returns A promise that resolves when the connection is established or rejects on error
32
+ */
33
+ connect(): Promise<void>;
34
+ /**
35
+ * Terminates the WebSocket connection and cancels any pending reconnection attempts.
36
+ */
37
+ disconnect(): void;
38
+ /**
39
+ * Sends data through the WebSocket connection.
40
+ * @param data - The string data to send
41
+ * @throws {Error} If the WebSocket is not connected
42
+ */
43
+ send(data: string): void;
44
+ /**
45
+ * Schedules a reconnection attempt using exponential backoff.
46
+ * The backoff time increases with each failed attempt, up to a maximum of 30 seconds.
47
+ * @private
48
+ */
49
+ private _scheduleReconnect;
50
+ /**
51
+ * Internal helper that adds (once) listeners for the browser's online/offline
52
+ * events so we can automatically attempt to connect when the network comes
53
+ * back and forcibly close when it goes away.
54
+ */
55
+ private _ensureOnlineOfflineListeners;
56
+ /** Removes previously registered online/offline listeners (if any) */
57
+ private _removeOnlineOfflineListeners;
58
+ }
@@ -0,0 +1,190 @@
1
+ import { deferred } from '../../utils.js';
2
+ import { AbstractTransport } from '../AbstractTransport.js';
3
+ import { onlineState } from './onlineState.js';
4
+ /**
5
+ * WebSocket-based transport implementation that provides communication over the WebSocket protocol.
6
+ * Includes automatic reconnection with exponential backoff.
7
+ */
8
+ export class WebSocketTransport extends AbstractTransport {
9
+ /**
10
+ * Creates a new WebSocket transport instance.
11
+ * @param url - The WebSocket server URL to connect to
12
+ * @param wsOptions - Optional configuration for the WebSocket connection
13
+ */
14
+ constructor(url, wsOptions) {
15
+ super();
16
+ this.url = url;
17
+ this.wsOptions = wsOptions;
18
+ this.ws = null;
19
+ this.reconnectTimer = null;
20
+ this.backoff = 1000;
21
+ this.connecting = false;
22
+ this.connectionDeferred = null;
23
+ this.onlineUnsubscriber = null;
24
+ /** Flag representing the *intent* to be connected. It is set by `connect()` and cleared by `disconnect()`. */
25
+ this.shouldBeConnected = false;
26
+ }
27
+ /**
28
+ * Establishes a connection to the WebSocket server.
29
+ * If a connection is already open or in progress, this method returns immediately.
30
+ * On connection failure, an automatic reconnection attempt will be scheduled.
31
+ * @returns A promise that resolves when the connection is established or rejects on error
32
+ */
33
+ async connect() {
34
+ // Record the caller's intent.
35
+ this.shouldBeConnected = true;
36
+ // Make sure we react to browser connectivity changes
37
+ this._ensureOnlineOfflineListeners();
38
+ // If the browser is known to be offline, defer the actual connection until
39
+ // it comes back online. We still return a promise that resolves once the
40
+ // connection is eventually established, so callers can `await` safely.
41
+ if (onlineState.isOffline) {
42
+ if (!this.connectionDeferred) {
43
+ this.connectionDeferred = deferred();
44
+ }
45
+ return this.connectionDeferred.promise;
46
+ }
47
+ // Return existing connection if already connected
48
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
49
+ return Promise.resolve();
50
+ }
51
+ // Return pending connection promise if already connecting
52
+ if (this.connecting && this.connectionDeferred) {
53
+ return this.connectionDeferred.promise;
54
+ }
55
+ this.connecting = true;
56
+ this.state = 'connecting';
57
+ // Create a new connection promise
58
+ this.connectionDeferred = deferred();
59
+ const { resolve, reject } = this.connectionDeferred;
60
+ try {
61
+ // Pass protocol option if available (standard 2nd arg)
62
+ // Other options like headers are not standard and require specific server/client handling
63
+ // or a different WebSocket client library.
64
+ this.ws = new WebSocket(this.url, this.wsOptions?.protocol);
65
+ this.ws.onopen = () => {
66
+ this.backoff = 1000; // Reset backoff on successful connection
67
+ this.state = 'connected';
68
+ this.connecting = false;
69
+ resolve();
70
+ };
71
+ this.ws.onclose = () => {
72
+ this.state = 'disconnected';
73
+ // If we were in the process of connecting, reject the promise
74
+ if (this.connecting) {
75
+ reject(new Error('Connection closed'));
76
+ this.connecting = false;
77
+ }
78
+ // Schedule reconnect regardless of whether it was a clean close
79
+ // as WebSockets don't always emit error events before closing
80
+ this._scheduleReconnect();
81
+ };
82
+ this.ws.onerror = error => {
83
+ this.state = 'error';
84
+ // If we're in the connection phase, reject the promise
85
+ if (this.connecting) {
86
+ this.connecting = false;
87
+ reject(error);
88
+ }
89
+ else {
90
+ // If error happens after established connection,
91
+ // schedule a reconnect. The socket will likely close
92
+ // right after this, but we schedule it anyway to be sure.
93
+ this._scheduleReconnect();
94
+ }
95
+ // Log the error for debugging
96
+ console.error('WebSocket error:', error);
97
+ };
98
+ this.ws.onmessage = event => {
99
+ this.onMessage.emit(event.data);
100
+ };
101
+ }
102
+ catch (error) {
103
+ this.state = 'error';
104
+ this.connecting = false;
105
+ reject(error);
106
+ this._scheduleReconnect();
107
+ }
108
+ return this.connectionDeferred.promise;
109
+ }
110
+ /**
111
+ * Terminates the WebSocket connection and cancels any pending reconnection attempts.
112
+ */
113
+ disconnect() {
114
+ // Clearing the intent stops automatic reconnection attempts.
115
+ this.shouldBeConnected = false;
116
+ // Remove listener now that we no longer intend to stay connected.
117
+ this._removeOnlineOfflineListeners();
118
+ if (this.reconnectTimer) {
119
+ clearTimeout(this.reconnectTimer);
120
+ this.reconnectTimer = null;
121
+ }
122
+ this.connecting = false;
123
+ if (this.ws) {
124
+ // Only attempt to close if not already closed
125
+ if (this.ws.readyState !== WebSocket.CLOSED && this.ws.readyState !== WebSocket.CLOSING) {
126
+ this.ws.close();
127
+ }
128
+ this.ws = null;
129
+ }
130
+ this.state = 'disconnected';
131
+ }
132
+ /**
133
+ * Sends data through the WebSocket connection.
134
+ * @param data - The string data to send
135
+ * @throws {Error} If the WebSocket is not connected
136
+ */
137
+ send(data) {
138
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
139
+ throw new Error('WebSocket is not connected');
140
+ }
141
+ this.ws.send(data);
142
+ }
143
+ /**
144
+ * Schedules a reconnection attempt using exponential backoff.
145
+ * The backoff time increases with each failed attempt, up to a maximum of 30 seconds.
146
+ * @private
147
+ */
148
+ _scheduleReconnect() {
149
+ // Only schedule a reconnect if the caller still wants to be connected.
150
+ if (!this.shouldBeConnected || onlineState.isOffline) {
151
+ return;
152
+ }
153
+ if (this.reconnectTimer) {
154
+ return;
155
+ }
156
+ this.reconnectTimer = setTimeout(() => {
157
+ this.reconnectTimer = null;
158
+ this.connect().catch(err => {
159
+ console.error('WebSocket reconnect failed:', err);
160
+ });
161
+ }, this.backoff);
162
+ this.backoff = Math.min(this.backoff * 1.5, 30000);
163
+ }
164
+ /**
165
+ * Internal helper that adds (once) listeners for the browser's online/offline
166
+ * events so we can automatically attempt to connect when the network comes
167
+ * back and forcibly close when it goes away.
168
+ */
169
+ _ensureOnlineOfflineListeners() {
170
+ if (!this.onlineUnsubscriber) {
171
+ this.onlineUnsubscriber = onlineState.onOnlineChange(isOnline => {
172
+ if (isOnline && this.shouldBeConnected && !this.connecting && this.state !== 'connected') {
173
+ const { resolve, reject } = this.connectionDeferred;
174
+ this.connectionDeferred = null;
175
+ this.connect().then(resolve, reject);
176
+ }
177
+ else if (!isOnline && this.ws) {
178
+ this.ws.close();
179
+ }
180
+ });
181
+ }
182
+ }
183
+ /** Removes previously registered online/offline listeners (if any) */
184
+ _removeOnlineOfflineListeners() {
185
+ if (this.onlineUnsubscriber) {
186
+ this.onlineUnsubscriber();
187
+ this.onlineUnsubscriber = null;
188
+ }
189
+ }
190
+ }
@@ -0,0 +1,9 @@
1
+ declare class OnlineState {
2
+ onOnlineChange: import("../../event-signal").Signal<(isOnline: boolean) => void>;
3
+ _isOnline: boolean;
4
+ constructor();
5
+ get isOnline(): boolean;
6
+ get isOffline(): boolean;
7
+ }
8
+ export declare const onlineState: OnlineState;
9
+ export {};
@@ -0,0 +1,18 @@
1
+ import { signal } from '../../event-signal';
2
+ class OnlineState {
3
+ constructor() {
4
+ this.onOnlineChange = signal();
5
+ this._isOnline = typeof navigator !== 'undefined' && navigator.onLine;
6
+ if (typeof addEventListener === 'function') {
7
+ addEventListener('online', () => (this._isOnline = true) && this.onOnlineChange.emit(true));
8
+ addEventListener('offline', () => (this._isOnline = false) || this.onOnlineChange.emit(false));
9
+ }
10
+ }
11
+ get isOnline() {
12
+ return this._isOnline;
13
+ }
14
+ get isOffline() {
15
+ return !this._isOnline;
16
+ }
17
+ }
18
+ export const onlineState = new OnlineState();
@@ -0,0 +1,23 @@
1
+ import type { Change, PatchesSnapshot } from '../types.js';
2
+ import type { PatchesStore, TrackedDoc } from './PatchesStore.js';
3
+ /**
4
+ * A trivial in‑memory implementation of OfflineStore (soon PatchesStore).
5
+ * All data lives in JS objects – nothing survives a page reload.
6
+ * Useful for unit tests or when you want the old 'stateless realtime' behaviour.
7
+ */
8
+ export declare class InMemoryStore implements PatchesStore {
9
+ private docs;
10
+ /** Signal emitted when pending changes are added (mirrors IndexedDBStore API) */
11
+ readonly onPendingChanges: import("../event-signal.js").Signal<(docId: string, changes: Change[]) => void>;
12
+ getDoc(docId: string): Promise<PatchesSnapshot | undefined>;
13
+ getPendingChanges(docId: string): Promise<Change[]>;
14
+ getLastRevs(docId: string): Promise<[number, number]>;
15
+ listDocs(includeDeleted?: boolean): Promise<TrackedDoc[]>;
16
+ savePendingChanges(docId: string, changes: Change[]): Promise<void>;
17
+ saveCommittedChanges(docId: string, changes: Change[], sentPendingRange?: [number, number]): Promise<void>;
18
+ trackDocs(docIds: string[]): Promise<void>;
19
+ untrackDocs(docIds: string[]): Promise<void>;
20
+ deleteDoc(docId: string): Promise<void>;
21
+ confirmDeleteDoc(docId: string): Promise<void>;
22
+ close(): Promise<void>;
23
+ }
@@ -0,0 +1,103 @@
1
+ import { signal } from '../event-signal.js';
2
+ import { transformPatch } from '../json-patch/transformPatch.js';
3
+ import { applyChanges } from '../utils.js';
4
+ /**
5
+ * A trivial in‑memory implementation of OfflineStore (soon PatchesStore).
6
+ * All data lives in JS objects – nothing survives a page reload.
7
+ * Useful for unit tests or when you want the old 'stateless realtime' behaviour.
8
+ */
9
+ export class InMemoryStore {
10
+ constructor() {
11
+ this.docs = new Map();
12
+ /** Signal emitted when pending changes are added (mirrors IndexedDBStore API) */
13
+ this.onPendingChanges = signal();
14
+ }
15
+ // ─── Reconstruction ────────────────────────────────────────────────────
16
+ async getDoc(docId) {
17
+ const buf = this.docs.get(docId);
18
+ if (!buf || buf.deleted)
19
+ return undefined;
20
+ const state = applyChanges(buf.snapshot?.state ?? null, buf.committed);
21
+ const committedRev = buf.committed.at(-1)?.rev ?? buf.snapshot?.rev ?? 0;
22
+ // Rebase pending if they are stale w.r.t committed
23
+ if (buf.pending.length && buf.pending[0].baseRev < committedRev) {
24
+ const patch = buf.committed.filter(c => c.rev > buf.pending[0].baseRev).flatMap(c => c.ops);
25
+ const offset = committedRev - buf.pending[0].baseRev;
26
+ buf.pending.forEach(ch => {
27
+ ch.rev += offset;
28
+ ch.ops = transformPatch(state, patch, ch.ops);
29
+ });
30
+ }
31
+ return {
32
+ state,
33
+ rev: committedRev,
34
+ changes: [...buf.pending],
35
+ };
36
+ }
37
+ async getPendingChanges(docId) {
38
+ return this.docs.get(docId)?.pending.slice() ?? [];
39
+ }
40
+ async getLastRevs(docId) {
41
+ const buf = this.docs.get(docId);
42
+ if (!buf)
43
+ return [0, 0];
44
+ const committedRev = buf.committed.at(-1)?.rev ?? buf.snapshot?.rev ?? 0;
45
+ const pendingRev = buf.pending.at(-1)?.rev ?? committedRev;
46
+ return [committedRev, pendingRev];
47
+ }
48
+ async listDocs(includeDeleted = false) {
49
+ return Array.from(this.docs.entries())
50
+ .filter(([, b]) => includeDeleted || !b.deleted)
51
+ .map(([docId, buf]) => ({
52
+ docId,
53
+ committedRev: buf.snapshot?.rev ?? buf.committed.at(-1)?.rev ?? 0,
54
+ deleted: buf.deleted,
55
+ }));
56
+ }
57
+ // ─── Writes ────────────────────────────────────────────────────────────
58
+ async savePendingChanges(docId, changes) {
59
+ const buf = this.docs.get(docId) ?? { committed: [], pending: [] };
60
+ if (!this.docs.has(docId))
61
+ this.docs.set(docId, buf);
62
+ buf.pending.push(...changes);
63
+ this.onPendingChanges.emit(docId, changes);
64
+ }
65
+ async saveCommittedChanges(docId, changes, sentPendingRange) {
66
+ const buf = this.docs.get(docId) ?? { committed: [], pending: [] };
67
+ if (!this.docs.has(docId))
68
+ this.docs.set(docId, buf);
69
+ buf.committed.push(...changes);
70
+ if (sentPendingRange) {
71
+ const [min, max] = sentPendingRange;
72
+ buf.pending = buf.pending.filter(p => p.rev < min || p.rev > max);
73
+ }
74
+ }
75
+ // ─── Metadata / Tracking ───────────────────────────────────────────
76
+ async trackDocs(docIds) {
77
+ for (const docId of docIds) {
78
+ const buf = this.docs.get(docId) ?? { committed: [], pending: [] };
79
+ buf.deleted = undefined; // Ensure not marked as deleted
80
+ if (!this.docs.has(docId)) {
81
+ this.docs.set(docId, buf);
82
+ }
83
+ }
84
+ }
85
+ async untrackDocs(docIds) {
86
+ docIds.forEach(this.docs.delete, this.docs);
87
+ }
88
+ // ─── Misc / Lifecycle ────────────────────────────────────────────────
89
+ async deleteDoc(docId) {
90
+ const buf = this.docs.get(docId) ?? { committed: [], pending: [] };
91
+ buf.deleted = true;
92
+ buf.committed = [];
93
+ buf.pending = [];
94
+ buf.snapshot = undefined;
95
+ this.docs.set(docId, buf);
96
+ }
97
+ async confirmDeleteDoc(docId) {
98
+ this.docs.delete(docId);
99
+ }
100
+ async close() {
101
+ this.docs.clear();
102
+ }
103
+ }