@typed/ui 1.0.0-beta.3 → 1.0.0-beta.4
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/dist/HttpRouter.js +5 -5
- package/package.json +16 -16
- package/src/HttpRouter.test.ts +21 -59
- package/src/HttpRouter.ts +9 -12
package/dist/HttpRouter.js
CHANGED
|
@@ -4,7 +4,7 @@ import { dual } from "effect/Function";
|
|
|
4
4
|
import * as Layer from "effect/Layer";
|
|
5
5
|
import * as Option from "effect/Option";
|
|
6
6
|
import * as Scope from "effect/Scope";
|
|
7
|
-
import * as
|
|
7
|
+
import * as Context from "effect/Context";
|
|
8
8
|
import { RouteContext } from "effect/unstable/http/HttpRouter";
|
|
9
9
|
import * as HttpServerError from "effect/unstable/http/HttpServerError";
|
|
10
10
|
import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
|
|
@@ -20,7 +20,7 @@ export const ssrForHttp = dual(2, (router, input) => {
|
|
|
20
20
|
onSome: (parent) => input.prefix(parent.route),
|
|
21
21
|
});
|
|
22
22
|
const entries = compile(matcher.cases);
|
|
23
|
-
const currentServices = yield* Effect.
|
|
23
|
+
const currentServices = yield* Effect.context();
|
|
24
24
|
yield* router.addAll(entries.map((e) => toRoute(e, currentServices)));
|
|
25
25
|
});
|
|
26
26
|
});
|
|
@@ -72,7 +72,7 @@ function toRoute(entry, currentServices) {
|
|
|
72
72
|
const prepared = yield* layerManager.prepare(entry.layers.concat(provided));
|
|
73
73
|
const guardExit = yield* entry
|
|
74
74
|
.guard(params)
|
|
75
|
-
.pipe(Effect.
|
|
75
|
+
.pipe(Effect.provideContext(prepared.services), Effect.exit);
|
|
76
76
|
if (Exit.isFailure(guardExit) || Option.isNone(guardExit.value)) {
|
|
77
77
|
yield* prepared.rollback;
|
|
78
78
|
return yield* new HttpServerError.HttpServerError({
|
|
@@ -84,11 +84,11 @@ function toRoute(entry, currentServices) {
|
|
|
84
84
|
const scope = yield* Scope.fork(rootScope);
|
|
85
85
|
const paramsRef = yield* RefSubject.make(matchedParams).pipe(Scope.provide(scope));
|
|
86
86
|
const preparedServices = prepared.services;
|
|
87
|
-
const handlerServices =
|
|
87
|
+
const handlerServices = Context.merge(Context.merge(currentServices, preparedServices), Context.make(Scope.Scope, scope));
|
|
88
88
|
const handlerFx = entry.handler(paramsRef);
|
|
89
89
|
const withLayouts = yield* layoutManager.apply(entry.layouts, matchedParams, handlerFx, preparedServices);
|
|
90
90
|
const withCatches = yield* catchManager.apply(entry.catches, withLayouts, preparedServices);
|
|
91
|
-
const html = yield* renderToHtmlString(withCatches).pipe(Effect.
|
|
91
|
+
const html = yield* renderToHtmlString(withCatches).pipe(Effect.provideContext(handlerServices));
|
|
92
92
|
return HttpServerResponse.text(html, {
|
|
93
93
|
headers: { "content-type": "text/html; charset=utf-8" },
|
|
94
94
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typed/ui",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.4",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist",
|
|
6
6
|
"src"
|
|
@@ -19,22 +19,22 @@
|
|
|
19
19
|
"publishConfig": {
|
|
20
20
|
"access": "public"
|
|
21
21
|
},
|
|
22
|
-
"dependencies": {
|
|
23
|
-
"@effect/platform-node": "4.0.0-beta.38",
|
|
24
|
-
"effect": "4.0.0-beta.38",
|
|
25
|
-
"happy-dom": "20.8.7",
|
|
26
|
-
"@typed/fx": "2.0.0-beta.3",
|
|
27
|
-
"@typed/template": "1.0.0-beta.3",
|
|
28
|
-
"@typed/router": "1.0.0-beta.3",
|
|
29
|
-
"@typed/navigation": "1.0.0-beta.3",
|
|
30
|
-
"@typed/id": "1.0.0-beta.3"
|
|
31
|
-
},
|
|
32
|
-
"devDependencies": {
|
|
33
|
-
"typescript": "5.9.3",
|
|
34
|
-
"vitest": "4.1.1"
|
|
35
|
-
},
|
|
36
22
|
"scripts": {
|
|
37
23
|
"build": "[ -d dist ] || rm -f tsconfig.tsbuildinfo; tsc",
|
|
38
24
|
"test": "vitest run --passWithNoTests"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@effect/platform-node": "catalog:",
|
|
28
|
+
"@typed/fx": "workspace:*",
|
|
29
|
+
"@typed/id": "workspace:*",
|
|
30
|
+
"@typed/navigation": "workspace:*",
|
|
31
|
+
"@typed/router": "workspace:*",
|
|
32
|
+
"@typed/template": "workspace:*",
|
|
33
|
+
"effect": "catalog:",
|
|
34
|
+
"happy-dom": "catalog:"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"typescript": "catalog:",
|
|
38
|
+
"vitest": "catalog:"
|
|
39
39
|
}
|
|
40
|
-
}
|
|
40
|
+
}
|
package/src/HttpRouter.test.ts
CHANGED
|
@@ -13,12 +13,7 @@ import { HttpClient, HttpRouter } from "effect/unstable/http";
|
|
|
13
13
|
|
|
14
14
|
describe("typed/ui/HttpRouter", () => {
|
|
15
15
|
it("renders simple html template", () => {
|
|
16
|
-
const matcher = Matcher.empty.match(
|
|
17
|
-
Route.Parse("home"),
|
|
18
|
-
html`
|
|
19
|
-
<div>Hello, world!</div>
|
|
20
|
-
`,
|
|
21
|
-
);
|
|
16
|
+
const matcher = Matcher.empty.match(Route.Parse("home"), html` <div>Hello, world!</div> `);
|
|
22
17
|
const Live = HttpRouter.use(ssrForHttp(matcher)).pipe(
|
|
23
18
|
Layer.provide(StaticHtmlRenderTemplate),
|
|
24
19
|
HttpRouter.serve,
|
|
@@ -49,12 +44,7 @@ describe("typed/ui/HttpRouter", () => {
|
|
|
49
44
|
|
|
50
45
|
it("renders html template with search params", () => {
|
|
51
46
|
const route = Route.Parse("search");
|
|
52
|
-
const matcher = Matcher.empty.match(
|
|
53
|
-
route,
|
|
54
|
-
html`
|
|
55
|
-
<div>Search results</div>
|
|
56
|
-
`,
|
|
57
|
-
);
|
|
47
|
+
const matcher = Matcher.empty.match(route, html` <div>Search results</div> `);
|
|
58
48
|
const Live = HttpRouter.use(ssrForHttp(matcher)).pipe(
|
|
59
49
|
Layer.provide(StaticHtmlRenderTemplate),
|
|
60
50
|
HttpRouter.serve,
|
|
@@ -70,18 +60,8 @@ describe("typed/ui/HttpRouter", () => {
|
|
|
70
60
|
const home = Route.Parse("home");
|
|
71
61
|
const about = Route.Parse("about");
|
|
72
62
|
const matcher = Matcher.empty
|
|
73
|
-
.match(
|
|
74
|
-
|
|
75
|
-
html`
|
|
76
|
-
<div>Home</div>
|
|
77
|
-
`,
|
|
78
|
-
)
|
|
79
|
-
.match(
|
|
80
|
-
about,
|
|
81
|
-
html`
|
|
82
|
-
<div>About</div>
|
|
83
|
-
`,
|
|
84
|
-
);
|
|
63
|
+
.match(home, html` <div>Home</div> `)
|
|
64
|
+
.match(about, html` <div>About</div> `);
|
|
85
65
|
const Live = HttpRouter.use(ssrForHttp(matcher)).pipe(
|
|
86
66
|
Layer.provide(StaticHtmlRenderTemplate),
|
|
87
67
|
HttpRouter.serve,
|
|
@@ -96,12 +76,7 @@ describe("typed/ui/HttpRouter", () => {
|
|
|
96
76
|
});
|
|
97
77
|
|
|
98
78
|
it("returns 404 for unmatched routes", () => {
|
|
99
|
-
const matcher = Matcher.empty.match(
|
|
100
|
-
Route.Parse("home"),
|
|
101
|
-
html`
|
|
102
|
-
<div>Home</div>
|
|
103
|
-
`,
|
|
104
|
-
);
|
|
79
|
+
const matcher = Matcher.empty.match(Route.Parse("home"), html` <div>Home</div> `);
|
|
105
80
|
const Live = HttpRouter.use(
|
|
106
81
|
Effect.fn(function* (router) {
|
|
107
82
|
yield* ssrForHttp(router, matcher);
|
|
@@ -135,12 +110,7 @@ describe("typed/ui/HttpRouter", () => {
|
|
|
135
110
|
});
|
|
136
111
|
|
|
137
112
|
it("sets correct content-type header", () => {
|
|
138
|
-
const matcher = Matcher.empty.match(
|
|
139
|
-
Route.Parse("home"),
|
|
140
|
-
html`
|
|
141
|
-
<div>Hello</div>
|
|
142
|
-
`,
|
|
143
|
-
);
|
|
113
|
+
const matcher = Matcher.empty.match(Route.Parse("home"), html` <div>Hello</div> `);
|
|
144
114
|
const Live = HttpRouter.use(ssrForHttp(matcher)).pipe(
|
|
145
115
|
Layer.provide(StaticHtmlRenderTemplate),
|
|
146
116
|
HttpRouter.serve,
|
|
@@ -157,12 +127,7 @@ describe("typed/ui/HttpRouter", () => {
|
|
|
157
127
|
const users = Route.Join(Route.Parse("api"), Route.Parse("users"));
|
|
158
128
|
const user = Route.Join(users, Route.Param("id"));
|
|
159
129
|
const matcher = Matcher.empty
|
|
160
|
-
.match(
|
|
161
|
-
users,
|
|
162
|
-
html`
|
|
163
|
-
<div>Users list</div>
|
|
164
|
-
`,
|
|
165
|
-
)
|
|
130
|
+
.match(users, html` <div>Users list</div> `)
|
|
166
131
|
.match(user, (params) => html`<div>User ${params.pipe(Fx.map((p) => p.id))}</div>`);
|
|
167
132
|
const Live = HttpRouter.use(ssrForHttp(matcher)).pipe(
|
|
168
133
|
Layer.provide(StaticHtmlRenderTemplate),
|
|
@@ -186,7 +151,11 @@ describe("typed/ui/HttpRouter", () => {
|
|
|
186
151
|
const origin = yield* Navigation.origin;
|
|
187
152
|
const base = yield* Navigation.base;
|
|
188
153
|
const currentEntry = yield* Navigation.currentEntry;
|
|
189
|
-
return html`<div
|
|
154
|
+
return html`<div
|
|
155
|
+
data-origin="${origin}"
|
|
156
|
+
data-base="${base}"
|
|
157
|
+
data-url="${currentEntry.url.href}"
|
|
158
|
+
></div>`;
|
|
190
159
|
}),
|
|
191
160
|
);
|
|
192
161
|
const Live = HttpRouter.use(ssrForHttp(matcher)).pipe(
|
|
@@ -244,22 +213,15 @@ describe("typed/ui/HttpRouter", () => {
|
|
|
244
213
|
it("provides CurrentRoute with parent for nested routes", () => {
|
|
245
214
|
const api = Route.Parse("api");
|
|
246
215
|
const users = Route.Parse("users");
|
|
247
|
-
const matcher = Matcher.empty
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
Fx.gen(function* () {
|
|
257
|
-
const currentRoute = yield* CurrentRoute;
|
|
258
|
-
const hasParent = currentRoute.parent !== undefined;
|
|
259
|
-
const parentPath = currentRoute.parent?.route.path ?? "none";
|
|
260
|
-
return html`<div data-has-parent="${hasParent}" data-parent-path="${parentPath}"></div>`;
|
|
261
|
-
}),
|
|
262
|
-
);
|
|
216
|
+
const matcher = Matcher.empty.match(Route.Slash, html` <div>API</div> `).match(
|
|
217
|
+
users,
|
|
218
|
+
Fx.gen(function* () {
|
|
219
|
+
const currentRoute = yield* CurrentRoute;
|
|
220
|
+
const hasParent = currentRoute.parent !== undefined;
|
|
221
|
+
const parentPath = currentRoute.parent?.route.path ?? "none";
|
|
222
|
+
return html`<div data-has-parent="${hasParent}" data-parent-path="${parentPath}"></div>`;
|
|
223
|
+
}),
|
|
224
|
+
);
|
|
263
225
|
const Live = HttpRouter.use(ssrForHttp(matcher)).pipe(
|
|
264
226
|
Layer.provide(StaticHtmlRenderTemplate),
|
|
265
227
|
HttpRouter.serve,
|
package/src/HttpRouter.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { dual } from "effect/Function";
|
|
|
4
4
|
import * as Layer from "effect/Layer";
|
|
5
5
|
import * as Option from "effect/Option";
|
|
6
6
|
import * as Scope from "effect/Scope";
|
|
7
|
-
import * as
|
|
7
|
+
import * as Context from "effect/Context";
|
|
8
8
|
import { type HttpRouter, type Route, RouteContext } from "effect/unstable/http/HttpRouter";
|
|
9
9
|
import * as HttpServerError from "effect/unstable/http/HttpServerError";
|
|
10
10
|
import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
|
|
@@ -43,7 +43,7 @@ export const ssrForHttp: {
|
|
|
43
43
|
onSome: (parent: CurrentRouteTree) => input.prefix(parent.route),
|
|
44
44
|
});
|
|
45
45
|
const entries = compile(matcher.cases);
|
|
46
|
-
const currentServices = yield* Effect.
|
|
46
|
+
const currentServices = yield* Effect.context<R>();
|
|
47
47
|
|
|
48
48
|
yield* router.addAll(entries.map((e: CompiledEntry) => toRoute(e, currentServices)));
|
|
49
49
|
});
|
|
@@ -71,10 +71,7 @@ function getStatus(error: HttpServerError.HttpServerError): number {
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
function toRoute(
|
|
75
|
-
entry: CompiledEntry,
|
|
76
|
-
currentServices: ServiceMap.ServiceMap<never>,
|
|
77
|
-
): Route<any, any> {
|
|
74
|
+
function toRoute(entry: CompiledEntry, currentServices: Context.Context<never>): Route<any, any> {
|
|
78
75
|
return {
|
|
79
76
|
["~effect/http/HttpRouter/Route"]: "~effect/http/HttpRouter/Route",
|
|
80
77
|
method: "GET",
|
|
@@ -123,7 +120,7 @@ function toRoute(
|
|
|
123
120
|
|
|
124
121
|
const guardExit = yield* entry
|
|
125
122
|
.guard(params)
|
|
126
|
-
.pipe(Effect.
|
|
123
|
+
.pipe(Effect.provideContext(prepared.services), Effect.exit);
|
|
127
124
|
|
|
128
125
|
if (Exit.isFailure(guardExit) || Option.isNone(guardExit.value)) {
|
|
129
126
|
yield* prepared.rollback;
|
|
@@ -138,10 +135,10 @@ function toRoute(
|
|
|
138
135
|
const scope = yield* Scope.fork(rootScope);
|
|
139
136
|
const paramsRef = yield* RefSubject.make(matchedParams).pipe(Scope.provide(scope));
|
|
140
137
|
|
|
141
|
-
const preparedServices = prepared.services as
|
|
142
|
-
const handlerServices =
|
|
143
|
-
|
|
144
|
-
|
|
138
|
+
const preparedServices = prepared.services as Context.Context<any>;
|
|
139
|
+
const handlerServices = Context.merge(
|
|
140
|
+
Context.merge(currentServices, preparedServices),
|
|
141
|
+
Context.make(Scope.Scope, scope),
|
|
145
142
|
);
|
|
146
143
|
|
|
147
144
|
const handlerFx = entry.handler(paramsRef);
|
|
@@ -156,7 +153,7 @@ function toRoute(
|
|
|
156
153
|
const withCatches = yield* catchManager.apply(entry.catches, withLayouts, preparedServices);
|
|
157
154
|
|
|
158
155
|
const html = yield* renderToHtmlString(withCatches).pipe(
|
|
159
|
-
Effect.
|
|
156
|
+
Effect.provideContext(handlerServices),
|
|
160
157
|
);
|
|
161
158
|
return HttpServerResponse.text(html, {
|
|
162
159
|
headers: { "content-type": "text/html; charset=utf-8" },
|