Skip to content

Commit 9cb3f7f

Browse files
committed
feat(runtime): add registry store and cdn-ready attributes
1 parent 34e38e6 commit 9cb3f7f

32 files changed

Lines changed: 1174 additions & 230 deletions

‎CHANGELOG.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Changelog
22

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+
316
## 0.2.2 - 2026-06-17
417

518
- Fixed release doctor validation to accept the pipeline-generated GitHub

‎README.md‎

Lines changed: 153 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ pnpm add @async/framework
1010
```
1111

1212
```html
13-
<main data-async-container>
13+
<main async:container>
1414
<button type="button" on:click="decrement">-</button>
15-
<strong data-async-text="count"></strong>
15+
<strong signal:text="count"></strong>
1616
<button type="button" on:click="increment">+</button>
1717
</main>
1818
<script type="module" src="./main.js"></script>
@@ -69,12 +69,78 @@ pnpm add @async/framework
6969
The package is ESM-only and supports Node.js 24 and newer for tests, examples,
7070
and package lifecycle tooling. Browser consumers import ESM directly.
7171

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+
72137
## Core API
73138

74139
```js
75140
import {
76141
AsyncLoader,
77142
Async,
143+
attributeName,
78144
asyncSignal,
79145
createApp,
80146
createCacheRegistry,
@@ -84,11 +150,13 @@ import {
84150
createSignal,
85151
createHandlerRegistry,
86152
createPartialRegistry,
153+
createRegistryStore,
87154
createRouteRegistry,
88155
createRouter,
89156
createServerProxy,
90157
createServerRegistry,
91158
createSignalRegistry,
159+
defineAttributeConfig,
92160
defineApp,
93161
defineCache,
94162
defineComponent,
@@ -170,6 +238,50 @@ Naming rules:
170238
Singular registry keys are canonical: `signal`, `handler`, `server`,
171239
`partial`, `route`, `component`, and nested `cache.browser` / `cache.server`.
172240

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+
173285
### Signals
174286

175287
```js
@@ -249,33 +361,50 @@ AsyncLoader scans regular HTML attributes:
249361

250362
| Attribute | Behavior |
251363
| --- | --- |
252-
| `data-async-container` | Marks a scannable app root |
364+
| `async:container` | Marks a scannable app root |
253365
| `on:click="selectProduct"` | Delegated command event |
254366
| `on:submit="preventDefault; save"` | Sequential command chain |
255367
| `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 |
264376

265377
```html
266-
<section data-async-boundary="product">
267-
<template data-async-loading="product">
378+
<section async:boundary="product">
379+
<template async:loading="product">
268380
<p>Loading...</p>
269381
</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>
272384
</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>
275387
</template>
276388
</section>
277389
```
278390

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+
279408
### Command Events
280409

281410
`on:*` works with any native DOM event name. `on:mount` and `on:visible` are
@@ -417,13 +546,13 @@ Router modes:
417546
CSR startup can use an empty route boundary:
418547

419548
```html
420-
<main data-async-container>
549+
<main async:container>
421550
<nav>
422551
<a href="/">Home</a>
423552
<a href="/products/sku-1">Product</a>
424553
</nav>
425554

426-
<section data-async-boundary="route"></section>
555+
<section async:boundary="route"></section>
427556
</main>
428557
```
429558

@@ -520,10 +649,10 @@ const response = await serverRuntime.render("/products/123");
520649
The returned HTML includes a route boundary plus a JSON snapshot:
521650

522651
```html
523-
<section data-async-boundary="route">
652+
<section async:boundary="route">
524653
<!-- server-rendered route partial -->
525654
</section>
526-
<script type="application/json" data-async-snapshot>{}</script>
655+
<script type="application/json" async:snapshot>{}</script>
527656
```
528657

529658
Browser activation scans the existing HTML and attaches events. It does not
@@ -558,8 +687,8 @@ const Toggle = defineComponent(function Toggle() {
558687
<button
559688
type="button"
560689
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}"
563692
>
564693
Toggle
565694
</button>
@@ -597,7 +726,7 @@ loader.swap(
597726
"product",
598727
`
599728
<article>
600-
<h1 data-async-text="product.title"></h1>
729+
<h1 signal:text="product.title"></h1>
601730
<button type="button" on:click="selectProduct">Select</button>
602731
</article>
603732
`

‎examples/cache/index.html‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
<title>AsyncLoader Cache</title>
77
</head>
88
<body>
9-
<main data-async-container>
9+
<main async:container>
1010
<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>
1313
</main>
1414
<script type="module" src="./main.js"></script>
1515
</body>

‎examples/components/main.js‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ const Toggle = defineComponent(function Toggle() {
1414
<button
1515
type="button"
1616
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}"
1919
>
2020
Toggle
2121
</button>

‎examples/counter/index.html‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
<meta charset="utf-8">
55
<title>AsyncLoader Counter</title>
66
</head>
7-
<body data-async-container>
7+
<body async:container>
88
<main>
9-
<p>Count: <strong data-async-text="counter.count"></strong></p>
9+
<p>Count: <strong signal:text="counter.count"></strong></p>
1010
<button type="button" on:click="counter.decrement">-</button>
1111
<button type="button" on:click="counter.increment">+</button>
1212
</main>

‎examples/partials/index.html‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
<title>AsyncLoader Partials</title>
77
</head>
88
<body>
9-
<main data-async-container>
9+
<main async:container>
1010
<button type="button" on:click="partialsDemo.loadProduct">Load product</button>
11-
<section data-async-boundary="product"></section>
11+
<section async:boundary="product"></section>
1212
</main>
1313
<script type="module" src="./main.js"></script>
1414
</body>

‎examples/product/index.html‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,26 @@
44
<meta charset="utf-8">
55
<title>AsyncLoader Product</title>
66
</head>
7-
<body data-async-container>
7+
<body async:container>
88
<main>
99
<label>
1010
Product
11-
<select data-async-value="productId">
11+
<select signal:value="productId">
1212
<option value="sku-1">Mechanical Keyboard</option>
1313
<option value="sku-2">Studio Headphones</option>
1414
</select>
1515
</label>
1616

17-
<section data-async-boundary="product">
18-
<template data-async-loading="product">
17+
<section async:boundary="product">
18+
<template async:loading="product">
1919
<p>Loading product...</p>
2020
</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>
2424
</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>
2727
</template>
2828
</section>
2929
</main>

‎examples/router/index.html‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
<title>AsyncLoader CSR Router</title>
77
</head>
88
<body>
9-
<main data-async-container>
9+
<main async:container>
1010
<nav>
1111
<a href="/">Home</a>
1212
<a href="/products/sku-1">Product</a>
1313
</nav>
14-
<section data-async-boundary="route"></section>
14+
<section async:boundary="route"></section>
1515
</main>
1616
<script type="module" src="./main.js"></script>
1717
</body>

0 commit comments

Comments
 (0)