@typed/ui 1.0.0-beta.0 → 1.0.0-beta.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.
package/README.md CHANGED
@@ -2,7 +2,12 @@
2
2
 
3
3
  > **Beta:** This package is in beta; APIs may change.
4
4
 
5
- `@typed/ui` provides **web integration** for the router and template: **Link** (typed anchor that uses Navigation for same-origin clicks) and **HttpRouter**-related helpers for SSR (`ssrForHttp`, `handleHttpServerError`). Use it when you need navigation-aware links and/or server-side rendering of the router with Effects HTTP server.
5
+ `@typed/ui` is the **web integration layer** for `@typed/router` and `@typed/template`. It bridges typed-smol's routing and template system with the browser and Effect's HTTP stack.
6
+
7
+ ## Capabilities
8
+
9
+ - **Link** — A typed anchor component that intercepts same-origin clicks and navigates via `Navigation.navigate` instead of a full page reload. Keeps routing SPA-style while preserving normal `<a>` semantics (href, target, keyboard, right-click).
10
+ - **SSR** — `ssrForHttp` compiles a router Matcher into HttpRouter GET handlers for server-side rendering. Requests are parsed, matched, and the corresponding Fx is rendered to HTML. `handleHttpServerError` adds global middleware for 404/400/500.
6
11
 
7
12
  ## Dependencies
8
13
 
@@ -27,25 +32,29 @@ Renders an `<a href="...">` that intercepts same-origin, same-document clicks an
27
32
 
28
33
  ```ts
29
34
  function Link<const Opts extends LinkOptions>(
30
- options: Opts
31
- ): Fx<RenderEvent, Renderable.ErrorFromObject<Opts>, Renderable.ServicesFromObject<Opts> | Scope | RenderTemplate>
35
+ options: Opts,
36
+ ): Fx<
37
+ RenderEvent,
38
+ Renderable.ErrorFromObject<Opts>,
39
+ Renderable.ServicesFromObject<Opts> | Scope | RenderTemplate
40
+ >;
32
41
  ```
33
42
 
34
43
  **`LinkOptions`**
35
44
 
36
- | Property | Type | Required | Description |
37
- |-----------|------|----------|-------------|
38
- | `href` | `Renderable<string, any, any>` | Yes | Target URL. |
39
- | `content` | `Renderable<string \| number \| boolean \| null \| undefined \| void \| RenderEvent, any, any>` | Yes | Link body (text or template content). |
40
- | `replace` | `boolean` | No | If `true`, use history replace instead of push. Default: `false`. |
45
+ | Property | Type | Required | Description |
46
+ | --------- | ----------------------------------------------------------------------------------------------- | -------- | ----------------------------------------------------------------- |
47
+ | `href` | `Renderable<string, any, any>` | Yes | Target URL. |
48
+ | `content` | `Renderable<string \| number \| boolean \| null \| undefined \| void \| RenderEvent, any, any>` | Yes | Link body (text or template content). |
49
+ | `replace` | `boolean` | No | If `true`, use history replace instead of push. Default: `false`. |
41
50
 
42
- In addition, `LinkOptions` accepts standard anchor event handlers (e.g. `onclick`), `ref`, and other writable `HTMLAnchorElement` properties. Custom `onclick` runs first; if the event is not `preventDefault`’d, the built-in navigation handler runs.
51
+ In addition, `LinkOptions` accepts standard anchor event handlers (e.g. `onclick`), `ref`, and other writable `HTMLAnchorElement` properties. Custom `onclick` runs first; if the event is not `preventDefault`'d, the built-in navigation handler runs.
43
52
 
44
53
  ---
45
54
 
46
55
  ### `ssrForHttp`
47
56
 
48
- Registers route handlers on an Effect **HttpRouter** for server-side rendering. The matchers routes are compiled and each case is exposed as a GET route; requests are parsed, matched, and the corresponding Fx is rendered to HTML. Requires **Router** and **Scope** to be provided elsewhere; other matcher services remain in the effect requirement.
57
+ Registers route handlers on an Effect **HttpRouter** for server-side rendering. The matcher's routes are compiled and each case is exposed as a GET route; requests are parsed, matched, and the corresponding Fx is rendered to HTML. Requires **Router** and **Scope** to be provided elsewhere; other matcher services remain in the effect requirement.
49
58
 
50
59
  **Overloads:**
51
60
 
@@ -53,13 +62,13 @@ Registers route handlers on an Effect **HttpRouter** for server-side rendering.
53
62
  // (router, matcher)
54
63
  function ssrForHttp<E, R>(
55
64
  router: HttpRouter,
56
- input: Matcher<RenderEvent, E, R>
57
- ): Effect.Effect<void, never, Exclude<R, Scope | Router>>
65
+ input: Matcher<RenderEvent, E, R>,
66
+ ): Effect.Effect<void, never, Exclude<R, Scope | Router>>;
58
67
 
59
68
  // (matcher)(router) — curried
60
69
  function ssrForHttp<E, R>(
61
- input: Matcher<RenderEvent, E, R>
62
- ): (router: HttpRouter) => Effect.Effect<void, never, Exclude<R, Scope | Router>>
70
+ input: Matcher<RenderEvent, E, R>,
71
+ ): (router: HttpRouter) => Effect.Effect<void, never, Exclude<R, Scope | Router>>;
63
72
  ```
64
73
 
65
74
  - **`router`** — Effect `HttpRouter` to attach GET handlers to.
@@ -71,14 +80,14 @@ function ssrForHttp<E, R>(
71
80
 
72
81
  Adds global middleware to an **HttpRouter** that catches `HttpServerError` and returns appropriate HTTP responses:
73
82
 
74
- | Error reason | Status |
75
- |------------------|--------|
76
- | `RouteNotFound` | 404 |
77
- | `RequestParseError` | 400 |
78
- | `InternalError` / `ResponseError` | 500 |
83
+ | Error reason | Status |
84
+ | --------------------------------- | ------ |
85
+ | `RouteNotFound` | 404 |
86
+ | `RequestParseError` | 400 |
87
+ | `InternalError` / `ResponseError` | 500 |
79
88
 
80
89
  ```ts
81
- function handleHttpServerError(router: HttpRouter): Effect.Effect<void, never, HttpRouter>
90
+ function handleHttpServerError(router: HttpRouter): Effect.Effect<void, never, HttpRouter>;
82
91
  ```
83
92
 
84
93
  Use after registering routes (e.g. after `ssrForHttp`) so unhandled route and parse errors are converted to 404/400/500 instead of failing the server.
@@ -91,9 +100,8 @@ import { html } from "@typed/template";
91
100
 
92
101
  // In a template: link that navigates via Navigation (no full reload)
93
102
  const nav = html`<nav>
94
- ${Link({ href: "/", content: "Home" })}
95
- ${Link({ href: "/todos", content: "Todos" })}
103
+ ${Link({ href: "/", content: "Home" })} ${Link({ href: "/todos", content: "Todos" })}
96
104
  </nav>`;
97
105
  ```
98
106
 
99
- For SSR, provide the router and matcher to `ssrForHttp` when setting up the HTTP server; see Effects `HttpRouter` and the TodoMVC example structure.
107
+ For SSR, provide the router and matcher to `ssrForHttp` when setting up the HTTP server; see Effect's `HttpRouter` and the TodoMVC example structure.
@@ -42,7 +42,7 @@ function getStatus(error) {
42
42
  }
43
43
  function toRoute(entry, currentServices) {
44
44
  return {
45
- "~effect/http/HttpRouter/Route": "~effect/http/HttpRouter/Route",
45
+ ["~effect/http/HttpRouter/Route"]: "~effect/http/HttpRouter/Route",
46
46
  method: "GET",
47
47
  path: entry.route.path,
48
48
  handler: Effect.gen(function* () {
package/package.json CHANGED
@@ -1,9 +1,6 @@
1
1
  {
2
2
  "name": "@typed/ui",
3
- "version": "1.0.0-beta.0",
4
- "publishConfig": {
5
- "access": "public"
6
- },
3
+ "version": "1.0.0-beta.1",
7
4
  "type": "module",
8
5
  "exports": {
9
6
  ".": {
@@ -15,21 +12,29 @@
15
12
  "import": "./dist/*.js"
16
13
  }
17
14
  },
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "scripts": {
19
+ "build": "[ -d dist ] || rm -f tsconfig.tsbuildinfo; tsc",
20
+ "test": "vitest run --passWithNoTests"
21
+ },
18
22
  "dependencies": {
19
- "@effect/platform-node": "4.0.0-beta.4",
20
- "effect": "4.0.0-beta.4",
21
- "happy-dom": "*",
22
- "@typed/fx": "2.0.0-beta.0",
23
- "@typed/navigation": "1.0.0-beta.0",
24
- "@typed/template": "1.0.0-beta.0",
25
- "@typed/router": "1.0.0-beta.0"
23
+ "@effect/platform-node": "catalog:",
24
+ "@typed/fx": "workspace:*",
25
+ "@typed/id": "workspace:*",
26
+ "@typed/navigation": "workspace:*",
27
+ "@typed/router": "workspace:*",
28
+ "@typed/template": "workspace:*",
29
+ "effect": "catalog:",
30
+ "happy-dom": "catalog:"
26
31
  },
27
32
  "devDependencies": {
28
- "typescript": "5.9.3",
29
- "vitest": "4.0.18"
33
+ "typescript": "catalog:",
34
+ "vitest": "catalog:"
30
35
  },
31
- "scripts": {
32
- "build": "tsc",
33
- "test": "vitest run --passWithNoTests"
34
- }
35
- }
36
+ "files": [
37
+ "dist",
38
+ "src"
39
+ ]
40
+ }
@@ -1,8 +1,8 @@
1
1
  import { NodeHttpServer } from "@effect/platform-node";
2
- import assert from "node:assert";
3
- import { describe, it } from "vitest";
2
+ import { assert, describe, it } from "vitest";
4
3
  import { Effect, Layer } from "effect";
5
4
  import { Fx } from "@typed/fx";
5
+ import { Ids } from "@typed/id";
6
6
  import { Navigation } from "@typed/navigation";
7
7
  import { CurrentRoute } from "@typed/router/CurrentRoute";
8
8
  import * as Matcher from "@typed/router/Matcher";
@@ -22,7 +22,7 @@ describe("typed/ui/HttpRouter", () => {
22
22
  const Live = HttpRouter.use(ssrForHttp(matcher)).pipe(
23
23
  Layer.provide(StaticHtmlRenderTemplate),
24
24
  HttpRouter.serve,
25
- Layer.provideMerge([NodeHttpServer.layerTest]),
25
+ Layer.provideMerge([Ids.Test(), NodeHttpServer.layerTest]),
26
26
  );
27
27
  return Effect.gen(function* () {
28
28
  const response = yield* HttpClient.get("/home").pipe(Effect.flatMap((r) => r.text));
@@ -39,7 +39,7 @@ describe("typed/ui/HttpRouter", () => {
39
39
  const Live = HttpRouter.use(ssrForHttp(matcher)).pipe(
40
40
  Layer.provide(StaticHtmlRenderTemplate),
41
41
  HttpRouter.serve,
42
- Layer.provideMerge(NodeHttpServer.layerTest),
42
+ Layer.provideMerge([Ids.Test(), NodeHttpServer.layerTest]),
43
43
  );
44
44
  return Effect.gen(function* () {
45
45
  const response = yield* HttpClient.get("/users/123").pipe(Effect.flatMap((r) => r.text));
@@ -58,7 +58,7 @@ describe("typed/ui/HttpRouter", () => {
58
58
  const Live = HttpRouter.use(ssrForHttp(matcher)).pipe(
59
59
  Layer.provide(StaticHtmlRenderTemplate),
60
60
  HttpRouter.serve,
61
- Layer.provideMerge(NodeHttpServer.layerTest),
61
+ Layer.provideMerge([Ids.Test(), NodeHttpServer.layerTest]),
62
62
  );
63
63
  return Effect.gen(function* () {
64
64
  const response = yield* HttpClient.get("/search?q=test").pipe(Effect.flatMap((r) => r.text));
@@ -85,7 +85,7 @@ describe("typed/ui/HttpRouter", () => {
85
85
  const Live = HttpRouter.use(ssrForHttp(matcher)).pipe(
86
86
  Layer.provide(StaticHtmlRenderTemplate),
87
87
  HttpRouter.serve,
88
- Layer.provideMerge(NodeHttpServer.layerTest),
88
+ Layer.provideMerge([Ids.Test(), NodeHttpServer.layerTest]),
89
89
  );
90
90
  return Effect.gen(function* () {
91
91
  const homeResponse = yield* HttpClient.get("/home").pipe(Effect.flatMap((r) => r.text));
@@ -110,7 +110,7 @@ describe("typed/ui/HttpRouter", () => {
110
110
  ).pipe(
111
111
  Layer.provide(StaticHtmlRenderTemplate),
112
112
  HttpRouter.serve,
113
- Layer.provideMerge(NodeHttpServer.layerTest),
113
+ Layer.provideMerge([Ids.Test(), NodeHttpServer.layerTest]),
114
114
  );
115
115
  return Effect.gen(function* () {
116
116
  const response = yield* HttpClient.get("/notfound");
@@ -125,7 +125,7 @@ describe("typed/ui/HttpRouter", () => {
125
125
  );
126
126
  const Live = HttpRouter.use(ssrForHttp(matcher)).pipe(
127
127
  HttpRouter.serve,
128
- Layer.provideMerge(NodeHttpServer.layerTest),
128
+ Layer.provideMerge([Ids.Test(), NodeHttpServer.layerTest]),
129
129
  Layer.provide(StaticHtmlRenderTemplate),
130
130
  );
131
131
  return Effect.gen(function* () {
@@ -144,7 +144,7 @@ describe("typed/ui/HttpRouter", () => {
144
144
  const Live = HttpRouter.use(ssrForHttp(matcher)).pipe(
145
145
  Layer.provide(StaticHtmlRenderTemplate),
146
146
  HttpRouter.serve,
147
- Layer.provideMerge(NodeHttpServer.layerTest),
147
+ Layer.provideMerge([Ids.Test(), NodeHttpServer.layerTest]),
148
148
  );
149
149
  return Effect.gen(function* () {
150
150
  const response = yield* HttpClient.get("/home");
@@ -167,7 +167,7 @@ describe("typed/ui/HttpRouter", () => {
167
167
  const Live = HttpRouter.use(ssrForHttp(matcher)).pipe(
168
168
  Layer.provide(StaticHtmlRenderTemplate),
169
169
  HttpRouter.serve,
170
- Layer.provideMerge(NodeHttpServer.layerTest),
170
+ Layer.provideMerge([Ids.Test(), NodeHttpServer.layerTest]),
171
171
  );
172
172
  return Effect.gen(function* () {
173
173
  const listResponse = yield* HttpClient.get("/api/users").pipe(Effect.flatMap((r) => r.text));
@@ -192,7 +192,7 @@ describe("typed/ui/HttpRouter", () => {
192
192
  const Live = HttpRouter.use(ssrForHttp(matcher)).pipe(
193
193
  Layer.provide(StaticHtmlRenderTemplate),
194
194
  HttpRouter.serve,
195
- Layer.provideMerge(NodeHttpServer.layerTest),
195
+ Layer.provideMerge([Ids.Test(), NodeHttpServer.layerTest]),
196
196
  );
197
197
  return Effect.gen(function* () {
198
198
  const response = yield* HttpClient.get("/test").pipe(Effect.flatMap((r) => r.text));
@@ -213,7 +213,7 @@ describe("typed/ui/HttpRouter", () => {
213
213
  const Live = HttpRouter.use(ssrForHttp(matcher)).pipe(
214
214
  Layer.provide(StaticHtmlRenderTemplate),
215
215
  HttpRouter.serve,
216
- Layer.provideMerge(NodeHttpServer.layerTest),
216
+ Layer.provideMerge([Ids.Test(), NodeHttpServer.layerTest]),
217
217
  );
218
218
  return Effect.gen(function* () {
219
219
  const response = yield* HttpClient.get("/users").pipe(Effect.flatMap((r) => r.text));
@@ -233,7 +233,7 @@ describe("typed/ui/HttpRouter", () => {
233
233
  const Live = HttpRouter.use(ssrForHttp(matcher)).pipe(
234
234
  Layer.provide(StaticHtmlRenderTemplate),
235
235
  HttpRouter.serve,
236
- Layer.provideMerge(NodeHttpServer.layerTest),
236
+ Layer.provideMerge([Ids.Test(), NodeHttpServer.layerTest]),
237
237
  );
238
238
  return Effect.gen(function* () {
239
239
  const response = yield* HttpClient.get("/home").pipe(Effect.flatMap((r) => r.text));
@@ -263,7 +263,7 @@ describe("typed/ui/HttpRouter", () => {
263
263
  const Live = HttpRouter.use(ssrForHttp(matcher)).pipe(
264
264
  Layer.provide(StaticHtmlRenderTemplate),
265
265
  HttpRouter.serve,
266
- Layer.provideMerge(NodeHttpServer.layerTest),
266
+ Layer.provideMerge([Ids.Test(), NodeHttpServer.layerTest]),
267
267
  Layer.provide(CurrentRoute.extend(api)),
268
268
  );
269
269
  return Effect.gen(function* () {
@@ -284,7 +284,7 @@ describe("typed/ui/HttpRouter", () => {
284
284
  const Live = HttpRouter.use(ssrForHttp(matcher)).pipe(
285
285
  Layer.provide(StaticHtmlRenderTemplate),
286
286
  HttpRouter.serve,
287
- Layer.provideMerge(NodeHttpServer.layerTest),
287
+ Layer.provideMerge([Ids.Test(), NodeHttpServer.layerTest]),
288
288
  );
289
289
  return Effect.gen(function* () {
290
290
  const response = yield* HttpClient.get("/about").pipe(Effect.flatMap((r) => r.text));
package/src/HttpRouter.ts CHANGED
@@ -76,7 +76,7 @@ function toRoute(
76
76
  currentServices: ServiceMap.ServiceMap<never>,
77
77
  ): Route<any, any> {
78
78
  return {
79
- "~effect/http/HttpRouter/Route": "~effect/http/HttpRouter/Route",
79
+ ["~effect/http/HttpRouter/Route"]: "~effect/http/HttpRouter/Route",
80
80
  method: "GET",
81
81
  path: entry.route.path,
82
82
  handler: Effect.gen(function* () {
package/src/Link.test.ts CHANGED
@@ -1,5 +1,4 @@
1
- import assert from "node:assert";
2
- import { describe, it } from "vitest";
1
+ import { assert, describe, it } from "vitest";
3
2
  import { Effect } from "effect";
4
3
  import * as Layer from "effect/Layer";
5
4
  import { Fx } from "@typed/fx";
package/tsconfig.json DELETED
@@ -1,6 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": { "rootDir": "src", "outDir": "dist" },
4
- "include": ["src"],
5
- "exclude": ["**/*.test.ts"]
6
- }