@async/framework 0.1.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.
@@ -0,0 +1,11 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>AsyncLoader Components</title>
6
+ </head>
7
+ <body>
8
+ <main id="app"></main>
9
+ <script type="module" src="./main.js"></script>
10
+ </body>
11
+ </html>
@@ -0,0 +1,26 @@
1
+ import { AsyncLoader, defineComponent, html } from "../../src/index.js";
2
+
3
+ const Toggle = defineComponent(function Toggle() {
4
+ const selected = this.signal("selected", false);
5
+ const toggle = this.handler("toggle", function () {
6
+ selected.update((value) => !value);
7
+ });
8
+
9
+ this.onMount(() => {
10
+ document.body.dataset.toggleMounted = "true";
11
+ });
12
+
13
+ return html`
14
+ <button
15
+ type="button"
16
+ on:click="${toggle}"
17
+ data-async-class:selected="${selected.id}"
18
+ data-async-attr:aria-pressed="${selected.id}"
19
+ >
20
+ Toggle
21
+ </button>
22
+ `;
23
+ });
24
+
25
+ const loader = AsyncLoader({ root: document });
26
+ loader.mount(document.querySelector("#app"), Toggle);
@@ -0,0 +1,15 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>AsyncLoader Counter</title>
6
+ </head>
7
+ <body data-async-container>
8
+ <main>
9
+ <p>Count: <strong data-async-text="counter.count"></strong></p>
10
+ <button type="button" on:click="counter.decrement">-</button>
11
+ <button type="button" on:click="counter.increment">+</button>
12
+ </main>
13
+ <script type="module" src="./main.js"></script>
14
+ </body>
15
+ </html>
@@ -0,0 +1,17 @@
1
+ import { Async, createSignal } from "../../src/index.js";
2
+
3
+ Async.use({
4
+ signal: {
5
+ counter: createSignal({ count: 0 })
6
+ },
7
+ handler: {
8
+ "counter.increment"() {
9
+ this.signals.update("counter.count", (count) => count + 1);
10
+ },
11
+ "counter.decrement"() {
12
+ this.signals.update("counter.count", (count) => count - 1);
13
+ }
14
+ }
15
+ });
16
+
17
+ Async.start({ root: document.body, router: false });
@@ -0,0 +1,15 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>AsyncLoader Partials</title>
7
+ </head>
8
+ <body>
9
+ <main data-async-container>
10
+ <button type="button" on:click="partialsDemo.loadProduct">Load product</button>
11
+ <section data-async-boundary="product"></section>
12
+ </main>
13
+ <script type="module" src="./main.js"></script>
14
+ </body>
15
+ </html>
@@ -0,0 +1,43 @@
1
+ import {
2
+ Async,
3
+ html
4
+ } from "../../src/index.js";
5
+
6
+ let runtime;
7
+
8
+ Async.use({
9
+ server: {
10
+ "partialsDemo.products.get"(id) {
11
+ return {
12
+ value: {
13
+ id,
14
+ title: "Keyboard"
15
+ }
16
+ };
17
+ }
18
+ },
19
+ partial: {
20
+ "partialsDemo.product.card": async function ({ id }) {
21
+ const product = await this.server.partialsDemo.products.get(id);
22
+ return html`
23
+ <article>
24
+ <h1>${product.title}</h1>
25
+ <p>${product.id}</p>
26
+ </article>
27
+ `;
28
+ }
29
+ },
30
+ handler: {
31
+ async "partialsDemo.loadProduct"() {
32
+ const result = await runtime.partials.render("partialsDemo.product.card", { id: "sku-1" }, {
33
+ cache: this.cache,
34
+ loader: this.loader,
35
+ server: this.server,
36
+ signals: this.signals
37
+ });
38
+ this.loader.swap("product", result.html);
39
+ }
40
+ }
41
+ });
42
+
43
+ runtime = Async.start({ root: document, router: false });
@@ -0,0 +1,32 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>AsyncLoader Product</title>
6
+ </head>
7
+ <body data-async-container>
8
+ <main>
9
+ <label>
10
+ Product
11
+ <select data-async-value="productId">
12
+ <option value="sku-1">Mechanical Keyboard</option>
13
+ <option value="sku-2">Studio Headphones</option>
14
+ </select>
15
+ </label>
16
+
17
+ <section data-async-boundary="product">
18
+ <template data-async-loading="product">
19
+ <p>Loading product...</p>
20
+ </template>
21
+ <template data-async-ready="product">
22
+ <h1 data-async-text="product.title"></h1>
23
+ <p data-async-text="product.description"></p>
24
+ </template>
25
+ <template data-async-error="product">
26
+ <p data-async-text="product.$error.message"></p>
27
+ </template>
28
+ </section>
29
+ </main>
30
+ <script type="module" src="./main.js"></script>
31
+ </body>
32
+ </html>
@@ -0,0 +1,24 @@
1
+ import { AsyncLoader, createSignal, createSignalRegistry, delay } from "../../src/index.js";
2
+
3
+ const products = {
4
+ "sku-1": {
5
+ title: "Mechanical Keyboard",
6
+ description: "A compact keyboard loaded through an async signal."
7
+ },
8
+ "sku-2": {
9
+ title: "Studio Headphones",
10
+ description: "Closed-back headphones loaded through the same boundary."
11
+ }
12
+ };
13
+
14
+ const signals = createSignalRegistry({
15
+ productId: createSignal("sku-1")
16
+ });
17
+
18
+ signals.asyncSignal("product", async function () {
19
+ const id = this.signals.get("productId");
20
+ await delay(150, this.abort);
21
+ return products[id];
22
+ });
23
+
24
+ AsyncLoader({ root: document.body, signals }).start();
@@ -0,0 +1,21 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>AsyncLoader Router</title>
7
+ </head>
8
+ <body>
9
+ <main data-async-container>
10
+ <nav>
11
+ <a href="/">Home</a>
12
+ <a href="/products/sku-1">Product</a>
13
+ </nav>
14
+ <section data-async-boundary="route">
15
+ <h1>Home</h1>
16
+ <p>Cart: <strong data-async-text="routerDemo.cartCount"></strong></p>
17
+ </section>
18
+ </main>
19
+ <script type="module" src="./main.js"></script>
20
+ </body>
21
+ </html>
@@ -0,0 +1,52 @@
1
+ import {
2
+ Async,
3
+ createSignal,
4
+ defineRoute,
5
+ html
6
+ } from "../../src/index.js";
7
+
8
+ Async.use({
9
+ signal: {
10
+ routerDemo: createSignal({
11
+ productId: "sku-1",
12
+ cartCount: 0
13
+ })
14
+ },
15
+ server: {
16
+ "routerDemo.cart.add"(productId) {
17
+ return {
18
+ value: { productId },
19
+ signals: {
20
+ "routerDemo.cartCount": 1
21
+ }
22
+ };
23
+ }
24
+ },
25
+ partial: {
26
+ "routerDemo.home"() {
27
+ return html`
28
+ <h1>Home</h1>
29
+ <p>Cart: <strong data-async-text="routerDemo.cartCount"></strong></p>
30
+ `;
31
+ },
32
+ "routerDemo.product.page"({ id }) {
33
+ return html`
34
+ <article>
35
+ <h1>Product ${id}</h1>
36
+ <p>Cart: <strong data-async-text="routerDemo.cartCount"></strong></p>
37
+ <button type="button" on:click="server.routerDemo.cart.add(routerDemo.productId)">Add</button>
38
+ </article>
39
+ `;
40
+ }
41
+ },
42
+ route: {
43
+ "/": defineRoute("routerDemo.home"),
44
+ "/products/:id": defineRoute("routerDemo.product.page")
45
+ }
46
+ });
47
+
48
+ Async.start({
49
+ mode: "ssr-spa",
50
+ root: document,
51
+ boundary: "route"
52
+ });
@@ -0,0 +1,21 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>AsyncLoader Server Call</title>
7
+ </head>
8
+ <body>
9
+ <main data-async-container>
10
+ <form on:submit="preventDefault; server.serverCall.products.save(serverCall.productId, $form)">
11
+ <label>
12
+ Title
13
+ <input name="title" value="Keyboard">
14
+ </label>
15
+ <button type="submit">Save</button>
16
+ </form>
17
+ <p>Saved: <strong data-async-text="serverCall.savedTitle"></strong></p>
18
+ </main>
19
+ <script type="module" src="./main.js"></script>
20
+ </body>
21
+ </html>
@@ -0,0 +1,22 @@
1
+ import { Async, createSignal } from "../../src/index.js";
2
+
3
+ Async.use({
4
+ signal: {
5
+ serverCall: createSignal({
6
+ productId: "sku-1",
7
+ savedTitle: ""
8
+ })
9
+ },
10
+ server: {
11
+ "serverCall.products.save"(productId, form) {
12
+ return {
13
+ value: { id: productId, ...form },
14
+ signals: {
15
+ "serverCall.savedTitle": form.title
16
+ }
17
+ };
18
+ }
19
+ }
20
+ });
21
+
22
+ Async.start({ root: document, router: false });
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>AsyncLoader SSR Activation</title>
7
+ </head>
8
+ <body>
9
+ <main id="app" data-async-container></main>
10
+ <script type="module" src="./main.js"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,89 @@
1
+ import {
2
+ createApp,
3
+ createSignal,
4
+ defineApp,
5
+ defineCache,
6
+ defineRoute,
7
+ html
8
+ } from "../../src/index.js";
9
+
10
+ function sharedDefinition() {
11
+ return {
12
+ signal: {
13
+ ssrDemo: createSignal({
14
+ productId: null,
15
+ selected: false
16
+ })
17
+ },
18
+ cache: {
19
+ browser: {
20
+ "ssrDemo.product": defineCache({ ttl: 60_000 })
21
+ }
22
+ },
23
+ handler: {
24
+ "ssrDemo.selectProduct"() {
25
+ this.signals.set("ssrDemo.selected", true);
26
+ }
27
+ },
28
+ route: {
29
+ "/ssr/:id": defineRoute("ssrDemo.product.page")
30
+ }
31
+ };
32
+ }
33
+
34
+ const serverApp = defineApp(sharedDefinition());
35
+ serverApp.use({
36
+ cache: {
37
+ server: {
38
+ "ssrDemo.products.get": defineCache({ ttl: 30_000 })
39
+ }
40
+ },
41
+ server: {
42
+ async "ssrDemo.products.get"(id) {
43
+ return this.cache.getOrSet(`ssrDemo.products:${id}`, () => {
44
+ return {
45
+ id,
46
+ title: "SSR Keyboard"
47
+ };
48
+ }, { cache: "ssrDemo.products.get" });
49
+ }
50
+ },
51
+ partial: {
52
+ async "ssrDemo.product.page"({ id }) {
53
+ const product = await this.server.ssrDemo.products.get(id);
54
+ return {
55
+ html: html`
56
+ <article>
57
+ <h1>${product.title}</h1>
58
+ <p>${product.id}</p>
59
+ <button type="button" on:click="ssrDemo.selectProduct" data-async-class:selected="ssrDemo.selected">
60
+ Select
61
+ </button>
62
+ </article>
63
+ `,
64
+ signals: {
65
+ "ssrDemo.productId": id
66
+ },
67
+ cache: {
68
+ browser: {
69
+ [`ssrDemo.product:${id}`]: product
70
+ }
71
+ }
72
+ };
73
+ }
74
+ }
75
+ });
76
+
77
+ const serverRuntime = createApp(serverApp, { target: "server" });
78
+ const response = await serverRuntime.render("/ssr/sku-1");
79
+ serverRuntime.destroy();
80
+
81
+ document.querySelector("#app").innerHTML = response.html;
82
+
83
+ const snapshot = JSON.parse(document.querySelector("[data-async-snapshot]").textContent);
84
+ const browserApp = defineApp(sharedDefinition());
85
+ createApp(browserApp, {
86
+ root: document,
87
+ router: false,
88
+ snapshot
89
+ }).start();
@@ -0,0 +1,16 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>AsyncLoader Streaming</title>
6
+ </head>
7
+ <body data-async-container>
8
+ <main>
9
+ <button type="button" on:click="streamingDemo.streamProduct">Stream product</button>
10
+ <section data-async-boundary="product">
11
+ <p>Waiting for streamed HTML...</p>
12
+ </section>
13
+ </main>
14
+ <script type="module" src="./main.js"></script>
15
+ </body>
16
+ </html>
@@ -0,0 +1,30 @@
1
+ import { Async, createSignal } from "../../src/index.js";
2
+
3
+ Async.use({
4
+ signal: {
5
+ streamingDemo: createSignal({
6
+ title: "Streamed Keyboard",
7
+ selected: false
8
+ })
9
+ },
10
+ handler: {
11
+ "streamingDemo.streamProduct"() {
12
+ this.loader.swap(
13
+ "product",
14
+ `
15
+ <article>
16
+ <h1 data-async-text="streamingDemo.title"></h1>
17
+ <button type="button" on:click="streamingDemo.select" data-async-class:selected="streamingDemo.selected">
18
+ Select
19
+ </button>
20
+ </article>
21
+ `
22
+ );
23
+ },
24
+ "streamingDemo.select"() {
25
+ this.signals.set("streamingDemo.selected", true);
26
+ }
27
+ }
28
+ });
29
+
30
+ Async.start({ root: document.body, router: false });
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@async/framework",
3
+ "version": "0.1.0",
4
+ "description": "No-build AsyncLoader app runtime with signals, command events, server calls, route partials, cache split, SSR activation, and streaming boundaries.",
5
+ "type": "module",
6
+ "packageManager": "pnpm@11.1.0",
7
+ "engines": {
8
+ "node": ">=24"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/async/framework.git"
13
+ },
14
+ "homepage": "https://async.github.io/framework/",
15
+ "bugs": {
16
+ "url": "https://github.com/async/framework/issues"
17
+ },
18
+ "keywords": [
19
+ "async",
20
+ "signals",
21
+ "async-loader",
22
+ "no-build",
23
+ "router",
24
+ "server-functions",
25
+ "ssr",
26
+ "cache",
27
+ "streaming",
28
+ "web-framework"
29
+ ],
30
+ "license": "MIT",
31
+ "exports": {
32
+ ".": "./src/index.js"
33
+ },
34
+ "files": [
35
+ "CHANGELOG.md",
36
+ "README.md",
37
+ "src",
38
+ "examples",
39
+ "LICENSE",
40
+ "package.json"
41
+ ],
42
+ "scripts": {
43
+ "async-pipeline": "async-pipeline",
44
+ "docs:build": "node scripts/build-pages.js",
45
+ "examples": "node --test tests/examples.test.js",
46
+ "examples:check": "node --test tests/examples.test.js",
47
+ "pack:check": "npm pack --dry-run --ignore-scripts",
48
+ "pipeline:github:check": "async-pipeline github check",
49
+ "pipeline:github:generate": "async-pipeline github generate",
50
+ "pipeline:pages": "async-pipeline run pages",
51
+ "pipeline:publish": "async-pipeline run publish",
52
+ "pipeline:publish:npm": "async-pipeline publish npm --package .",
53
+ "pipeline:release-doctor": "async-pipeline run release-doctor",
54
+ "pipeline:release:doctor": "async-pipeline release doctor --package .",
55
+ "pipeline:release:ensure": "async-pipeline release ensure --package .",
56
+ "pipeline:sync:check": "async-pipeline sync check",
57
+ "pipeline:sync:generate": "async-pipeline sync generate",
58
+ "pipeline:task:docs.site": "async-pipeline run-task docs.site",
59
+ "pipeline:verify": "async-pipeline run verify",
60
+ "release:check": "pnpm run pipeline:verify -- --force && pnpm run pipeline:pages -- --force && pnpm run pipeline:sync:check && pnpm run pipeline:github:check",
61
+ "test": "node --test tests/*.test.js"
62
+ },
63
+ "devDependencies": {
64
+ "@async/pipeline": "0.8.5",
65
+ "happy-dom": "20.10.5"
66
+ }
67
+ }