effect-start 0.11.0 → 0.12.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.
- package/README.md +42 -73
- package/package.json +8 -9
- package/src/Commander.test.ts +3 -4
- package/src/FileHttpRouter.test.ts +6 -43
- package/src/FileHttpRouter.ts +0 -15
- package/src/FileRouter.ts +79 -37
- package/src/FileRouterCodegen.test.ts +449 -265
- package/src/FileRouterCodegen.ts +67 -22
- package/src/Route.ts +1 -2
- package/src/Router.test.ts +11 -52
- package/src/Router.ts +55 -137
- package/src/SchemaExtra.ts +102 -0
- package/src/Start.ts +2 -2
- package/src/TestLogger.test.ts +38 -0
- package/src/TestLogger.ts +56 -0
- package/src/bun/BunHttpServer.ts +21 -14
- package/src/bun/BunRoute.test.ts +17 -51
- package/src/bun/BunRoute.ts +7 -61
- package/src/bun/BunRoute_bundles.test.ts +6 -5
package/src/FileRouterCodegen.ts
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import type { PlatformError } from "@effect/platform/Error"
|
|
2
2
|
import * as FileSystem from "@effect/platform/FileSystem"
|
|
3
3
|
import * as Effect from "effect/Effect"
|
|
4
|
+
import * as Function from "effect/Function"
|
|
5
|
+
import * as Schema from "effect/Schema"
|
|
4
6
|
import * as NPath from "node:path"
|
|
5
7
|
import * as FileRouter from "./FileRouter.ts"
|
|
8
|
+
import * as FileRouterPattern from "./FileRouterPattern.ts"
|
|
6
9
|
import * as Route from "./Route.ts"
|
|
10
|
+
import * as Router from "./Router.ts"
|
|
11
|
+
import * as SchemaExtra from "./SchemaExtra.ts"
|
|
7
12
|
|
|
8
13
|
export function validateRouteModule(
|
|
9
14
|
module: unknown,
|
|
10
|
-
):
|
|
15
|
+
): module is FileRouter.RouteModule {
|
|
11
16
|
if (typeof module !== "object" || module === null) {
|
|
12
17
|
return false
|
|
13
18
|
}
|
|
@@ -17,37 +22,77 @@ export function validateRouteModule(
|
|
|
17
22
|
return Route.isRouteSet(module.default)
|
|
18
23
|
}
|
|
19
24
|
|
|
25
|
+
export function generatePathParamsSchema(
|
|
26
|
+
segments: ReadonlyArray<FileRouterPattern.Segment>,
|
|
27
|
+
): Schema.Struct<any> | null {
|
|
28
|
+
const fields: Record<
|
|
29
|
+
PropertyKey,
|
|
30
|
+
Schema.Schema.Any | Schema.PropertySignature.All
|
|
31
|
+
> = {}
|
|
32
|
+
|
|
33
|
+
for (const segment of segments) {
|
|
34
|
+
if (
|
|
35
|
+
segment._tag === "ParamSegment"
|
|
36
|
+
|| segment._tag === "RestSegment"
|
|
37
|
+
) {
|
|
38
|
+
fields[segment.name] = segment.optional
|
|
39
|
+
? Function.pipe(Schema.String, Schema.optional)
|
|
40
|
+
: Schema.String
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (Object.keys(fields).length === 0) {
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return Schema.Struct(fields)
|
|
49
|
+
}
|
|
50
|
+
|
|
20
51
|
/**
|
|
21
52
|
* Validates all route modules in the given route handles.
|
|
22
53
|
*/
|
|
54
|
+
|
|
23
55
|
export function validateRouteModules(
|
|
24
56
|
routesPath: string,
|
|
25
57
|
handles: FileRouter.OrderedRouteHandles,
|
|
26
|
-
): Effect.Effect<void,
|
|
58
|
+
): Effect.Effect<void, PlatformError, FileSystem.FileSystem> {
|
|
27
59
|
return Effect.gen(function*() {
|
|
60
|
+
const fs = yield* FileSystem.FileSystem
|
|
28
61
|
const routeHandles = handles.filter(h => h.handle === "route")
|
|
29
62
|
|
|
30
63
|
for (const handle of routeHandles) {
|
|
31
64
|
const routeModulePath = NPath.resolve(routesPath, handle.modulePath)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return Effect.logWarning(
|
|
45
|
-
`Route module ${routeModulePath} should export default Route`,
|
|
46
|
-
)
|
|
47
|
-
}
|
|
48
|
-
return Effect.void
|
|
49
|
-
}),
|
|
65
|
+
const expectedSchema = generatePathParamsSchema(handle.segments)
|
|
66
|
+
|
|
67
|
+
const fileExists = yield* fs.exists(routeModulePath)
|
|
68
|
+
if (!fileExists) {
|
|
69
|
+
continue
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const module = yield* Effect.promise(() => import(routeModulePath))
|
|
73
|
+
|
|
74
|
+
if (!validateRouteModule(module)) {
|
|
75
|
+
yield* Effect.logWarning(
|
|
76
|
+
`Route module ${routeModulePath} should export default Route`,
|
|
50
77
|
)
|
|
78
|
+
continue
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const routeSet = module.default
|
|
82
|
+
const userSchema = routeSet.schema?.PathParams
|
|
83
|
+
|
|
84
|
+
if (
|
|
85
|
+
expectedSchema
|
|
86
|
+
&& userSchema
|
|
87
|
+
&& !SchemaExtra.schemaEqual(userSchema, expectedSchema)
|
|
88
|
+
) {
|
|
89
|
+
const relativeFilePath = NPath.relative(process.cwd(), routeModulePath)
|
|
90
|
+
yield* Effect.logError(
|
|
91
|
+
`Route '${relativeFilePath}' has incorrect PathParams schema, expected schemaPathParams(${
|
|
92
|
+
SchemaExtra.formatSchemaCode(expectedSchema)
|
|
93
|
+
})`,
|
|
94
|
+
)
|
|
95
|
+
}
|
|
51
96
|
}
|
|
52
97
|
})
|
|
53
98
|
}
|
|
@@ -154,7 +199,7 @@ export const routes = ${routesArray} as const
|
|
|
154
199
|
*/
|
|
155
200
|
export function update(
|
|
156
201
|
routesPath: string,
|
|
157
|
-
manifestPath = "
|
|
202
|
+
manifestPath = "manifest.ts",
|
|
158
203
|
): Effect.Effect<void, PlatformError, FileSystem.FileSystem> {
|
|
159
204
|
return Effect.gen(function*() {
|
|
160
205
|
manifestPath = NPath.resolve(routesPath, manifestPath)
|
|
@@ -184,7 +229,7 @@ export function update(
|
|
|
184
229
|
|
|
185
230
|
export function dump(
|
|
186
231
|
routesPath: string,
|
|
187
|
-
manifestPath = "
|
|
232
|
+
manifestPath = "manifest.ts",
|
|
188
233
|
): Effect.Effect<void, PlatformError, FileSystem.FileSystem> {
|
|
189
234
|
return Effect.gen(function*() {
|
|
190
235
|
manifestPath = NPath.resolve(routesPath, manifestPath)
|
package/src/Route.ts
CHANGED
package/src/Router.test.ts
CHANGED
|
@@ -264,11 +264,11 @@ t.describe("Router", () => {
|
|
|
264
264
|
t.test("infers never for routes without requirements", () => {
|
|
265
265
|
const router = Router.mount("/hello", Route.text("Hello"))
|
|
266
266
|
|
|
267
|
-
type RouterError = Router.
|
|
268
|
-
type
|
|
267
|
+
type RouterError = Router.Router.Error<typeof router>
|
|
268
|
+
type RouterRequirements = Router.Router.Requirements<typeof router>
|
|
269
269
|
|
|
270
270
|
const _checkError: RouterError = undefined as never
|
|
271
|
-
const
|
|
271
|
+
const _checkRequirements: RouterRequirements = undefined as never
|
|
272
272
|
|
|
273
273
|
t.expect(true).toBe(true)
|
|
274
274
|
})
|
|
@@ -283,7 +283,7 @@ t.describe("Router", () => {
|
|
|
283
283
|
Route.text(Effect.fail(new MyError())),
|
|
284
284
|
)
|
|
285
285
|
|
|
286
|
-
type RouterError = Router.
|
|
286
|
+
type RouterError = Router.Router.Error<typeof router>
|
|
287
287
|
|
|
288
288
|
const _checkError: MyError extends RouterError ? true : false = true
|
|
289
289
|
|
|
@@ -306,9 +306,10 @@ t.describe("Router", () => {
|
|
|
306
306
|
),
|
|
307
307
|
)
|
|
308
308
|
|
|
309
|
-
type
|
|
309
|
+
type RouterRequirements = Router.Router.Requirements<typeof router>
|
|
310
310
|
|
|
311
|
-
const
|
|
311
|
+
const _checkRequirements: MyService extends RouterRequirements ? true
|
|
312
|
+
: false = true
|
|
312
313
|
|
|
313
314
|
t.expect(true).toBe(true)
|
|
314
315
|
})
|
|
@@ -325,7 +326,7 @@ t.describe("Router", () => {
|
|
|
325
326
|
.mount("/a", Route.text(Effect.fail(new ErrorA())))
|
|
326
327
|
.mount("/b", Route.text(Effect.fail(new ErrorB())))
|
|
327
328
|
|
|
328
|
-
type RouterError = Router.
|
|
329
|
+
type RouterError = Router.Router.Error<typeof router>
|
|
329
330
|
|
|
330
331
|
const _checkA: ErrorA extends RouterError ? true : false = true
|
|
331
332
|
const _checkB: ErrorB extends RouterError ? true : false = true
|
|
@@ -363,54 +364,12 @@ t.describe("Router", () => {
|
|
|
363
364
|
),
|
|
364
365
|
)
|
|
365
366
|
|
|
366
|
-
type
|
|
367
|
+
type RouterRequirements = Router.Router.Requirements<typeof router>
|
|
367
368
|
|
|
368
|
-
const _checkA: ServiceA extends
|
|
369
|
-
const _checkB: ServiceB extends
|
|
369
|
+
const _checkA: ServiceA extends RouterRequirements ? true : false = true
|
|
370
|
+
const _checkB: ServiceB extends RouterRequirements ? true : false = true
|
|
370
371
|
|
|
371
372
|
t.expect(true).toBe(true)
|
|
372
373
|
})
|
|
373
374
|
})
|
|
374
|
-
|
|
375
|
-
t.describe("fromManifest", () => {
|
|
376
|
-
t.test("loads routes from manifest", async () => {
|
|
377
|
-
const manifest: Router.RouterManifest = {
|
|
378
|
-
routes: [
|
|
379
|
-
{
|
|
380
|
-
path: "/test",
|
|
381
|
-
load: () => Promise.resolve({ default: Route.text("Test") }),
|
|
382
|
-
},
|
|
383
|
-
],
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const router = await Effect.runPromise(Router.fromManifest(manifest))
|
|
387
|
-
|
|
388
|
-
t.expect(router.entries).toHaveLength(1)
|
|
389
|
-
t.expect(router.entries[0].path).toBe("/test")
|
|
390
|
-
})
|
|
391
|
-
|
|
392
|
-
t.test("loads layers from manifest", async () => {
|
|
393
|
-
const layer = Route.layer(
|
|
394
|
-
Route.html(function*(c) {
|
|
395
|
-
const inner = yield* c.next()
|
|
396
|
-
return `<wrap>${inner}</wrap>`
|
|
397
|
-
}),
|
|
398
|
-
)
|
|
399
|
-
|
|
400
|
-
const manifest: Router.RouterManifest = {
|
|
401
|
-
routes: [
|
|
402
|
-
{
|
|
403
|
-
path: "/test",
|
|
404
|
-
load: () => Promise.resolve({ default: Route.text("Test") }),
|
|
405
|
-
layers: [() => Promise.resolve({ default: layer })],
|
|
406
|
-
},
|
|
407
|
-
],
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
const router = await Effect.runPromise(Router.fromManifest(manifest))
|
|
411
|
-
|
|
412
|
-
t.expect(router.entries).toHaveLength(1)
|
|
413
|
-
t.expect(router.entries[0].layers).toHaveLength(1)
|
|
414
|
-
})
|
|
415
|
-
})
|
|
416
375
|
})
|
package/src/Router.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import * as Context from "effect/Context"
|
|
2
1
|
import * as Data from "effect/Data"
|
|
3
|
-
import * as Effect from "effect/Effect"
|
|
4
|
-
import * as Function from "effect/Function"
|
|
5
|
-
import * as Layer from "effect/Layer"
|
|
6
2
|
import * as Pipeable from "effect/Pipeable"
|
|
7
3
|
import * as Predicate from "effect/Predicate"
|
|
8
|
-
import * as
|
|
9
|
-
|
|
4
|
+
import * as Route from "./Route.ts"
|
|
5
|
+
|
|
6
|
+
type RouterModule = typeof import("./Router.ts")
|
|
7
|
+
|
|
8
|
+
type Self =
|
|
9
|
+
| Router.Any
|
|
10
|
+
| RouterModule
|
|
11
|
+
| undefined
|
|
10
12
|
|
|
11
13
|
export type RouterErrorReason =
|
|
12
14
|
| "UnsupportedPattern"
|
|
@@ -18,85 +20,26 @@ export class RouterError extends Data.TaggedError("RouterError")<{
|
|
|
18
20
|
message: string
|
|
19
21
|
}> {}
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export type LazyRoute = {
|
|
26
|
-
path: `/${string}`
|
|
27
|
-
load: () => Promise<ServerModule>
|
|
28
|
-
layers?: ReadonlyArray<() => Promise<unknown>>
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export type RouterManifest = {
|
|
32
|
-
routes: readonly LazyRoute[]
|
|
33
|
-
layers?: any[]
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export type RouterContext = RouterManifest
|
|
37
|
-
|
|
38
|
-
export class Router extends Context.Tag("effect-start/Router")<
|
|
39
|
-
Router,
|
|
40
|
-
RouterContext
|
|
41
|
-
>() {}
|
|
42
|
-
|
|
43
|
-
export function layer(
|
|
44
|
-
manifest: RouterManifest,
|
|
45
|
-
): Layer.Layer<Router, never, never> {
|
|
46
|
-
return Layer.effect(
|
|
47
|
-
Router,
|
|
48
|
-
Effect.gen(function*() {
|
|
49
|
-
return {
|
|
50
|
-
...manifest,
|
|
51
|
-
}
|
|
52
|
-
}),
|
|
53
|
-
)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export const layerFiles = FileRouter.layer
|
|
57
|
-
|
|
58
|
-
export function layerPromise(
|
|
59
|
-
load: () => Promise<RouterManifest>,
|
|
60
|
-
): Layer.Layer<Router, never, never> {
|
|
61
|
-
return Layer.unwrapEffect(
|
|
62
|
-
Effect.gen(function*() {
|
|
63
|
-
const importedModule = yield* Function.pipe(
|
|
64
|
-
Effect.promise(() => load()),
|
|
65
|
-
Effect.orDie,
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
return layer(importedModule)
|
|
69
|
-
}),
|
|
70
|
-
)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const RouterBuilderTypeId: unique symbol = Symbol.for(
|
|
74
|
-
"effect-start/RouterBuilder",
|
|
23
|
+
const TypeId: unique symbol = Symbol.for(
|
|
24
|
+
"effect-start/Router",
|
|
75
25
|
)
|
|
76
26
|
|
|
77
|
-
type RouterModule = typeof import("./Router.ts")
|
|
78
|
-
|
|
79
|
-
type Self =
|
|
80
|
-
| RouterBuilder<any, any>
|
|
81
|
-
| RouterModule
|
|
82
|
-
| undefined
|
|
83
|
-
|
|
84
27
|
export type RouterEntry = {
|
|
85
28
|
path: `/${string}`
|
|
86
29
|
route: Route.RouteSet.Default
|
|
87
30
|
layers: Route.RouteLayer[]
|
|
88
31
|
}
|
|
89
32
|
|
|
90
|
-
type
|
|
33
|
+
type Methods = {
|
|
91
34
|
use: typeof use
|
|
92
35
|
mount: typeof mount
|
|
93
36
|
}
|
|
94
37
|
|
|
95
|
-
export interface
|
|
38
|
+
export interface Router<
|
|
96
39
|
out E = never,
|
|
97
40
|
out R = never,
|
|
98
|
-
> extends Pipeable.Pipeable,
|
|
99
|
-
[
|
|
41
|
+
> extends Pipeable.Pipeable, Methods {
|
|
42
|
+
[TypeId]: typeof TypeId
|
|
100
43
|
readonly entries: readonly RouterEntry[]
|
|
101
44
|
readonly globalLayers: readonly Route.RouteLayer[]
|
|
102
45
|
readonly mounts: Record<`/${string}`, Route.RouteSet.Default>
|
|
@@ -104,18 +47,17 @@ export interface RouterBuilder<
|
|
|
104
47
|
readonly _R: () => R
|
|
105
48
|
}
|
|
106
49
|
|
|
107
|
-
export namespace
|
|
108
|
-
export type Any =
|
|
109
|
-
|
|
110
|
-
export type
|
|
111
|
-
export type Context<T> = T extends RouterBuilder<any, infer R> ? R : never
|
|
50
|
+
export namespace Router {
|
|
51
|
+
export type Any = Router<any, any>
|
|
52
|
+
export type Error<T> = T extends Router<infer E, any> ? E : never
|
|
53
|
+
export type Requirements<T> = T extends Router<any, infer R> ? R : never
|
|
112
54
|
}
|
|
113
55
|
|
|
114
|
-
const
|
|
115
|
-
[
|
|
56
|
+
const Proto: Methods & {
|
|
57
|
+
[TypeId]: typeof TypeId
|
|
116
58
|
pipe: Pipeable.Pipeable["pipe"]
|
|
117
59
|
} = {
|
|
118
|
-
[
|
|
60
|
+
[TypeId]: TypeId,
|
|
119
61
|
|
|
120
62
|
pipe() {
|
|
121
63
|
return Pipeable.pipeArguments(this, arguments)
|
|
@@ -143,17 +85,17 @@ function addRoute<
|
|
|
143
85
|
RouteE,
|
|
144
86
|
RouteR,
|
|
145
87
|
>(
|
|
146
|
-
builder:
|
|
88
|
+
builder: Router<E, R>,
|
|
147
89
|
path: `/${string}`,
|
|
148
90
|
route: Route.RouteSet.Default,
|
|
149
|
-
):
|
|
91
|
+
): Router<E | RouteE, R | RouteR> {
|
|
150
92
|
const existingEntry = builder.entries.find((e) => e.path === path)
|
|
151
93
|
if (existingEntry) {
|
|
152
94
|
const updatedEntry: RouterEntry = {
|
|
153
95
|
...existingEntry,
|
|
154
96
|
route: Route.merge(existingEntry.route, route),
|
|
155
97
|
}
|
|
156
|
-
return
|
|
98
|
+
return make(
|
|
157
99
|
builder.entries.map((e) => (e.path === path ? updatedEntry : e)),
|
|
158
100
|
builder.globalLayers,
|
|
159
101
|
)
|
|
@@ -165,18 +107,17 @@ function addRoute<
|
|
|
165
107
|
layers: [...builder.globalLayers],
|
|
166
108
|
}
|
|
167
109
|
|
|
168
|
-
return
|
|
110
|
+
return make([...builder.entries, newEntry], builder.globalLayers)
|
|
169
111
|
}
|
|
170
112
|
|
|
171
113
|
function addGlobalLayer<E, R>(
|
|
172
|
-
builder:
|
|
114
|
+
builder: Router<E, R>,
|
|
173
115
|
layerRoute: Route.RouteLayer,
|
|
174
|
-
):
|
|
116
|
+
): Router<E, R> {
|
|
175
117
|
const newGlobalLayers = [...builder.globalLayers, layerRoute]
|
|
176
|
-
return
|
|
118
|
+
return make(builder.entries, newGlobalLayers)
|
|
177
119
|
}
|
|
178
120
|
|
|
179
|
-
|
|
180
121
|
function findMatchingLayerRoutes(
|
|
181
122
|
route: Route.Route.Default,
|
|
182
123
|
layers: readonly Route.RouteLayer[],
|
|
@@ -244,10 +185,10 @@ function applyLayersToRouteSet(
|
|
|
244
185
|
} as unknown as Route.RouteSet.Default
|
|
245
186
|
}
|
|
246
187
|
|
|
247
|
-
function
|
|
188
|
+
export function make<E, R>(
|
|
248
189
|
entries: readonly RouterEntry[],
|
|
249
190
|
globalLayers: readonly Route.RouteLayer[] = [],
|
|
250
|
-
):
|
|
191
|
+
): Router<E, R> {
|
|
251
192
|
const mounts: Record<`/${string}`, Route.RouteSet.Default> = {}
|
|
252
193
|
|
|
253
194
|
for (const entry of entries) {
|
|
@@ -256,15 +197,18 @@ function makeBuilder<E, R>(
|
|
|
256
197
|
}
|
|
257
198
|
}
|
|
258
199
|
|
|
259
|
-
return Object.assign(
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
200
|
+
return Object.assign(
|
|
201
|
+
Object.create(Proto),
|
|
202
|
+
{
|
|
203
|
+
entries,
|
|
204
|
+
globalLayers,
|
|
205
|
+
mounts,
|
|
206
|
+
},
|
|
207
|
+
)
|
|
264
208
|
}
|
|
265
209
|
|
|
266
|
-
export function
|
|
267
|
-
return Predicate.hasProperty(input,
|
|
210
|
+
export function isRouter(input: unknown): input is Router.Any {
|
|
211
|
+
return Predicate.hasProperty(input, TypeId)
|
|
268
212
|
}
|
|
269
213
|
|
|
270
214
|
export function use<
|
|
@@ -272,13 +216,14 @@ export function use<
|
|
|
272
216
|
>(
|
|
273
217
|
this: S,
|
|
274
218
|
layerRoute: Route.RouteLayer,
|
|
275
|
-
): S extends
|
|
276
|
-
:
|
|
219
|
+
): S extends Router<infer E, infer R> ? Router<E, R>
|
|
220
|
+
: Router<never, never>
|
|
277
221
|
{
|
|
278
|
-
const
|
|
222
|
+
const router = isRouter(this)
|
|
279
223
|
? this
|
|
280
|
-
:
|
|
281
|
-
|
|
224
|
+
: make<never, never>([], [])
|
|
225
|
+
|
|
226
|
+
return addGlobalLayer(router, layerRoute) as any
|
|
282
227
|
}
|
|
283
228
|
|
|
284
229
|
export function mount<
|
|
@@ -289,49 +234,22 @@ export function mount<
|
|
|
289
234
|
this: S,
|
|
290
235
|
path: `/${string}`,
|
|
291
236
|
route: Route.RouteSet<Routes, Schemas>,
|
|
292
|
-
): S extends
|
|
237
|
+
): S extends Router<infer E, infer R> ? Router<
|
|
293
238
|
E | ExtractRouteSetError<Route.RouteSet<Routes, Schemas>>,
|
|
294
239
|
R | ExtractRouteSetContext<Route.RouteSet<Routes, Schemas>>
|
|
295
240
|
>
|
|
296
|
-
:
|
|
241
|
+
: Router<
|
|
297
242
|
ExtractRouteSetError<Route.RouteSet<Routes, Schemas>>,
|
|
298
243
|
ExtractRouteSetContext<Route.RouteSet<Routes, Schemas>>
|
|
299
244
|
>
|
|
300
245
|
{
|
|
301
|
-
const
|
|
246
|
+
const router = isRouter(this)
|
|
302
247
|
? this
|
|
303
|
-
:
|
|
304
|
-
return addRoute(builder, path, route as Route.RouteSet.Default) as any
|
|
305
|
-
}
|
|
248
|
+
: make<never, never>([], [])
|
|
306
249
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
manifest.routes,
|
|
313
|
-
(lazyRoute) =>
|
|
314
|
-
Effect.gen(function*() {
|
|
315
|
-
const routeModule = yield* Effect.promise(() => lazyRoute.load())
|
|
316
|
-
const layerModules = lazyRoute.layers
|
|
317
|
-
? yield* Effect.forEach(
|
|
318
|
-
lazyRoute.layers,
|
|
319
|
-
(loadLayer) => Effect.promise(() => loadLayer()),
|
|
320
|
-
)
|
|
321
|
-
: []
|
|
322
|
-
|
|
323
|
-
const layers = layerModules
|
|
324
|
-
.map((m: any) => m.default)
|
|
325
|
-
.filter(Route.isRouteLayer)
|
|
326
|
-
|
|
327
|
-
return {
|
|
328
|
-
path: lazyRoute.path,
|
|
329
|
-
route: routeModule.default,
|
|
330
|
-
layers,
|
|
331
|
-
}
|
|
332
|
-
}),
|
|
333
|
-
)
|
|
334
|
-
|
|
335
|
-
return makeBuilder(loadedEntries, [])
|
|
336
|
-
})
|
|
250
|
+
return addRoute(
|
|
251
|
+
router,
|
|
252
|
+
path,
|
|
253
|
+
route as Route.RouteSet.Default,
|
|
254
|
+
) as any
|
|
337
255
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as Function from "effect/Function"
|
|
2
|
+
import * as Schema from "effect/Schema"
|
|
3
|
+
import * as SchemaAST from "effect/SchemaAST"
|
|
4
|
+
|
|
5
|
+
export function getBaseSchemaAST(schema: Schema.Schema.Any): SchemaAST.AST {
|
|
6
|
+
let current = schema.ast
|
|
7
|
+
|
|
8
|
+
while (
|
|
9
|
+
SchemaAST.isRefinement(current) || SchemaAST.isTransformation(current)
|
|
10
|
+
) {
|
|
11
|
+
current = current.from
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return current
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isOptional(schema: Schema.Schema.Any): boolean {
|
|
18
|
+
const ast = schema.ast
|
|
19
|
+
|
|
20
|
+
if (ast._tag === "Union") {
|
|
21
|
+
return ast.types.some((t: SchemaAST.AST) => t._tag === "UndefinedKeyword")
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return false
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function schemaEqual(
|
|
28
|
+
userSchema: Schema.Struct<any> | undefined,
|
|
29
|
+
expectedSchema: Schema.Struct<any> | null,
|
|
30
|
+
): boolean {
|
|
31
|
+
if (!userSchema && !expectedSchema) {
|
|
32
|
+
return true
|
|
33
|
+
}
|
|
34
|
+
if (!userSchema || !expectedSchema) {
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const userFields = userSchema.fields
|
|
39
|
+
const expectedFields = expectedSchema.fields
|
|
40
|
+
|
|
41
|
+
const userKeys = Object.keys(userFields).sort()
|
|
42
|
+
const expectedKeys = Object.keys(expectedFields).sort()
|
|
43
|
+
|
|
44
|
+
if (userKeys.length !== expectedKeys.length) {
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (let i = 0; i < userKeys.length; i++) {
|
|
49
|
+
if (userKeys[i] !== expectedKeys[i]) {
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const key of userKeys) {
|
|
55
|
+
const userFieldSchema = userFields[key]
|
|
56
|
+
const expectedFieldSchema = expectedFields[key]
|
|
57
|
+
|
|
58
|
+
const userOptional = isOptional(userFieldSchema)
|
|
59
|
+
const expectedOptional = isOptional(expectedFieldSchema)
|
|
60
|
+
|
|
61
|
+
if (userOptional !== expectedOptional) {
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const userBaseAST = getBaseSchemaAST(userFieldSchema)
|
|
66
|
+
const expectedBaseAST = getBaseSchemaAST(expectedFieldSchema)
|
|
67
|
+
|
|
68
|
+
if (userBaseAST._tag !== expectedBaseAST._tag) {
|
|
69
|
+
return false
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return true
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function getSchemaTypeName(schema: Schema.Schema.Any): string {
|
|
77
|
+
const baseAST = getBaseSchemaAST(schema)
|
|
78
|
+
switch (baseAST._tag) {
|
|
79
|
+
case "StringKeyword":
|
|
80
|
+
return "Schema.String"
|
|
81
|
+
case "NumberKeyword":
|
|
82
|
+
return "Schema.Number"
|
|
83
|
+
case "BooleanKeyword":
|
|
84
|
+
return "Schema.Boolean"
|
|
85
|
+
default:
|
|
86
|
+
return "Schema.String"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function formatSchemaCode(schema: Schema.Struct<any>): string {
|
|
91
|
+
const fields = schema.fields
|
|
92
|
+
const fieldStrings: string[] = []
|
|
93
|
+
|
|
94
|
+
for (const [key, fieldSchema] of Object.entries(fields)) {
|
|
95
|
+
const optional = isOptional(fieldSchema)
|
|
96
|
+
const typeName = getSchemaTypeName(fieldSchema)
|
|
97
|
+
const fieldStr = optional ? `${key}?: ${typeName}` : `${key}: ${typeName}`
|
|
98
|
+
fieldStrings.push(fieldStr)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return `{ ${fieldStrings.join(", ")} }`
|
|
102
|
+
}
|
package/src/Start.ts
CHANGED
|
@@ -67,7 +67,7 @@ export function serve<ROut, E>(
|
|
|
67
67
|
| HttpClient.HttpClient
|
|
68
68
|
| HttpRouter.Default
|
|
69
69
|
| FileSystem.FileSystem
|
|
70
|
-
| BunHttpServer.
|
|
70
|
+
| BunHttpServer.BunHttpServer
|
|
71
71
|
>
|
|
72
72
|
}>,
|
|
73
73
|
) {
|
|
@@ -79,7 +79,7 @@ export function serve<ROut, E>(
|
|
|
79
79
|
)
|
|
80
80
|
|
|
81
81
|
return Function.pipe(
|
|
82
|
-
BunHttpServer.
|
|
82
|
+
BunHttpServer.layerFileRouter(),
|
|
83
83
|
HttpServer.withLogAddress,
|
|
84
84
|
Layer.provide(appLayer),
|
|
85
85
|
Layer.provide([
|