@spoosh/plugin-optimistic 0.7.2 → 0.8.1
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/README.md +40 -39
- package/dist/index.d.mts +103 -103
- package/dist/index.d.ts +103 -103
- package/dist/index.js +162 -225
- package/dist/index.mjs +160 -223
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -25,84 +25,60 @@ __export(src_exports, {
|
|
|
25
25
|
module.exports = __toCommonJS(src_exports);
|
|
26
26
|
|
|
27
27
|
// src/plugin.ts
|
|
28
|
-
var
|
|
28
|
+
var import_core2 = require("@spoosh/core");
|
|
29
29
|
var import_plugin_invalidation = require("@spoosh/plugin-invalidation");
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
...state,
|
|
33
|
-
WHERE(predicate) {
|
|
34
|
-
return createBuilder({ ...state, where: predicate });
|
|
35
|
-
},
|
|
36
|
-
UPDATE_CACHE(updater) {
|
|
37
|
-
return createBuilder({ ...state, updater });
|
|
38
|
-
},
|
|
39
|
-
ON_SUCCESS() {
|
|
40
|
-
return createBuilder({ ...state, timing: "onSuccess" });
|
|
41
|
-
},
|
|
42
|
-
NO_ROLLBACK() {
|
|
43
|
-
return createBuilder({ ...state, rollbackOnError: false });
|
|
44
|
-
},
|
|
45
|
-
ON_ERROR(callback) {
|
|
46
|
-
return createBuilder({ ...state, onError: callback });
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
function createOptimisticProxy() {
|
|
51
|
-
const createMethodsProxy = (path) => ({
|
|
52
|
-
GET: () => createBuilder({
|
|
53
|
-
path,
|
|
54
|
-
method: "GET",
|
|
55
|
-
timing: "immediate",
|
|
56
|
-
rollbackOnError: true
|
|
57
|
-
})
|
|
58
|
-
});
|
|
59
|
-
return ((path) => createMethodsProxy(path));
|
|
60
|
-
}
|
|
30
|
+
|
|
31
|
+
// src/utils/path.ts
|
|
61
32
|
function isParameterSegment(segment) {
|
|
62
33
|
return segment.startsWith(":");
|
|
63
34
|
}
|
|
64
|
-
function pathMatchesPattern(
|
|
65
|
-
const
|
|
35
|
+
function pathMatchesPattern(resolvedPath, pattern) {
|
|
36
|
+
const resolvedSegments = resolvedPath.split("/").filter(Boolean);
|
|
66
37
|
const patternSegments = pattern.split("/").filter(Boolean);
|
|
67
|
-
if (
|
|
68
|
-
return { matches: false, params: {}
|
|
38
|
+
if (resolvedSegments.length !== patternSegments.length) {
|
|
39
|
+
return { matches: false, params: {} };
|
|
69
40
|
}
|
|
70
41
|
const params = {};
|
|
71
|
-
const paramMapping = {};
|
|
72
42
|
for (let i = 0; i < patternSegments.length; i++) {
|
|
73
43
|
const patternSeg = patternSegments[i];
|
|
74
|
-
const
|
|
44
|
+
const resolvedSeg = resolvedSegments[i];
|
|
75
45
|
if (isParameterSegment(patternSeg)) {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
params[targetParamName] = actualSeg;
|
|
83
|
-
} else if (isParameterSegment(actualSeg)) {
|
|
84
|
-
continue;
|
|
85
|
-
} else if (patternSeg !== actualSeg) {
|
|
86
|
-
return { matches: false, params: {}, paramMapping: {} };
|
|
46
|
+
const paramName = patternSeg.slice(1);
|
|
47
|
+
params[paramName] = resolvedSeg;
|
|
48
|
+
} else if (patternSeg !== resolvedSeg) {
|
|
49
|
+
return { matches: false, params: {} };
|
|
87
50
|
}
|
|
88
51
|
}
|
|
89
|
-
return { matches: true, params
|
|
90
|
-
}
|
|
91
|
-
function hasPatternParams(path) {
|
|
92
|
-
return path.split("/").some(isParameterSegment);
|
|
52
|
+
return { matches: true, params };
|
|
93
53
|
}
|
|
94
|
-
|
|
54
|
+
|
|
55
|
+
// src/utils/cache-key.ts
|
|
56
|
+
var import_core = require("@spoosh/core");
|
|
57
|
+
function formatCacheKeyForTrace(key) {
|
|
58
|
+
const resolvedPath = (0, import_core.generateSelfTagFromKey)(key);
|
|
59
|
+
if (!resolvedPath) return "unknown";
|
|
95
60
|
try {
|
|
96
61
|
const parsed = JSON.parse(key);
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
62
|
+
const opts = parsed.options ?? parsed.pageRequest;
|
|
63
|
+
const query = opts?.query;
|
|
64
|
+
if (!query || Object.keys(query).length === 0) {
|
|
65
|
+
return resolvedPath;
|
|
100
66
|
}
|
|
101
|
-
|
|
67
|
+
const queryString = Object.entries(query).map(
|
|
68
|
+
([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`
|
|
69
|
+
).join("&");
|
|
70
|
+
return `${resolvedPath}?${queryString}`;
|
|
102
71
|
} catch {
|
|
103
|
-
return
|
|
72
|
+
return resolvedPath;
|
|
104
73
|
}
|
|
105
74
|
}
|
|
75
|
+
function stringifyParams(params) {
|
|
76
|
+
const result = {};
|
|
77
|
+
for (const [key, value] of Object.entries(params)) {
|
|
78
|
+
result[key] = String(value);
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
106
82
|
function extractOptionsFromKey(key) {
|
|
107
83
|
try {
|
|
108
84
|
const parsed = JSON.parse(key);
|
|
@@ -113,103 +89,117 @@ function extractOptionsFromKey(key) {
|
|
|
113
89
|
result.query = opts.query;
|
|
114
90
|
}
|
|
115
91
|
if (opts.params) {
|
|
116
|
-
result.params = opts.params;
|
|
92
|
+
result.params = stringifyParams(opts.params);
|
|
117
93
|
}
|
|
118
94
|
return Object.keys(result).length > 0 ? result : null;
|
|
119
95
|
} catch {
|
|
120
96
|
return null;
|
|
121
97
|
}
|
|
122
98
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
99
|
+
|
|
100
|
+
// src/plugin.ts
|
|
101
|
+
var import_core3 = require("@spoosh/core");
|
|
102
|
+
|
|
103
|
+
// src/builder/index.ts
|
|
104
|
+
function createBuilder(state, isConfirmed = false) {
|
|
105
|
+
return {
|
|
106
|
+
...state,
|
|
107
|
+
filter(predicate) {
|
|
108
|
+
return createBuilder({ ...state, filter: predicate }, isConfirmed);
|
|
109
|
+
},
|
|
110
|
+
set(updater) {
|
|
111
|
+
if (isConfirmed) {
|
|
112
|
+
return createBuilder(
|
|
113
|
+
{
|
|
114
|
+
...state,
|
|
115
|
+
confirmedUpdater: updater
|
|
116
|
+
},
|
|
117
|
+
isConfirmed
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
return createBuilder(
|
|
121
|
+
{
|
|
122
|
+
...state,
|
|
123
|
+
immediateUpdater: updater
|
|
124
|
+
},
|
|
125
|
+
isConfirmed
|
|
126
|
+
);
|
|
127
|
+
},
|
|
128
|
+
confirmed() {
|
|
129
|
+
return createBuilder(state, true);
|
|
130
|
+
},
|
|
131
|
+
disableRollback() {
|
|
132
|
+
return createBuilder({ ...state, rollbackOnError: false }, isConfirmed);
|
|
133
|
+
},
|
|
134
|
+
onError(callback) {
|
|
135
|
+
return createBuilder({ ...state, onError: callback }, isConfirmed);
|
|
134
136
|
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function createCacheProxy() {
|
|
140
|
+
return ((path) => createBuilder({
|
|
141
|
+
path,
|
|
142
|
+
rollbackOnError: true
|
|
143
|
+
}));
|
|
137
144
|
}
|
|
145
|
+
|
|
146
|
+
// src/plugin.ts
|
|
138
147
|
function resolveOptimisticTargets(context) {
|
|
139
148
|
const pluginOptions = context.pluginOptions;
|
|
140
149
|
if (!pluginOptions?.optimistic) return [];
|
|
141
|
-
const
|
|
142
|
-
const result = pluginOptions.optimistic(
|
|
150
|
+
const cacheProxy = createCacheProxy();
|
|
151
|
+
const result = pluginOptions.optimistic(cacheProxy);
|
|
143
152
|
const targets = Array.isArray(result) ? result : [result];
|
|
144
153
|
return targets;
|
|
145
154
|
}
|
|
146
|
-
function getMatchingEntries(stateManager, targetPath
|
|
155
|
+
function getMatchingEntries(stateManager, targetPath) {
|
|
147
156
|
const results = [];
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
targetPath
|
|
157
|
-
);
|
|
158
|
-
if (matches) {
|
|
159
|
-
results.push({ key, entry, extractedParams: params, paramMapping });
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
} else {
|
|
163
|
-
const allEntries = stateManager.getAllCacheEntries();
|
|
164
|
-
for (const { key, entry } of allEntries) {
|
|
165
|
-
if (!key.includes(`"method":"${targetMethod}"`)) continue;
|
|
166
|
-
const actualPath = extractPathFromKey(key);
|
|
167
|
-
if (!actualPath) continue;
|
|
168
|
-
if (actualPath === targetPath) {
|
|
169
|
-
results.push({ key, entry, extractedParams: {}, paramMapping: {} });
|
|
170
|
-
} else if (hasPatternParams(actualPath)) {
|
|
171
|
-
const { matches, params, paramMapping } = pathMatchesPattern(
|
|
172
|
-
targetPath,
|
|
173
|
-
actualPath
|
|
174
|
-
);
|
|
175
|
-
if (matches) {
|
|
176
|
-
results.push({ key, entry, extractedParams: params, paramMapping });
|
|
177
|
-
}
|
|
178
|
-
}
|
|
157
|
+
const allEntries = stateManager.getAllCacheEntries();
|
|
158
|
+
for (const { key, entry } of allEntries) {
|
|
159
|
+
if (!key.includes(`"method":"GET"`)) continue;
|
|
160
|
+
const resolvedPath = (0, import_core3.generateSelfTagFromKey)(key);
|
|
161
|
+
if (!resolvedPath) continue;
|
|
162
|
+
const { matches, params } = pathMatchesPattern(resolvedPath, targetPath);
|
|
163
|
+
if (matches) {
|
|
164
|
+
results.push({ key, entry, extractedParams: params });
|
|
179
165
|
}
|
|
180
166
|
}
|
|
181
167
|
return results;
|
|
182
168
|
}
|
|
183
|
-
function
|
|
184
|
-
if (!target.updater) return [];
|
|
169
|
+
function applyUpdate(stateManager, target, updater, response, t) {
|
|
185
170
|
const snapshots = [];
|
|
186
|
-
const matchingEntries = getMatchingEntries(
|
|
187
|
-
|
|
188
|
-
target.path
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
for (const { key, entry, extractedParams
|
|
192
|
-
if (target.
|
|
171
|
+
const matchingEntries = getMatchingEntries(stateManager, target.path);
|
|
172
|
+
if (matchingEntries.length === 0) {
|
|
173
|
+
t?.skip(`Skipped ${target.path} (no cache entry)`);
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
for (const { key, entry, extractedParams } of matchingEntries) {
|
|
177
|
+
if (target.filter) {
|
|
193
178
|
const options = extractOptionsFromKey(key) ?? {};
|
|
194
|
-
const mappedParams = mapParamsToTargetNames(
|
|
195
|
-
options.params,
|
|
196
|
-
paramMapping
|
|
197
|
-
);
|
|
198
179
|
const mergedOptions = {
|
|
199
180
|
...options,
|
|
200
181
|
params: {
|
|
201
|
-
...
|
|
202
|
-
...
|
|
182
|
+
...options.params,
|
|
183
|
+
...extractedParams
|
|
203
184
|
}
|
|
204
185
|
};
|
|
205
|
-
|
|
186
|
+
try {
|
|
187
|
+
if (!target.filter(mergedOptions)) {
|
|
188
|
+
t?.skip(
|
|
189
|
+
`Skipped ${formatCacheKeyForTrace(key)} (filter not matched)`
|
|
190
|
+
);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
} catch {
|
|
194
|
+
t?.skip(`Skipped ${formatCacheKeyForTrace(key)} (filter error)`);
|
|
206
195
|
continue;
|
|
207
196
|
}
|
|
208
197
|
}
|
|
209
198
|
if (entry?.state.data === void 0) {
|
|
199
|
+
t?.skip(`Skipped ${formatCacheKeyForTrace(key)} (no cached data)`);
|
|
210
200
|
continue;
|
|
211
201
|
}
|
|
212
|
-
const afterData =
|
|
202
|
+
const afterData = updater(entry.state.data, response);
|
|
213
203
|
snapshots.push({ key, previousData: entry.state.data, afterData });
|
|
214
204
|
stateManager.setCache(key, {
|
|
215
205
|
previousData: entry.state.data,
|
|
@@ -219,14 +209,10 @@ function applyOptimisticUpdate(stateManager, target, t) {
|
|
|
219
209
|
}
|
|
220
210
|
});
|
|
221
211
|
stateManager.setMeta(key, { isOptimistic: true });
|
|
222
|
-
t?.log("Marked as optimistic", {
|
|
223
|
-
color: "info",
|
|
224
|
-
info: [{ value: { isOptimistic: true } }]
|
|
225
|
-
});
|
|
226
212
|
}
|
|
227
213
|
return snapshots;
|
|
228
214
|
}
|
|
229
|
-
function confirmOptimistic(stateManager, snapshots
|
|
215
|
+
function confirmOptimistic(stateManager, snapshots) {
|
|
230
216
|
for (const { key } of snapshots) {
|
|
231
217
|
const entry = stateManager.getCache(key);
|
|
232
218
|
if (entry) {
|
|
@@ -234,14 +220,10 @@ function confirmOptimistic(stateManager, snapshots, t) {
|
|
|
234
220
|
previousData: void 0
|
|
235
221
|
});
|
|
236
222
|
stateManager.setMeta(key, { isOptimistic: false });
|
|
237
|
-
t?.log("Optimistic confirmed", {
|
|
238
|
-
color: "success",
|
|
239
|
-
info: [{ value: { isOptimistic: false } }]
|
|
240
|
-
});
|
|
241
223
|
}
|
|
242
224
|
}
|
|
243
225
|
}
|
|
244
|
-
function rollbackOptimistic(stateManager, snapshots
|
|
226
|
+
function rollbackOptimistic(stateManager, snapshots) {
|
|
245
227
|
for (const { key, previousData } of snapshots) {
|
|
246
228
|
const entry = stateManager.getCache(key);
|
|
247
229
|
if (entry) {
|
|
@@ -253,32 +235,16 @@ function rollbackOptimistic(stateManager, snapshots, t) {
|
|
|
253
235
|
}
|
|
254
236
|
});
|
|
255
237
|
stateManager.setMeta(key, { isOptimistic: false });
|
|
256
|
-
t?.log("Optimistic rolled back", {
|
|
257
|
-
color: "warning",
|
|
258
|
-
info: [{ value: { isOptimistic: false } }]
|
|
259
|
-
});
|
|
260
238
|
}
|
|
261
239
|
}
|
|
262
240
|
}
|
|
263
|
-
function
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
if (snapshots.length === 1) {
|
|
267
|
-
return mode === "apply" || mode === "onSuccess" ? { before: first.previousData, after: first.afterData, label } : { before: first.afterData, after: first.previousData, label };
|
|
268
|
-
}
|
|
269
|
-
return mode === "apply" || mode === "onSuccess" ? {
|
|
270
|
-
before: snapshots.map((s) => ({ key: s.key, data: s.previousData })),
|
|
271
|
-
after: snapshots.map((s) => ({ key: s.key, data: s.afterData })),
|
|
272
|
-
label
|
|
273
|
-
} : {
|
|
274
|
-
before: snapshots.map((s) => ({ key: s.key, data: s.afterData })),
|
|
275
|
-
after: snapshots.map((s) => ({ key: s.key, data: s.previousData })),
|
|
276
|
-
label
|
|
277
|
-
};
|
|
241
|
+
function buildSingleDiff(snapshot, mode = "apply") {
|
|
242
|
+
const label = mode === "apply" ? "Optimistic update" : mode === "rollback" ? "Rollback optimistic" : "Confirmed update";
|
|
243
|
+
return mode === "apply" || mode === "confirmed" ? { before: snapshot.previousData, after: snapshot.afterData, label } : { before: snapshot.afterData, after: snapshot.previousData, label };
|
|
278
244
|
}
|
|
279
245
|
var PLUGIN_NAME = "spoosh:optimistic";
|
|
280
246
|
function optimisticPlugin() {
|
|
281
|
-
return (0,
|
|
247
|
+
return (0, import_core2.createSpooshPlugin)({
|
|
282
248
|
name: PLUGIN_NAME,
|
|
283
249
|
operations: ["write"],
|
|
284
250
|
dependencies: ["spoosh:invalidation"],
|
|
@@ -291,28 +257,37 @@ function optimisticPlugin() {
|
|
|
291
257
|
return next();
|
|
292
258
|
}
|
|
293
259
|
context.plugins.get("spoosh:invalidation")?.setDefaultMode("none");
|
|
294
|
-
const
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
const snapshots =
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
260
|
+
const allImmediateSnapshots = [];
|
|
261
|
+
for (const target of targets) {
|
|
262
|
+
if (!target.immediateUpdater) continue;
|
|
263
|
+
const snapshots = applyUpdate(
|
|
264
|
+
stateManager,
|
|
265
|
+
target,
|
|
266
|
+
target.immediateUpdater,
|
|
267
|
+
void 0,
|
|
268
|
+
t
|
|
269
|
+
);
|
|
270
|
+
for (const snapshot of snapshots) {
|
|
271
|
+
t?.log(
|
|
272
|
+
`Applied optimistic update to ${formatCacheKeyForTrace(snapshot.key)}`,
|
|
273
|
+
{ diff: buildSingleDiff(snapshot) }
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
allImmediateSnapshots.push(...snapshots);
|
|
304
277
|
}
|
|
305
278
|
const response = await next();
|
|
306
279
|
if (response.error) {
|
|
307
280
|
const shouldRollback = targets.some(
|
|
308
|
-
(
|
|
281
|
+
(target) => target.rollbackOnError && target.immediateUpdater
|
|
309
282
|
);
|
|
310
|
-
if (shouldRollback &&
|
|
311
|
-
rollbackOptimistic(stateManager,
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
283
|
+
if (shouldRollback && allImmediateSnapshots.length > 0) {
|
|
284
|
+
rollbackOptimistic(stateManager, allImmediateSnapshots);
|
|
285
|
+
for (const snapshot of allImmediateSnapshots) {
|
|
286
|
+
t?.log(`Rolled back ${formatCacheKeyForTrace(snapshot.key)}`, {
|
|
287
|
+
color: "warning",
|
|
288
|
+
diff: buildSingleDiff(snapshot, "rollback")
|
|
289
|
+
});
|
|
290
|
+
}
|
|
316
291
|
}
|
|
317
292
|
for (const target of targets) {
|
|
318
293
|
if (target.onError) {
|
|
@@ -320,66 +295,28 @@ function optimisticPlugin() {
|
|
|
320
295
|
}
|
|
321
296
|
}
|
|
322
297
|
} else {
|
|
323
|
-
if (
|
|
324
|
-
confirmOptimistic(stateManager,
|
|
298
|
+
if (allImmediateSnapshots.length > 0) {
|
|
299
|
+
confirmOptimistic(stateManager, allImmediateSnapshots);
|
|
325
300
|
}
|
|
326
|
-
const
|
|
327
|
-
(target)
|
|
328
|
-
|
|
329
|
-
const onSuccessSnapshots = [];
|
|
330
|
-
for (const target of onSuccessTargets) {
|
|
331
|
-
if (!target.updater) continue;
|
|
332
|
-
const matchingEntries = getMatchingEntries(
|
|
301
|
+
for (const target of targets) {
|
|
302
|
+
if (!target.confirmedUpdater) continue;
|
|
303
|
+
const snapshots = applyUpdate(
|
|
333
304
|
stateManager,
|
|
334
|
-
target
|
|
335
|
-
target.
|
|
305
|
+
target,
|
|
306
|
+
target.confirmedUpdater,
|
|
307
|
+
response.data,
|
|
308
|
+
t
|
|
336
309
|
);
|
|
337
|
-
for (const {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
if (target.where) {
|
|
344
|
-
const options = extractOptionsFromKey(key) ?? {};
|
|
345
|
-
const mappedParams = mapParamsToTargetNames(
|
|
346
|
-
options.params,
|
|
347
|
-
paramMapping
|
|
348
|
-
);
|
|
349
|
-
const mergedOptions = {
|
|
350
|
-
...options,
|
|
351
|
-
params: {
|
|
352
|
-
...extractedParams,
|
|
353
|
-
...mappedParams
|
|
354
|
-
}
|
|
355
|
-
};
|
|
356
|
-
if (!target.where(mergedOptions)) {
|
|
357
|
-
continue;
|
|
310
|
+
for (const snapshot of snapshots) {
|
|
311
|
+
t?.log(
|
|
312
|
+
`Applied confirmed update to ${formatCacheKeyForTrace(snapshot.key)}`,
|
|
313
|
+
{
|
|
314
|
+
color: "success",
|
|
315
|
+
diff: buildSingleDiff(snapshot, "confirmed")
|
|
358
316
|
}
|
|
359
|
-
|
|
360
|
-
if (entry?.state.data === void 0) {
|
|
361
|
-
continue;
|
|
362
|
-
}
|
|
363
|
-
const afterData = target.updater(entry.state.data, response.data);
|
|
364
|
-
onSuccessSnapshots.push({
|
|
365
|
-
key,
|
|
366
|
-
previousData: entry.state.data,
|
|
367
|
-
afterData
|
|
368
|
-
});
|
|
369
|
-
stateManager.setCache(key, {
|
|
370
|
-
state: {
|
|
371
|
-
...entry.state,
|
|
372
|
-
data: afterData
|
|
373
|
-
}
|
|
374
|
-
});
|
|
317
|
+
);
|
|
375
318
|
}
|
|
376
319
|
}
|
|
377
|
-
if (onSuccessSnapshots.length > 0) {
|
|
378
|
-
t?.log(`Applied ${onSuccessSnapshots.length} onSuccess update(s)`, {
|
|
379
|
-
color: "success",
|
|
380
|
-
diff: buildSnapshotDiff(onSuccessSnapshots, "onSuccess")
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
320
|
}
|
|
384
321
|
return response;
|
|
385
322
|
}
|