@async/framework 0.2.2 → 0.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 - 2026-06-17
4
+
5
+ - Added a shared registry store behind `Async`, app runtimes, and concrete
6
+ registries so apps can inspect signals, handlers, server ids, routes,
7
+ partials, components, and split cache state from one place.
8
+ - Added configurable HTML attribute prefixes, with `async:*`, `signal:*`, and
9
+ `on:*` as the defaults plus explicit support for `data-async-*`,
10
+ `data-signal-*`, and `data-on-*`.
11
+ - Declared the UNPKG package entry explicitly so the package root can be used as
12
+ a no-build browser ESM CDN import.
13
+ - Documented the UNPKG import-map setup for importing `@async/framework` by
14
+ package name in no-build browser apps.
15
+
3
16
  ## 0.2.2 - 2026-06-17
4
17
 
5
18
  - Fixed release doctor validation to accept the pipeline-generated GitHub
package/README.md CHANGED
@@ -10,9 +10,9 @@ pnpm add @async/framework
10
10
  ```
11
11
 
12
12
  ```html
13
- <main data-async-container>
13
+ <main async:container>
14
14
  <button type="button" on:click="decrement">-</button>
15
- <strong data-async-text="count"></strong>
15
+ <strong signal:text="count"></strong>
16
16
  <button type="button" on:click="increment">+</button>
17
17
  </main>
18
18
  <script type="module" src="./main.js"></script>
@@ -69,12 +69,78 @@ pnpm add @async/framework
69
69
  The package is ESM-only and supports Node.js 24 and newer for tests, examples,
70
70
  and package lifecycle tooling. Browser consumers import ESM directly.
71
71
 
72
+ ## CDN
73
+
74
+ The package is browser-ready ESM and can be loaded from UNPKG without a build
75
+ step. Use `@latest` for quick prototypes, and pin an exact version in
76
+ production:
77
+
78
+ ```html
79
+ <main async:container>
80
+ <button type="button" on:click="increment">+</button>
81
+ <strong signal:text="count"></strong>
82
+ </main>
83
+
84
+ <script type="module">
85
+ import {
86
+ Async,
87
+ createSignal
88
+ } from "https://unpkg.com/@async/framework@latest";
89
+
90
+ Async.use({
91
+ signal: {
92
+ count: createSignal(0)
93
+ },
94
+ handler: {
95
+ increment() {
96
+ this.signals.update("count", (count) => count + 1);
97
+ }
98
+ }
99
+ });
100
+
101
+ Async.start({ root: document });
102
+ </script>
103
+ ```
104
+
105
+ You can also use an import map so app code imports `@async/framework` by name:
106
+
107
+ ```html
108
+ <script type="importmap">
109
+ {
110
+ "imports": {
111
+ "@async/framework": "https://unpkg.com/@async/framework@latest"
112
+ }
113
+ }
114
+ </script>
115
+
116
+ <script type="module">
117
+ import {
118
+ Async,
119
+ createSignal
120
+ } from "@async/framework";
121
+
122
+ Async.use({
123
+ signal: {
124
+ count: createSignal(0)
125
+ },
126
+ handler: {
127
+ increment() {
128
+ this.signals.update("count", (count) => count + 1);
129
+ }
130
+ }
131
+ });
132
+
133
+ Async.start({ root: document });
134
+ </script>
135
+ ```
136
+
72
137
  ## Core API
73
138
 
74
139
  ```js
75
140
  import {
76
141
  AsyncLoader,
77
142
  Async,
143
+ attributeName,
78
144
  asyncSignal,
79
145
  createApp,
80
146
  createCacheRegistry,
@@ -84,11 +150,13 @@ import {
84
150
  createSignal,
85
151
  createHandlerRegistry,
86
152
  createPartialRegistry,
153
+ createRegistryStore,
87
154
  createRouteRegistry,
88
155
  createRouter,
89
156
  createServerProxy,
90
157
  createServerRegistry,
91
158
  createSignalRegistry,
159
+ defineAttributeConfig,
92
160
  defineApp,
93
161
  defineCache,
94
162
  defineComponent,
@@ -170,6 +238,50 @@ Naming rules:
170
238
  Singular registry keys are canonical: `signal`, `handler`, `server`,
171
239
  `partial`, `route`, `component`, and nested `cache.browser` / `cache.server`.
172
240
 
241
+ ### Registry Inspection
242
+
243
+ `Async.registry` is the global inspection surface for registered app pieces.
244
+ Every runtime and concrete registry also points at the same backing store:
245
+
246
+ ```js
247
+ Async.registry.keys("signal");
248
+ Async.registry.entries("route");
249
+ Async.registry.snapshot();
250
+
251
+ const runtime = Async.start({ root: document });
252
+
253
+ runtime.registry.keys("handler");
254
+ runtime.signals.registry === runtime.registry;
255
+ runtime.browser.cache.registry === runtime.registry;
256
+ ```
257
+
258
+ Supported inspection types:
259
+
260
+ ```txt
261
+ signal
262
+ handler
263
+ server
264
+ partial
265
+ route
266
+ component
267
+ cache.browser
268
+ cache.server
269
+ cache.browser.entries
270
+ cache.server.entries
271
+ ```
272
+
273
+ Browser runtime inspection exposes server ids as descriptors, not executable
274
+ server functions, and does not expose server cache contents:
275
+
276
+ ```js
277
+ runtime.registry.keys("server");
278
+ runtime.registry.get("server", "products.get");
279
+ // { id: "products.get", kind: "server" }
280
+
281
+ runtime.registry.snapshot().entries.server;
282
+ // {}
283
+ ```
284
+
173
285
  ### Signals
174
286
 
175
287
  ```js
@@ -249,33 +361,50 @@ AsyncLoader scans regular HTML attributes:
249
361
 
250
362
  | Attribute | Behavior |
251
363
  | --- | --- |
252
- | `data-async-container` | Marks a scannable app root |
364
+ | `async:container` | Marks a scannable app root |
253
365
  | `on:click="selectProduct"` | Delegated command event |
254
366
  | `on:submit="preventDefault; save"` | Sequential command chain |
255
367
  | `on:click="server.cart.add(productId)"` | Server command with signal args |
256
- | `data-async-text="product.title"` | Text binding |
257
- | `data-async-value="productId"` | Form value binding with writeback |
258
- | `data-async-attr:disabled="product.$loading"` | Attribute binding |
259
- | `data-async-class:selected="selected"` | Class toggle |
260
- | `data-async-boundary="product"` | Async or streamed replacement boundary |
261
- | `data-async-loading="product"` | Boundary loading template |
262
- | `data-async-ready="product"` | Boundary ready template |
263
- | `data-async-error="product"` | Boundary error template |
368
+ | `signal:text="product.title"` | Text binding |
369
+ | `signal:value="productId"` | Form value binding with writeback |
370
+ | `signal:attr:disabled="product.$loading"` | Attribute binding |
371
+ | `signal:class:selected="selected"` | Class toggle |
372
+ | `async:boundary="product"` | Async or streamed replacement boundary |
373
+ | `async:loading="product"` | Boundary loading template |
374
+ | `async:ready="product"` | Boundary ready template |
375
+ | `async:error="product"` | Boundary error template |
264
376
 
265
377
  ```html
266
- <section data-async-boundary="product">
267
- <template data-async-loading="product">
378
+ <section async:boundary="product">
379
+ <template async:loading="product">
268
380
  <p>Loading...</p>
269
381
  </template>
270
- <template data-async-ready="product">
271
- <h1 data-async-text="product.title"></h1>
382
+ <template async:ready="product">
383
+ <h1 signal:text="product.title"></h1>
272
384
  </template>
273
- <template data-async-error="product">
274
- <p data-async-text="product.$error.message"></p>
385
+ <template async:error="product">
386
+ <p signal:text="product.$error.message"></p>
275
387
  </template>
276
388
  </section>
277
389
  ```
278
390
 
391
+ The default prefixes are `async:`, `signal:`, and `on:`. You can switch to
392
+ data attributes when a host needs that shape:
393
+
394
+ ```js
395
+ Async.start({
396
+ root: document,
397
+ attributes: {
398
+ async: "data-async-",
399
+ signal: "data-signal-",
400
+ on: "data-on-"
401
+ }
402
+ });
403
+ ```
404
+
405
+ That maps to `data-async-container`, `data-on-click="save"`, and
406
+ `data-signal-text="product.title"`.
407
+
279
408
  ### Command Events
280
409
 
281
410
  `on:*` works with any native DOM event name. `on:mount` and `on:visible` are
@@ -417,13 +546,13 @@ Router modes:
417
546
  CSR startup can use an empty route boundary:
418
547
 
419
548
  ```html
420
- <main data-async-container>
549
+ <main async:container>
421
550
  <nav>
422
551
  <a href="/">Home</a>
423
552
  <a href="/products/sku-1">Product</a>
424
553
  </nav>
425
554
 
426
- <section data-async-boundary="route"></section>
555
+ <section async:boundary="route"></section>
427
556
  </main>
428
557
  ```
429
558
 
@@ -520,10 +649,10 @@ const response = await serverRuntime.render("/products/123");
520
649
  The returned HTML includes a route boundary plus a JSON snapshot:
521
650
 
522
651
  ```html
523
- <section data-async-boundary="route">
652
+ <section async:boundary="route">
524
653
  <!-- server-rendered route partial -->
525
654
  </section>
526
- <script type="application/json" data-async-snapshot>{}</script>
655
+ <script type="application/json" async:snapshot>{}</script>
527
656
  ```
528
657
 
529
658
  Browser activation scans the existing HTML and attaches events. It does not
@@ -558,8 +687,8 @@ const Toggle = defineComponent(function Toggle() {
558
687
  <button
559
688
  type="button"
560
689
  on:click="${toggle}"
561
- data-async-class:selected="${selected.id}"
562
- data-async-attr:aria-pressed="${selected.id}"
690
+ signal:class:selected="${selected.id}"
691
+ signal:attr:aria-pressed="${selected.id}"
563
692
  >
564
693
  Toggle
565
694
  </button>
@@ -597,7 +726,7 @@ loader.swap(
597
726
  "product",
598
727
  `
599
728
  <article>
600
- <h1 data-async-text="product.title"></h1>
729
+ <h1 signal:text="product.title"></h1>
601
730
  <button type="button" on:click="selectProduct">Select</button>
602
731
  </article>
603
732
  `
@@ -6,10 +6,10 @@
6
6
  <title>AsyncLoader Cache</title>
7
7
  </head>
8
8
  <body>
9
- <main data-async-container>
9
+ <main async:container>
10
10
  <button type="button" on:click="cacheDemo.loadProduct">Load product</button>
11
- <p>Product: <strong data-async-text="cacheDemo.title"></strong></p>
12
- <p>Server calls: <strong data-async-text="cacheDemo.calls"></strong></p>
11
+ <p>Product: <strong signal:text="cacheDemo.title"></strong></p>
12
+ <p>Server calls: <strong signal:text="cacheDemo.calls"></strong></p>
13
13
  </main>
14
14
  <script type="module" src="./main.js"></script>
15
15
  </body>
@@ -14,8 +14,8 @@ const Toggle = defineComponent(function Toggle() {
14
14
  <button
15
15
  type="button"
16
16
  on:click="${toggle}"
17
- data-async-class:selected="${selected.id}"
18
- data-async-attr:aria-pressed="${selected.id}"
17
+ signal:class:selected="${selected.id}"
18
+ signal:attr:aria-pressed="${selected.id}"
19
19
  >
20
20
  Toggle
21
21
  </button>
@@ -4,9 +4,9 @@
4
4
  <meta charset="utf-8">
5
5
  <title>AsyncLoader Counter</title>
6
6
  </head>
7
- <body data-async-container>
7
+ <body async:container>
8
8
  <main>
9
- <p>Count: <strong data-async-text="counter.count"></strong></p>
9
+ <p>Count: <strong signal:text="counter.count"></strong></p>
10
10
  <button type="button" on:click="counter.decrement">-</button>
11
11
  <button type="button" on:click="counter.increment">+</button>
12
12
  </main>
@@ -6,9 +6,9 @@
6
6
  <title>AsyncLoader Partials</title>
7
7
  </head>
8
8
  <body>
9
- <main data-async-container>
9
+ <main async:container>
10
10
  <button type="button" on:click="partialsDemo.loadProduct">Load product</button>
11
- <section data-async-boundary="product"></section>
11
+ <section async:boundary="product"></section>
12
12
  </main>
13
13
  <script type="module" src="./main.js"></script>
14
14
  </body>
@@ -4,26 +4,26 @@
4
4
  <meta charset="utf-8">
5
5
  <title>AsyncLoader Product</title>
6
6
  </head>
7
- <body data-async-container>
7
+ <body async:container>
8
8
  <main>
9
9
  <label>
10
10
  Product
11
- <select data-async-value="productId">
11
+ <select signal:value="productId">
12
12
  <option value="sku-1">Mechanical Keyboard</option>
13
13
  <option value="sku-2">Studio Headphones</option>
14
14
  </select>
15
15
  </label>
16
16
 
17
- <section data-async-boundary="product">
18
- <template data-async-loading="product">
17
+ <section async:boundary="product">
18
+ <template async:loading="product">
19
19
  <p>Loading product...</p>
20
20
  </template>
21
- <template data-async-ready="product">
22
- <h1 data-async-text="product.title"></h1>
23
- <p data-async-text="product.description"></p>
21
+ <template async:ready="product">
22
+ <h1 signal:text="product.title"></h1>
23
+ <p signal:text="product.description"></p>
24
24
  </template>
25
- <template data-async-error="product">
26
- <p data-async-text="product.$error.message"></p>
25
+ <template async:error="product">
26
+ <p signal:text="product.$error.message"></p>
27
27
  </template>
28
28
  </section>
29
29
  </main>
@@ -6,12 +6,12 @@
6
6
  <title>AsyncLoader CSR Router</title>
7
7
  </head>
8
8
  <body>
9
- <main data-async-container>
9
+ <main async:container>
10
10
  <nav>
11
11
  <a href="/">Home</a>
12
12
  <a href="/products/sku-1">Product</a>
13
13
  </nav>
14
- <section data-async-boundary="route"></section>
14
+ <section async:boundary="route"></section>
15
15
  </main>
16
16
  <script type="module" src="./main.js"></script>
17
17
  </body>
@@ -26,14 +26,14 @@ Async.use({
26
26
  "routerDemo.home"() {
27
27
  return html`
28
28
  <h1>Home</h1>
29
- <p>Cart: <strong data-async-text="routerDemo.cartCount"></strong></p>
29
+ <p>Cart: <strong signal:text="routerDemo.cartCount"></strong></p>
30
30
  `;
31
31
  },
32
32
  "routerDemo.product.page"({ id }) {
33
33
  return html`
34
34
  <article>
35
35
  <h1>Product ${id}</h1>
36
- <p>Cart: <strong data-async-text="routerDemo.cartCount"></strong></p>
36
+ <p>Cart: <strong signal:text="routerDemo.cartCount"></strong></p>
37
37
  <button type="button" on:click="server.routerDemo.cart.add(routerDemo.productId)">Add</button>
38
38
  </article>
39
39
  `;
@@ -6,7 +6,7 @@
6
6
  <title>AsyncLoader Server Call</title>
7
7
  </head>
8
8
  <body>
9
- <main data-async-container>
9
+ <main async:container>
10
10
  <form on:submit="preventDefault; server.serverCall.products.save(serverCall.productId, $form)">
11
11
  <label>
12
12
  Title
@@ -14,7 +14,7 @@
14
14
  </label>
15
15
  <button type="submit">Save</button>
16
16
  </form>
17
- <p>Saved: <strong data-async-text="serverCall.savedTitle"></strong></p>
17
+ <p>Saved: <strong signal:text="serverCall.savedTitle"></strong></p>
18
18
  </main>
19
19
  <script type="module" src="./main.js"></script>
20
20
  </body>
@@ -6,7 +6,7 @@
6
6
  <title>AsyncLoader SSR Activation</title>
7
7
  </head>
8
8
  <body>
9
- <main id="app" data-async-container></main>
9
+ <main id="app" async:container></main>
10
10
  <script type="module" src="./main.js"></script>
11
11
  </body>
12
12
  </html>
@@ -56,7 +56,7 @@ serverApp.use({
56
56
  <article>
57
57
  <h1>${product.title}</h1>
58
58
  <p>${product.id}</p>
59
- <button type="button" on:click="ssrDemo.selectProduct" data-async-class:selected="ssrDemo.selected">
59
+ <button type="button" on:click="ssrDemo.selectProduct" signal:class:selected="ssrDemo.selected">
60
60
  Select
61
61
  </button>
62
62
  </article>
@@ -80,7 +80,7 @@ serverRuntime.destroy();
80
80
 
81
81
  document.querySelector("#app").innerHTML = response.html;
82
82
 
83
- const snapshot = JSON.parse(document.querySelector("[data-async-snapshot]").textContent);
83
+ const snapshot = JSON.parse(document.querySelector("[async\\:snapshot]").textContent);
84
84
  const browserApp = defineApp(sharedDefinition());
85
85
  createApp(browserApp, {
86
86
  root: document,
@@ -4,10 +4,10 @@
4
4
  <meta charset="utf-8">
5
5
  <title>AsyncLoader Streaming</title>
6
6
  </head>
7
- <body data-async-container>
7
+ <body async:container>
8
8
  <main>
9
9
  <button type="button" on:click="streamingDemo.streamProduct">Stream product</button>
10
- <section data-async-boundary="product">
10
+ <section async:boundary="product">
11
11
  <p>Waiting for streamed HTML...</p>
12
12
  </section>
13
13
  </main>
@@ -13,8 +13,8 @@ Async.use({
13
13
  "product",
14
14
  `
15
15
  <article>
16
- <h1 data-async-text="streamingDemo.title"></h1>
17
- <button type="button" on:click="streamingDemo.select" data-async-class:selected="streamingDemo.selected">
16
+ <h1 signal:text="streamingDemo.title"></h1>
17
+ <button type="button" on:click="streamingDemo.select" signal:class:selected="streamingDemo.selected">
18
18
  Select
19
19
  </button>
20
20
  </article>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@async/framework",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "No-build AsyncLoader 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",
@@ -28,8 +28,15 @@
28
28
  "web-framework"
29
29
  ],
30
30
  "license": "MIT",
31
+ "unpkg": "./src/index.js",
31
32
  "exports": {
32
- ".": "./src/index.js"
33
+ ".": {
34
+ "unpkg": "./src/index.js",
35
+ "browser": "./src/index.js",
36
+ "import": "./src/index.js",
37
+ "default": "./src/index.js"
38
+ },
39
+ "./package.json": "./package.json"
33
40
  },
34
41
  "files": [
35
42
  "CHANGELOG.md",