@legendapp/state 3.0.0-beta.45 → 3.0.0-beta.47

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.js CHANGED
@@ -1229,6 +1229,16 @@ function observe(selectorOrRun, reactionOrOptions, options) {
1229
1229
  };
1230
1230
  }
1231
1231
 
1232
+ // src/toPrimitive.ts
1233
+ function toPrimitive(node, hint) {
1234
+ const value = peek(node);
1235
+ if (value === null || value === void 0 || isPrimitive(value)) {
1236
+ return value;
1237
+ }
1238
+ const method = hint === "string" ? isFunction(value.toString) ? value.toString : value.valueOf : isFunction(value.valueOf) ? value.valueOf : value.toString;
1239
+ return isFunction(method) ? method.call(value) : value;
1240
+ }
1241
+
1232
1242
  // src/when.ts
1233
1243
  function _when(predicate, effect, checkReady) {
1234
1244
  if (isPromise(predicate)) {
@@ -1546,9 +1556,7 @@ var proxyHandler = {
1546
1556
  get(node, p, receiver) {
1547
1557
  var _a, _b, _c;
1548
1558
  if (p === symbolToPrimitive) {
1549
- throw new Error(
1550
- process.env.NODE_ENV === "development" ? "[legend-state] observable should not be used as a primitive. You may have forgotten to use .get() or .peek() to get the value of the observable." : "[legend-state] observable is not a primitive."
1551
- );
1559
+ return (hint) => toPrimitive(node, hint);
1552
1560
  }
1553
1561
  if (p === symbolGetNode) {
1554
1562
  return node;
@@ -2432,6 +2440,12 @@ Object.defineProperty(ObservablePrimitiveClass.prototype, symbolGetNode, {
2432
2440
  return this._node;
2433
2441
  }
2434
2442
  });
2443
+ Object.defineProperty(ObservablePrimitiveClass.prototype, symbolToPrimitive, {
2444
+ configurable: true,
2445
+ value(hint) {
2446
+ return toPrimitive(this._node, hint);
2447
+ }
2448
+ });
2435
2449
  ObservablePrimitiveClass.prototype.toggle = function() {
2436
2450
  const value = this.peek();
2437
2451
  if (value === void 0 || value === null || isBoolean(value)) {
package/index.mjs CHANGED
@@ -1227,6 +1227,16 @@ function observe(selectorOrRun, reactionOrOptions, options) {
1227
1227
  };
1228
1228
  }
1229
1229
 
1230
+ // src/toPrimitive.ts
1231
+ function toPrimitive(node, hint) {
1232
+ const value = peek(node);
1233
+ if (value === null || value === void 0 || isPrimitive(value)) {
1234
+ return value;
1235
+ }
1236
+ const method = hint === "string" ? isFunction(value.toString) ? value.toString : value.valueOf : isFunction(value.valueOf) ? value.valueOf : value.toString;
1237
+ return isFunction(method) ? method.call(value) : value;
1238
+ }
1239
+
1230
1240
  // src/when.ts
1231
1241
  function _when(predicate, effect, checkReady) {
1232
1242
  if (isPromise(predicate)) {
@@ -1544,9 +1554,7 @@ var proxyHandler = {
1544
1554
  get(node, p, receiver) {
1545
1555
  var _a, _b, _c;
1546
1556
  if (p === symbolToPrimitive) {
1547
- throw new Error(
1548
- process.env.NODE_ENV === "development" ? "[legend-state] observable should not be used as a primitive. You may have forgotten to use .get() or .peek() to get the value of the observable." : "[legend-state] observable is not a primitive."
1549
- );
1557
+ return (hint) => toPrimitive(node, hint);
1550
1558
  }
1551
1559
  if (p === symbolGetNode) {
1552
1560
  return node;
@@ -2430,6 +2438,12 @@ Object.defineProperty(ObservablePrimitiveClass.prototype, symbolGetNode, {
2430
2438
  return this._node;
2431
2439
  }
2432
2440
  });
2441
+ Object.defineProperty(ObservablePrimitiveClass.prototype, symbolToPrimitive, {
2442
+ configurable: true,
2443
+ value(hint) {
2444
+ return toPrimitive(this._node, hint);
2445
+ }
2446
+ });
2433
2447
  ObservablePrimitiveClass.prototype.toggle = function() {
2434
2448
  const value = this.peek();
2435
2449
  if (value === void 0 || value === null || isBoolean(value)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/state",
3
- "version": "3.0.0-beta.45",
3
+ "version": "3.0.0-beta.47",
4
4
  "description": "legend-state",
5
5
  "sideEffects": false,
6
6
  "private": false,
@@ -48,35 +48,77 @@ function arrayToRecord(arr, keyField) {
48
48
  }
49
49
  return record;
50
50
  }
51
- function retrySet(params, retry, action, itemKey, itemValue, change, queuedRetries, itemValueFull, actionFn, saveResult) {
52
- if (action === "delete") {
53
- if (queuedRetries.create.has(itemKey)) {
54
- queuedRetries.create.delete(itemKey);
55
- }
56
- if (queuedRetries.update.has(itemKey)) {
57
- queuedRetries.update.delete(itemKey);
58
- }
59
- } else {
60
- if (queuedRetries.delete.has(itemKey)) {
61
- queuedRetries.delete.delete(itemKey);
62
- }
51
+ function mergeQueuedRetryValue(currentValue, nextValue) {
52
+ if (currentValue && nextValue && typeof currentValue === "object" && typeof nextValue === "object") {
53
+ Object.assign(currentValue, nextValue);
54
+ return currentValue;
55
+ }
56
+ return nextValue;
57
+ }
58
+ function queueRetryValue(queuedRetries, itemKey, itemValue, itemValueFull) {
59
+ const queuedRetry = queuedRetries.get(itemKey);
60
+ if (queuedRetry) {
61
+ queuedRetry.value = mergeQueuedRetryValue(queuedRetry.value, itemValue);
62
+ queuedRetry.fullValue = mergeQueuedRetryValue(queuedRetry.fullValue, itemValueFull);
63
+ return queuedRetry;
63
64
  }
64
- const queuedRetry = queuedRetries[action].get(itemKey);
65
+ const nextQueuedRetry = {
66
+ value: itemValue,
67
+ fullValue: itemValueFull
68
+ };
69
+ queuedRetries.set(itemKey, nextQueuedRetry);
70
+ return nextQueuedRetry;
71
+ }
72
+ function cancelQueuedRetry(queuedRetries, itemKey) {
73
+ const queuedRetry = queuedRetries.get(itemKey);
65
74
  if (queuedRetry) {
66
- itemValue = Object.assign(queuedRetry, itemValue);
75
+ queuedRetry.cancelled = true;
76
+ queuedRetries.delete(itemKey);
77
+ }
78
+ }
79
+ function retrySet(params, retry, action, itemKey, itemValue, change, queuedRetries, itemValueFull, actionFn, saveResult, options) {
80
+ if (action === "delete") {
81
+ cancelQueuedRetry(queuedRetries.create, itemKey);
82
+ cancelQueuedRetry(queuedRetries.update, itemKey);
83
+ } else {
84
+ cancelQueuedRetry(queuedRetries.delete, itemKey);
67
85
  }
68
- queuedRetries[action].set(itemKey, itemValue);
69
- const clonedValue = clone(itemValueFull);
86
+ const queuedRetry = queueRetryValue(queuedRetries[action], itemKey, itemValue, itemValueFull);
70
87
  const paramsWithChanges = { ...params, changes: [change] };
71
- return runWithRetry(
72
- paramsWithChanges,
73
- retry,
74
- "create_" + itemKey,
75
- () => actionFn(itemValue, paramsWithChanges).then((result) => {
76
- queuedRetries[action].delete(itemKey);
77
- return saveResult(itemKey, clonedValue, result, true, change);
78
- })
79
- );
88
+ const runAttempt = () => {
89
+ var _a;
90
+ if (queuedRetry.cancelled) {
91
+ return Promise.resolve(void 0);
92
+ }
93
+ const refreshed = (_a = options == null ? void 0 : options.refreshValue) == null ? void 0 : _a.call(options);
94
+ const runWithRefreshedValue = (retryValue) => {
95
+ if (retryValue === false || queuedRetry.cancelled) {
96
+ queuedRetry.cancelled = true;
97
+ queuedRetries[action].delete(itemKey);
98
+ return Promise.resolve(void 0);
99
+ }
100
+ if (retryValue) {
101
+ queuedRetry.value = retryValue.value;
102
+ queuedRetry.fullValue = retryValue.fullValue;
103
+ }
104
+ const attemptValue = queuedRetry.value;
105
+ const attemptFullValue = clone(queuedRetry.fullValue);
106
+ return actionFn(attemptValue, paramsWithChanges).catch((error) => {
107
+ var _a2;
108
+ (_a2 = options == null ? void 0 : options.onAttemptFailure) == null ? void 0 : _a2.call(options);
109
+ throw error;
110
+ }).then(async (result) => {
111
+ queuedRetries[action].delete(itemKey);
112
+ if (queuedRetry.cancelled) {
113
+ return result;
114
+ }
115
+ await saveResult(itemKey, attemptFullValue, result, true, change);
116
+ return result;
117
+ });
118
+ };
119
+ return state.isPromise(refreshed) ? refreshed.then(runWithRefreshedValue) : runWithRefreshedValue(refreshed);
120
+ };
121
+ return runWithRetry(paramsWithChanges, retry, action + "_" + itemKey, runAttempt);
80
122
  }
81
123
  function syncedCrud(props) {
82
124
  const {
@@ -102,12 +144,53 @@ function syncedCrud(props) {
102
144
  ...rest
103
145
  } = props;
104
146
  const fieldId = fieldIdProp || "id";
105
- const pendingCreates = /* @__PURE__ */ new Set();
147
+ const pendingCreates = /* @__PURE__ */ new Map();
106
148
  const queuedRetries = {
107
149
  create: /* @__PURE__ */ new Map(),
108
150
  update: /* @__PURE__ */ new Map(),
109
151
  delete: /* @__PURE__ */ new Map()
110
152
  };
153
+ const beginPendingCreate = (id, change) => {
154
+ const pendingCreate = pendingCreates.get(id) || { hasFailed: false };
155
+ pendingCreate.hasFailed = false;
156
+ pendingCreate.change || (pendingCreate.change = change);
157
+ pendingCreates.set(id, pendingCreate);
158
+ return pendingCreate;
159
+ };
160
+ const clearPendingCreate = (id) => {
161
+ if (!state.isNullOrUndefined(id)) {
162
+ pendingCreates.delete(id);
163
+ cancelQueuedRetry(queuedRetries.create, id);
164
+ }
165
+ };
166
+ const cancelFailedPendingCreate = (id) => {
167
+ var _a;
168
+ if (!state.isNullOrUndefined(id) && ((_a = pendingCreates.get(id)) == null ? void 0 : _a.hasFailed)) {
169
+ clearPendingCreate(id);
170
+ return true;
171
+ }
172
+ return false;
173
+ };
174
+ const clearPendingCreateForValue = (value) => {
175
+ if (value && typeof value === "object") {
176
+ clearPendingCreate(value[fieldId]);
177
+ }
178
+ };
179
+ const clearPendingCreatesForValues = (values) => {
180
+ if (values == null ? void 0 : values.length) {
181
+ for (let i = 0; i < values.length; i++) {
182
+ clearPendingCreateForValue(values[i]);
183
+ }
184
+ }
185
+ };
186
+ const markPendingCreateFailed = (itemKey) => {
187
+ const pendingCreate = pendingCreates.get(itemKey);
188
+ if (pendingCreate && !pendingCreate.hasFailed) {
189
+ pendingCreate.hasFailed = true;
190
+ cancelQueuedRetry(queuedRetries.update, itemKey);
191
+ cancelQueuedRetry(queuedRetries.delete, itemKey);
192
+ }
193
+ };
111
194
  let asType = props.as;
112
195
  if (!asType) {
113
196
  asType = getFn ? "value" : "object";
@@ -167,6 +250,7 @@ function syncedCrud(props) {
167
250
  };
168
251
  const processResults = (data) => {
169
252
  data || (data = []);
253
+ clearPendingCreatesForValues(data);
170
254
  if (fieldUpdatedAt) {
171
255
  const newLastSync = computeLastSync(data, fieldUpdatedAt, fieldCreatedAt);
172
256
  if (newLastSync && newLastSync !== lastSync) {
@@ -183,6 +267,7 @@ function syncedCrud(props) {
183
267
  } else if (getFn) {
184
268
  const dataPromise = getFn(getParams);
185
269
  const processData = (data) => {
270
+ clearPendingCreateForValue(data);
186
271
  let transformed = data;
187
272
  if (data) {
188
273
  const newLastSync = data[fieldUpdatedAt] || data[fieldCreatedAt];
@@ -217,6 +302,28 @@ function syncedCrud(props) {
217
302
  !state.isNullOrUndefined(itemValue[fieldId]) ? { [fieldId]: itemValue[fieldId] } : {}
218
303
  ) : itemValue;
219
304
  };
305
+ const getCurrentValueAtKey = (itemKey) => {
306
+ const currentPeeked = state.getNodeValue(node);
307
+ if (asType === "value") {
308
+ return currentPeeked;
309
+ }
310
+ if (asType === "array") {
311
+ return state.isArray(currentPeeked) ? currentPeeked.find((value2) => (value2 == null ? void 0 : value2[fieldId]) === itemKey) : void 0;
312
+ }
313
+ if (asType === "Map") {
314
+ return currentPeeked == null ? void 0 : currentPeeked.get(itemKey);
315
+ }
316
+ return currentPeeked == null ? void 0 : currentPeeked[itemKey];
317
+ };
318
+ const isFailedCreateReadyToRetry = (itemKey) => {
319
+ const pendingCreate = pendingCreates.get(itemKey);
320
+ return !!(pendingCreate == null ? void 0 : pendingCreate.hasFailed) && !pendingCreate.promise;
321
+ };
322
+ const addCreate = (id, itemValue, change) => {
323
+ const pendingCreate = beginPendingCreate(id, change);
324
+ changesById.set(id, pendingCreate.change);
325
+ creates.set(id, itemValue);
326
+ };
220
327
  changes.forEach((change) => {
221
328
  var _a, _b;
222
329
  const { path, prevAtPath, valueAtPath, pathTypes } = change;
@@ -229,12 +336,14 @@ function syncedCrud(props) {
229
336
  const id = value[fieldId];
230
337
  if (!state.isNullOrUndefined(id)) {
231
338
  changesById.set(id, change);
232
- if (pendingCreates.has(id)) {
339
+ if (isFailedCreateReadyToRetry(id)) {
340
+ isCreate = true;
341
+ } else if (pendingCreates.has(id)) {
233
342
  isCreate = false;
234
343
  }
235
344
  if (isCreate || retryAsCreate) {
236
345
  if (createFn) {
237
- creates.set(id, value);
346
+ addCreate(id, value, change);
238
347
  } else {
239
348
  console.warn("[legend-state] missing create function");
240
349
  }
@@ -259,8 +368,11 @@ function syncedCrud(props) {
259
368
  console.error("[legend-state]: added synced item without an id");
260
369
  }
261
370
  } else if (path.length === 0) {
262
- deletes.add(prevAtPath);
263
- changesById.set(prevAtPath[fieldId], change);
371
+ const id = prevAtPath == null ? void 0 : prevAtPath[fieldId];
372
+ if (!cancelFailedPendingCreate(id)) {
373
+ deletes.add(prevAtPath);
374
+ changesById.set(prevAtPath[fieldId], change);
375
+ }
264
376
  }
265
377
  } else {
266
378
  let itemsChanged = [];
@@ -283,7 +395,11 @@ function syncedCrud(props) {
283
395
  for (let i = 0; i < lengthPrev; i++) {
284
396
  const key = keysPrev[i];
285
397
  if (!keysSet.has(key)) {
286
- deletes.add(prevAsObject[key]);
398
+ const prevValue = prevAsObject[key];
399
+ const id = prevValue == null ? void 0 : prevValue[fieldId];
400
+ if (!cancelFailedPendingCreate(id)) {
401
+ deletes.add(prevValue);
402
+ }
287
403
  }
288
404
  }
289
405
  for (let i = 0; i < length; i++) {
@@ -305,8 +421,11 @@ function syncedCrud(props) {
305
421
  const itemValue = asMap ? value.get(itemKey) : value[itemKey];
306
422
  if (!itemValue) {
307
423
  if (path.length === 1 && prevAtPath) {
308
- deletes.add(prevAtPath);
309
- changesById.set(prevAtPath[fieldId], change);
424
+ const id = prevAtPath[fieldId];
425
+ if (!cancelFailedPendingCreate(id)) {
426
+ deletes.add(prevAtPath);
427
+ changesById.set(id, change);
428
+ }
310
429
  }
311
430
  } else {
312
431
  if (generateId) {
@@ -322,27 +441,26 @@ function syncedCrud(props) {
322
441
  }
323
442
  }
324
443
  itemsChanged == null ? void 0 : itemsChanged.forEach(([item, prev, fullValue]) => {
325
- const isCreate = !pendingCreates.has(item[fieldId]) && (fieldCreatedAt ? !item[fieldCreatedAt] && !(prev == null ? void 0 : prev[fieldCreatedAt]) : fieldUpdatedAt ? !item[fieldUpdatedAt] && !(prev == null ? void 0 : prev[fieldCreatedAt]) : state.isNullOrUndefined(prev));
444
+ const id = item[fieldId];
445
+ const isFailedCreateRetry = isFailedCreateReadyToRetry(id);
446
+ const isCreate = isFailedCreateRetry || !pendingCreates.has(id) && (fieldCreatedAt ? !item[fieldCreatedAt] && !(prev == null ? void 0 : prev[fieldCreatedAt]) : fieldUpdatedAt ? !item[fieldUpdatedAt] && !(prev == null ? void 0 : prev[fieldCreatedAt]) : state.isNullOrUndefined(prev));
326
447
  if (isCreate) {
327
- if (!item[fieldId]) {
448
+ if (!id) {
328
449
  console.error("[legend-state]: added item without an id");
329
450
  }
330
451
  if (createFn) {
331
- const id = item[fieldId];
332
- changesById.set(id, change);
333
- pendingCreates.add(id);
334
- creates.set(id, item);
452
+ addCreate(id, isFailedCreateRetry ? fullValue : item, change);
335
453
  } else {
336
454
  console.warn("[legend-state] missing create function");
337
455
  }
338
456
  } else {
339
457
  if (updateFn) {
340
- const id = item[fieldId];
341
- changesById.set(id, change);
342
- updates.set(id, updates.has(id) ? Object.assign(updates.get(id), item) : item);
458
+ const id2 = item[fieldId];
459
+ changesById.set(id2, change);
460
+ updates.set(id2, updates.has(id2) ? Object.assign(updates.get(id2), item) : item);
343
461
  updateFullValues.set(
344
- id,
345
- updateFullValues.has(id) ? Object.assign(updateFullValues.get(id), fullValue) : fullValue
462
+ id2,
463
+ updateFullValues.has(id2) ? Object.assign(updateFullValues.get(id2), fullValue) : fullValue
346
464
  );
347
465
  } else {
348
466
  console.warn("[legend-state] missing update function");
@@ -413,7 +531,28 @@ function syncedCrud(props) {
413
531
  await waitForSet(waitForSetParam, changes, itemValue, { type: "create" });
414
532
  }
415
533
  const createObj = await transformOut(itemValue, transform == null ? void 0 : transform.save);
416
- return retrySet(
534
+ const pendingCreate = pendingCreates.get(itemKey) || { hasFailed: false };
535
+ pendingCreates.set(itemKey, pendingCreate);
536
+ const refreshCreateValue = async () => {
537
+ const currentPendingCreate = pendingCreates.get(itemKey);
538
+ if (!currentPendingCreate) {
539
+ return false;
540
+ }
541
+ if (!currentPendingCreate.hasFailed) {
542
+ return void 0;
543
+ }
544
+ const currentValue = getCurrentValueAtKey(itemKey);
545
+ if (state.isNullOrUndefined(currentValue)) {
546
+ clearPendingCreate(itemKey);
547
+ return false;
548
+ }
549
+ const nextCreateObj = await transformOut(
550
+ clone(currentValue),
551
+ transform == null ? void 0 : transform.save
552
+ );
553
+ return { value: nextCreateObj, fullValue: nextCreateObj };
554
+ };
555
+ const createPromise = retrySet(
417
556
  params,
418
557
  retry,
419
558
  "create",
@@ -423,13 +562,31 @@ function syncedCrud(props) {
423
562
  queuedRetries,
424
563
  createObj,
425
564
  createFn,
426
- saveResult
427
- ).then(() => {
428
- pendingCreates.delete(itemKey);
565
+ saveResult,
566
+ {
567
+ onAttemptFailure: () => markPendingCreateFailed(itemKey),
568
+ refreshValue: refreshCreateValue
569
+ }
570
+ );
571
+ pendingCreate.promise = createPromise;
572
+ return createPromise.then((result) => {
573
+ if (pendingCreates.get(itemKey) === pendingCreate) {
574
+ pendingCreates.delete(itemKey);
575
+ }
576
+ return result;
577
+ }).catch((error) => {
578
+ if (pendingCreates.get(itemKey) === pendingCreate) {
579
+ pendingCreate.promise = void 0;
580
+ }
581
+ throw error;
429
582
  });
430
583
  }),
431
584
  // Handle updates
432
585
  ...Array.from(updates).map(async ([itemKey, itemValue]) => {
586
+ const pendingCreate = pendingCreates.get(itemKey);
587
+ if ((pendingCreate == null ? void 0 : pendingCreate.hasFailed) && pendingCreate.promise) {
588
+ await pendingCreate.promise;
589
+ }
433
590
  const fullValue = updateFullValues.get(itemKey);
434
591
  if (waitForSetParam) {
435
592
  await waitForSet(waitForSetParam, changes, fullValue, { type: "update" });
@@ -512,6 +669,7 @@ function syncedCrud(props) {
512
669
  paramsForUpdate.lastSync = newLastSync;
513
670
  }
514
671
  const rowsTransformed = (transform == null ? void 0 : transform.load) ? await transformRows(rows) : rows;
672
+ clearPendingCreatesForValues(rows);
515
673
  paramsForUpdate.value = resultsToOutType(rowsTransformed);
516
674
  params.update(paramsForUpdate);
517
675
  }
@@ -46,35 +46,77 @@ function arrayToRecord(arr, keyField) {
46
46
  }
47
47
  return record;
48
48
  }
49
- function retrySet(params, retry, action, itemKey, itemValue, change, queuedRetries, itemValueFull, actionFn, saveResult) {
50
- if (action === "delete") {
51
- if (queuedRetries.create.has(itemKey)) {
52
- queuedRetries.create.delete(itemKey);
53
- }
54
- if (queuedRetries.update.has(itemKey)) {
55
- queuedRetries.update.delete(itemKey);
56
- }
57
- } else {
58
- if (queuedRetries.delete.has(itemKey)) {
59
- queuedRetries.delete.delete(itemKey);
60
- }
49
+ function mergeQueuedRetryValue(currentValue, nextValue) {
50
+ if (currentValue && nextValue && typeof currentValue === "object" && typeof nextValue === "object") {
51
+ Object.assign(currentValue, nextValue);
52
+ return currentValue;
53
+ }
54
+ return nextValue;
55
+ }
56
+ function queueRetryValue(queuedRetries, itemKey, itemValue, itemValueFull) {
57
+ const queuedRetry = queuedRetries.get(itemKey);
58
+ if (queuedRetry) {
59
+ queuedRetry.value = mergeQueuedRetryValue(queuedRetry.value, itemValue);
60
+ queuedRetry.fullValue = mergeQueuedRetryValue(queuedRetry.fullValue, itemValueFull);
61
+ return queuedRetry;
61
62
  }
62
- const queuedRetry = queuedRetries[action].get(itemKey);
63
+ const nextQueuedRetry = {
64
+ value: itemValue,
65
+ fullValue: itemValueFull
66
+ };
67
+ queuedRetries.set(itemKey, nextQueuedRetry);
68
+ return nextQueuedRetry;
69
+ }
70
+ function cancelQueuedRetry(queuedRetries, itemKey) {
71
+ const queuedRetry = queuedRetries.get(itemKey);
63
72
  if (queuedRetry) {
64
- itemValue = Object.assign(queuedRetry, itemValue);
73
+ queuedRetry.cancelled = true;
74
+ queuedRetries.delete(itemKey);
75
+ }
76
+ }
77
+ function retrySet(params, retry, action, itemKey, itemValue, change, queuedRetries, itemValueFull, actionFn, saveResult, options) {
78
+ if (action === "delete") {
79
+ cancelQueuedRetry(queuedRetries.create, itemKey);
80
+ cancelQueuedRetry(queuedRetries.update, itemKey);
81
+ } else {
82
+ cancelQueuedRetry(queuedRetries.delete, itemKey);
65
83
  }
66
- queuedRetries[action].set(itemKey, itemValue);
67
- const clonedValue = clone(itemValueFull);
84
+ const queuedRetry = queueRetryValue(queuedRetries[action], itemKey, itemValue, itemValueFull);
68
85
  const paramsWithChanges = { ...params, changes: [change] };
69
- return runWithRetry(
70
- paramsWithChanges,
71
- retry,
72
- "create_" + itemKey,
73
- () => actionFn(itemValue, paramsWithChanges).then((result) => {
74
- queuedRetries[action].delete(itemKey);
75
- return saveResult(itemKey, clonedValue, result, true, change);
76
- })
77
- );
86
+ const runAttempt = () => {
87
+ var _a;
88
+ if (queuedRetry.cancelled) {
89
+ return Promise.resolve(void 0);
90
+ }
91
+ const refreshed = (_a = options == null ? void 0 : options.refreshValue) == null ? void 0 : _a.call(options);
92
+ const runWithRefreshedValue = (retryValue) => {
93
+ if (retryValue === false || queuedRetry.cancelled) {
94
+ queuedRetry.cancelled = true;
95
+ queuedRetries[action].delete(itemKey);
96
+ return Promise.resolve(void 0);
97
+ }
98
+ if (retryValue) {
99
+ queuedRetry.value = retryValue.value;
100
+ queuedRetry.fullValue = retryValue.fullValue;
101
+ }
102
+ const attemptValue = queuedRetry.value;
103
+ const attemptFullValue = clone(queuedRetry.fullValue);
104
+ return actionFn(attemptValue, paramsWithChanges).catch((error) => {
105
+ var _a2;
106
+ (_a2 = options == null ? void 0 : options.onAttemptFailure) == null ? void 0 : _a2.call(options);
107
+ throw error;
108
+ }).then(async (result) => {
109
+ queuedRetries[action].delete(itemKey);
110
+ if (queuedRetry.cancelled) {
111
+ return result;
112
+ }
113
+ await saveResult(itemKey, attemptFullValue, result, true, change);
114
+ return result;
115
+ });
116
+ };
117
+ return isPromise(refreshed) ? refreshed.then(runWithRefreshedValue) : runWithRefreshedValue(refreshed);
118
+ };
119
+ return runWithRetry(paramsWithChanges, retry, action + "_" + itemKey, runAttempt);
78
120
  }
79
121
  function syncedCrud(props) {
80
122
  const {
@@ -100,12 +142,53 @@ function syncedCrud(props) {
100
142
  ...rest
101
143
  } = props;
102
144
  const fieldId = fieldIdProp || "id";
103
- const pendingCreates = /* @__PURE__ */ new Set();
145
+ const pendingCreates = /* @__PURE__ */ new Map();
104
146
  const queuedRetries = {
105
147
  create: /* @__PURE__ */ new Map(),
106
148
  update: /* @__PURE__ */ new Map(),
107
149
  delete: /* @__PURE__ */ new Map()
108
150
  };
151
+ const beginPendingCreate = (id, change) => {
152
+ const pendingCreate = pendingCreates.get(id) || { hasFailed: false };
153
+ pendingCreate.hasFailed = false;
154
+ pendingCreate.change || (pendingCreate.change = change);
155
+ pendingCreates.set(id, pendingCreate);
156
+ return pendingCreate;
157
+ };
158
+ const clearPendingCreate = (id) => {
159
+ if (!isNullOrUndefined(id)) {
160
+ pendingCreates.delete(id);
161
+ cancelQueuedRetry(queuedRetries.create, id);
162
+ }
163
+ };
164
+ const cancelFailedPendingCreate = (id) => {
165
+ var _a;
166
+ if (!isNullOrUndefined(id) && ((_a = pendingCreates.get(id)) == null ? void 0 : _a.hasFailed)) {
167
+ clearPendingCreate(id);
168
+ return true;
169
+ }
170
+ return false;
171
+ };
172
+ const clearPendingCreateForValue = (value) => {
173
+ if (value && typeof value === "object") {
174
+ clearPendingCreate(value[fieldId]);
175
+ }
176
+ };
177
+ const clearPendingCreatesForValues = (values) => {
178
+ if (values == null ? void 0 : values.length) {
179
+ for (let i = 0; i < values.length; i++) {
180
+ clearPendingCreateForValue(values[i]);
181
+ }
182
+ }
183
+ };
184
+ const markPendingCreateFailed = (itemKey) => {
185
+ const pendingCreate = pendingCreates.get(itemKey);
186
+ if (pendingCreate && !pendingCreate.hasFailed) {
187
+ pendingCreate.hasFailed = true;
188
+ cancelQueuedRetry(queuedRetries.update, itemKey);
189
+ cancelQueuedRetry(queuedRetries.delete, itemKey);
190
+ }
191
+ };
109
192
  let asType = props.as;
110
193
  if (!asType) {
111
194
  asType = getFn ? "value" : "object";
@@ -165,6 +248,7 @@ function syncedCrud(props) {
165
248
  };
166
249
  const processResults = (data) => {
167
250
  data || (data = []);
251
+ clearPendingCreatesForValues(data);
168
252
  if (fieldUpdatedAt) {
169
253
  const newLastSync = computeLastSync(data, fieldUpdatedAt, fieldCreatedAt);
170
254
  if (newLastSync && newLastSync !== lastSync) {
@@ -181,6 +265,7 @@ function syncedCrud(props) {
181
265
  } else if (getFn) {
182
266
  const dataPromise = getFn(getParams);
183
267
  const processData = (data) => {
268
+ clearPendingCreateForValue(data);
184
269
  let transformed = data;
185
270
  if (data) {
186
271
  const newLastSync = data[fieldUpdatedAt] || data[fieldCreatedAt];
@@ -215,6 +300,28 @@ function syncedCrud(props) {
215
300
  !isNullOrUndefined(itemValue[fieldId]) ? { [fieldId]: itemValue[fieldId] } : {}
216
301
  ) : itemValue;
217
302
  };
303
+ const getCurrentValueAtKey = (itemKey) => {
304
+ const currentPeeked = getNodeValue(node);
305
+ if (asType === "value") {
306
+ return currentPeeked;
307
+ }
308
+ if (asType === "array") {
309
+ return isArray(currentPeeked) ? currentPeeked.find((value2) => (value2 == null ? void 0 : value2[fieldId]) === itemKey) : void 0;
310
+ }
311
+ if (asType === "Map") {
312
+ return currentPeeked == null ? void 0 : currentPeeked.get(itemKey);
313
+ }
314
+ return currentPeeked == null ? void 0 : currentPeeked[itemKey];
315
+ };
316
+ const isFailedCreateReadyToRetry = (itemKey) => {
317
+ const pendingCreate = pendingCreates.get(itemKey);
318
+ return !!(pendingCreate == null ? void 0 : pendingCreate.hasFailed) && !pendingCreate.promise;
319
+ };
320
+ const addCreate = (id, itemValue, change) => {
321
+ const pendingCreate = beginPendingCreate(id, change);
322
+ changesById.set(id, pendingCreate.change);
323
+ creates.set(id, itemValue);
324
+ };
218
325
  changes.forEach((change) => {
219
326
  var _a, _b;
220
327
  const { path, prevAtPath, valueAtPath, pathTypes } = change;
@@ -227,12 +334,14 @@ function syncedCrud(props) {
227
334
  const id = value[fieldId];
228
335
  if (!isNullOrUndefined(id)) {
229
336
  changesById.set(id, change);
230
- if (pendingCreates.has(id)) {
337
+ if (isFailedCreateReadyToRetry(id)) {
338
+ isCreate = true;
339
+ } else if (pendingCreates.has(id)) {
231
340
  isCreate = false;
232
341
  }
233
342
  if (isCreate || retryAsCreate) {
234
343
  if (createFn) {
235
- creates.set(id, value);
344
+ addCreate(id, value, change);
236
345
  } else {
237
346
  console.warn("[legend-state] missing create function");
238
347
  }
@@ -257,8 +366,11 @@ function syncedCrud(props) {
257
366
  console.error("[legend-state]: added synced item without an id");
258
367
  }
259
368
  } else if (path.length === 0) {
260
- deletes.add(prevAtPath);
261
- changesById.set(prevAtPath[fieldId], change);
369
+ const id = prevAtPath == null ? void 0 : prevAtPath[fieldId];
370
+ if (!cancelFailedPendingCreate(id)) {
371
+ deletes.add(prevAtPath);
372
+ changesById.set(prevAtPath[fieldId], change);
373
+ }
262
374
  }
263
375
  } else {
264
376
  let itemsChanged = [];
@@ -281,7 +393,11 @@ function syncedCrud(props) {
281
393
  for (let i = 0; i < lengthPrev; i++) {
282
394
  const key = keysPrev[i];
283
395
  if (!keysSet.has(key)) {
284
- deletes.add(prevAsObject[key]);
396
+ const prevValue = prevAsObject[key];
397
+ const id = prevValue == null ? void 0 : prevValue[fieldId];
398
+ if (!cancelFailedPendingCreate(id)) {
399
+ deletes.add(prevValue);
400
+ }
285
401
  }
286
402
  }
287
403
  for (let i = 0; i < length; i++) {
@@ -303,8 +419,11 @@ function syncedCrud(props) {
303
419
  const itemValue = asMap ? value.get(itemKey) : value[itemKey];
304
420
  if (!itemValue) {
305
421
  if (path.length === 1 && prevAtPath) {
306
- deletes.add(prevAtPath);
307
- changesById.set(prevAtPath[fieldId], change);
422
+ const id = prevAtPath[fieldId];
423
+ if (!cancelFailedPendingCreate(id)) {
424
+ deletes.add(prevAtPath);
425
+ changesById.set(id, change);
426
+ }
308
427
  }
309
428
  } else {
310
429
  if (generateId) {
@@ -320,27 +439,26 @@ function syncedCrud(props) {
320
439
  }
321
440
  }
322
441
  itemsChanged == null ? void 0 : itemsChanged.forEach(([item, prev, fullValue]) => {
323
- const isCreate = !pendingCreates.has(item[fieldId]) && (fieldCreatedAt ? !item[fieldCreatedAt] && !(prev == null ? void 0 : prev[fieldCreatedAt]) : fieldUpdatedAt ? !item[fieldUpdatedAt] && !(prev == null ? void 0 : prev[fieldCreatedAt]) : isNullOrUndefined(prev));
442
+ const id = item[fieldId];
443
+ const isFailedCreateRetry = isFailedCreateReadyToRetry(id);
444
+ const isCreate = isFailedCreateRetry || !pendingCreates.has(id) && (fieldCreatedAt ? !item[fieldCreatedAt] && !(prev == null ? void 0 : prev[fieldCreatedAt]) : fieldUpdatedAt ? !item[fieldUpdatedAt] && !(prev == null ? void 0 : prev[fieldCreatedAt]) : isNullOrUndefined(prev));
324
445
  if (isCreate) {
325
- if (!item[fieldId]) {
446
+ if (!id) {
326
447
  console.error("[legend-state]: added item without an id");
327
448
  }
328
449
  if (createFn) {
329
- const id = item[fieldId];
330
- changesById.set(id, change);
331
- pendingCreates.add(id);
332
- creates.set(id, item);
450
+ addCreate(id, isFailedCreateRetry ? fullValue : item, change);
333
451
  } else {
334
452
  console.warn("[legend-state] missing create function");
335
453
  }
336
454
  } else {
337
455
  if (updateFn) {
338
- const id = item[fieldId];
339
- changesById.set(id, change);
340
- updates.set(id, updates.has(id) ? Object.assign(updates.get(id), item) : item);
456
+ const id2 = item[fieldId];
457
+ changesById.set(id2, change);
458
+ updates.set(id2, updates.has(id2) ? Object.assign(updates.get(id2), item) : item);
341
459
  updateFullValues.set(
342
- id,
343
- updateFullValues.has(id) ? Object.assign(updateFullValues.get(id), fullValue) : fullValue
460
+ id2,
461
+ updateFullValues.has(id2) ? Object.assign(updateFullValues.get(id2), fullValue) : fullValue
344
462
  );
345
463
  } else {
346
464
  console.warn("[legend-state] missing update function");
@@ -411,7 +529,28 @@ function syncedCrud(props) {
411
529
  await waitForSet(waitForSetParam, changes, itemValue, { type: "create" });
412
530
  }
413
531
  const createObj = await transformOut(itemValue, transform == null ? void 0 : transform.save);
414
- return retrySet(
532
+ const pendingCreate = pendingCreates.get(itemKey) || { hasFailed: false };
533
+ pendingCreates.set(itemKey, pendingCreate);
534
+ const refreshCreateValue = async () => {
535
+ const currentPendingCreate = pendingCreates.get(itemKey);
536
+ if (!currentPendingCreate) {
537
+ return false;
538
+ }
539
+ if (!currentPendingCreate.hasFailed) {
540
+ return void 0;
541
+ }
542
+ const currentValue = getCurrentValueAtKey(itemKey);
543
+ if (isNullOrUndefined(currentValue)) {
544
+ clearPendingCreate(itemKey);
545
+ return false;
546
+ }
547
+ const nextCreateObj = await transformOut(
548
+ clone(currentValue),
549
+ transform == null ? void 0 : transform.save
550
+ );
551
+ return { value: nextCreateObj, fullValue: nextCreateObj };
552
+ };
553
+ const createPromise = retrySet(
415
554
  params,
416
555
  retry,
417
556
  "create",
@@ -421,13 +560,31 @@ function syncedCrud(props) {
421
560
  queuedRetries,
422
561
  createObj,
423
562
  createFn,
424
- saveResult
425
- ).then(() => {
426
- pendingCreates.delete(itemKey);
563
+ saveResult,
564
+ {
565
+ onAttemptFailure: () => markPendingCreateFailed(itemKey),
566
+ refreshValue: refreshCreateValue
567
+ }
568
+ );
569
+ pendingCreate.promise = createPromise;
570
+ return createPromise.then((result) => {
571
+ if (pendingCreates.get(itemKey) === pendingCreate) {
572
+ pendingCreates.delete(itemKey);
573
+ }
574
+ return result;
575
+ }).catch((error) => {
576
+ if (pendingCreates.get(itemKey) === pendingCreate) {
577
+ pendingCreate.promise = void 0;
578
+ }
579
+ throw error;
427
580
  });
428
581
  }),
429
582
  // Handle updates
430
583
  ...Array.from(updates).map(async ([itemKey, itemValue]) => {
584
+ const pendingCreate = pendingCreates.get(itemKey);
585
+ if ((pendingCreate == null ? void 0 : pendingCreate.hasFailed) && pendingCreate.promise) {
586
+ await pendingCreate.promise;
587
+ }
431
588
  const fullValue = updateFullValues.get(itemKey);
432
589
  if (waitForSetParam) {
433
590
  await waitForSet(waitForSetParam, changes, fullValue, { type: "update" });
@@ -510,6 +667,7 @@ function syncedCrud(props) {
510
667
  paramsForUpdate.lastSync = newLastSync;
511
668
  }
512
669
  const rowsTransformed = (transform == null ? void 0 : transform.load) ? await transformRows(rows) : rows;
670
+ clearPendingCreatesForValues(rows);
513
671
  paramsForUpdate.value = resultsToOutType(rowsTransformed);
514
672
  params.update(paramsForUpdate);
515
673
  }