@vorplex/core 0.0.32 → 0.0.34

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.
@@ -1,4 +1,4 @@
1
- export type ArrayChange = Record<`$${number}` | `$${number}+`, any | typeof $Changes.deleted>;
1
+ export type ArrayChange = Record<`$${number}` | `$${number}+` | `${'$'}{${string | number}}`, any | typeof $Changes.deleted>;
2
2
  export interface ChangeCompareResult {
3
3
  differences: any;
4
4
  similarities: any;
@@ -29,15 +29,12 @@ export interface ChangeRebase<T = any> {
29
29
  * The `.source` with local conflicting changes applied
30
30
  */
31
31
  local: T;
32
- /**
33
- * The remote with local non-conflicting changes applied
34
- */
35
- result: T;
36
32
  };
37
33
  };
38
34
  }
39
35
  export declare class $Changes {
40
36
  static readonly deleted: "[deleted]";
37
+ private static valueHasId;
41
38
  /**
42
39
  * Compute a minimal change object between `a` (base) and `b` (target).
43
40
  * Returns `undefined` when there is no difference.
@@ -1,6 +1,9 @@
1
1
  import { $Value } from '../value/value.util';
2
2
  export class $Changes {
3
3
  static deleted = '[deleted]';
4
+ static valueHasId(value) {
5
+ return value !== null && typeof value === 'object' && !Array.isArray(value) && 'id' in value;
6
+ }
4
7
  /**
5
8
  * Compute a minimal change object between `a` (base) and `b` (target).
6
9
  * Returns `undefined` when there is no difference.
@@ -25,6 +28,30 @@ export class $Changes {
25
28
  return Object.keys(changes).length ? changes : undefined;
26
29
  }
27
30
  function getArrayChanges(base, target) {
31
+ const idKey = (id) => '$' + '{' + id + '}';
32
+ function getIdBasedArrayChanges(base, target) {
33
+ const changes = {};
34
+ const targetById = new Map(target.filter($Changes.valueHasId).map((item) => [item.id, item]));
35
+ const baseIds = new Set(base.filter($Changes.valueHasId).map((item) => item.id));
36
+ for (const item of base) {
37
+ if (!$Changes.valueHasId(item))
38
+ continue;
39
+ const targetItem = targetById.get(item.id);
40
+ if (targetItem === undefined) {
41
+ changes[idKey(item.id)] = $Changes.deleted;
42
+ }
43
+ else {
44
+ const diff = $Changes.get(item, targetItem);
45
+ if (diff !== undefined)
46
+ changes[idKey(item.id)] = diff;
47
+ }
48
+ }
49
+ for (let i = 0; i < target.length; i++) {
50
+ if ($Changes.valueHasId(target[i]) && !baseIds.has(target[i].id))
51
+ changes[`$${i}+`] = target[i];
52
+ }
53
+ return Object.keys(changes).length ? changes : undefined;
54
+ }
28
55
  function getArrayOperations(base, target) {
29
56
  function getLcsMatrix(a, b) {
30
57
  const matrix = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
@@ -60,12 +87,14 @@ export class $Changes {
60
87
  }
61
88
  if (!target.length)
62
89
  return [];
90
+ if (base.every($Changes.valueHasId) && target.every($Changes.valueHasId))
91
+ return getIdBasedArrayChanges(base, target);
63
92
  const operations = getArrayOperations(base, target);
64
93
  const paired = [];
65
94
  for (let i = 0; i < operations.length; i++) {
66
95
  const current = operations[i];
67
96
  const next = operations[i + 1];
68
- if (current.type === 'delete' && next?.type === 'insert') {
97
+ if (current.type === 'delete' && next?.type === 'insert' && $Value.isPrimitive(base[current.sourceIndex])) {
69
98
  paired.push({ type: 'update', sourceIndex: current.sourceIndex, targetIndex: next.targetIndex, value: next.value });
70
99
  i++;
71
100
  }
@@ -147,7 +176,10 @@ export class $Changes {
147
176
  else if ($Value.equals(aValue, bValue)) {
148
177
  similarities[key] = aValue;
149
178
  }
150
- else if ($Value.isObject(aValue) && $Value.isObject(bValue) && !/^\$\d+\+$/.test(key)) {
179
+ else if (/^\$\d+\+$/.test(key)) {
180
+ differences[key] = aValue;
181
+ }
182
+ else if ($Value.isObject(aValue) && $Value.isObject(bValue)) {
151
183
  const nested = $Changes.compareChanges(aValue, bValue);
152
184
  if (nested.differences !== undefined)
153
185
  differences[key] = nested.differences;
@@ -173,9 +205,19 @@ export class $Changes {
173
205
  const updates = new Map();
174
206
  const inserts = new Map();
175
207
  for (const [key, value] of Object.entries(changes)) {
208
+ const idMatch = key.match(/^\$\{(?<id>.+)\}$/);
176
209
  const insertMatch = key.match(/^\$(?<index>\d+)\+$/);
177
210
  const updateMatch = key.match(/^\$(?<index>\d+)$/);
178
- if (insertMatch) {
211
+ if (idMatch) {
212
+ const index = base.findIndex((item) => $Changes.valueHasId(item) && String(item.id) === idMatch.groups.id);
213
+ if (index !== -1) {
214
+ if (value === $Changes.deleted)
215
+ deletes.push(index);
216
+ else
217
+ updates.set(index, value);
218
+ }
219
+ }
220
+ else if (insertMatch) {
179
221
  inserts.set(Number.parseInt(insertMatch.groups.index, 10), value);
180
222
  }
181
223
  else if (updateMatch) {
@@ -210,11 +252,13 @@ export class $Changes {
210
252
  return false;
211
253
  if (!Object.keys(change).length)
212
254
  return false;
213
- for (const k of Object.keys(change)) {
214
- if (!k.startsWith('$'))
255
+ for (const key of Object.keys(change)) {
256
+ if (!key.startsWith('$'))
215
257
  return false;
216
- const key = k.endsWith('+') ? k.slice(1, -1) : k.slice(1);
217
- if (Number.isNaN(Number.parseInt(key, 10)))
258
+ if (/^\$\{.+\}$/.test(key))
259
+ continue;
260
+ const index = key.endsWith('+') ? key.slice(1, -1) : key.slice(1);
261
+ if (Number.isNaN(Number.parseInt(index, 10)))
218
262
  return false;
219
263
  }
220
264
  return true;
@@ -276,17 +320,15 @@ export class $Changes {
276
320
  return { result };
277
321
  }
278
322
  const sourceWithDifferences = $Changes.apply(source, remoteCompare.differences, localCompare.differences, remoteCompare.similarities, localCompare.similarities);
279
- const mergedWithLocalDifferences = $Changes.apply(remote, localCompare.differences);
280
323
  return {
281
- result: $Changes.apply(mergedWithLocalDifferences, localCompare.conflicts),
324
+ result: $Changes.apply(remote, local),
282
325
  conflict: {
283
326
  local: localCompare,
284
327
  remote: remoteCompare,
285
328
  merge: {
286
329
  source: sourceWithDifferences,
287
330
  remote: $Changes.apply(sourceWithDifferences, remoteCompare.conflicts),
288
- local: $Changes.apply(sourceWithDifferences, localCompare.conflicts),
289
- result: mergedWithLocalDifferences,
331
+ local: $Changes.apply(sourceWithDifferences, localCompare.conflicts)
290
332
  },
291
333
  },
292
334
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vorplex/core",
3
- "version": "0.0.32",
3
+ "version": "0.0.34",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "files": [