@async/framework 0.11.4 → 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 +25 -0
- package/browser.js +196 -86
- package/browser.min.js +1 -1
- package/browser.ts +196 -86
- package/browser.umd.js +196 -86
- package/browser.umd.min.js +1 -1
- package/framework.ts +196 -86
- package/package.json +1 -1
- package/server.js +196 -86
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 (!
|
|
121
|
+
if (!isRunCurrent(run)) {
|
|
161
122
|
return value;
|
|
162
123
|
}
|
|
163
|
-
|
|
164
|
-
loading = false;
|
|
165
|
-
status = value === undefined ? "idle" : "ready";
|
|
166
|
-
notify();
|
|
167
|
-
return value;
|
|
168
|
-
}
|
|
169
|
-
finishError(runVersion, cause);
|
|
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;
|
|
301
|
+
}
|
|
302
|
+
|
|
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
|
+
}
|
|
262
314
|
}
|
|
263
315
|
|
|
264
|
-
function
|
|
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
|
});
|
|
@@ -1229,10 +1322,16 @@
|
|
|
1229
1322
|
let subscriptionCounter = 0;
|
|
1230
1323
|
let effectCounter = 0;
|
|
1231
1324
|
|
|
1325
|
+
for (const id of entries.keys()) {
|
|
1326
|
+
if (asyncDescriptors.has(id)) {
|
|
1327
|
+
throw new Error(`Signal "${id}" is already registered.`);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1232
1331
|
const registry = attachRegistryInspection({
|
|
1233
1332
|
register(id, signalLike) {
|
|
1234
1333
|
assertId(id);
|
|
1235
|
-
if (entries.has(id)) {
|
|
1334
|
+
if (entries.has(id) || asyncDescriptors.has(id)) {
|
|
1236
1335
|
throw new Error(`Signal "${id}" is already registered.`);
|
|
1237
1336
|
}
|
|
1238
1337
|
const entry = normalizeSignal(signalLike);
|
|
@@ -1250,14 +1349,19 @@
|
|
|
1250
1349
|
|
|
1251
1350
|
unregister(id) {
|
|
1252
1351
|
assertId(id);
|
|
1253
|
-
|
|
1352
|
+
const hadEntry = entries.has(id);
|
|
1353
|
+
const hadDescriptor = asyncDescriptors.has(id);
|
|
1354
|
+
if (!hadEntry && !hadDescriptor) {
|
|
1254
1355
|
return false;
|
|
1255
1356
|
}
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1357
|
+
if (hadEntry) {
|
|
1358
|
+
registryCleanups.get(id)?.();
|
|
1359
|
+
registryCleanups.delete(id);
|
|
1360
|
+
entries.get(id)?._dispose?.();
|
|
1361
|
+
entries.delete(id);
|
|
1362
|
+
boundEntries.delete(id);
|
|
1363
|
+
}
|
|
1364
|
+
asyncDescriptors.delete(id);
|
|
1261
1365
|
return true;
|
|
1262
1366
|
},
|
|
1263
1367
|
|
|
@@ -1449,6 +1553,9 @@
|
|
|
1449
1553
|
_adoptMany(map = {}) {
|
|
1450
1554
|
for (const [id, signalLike] of Object.entries(map ?? {})) {
|
|
1451
1555
|
if (!entries.has(id)) {
|
|
1556
|
+
if (asyncDescriptors.has(id)) {
|
|
1557
|
+
throw new Error(`Signal "${id}" is already registered.`);
|
|
1558
|
+
}
|
|
1452
1559
|
const entry = cloneSignalDeclaration(signalLike);
|
|
1453
1560
|
entries.set(id, entry);
|
|
1454
1561
|
bindEntry(id, entry);
|
|
@@ -5349,6 +5456,9 @@
|
|
|
5349
5456
|
return;
|
|
5350
5457
|
}
|
|
5351
5458
|
for (const [id, value] of Object.entries(entries)) {
|
|
5459
|
+
if (type === "asyncSignal" && runtime.signals?.has?.(id) && !runtime.registry.has(type, id)) {
|
|
5460
|
+
throw new Error(`Signal "${id}" is already registered.`);
|
|
5461
|
+
}
|
|
5352
5462
|
registerSnapshotEntry(runtime.registry, type, id, value, options);
|
|
5353
5463
|
}
|
|
5354
5464
|
concreteRegistry?._adoptMany?.(entries);
|