@async/framework 0.11.5 → 0.11.6
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/CHANGELOG.md +13 -0
- package/browser.js +172 -79
- package/browser.min.js +1 -1
- package/browser.ts +172 -79
- package/browser.umd.js +172 -79
- package/browser.umd.min.js +1 -1
- package/framework.ts +172 -79
- package/package.json +1 -1
- package/server.js +172 -79
package/browser.umd.js
CHANGED
|
@@ -30,8 +30,9 @@
|
|
|
30
30
|
let version = 0;
|
|
31
31
|
let registry;
|
|
32
32
|
let registeredId = id;
|
|
33
|
-
let
|
|
34
|
-
let
|
|
33
|
+
let activeRun;
|
|
34
|
+
let executionToken = 0;
|
|
35
|
+
let disposed = false;
|
|
35
36
|
const subscribers = new Set();
|
|
36
37
|
const dependencyCleanups = new Set();
|
|
37
38
|
|
|
@@ -73,109 +74,62 @@
|
|
|
73
74
|
},
|
|
74
75
|
|
|
75
76
|
refresh() {
|
|
76
|
-
if (!registry) {
|
|
77
|
+
if (!registry || disposed) {
|
|
77
78
|
throw new Error(`Async signal "${registeredId}" is not registered.`);
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
activeAbort.cancel(new Error(`Async signal "${registeredId}" refreshed.`));
|
|
82
|
-
}
|
|
81
|
+
cancelRun(activeRun, new Error(`Async signal "${registeredId}" refreshed.`));
|
|
83
82
|
|
|
83
|
+
const runRegistry = registry;
|
|
84
|
+
const runId = registeredId;
|
|
84
85
|
const runVersion = version + 1;
|
|
85
86
|
version = runVersion;
|
|
86
87
|
loading = true;
|
|
87
88
|
error = null;
|
|
88
89
|
status = "loading";
|
|
89
90
|
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
activeAbort = controller.signal;
|
|
93
|
-
attachCancel(activeAbort, controller);
|
|
91
|
+
const run = createRun(runRegistry, runId, runVersion);
|
|
92
|
+
activeRun = run;
|
|
94
93
|
notify();
|
|
95
94
|
|
|
96
|
-
const context =
|
|
97
|
-
signals: registry,
|
|
98
|
-
id: registeredId,
|
|
99
|
-
get server() {
|
|
100
|
-
const context = registry._context?.() ?? {};
|
|
101
|
-
const server = context.server;
|
|
102
|
-
if (typeof server?._withContext === "function") {
|
|
103
|
-
return server._withContext({
|
|
104
|
-
signals: registry,
|
|
105
|
-
router: context.router,
|
|
106
|
-
loader: context.loader,
|
|
107
|
-
cache: context.cache,
|
|
108
|
-
abort: activeAbort,
|
|
109
|
-
scheduler: context.scheduler
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
return server;
|
|
113
|
-
},
|
|
114
|
-
get router() {
|
|
115
|
-
return registry._context?.().router;
|
|
116
|
-
},
|
|
117
|
-
get loader() {
|
|
118
|
-
return registry._context?.().loader;
|
|
119
|
-
},
|
|
120
|
-
get cache() {
|
|
121
|
-
return registry._context?.().cache;
|
|
122
|
-
},
|
|
123
|
-
get scheduler() {
|
|
124
|
-
return registry._context?.().scheduler;
|
|
125
|
-
},
|
|
126
|
-
get version() {
|
|
127
|
-
return runVersion;
|
|
128
|
-
},
|
|
129
|
-
get abort() {
|
|
130
|
-
return activeAbort;
|
|
131
|
-
},
|
|
132
|
-
refresh() {
|
|
133
|
-
return state.refresh();
|
|
134
|
-
}
|
|
135
|
-
};
|
|
95
|
+
const context = createRunContext(run);
|
|
136
96
|
|
|
137
97
|
let outcome;
|
|
138
98
|
try {
|
|
139
|
-
outcome =
|
|
99
|
+
outcome = runRegistry._collectDependencies(() => fn.call(context));
|
|
140
100
|
} catch (cause) {
|
|
141
|
-
finishError(
|
|
101
|
+
finishError(run, cause);
|
|
142
102
|
return Promise.reject(cause);
|
|
143
103
|
}
|
|
144
104
|
|
|
145
|
-
syncDependencies(outcome.dependencies);
|
|
105
|
+
syncDependencies(outcome.dependencies, run);
|
|
146
106
|
|
|
147
107
|
return Promise.resolve(outcome.value).then(
|
|
148
108
|
(nextValue) => {
|
|
149
|
-
if (!
|
|
109
|
+
if (!isRunCurrent(run)) {
|
|
150
110
|
return value;
|
|
151
111
|
}
|
|
152
112
|
value = nextValue;
|
|
153
113
|
loading = false;
|
|
154
114
|
error = null;
|
|
155
115
|
status = "ready";
|
|
116
|
+
activeRun = undefined;
|
|
156
117
|
notify();
|
|
157
118
|
return value;
|
|
158
119
|
},
|
|
159
120
|
(cause) => {
|
|
160
|
-
if (!
|
|
161
|
-
return value;
|
|
162
|
-
}
|
|
163
|
-
if (activeAbort?.aborted) {
|
|
164
|
-
loading = false;
|
|
165
|
-
status = value === undefined ? "idle" : "ready";
|
|
166
|
-
notify();
|
|
121
|
+
if (!isRunCurrent(run)) {
|
|
167
122
|
return value;
|
|
168
123
|
}
|
|
169
|
-
finishError(
|
|
124
|
+
finishError(run, cause);
|
|
170
125
|
return value;
|
|
171
126
|
}
|
|
172
127
|
);
|
|
173
128
|
},
|
|
174
129
|
|
|
175
130
|
cancel(reason) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
131
|
+
cancelCurrentRun(reason, { settle: true, notifyChange: true });
|
|
132
|
+
return value;
|
|
179
133
|
},
|
|
180
134
|
|
|
181
135
|
subscribe(fn) {
|
|
@@ -204,9 +158,7 @@
|
|
|
204
158
|
if (!isAsyncSignalSnapshot(snapshot)) {
|
|
205
159
|
return state.set(snapshot);
|
|
206
160
|
}
|
|
207
|
-
|
|
208
|
-
activeAbort.cancel(new Error(`Async signal "${registeredId}" restored from snapshot.`));
|
|
209
|
-
}
|
|
161
|
+
cancelCurrentRun(new Error(`Async signal "${registeredId}" restored from snapshot.`));
|
|
210
162
|
value = snapshot.value;
|
|
211
163
|
loading = Boolean(snapshot.loading);
|
|
212
164
|
error = snapshot.error ?? null;
|
|
@@ -222,7 +174,7 @@
|
|
|
222
174
|
registry = nextRegistry;
|
|
223
175
|
registeredId = nextId;
|
|
224
176
|
const start = () => {
|
|
225
|
-
if (registry === nextRegistry && status === "idle") {
|
|
177
|
+
if (!disposed && registry === nextRegistry && status === "idle") {
|
|
226
178
|
state.refresh();
|
|
227
179
|
}
|
|
228
180
|
};
|
|
@@ -238,30 +190,161 @@
|
|
|
238
190
|
},
|
|
239
191
|
|
|
240
192
|
_dispose() {
|
|
241
|
-
|
|
193
|
+
if (disposed) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
disposed = true;
|
|
197
|
+
cancelQueuedWork();
|
|
198
|
+
cancelCurrentRun(new Error(`Async signal "${registeredId}" disposed.`));
|
|
242
199
|
for (const cleanup of dependencyCleanups) {
|
|
243
200
|
cleanup();
|
|
244
201
|
}
|
|
245
202
|
dependencyCleanups.clear();
|
|
246
203
|
subscribers.clear();
|
|
204
|
+
registry = undefined;
|
|
247
205
|
}
|
|
248
206
|
};
|
|
249
207
|
|
|
250
|
-
function
|
|
251
|
-
|
|
208
|
+
function createRun(runRegistry, runId, runVersion) {
|
|
209
|
+
const controller = new AbortController();
|
|
210
|
+
const abort = controller.signal;
|
|
211
|
+
const runContext = captureRunContext(runRegistry, abort);
|
|
212
|
+
const run = {
|
|
213
|
+
token: ++executionToken,
|
|
214
|
+
version: runVersion,
|
|
215
|
+
registry: runRegistry,
|
|
216
|
+
id: runId,
|
|
217
|
+
controller,
|
|
218
|
+
abort,
|
|
219
|
+
...runContext
|
|
220
|
+
};
|
|
221
|
+
attachCancel(abort, controller, (reason) => {
|
|
222
|
+
cancelRun(run, reason, { settle: true, notifyChange: true });
|
|
223
|
+
});
|
|
224
|
+
return run;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function captureRunContext(runRegistry, abort) {
|
|
228
|
+
const context = runRegistry._context?.() ?? {};
|
|
229
|
+
const serverContext = {
|
|
230
|
+
signals: runRegistry,
|
|
231
|
+
router: context.router,
|
|
232
|
+
loader: context.loader,
|
|
233
|
+
cache: context.cache,
|
|
234
|
+
abort,
|
|
235
|
+
scheduler: context.scheduler
|
|
236
|
+
};
|
|
237
|
+
const server = typeof context.server?._withContext === "function"
|
|
238
|
+
? context.server._withContext(serverContext)
|
|
239
|
+
: context.server;
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
signals: runRegistry,
|
|
243
|
+
server,
|
|
244
|
+
router: context.router,
|
|
245
|
+
loader: context.loader,
|
|
246
|
+
cache: context.cache,
|
|
247
|
+
scheduler: context.scheduler
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function createRunContext(run) {
|
|
252
|
+
return {
|
|
253
|
+
signals: run.signals,
|
|
254
|
+
id: run.id,
|
|
255
|
+
get server() {
|
|
256
|
+
return run.server;
|
|
257
|
+
},
|
|
258
|
+
get router() {
|
|
259
|
+
return run.router;
|
|
260
|
+
},
|
|
261
|
+
get loader() {
|
|
262
|
+
return run.loader;
|
|
263
|
+
},
|
|
264
|
+
get cache() {
|
|
265
|
+
return run.cache;
|
|
266
|
+
},
|
|
267
|
+
get scheduler() {
|
|
268
|
+
return run.scheduler;
|
|
269
|
+
},
|
|
270
|
+
get version() {
|
|
271
|
+
return run.version;
|
|
272
|
+
},
|
|
273
|
+
get abort() {
|
|
274
|
+
return run.abort;
|
|
275
|
+
},
|
|
276
|
+
refresh() {
|
|
277
|
+
return state.refresh();
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function finishError(run, cause) {
|
|
283
|
+
if (!isRunCurrent(run)) {
|
|
252
284
|
return;
|
|
253
285
|
}
|
|
254
286
|
loading = false;
|
|
255
287
|
error = cause;
|
|
256
288
|
status = "error";
|
|
289
|
+
activeRun = undefined;
|
|
257
290
|
notify();
|
|
258
291
|
}
|
|
259
292
|
|
|
260
|
-
function
|
|
261
|
-
return
|
|
293
|
+
function isRunCurrent(run) {
|
|
294
|
+
return Boolean(run)
|
|
295
|
+
&& !disposed
|
|
296
|
+
&& activeRun === run
|
|
297
|
+
&& run.token === executionToken
|
|
298
|
+
&& run.registry === registry
|
|
299
|
+
&& run.id === registeredId
|
|
300
|
+
&& !run.abort.aborted;
|
|
262
301
|
}
|
|
263
302
|
|
|
264
|
-
function
|
|
303
|
+
function cancelCurrentRun(reason, options = {}) {
|
|
304
|
+
const run = activeRun;
|
|
305
|
+
const shouldSettle = Boolean(run) || loading;
|
|
306
|
+
if (run) {
|
|
307
|
+
cancelRun(run, reason);
|
|
308
|
+
} else if (shouldSettle) {
|
|
309
|
+
executionToken += 1;
|
|
310
|
+
}
|
|
311
|
+
if (options.settle && shouldSettle && !disposed) {
|
|
312
|
+
settleCanceled(options.notifyChange);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function cancelRun(run, reason, options = {}) {
|
|
317
|
+
if (!run) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const wasActive = activeRun === run;
|
|
321
|
+
if (wasActive) {
|
|
322
|
+
executionToken += 1;
|
|
323
|
+
activeRun = undefined;
|
|
324
|
+
}
|
|
325
|
+
if (!run.abort.aborted) {
|
|
326
|
+
run.controller.abort(reason);
|
|
327
|
+
}
|
|
328
|
+
if (wasActive && options.settle && !disposed) {
|
|
329
|
+
settleCanceled(options.notifyChange);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function settleCanceled(notifyChange = false) {
|
|
334
|
+
const nextStatus = value === undefined ? "idle" : "ready";
|
|
335
|
+
const changed = loading || error !== null || status !== nextStatus;
|
|
336
|
+
loading = false;
|
|
337
|
+
error = null;
|
|
338
|
+
status = nextStatus;
|
|
339
|
+
if (notifyChange && changed) {
|
|
340
|
+
notify();
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function syncDependencies(dependencies, run) {
|
|
345
|
+
if (!isRunCurrent(run)) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
265
348
|
for (const cleanup of dependencyCleanups) {
|
|
266
349
|
cleanup();
|
|
267
350
|
}
|
|
@@ -270,15 +353,16 @@
|
|
|
270
353
|
for (const dependency of dependencies) {
|
|
271
354
|
const dependencyId = String(dependency).split(".")[0];
|
|
272
355
|
if (dependencyId && dependencyId !== registeredId) {
|
|
273
|
-
dependencyCleanups.add(registry.subscribe(dependency, () => scheduleRefresh()));
|
|
356
|
+
dependencyCleanups.add(run.registry.subscribe(dependency, () => scheduleRefresh()));
|
|
274
357
|
}
|
|
275
358
|
}
|
|
276
359
|
}
|
|
277
360
|
|
|
278
361
|
function scheduleRefresh() {
|
|
279
|
-
if (
|
|
280
|
-
|
|
362
|
+
if (disposed || !registry) {
|
|
363
|
+
return;
|
|
281
364
|
}
|
|
365
|
+
cancelRun(activeRun, new Error(`Async signal "${registeredId}" dependency changed.`));
|
|
282
366
|
const scheduler = registry?._context?.().scheduler;
|
|
283
367
|
if (!scheduler) {
|
|
284
368
|
state.refresh();
|
|
@@ -290,6 +374,11 @@
|
|
|
290
374
|
});
|
|
291
375
|
}
|
|
292
376
|
|
|
377
|
+
function cancelQueuedWork() {
|
|
378
|
+
const scheduler = registry?._context?.().scheduler;
|
|
379
|
+
scheduler?.cancelScope?.(registeredId);
|
|
380
|
+
}
|
|
381
|
+
|
|
293
382
|
function notify() {
|
|
294
383
|
for (const subscriber of [...subscribers]) {
|
|
295
384
|
subscriber(state);
|
|
@@ -324,11 +413,15 @@
|
|
|
324
413
|
return value === undefined ? "idle" : "ready";
|
|
325
414
|
}
|
|
326
415
|
|
|
327
|
-
function attachCancel(signal, controller) {
|
|
416
|
+
function attachCancel(signal, controller, onCancel) {
|
|
328
417
|
Object.defineProperty(signal, "cancel", {
|
|
329
418
|
configurable: true,
|
|
330
419
|
enumerable: false,
|
|
331
420
|
value(reason) {
|
|
421
|
+
if (typeof onCancel === "function") {
|
|
422
|
+
onCancel(reason);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
332
425
|
controller.abort(reason);
|
|
333
426
|
}
|
|
334
427
|
});
|