@async/framework 0.11.5 → 0.11.7
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 +29 -0
- package/README.md +1 -0
- package/browser.js +192 -104
- package/browser.min.js +1 -1
- package/browser.ts +192 -104
- package/browser.umd.js +192 -104
- package/browser.umd.min.js +1 -1
- package/framework.ts +194 -106
- package/package.json +1 -1
- package/server.js +194 -106
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
|
+
}
|
|
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
|
+
}
|
|
262
342
|
}
|
|
263
343
|
|
|
264
|
-
function syncDependencies(dependencies) {
|
|
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
|
});
|
|
@@ -2296,9 +2389,10 @@
|
|
|
2296
2389
|
})();
|
|
2297
2390
|
|
|
2298
2391
|
const __serverModule = (() => {
|
|
2299
|
-
const
|
|
2392
|
+
const serverEnvelopeKind = Symbol.for("@async/framework.serverResult");
|
|
2393
|
+
const serverEnvelopeWireKey = "__async_server_result__";
|
|
2394
|
+
const serverEnvelopeWireVersion = 1;
|
|
2300
2395
|
const appliedServerResult = Symbol.for("@async/framework.appliedServerResult");
|
|
2301
|
-
const appliedServerValues = new WeakSet();
|
|
2302
2396
|
|
|
2303
2397
|
function createServerProxy({
|
|
2304
2398
|
endpoint = "/__async/server",
|
|
@@ -2341,9 +2435,7 @@
|
|
|
2341
2435
|
throw new Error(`Server function "${id}" failed with ${response.status}.`);
|
|
2342
2436
|
}
|
|
2343
2437
|
|
|
2344
|
-
|
|
2345
|
-
await applyServerResult(result, runContext);
|
|
2346
|
-
return markAppliedServerValue(unwrapServerResult(result));
|
|
2438
|
+
return consumeServerResult(await readServerResponse(id, response), runContext);
|
|
2347
2439
|
}
|
|
2348
2440
|
|
|
2349
2441
|
return createServerNamespace(run, {
|
|
@@ -2378,7 +2470,7 @@
|
|
|
2378
2470
|
if (!isServerEnvelope(result)) {
|
|
2379
2471
|
return result;
|
|
2380
2472
|
}
|
|
2381
|
-
if (result[appliedServerResult]
|
|
2473
|
+
if (result[appliedServerResult]) {
|
|
2382
2474
|
return result;
|
|
2383
2475
|
}
|
|
2384
2476
|
|
|
@@ -2410,18 +2502,16 @@
|
|
|
2410
2502
|
return result;
|
|
2411
2503
|
}
|
|
2412
2504
|
|
|
2413
|
-
function
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
}
|
|
2417
|
-
return result;
|
|
2505
|
+
async function consumeServerResult(result, context = {}) {
|
|
2506
|
+
await applyServerResult(result, context);
|
|
2507
|
+
return unwrapServerResult(result);
|
|
2418
2508
|
}
|
|
2419
2509
|
|
|
2420
|
-
function
|
|
2421
|
-
if (
|
|
2422
|
-
|
|
2510
|
+
function unwrapServerResult(result) {
|
|
2511
|
+
if (isServerEnvelope(result)) {
|
|
2512
|
+
return Object.hasOwn(result, "value") ? result.value : undefined;
|
|
2423
2513
|
}
|
|
2424
|
-
return
|
|
2514
|
+
return result;
|
|
2425
2515
|
}
|
|
2426
2516
|
|
|
2427
2517
|
function markAppliedServerResult(result) {
|
|
@@ -2465,9 +2555,7 @@
|
|
|
2465
2555
|
throw new Error("Server namespace is not directly callable.");
|
|
2466
2556
|
}
|
|
2467
2557
|
const context = contextProvider() ?? {};
|
|
2468
|
-
|
|
2469
|
-
await applyServerResult(result, context);
|
|
2470
|
-
return unwrapServerResult(result);
|
|
2558
|
+
return run(parts.join("."), args, context);
|
|
2471
2559
|
};
|
|
2472
2560
|
|
|
2473
2561
|
const proxy = new Proxy(callable, {
|
|
@@ -2524,7 +2612,7 @@
|
|
|
2524
2612
|
|
|
2525
2613
|
async function readServerResponse(id, response) {
|
|
2526
2614
|
if (response.status === 204) {
|
|
2527
|
-
return
|
|
2615
|
+
return undefined;
|
|
2528
2616
|
}
|
|
2529
2617
|
const type = response.headers.get("content-type") ?? "";
|
|
2530
2618
|
if (type.includes("application/json")) {
|
|
@@ -2542,7 +2630,7 @@
|
|
|
2542
2630
|
if (typeof response.text !== "function") {
|
|
2543
2631
|
throw new Error(`Server function "${id}" transport returned an invalid response: missing text().`);
|
|
2544
2632
|
}
|
|
2545
|
-
return
|
|
2633
|
+
return response.text();
|
|
2546
2634
|
}
|
|
2547
2635
|
|
|
2548
2636
|
function snapshotSignalPaths(paths = [], signals) {
|
|
@@ -2687,7 +2775,8 @@
|
|
|
2687
2775
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2688
2776
|
return false;
|
|
2689
2777
|
}
|
|
2690
|
-
return
|
|
2778
|
+
return value[serverEnvelopeKind] === true
|
|
2779
|
+
|| value[serverEnvelopeWireKey] === serverEnvelopeWireVersion;
|
|
2691
2780
|
}
|
|
2692
2781
|
|
|
2693
2782
|
function toError(value) {
|
|
@@ -2709,11 +2798,11 @@
|
|
|
2709
2798
|
throw new TypeError("Server function id must be a non-empty string.");
|
|
2710
2799
|
}
|
|
2711
2800
|
}
|
|
2712
|
-
return { createServerProxy, resolveServerCommandArguments, applyServerResult, unwrapServerResult, defaultInput, createServerNamespace, createSignalReader, assertServerId };
|
|
2801
|
+
return { createServerProxy, resolveServerCommandArguments, applyServerResult, consumeServerResult, unwrapServerResult, defaultInput, createServerNamespace, createSignalReader, assertServerId };
|
|
2713
2802
|
})();
|
|
2714
2803
|
|
|
2715
2804
|
const __handlersModule = (() => {
|
|
2716
|
-
const {
|
|
2805
|
+
const { defaultInput, resolveServerCommandArguments } = __serverModule;
|
|
2717
2806
|
const { attachRegistryInspection, createRegistryStore } = __registryStoreModule;
|
|
2718
2807
|
const { createLazyRegistry, isLazyDescriptor } = __lazyRegistryModule;
|
|
2719
2808
|
const builtInTokens = new Set(["prevent", "preventDefault", "stopPropagation", "stopImmediatePropagation"]);
|
|
@@ -2811,8 +2900,7 @@
|
|
|
2811
2900
|
signalPaths: resolved.signalPaths,
|
|
2812
2901
|
signalValues: resolved.signalValues
|
|
2813
2902
|
});
|
|
2814
|
-
|
|
2815
|
-
results.push(unwrapServerResult(result));
|
|
2903
|
+
results.push(result);
|
|
2816
2904
|
continue;
|
|
2817
2905
|
}
|
|
2818
2906
|
|