@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/browser.ts CHANGED
@@ -20,8 +20,9 @@ const __asyncSignalModule = (() => {
20
20
  let version = 0;
21
21
  let registry;
22
22
  let registeredId = id;
23
- let activeController;
24
- let activeAbort;
23
+ let activeRun;
24
+ let executionToken = 0;
25
+ let disposed = false;
25
26
  const subscribers = new Set();
26
27
  const dependencyCleanups = new Set();
27
28
 
@@ -63,109 +64,62 @@ const __asyncSignalModule = (() => {
63
64
  },
64
65
 
65
66
  refresh() {
66
- if (!registry) {
67
+ if (!registry || disposed) {
67
68
  throw new Error(`Async signal "${registeredId}" is not registered.`);
68
69
  }
69
70
 
70
- if (activeAbort && !activeAbort.aborted) {
71
- activeAbort.cancel(new Error(`Async signal "${registeredId}" refreshed.`));
72
- }
71
+ cancelRun(activeRun, new Error(`Async signal "${registeredId}" refreshed.`));
73
72
 
73
+ const runRegistry = registry;
74
+ const runId = registeredId;
74
75
  const runVersion = version + 1;
75
76
  version = runVersion;
76
77
  loading = true;
77
78
  error = null;
78
79
  status = "loading";
79
80
 
80
- const controller = new AbortController();
81
- activeController = controller;
82
- activeAbort = controller.signal;
83
- attachCancel(activeAbort, controller);
81
+ const run = createRun(runRegistry, runId, runVersion);
82
+ activeRun = run;
84
83
  notify();
85
84
 
86
- const context = {
87
- signals: registry,
88
- id: registeredId,
89
- get server() {
90
- const context = registry._context?.() ?? {};
91
- const server = context.server;
92
- if (typeof server?._withContext === "function") {
93
- return server._withContext({
94
- signals: registry,
95
- router: context.router,
96
- loader: context.loader,
97
- cache: context.cache,
98
- abort: activeAbort,
99
- scheduler: context.scheduler
100
- });
101
- }
102
- return server;
103
- },
104
- get router() {
105
- return registry._context?.().router;
106
- },
107
- get loader() {
108
- return registry._context?.().loader;
109
- },
110
- get cache() {
111
- return registry._context?.().cache;
112
- },
113
- get scheduler() {
114
- return registry._context?.().scheduler;
115
- },
116
- get version() {
117
- return runVersion;
118
- },
119
- get abort() {
120
- return activeAbort;
121
- },
122
- refresh() {
123
- return state.refresh();
124
- }
125
- };
85
+ const context = createRunContext(run);
126
86
 
127
87
  let outcome;
128
88
  try {
129
- outcome = registry._collectDependencies(() => fn.call(context));
89
+ outcome = runRegistry._collectDependencies(() => fn.call(context));
130
90
  } catch (cause) {
131
- finishError(runVersion, cause);
91
+ finishError(run, cause);
132
92
  return Promise.reject(cause);
133
93
  }
134
94
 
135
- syncDependencies(outcome.dependencies);
95
+ syncDependencies(outcome.dependencies, run);
136
96
 
137
97
  return Promise.resolve(outcome.value).then(
138
98
  (nextValue) => {
139
- if (!isCurrent(runVersion)) {
99
+ if (!isRunCurrent(run)) {
140
100
  return value;
141
101
  }
142
102
  value = nextValue;
143
103
  loading = false;
144
104
  error = null;
145
105
  status = "ready";
106
+ activeRun = undefined;
146
107
  notify();
147
108
  return value;
148
109
  },
149
110
  (cause) => {
150
- if (!isCurrent(runVersion)) {
111
+ if (!isRunCurrent(run)) {
151
112
  return value;
152
113
  }
153
- if (activeAbort?.aborted) {
154
- loading = false;
155
- status = value === undefined ? "idle" : "ready";
156
- notify();
157
- return value;
158
- }
159
- finishError(runVersion, cause);
114
+ finishError(run, cause);
160
115
  return value;
161
116
  }
162
117
  );
163
118
  },
164
119
 
165
120
  cancel(reason) {
166
- if (activeAbort && !activeAbort.aborted) {
167
- activeAbort.cancel(reason);
168
- }
121
+ cancelCurrentRun(reason, { settle: true, notifyChange: true });
122
+ return value;
169
123
  },
170
124
 
171
125
  subscribe(fn) {
@@ -194,9 +148,7 @@ const __asyncSignalModule = (() => {
194
148
  if (!isAsyncSignalSnapshot(snapshot)) {
195
149
  return state.set(snapshot);
196
150
  }
197
- if (activeAbort && !activeAbort.aborted) {
198
- activeAbort.cancel(new Error(`Async signal "${registeredId}" restored from snapshot.`));
199
- }
151
+ cancelCurrentRun(new Error(`Async signal "${registeredId}" restored from snapshot.`));
200
152
  value = snapshot.value;
201
153
  loading = Boolean(snapshot.loading);
202
154
  error = snapshot.error ?? null;
@@ -212,7 +164,7 @@ const __asyncSignalModule = (() => {
212
164
  registry = nextRegistry;
213
165
  registeredId = nextId;
214
166
  const start = () => {
215
- if (registry === nextRegistry && status === "idle") {
167
+ if (!disposed && registry === nextRegistry && status === "idle") {
216
168
  state.refresh();
217
169
  }
218
170
  };
@@ -228,30 +180,161 @@ const __asyncSignalModule = (() => {
228
180
  },
229
181
 
230
182
  _dispose() {
231
- state.cancel(new Error(`Async signal "${registeredId}" disposed.`));
183
+ if (disposed) {
184
+ return;
185
+ }
186
+ disposed = true;
187
+ cancelQueuedWork();
188
+ cancelCurrentRun(new Error(`Async signal "${registeredId}" disposed.`));
232
189
  for (const cleanup of dependencyCleanups) {
233
190
  cleanup();
234
191
  }
235
192
  dependencyCleanups.clear();
236
193
  subscribers.clear();
194
+ registry = undefined;
237
195
  }
238
196
  };
239
197
 
240
- function finishError(runVersion, cause) {
241
- if (!isCurrent(runVersion)) {
198
+ function createRun(runRegistry, runId, runVersion) {
199
+ const controller = new AbortController();
200
+ const abort = controller.signal;
201
+ const runContext = captureRunContext(runRegistry, abort);
202
+ const run = {
203
+ token: ++executionToken,
204
+ version: runVersion,
205
+ registry: runRegistry,
206
+ id: runId,
207
+ controller,
208
+ abort,
209
+ ...runContext
210
+ };
211
+ attachCancel(abort, controller, (reason) => {
212
+ cancelRun(run, reason, { settle: true, notifyChange: true });
213
+ });
214
+ return run;
215
+ }
216
+
217
+ function captureRunContext(runRegistry, abort) {
218
+ const context = runRegistry._context?.() ?? {};
219
+ const serverContext = {
220
+ signals: runRegistry,
221
+ router: context.router,
222
+ loader: context.loader,
223
+ cache: context.cache,
224
+ abort,
225
+ scheduler: context.scheduler
226
+ };
227
+ const server = typeof context.server?._withContext === "function"
228
+ ? context.server._withContext(serverContext)
229
+ : context.server;
230
+
231
+ return {
232
+ signals: runRegistry,
233
+ server,
234
+ router: context.router,
235
+ loader: context.loader,
236
+ cache: context.cache,
237
+ scheduler: context.scheduler
238
+ };
239
+ }
240
+
241
+ function createRunContext(run) {
242
+ return {
243
+ signals: run.signals,
244
+ id: run.id,
245
+ get server() {
246
+ return run.server;
247
+ },
248
+ get router() {
249
+ return run.router;
250
+ },
251
+ get loader() {
252
+ return run.loader;
253
+ },
254
+ get cache() {
255
+ return run.cache;
256
+ },
257
+ get scheduler() {
258
+ return run.scheduler;
259
+ },
260
+ get version() {
261
+ return run.version;
262
+ },
263
+ get abort() {
264
+ return run.abort;
265
+ },
266
+ refresh() {
267
+ return state.refresh();
268
+ }
269
+ };
270
+ }
271
+
272
+ function finishError(run, cause) {
273
+ if (!isRunCurrent(run)) {
242
274
  return;
243
275
  }
244
276
  loading = false;
245
277
  error = cause;
246
278
  status = "error";
279
+ activeRun = undefined;
247
280
  notify();
248
281
  }
249
282
 
250
- function isCurrent(runVersion) {
251
- return runVersion === version && activeController?.signal === activeAbort;
283
+ function isRunCurrent(run) {
284
+ return Boolean(run)
285
+ && !disposed
286
+ && activeRun === run
287
+ && run.token === executionToken
288
+ && run.registry === registry
289
+ && run.id === registeredId
290
+ && !run.abort.aborted;
291
+ }
292
+
293
+ function cancelCurrentRun(reason, options = {}) {
294
+ const run = activeRun;
295
+ const shouldSettle = Boolean(run) || loading;
296
+ if (run) {
297
+ cancelRun(run, reason);
298
+ } else if (shouldSettle) {
299
+ executionToken += 1;
300
+ }
301
+ if (options.settle && shouldSettle && !disposed) {
302
+ settleCanceled(options.notifyChange);
303
+ }
304
+ }
305
+
306
+ function cancelRun(run, reason, options = {}) {
307
+ if (!run) {
308
+ return;
309
+ }
310
+ const wasActive = activeRun === run;
311
+ if (wasActive) {
312
+ executionToken += 1;
313
+ activeRun = undefined;
314
+ }
315
+ if (!run.abort.aborted) {
316
+ run.controller.abort(reason);
317
+ }
318
+ if (wasActive && options.settle && !disposed) {
319
+ settleCanceled(options.notifyChange);
320
+ }
321
+ }
322
+
323
+ function settleCanceled(notifyChange = false) {
324
+ const nextStatus = value === undefined ? "idle" : "ready";
325
+ const changed = loading || error !== null || status !== nextStatus;
326
+ loading = false;
327
+ error = null;
328
+ status = nextStatus;
329
+ if (notifyChange && changed) {
330
+ notify();
331
+ }
252
332
  }
253
333
 
254
- function syncDependencies(dependencies) {
334
+ function syncDependencies(dependencies, run) {
335
+ if (!isRunCurrent(run)) {
336
+ return;
337
+ }
255
338
  for (const cleanup of dependencyCleanups) {
256
339
  cleanup();
257
340
  }
@@ -260,15 +343,16 @@ const __asyncSignalModule = (() => {
260
343
  for (const dependency of dependencies) {
261
344
  const dependencyId = String(dependency).split(".")[0];
262
345
  if (dependencyId && dependencyId !== registeredId) {
263
- dependencyCleanups.add(registry.subscribe(dependency, () => scheduleRefresh()));
346
+ dependencyCleanups.add(run.registry.subscribe(dependency, () => scheduleRefresh()));
264
347
  }
265
348
  }
266
349
  }
267
350
 
268
351
  function scheduleRefresh() {
269
- if (activeAbort && !activeAbort.aborted) {
270
- activeAbort.cancel(new Error(`Async signal "${registeredId}" dependency changed.`));
352
+ if (disposed || !registry) {
353
+ return;
271
354
  }
355
+ cancelRun(activeRun, new Error(`Async signal "${registeredId}" dependency changed.`));
272
356
  const scheduler = registry?._context?.().scheduler;
273
357
  if (!scheduler) {
274
358
  state.refresh();
@@ -280,6 +364,11 @@ const __asyncSignalModule = (() => {
280
364
  });
281
365
  }
282
366
 
367
+ function cancelQueuedWork() {
368
+ const scheduler = registry?._context?.().scheduler;
369
+ scheduler?.cancelScope?.(registeredId);
370
+ }
371
+
283
372
  function notify() {
284
373
  for (const subscriber of [...subscribers]) {
285
374
  subscriber(state);
@@ -314,11 +403,15 @@ const __asyncSignalModule = (() => {
314
403
  return value === undefined ? "idle" : "ready";
315
404
  }
316
405
 
317
- function attachCancel(signal, controller) {
406
+ function attachCancel(signal, controller, onCancel) {
318
407
  Object.defineProperty(signal, "cancel", {
319
408
  configurable: true,
320
409
  enumerable: false,
321
410
  value(reason) {
411
+ if (typeof onCancel === "function") {
412
+ onCancel(reason);
413
+ return;
414
+ }
322
415
  controller.abort(reason);
323
416
  }
324
417
  });
@@ -2286,9 +2379,10 @@ const __componentModule = (() => {
2286
2379
  })();
2287
2380
 
2288
2381
  const __serverModule = (() => {
2289
- const serverEnvelopeKeys = new Set(["value", "signals", "boundary", "html", "redirect", "error"]);
2382
+ const serverEnvelopeKind = Symbol.for("@async/framework.serverResult");
2383
+ const serverEnvelopeWireKey = "__async_server_result__";
2384
+ const serverEnvelopeWireVersion = 1;
2290
2385
  const appliedServerResult = Symbol.for("@async/framework.appliedServerResult");
2291
- const appliedServerValues = new WeakSet();
2292
2386
 
2293
2387
  function createServerProxy({
2294
2388
  endpoint = "/__async/server",
@@ -2331,9 +2425,7 @@ const __serverModule = (() => {
2331
2425
  throw new Error(`Server function "${id}" failed with ${response.status}.`);
2332
2426
  }
2333
2427
 
2334
- const result = await readServerResponse(id, response);
2335
- await applyServerResult(result, runContext);
2336
- return markAppliedServerValue(unwrapServerResult(result));
2428
+ return consumeServerResult(await readServerResponse(id, response), runContext);
2337
2429
  }
2338
2430
 
2339
2431
  return createServerNamespace(run, {
@@ -2368,7 +2460,7 @@ const __serverModule = (() => {
2368
2460
  if (!isServerEnvelope(result)) {
2369
2461
  return result;
2370
2462
  }
2371
- if (result[appliedServerResult] || appliedServerValues.has(result)) {
2463
+ if (result[appliedServerResult]) {
2372
2464
  return result;
2373
2465
  }
2374
2466
 
@@ -2400,18 +2492,16 @@ const __serverModule = (() => {
2400
2492
  return result;
2401
2493
  }
2402
2494
 
2403
- function unwrapServerResult(result) {
2404
- if (isServerEnvelope(result) && Object.hasOwn(result, "value")) {
2405
- return result.value;
2406
- }
2407
- return result;
2495
+ async function consumeServerResult(result, context = {}) {
2496
+ await applyServerResult(result, context);
2497
+ return unwrapServerResult(result);
2408
2498
  }
2409
2499
 
2410
- function markAppliedServerValue(value) {
2411
- if (value && typeof value === "object") {
2412
- appliedServerValues.add(value);
2500
+ function unwrapServerResult(result) {
2501
+ if (isServerEnvelope(result)) {
2502
+ return Object.hasOwn(result, "value") ? result.value : undefined;
2413
2503
  }
2414
- return value;
2504
+ return result;
2415
2505
  }
2416
2506
 
2417
2507
  function markAppliedServerResult(result) {
@@ -2455,9 +2545,7 @@ const __serverModule = (() => {
2455
2545
  throw new Error("Server namespace is not directly callable.");
2456
2546
  }
2457
2547
  const context = contextProvider() ?? {};
2458
- const result = await run(parts.join("."), args, context);
2459
- await applyServerResult(result, context);
2460
- return unwrapServerResult(result);
2548
+ return run(parts.join("."), args, context);
2461
2549
  };
2462
2550
 
2463
2551
  const proxy = new Proxy(callable, {
@@ -2514,7 +2602,7 @@ const __serverModule = (() => {
2514
2602
 
2515
2603
  async function readServerResponse(id, response) {
2516
2604
  if (response.status === 204) {
2517
- return { value: undefined };
2605
+ return undefined;
2518
2606
  }
2519
2607
  const type = response.headers.get("content-type") ?? "";
2520
2608
  if (type.includes("application/json")) {
@@ -2532,7 +2620,7 @@ const __serverModule = (() => {
2532
2620
  if (typeof response.text !== "function") {
2533
2621
  throw new Error(`Server function "${id}" transport returned an invalid response: missing text().`);
2534
2622
  }
2535
- return { value: await response.text() };
2623
+ return response.text();
2536
2624
  }
2537
2625
 
2538
2626
  function snapshotSignalPaths(paths = [], signals) {
@@ -2677,7 +2765,8 @@ const __serverModule = (() => {
2677
2765
  if (!value || typeof value !== "object" || Array.isArray(value)) {
2678
2766
  return false;
2679
2767
  }
2680
- return Object.keys(value).some((key) => serverEnvelopeKeys.has(key));
2768
+ return value[serverEnvelopeKind] === true
2769
+ || value[serverEnvelopeWireKey] === serverEnvelopeWireVersion;
2681
2770
  }
2682
2771
 
2683
2772
  function toError(value) {
@@ -2699,11 +2788,11 @@ const __serverModule = (() => {
2699
2788
  throw new TypeError("Server function id must be a non-empty string.");
2700
2789
  }
2701
2790
  }
2702
- return { createServerProxy, resolveServerCommandArguments, applyServerResult, unwrapServerResult, defaultInput, createServerNamespace, createSignalReader, assertServerId };
2791
+ return { createServerProxy, resolveServerCommandArguments, applyServerResult, consumeServerResult, unwrapServerResult, defaultInput, createServerNamespace, createSignalReader, assertServerId };
2703
2792
  })();
2704
2793
 
2705
2794
  const __handlersModule = (() => {
2706
- const { applyServerResult, defaultInput, resolveServerCommandArguments, unwrapServerResult } = __serverModule;
2795
+ const { defaultInput, resolveServerCommandArguments } = __serverModule;
2707
2796
  const { attachRegistryInspection, createRegistryStore } = __registryStoreModule;
2708
2797
  const { createLazyRegistry, isLazyDescriptor } = __lazyRegistryModule;
2709
2798
  const builtInTokens = new Set(["prevent", "preventDefault", "stopPropagation", "stopImmediatePropagation"]);
@@ -2801,8 +2890,7 @@ const __handlersModule = (() => {
2801
2890
  signalPaths: resolved.signalPaths,
2802
2891
  signalValues: resolved.signalValues
2803
2892
  });
2804
- await applyServerResult(result, runContext);
2805
- results.push(unwrapServerResult(result));
2893
+ results.push(result);
2806
2894
  continue;
2807
2895
  }
2808
2896