@blueshed/railroad 0.3.0 → 0.3.1

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/routes.ts +25 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blueshed/railroad",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Signals, JSX, and routes — a micro UI framework for Bun",
5
5
  "type": "module",
6
6
  "main": "index.ts",
package/routes.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  * navigate(path) — set location.hash programmatically
8
8
  * matchRoute(pattern, path) — pure pattern matcher, returns params or null
9
9
  *
10
- * Handlers receive (params, params$) and return a Node.
10
+ * Handlers receive (params, params$) and return a Node (sync or async).
11
11
  * params — plain object for destructuring: ({ id }) => ...
12
12
  * params$ — Signal that updates when params change within the same pattern
13
13
  *
@@ -16,6 +16,7 @@
16
16
  * routes(app, {
17
17
  * "/": () => <Home />,
18
18
  * "/site/:id": ({ id }, params$) => <SiteDetail id={id} params$={params$} />,
19
+ * "/status": async () => { const s = await api.get(); return <Status data={s} />; },
19
20
  * });
20
21
  */
21
22
 
@@ -81,7 +82,7 @@ export function navigate(path: string): void {
81
82
  type RouteHandler = (
82
83
  params: Record<string, string>,
83
84
  params$: Signal<Record<string, string>>,
84
- ) => Node;
85
+ ) => Node | Promise<Node>;
85
86
 
86
87
  export function routes(
87
88
  target: HTMLElement,
@@ -91,8 +92,15 @@ export function routes(
91
92
  let activePattern: string | null = null;
92
93
  let activeParams: Signal<Record<string, string>> | null = null;
93
94
  let activeDispose: Dispose | null = null;
95
+ let runId = 0;
96
+ let asyncPending = false;
94
97
 
95
98
  function teardown() {
99
+ runId++;
100
+ if (asyncPending) {
101
+ popDisposeScope()();
102
+ asyncPending = false;
103
+ }
96
104
  if (activeDispose) activeDispose();
97
105
  activeDispose = null;
98
106
  activePattern = null;
@@ -101,11 +109,23 @@ export function routes(
101
109
  }
102
110
 
103
111
  function run(handler: RouteHandler, params: Record<string, string>) {
112
+ const myRunId = ++runId;
104
113
  activeParams = signal(params);
105
114
  pushDisposeScope();
106
- const node = handler(params, activeParams);
107
- activeDispose = popDisposeScope();
108
- target.appendChild(node);
115
+ const result = handler(params, activeParams);
116
+
117
+ if (result instanceof Promise) {
118
+ asyncPending = true;
119
+ result.then((node) => {
120
+ if (myRunId !== runId) return; // navigated away during await
121
+ asyncPending = false;
122
+ activeDispose = popDisposeScope();
123
+ target.appendChild(node);
124
+ });
125
+ } else {
126
+ activeDispose = popDisposeScope();
127
+ target.appendChild(result);
128
+ }
109
129
  }
110
130
 
111
131
  const disposeEffect = effect(() => {