@dabble/patches 0.4.4 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/algorithms/client/applyCommittedChanges.d.ts +8 -2
- package/dist/algorithms/client/applyCommittedChanges.js +30 -38
- package/dist/algorithms/client/batching.d.ts +8 -2
- package/dist/algorithms/client/batching.js +38 -37
- package/dist/algorithms/client/breakChange.d.ts +8 -2
- package/dist/algorithms/client/breakChange.js +191 -240
- package/dist/algorithms/client/createStateFromSnapshot.d.ts +8 -2
- package/dist/algorithms/client/createStateFromSnapshot.js +7 -8
- package/dist/algorithms/client/getJSONByteSize.d.ts +3 -1
- package/dist/algorithms/client/getJSONByteSize.js +12 -11
- package/dist/algorithms/client/makeChange.d.ts +8 -2
- package/dist/algorithms/client/makeChange.js +28 -36
- package/dist/algorithms/server/commitChanges.d.ts +9 -3
- package/dist/algorithms/server/commitChanges.js +69 -78
- package/dist/algorithms/server/createVersion.d.ts +9 -3
- package/dist/algorithms/server/createVersion.js +21 -27
- package/dist/algorithms/server/getSnapshotAtRevision.d.ts +9 -3
- package/dist/algorithms/server/getSnapshotAtRevision.js +27 -28
- package/dist/algorithms/server/getStateAtRevision.d.ts +9 -3
- package/dist/algorithms/server/getStateAtRevision.js +13 -17
- package/dist/algorithms/server/handleOfflineSessionsAndBatches.d.ts +9 -3
- package/dist/algorithms/server/handleOfflineSessionsAndBatches.js +60 -77
- package/dist/algorithms/server/transformIncomingChanges.d.ts +8 -2
- package/dist/algorithms/server/transformIncomingChanges.js +27 -39
- package/dist/algorithms/shared/applyChanges.d.ts +8 -2
- package/dist/algorithms/shared/applyChanges.js +11 -16
- package/dist/algorithms/shared/rebaseChanges.d.ts +8 -2
- package/dist/algorithms/shared/rebaseChanges.js +30 -49
- package/dist/chunk-IZ2YBCUP.js +56 -0
- package/dist/client/InMemoryStore.d.ts +9 -3
- package/dist/client/InMemoryStore.js +92 -101
- package/dist/client/IndexedDBStore.d.ts +9 -3
- package/dist/client/IndexedDBStore.js +378 -491
- package/dist/client/Patches.d.ts +18 -13
- package/dist/client/Patches.js +152 -207
- package/dist/client/PatchesDoc.d.ts +14 -8
- package/dist/client/PatchesDoc.js +147 -154
- package/dist/client/PatchesHistoryClient.d.ts +12 -5
- package/dist/client/PatchesHistoryClient.js +110 -117
- package/dist/client/PatchesStore.d.ts +9 -3
- package/dist/client/PatchesStore.js +0 -1
- package/dist/client/index.d.ts +12 -6
- package/dist/client/index.js +5 -5
- package/dist/data/change.d.ts +9 -3
- package/dist/data/change.js +23 -15
- package/dist/data/version.d.ts +9 -3
- package/dist/data/version.js +11 -15
- package/dist/event-signal.d.ts +7 -6
- package/dist/event-signal.js +24 -39
- package/dist/index-CvQws3AB.d.ts +36 -0
- package/dist/index.d.ts +27 -5
- package/dist/index.js +10 -4
- package/dist/json-patch/JSONPatch.d.ts +9 -5
- package/dist/json-patch/JSONPatch.js +175 -183
- package/dist/json-patch/applyPatch.d.ts +5 -2
- package/dist/json-patch/applyPatch.js +27 -35
- package/dist/json-patch/composePatch.d.ts +5 -2
- package/dist/json-patch/composePatch.js +34 -34
- package/dist/json-patch/createJSONPatch.d.ts +7 -2
- package/dist/json-patch/createJSONPatch.js +11 -38
- package/dist/json-patch/index.d.ts +14 -6
- package/dist/json-patch/index.js +20 -9
- package/dist/json-patch/invertPatch.d.ts +5 -2
- package/dist/json-patch/invertPatch.js +31 -30
- package/dist/json-patch/ops/add.d.ts +5 -2
- package/dist/json-patch/ops/add.js +53 -51
- package/dist/json-patch/ops/bitmask.d.ts +8 -5
- package/dist/json-patch/ops/bitmask.js +41 -44
- package/dist/json-patch/ops/copy.d.ts +5 -2
- package/dist/json-patch/ops/copy.js +32 -33
- package/dist/json-patch/ops/increment.d.ts +5 -2
- package/dist/json-patch/ops/increment.js +21 -20
- package/dist/json-patch/ops/index.d.ts +10 -21
- package/dist/json-patch/ops/index.js +34 -24
- package/dist/json-patch/ops/move.d.ts +5 -2
- package/dist/json-patch/ops/move.js +132 -198
- package/dist/json-patch/ops/remove.d.ts +5 -2
- package/dist/json-patch/ops/remove.js +33 -30
- package/dist/json-patch/ops/replace.d.ts +5 -2
- package/dist/json-patch/ops/replace.js +45 -43
- package/dist/json-patch/ops/test.d.ts +5 -2
- package/dist/json-patch/ops/test.js +25 -21
- package/dist/json-patch/ops/text.d.ts +5 -2
- package/dist/json-patch/ops/text.js +54 -54
- package/dist/json-patch/pathProxy.d.ts +9 -3
- package/dist/json-patch/pathProxy.js +27 -48
- package/dist/json-patch/state.d.ts +5 -2
- package/dist/json-patch/state.js +11 -7
- package/dist/json-patch/transformPatch.d.ts +6 -2
- package/dist/json-patch/transformPatch.js +21 -24
- package/dist/json-patch/types.d.ts +9 -7
- package/dist/json-patch/types.js +0 -1
- package/dist/json-patch/utils/deepEqual.d.ts +3 -1
- package/dist/json-patch/utils/deepEqual.js +32 -28
- package/dist/json-patch/utils/exit.d.ts +5 -2
- package/dist/json-patch/utils/exit.js +7 -3
- package/dist/json-patch/utils/get.d.ts +5 -2
- package/dist/json-patch/utils/get.js +8 -4
- package/dist/json-patch/utils/getOpData.d.ts +5 -2
- package/dist/json-patch/utils/getOpData.js +12 -9
- package/dist/json-patch/utils/getType.d.ts +6 -3
- package/dist/json-patch/utils/getType.js +9 -4
- package/dist/json-patch/utils/index.d.ts +15 -14
- package/dist/json-patch/utils/index.js +14 -14
- package/dist/json-patch/utils/log.d.ts +4 -2
- package/dist/json-patch/utils/log.js +8 -3
- package/dist/json-patch/utils/ops.d.ts +8 -5
- package/dist/json-patch/utils/ops.js +83 -100
- package/dist/json-patch/utils/paths.d.ts +12 -9
- package/dist/json-patch/utils/paths.js +54 -51
- package/dist/json-patch/utils/pluck.d.ts +8 -5
- package/dist/json-patch/utils/pluck.js +32 -26
- package/dist/json-patch/utils/shallowCopy.d.ts +3 -1
- package/dist/json-patch/utils/shallowCopy.js +22 -18
- package/dist/json-patch/utils/softWrites.d.ts +6 -3
- package/dist/json-patch/utils/softWrites.js +17 -16
- package/dist/json-patch/utils/toArrayIndex.d.ts +3 -1
- package/dist/json-patch/utils/toArrayIndex.js +14 -10
- package/dist/json-patch/utils/toKeys.d.ts +3 -1
- package/dist/json-patch/utils/toKeys.js +15 -11
- package/dist/json-patch/utils/updateArrayIndexes.d.ts +5 -2
- package/dist/json-patch/utils/updateArrayIndexes.js +33 -37
- package/dist/json-patch/utils/updateArrayPath.d.ts +5 -2
- package/dist/json-patch/utils/updateArrayPath.js +29 -42
- package/dist/net/PatchesClient.d.ts +128 -0
- package/dist/net/PatchesClient.js +161 -0
- package/dist/net/PatchesSync.d.ts +19 -9
- package/dist/net/PatchesSync.js +291 -386
- package/dist/net/error.d.ts +3 -1
- package/dist/net/error.js +9 -6
- package/dist/net/http/FetchTransport.d.ts +21 -0
- package/dist/net/http/FetchTransport.js +34 -0
- package/dist/net/index.d.ts +26 -12
- package/dist/net/index.js +12 -10
- package/dist/net/protocol/JSONRPCClient.d.ts +11 -4
- package/dist/net/protocol/JSONRPCClient.js +95 -103
- package/dist/net/protocol/JSONRPCServer.d.ts +15 -8
- package/dist/net/protocol/JSONRPCServer.js +101 -123
- package/dist/net/protocol/types.d.ts +21 -15
- package/dist/net/protocol/types.js +0 -1
- package/dist/net/protocol/utils.d.ts +12 -0
- package/dist/net/protocol/utils.js +15 -0
- package/dist/net/types.d.ts +4 -2
- package/dist/net/types.js +0 -1
- package/dist/net/webrtc/WebRTCAwareness.d.ts +14 -4
- package/dist/net/webrtc/WebRTCAwareness.js +111 -120
- package/dist/net/webrtc/WebRTCTransport.d.ts +16 -8
- package/dist/net/webrtc/WebRTCTransport.js +149 -157
- package/dist/net/webrtc/index.d.ts +10 -2
- package/dist/net/webrtc/index.js +2 -2
- package/dist/net/websocket/AuthorizationProvider.d.ts +7 -5
- package/dist/net/websocket/AuthorizationProvider.js +12 -17
- package/dist/net/websocket/PatchesWebSocket.d.ts +14 -109
- package/dist/net/websocket/PatchesWebSocket.js +37 -184
- package/dist/net/websocket/RPCServer.d.ts +19 -10
- package/dist/net/websocket/RPCServer.js +190 -192
- package/dist/net/websocket/SignalingService.d.ts +12 -32
- package/dist/net/websocket/SignalingService.js +126 -133
- package/dist/net/websocket/WebSocketServer.d.ts +17 -4
- package/dist/net/websocket/WebSocketServer.js +64 -72
- package/dist/net/websocket/WebSocketTransport.d.ts +13 -5
- package/dist/net/websocket/WebSocketTransport.js +178 -207
- package/dist/net/websocket/onlineState.d.ts +6 -3
- package/dist/net/websocket/onlineState.js +25 -21
- package/dist/server/PatchesBranchManager.d.ts +12 -5
- package/dist/server/PatchesBranchManager.js +132 -142
- package/dist/server/PatchesHistoryManager.d.ts +11 -3
- package/dist/server/PatchesHistoryManager.js +81 -84
- package/dist/server/PatchesServer.d.ts +16 -10
- package/dist/server/PatchesServer.js +131 -137
- package/dist/server/index.d.ts +7 -2
- package/dist/server/index.js +9 -3
- package/dist/server/types.d.ts +9 -3
- package/dist/server/types.js +0 -1
- package/dist/types.d.ts +49 -19
- package/dist/types.js +1 -1
- package/dist/utils/concurrency.d.ts +7 -5
- package/dist/utils/concurrency.js +43 -53
- package/dist/utils/deferred.d.ts +4 -2
- package/dist/utils/deferred.js +25 -21
- package/package.json +5 -7
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import
|
|
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
|
-
|
|
12
|
+
declare function applyCommittedChanges(snapshot: PatchesSnapshot, committedChangesFromServer: Change[]): PatchesSnapshot;
|
|
13
|
+
|
|
14
|
+
export { applyCommittedChanges };
|
|
@@ -1,40 +1,32 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
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
|
-
|
|
7
|
+
declare function breakIntoBatches(changes: Change[], maxPayloadBytes?: number): Change[][];
|
|
8
|
+
|
|
9
|
+
export { breakIntoBatches };
|
|
@@ -1,41 +1,42 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
13
|
+
declare function breakChange(orig: Change, maxBytes: number): Change[];
|
|
14
|
+
|
|
15
|
+
export { breakChange };
|
|
@@ -1,258 +1,209 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
byOps.push(
|
|
21
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
154
|
-
|
|
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
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
176
|
+
results.push(deriveNewChange(origChange, rev++, [chunkOp]));
|
|
177
|
+
currentChunk = [item];
|
|
178
|
+
chunkStartIndex = i;
|
|
179
|
+
} else {
|
|
180
|
+
currentChunk.push(item);
|
|
181
|
+
}
|
|
249
182
|
}
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
255
|
-
|
|
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
|
+
};
|