@dabble/patches 0.3.2 → 0.4.1
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/makeChange.d.ts +2 -3
- package/dist/algorithms/client/makeChange.js +1 -1
- package/dist/algorithms/server/getSnapshotAtRevision.d.ts +1 -1
- package/dist/algorithms/server/getStateAtRevision.d.ts +1 -1
- package/dist/algorithms/server/handleOfflineSessionsAndBatches.d.ts +1 -1
- package/dist/client/InMemoryStore.js +1 -3
- package/dist/client/IndexedDBStore.js +345 -342
- package/dist/client/Patches.js +156 -156
- package/dist/client/PatchesDoc.d.ts +4 -5
- package/dist/client/PatchesDoc.js +16 -13
- package/dist/client/PatchesHistoryClient.js +12 -8
- package/dist/json-patch/JSONPatch.js +2 -0
- package/dist/json-patch/createJSONPatch.d.ts +15 -18
- package/dist/json-patch/createJSONPatch.js +18 -20
- package/dist/json-patch/pathProxy.d.ts +22 -0
- package/dist/json-patch/pathProxy.js +50 -0
- package/dist/json-patch/utils/getType.d.ts +1 -1
- package/dist/net/PatchesSync.js +307 -303
- package/dist/net/error.js +1 -0
- package/dist/net/protocol/JSONRPCClient.js +4 -3
- package/dist/net/protocol/JSONRPCServer.js +6 -8
- package/dist/net/webrtc/WebRTCAwareness.js +12 -7
- package/dist/net/webrtc/WebRTCTransport.d.ts +1 -1
- package/dist/net/webrtc/WebRTCTransport.js +27 -21
- package/dist/net/websocket/PatchesWebSocket.js +7 -2
- package/dist/net/websocket/RPCServer.js +5 -0
- package/dist/net/websocket/SignalingService.js +1 -3
- package/dist/net/websocket/WebSocketServer.js +2 -0
- package/dist/net/websocket/WebSocketTransport.js +21 -19
- package/dist/net/websocket/onlineState.js +2 -2
- package/dist/server/PatchesBranchManager.js +2 -0
- package/dist/server/PatchesHistoryManager.js +2 -0
- package/dist/server/PatchesServer.d.ts +2 -2
- package/dist/server/PatchesServer.js +7 -5
- package/dist/types.d.ts +15 -6
- package/dist/types.js +1 -1
- package/package.json +3 -2
- package/dist/json-patch/patchProxy.d.ts +0 -42
- package/dist/json-patch/patchProxy.js +0 -126
package/dist/client/Patches.js
CHANGED
|
@@ -41,170 +41,170 @@ import { PatchesDoc } from './PatchesDoc.js';
|
|
|
41
41
|
* Can be used standalone or with PatchesSync for network synchronization.
|
|
42
42
|
*/
|
|
43
43
|
let Patches = (() => {
|
|
44
|
-
var _a;
|
|
45
44
|
let _instanceExtraInitializers = [];
|
|
46
45
|
let _openDoc_decorators;
|
|
47
|
-
return
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
doc
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
* @param docId - The document ID to close.
|
|
129
|
-
* @param options - Optional: set untrack to true to also untrack the doc.
|
|
130
|
-
*/
|
|
131
|
-
async closeDoc(docId, { untrack = false } = {}) {
|
|
132
|
-
const managed = this.docs.get(docId);
|
|
133
|
-
if (managed) {
|
|
134
|
-
managed.unsubscribe();
|
|
135
|
-
this.docs.delete(docId);
|
|
136
|
-
if (untrack) {
|
|
137
|
-
await this.untrackDocs([docId]);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
46
|
+
return class Patches {
|
|
47
|
+
static {
|
|
48
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
49
|
+
_openDoc_decorators = [singleInvocation(true)];
|
|
50
|
+
__esDecorate(this, null, _openDoc_decorators, { kind: "method", name: "openDoc", static: false, private: false, access: { has: obj => "openDoc" in obj, get: obj => obj.openDoc }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
51
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
52
|
+
}
|
|
53
|
+
options = __runInitializers(this, _instanceExtraInitializers);
|
|
54
|
+
docs = new Map();
|
|
55
|
+
docOptions;
|
|
56
|
+
store;
|
|
57
|
+
trackedDocs = new Set();
|
|
58
|
+
// Public signals
|
|
59
|
+
onError = signal();
|
|
60
|
+
onServerCommit = signal();
|
|
61
|
+
onTrackDocs = signal();
|
|
62
|
+
onUntrackDocs = signal();
|
|
63
|
+
onDeleteDoc = signal();
|
|
64
|
+
onChange = signal();
|
|
65
|
+
constructor(opts) {
|
|
66
|
+
this.options = opts;
|
|
67
|
+
this.store = opts.store;
|
|
68
|
+
this.docOptions = opts.docOptions ?? {};
|
|
69
|
+
this.store.listDocs().then(docs => {
|
|
70
|
+
this.trackDocs(docs.map(({ docId }) => docId));
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
// --- Public API Methods ---
|
|
74
|
+
/**
|
|
75
|
+
* Tracks the given document IDs, adding them to the set of tracked documents and notifying listeners.
|
|
76
|
+
* Tracked docs are kept in sync with the server, even when not open locally.
|
|
77
|
+
* This allows for background syncing and updates of unopened documents.
|
|
78
|
+
* @param docIds - Array of document IDs to track.
|
|
79
|
+
*/
|
|
80
|
+
async trackDocs(docIds) {
|
|
81
|
+
docIds = docIds.filter(id => !this.trackedDocs.has(id));
|
|
82
|
+
if (!docIds.length)
|
|
83
|
+
return;
|
|
84
|
+
docIds.forEach(this.trackedDocs.add, this.trackedDocs);
|
|
85
|
+
this.onTrackDocs.emit(docIds);
|
|
86
|
+
await this.store.trackDocs(docIds);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Untracks the given document IDs, removing them from the set of tracked documents and notifying listeners.
|
|
90
|
+
* Untracked docs will no longer be kept in sync with the server, even if not open locally.
|
|
91
|
+
* Closes any open docs and removes them from the store.
|
|
92
|
+
* @param docIds - Array of document IDs to untrack.
|
|
93
|
+
*/
|
|
94
|
+
async untrackDocs(docIds) {
|
|
95
|
+
docIds = docIds.filter(id => this.trackedDocs.has(id));
|
|
96
|
+
if (!docIds.length)
|
|
97
|
+
return;
|
|
98
|
+
docIds.forEach(this.trackedDocs.delete, this.trackedDocs);
|
|
99
|
+
this.onUntrackDocs.emit(docIds);
|
|
100
|
+
// Close any open PatchesDoc instances first
|
|
101
|
+
const closedPromises = docIds.filter(id => this.docs.has(id)).map(id => this.closeDoc(id)); // closeDoc removes from this.docs map
|
|
102
|
+
await Promise.all(closedPromises);
|
|
103
|
+
// Remove from store
|
|
104
|
+
await this.store.untrackDocs(docIds);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Opens a document by ID, loading its state from the store and setting up change listeners.
|
|
108
|
+
* If the doc is already open, returns the existing instance.
|
|
109
|
+
* @param docId - The document ID to open.
|
|
110
|
+
* @param opts - Optional metadata to merge with the doc's metadata.
|
|
111
|
+
* @returns The opened PatchesDoc instance.
|
|
112
|
+
*/
|
|
113
|
+
async openDoc(docId, opts = {}) {
|
|
114
|
+
const existing = this.docs.get(docId);
|
|
115
|
+
if (existing)
|
|
116
|
+
return existing.doc;
|
|
117
|
+
// Ensure the doc is tracked before proceeding
|
|
118
|
+
await this.trackDocs([docId]);
|
|
119
|
+
// Load initial state from store
|
|
120
|
+
const snapshot = await this.store.getDoc(docId);
|
|
121
|
+
const initialState = (snapshot?.state ?? {});
|
|
122
|
+
const mergedMetadata = { ...this.options.metadata, ...opts.metadata };
|
|
123
|
+
const doc = new PatchesDoc(initialState, mergedMetadata, this.docOptions);
|
|
124
|
+
doc.setId(docId);
|
|
125
|
+
if (snapshot) {
|
|
126
|
+
doc.import(snapshot);
|
|
140
127
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
128
|
+
// Set up local listener -> store
|
|
129
|
+
const unsubscribe = doc.onChange(changes => this._savePendingChanges(docId, changes));
|
|
130
|
+
this.docs.set(docId, { doc, unsubscribe });
|
|
131
|
+
return doc;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Closes an open document by ID, removing listeners and optionally untracking it.
|
|
135
|
+
* @param docId - The document ID to close.
|
|
136
|
+
* @param options - Optional: set untrack to true to also untrack the doc.
|
|
137
|
+
*/
|
|
138
|
+
async closeDoc(docId, { untrack = false } = {}) {
|
|
139
|
+
const managed = this.docs.get(docId);
|
|
140
|
+
if (managed) {
|
|
141
|
+
managed.unsubscribe();
|
|
142
|
+
this.docs.delete(docId);
|
|
143
|
+
if (untrack) {
|
|
153
144
|
await this.untrackDocs([docId]);
|
|
154
145
|
}
|
|
155
|
-
// Mark document as deleted in store (adds a tombstone until sync commits it)
|
|
156
|
-
await this.store.deleteDoc(docId);
|
|
157
|
-
this.onDeleteDoc.emit(docId);
|
|
158
146
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Deletes a document by ID, closing it if open, untracking it, and removing it from the store.
|
|
150
|
+
* Emits the onDeleteDoc signal.
|
|
151
|
+
* @param docId - The document ID to delete.
|
|
152
|
+
*/
|
|
153
|
+
async deleteDoc(docId) {
|
|
154
|
+
// Close if open locally
|
|
155
|
+
if (this.docs.has(docId)) {
|
|
156
|
+
await this.closeDoc(docId);
|
|
167
157
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
*/
|
|
172
|
-
close() {
|
|
173
|
-
// Clean up local PatchesDoc listeners
|
|
174
|
-
this.docs.forEach(managed => managed.unsubscribe());
|
|
175
|
-
this.docs.clear();
|
|
176
|
-
// Close store connection
|
|
177
|
-
this.store.close();
|
|
178
|
-
this.onChange.clear();
|
|
179
|
-
this.onDeleteDoc.clear();
|
|
180
|
-
this.onUntrackDocs.clear();
|
|
181
|
-
this.onTrackDocs.clear();
|
|
182
|
-
this.onServerCommit.clear();
|
|
183
|
-
this.onError.clear();
|
|
158
|
+
// Unsubscribe from server if tracked (deletes the doc from the store before the next step adds a tombstone)
|
|
159
|
+
if (this.trackedDocs.has(docId)) {
|
|
160
|
+
await this.untrackDocs([docId]);
|
|
184
161
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
162
|
+
// Mark document as deleted in store (adds a tombstone until sync commits it)
|
|
163
|
+
await this.store.deleteDoc(docId);
|
|
164
|
+
this.onDeleteDoc.emit(docId);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Gets an open document instance by ID, if it exists.
|
|
168
|
+
* Used by PatchesSync for applying server changes to open docs.
|
|
169
|
+
* @param docId - The document ID to get.
|
|
170
|
+
* @returns The PatchesDoc instance or undefined if not open.
|
|
171
|
+
*/
|
|
172
|
+
getOpenDoc(docId) {
|
|
173
|
+
return this.docs.get(docId)?.doc;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Closes all open documents and cleans up listeners and store connections.
|
|
177
|
+
* Should be called when shutting down the client.
|
|
178
|
+
*/
|
|
179
|
+
close() {
|
|
180
|
+
// Clean up local PatchesDoc listeners
|
|
181
|
+
this.docs.forEach(managed => managed.unsubscribe());
|
|
182
|
+
this.docs.clear();
|
|
183
|
+
// Close store connection
|
|
184
|
+
this.store.close();
|
|
185
|
+
this.onChange.clear();
|
|
186
|
+
this.onDeleteDoc.clear();
|
|
187
|
+
this.onUntrackDocs.clear();
|
|
188
|
+
this.onTrackDocs.clear();
|
|
189
|
+
this.onServerCommit.clear();
|
|
190
|
+
this.onError.clear();
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Internal handler for saving pending changes to the store.
|
|
194
|
+
* @param docId - The document ID to save the changes for.
|
|
195
|
+
* @param changes - The changes to save.
|
|
196
|
+
*/
|
|
197
|
+
async _savePendingChanges(docId, changes) {
|
|
198
|
+
try {
|
|
199
|
+
await this.store.savePendingChanges(docId, changes);
|
|
200
|
+
// Only after it is persisted, emit the change (for PatchesSync to flush)
|
|
201
|
+
this.onChange.emit(docId, changes);
|
|
200
202
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
})(),
|
|
208
|
-
_a;
|
|
203
|
+
catch (err) {
|
|
204
|
+
console.error(`Error saving pending changes for doc ${docId}:`, err);
|
|
205
|
+
this.onError.emit(err, { docId });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
209
|
})();
|
|
210
210
|
export { Patches };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { type Unsubscriber } from '../event-signal.js';
|
|
2
|
-
import type {
|
|
3
|
-
import type { Change, DeepRequired, PatchesSnapshot, SyncingState } from '../types.js';
|
|
2
|
+
import type { Change, ChangeMutator, PatchesSnapshot, SyncingState } from '../types.js';
|
|
4
3
|
/**
|
|
5
4
|
* Options for creating a PatchesDoc instance
|
|
6
5
|
*/
|
|
@@ -68,10 +67,10 @@ export declare class PatchesDoc<T extends object = object> {
|
|
|
68
67
|
setChangeMetadata(metadata: Record<string, any>): void;
|
|
69
68
|
/**
|
|
70
69
|
* Applies an update to the local state, generating a patch and adding it to pending changes.
|
|
71
|
-
* @param mutator Function
|
|
72
|
-
* @returns The generated Change
|
|
70
|
+
* @param mutator Function that uses JSONPatch methods with type-safe paths.
|
|
71
|
+
* @returns The generated Change objects.
|
|
73
72
|
*/
|
|
74
|
-
change(mutator:
|
|
73
|
+
change(mutator: ChangeMutator<T>): Change[];
|
|
75
74
|
/**
|
|
76
75
|
* Returns the pending changes for this document.
|
|
77
76
|
* @returns The pending changes.
|
|
@@ -8,6 +8,20 @@ import { signal } from '../event-signal.js';
|
|
|
8
8
|
* changes currently being sent to the server.
|
|
9
9
|
*/
|
|
10
10
|
export class PatchesDoc {
|
|
11
|
+
_id = null;
|
|
12
|
+
_state;
|
|
13
|
+
_snapshot;
|
|
14
|
+
_changeMetadata = {};
|
|
15
|
+
_syncing = null;
|
|
16
|
+
_maxPayloadBytes;
|
|
17
|
+
/** Subscribe to be notified before local state changes. */
|
|
18
|
+
onBeforeChange = signal();
|
|
19
|
+
/** Subscribe to be notified after local state changes are applied. */
|
|
20
|
+
onChange = signal();
|
|
21
|
+
/** Subscribe to be notified whenever state changes from any source. */
|
|
22
|
+
onUpdate = signal();
|
|
23
|
+
/** Subscribe to be notified when syncing state changes. */
|
|
24
|
+
onSyncing = signal();
|
|
11
25
|
/**
|
|
12
26
|
* Creates an instance of PatchesDoc.
|
|
13
27
|
* @param initialState Optional initial state.
|
|
@@ -15,17 +29,6 @@ export class PatchesDoc {
|
|
|
15
29
|
* @param options Additional options for the document.
|
|
16
30
|
*/
|
|
17
31
|
constructor(initialState = {}, initialMetadata = {}, options = {}) {
|
|
18
|
-
this._id = null;
|
|
19
|
-
this._changeMetadata = {};
|
|
20
|
-
this._syncing = null;
|
|
21
|
-
/** Subscribe to be notified before local state changes. */
|
|
22
|
-
this.onBeforeChange = signal();
|
|
23
|
-
/** Subscribe to be notified after local state changes are applied. */
|
|
24
|
-
this.onChange = signal();
|
|
25
|
-
/** Subscribe to be notified whenever state changes from any source. */
|
|
26
|
-
this.onUpdate = signal();
|
|
27
|
-
/** Subscribe to be notified when syncing state changes. */
|
|
28
|
-
this.onSyncing = signal();
|
|
29
32
|
this._state = structuredClone(initialState);
|
|
30
33
|
this._snapshot = { state: this._state, rev: 0, changes: [] };
|
|
31
34
|
this._changeMetadata = initialMetadata;
|
|
@@ -83,8 +86,8 @@ export class PatchesDoc {
|
|
|
83
86
|
}
|
|
84
87
|
/**
|
|
85
88
|
* Applies an update to the local state, generating a patch and adding it to pending changes.
|
|
86
|
-
* @param mutator Function
|
|
87
|
-
* @returns The generated Change
|
|
89
|
+
* @param mutator Function that uses JSONPatch methods with type-safe paths.
|
|
90
|
+
* @returns The generated Change objects.
|
|
88
91
|
*/
|
|
89
92
|
change(mutator) {
|
|
90
93
|
const changes = makeChange(this._snapshot, mutator, this._changeMetadata, this._maxPayloadBytes);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { applyChanges } from '../algorithms/shared/applyChanges.js';
|
|
2
2
|
import { signal } from '../event-signal.js';
|
|
3
3
|
class LRUCache {
|
|
4
|
+
maxSize;
|
|
5
|
+
cache = new Map();
|
|
4
6
|
constructor(maxSize) {
|
|
5
7
|
this.maxSize = maxSize;
|
|
6
|
-
this.cache = new Map();
|
|
7
8
|
}
|
|
8
9
|
get(key) {
|
|
9
10
|
const value = this.cache.get(key);
|
|
@@ -36,15 +37,18 @@ class LRUCache {
|
|
|
36
37
|
* Read-only: allows listing versions, loading states/changes, and scrubbing.
|
|
37
38
|
*/
|
|
38
39
|
export class PatchesHistoryClient {
|
|
40
|
+
api;
|
|
41
|
+
/** Document ID */
|
|
42
|
+
id;
|
|
43
|
+
/** Event signal for versions changes */
|
|
44
|
+
onVersionsChange = signal();
|
|
45
|
+
/** Event signal for state changes */
|
|
46
|
+
onStateChange = signal();
|
|
47
|
+
_versions = [];
|
|
48
|
+
_state = null;
|
|
49
|
+
cache = new LRUCache(6);
|
|
39
50
|
constructor(id, api) {
|
|
40
51
|
this.api = api;
|
|
41
|
-
/** Event signal for versions changes */
|
|
42
|
-
this.onVersionsChange = signal();
|
|
43
|
-
/** Event signal for state changes */
|
|
44
|
-
this.onStateChange = signal();
|
|
45
|
-
this._versions = [];
|
|
46
|
-
this._state = null;
|
|
47
|
-
this.cache = new LRUCache(6);
|
|
48
52
|
this.id = id;
|
|
49
53
|
}
|
|
50
54
|
/** List of loaded versions */
|
|
@@ -21,6 +21,8 @@ import { transformPatch } from './transformPatch.js';
|
|
|
21
21
|
* together which may form a single operation or transaction.
|
|
22
22
|
*/
|
|
23
23
|
export class JSONPatch {
|
|
24
|
+
ops;
|
|
25
|
+
custom;
|
|
24
26
|
/**
|
|
25
27
|
* Create a new JSONPatch, optionally with an existing array of operations.
|
|
26
28
|
*/
|
|
@@ -1,36 +1,33 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ChangeMutator } from '../types.js';
|
|
2
2
|
import { JSONPatch } from './JSONPatch.js';
|
|
3
3
|
/**
|
|
4
|
-
* Creates a `JSONPatch` instance
|
|
4
|
+
* Creates a `JSONPatch` instance using a path-only proxy for type-safe operation generation.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* are automatically converted into JSON Patch operations and added to the patch instance.
|
|
10
|
-
* You can also directly call methods on the `patch` instance within the updater.
|
|
6
|
+
* The mutator function receives a JSONPatch instance and a PathProxy for creating
|
|
7
|
+
* type-safe JSON Pointer paths. All modifications must be done through explicit
|
|
8
|
+
* JSONPatch methods - the path proxy will throw errors if mutation is attempted.
|
|
11
9
|
*
|
|
12
10
|
* @template T The type of the target object.
|
|
13
|
-
* @param target The initial state of the object.
|
|
14
|
-
* @param
|
|
15
|
-
*
|
|
16
|
-
* @returns A `JSONPatch` instance containing the operations generated within the updater.
|
|
11
|
+
* @param target The initial state of the object (used for type inference only).
|
|
12
|
+
* @param mutator A function that receives a JSONPatch instance and a PathProxy.
|
|
13
|
+
* @returns A `JSONPatch` instance containing the operations generated within the mutator.
|
|
17
14
|
*
|
|
18
15
|
* @example
|
|
19
16
|
* ```ts
|
|
20
17
|
* const myObj = { name: { first: 'Alice' }, age: 30, tags: ['a'] };
|
|
21
18
|
*
|
|
22
|
-
* const patch = createJSONPatch(myObj, (
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
19
|
+
* const patch = createJSONPatch(myObj, (patch, path) => {
|
|
20
|
+
* patch.replace(path.name.first, 'Bob'); // Type-safe path creation
|
|
21
|
+
* patch.increment(path.age, 1); // Explicit operations only
|
|
22
|
+
* patch.add(path.tags[1], 'b'); // Array path handling
|
|
26
23
|
* });
|
|
27
24
|
*
|
|
28
25
|
* console.log(patch.ops);
|
|
29
26
|
* // [
|
|
30
27
|
* // { op: 'replace', path: '/name/first', value: 'Bob' },
|
|
31
|
-
* // { op: '
|
|
32
|
-
* // { op: '
|
|
28
|
+
* // { op: 'increment', path: '/age', value: 1 },
|
|
29
|
+
* // { op: 'add', path: '/tags/1', value: 'b' }
|
|
33
30
|
* // ]
|
|
34
31
|
* ```
|
|
35
32
|
*/
|
|
36
|
-
export declare function createJSONPatch<T>(
|
|
33
|
+
export declare function createJSONPatch<T>(mutator: ChangeMutator<T>): JSONPatch;
|
|
@@ -1,41 +1,39 @@
|
|
|
1
1
|
import { JSONPatch } from './JSONPatch.js';
|
|
2
|
-
import {
|
|
2
|
+
import { createPathProxy } from './pathProxy.js';
|
|
3
3
|
/**
|
|
4
|
-
* Creates a `JSONPatch` instance
|
|
4
|
+
* Creates a `JSONPatch` instance using a path-only proxy for type-safe operation generation.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* are automatically converted into JSON Patch operations and added to the patch instance.
|
|
10
|
-
* You can also directly call methods on the `patch` instance within the updater.
|
|
6
|
+
* The mutator function receives a JSONPatch instance and a PathProxy for creating
|
|
7
|
+
* type-safe JSON Pointer paths. All modifications must be done through explicit
|
|
8
|
+
* JSONPatch methods - the path proxy will throw errors if mutation is attempted.
|
|
11
9
|
*
|
|
12
10
|
* @template T The type of the target object.
|
|
13
|
-
* @param target The initial state of the object.
|
|
14
|
-
* @param
|
|
15
|
-
*
|
|
16
|
-
* @returns A `JSONPatch` instance containing the operations generated within the updater.
|
|
11
|
+
* @param target The initial state of the object (used for type inference only).
|
|
12
|
+
* @param mutator A function that receives a JSONPatch instance and a PathProxy.
|
|
13
|
+
* @returns A `JSONPatch` instance containing the operations generated within the mutator.
|
|
17
14
|
*
|
|
18
15
|
* @example
|
|
19
16
|
* ```ts
|
|
20
17
|
* const myObj = { name: { first: 'Alice' }, age: 30, tags: ['a'] };
|
|
21
18
|
*
|
|
22
|
-
* const patch = createJSONPatch(myObj, (
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
19
|
+
* const patch = createJSONPatch(myObj, (patch, path) => {
|
|
20
|
+
* patch.replace(path.name.first, 'Bob'); // Type-safe path creation
|
|
21
|
+
* patch.increment(path.age, 1); // Explicit operations only
|
|
22
|
+
* patch.add(path.tags[1], 'b'); // Array path handling
|
|
26
23
|
* });
|
|
27
24
|
*
|
|
28
25
|
* console.log(patch.ops);
|
|
29
26
|
* // [
|
|
30
27
|
* // { op: 'replace', path: '/name/first', value: 'Bob' },
|
|
31
|
-
* // { op: '
|
|
32
|
-
* // { op: '
|
|
28
|
+
* // { op: 'increment', path: '/age', value: 1 },
|
|
29
|
+
* // { op: 'add', path: '/tags/1', value: 'b' }
|
|
33
30
|
* // ]
|
|
34
31
|
* ```
|
|
35
32
|
*/
|
|
36
|
-
export function createJSONPatch(
|
|
33
|
+
export function createJSONPatch(mutator) {
|
|
37
34
|
const patch = new JSONPatch();
|
|
38
|
-
//
|
|
39
|
-
|
|
35
|
+
// Create path-only proxy for type-safe path generation
|
|
36
|
+
const pathProxy = createPathProxy();
|
|
37
|
+
mutator(patch, pathProxy);
|
|
40
38
|
return patch;
|
|
41
39
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PathProxy } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a path proxy for generating JSON Pointer paths in a type-safe way.
|
|
4
|
+
* This proxy should ONLY be used for path creation with JSONPatch methods.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* ```ts
|
|
8
|
+
* const patch = new JSONPatch();
|
|
9
|
+
* const path = createPathProxy<MyType>();
|
|
10
|
+
* patch.replace(path.content, 'new text'); // Path is '/content'
|
|
11
|
+
* patch.increment(path.counter, 5); // Path is '/counter'
|
|
12
|
+
* patch.add(path.items[0], newItem); // Path is '/items/0'
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* The proxy will throw errors if you attempt to set properties or delete properties.
|
|
16
|
+
* This prevents accidental mutation and ensures explicit patch operations are used.
|
|
17
|
+
*
|
|
18
|
+
* @template T The type of the object to create paths for.
|
|
19
|
+
* @returns A path proxy object.
|
|
20
|
+
*/
|
|
21
|
+
export declare const createPathProxy: <T>() => PathProxy<T>;
|
|
22
|
+
export declare function pathProxy<T>(path?: string): PathProxy<T>;
|