@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/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 activeController;
26
- let activeAbort;
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
- if (activeAbort && !activeAbort.aborted) {
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 controller = new AbortController();
83
- activeController = controller;
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 = registry._collectDependencies(() => fn.call(context));
91
+ outcome = runRegistry._collectDependencies(() => fn.call(context));
132
92
  } catch (cause) {
133
- finishError(runVersion, cause);
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 (!isCurrent(runVersion)) {
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 (!isCurrent(runVersion)) {
113
+ if (!isRunCurrent(run)) {
153
114
  return value;
154
115
  }
155
- if (activeAbort?.aborted) {
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
- if (activeAbort && !activeAbort.aborted) {
169
- activeAbort.cancel(reason);
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
- if (activeAbort && !activeAbort.aborted) {
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
- state.cancel(new Error(`Async signal "${registeredId}" disposed.`));
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 finishError(runVersion, cause) {
243
- if (!isCurrent(runVersion)) {
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 isCurrent(runVersion) {
253
- return runVersion === version && activeController?.signal === activeAbort;
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
+ }
306
+ }
307
+
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
+ }
254
334
  }
255
335
 
256
- function syncDependencies(dependencies) {
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 (activeAbort && !activeAbort.aborted) {
272
- activeAbort.cancel(new Error(`Async signal "${registeredId}" dependency changed.`));
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
  });
@@ -2288,9 +2381,10 @@ const __componentModule = (() => {
2288
2381
  })();
2289
2382
 
2290
2383
  const __serverModule = (() => {
2291
- const serverEnvelopeKeys = new Set(["value", "signals", "boundary", "html", "redirect", "error"]);
2384
+ const serverEnvelopeKind = Symbol.for("@async/framework.serverResult");
2385
+ const serverEnvelopeWireKey = "__async_server_result__";
2386
+ const serverEnvelopeWireVersion = 1;
2292
2387
  const appliedServerResult = Symbol.for("@async/framework.appliedServerResult");
2293
- const appliedServerValues = new WeakSet();
2294
2388
 
2295
2389
  function createServerProxy({
2296
2390
  endpoint = "/__async/server",
@@ -2333,9 +2427,7 @@ const __serverModule = (() => {
2333
2427
  throw new Error(`Server function "${id}" failed with ${response.status}.`);
2334
2428
  }
2335
2429
 
2336
- const result = await readServerResponse(id, response);
2337
- await applyServerResult(result, runContext);
2338
- return markAppliedServerValue(unwrapServerResult(result));
2430
+ return consumeServerResult(await readServerResponse(id, response), runContext);
2339
2431
  }
2340
2432
 
2341
2433
  return createServerNamespace(run, {
@@ -2370,7 +2462,7 @@ const __serverModule = (() => {
2370
2462
  if (!isServerEnvelope(result)) {
2371
2463
  return result;
2372
2464
  }
2373
- if (result[appliedServerResult] || appliedServerValues.has(result)) {
2465
+ if (result[appliedServerResult]) {
2374
2466
  return result;
2375
2467
  }
2376
2468
 
@@ -2402,18 +2494,16 @@ const __serverModule = (() => {
2402
2494
  return result;
2403
2495
  }
2404
2496
 
2405
- function unwrapServerResult(result) {
2406
- if (isServerEnvelope(result) && Object.hasOwn(result, "value")) {
2407
- return result.value;
2408
- }
2409
- return result;
2497
+ async function consumeServerResult(result, context = {}) {
2498
+ await applyServerResult(result, context);
2499
+ return unwrapServerResult(result);
2410
2500
  }
2411
2501
 
2412
- function markAppliedServerValue(value) {
2413
- if (value && typeof value === "object") {
2414
- appliedServerValues.add(value);
2502
+ function unwrapServerResult(result) {
2503
+ if (isServerEnvelope(result)) {
2504
+ return Object.hasOwn(result, "value") ? result.value : undefined;
2415
2505
  }
2416
- return value;
2506
+ return result;
2417
2507
  }
2418
2508
 
2419
2509
  function markAppliedServerResult(result) {
@@ -2457,9 +2547,7 @@ const __serverModule = (() => {
2457
2547
  throw new Error("Server namespace is not directly callable.");
2458
2548
  }
2459
2549
  const context = contextProvider() ?? {};
2460
- const result = await run(parts.join("."), args, context);
2461
- await applyServerResult(result, context);
2462
- return unwrapServerResult(result);
2550
+ return run(parts.join("."), args, context);
2463
2551
  };
2464
2552
 
2465
2553
  const proxy = new Proxy(callable, {
@@ -2516,7 +2604,7 @@ const __serverModule = (() => {
2516
2604
 
2517
2605
  async function readServerResponse(id, response) {
2518
2606
  if (response.status === 204) {
2519
- return { value: undefined };
2607
+ return undefined;
2520
2608
  }
2521
2609
  const type = response.headers.get("content-type") ?? "";
2522
2610
  if (type.includes("application/json")) {
@@ -2534,7 +2622,7 @@ const __serverModule = (() => {
2534
2622
  if (typeof response.text !== "function") {
2535
2623
  throw new Error(`Server function "${id}" transport returned an invalid response: missing text().`);
2536
2624
  }
2537
- return { value: await response.text() };
2625
+ return response.text();
2538
2626
  }
2539
2627
 
2540
2628
  function snapshotSignalPaths(paths = [], signals) {
@@ -2679,7 +2767,8 @@ const __serverModule = (() => {
2679
2767
  if (!value || typeof value !== "object" || Array.isArray(value)) {
2680
2768
  return false;
2681
2769
  }
2682
- return Object.keys(value).some((key) => serverEnvelopeKeys.has(key));
2770
+ return value[serverEnvelopeKind] === true
2771
+ || value[serverEnvelopeWireKey] === serverEnvelopeWireVersion;
2683
2772
  }
2684
2773
 
2685
2774
  function toError(value) {
@@ -2701,11 +2790,11 @@ const __serverModule = (() => {
2701
2790
  throw new TypeError("Server function id must be a non-empty string.");
2702
2791
  }
2703
2792
  }
2704
- return { createServerProxy, resolveServerCommandArguments, applyServerResult, unwrapServerResult, defaultInput, createServerNamespace, createSignalReader, assertServerId };
2793
+ return { createServerProxy, resolveServerCommandArguments, applyServerResult, consumeServerResult, unwrapServerResult, defaultInput, createServerNamespace, createSignalReader, assertServerId };
2705
2794
  })();
2706
2795
 
2707
2796
  const __handlersModule = (() => {
2708
- const { applyServerResult, defaultInput, resolveServerCommandArguments, unwrapServerResult } = __serverModule;
2797
+ const { defaultInput, resolveServerCommandArguments } = __serverModule;
2709
2798
  const { attachRegistryInspection, createRegistryStore } = __registryStoreModule;
2710
2799
  const { createLazyRegistry, isLazyDescriptor } = __lazyRegistryModule;
2711
2800
  const builtInTokens = new Set(["prevent", "preventDefault", "stopPropagation", "stopImmediatePropagation"]);
@@ -2803,8 +2892,7 @@ const __handlersModule = (() => {
2803
2892
  signalPaths: resolved.signalPaths,
2804
2893
  signalValues: resolved.signalValues
2805
2894
  });
2806
- await applyServerResult(result, runContext);
2807
- results.push(unwrapServerResult(result));
2895
+ results.push(result);
2808
2896
  continue;
2809
2897
  }
2810
2898
 
@@ -5659,7 +5747,7 @@ const __requestContextModule = (() => {
5659
5747
  const __serverRegistryModule = (() => {
5660
5748
  const { readRequestContext } = __requestContextModule;
5661
5749
  const { attachRegistryInspection, createRegistryStore } = __registryStoreModule;
5662
- const { assertServerId, createServerNamespace, createSignalReader } = __serverModule;
5750
+ const { assertServerId, consumeServerResult, createServerNamespace, createSignalReader } = __serverModule;
5663
5751
  function createServerRegistry(initialMap = {}, options = {}) {
5664
5752
  const registryStore = options.registry ?? createRegistryStore();
5665
5753
  const type = options.type ?? "server";
@@ -5725,7 +5813,7 @@ const __serverRegistryModule = (() => {
5725
5813
  server
5726
5814
  };
5727
5815
 
5728
- return fn.call(runContext, ...args);
5816
+ return consumeServerResult(await fn.call(runContext, ...args), runContext);
5729
5817
  },
5730
5818
 
5731
5819
  _setContext(context = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@async/framework",
3
- "version": "0.11.5",
3
+ "version": "0.11.7",
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",