@async/framework 0.4.0 → 0.6.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@async/framework",
3
- "version": "0.4.0",
4
- "description": "No-build AsyncLoader app runtime with signals, command events, server calls, route partials, cache split, SSR activation, and streaming boundaries.",
3
+ "version": "0.6.0",
4
+ "description": "No-build Loader app runtime with signals, command events, server calls, route partials, cache split, SSR activation, and streaming boundaries.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.1.0",
7
7
  "engines": {
@@ -28,21 +28,49 @@
28
28
  "web-framework"
29
29
  ],
30
30
  "license": "MIT",
31
- "unpkg": "./framework.js",
31
+ "types": "./framework.d.ts",
32
+ "unpkg": "./framework.umd.min.js",
33
+ "jsdelivr": "./framework.umd.min.js",
32
34
  "exports": {
33
35
  ".": {
34
- "unpkg": "./framework.js",
36
+ "types": "./framework.d.ts",
37
+ "unpkg": "./framework.umd.min.js",
35
38
  "browser": "./framework.js",
36
39
  "import": "./src/index.js",
37
40
  "default": "./src/index.js"
38
41
  },
39
- "./framework.js": "./framework.js",
42
+ "./framework.js": {
43
+ "types": "./framework.d.ts",
44
+ "default": "./framework.js"
45
+ },
46
+ "./framework.min.js": {
47
+ "types": "./framework.d.ts",
48
+ "default": "./framework.min.js"
49
+ },
50
+ "./framework.umd.js": {
51
+ "types": "./framework.d.ts",
52
+ "default": "./framework.umd.js"
53
+ },
54
+ "./framework.umd.min.js": {
55
+ "types": "./framework.d.ts",
56
+ "default": "./framework.umd.min.js"
57
+ },
58
+ "./framework.ts": {
59
+ "types": "./framework.d.ts",
60
+ "default": "./framework.ts"
61
+ },
62
+ "./framework.d.ts": "./framework.d.ts",
40
63
  "./package.json": "./package.json"
41
64
  },
42
65
  "files": [
43
66
  "CHANGELOG.md",
44
67
  "README.md",
68
+ "framework.d.ts",
45
69
  "framework.js",
70
+ "framework.min.js",
71
+ "framework.umd.js",
72
+ "framework.umd.min.js",
73
+ "framework.ts",
46
74
  "src",
47
75
  "examples",
48
76
  "LICENSE",
@@ -68,6 +96,7 @@
68
96
  "pipeline:sync:generate": "async-pipeline sync generate",
69
97
  "pipeline:task:docs.site": "async-pipeline run-task docs.site",
70
98
  "pipeline:verify": "async-pipeline run verify",
99
+ "registry:lint": "node scripts/registry-lint.js",
71
100
  "release:check": "pnpm run pipeline:verify -- --force && pnpm run pipeline:pages -- --force && pnpm run pipeline:sync:check && pnpm run pipeline:github:check",
72
101
  "test": "node --test tests/*.test.js"
73
102
  },
package/src/app.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createCacheRegistry } from "./cache.js";
2
2
  import { createComponentRegistry } from "./component.js";
3
3
  import { createHandlerRegistry } from "./handlers.js";
4
- import { AsyncLoader } from "./loader.js";
4
+ import { Loader } from "./loader.js";
5
5
  import { createPartialRegistry } from "./partials.js";
6
6
  import { createRouteRegistry, createRouter } from "./router.js";
7
7
  import { createServerRegistry } from "./server.js";
@@ -101,7 +101,7 @@ export function createApp(appOrDefinition = Async, options = {}) {
101
101
  started = true;
102
102
 
103
103
  if (target !== "server") {
104
- loader = loader ?? AsyncLoader({
104
+ loader = loader ?? Loader({
105
105
  root: options.root,
106
106
  signals,
107
107
  handlers,
@@ -82,7 +82,18 @@ export function asyncSignal(id, fn) {
82
82
  signals: registry,
83
83
  id: registeredId,
84
84
  get server() {
85
- return registry._context?.().server;
85
+ const context = registry._context?.() ?? {};
86
+ const server = context.server;
87
+ if (typeof server?._withContext === "function") {
88
+ return server._withContext({
89
+ signals: registry,
90
+ router: context.router,
91
+ loader: context.loader,
92
+ cache: context.cache,
93
+ abort: activeAbort
94
+ });
95
+ }
96
+ return server;
86
97
  },
87
98
  get router() {
88
99
  return registry._context?.().router;
package/src/cache.js CHANGED
@@ -34,6 +34,11 @@ export function createCacheRegistry(initialMap = {}, { now = () => Date.now(), r
34
34
  return registryApi;
35
35
  },
36
36
 
37
+ unregister(id) {
38
+ assertId(id);
39
+ return definitions.delete(id);
40
+ },
41
+
37
42
  resolve(id) {
38
43
  assertId(id);
39
44
  return definitions.get(id);
package/src/component.js CHANGED
@@ -1,4 +1,5 @@
1
- import { rawHtml, renderTemplate } from "./html.js";
1
+ import { attributeName } from "./attributes.js";
2
+ import { escapeHtml, rawHtml, renderTemplate } from "./html.js";
2
3
  import { attachRegistryInspection, createRegistryStore } from "./registry-store.js";
3
4
 
4
5
  const componentKind = Symbol.for("@async/framework.component");
@@ -44,6 +45,13 @@ export function createComponentRegistry(initialMap = {}, options = {}) {
44
45
  return registry;
45
46
  },
46
47
 
48
+ unregister(id) {
49
+ if (typeof id !== "string" || id.length === 0) {
50
+ throw new TypeError("Component id must be a non-empty string.");
51
+ }
52
+ return entries.delete(id);
53
+ },
54
+
47
55
  resolve(id) {
48
56
  if (typeof id !== "string" || id.length === 0) {
49
57
  throw new TypeError("Component id must be a non-empty string.");
@@ -75,29 +83,32 @@ export function renderComponent(Component, props = {}, runtime, parentScope = "c
75
83
  const visibleHooks = [];
76
84
  const destroyHooks = [];
77
85
  const bindingIds = [];
78
- const context = createComponentContext({
79
- runtime,
80
- scope,
81
- cleanups,
82
- attachHooks,
83
- visibleHooks,
84
- destroyHooks
85
- });
86
-
87
- const output = Component.call(context, props);
88
- const html = renderTemplate(output, {
86
+ const templateOptions = {
89
87
  attributes: runtime.attributes,
90
88
  signals: runtime.signals,
91
89
  bind(value) {
92
90
  const id = runtime.loader?._registerBinding?.(value);
93
91
  if (!id) {
94
- throw new Error("Inline template bindings require an AsyncLoader.");
92
+ throw new Error("Inline template bindings require a Loader.");
95
93
  }
96
94
  bindingIds.push(id);
97
95
  return id;
98
96
  }
97
+ };
98
+ const renderScopedTemplate = (value) => renderTemplate(value, templateOptions);
99
+ const context = createComponentContext({
100
+ runtime,
101
+ scope,
102
+ cleanups,
103
+ attachHooks,
104
+ visibleHooks,
105
+ destroyHooks,
106
+ renderScopedTemplate
99
107
  });
100
108
 
109
+ const output = Component.call(context, props);
110
+ const html = renderScopedTemplate(output);
111
+
101
112
  return {
102
113
  html,
103
114
  attach(target) {
@@ -133,7 +144,7 @@ export function renderComponent(Component, props = {}, runtime, parentScope = "c
133
144
  };
134
145
  }
135
146
 
136
- function createComponentContext({ runtime, scope, cleanups, attachHooks, visibleHooks, destroyHooks }) {
147
+ function createComponentContext({ runtime, scope, cleanups, attachHooks, visibleHooks, destroyHooks, renderScopedTemplate }) {
137
148
  const { signals, handlers, loader, server, router, cache } = runtime;
138
149
  const generatedHandlers = new WeakMap();
139
150
  let generatedHandlerCounter = 0;
@@ -149,14 +160,27 @@ function createComponentContext({ runtime, scope, cleanups, attachHooks, visible
149
160
 
150
161
  signal(name, initial) {
151
162
  if (arguments.length === 1) {
152
- return signals.ensure(scoped(scope, `signal.${++generatedSignalCounter}`), name);
163
+ const id = scoped(scope, `signal.${++generatedSignalCounter}`);
164
+ const ref = signals.ensure(id, name);
165
+ cleanups.push(() => signals.unregister?.(id));
166
+ return ref;
167
+ }
168
+ const id = scoped(scope, name);
169
+ const created = !signals.has(id);
170
+ const ref = signals.ensure(id, initial);
171
+ if (created) {
172
+ cleanups.push(() => signals.unregister?.(id));
153
173
  }
154
- return signals.ensure(scoped(scope, name), initial);
174
+ return ref;
155
175
  },
156
176
 
157
177
  computed(name, fn) {
158
178
  const id = scoped(scope, name);
179
+ const created = !signals.has(id);
159
180
  const ref = signals.ensure(id, undefined);
181
+ if (created) {
182
+ cleanups.push(() => signals.unregister?.(id));
183
+ }
160
184
  const cleanup = signals.effect(() => {
161
185
  signals.set(id, fn.call(context));
162
186
  });
@@ -166,9 +190,13 @@ function createComponentContext({ runtime, scope, cleanups, attachHooks, visible
166
190
 
167
191
  asyncSignal(name, fn) {
168
192
  const id = scoped(scope, name);
193
+ const created = !signals.has(id);
169
194
  if (!signals.has(id)) {
170
195
  signals.asyncSignal(id, fn);
171
196
  }
197
+ if (created) {
198
+ cleanups.push(() => signals.unregister?.(id));
199
+ }
172
200
  return signals.ref(id);
173
201
  },
174
202
 
@@ -202,6 +230,26 @@ function createComponentContext({ runtime, scope, cleanups, attachHooks, visible
202
230
  return rawHtml(child.html);
203
231
  },
204
232
 
233
+ suspense(signalRef, views) {
234
+ const id = signalRef?.id;
235
+ if (!id) {
236
+ throw new TypeError("this.suspense(signalRef, views) requires a signal ref.");
237
+ }
238
+
239
+ const normalized = normalizeSuspenseViews(views);
240
+ const chunks = [];
241
+ for (const state of ["loading", "ready", "error"]) {
242
+ const view = normalized[state];
243
+ if (!view) {
244
+ continue;
245
+ }
246
+ const attr = attributeName(runtime.attributes, "async", state);
247
+ const body = renderScopedTemplate(view.call(context, signalRef));
248
+ chunks.push(`<template ${attr}="${escapeHtml(id)}">${body}</template>`);
249
+ }
250
+ return rawHtml(chunks.join(""));
251
+ },
252
+
205
253
  on(eventName, fn) {
206
254
  if (typeof eventName !== "string" || eventName.length === 0) {
207
255
  throw new TypeError("Component lifecycle event must be a non-empty string.");
@@ -241,6 +289,7 @@ function createComponentContext({ runtime, scope, cleanups, attachHooks, visible
241
289
  handlers.register(id, function runComponentHandler(handlerContext) {
242
290
  return fn.call({ ...context, ...handlerContext }, handlerContext);
243
291
  });
292
+ cleanups.push(() => handlers.unregister?.(id));
244
293
  return id;
245
294
  }
246
295
  }
@@ -252,6 +301,21 @@ function scoped(scope, name) {
252
301
  return `${scope}.${name}`;
253
302
  }
254
303
 
304
+ function normalizeSuspenseViews(views) {
305
+ const normalized = typeof views === "function" ? { ready: views } : views;
306
+ if (!normalized || typeof normalized !== "object" || Array.isArray(normalized)) {
307
+ throw new TypeError("this.suspense(signalRef, views) requires views to be a function or object.");
308
+ }
309
+
310
+ for (const state of ["loading", "ready", "error"]) {
311
+ if (Object.hasOwn(normalized, state) && normalized[state] !== undefined && typeof normalized[state] !== "function") {
312
+ throw new TypeError(`this.suspense(signalRef, views) view "${state}" must be a function.`);
313
+ }
314
+ }
315
+
316
+ return normalized;
317
+ }
318
+
255
319
  function componentName(Component) {
256
320
  return Component.displayName || Component.name || "anonymous";
257
321
  }
package/src/handlers.js CHANGED
@@ -6,11 +6,10 @@ import {
6
6
  } from "./server.js";
7
7
  import { attachRegistryInspection, createRegistryStore } from "./registry-store.js";
8
8
 
9
- const builtInTokens = new Set(["preventDefault", "stopPropagation", "stopImmediatePropagation"]);
9
+ const builtInTokens = new Set(["prevent", "preventDefault", "stopPropagation", "stopImmediatePropagation"]);
10
10
  const builtInHandlers = {
11
- preventDefault() {
12
- this.event?.preventDefault?.();
13
- },
11
+ prevent: preventDefault,
12
+ preventDefault,
14
13
  stopPropagation() {
15
14
  this.event?.stopPropagation?.();
16
15
  },
@@ -19,6 +18,10 @@ const builtInHandlers = {
19
18
  }
20
19
  };
21
20
 
21
+ function preventDefault() {
22
+ this.event?.preventDefault?.();
23
+ }
24
+
22
25
  export function createHandlerRegistry(initialMap = {}, options = {}) {
23
26
  const registryStore = options.registry ?? createRegistryStore();
24
27
  const type = options.type ?? "handler";
@@ -44,6 +47,11 @@ export function createHandlerRegistry(initialMap = {}, options = {}) {
44
47
  return registry;
45
48
  },
46
49
 
50
+ unregister(id) {
51
+ assertId(id);
52
+ return handlers.delete(id);
53
+ },
54
+
47
55
  resolve(id) {
48
56
  assertId(id);
49
57
  return handlers.get(id);
package/src/index.js CHANGED
@@ -6,7 +6,7 @@ export { component, createComponentRegistry, defineComponent } from "./component
6
6
  export { delay } from "./delay.js";
7
7
  export { createHandlerRegistry } from "./handlers.js";
8
8
  export { html } from "./html.js";
9
- export { AsyncLoader } from "./loader.js";
9
+ 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";
package/src/loader.js CHANGED
@@ -5,7 +5,7 @@ import { matchAttribute, normalizeAttributeConfig, readAttribute } from "./attri
5
5
 
6
6
  const inlineBindingPrefix = "__async:inline:";
7
7
 
8
- export function AsyncLoader({ root, signals, handlers, server, router, cache, attributes } = {}) {
8
+ export function Loader({ root, signals, handlers, server, router, cache, attributes } = {}) {
9
9
  const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
10
10
  const rootNode = root ?? documentRef;
11
11
  const signalRegistry = signals ?? createSignalRegistry();
@@ -19,6 +19,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
19
19
  const boundaryState = new WeakMap();
20
20
  const renderingBoundaries = new WeakSet();
21
21
  const inlineBindings = new Map();
22
+ const scopedCleanups = new WeakMap();
22
23
  let inlineBindingCounter = 0;
23
24
  let destroyed = false;
24
25
 
@@ -53,6 +54,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
53
54
  if (!boundary) {
54
55
  throw new Error(`Boundary "${boundaryId}" was not found.`);
55
56
  }
57
+ cleanupChildren(boundary);
56
58
  boundary.replaceChildren(toFragment(fragmentOrTemplate, documentRef));
57
59
  api.scan(boundary);
58
60
  return boundary;
@@ -69,11 +71,12 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
69
71
  cache: api.cache,
70
72
  attributes: attributeConfig
71
73
  });
74
+ cleanupChildren(target);
72
75
  target.replaceChildren(toFragment(rendered.html, target.ownerDocument));
73
76
  api.scan(target);
74
77
  rendered.mount(target);
75
78
  rendered.visible(target, api._observeVisible);
76
- cleanups.add(rendered.cleanup);
79
+ addCleanup(rendered.cleanup, target, "children");
77
80
  return rendered;
78
81
  },
79
82
 
@@ -83,7 +86,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
83
86
  }
84
87
  destroyed = true;
85
88
  for (const cleanup of [...cleanups]) {
86
- cleanup();
89
+ runCleanup(cleanup);
87
90
  }
88
91
  cleanups.clear();
89
92
  },
@@ -104,6 +107,13 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
104
107
  };
105
108
 
106
109
  signalRegistry._setContext?.({ server: api.server, router: api.router, loader: api, cache: api.cache });
110
+ api.server?._setContext?.({
111
+ signals: signalRegistry,
112
+ handlers: handlerRegistry,
113
+ loader: api,
114
+ router: api.router,
115
+ cache: api.cache
116
+ });
107
117
 
108
118
  function bindEventAttributes(scope) {
109
119
  for (const element of elementsIn(scope)) {
@@ -152,7 +162,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
152
162
  };
153
163
 
154
164
  element.addEventListener(eventName, listener);
155
- cleanups.add(() => element.removeEventListener(eventName, listener));
165
+ addCleanup(() => element.removeEventListener(eventName, listener), element);
156
166
  }
157
167
 
158
168
  function bindSignalAttributes(scope) {
@@ -187,6 +197,12 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
187
197
  bindSignal(element, `attr:${attr}:${path}`, path, (value) => updateAttribute(element, attr, value));
188
198
  continue;
189
199
  }
200
+ if (signalName.startsWith("prop:")) {
201
+ const prop = signalName.slice("prop:".length);
202
+ const path = element.getAttribute(name);
203
+ bindSignal(element, `prop:${prop}:${path}`, path, (value) => updateProperty(element, prop, value));
204
+ continue;
205
+ }
190
206
  if (signalName.startsWith("class:")) {
191
207
  const className = signalName.slice("class:".length);
192
208
  const path = element.getAttribute(name);
@@ -255,7 +271,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
255
271
 
256
272
  const read = () => readBinding(path, options);
257
273
  apply(read());
258
- cleanups.add(subscribeBinding(path, () => apply(read())));
274
+ addCleanup(subscribeBinding(path, () => apply(read())), element);
259
275
  }
260
276
 
261
277
  function bindValueWriter(element, path) {
@@ -319,7 +335,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
319
335
  cleanup: signalRegistry.subscribe(`${id}.$status`, () => renderBoundary(boundary))
320
336
  };
321
337
  boundaryState.set(boundary, state);
322
- cleanups.add(state.cleanup);
338
+ addCleanup(state.cleanup, boundary);
323
339
  }
324
340
  renderBoundary(boundary);
325
341
  }
@@ -335,6 +351,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
335
351
  if (!template) {
336
352
  return;
337
353
  }
354
+ cleanupChildren(boundary);
338
355
  boundary.replaceChildren(template.content.cloneNode(true));
339
356
  renderingBoundaries.add(boundary);
340
357
  try {
@@ -368,7 +385,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
368
385
  continue;
369
386
  }
370
387
  visibleElements.add(element);
371
- cleanups.add(observeVisible(element, () => runPseudo(element, ref)));
388
+ addCleanup(observeVisible(element, () => runPseudo(element, ref)), element);
372
389
  }
373
390
  }
374
391
 
@@ -398,7 +415,7 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
398
415
  });
399
416
  for (const result of results) {
400
417
  if (typeof result === "function") {
401
- cleanups.add(result);
418
+ addCleanup(result, element);
402
419
  }
403
420
  }
404
421
  } catch (error) {
@@ -430,13 +447,72 @@ export function AsyncLoader({ root, signals, handlers, server, router, cache, at
430
447
 
431
448
  function assertActive() {
432
449
  if (destroyed) {
433
- throw new Error("AsyncLoader has been destroyed.");
450
+ throw new Error("Loader has been destroyed.");
451
+ }
452
+ }
453
+
454
+ function addCleanup(cleanup, owner, mode = "self") {
455
+ if (typeof cleanup !== "function") {
456
+ return cleanup;
457
+ }
458
+ cleanups.add(cleanup);
459
+ if (owner) {
460
+ const records = scopedCleanups.get(owner) ?? [];
461
+ records.push({ cleanup, mode });
462
+ scopedCleanups.set(owner, records);
463
+ }
464
+ return cleanup;
465
+ }
466
+
467
+ function runCleanup(cleanup) {
468
+ if (typeof cleanup !== "function" || !cleanups.has(cleanup)) {
469
+ return;
470
+ }
471
+ cleanups.delete(cleanup);
472
+ cleanup();
473
+ }
474
+
475
+ function cleanupChildren(container) {
476
+ runScopedCleanups(container, "children");
477
+ for (const child of [...(container.childNodes ?? [])]) {
478
+ cleanupNode(child);
479
+ }
480
+ }
481
+
482
+ function cleanupNode(node) {
483
+ if (node.nodeType !== 1) {
484
+ return;
485
+ }
486
+ for (const element of elementsIn(node)) {
487
+ runScopedCleanups(element);
488
+ }
489
+ }
490
+
491
+ function runScopedCleanups(element, mode) {
492
+ const records = scopedCleanups.get(element);
493
+ if (!records) {
494
+ return;
495
+ }
496
+ const remaining = [];
497
+ for (const record of records) {
498
+ if (mode && record.mode !== mode) {
499
+ remaining.push(record);
500
+ continue;
501
+ }
502
+ runCleanup(record.cleanup);
503
+ }
504
+ if (remaining.length > 0) {
505
+ scopedCleanups.set(element, remaining);
506
+ } else {
507
+ scopedCleanups.delete(element);
434
508
  }
435
509
  }
436
510
 
437
511
  return api;
438
512
  }
439
513
 
514
+ export const AsyncLoader = Loader;
515
+
440
516
  function normalizeClassTokens(value, tokens = new Set()) {
441
517
  if (value == null || value === false) {
442
518
  return tokens;
@@ -581,6 +657,14 @@ function updateAttribute(element, attr, value) {
581
657
  }
582
658
  }
583
659
 
660
+ function updateProperty(element, prop, value) {
661
+ if (value == null) {
662
+ element[prop] = "";
663
+ return;
664
+ }
665
+ element[prop] = value;
666
+ }
667
+
584
668
  function selectAll(scope, selector) {
585
669
  const elements = [];
586
670
  if (scope?.nodeType === 1 && scope.matches?.(selector)) {
package/src/partials.js CHANGED
@@ -26,6 +26,11 @@ export function createPartialRegistry(initialMap = {}, options = {}) {
26
26
  return registry;
27
27
  },
28
28
 
29
+ unregister(id) {
30
+ assertId(id);
31
+ return entries.delete(id);
32
+ },
33
+
29
34
  resolve(id) {
30
35
  assertId(id);
31
36
  return entries.get(id);
@@ -34,6 +34,10 @@ export function createRegistryStore(initial = {}, options = {}) {
34
34
  return value;
35
35
  },
36
36
 
37
+ unregister(type, id) {
38
+ return registry.delete(type, id);
39
+ },
40
+
37
41
  delete(type, id) {
38
42
  return registry._map(type).delete(id);
39
43
  },
package/src/router.js CHANGED
@@ -1,4 +1,4 @@
1
- import { AsyncLoader } from "./loader.js";
1
+ import { Loader } from "./loader.js";
2
2
  import { createHandlerRegistry } from "./handlers.js";
3
3
  import { createSignalRegistry } from "./signals.js";
4
4
  import { applyServerResult } from "./server.js";
@@ -41,6 +41,15 @@ export function createRouteRegistry(initialMap = {}, options = {}) {
41
41
  return registry;
42
42
  },
43
43
 
44
+ unregister(pattern) {
45
+ assertPattern(pattern);
46
+ const index = routes.findIndex((candidate) => candidate.pattern === pattern);
47
+ if (index !== -1) {
48
+ routes.splice(index, 1);
49
+ }
50
+ return entries.delete(pattern);
51
+ },
52
+
44
53
  match(url) {
45
54
  const path = toUrl(url).pathname;
46
55
  for (const candidate of routes) {
@@ -119,7 +128,7 @@ export function createRouter({
119
128
  const attributeConfig = normalizeAttributeConfig(attributes ?? loader?.attributes);
120
129
  const loaderInstance =
121
130
  loader ??
122
- AsyncLoader({
131
+ Loader({
123
132
  root: rootNode,
124
133
  signals: signalRegistry,
125
134
  handlers: handlerRegistry,