@legendapp/state 3.0.0-alpha.15 → 3.0.0-alpha.16

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/index.d.mts CHANGED
@@ -308,7 +308,7 @@ declare function opaqueObject<T extends object>(value: T): OpaqueObject<T>;
308
308
  declare function getValueAtPath(obj: Record<string, any>, path: string[]): any;
309
309
  declare function setAtPath<T extends object>(obj: T, path: string[], pathTypes: TypeAtPath[], value: any, mode?: 'set' | 'merge', fullObj?: T, restore?: (path: string[], value: any) => void): T;
310
310
  declare function setInObservableAtPath(value$: ObservableParam, path: string[], pathTypes: TypeAtPath[], value: any, mode: 'assign' | 'set' | 'merge'): void;
311
- declare function mergeIntoObservable<T extends ObservableParam<Record<string, any>> | object>(target: T, ...sources: any[]): T;
311
+ declare function mergeIntoObservable<T extends ObservableParam<any>>(target: T, ...sources: any[]): T;
312
312
  declare function constructObjectWithPath(path: string[], pathTypes: TypeAtPath[], value: any): object;
313
313
  declare function deconstructObjectWithPath(path: string[], pathTypes: TypeAtPath[], value: any): object;
314
314
  declare function isObservableValueReady(value: any): boolean;
@@ -316,6 +316,7 @@ declare function setSilently(value$: ObservableParam, newValue: any): any;
316
316
  declare function initializePathType(pathType: TypeAtPath): any;
317
317
  declare function applyChange<T extends object>(value: T, change: Change, applyPrevious?: boolean): T;
318
318
  declare function applyChanges<T extends object>(value: T, changes: Change[], applyPrevious?: boolean): T;
319
+ declare function deepMerge<T extends object>(target: T, ...sources: any[]): T;
319
320
 
320
321
  declare const hasOwnProperty: (v: PropertyKey) => boolean;
321
322
  declare function isArray(obj: unknown): obj is Array<any>;
@@ -375,6 +376,7 @@ declare function runWithRetry<T>(state: {
375
376
  declare const internal: {
376
377
  createPreviousHandler: typeof createPreviousHandler;
377
378
  clone: typeof clone;
379
+ deepMerge: typeof deepMerge;
378
380
  ensureNodeValue: typeof ensureNodeValue;
379
381
  findIDKey: typeof findIDKey;
380
382
  get: typeof get;
package/index.d.ts CHANGED
@@ -308,7 +308,7 @@ declare function opaqueObject<T extends object>(value: T): OpaqueObject<T>;
308
308
  declare function getValueAtPath(obj: Record<string, any>, path: string[]): any;
309
309
  declare function setAtPath<T extends object>(obj: T, path: string[], pathTypes: TypeAtPath[], value: any, mode?: 'set' | 'merge', fullObj?: T, restore?: (path: string[], value: any) => void): T;
310
310
  declare function setInObservableAtPath(value$: ObservableParam, path: string[], pathTypes: TypeAtPath[], value: any, mode: 'assign' | 'set' | 'merge'): void;
311
- declare function mergeIntoObservable<T extends ObservableParam<Record<string, any>> | object>(target: T, ...sources: any[]): T;
311
+ declare function mergeIntoObservable<T extends ObservableParam<any>>(target: T, ...sources: any[]): T;
312
312
  declare function constructObjectWithPath(path: string[], pathTypes: TypeAtPath[], value: any): object;
313
313
  declare function deconstructObjectWithPath(path: string[], pathTypes: TypeAtPath[], value: any): object;
314
314
  declare function isObservableValueReady(value: any): boolean;
@@ -316,6 +316,7 @@ declare function setSilently(value$: ObservableParam, newValue: any): any;
316
316
  declare function initializePathType(pathType: TypeAtPath): any;
317
317
  declare function applyChange<T extends object>(value: T, change: Change, applyPrevious?: boolean): T;
318
318
  declare function applyChanges<T extends object>(value: T, changes: Change[], applyPrevious?: boolean): T;
319
+ declare function deepMerge<T extends object>(target: T, ...sources: any[]): T;
319
320
 
320
321
  declare const hasOwnProperty: (v: PropertyKey) => boolean;
321
322
  declare function isArray(obj: unknown): obj is Array<any>;
@@ -375,6 +376,7 @@ declare function runWithRetry<T>(state: {
375
376
  declare const internal: {
376
377
  createPreviousHandler: typeof createPreviousHandler;
377
378
  clone: typeof clone;
379
+ deepMerge: typeof deepMerge;
378
380
  ensureNodeValue: typeof ensureNodeValue;
379
381
  findIDKey: typeof findIDKey;
380
382
  get: typeof get;
package/index.js CHANGED
@@ -33,6 +33,9 @@ function isPromise(obj) {
33
33
  function isMap(obj) {
34
34
  return obj instanceof Map;
35
35
  }
36
+ function isSet(obj) {
37
+ return obj instanceof Set;
38
+ }
36
39
  function isNumber(obj) {
37
40
  const n = obj;
38
41
  return n - n < 1;
@@ -42,6 +45,8 @@ function isEmpty(obj) {
42
45
  return false;
43
46
  if (isArray(obj))
44
47
  return obj.length === 0;
48
+ if (isMap(obj) || isSet(obj))
49
+ return obj.size === 0;
45
50
  for (const key in obj) {
46
51
  if (hasOwnProperty.call(obj, key)) {
47
52
  return false;
@@ -305,13 +310,13 @@ function setAtPath(obj, path, pathTypes, value, mode, fullObj, restore) {
305
310
  }
306
311
  if (p === void 0) {
307
312
  if (mode === "merge") {
308
- obj = _mergeIntoObservable(obj, value);
313
+ obj = deepMerge(obj, value, false, 0);
309
314
  } else {
310
315
  obj = value;
311
316
  }
312
317
  } else {
313
318
  if (mode === "merge") {
314
- o[p] = _mergeIntoObservable(o[p], value);
319
+ o[p] = deepMerge(o[p], value, false, 0);
315
320
  } else if (isMap(o)) {
316
321
  o.set(p, value);
317
322
  } else {
@@ -342,56 +347,58 @@ function setInObservableAtPath(value$, path, pathTypes, value, mode) {
342
347
  }
343
348
  }
344
349
  function mergeIntoObservable(target, ...sources) {
350
+ if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") {
351
+ if (!isObservable(target)) {
352
+ console.error("[legend-state] should only use mergeIntoObservable with observables");
353
+ }
354
+ }
345
355
  beginBatch();
346
356
  for (let i = 0; i < sources.length; i++) {
347
- target = _mergeIntoObservable(
357
+ _mergeIntoObservable(
348
358
  target,
349
359
  sources[i],
350
360
  /*assign*/
351
- i < sources.length - 1
361
+ i < sources.length - 1,
362
+ 0
352
363
  );
353
364
  }
354
365
  endBatch();
355
366
  return target;
356
367
  }
357
- function _mergeIntoObservable(target, source, assign2) {
358
- var _a;
368
+ function _mergeIntoObservable(target, source, assign2, levelsDeep) {
359
369
  if (isObservable(source)) {
360
370
  source = source.peek();
361
371
  }
362
- const needsSet = isObservable(target);
363
- const targetValue = needsSet ? target.peek() : target;
372
+ const targetValue = target.peek();
364
373
  const isTargetArr = isArray(targetValue);
365
374
  const isTargetObj = !isTargetArr && isObject(targetValue);
366
- if (isTargetObj && isObject(source) && !isEmpty(targetValue) || isTargetArr && targetValue.length > 0) {
367
- const keys = Object.keys(source);
375
+ const isSourceMap = isMap(source);
376
+ const isSourceSet = isSet(source);
377
+ if (isSourceSet && isSet(targetValue)) {
378
+ target.set(/* @__PURE__ */ new Set([...source, ...targetValue]));
379
+ } else if (isTargetObj && isObject(source) && !isEmpty(targetValue) || isTargetArr && targetValue.length > 0) {
380
+ const keys = isSourceMap || isSourceSet ? Array.from(source.keys()) : Object.keys(source);
368
381
  for (let i = 0; i < keys.length; i++) {
369
382
  const key = keys[i];
370
- const sourceValue = source[key];
383
+ const sourceValue = isSourceSet ? key : isSourceMap ? source.get(key) : source[key];
371
384
  if (sourceValue === symbolDelete) {
372
- needsSet && ((_a = target[key]) == null ? void 0 : _a.delete) ? target[key].delete() : delete target[key];
385
+ target[key].delete();
373
386
  } else {
374
387
  const isObj = isObject(sourceValue);
375
388
  const isArr = !isObj && isArray(sourceValue);
376
389
  const targetChild = target[key];
377
- if ((isObj || isArr) && targetChild && (needsSet || !isEmpty(targetChild))) {
378
- if (!needsSet && (!targetChild || (isObj ? !isObject(targetChild) : !isArray(targetChild)))) {
379
- target[key] = assign2 ? isArr ? [...sourceValue] : { ...sourceValue } : sourceValue;
380
- } else {
381
- _mergeIntoObservable(targetChild, sourceValue);
382
- }
383
- } else {
384
- if (needsSet) {
390
+ if ((isObj || isArr) && targetChild) {
391
+ if (levelsDeep > 0 && isEmpty(sourceValue)) {
385
392
  targetChild.set(sourceValue);
386
- } else {
387
- const toSet = isObservable(sourceValue) ? sourceValue : isObject(sourceValue) ? { ...sourceValue } : isArray(sourceValue) ? [...sourceValue] : sourceValue;
388
- target[key] = toSet;
389
393
  }
394
+ _mergeIntoObservable(targetChild, sourceValue, false, levelsDeep + 1);
395
+ } else {
396
+ targetChild.set(sourceValue);
390
397
  }
391
398
  }
392
399
  }
393
400
  } else if (source !== void 0) {
394
- needsSet ? target.set(source) : target = assign2 ? isArray(source) ? [...source] : { ...source } : source;
401
+ target.set(source);
395
402
  }
396
403
  return target;
397
404
  }
@@ -446,6 +453,25 @@ function applyChanges(value, changes, applyPrevious) {
446
453
  }
447
454
  return value;
448
455
  }
456
+ function deepMerge(target, ...sources) {
457
+ const result = { ...target };
458
+ for (let i = 0; i < sources.length; i++) {
459
+ const obj2 = sources[i];
460
+ for (const key in obj2) {
461
+ if (hasOwnProperty.call(obj2, key)) {
462
+ if (obj2[key] instanceof Object && !isObservable(obj2[key]) && Object.keys(obj2[key]).length > 0) {
463
+ result[key] = deepMerge(
464
+ result[key] || (isArray(obj2[key]) ? [] : {}),
465
+ obj2[key]
466
+ );
467
+ } else {
468
+ result[key] = obj2[key];
469
+ }
470
+ }
471
+ }
472
+ }
473
+ return result;
474
+ }
449
475
 
450
476
  // src/batching.ts
451
477
  var timeout;
@@ -2315,6 +2341,7 @@ function runWithRetry(state, fn) {
2315
2341
  var internal = {
2316
2342
  createPreviousHandler,
2317
2343
  clone,
2344
+ deepMerge,
2318
2345
  ensureNodeValue,
2319
2346
  findIDKey,
2320
2347
  get,
package/index.mjs CHANGED
@@ -31,6 +31,9 @@ function isPromise(obj) {
31
31
  function isMap(obj) {
32
32
  return obj instanceof Map;
33
33
  }
34
+ function isSet(obj) {
35
+ return obj instanceof Set;
36
+ }
34
37
  function isNumber(obj) {
35
38
  const n = obj;
36
39
  return n - n < 1;
@@ -40,6 +43,8 @@ function isEmpty(obj) {
40
43
  return false;
41
44
  if (isArray(obj))
42
45
  return obj.length === 0;
46
+ if (isMap(obj) || isSet(obj))
47
+ return obj.size === 0;
43
48
  for (const key in obj) {
44
49
  if (hasOwnProperty.call(obj, key)) {
45
50
  return false;
@@ -303,13 +308,13 @@ function setAtPath(obj, path, pathTypes, value, mode, fullObj, restore) {
303
308
  }
304
309
  if (p === void 0) {
305
310
  if (mode === "merge") {
306
- obj = _mergeIntoObservable(obj, value);
311
+ obj = deepMerge(obj, value, false, 0);
307
312
  } else {
308
313
  obj = value;
309
314
  }
310
315
  } else {
311
316
  if (mode === "merge") {
312
- o[p] = _mergeIntoObservable(o[p], value);
317
+ o[p] = deepMerge(o[p], value, false, 0);
313
318
  } else if (isMap(o)) {
314
319
  o.set(p, value);
315
320
  } else {
@@ -340,56 +345,58 @@ function setInObservableAtPath(value$, path, pathTypes, value, mode) {
340
345
  }
341
346
  }
342
347
  function mergeIntoObservable(target, ...sources) {
348
+ if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") {
349
+ if (!isObservable(target)) {
350
+ console.error("[legend-state] should only use mergeIntoObservable with observables");
351
+ }
352
+ }
343
353
  beginBatch();
344
354
  for (let i = 0; i < sources.length; i++) {
345
- target = _mergeIntoObservable(
355
+ _mergeIntoObservable(
346
356
  target,
347
357
  sources[i],
348
358
  /*assign*/
349
- i < sources.length - 1
359
+ i < sources.length - 1,
360
+ 0
350
361
  );
351
362
  }
352
363
  endBatch();
353
364
  return target;
354
365
  }
355
- function _mergeIntoObservable(target, source, assign2) {
356
- var _a;
366
+ function _mergeIntoObservable(target, source, assign2, levelsDeep) {
357
367
  if (isObservable(source)) {
358
368
  source = source.peek();
359
369
  }
360
- const needsSet = isObservable(target);
361
- const targetValue = needsSet ? target.peek() : target;
370
+ const targetValue = target.peek();
362
371
  const isTargetArr = isArray(targetValue);
363
372
  const isTargetObj = !isTargetArr && isObject(targetValue);
364
- if (isTargetObj && isObject(source) && !isEmpty(targetValue) || isTargetArr && targetValue.length > 0) {
365
- const keys = Object.keys(source);
373
+ const isSourceMap = isMap(source);
374
+ const isSourceSet = isSet(source);
375
+ if (isSourceSet && isSet(targetValue)) {
376
+ target.set(/* @__PURE__ */ new Set([...source, ...targetValue]));
377
+ } else if (isTargetObj && isObject(source) && !isEmpty(targetValue) || isTargetArr && targetValue.length > 0) {
378
+ const keys = isSourceMap || isSourceSet ? Array.from(source.keys()) : Object.keys(source);
366
379
  for (let i = 0; i < keys.length; i++) {
367
380
  const key = keys[i];
368
- const sourceValue = source[key];
381
+ const sourceValue = isSourceSet ? key : isSourceMap ? source.get(key) : source[key];
369
382
  if (sourceValue === symbolDelete) {
370
- needsSet && ((_a = target[key]) == null ? void 0 : _a.delete) ? target[key].delete() : delete target[key];
383
+ target[key].delete();
371
384
  } else {
372
385
  const isObj = isObject(sourceValue);
373
386
  const isArr = !isObj && isArray(sourceValue);
374
387
  const targetChild = target[key];
375
- if ((isObj || isArr) && targetChild && (needsSet || !isEmpty(targetChild))) {
376
- if (!needsSet && (!targetChild || (isObj ? !isObject(targetChild) : !isArray(targetChild)))) {
377
- target[key] = assign2 ? isArr ? [...sourceValue] : { ...sourceValue } : sourceValue;
378
- } else {
379
- _mergeIntoObservable(targetChild, sourceValue);
380
- }
381
- } else {
382
- if (needsSet) {
388
+ if ((isObj || isArr) && targetChild) {
389
+ if (levelsDeep > 0 && isEmpty(sourceValue)) {
383
390
  targetChild.set(sourceValue);
384
- } else {
385
- const toSet = isObservable(sourceValue) ? sourceValue : isObject(sourceValue) ? { ...sourceValue } : isArray(sourceValue) ? [...sourceValue] : sourceValue;
386
- target[key] = toSet;
387
391
  }
392
+ _mergeIntoObservable(targetChild, sourceValue, false, levelsDeep + 1);
393
+ } else {
394
+ targetChild.set(sourceValue);
388
395
  }
389
396
  }
390
397
  }
391
398
  } else if (source !== void 0) {
392
- needsSet ? target.set(source) : target = assign2 ? isArray(source) ? [...source] : { ...source } : source;
399
+ target.set(source);
393
400
  }
394
401
  return target;
395
402
  }
@@ -444,6 +451,25 @@ function applyChanges(value, changes, applyPrevious) {
444
451
  }
445
452
  return value;
446
453
  }
454
+ function deepMerge(target, ...sources) {
455
+ const result = { ...target };
456
+ for (let i = 0; i < sources.length; i++) {
457
+ const obj2 = sources[i];
458
+ for (const key in obj2) {
459
+ if (hasOwnProperty.call(obj2, key)) {
460
+ if (obj2[key] instanceof Object && !isObservable(obj2[key]) && Object.keys(obj2[key]).length > 0) {
461
+ result[key] = deepMerge(
462
+ result[key] || (isArray(obj2[key]) ? [] : {}),
463
+ obj2[key]
464
+ );
465
+ } else {
466
+ result[key] = obj2[key];
467
+ }
468
+ }
469
+ }
470
+ }
471
+ return result;
472
+ }
447
473
 
448
474
  // src/batching.ts
449
475
  var timeout;
@@ -2313,6 +2339,7 @@ function runWithRetry(state, fn) {
2313
2339
  var internal = {
2314
2340
  createPreviousHandler,
2315
2341
  clone,
2342
+ deepMerge,
2316
2343
  ensureNodeValue,
2317
2344
  findIDKey,
2318
2345
  get,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/state",
3
- "version": "3.0.0-alpha.15",
3
+ "version": "3.0.0-alpha.16",
4
4
  "description": "legend-state",
5
5
  "sideEffects": false,
6
6
  "private": false,
@@ -123,11 +123,18 @@ function syncedCrud(props) {
123
123
  }
124
124
  } : void 0;
125
125
  const set = createFn || updateFn || deleteFn ? async (params) => {
126
- const { value, changes, update, retryAsCreate, valuePrevious, node } = params;
126
+ const { value, changes, update, retryAsCreate, node } = params;
127
127
  const creates = /* @__PURE__ */ new Map();
128
128
  const updates = /* @__PURE__ */ new Map();
129
129
  const deletes = /* @__PURE__ */ new Set();
130
- changes.forEach(({ path, prevAtPath, valueAtPath }) => {
130
+ const getUpdateValue = (itemValue, prev) => {
131
+ return updatePartial ? Object.assign(
132
+ sync.diffObjects(prev, itemValue),
133
+ itemValue[fieldId] ? { [fieldId]: itemValue[fieldId] } : {}
134
+ ) : itemValue;
135
+ };
136
+ changes.forEach((change) => {
137
+ const { path, prevAtPath, valueAtPath, pathTypes } = change;
131
138
  if (asType === "value") {
132
139
  if (value) {
133
140
  let id = value == null ? void 0 : value[fieldId];
@@ -140,12 +147,18 @@ function syncedCrud(props) {
140
147
  creates.set(id, value);
141
148
  } else if (path.length === 0) {
142
149
  if (valueAtPath) {
143
- updates.set(id, valueAtPath);
150
+ updates.set(id, getUpdateValue(valueAtPath, prevAtPath));
144
151
  } else if (prevAtPath) {
145
152
  deletes.add(prevAtPath == null ? void 0 : prevAtPath.id);
146
153
  }
147
- } else {
148
- updates.set(id, Object.assign(updates.get(id) || { id }, value));
154
+ } else if (!updates.has(id)) {
155
+ const previous = state.applyChanges(
156
+ clone(value),
157
+ changes,
158
+ /*applyPrevious*/
159
+ true
160
+ );
161
+ updates.set(id, getUpdateValue(value, previous));
149
162
  }
150
163
  } else {
151
164
  console.error("[legend-state]: added synced item without an id");
@@ -154,13 +167,22 @@ function syncedCrud(props) {
154
167
  deletes.add(prevAtPath);
155
168
  }
156
169
  } else {
157
- let itemsChanged = void 0;
170
+ let itemsChanged = [];
158
171
  if (path.length === 0) {
159
- itemsChanged = (asMap ? Array.from(valueAtPath.entries()) : Object.entries(valueAtPath)).filter(([key, value2]) => {
172
+ const changed = asMap ? Array.from(valueAtPath.entries()) : Object.entries(valueAtPath);
173
+ for (let i = 0; i < changed.length; i++) {
174
+ const [key, value2] = changed[i];
160
175
  const prev = asMap ? prevAtPath.get(key) : prevAtPath[key];
161
- const isDiff = !prevAtPath || !sync.deepEqual(value2, prev);
162
- return isDiff;
163
- });
176
+ if (state.isNullOrUndefined(value2) && !state.isNullOrUndefined(prev)) {
177
+ deletes.add(prev);
178
+ return false;
179
+ } else {
180
+ const isDiff = !prevAtPath || !sync.deepEqual(value2, prev);
181
+ if (isDiff) {
182
+ itemsChanged.push([getUpdateValue(value2, prev), prev]);
183
+ }
184
+ }
185
+ }
164
186
  } else {
165
187
  const itemKey = path[0];
166
188
  const itemValue = asMap ? value.get(itemKey) : value[itemKey];
@@ -169,36 +191,34 @@ function syncedCrud(props) {
169
191
  deletes.add(prevAtPath);
170
192
  }
171
193
  } else {
172
- itemsChanged = [[itemKey, itemValue]];
194
+ const previous = state.setAtPath(
195
+ clone(itemValue),
196
+ path.slice(1),
197
+ pathTypes.slice(1),
198
+ prevAtPath
199
+ );
200
+ itemsChanged = [[getUpdateValue(itemValue, previous), previous]];
173
201
  }
174
202
  }
175
- itemsChanged == null ? void 0 : itemsChanged.forEach(([itemKey, item]) => {
176
- if (state.isNullOrUndefined(item)) {
177
- const prev = valuePrevious[itemKey];
178
- if (prev) {
179
- deletes.add(prev);
203
+ itemsChanged == null ? void 0 : itemsChanged.forEach(([item, prev]) => {
204
+ const isCreate = fieldCreatedAt ? !item[fieldCreatedAt] && !(prev == null ? void 0 : prev[fieldCreatedAt]) : fieldUpdatedAt ? !item[fieldUpdatedAt] && !(prev == null ? void 0 : prev[fieldCreatedAt]) : state.isNullOrUndefined(prev);
205
+ if (isCreate) {
206
+ if (generateId) {
207
+ ensureId(item, fieldId, generateId);
208
+ }
209
+ if (!item.id) {
210
+ console.error("[legend-state]: added item without an id");
211
+ }
212
+ if (createFn) {
213
+ creates.set(item.id, item);
214
+ } else {
215
+ console.log("[legend-state] missing create function");
180
216
  }
181
217
  } else {
182
- const prev = asMap ? valuePrevious.get(itemKey) : valuePrevious[itemKey];
183
- const isCreate = fieldCreatedAt ? !item[fieldCreatedAt] : fieldUpdatedAt ? !item[fieldUpdatedAt] : state.isNullOrUndefined(prev);
184
- if (isCreate) {
185
- if (generateId) {
186
- ensureId(item, fieldId, generateId);
187
- }
188
- if (!item.id) {
189
- console.error("[legend-state]: added item without an id");
190
- }
191
- if (createFn) {
192
- creates.set(item.id, item);
193
- } else {
194
- console.log("[legend-state] missing create function");
195
- }
218
+ if (updateFn) {
219
+ updates.set(item.id, item);
196
220
  } else {
197
- if (updateFn) {
198
- updates.set(item.id, item);
199
- } else {
200
- console.log("[legend-state] missing update function");
201
- }
221
+ console.log("[legend-state] missing update function");
202
222
  }
203
223
  }
204
224
  });
@@ -256,10 +276,7 @@ function syncedCrud(props) {
256
276
  );
257
277
  }),
258
278
  ...Array.from(updates).map(async ([itemKey, itemValue]) => {
259
- const toSave = updatePartial ? Object.assign(
260
- sync.diffObjects(asType === "value" ? valuePrevious : valuePrevious[itemKey], itemValue),
261
- itemValue[fieldId] ? { [fieldId]: itemValue[fieldId] } : {}
262
- ) : itemValue;
279
+ const toSave = itemValue;
263
280
  const changed = await transformOut(toSave, transform == null ? void 0 : transform.save);
264
281
  if (Object.keys(changed).length > 0) {
265
282
  return updateFn(changed, params).then(
@@ -267,11 +284,11 @@ function syncedCrud(props) {
267
284
  );
268
285
  }
269
286
  }),
270
- ...Array.from(deletes).map((valuePrevious2) => {
287
+ ...Array.from(deletes).map((valuePrevious) => {
271
288
  if (deleteFn) {
272
- deleteFn(valuePrevious2, params);
289
+ deleteFn(valuePrevious, params);
273
290
  } else if (fieldDeleted && updateFn) {
274
- const valueId = valuePrevious2[fieldId];
291
+ const valueId = valuePrevious[fieldId];
275
292
  updateFn(
276
293
  { ...valueId ? { [fieldId]: valueId } : {}, [fieldDeleted]: true },
277
294
  params
@@ -1,4 +1,4 @@
1
- import { isPromise, isNullOrUndefined, isArray, isObservable, internal, getNodeValue } from '@legendapp/state';
1
+ import { isPromise, applyChanges, isNullOrUndefined, setAtPath, isArray, internal, isObservable, getNodeValue } from '@legendapp/state';
2
2
  import { synced, deepEqual, diffObjects } from '@legendapp/state/sync';
3
3
 
4
4
  // src/sync-plugins/crud.ts
@@ -121,11 +121,18 @@ function syncedCrud(props) {
121
121
  }
122
122
  } : void 0;
123
123
  const set = createFn || updateFn || deleteFn ? async (params) => {
124
- const { value, changes, update, retryAsCreate, valuePrevious, node } = params;
124
+ const { value, changes, update, retryAsCreate, node } = params;
125
125
  const creates = /* @__PURE__ */ new Map();
126
126
  const updates = /* @__PURE__ */ new Map();
127
127
  const deletes = /* @__PURE__ */ new Set();
128
- changes.forEach(({ path, prevAtPath, valueAtPath }) => {
128
+ const getUpdateValue = (itemValue, prev) => {
129
+ return updatePartial ? Object.assign(
130
+ diffObjects(prev, itemValue),
131
+ itemValue[fieldId] ? { [fieldId]: itemValue[fieldId] } : {}
132
+ ) : itemValue;
133
+ };
134
+ changes.forEach((change) => {
135
+ const { path, prevAtPath, valueAtPath, pathTypes } = change;
129
136
  if (asType === "value") {
130
137
  if (value) {
131
138
  let id = value == null ? void 0 : value[fieldId];
@@ -138,12 +145,18 @@ function syncedCrud(props) {
138
145
  creates.set(id, value);
139
146
  } else if (path.length === 0) {
140
147
  if (valueAtPath) {
141
- updates.set(id, valueAtPath);
148
+ updates.set(id, getUpdateValue(valueAtPath, prevAtPath));
142
149
  } else if (prevAtPath) {
143
150
  deletes.add(prevAtPath == null ? void 0 : prevAtPath.id);
144
151
  }
145
- } else {
146
- updates.set(id, Object.assign(updates.get(id) || { id }, value));
152
+ } else if (!updates.has(id)) {
153
+ const previous = applyChanges(
154
+ clone(value),
155
+ changes,
156
+ /*applyPrevious*/
157
+ true
158
+ );
159
+ updates.set(id, getUpdateValue(value, previous));
147
160
  }
148
161
  } else {
149
162
  console.error("[legend-state]: added synced item without an id");
@@ -152,13 +165,22 @@ function syncedCrud(props) {
152
165
  deletes.add(prevAtPath);
153
166
  }
154
167
  } else {
155
- let itemsChanged = void 0;
168
+ let itemsChanged = [];
156
169
  if (path.length === 0) {
157
- itemsChanged = (asMap ? Array.from(valueAtPath.entries()) : Object.entries(valueAtPath)).filter(([key, value2]) => {
170
+ const changed = asMap ? Array.from(valueAtPath.entries()) : Object.entries(valueAtPath);
171
+ for (let i = 0; i < changed.length; i++) {
172
+ const [key, value2] = changed[i];
158
173
  const prev = asMap ? prevAtPath.get(key) : prevAtPath[key];
159
- const isDiff = !prevAtPath || !deepEqual(value2, prev);
160
- return isDiff;
161
- });
174
+ if (isNullOrUndefined(value2) && !isNullOrUndefined(prev)) {
175
+ deletes.add(prev);
176
+ return false;
177
+ } else {
178
+ const isDiff = !prevAtPath || !deepEqual(value2, prev);
179
+ if (isDiff) {
180
+ itemsChanged.push([getUpdateValue(value2, prev), prev]);
181
+ }
182
+ }
183
+ }
162
184
  } else {
163
185
  const itemKey = path[0];
164
186
  const itemValue = asMap ? value.get(itemKey) : value[itemKey];
@@ -167,36 +189,34 @@ function syncedCrud(props) {
167
189
  deletes.add(prevAtPath);
168
190
  }
169
191
  } else {
170
- itemsChanged = [[itemKey, itemValue]];
192
+ const previous = setAtPath(
193
+ clone(itemValue),
194
+ path.slice(1),
195
+ pathTypes.slice(1),
196
+ prevAtPath
197
+ );
198
+ itemsChanged = [[getUpdateValue(itemValue, previous), previous]];
171
199
  }
172
200
  }
173
- itemsChanged == null ? void 0 : itemsChanged.forEach(([itemKey, item]) => {
174
- if (isNullOrUndefined(item)) {
175
- const prev = valuePrevious[itemKey];
176
- if (prev) {
177
- deletes.add(prev);
201
+ itemsChanged == null ? void 0 : itemsChanged.forEach(([item, prev]) => {
202
+ const isCreate = fieldCreatedAt ? !item[fieldCreatedAt] && !(prev == null ? void 0 : prev[fieldCreatedAt]) : fieldUpdatedAt ? !item[fieldUpdatedAt] && !(prev == null ? void 0 : prev[fieldCreatedAt]) : isNullOrUndefined(prev);
203
+ if (isCreate) {
204
+ if (generateId) {
205
+ ensureId(item, fieldId, generateId);
206
+ }
207
+ if (!item.id) {
208
+ console.error("[legend-state]: added item without an id");
209
+ }
210
+ if (createFn) {
211
+ creates.set(item.id, item);
212
+ } else {
213
+ console.log("[legend-state] missing create function");
178
214
  }
179
215
  } else {
180
- const prev = asMap ? valuePrevious.get(itemKey) : valuePrevious[itemKey];
181
- const isCreate = fieldCreatedAt ? !item[fieldCreatedAt] : fieldUpdatedAt ? !item[fieldUpdatedAt] : isNullOrUndefined(prev);
182
- if (isCreate) {
183
- if (generateId) {
184
- ensureId(item, fieldId, generateId);
185
- }
186
- if (!item.id) {
187
- console.error("[legend-state]: added item without an id");
188
- }
189
- if (createFn) {
190
- creates.set(item.id, item);
191
- } else {
192
- console.log("[legend-state] missing create function");
193
- }
216
+ if (updateFn) {
217
+ updates.set(item.id, item);
194
218
  } else {
195
- if (updateFn) {
196
- updates.set(item.id, item);
197
- } else {
198
- console.log("[legend-state] missing update function");
199
- }
219
+ console.log("[legend-state] missing update function");
200
220
  }
201
221
  }
202
222
  });
@@ -254,10 +274,7 @@ function syncedCrud(props) {
254
274
  );
255
275
  }),
256
276
  ...Array.from(updates).map(async ([itemKey, itemValue]) => {
257
- const toSave = updatePartial ? Object.assign(
258
- diffObjects(asType === "value" ? valuePrevious : valuePrevious[itemKey], itemValue),
259
- itemValue[fieldId] ? { [fieldId]: itemValue[fieldId] } : {}
260
- ) : itemValue;
277
+ const toSave = itemValue;
261
278
  const changed = await transformOut(toSave, transform == null ? void 0 : transform.save);
262
279
  if (Object.keys(changed).length > 0) {
263
280
  return updateFn(changed, params).then(
@@ -265,11 +282,11 @@ function syncedCrud(props) {
265
282
  );
266
283
  }
267
284
  }),
268
- ...Array.from(deletes).map((valuePrevious2) => {
285
+ ...Array.from(deletes).map((valuePrevious) => {
269
286
  if (deleteFn) {
270
- deleteFn(valuePrevious2, params);
287
+ deleteFn(valuePrevious, params);
271
288
  } else if (fieldDeleted && updateFn) {
272
- const valueId = valuePrevious2[fieldId];
289
+ const valueId = valuePrevious[fieldId];
273
290
  updateFn(
274
291
  { ...valueId ? { [fieldId]: valueId } : {}, [fieldDeleted]: true },
275
292
  params
@@ -216,7 +216,7 @@ function syncedKeel(props) {
216
216
  const create = createParam ? async (input, params) => {
217
217
  const { data, error } = await createParam(convertObjectToCreate(input));
218
218
  if (error) {
219
- handleSetError(error, params, "create");
219
+ await handleSetError(error, params, "create");
220
220
  }
221
221
  return data;
222
222
  } : void 0;
@@ -229,7 +229,7 @@ function syncedKeel(props) {
229
229
  if (!state.isEmpty(values)) {
230
230
  const { data, error } = await updateParam({ where: { id }, values });
231
231
  if (error) {
232
- handleSetError(error, params, "update");
232
+ await handleSetError(error, params, "update");
233
233
  }
234
234
  return data;
235
235
  }
@@ -237,7 +237,7 @@ function syncedKeel(props) {
237
237
  const deleteFn = deleteParam ? async (value, params) => {
238
238
  const { data, error } = await deleteParam({ id: value.id });
239
239
  if (error) {
240
- handleSetError(error, params, "delete");
240
+ await handleSetError(error, params, "delete");
241
241
  }
242
242
  return data;
243
243
  } : void 0;
@@ -210,7 +210,7 @@ function syncedKeel(props) {
210
210
  const create = createParam ? async (input, params) => {
211
211
  const { data, error } = await createParam(convertObjectToCreate(input));
212
212
  if (error) {
213
- handleSetError(error, params, "create");
213
+ await handleSetError(error, params, "create");
214
214
  }
215
215
  return data;
216
216
  } : void 0;
@@ -223,7 +223,7 @@ function syncedKeel(props) {
223
223
  if (!isEmpty(values)) {
224
224
  const { data, error } = await updateParam({ where: { id }, values });
225
225
  if (error) {
226
- handleSetError(error, params, "update");
226
+ await handleSetError(error, params, "update");
227
227
  }
228
228
  return data;
229
229
  }
@@ -231,7 +231,7 @@ function syncedKeel(props) {
231
231
  const deleteFn = deleteParam ? async (value, params) => {
232
232
  const { data, error } = await deleteParam({ id: value.id });
233
233
  if (error) {
234
- handleSetError(error, params, "delete");
234
+ await handleSetError(error, params, "delete");
235
235
  }
236
236
  return data;
237
237
  } : void 0;
package/sync.d.mts CHANGED
@@ -32,7 +32,6 @@ interface SyncedGetParams<T> extends SyncedGetSetSubscribeBaseParams<T> {
32
32
  options: SyncedOptions;
33
33
  }
34
34
  interface SyncedSetParams<T> extends Pick<SetParams<T>, 'changes' | 'value'>, SyncedGetSetSubscribeBaseParams<T> {
35
- valuePrevious: T;
36
35
  update: UpdateFn<T>;
37
36
  cancelRetry: () => void;
38
37
  retryNum: number;
@@ -108,7 +107,6 @@ interface ObservableSyncSetParams<T> {
108
107
  options: SyncedOptions<T>;
109
108
  changes: Change[];
110
109
  value: T;
111
- valuePrevious: T;
112
110
  }
113
111
  interface ObservableSyncFunctions<T = any> {
114
112
  get?(params: SyncedGetParams<T>): T | Promise<T>;
package/sync.d.ts CHANGED
@@ -32,7 +32,6 @@ interface SyncedGetParams<T> extends SyncedGetSetSubscribeBaseParams<T> {
32
32
  options: SyncedOptions;
33
33
  }
34
34
  interface SyncedSetParams<T> extends Pick<SetParams<T>, 'changes' | 'value'>, SyncedGetSetSubscribeBaseParams<T> {
35
- valuePrevious: T;
36
35
  update: UpdateFn<T>;
37
36
  cancelRetry: () => void;
38
37
  retryNum: number;
@@ -108,7 +107,6 @@ interface ObservableSyncSetParams<T> {
108
107
  options: SyncedOptions<T>;
109
108
  changes: Change[];
110
109
  value: T;
111
- valuePrevious: T;
112
110
  }
113
111
  interface ObservableSyncFunctions<T = any> {
114
112
  get?(params: SyncedGetParams<T>): T | Promise<T>;
package/sync.js CHANGED
@@ -137,7 +137,17 @@ function transformStringifyDates(...args) {
137
137
  }
138
138
  };
139
139
  }
140
- var { clone, getNode, getNodeValue, getValueAtPath, globalState, runWithRetry, symbolLinked, createPreviousHandler } = state.internal;
140
+ var {
141
+ clone,
142
+ deepMerge,
143
+ getNode,
144
+ getNodeValue,
145
+ getValueAtPath,
146
+ globalState,
147
+ runWithRetry,
148
+ symbolLinked,
149
+ createPreviousHandler
150
+ } = state.internal;
141
151
  var mapSyncPlugins = /* @__PURE__ */ new WeakMap();
142
152
  var allSyncStates = /* @__PURE__ */ new Map();
143
153
  var metadatas = /* @__PURE__ */ new WeakMap();
@@ -247,7 +257,6 @@ function mergeQueuedChanges(allChanges) {
247
257
  if (!previousByOptions.has(syncOptions)) {
248
258
  previousByOptions.set(syncOptions, getPrevious());
249
259
  }
250
- value.valuePrevious = previousByOptions.get(syncOptions);
251
260
  targetMap.set(syncOptions, value);
252
261
  }
253
262
  return Array.from(outRemote.values()).concat(Array.from(outLocal.values()));
@@ -360,8 +369,7 @@ async function prepChangeRemote(queuedChange) {
360
369
  localState,
361
370
  syncOptions,
362
371
  inRemoteChange,
363
- isApplyingPending,
364
- valuePrevious
372
+ isApplyingPending
365
373
  } = queuedChange;
366
374
  const persist = syncOptions.persist;
367
375
  const { config: configLocal } = parseLocalConfig(persist);
@@ -437,8 +445,7 @@ async function prepChangeRemote(queuedChange) {
437
445
  pathTypes,
438
446
  prevAtPath,
439
447
  valueAtPath: valueTransformed,
440
- pathStr,
441
- valuePrevious
448
+ pathStr
442
449
  });
443
450
  })
444
451
  );
@@ -485,7 +492,7 @@ async function doChangeRemote(changeInfo) {
485
492
  if (!changeInfo)
486
493
  return;
487
494
  const { queuedChange, changesRemote } = changeInfo;
488
- const { value$: obs$, syncState: syncState2, localState, syncOptions, valuePrevious: previous } = queuedChange;
495
+ const { value$: obs$, syncState: syncState2, localState, syncOptions } = queuedChange;
489
496
  const { pluginPersist } = localState;
490
497
  const node = getNode(obs$);
491
498
  const state$ = node.state;
@@ -505,11 +512,6 @@ async function doChangeRemote(changeInfo) {
505
512
  if (!state.isNullOrUndefined(pendingAtPath)) {
506
513
  const { p } = pendingAtPath;
507
514
  change.prevAtPath = p;
508
- if (key) {
509
- state.setAtPath(change.valuePrevious, change.path, change.pathTypes, p, "set");
510
- } else {
511
- change.valuePrevious = p;
512
- }
513
515
  }
514
516
  });
515
517
  }
@@ -539,14 +541,13 @@ async function doChangeRemote(changeInfo) {
539
541
  value$: obs$,
540
542
  changes: changesRemote,
541
543
  value,
542
- valuePrevious: previous,
543
544
  onError,
544
545
  update: (params) => {
545
546
  if (updateResult) {
546
547
  const { value: value2, lastSync, mode } = params;
547
548
  updateResult = {
548
549
  lastSync: Math.max(updateResult.lastSync || 0, lastSync || 0),
549
- value: state.mergeIntoObservable(updateResult.value, value2),
550
+ value: deepMerge(updateResult.value, value2),
550
551
  mode
551
552
  };
552
553
  } else {
@@ -703,25 +704,6 @@ async function loadLocal(value$, syncOptions, syncState$, localState) {
703
704
  }
704
705
  syncState$.isPersistLoaded.set(!(syncStateValue.numPendingLocalLoads > 0));
705
706
  }
706
- function deepMerge(target, ...sources) {
707
- const result = { ...target };
708
- for (let i = 0; i < sources.length; i++) {
709
- const obj2 = sources[i];
710
- for (const key in obj2) {
711
- if (state.hasOwnProperty.call(obj2, key)) {
712
- if (obj2[key] instanceof Object && !state.isObservable(obj2[key]) && Object.keys(obj2[key]).length > 0) {
713
- result[key] = deepMerge(
714
- result[key] || (state.isArray(obj2[key]) ? [] : {}),
715
- obj2[key]
716
- );
717
- } else {
718
- result[key] = obj2[key];
719
- }
720
- }
721
- }
722
- }
723
- return result;
724
- }
725
707
  function syncObservable(obs$, syncOptionsOrSynced) {
726
708
  let syncOptions = syncOptionsOrSynced;
727
709
  if (state.isFunction(syncOptions)) {
package/sync.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { isObject, isDate, isNullOrUndefined, isString, endBatch, beginBatch, isFunction, syncState, when, linked, internal, observable, isPromise, mergeIntoObservable, hasOwnProperty, isObservable, isArray, isEmpty, whenReady, shouldIgnoreUnobserved, constructObjectWithPath, setAtPath } from '@legendapp/state';
1
+ import { isObject, isDate, isNullOrUndefined, isString, endBatch, beginBatch, isFunction, syncState, when, linked, internal, observable, isPromise, mergeIntoObservable, isEmpty, whenReady, shouldIgnoreUnobserved, constructObjectWithPath, setAtPath, isArray } from '@legendapp/state';
2
2
 
3
3
  // src/sync/configureObservableSync.ts
4
4
  var observableSyncConfiguration = {};
@@ -135,7 +135,17 @@ function transformStringifyDates(...args) {
135
135
  }
136
136
  };
137
137
  }
138
- var { clone, getNode, getNodeValue, getValueAtPath, globalState, runWithRetry, symbolLinked, createPreviousHandler } = internal;
138
+ var {
139
+ clone,
140
+ deepMerge,
141
+ getNode,
142
+ getNodeValue,
143
+ getValueAtPath,
144
+ globalState,
145
+ runWithRetry,
146
+ symbolLinked,
147
+ createPreviousHandler
148
+ } = internal;
139
149
  var mapSyncPlugins = /* @__PURE__ */ new WeakMap();
140
150
  var allSyncStates = /* @__PURE__ */ new Map();
141
151
  var metadatas = /* @__PURE__ */ new WeakMap();
@@ -245,7 +255,6 @@ function mergeQueuedChanges(allChanges) {
245
255
  if (!previousByOptions.has(syncOptions)) {
246
256
  previousByOptions.set(syncOptions, getPrevious());
247
257
  }
248
- value.valuePrevious = previousByOptions.get(syncOptions);
249
258
  targetMap.set(syncOptions, value);
250
259
  }
251
260
  return Array.from(outRemote.values()).concat(Array.from(outLocal.values()));
@@ -358,8 +367,7 @@ async function prepChangeRemote(queuedChange) {
358
367
  localState,
359
368
  syncOptions,
360
369
  inRemoteChange,
361
- isApplyingPending,
362
- valuePrevious
370
+ isApplyingPending
363
371
  } = queuedChange;
364
372
  const persist = syncOptions.persist;
365
373
  const { config: configLocal } = parseLocalConfig(persist);
@@ -435,8 +443,7 @@ async function prepChangeRemote(queuedChange) {
435
443
  pathTypes,
436
444
  prevAtPath,
437
445
  valueAtPath: valueTransformed,
438
- pathStr,
439
- valuePrevious
446
+ pathStr
440
447
  });
441
448
  })
442
449
  );
@@ -483,7 +490,7 @@ async function doChangeRemote(changeInfo) {
483
490
  if (!changeInfo)
484
491
  return;
485
492
  const { queuedChange, changesRemote } = changeInfo;
486
- const { value$: obs$, syncState: syncState2, localState, syncOptions, valuePrevious: previous } = queuedChange;
493
+ const { value$: obs$, syncState: syncState2, localState, syncOptions } = queuedChange;
487
494
  const { pluginPersist } = localState;
488
495
  const node = getNode(obs$);
489
496
  const state$ = node.state;
@@ -503,11 +510,6 @@ async function doChangeRemote(changeInfo) {
503
510
  if (!isNullOrUndefined(pendingAtPath)) {
504
511
  const { p } = pendingAtPath;
505
512
  change.prevAtPath = p;
506
- if (key) {
507
- setAtPath(change.valuePrevious, change.path, change.pathTypes, p, "set");
508
- } else {
509
- change.valuePrevious = p;
510
- }
511
513
  }
512
514
  });
513
515
  }
@@ -537,14 +539,13 @@ async function doChangeRemote(changeInfo) {
537
539
  value$: obs$,
538
540
  changes: changesRemote,
539
541
  value,
540
- valuePrevious: previous,
541
542
  onError,
542
543
  update: (params) => {
543
544
  if (updateResult) {
544
545
  const { value: value2, lastSync, mode } = params;
545
546
  updateResult = {
546
547
  lastSync: Math.max(updateResult.lastSync || 0, lastSync || 0),
547
- value: mergeIntoObservable(updateResult.value, value2),
548
+ value: deepMerge(updateResult.value, value2),
548
549
  mode
549
550
  };
550
551
  } else {
@@ -701,25 +702,6 @@ async function loadLocal(value$, syncOptions, syncState$, localState) {
701
702
  }
702
703
  syncState$.isPersistLoaded.set(!(syncStateValue.numPendingLocalLoads > 0));
703
704
  }
704
- function deepMerge(target, ...sources) {
705
- const result = { ...target };
706
- for (let i = 0; i < sources.length; i++) {
707
- const obj2 = sources[i];
708
- for (const key in obj2) {
709
- if (hasOwnProperty.call(obj2, key)) {
710
- if (obj2[key] instanceof Object && !isObservable(obj2[key]) && Object.keys(obj2[key]).length > 0) {
711
- result[key] = deepMerge(
712
- result[key] || (isArray(obj2[key]) ? [] : {}),
713
- obj2[key]
714
- );
715
- } else {
716
- result[key] = obj2[key];
717
- }
718
- }
719
- }
720
- }
721
- return result;
722
- }
723
705
  function syncObservable(obs$, syncOptionsOrSynced) {
724
706
  let syncOptions = syncOptionsOrSynced;
725
707
  if (isFunction(syncOptions)) {