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