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