@iodev/patch-and-resolve 1.0.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/LICENSE +674 -0
- package/README.md +402 -0
- package/dist/components/ConflictModal.d.ts +23 -0
- package/dist/components/PatchManager.d.ts +10 -0
- package/dist/hooks/usePatchManager.d.ts +17 -0
- package/dist/index.d.ts +9 -0
- package/dist/patch-and-resolve.es.js +1450 -0
- package/dist/patch-and-resolve.umd.js +30 -0
- package/dist/services/ConflictResolver.d.ts +41 -0
- package/dist/types/index.d.ts +76 -0
- package/dist/utils/deepPath.d.ts +40 -0
- package/dist/utils/jsonPatchAdapter.d.ts +44 -0
- package/package.json +79 -0
package/README.md
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
# Patch and Resolve
|
|
2
|
+
|
|
3
|
+
A library for merging conflicting JSON patches with automatic conflict detection and resolution UI.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This library helps resolve conflicts when two patches modify the same document. Common use case: a user has a document open on multiple devices (desktop and mobile), makes different changes on each, and both try to save at the same time.
|
|
8
|
+
|
|
9
|
+
**Key Features:**
|
|
10
|
+
- Automatically merges non-conflicting patches
|
|
11
|
+
- Detects and reports conflicts when patches modify the same fields
|
|
12
|
+
- Provides a modal UI for manual conflict resolution
|
|
13
|
+
- Completely agnostic to your storage/API layer
|
|
14
|
+
|
|
15
|
+
## Demo
|
|
16
|
+
|
|
17
|
+
The demo application shows how to merge multiple remote patches into your local changes:
|
|
18
|
+
|
|
19
|
+
**Step 1: Start with local and remote patches**
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
**Step 2: When conflicts are detected, navigate through them one at a time**
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
**Step 3: Select a value to enable the Next button**
|
|
28
|
+
|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
**Step 4: After resolving all conflicts, see the merged result**
|
|
32
|
+
|
|
33
|
+

|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
### Basic Example
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { ConflictResolver } from 'patch-and-resolve';
|
|
47
|
+
|
|
48
|
+
const resolver = new ConflictResolver();
|
|
49
|
+
|
|
50
|
+
// Two patches from different sources
|
|
51
|
+
const desktopPatch = { message: 'Updated on desktop', x: 100 };
|
|
52
|
+
const mobilePatch = { imageId: 'img123', y: 200 };
|
|
53
|
+
|
|
54
|
+
// Try to merge them
|
|
55
|
+
const result = resolver.mergePatches(desktopPatch, mobilePatch);
|
|
56
|
+
|
|
57
|
+
if (result.success) {
|
|
58
|
+
// No conflicts - patches modified different fields
|
|
59
|
+
console.log('Merged:', result.merged);
|
|
60
|
+
// { message: 'Updated on desktop', x: 100, imageId: 'img123', y: 200 }
|
|
61
|
+
} else {
|
|
62
|
+
// Conflicts detected - show modal to user
|
|
63
|
+
console.log('Conflicts:', result.conflicts);
|
|
64
|
+
// Manually resolve
|
|
65
|
+
const resolved = resolver.resolveConflict('use-first', desktopPatch, mobilePatch);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### React Hook
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import { usePatchMerger } from 'patch-and-resolve';
|
|
73
|
+
|
|
74
|
+
function MyComponent() {
|
|
75
|
+
const { mergePatches, resolveConflict, mergeResult, hasConflict } = usePatchMerger({
|
|
76
|
+
onMergeSuccess: (merged) => {
|
|
77
|
+
// Save the merged patch
|
|
78
|
+
saveToDB(merged);
|
|
79
|
+
},
|
|
80
|
+
onConflict: (result) => {
|
|
81
|
+
// Show conflict UI
|
|
82
|
+
setShowConflictModal(true);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Merge two patches
|
|
87
|
+
mergePatches(patch1, patch2);
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### React Component
|
|
92
|
+
|
|
93
|
+
The library includes a complete demo component:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { PatchManager } from 'patch-and-resolve';
|
|
97
|
+
|
|
98
|
+
<PatchManager
|
|
99
|
+
onMergeComplete={(merged) => {
|
|
100
|
+
// Your app handles saving
|
|
101
|
+
await myApiClient.savePatch(merged);
|
|
102
|
+
}}
|
|
103
|
+
/>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Advanced Features
|
|
107
|
+
|
|
108
|
+
### Multiple Remote Patches
|
|
109
|
+
|
|
110
|
+
The library now supports merging an array of remote patches into your local changes. This is useful when you need to catch up with multiple versions from a server:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import { ConflictResolver } from 'patch-and-resolve';
|
|
114
|
+
|
|
115
|
+
const resolver = new ConflictResolver();
|
|
116
|
+
|
|
117
|
+
// Desktop at version 15, server has versions 16-20
|
|
118
|
+
const localPatch = { message: 'Local changes', version: 15 };
|
|
119
|
+
const remotePatches = [
|
|
120
|
+
{ message: 'Server v16', version: 16 },
|
|
121
|
+
{ message: 'Server v17', version: 17 },
|
|
122
|
+
{ x: 100, version: 18 },
|
|
123
|
+
{ y: 200, version: 19 },
|
|
124
|
+
{ color: 'blue', version: 20 }
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
const result = resolver.mergePatches(localPatch, remotePatches);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Remote patches are merged sequentially, and later patches can overwrite earlier ones without conflict. Conflicts only occur between your local changes and the remote patches.
|
|
131
|
+
|
|
132
|
+
### Nested Object Support
|
|
133
|
+
|
|
134
|
+
The library supports merging patches with nested object structures. This method handles complex nested data while following simple conflict rules:
|
|
135
|
+
|
|
136
|
+
**Conflict Rules:**
|
|
137
|
+
- ✅ Conflicts occur **only when the exact same path** is modified by both patches
|
|
138
|
+
- ✅ Different paths merge automatically, even under the same parent object
|
|
139
|
+
- ✅ Arrays are treated as single values (conflict if entire array differs)
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { ConflictResolver } from 'patch-and-resolve';
|
|
143
|
+
|
|
144
|
+
const resolver = new ConflictResolver();
|
|
145
|
+
|
|
146
|
+
const localPatch = {
|
|
147
|
+
user: {
|
|
148
|
+
name: 'Alice',
|
|
149
|
+
email: 'alice@example.com',
|
|
150
|
+
},
|
|
151
|
+
title: 'My Project',
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const remotePatch = {
|
|
155
|
+
user: {
|
|
156
|
+
phone: '555-1234', // Different path: user.phone
|
|
157
|
+
address: { // Different path: user.address.city
|
|
158
|
+
city: 'NYC',
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
description: 'Updated', // Different path: description
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const result = resolver.mergePatches(localPatch, [remotePatch]);
|
|
165
|
+
|
|
166
|
+
// Result: Success! All paths are different, so no conflicts
|
|
167
|
+
// Merged: {
|
|
168
|
+
// user: {
|
|
169
|
+
// name: 'Alice', // from local
|
|
170
|
+
// email: 'alice@...', // from local
|
|
171
|
+
// phone: '555-1234', // from remote
|
|
172
|
+
// address: { city: 'NYC' } // from remote
|
|
173
|
+
// },
|
|
174
|
+
// title: 'My Project', // from local
|
|
175
|
+
// description: 'Updated' // from remote
|
|
176
|
+
// }
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Conflict Example:**
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
const localPatch = {
|
|
183
|
+
user: {
|
|
184
|
+
name: 'Alice', // This path: user.name
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const remotePatch = {
|
|
189
|
+
user: {
|
|
190
|
+
name: 'Bob', // Same path: user.name → CONFLICT!
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const result = resolver.mergePatches(localPatch, [remotePatch]);
|
|
195
|
+
|
|
196
|
+
// Result: Conflict detected
|
|
197
|
+
// result.conflicts[0] = {
|
|
198
|
+
// path: 'user.name',
|
|
199
|
+
// localValue: 'Alice',
|
|
200
|
+
// remoteValue: 'Bob',
|
|
201
|
+
// remotePatchIndex: 0
|
|
202
|
+
// }
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Resolving Conflicts:**
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
// After user makes choices in the UI
|
|
209
|
+
const resolutions = [
|
|
210
|
+
{ conflictIndex: 0, strategy: 'use-local' }, // Keep 'Alice'
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
const resolved = resolver.applyResolutions(localPatch, [remotePatch], resolutions);
|
|
214
|
+
// resolved.resolved contains the final merged patch
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
**Path Format:**
|
|
218
|
+
- Paths use dot notation: `"user.name"`, `"user.address.city"`
|
|
219
|
+
- Arrays are identified by their parent path: `"tags"`, `"pages"`
|
|
220
|
+
- Version fields are automatically handled and excluded from conflict detection
|
|
221
|
+
|
|
222
|
+
### Conflict Navigation
|
|
223
|
+
|
|
224
|
+
When multiple conflicts are detected, the UI provides Previous/Next/Finish buttons to step through them one at a time:
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import { usePatchManager } from 'patch-and-resolve';
|
|
228
|
+
|
|
229
|
+
const { mergePatches, applyResolutions } = usePatchManager({
|
|
230
|
+
onMergeSuccess: (merged) => saveToDB(merged)
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Merge and get conflicts
|
|
234
|
+
mergePatches(localPatch, remotePatches);
|
|
235
|
+
|
|
236
|
+
// Resolve conflicts one by one
|
|
237
|
+
const resolutions = [
|
|
238
|
+
{ conflictIndex: 0, strategy: 'use-local' },
|
|
239
|
+
{ conflictIndex: 1, strategy: 'use-remote' },
|
|
240
|
+
{ conflictIndex: 2, strategy: 'use-local' }
|
|
241
|
+
];
|
|
242
|
+
|
|
243
|
+
applyResolutions(resolutions);
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Custom Conflict Visualization
|
|
247
|
+
|
|
248
|
+
Use the `renderConflictValue` prop to customize how conflict values are displayed. This is powerful for showing semantic previews instead of raw JSON:
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
import { ConflictModal, ConflictValueRenderContext } from 'patch-and-resolve';
|
|
252
|
+
|
|
253
|
+
<ConflictModal
|
|
254
|
+
conflicts={conflicts}
|
|
255
|
+
onResolve={handleResolve}
|
|
256
|
+
onCancel={handleCancel}
|
|
257
|
+
renderConflictValue={(value, context: ConflictValueRenderContext) => {
|
|
258
|
+
const { conflict, side, isSelected } = context;
|
|
259
|
+
|
|
260
|
+
// Show a visual preview for your specific data structure
|
|
261
|
+
if (value.x !== undefined && value.y !== undefined) {
|
|
262
|
+
return (
|
|
263
|
+
<div>
|
|
264
|
+
<div
|
|
265
|
+
style={{
|
|
266
|
+
position: 'relative',
|
|
267
|
+
width: 200,
|
|
268
|
+
height: 150,
|
|
269
|
+
border: '1px solid #ccc',
|
|
270
|
+
margin: '8px 0'
|
|
271
|
+
}}
|
|
272
|
+
>
|
|
273
|
+
<div style={{
|
|
274
|
+
position: 'absolute',
|
|
275
|
+
left: value.x,
|
|
276
|
+
top: value.y,
|
|
277
|
+
padding: '4px 8px',
|
|
278
|
+
background: isSelected ? '#4CAF50' : '#2196F3',
|
|
279
|
+
color: 'white',
|
|
280
|
+
borderRadius: '4px'
|
|
281
|
+
}}>
|
|
282
|
+
{value.message}
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
|
|
286
|
+
{/* Also show the raw JSON */}
|
|
287
|
+
<pre>{JSON.stringify(value, null, 2)}</pre>
|
|
288
|
+
</div>
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Default rendering for other types
|
|
293
|
+
return <pre>{JSON.stringify(value, null, 2)}</pre>;
|
|
294
|
+
}}
|
|
295
|
+
/>
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
The `ConflictValueRenderContext` provides:
|
|
299
|
+
- `conflict`: The full conflict object with path, localValue, remoteValue
|
|
300
|
+
- `side`: Either `'local'` or `'remote'`
|
|
301
|
+
- `isSelected`: Boolean indicating if this value is currently selected
|
|
302
|
+
|
|
303
|
+
This allows you to create rich, context-aware visualizations that help users make informed decisions about which value to keep.
|
|
304
|
+
|
|
305
|
+
### Integration with JSON Patch (RFC 6902)
|
|
306
|
+
|
|
307
|
+
If your application uses JSON Patch format (RFC 6902) with libraries like `fast-json-patch`, you can convert between JSON Patch operations and the simple diff format used by this library:
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
import { jsonPatchToDiff, diffToJsonPatch, ConflictResolver } from 'patch-and-resolve';
|
|
311
|
+
|
|
312
|
+
// Backend sends JSON Patch operations
|
|
313
|
+
const localOps = [
|
|
314
|
+
{ op: "replace", path: "/message", value: "Local edit" },
|
|
315
|
+
{ op: "add", path: "/x", value: 100 }
|
|
316
|
+
];
|
|
317
|
+
|
|
318
|
+
const remoteOps = [
|
|
319
|
+
{ op: "replace", path: "/message", value: "Remote edit" },
|
|
320
|
+
{ op: "add", path: "/y", value: 200 }
|
|
321
|
+
];
|
|
322
|
+
|
|
323
|
+
// Convert to diffs for conflict resolution
|
|
324
|
+
const localDiff = jsonPatchToDiff(localOps); // { message: "Local edit", x: 100 }
|
|
325
|
+
const remoteDiff = jsonPatchToDiff(remoteOps); // { message: "Remote edit", y: 200 }
|
|
326
|
+
|
|
327
|
+
// Merge with conflict detection
|
|
328
|
+
const resolver = new ConflictResolver();
|
|
329
|
+
const result = resolver.mergePatches(localDiff, [remoteDiff]);
|
|
330
|
+
|
|
331
|
+
if (result.success) {
|
|
332
|
+
// Convert back to JSON Patch if needed
|
|
333
|
+
const patchOps = diffToJsonPatch(result.merged);
|
|
334
|
+
// Result: [
|
|
335
|
+
// { op: "replace", path: "/message", value: "Local edit" },
|
|
336
|
+
// { op: "replace", path: "/x", value: 100 },
|
|
337
|
+
// { op: "replace", path: "/y", value: 200 }
|
|
338
|
+
// ]
|
|
339
|
+
|
|
340
|
+
// Apply to your document with fast-json-patch
|
|
341
|
+
applyPatch(document, patchOps);
|
|
342
|
+
} else {
|
|
343
|
+
// Show conflict UI to user
|
|
344
|
+
// After user resolves conflicts:
|
|
345
|
+
const resolved = resolver.applyResolutions(localDiff, [remoteDiff], resolutions);
|
|
346
|
+
|
|
347
|
+
// Convert resolved patch back to JSON Patch format
|
|
348
|
+
const resolvedOps = diffToJsonPatch(resolved.resolved);
|
|
349
|
+
// Send back to server or apply locally
|
|
350
|
+
applyPatch(document, resolvedOps);
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
**Supported JSON Patch Operations:**
|
|
355
|
+
- ✅ `add` and `replace` - Converted to field updates
|
|
356
|
+
- ❌ `remove`, `move`, `copy`, `test` - Ignored (don't map to simple diffs)
|
|
357
|
+
- ⚠️ Only top-level paths supported (e.g., `/message` works, `/user/name` ignored)
|
|
358
|
+
|
|
359
|
+
This makes the library compatible with standard JSON Patch workflows while providing an intuitive UI for conflict resolution.
|
|
360
|
+
|
|
361
|
+
## Development
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
npm run dev # Start demo app
|
|
365
|
+
npm test # Run unit tests
|
|
366
|
+
npm run test:e2e # Run end-to-end tests in headless Chromium
|
|
367
|
+
npm run test:all # Run all tests (unit + e2e)
|
|
368
|
+
npm run build # Build library
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### End-to-End Testing
|
|
372
|
+
|
|
373
|
+
The project includes comprehensive Playwright tests that verify the conflict modal works correctly in a real browser environment:
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
npm run test:e2e # Run e2e tests (headless)
|
|
377
|
+
npm run test:e2e:headed # Run e2e tests with visible browser
|
|
378
|
+
npm run test:e2e:ui # Run e2e tests with Playwright UI
|
|
379
|
+
npx playwright show-report # View last test report
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
The e2e tests cover:
|
|
383
|
+
- ✓ Merging non-conflicting patches
|
|
384
|
+
- ✓ Detecting conflicts
|
|
385
|
+
- ✓ Displaying the conflict modal
|
|
386
|
+
- ✓ Resolving conflicts with user choice (use patch 1 vs patch 2)
|
|
387
|
+
- ✓ Version number handling (automatically uses higher version)
|
|
388
|
+
- ✓ Canceling conflict resolution
|
|
389
|
+
|
|
390
|
+
## Commit Convention
|
|
391
|
+
|
|
392
|
+
This project uses [Conventional Commits](https://www.conventionalcommits.org/):
|
|
393
|
+
|
|
394
|
+
- `feat:` - New feature (triggers minor version bump)
|
|
395
|
+
- `fix:` - Bug fix (triggers patch version bump)
|
|
396
|
+
- `feat!:` or `fix!:` - Breaking change (triggers major version bump)
|
|
397
|
+
- `chore:`, `docs:`, `style:`, `refactor:`, `test:` - No version bump
|
|
398
|
+
|
|
399
|
+
## Versioning
|
|
400
|
+
|
|
401
|
+
Versions are automatically managed by semantic-release based on commit messages.
|
|
402
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { Conflict, ConflictResolution } from '../types';
|
|
3
|
+
export interface ConflictValueRenderContext {
|
|
4
|
+
conflict: Conflict;
|
|
5
|
+
side: 'local' | 'remote';
|
|
6
|
+
isSelected: boolean;
|
|
7
|
+
}
|
|
8
|
+
interface ConflictModalProps {
|
|
9
|
+
conflicts: Conflict[];
|
|
10
|
+
onResolveAll: (resolutions: ConflictResolution[]) => void;
|
|
11
|
+
onClose: () => void;
|
|
12
|
+
/**
|
|
13
|
+
* Optional custom renderer for conflict values.
|
|
14
|
+
* Use this to show semantic previews instead of raw JSON.
|
|
15
|
+
* If not provided, displays values as formatted JSON.
|
|
16
|
+
*/
|
|
17
|
+
renderConflictValue?: (value: any, context: ConflictValueRenderContext) => React.ReactNode;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Modal component for stepping through and resolving patch conflicts one at a time
|
|
21
|
+
*/
|
|
22
|
+
export declare const ConflictModal: React.FC<ConflictModalProps>;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
import { Patch } from '../types';
|
|
3
|
+
interface PatchManagerProps {
|
|
4
|
+
onMergeComplete?: (mergedPatch: Patch) => void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Demo component showing how to merge multiple patches and handle conflicts
|
|
8
|
+
*/
|
|
9
|
+
export declare const PatchManager: React.FC<PatchManagerProps>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Patch, MergeResult, ConflictResolution } from '../types';
|
|
2
|
+
interface UsePatchMergerOptions {
|
|
3
|
+
onMergeSuccess?: (mergedPatch: Patch) => void;
|
|
4
|
+
onConflict?: (result: MergeResult) => void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Custom hook for merging multiple patches and handling conflicts
|
|
8
|
+
*/
|
|
9
|
+
export declare function usePatchMerger(options?: UsePatchMergerOptions): {
|
|
10
|
+
merging: boolean;
|
|
11
|
+
mergeResult: MergeResult | null;
|
|
12
|
+
hasConflict: boolean | null;
|
|
13
|
+
mergePatches: (local: Patch, remote: Patch[]) => MergeResult;
|
|
14
|
+
applyResolutions: (resolutions: ConflictResolution[]) => Patch | null;
|
|
15
|
+
clearConflict: () => void;
|
|
16
|
+
};
|
|
17
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { PatchManager } from './components/PatchManager';
|
|
2
|
+
export { ConflictModal } from './components/ConflictModal';
|
|
3
|
+
export type { ConflictValueRenderContext } from './components/ConflictModal';
|
|
4
|
+
export { usePatchMerger } from './hooks/usePatchManager';
|
|
5
|
+
export { ConflictResolver } from './services/ConflictResolver';
|
|
6
|
+
export type { Patch, Conflict, MergeResult, ResolveStrategy, ConflictResolution, ResolveResult, } from './types';
|
|
7
|
+
export { jsonPatchToDiff, diffToJsonPatch, } from './utils/jsonPatchAdapter';
|
|
8
|
+
export type { JsonPatchOperation } from './utils/jsonPatchAdapter';
|
|
9
|
+
export { getNestedValue, setNestedValue, hasNestedPath, getAllPaths, deepEqual, } from './utils/deepPath';
|