effect-start 0.13.1 → 0.14.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 +9 -9
- package/src/Bundle.ts +0 -35
- package/src/BundleHttp.test.ts +4 -6
- package/src/BundleHttp.ts +2 -1
- package/src/Effect_HttpRouter.test.ts +2 -3
- package/src/FileHttpRouter.test.ts +2 -2
- package/src/FileRouterCodegen.test.ts +1 -1
- package/src/FileRouter_files.test.ts +1 -1
- package/src/HttpAppExtra.test.ts +1 -1
- package/src/Start.ts +0 -34
- package/src/StartApp.ts +20 -16
- package/src/bun/BunBundle.test.ts +1 -1
- package/src/bun/BunBundle_imports.test.ts +2 -2
- package/src/bun/BunRoute_bundles.test.ts +1 -1
- package/src/{SseHttpResponse.ts → experimental/SseHttpResponse.ts} +2 -1
- package/src/experimental/index.ts +2 -0
- package/src/index.ts +2 -20
- package/src/middlewares/index.ts +1 -0
- package/src/{TestHttpClient.test.ts → testing/TestHttpClient.test.ts} +1 -1
- package/src/{TestHttpClient.ts → testing/TestHttpClient.ts} +0 -1
- package/src/testing/index.ts +3 -0
- package/src/x/tailwind/TailwindPlugin.ts +23 -17
- package/src/JsModule.test.ts +0 -14
- package/src/JsModule.ts +0 -116
- package/src/PublicDirectory.test.ts +0 -280
- package/src/PublicDirectory.ts +0 -108
- package/src/StartHttp.ts +0 -42
- /package/src/{EncryptedCookies.test.ts → experimental/EncryptedCookies.test.ts} +0 -0
- /package/src/{EncryptedCookies.ts → experimental/EncryptedCookies.ts} +0 -0
- /package/src/{TestLogger.test.ts → testing/TestLogger.test.ts} +0 -0
- /package/src/{TestLogger.ts → testing/TestLogger.ts} +0 -0
- /package/src/{testing.ts → testing/utils.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "effect-start",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./src/index.ts",
|
|
8
|
+
"./StartApp": "./src/StartApp.ts",
|
|
8
9
|
"./FileRouter": "./src/FileRouter.ts",
|
|
9
10
|
"./Route": "./src/Route.ts",
|
|
10
|
-
"./EncryptedCookies": "./src/EncryptedCookies.ts",
|
|
11
11
|
"./bun": "./src/bun/index.ts",
|
|
12
12
|
"./client": "./src/client/index.ts",
|
|
13
|
-
"./
|
|
14
|
-
"./package.json": "./package.json",
|
|
15
|
-
"./jsx-runtime": "./src/jsx-runtime.ts",
|
|
16
|
-
"./jsx-dev-runtime": "./src/jsx-runtime.ts",
|
|
17
|
-
"./hyper": "./src/hyper/index.ts",
|
|
13
|
+
"./testing": "./src/testing/index.ts",
|
|
18
14
|
"./x/*": "./src/x/*/index.ts",
|
|
19
15
|
"./x/tailwind/plugin": "./src/x/tailwind/plugin.ts",
|
|
20
|
-
"./middlewares
|
|
21
|
-
"./
|
|
16
|
+
"./middlewares": "./src/middlewares/index.ts",
|
|
17
|
+
"./experimental": "./src/experimental/index.ts",
|
|
18
|
+
"./assets.d.ts": "./src/assets.d.ts",
|
|
19
|
+
"./jsx-runtime": "./src/jsx-runtime.ts",
|
|
20
|
+
"./jsx-dev-runtime": "./src/jsx-runtime.ts",
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
24
|
"format": "bunx dprint fmt",
|
package/src/Bundle.ts
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
PubSub,
|
|
7
7
|
} from "effect"
|
|
8
8
|
import * as Schema from "effect/Schema"
|
|
9
|
-
import { importBlob } from "./JsModule.ts"
|
|
10
9
|
|
|
11
10
|
export const BundleEntrypointMetaKey: unique symbol = Symbol.for(
|
|
12
11
|
"effect-start/BundleEntrypointMetaKey",
|
|
@@ -131,37 +130,3 @@ export type Tag = Context.Tag<
|
|
|
131
130
|
|
|
132
131
|
export class ClientBundle extends Tag("ClientBundle")<ClientBundle>() {}
|
|
133
132
|
export class ServerBundle extends Tag("ServerBundle")<ServerBundle>() {}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Lodas a bundle as a javascript module.
|
|
137
|
-
* Bundle must have only one entrypoint.
|
|
138
|
-
*/
|
|
139
|
-
export function load<M>(
|
|
140
|
-
bundle: Effect.Effect<BundleContext, BundleError>,
|
|
141
|
-
): Effect.Effect<M, BundleError> {
|
|
142
|
-
return Effect.gen(function*() {
|
|
143
|
-
const context = yield* bundle
|
|
144
|
-
const [artifact, ...rest] = Object.values(context.entrypoints)
|
|
145
|
-
|
|
146
|
-
if (rest.length > 0) {
|
|
147
|
-
return yield* Effect.fail(
|
|
148
|
-
new BundleError({
|
|
149
|
-
message: "Multiple entrypoints are not supported in load()",
|
|
150
|
-
}),
|
|
151
|
-
)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return yield* Effect.tryPromise({
|
|
155
|
-
try: () => {
|
|
156
|
-
const blob = context.getArtifact(artifact)
|
|
157
|
-
|
|
158
|
-
return importBlob<M>(blob!)
|
|
159
|
-
},
|
|
160
|
-
catch: (e) =>
|
|
161
|
-
new BundleError({
|
|
162
|
-
message: "Failed to load entrypoint",
|
|
163
|
-
cause: e,
|
|
164
|
-
}),
|
|
165
|
-
})
|
|
166
|
-
})
|
|
167
|
-
}
|
package/src/BundleHttp.test.ts
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import * as HttpRouter from "@effect/platform/HttpRouter"
|
|
2
2
|
import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
|
|
3
3
|
import * as t from "bun:test"
|
|
4
|
-
import {
|
|
5
|
-
Bundle,
|
|
6
|
-
BundleHttp,
|
|
7
|
-
effectFn,
|
|
8
|
-
TestHttpClient,
|
|
9
|
-
} from "effect-start"
|
|
10
4
|
import * as Effect from "effect/Effect"
|
|
11
5
|
import * as Layer from "effect/Layer"
|
|
12
6
|
import IndexHtml from "../static/react-dashboard.html" with { type: "file" }
|
|
13
7
|
import * as BunBundle from "./bun/BunBundle.ts"
|
|
8
|
+
import * as Bundle from "./Bundle.ts"
|
|
9
|
+
import * as BundleHttp from "./BundleHttp.ts"
|
|
10
|
+
import { effectFn } from "./testing"
|
|
11
|
+
import * as TestHttpClient from "./testing/TestHttpClient.ts"
|
|
14
12
|
|
|
15
13
|
const effect = effectFn(
|
|
16
14
|
Layer.effect(
|
package/src/BundleHttp.ts
CHANGED
|
@@ -13,7 +13,8 @@ import * as Stream from "effect/Stream"
|
|
|
13
13
|
import * as NPath from "node:path"
|
|
14
14
|
import * as NUrl from "node:url"
|
|
15
15
|
import * as Bundle from "./Bundle.ts"
|
|
16
|
-
import * as SseHttpResponse from "./SseHttpResponse.ts"
|
|
16
|
+
import * as SseHttpResponse from "./experimental/SseHttpResponse.ts"
|
|
17
|
+
|
|
17
18
|
|
|
18
19
|
const DefaultBundleEndpoint = "/_bundle"
|
|
19
20
|
|
|
@@ -7,9 +7,8 @@
|
|
|
7
7
|
import * as HttpRouter from "@effect/platform/HttpRouter"
|
|
8
8
|
import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
|
|
9
9
|
import * as t from "bun:test"
|
|
10
|
-
import
|
|
11
|
-
import * as TestHttpClient from "
|
|
12
|
-
import { effectFn } from "../src/testing.ts"
|
|
10
|
+
import { effectFn } from "../src/testing"
|
|
11
|
+
import * as TestHttpClient from "./testing/TestHttpClient.ts"
|
|
13
12
|
|
|
14
13
|
const effect = effectFn()
|
|
15
14
|
|
|
@@ -7,8 +7,8 @@ import * as Effect from "effect/Effect"
|
|
|
7
7
|
import * as FileHttpRouter from "./FileHttpRouter.ts"
|
|
8
8
|
import * as FileRouter from "./FileRouter.ts"
|
|
9
9
|
import * as Route from "./Route.ts"
|
|
10
|
-
import
|
|
11
|
-
import
|
|
10
|
+
import { effectFn } from "./testing"
|
|
11
|
+
import * as TestHttpClient from "./testing/TestHttpClient.ts"
|
|
12
12
|
|
|
13
13
|
class CustomError extends Data.TaggedError("CustomError") {}
|
|
14
14
|
|
|
@@ -12,7 +12,7 @@ import * as FileRouterCodegen from "./FileRouterCodegen.ts"
|
|
|
12
12
|
import * as NodeFileSystem from "./NodeFileSystem.ts"
|
|
13
13
|
import * as Route from "./Route.ts"
|
|
14
14
|
import * as SchemaExtra from "./SchemaExtra.ts"
|
|
15
|
-
import * as TestLogger from "./TestLogger.ts"
|
|
15
|
+
import * as TestLogger from "./testing/TestLogger.ts"
|
|
16
16
|
|
|
17
17
|
function createTempDirWithFiles(
|
|
18
18
|
files: Record<string, string>,
|
|
@@ -2,7 +2,7 @@ import * as t from "bun:test"
|
|
|
2
2
|
import { MemoryFileSystem } from "effect-memfs"
|
|
3
3
|
import * as Effect from "effect/Effect"
|
|
4
4
|
import * as FileRouter from "./FileRouter.ts"
|
|
5
|
-
import { effectFn } from "./testing
|
|
5
|
+
import { effectFn } from "./testing"
|
|
6
6
|
|
|
7
7
|
const Files = {
|
|
8
8
|
"/routes/about/layer.tsx": "",
|
package/src/HttpAppExtra.test.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "effect"
|
|
8
8
|
import * as Cause from "effect/Cause"
|
|
9
9
|
import * as HttpAppExtra from "./HttpAppExtra.ts"
|
|
10
|
-
import { effectFn } from "./testing
|
|
10
|
+
import { effectFn } from "./testing"
|
|
11
11
|
|
|
12
12
|
const mockRequest = HttpServerRequest.HttpServerRequest.of({
|
|
13
13
|
url: "http://localhost:3000/test",
|
package/src/Start.ts
CHANGED
|
@@ -3,48 +3,14 @@ import * as FileSystem from "@effect/platform/FileSystem"
|
|
|
3
3
|
import * as HttpClient from "@effect/platform/HttpClient"
|
|
4
4
|
import * as HttpRouter from "@effect/platform/HttpRouter"
|
|
5
5
|
import * as HttpServer from "@effect/platform/HttpServer"
|
|
6
|
-
import * as Config from "effect/Config"
|
|
7
6
|
import * as Effect from "effect/Effect"
|
|
8
7
|
import * as Function from "effect/Function"
|
|
9
8
|
import * as Layer from "effect/Layer"
|
|
10
|
-
import * as Option from "effect/Option"
|
|
11
|
-
import * as BunBundle from "./bun/BunBundle.ts"
|
|
12
9
|
import * as BunHttpServer from "./bun/BunHttpServer.ts"
|
|
13
|
-
import * as BunRoute from "./bun/BunRoute.ts"
|
|
14
10
|
import * as BunRuntime from "./bun/BunRuntime.ts"
|
|
15
|
-
import * as Bundle from "./Bundle.ts"
|
|
16
|
-
import * as BundleHttp from "./BundleHttp.ts"
|
|
17
|
-
import * as HttpAppExtra from "./HttpAppExtra.ts"
|
|
18
11
|
import * as NodeFileSystem from "./NodeFileSystem.ts"
|
|
19
|
-
import * as Router from "./Router.ts"
|
|
20
12
|
import * as StartApp from "./StartApp.ts"
|
|
21
13
|
|
|
22
|
-
export function bundleClient(config: BunBundle.BuildOptions | string) {
|
|
23
|
-
const clientLayer = Layer.effect(
|
|
24
|
-
Bundle.ClientBundle,
|
|
25
|
-
Function.pipe(
|
|
26
|
-
BunBundle.buildClient(config),
|
|
27
|
-
Bundle.handleBundleErrorSilently,
|
|
28
|
-
),
|
|
29
|
-
)
|
|
30
|
-
const assetsLayer = Layer.effectDiscard(Effect.gen(function*() {
|
|
31
|
-
const router = yield* HttpRouter.Default
|
|
32
|
-
const app = BundleHttp.toHttpApp(Bundle.ClientBundle)
|
|
33
|
-
|
|
34
|
-
yield* router.mountApp(
|
|
35
|
-
"/_bundle",
|
|
36
|
-
// we need to use as any here because HttpRouter.Default
|
|
37
|
-
// only accepts default services.
|
|
38
|
-
app as any,
|
|
39
|
-
)
|
|
40
|
-
}))
|
|
41
|
-
|
|
42
|
-
return Layer.mergeAll(
|
|
43
|
-
clientLayer,
|
|
44
|
-
assetsLayer,
|
|
45
|
-
)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
14
|
export function layer<
|
|
49
15
|
Layers extends [
|
|
50
16
|
Layer.Layer<never, any, any>,
|
package/src/StartApp.ts
CHANGED
|
@@ -3,23 +3,22 @@ import * as Context from "effect/Context"
|
|
|
3
3
|
import * as Effect from "effect/Effect"
|
|
4
4
|
import * as Function from "effect/Function"
|
|
5
5
|
import * as Layer from "effect/Layer"
|
|
6
|
+
import * as PubSub from "effect/PubSub"
|
|
6
7
|
import * as Ref from "effect/Ref"
|
|
7
8
|
|
|
8
|
-
type NewType = HttpApp.Default<never, never>
|
|
9
|
-
|
|
10
9
|
type StartMiddleware = <E, R>(
|
|
11
10
|
self: HttpApp.Default<E, R>,
|
|
12
|
-
) =>
|
|
11
|
+
) => HttpApp.Default<never, never>
|
|
13
12
|
|
|
14
13
|
export class StartApp extends Context.Tag("effect-start/StartApp")<
|
|
15
14
|
StartApp,
|
|
16
15
|
{
|
|
17
16
|
readonly env: "development" | "production" | string
|
|
18
|
-
readonly relativeUrlRoot?: string
|
|
19
17
|
readonly addMiddleware: (
|
|
20
18
|
middleware: StartMiddleware,
|
|
21
19
|
) => Effect.Effect<void>
|
|
22
20
|
readonly middleware: Ref.Ref<StartMiddleware>
|
|
21
|
+
readonly events: PubSub.PubSub<any>
|
|
23
22
|
}
|
|
24
23
|
>() {
|
|
25
24
|
}
|
|
@@ -27,17 +26,22 @@ export class StartApp extends Context.Tag("effect-start/StartApp")<
|
|
|
27
26
|
export function layer(options?: {
|
|
28
27
|
env?: string
|
|
29
28
|
}) {
|
|
30
|
-
return Layer.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
return Layer.effect(
|
|
30
|
+
StartApp,
|
|
31
|
+
Effect.gen(function*() {
|
|
32
|
+
const env = options?.env ?? process.env.NODE_ENV ?? "development"
|
|
33
|
+
const middleware = yield* Ref.make(
|
|
34
|
+
Function.identity as StartMiddleware,
|
|
35
|
+
)
|
|
36
|
+
const events = yield* PubSub.unbounded()
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
return StartApp.of({
|
|
39
|
+
env,
|
|
40
|
+
middleware,
|
|
41
|
+
addMiddleware: (f) =>
|
|
42
|
+
Ref.update(middleware, (prev) => (app) => f(prev(app))),
|
|
43
|
+
events,
|
|
44
|
+
})
|
|
45
|
+
}),
|
|
46
|
+
)
|
|
43
47
|
}
|
|
@@ -7,7 +7,7 @@ import * as NOS from "node:os"
|
|
|
7
7
|
import * as NPath from "node:path"
|
|
8
8
|
import * as Bundle from "../Bundle.ts"
|
|
9
9
|
import * as BundleHttp from "../BundleHttp.ts"
|
|
10
|
-
import * as TestHttpClient from "../TestHttpClient.ts"
|
|
10
|
+
import * as TestHttpClient from "../testing/TestHttpClient.ts"
|
|
11
11
|
import * as BunBundle from "./BunBundle.ts"
|
|
12
12
|
|
|
13
13
|
t.describe("BunBundle manifest structure", () => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as t from "bun:test"
|
|
2
|
-
import { effectFn } from "../testing
|
|
2
|
+
import { effectFn } from "../testing"
|
|
3
3
|
import * as BunBundle from "./BunBundle.ts"
|
|
4
4
|
import * as BunImportTrackerPlugin from "./BunImportTrackerPlugin.ts"
|
|
5
5
|
|
|
@@ -35,7 +35,7 @@ t.it("imports", () =>
|
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
kind: "import-statement",
|
|
38
|
-
path: "src/testing
|
|
38
|
+
path: "src/testing",
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
kind: "import-statement",
|
|
@@ -2,7 +2,7 @@ import * as t from "bun:test"
|
|
|
2
2
|
import * as Effect from "effect/Effect"
|
|
3
3
|
import * as Route from "../Route.ts"
|
|
4
4
|
import * as Router from "../Router.ts"
|
|
5
|
-
import * as TestHttpClient from "../TestHttpClient.ts"
|
|
5
|
+
import * as TestHttpClient from "../testing/TestHttpClient.ts"
|
|
6
6
|
import * as BunHttpServer from "./BunHttpServer.ts"
|
|
7
7
|
import * as BunRoute from "./BunRoute.ts"
|
|
8
8
|
|
|
@@ -4,7 +4,8 @@ import * as Effect from "effect/Effect"
|
|
|
4
4
|
import * as Function from "effect/Function"
|
|
5
5
|
import * as Schedule from "effect/Schedule"
|
|
6
6
|
import * as Stream from "effect/Stream"
|
|
7
|
-
import * as StreamExtra from "
|
|
7
|
+
import * as StreamExtra from "../StreamExtra.ts"
|
|
8
|
+
|
|
8
9
|
|
|
9
10
|
const DefaultHeartbeatInterval = Duration.seconds(5)
|
|
10
11
|
|
package/src/index.ts
CHANGED
|
@@ -1,24 +1,6 @@
|
|
|
1
|
-
export * as
|
|
2
|
-
export * as BundleHttp from "./BundleHttp.ts"
|
|
3
|
-
|
|
4
|
-
export * as StartHttp from "./StartHttp.ts"
|
|
5
|
-
|
|
6
|
-
export * as HttpAppExtra from "./HttpAppExtra.ts"
|
|
7
|
-
|
|
8
|
-
export * as PublicDirectory from "./PublicDirectory.ts"
|
|
9
|
-
|
|
10
|
-
export * as TestHttpClient from "./TestHttpClient.ts"
|
|
11
|
-
export {
|
|
12
|
-
effectFn,
|
|
13
|
-
} from "./testing.ts"
|
|
1
|
+
export * as Start from "./Start.ts"
|
|
14
2
|
|
|
15
|
-
export * as FileHttpRouter from "./FileHttpRouter.ts"
|
|
16
3
|
export * as FileRouter from "./FileRouter.ts"
|
|
17
|
-
|
|
18
4
|
export * as Route from "./Route.ts"
|
|
19
|
-
export * as Router from "./Router.ts"
|
|
20
5
|
|
|
21
|
-
export * as
|
|
22
|
-
|
|
23
|
-
export * as Hyper from "./Hyper.ts"
|
|
24
|
-
export * as HyperHtml from "./HyperHtml.ts"
|
|
6
|
+
export * as Bundle from "./Bundle.ts"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * as BasicAuthMiddleware from "./BasicAuthMiddleware.ts"
|
|
@@ -2,8 +2,8 @@ import * as HttpServerRequest from "@effect/platform/HttpServerRequest"
|
|
|
2
2
|
import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
|
|
3
3
|
import * as t from "bun:test"
|
|
4
4
|
import * as Effect from "effect/Effect"
|
|
5
|
+
import { effectFn } from "./index.ts"
|
|
5
6
|
import * as TestHttpClient from "./TestHttpClient.ts"
|
|
6
|
-
import { effectFn } from "./testing.ts"
|
|
7
7
|
|
|
8
8
|
const App = Effect.gen(function*() {
|
|
9
9
|
const req = yield* HttpServerRequest.HttpServerRequest
|
|
@@ -10,7 +10,6 @@ import * as UrlParams from "@effect/platform/UrlParams"
|
|
|
10
10
|
import * as Effect from "effect/Effect"
|
|
11
11
|
import * as Either from "effect/Either"
|
|
12
12
|
import * as Function from "effect/Function"
|
|
13
|
-
import * as Scope from "effect/Scope"
|
|
14
13
|
import * as Stream from "effect/Stream"
|
|
15
14
|
|
|
16
15
|
const WebHeaders = globalThis.Headers
|
|
@@ -44,21 +44,22 @@ export const make = (opts?: {
|
|
|
44
44
|
// (imported path) -> (importer paths)
|
|
45
45
|
const importDescendants = new Map<string, Set<string>>()
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
const prepopulateCandidates = opts?.scanPath
|
|
48
|
+
? async () => {
|
|
49
|
+
const candidates = await scanFiles(opts.scanPath!)
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
}
|
|
51
|
+
scannedCandidates.clear()
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
53
|
+
candidates.forEach(candidate => scannedCandidates.add(candidate))
|
|
54
|
+
}
|
|
55
|
+
: null
|
|
56
|
+
|
|
57
|
+
// Track import relationships when dynamically scanning
|
|
58
|
+
// from tailwind entrypoints.
|
|
59
|
+
// As of Bun 1.3 this pathway break for Bun Full-Stack server.
|
|
60
|
+
// Better to pass scanPath explicitly.
|
|
61
|
+
// @see https://github.com/oven-sh/bun/issues/20877
|
|
62
|
+
if (!prepopulateCandidates) {
|
|
62
63
|
builder.onResolve({
|
|
63
64
|
filter: /.*/,
|
|
64
65
|
}, (args) => {
|
|
@@ -130,14 +131,15 @@ export const make = (opts?: {
|
|
|
130
131
|
onDependency: (path) => {},
|
|
131
132
|
})
|
|
132
133
|
|
|
134
|
+
await prepopulateCandidates?.()
|
|
135
|
+
|
|
133
136
|
// wait for other files to be loaded so we can collect class name candidates
|
|
134
137
|
await args.defer()
|
|
135
138
|
|
|
136
|
-
const candidates = new Set<string>()
|
|
139
|
+
const candidates = new Set<string>(scannedCandidates)
|
|
137
140
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
{
|
|
141
|
+
// when we scan a path, we don't need to track candidate tree
|
|
142
|
+
if (!prepopulateCandidates) {
|
|
141
143
|
const pendingModules = [
|
|
142
144
|
// get class name candidates from all modules that import this one
|
|
143
145
|
...(importAncestors.get(args.path) ?? []),
|
|
@@ -295,6 +297,10 @@ async function scanFiles(dir: string): Promise<Set<string>> {
|
|
|
295
297
|
absolute: true,
|
|
296
298
|
})
|
|
297
299
|
) {
|
|
300
|
+
if (filePath.includes("/node_modules/")) {
|
|
301
|
+
continue
|
|
302
|
+
}
|
|
303
|
+
|
|
298
304
|
const contents = await Bun.file(filePath).text()
|
|
299
305
|
const classNames = extractClassNames(contents)
|
|
300
306
|
|
package/src/JsModule.test.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import * as t from "bun:test"
|
|
2
|
-
import * as JsModule from "./JsModule.ts"
|
|
3
|
-
|
|
4
|
-
t.describe(`${JsModule.importSource.name}`, () => {
|
|
5
|
-
t.it("imports a string", async () => {
|
|
6
|
-
const mod = await JsModule.importSource<any>(`
|
|
7
|
-
export const b = "B"
|
|
8
|
-
`)
|
|
9
|
-
|
|
10
|
-
t
|
|
11
|
-
.expect(mod.b)
|
|
12
|
-
.toBe("B")
|
|
13
|
-
})
|
|
14
|
-
})
|
package/src/JsModule.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import * as Array from "effect/Array"
|
|
2
|
-
import * as Function from "effect/Function"
|
|
3
|
-
import * as Iterable from "effect/Iterable"
|
|
4
|
-
import * as Order from "effect/Order"
|
|
5
|
-
import * as Record from "effect/Record"
|
|
6
|
-
import * as NFS from "node:fs"
|
|
7
|
-
import * as NFSP from "node:fs/promises"
|
|
8
|
-
import * as NPath from "node:path"
|
|
9
|
-
import * as process from "node:process"
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Imports a blob as a module.
|
|
13
|
-
* Useful for loading code from build artifacts.
|
|
14
|
-
*
|
|
15
|
-
* Temporary files are wrriten to closest node_modules/ for node resolver
|
|
16
|
-
* to pick up dependencies correctly and to avoid arbitrary file watchers
|
|
17
|
-
* from detecting them.
|
|
18
|
-
*/
|
|
19
|
-
export async function importBlob<M = unknown>(
|
|
20
|
-
blob: Blob,
|
|
21
|
-
entrypoint = "index.js",
|
|
22
|
-
): Promise<M> {
|
|
23
|
-
return await importBundle({
|
|
24
|
-
[entrypoint]: blob,
|
|
25
|
-
}, entrypoint)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Imports an entrypoint from multiple blobs.
|
|
30
|
-
* Useful for loading code from build artifacts.
|
|
31
|
-
*
|
|
32
|
-
* Temporary files are wrriten to closest node_modules/ for node resolver
|
|
33
|
-
* to pick up dependencies correctly and to avoid arbitrary file watchers
|
|
34
|
-
* from detecting them.
|
|
35
|
-
*
|
|
36
|
-
* WARNING: dynamic imports that happened after this function will fail
|
|
37
|
-
*/
|
|
38
|
-
export async function importBundle<M = unknown>(
|
|
39
|
-
blobs: {
|
|
40
|
-
[path: string]: Blob
|
|
41
|
-
},
|
|
42
|
-
entrypoint: string,
|
|
43
|
-
basePath = findNodeModules() + "/.tmp",
|
|
44
|
-
): Promise<M> {
|
|
45
|
-
const sortedBlobs = Function.pipe(
|
|
46
|
-
blobs,
|
|
47
|
-
Record.toEntries,
|
|
48
|
-
Array.sortWith(v => v[0], Order.string),
|
|
49
|
-
Array.map(v => v[1]),
|
|
50
|
-
)
|
|
51
|
-
const bundleBlob = new Blob(sortedBlobs)
|
|
52
|
-
const hashPrefix = await hashBuffer(await bundleBlob.arrayBuffer())
|
|
53
|
-
.then(v => v.slice(0, 8))
|
|
54
|
-
const dir = `${basePath}/effect-start-${hashPrefix}`
|
|
55
|
-
|
|
56
|
-
await NFSP.mkdir(dir, { recursive: true })
|
|
57
|
-
|
|
58
|
-
await Promise.all(Function.pipe(
|
|
59
|
-
blobs,
|
|
60
|
-
Record.toEntries,
|
|
61
|
-
Array.map(([path, blob]) => {
|
|
62
|
-
const fullPath = `${dir}/${path}`
|
|
63
|
-
|
|
64
|
-
return blob
|
|
65
|
-
.arrayBuffer()
|
|
66
|
-
.then(v => NFSP.writeFile(fullPath, Buffer.from(v)))
|
|
67
|
-
}),
|
|
68
|
-
))
|
|
69
|
-
|
|
70
|
-
const bundleModule = await import(`${dir}/${entrypoint}`)
|
|
71
|
-
|
|
72
|
-
await NFSP
|
|
73
|
-
.rmdir(dir, { recursive: true })
|
|
74
|
-
// if called concurrently, file sometimes may be deleted
|
|
75
|
-
// safe ignore when this happens
|
|
76
|
-
.catch(() => {})
|
|
77
|
-
|
|
78
|
-
return bundleModule
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export function findNodeModules(startDir = process.cwd()) {
|
|
82
|
-
let currentDir = NPath.resolve(startDir)
|
|
83
|
-
|
|
84
|
-
while (currentDir !== NPath.parse(currentDir).root) {
|
|
85
|
-
const nodeModulesPath = NPath.join(currentDir, "node_modules")
|
|
86
|
-
if (
|
|
87
|
-
NFS.statSync(nodeModulesPath).isDirectory()
|
|
88
|
-
) {
|
|
89
|
-
return nodeModulesPath
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
currentDir = NPath.dirname(currentDir)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return null
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Loads a JS module from a string using data: import
|
|
100
|
-
*/
|
|
101
|
-
export async function importSource<M = unknown>(
|
|
102
|
-
code: string,
|
|
103
|
-
): Promise<M> {
|
|
104
|
-
const dataUrl = `data:text/javascript,${encodeURIComponent(code)}`
|
|
105
|
-
return await import(dataUrl)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async function hashBuffer(buffer: BufferSource) {
|
|
109
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", buffer)
|
|
110
|
-
|
|
111
|
-
return Function.pipe(
|
|
112
|
-
new Uint8Array(hashBuffer),
|
|
113
|
-
Iterable.map(b => b.toString(16).padStart(2, "0")),
|
|
114
|
-
Iterable.reduce("", (a, b) => a + b),
|
|
115
|
-
)
|
|
116
|
-
}
|
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
|
|
2
|
-
import * as t from "bun:test"
|
|
3
|
-
import { MemoryFileSystem } from "effect-memfs"
|
|
4
|
-
import {
|
|
5
|
-
effectFn,
|
|
6
|
-
TestHttpClient,
|
|
7
|
-
} from "effect-start"
|
|
8
|
-
import * as Effect from "effect/Effect"
|
|
9
|
-
import * as PublicDirectory from "./PublicDirectory.ts"
|
|
10
|
-
|
|
11
|
-
const TestFiles = {
|
|
12
|
-
"/test-public/index.html": "<html><body>Hello World</body></html>",
|
|
13
|
-
"/test-public/style.css": "body { color: red; }",
|
|
14
|
-
"/test-public/script.js": "console.log('hello');",
|
|
15
|
-
"/test-public/data.json": "{\"message\": \"test\"}",
|
|
16
|
-
"/test-public/image.png": "fake-png-data",
|
|
17
|
-
"/test-public/nested/file.txt": "nested content",
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const effect = effectFn()
|
|
21
|
-
|
|
22
|
-
t.it("serves index.html for root path", () => {
|
|
23
|
-
effect(function*() {
|
|
24
|
-
const app = PublicDirectory.make({ directory: "/test-public" })
|
|
25
|
-
const Client = TestHttpClient.make(app)
|
|
26
|
-
|
|
27
|
-
const res = yield* Client.get("/").pipe(
|
|
28
|
-
Effect.provide(MemoryFileSystem.layerWith(TestFiles)),
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
t
|
|
32
|
-
.expect(
|
|
33
|
-
res.status,
|
|
34
|
-
)
|
|
35
|
-
.toBe(200)
|
|
36
|
-
|
|
37
|
-
const body = yield* res.text
|
|
38
|
-
t
|
|
39
|
-
.expect(
|
|
40
|
-
body,
|
|
41
|
-
)
|
|
42
|
-
.toBe("<html><body>Hello World</body></html>")
|
|
43
|
-
|
|
44
|
-
t
|
|
45
|
-
.expect(
|
|
46
|
-
res.headers["content-type"],
|
|
47
|
-
)
|
|
48
|
-
.toBe("text/html")
|
|
49
|
-
})
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
t.it("serves CSS files with correct content type", () => {
|
|
53
|
-
effect(function*() {
|
|
54
|
-
const app = PublicDirectory.make({ directory: "/test-public" })
|
|
55
|
-
const Client = TestHttpClient.make(app)
|
|
56
|
-
|
|
57
|
-
const res = yield* Client.get("/style.css").pipe(
|
|
58
|
-
Effect.provide(MemoryFileSystem.layerWith(TestFiles)),
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
t
|
|
62
|
-
.expect(
|
|
63
|
-
res.status,
|
|
64
|
-
)
|
|
65
|
-
.toBe(200)
|
|
66
|
-
|
|
67
|
-
const body = yield* res.text
|
|
68
|
-
t
|
|
69
|
-
.expect(
|
|
70
|
-
body,
|
|
71
|
-
)
|
|
72
|
-
.toBe("body { color: red; }")
|
|
73
|
-
|
|
74
|
-
t
|
|
75
|
-
.expect(
|
|
76
|
-
res.headers["content-type"],
|
|
77
|
-
)
|
|
78
|
-
.toBe("text/css")
|
|
79
|
-
})
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
t.it("serves JavaScript files with correct content type", () => {
|
|
83
|
-
effect(function*() {
|
|
84
|
-
const app = PublicDirectory.make({ directory: "/test-public" })
|
|
85
|
-
const Client = TestHttpClient.make(app)
|
|
86
|
-
|
|
87
|
-
const res = yield* Client.get("/script.js").pipe(
|
|
88
|
-
Effect.provide(MemoryFileSystem.layerWith(TestFiles)),
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
t
|
|
92
|
-
.expect(
|
|
93
|
-
res.status,
|
|
94
|
-
)
|
|
95
|
-
.toBe(200)
|
|
96
|
-
|
|
97
|
-
const body = yield* res.text
|
|
98
|
-
t
|
|
99
|
-
.expect(
|
|
100
|
-
body,
|
|
101
|
-
)
|
|
102
|
-
.toBe("console.log('hello');")
|
|
103
|
-
|
|
104
|
-
t
|
|
105
|
-
.expect(
|
|
106
|
-
res.headers["content-type"],
|
|
107
|
-
)
|
|
108
|
-
.toBe("application/javascript")
|
|
109
|
-
})
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
t.it("serves JSON files with correct content type", () => {
|
|
113
|
-
effect(function*() {
|
|
114
|
-
const app = PublicDirectory.make({ directory: "/test-public" })
|
|
115
|
-
const Client = TestHttpClient.make(app)
|
|
116
|
-
|
|
117
|
-
const res = yield* Client.get("/data.json").pipe(
|
|
118
|
-
Effect.provide(MemoryFileSystem.layerWith(TestFiles)),
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
t
|
|
122
|
-
.expect(
|
|
123
|
-
res.status,
|
|
124
|
-
)
|
|
125
|
-
.toBe(200)
|
|
126
|
-
|
|
127
|
-
const body = yield* res.text
|
|
128
|
-
t
|
|
129
|
-
.expect(
|
|
130
|
-
body,
|
|
131
|
-
)
|
|
132
|
-
.toBe("{\"message\": \"test\"}")
|
|
133
|
-
|
|
134
|
-
t
|
|
135
|
-
.expect(
|
|
136
|
-
res.headers["content-type"],
|
|
137
|
-
)
|
|
138
|
-
.toBe("application/json")
|
|
139
|
-
})
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
t.it("serves nested files", () => {
|
|
143
|
-
effect(function*() {
|
|
144
|
-
const app = PublicDirectory.make({ directory: "/test-public" })
|
|
145
|
-
const Client = TestHttpClient.make(app)
|
|
146
|
-
|
|
147
|
-
const res = yield* Client.get("/nested/file.txt").pipe(
|
|
148
|
-
Effect.provide(MemoryFileSystem.layerWith(TestFiles)),
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
t
|
|
152
|
-
.expect(
|
|
153
|
-
res.status,
|
|
154
|
-
)
|
|
155
|
-
.toBe(200)
|
|
156
|
-
|
|
157
|
-
const body = yield* res.text
|
|
158
|
-
t
|
|
159
|
-
.expect(
|
|
160
|
-
body,
|
|
161
|
-
)
|
|
162
|
-
.toBe("nested content")
|
|
163
|
-
|
|
164
|
-
t
|
|
165
|
-
.expect(
|
|
166
|
-
res.headers["content-type"],
|
|
167
|
-
)
|
|
168
|
-
.toBe("text/plain")
|
|
169
|
-
})
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
t.it("returns 404 for non-existent files", () => {
|
|
173
|
-
effect(function*() {
|
|
174
|
-
const app = PublicDirectory.make({ directory: "/test-public" })
|
|
175
|
-
const Client = TestHttpClient.make(app)
|
|
176
|
-
|
|
177
|
-
const res = yield* Client.get("/nonexistent.txt").pipe(
|
|
178
|
-
Effect.provide(MemoryFileSystem.layerWith(TestFiles)),
|
|
179
|
-
Effect.catchTag(
|
|
180
|
-
"RouteNotFound",
|
|
181
|
-
() => HttpServerResponse.empty({ status: 404 }),
|
|
182
|
-
),
|
|
183
|
-
)
|
|
184
|
-
|
|
185
|
-
t
|
|
186
|
-
.expect(
|
|
187
|
-
res.status,
|
|
188
|
-
)
|
|
189
|
-
.toBe(404)
|
|
190
|
-
})
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
t.it("prevents directory traversal attacks", () => {
|
|
194
|
-
effect(function*() {
|
|
195
|
-
const app = PublicDirectory.make({ directory: "/test-public" })
|
|
196
|
-
const Client = TestHttpClient.make(app)
|
|
197
|
-
|
|
198
|
-
const res = yield* Client.get("/../../../etc/passwd").pipe(
|
|
199
|
-
Effect.provide(MemoryFileSystem.layerWith(TestFiles)),
|
|
200
|
-
Effect.catchTag(
|
|
201
|
-
"RouteNotFound",
|
|
202
|
-
() => HttpServerResponse.empty({ status: 404 }),
|
|
203
|
-
),
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
t
|
|
207
|
-
.expect(
|
|
208
|
-
res.status,
|
|
209
|
-
)
|
|
210
|
-
.toBe(404)
|
|
211
|
-
})
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
t.it("works with custom prefix", () => {
|
|
215
|
-
effect(function*() {
|
|
216
|
-
const app = PublicDirectory.make({
|
|
217
|
-
directory: "/test-public",
|
|
218
|
-
prefix: "/static",
|
|
219
|
-
})
|
|
220
|
-
const Client = TestHttpClient.make(app)
|
|
221
|
-
|
|
222
|
-
const res = yield* Client.get("/static/style.css").pipe(
|
|
223
|
-
Effect.provide(MemoryFileSystem.layerWith(TestFiles)),
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
t
|
|
227
|
-
.expect(
|
|
228
|
-
res.status,
|
|
229
|
-
)
|
|
230
|
-
.toBe(200)
|
|
231
|
-
|
|
232
|
-
const body = yield* res.text
|
|
233
|
-
t
|
|
234
|
-
.expect(
|
|
235
|
-
body,
|
|
236
|
-
)
|
|
237
|
-
.toBe("body { color: red; }")
|
|
238
|
-
})
|
|
239
|
-
})
|
|
240
|
-
|
|
241
|
-
t.it("ignores requests without prefix when prefix is set", () => {
|
|
242
|
-
effect(function*() {
|
|
243
|
-
const app = PublicDirectory.make({
|
|
244
|
-
directory: "/test-public",
|
|
245
|
-
prefix: "/static",
|
|
246
|
-
})
|
|
247
|
-
const Client = TestHttpClient.make(app)
|
|
248
|
-
|
|
249
|
-
const res = yield* Client.get("/style.css").pipe(
|
|
250
|
-
Effect.provide(MemoryFileSystem.layerWith(TestFiles)),
|
|
251
|
-
Effect.catchTag(
|
|
252
|
-
"RouteNotFound",
|
|
253
|
-
() => HttpServerResponse.empty({ status: 404 }),
|
|
254
|
-
),
|
|
255
|
-
)
|
|
256
|
-
|
|
257
|
-
t
|
|
258
|
-
.expect(
|
|
259
|
-
res.status,
|
|
260
|
-
)
|
|
261
|
-
.toBe(404)
|
|
262
|
-
})
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
t.it("sets cache control headers", () => {
|
|
266
|
-
effect(function*() {
|
|
267
|
-
const app = PublicDirectory.make({ directory: "/test-public" })
|
|
268
|
-
const Client = TestHttpClient.make(app)
|
|
269
|
-
|
|
270
|
-
const res = yield* Client.get("/style.css").pipe(
|
|
271
|
-
Effect.provide(MemoryFileSystem.layerWith(TestFiles)),
|
|
272
|
-
)
|
|
273
|
-
|
|
274
|
-
t
|
|
275
|
-
.expect(
|
|
276
|
-
res.headers["cache-control"],
|
|
277
|
-
)
|
|
278
|
-
.toBe("public, max-age=3600")
|
|
279
|
-
})
|
|
280
|
-
})
|
package/src/PublicDirectory.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import * as FileSystem from "@effect/platform/FileSystem"
|
|
2
|
-
import * as HttpApp from "@effect/platform/HttpApp"
|
|
3
|
-
import { RouteNotFound } from "@effect/platform/HttpServerError"
|
|
4
|
-
import * as HttpServerRequest from "@effect/platform/HttpServerRequest"
|
|
5
|
-
import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
|
|
6
|
-
import * as Effect from "effect/Effect"
|
|
7
|
-
import * as Function from "effect/Function"
|
|
8
|
-
import * as NPath from "node:path"
|
|
9
|
-
|
|
10
|
-
export interface PublicDirectoryOptions {
|
|
11
|
-
readonly directory?: string
|
|
12
|
-
readonly prefix?: string
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const make = (
|
|
16
|
-
options: PublicDirectoryOptions = {},
|
|
17
|
-
): HttpApp.Default<RouteNotFound, FileSystem.FileSystem> =>
|
|
18
|
-
Effect.gen(function*() {
|
|
19
|
-
const request = yield* HttpServerRequest.HttpServerRequest
|
|
20
|
-
const fs = yield* FileSystem.FileSystem
|
|
21
|
-
|
|
22
|
-
const directory = options.directory ?? NPath.join(process.cwd(), "public")
|
|
23
|
-
const prefix = options.prefix ?? ""
|
|
24
|
-
|
|
25
|
-
let pathname = request.url
|
|
26
|
-
|
|
27
|
-
if (prefix && !pathname.startsWith(prefix)) {
|
|
28
|
-
return yield* Effect.fail(new RouteNotFound({ request }))
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (prefix) {
|
|
32
|
-
pathname = pathname.slice(prefix.length)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (pathname.startsWith("/")) {
|
|
36
|
-
pathname = pathname.slice(1)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (pathname === "") {
|
|
40
|
-
pathname = "index.html"
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const filePath = NPath.join(directory, pathname)
|
|
44
|
-
|
|
45
|
-
if (!filePath.startsWith(directory)) {
|
|
46
|
-
return yield* Effect.fail(new RouteNotFound({ request }))
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const exists = yield* Function.pipe(
|
|
50
|
-
fs.exists(filePath),
|
|
51
|
-
Effect.catchAll(() => Effect.succeed(false)),
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
if (!exists) {
|
|
55
|
-
return yield* Effect.fail(new RouteNotFound({ request }))
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const stat = yield* Function.pipe(
|
|
59
|
-
fs.stat(filePath),
|
|
60
|
-
Effect.catchAll(() => Effect.fail(new RouteNotFound({ request }))),
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
if (stat.type !== "File") {
|
|
64
|
-
return yield* Effect.fail(new RouteNotFound({ request }))
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const content = yield* Function.pipe(
|
|
68
|
-
fs.readFile(filePath),
|
|
69
|
-
Effect.catchAll(() => Effect.fail(new RouteNotFound({ request }))),
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
const mimeType = getMimeType(filePath)
|
|
73
|
-
|
|
74
|
-
return HttpServerResponse.uint8Array(content, {
|
|
75
|
-
headers: {
|
|
76
|
-
"Content-Type": mimeType,
|
|
77
|
-
"Cache-Control": "public, max-age=3600",
|
|
78
|
-
},
|
|
79
|
-
})
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
function getMimeType(filePath: string): string {
|
|
83
|
-
const ext = NPath.extname(filePath).toLowerCase()
|
|
84
|
-
|
|
85
|
-
const mimeTypes: Record<string, string> = {
|
|
86
|
-
".html": "text/html",
|
|
87
|
-
".htm": "text/html",
|
|
88
|
-
".css": "text/css",
|
|
89
|
-
".js": "application/javascript",
|
|
90
|
-
".mjs": "application/javascript",
|
|
91
|
-
".json": "application/json",
|
|
92
|
-
".png": "image/png",
|
|
93
|
-
".jpg": "image/jpeg",
|
|
94
|
-
".jpeg": "image/jpeg",
|
|
95
|
-
".gif": "image/gif",
|
|
96
|
-
".svg": "image/svg+xml",
|
|
97
|
-
".ico": "image/x-icon",
|
|
98
|
-
".txt": "text/plain",
|
|
99
|
-
".pdf": "application/pdf",
|
|
100
|
-
".woff": "font/woff",
|
|
101
|
-
".woff2": "font/woff2",
|
|
102
|
-
".ttf": "font/ttf",
|
|
103
|
-
".otf": "font/otf",
|
|
104
|
-
".eot": "application/vnd.ms-fontobject",
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return mimeTypes[ext] ?? "application/octet-stream"
|
|
108
|
-
}
|
package/src/StartHttp.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import * as HttpApp from "@effect/platform/HttpApp"
|
|
2
|
-
import * as HttpMiddleware from "@effect/platform/HttpMiddleware"
|
|
3
|
-
import * as HttpServerRequest from "@effect/platform/HttpServerRequest"
|
|
4
|
-
import * as HttpServerResponse from "@effect/platform/HttpServerResponse"
|
|
5
|
-
import * as Effect from "effect/Effect"
|
|
6
|
-
import {
|
|
7
|
-
Bundle,
|
|
8
|
-
BundleHttp,
|
|
9
|
-
} from "."
|
|
10
|
-
|
|
11
|
-
type SsrRenderer = (req: Request) => PromiseLike<Response>
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Attempts to render SSR page. If the renderer returns 404,
|
|
15
|
-
* we fall back to app.
|
|
16
|
-
*/
|
|
17
|
-
export function ssr(renderer: SsrRenderer) {
|
|
18
|
-
return Effect.gen(function*() {
|
|
19
|
-
const request = yield* HttpServerRequest.HttpServerRequest
|
|
20
|
-
const webRequest = request.source as Request
|
|
21
|
-
const ssrRes = yield* Effect.tryPromise(() => renderer(webRequest))
|
|
22
|
-
|
|
23
|
-
return HttpServerResponse.raw(ssrRes.body, {
|
|
24
|
-
status: ssrRes.status,
|
|
25
|
-
headers: ssrRes.headers,
|
|
26
|
-
})
|
|
27
|
-
})
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function withBundleAssets(opts?: {
|
|
31
|
-
path?: string
|
|
32
|
-
}) {
|
|
33
|
-
return HttpMiddleware.make(app =>
|
|
34
|
-
Effect.gen(function*() {
|
|
35
|
-
const request = yield* HttpServerRequest.HttpServerRequest
|
|
36
|
-
const bundleResponse = yield* BundleHttp.httpApp()
|
|
37
|
-
|
|
38
|
-
// Fallback to original app
|
|
39
|
-
return yield* app
|
|
40
|
-
})
|
|
41
|
-
)
|
|
42
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|