@dabble/patches 0.4.5 → 0.4.7

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 (181) hide show
  1. package/dist/algorithms/client/applyCommittedChanges.d.ts +8 -2
  2. package/dist/algorithms/client/applyCommittedChanges.js +30 -38
  3. package/dist/algorithms/client/batching.d.ts +8 -2
  4. package/dist/algorithms/client/batching.js +38 -37
  5. package/dist/algorithms/client/breakChange.d.ts +8 -2
  6. package/dist/algorithms/client/breakChange.js +191 -240
  7. package/dist/algorithms/client/createStateFromSnapshot.d.ts +8 -2
  8. package/dist/algorithms/client/createStateFromSnapshot.js +7 -8
  9. package/dist/algorithms/client/getJSONByteSize.d.ts +3 -1
  10. package/dist/algorithms/client/getJSONByteSize.js +12 -11
  11. package/dist/algorithms/client/makeChange.d.ts +8 -2
  12. package/dist/algorithms/client/makeChange.js +28 -36
  13. package/dist/algorithms/server/commitChanges.d.ts +9 -3
  14. package/dist/algorithms/server/commitChanges.js +69 -78
  15. package/dist/algorithms/server/createVersion.d.ts +9 -3
  16. package/dist/algorithms/server/createVersion.js +21 -27
  17. package/dist/algorithms/server/getSnapshotAtRevision.d.ts +9 -3
  18. package/dist/algorithms/server/getSnapshotAtRevision.js +27 -28
  19. package/dist/algorithms/server/getStateAtRevision.d.ts +9 -3
  20. package/dist/algorithms/server/getStateAtRevision.js +13 -17
  21. package/dist/algorithms/server/handleOfflineSessionsAndBatches.d.ts +9 -3
  22. package/dist/algorithms/server/handleOfflineSessionsAndBatches.js +60 -77
  23. package/dist/algorithms/server/transformIncomingChanges.d.ts +8 -2
  24. package/dist/algorithms/server/transformIncomingChanges.js +27 -39
  25. package/dist/algorithms/shared/applyChanges.d.ts +8 -2
  26. package/dist/algorithms/shared/applyChanges.js +11 -16
  27. package/dist/algorithms/shared/rebaseChanges.d.ts +8 -2
  28. package/dist/algorithms/shared/rebaseChanges.js +30 -49
  29. package/dist/chunk-IZ2YBCUP.js +56 -0
  30. package/dist/client/InMemoryStore.d.ts +9 -3
  31. package/dist/client/InMemoryStore.js +92 -101
  32. package/dist/client/IndexedDBStore.d.ts +9 -3
  33. package/dist/client/IndexedDBStore.js +378 -491
  34. package/dist/client/Patches.d.ts +18 -13
  35. package/dist/client/Patches.js +152 -207
  36. package/dist/client/PatchesDoc.d.ts +14 -8
  37. package/dist/client/PatchesDoc.js +147 -154
  38. package/dist/client/PatchesHistoryClient.d.ts +12 -5
  39. package/dist/client/PatchesHistoryClient.js +110 -117
  40. package/dist/client/PatchesStore.d.ts +9 -3
  41. package/dist/client/PatchesStore.js +0 -1
  42. package/dist/client/index.d.ts +12 -6
  43. package/dist/client/index.js +5 -5
  44. package/dist/data/change.d.ts +9 -3
  45. package/dist/data/change.js +23 -15
  46. package/dist/data/version.d.ts +9 -3
  47. package/dist/data/version.js +11 -15
  48. package/dist/event-signal.d.ts +7 -6
  49. package/dist/event-signal.js +24 -39
  50. package/dist/index-CvQws3AB.d.ts +36 -0
  51. package/dist/index.d.ts +27 -5
  52. package/dist/index.js +10 -4
  53. package/dist/json-patch/JSONPatch.d.ts +9 -5
  54. package/dist/json-patch/JSONPatch.js +175 -183
  55. package/dist/json-patch/applyPatch.d.ts +5 -2
  56. package/dist/json-patch/applyPatch.js +27 -35
  57. package/dist/json-patch/composePatch.d.ts +5 -2
  58. package/dist/json-patch/composePatch.js +34 -34
  59. package/dist/json-patch/createJSONPatch.d.ts +7 -2
  60. package/dist/json-patch/createJSONPatch.js +11 -38
  61. package/dist/json-patch/index.d.ts +14 -6
  62. package/dist/json-patch/index.js +20 -9
  63. package/dist/json-patch/invertPatch.d.ts +5 -2
  64. package/dist/json-patch/invertPatch.js +31 -30
  65. package/dist/json-patch/ops/add.d.ts +5 -2
  66. package/dist/json-patch/ops/add.js +53 -51
  67. package/dist/json-patch/ops/bitmask.d.ts +8 -5
  68. package/dist/json-patch/ops/bitmask.js +41 -44
  69. package/dist/json-patch/ops/copy.d.ts +5 -2
  70. package/dist/json-patch/ops/copy.js +32 -33
  71. package/dist/json-patch/ops/increment.d.ts +5 -2
  72. package/dist/json-patch/ops/increment.js +21 -20
  73. package/dist/json-patch/ops/index.d.ts +10 -21
  74. package/dist/json-patch/ops/index.js +34 -24
  75. package/dist/json-patch/ops/move.d.ts +5 -2
  76. package/dist/json-patch/ops/move.js +132 -198
  77. package/dist/json-patch/ops/remove.d.ts +5 -2
  78. package/dist/json-patch/ops/remove.js +33 -30
  79. package/dist/json-patch/ops/replace.d.ts +5 -2
  80. package/dist/json-patch/ops/replace.js +45 -43
  81. package/dist/json-patch/ops/test.d.ts +5 -2
  82. package/dist/json-patch/ops/test.js +25 -21
  83. package/dist/json-patch/ops/text.d.ts +5 -2
  84. package/dist/json-patch/ops/text.js +54 -54
  85. package/dist/json-patch/pathProxy.d.ts +9 -3
  86. package/dist/json-patch/pathProxy.js +27 -48
  87. package/dist/json-patch/state.d.ts +5 -2
  88. package/dist/json-patch/state.js +11 -7
  89. package/dist/json-patch/transformPatch.d.ts +6 -2
  90. package/dist/json-patch/transformPatch.js +21 -24
  91. package/dist/json-patch/types.d.ts +9 -7
  92. package/dist/json-patch/types.js +0 -1
  93. package/dist/json-patch/utils/deepEqual.d.ts +3 -1
  94. package/dist/json-patch/utils/deepEqual.js +32 -28
  95. package/dist/json-patch/utils/exit.d.ts +5 -2
  96. package/dist/json-patch/utils/exit.js +7 -3
  97. package/dist/json-patch/utils/get.d.ts +5 -2
  98. package/dist/json-patch/utils/get.js +8 -4
  99. package/dist/json-patch/utils/getOpData.d.ts +5 -2
  100. package/dist/json-patch/utils/getOpData.js +12 -9
  101. package/dist/json-patch/utils/getType.d.ts +6 -3
  102. package/dist/json-patch/utils/getType.js +9 -4
  103. package/dist/json-patch/utils/index.d.ts +15 -14
  104. package/dist/json-patch/utils/index.js +14 -14
  105. package/dist/json-patch/utils/log.d.ts +4 -5
  106. package/dist/json-patch/utils/log.js +8 -3
  107. package/dist/json-patch/utils/ops.d.ts +8 -5
  108. package/dist/json-patch/utils/ops.js +83 -100
  109. package/dist/json-patch/utils/paths.d.ts +12 -9
  110. package/dist/json-patch/utils/paths.js +54 -51
  111. package/dist/json-patch/utils/pluck.d.ts +8 -5
  112. package/dist/json-patch/utils/pluck.js +32 -26
  113. package/dist/json-patch/utils/shallowCopy.d.ts +3 -1
  114. package/dist/json-patch/utils/shallowCopy.js +22 -18
  115. package/dist/json-patch/utils/softWrites.d.ts +6 -3
  116. package/dist/json-patch/utils/softWrites.js +17 -16
  117. package/dist/json-patch/utils/toArrayIndex.d.ts +3 -1
  118. package/dist/json-patch/utils/toArrayIndex.js +14 -10
  119. package/dist/json-patch/utils/toKeys.d.ts +3 -1
  120. package/dist/json-patch/utils/toKeys.js +15 -11
  121. package/dist/json-patch/utils/updateArrayIndexes.d.ts +5 -2
  122. package/dist/json-patch/utils/updateArrayIndexes.js +33 -37
  123. package/dist/json-patch/utils/updateArrayPath.d.ts +5 -2
  124. package/dist/json-patch/utils/updateArrayPath.js +29 -42
  125. package/dist/net/PatchesClient.d.ts +128 -0
  126. package/dist/net/PatchesClient.js +161 -0
  127. package/dist/net/PatchesSync.d.ts +19 -9
  128. package/dist/net/PatchesSync.js +291 -386
  129. package/dist/net/error.d.ts +3 -1
  130. package/dist/net/error.js +9 -6
  131. package/dist/net/http/FetchTransport.d.ts +21 -0
  132. package/dist/net/http/FetchTransport.js +34 -0
  133. package/dist/net/index.d.ts +26 -12
  134. package/dist/net/index.js +12 -10
  135. package/dist/net/protocol/JSONRPCClient.d.ts +11 -4
  136. package/dist/net/protocol/JSONRPCClient.js +95 -103
  137. package/dist/net/protocol/JSONRPCServer.d.ts +15 -8
  138. package/dist/net/protocol/JSONRPCServer.js +101 -123
  139. package/dist/net/protocol/types.d.ts +21 -15
  140. package/dist/net/protocol/types.js +0 -1
  141. package/dist/net/protocol/utils.d.ts +12 -0
  142. package/dist/net/protocol/utils.js +15 -0
  143. package/dist/net/types.d.ts +4 -2
  144. package/dist/net/types.js +0 -1
  145. package/dist/net/webrtc/WebRTCAwareness.d.ts +14 -4
  146. package/dist/net/webrtc/WebRTCAwareness.js +111 -120
  147. package/dist/net/webrtc/WebRTCTransport.d.ts +16 -8
  148. package/dist/net/webrtc/WebRTCTransport.js +149 -157
  149. package/dist/net/webrtc/index.d.ts +10 -2
  150. package/dist/net/webrtc/index.js +2 -2
  151. package/dist/net/websocket/AuthorizationProvider.d.ts +7 -5
  152. package/dist/net/websocket/AuthorizationProvider.js +12 -17
  153. package/dist/net/websocket/PatchesWebSocket.d.ts +14 -109
  154. package/dist/net/websocket/PatchesWebSocket.js +37 -184
  155. package/dist/net/websocket/RPCServer.d.ts +19 -10
  156. package/dist/net/websocket/RPCServer.js +190 -192
  157. package/dist/net/websocket/SignalingService.d.ts +12 -32
  158. package/dist/net/websocket/SignalingService.js +126 -133
  159. package/dist/net/websocket/WebSocketServer.d.ts +17 -4
  160. package/dist/net/websocket/WebSocketServer.js +64 -72
  161. package/dist/net/websocket/WebSocketTransport.d.ts +13 -5
  162. package/dist/net/websocket/WebSocketTransport.js +178 -207
  163. package/dist/net/websocket/onlineState.d.ts +6 -3
  164. package/dist/net/websocket/onlineState.js +25 -21
  165. package/dist/server/PatchesBranchManager.d.ts +12 -5
  166. package/dist/server/PatchesBranchManager.js +132 -142
  167. package/dist/server/PatchesHistoryManager.d.ts +11 -3
  168. package/dist/server/PatchesHistoryManager.js +81 -84
  169. package/dist/server/PatchesServer.d.ts +16 -10
  170. package/dist/server/PatchesServer.js +131 -137
  171. package/dist/server/index.d.ts +7 -2
  172. package/dist/server/index.js +9 -3
  173. package/dist/server/types.d.ts +9 -3
  174. package/dist/server/types.js +0 -1
  175. package/dist/types.d.ts +38 -19
  176. package/dist/types.js +1 -1
  177. package/dist/utils/concurrency.d.ts +7 -5
  178. package/dist/utils/concurrency.js +43 -53
  179. package/dist/utils/deferred.d.ts +4 -2
  180. package/dist/utils/deferred.js +25 -21
  181. package/package.json +13 -15
@@ -1,8 +1,14 @@
1
- import type { Change, PatchesSnapshot } from '../../types.js';
1
+ import { PatchesSnapshot, Change } from '../../types.js';
2
+ import '../../json-patch/JSONPatch.js';
3
+ import '@dabble/delta';
4
+ import '../../json-patch/types.js';
5
+
2
6
  /**
3
7
  * Applies incoming changes from the server that were *not* initiated by this client.
4
8
  * @param snapshot The current state of the document (the state without pending changes applied) and the pending changes.
5
9
  * @param committedChangesFromServer An array of sequential changes from the server.
6
10
  * @returns The new committed state, the new committed revision, and the new/rebased pending changes.
7
11
  */
8
- export declare function applyCommittedChanges(snapshot: PatchesSnapshot, committedChangesFromServer: Change[]): PatchesSnapshot;
12
+ declare function applyCommittedChanges(snapshot: PatchesSnapshot, committedChangesFromServer: Change[]): PatchesSnapshot;
13
+
14
+ export { applyCommittedChanges };
@@ -1,40 +1,32 @@
1
- import { applyChanges } from '../shared/applyChanges.js';
2
- import { rebaseChanges } from '../shared/rebaseChanges.js';
3
- /**
4
- * Applies incoming changes from the server that were *not* initiated by this client.
5
- * @param snapshot The current state of the document (the state without pending changes applied) and the pending changes.
6
- * @param committedChangesFromServer An array of sequential changes from the server.
7
- * @returns The new committed state, the new committed revision, and the new/rebased pending changes.
8
- */
9
- export function applyCommittedChanges(snapshot, committedChangesFromServer) {
10
- let { state, rev, changes } = snapshot;
11
- // Filter out any server changes that are already reflected in the current snapshot's revision.
12
- // Server changes should always have a rev.
13
- const newServerChanges = committedChangesFromServer.filter(change => change.rev > rev);
14
- if (newServerChanges.length === 0) {
15
- // No new changes to apply, return the snapshot as is.
16
- return { state, rev, changes };
17
- }
18
- const firstChange = newServerChanges[0];
19
- const lastChange = newServerChanges[newServerChanges.length - 1];
20
- // Ensure the new server changes are sequential to the current snapshot's revision.
21
- if (firstChange.rev !== rev + 1) {
22
- throw new Error(`Missing changes from the server. Expected rev ${rev + 1}, got ${firstChange.rev}. Request changes since ${rev}.`);
23
- }
24
- // 1. Apply to committed state
25
- try {
26
- state = applyChanges(state, newServerChanges);
27
- }
28
- catch (error) {
29
- console.error('Failed to apply server changes to committed state:', error);
30
- const errorMessage = error instanceof Error ? error.message : String(error);
31
- throw new Error(`Critical sync error applying server changes: ${errorMessage}`);
32
- }
33
- // 2. Update committed revision to the latest one from the applied server changes.
34
- rev = lastChange.rev;
35
- // 3. Rebase pending local changes against the newly applied server changes.
36
- if (changes && changes.length > 0) {
37
- changes = rebaseChanges(newServerChanges, changes);
38
- }
1
+ import "../../chunk-IZ2YBCUP.js";
2
+ import { applyChanges } from "../shared/applyChanges.js";
3
+ import { rebaseChanges } from "../shared/rebaseChanges.js";
4
+ function applyCommittedChanges(snapshot, committedChangesFromServer) {
5
+ let { state, rev, changes } = snapshot;
6
+ const newServerChanges = committedChangesFromServer.filter((change) => change.rev > rev);
7
+ if (newServerChanges.length === 0) {
39
8
  return { state, rev, changes };
9
+ }
10
+ const firstChange = newServerChanges[0];
11
+ const lastChange = newServerChanges[newServerChanges.length - 1];
12
+ if (firstChange.rev !== rev + 1) {
13
+ throw new Error(
14
+ `Missing changes from the server. Expected rev ${rev + 1}, got ${firstChange.rev}. Request changes since ${rev}.`
15
+ );
16
+ }
17
+ try {
18
+ state = applyChanges(state, newServerChanges);
19
+ } catch (error) {
20
+ console.error("Failed to apply server changes to committed state:", error);
21
+ const errorMessage = error instanceof Error ? error.message : String(error);
22
+ throw new Error(`Critical sync error applying server changes: ${errorMessage}`);
23
+ }
24
+ rev = lastChange.rev;
25
+ if (changes && changes.length > 0) {
26
+ changes = rebaseChanges(newServerChanges, changes);
27
+ }
28
+ return { state, rev, changes };
40
29
  }
30
+ export {
31
+ applyCommittedChanges
32
+ };
@@ -1,3 +1,9 @@
1
- import type { Change } from '../../types.js';
1
+ import { Change } from '../../types.js';
2
+ import '../../json-patch/JSONPatch.js';
3
+ import '@dabble/delta';
4
+ import '../../json-patch/types.js';
5
+
2
6
  /** Break changes into batches based on maxPayloadBytes. */
3
- export declare function breakIntoBatches(changes: Change[], maxPayloadBytes?: number): Change[][];
7
+ declare function breakIntoBatches(changes: Change[], maxPayloadBytes?: number): Change[][];
8
+
9
+ export { breakIntoBatches };
@@ -1,41 +1,42 @@
1
- import { createId } from 'crypto-id';
2
- import { breakChange } from './breakChange.js';
3
- import { getJSONByteSize } from './getJSONByteSize.js';
4
- /** Break changes into batches based on maxPayloadBytes. */
5
- export function breakIntoBatches(changes, maxPayloadBytes) {
6
- if (!maxPayloadBytes || getJSONByteSize(changes) < maxPayloadBytes) {
7
- return [changes];
1
+ import "../../chunk-IZ2YBCUP.js";
2
+ import { createId } from "crypto-id";
3
+ import { breakChange } from "./breakChange.js";
4
+ import { getJSONByteSize } from "./getJSONByteSize.js";
5
+ function breakIntoBatches(changes, maxPayloadBytes) {
6
+ if (!maxPayloadBytes || getJSONByteSize(changes) < maxPayloadBytes) {
7
+ return [changes];
8
+ }
9
+ const batchId = createId(12);
10
+ const batches = [];
11
+ let currentBatch = [];
12
+ let currentSize = 2;
13
+ for (const change of changes) {
14
+ const changeWithBatchId = { ...change, batchId };
15
+ const individualActualSize = getJSONByteSize(changeWithBatchId);
16
+ let itemsToProcess;
17
+ if (individualActualSize > maxPayloadBytes) {
18
+ itemsToProcess = breakChange(changeWithBatchId, maxPayloadBytes);
19
+ } else {
20
+ itemsToProcess = [changeWithBatchId];
8
21
  }
9
- const batchId = createId(12);
10
- const batches = [];
11
- let currentBatch = [];
12
- let currentSize = 2; // Account for [] wrapper
13
- for (const change of changes) {
14
- // Add batchId if breaking up
15
- const changeWithBatchId = { ...change, batchId };
16
- const individualActualSize = getJSONByteSize(changeWithBatchId);
17
- let itemsToProcess;
18
- if (individualActualSize > maxPayloadBytes) {
19
- itemsToProcess = breakChange(changeWithBatchId, maxPayloadBytes);
20
- }
21
- else {
22
- itemsToProcess = [changeWithBatchId];
23
- }
24
- for (const item of itemsToProcess) {
25
- const itemActualSize = getJSONByteSize(item);
26
- const itemSizeForBatching = itemActualSize + (currentBatch.length > 0 ? 1 : 0);
27
- if (currentBatch.length > 0 && currentSize + itemSizeForBatching > maxPayloadBytes) {
28
- batches.push(currentBatch);
29
- currentBatch = [];
30
- currentSize = 2;
31
- }
32
- const actualItemContribution = itemActualSize + (currentBatch.length > 0 ? 1 : 0);
33
- currentBatch.push(item);
34
- currentSize += actualItemContribution;
35
- }
36
- }
37
- if (currentBatch.length > 0) {
22
+ for (const item of itemsToProcess) {
23
+ const itemActualSize = getJSONByteSize(item);
24
+ const itemSizeForBatching = itemActualSize + (currentBatch.length > 0 ? 1 : 0);
25
+ if (currentBatch.length > 0 && currentSize + itemSizeForBatching > maxPayloadBytes) {
38
26
  batches.push(currentBatch);
27
+ currentBatch = [];
28
+ currentSize = 2;
29
+ }
30
+ const actualItemContribution = itemActualSize + (currentBatch.length > 0 ? 1 : 0);
31
+ currentBatch.push(item);
32
+ currentSize += actualItemContribution;
39
33
  }
40
- return batches;
34
+ }
35
+ if (currentBatch.length > 0) {
36
+ batches.push(currentBatch);
37
+ }
38
+ return batches;
41
39
  }
40
+ export {
41
+ breakIntoBatches
42
+ };
@@ -1,4 +1,8 @@
1
- import type { Change } from '../../types.js';
1
+ import { Change } from '../../types.js';
2
+ import '../../json-patch/JSONPatch.js';
3
+ import '@dabble/delta';
4
+ import '../../json-patch/types.js';
5
+
2
6
  /**
3
7
  * Break a single Change into multiple Changes so that the JSON string size never exceeds `maxBytes`.
4
8
  *
@@ -6,4 +10,6 @@ import type { Change } from '../../types.js';
6
10
  * - If an individual op is still too big and is a "@txt" op,
7
11
  * split its Delta payload into smaller Deltas
8
12
  */
9
- export declare function breakChange(orig: Change, maxBytes: number): Change[];
13
+ declare function breakChange(orig: Change, maxBytes: number): Change[];
14
+
15
+ export { breakChange };
@@ -1,258 +1,209 @@
1
- import { createChange } from '../../data/change.js';
2
- import { getJSONByteSize } from './getJSONByteSize.js';
3
- /**
4
- * Break a single Change into multiple Changes so that the JSON string size never exceeds `maxBytes`.
5
- *
6
- * - Splits first by JSON-Patch *ops*
7
- * - If an individual op is still too big and is a "@txt" op,
8
- * split its Delta payload into smaller Deltas
9
- */
10
- export function breakChange(orig, maxBytes) {
11
- if (getJSONByteSize(orig) <= maxBytes)
12
- return [orig];
13
- // First pass: split by ops
14
- const byOps = [];
15
- let group = [];
16
- let rev = orig.rev;
17
- const flush = () => {
18
- if (!group.length)
19
- return;
20
- byOps.push(deriveNewChange(orig, rev++, group));
21
- group = [];
22
- };
23
- for (const op of orig.ops) {
24
- const tentative = group.concat(op);
25
- if (getJSONByteSize({ ...orig, ops: tentative }) > maxBytes)
26
- flush();
27
- // Handle the case where a single op is too large
28
- if (group.length === 0 && getJSONByteSize({ ...orig, ops: [op] }) > maxBytes) {
29
- // We have a single op that's too big - can only be @txt op with large delta
30
- if (op.op === '@txt' && op.value) {
31
- const pieces = breakTextOp(orig, op, maxBytes, rev);
32
- byOps.push(...pieces);
33
- // Only update rev if we got results from breakTextOp
34
- if (pieces.length > 0) {
35
- rev = pieces[pieces.length - 1].rev + 1; // Update rev for next changes
36
- }
37
- continue;
38
- }
39
- else if (op.op === 'replace' || op.op === 'add') {
40
- // For replace/add operations with large value payloads, try to split the value if it's a string or array
41
- const pieces = breakLargeValueOp(orig, op, maxBytes, rev);
42
- byOps.push(...pieces);
43
- if (pieces.length > 0) {
44
- rev = pieces[pieces.length - 1].rev + 1;
45
- }
46
- continue;
47
- }
48
- else {
49
- // Non-splittable op that's too large, include it anyway with a warning
50
- console.warn(`Warning: Single operation of type ${op.op} exceeds maxBytes. Including it anyway.`);
51
- group.push(op);
52
- continue;
53
- }
1
+ import "../../chunk-IZ2YBCUP.js";
2
+ import { createChange } from "../../data/change.js";
3
+ import { getJSONByteSize } from "./getJSONByteSize.js";
4
+ function breakChange(orig, maxBytes) {
5
+ if (getJSONByteSize(orig) <= maxBytes) return [orig];
6
+ const byOps = [];
7
+ let group = [];
8
+ let rev = orig.rev;
9
+ const flush = () => {
10
+ if (!group.length) return;
11
+ byOps.push(deriveNewChange(orig, rev++, group));
12
+ group = [];
13
+ };
14
+ for (const op of orig.ops) {
15
+ const tentative = group.concat(op);
16
+ if (getJSONByteSize({ ...orig, ops: tentative }) > maxBytes) flush();
17
+ if (group.length === 0 && getJSONByteSize({ ...orig, ops: [op] }) > maxBytes) {
18
+ if (op.op === "@txt" && op.value) {
19
+ const pieces = breakTextOp(orig, op, maxBytes, rev);
20
+ byOps.push(...pieces);
21
+ if (pieces.length > 0) {
22
+ rev = pieces[pieces.length - 1].rev + 1;
54
23
  }
24
+ continue;
25
+ } else if (op.op === "replace" || op.op === "add") {
26
+ const pieces = breakLargeValueOp(orig, op, maxBytes, rev);
27
+ byOps.push(...pieces);
28
+ if (pieces.length > 0) {
29
+ rev = pieces[pieces.length - 1].rev + 1;
30
+ }
31
+ continue;
32
+ } else {
33
+ console.warn(`Warning: Single operation of type ${op.op} exceeds maxBytes. Including it anyway.`);
55
34
  group.push(op);
35
+ continue;
36
+ }
56
37
  }
57
- flush();
58
- return byOps;
38
+ group.push(op);
39
+ }
40
+ flush();
41
+ return byOps;
59
42
  }
60
- /**
61
- * Break a large @txt operation into multiple smaller operations
62
- */
63
43
  function breakTextOp(origChange, textOp, maxBytes, startRev) {
64
- const results = [];
65
- let rev = startRev;
66
- const baseSize = getJSONByteSize({ ...origChange, ops: [{ ...textOp, value: '' }] });
67
- const budget = maxBytes - baseSize;
68
- const buffer = 20;
69
- const maxLength = Math.max(1, budget - buffer);
70
- let deltaOps = [];
71
- if (textOp.value) {
72
- if (Array.isArray(textOp.value)) {
73
- deltaOps = textOp.value;
74
- }
75
- else if (textOp.value.ops && Array.isArray(textOp.value.ops)) {
76
- deltaOps = textOp.value.ops;
77
- }
78
- else if (typeof textOp.value === 'object') {
79
- deltaOps = [textOp.value];
80
- }
44
+ const results = [];
45
+ let rev = startRev;
46
+ const baseSize = getJSONByteSize({ ...origChange, ops: [{ ...textOp, value: "" }] });
47
+ const budget = maxBytes - baseSize;
48
+ const buffer = 20;
49
+ const maxLength = Math.max(1, budget - buffer);
50
+ let deltaOps = [];
51
+ if (textOp.value) {
52
+ if (Array.isArray(textOp.value)) {
53
+ deltaOps = textOp.value;
54
+ } else if (textOp.value.ops && Array.isArray(textOp.value.ops)) {
55
+ deltaOps = textOp.value.ops;
56
+ } else if (typeof textOp.value === "object") {
57
+ deltaOps = [textOp.value];
81
58
  }
82
- let currentOpsForNextChangePiece = [];
83
- let retainToPrefixCurrentPiece = 0; // Retain that should prefix the ops in currentOpsForNextChangePiece
84
- const flushCurrentChangePiece = () => {
85
- if (!currentOpsForNextChangePiece.length)
86
- return;
87
- const opsToFlush = [...currentOpsForNextChangePiece];
88
- if (retainToPrefixCurrentPiece > 0) {
89
- if (!opsToFlush[0]?.retain) {
90
- // Only add if not already starting with a retain
91
- opsToFlush.unshift({ retain: retainToPrefixCurrentPiece });
92
- }
93
- else {
94
- // If it starts with retain, assume it's the intended one from deltaOps.
95
- // This might need adjustment if a small retain op is batched after a large retain prefix.
96
- // For now, this prioritizes an existing retain op at the start of the batch.
97
- }
98
- }
99
- results.push(deriveNewChange(origChange, rev++, [{ ...textOp, value: opsToFlush }]));
100
- currentOpsForNextChangePiece = [];
101
- // retainToPrefixCurrentPiece is NOT reset here, it carries over for the start of the next piece IF it's non-zero from a previous retain op.
102
- };
103
- for (const op of deltaOps) {
104
- // Try adding current op (with its necessary prefix) to the current batch
105
- const testBatchOps = [...currentOpsForNextChangePiece];
106
- if (retainToPrefixCurrentPiece > 0 && testBatchOps.length === 0) {
107
- // If batch is empty, it needs the prefix
108
- testBatchOps.push({ retain: retainToPrefixCurrentPiece });
109
- }
110
- testBatchOps.push(op);
111
- const testBatchSize = getJSONByteSize({ ...origChange, ops: [{ ...textOp, value: testBatchOps }] });
112
- if (currentOpsForNextChangePiece.length > 0 && testBatchSize > maxBytes) {
113
- flushCurrentChangePiece();
114
- // After flush, retainToPrefixCurrentPiece still holds the value for the *start* of the new piece (current op)
115
- }
116
- // Check if the op itself (with its prefix) is too large for a new piece
117
- const opStandaloneOps = retainToPrefixCurrentPiece > 0 ? [{ retain: retainToPrefixCurrentPiece }, op] : [op];
118
- const opStandaloneSize = getJSONByteSize({ ...origChange, ops: [{ ...textOp, value: opStandaloneOps }] });
119
- if (currentOpsForNextChangePiece.length === 0 && opStandaloneSize > maxBytes) {
120
- if (op.insert && typeof op.insert === 'string') {
121
- const insertChunks = splitLargeInsertText(op.insert, maxLength, op.attributes);
122
- for (let i = 0; i < insertChunks.length; i++) {
123
- const chunkOp = insertChunks[i];
124
- const opsForThisChunk = [];
125
- if (i === 0 && retainToPrefixCurrentPiece > 0) {
126
- // Prefix only the first chunk
127
- opsForThisChunk.push({ retain: retainToPrefixCurrentPiece });
128
- }
129
- opsForThisChunk.push(chunkOp);
130
- results.push(deriveNewChange(origChange, rev++, [{ ...textOp, value: opsForThisChunk }]));
131
- }
132
- retainToPrefixCurrentPiece = 0; // An insert consumes the preceding retain for the next original op
133
- }
134
- else {
135
- // Non-splittable large op (e.g., large retain)
136
- console.warn(`Warning: Single delta op too large, including with prefix: ${JSON.stringify(op)}`);
137
- results.push(deriveNewChange(origChange, rev++, [{ ...textOp, value: opStandaloneOps }]));
138
- retainToPrefixCurrentPiece = op.retain || 0;
139
- }
140
- }
141
- else {
142
- // Op fits into current batch (or starts a new one that fits)
143
- currentOpsForNextChangePiece.push(op);
144
- if (op.retain) {
145
- retainToPrefixCurrentPiece += op.retain; // Accumulate retain for the next op or flush
146
- }
147
- else {
148
- // Insert or delete
149
- retainToPrefixCurrentPiece = 0; // Consumes retain for the next op
150
- }
151
- }
59
+ }
60
+ let currentOpsForNextChangePiece = [];
61
+ let retainToPrefixCurrentPiece = 0;
62
+ const flushCurrentChangePiece = () => {
63
+ if (!currentOpsForNextChangePiece.length) return;
64
+ const opsToFlush = [...currentOpsForNextChangePiece];
65
+ if (retainToPrefixCurrentPiece > 0) {
66
+ if (!opsToFlush[0]?.retain) {
67
+ opsToFlush.unshift({ retain: retainToPrefixCurrentPiece });
68
+ } else {
69
+ }
152
70
  }
153
- if (currentOpsForNextChangePiece.length > 0) {
154
- flushCurrentChangePiece();
71
+ results.push(deriveNewChange(origChange, rev++, [{ ...textOp, value: opsToFlush }]));
72
+ currentOpsForNextChangePiece = [];
73
+ };
74
+ for (const op of deltaOps) {
75
+ const testBatchOps = [...currentOpsForNextChangePiece];
76
+ if (retainToPrefixCurrentPiece > 0 && testBatchOps.length === 0) {
77
+ testBatchOps.push({ retain: retainToPrefixCurrentPiece });
155
78
  }
156
- return results;
157
- }
158
- /**
159
- * Split a large insert string into multiple delta insert operations.
160
- * Each operation will have the original attributes.
161
- */
162
- function splitLargeInsertText(text, maxChunkLength, attributes) {
163
- const results = [];
164
- if (maxChunkLength <= 0) {
165
- console.warn('splitLargeInsertText: maxChunkLength is invalid, returning original text as one chunk.');
166
- return [{ insert: text, attributes }];
79
+ testBatchOps.push(op);
80
+ const testBatchSize = getJSONByteSize({ ...origChange, ops: [{ ...textOp, value: testBatchOps }] });
81
+ if (currentOpsForNextChangePiece.length > 0 && testBatchSize > maxBytes) {
82
+ flushCurrentChangePiece();
167
83
  }
168
- for (let i = 0; i < text.length; i += maxChunkLength) {
169
- const chunkText = text.slice(i, i + maxChunkLength);
170
- results.push({ insert: chunkText, attributes: attributes ? { ...attributes } : undefined });
84
+ const opStandaloneOps = retainToPrefixCurrentPiece > 0 ? [{ retain: retainToPrefixCurrentPiece }, op] : [op];
85
+ const opStandaloneSize = getJSONByteSize({ ...origChange, ops: [{ ...textOp, value: opStandaloneOps }] });
86
+ if (currentOpsForNextChangePiece.length === 0 && opStandaloneSize > maxBytes) {
87
+ if (op.insert && typeof op.insert === "string") {
88
+ const insertChunks = splitLargeInsertText(op.insert, maxLength, op.attributes);
89
+ for (let i = 0; i < insertChunks.length; i++) {
90
+ const chunkOp = insertChunks[i];
91
+ const opsForThisChunk = [];
92
+ if (i === 0 && retainToPrefixCurrentPiece > 0) {
93
+ opsForThisChunk.push({ retain: retainToPrefixCurrentPiece });
94
+ }
95
+ opsForThisChunk.push(chunkOp);
96
+ results.push(deriveNewChange(origChange, rev++, [{ ...textOp, value: opsForThisChunk }]));
97
+ }
98
+ retainToPrefixCurrentPiece = 0;
99
+ } else {
100
+ console.warn(`Warning: Single delta op too large, including with prefix: ${JSON.stringify(op)}`);
101
+ results.push(deriveNewChange(origChange, rev++, [{ ...textOp, value: opStandaloneOps }]));
102
+ retainToPrefixCurrentPiece = op.retain || 0;
103
+ }
104
+ } else {
105
+ currentOpsForNextChangePiece.push(op);
106
+ if (op.retain) {
107
+ retainToPrefixCurrentPiece += op.retain;
108
+ } else {
109
+ retainToPrefixCurrentPiece = 0;
110
+ }
171
111
  }
172
- return results;
112
+ }
113
+ if (currentOpsForNextChangePiece.length > 0) {
114
+ flushCurrentChangePiece();
115
+ }
116
+ return results;
117
+ }
118
+ function splitLargeInsertText(text, maxChunkLength, attributes) {
119
+ const results = [];
120
+ if (maxChunkLength <= 0) {
121
+ console.warn("splitLargeInsertText: maxChunkLength is invalid, returning original text as one chunk.");
122
+ return [{ insert: text, attributes }];
123
+ }
124
+ for (let i = 0; i < text.length; i += maxChunkLength) {
125
+ const chunkText = text.slice(i, i + maxChunkLength);
126
+ results.push({ insert: chunkText, attributes: attributes ? { ...attributes } : void 0 });
127
+ }
128
+ return results;
173
129
  }
174
- /**
175
- * Attempt to break a large value in a replace/add operation
176
- */
177
130
  function breakLargeValueOp(origChange, op, maxBytes, startRev) {
178
- const results = [];
179
- let rev = startRev;
180
- const baseOpSize = getJSONByteSize({ ...op, value: '' });
181
- const baseChangeSize = getJSONByteSize({ ...origChange, ops: [{ ...op, value: '' }] }) - baseOpSize;
182
- const valueBudget = maxBytes - baseChangeSize - 50;
183
- if (typeof op.value === 'string' && op.value.length > 100) {
184
- const text = op.value;
185
- const targetChunkSize = Math.max(1, valueBudget);
186
- const numChunks = Math.ceil(text.length / targetChunkSize);
187
- const chunkSize = Math.ceil(text.length / numChunks);
188
- for (let i = 0; i < text.length; i += chunkSize) {
189
- const chunk = text.slice(i, i + chunkSize);
190
- const newOp = { op: 'add' };
191
- if (i === 0) {
192
- newOp.op = op.op;
193
- newOp.path = op.path;
194
- newOp.value = chunk;
195
- }
196
- else {
197
- newOp.op = 'patch';
198
- newOp.path = op.path;
199
- newOp.appendString = chunk;
200
- }
201
- results.push(deriveNewChange(origChange, rev++, [newOp]));
202
- }
203
- return results;
131
+ const results = [];
132
+ let rev = startRev;
133
+ const baseOpSize = getJSONByteSize({ ...op, value: "" });
134
+ const baseChangeSize = getJSONByteSize({ ...origChange, ops: [{ ...op, value: "" }] }) - baseOpSize;
135
+ const valueBudget = maxBytes - baseChangeSize - 50;
136
+ if (typeof op.value === "string" && op.value.length > 100) {
137
+ const text = op.value;
138
+ const targetChunkSize = Math.max(1, valueBudget);
139
+ const numChunks = Math.ceil(text.length / targetChunkSize);
140
+ const chunkSize = Math.ceil(text.length / numChunks);
141
+ for (let i = 0; i < text.length; i += chunkSize) {
142
+ const chunk = text.slice(i, i + chunkSize);
143
+ const newOp = { op: "add" };
144
+ if (i === 0) {
145
+ newOp.op = op.op;
146
+ newOp.path = op.path;
147
+ newOp.value = chunk;
148
+ } else {
149
+ newOp.op = "patch";
150
+ newOp.path = op.path;
151
+ newOp.appendString = chunk;
152
+ }
153
+ results.push(deriveNewChange(origChange, rev++, [newOp]));
204
154
  }
205
- else if (Array.isArray(op.value) && op.value.length > 1) {
206
- const originalArray = op.value;
207
- let currentChunk = [];
208
- let chunkStartIndex = 0;
209
- for (let i = 0; i < originalArray.length; i++) {
210
- const item = originalArray[i];
211
- const tentativeChunk = [...currentChunk, item];
212
- const tentativeOp = { ...op, value: tentativeChunk };
213
- const tentativeChangeSize = getJSONByteSize({ ...origChange, ops: [tentativeOp] });
214
- if (currentChunk.length > 0 && tentativeChangeSize > maxBytes) {
215
- const chunkOp = {};
216
- if (chunkStartIndex === 0) {
217
- chunkOp.op = op.op;
218
- chunkOp.path = op.path;
219
- chunkOp.value = currentChunk;
220
- }
221
- else {
222
- chunkOp.op = 'patch';
223
- chunkOp.path = op.path;
224
- chunkOp.appendArray = currentChunk;
225
- }
226
- results.push(deriveNewChange(origChange, rev++, [chunkOp]));
227
- currentChunk = [item];
228
- chunkStartIndex = i;
229
- }
230
- else {
231
- currentChunk.push(item);
232
- }
233
- }
234
- if (currentChunk.length > 0) {
235
- const chunkOp = {};
236
- if (chunkStartIndex === 0) {
237
- chunkOp.op = op.op;
238
- chunkOp.path = op.path;
239
- chunkOp.value = currentChunk;
240
- }
241
- else {
242
- chunkOp.op = 'patch';
243
- chunkOp.path = op.path;
244
- chunkOp.appendArray = currentChunk;
245
- }
246
- results.push(deriveNewChange(origChange, rev++, [chunkOp]));
155
+ return results;
156
+ } else if (Array.isArray(op.value) && op.value.length > 1) {
157
+ const originalArray = op.value;
158
+ let currentChunk = [];
159
+ let chunkStartIndex = 0;
160
+ for (let i = 0; i < originalArray.length; i++) {
161
+ const item = originalArray[i];
162
+ const tentativeChunk = [...currentChunk, item];
163
+ const tentativeOp = { ...op, value: tentativeChunk };
164
+ const tentativeChangeSize = getJSONByteSize({ ...origChange, ops: [tentativeOp] });
165
+ if (currentChunk.length > 0 && tentativeChangeSize > maxBytes) {
166
+ const chunkOp = {};
167
+ if (chunkStartIndex === 0) {
168
+ chunkOp.op = op.op;
169
+ chunkOp.path = op.path;
170
+ chunkOp.value = currentChunk;
171
+ } else {
172
+ chunkOp.op = "patch";
173
+ chunkOp.path = op.path;
174
+ chunkOp.appendArray = currentChunk;
247
175
  }
248
- return results;
176
+ results.push(deriveNewChange(origChange, rev++, [chunkOp]));
177
+ currentChunk = [item];
178
+ chunkStartIndex = i;
179
+ } else {
180
+ currentChunk.push(item);
181
+ }
249
182
  }
250
- console.warn(`Warning: Single operation of type ${op.op} (path: ${op.path}) could not be split further by breakLargeValueOp despite exceeding maxBytes. Including as is.`);
251
- return [deriveNewChange(origChange, rev++, [op])]; // Return original op in a new change if not splittable by this func
183
+ if (currentChunk.length > 0) {
184
+ const chunkOp = {};
185
+ if (chunkStartIndex === 0) {
186
+ chunkOp.op = op.op;
187
+ chunkOp.path = op.path;
188
+ chunkOp.value = currentChunk;
189
+ } else {
190
+ chunkOp.op = "patch";
191
+ chunkOp.path = op.path;
192
+ chunkOp.appendArray = currentChunk;
193
+ }
194
+ results.push(deriveNewChange(origChange, rev++, [chunkOp]));
195
+ }
196
+ return results;
197
+ }
198
+ console.warn(
199
+ `Warning: Single operation of type ${op.op} (path: ${op.path}) could not be split further by breakLargeValueOp despite exceeding maxBytes. Including as is.`
200
+ );
201
+ return [deriveNewChange(origChange, rev++, [op])];
252
202
  }
253
203
  function deriveNewChange(origChange, rev, ops) {
254
- // Filter out metadata that shouldn't be part of the new change object
255
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
256
- const { id: _id, ops: _o, rev: _r, baseRev: _br, created: _c, batchId: _bi, ...metadata } = origChange;
257
- return createChange(origChange.baseRev, rev, ops, metadata);
204
+ const { id: _id, ops: _o, rev: _r, baseRev: _br, created: _c, batchId: _bi, ...metadata } = origChange;
205
+ return createChange(origChange.baseRev, rev, ops, metadata);
258
206
  }
207
+ export {
208
+ breakChange
209
+ };