@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/server.js
CHANGED
|
@@ -21,8 +21,9 @@ const __asyncSignalModule = (() => {
|
|
|
21
21
|
let version = 0;
|
|
22
22
|
let registry;
|
|
23
23
|
let registeredId = id;
|
|
24
|
-
let
|
|
25
|
-
let
|
|
24
|
+
let activeRun;
|
|
25
|
+
let executionToken = 0;
|
|
26
|
+
let disposed = false;
|
|
26
27
|
const subscribers = new Set();
|
|
27
28
|
const dependencyCleanups = new Set();
|
|
28
29
|
|
|
@@ -64,109 +65,62 @@ const __asyncSignalModule = (() => {
|
|
|
64
65
|
},
|
|
65
66
|
|
|
66
67
|
refresh() {
|
|
67
|
-
if (!registry) {
|
|
68
|
+
if (!registry || disposed) {
|
|
68
69
|
throw new Error(`Async signal "${registeredId}" is not registered.`);
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
activeAbort.cancel(new Error(`Async signal "${registeredId}" refreshed.`));
|
|
73
|
-
}
|
|
72
|
+
cancelRun(activeRun, new Error(`Async signal "${registeredId}" refreshed.`));
|
|
74
73
|
|
|
74
|
+
const runRegistry = registry;
|
|
75
|
+
const runId = registeredId;
|
|
75
76
|
const runVersion = version + 1;
|
|
76
77
|
version = runVersion;
|
|
77
78
|
loading = true;
|
|
78
79
|
error = null;
|
|
79
80
|
status = "loading";
|
|
80
81
|
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
activeAbort = controller.signal;
|
|
84
|
-
attachCancel(activeAbort, controller);
|
|
82
|
+
const run = createRun(runRegistry, runId, runVersion);
|
|
83
|
+
activeRun = run;
|
|
85
84
|
notify();
|
|
86
85
|
|
|
87
|
-
const context =
|
|
88
|
-
signals: registry,
|
|
89
|
-
id: registeredId,
|
|
90
|
-
get server() {
|
|
91
|
-
const context = registry._context?.() ?? {};
|
|
92
|
-
const server = context.server;
|
|
93
|
-
if (typeof server?._withContext === "function") {
|
|
94
|
-
return server._withContext({
|
|
95
|
-
signals: registry,
|
|
96
|
-
router: context.router,
|
|
97
|
-
loader: context.loader,
|
|
98
|
-
cache: context.cache,
|
|
99
|
-
abort: activeAbort,
|
|
100
|
-
scheduler: context.scheduler
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
return server;
|
|
104
|
-
},
|
|
105
|
-
get router() {
|
|
106
|
-
return registry._context?.().router;
|
|
107
|
-
},
|
|
108
|
-
get loader() {
|
|
109
|
-
return registry._context?.().loader;
|
|
110
|
-
},
|
|
111
|
-
get cache() {
|
|
112
|
-
return registry._context?.().cache;
|
|
113
|
-
},
|
|
114
|
-
get scheduler() {
|
|
115
|
-
return registry._context?.().scheduler;
|
|
116
|
-
},
|
|
117
|
-
get version() {
|
|
118
|
-
return runVersion;
|
|
119
|
-
},
|
|
120
|
-
get abort() {
|
|
121
|
-
return activeAbort;
|
|
122
|
-
},
|
|
123
|
-
refresh() {
|
|
124
|
-
return state.refresh();
|
|
125
|
-
}
|
|
126
|
-
};
|
|
86
|
+
const context = createRunContext(run);
|
|
127
87
|
|
|
128
88
|
let outcome;
|
|
129
89
|
try {
|
|
130
|
-
outcome =
|
|
90
|
+
outcome = runRegistry._collectDependencies(() => fn.call(context));
|
|
131
91
|
} catch (cause) {
|
|
132
|
-
finishError(
|
|
92
|
+
finishError(run, cause);
|
|
133
93
|
return Promise.reject(cause);
|
|
134
94
|
}
|
|
135
95
|
|
|
136
|
-
syncDependencies(outcome.dependencies);
|
|
96
|
+
syncDependencies(outcome.dependencies, run);
|
|
137
97
|
|
|
138
98
|
return Promise.resolve(outcome.value).then(
|
|
139
99
|
(nextValue) => {
|
|
140
|
-
if (!
|
|
100
|
+
if (!isRunCurrent(run)) {
|
|
141
101
|
return value;
|
|
142
102
|
}
|
|
143
103
|
value = nextValue;
|
|
144
104
|
loading = false;
|
|
145
105
|
error = null;
|
|
146
106
|
status = "ready";
|
|
107
|
+
activeRun = undefined;
|
|
147
108
|
notify();
|
|
148
109
|
return value;
|
|
149
110
|
},
|
|
150
111
|
(cause) => {
|
|
151
|
-
if (!
|
|
112
|
+
if (!isRunCurrent(run)) {
|
|
152
113
|
return value;
|
|
153
114
|
}
|
|
154
|
-
|
|
155
|
-
loading = false;
|
|
156
|
-
status = value === undefined ? "idle" : "ready";
|
|
157
|
-
notify();
|
|
158
|
-
return value;
|
|
159
|
-
}
|
|
160
|
-
finishError(runVersion, cause);
|
|
115
|
+
finishError(run, cause);
|
|
161
116
|
return value;
|
|
162
117
|
}
|
|
163
118
|
);
|
|
164
119
|
},
|
|
165
120
|
|
|
166
121
|
cancel(reason) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
122
|
+
cancelCurrentRun(reason, { settle: true, notifyChange: true });
|
|
123
|
+
return value;
|
|
170
124
|
},
|
|
171
125
|
|
|
172
126
|
subscribe(fn) {
|
|
@@ -195,9 +149,7 @@ const __asyncSignalModule = (() => {
|
|
|
195
149
|
if (!isAsyncSignalSnapshot(snapshot)) {
|
|
196
150
|
return state.set(snapshot);
|
|
197
151
|
}
|
|
198
|
-
|
|
199
|
-
activeAbort.cancel(new Error(`Async signal "${registeredId}" restored from snapshot.`));
|
|
200
|
-
}
|
|
152
|
+
cancelCurrentRun(new Error(`Async signal "${registeredId}" restored from snapshot.`));
|
|
201
153
|
value = snapshot.value;
|
|
202
154
|
loading = Boolean(snapshot.loading);
|
|
203
155
|
error = snapshot.error ?? null;
|
|
@@ -213,7 +165,7 @@ const __asyncSignalModule = (() => {
|
|
|
213
165
|
registry = nextRegistry;
|
|
214
166
|
registeredId = nextId;
|
|
215
167
|
const start = () => {
|
|
216
|
-
if (registry === nextRegistry && status === "idle") {
|
|
168
|
+
if (!disposed && registry === nextRegistry && status === "idle") {
|
|
217
169
|
state.refresh();
|
|
218
170
|
}
|
|
219
171
|
};
|
|
@@ -229,30 +181,161 @@ const __asyncSignalModule = (() => {
|
|
|
229
181
|
},
|
|
230
182
|
|
|
231
183
|
_dispose() {
|
|
232
|
-
|
|
184
|
+
if (disposed) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
disposed = true;
|
|
188
|
+
cancelQueuedWork();
|
|
189
|
+
cancelCurrentRun(new Error(`Async signal "${registeredId}" disposed.`));
|
|
233
190
|
for (const cleanup of dependencyCleanups) {
|
|
234
191
|
cleanup();
|
|
235
192
|
}
|
|
236
193
|
dependencyCleanups.clear();
|
|
237
194
|
subscribers.clear();
|
|
195
|
+
registry = undefined;
|
|
238
196
|
}
|
|
239
197
|
};
|
|
240
198
|
|
|
241
|
-
function
|
|
242
|
-
|
|
199
|
+
function createRun(runRegistry, runId, runVersion) {
|
|
200
|
+
const controller = new AbortController();
|
|
201
|
+
const abort = controller.signal;
|
|
202
|
+
const runContext = captureRunContext(runRegistry, abort);
|
|
203
|
+
const run = {
|
|
204
|
+
token: ++executionToken,
|
|
205
|
+
version: runVersion,
|
|
206
|
+
registry: runRegistry,
|
|
207
|
+
id: runId,
|
|
208
|
+
controller,
|
|
209
|
+
abort,
|
|
210
|
+
...runContext
|
|
211
|
+
};
|
|
212
|
+
attachCancel(abort, controller, (reason) => {
|
|
213
|
+
cancelRun(run, reason, { settle: true, notifyChange: true });
|
|
214
|
+
});
|
|
215
|
+
return run;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function captureRunContext(runRegistry, abort) {
|
|
219
|
+
const context = runRegistry._context?.() ?? {};
|
|
220
|
+
const serverContext = {
|
|
221
|
+
signals: runRegistry,
|
|
222
|
+
router: context.router,
|
|
223
|
+
loader: context.loader,
|
|
224
|
+
cache: context.cache,
|
|
225
|
+
abort,
|
|
226
|
+
scheduler: context.scheduler
|
|
227
|
+
};
|
|
228
|
+
const server = typeof context.server?._withContext === "function"
|
|
229
|
+
? context.server._withContext(serverContext)
|
|
230
|
+
: context.server;
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
signals: runRegistry,
|
|
234
|
+
server,
|
|
235
|
+
router: context.router,
|
|
236
|
+
loader: context.loader,
|
|
237
|
+
cache: context.cache,
|
|
238
|
+
scheduler: context.scheduler
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function createRunContext(run) {
|
|
243
|
+
return {
|
|
244
|
+
signals: run.signals,
|
|
245
|
+
id: run.id,
|
|
246
|
+
get server() {
|
|
247
|
+
return run.server;
|
|
248
|
+
},
|
|
249
|
+
get router() {
|
|
250
|
+
return run.router;
|
|
251
|
+
},
|
|
252
|
+
get loader() {
|
|
253
|
+
return run.loader;
|
|
254
|
+
},
|
|
255
|
+
get cache() {
|
|
256
|
+
return run.cache;
|
|
257
|
+
},
|
|
258
|
+
get scheduler() {
|
|
259
|
+
return run.scheduler;
|
|
260
|
+
},
|
|
261
|
+
get version() {
|
|
262
|
+
return run.version;
|
|
263
|
+
},
|
|
264
|
+
get abort() {
|
|
265
|
+
return run.abort;
|
|
266
|
+
},
|
|
267
|
+
refresh() {
|
|
268
|
+
return state.refresh();
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function finishError(run, cause) {
|
|
274
|
+
if (!isRunCurrent(run)) {
|
|
243
275
|
return;
|
|
244
276
|
}
|
|
245
277
|
loading = false;
|
|
246
278
|
error = cause;
|
|
247
279
|
status = "error";
|
|
280
|
+
activeRun = undefined;
|
|
248
281
|
notify();
|
|
249
282
|
}
|
|
250
283
|
|
|
251
|
-
function
|
|
252
|
-
return
|
|
284
|
+
function isRunCurrent(run) {
|
|
285
|
+
return Boolean(run)
|
|
286
|
+
&& !disposed
|
|
287
|
+
&& activeRun === run
|
|
288
|
+
&& run.token === executionToken
|
|
289
|
+
&& run.registry === registry
|
|
290
|
+
&& run.id === registeredId
|
|
291
|
+
&& !run.abort.aborted;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function cancelCurrentRun(reason, options = {}) {
|
|
295
|
+
const run = activeRun;
|
|
296
|
+
const shouldSettle = Boolean(run) || loading;
|
|
297
|
+
if (run) {
|
|
298
|
+
cancelRun(run, reason);
|
|
299
|
+
} else if (shouldSettle) {
|
|
300
|
+
executionToken += 1;
|
|
301
|
+
}
|
|
302
|
+
if (options.settle && shouldSettle && !disposed) {
|
|
303
|
+
settleCanceled(options.notifyChange);
|
|
304
|
+
}
|
|
253
305
|
}
|
|
254
306
|
|
|
255
|
-
function
|
|
307
|
+
function cancelRun(run, reason, options = {}) {
|
|
308
|
+
if (!run) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const wasActive = activeRun === run;
|
|
312
|
+
if (wasActive) {
|
|
313
|
+
executionToken += 1;
|
|
314
|
+
activeRun = undefined;
|
|
315
|
+
}
|
|
316
|
+
if (!run.abort.aborted) {
|
|
317
|
+
run.controller.abort(reason);
|
|
318
|
+
}
|
|
319
|
+
if (wasActive && options.settle && !disposed) {
|
|
320
|
+
settleCanceled(options.notifyChange);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function settleCanceled(notifyChange = false) {
|
|
325
|
+
const nextStatus = value === undefined ? "idle" : "ready";
|
|
326
|
+
const changed = loading || error !== null || status !== nextStatus;
|
|
327
|
+
loading = false;
|
|
328
|
+
error = null;
|
|
329
|
+
status = nextStatus;
|
|
330
|
+
if (notifyChange && changed) {
|
|
331
|
+
notify();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function syncDependencies(dependencies, run) {
|
|
336
|
+
if (!isRunCurrent(run)) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
256
339
|
for (const cleanup of dependencyCleanups) {
|
|
257
340
|
cleanup();
|
|
258
341
|
}
|
|
@@ -261,15 +344,16 @@ const __asyncSignalModule = (() => {
|
|
|
261
344
|
for (const dependency of dependencies) {
|
|
262
345
|
const dependencyId = String(dependency).split(".")[0];
|
|
263
346
|
if (dependencyId && dependencyId !== registeredId) {
|
|
264
|
-
dependencyCleanups.add(registry.subscribe(dependency, () => scheduleRefresh()));
|
|
347
|
+
dependencyCleanups.add(run.registry.subscribe(dependency, () => scheduleRefresh()));
|
|
265
348
|
}
|
|
266
349
|
}
|
|
267
350
|
}
|
|
268
351
|
|
|
269
352
|
function scheduleRefresh() {
|
|
270
|
-
if (
|
|
271
|
-
|
|
353
|
+
if (disposed || !registry) {
|
|
354
|
+
return;
|
|
272
355
|
}
|
|
356
|
+
cancelRun(activeRun, new Error(`Async signal "${registeredId}" dependency changed.`));
|
|
273
357
|
const scheduler = registry?._context?.().scheduler;
|
|
274
358
|
if (!scheduler) {
|
|
275
359
|
state.refresh();
|
|
@@ -281,6 +365,11 @@ const __asyncSignalModule = (() => {
|
|
|
281
365
|
});
|
|
282
366
|
}
|
|
283
367
|
|
|
368
|
+
function cancelQueuedWork() {
|
|
369
|
+
const scheduler = registry?._context?.().scheduler;
|
|
370
|
+
scheduler?.cancelScope?.(registeredId);
|
|
371
|
+
}
|
|
372
|
+
|
|
284
373
|
function notify() {
|
|
285
374
|
for (const subscriber of [...subscribers]) {
|
|
286
375
|
subscriber(state);
|
|
@@ -315,11 +404,15 @@ const __asyncSignalModule = (() => {
|
|
|
315
404
|
return value === undefined ? "idle" : "ready";
|
|
316
405
|
}
|
|
317
406
|
|
|
318
|
-
function attachCancel(signal, controller) {
|
|
407
|
+
function attachCancel(signal, controller, onCancel) {
|
|
319
408
|
Object.defineProperty(signal, "cancel", {
|
|
320
409
|
configurable: true,
|
|
321
410
|
enumerable: false,
|
|
322
411
|
value(reason) {
|
|
412
|
+
if (typeof onCancel === "function") {
|
|
413
|
+
onCancel(reason);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
323
416
|
controller.abort(reason);
|
|
324
417
|
}
|
|
325
418
|
});
|
|
@@ -1220,10 +1313,16 @@ const __signalsModule = (() => {
|
|
|
1220
1313
|
let subscriptionCounter = 0;
|
|
1221
1314
|
let effectCounter = 0;
|
|
1222
1315
|
|
|
1316
|
+
for (const id of entries.keys()) {
|
|
1317
|
+
if (asyncDescriptors.has(id)) {
|
|
1318
|
+
throw new Error(`Signal "${id}" is already registered.`);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1223
1322
|
const registry = attachRegistryInspection({
|
|
1224
1323
|
register(id, signalLike) {
|
|
1225
1324
|
assertId(id);
|
|
1226
|
-
if (entries.has(id)) {
|
|
1325
|
+
if (entries.has(id) || asyncDescriptors.has(id)) {
|
|
1227
1326
|
throw new Error(`Signal "${id}" is already registered.`);
|
|
1228
1327
|
}
|
|
1229
1328
|
const entry = normalizeSignal(signalLike);
|
|
@@ -1241,14 +1340,19 @@ const __signalsModule = (() => {
|
|
|
1241
1340
|
|
|
1242
1341
|
unregister(id) {
|
|
1243
1342
|
assertId(id);
|
|
1244
|
-
|
|
1343
|
+
const hadEntry = entries.has(id);
|
|
1344
|
+
const hadDescriptor = asyncDescriptors.has(id);
|
|
1345
|
+
if (!hadEntry && !hadDescriptor) {
|
|
1245
1346
|
return false;
|
|
1246
1347
|
}
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1348
|
+
if (hadEntry) {
|
|
1349
|
+
registryCleanups.get(id)?.();
|
|
1350
|
+
registryCleanups.delete(id);
|
|
1351
|
+
entries.get(id)?._dispose?.();
|
|
1352
|
+
entries.delete(id);
|
|
1353
|
+
boundEntries.delete(id);
|
|
1354
|
+
}
|
|
1355
|
+
asyncDescriptors.delete(id);
|
|
1252
1356
|
return true;
|
|
1253
1357
|
},
|
|
1254
1358
|
|
|
@@ -1440,6 +1544,9 @@ const __signalsModule = (() => {
|
|
|
1440
1544
|
_adoptMany(map = {}) {
|
|
1441
1545
|
for (const [id, signalLike] of Object.entries(map ?? {})) {
|
|
1442
1546
|
if (!entries.has(id)) {
|
|
1547
|
+
if (asyncDescriptors.has(id)) {
|
|
1548
|
+
throw new Error(`Signal "${id}" is already registered.`);
|
|
1549
|
+
}
|
|
1443
1550
|
const entry = cloneSignalDeclaration(signalLike);
|
|
1444
1551
|
entries.set(id, entry);
|
|
1445
1552
|
bindEntry(id, entry);
|
|
@@ -5340,6 +5447,9 @@ const __appModule = (() => {
|
|
|
5340
5447
|
return;
|
|
5341
5448
|
}
|
|
5342
5449
|
for (const [id, value] of Object.entries(entries)) {
|
|
5450
|
+
if (type === "asyncSignal" && runtime.signals?.has?.(id) && !runtime.registry.has(type, id)) {
|
|
5451
|
+
throw new Error(`Signal "${id}" is already registered.`);
|
|
5452
|
+
}
|
|
5343
5453
|
registerSnapshotEntry(runtime.registry, type, id, value, options);
|
|
5344
5454
|
}
|
|
5345
5455
|
concreteRegistry?._adoptMany?.(entries);
|