effect-start 0.15.0 → 0.17.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/package.json +2 -1
- package/src/ContentNegotiation.test.ts +103 -0
- package/src/ContentNegotiation.ts +10 -3
- package/src/Development.test.ts +119 -0
- package/src/Development.ts +137 -0
- package/src/Entity.test.ts +592 -0
- package/src/Entity.ts +359 -0
- package/src/FileRouter.ts +2 -2
- package/src/Http.test.ts +315 -20
- package/src/Http.ts +153 -11
- package/src/PathPattern.ts +3 -1
- package/src/Route.ts +26 -10
- package/src/RouteBody.test.ts +98 -66
- package/src/RouteBody.ts +125 -35
- package/src/RouteHook.ts +15 -14
- package/src/RouteHttp.test.ts +2549 -83
- package/src/RouteHttp.ts +337 -113
- package/src/RouteHttpTracer.ts +92 -0
- package/src/RouteMount.test.ts +23 -10
- package/src/RouteMount.ts +161 -4
- package/src/RouteSchema.test.ts +346 -0
- package/src/RouteSchema.ts +386 -7
- package/src/RouteSse.test.ts +249 -0
- package/src/RouteSse.ts +195 -0
- package/src/RouteTree.test.ts +233 -85
- package/src/RouteTree.ts +98 -44
- package/src/StreamExtra.ts +21 -1
- package/src/Values.test.ts +263 -0
- package/src/Values.ts +68 -6
- package/src/bun/BunBundle.ts +0 -73
- package/src/bun/BunHttpServer.ts +23 -7
- package/src/bun/BunRoute.test.ts +162 -0
- package/src/bun/BunRoute.ts +144 -105
- package/src/hyper/HyperHtml.test.ts +119 -0
- package/src/hyper/HyperHtml.ts +10 -2
- package/src/hyper/HyperNode.ts +2 -0
- package/src/hyper/HyperRoute.test.tsx +197 -0
- package/src/hyper/HyperRoute.ts +61 -0
- package/src/hyper/index.ts +4 -0
- package/src/hyper/jsx.d.ts +15 -0
- package/src/index.ts +2 -0
- package/src/node/FileSystem.ts +8 -0
- package/src/testing/TestLogger.test.ts +0 -3
- package/src/testing/TestLogger.ts +15 -9
- package/src/FileSystemExtra.test.ts +0 -242
- package/src/FileSystemExtra.ts +0 -66
package/src/RouteTree.ts
CHANGED
|
@@ -6,21 +6,31 @@ import * as RouteMount from "./RouteMount.ts"
|
|
|
6
6
|
const TypeId: unique symbol = Symbol.for("effect-start/RouteTree")
|
|
7
7
|
const RouteTreeRoutes: unique symbol = Symbol()
|
|
8
8
|
|
|
9
|
+
type MethodRoute = Route.Route.With<{ method: string }>
|
|
10
|
+
|
|
11
|
+
export type RouteTuple = Iterable<MethodRoute>
|
|
12
|
+
|
|
13
|
+
export type LayerRoute = Iterable<Route.Route.With<{ method: "*" }>>
|
|
14
|
+
|
|
15
|
+
type LayerKey = "*"
|
|
16
|
+
const LayerKey: LayerKey = "*"
|
|
17
|
+
|
|
18
|
+
export type InputRouteMap = {
|
|
19
|
+
[LayerKey]?: LayerRoute
|
|
20
|
+
} & {
|
|
21
|
+
[path: PathPattern.PathPattern]: RouteTuple | RouteTree
|
|
22
|
+
}
|
|
23
|
+
|
|
9
24
|
export type RouteMap = {
|
|
10
|
-
[path: PathPattern.PathPattern]:
|
|
11
|
-
Route.Route.With<{
|
|
12
|
-
method: RouteMount.RouteMount.Method
|
|
13
|
-
format?: string
|
|
14
|
-
}>
|
|
15
|
-
>
|
|
25
|
+
[path: PathPattern.PathPattern]: Route.Route.Tuple
|
|
16
26
|
}
|
|
17
27
|
|
|
18
28
|
export type Routes<
|
|
19
|
-
T extends RouteTree
|
|
29
|
+
T extends RouteTree,
|
|
20
30
|
> = T[typeof RouteTreeRoutes]
|
|
21
31
|
|
|
22
32
|
export interface RouteTree<
|
|
23
|
-
Routes extends RouteMap =
|
|
33
|
+
Routes extends RouteMap = RouteMap,
|
|
24
34
|
> {
|
|
25
35
|
[TypeId]: typeof TypeId
|
|
26
36
|
[RouteTreeRoutes]: Routes
|
|
@@ -74,19 +84,78 @@ function sortRoutes(input: RouteMap): RouteMap {
|
|
|
74
84
|
return sorted
|
|
75
85
|
}
|
|
76
86
|
|
|
87
|
+
type PrefixKeys<T, Prefix extends string> = {
|
|
88
|
+
[K in keyof T as K extends string ? `${Prefix}${K}` : never]: T[K]
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
type InferItems<T> = T extends Route.RouteSet.Data<any, any, infer M> ? M
|
|
92
|
+
: []
|
|
93
|
+
|
|
94
|
+
type LayerItems<T extends InputRouteMap> = "*" extends keyof T
|
|
95
|
+
? InferItems<T["*"]>
|
|
96
|
+
: []
|
|
97
|
+
|
|
98
|
+
type FlattenRouteMap<T extends InputRouteMap> =
|
|
99
|
+
& {
|
|
100
|
+
[K in Exclude<keyof T, "*"> as T[K] extends RouteTree ? never : K]: [
|
|
101
|
+
...LayerItems<T>,
|
|
102
|
+
...InferItems<T[K]>,
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
& UnionToIntersection<FlattenNested<T, Exclude<keyof T, "*">, LayerItems<T>>>
|
|
106
|
+
|
|
107
|
+
type FlattenNested<
|
|
108
|
+
T,
|
|
109
|
+
K,
|
|
110
|
+
L extends Route.Route.Tuple,
|
|
111
|
+
> = K extends keyof T
|
|
112
|
+
? T[K] extends RouteTree<infer R>
|
|
113
|
+
? PrefixKeys<PrependLayers<R, L>, K & string>
|
|
114
|
+
: {}
|
|
115
|
+
: {}
|
|
116
|
+
|
|
117
|
+
type PrependLayers<T extends RouteMap, L extends Route.Route.Tuple> = {
|
|
118
|
+
[K in keyof T]: T[K] extends Route.Route.Tuple ? [...L, ...T[K]] : never
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
type UnionToIntersection<U> = (
|
|
122
|
+
U extends any ? (x: U) => void : never
|
|
123
|
+
) extends (x: infer I) => void ? I
|
|
124
|
+
: never
|
|
125
|
+
|
|
77
126
|
export function make<
|
|
78
|
-
const Routes extends
|
|
127
|
+
const Routes extends InputRouteMap,
|
|
79
128
|
>(
|
|
80
|
-
|
|
81
|
-
): RouteTree<
|
|
82
|
-
|
|
83
|
-
|
|
129
|
+
input: Routes,
|
|
130
|
+
): RouteTree<FlattenRouteMap<Routes>> {
|
|
131
|
+
const layerRoutes = [...(input[LayerKey] ?? [])]
|
|
132
|
+
const merged: RouteMap = {}
|
|
133
|
+
|
|
134
|
+
function flatten(
|
|
135
|
+
map: InputRouteMap,
|
|
136
|
+
prefix: string,
|
|
137
|
+
layers: MethodRoute[],
|
|
138
|
+
): void {
|
|
139
|
+
for (const key of Object.keys(map)) {
|
|
140
|
+
if (key === LayerKey) continue
|
|
141
|
+
const path = key as PathPattern.PathPattern
|
|
142
|
+
const entry = map[path]
|
|
143
|
+
const fullPath = `${prefix}${path}` as PathPattern.PathPattern
|
|
144
|
+
|
|
145
|
+
if (isRouteTree(entry)) {
|
|
146
|
+
flatten(routes(entry), fullPath, layers)
|
|
147
|
+
} else {
|
|
148
|
+
merged[fullPath] = [...layers, ...(entry as RouteTuple)]
|
|
149
|
+
}
|
|
150
|
+
}
|
|
84
151
|
}
|
|
85
|
-
|
|
152
|
+
|
|
153
|
+
flatten(input, "", layerRoutes)
|
|
154
|
+
|
|
86
155
|
return {
|
|
87
156
|
[TypeId]: TypeId,
|
|
88
|
-
[RouteTreeRoutes]: sortRoutes(
|
|
89
|
-
} as RouteTree<
|
|
157
|
+
[RouteTreeRoutes]: sortRoutes(merged),
|
|
158
|
+
} as RouteTree<FlattenRouteMap<Routes>>
|
|
90
159
|
}
|
|
91
160
|
|
|
92
161
|
export type WalkDescriptor = {
|
|
@@ -94,44 +163,29 @@ export type WalkDescriptor = {
|
|
|
94
163
|
method: string
|
|
95
164
|
} & Route.RouteDescriptor.Any
|
|
96
165
|
|
|
97
|
-
function*
|
|
166
|
+
function* flattenRoutes(
|
|
98
167
|
path: PathPattern.PathPattern,
|
|
99
|
-
|
|
100
|
-
parentDescriptor: { method: string } & Route.RouteDescriptor.Any,
|
|
168
|
+
routes: Iterable<MethodRoute>,
|
|
101
169
|
): Generator<RouteMount.MountedRoute> {
|
|
102
|
-
for (const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
...Route.descriptor(item),
|
|
107
|
-
path,
|
|
108
|
-
}
|
|
109
|
-
yield Route.make(
|
|
110
|
-
// handler receives mergedDescriptor (which includes path) at runtime
|
|
111
|
-
item.handler as any,
|
|
112
|
-
mergedDescriptor,
|
|
113
|
-
) as RouteMount.MountedRoute
|
|
114
|
-
} else if (Route.isRouteSet(item)) {
|
|
115
|
-
const mergedDescriptor = {
|
|
116
|
-
...parentDescriptor,
|
|
117
|
-
...Route.descriptor(item),
|
|
118
|
-
}
|
|
119
|
-
yield* flattenItems(path, Route.items(item), mergedDescriptor)
|
|
170
|
+
for (const route of routes) {
|
|
171
|
+
const descriptor = {
|
|
172
|
+
...route[Route.RouteDescriptor],
|
|
173
|
+
path,
|
|
120
174
|
}
|
|
175
|
+
yield Route.make(
|
|
176
|
+
route.handler as any,
|
|
177
|
+
descriptor,
|
|
178
|
+
) as RouteMount.MountedRoute
|
|
121
179
|
}
|
|
122
180
|
}
|
|
123
181
|
|
|
124
182
|
export function* walk(
|
|
125
183
|
tree: RouteTree,
|
|
126
184
|
): Generator<RouteMount.MountedRoute> {
|
|
127
|
-
const _routes = routes(tree)
|
|
185
|
+
const _routes = routes(tree) as RouteMap
|
|
186
|
+
|
|
128
187
|
for (const path of Object.keys(_routes) as PathPattern.PathPattern[]) {
|
|
129
|
-
|
|
130
|
-
yield* flattenItems(
|
|
131
|
-
path,
|
|
132
|
-
Route.items(routeSet),
|
|
133
|
-
Route.descriptor(routeSet),
|
|
134
|
-
)
|
|
188
|
+
yield* flattenRoutes(path, _routes[path])
|
|
135
189
|
}
|
|
136
190
|
}
|
|
137
191
|
|
package/src/StreamExtra.ts
CHANGED
|
@@ -4,11 +4,31 @@ import * as Fiber from "effect/Fiber"
|
|
|
4
4
|
import { dual } from "effect/Function"
|
|
5
5
|
import * as Predicate from "effect/Predicate"
|
|
6
6
|
import * as Runtime from "effect/Runtime"
|
|
7
|
-
import * as Stream from "effect/Stream"
|
|
8
7
|
import {
|
|
9
8
|
runForEachChunk,
|
|
10
9
|
StreamTypeId,
|
|
11
10
|
} from "effect/Stream"
|
|
11
|
+
import type * as Stream from "effect/Stream"
|
|
12
|
+
|
|
13
|
+
export const isStream = (
|
|
14
|
+
u: unknown,
|
|
15
|
+
): u is Stream.Stream<unknown, unknown, unknown> =>
|
|
16
|
+
Predicate.hasProperty(u, StreamTypeId)
|
|
17
|
+
|
|
18
|
+
export type IsStream<T> = T extends Stream.Stream<infer _A, infer _E, infer _R>
|
|
19
|
+
? true
|
|
20
|
+
: false
|
|
21
|
+
|
|
22
|
+
export type Chunk<T> = T extends Stream.Stream<infer A, infer _E, infer _R> ? A
|
|
23
|
+
: never
|
|
24
|
+
|
|
25
|
+
export type StreamError<T> = T extends
|
|
26
|
+
Stream.Stream<infer _A, infer E, infer _R> ? E
|
|
27
|
+
: never
|
|
28
|
+
|
|
29
|
+
export type Context<T> = T extends Stream.Stream<infer _A, infer _E, infer R>
|
|
30
|
+
? R
|
|
31
|
+
: never
|
|
12
32
|
|
|
13
33
|
/**
|
|
14
34
|
* Patched version of original Stream.toReadableStreamRuntime (v3.14.4) to
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import * as test from "bun:test"
|
|
2
|
+
import * as Values from "./Values.ts"
|
|
3
|
+
|
|
4
|
+
test.describe("isPlainObject", () => {
|
|
5
|
+
test.it("returns true for plain objects", () => {
|
|
6
|
+
test
|
|
7
|
+
.expect(Values.isPlainObject({}))
|
|
8
|
+
.toBe(true)
|
|
9
|
+
|
|
10
|
+
test
|
|
11
|
+
.expect(Values.isPlainObject({ a: 1, b: 2 }))
|
|
12
|
+
.toBe(true)
|
|
13
|
+
|
|
14
|
+
test
|
|
15
|
+
.expect(Values.isPlainObject({ nested: { value: true } }))
|
|
16
|
+
.toBe(true)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test.it("returns false for null", () => {
|
|
20
|
+
test
|
|
21
|
+
.expect(Values.isPlainObject(null))
|
|
22
|
+
.toBe(false)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test.it("returns false for primitives", () => {
|
|
26
|
+
test
|
|
27
|
+
.expect(Values.isPlainObject("string"))
|
|
28
|
+
.toBe(false)
|
|
29
|
+
|
|
30
|
+
test
|
|
31
|
+
.expect(Values.isPlainObject(42))
|
|
32
|
+
.toBe(false)
|
|
33
|
+
|
|
34
|
+
test
|
|
35
|
+
.expect(Values.isPlainObject(true))
|
|
36
|
+
.toBe(false)
|
|
37
|
+
|
|
38
|
+
test
|
|
39
|
+
.expect(Values.isPlainObject(undefined))
|
|
40
|
+
.toBe(false)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test.it("returns false for ArrayBuffer", () => {
|
|
44
|
+
test
|
|
45
|
+
.expect(Values.isPlainObject(new ArrayBuffer(8)))
|
|
46
|
+
.toBe(false)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test.it("returns false for ArrayBufferView (Uint8Array)", () => {
|
|
50
|
+
test
|
|
51
|
+
.expect(Values.isPlainObject(new Uint8Array([1, 2, 3])))
|
|
52
|
+
.toBe(false)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test.it("returns false for Blob", () => {
|
|
56
|
+
test
|
|
57
|
+
.expect(Values.isPlainObject(new Blob(["test"])))
|
|
58
|
+
.toBe(false)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test.it("returns false for FormData", () => {
|
|
62
|
+
test
|
|
63
|
+
.expect(Values.isPlainObject(new FormData()))
|
|
64
|
+
.toBe(false)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test.it("returns false for URLSearchParams", () => {
|
|
68
|
+
test
|
|
69
|
+
.expect(Values.isPlainObject(new URLSearchParams()))
|
|
70
|
+
.toBe(false)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test.it("returns false for ReadableStream", () => {
|
|
74
|
+
test
|
|
75
|
+
.expect(Values.isPlainObject(new ReadableStream()))
|
|
76
|
+
.toBe(false)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test.it("returns false for arrays", () => {
|
|
80
|
+
test
|
|
81
|
+
.expect(Values.isPlainObject([]))
|
|
82
|
+
.toBe(false)
|
|
83
|
+
|
|
84
|
+
test
|
|
85
|
+
.expect(Values.isPlainObject([1, 2, 3]))
|
|
86
|
+
.toBe(false)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test.it("returns false for functions", () => {
|
|
90
|
+
test
|
|
91
|
+
.expect(Values.isPlainObject(() => {}))
|
|
92
|
+
.toBe(false)
|
|
93
|
+
|
|
94
|
+
test
|
|
95
|
+
.expect(Values.isPlainObject(function() {}))
|
|
96
|
+
.toBe(false)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test.it("returns false for class instances", () => {
|
|
100
|
+
class MyClass {
|
|
101
|
+
value = 42
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
test
|
|
105
|
+
.expect(Values.isPlainObject(new MyClass()))
|
|
106
|
+
.toBe(false)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test.it("returns false for Date", () => {
|
|
110
|
+
test
|
|
111
|
+
.expect(Values.isPlainObject(new Date()))
|
|
112
|
+
.toBe(false)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test.it("returns false for Map", () => {
|
|
116
|
+
test
|
|
117
|
+
.expect(Values.isPlainObject(new Map()))
|
|
118
|
+
.toBe(false)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test.it("returns false for Set", () => {
|
|
122
|
+
test
|
|
123
|
+
.expect(Values.isPlainObject(new Set()))
|
|
124
|
+
.toBe(false)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test.it("returns false for RegExp", () => {
|
|
128
|
+
test
|
|
129
|
+
.expect(Values.isPlainObject(/test/))
|
|
130
|
+
.toBe(false)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test.it("returns false for Error", () => {
|
|
134
|
+
test
|
|
135
|
+
.expect(Values.isPlainObject(new Error("test")))
|
|
136
|
+
.toBe(false)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
test.it("returns false for Promise", () => {
|
|
140
|
+
test
|
|
141
|
+
.expect(Values.isPlainObject(Promise.resolve()))
|
|
142
|
+
.toBe(false)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test.it("returns true for Object.create(null)", () => {
|
|
146
|
+
test
|
|
147
|
+
.expect(Values.isPlainObject(Object.create(null)))
|
|
148
|
+
.toBe(true)
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
test.describe("IsPlainObject", () => {
|
|
153
|
+
test.it("returns true for plain objects", () => {
|
|
154
|
+
test
|
|
155
|
+
.expectTypeOf<Values.IsPlainObject<{ a: number }>>()
|
|
156
|
+
.toEqualTypeOf<true>()
|
|
157
|
+
|
|
158
|
+
test
|
|
159
|
+
.expectTypeOf<Values.IsPlainObject<{ a: number; b: string }>>()
|
|
160
|
+
.toEqualTypeOf<true>()
|
|
161
|
+
|
|
162
|
+
test
|
|
163
|
+
.expectTypeOf<Values.IsPlainObject<{}>>()
|
|
164
|
+
.toEqualTypeOf<true>()
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
test.it("returns false for functions", () => {
|
|
168
|
+
test
|
|
169
|
+
.expectTypeOf<Values.IsPlainObject<() => void>>()
|
|
170
|
+
.toEqualTypeOf<false>()
|
|
171
|
+
|
|
172
|
+
test
|
|
173
|
+
.expectTypeOf<Values.IsPlainObject<(a: number) => string>>()
|
|
174
|
+
.toEqualTypeOf<false>()
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
test.it("returns false for built-in classes", () => {
|
|
178
|
+
test
|
|
179
|
+
.expectTypeOf<Values.IsPlainObject<Request>>()
|
|
180
|
+
.toEqualTypeOf<false>()
|
|
181
|
+
|
|
182
|
+
test
|
|
183
|
+
.expectTypeOf<Values.IsPlainObject<Response>>()
|
|
184
|
+
.toEqualTypeOf<false>()
|
|
185
|
+
|
|
186
|
+
test
|
|
187
|
+
.expectTypeOf<Values.IsPlainObject<Date>>()
|
|
188
|
+
.toEqualTypeOf<false>()
|
|
189
|
+
|
|
190
|
+
test
|
|
191
|
+
.expectTypeOf<Values.IsPlainObject<Map<string, number>>>()
|
|
192
|
+
.toEqualTypeOf<false>()
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
test.it("returns false for primitives", () => {
|
|
196
|
+
test
|
|
197
|
+
.expectTypeOf<Values.IsPlainObject<string>>()
|
|
198
|
+
.toEqualTypeOf<false>()
|
|
199
|
+
|
|
200
|
+
test
|
|
201
|
+
.expectTypeOf<Values.IsPlainObject<number>>()
|
|
202
|
+
.toEqualTypeOf<false>()
|
|
203
|
+
|
|
204
|
+
test
|
|
205
|
+
.expectTypeOf<Values.IsPlainObject<boolean>>()
|
|
206
|
+
.toEqualTypeOf<false>()
|
|
207
|
+
})
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
test.describe("Simplify", () => {
|
|
211
|
+
test.it("expands nested plain objects", () => {
|
|
212
|
+
type Input = {
|
|
213
|
+
readonly a: { readonly x: number }
|
|
214
|
+
readonly b: string
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
test
|
|
218
|
+
.expectTypeOf<Values.Simplify<Input>>()
|
|
219
|
+
.toEqualTypeOf<{
|
|
220
|
+
a: { x: number }
|
|
221
|
+
b: string
|
|
222
|
+
}>()
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
test.it("preserves Request type", () => {
|
|
226
|
+
type Input = {
|
|
227
|
+
request: Request
|
|
228
|
+
data: { value: number }
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
type Result = Values.Simplify<Input>
|
|
232
|
+
|
|
233
|
+
test
|
|
234
|
+
.expectTypeOf<Result["request"]>()
|
|
235
|
+
.toEqualTypeOf<Request>()
|
|
236
|
+
|
|
237
|
+
test
|
|
238
|
+
.expectTypeOf<Result["data"]>()
|
|
239
|
+
.toEqualTypeOf<{ value: number }>()
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
test.it("preserves other built-in types", () => {
|
|
243
|
+
type Input = {
|
|
244
|
+
date: Date
|
|
245
|
+
map: Map<string, number>
|
|
246
|
+
response: Response
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
type Result = Values.Simplify<Input>
|
|
250
|
+
|
|
251
|
+
test
|
|
252
|
+
.expectTypeOf<Result["date"]>()
|
|
253
|
+
.toEqualTypeOf<Date>()
|
|
254
|
+
|
|
255
|
+
test
|
|
256
|
+
.expectTypeOf<Result["map"]>()
|
|
257
|
+
.toEqualTypeOf<Map<string, number>>()
|
|
258
|
+
|
|
259
|
+
test
|
|
260
|
+
.expectTypeOf<Result["response"]>()
|
|
261
|
+
.toEqualTypeOf<Response>()
|
|
262
|
+
})
|
|
263
|
+
})
|
package/src/Values.ts
CHANGED
|
@@ -4,13 +4,75 @@ type JsonPrimitives =
|
|
|
4
4
|
| boolean
|
|
5
5
|
| null
|
|
6
6
|
|
|
7
|
+
export type JsonObject = {
|
|
8
|
+
[key: string]:
|
|
9
|
+
| Json
|
|
10
|
+
// undefined won't be included in JSON objects but this will allow
|
|
11
|
+
// to use Json type in functions that return object of multiple shapes
|
|
12
|
+
| undefined
|
|
13
|
+
}
|
|
14
|
+
|
|
7
15
|
export type Json =
|
|
8
16
|
| JsonPrimitives
|
|
9
17
|
| Json[]
|
|
10
|
-
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
| JsonObject
|
|
19
|
+
|
|
20
|
+
export function isPlainObject(
|
|
21
|
+
value: unknown,
|
|
22
|
+
): value is Record<string, unknown> {
|
|
23
|
+
if (value === null || typeof value !== "object") {
|
|
24
|
+
return false
|
|
16
25
|
}
|
|
26
|
+
|
|
27
|
+
// Check for built-in types and web APIs
|
|
28
|
+
if (
|
|
29
|
+
ArrayBuffer.isView(value)
|
|
30
|
+
|| value instanceof ArrayBuffer
|
|
31
|
+
|| value instanceof Blob
|
|
32
|
+
|| value instanceof FormData
|
|
33
|
+
|| value instanceof URLSearchParams
|
|
34
|
+
|| value instanceof ReadableStream
|
|
35
|
+
|| value instanceof Date
|
|
36
|
+
|| value instanceof Map
|
|
37
|
+
|| value instanceof Set
|
|
38
|
+
|| value instanceof RegExp
|
|
39
|
+
|| value instanceof Error
|
|
40
|
+
|| value instanceof Promise
|
|
41
|
+
|| Array.isArray(value)
|
|
42
|
+
) {
|
|
43
|
+
return false
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check if it's a plain object (Object.prototype or null prototype)
|
|
47
|
+
const proto = Object.getPrototypeOf(value)
|
|
48
|
+
return proto === null || proto === Object.prototype
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Type helper that returns `true` if type T has any method properties,
|
|
53
|
+
* otherwise returns `never`.
|
|
54
|
+
*
|
|
55
|
+
* Used internally by IsPlainObject to distinguish plain objects from
|
|
56
|
+
* class instances or objects with methods.
|
|
57
|
+
*/
|
|
58
|
+
type HasMethod<T> = {
|
|
59
|
+
[K in keyof T]: T[K] extends (...args: any[]) => any ? true : never
|
|
60
|
+
}[keyof T]
|
|
61
|
+
|
|
62
|
+
export type IsPlainObject<T> = T extends object ? T extends Function ? false
|
|
63
|
+
: HasMethod<T> extends never ? true
|
|
64
|
+
: false
|
|
65
|
+
: false
|
|
66
|
+
|
|
67
|
+
export type Simplify<T> = {
|
|
68
|
+
-readonly [K in keyof T]: IsPlainObject<T[K]> extends true
|
|
69
|
+
? { -readonly [P in keyof T[K]]: T[K][P] }
|
|
70
|
+
: T[K]
|
|
71
|
+
} extends infer U ? { [K in keyof U]: U[K] } : never
|
|
72
|
+
|
|
73
|
+
export const concatBytes = (a: Uint8Array, b: Uint8Array): Uint8Array => {
|
|
74
|
+
const result = new Uint8Array(a.byteLength + b.byteLength)
|
|
75
|
+
result.set(a)
|
|
76
|
+
result.set(b, a.byteLength)
|
|
77
|
+
return result
|
|
78
|
+
}
|
package/src/bun/BunBundle.ts
CHANGED
|
@@ -9,10 +9,7 @@ import {
|
|
|
9
9
|
Iterable,
|
|
10
10
|
Layer,
|
|
11
11
|
pipe,
|
|
12
|
-
PubSub,
|
|
13
12
|
Record,
|
|
14
|
-
Stream,
|
|
15
|
-
SynchronizedRef,
|
|
16
13
|
} from "effect"
|
|
17
14
|
import * as NPath from "node:path"
|
|
18
15
|
import type {
|
|
@@ -20,7 +17,6 @@ import type {
|
|
|
20
17
|
BundleManifest,
|
|
21
18
|
} from "../bundler/Bundle.ts"
|
|
22
19
|
import * as Bundle from "../bundler/Bundle.ts"
|
|
23
|
-
import * as FileSystemExtra from "../FileSystemExtra.ts"
|
|
24
20
|
import { BunImportTrackerPlugin } from "./index.ts"
|
|
25
21
|
|
|
26
22
|
export type BuildOptions = Omit<
|
|
@@ -125,75 +121,6 @@ export function layer<T>(
|
|
|
125
121
|
return Layer.effect(tag, build(config))
|
|
126
122
|
}
|
|
127
123
|
|
|
128
|
-
export function layerDev<T>(
|
|
129
|
-
tag: Context.Tag<T, BundleContext>,
|
|
130
|
-
config: BuildOptions,
|
|
131
|
-
) {
|
|
132
|
-
return Layer.scoped(
|
|
133
|
-
tag,
|
|
134
|
-
Effect.gen(function*() {
|
|
135
|
-
const loadRefKey = "_loadRef"
|
|
136
|
-
const sharedBundle = yield* build(config)
|
|
137
|
-
|
|
138
|
-
const loadRef = yield* SynchronizedRef.make(null)
|
|
139
|
-
sharedBundle[loadRefKey] = loadRef
|
|
140
|
-
sharedBundle.events = yield* PubSub.unbounded<Bundle.BundleEvent>()
|
|
141
|
-
|
|
142
|
-
yield* Effect.fork(
|
|
143
|
-
pipe(
|
|
144
|
-
FileSystemExtra.watchSource({
|
|
145
|
-
filter: FileSystemExtra.filterSourceFiles,
|
|
146
|
-
}),
|
|
147
|
-
Stream.map(v =>
|
|
148
|
-
({
|
|
149
|
-
_tag: "Change",
|
|
150
|
-
path: v.path,
|
|
151
|
-
}) as Bundle.BundleEvent
|
|
152
|
-
),
|
|
153
|
-
Stream.onError(err =>
|
|
154
|
-
Effect.logError("Error while watching files", err)
|
|
155
|
-
),
|
|
156
|
-
Stream.runForEach((v) =>
|
|
157
|
-
pipe(
|
|
158
|
-
Effect.gen(function*() {
|
|
159
|
-
yield* Effect.logDebug("Updating bundle: " + tag.key)
|
|
160
|
-
|
|
161
|
-
const newBundle = yield* build(config)
|
|
162
|
-
|
|
163
|
-
Object.assign(sharedBundle, newBundle)
|
|
164
|
-
|
|
165
|
-
// Clean old loaded bundle
|
|
166
|
-
yield* SynchronizedRef.update(loadRef, () => null)
|
|
167
|
-
|
|
168
|
-
// publish event after the built
|
|
169
|
-
if (sharedBundle.events) {
|
|
170
|
-
yield* PubSub.publish(sharedBundle.events, v)
|
|
171
|
-
}
|
|
172
|
-
}),
|
|
173
|
-
Effect.catchAll(err =>
|
|
174
|
-
Effect.gen(function*() {
|
|
175
|
-
yield* Effect.logError(
|
|
176
|
-
"Error while updating bundle",
|
|
177
|
-
err,
|
|
178
|
-
)
|
|
179
|
-
if (sharedBundle.events) {
|
|
180
|
-
yield* PubSub.publish(sharedBundle.events, {
|
|
181
|
-
_tag: "BuildError",
|
|
182
|
-
error: String(err),
|
|
183
|
-
})
|
|
184
|
-
}
|
|
185
|
-
})
|
|
186
|
-
),
|
|
187
|
-
)
|
|
188
|
-
),
|
|
189
|
-
),
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
return sharedBundle
|
|
193
|
-
}),
|
|
194
|
-
)
|
|
195
|
-
}
|
|
196
|
-
|
|
197
124
|
/**
|
|
198
125
|
* Finds common path prefix across provided paths.
|
|
199
126
|
*/
|
package/src/bun/BunHttpServer.ts
CHANGED
|
@@ -201,17 +201,33 @@ export function layerRoutes(
|
|
|
201
201
|
return Layer.effectDiscard(
|
|
202
202
|
Effect.gen(function*() {
|
|
203
203
|
const bunServer = yield* BunHttpServer
|
|
204
|
-
const
|
|
205
|
-
|
|
204
|
+
const runtime = yield* Effect.runtime<BunHttpServer>()
|
|
205
|
+
const toWebHandler = RouteHttp.toWebHandlerRuntime(runtime)
|
|
206
|
+
|
|
207
|
+
const bunRoutes: BunRoute.BunRoutes = {}
|
|
208
|
+
const pathGroups = new Map<string, RouteMount.MountedRoute[]>()
|
|
209
|
+
|
|
210
|
+
for (const route of RouteTree.walk(tree)) {
|
|
211
|
+
const bunDescriptors = BunRoute.descriptors(route)
|
|
212
|
+
if (bunDescriptors) {
|
|
213
|
+
const htmlBundle = yield* Effect.promise(bunDescriptors.bunLoad)
|
|
214
|
+
bunRoutes[`${bunDescriptors.bunPrefix}/*`] = htmlBundle
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const path = Route.descriptor(route).path
|
|
218
|
+
const group = pathGroups.get(path) ?? []
|
|
219
|
+
group.push(route)
|
|
220
|
+
pathGroups.set(path, group)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
for (const [path, routes] of pathGroups) {
|
|
224
|
+
const handler = toWebHandler(routes)
|
|
206
225
|
for (const bunPath of PathPattern.toBun(path)) {
|
|
207
|
-
|
|
226
|
+
bunRoutes[bunPath] = handler
|
|
208
227
|
}
|
|
209
228
|
}
|
|
210
229
|
|
|
211
|
-
|
|
212
|
-
// than add them after startup?
|
|
213
|
-
// now that we have Rooutes.Route thats should be possible
|
|
214
|
-
bunServer.addRoutes(routes)
|
|
230
|
+
bunServer.addRoutes(bunRoutes)
|
|
215
231
|
}),
|
|
216
232
|
)
|
|
217
233
|
}
|