@async/framework 0.7.0 → 0.8.0

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/src/app.js CHANGED
@@ -4,16 +4,18 @@ import { createHandlerRegistry } from "./handlers.js";
4
4
  import { Loader } from "./loader.js";
5
5
  import { createPartialRegistry } from "./partials.js";
6
6
  import { createRouteRegistry, createRouter } from "./router.js";
7
- import { createServerRegistry } from "./server.js";
7
+ import { createScheduler } from "./scheduler.js";
8
+ import { createServerNamespace } from "./server.js";
8
9
  import { createSignal, createSignalRegistry } from "./signals.js";
9
10
  import { createRegistryStore } from "./registry-store.js";
10
11
  import { attributeName, normalizeAttributeConfig } from "./attributes.js";
11
12
 
12
13
  const registryTypes = new Set(["signal", "handler", "server", "partial", "route", "component"]);
13
14
 
14
- export function defineApp(initial) {
15
+ export function defineApp(initial, options = {}) {
15
16
  const registry = createRegistryStore(undefined, { target: "browser" });
16
17
  const runtimes = new Set();
18
+ const createRuntime = options.createRuntime ?? createApp;
17
19
 
18
20
  const app = {
19
21
  registry,
@@ -32,7 +34,7 @@ export function defineApp(initial) {
32
34
  },
33
35
 
34
36
  start(options = {}) {
35
- const runtime = createApp(app, options).start();
37
+ const runtime = createRuntime(app, options).start();
36
38
  app.runtime = runtime;
37
39
  return runtime;
38
40
  },
@@ -57,13 +59,18 @@ export function defineApp(initial) {
57
59
  export function createApp(appOrDefinition = Async, options = {}) {
58
60
  const app = isAppHub(appOrDefinition) ? appOrDefinition : defineApp(appOrDefinition ?? {});
59
61
  const target = options.target ?? "browser";
62
+ const scheduler = options.scheduler ?? options.loader?.scheduler ?? createScheduler({
63
+ strategy: target === "server" ? "manual" : "microtask"
64
+ });
65
+ const ownsScheduler = !options.scheduler && !options.loader?.scheduler;
60
66
  const attributes = normalizeAttributeConfig(options.attributes);
61
67
  const registry = options.registry ?? app.registry.view({ target });
62
68
  const signals = options.signals ?? createSignalRegistry(undefined, { registry, type: "signal" });
63
69
  const handlers = options.handlers ?? createHandlerRegistry(undefined, { registry, type: "handler" });
64
70
  const serverCache = createCacheRegistry(undefined, { registry, type: "cache.server" });
65
71
  const browserCache = createCacheRegistry(undefined, { registry, type: "cache.browser" });
66
- const server = options.server ?? createServerRegistry(undefined, { registry, type: "server" });
72
+ const serverFactory = options.serverFactory ?? createServerReferenceRegistry;
73
+ const server = options.server ?? serverFactory(undefined, { registry, type: "server" });
67
74
  const partials = options.partials ?? createPartialRegistry(undefined, { registry, type: "partial" });
68
75
  const routes = options.routes ?? createRouteRegistry(undefined, { registry, type: "route" });
69
76
  const components = options.components ?? createComponentRegistry(undefined, { registry, type: "component" });
@@ -91,6 +98,7 @@ export function createApp(appOrDefinition = Async, options = {}) {
91
98
  },
92
99
  loader,
93
100
  router,
101
+ scheduler,
94
102
  attributes,
95
103
 
96
104
  start() {
@@ -107,12 +115,13 @@ export function createApp(appOrDefinition = Async, options = {}) {
107
115
  handlers,
108
116
  server,
109
117
  cache: browserCache,
118
+ scheduler,
110
119
  attributes
111
120
  });
112
121
  runtime.loader = loader;
113
122
 
114
123
  configureServerContext({ cache: browserCache });
115
- signals._setContext?.({ server, loader, cache: browserCache });
124
+ signals._setContext?.({ server, loader, cache: browserCache, scheduler });
116
125
 
117
126
  loader.start();
118
127
 
@@ -128,6 +137,7 @@ export function createApp(appOrDefinition = Async, options = {}) {
128
137
  server,
129
138
  cache: browserCache,
130
139
  partials,
140
+ scheduler,
131
141
  fetch: options.fetch,
132
142
  routeEndpoint: options.routeEndpoint,
133
143
  attributes
@@ -139,7 +149,7 @@ export function createApp(appOrDefinition = Async, options = {}) {
139
149
  }
140
150
  } else {
141
151
  configureServerContext({ cache: serverCache });
142
- signals._setContext?.({ server, cache: serverCache });
152
+ signals._setContext?.({ server, cache: serverCache, scheduler });
143
153
  }
144
154
 
145
155
  return runtime;
@@ -153,9 +163,10 @@ export function createApp(appOrDefinition = Async, options = {}) {
153
163
  async render(url) {
154
164
  assertActive();
155
165
  configureServerContext({ cache: serverCache });
156
- signals._setContext?.({ server, cache: serverCache });
166
+ signals._setContext?.({ server, cache: serverCache, scheduler });
157
167
  const matched = routes.match(url);
158
168
  if (!matched) {
169
+ await scheduler.flush();
159
170
  return {
160
171
  html: renderDocument("", { status: 404, signals, browserCache, boundary: options.boundary ?? "route", attributes }),
161
172
  status: 404,
@@ -175,8 +186,8 @@ export function createApp(appOrDefinition = Async, options = {}) {
175
186
  cache: serverCache,
176
187
  browserCache,
177
188
  partials,
178
- request: options.request,
179
- locals: options.locals
189
+ scheduler,
190
+ ...currentRequestContext()
180
191
  })
181
192
  : { html: "" };
182
193
 
@@ -189,6 +200,8 @@ export function createApp(appOrDefinition = Async, options = {}) {
189
200
  browserCache.restore(result.cache.browser);
190
201
  }
191
202
 
203
+ await scheduler.flush();
204
+
192
205
  const status = result.status ?? 200;
193
206
  return {
194
207
  html: renderDocument(result.html, { status, signals, browserCache, boundary: result.boundary ?? options.boundary ?? "route", attributes }),
@@ -207,6 +220,9 @@ export function createApp(appOrDefinition = Async, options = {}) {
207
220
  router?.destroy?.();
208
221
  loader?.destroy?.();
209
222
  signals.destroy?.();
223
+ if (ownsScheduler) {
224
+ scheduler.destroy();
225
+ }
210
226
  },
211
227
 
212
228
  _applyUse(normalized) {
@@ -227,11 +243,23 @@ export function createApp(appOrDefinition = Async, options = {}) {
227
243
  loader,
228
244
  router,
229
245
  cache,
230
- request: options.request,
231
- locals: options.locals
246
+ scheduler,
247
+ requestContext: options.requestContext,
248
+ ...currentRequestContext()
232
249
  });
233
250
  }
234
251
 
252
+ function currentRequestContext() {
253
+ const context = readRequestContextLike(options.requestContext);
254
+ return {
255
+ requestContext: context,
256
+ request: context.request ?? options.request,
257
+ headers: context.headers ?? options.headers,
258
+ cookies: context.cookies ?? options.cookies,
259
+ locals: context.locals ?? options.locals
260
+ };
261
+ }
262
+
235
263
  function assertActive() {
236
264
  if (destroyed) {
237
265
  throw new Error("Async app runtime has been destroyed.");
@@ -385,6 +413,77 @@ function attachServerCache(server, cache) {
385
413
  }
386
414
  }
387
415
 
416
+ function createServerReferenceRegistry(initialMap = {}, options = {}) {
417
+ const registry = options.registry ?? createRegistryStore();
418
+ const type = options.type ?? "server";
419
+ const defaults = {};
420
+
421
+ const reference = {
422
+ registry,
423
+
424
+ register(id, value) {
425
+ registry.register(type, id, value);
426
+ return id;
427
+ },
428
+
429
+ registerMany(map) {
430
+ for (const [id, value] of Object.entries(map ?? {})) {
431
+ reference.register(id, value);
432
+ }
433
+ return reference;
434
+ },
435
+
436
+ unregister(id) {
437
+ return registry.unregister(type, id);
438
+ },
439
+
440
+ resolve() {
441
+ return undefined;
442
+ },
443
+
444
+ async run(id) {
445
+ throw new Error(`Server command "${id}" cannot run without a server proxy or server registry.`);
446
+ },
447
+
448
+ keys() {
449
+ return registry.keys(type);
450
+ },
451
+
452
+ entries() {
453
+ return registry.entries(type);
454
+ },
455
+
456
+ inspect() {
457
+ return registry.entries(type);
458
+ },
459
+
460
+ _setContext(context = {}) {
461
+ Object.assign(defaults, context);
462
+ return reference;
463
+ },
464
+
465
+ _adoptMany() {
466
+ return reference;
467
+ }
468
+ };
469
+
470
+ reference.registerMany(initialMap);
471
+ return createServerNamespace((id, args, context) => reference.run(id, args, context), reference, () => defaults);
472
+ }
473
+
474
+ function readRequestContextLike(store) {
475
+ if (!store) {
476
+ return {};
477
+ }
478
+ if (typeof store.get === "function") {
479
+ return store.get() ?? {};
480
+ }
481
+ if (typeof store.getStore === "function") {
482
+ return store.getStore() ?? {};
483
+ }
484
+ return {};
485
+ }
486
+
388
487
  function normalizeEntries(type, entries = {}) {
389
488
  if (type !== "signal") {
390
489
  return { ...(entries ?? {}) };
@@ -90,7 +90,8 @@ export function asyncSignal(id, fn) {
90
90
  router: context.router,
91
91
  loader: context.loader,
92
92
  cache: context.cache,
93
- abort: activeAbort
93
+ abort: activeAbort,
94
+ scheduler: context.scheduler
94
95
  });
95
96
  }
96
97
  return server;
@@ -104,6 +105,9 @@ export function asyncSignal(id, fn) {
104
105
  get cache() {
105
106
  return registry._context?.().cache;
106
107
  },
108
+ get scheduler() {
109
+ return registry._context?.().scheduler;
110
+ },
107
111
  get version() {
108
112
  return runVersion;
109
113
  },
@@ -180,11 +184,20 @@ export function asyncSignal(id, fn) {
180
184
  _bindRegistry(nextRegistry, nextId) {
181
185
  registry = nextRegistry;
182
186
  registeredId = nextId;
183
- queueMicrotask(() => {
187
+ const start = () => {
184
188
  if (registry === nextRegistry && status === "idle") {
185
189
  state.refresh();
186
190
  }
187
- });
191
+ };
192
+ const scheduler = registry._context?.().scheduler;
193
+ if (scheduler) {
194
+ scheduler.enqueue("async", start, {
195
+ scope: registeredId,
196
+ key: `asyncSignal:${registeredId}:initial`
197
+ });
198
+ } else {
199
+ queueMicrotask(start);
200
+ }
188
201
  },
189
202
 
190
203
  _dispose() {
@@ -220,11 +233,26 @@ export function asyncSignal(id, fn) {
220
233
  for (const dependency of dependencies) {
221
234
  const dependencyId = String(dependency).split(".")[0];
222
235
  if (dependencyId && dependencyId !== registeredId) {
223
- dependencyCleanups.add(registry.subscribe(dependency, () => state.refresh()));
236
+ dependencyCleanups.add(registry.subscribe(dependency, () => scheduleRefresh()));
224
237
  }
225
238
  }
226
239
  }
227
240
 
241
+ function scheduleRefresh() {
242
+ if (activeAbort && !activeAbort.aborted) {
243
+ activeAbort.cancel(new Error(`Async signal "${registeredId}" dependency changed.`));
244
+ }
245
+ const scheduler = registry?._context?.().scheduler;
246
+ if (!scheduler) {
247
+ state.refresh();
248
+ return;
249
+ }
250
+ scheduler.enqueue("async", () => state.refresh(), {
251
+ scope: registeredId,
252
+ key: `asyncSignal:${registeredId}:refresh`
253
+ });
254
+ }
255
+
228
256
  function notify() {
229
257
  for (const subscriber of [...subscribers]) {
230
258
  subscriber(state);
package/src/browser.js ADDED
@@ -0,0 +1,15 @@
1
+ export { asyncSignal } from "./async-signal.js";
2
+ export { Async, createApp, defineApp, readSnapshot } from "./app.js";
3
+ export { attributeName, defineAttributeConfig } from "./attributes.js";
4
+ export { createCacheRegistry, defineCache } from "./cache.js";
5
+ export { component, createComponentRegistry, defineComponent } from "./component.js";
6
+ export { delay } from "./delay.js";
7
+ export { createHandlerRegistry } from "./handlers.js";
8
+ export { html } from "./html.js";
9
+ export { Loader, AsyncLoader } from "./loader.js";
10
+ export { createPartialRegistry } from "./partials.js";
11
+ export { createRegistryStore } from "./registry-store.js";
12
+ export { createRouteRegistry, createRouter, defineRoute, route } from "./router.js";
13
+ export { createScheduler } from "./scheduler.js";
14
+ export { applyServerResult, createServerProxy, resolveServerCommandArguments, unwrapServerResult } from "./server.js";
15
+ export { computed, createSignal, createSignalRegistry, effect, signal } from "./signals.js";
package/src/component.js CHANGED
@@ -113,10 +113,15 @@ export function renderComponent(Component, props = {}, runtime, parentScope = "c
113
113
  html,
114
114
  attach(target) {
115
115
  for (const hook of attachHooks) {
116
- const cleanup = hook(target);
117
- if (typeof cleanup === "function") {
118
- cleanups.push(cleanup);
119
- }
116
+ runtime.scheduler?.enqueue("lifecycle", () => {
117
+ const cleanup = hook(target);
118
+ if (typeof cleanup === "function") {
119
+ cleanups.push(cleanup);
120
+ }
121
+ }, {
122
+ scope,
123
+ key: `attach:${attachHooks.indexOf(hook)}`
124
+ }) ?? runAttachHook(hook, target);
120
125
  }
121
126
  },
122
127
  mount(target) {
@@ -124,7 +129,17 @@ export function renderComponent(Component, props = {}, runtime, parentScope = "c
124
129
  },
125
130
  visible(target, observeVisible) {
126
131
  for (const hook of visibleHooks) {
127
- const cleanup = observeVisible(target, hook);
132
+ const cleanup = observeVisible(target, () => {
133
+ runtime.scheduler?.enqueue("lifecycle", () => {
134
+ const hookCleanup = hook(target);
135
+ if (typeof hookCleanup === "function") {
136
+ cleanups.push(hookCleanup);
137
+ }
138
+ }, {
139
+ scope,
140
+ key: `visible:${visibleHooks.indexOf(hook)}`
141
+ }) ?? runVisibleHook(hook, target);
142
+ });
128
143
  if (typeof cleanup === "function") {
129
144
  cleanups.push(cleanup);
130
145
  }
@@ -134,6 +149,7 @@ export function renderComponent(Component, props = {}, runtime, parentScope = "c
134
149
  while (destroyHooks.length > 0) {
135
150
  destroyHooks.pop()?.();
136
151
  }
152
+ runtime.scheduler?.markScopeDestroyed(scope);
137
153
  while (cleanups.length > 0) {
138
154
  cleanups.pop()?.();
139
155
  }
@@ -142,10 +158,24 @@ export function renderComponent(Component, props = {}, runtime, parentScope = "c
142
158
  }
143
159
  }
144
160
  };
161
+
162
+ function runAttachHook(hook, target) {
163
+ const cleanup = hook(target);
164
+ if (typeof cleanup === "function") {
165
+ cleanups.push(cleanup);
166
+ }
167
+ }
168
+
169
+ function runVisibleHook(hook, target) {
170
+ const cleanup = hook(target);
171
+ if (typeof cleanup === "function") {
172
+ cleanups.push(cleanup);
173
+ }
174
+ }
145
175
  }
146
176
 
147
177
  function createComponentContext({ runtime, scope, cleanups, attachHooks, visibleHooks, destroyHooks, renderScopedTemplate }) {
148
- const { signals, handlers, loader, server, router, cache } = runtime;
178
+ const { signals, handlers, loader, server, router, cache, scheduler } = runtime;
149
179
  const generatedHandlers = new WeakMap();
150
180
  let generatedHandlerCounter = 0;
151
181
  let generatedSignalCounter = 0;
@@ -157,6 +187,7 @@ function createComponentContext({ runtime, scope, cleanups, attachHooks, visible
157
187
  server,
158
188
  router,
159
189
  cache,
190
+ scheduler,
160
191
 
161
192
  signal(name, initial) {
162
193
  if (arguments.length === 1) {
@@ -201,7 +232,11 @@ function createComponentContext({ runtime, scope, cleanups, attachHooks, visible
201
232
  },
202
233
 
203
234
  effect(fn) {
204
- const cleanup = signals.effect(() => fn.call(context));
235
+ const cleanup = signals.effect(() => fn.call(context), {
236
+ scheduler,
237
+ phase: "effect",
238
+ scope
239
+ });
205
240
  cleanups.push(cleanup);
206
241
  return cleanup;
207
242
  },
package/src/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { asyncSignal } from "./async-signal.js";
2
- export { Async, createApp, defineApp, readSnapshot } from "./app.js";
2
+ export { Async, createApp, defineApp, readSnapshot } from "./server-entry.js";
3
3
  export { attributeName, defineAttributeConfig } from "./attributes.js";
4
4
  export { createCacheRegistry, defineCache } from "./cache.js";
5
5
  export { component, createComponentRegistry, defineComponent } from "./component.js";
@@ -10,5 +10,8 @@ export { Loader, AsyncLoader } from "./loader.js";
10
10
  export { createPartialRegistry } from "./partials.js";
11
11
  export { createRegistryStore } from "./registry-store.js";
12
12
  export { createRouteRegistry, createRouter, defineRoute, route } from "./router.js";
13
- export { createServerProxy, createServerRegistry } from "./server.js";
13
+ export { createScheduler } from "./scheduler.js";
14
+ export { createRequestContextStore } from "./request-context.js";
15
+ export { applyServerResult, createServerProxy, resolveServerCommandArguments, unwrapServerResult } from "./server.js";
16
+ export { createServerRegistry } from "./server-registry.js";
14
17
  export { computed, createSignal, createSignalRegistry, effect, signal } from "./signals.js";
package/src/loader.js CHANGED
@@ -1,15 +1,18 @@
1
1
  import { renderComponent } from "./component.js";
2
2
  import { createHandlerRegistry } from "./handlers.js";
3
+ import { createScheduler } from "./scheduler.js";
3
4
  import { createSignalRegistry, isSignalRef } from "./signals.js";
4
5
  import { matchAttribute, normalizeAttributeConfig, readAttribute } from "./attributes.js";
5
6
 
6
7
  const inlineBindingPrefix = "__async:inline:";
7
8
 
8
- export function Loader({ root, signals, handlers, server, router, cache, attributes } = {}) {
9
+ export function Loader({ root, signals, handlers, server, router, cache, attributes, scheduler } = {}) {
9
10
  const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
10
11
  const rootNode = root ?? documentRef;
11
12
  const signalRegistry = signals ?? createSignalRegistry();
12
13
  const handlerRegistry = handlers ?? createHandlerRegistry();
14
+ const schedulerInstance = scheduler ?? createScheduler();
15
+ const ownsScheduler = !scheduler;
13
16
  const attributeConfig = normalizeAttributeConfig(attributes);
14
17
  const cleanups = new Set();
15
18
  const eventBindings = new WeakMap();
@@ -30,6 +33,7 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
30
33
  server,
31
34
  router,
32
35
  cache,
36
+ scheduler: schedulerInstance,
33
37
  attributes: attributeConfig,
34
38
 
35
39
  start() {
@@ -69,6 +73,7 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
69
73
  server: api.server,
70
74
  router: api.router,
71
75
  cache: api.cache,
76
+ scheduler: schedulerInstance,
72
77
  attributes: attributeConfig
73
78
  });
74
79
  cleanupChildren(target);
@@ -89,6 +94,9 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
89
94
  runCleanup(cleanup);
90
95
  }
91
96
  cleanups.clear();
97
+ if (ownsScheduler) {
98
+ schedulerInstance.destroy();
99
+ }
92
100
  },
93
101
 
94
102
  _observeVisible(target, fn) {
@@ -106,13 +114,14 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
106
114
  }
107
115
  };
108
116
 
109
- signalRegistry._setContext?.({ server: api.server, router: api.router, loader: api, cache: api.cache });
117
+ signalRegistry._setContext?.({ server: api.server, router: api.router, loader: api, cache: api.cache, scheduler: schedulerInstance });
110
118
  api.server?._setContext?.({
111
119
  signals: signalRegistry,
112
120
  handlers: handlerRegistry,
113
121
  loader: api,
114
122
  router: api.router,
115
- cache: api.cache
123
+ cache: api.cache,
124
+ scheduler: schedulerInstance
116
125
  });
117
126
 
118
127
  function bindEventAttributes(scope) {
@@ -144,18 +153,19 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
144
153
 
145
154
  const listener = async (event) => {
146
155
  try {
147
- await handlerRegistry.run(ref, {
156
+ await schedulerInstance.batch(() => handlerRegistry.run(ref, {
148
157
  signals: signalRegistry,
149
158
  handlers: handlerRegistry,
150
159
  loader: api,
151
160
  server: api.server,
152
161
  router: api.router,
153
162
  cache: api.cache,
163
+ scheduler: schedulerInstance,
154
164
  event,
155
165
  element,
156
166
  el: element,
157
167
  root: rootNode
158
- });
168
+ }));
159
169
  } catch (error) {
160
170
  dispatchAsyncError(element, error);
161
171
  }
@@ -271,7 +281,12 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
271
281
 
272
282
  const read = () => readBinding(path, options);
273
283
  apply(read());
274
- addCleanup(subscribeBinding(path, () => apply(read())), element);
284
+ addCleanup(subscribeBinding(path, () => {
285
+ schedulerInstance.enqueue("binding", () => apply(read()), {
286
+ scope: element,
287
+ key
288
+ });
289
+ }), element);
275
290
  }
276
291
 
277
292
  function bindValueWriter(element, path) {
@@ -332,7 +347,12 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
332
347
  const state = {
333
348
  id,
334
349
  templates,
335
- cleanup: signalRegistry.subscribe(`${id}.$status`, () => renderBoundary(boundary))
350
+ cleanup: signalRegistry.subscribe(`${id}.$status`, () => {
351
+ schedulerInstance.enqueue("binding", () => renderBoundary(boundary), {
352
+ scope: boundary,
353
+ key: `boundary:${id}`
354
+ });
355
+ })
336
356
  };
337
357
  boundaryState.set(boundary, state);
338
358
  addCleanup(state.cleanup, boundary);
@@ -372,7 +392,7 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
372
392
  }
373
393
  mountedElements.add(element);
374
394
  for (const ref of refs) {
375
- runPseudo(element, ref);
395
+ scheduleLifecycle(element, () => runPseudo(element, ref), `attach:${ref}`);
376
396
  }
377
397
  }
378
398
 
@@ -385,7 +405,7 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
385
405
  continue;
386
406
  }
387
407
  visibleElements.add(element);
388
- addCleanup(observeVisible(element, () => runPseudo(element, ref)), element);
408
+ addCleanup(observeVisible(element, () => scheduleLifecycle(element, () => runPseudo(element, ref), `visible:${ref}`)), element);
389
409
  }
390
410
  }
391
411
 
@@ -409,6 +429,7 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
409
429
  server: api.server,
410
430
  router: api.router,
411
431
  cache: api.cache,
432
+ scheduler: schedulerInstance,
412
433
  element,
413
434
  el: element,
414
435
  root: rootNode
@@ -427,10 +448,13 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
427
448
  const ownerWindow = target.ownerDocument?.defaultView ?? globalThis;
428
449
  const Observer = ownerWindow.IntersectionObserver ?? globalThis.IntersectionObserver;
429
450
  if (!Observer) {
430
- queueMicrotask(() => {
451
+ schedulerInstance.enqueue("lifecycle", () => {
431
452
  if (!destroyed) {
432
453
  fn(target);
433
454
  }
455
+ }, {
456
+ scope: target,
457
+ key: "visible:fallback"
434
458
  });
435
459
  return () => {};
436
460
  }
@@ -485,6 +509,7 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
485
509
  }
486
510
  for (const element of elementsIn(node)) {
487
511
  runScopedCleanups(element);
512
+ schedulerInstance.markScopeDestroyed(element);
488
513
  }
489
514
  }
490
515
 
@@ -508,6 +533,13 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
508
533
  }
509
534
  }
510
535
 
536
+ function scheduleLifecycle(element, fn, key) {
537
+ schedulerInstance.enqueue("lifecycle", fn, {
538
+ scope: element,
539
+ key
540
+ });
541
+ }
542
+
511
543
  return api;
512
544
  }
513
545
 
@@ -0,0 +1,40 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+
3
+ export function createRequestContextStore() {
4
+ const storage = new AsyncLocalStorage();
5
+
6
+ return {
7
+ storage,
8
+
9
+ run(context, fn, ...args) {
10
+ if (typeof fn !== "function") {
11
+ throw new TypeError("requestContext.run(context, fn) requires a function.");
12
+ }
13
+ return storage.run(context ?? {}, fn, ...args);
14
+ },
15
+
16
+ get() {
17
+ return storage.getStore();
18
+ },
19
+
20
+ snapshot() {
21
+ return { ...(storage.getStore() ?? {}) };
22
+ }
23
+ };
24
+ }
25
+
26
+ export function readRequestContext(store) {
27
+ if (!store) {
28
+ return {};
29
+ }
30
+ if (typeof store.get === "function") {
31
+ return store.get() ?? {};
32
+ }
33
+ if (typeof store.getStore === "function") {
34
+ return store.getStore() ?? {};
35
+ }
36
+ if (typeof store === "object") {
37
+ return store;
38
+ }
39
+ return {};
40
+ }