@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 +17 -3
- package/index.mjs +17 -3
- package/package.json +1 -1
- package/sync-plugins/crud.js +206 -48
- package/sync-plugins/crud.mjs +206 -48
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
|
-
|
|
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
|
-
|
|
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
package/sync-plugins/crud.js
CHANGED
|
@@ -48,35 +48,77 @@ function arrayToRecord(arr, keyField) {
|
|
|
48
48
|
}
|
|
49
49
|
return record;
|
|
50
50
|
}
|
|
51
|
-
function
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
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
|
-
|
|
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]
|
|
69
|
-
const clonedValue = clone(itemValueFull);
|
|
86
|
+
const queuedRetry = queueRetryValue(queuedRetries[action], itemKey, itemValue, itemValueFull);
|
|
70
87
|
const paramsWithChanges = { ...params, changes: [change] };
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
263
|
-
|
|
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
|
-
|
|
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
|
-
|
|
309
|
-
|
|
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
|
|
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 (!
|
|
448
|
+
if (!id) {
|
|
328
449
|
console.error("[legend-state]: added item without an id");
|
|
329
450
|
}
|
|
330
451
|
if (createFn) {
|
|
331
|
-
|
|
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
|
|
341
|
-
changesById.set(
|
|
342
|
-
updates.set(
|
|
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
|
-
|
|
345
|
-
updateFullValues.has(
|
|
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
|
-
|
|
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
|
-
|
|
428
|
-
|
|
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
|
}
|
package/sync-plugins/crud.mjs
CHANGED
|
@@ -46,35 +46,77 @@ function arrayToRecord(arr, keyField) {
|
|
|
46
46
|
}
|
|
47
47
|
return record;
|
|
48
48
|
}
|
|
49
|
-
function
|
|
50
|
-
if (
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
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
|
-
|
|
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]
|
|
67
|
-
const clonedValue = clone(itemValueFull);
|
|
84
|
+
const queuedRetry = queueRetryValue(queuedRetries[action], itemKey, itemValue, itemValueFull);
|
|
68
85
|
const paramsWithChanges = { ...params, changes: [change] };
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
261
|
-
|
|
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
|
-
|
|
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
|
-
|
|
307
|
-
|
|
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
|
|
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 (!
|
|
446
|
+
if (!id) {
|
|
326
447
|
console.error("[legend-state]: added item without an id");
|
|
327
448
|
}
|
|
328
449
|
if (createFn) {
|
|
329
|
-
|
|
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
|
|
339
|
-
changesById.set(
|
|
340
|
-
updates.set(
|
|
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
|
-
|
|
343
|
-
updateFullValues.has(
|
|
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
|
-
|
|
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
|
-
|
|
426
|
-
|
|
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
|
}
|