effect-start 0.32.0 → 0.34.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/dist/Entity.d.ts +6 -1
- package/dist/Entity.d.ts.map +1 -1
- package/dist/Entity.js +43 -5
- package/dist/Entity.js.map +1 -1
- package/dist/Fetch.js.map +1 -1
- package/dist/FileRouterCodegen.js +2 -2
- package/dist/FileRouterCodegen.js.map +1 -1
- package/dist/Html.d.ts +5 -11
- package/dist/Html.d.ts.map +1 -1
- package/dist/Html.js +21 -1
- package/dist/Html.js.map +1 -1
- package/dist/KeyValueStore.d.ts +37 -0
- package/dist/KeyValueStore.d.ts.map +1 -0
- package/dist/KeyValueStore.js +99 -0
- package/dist/KeyValueStore.js.map +1 -0
- package/dist/Route.d.ts +2 -2
- package/dist/Route.d.ts.map +1 -1
- package/dist/Route.js +1 -1
- package/dist/Route.js.map +1 -1
- package/dist/RouteBody.d.ts +2 -2
- package/dist/RouteBody.d.ts.map +1 -1
- package/dist/RouteHttp.d.ts.map +1 -1
- package/dist/RouteHttp.js +45 -35
- package/dist/RouteHttp.js.map +1 -1
- package/dist/RouteMount.d.ts +20 -31
- package/dist/RouteMount.d.ts.map +1 -1
- package/dist/RouteMount.js +0 -15
- package/dist/RouteMount.js.map +1 -1
- package/dist/Start.d.ts.map +1 -1
- package/dist/Start.js +4 -0
- package/dist/Start.js.map +1 -1
- package/dist/StaticFiles.d.ts +2 -2
- package/dist/StaticFiles.d.ts.map +1 -1
- package/dist/StaticFiles.js +7 -8
- package/dist/StaticFiles.js.map +1 -1
- package/dist/bun/BunRoute.d.ts.map +1 -1
- package/dist/bun/BunRoute.js +90 -78
- package/dist/bun/BunRoute.js.map +1 -1
- package/dist/bun/BunServer.d.ts +1 -1
- package/dist/bun/BunServer.d.ts.map +1 -1
- package/dist/bun/BunServer.js +8 -1
- package/dist/bun/BunServer.js.map +1 -1
- package/dist/bundler/BundleRoute.d.ts +4 -4
- package/dist/bundler/BundleRoute.d.ts.map +1 -1
- package/dist/datastar/attributes/computed.js +3 -3
- package/dist/datastar/attributes/computed.js.map +1 -1
- package/dist/datastar/attributes/on.js +11 -36
- package/dist/datastar/attributes/on.js.map +1 -1
- package/dist/datastar/engine.d.ts +9 -7
- package/dist/datastar/engine.d.ts.map +1 -1
- package/dist/datastar/engine.js +45 -29
- package/dist/datastar/engine.js.map +1 -1
- package/dist/datastar/jsx.d.ts +70 -0
- package/dist/datastar/jsx.d.ts.map +1 -0
- package/dist/datastar/jsx.js +2 -0
- package/dist/datastar/jsx.js.map +1 -0
- package/dist/datastar/window.d.ts +8 -0
- package/dist/datastar/window.d.ts.map +1 -0
- package/dist/datastar/window.js +4 -0
- package/dist/datastar/window.js.map +1 -0
- package/dist/experimental/KeyValueStore.d.ts +37 -0
- package/dist/experimental/KeyValueStore.d.ts.map +1 -0
- package/dist/experimental/KeyValueStore.js +99 -0
- package/dist/experimental/KeyValueStore.js.map +1 -0
- package/dist/experimental/SqlCache.d.ts +19 -0
- package/dist/experimental/SqlCache.d.ts.map +1 -0
- package/dist/experimental/SqlCache.js +35 -0
- package/dist/experimental/SqlCache.js.map +1 -0
- package/dist/experimental/SqlIntrospect.d.ts +92 -0
- package/dist/experimental/SqlIntrospect.d.ts.map +1 -0
- package/dist/experimental/SqlIntrospect.js +478 -0
- package/dist/experimental/SqlIntrospect.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/jsx-runtime.d.ts +2 -2
- package/dist/jsx-runtime.d.ts.map +1 -1
- package/dist/jsx.d.ts +3216 -0
- package/dist/jsx.d.ts.map +1 -0
- package/dist/jsx.js +6 -0
- package/dist/jsx.js.map +1 -0
- package/dist/lint/plugin.d.ts +4 -3
- package/dist/lint/plugin.js +56 -17
- package/dist/lint/plugin.js.map +1 -1
- package/dist/sql/index.d.ts +0 -2
- package/dist/sql/index.d.ts.map +1 -1
- package/dist/sql/index.js +0 -2
- package/dist/sql/index.js.map +1 -1
- package/dist/studio/StudioLogger.d.ts.map +1 -1
- package/dist/studio/StudioLogger.js +2 -1
- package/dist/studio/StudioLogger.js.map +1 -1
- package/dist/studio/StudioStore.d.ts +3 -0
- package/dist/studio/StudioStore.d.ts.map +1 -1
- package/dist/studio/StudioStore.js +13 -0
- package/dist/studio/StudioStore.js.map +1 -1
- package/dist/studio/_Pretty.d.ts +4 -0
- package/dist/studio/_Pretty.d.ts.map +1 -0
- package/dist/studio/_Pretty.js +56 -0
- package/dist/studio/_Pretty.js.map +1 -0
- package/dist/studio/routes/errors/route.d.ts +2 -2
- package/dist/studio/routes/fiberDetail.d.ts +2 -2
- package/dist/studio/routes/fibers/route.d.ts +2 -2
- package/dist/studio/routes/layout.d.ts +2 -0
- package/dist/studio/routes/layout.d.ts.map +1 -1
- package/dist/studio/routes/layout.html +3 -12
- package/dist/studio/routes/layout.js +6 -1
- package/dist/studio/routes/layout.js.map +1 -1
- package/dist/studio/routes/logs/route.d.ts +2 -2
- package/dist/studio/routes/metrics/route.d.ts +2 -2
- package/dist/studio/routes/route.d.ts +2 -2
- package/dist/studio/routes/routes/route.d.ts +2 -2
- package/dist/studio/routes/services/route.d.ts +2 -2
- package/dist/studio/routes/system/route.d.ts +2 -2
- package/dist/studio/routes/traceDetail.d.ts +2 -2
- package/dist/studio/routes/traces/route.d.ts +2 -2
- package/dist/studio/routes/traces/route.d.ts.map +1 -1
- package/dist/studio/routes/traces/route.js +5 -2
- package/dist/studio/routes/traces/route.js.map +1 -1
- package/dist/studio/routes/tree.d.ts +22 -0
- package/dist/studio/routes/tree.d.ts.map +1 -1
- package/dist/studio/ui/Errors.d.ts +1 -1
- package/dist/studio/ui/Errors.js +1 -1
- package/dist/studio/ui/Errors.js.map +1 -1
- package/dist/studio/ui/Fibers.d.ts +2 -2
- package/dist/studio/ui/Fibers.d.ts.map +1 -1
- package/dist/studio/ui/Fibers.js +4 -3
- package/dist/studio/ui/Fibers.js.map +1 -1
- package/dist/studio/ui/Logs.d.ts +1 -1
- package/dist/studio/ui/Logs.d.ts.map +1 -1
- package/dist/studio/ui/Logs.js +2 -1
- package/dist/studio/ui/Logs.js.map +1 -1
- package/dist/studio/ui/Metrics.d.ts +1 -1
- package/dist/studio/ui/Routes.d.ts +1 -1
- package/dist/studio/ui/Routes.d.ts.map +1 -1
- package/dist/studio/ui/Services.d.ts +1 -1
- package/dist/studio/ui/Services.d.ts.map +1 -1
- package/dist/studio/ui/Shell.d.ts +2 -2
- package/dist/studio/ui/Shell.d.ts.map +1 -1
- package/dist/studio/ui/System.d.ts +1 -1
- package/dist/studio/ui/Traces.d.ts +3 -3
- package/dist/studio/ui/Traces.d.ts.map +1 -1
- package/dist/studio/ui/Traces.js +5 -11
- package/dist/studio/ui/Traces.js.map +1 -1
- package/dist/studio/ui/_PrettyValue.d.ts +10 -0
- package/dist/studio/ui/_PrettyValue.d.ts.map +1 -0
- package/dist/studio/ui/_PrettyValue.js +27 -0
- package/dist/studio/ui/_PrettyValue.js.map +1 -0
- package/dist/tailwind/TailwindPlugin.d.ts.map +1 -1
- package/dist/tailwind/TailwindPlugin.js +89 -62
- package/dist/tailwind/TailwindPlugin.js.map +1 -1
- package/dist/ts/import-plugin.cjs +388 -0
- package/dist/ts/import-plugin.cjs.map +1 -0
- package/dist/ts/import-plugin.d.cts +87 -0
- package/dist/ts/import-plugin.d.cts.map +1 -0
- package/dist/ts/import-plugin.d.ts +87 -0
- package/dist/ts/import-plugin.d.ts.map +1 -0
- package/dist/ts/import-plugin.js +390 -0
- package/dist/ts/import-plugin.js.map +1 -0
- package/package.json +109 -8
- package/src/Entity.ts +62 -8
- package/src/Fetch.ts +1 -1
- package/src/FileRouterCodegen.ts +2 -2
- package/src/Html.ts +28 -21
- package/src/Route.ts +2 -2
- package/src/RouteBody.ts +2 -2
- package/src/RouteHttp.ts +45 -47
- package/src/RouteMount.ts +23 -65
- package/src/Start.ts +4 -0
- package/src/StaticFiles.ts +7 -10
- package/src/bun/BunRoute.ts +117 -95
- package/src/bun/BunServer.ts +9 -2
- package/src/datastar/README.md +24 -8
- package/src/datastar/attributes/computed.ts +3 -3
- package/src/datastar/attributes/on.ts +11 -37
- package/src/datastar/engine.ts +61 -37
- package/src/datastar/jsx.d.ts +12 -26
- package/src/datastar/types.d.ts +8 -0
- package/src/experimental/KeyValueStore.ts +161 -0
- package/src/{sql → experimental}/SqlCache.ts +1 -1
- package/src/{sql → experimental}/SqlIntrospect.ts +1 -1
- package/src/index.ts +1 -0
- package/src/jsx-runtime.ts +1 -1
- package/src/jsx.d.ts +17 -2
- package/src/lint/plugin.js +54 -19
- package/src/sql/index.ts +0 -2
- package/src/studio/StudioLogger.ts +2 -1
- package/src/studio/StudioStore.ts +18 -0
- package/src/studio/_Pretty.ts +59 -0
- package/src/studio/routes/layout.html +3 -12
- package/src/studio/routes/layout.tsx +9 -1
- package/src/studio/routes/traces/route.tsx +5 -1
- package/src/studio/ui/Errors.tsx +1 -1
- package/src/studio/ui/Fibers.tsx +14 -10
- package/src/studio/ui/Logs.tsx +15 -10
- package/src/studio/ui/Traces.tsx +40 -68
- package/src/studio/ui/_PrettyValue.tsx +34 -0
- package/src/tailwind/TailwindPlugin.ts +102 -75
- package/src/RouteTrie.ts +0 -205
- package/src/experimental/index.ts +0 -1
package/src/Entity.ts
CHANGED
|
@@ -20,7 +20,10 @@ function isBinary(v: unknown): v is Uint8Array | ArrayBuffer {
|
|
|
20
20
|
* Header keys are guaranteed to be lowercase.
|
|
21
21
|
*/
|
|
22
22
|
export type Headers = {
|
|
23
|
-
|
|
23
|
+
// `set-cookie` is the only header that supports multiple values (as an array),
|
|
24
|
+
// since it requires separate `Set-Cookie` headers per cookie (RFC 6265).
|
|
25
|
+
readonly "set-cookie"?: string | ReadonlyArray<string> | null
|
|
26
|
+
readonly [header: string]: string | ReadonlyArray<string> | null | undefined
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
export interface Entity<T = unknown, E = never> extends Effect.Effect<Entity<T, E>, E> {
|
|
@@ -50,6 +53,9 @@ export interface Entity<T = unknown, E = never> extends Effect.Effect<Entity<T,
|
|
|
50
53
|
: [T] extends [Values.Json]
|
|
51
54
|
? Effect.Effect<T, ParseResult.ParseError | E>
|
|
52
55
|
: Effect.Effect<unknown, ParseResult.ParseError | E>
|
|
56
|
+
readonly schemaJson: <A, I, R>(
|
|
57
|
+
schema: Schema.Schema<A, I, R>,
|
|
58
|
+
) => Effect.Effect<A, ParseResult.ParseError | E, R>
|
|
53
59
|
readonly bytes: Effect.Effect<Uint8Array, ParseResult.ParseError | E>
|
|
54
60
|
readonly stream: T extends Stream.Stream<infer A, infer E1, any>
|
|
55
61
|
? Stream.Stream<A, ParseResult.ParseError | E | E1>
|
|
@@ -76,6 +82,16 @@ function parseJson(s: string): Effect.Effect<unknown, ParseResult.ParseError> {
|
|
|
76
82
|
}
|
|
77
83
|
}
|
|
78
84
|
|
|
85
|
+
function isDirectJson(value: unknown): value is Exclude<Values.Json, string> {
|
|
86
|
+
return (
|
|
87
|
+
value === null ||
|
|
88
|
+
typeof value === "number" ||
|
|
89
|
+
typeof value === "boolean" ||
|
|
90
|
+
Array.isArray(value) ||
|
|
91
|
+
Values.isPlainObject(value)
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
79
95
|
function getText(
|
|
80
96
|
self: Entity<unknown, unknown>,
|
|
81
97
|
): Effect.Effect<string, ParseResult.ParseError | unknown> {
|
|
@@ -123,7 +139,7 @@ function getJson(
|
|
|
123
139
|
if (isEntity(inner)) {
|
|
124
140
|
return inner.json
|
|
125
141
|
}
|
|
126
|
-
if (
|
|
142
|
+
if (isDirectJson(inner)) {
|
|
127
143
|
return Effect.succeed(inner)
|
|
128
144
|
}
|
|
129
145
|
if (typeof inner === "string") {
|
|
@@ -136,7 +152,7 @@ function getJson(
|
|
|
136
152
|
},
|
|
137
153
|
)
|
|
138
154
|
}
|
|
139
|
-
if (
|
|
155
|
+
if (isDirectJson(v)) {
|
|
140
156
|
return Effect.succeed(v)
|
|
141
157
|
}
|
|
142
158
|
if (typeof v === "string") {
|
|
@@ -188,13 +204,20 @@ function getBytes(
|
|
|
188
204
|
if (typeof v === "string") {
|
|
189
205
|
return Effect.succeed(textEncoder.encode(v))
|
|
190
206
|
}
|
|
191
|
-
// Allows entity.stream to work when body is a JSON
|
|
192
|
-
if (
|
|
207
|
+
// Allows entity.stream to work when body is a JSON value.
|
|
208
|
+
if (isDirectJson(v)) {
|
|
193
209
|
return Effect.succeed(textEncoder.encode(JSON.stringify(v)))
|
|
194
210
|
}
|
|
195
211
|
return Effect.fail(mismatch(Schema.Uint8ArrayFromSelf, v))
|
|
196
212
|
}
|
|
197
213
|
|
|
214
|
+
export function schemaJson<T, E, A, I, R>(
|
|
215
|
+
self: Entity<T, E>,
|
|
216
|
+
schema: Schema.Schema<A, I, R>,
|
|
217
|
+
): Effect.Effect<A, ParseResult.ParseError | E, R> {
|
|
218
|
+
return Effect.flatMap(self.json, Schema.decodeUnknown(schema))
|
|
219
|
+
}
|
|
220
|
+
|
|
198
221
|
function getStream<A, E1, E2>(
|
|
199
222
|
self: Entity<Stream.Stream<A, E1, never>, E2>,
|
|
200
223
|
): Stream.Stream<A, ParseResult.ParseError | E1 | E2>
|
|
@@ -234,6 +257,11 @@ const Proto: Proto = Object.defineProperties(Object.create(Effectable.CommitProt
|
|
|
234
257
|
return getJson(this)
|
|
235
258
|
},
|
|
236
259
|
},
|
|
260
|
+
schemaJson: {
|
|
261
|
+
value(this: Entity<unknown, unknown>, schema: Schema.Schema.Any) {
|
|
262
|
+
return schemaJson(this, schema)
|
|
263
|
+
},
|
|
264
|
+
},
|
|
237
265
|
bytes: {
|
|
238
266
|
get(this: Entity<unknown, unknown>) {
|
|
239
267
|
return getBytes(this)
|
|
@@ -278,6 +306,32 @@ export function effect<A, E, R>(body: Effect.Effect<Entity<A> | A, E, R>): Entit
|
|
|
278
306
|
return make(body) as unknown as Entity<A, E>
|
|
279
307
|
}
|
|
280
308
|
|
|
309
|
+
function mergeSetCookie(
|
|
310
|
+
existing: string | ReadonlyArray<string> | null | undefined,
|
|
311
|
+
incoming: string | ReadonlyArray<string> | null | undefined,
|
|
312
|
+
): string | ReadonlyArray<string> | undefined {
|
|
313
|
+
if (incoming == null) return existing ?? undefined
|
|
314
|
+
if (existing == null) return incoming
|
|
315
|
+
const a = Array.isArray(existing) ? existing : [existing]
|
|
316
|
+
const b = Array.isArray(incoming) ? incoming : [incoming]
|
|
317
|
+
return [...a, ...b]
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function merge<T, E>(entity: Entity<T, E>, options: Options): Entity<T, E> {
|
|
321
|
+
const headers: Headers = options.headers
|
|
322
|
+
? {
|
|
323
|
+
...entity.headers,
|
|
324
|
+
...options.headers,
|
|
325
|
+
"set-cookie": mergeSetCookie(entity.headers["set-cookie"], options.headers["set-cookie"]),
|
|
326
|
+
}
|
|
327
|
+
: entity.headers
|
|
328
|
+
return make(entity.body, {
|
|
329
|
+
headers,
|
|
330
|
+
status: options.status ?? entity.status,
|
|
331
|
+
url: options.url ?? entity.url,
|
|
332
|
+
}) as Entity<T, E>
|
|
333
|
+
}
|
|
334
|
+
|
|
281
335
|
export function resolve<A, E>(entity: Entity<A, E>): Effect.Effect<Entity<A, E>, E, never> {
|
|
282
336
|
const body = entity.body
|
|
283
337
|
if (Effect.isEffect(body)) {
|
|
@@ -297,13 +351,13 @@ export function resolve<A, E>(entity: Entity<A, E>): Effect.Effect<Entity<A, E>,
|
|
|
297
351
|
export function type(self: Entity): string {
|
|
298
352
|
const h = self.headers
|
|
299
353
|
if (h["content-type"]) {
|
|
300
|
-
return h["content-type"]
|
|
354
|
+
return h["content-type"] as string
|
|
301
355
|
}
|
|
302
356
|
const v = self.body
|
|
303
357
|
if (typeof v === "string") {
|
|
304
358
|
return "text/plain"
|
|
305
359
|
}
|
|
306
|
-
if (
|
|
360
|
+
if (isDirectJson(v)) {
|
|
307
361
|
return "application/json"
|
|
308
362
|
}
|
|
309
363
|
return "application/octet-stream"
|
|
@@ -312,7 +366,7 @@ export function type(self: Entity): string {
|
|
|
312
366
|
export function length(self: Entity): number | undefined {
|
|
313
367
|
const h = self.headers
|
|
314
368
|
if (h["content-length"]) {
|
|
315
|
-
return parseInt(h["content-length"], 10)
|
|
369
|
+
return parseInt(h["content-length"] as string, 10)
|
|
316
370
|
}
|
|
317
371
|
const v = self.body
|
|
318
372
|
if (typeof v === "string") {
|
package/src/Fetch.ts
CHANGED
|
@@ -232,7 +232,7 @@ export function followRedirects(options?: { readonly maxRedirects?: number }): M
|
|
|
232
232
|
return entity
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
const location = entity.headers["location"]
|
|
235
|
+
const location = entity.headers["location"] as string | undefined
|
|
236
236
|
if (!location) {
|
|
237
237
|
return entity
|
|
238
238
|
}
|
package/src/FileRouterCodegen.ts
CHANGED
|
@@ -137,10 +137,10 @@ export function generateCode(fileRoutes: FileRouter.OrderedFileRoutes): string |
|
|
|
137
137
|
currentPath = parentPath || "/"
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
// Order:
|
|
140
|
+
// Order: layers from outermost to innermost, then route
|
|
141
141
|
const loaders: Array<string> = [
|
|
142
|
+
...allLayers.map((layer) => `() => import(".${layer.modulePath}")`),
|
|
142
143
|
`() => import(".${route.modulePath}")`,
|
|
143
|
-
...allLayers.reverse().map((layer) => `() => import(".${layer.modulePath}")`),
|
|
144
144
|
]
|
|
145
145
|
|
|
146
146
|
entries.push({ path, loaders })
|
package/src/Html.ts
CHANGED
|
@@ -1,28 +1,14 @@
|
|
|
1
|
-
import type { JSX } from "
|
|
1
|
+
import type { JSX, HtmlElement, HtmlElemenetProps, HtmlElementType, HtmlComponent } from "../src/jsx.d.ts"
|
|
2
|
+
|
|
3
|
+
export type ElementType = HtmlElementType
|
|
4
|
+
export type ElemenetProps = HtmlElemenetProps
|
|
5
|
+
export type Component = HtmlComponent
|
|
6
|
+
export type Element = HtmlElement
|
|
2
7
|
|
|
3
8
|
export const TypeId = "~effect-start/HyperNode" as const
|
|
4
9
|
|
|
5
10
|
const NoChildren: ReadonlyArray<never> = Object.freeze([])
|
|
6
11
|
|
|
7
|
-
type Primitive = string | number | boolean | null | undefined
|
|
8
|
-
|
|
9
|
-
export type ElementType = string | Component
|
|
10
|
-
|
|
11
|
-
export type ElemenetProps = {
|
|
12
|
-
[key: string]:
|
|
13
|
-
| Primitive
|
|
14
|
-
| Element
|
|
15
|
-
| Iterable<Primitive | Element>
|
|
16
|
-
| Record<string, unknown>
|
|
17
|
-
| ((window: Window) => void)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export type Component = (props: ElemenetProps) => Element | Primitive
|
|
21
|
-
|
|
22
|
-
export interface Element {
|
|
23
|
-
type: ElementType
|
|
24
|
-
props: ElemenetProps
|
|
25
|
-
}
|
|
26
12
|
|
|
27
13
|
export function make(type: ElementType, props: ElemenetProps): Element {
|
|
28
14
|
return {
|
|
@@ -74,6 +60,27 @@ const RAW_TEXT_TAGS = ["script", "style"]
|
|
|
74
60
|
|
|
75
61
|
const escapeRawText = (text: string) => text.replaceAll("</", "<\\/")
|
|
76
62
|
|
|
63
|
+
const serializeObjectProperty = (value: unknown): string | undefined => {
|
|
64
|
+
if (value === undefined) return undefined
|
|
65
|
+
if (typeof value === "function") return value.toString()
|
|
66
|
+
if (Array.isArray(value)) {
|
|
67
|
+
return `[${value.map((item) => serializeObjectProperty(item) ?? "null").join(",")}]`
|
|
68
|
+
}
|
|
69
|
+
if (value && typeof value === "object") {
|
|
70
|
+
const props = Object.entries(value)
|
|
71
|
+
.flatMap(([key, prop]) => {
|
|
72
|
+
const serialized = serializeObjectProperty(prop)
|
|
73
|
+
return serialized === undefined ? [] : [`${JSON.stringify(key)}:${serialized}`]
|
|
74
|
+
})
|
|
75
|
+
.join(",")
|
|
76
|
+
return `{${props}}`
|
|
77
|
+
}
|
|
78
|
+
return JSON.stringify(value)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const serializeDataAttributeObject = (key: string, value: Record<string, unknown>): string =>
|
|
82
|
+
key === "data-computed" ? serializeObjectProperty(value)! : JSON.stringify(value)
|
|
83
|
+
|
|
77
84
|
export function renderToString(
|
|
78
85
|
node: JSX.Children,
|
|
79
86
|
hooks?: { onNode?: (node: Element) => void },
|
|
@@ -143,7 +150,7 @@ export function renderToString(
|
|
|
143
150
|
if (key.startsWith("data-") && typeof value === "function") {
|
|
144
151
|
result += ` ${esc(resolvedKey)}="${esc(value.toString())}"`
|
|
145
152
|
} else if (key.startsWith("data-") && typeof value === "object") {
|
|
146
|
-
result += ` ${esc(resolvedKey)}='${escSQ(
|
|
153
|
+
result += ` ${esc(resolvedKey)}='${escSQ(serializeDataAttributeObject(key, value))}'`
|
|
147
154
|
} else {
|
|
148
155
|
result += ` ${esc(resolvedKey)}="${esc(value)}"`
|
|
149
156
|
}
|
package/src/Route.ts
CHANGED
|
@@ -10,7 +10,7 @@ import * as RouteBody from "./RouteBody.ts"
|
|
|
10
10
|
import * as RouteTree from "./RouteTree.ts"
|
|
11
11
|
import type * as Values from "./_Values.ts"
|
|
12
12
|
import * as Html from "./Html.ts"
|
|
13
|
-
import type { JSX } from "
|
|
13
|
+
import type { JSX } from "../src/jsx.d.ts"
|
|
14
14
|
|
|
15
15
|
export const render = RouteBody.render
|
|
16
16
|
|
|
@@ -204,7 +204,7 @@ export type ExtractContext<
|
|
|
204
204
|
export * from "./RouteHook.ts"
|
|
205
205
|
export * from "./RouteSchema.ts"
|
|
206
206
|
|
|
207
|
-
export {
|
|
207
|
+
export { del, get, head, options, patch, post, put, use } from "./RouteMount.ts"
|
|
208
208
|
|
|
209
209
|
export const text = RouteBody.build<string, "text">({
|
|
210
210
|
format: "text",
|
package/src/RouteBody.ts
CHANGED
|
@@ -25,7 +25,7 @@ type YieldContext<T> = T extends Utils.YieldWrap<Effect.Effect<any, any, infer R
|
|
|
25
25
|
|
|
26
26
|
type Next<B, A> = (context?: Partial<B> & Record<string, unknown>) => Entity.Entity<UnwrapStream<A>>
|
|
27
27
|
|
|
28
|
-
type HandlerReturn<A> = A | Entity.Entity<A> | ((self: Route.RouteSet.Any) => Route.RouteSet.Any)
|
|
28
|
+
type HandlerReturn<A> = A | Entity.Entity<A, any> | ((self: Route.RouteSet.Any) => Route.RouteSet.Any)
|
|
29
29
|
|
|
30
30
|
type HandlerFunction<B, A, E, R> = (
|
|
31
31
|
context: Values.Simplify<B>,
|
|
@@ -41,7 +41,7 @@ export type GeneratorHandler<B, A, Y> = (
|
|
|
41
41
|
|
|
42
42
|
export type HandlerInput<B, A, E, R> =
|
|
43
43
|
| A
|
|
44
|
-
| Entity.Entity<A>
|
|
44
|
+
| Entity.Entity<A, any>
|
|
45
45
|
| Effect.Effect<HandlerReturn<A>, E, R>
|
|
46
46
|
| HandlerFunction<B, A, E, R>
|
|
47
47
|
|
package/src/RouteHttp.ts
CHANGED
|
@@ -76,7 +76,7 @@ const respondError = (
|
|
|
76
76
|
|
|
77
77
|
function streamResponse(
|
|
78
78
|
stream: Stream.Stream<unknown, unknown, unknown>,
|
|
79
|
-
headers:
|
|
79
|
+
headers: globalThis.Headers,
|
|
80
80
|
status: number,
|
|
81
81
|
runtime: Runtime.Runtime<any>,
|
|
82
82
|
): Response {
|
|
@@ -92,10 +92,27 @@ function streamResponse(
|
|
|
92
92
|
)
|
|
93
93
|
return new Response(Stream.toReadableStreamRuntime(byteStream, runtime), {
|
|
94
94
|
status,
|
|
95
|
-
headers
|
|
95
|
+
headers,
|
|
96
96
|
})
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
function toHeaders(entityHeaders: Entity.Headers, contentType: string): globalThis.Headers {
|
|
100
|
+
const headers = new Headers()
|
|
101
|
+
for (const key in entityHeaders) {
|
|
102
|
+
const value = entityHeaders[key]
|
|
103
|
+
if (value == null) continue
|
|
104
|
+
if (typeof value === "string") {
|
|
105
|
+
headers.set(key, value)
|
|
106
|
+
} else {
|
|
107
|
+
for (const v of value) headers.append(key, v)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (!headers.has("content-type")) {
|
|
111
|
+
headers.set("content-type", contentType)
|
|
112
|
+
}
|
|
113
|
+
return headers
|
|
114
|
+
}
|
|
115
|
+
|
|
99
116
|
function toResponse(
|
|
100
117
|
entity: Entity.Entity<any>,
|
|
101
118
|
format: string | undefined,
|
|
@@ -103,7 +120,7 @@ function toResponse(
|
|
|
103
120
|
): Effect.Effect<Response, ParseResult.ParseError> {
|
|
104
121
|
const contentType = Entity.type(entity)
|
|
105
122
|
const status = entity.status ?? 200
|
|
106
|
-
const headers =
|
|
123
|
+
const headers = toHeaders(entity.headers, contentType)
|
|
107
124
|
|
|
108
125
|
if (StreamExtra.isStream(entity.body)) {
|
|
109
126
|
return Effect.succeed(streamResponse(entity.body, headers, status, runtime))
|
|
@@ -170,64 +187,45 @@ export const toWebHandlerRuntime = <R>(runtime: Runtime.Runtime<R>) => {
|
|
|
170
187
|
const runFork = Runtime.runFork(runtime)
|
|
171
188
|
|
|
172
189
|
return (routes: Iterable<UnboundedRouteWithMethod>): Http.WebHandler => {
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const methodGroups: {
|
|
179
|
-
[method in Http.Method]?: Array<UnboundedRouteWithMethod>
|
|
180
|
-
} = {
|
|
181
|
-
GET: undefined,
|
|
182
|
-
POST: undefined,
|
|
183
|
-
PUT: undefined,
|
|
184
|
-
PATCH: undefined,
|
|
185
|
-
DELETE: undefined,
|
|
186
|
-
HEAD: undefined,
|
|
187
|
-
OPTIONS: undefined,
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
for (const method in grouped) {
|
|
191
|
-
if (method !== "*") {
|
|
192
|
-
methodGroups[method] = grouped[method]
|
|
193
|
-
}
|
|
190
|
+
const allRoutes = Array.from(routes)
|
|
191
|
+
const methods = new Set<string>()
|
|
192
|
+
for (const route of allRoutes) {
|
|
193
|
+
const m = Route.descriptor(route).method?.toUpperCase()
|
|
194
|
+
if (m && m !== "*") methods.add(m)
|
|
194
195
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
methodGroups["HEAD"] = methodGroups["GET"]
|
|
196
|
+
if (methods.has("GET") && !methods.has("HEAD")) {
|
|
197
|
+
methods.add("HEAD")
|
|
198
198
|
}
|
|
199
|
-
|
|
200
|
-
const allowedMethods = Object.keys(methodGroups)
|
|
201
|
-
.filter((m) => methodGroups[m] !== undefined && methodGroups[m]!.length > 0)
|
|
202
|
-
.join(", ")
|
|
199
|
+
const allowedMethods = Array.from(methods).join(", ")
|
|
203
200
|
|
|
204
201
|
return (request) =>
|
|
205
202
|
new Promise((resolve) => {
|
|
206
203
|
const method = request.method.toUpperCase()
|
|
207
204
|
const accept = request.headers.get("accept")
|
|
208
|
-
const
|
|
205
|
+
const matchingRoutes = allRoutes.filter((route) => {
|
|
206
|
+
const m = Route.descriptor(route).method?.toUpperCase()
|
|
207
|
+
return m === "*" || m === method || (method === "HEAD" && m === "GET")
|
|
208
|
+
})
|
|
209
209
|
|
|
210
|
-
if (
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
210
|
+
if (matchingRoutes.length === 0) {
|
|
211
|
+
if (method === "OPTIONS" || methods.size === 0) {
|
|
212
|
+
return resolve(
|
|
213
|
+
new Response(null, {
|
|
214
|
+
status: 204,
|
|
215
|
+
headers: { allow: allowedMethods },
|
|
216
|
+
}),
|
|
217
|
+
)
|
|
218
|
+
}
|
|
218
219
|
|
|
219
|
-
if (methodRoutes.length === 0 && wildcards.length === 0) {
|
|
220
220
|
return resolve(
|
|
221
221
|
respondError({ status: 405, message: "method not allowed" }, { allow: allowedMethods }),
|
|
222
222
|
)
|
|
223
223
|
}
|
|
224
|
-
|
|
225
|
-
const allRoutes = [...wildcards, ...methodRoutes]
|
|
226
|
-
const selectedFormat = determineSelectedFormat(accept, allRoutes)
|
|
224
|
+
const selectedFormat = determineSelectedFormat(accept, matchingRoutes)
|
|
227
225
|
|
|
228
226
|
const specificFormats = new Set<string>()
|
|
229
227
|
let hasWildcardFormatRoutes = false
|
|
230
|
-
for (const r of
|
|
228
|
+
for (const r of matchingRoutes) {
|
|
231
229
|
const format = Route.descriptor(r).format
|
|
232
230
|
if (format === "*") hasWildcardFormatRoutes = true
|
|
233
231
|
else if (format) specificFormats.add(format)
|
|
@@ -249,13 +247,13 @@ export const toWebHandlerRuntime = <R>(runtime: Runtime.Runtime<R>) => {
|
|
|
249
247
|
currentContext = passedContext
|
|
250
248
|
}
|
|
251
249
|
|
|
252
|
-
if (index >=
|
|
250
|
+
if (index >= matchingRoutes.length) {
|
|
253
251
|
return Effect.succeed(
|
|
254
252
|
Entity.make({ status: 404, message: "route not found" }, { status: 404 }),
|
|
255
253
|
)
|
|
256
254
|
}
|
|
257
255
|
|
|
258
|
-
const route =
|
|
256
|
+
const route = matchingRoutes[index++]
|
|
259
257
|
const descriptor = Route.descriptor(route)
|
|
260
258
|
const format = descriptor.format
|
|
261
259
|
const handler = route.handler as unknown as Handler
|
package/src/RouteMount.ts
CHANGED
|
@@ -10,7 +10,7 @@ const RouteSetTypeId = "~effect-start/RouteSet" as const
|
|
|
10
10
|
// oxlint-disable-next-line import/first, typescript/consistent-type-imports -- typeof import() is not an import statement
|
|
11
11
|
type Module = typeof import("./RouteMount.ts")
|
|
12
12
|
|
|
13
|
-
export type Self = RouteMount.Builder | Module
|
|
13
|
+
export type Self = RouteMount.Builder<any, any> | Omit<RouteMount.Builder<any, any>, "use"> | Module
|
|
14
14
|
|
|
15
15
|
export const use = makeMethodDescriber("*")
|
|
16
16
|
export const get = makeMethodDescriber("GET")
|
|
@@ -21,28 +21,6 @@ export const patch = makeMethodDescriber("PATCH")
|
|
|
21
21
|
export const head = makeMethodDescriber("HEAD")
|
|
22
22
|
export const options = makeMethodDescriber("OPTIONS")
|
|
23
23
|
|
|
24
|
-
export const add: RouteMount.Add = function (
|
|
25
|
-
this: Self,
|
|
26
|
-
path: string,
|
|
27
|
-
routes: Route.RouteSet.Any | ((self: RouteMount.Builder<{}, []>) => Route.RouteSet.Any),
|
|
28
|
-
) {
|
|
29
|
-
const baseItems = Route.isRouteSet(this) ? Route.items(this) : ([] as const)
|
|
30
|
-
|
|
31
|
-
const routeSet = typeof routes === "function" ? routes(make<{}, []>([])) : routes
|
|
32
|
-
const routeItems = Route.items(routeSet)
|
|
33
|
-
const newItems = routeItems.map((item) => {
|
|
34
|
-
const itemDescriptor = Route.descriptor(item) as { path?: string }
|
|
35
|
-
const concatenatedPath =
|
|
36
|
-
typeof itemDescriptor?.path === "string" ? path + itemDescriptor.path : path
|
|
37
|
-
const newDescriptor = { ...itemDescriptor, path: concatenatedPath }
|
|
38
|
-
return Route.isRoute(item)
|
|
39
|
-
? Route.make(item.handler as Route.Route.Handler<any, any, any, any>, newDescriptor)
|
|
40
|
-
: Route.set(Route.items(item), newDescriptor)
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
return make([...baseItems, ...newItems] as any)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
24
|
const Proto = Object.assign(Object.create(null), {
|
|
47
25
|
[RouteSetTypeId]: RouteSetTypeId,
|
|
48
26
|
*[Symbol.iterator](this: Route.RouteSet.Any) {
|
|
@@ -56,7 +34,6 @@ const Proto = Object.assign(Object.create(null), {
|
|
|
56
34
|
patch,
|
|
57
35
|
head,
|
|
58
36
|
options,
|
|
59
|
-
add,
|
|
60
37
|
})
|
|
61
38
|
|
|
62
39
|
function make<
|
|
@@ -116,7 +93,19 @@ export namespace RouteMount {
|
|
|
116
93
|
export type MountSet = Route.RouteSet.RouteSet<{ method: Method }, {}, Route.Route.Tuple>
|
|
117
94
|
|
|
118
95
|
export interface Builder<D extends {} = {}, I extends Route.Route.Tuple = []>
|
|
119
|
-
extends Route.RouteSet.RouteSet<D, {}, I
|
|
96
|
+
extends Route.RouteSet.RouteSet<D, {}, I> {
|
|
97
|
+
use: Describer<"*">
|
|
98
|
+
get: Describer<"GET">
|
|
99
|
+
post: Describer<"POST">
|
|
100
|
+
put: Describer<"PUT">
|
|
101
|
+
del: Describer<"DELETE">
|
|
102
|
+
patch: Describer<"PATCH">
|
|
103
|
+
head: Describer<"HEAD">
|
|
104
|
+
options: Describer<"OPTIONS">
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export type BuilderAfter<M extends Method, I extends Route.Route.Tuple> =
|
|
108
|
+
M extends "*" ? Builder<{}, I> : Omit<Builder<{}, I>, "use">
|
|
120
109
|
|
|
121
110
|
export type EmptySet<M extends Method, B = {}> = Route.RouteSet.RouteSet<{ method: M }, B, []>
|
|
122
111
|
|
|
@@ -142,37 +131,6 @@ export namespace RouteMount {
|
|
|
142
131
|
}[number]
|
|
143
132
|
>
|
|
144
133
|
|
|
145
|
-
type PrefixPathItem<Prefix extends string, T> =
|
|
146
|
-
T extends Route.Route.Route<infer D, infer B, infer A, infer E, infer R>
|
|
147
|
-
? D extends { path: infer P extends string }
|
|
148
|
-
? Route.Route.Route<Omit<D, "path"> & { path: `${Prefix}${P}` }, B, A, E, R>
|
|
149
|
-
: Route.Route.Route<D & { path: Prefix }, B, A, E, R>
|
|
150
|
-
: T
|
|
151
|
-
|
|
152
|
-
export type PrefixPath<Prefix extends string, I extends Route.Route.Tuple> = {
|
|
153
|
-
[K in keyof I]: PrefixPathItem<Prefix, I[K]>
|
|
154
|
-
} extends infer R extends Route.Route.Tuple
|
|
155
|
-
? R
|
|
156
|
-
: never
|
|
157
|
-
|
|
158
|
-
export interface Add {
|
|
159
|
-
<S extends Self, P extends string, R extends Route.RouteSet.Any>(
|
|
160
|
-
this: S,
|
|
161
|
-
path: P,
|
|
162
|
-
routes: R,
|
|
163
|
-
): Builder<{}, [...Items<S>, ...PrefixPath<P, Route.RouteSet.Items<R>>]>
|
|
164
|
-
|
|
165
|
-
<S extends Self, P extends string, R extends Route.RouteSet.Any>(
|
|
166
|
-
this: S,
|
|
167
|
-
path: P,
|
|
168
|
-
/**
|
|
169
|
-
* Callback form provides a builder seeded with higher-level bindings so
|
|
170
|
-
* nested routes can type-infer outer context when mounting.
|
|
171
|
-
*/
|
|
172
|
-
routes: (self: Builder<{}, []>) => R,
|
|
173
|
-
): Builder<{}, [...Items<S>, ...PrefixPath<P, Route.RouteSet.Items<R>>]>
|
|
174
|
-
}
|
|
175
|
-
|
|
176
134
|
// Flatten items: merge method into descriptor and accumulate bindings through the chain
|
|
177
135
|
// `request` is omitted from bindings since it's implicit (always available)
|
|
178
136
|
export type FlattenItems<M extends Method, B, I extends Route.Route.Tuple> = I extends [
|
|
@@ -195,13 +153,13 @@ export namespace RouteMount {
|
|
|
195
153
|
<S extends Self, A extends Route.RouteSet.Any>(
|
|
196
154
|
this: S,
|
|
197
155
|
ab: (a: EmptySet<M, BuilderBindings<S>>) => A,
|
|
198
|
-
):
|
|
156
|
+
): BuilderAfter<M, [...Items<S>, ...FlattenItems<M, BuilderBindings<S>, Route.RouteSet.Items<A>>]>
|
|
199
157
|
|
|
200
158
|
<S extends Self, A extends Route.RouteSet.Any, B extends Route.RouteSet.Any>(
|
|
201
159
|
this: S,
|
|
202
160
|
ab: (a: EmptySet<M, BuilderBindings<S>>) => A,
|
|
203
161
|
bc: (b: A) => B,
|
|
204
|
-
):
|
|
162
|
+
): BuilderAfter<M, [...Items<S>, ...FlattenItems<M, BuilderBindings<S>, Route.RouteSet.Items<B>>]>
|
|
205
163
|
|
|
206
164
|
<
|
|
207
165
|
S extends Self,
|
|
@@ -213,7 +171,7 @@ export namespace RouteMount {
|
|
|
213
171
|
ab: (a: EmptySet<M, BuilderBindings<S>>) => A,
|
|
214
172
|
bc: (b: A) => B,
|
|
215
173
|
cd: (c: B) => C,
|
|
216
|
-
):
|
|
174
|
+
): BuilderAfter<M, [...Items<S>, ...FlattenItems<M, BuilderBindings<S>, Route.RouteSet.Items<C>>]>
|
|
217
175
|
|
|
218
176
|
<
|
|
219
177
|
S extends Self,
|
|
@@ -227,7 +185,7 @@ export namespace RouteMount {
|
|
|
227
185
|
bc: (b: A) => B,
|
|
228
186
|
cd: (c: B) => C,
|
|
229
187
|
de: (d: C) => D,
|
|
230
|
-
):
|
|
188
|
+
): BuilderAfter<M, [...Items<S>, ...FlattenItems<M, BuilderBindings<S>, Route.RouteSet.Items<D>>]>
|
|
231
189
|
|
|
232
190
|
<
|
|
233
191
|
S extends Self,
|
|
@@ -243,7 +201,7 @@ export namespace RouteMount {
|
|
|
243
201
|
cd: (c: B) => C,
|
|
244
202
|
de: (d: C) => D,
|
|
245
203
|
ef: (e: D) => E,
|
|
246
|
-
):
|
|
204
|
+
): BuilderAfter<M, [...Items<S>, ...FlattenItems<M, BuilderBindings<S>, Route.RouteSet.Items<E>>]>
|
|
247
205
|
|
|
248
206
|
<
|
|
249
207
|
S extends Self,
|
|
@@ -261,7 +219,7 @@ export namespace RouteMount {
|
|
|
261
219
|
de: (d: C) => D,
|
|
262
220
|
ef: (e: D) => E,
|
|
263
221
|
fg: (f: E) => F,
|
|
264
|
-
):
|
|
222
|
+
): BuilderAfter<M, [...Items<S>, ...FlattenItems<M, BuilderBindings<S>, Route.RouteSet.Items<F>>]>
|
|
265
223
|
|
|
266
224
|
<
|
|
267
225
|
S extends Self,
|
|
@@ -281,7 +239,7 @@ export namespace RouteMount {
|
|
|
281
239
|
ef: (e: D) => E,
|
|
282
240
|
fg: (f: E) => F,
|
|
283
241
|
gh: (g: F) => G,
|
|
284
|
-
):
|
|
242
|
+
): BuilderAfter<M, [...Items<S>, ...FlattenItems<M, BuilderBindings<S>, Route.RouteSet.Items<G>>]>
|
|
285
243
|
|
|
286
244
|
<
|
|
287
245
|
S extends Self,
|
|
@@ -303,7 +261,7 @@ export namespace RouteMount {
|
|
|
303
261
|
fg: (f: E) => F,
|
|
304
262
|
gh: (g: F) => G,
|
|
305
263
|
hi: (h: G) => H,
|
|
306
|
-
):
|
|
264
|
+
): BuilderAfter<M, [...Items<S>, ...FlattenItems<M, BuilderBindings<S>, Route.RouteSet.Items<H>>]>
|
|
307
265
|
|
|
308
266
|
<
|
|
309
267
|
S extends Self,
|
|
@@ -327,6 +285,6 @@ export namespace RouteMount {
|
|
|
327
285
|
gh: (g: F) => G,
|
|
328
286
|
hi: (h: G) => H,
|
|
329
287
|
ij: (i: H) => I,
|
|
330
|
-
):
|
|
288
|
+
): BuilderAfter<M, [...Items<S>, ...FlattenItems<M, BuilderBindings<S>, Route.RouteSet.Items<I>>]>
|
|
331
289
|
}
|
|
332
290
|
}
|
package/src/Start.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type * as FileSystem from "./FileSystem.ts"
|
|
2
|
+
import * as Cause from "effect/Cause"
|
|
2
3
|
import * as Context from "effect/Context"
|
|
3
4
|
import * as Deferred from "effect/Deferred"
|
|
4
5
|
import * as Effect from "effect/Effect"
|
|
@@ -140,6 +141,9 @@ export function build<const Layers extends readonly [Layer.Layer.Any, ...Array<L
|
|
|
140
141
|
map.delete(layer)
|
|
141
142
|
return map
|
|
142
143
|
})
|
|
144
|
+
if (Cause.isDie(exit.cause) || Cause.isInterruptedOnly(exit.cause)) {
|
|
145
|
+
return yield* exit
|
|
146
|
+
}
|
|
143
147
|
}
|
|
144
148
|
}
|
|
145
149
|
}
|
package/src/StaticFiles.ts
CHANGED
|
@@ -41,17 +41,14 @@ export const make = (directory: string) =>
|
|
|
41
41
|
return emptyNotFound
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
const headers: Entity.Headers = {
|
|
45
|
-
"content-length": String(info.size),
|
|
46
|
-
"content-type": Mime.fromPath(relativePath),
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (Option.isSome(info.mtime)) {
|
|
50
|
-
headers["last-modified"] = info.mtime.value.toUTCString()
|
|
51
|
-
}
|
|
52
|
-
|
|
53
44
|
const bytes = yield* fs.readFile(absolutePath)
|
|
54
|
-
return Entity.make(bytes, {
|
|
45
|
+
return Entity.make(bytes, {
|
|
46
|
+
headers: {
|
|
47
|
+
"content-length": String(info.size),
|
|
48
|
+
"content-type": Mime.fromPath(relativePath),
|
|
49
|
+
...(Option.isSome(info.mtime) ? { "last-modified": info.mtime.value.toUTCString() } : {}),
|
|
50
|
+
},
|
|
51
|
+
})
|
|
55
52
|
}),
|
|
56
53
|
)
|
|
57
54
|
|