@thomasfosterau/effect-sveltekit 0.0.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/LICENSE +21 -0
- package/README.md +66 -0
- package/dist/Hooks.d.ts +33 -0
- package/dist/Hooks.js +34 -0
- package/dist/Hooks.js.map +1 -0
- package/dist/Navigation.d.ts +110 -0
- package/dist/Navigation.js +155 -0
- package/dist/Navigation.js.map +1 -0
- package/dist/Remote.d.ts +52 -0
- package/dist/Remote.js +67 -0
- package/dist/Remote.js.map +1 -0
- package/dist/State.d.ts +17 -0
- package/dist/State.js +65 -0
- package/dist/State.js.map +1 -0
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/adapter/handler.d.ts +67 -0
- package/dist/adapter/handler.js +158 -0
- package/dist/adapter/handler.js.map +1 -0
- package/dist/adapter/index.d.ts +29 -0
- package/dist/adapter/index.js +105 -0
- package/dist/adapter/index.js.map +1 -0
- package/dist/adapter/serve.d.ts +46 -0
- package/dist/adapter/serve.js +57 -0
- package/dist/adapter/serve.js.map +1 -0
- package/dist/app-runtime.d.ts +212 -0
- package/dist/app-runtime.js +173 -0
- package/dist/app-runtime.js.map +1 -0
- package/dist/client/Hooks.d.ts +48 -0
- package/dist/client/Hooks.js +61 -0
- package/dist/client/Hooks.js.map +1 -0
- package/dist/client.d.ts +6 -0
- package/dist/client.js +6 -0
- package/dist/config.d.ts +17 -0
- package/dist/config.js +51 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/internal/app-state.d.ts +39 -0
- package/dist/internal/app-state.js +52 -0
- package/dist/internal/app-state.js.map +1 -0
- package/dist/internal/auto.d.ts +70 -0
- package/dist/internal/auto.js +161 -0
- package/dist/internal/auto.js.map +1 -0
- package/dist/internal/default-runtime.js +21 -0
- package/dist/internal/default-runtime.js.map +1 -0
- package/dist/internal/error.js +31 -0
- package/dist/internal/error.js.map +1 -0
- package/dist/internal/navigation-events.js +41 -0
- package/dist/internal/navigation-events.js.map +1 -0
- package/dist/remote-reactive.d.ts +38 -0
- package/dist/remote-reactive.js +44 -0
- package/dist/remote-reactive.js.map +1 -0
- package/dist/remote.server.d.ts +48 -0
- package/dist/remote.server.js +90 -0
- package/dist/remote.server.js.map +1 -0
- package/dist/runtime.d.ts +130 -0
- package/dist/runtime.js +143 -0
- package/dist/runtime.js.map +1 -0
- package/dist/server/Endpoint.d.ts +101 -0
- package/dist/server/Endpoint.js +109 -0
- package/dist/server/Endpoint.js.map +1 -0
- package/dist/server/Hooks.d.ts +97 -0
- package/dist/server/Hooks.js +108 -0
- package/dist/server/Hooks.js.map +1 -0
- package/dist/server.d.ts +108 -0
- package/dist/server.js +131 -0
- package/dist/server.js.map +1 -0
- package/dist/transport.d.ts +23 -0
- package/dist/transport.js +34 -0
- package/dist/transport.js.map +1 -0
- package/dist/universal.d.ts +35 -0
- package/dist/universal.js +36 -0
- package/dist/universal.js.map +1 -0
- package/dist/vite.d.ts +42 -0
- package/dist/vite.js +242 -0
- package/dist/vite.js.map +1 -0
- package/package.json +101 -0
package/dist/State.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
2
|
+
import { pageSnapshotStream } from "./internal/app-state.js";
|
|
3
|
+
import { afterNavigate } from "$app/navigation";
|
|
4
|
+
import { page } from "$app/state";
|
|
5
|
+
//#region src/State.ts
|
|
6
|
+
/**
|
|
7
|
+
* A minimal Effect-side bridge for SvelteKit's
|
|
8
|
+
* [`$app/state`](https://svelte.dev/docs/kit/$app-state) `page`.
|
|
9
|
+
*
|
|
10
|
+
* `$app/state` exposes rune-reactive `page` / `navigating` / `updated` for use
|
|
11
|
+
* inside components. This bridges `page` to an Effect `Stream` so navigation can
|
|
12
|
+
* drive Effect pipelines without touching Svelte runes: it emits the current
|
|
13
|
+
* `page` immediately, then again after each navigation, by registering
|
|
14
|
+
* `afterNavigate` (from `$app/navigation`) and reading `page` (from
|
|
15
|
+
* `$app/state`) at callback time.
|
|
16
|
+
*
|
|
17
|
+
* Each emission is a *shallow snapshot* (`url`, `params`, `route`, `data`,
|
|
18
|
+
* `status`, `error`, `form`, `state`) rather than the live `$app/state` proxy:
|
|
19
|
+
* the proxy mutates in place on every navigation, so a captured reference is
|
|
20
|
+
* unreliable for downstream consumers — the snapshot gives each emission a
|
|
21
|
+
* stable value. Reading at `afterNavigate` time also avoids needing a `.svelte`
|
|
22
|
+
* runes file.
|
|
23
|
+
*
|
|
24
|
+
* Like the navigation-event helpers, `page` registers its callback during
|
|
25
|
+
* component initialization (where `$app/navigation` requires), so call it at the
|
|
26
|
+
* top level of `<script>`. Client-only.
|
|
27
|
+
*
|
|
28
|
+
* NOTE: this is intentionally minimal. A richer reactive bridge (a `CurrentPage`
|
|
29
|
+
* service readable from any Effect, `navigating` / `updated` streams) may later
|
|
30
|
+
* compose with the official `@effect/atom-svelte` integration (see issue #23)
|
|
31
|
+
* rather than re-implementing runes plumbing here.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```svelte
|
|
35
|
+
* <script lang="ts">
|
|
36
|
+
* import { State } from '@thomasfosterau/effect-sveltekit/client';
|
|
37
|
+
* import { Stream, Effect } from 'effect';
|
|
38
|
+
* import { getRuntime } from '@thomasfosterau/effect-svelte';
|
|
39
|
+
*
|
|
40
|
+
* const pages = State.page();
|
|
41
|
+
* getRuntime().runFork(
|
|
42
|
+
* Stream.runForEach(pages, (page) =>
|
|
43
|
+
* Effect.logInfo(`page: ${page.url.pathname}`),
|
|
44
|
+
* ),
|
|
45
|
+
* );
|
|
46
|
+
* <\/script>
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
var State_exports = /* @__PURE__ */ __exportAll({ page: () => page$1 });
|
|
50
|
+
/**
|
|
51
|
+
* A `Stream` of the current SvelteKit `page`, emitting a shallow snapshot
|
|
52
|
+
* immediately and again after every navigation. Call during component
|
|
53
|
+
* initialization.
|
|
54
|
+
*/
|
|
55
|
+
function page$1() {
|
|
56
|
+
return pageSnapshotStream((callback) => {
|
|
57
|
+
afterNavigate(() => {
|
|
58
|
+
callback();
|
|
59
|
+
});
|
|
60
|
+
}, () => page);
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
export { State_exports };
|
|
64
|
+
|
|
65
|
+
//# sourceMappingURL=State.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"State.js","names":["page","kitPage"],"sources":["../src/State.ts"],"sourcesContent":["/**\n * A minimal Effect-side bridge for SvelteKit's\n * [`$app/state`](https://svelte.dev/docs/kit/$app-state) `page`.\n *\n * `$app/state` exposes rune-reactive `page` / `navigating` / `updated` for use\n * inside components. This bridges `page` to an Effect `Stream` so navigation can\n * drive Effect pipelines without touching Svelte runes: it emits the current\n * `page` immediately, then again after each navigation, by registering\n * `afterNavigate` (from `$app/navigation`) and reading `page` (from\n * `$app/state`) at callback time.\n *\n * Each emission is a *shallow snapshot* (`url`, `params`, `route`, `data`,\n * `status`, `error`, `form`, `state`) rather than the live `$app/state` proxy:\n * the proxy mutates in place on every navigation, so a captured reference is\n * unreliable for downstream consumers — the snapshot gives each emission a\n * stable value. Reading at `afterNavigate` time also avoids needing a `.svelte`\n * runes file.\n *\n * Like the navigation-event helpers, `page` registers its callback during\n * component initialization (where `$app/navigation` requires), so call it at the\n * top level of `<script>`. Client-only.\n *\n * NOTE: this is intentionally minimal. A richer reactive bridge (a `CurrentPage`\n * service readable from any Effect, `navigating` / `updated` streams) may later\n * compose with the official `@effect/atom-svelte` integration (see issue #23)\n * rather than re-implementing runes plumbing here.\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { State } from '@thomasfosterau/effect-sveltekit/client';\n * import { Stream, Effect } from 'effect';\n * import { getRuntime } from '@thomasfosterau/effect-svelte';\n *\n * const pages = State.page();\n * getRuntime().runFork(\n * Stream.runForEach(pages, (page) =>\n * Effect.logInfo(`page: ${page.url.pathname}`),\n * ),\n * );\n * </script>\n * ```\n */\n\nimport { afterNavigate as kitAfterNavigate } from \"$app/navigation\";\nimport { page as kitPage } from \"$app/state\";\nimport type { Page } from \"@sveltejs/kit\";\nimport type { Stream } from \"effect\";\nimport { type PageSnapshot, pageSnapshotStream } from \"./internal/app-state.js\";\n\n/** A shallow snapshot of SvelteKit's `page` — see {@link page}. */\nexport type { PageSnapshot } from \"./internal/app-state.js\";\n\n/**\n * A `Stream` of the current SvelteKit `page`, emitting a shallow snapshot\n * immediately and again after every navigation. Call during component\n * initialization.\n */\nexport function page(): Stream.Stream<PageSnapshot<Page>> {\n return pageSnapshotStream<Page>(\n (callback) => {\n kitAfterNavigate(() => {\n callback();\n });\n },\n () => kitPage,\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0DA,SAAgBA,SAA0C;CACxD,OAAO,oBACJ,aAAa;EACZ,oBAAuB;GACrB,SAAS;EACX,CAAC;CACH,SACMC,IACR;AACF"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __exportAll = (all, no_symbols) => {
|
|
4
|
+
let target = {};
|
|
5
|
+
for (var name in all) __defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true
|
|
8
|
+
});
|
|
9
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
10
|
+
return target;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { __exportAll };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { SSRManifest, Server } from "@sveltejs/kit";
|
|
3
|
+
import * as FileSystem from "effect/FileSystem";
|
|
4
|
+
import * as Path from "effect/Path";
|
|
5
|
+
import * as HttpPlatform from "effect/unstable/http/HttpPlatform";
|
|
6
|
+
import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
|
|
7
|
+
import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
|
|
8
|
+
|
|
9
|
+
//#region src/adapter/handler.d.ts
|
|
10
|
+
/**
|
|
11
|
+
* The services required by the SvelteKit HTTP application. All of them are
|
|
12
|
+
* provided by `NodeHttpServer.layer` from `@effect/platform-node`.
|
|
13
|
+
*/
|
|
14
|
+
type SvelteKitHttpAppServices = FileSystem.FileSystem | Path.Path | HttpPlatform.HttpPlatform;
|
|
15
|
+
/**
|
|
16
|
+
* The SvelteKit application as a composable Effect HTTP handler: an effect that
|
|
17
|
+
* produces an {@link HttpServerResponse.HttpServerResponse} for the current
|
|
18
|
+
* request. The `HttpServerRequest` is supplied per request by the server.
|
|
19
|
+
*/
|
|
20
|
+
type SvelteKitHttpApp = Effect.Effect<HttpServerResponse.HttpServerResponse, never, SvelteKitHttpAppServices | HttpServerRequest.HttpServerRequest>;
|
|
21
|
+
interface SvelteKitHandlerOptions {
|
|
22
|
+
/** The SvelteKit `Server` class exported by the build output (`./server/index.js`). */
|
|
23
|
+
readonly Server: typeof Server;
|
|
24
|
+
/** The SSR manifest exported by the build output (`./manifest.js`). */
|
|
25
|
+
readonly manifest: SSRManifest;
|
|
26
|
+
/** The set of prerendered paths exported by the build output (`./manifest.js`). */
|
|
27
|
+
readonly prerendered: ReadonlySet<string>;
|
|
28
|
+
/** The configured `paths.base`, exported by the build output (`./manifest.js`). */
|
|
29
|
+
readonly base: string;
|
|
30
|
+
/** Absolute path of the build output directory (contains `client/`, `server/`, `prerendered/`). */
|
|
31
|
+
readonly dir: string;
|
|
32
|
+
/** Environment variables passed to the SvelteKit server (e.g. `process.env`). */
|
|
33
|
+
readonly env?: Record<string, string | undefined>;
|
|
34
|
+
/** Prefix for the runtime configuration environment variables (ORIGIN, ...). */
|
|
35
|
+
readonly envPrefix?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Creates the SvelteKit HTTP handler.
|
|
39
|
+
*
|
|
40
|
+
* The returned effect initialises the SvelteKit server (running your app's
|
|
41
|
+
* server `init` hook) and yields an HTTP handler that:
|
|
42
|
+
*
|
|
43
|
+
* 1. serves static client assets from `<dir>/client` (with immutable cache
|
|
44
|
+
* headers for hashed build assets),
|
|
45
|
+
* 2. serves prerendered pages from `<dir>/prerendered`,
|
|
46
|
+
* 3. falls back to server-side rendering via the SvelteKit server.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* // Mounting SvelteKit inside an existing effect/unstable/http server:
|
|
51
|
+
* import { handler } from "./build/handler.js";
|
|
52
|
+
* import * as HttpServer from "effect/unstable/http/HttpServer";
|
|
53
|
+
* import { NodeHttpServer, NodeRuntime } from "@effect/platform-node";
|
|
54
|
+
* import { Effect, Layer } from "effect";
|
|
55
|
+
* import { createServer } from "node:http";
|
|
56
|
+
*
|
|
57
|
+
* const serverLayer = Layer.unwrap(
|
|
58
|
+
* Effect.map(handler, (app) => HttpServer.serve(app))
|
|
59
|
+
* ).pipe(Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })));
|
|
60
|
+
*
|
|
61
|
+
* NodeRuntime.runMain(Layer.launch(serverLayer));
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
declare const makeHandler: (options: SvelteKitHandlerOptions) => Effect.Effect<SvelteKitHttpApp, never, SvelteKitHttpAppServices>;
|
|
65
|
+
//#endregion
|
|
66
|
+
export { SvelteKitHandlerOptions, SvelteKitHttpApp, SvelteKitHttpAppServices, makeHandler };
|
|
67
|
+
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { Config, Effect, Option, Stream } from "effect";
|
|
2
|
+
import * as FileSystem from "effect/FileSystem";
|
|
3
|
+
import * as Path from "effect/Path";
|
|
4
|
+
import "effect/unstable/http/HttpPlatform";
|
|
5
|
+
import * as HttpMethod from "effect/unstable/http/HttpMethod";
|
|
6
|
+
import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
|
|
7
|
+
import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
|
|
8
|
+
//#region src/adapter/handler.ts
|
|
9
|
+
/** Runtime configuration, read from the environment (compatible with adapter-node). */
|
|
10
|
+
const handlerConfig = (prefix) => Config.all({
|
|
11
|
+
origin: Config.option(Config.string(`${prefix}ORIGIN`)),
|
|
12
|
+
protocolHeader: Config.option(Config.string(`${prefix}PROTOCOL_HEADER`)),
|
|
13
|
+
hostHeader: Config.option(Config.string(`${prefix}HOST_HEADER`)),
|
|
14
|
+
portHeader: Config.option(Config.string(`${prefix}PORT_HEADER`)),
|
|
15
|
+
addressHeader: Config.option(Config.string(`${prefix}ADDRESS_HEADER`)),
|
|
16
|
+
xffDepth: Config.int(`${prefix}XFF_DEPTH`).pipe(Config.withDefault(1))
|
|
17
|
+
});
|
|
18
|
+
/** Compute the request origin from configuration and headers (adapter-node semantics). */
|
|
19
|
+
const originOf = (request, config) => {
|
|
20
|
+
if (Option.isSome(config.origin)) return config.origin.value;
|
|
21
|
+
const headers = request.headers;
|
|
22
|
+
const protocolHeader = Option.getOrUndefined(config.protocolHeader)?.toLowerCase();
|
|
23
|
+
const hostHeader = Option.getOrUndefined(config.hostHeader)?.toLowerCase() ?? "host";
|
|
24
|
+
const portHeader = Option.getOrUndefined(config.portHeader)?.toLowerCase();
|
|
25
|
+
const protocol = protocolHeader && headers[protocolHeader] || "http";
|
|
26
|
+
const host = headers[hostHeader] ?? "localhost";
|
|
27
|
+
const port = portHeader ? headers[portHeader] : void 0;
|
|
28
|
+
return port ? `${protocol}://${host.split(":")[0]}:${port}` : `${protocol}://${host}`;
|
|
29
|
+
};
|
|
30
|
+
/** Resolve the client address, honouring ADDRESS_HEADER / XFF_DEPTH if configured. */
|
|
31
|
+
const clientAddressOf = (request, config) => {
|
|
32
|
+
const addressHeader = Option.getOrUndefined(config.addressHeader)?.toLowerCase();
|
|
33
|
+
if (addressHeader) {
|
|
34
|
+
const value = request.headers[addressHeader];
|
|
35
|
+
if (value) {
|
|
36
|
+
if (addressHeader === "x-forwarded-for") {
|
|
37
|
+
const addresses = value.split(",").map((address) => address.trim());
|
|
38
|
+
if (config.xffDepth < 1 || config.xffDepth > addresses.length) return addresses[0] ?? "";
|
|
39
|
+
return addresses[addresses.length - config.xffDepth];
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return Option.getOrElse(request.remoteAddress, () => "");
|
|
45
|
+
};
|
|
46
|
+
/** Build a web `Request` for SvelteKit from an `HttpServerRequest`. */
|
|
47
|
+
const toWebRequest = (request, url, context) => {
|
|
48
|
+
const init = {
|
|
49
|
+
method: request.method,
|
|
50
|
+
headers: { ...request.headers }
|
|
51
|
+
};
|
|
52
|
+
if (HttpMethod.hasBody(request.method)) {
|
|
53
|
+
init.body = Stream.toReadableStreamWith(request.stream, context);
|
|
54
|
+
init.duplex = "half";
|
|
55
|
+
}
|
|
56
|
+
return new Request(url, init);
|
|
57
|
+
};
|
|
58
|
+
const IMMUTABLE_HEADERS = { "cache-control": "public, max-age=31536000, immutable" };
|
|
59
|
+
/**
|
|
60
|
+
* Creates the SvelteKit HTTP handler.
|
|
61
|
+
*
|
|
62
|
+
* The returned effect initialises the SvelteKit server (running your app's
|
|
63
|
+
* server `init` hook) and yields an HTTP handler that:
|
|
64
|
+
*
|
|
65
|
+
* 1. serves static client assets from `<dir>/client` (with immutable cache
|
|
66
|
+
* headers for hashed build assets),
|
|
67
|
+
* 2. serves prerendered pages from `<dir>/prerendered`,
|
|
68
|
+
* 3. falls back to server-side rendering via the SvelteKit server.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* // Mounting SvelteKit inside an existing effect/unstable/http server:
|
|
73
|
+
* import { handler } from "./build/handler.js";
|
|
74
|
+
* import * as HttpServer from "effect/unstable/http/HttpServer";
|
|
75
|
+
* import { NodeHttpServer, NodeRuntime } from "@effect/platform-node";
|
|
76
|
+
* import { Effect, Layer } from "effect";
|
|
77
|
+
* import { createServer } from "node:http";
|
|
78
|
+
*
|
|
79
|
+
* const serverLayer = Layer.unwrap(
|
|
80
|
+
* Effect.map(handler, (app) => HttpServer.serve(app))
|
|
81
|
+
* ).pipe(Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })));
|
|
82
|
+
*
|
|
83
|
+
* NodeRuntime.runMain(Layer.launch(serverLayer));
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
const makeHandler = (options) => Effect.gen(function* () {
|
|
87
|
+
const config = yield* handlerConfig(options.envPrefix ?? "").pipe(Effect.orDie);
|
|
88
|
+
const fs = yield* FileSystem.FileSystem;
|
|
89
|
+
const path = yield* Path.Path;
|
|
90
|
+
const context = yield* Effect.context();
|
|
91
|
+
const clientDir = path.join(options.dir, "client");
|
|
92
|
+
const prerenderedDir = path.join(options.dir, "prerendered");
|
|
93
|
+
const serverDir = path.join(options.dir, "server");
|
|
94
|
+
const server = new options.Server(options.manifest);
|
|
95
|
+
yield* Effect.promise(() => server.init({
|
|
96
|
+
env: options.env ?? {},
|
|
97
|
+
read: (file) => Stream.toReadableStreamWith(fs.stream(path.join(serverDir, file)), context)
|
|
98
|
+
}));
|
|
99
|
+
/**
|
|
100
|
+
* Serve a file from `root` if `pathname` resolves to an existing file
|
|
101
|
+
* inside it. Returns `Option.none()` when the file does not exist or the
|
|
102
|
+
* path escapes `root`.
|
|
103
|
+
*/
|
|
104
|
+
const serveFile = (root, pathname, headers) => Effect.gen(function* () {
|
|
105
|
+
const filePath = path.join(root, pathname);
|
|
106
|
+
const relative = path.relative(root, filePath);
|
|
107
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) return Option.none();
|
|
108
|
+
const stat = yield* fs.stat(filePath).pipe(Effect.option);
|
|
109
|
+
if (Option.isNone(stat) || stat.value.type !== "File") return Option.none();
|
|
110
|
+
const response = yield* HttpServerResponse.file(filePath, { headers }).pipe(Effect.orDie);
|
|
111
|
+
return Option.some(response);
|
|
112
|
+
});
|
|
113
|
+
/** Serve a prerendered page, trying the file layouts SvelteKit emits. */
|
|
114
|
+
const servePrerendered = (pathname) => {
|
|
115
|
+
const candidates = pathname === "/" ? ["/index.html"] : pathname.endsWith("/") ? [`${pathname}index.html`, `${pathname.slice(0, -1)}.html`] : [
|
|
116
|
+
pathname,
|
|
117
|
+
`${pathname}.html`,
|
|
118
|
+
`${pathname}/index.html`
|
|
119
|
+
];
|
|
120
|
+
return Effect.gen(function* () {
|
|
121
|
+
let found = Option.none();
|
|
122
|
+
for (const candidate of candidates) {
|
|
123
|
+
if (Option.isSome(found)) break;
|
|
124
|
+
found = yield* serveFile(prerenderedDir, candidate);
|
|
125
|
+
}
|
|
126
|
+
return found;
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
const immutablePrefix = `/${options.manifest.appPath}/immutable/`;
|
|
130
|
+
return Effect.gen(function* () {
|
|
131
|
+
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
132
|
+
const requestContext = yield* Effect.context();
|
|
133
|
+
const origin = originOf(request, config);
|
|
134
|
+
const url = new URL(request.url, origin);
|
|
135
|
+
let pathname;
|
|
136
|
+
try {
|
|
137
|
+
pathname = decodeURIComponent(url.pathname);
|
|
138
|
+
} catch {
|
|
139
|
+
return HttpServerResponse.empty({ status: 400 });
|
|
140
|
+
}
|
|
141
|
+
if (request.method === "GET" || request.method === "HEAD") {
|
|
142
|
+
const isImmutable = pathname.startsWith(immutablePrefix);
|
|
143
|
+
const asset = yield* serveFile(clientDir, pathname, isImmutable ? IMMUTABLE_HEADERS : void 0);
|
|
144
|
+
if (Option.isSome(asset)) return asset.value;
|
|
145
|
+
if (options.prerendered.has(pathname) || options.prerendered.has(pathname + "/")) {
|
|
146
|
+
const page = yield* servePrerendered(pathname);
|
|
147
|
+
if (Option.isSome(page)) return page.value;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const webRequest = toWebRequest(request, url, requestContext);
|
|
151
|
+
const response = yield* Effect.promise(() => server.respond(webRequest, { getClientAddress: () => clientAddressOf(request, config) }));
|
|
152
|
+
return HttpServerResponse.fromWeb(response);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
//#endregion
|
|
156
|
+
export { makeHandler };
|
|
157
|
+
|
|
158
|
+
//# sourceMappingURL=handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.js","names":[],"sources":["../../src/adapter/handler.ts"],"sourcesContent":["/**\n * Runtime handler for the Effect SvelteKit adapter.\n *\n * `makeHandler` turns a built SvelteKit app (the `Server` class and manifest\n * emitted by `vite build`) into an Effect HTTP application that serves\n * static client assets, prerendered pages, and server-side rendered routes.\n *\n * The returned app is an ordinary Effect value: it can be served with the\n * generated `build/index.js` entry (see `./serve.js`), or mounted inside any\n * existing `effect/unstable/http` HTTP server alongside other routes.\n */\n\nimport type { SSRManifest, Server as SvelteKitServer } from \"@sveltejs/kit\";\nimport * as FileSystem from \"effect/FileSystem\";\nimport * as HttpMethod from \"effect/unstable/http/HttpMethod\";\nimport * as HttpPlatform from \"effect/unstable/http/HttpPlatform\";\nimport * as HttpServerRequest from \"effect/unstable/http/HttpServerRequest\";\nimport * as HttpServerResponse from \"effect/unstable/http/HttpServerResponse\";\nimport * as Path from \"effect/Path\";\nimport { Config, Context, Effect, Option, Stream } from \"effect\";\n\n/**\n * The services required by the SvelteKit HTTP application. All of them are\n * provided by `NodeHttpServer.layer` from `@effect/platform-node`.\n */\nexport type SvelteKitHttpAppServices =\n | FileSystem.FileSystem\n | Path.Path\n | HttpPlatform.HttpPlatform;\n\n/**\n * The SvelteKit application as a composable Effect HTTP handler: an effect that\n * produces an {@link HttpServerResponse.HttpServerResponse} for the current\n * request. The `HttpServerRequest` is supplied per request by the server.\n */\nexport type SvelteKitHttpApp = Effect.Effect<\n HttpServerResponse.HttpServerResponse,\n never,\n SvelteKitHttpAppServices | HttpServerRequest.HttpServerRequest\n>;\n\nexport interface SvelteKitHandlerOptions {\n /** The SvelteKit `Server` class exported by the build output (`./server/index.js`). */\n readonly Server: typeof SvelteKitServer;\n /** The SSR manifest exported by the build output (`./manifest.js`). */\n readonly manifest: SSRManifest;\n /** The set of prerendered paths exported by the build output (`./manifest.js`). */\n readonly prerendered: ReadonlySet<string>;\n /** The configured `paths.base`, exported by the build output (`./manifest.js`). */\n readonly base: string;\n /** Absolute path of the build output directory (contains `client/`, `server/`, `prerendered/`). */\n readonly dir: string;\n /** Environment variables passed to the SvelteKit server (e.g. `process.env`). */\n readonly env?: Record<string, string | undefined>;\n /** Prefix for the runtime configuration environment variables (ORIGIN, ...). */\n readonly envPrefix?: string;\n}\n\n/** Runtime configuration, read from the environment (compatible with adapter-node). */\nconst handlerConfig = (prefix: string) =>\n Config.all({\n origin: Config.option(Config.string(`${prefix}ORIGIN`)),\n protocolHeader: Config.option(Config.string(`${prefix}PROTOCOL_HEADER`)),\n hostHeader: Config.option(Config.string(`${prefix}HOST_HEADER`)),\n portHeader: Config.option(Config.string(`${prefix}PORT_HEADER`)),\n addressHeader: Config.option(Config.string(`${prefix}ADDRESS_HEADER`)),\n xffDepth: Config.int(`${prefix}XFF_DEPTH`).pipe(Config.withDefault(1)),\n });\n\ntype HandlerConfig = Effect.Success<ReturnType<typeof handlerConfig>>;\n\n/** Compute the request origin from configuration and headers (adapter-node semantics). */\nconst originOf = (request: HttpServerRequest.HttpServerRequest, config: HandlerConfig): string => {\n if (Option.isSome(config.origin)) return config.origin.value;\n\n const headers = request.headers;\n const protocolHeader = Option.getOrUndefined(config.protocolHeader)?.toLowerCase();\n const hostHeader = Option.getOrUndefined(config.hostHeader)?.toLowerCase() ?? \"host\";\n const portHeader = Option.getOrUndefined(config.portHeader)?.toLowerCase();\n\n const protocol = (protocolHeader && headers[protocolHeader]) || \"http\";\n const host = headers[hostHeader] ?? \"localhost\";\n const port = portHeader ? headers[portHeader] : undefined;\n\n return port ? `${protocol}://${host.split(\":\")[0]}:${port}` : `${protocol}://${host}`;\n};\n\n/** Resolve the client address, honouring ADDRESS_HEADER / XFF_DEPTH if configured. */\nconst clientAddressOf = (\n request: HttpServerRequest.HttpServerRequest,\n config: HandlerConfig,\n): string => {\n const addressHeader = Option.getOrUndefined(config.addressHeader)?.toLowerCase();\n if (addressHeader) {\n const value = request.headers[addressHeader];\n if (value) {\n if (addressHeader === \"x-forwarded-for\") {\n const addresses = value.split(\",\").map((address) => address.trim());\n if (config.xffDepth < 1 || config.xffDepth > addresses.length) {\n return addresses[0] ?? \"\";\n }\n return addresses[addresses.length - config.xffDepth]!;\n }\n return value;\n }\n }\n return Option.getOrElse(request.remoteAddress, () => \"\");\n};\n\n/** Build a web `Request` for SvelteKit from an `HttpServerRequest`. */\nconst toWebRequest = (\n request: HttpServerRequest.HttpServerRequest,\n url: URL,\n context: Context.Context<never>,\n): Request => {\n // `Headers` is a redactable record-like value; copy the enumerable string\n // entries into a plain record for the web `Request` constructor.\n const init: RequestInit & { duplex?: \"half\" } = {\n method: request.method,\n headers: { ...request.headers },\n };\n if (HttpMethod.hasBody(request.method)) {\n init.body = Stream.toReadableStreamWith(request.stream, context);\n init.duplex = \"half\";\n }\n return new Request(url, init);\n};\n\nconst IMMUTABLE_HEADERS = { \"cache-control\": \"public, max-age=31536000, immutable\" };\n\n/**\n * Creates the SvelteKit HTTP handler.\n *\n * The returned effect initialises the SvelteKit server (running your app's\n * server `init` hook) and yields an HTTP handler that:\n *\n * 1. serves static client assets from `<dir>/client` (with immutable cache\n * headers for hashed build assets),\n * 2. serves prerendered pages from `<dir>/prerendered`,\n * 3. falls back to server-side rendering via the SvelteKit server.\n *\n * @example\n * ```ts\n * // Mounting SvelteKit inside an existing effect/unstable/http server:\n * import { handler } from \"./build/handler.js\";\n * import * as HttpServer from \"effect/unstable/http/HttpServer\";\n * import { NodeHttpServer, NodeRuntime } from \"@effect/platform-node\";\n * import { Effect, Layer } from \"effect\";\n * import { createServer } from \"node:http\";\n *\n * const serverLayer = Layer.unwrap(\n * Effect.map(handler, (app) => HttpServer.serve(app))\n * ).pipe(Layer.provide(NodeHttpServer.layer(createServer, { port: 3000 })));\n *\n * NodeRuntime.runMain(Layer.launch(serverLayer));\n * ```\n */\nexport const makeHandler = (\n options: SvelteKitHandlerOptions,\n): Effect.Effect<SvelteKitHttpApp, never, SvelteKitHttpAppServices> =>\n Effect.gen(function* () {\n const config = yield* handlerConfig(options.envPrefix ?? \"\").pipe(Effect.orDie);\n const fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const context = yield* Effect.context<never>();\n\n const clientDir = path.join(options.dir, \"client\");\n const prerenderedDir = path.join(options.dir, \"prerendered\");\n const serverDir = path.join(options.dir, \"server\");\n\n // Initialise the SvelteKit server. `read` backs the `read()` helper from\n // `$app/server`, streaming server assets from the build output.\n const server = new options.Server(options.manifest);\n yield* Effect.promise(() =>\n server.init({\n env: (options.env ?? {}) as Record<string, string>,\n read: (file) => Stream.toReadableStreamWith(fs.stream(path.join(serverDir, file)), context),\n }),\n );\n\n /**\n * Serve a file from `root` if `pathname` resolves to an existing file\n * inside it. Returns `Option.none()` when the file does not exist or the\n * path escapes `root`.\n */\n const serveFile = (\n root: string,\n pathname: string,\n headers?: Record<string, string>,\n ): Effect.Effect<\n Option.Option<HttpServerResponse.HttpServerResponse>,\n never,\n SvelteKitHttpAppServices\n > =>\n Effect.gen(function* () {\n const filePath = path.join(root, pathname);\n\n // Prevent path traversal outside of the served directory.\n const relative = path.relative(root, filePath);\n if (relative.startsWith(\"..\") || path.isAbsolute(relative)) {\n return Option.none();\n }\n\n const stat = yield* fs.stat(filePath).pipe(Effect.option);\n if (Option.isNone(stat) || stat.value.type !== \"File\") {\n return Option.none();\n }\n\n const response = yield* HttpServerResponse.file(filePath, { headers }).pipe(Effect.orDie);\n return Option.some(response);\n });\n\n /** Serve a prerendered page, trying the file layouts SvelteKit emits. */\n const servePrerendered = (\n pathname: string,\n ): Effect.Effect<\n Option.Option<HttpServerResponse.HttpServerResponse>,\n never,\n SvelteKitHttpAppServices\n > => {\n const candidates =\n pathname === \"/\"\n ? [\"/index.html\"]\n : pathname.endsWith(\"/\")\n ? [`${pathname}index.html`, `${pathname.slice(0, -1)}.html`]\n : [pathname, `${pathname}.html`, `${pathname}/index.html`];\n\n return Effect.gen(function* () {\n let found = Option.none<HttpServerResponse.HttpServerResponse>();\n for (const candidate of candidates) {\n if (Option.isSome(found)) break;\n found = yield* serveFile(prerenderedDir, candidate);\n }\n return found;\n });\n };\n\n const immutablePrefix = `/${options.manifest.appPath}/immutable/`;\n\n // The HTTP handler itself, evaluated once per request.\n return Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const requestContext = yield* Effect.context<never>();\n\n const origin = originOf(request, config);\n const url = new URL(request.url, origin);\n\n let pathname: string;\n try {\n pathname = decodeURIComponent(url.pathname);\n } catch {\n return HttpServerResponse.empty({ status: 400 });\n }\n\n // 1. Static client assets (hashed build assets get immutable caching).\n if (request.method === \"GET\" || request.method === \"HEAD\") {\n const isImmutable = pathname.startsWith(immutablePrefix);\n const asset = yield* serveFile(\n clientDir,\n pathname,\n isImmutable ? IMMUTABLE_HEADERS : undefined,\n );\n if (Option.isSome(asset)) return asset.value;\n\n // 2. Prerendered pages.\n if (options.prerendered.has(pathname) || options.prerendered.has(pathname + \"/\")) {\n const page = yield* servePrerendered(pathname);\n if (Option.isSome(page)) return page.value;\n }\n }\n\n // 3. Server-side rendering.\n const webRequest = toWebRequest(request, url, requestContext);\n const response = yield* Effect.promise(() =>\n server.respond(webRequest, {\n getClientAddress: () => clientAddressOf(request, config),\n }),\n );\n return HttpServerResponse.fromWeb(response);\n });\n });\n"],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Adapter } from "@sveltejs/kit";
|
|
2
|
+
|
|
3
|
+
//#region src/adapter/index.d.ts
|
|
4
|
+
interface EffectAdapterOptions {
|
|
5
|
+
/**
|
|
6
|
+
* The directory to write the production build to.
|
|
7
|
+
* @default "build"
|
|
8
|
+
*/
|
|
9
|
+
readonly out?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Precompress static assets and prerendered pages with gzip and brotli.
|
|
12
|
+
* @default true
|
|
13
|
+
*/
|
|
14
|
+
readonly precompress?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Prefix for the environment variables the production server reads
|
|
17
|
+
* (`PORT`, `HOST`, `ORIGIN`, `PROTOCOL_HEADER`, `HOST_HEADER`,
|
|
18
|
+
* `PORT_HEADER`, `ADDRESS_HEADER`, `XFF_DEPTH`).
|
|
19
|
+
* @default ""
|
|
20
|
+
*/
|
|
21
|
+
readonly envPrefix?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Creates the Effect SvelteKit adapter.
|
|
25
|
+
*/
|
|
26
|
+
declare function effectAdapter(options?: EffectAdapterOptions): Adapter;
|
|
27
|
+
//#endregion
|
|
28
|
+
export { EffectAdapterOptions, effectAdapter as default, effectAdapter };
|
|
29
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
//#region src/adapter/index.ts
|
|
3
|
+
/**
|
|
4
|
+
* Effect deployment adapter for SvelteKit.
|
|
5
|
+
*
|
|
6
|
+
* Like `@sveltejs/adapter-node`, but the production server is an
|
|
7
|
+
* `effect/unstable/http` HTTP server: requests are handled by an Effect HTTP
|
|
8
|
+
* handler, configuration is read through `Config`, and startup/shutdown is
|
|
9
|
+
* managed by Effect layers and scopes (SIGINT/SIGTERM trigger a graceful
|
|
10
|
+
* shutdown that runs all finalizers).
|
|
11
|
+
*
|
|
12
|
+
* ```js
|
|
13
|
+
* // svelte.config.js
|
|
14
|
+
* import adapter from "@thomasfosterau/effect-sveltekit/adapter";
|
|
15
|
+
*
|
|
16
|
+
* const config = {
|
|
17
|
+
* kit: { adapter: adapter() },
|
|
18
|
+
* };
|
|
19
|
+
*
|
|
20
|
+
* export default config;
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* The build output (`build/` by default) contains:
|
|
24
|
+
*
|
|
25
|
+
* - `index.js` — runs the production server: `node build`
|
|
26
|
+
* - `handler.js` — exports the SvelteKit app as a composable Effect HTTP
|
|
27
|
+
* handler, for mounting inside an existing `effect/unstable/http` server
|
|
28
|
+
* - `client/`, `server/`, `prerendered/`, `manifest.js` — the built app
|
|
29
|
+
*
|
|
30
|
+
* Unlike `adapter-node`, the server output is not bundled: the deployment
|
|
31
|
+
* needs `node_modules` with the app's production dependencies installed
|
|
32
|
+
* (including `@thomasfosterau/effect-sveltekit`, `effect`, and
|
|
33
|
+
* `@effect/platform-node`).
|
|
34
|
+
*/
|
|
35
|
+
const PACKAGE = "@thomasfosterau/effect-sveltekit";
|
|
36
|
+
/** Template for the generated `<out>/handler.js`. */
|
|
37
|
+
const handlerTemplate = (envPrefix) => `\
|
|
38
|
+
import { dirname } from "node:path";
|
|
39
|
+
import { fileURLToPath } from "node:url";
|
|
40
|
+
import process from "node:process";
|
|
41
|
+
import { makeHandler } from "${PACKAGE}/adapter/handler";
|
|
42
|
+
import { Server } from "./server/index.js";
|
|
43
|
+
import { manifest, prerendered, base } from "./manifest.js";
|
|
44
|
+
|
|
45
|
+
const dir = dirname(fileURLToPath(import.meta.url));
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The SvelteKit application as a composable Effect HTTP handler.
|
|
49
|
+
*
|
|
50
|
+
* Serve it with the generated index.js, or mount it inside any
|
|
51
|
+
* \`effect/unstable/http\` HTTP server.
|
|
52
|
+
*/
|
|
53
|
+
export const handler = makeHandler({
|
|
54
|
+
Server,
|
|
55
|
+
manifest,
|
|
56
|
+
prerendered,
|
|
57
|
+
base,
|
|
58
|
+
dir,
|
|
59
|
+
env: process.env,
|
|
60
|
+
envPrefix: ${JSON.stringify(envPrefix)},
|
|
61
|
+
});
|
|
62
|
+
`;
|
|
63
|
+
/** Template for the generated `<out>/index.js`. */
|
|
64
|
+
const entryTemplate = (envPrefix) => `\
|
|
65
|
+
import { serve } from "${PACKAGE}/adapter/serve";
|
|
66
|
+
import { handler } from "./handler.js";
|
|
67
|
+
|
|
68
|
+
serve(handler, { envPrefix: ${JSON.stringify(envPrefix)} });
|
|
69
|
+
`;
|
|
70
|
+
/**
|
|
71
|
+
* Creates the Effect SvelteKit adapter.
|
|
72
|
+
*/
|
|
73
|
+
function effectAdapter(options = {}) {
|
|
74
|
+
const { out = "build", precompress = true, envPrefix = "" } = options;
|
|
75
|
+
return {
|
|
76
|
+
name: `${PACKAGE}/adapter`,
|
|
77
|
+
async adapt(builder) {
|
|
78
|
+
const base = builder.config.kit.paths.base;
|
|
79
|
+
builder.rimraf(out);
|
|
80
|
+
builder.mkdirp(out);
|
|
81
|
+
builder.log.minor("Copying assets");
|
|
82
|
+
builder.writeClient(`${out}/client${base}`);
|
|
83
|
+
builder.writePrerendered(`${out}/prerendered${base}`);
|
|
84
|
+
if (precompress) {
|
|
85
|
+
builder.log.minor("Compressing assets");
|
|
86
|
+
await Promise.all([builder.compress(`${out}/client`), builder.compress(`${out}/prerendered`)]);
|
|
87
|
+
}
|
|
88
|
+
builder.log.minor("Building server");
|
|
89
|
+
builder.writeServer(`${out}/server`);
|
|
90
|
+
writeFileSync(`${out}/manifest.js`, [
|
|
91
|
+
`export const manifest = ${builder.generateManifest({ relativePath: "./server" })};`,
|
|
92
|
+
`export const prerendered = new Set(${JSON.stringify(builder.prerendered.paths)});`,
|
|
93
|
+
`export const base = ${JSON.stringify(base)};`
|
|
94
|
+
].join("\n\n"));
|
|
95
|
+
builder.log.minor("Writing server entry");
|
|
96
|
+
writeFileSync(`${out}/handler.js`, handlerTemplate(envPrefix));
|
|
97
|
+
writeFileSync(`${out}/index.js`, entryTemplate(envPrefix));
|
|
98
|
+
},
|
|
99
|
+
supports: { read: () => true }
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
//#endregion
|
|
103
|
+
export { effectAdapter as default, effectAdapter };
|
|
104
|
+
|
|
105
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/adapter/index.ts"],"sourcesContent":["/**\n * Effect deployment adapter for SvelteKit.\n *\n * Like `@sveltejs/adapter-node`, but the production server is an\n * `effect/unstable/http` HTTP server: requests are handled by an Effect HTTP\n * handler, configuration is read through `Config`, and startup/shutdown is\n * managed by Effect layers and scopes (SIGINT/SIGTERM trigger a graceful\n * shutdown that runs all finalizers).\n *\n * ```js\n * // svelte.config.js\n * import adapter from \"@thomasfosterau/effect-sveltekit/adapter\";\n *\n * const config = {\n * kit: { adapter: adapter() },\n * };\n *\n * export default config;\n * ```\n *\n * The build output (`build/` by default) contains:\n *\n * - `index.js` — runs the production server: `node build`\n * - `handler.js` — exports the SvelteKit app as a composable Effect HTTP\n * handler, for mounting inside an existing `effect/unstable/http` server\n * - `client/`, `server/`, `prerendered/`, `manifest.js` — the built app\n *\n * Unlike `adapter-node`, the server output is not bundled: the deployment\n * needs `node_modules` with the app's production dependencies installed\n * (including `@thomasfosterau/effect-sveltekit`, `effect`, and\n * `@effect/platform-node`).\n */\n\nimport { writeFileSync } from \"node:fs\";\nimport type { Adapter } from \"@sveltejs/kit\";\n\nexport interface EffectAdapterOptions {\n /**\n * The directory to write the production build to.\n * @default \"build\"\n */\n readonly out?: string;\n /**\n * Precompress static assets and prerendered pages with gzip and brotli.\n * @default true\n */\n readonly precompress?: boolean;\n /**\n * Prefix for the environment variables the production server reads\n * (`PORT`, `HOST`, `ORIGIN`, `PROTOCOL_HEADER`, `HOST_HEADER`,\n * `PORT_HEADER`, `ADDRESS_HEADER`, `XFF_DEPTH`).\n * @default \"\"\n */\n readonly envPrefix?: string;\n}\n\nconst PACKAGE = \"@thomasfosterau/effect-sveltekit\";\n\n/** Template for the generated `<out>/handler.js`. */\nconst handlerTemplate = (envPrefix: string): string => `\\\nimport { dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport process from \"node:process\";\nimport { makeHandler } from \"${PACKAGE}/adapter/handler\";\nimport { Server } from \"./server/index.js\";\nimport { manifest, prerendered, base } from \"./manifest.js\";\n\nconst dir = dirname(fileURLToPath(import.meta.url));\n\n/**\n * The SvelteKit application as a composable Effect HTTP handler.\n *\n * Serve it with the generated index.js, or mount it inside any\n * \\`effect/unstable/http\\` HTTP server.\n */\nexport const handler = makeHandler({\n Server,\n manifest,\n prerendered,\n base,\n dir,\n env: process.env,\n envPrefix: ${JSON.stringify(envPrefix)},\n});\n`;\n\n/** Template for the generated `<out>/index.js`. */\nconst entryTemplate = (envPrefix: string): string => `\\\nimport { serve } from \"${PACKAGE}/adapter/serve\";\nimport { handler } from \"./handler.js\";\n\nserve(handler, { envPrefix: ${JSON.stringify(envPrefix)} });\n`;\n\n/**\n * Creates the Effect SvelteKit adapter.\n */\nexport function effectAdapter(options: EffectAdapterOptions = {}): Adapter {\n const { out = \"build\", precompress = true, envPrefix = \"\" } = options;\n\n return {\n name: `${PACKAGE}/adapter`,\n\n async adapt(builder) {\n const base = builder.config.kit.paths.base;\n\n builder.rimraf(out);\n builder.mkdirp(out);\n\n builder.log.minor(\"Copying assets\");\n builder.writeClient(`${out}/client${base}`);\n builder.writePrerendered(`${out}/prerendered${base}`);\n\n if (precompress) {\n builder.log.minor(\"Compressing assets\");\n await Promise.all([\n builder.compress(`${out}/client`),\n builder.compress(`${out}/prerendered`),\n ]);\n }\n\n builder.log.minor(\"Building server\");\n builder.writeServer(`${out}/server`);\n\n writeFileSync(\n `${out}/manifest.js`,\n [\n `export const manifest = ${builder.generateManifest({ relativePath: \"./server\" })};`,\n `export const prerendered = new Set(${JSON.stringify(builder.prerendered.paths)});`,\n `export const base = ${JSON.stringify(base)};`,\n ].join(\"\\n\\n\"),\n );\n\n builder.log.minor(\"Writing server entry\");\n writeFileSync(`${out}/handler.js`, handlerTemplate(envPrefix));\n writeFileSync(`${out}/index.js`, entryTemplate(envPrefix));\n },\n\n supports: {\n // `read()` from `$app/server` is backed by the platform FileSystem.\n read: () => true,\n },\n };\n}\n\nexport default effectAdapter;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,MAAM,UAAU;;AAGhB,MAAM,mBAAmB,cAA8B;;;;+BAIxB,QAAQ;;;;;;;;;;;;;;;;;;;eAmBxB,KAAK,UAAU,SAAS,EAAE;;;;AAKzC,MAAM,iBAAiB,cAA8B;yBAC5B,QAAQ;;;8BAGH,KAAK,UAAU,SAAS,EAAE;;;;;AAMxD,SAAgB,cAAc,UAAgC,CAAC,GAAY;CACzE,MAAM,EAAE,MAAM,SAAS,cAAc,MAAM,YAAY,OAAO;CAE9D,OAAO;EACL,MAAM,GAAG,QAAQ;EAEjB,MAAM,MAAM,SAAS;GACnB,MAAM,OAAO,QAAQ,OAAO,IAAI,MAAM;GAEtC,QAAQ,OAAO,GAAG;GAClB,QAAQ,OAAO,GAAG;GAElB,QAAQ,IAAI,MAAM,gBAAgB;GAClC,QAAQ,YAAY,GAAG,IAAI,SAAS,MAAM;GAC1C,QAAQ,iBAAiB,GAAG,IAAI,cAAc,MAAM;GAEpD,IAAI,aAAa;IACf,QAAQ,IAAI,MAAM,oBAAoB;IACtC,MAAM,QAAQ,IAAI,CAChB,QAAQ,SAAS,GAAG,IAAI,QAAQ,GAChC,QAAQ,SAAS,GAAG,IAAI,aAAa,CACvC,CAAC;GACH;GAEA,QAAQ,IAAI,MAAM,iBAAiB;GACnC,QAAQ,YAAY,GAAG,IAAI,QAAQ;GAEnC,cACE,GAAG,IAAI,eACP;IACE,2BAA2B,QAAQ,iBAAiB,EAAE,cAAc,WAAW,CAAC,EAAE;IAClF,sCAAsC,KAAK,UAAU,QAAQ,YAAY,KAAK,EAAE;IAChF,uBAAuB,KAAK,UAAU,IAAI,EAAE;GAC9C,EAAE,KAAK,MAAM,CACf;GAEA,QAAQ,IAAI,MAAM,sBAAsB;GACxC,cAAc,GAAG,IAAI,cAAc,gBAAgB,SAAS,CAAC;GAC7D,cAAc,GAAG,IAAI,YAAY,cAAc,SAAS,CAAC;EAC3D;EAEA,UAAU,EAER,YAAY,KACd;CACF;AACF"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { SvelteKitHttpApp } from "./handler.js";
|
|
2
|
+
import { Effect, Layer } from "effect";
|
|
3
|
+
import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";
|
|
4
|
+
|
|
5
|
+
//#region src/adapter/serve.d.ts
|
|
6
|
+
interface ServeOptions {
|
|
7
|
+
/** Prefix for the configuration environment variables (PORT, HOST, ...). */
|
|
8
|
+
readonly envPrefix?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Additional layer merged into the server context, e.g. tracing or logging
|
|
11
|
+
* layers.
|
|
12
|
+
*/
|
|
13
|
+
readonly layer?: Layer.Layer<never, never>;
|
|
14
|
+
/**
|
|
15
|
+
* Disable the default request logger middleware.
|
|
16
|
+
*/
|
|
17
|
+
readonly disableLogger?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Builds the full server layer for a SvelteKit HttpApp: HTTP listener
|
|
21
|
+
* (`PORT` / `HOST` environment variables), request logging, and the
|
|
22
|
+
* platform services the handler needs.
|
|
23
|
+
*
|
|
24
|
+
* Launch it with `Layer.launch`, or merge it into a larger application.
|
|
25
|
+
*/
|
|
26
|
+
declare const layer: (handler: Effect.Effect<SvelteKitHttpApp, never, NodeHttpServerServices>, options?: ServeOptions) => Layer.Layer<never, unknown>;
|
|
27
|
+
/** The services provided to the handler effect by `NodeHttpServer.layerConfig`. */
|
|
28
|
+
type NodeHttpServerServices = Layer.Success<ReturnType<typeof NodeHttpServer.layerConfig>>;
|
|
29
|
+
/**
|
|
30
|
+
* Starts the production server and runs it until the process receives
|
|
31
|
+
* SIGINT/SIGTERM, at which point the server (and every Effect resource) is
|
|
32
|
+
* shut down gracefully.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* // build/index.js (generated by the adapter)
|
|
37
|
+
* import { handler } from "./handler.js";
|
|
38
|
+
* import { serve } from "@thomasfosterau/effect-sveltekit/adapter/serve";
|
|
39
|
+
*
|
|
40
|
+
* serve(handler);
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
declare const serve: (handler: Effect.Effect<SvelteKitHttpApp, never, NodeHttpServerServices>, options?: ServeOptions) => void;
|
|
44
|
+
//#endregion
|
|
45
|
+
export { ServeOptions, layer, serve };
|
|
46
|
+
//# sourceMappingURL=serve.d.ts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Config, Effect, Layer } from "effect";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import * as HttpMiddleware from "effect/unstable/http/HttpMiddleware";
|
|
4
|
+
import * as HttpServer from "effect/unstable/http/HttpServer";
|
|
5
|
+
import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";
|
|
6
|
+
import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
|
|
7
|
+
//#region src/adapter/serve.ts
|
|
8
|
+
/**
|
|
9
|
+
* Production server entry for the Effect SvelteKit adapter.
|
|
10
|
+
*
|
|
11
|
+
* The adapter's generated `build/index.js` calls {@link serve} with the
|
|
12
|
+
* handler from `build/handler.js`. The server runs on
|
|
13
|
+
* `@effect/platform-node`'s `NodeHttpServer`, with configuration read from
|
|
14
|
+
* the environment and graceful shutdown handled by Effect scopes.
|
|
15
|
+
*
|
|
16
|
+
* For full control (custom layers, tracing, additional routes), skip the
|
|
17
|
+
* generated entry and compose {@link layer} — or the handler itself — into
|
|
18
|
+
* your own Effect application.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Builds the full server layer for a SvelteKit HttpApp: HTTP listener
|
|
22
|
+
* (`PORT` / `HOST` environment variables), request logging, and the
|
|
23
|
+
* platform services the handler needs.
|
|
24
|
+
*
|
|
25
|
+
* Launch it with `Layer.launch`, or merge it into a larger application.
|
|
26
|
+
*/
|
|
27
|
+
const layer = (handler, options = {}) => {
|
|
28
|
+
const prefix = options.envPrefix ?? "";
|
|
29
|
+
const serveLayer = Layer.unwrap(Effect.map(handler, (app) => options.disableLogger ? HttpServer.serve(app) : HttpServer.serve(app, HttpMiddleware.logger)));
|
|
30
|
+
const listenLayer = NodeHttpServer.layerConfig(createServer, {
|
|
31
|
+
port: Config.int(`${prefix}PORT`).pipe(Config.withDefault(3e3)),
|
|
32
|
+
host: Config.string(`${prefix}HOST`).pipe(Config.withDefault("0.0.0.0"))
|
|
33
|
+
});
|
|
34
|
+
const withListener = serveLayer.pipe(HttpServer.withLogAddress, Layer.provide(listenLayer));
|
|
35
|
+
return options.layer ? Layer.merge(withListener, options.layer) : withListener;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Starts the production server and runs it until the process receives
|
|
39
|
+
* SIGINT/SIGTERM, at which point the server (and every Effect resource) is
|
|
40
|
+
* shut down gracefully.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* // build/index.js (generated by the adapter)
|
|
45
|
+
* import { handler } from "./handler.js";
|
|
46
|
+
* import { serve } from "@thomasfosterau/effect-sveltekit/adapter/serve";
|
|
47
|
+
*
|
|
48
|
+
* serve(handler);
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
const serve = (handler, options = {}) => {
|
|
52
|
+
NodeRuntime.runMain(Layer.launch(layer(handler, options)));
|
|
53
|
+
};
|
|
54
|
+
//#endregion
|
|
55
|
+
export { layer, serve };
|
|
56
|
+
|
|
57
|
+
//# sourceMappingURL=serve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.js","names":[],"sources":["../../src/adapter/serve.ts"],"sourcesContent":["/**\n * Production server entry for the Effect SvelteKit adapter.\n *\n * The adapter's generated `build/index.js` calls {@link serve} with the\n * handler from `build/handler.js`. The server runs on\n * `@effect/platform-node`'s `NodeHttpServer`, with configuration read from\n * the environment and graceful shutdown handled by Effect scopes.\n *\n * For full control (custom layers, tracing, additional routes), skip the\n * generated entry and compose {@link layer} — or the handler itself — into\n * your own Effect application.\n */\n\nimport { createServer } from \"node:http\";\nimport * as HttpMiddleware from \"effect/unstable/http/HttpMiddleware\";\nimport * as HttpServer from \"effect/unstable/http/HttpServer\";\nimport * as NodeHttpServer from \"@effect/platform-node/NodeHttpServer\";\nimport * as NodeRuntime from \"@effect/platform-node/NodeRuntime\";\nimport { Config, Effect, Layer } from \"effect\";\nimport type { SvelteKitHttpApp } from \"./handler.js\";\n\nexport interface ServeOptions {\n /** Prefix for the configuration environment variables (PORT, HOST, ...). */\n readonly envPrefix?: string;\n /**\n * Additional layer merged into the server context, e.g. tracing or logging\n * layers.\n */\n readonly layer?: Layer.Layer<never, never>;\n /**\n * Disable the default request logger middleware.\n */\n readonly disableLogger?: boolean;\n}\n\n/**\n * Builds the full server layer for a SvelteKit HttpApp: HTTP listener\n * (`PORT` / `HOST` environment variables), request logging, and the\n * platform services the handler needs.\n *\n * Launch it with `Layer.launch`, or merge it into a larger application.\n */\nexport const layer = (\n handler: Effect.Effect<SvelteKitHttpApp, never, NodeHttpServerServices>,\n options: ServeOptions = {},\n): Layer.Layer<never, unknown> => {\n const prefix = options.envPrefix ?? \"\";\n\n const serveLayer = Layer.unwrap(\n Effect.map(handler, (app) =>\n options.disableLogger ? HttpServer.serve(app) : HttpServer.serve(app, HttpMiddleware.logger),\n ),\n );\n\n const listenLayer = NodeHttpServer.layerConfig(createServer, {\n port: Config.int(`${prefix}PORT`).pipe(Config.withDefault(3000)),\n host: Config.string(`${prefix}HOST`).pipe(Config.withDefault(\"0.0.0.0\")),\n });\n\n const withListener = serveLayer.pipe(HttpServer.withLogAddress, Layer.provide(listenLayer));\n\n return options.layer ? Layer.merge(withListener, options.layer) : withListener;\n};\n\n/** The services provided to the handler effect by `NodeHttpServer.layerConfig`. */\ntype NodeHttpServerServices = Layer.Success<ReturnType<typeof NodeHttpServer.layerConfig>>;\n\n/**\n * Starts the production server and runs it until the process receives\n * SIGINT/SIGTERM, at which point the server (and every Effect resource) is\n * shut down gracefully.\n *\n * @example\n * ```ts\n * // build/index.js (generated by the adapter)\n * import { handler } from \"./handler.js\";\n * import { serve } from \"@thomasfosterau/effect-sveltekit/adapter/serve\";\n *\n * serve(handler);\n * ```\n */\nexport const serve = (\n handler: Effect.Effect<SvelteKitHttpApp, never, NodeHttpServerServices>,\n options: ServeOptions = {},\n): void => {\n NodeRuntime.runMain(Layer.launch(layer(handler, options)));\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,MAAa,SACX,SACA,UAAwB,CAAC,MACO;CAChC,MAAM,SAAS,QAAQ,aAAa;CAEpC,MAAM,aAAa,MAAM,OACvB,OAAO,IAAI,UAAU,QACnB,QAAQ,gBAAgB,WAAW,MAAM,GAAG,IAAI,WAAW,MAAM,KAAK,eAAe,MAAM,CAC7F,CACF;CAEA,MAAM,cAAc,eAAe,YAAY,cAAc;EAC3D,MAAM,OAAO,IAAI,GAAG,OAAO,KAAK,EAAE,KAAK,OAAO,YAAY,GAAI,CAAC;EAC/D,MAAM,OAAO,OAAO,GAAG,OAAO,KAAK,EAAE,KAAK,OAAO,YAAY,SAAS,CAAC;CACzE,CAAC;CAED,MAAM,eAAe,WAAW,KAAK,WAAW,gBAAgB,MAAM,QAAQ,WAAW,CAAC;CAE1F,OAAO,QAAQ,QAAQ,MAAM,MAAM,cAAc,QAAQ,KAAK,IAAI;AACpE;;;;;;;;;;;;;;;AAmBA,MAAa,SACX,SACA,UAAwB,CAAC,MAChB;CACT,YAAY,QAAQ,MAAM,OAAO,MAAM,SAAS,OAAO,CAAC,CAAC;AAC3D"}
|