@thomasfosterau/effect-sveltekit 0.4.1 → 0.6.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.md +9 -0
- package/README.md +183 -85
- package/dist/Hooks.d.ts +1 -1
- package/dist/Hooks.js.map +1 -1
- package/dist/Navigation.d.ts +83 -19
- package/dist/Navigation.js +93 -20
- package/dist/Navigation.js.map +1 -1
- package/dist/Remote.d.ts +37 -19
- package/dist/Remote.js +20 -7
- package/dist/Remote.js.map +1 -1
- package/dist/State.d.ts +77 -8
- package/dist/State.js +116 -27
- package/dist/State.js.map +1 -1
- package/dist/adapter/handler.d.ts +14 -12
- package/dist/adapter/handler.js +5 -7
- package/dist/adapter/handler.js.map +1 -1
- package/dist/adapter/index.js +25 -21
- package/dist/adapter/index.js.map +1 -1
- package/dist/api-client.js +1 -1
- package/dist/api-client.js.map +1 -1
- package/dist/app-runtime.d.ts +7 -6
- package/dist/app-runtime.js +5 -5
- package/dist/app-runtime.js.map +1 -1
- package/dist/client/Hooks.d.ts +1 -1
- package/dist/client/Hooks.js.map +1 -1
- package/dist/errors.js +3 -2
- package/dist/errors.js.map +1 -1
- package/dist/forms.d.ts +72 -9
- package/dist/forms.js +66 -5
- package/dist/forms.js.map +1 -1
- package/dist/internal/app-state.d.ts +38 -3
- package/dist/internal/app-state.js +58 -15
- package/dist/internal/app-state.js.map +1 -1
- package/dist/internal/auto.d.ts +2 -1
- package/dist/internal/auto.js.map +1 -1
- package/dist/internal/enhance.d.ts +107 -9
- package/dist/internal/enhance.js +30 -6
- package/dist/internal/enhance.js.map +1 -1
- package/dist/internal/error.js +23 -2
- package/dist/internal/error.js.map +1 -1
- package/dist/internal/remote-run.d.ts +3 -3
- package/dist/internal/remote-run.js.map +1 -1
- package/dist/internal/watch.svelte.js +33 -0
- package/dist/internal/watch.svelte.js.map +1 -0
- package/dist/kit.js +3 -3
- package/dist/kit.js.map +1 -1
- package/dist/realtime.d.ts +28 -7
- package/dist/realtime.js +68 -43
- package/dist/realtime.js.map +1 -1
- package/dist/remote-functions.d.ts +4 -4
- package/dist/remote-functions.js.map +1 -1
- package/dist/remote-reactive.d.ts +1 -1
- package/dist/remote-reactive.js.map +1 -1
- package/dist/response-headers.d.ts +1 -1
- package/dist/response-headers.js.map +1 -1
- package/dist/server/Endpoint.d.ts +2 -2
- package/dist/server/Endpoint.js +2 -2
- package/dist/server/Endpoint.js.map +1 -1
- package/dist/server/Hooks.d.ts +1 -1
- package/dist/server/Hooks.js.map +1 -1
- package/dist/transport.d.ts +1 -1
- package/dist/transport.js.map +1 -1
- package/dist/vite.d.ts +2 -2
- package/dist/vite.js.map +1 -1
- package/package.json +10 -8
- package/LICENSE +0 -21
package/dist/State.js
CHANGED
|
@@ -1,34 +1,48 @@
|
|
|
1
1
|
import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
2
|
-
import { pageSnapshotStream } from "./internal/app-state.js";
|
|
2
|
+
import { UpdateCheckError, checkForUpdateWith, makeCurrentPage, pageSnapshotStream, snapshotNavigating, watchedStream } from "./internal/app-state.js";
|
|
3
|
+
import { watch } from "./internal/watch.svelte.js";
|
|
4
|
+
import { Context, Layer } from "effect";
|
|
3
5
|
import { afterNavigate } from "$app/navigation";
|
|
4
|
-
import { page } from "$app/state";
|
|
6
|
+
import { navigating, page, updated } from "$app/state";
|
|
5
7
|
//#region src/State.ts
|
|
6
8
|
/**
|
|
7
|
-
*
|
|
8
|
-
* [`$app/state`](https://svelte.dev/docs/kit/$app-state) `page
|
|
9
|
+
* An Effect-side bridge for SvelteKit's
|
|
10
|
+
* [`$app/state`](https://svelte.dev/docs/kit/$app-state): `page`, `navigating`
|
|
11
|
+
* and `updated`.
|
|
9
12
|
*
|
|
10
|
-
* `$app/state` exposes rune-reactive
|
|
11
|
-
*
|
|
12
|
-
* drive Effect pipelines without touching Svelte runes:
|
|
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.
|
|
13
|
+
* `$app/state` exposes rune-reactive objects for use inside components. This
|
|
14
|
+
* module turns them into Effect values so navigation and version state can
|
|
15
|
+
* drive Effect pipelines without touching Svelte runes:
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
17
|
+
* - {@link page} — a `Stream` of page snapshots, emitted now and after each
|
|
18
|
+
* navigation (via `afterNavigate`, so call it during component
|
|
19
|
+
* initialization).
|
|
20
|
+
* - {@link navigating} — a `Stream` of the in-flight navigation as an
|
|
21
|
+
* `Option` (`None` when idle).
|
|
22
|
+
* - {@link updated} / {@link checkForUpdate} — whether a new app version has
|
|
23
|
+
* been deployed, as a `Stream<boolean>` and as an on-demand check.
|
|
24
|
+
* - {@link CurrentPage} — a `Context` service exposing the current page to any
|
|
25
|
+
* client Effect; provide {@link CurrentPage.layer} in your client runtime.
|
|
23
26
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
+
* Each page emission is a *shallow snapshot* (`url`, `params`, `route`, `data`,
|
|
28
|
+
* `status`, `error`, `form`, `state`, `shallow`) rather than the live
|
|
29
|
+
* `$app/state` proxy: the proxy mutates in place on every navigation, so a
|
|
30
|
+
* captured reference is unreliable for downstream consumers — the snapshot
|
|
31
|
+
* gives each emission a stable value. `navigating` emissions are snapshots for
|
|
32
|
+
* the same reason.
|
|
27
33
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
34
|
+
* `navigating`, `updated` and `CurrentPage.changes` watch the rune state with a
|
|
35
|
+
* root `$effect`, so they work anywhere on the client (not only during
|
|
36
|
+
* component initialization). Each stream is cold: a run opens its own watcher,
|
|
37
|
+
* emits the current value on the next Svelte flush and again whenever it
|
|
38
|
+
* changes, and disposes the watcher when the stream ends or is interrupted.
|
|
39
|
+
* All of this is client-only: during server rendering the watched streams
|
|
40
|
+
* never emit, so consuming one there (for example with `Stream.runHead`)
|
|
41
|
+
* hangs rather than failing.
|
|
42
|
+
*
|
|
43
|
+
* These are streams and a service rather than atoms; an atom-based variant
|
|
44
|
+
* may follow once the official `@effect/atom-svelte` integration lands (see
|
|
45
|
+
* issue #23).
|
|
32
46
|
*
|
|
33
47
|
* @example
|
|
34
48
|
* ```svelte
|
|
@@ -46,11 +60,20 @@ import { page } from "$app/state";
|
|
|
46
60
|
* <\/script>
|
|
47
61
|
* ```
|
|
48
62
|
*/
|
|
49
|
-
var State_exports = /* @__PURE__ */ __exportAll({
|
|
63
|
+
var State_exports = /* @__PURE__ */ __exportAll({
|
|
64
|
+
CurrentPage: () => CurrentPage,
|
|
65
|
+
UpdateCheckError: () => UpdateCheckError,
|
|
66
|
+
checkForUpdate: () => checkForUpdate,
|
|
67
|
+
navigating: () => navigating$1,
|
|
68
|
+
page: () => page$1,
|
|
69
|
+
updated: () => updated$1
|
|
70
|
+
});
|
|
50
71
|
/**
|
|
51
72
|
* A `Stream` of the current SvelteKit `page`, emitting a shallow snapshot
|
|
52
|
-
* immediately and again after every navigation. Call during
|
|
53
|
-
* initialization.
|
|
73
|
+
* immediately and again after every navigation; never fails. Call during
|
|
74
|
+
* component initialization. It follows `afterNavigate`, so page changes
|
|
75
|
+
* without a navigation (shallow routing, form results) don't emit — use
|
|
76
|
+
* {@link CurrentPage}'s `changes` for those.
|
|
54
77
|
*/
|
|
55
78
|
function page$1() {
|
|
56
79
|
return pageSnapshotStream((callback) => {
|
|
@@ -59,7 +82,73 @@ function page$1() {
|
|
|
59
82
|
});
|
|
60
83
|
}, () => page);
|
|
61
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* A `Stream` of SvelteKit's in-flight navigation: `Option.some` with a
|
|
87
|
+
* {@link NavigatingSnapshot} while a navigation is in progress, `Option.none()`
|
|
88
|
+
* when idle. Emits the current state on the next Svelte flush, then on every
|
|
89
|
+
* change; never fails. Client-only, but callable anywhere (not only during
|
|
90
|
+
* component initialization).
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* const spinner = Stream.runForEach(State.navigating(), (nav) =>
|
|
95
|
+
* Effect.sync(() => (busy = Option.isSome(nav))),
|
|
96
|
+
* );
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
function navigating$1() {
|
|
100
|
+
return watchedStream(watch, () => snapshotNavigating(navigating));
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* A `Stream` of SvelteKit's `updated.current`: whether a new version of the app
|
|
104
|
+
* has been deployed since this one loaded. Emits the current value on the next
|
|
105
|
+
* Svelte flush, then on every change (SvelteKit only ever flips it from `false`
|
|
106
|
+
* to `true`); never fails. Client-only, but callable anywhere.
|
|
107
|
+
*/
|
|
108
|
+
function updated$1() {
|
|
109
|
+
return watchedStream(watch, () => updated.current);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Asks SvelteKit to check for a new app version now (`updated.check()`),
|
|
113
|
+
* succeeding with whether one is available; a positive result also flips
|
|
114
|
+
* {@link updated}. Always succeeds with `false` in development. Fails with
|
|
115
|
+
* {@link UpdateCheckError} if the check rejects. Client-only.
|
|
116
|
+
*/
|
|
117
|
+
function checkForUpdate() {
|
|
118
|
+
return checkForUpdateWith(() => updated.check());
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The current SvelteKit page as an Effect service, so any client Effect can read
|
|
122
|
+
* it with `yield* State.CurrentPage` instead of taking it as an argument.
|
|
123
|
+
* `get` snapshots the page when run; `changes` emits the current page on the
|
|
124
|
+
* next Svelte flush, then a fresh snapshot whenever any field changes
|
|
125
|
+
* (navigations, but also shallow routing and form results). Neither fails on
|
|
126
|
+
* the client; on the server, Kit's `page` throws outside synchronous component
|
|
127
|
+
* rendering (including after an async boundary in an Effect forked during
|
|
128
|
+
* render), so `get` dies with a defect there.
|
|
129
|
+
*
|
|
130
|
+
* Provide {@link CurrentPage.layer} (backed by `$app/state`) in your client
|
|
131
|
+
* runtime, or `Layer.succeed(State.CurrentPage, …)` in tests.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```ts
|
|
135
|
+
* const trackView = Effect.gen(function* () {
|
|
136
|
+
* const page = yield* (yield* State.CurrentPage).get;
|
|
137
|
+
* yield* analytics.view(page.url.pathname);
|
|
138
|
+
* });
|
|
139
|
+
*
|
|
140
|
+
* runtime.runFork(trackView.pipe(Effect.provide(State.CurrentPage.layer)));
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
var CurrentPage = class CurrentPage extends Context.Service()("@thomasfosterau/effect-sveltekit/CurrentPage") {
|
|
144
|
+
/**
|
|
145
|
+
* A layer providing {@link CurrentPage} from `$app/state`'s `page`. Builds
|
|
146
|
+
* instantly and never fails; client-only (during server rendering `changes`
|
|
147
|
+
* never emits, so consuming it there hangs).
|
|
148
|
+
*/
|
|
149
|
+
static layer = Layer.succeed(CurrentPage, makeCurrentPage(() => page, watch));
|
|
150
|
+
};
|
|
62
151
|
//#endregion
|
|
63
|
-
export { State_exports, page$1 as page };
|
|
152
|
+
export { CurrentPage, State_exports, UpdateCheckError, checkForUpdate, navigating$1 as navigating, page$1 as page, updated$1 as updated };
|
|
64
153
|
|
|
65
154
|
//# sourceMappingURL=State.js.map
|
package/dist/State.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"State.js","names":["page","kitPage"],"sources":["../src/State.ts"],"sourcesContent":["/**\n *
|
|
1
|
+
{"version":3,"file":"State.js","names":["page","kitPage","navigating","kitNavigating","updated","kitUpdated"],"sources":["../src/State.ts"],"sourcesContent":["/**\n * An Effect-side bridge for SvelteKit's\n * [`$app/state`](https://svelte.dev/docs/kit/$app-state): `page`, `navigating`\n * and `updated`.\n *\n * `$app/state` exposes rune-reactive objects for use inside components. This\n * module turns them into Effect values so navigation and version state can\n * drive Effect pipelines without touching Svelte runes:\n *\n * - {@link page} — a `Stream` of page snapshots, emitted now and after each\n * navigation (via `afterNavigate`, so call it during component\n * initialization).\n * - {@link navigating} — a `Stream` of the in-flight navigation as an\n * `Option` (`None` when idle).\n * - {@link updated} / {@link checkForUpdate} — whether a new app version has\n * been deployed, as a `Stream<boolean>` and as an on-demand check.\n * - {@link CurrentPage} — a `Context` service exposing the current page to any\n * client Effect; provide {@link CurrentPage.layer} in your client runtime.\n *\n * Each page emission is a *shallow snapshot* (`url`, `params`, `route`, `data`,\n * `status`, `error`, `form`, `state`, `shallow`) rather than the live\n * `$app/state` proxy: the proxy mutates in place on every navigation, so a\n * captured reference is unreliable for downstream consumers — the snapshot\n * gives each emission a stable value. `navigating` emissions are snapshots for\n * the same reason.\n *\n * `navigating`, `updated` and `CurrentPage.changes` watch the rune state with a\n * root `$effect`, so they work anywhere on the client (not only during\n * component initialization). Each stream is cold: a run opens its own watcher,\n * emits the current value on the next Svelte flush and again whenever it\n * changes, and disposes the watcher when the stream ends or is interrupted.\n * All of this is client-only: during server rendering the watched streams\n * never emit, so consuming one there (for example with `Stream.runHead`)\n * hangs rather than failing.\n *\n * These are streams and a service rather than atoms; an atom-based variant\n * may follow once the official `@effect/atom-svelte` integration lands (see\n * issue #23).\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 { navigating as kitNavigating, page as kitPage, updated as kitUpdated } from \"$app/state\";\nimport type { Page } from \"$app/state\";\nimport { Context, Layer } from \"effect\";\nimport type { Effect, Option, Stream } from \"effect\";\nimport {\n type CurrentPageShape,\n type NavigatingSnapshot,\n type PageSnapshot,\n type UpdateCheckError,\n checkForUpdateWith,\n makeCurrentPage,\n pageSnapshotStream,\n snapshotNavigating,\n watchedStream,\n} from \"./internal/app-state.js\";\nimport { watch } from \"./internal/watch.svelte.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 snapshot of an in-flight SvelteKit navigation (`type`, `from`, `to`,\n * `willUnload`, `delta`, `complete`) — see {@link navigating}.\n */\nexport type { NavigatingSnapshot } from \"./internal/app-state.js\";\n\n/**\n * The error {@link checkForUpdate} fails with when SvelteKit's\n * `updated.check()` rejects (misuse such as calling it during server\n * rendering; a failed version request resolves `false` instead). The original\n * error is on `cause`.\n */\nexport { UpdateCheckError } 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; never fails. Call during\n * component initialization. It follows `afterNavigate`, so page changes\n * without a navigation (shallow routing, form results) don't emit — use\n * {@link CurrentPage}'s `changes` for those.\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\n/**\n * A `Stream` of SvelteKit's in-flight navigation: `Option.some` with a\n * {@link NavigatingSnapshot} while a navigation is in progress, `Option.none()`\n * when idle. Emits the current state on the next Svelte flush, then on every\n * change; never fails. Client-only, but callable anywhere (not only during\n * component initialization).\n *\n * @example\n * ```ts\n * const spinner = Stream.runForEach(State.navigating(), (nav) =>\n * Effect.sync(() => (busy = Option.isSome(nav))),\n * );\n * ```\n */\nexport function navigating(): Stream.Stream<Option.Option<NavigatingSnapshot>> {\n return watchedStream(watch, () => snapshotNavigating(kitNavigating));\n}\n\n/**\n * A `Stream` of SvelteKit's `updated.current`: whether a new version of the app\n * has been deployed since this one loaded. Emits the current value on the next\n * Svelte flush, then on every change (SvelteKit only ever flips it from `false`\n * to `true`); never fails. Client-only, but callable anywhere.\n */\nexport function updated(): Stream.Stream<boolean> {\n return watchedStream(watch, () => kitUpdated.current);\n}\n\n/**\n * Asks SvelteKit to check for a new app version now (`updated.check()`),\n * succeeding with whether one is available; a positive result also flips\n * {@link updated}. Always succeeds with `false` in development. Fails with\n * {@link UpdateCheckError} if the check rejects. Client-only.\n */\nexport function checkForUpdate(): Effect.Effect<boolean, UpdateCheckError> {\n return checkForUpdateWith(() => kitUpdated.check());\n}\n\n/**\n * The service shape of {@link CurrentPage}: `get` snapshots the current page\n * and `changes` streams it. Neither fails on the client; see\n * {@link CurrentPage} for the server.\n */\nexport interface CurrentPageInterface extends CurrentPageShape<Page> {}\n\n/**\n * The current SvelteKit page as an Effect service, so any client Effect can read\n * it with `yield* State.CurrentPage` instead of taking it as an argument.\n * `get` snapshots the page when run; `changes` emits the current page on the\n * next Svelte flush, then a fresh snapshot whenever any field changes\n * (navigations, but also shallow routing and form results). Neither fails on\n * the client; on the server, Kit's `page` throws outside synchronous component\n * rendering (including after an async boundary in an Effect forked during\n * render), so `get` dies with a defect there.\n *\n * Provide {@link CurrentPage.layer} (backed by `$app/state`) in your client\n * runtime, or `Layer.succeed(State.CurrentPage, …)` in tests.\n *\n * @example\n * ```ts\n * const trackView = Effect.gen(function* () {\n * const page = yield* (yield* State.CurrentPage).get;\n * yield* analytics.view(page.url.pathname);\n * });\n *\n * runtime.runFork(trackView.pipe(Effect.provide(State.CurrentPage.layer)));\n * ```\n */\nexport class CurrentPage extends Context.Service<CurrentPage, CurrentPageInterface>()(\n \"@thomasfosterau/effect-sveltekit/CurrentPage\",\n) {\n /**\n * A layer providing {@link CurrentPage} from `$app/state`'s `page`. Builds\n * instantly and never fails; client-only (during server rendering `changes`\n * never emits, so consuming it there hangs).\n */\n static readonly layer: Layer.Layer<CurrentPage> = Layer.succeed(\n CurrentPage,\n makeCurrentPage(() => kitPage, watch),\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkGA,SAAgBA,SAA0C;CACxD,OAAO,oBACJ,aAAa;EACZ,oBAAuB;GACrB,SAAS;EACX,CAAC;CACH,SACMC,IACR;AACF;;;;;;;;;;;;;;;AAgBA,SAAgBC,eAA+D;CAC7E,OAAO,cAAc,aAAa,mBAAmBC,UAAa,CAAC;AACrE;;;;;;;AAQA,SAAgBC,YAAkC;CAChD,OAAO,cAAc,aAAaC,QAAW,OAAO;AACtD;;;;;;;AAQA,SAAgB,iBAA2D;CACzE,OAAO,yBAAyBA,QAAW,MAAM,CAAC;AACpD;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAa,cAAb,MAAa,oBAAoB,QAAQ,QAA2C,CAAC,CACnF,8CACF,CAAC,CAAC;;;;;;CAMA,OAAgB,QAAkC,MAAM,QACtD,aACA,sBAAsBJ,MAAS,KAAK,CACtC;AACF"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AssetSource, FileSystemAssetServices } from "./assets.js";
|
|
2
2
|
import { Effect, Layer } from "effect";
|
|
3
|
-
import {
|
|
3
|
+
import { Server } from "@sveltejs/kit";
|
|
4
4
|
import * as HttpServerRequest from "effect/http/HttpServerRequest";
|
|
5
5
|
import * as HttpServerResponse from "effect/http/HttpServerResponse";
|
|
6
6
|
//#region src/adapter/handler.d.ts
|
|
@@ -19,10 +19,15 @@ type SvelteKitHttpAppServices = FileSystemAssetServices;
|
|
|
19
19
|
type SvelteKitHttpApp<R = SvelteKitHttpAppServices> = Effect.Effect<HttpServerResponse.HttpServerResponse, never, R | HttpServerRequest.HttpServerRequest>;
|
|
20
20
|
/** The SvelteKit build output {@link makeHandler} serves. */
|
|
21
21
|
interface SvelteKitHandlerOptions {
|
|
22
|
-
/** The SvelteKit `
|
|
23
|
-
readonly
|
|
24
|
-
/**
|
|
25
|
-
|
|
22
|
+
/** The SvelteKit `server` instance exported by the build output (`./server.js`). */
|
|
23
|
+
readonly server: Server;
|
|
24
|
+
/**
|
|
25
|
+
* The application path, including any `paths.base` without its leading
|
|
26
|
+
* slash (`"_app"`, or `"my-app/_app"` under a base), exported by the build
|
|
27
|
+
* output (`./manifest.js`). Its `immutable/` subtree gets immutable cache
|
|
28
|
+
* headers.
|
|
29
|
+
*/
|
|
30
|
+
readonly appPath: string;
|
|
26
31
|
/** The set of prerendered paths exported by the build output (`./manifest.js`). */
|
|
27
32
|
readonly prerendered: ReadonlySet<string>;
|
|
28
33
|
/** The configured `paths.base`, exported by the build output (`./manifest.js`). */
|
|
@@ -36,10 +41,8 @@ interface SvelteKitHandlerOptions {
|
|
|
36
41
|
}
|
|
37
42
|
/** Options for the platform-neutral SSR core, {@link makeHandlerWith}. */
|
|
38
43
|
interface SvelteKitNeutralHandlerOptions<RAssets> {
|
|
39
|
-
/** The SvelteKit `
|
|
40
|
-
readonly
|
|
41
|
-
/** The SSR manifest exported by the build output. */
|
|
42
|
-
readonly manifest: SSRManifest;
|
|
44
|
+
/** The SvelteKit `server` instance exported by the build output (`./server.js`). */
|
|
45
|
+
readonly server: Server;
|
|
43
46
|
/** Environment variables passed to the SvelteKit server. */
|
|
44
47
|
readonly env?: Record<string, string | undefined> | undefined;
|
|
45
48
|
/** Prefix for the runtime configuration environment variables (ORIGIN, ...). */
|
|
@@ -97,14 +100,13 @@ declare const makeHandler: (options: SvelteKitHandlerOptions) => Effect.Effect<S
|
|
|
97
100
|
* ```ts
|
|
98
101
|
* // Workers: a fetch handler with assets from env.ASSETS
|
|
99
102
|
* import { makeHandlerWith, AssetSource, toWebHandler } from "...adapter/handler";
|
|
100
|
-
* import {
|
|
101
|
-
* import { manifest } from "./manifest.js";
|
|
103
|
+
* import { server } from "./server.js";
|
|
102
104
|
*
|
|
103
105
|
* export default {
|
|
104
106
|
* fetch: (request, env, ctx) =>
|
|
105
107
|
* toWebHandler(
|
|
106
108
|
* makeHandlerWith({
|
|
107
|
-
*
|
|
109
|
+
* server, env,
|
|
108
110
|
* assets: AssetSource.cloudflareAssets(env.ASSETS),
|
|
109
111
|
* read: () => { throw new Error("read() not supported on this deployment"); },
|
|
110
112
|
* }),
|
package/dist/adapter/handler.js
CHANGED
|
@@ -89,14 +89,13 @@ const makeHandler = (options) => Effect.gen(function* () {
|
|
|
89
89
|
const context = yield* Effect.context();
|
|
90
90
|
const serverDir = path.join(options.dir, "server");
|
|
91
91
|
return yield* makeHandlerWith({
|
|
92
|
-
|
|
93
|
-
manifest: options.manifest,
|
|
92
|
+
server: options.server,
|
|
94
93
|
env: options.env,
|
|
95
94
|
envPrefix: options.envPrefix,
|
|
96
95
|
read: (file) => Stream.toReadableStreamWith(fs.stream(path.join(serverDir, file)), context),
|
|
97
96
|
assets: AssetSource.fileSystem({
|
|
98
97
|
dir: options.dir,
|
|
99
|
-
appPath: options.
|
|
98
|
+
appPath: options.appPath,
|
|
100
99
|
prerendered: options.prerendered
|
|
101
100
|
})
|
|
102
101
|
});
|
|
@@ -117,14 +116,13 @@ const makeHandler = (options) => Effect.gen(function* () {
|
|
|
117
116
|
* ```ts
|
|
118
117
|
* // Workers: a fetch handler with assets from env.ASSETS
|
|
119
118
|
* import { makeHandlerWith, AssetSource, toWebHandler } from "...adapter/handler";
|
|
120
|
-
* import {
|
|
121
|
-
* import { manifest } from "./manifest.js";
|
|
119
|
+
* import { server } from "./server.js";
|
|
122
120
|
*
|
|
123
121
|
* export default {
|
|
124
122
|
* fetch: (request, env, ctx) =>
|
|
125
123
|
* toWebHandler(
|
|
126
124
|
* makeHandlerWith({
|
|
127
|
-
*
|
|
125
|
+
* server, env,
|
|
128
126
|
* assets: AssetSource.cloudflareAssets(env.ASSETS),
|
|
129
127
|
* read: () => { throw new Error("read() not supported on this deployment"); },
|
|
130
128
|
* }),
|
|
@@ -134,7 +132,7 @@ const makeHandler = (options) => Effect.gen(function* () {
|
|
|
134
132
|
*/
|
|
135
133
|
const makeHandlerWith = (options) => Effect.gen(function* () {
|
|
136
134
|
const config = yield* handlerConfig(options.envPrefix ?? "").pipe(Effect.orDie);
|
|
137
|
-
const server =
|
|
135
|
+
const server = options.server;
|
|
138
136
|
yield* Effect.promise(() => server.init({
|
|
139
137
|
env: options.env ?? {},
|
|
140
138
|
read: options.read
|
|
@@ -1 +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 from\n * disk — the Node default.\n *\n * {@link makeHandlerWith} is the platform-neutral core: it takes an injected\n * {@link AssetSource} (where assets come from) and a `read` capability (how to\n * read server assets), so the same SvelteKit `HttpApp` can run on a Node server\n * or on Cloudflare Workers (assets from `env.ASSETS`).\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`), mounted inside any\n * existing `effect/http` HTTP server, or converted to a `fetch` handler\n * (see {@link toWebHandler}).\n */\n\nimport type { SSRManifest, Server as SvelteKitServer } from \"@sveltejs/kit\";\nimport * as FileSystem from \"effect/FileSystem\";\nimport * as HttpMethod from \"effect/http/HttpMethod\";\nimport * as HttpEffect from \"effect/http/HttpEffect\";\nimport * as HttpServerRequest from \"effect/http/HttpServerRequest\";\nimport * as HttpServerResponse from \"effect/http/HttpServerResponse\";\nimport * as Path from \"effect/Path\";\nimport { Config, Context, Effect, Layer, Option, Stream } from \"effect\";\nimport { AssetSource, type FileSystemAssetServices } from \"./assets.js\";\n\nexport { AssetSource, type FileSystemAssetServices } from \"./assets.js\";\n\n/**\n * The services required by the SvelteKit HTTP application when serving assets\n * from the file system (the Node default). All of them are provided by\n * `NodeHttpServer.layer` from `@effect/platform-node`.\n */\nexport type SvelteKitHttpAppServices = FileSystemAssetServices;\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. `R`\n * is the asset source's services (defaults to the file-system services).\n */\nexport type SvelteKitHttpApp<R = SvelteKitHttpAppServices> = Effect.Effect<\n HttpServerResponse.HttpServerResponse,\n never,\n R | HttpServerRequest.HttpServerRequest\n>;\n\n/** The SvelteKit build output {@link makeHandler} serves. */\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/** Options for the platform-neutral SSR core, {@link makeHandlerWith}. */\nexport interface SvelteKitNeutralHandlerOptions<RAssets> {\n /** The SvelteKit `Server` class exported by the build output. */\n readonly Server: typeof SvelteKitServer;\n /** The SSR manifest exported by the build output. */\n readonly manifest: SSRManifest;\n /** Environment variables passed to the SvelteKit server. */\n readonly env?: Record<string, string | undefined> | undefined;\n /** Prefix for the runtime configuration environment variables (ORIGIN, ...). */\n readonly envPrefix?: string | undefined;\n /** Where static client assets and prerendered pages come from. */\n readonly assets: AssetSource<RAssets>;\n /**\n * How to read a server asset (backs `read()` from `$app/server`). On Node\n * this streams from `<dir>/server`; on Workers, back it with the platform's\n * own mechanism.\n */\n readonly read: (file: string) => ReadableStream<Uint8Array>;\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\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/http server:\n * import { handler } from \"./build/handler.js\";\n * import * as HttpServer from \"effect/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 fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const context = yield* Effect.context<never>();\n\n const serverDir = path.join(options.dir, \"server\");\n\n return yield* makeHandlerWith<SvelteKitHttpAppServices>({\n Server: options.Server,\n manifest: options.manifest,\n env: options.env,\n envPrefix: options.envPrefix,\n // `read` backs the `read()` helper from `$app/server`, streaming server\n // assets from the build output.\n read: (file) => Stream.toReadableStreamWith(fs.stream(path.join(serverDir, file)), context),\n assets: AssetSource.fileSystem({\n dir: options.dir,\n appPath: options.manifest.appPath,\n prerendered: options.prerendered,\n }),\n });\n });\n\n/**\n * The platform-neutral SSR core. Like {@link makeHandler}, but the asset source\n * and the server-asset `read` capability are injected, so the same SvelteKit\n * `HttpApp` can run on Node (file-system assets) or on Cloudflare Workers\n * (`AssetSource.cloudflareAssets(env.ASSETS)`).\n *\n * The returned app:\n *\n * 1. falls through to the injected {@link AssetSource} for static client assets\n * and prerendered pages (GET/HEAD), then\n * 2. server-side renders via the SvelteKit server.\n *\n * @example\n * ```ts\n * // Workers: a fetch handler with assets from env.ASSETS\n * import { makeHandlerWith, AssetSource, toWebHandler } from \"...adapter/handler\";\n * import { Server } from \"./server/index.js\";\n * import { manifest } from \"./manifest.js\";\n *\n * export default {\n * fetch: (request, env, ctx) =>\n * toWebHandler(\n * makeHandlerWith({\n * Server, manifest, env,\n * assets: AssetSource.cloudflareAssets(env.ASSETS),\n * read: () => { throw new Error(\"read() not supported on this deployment\"); },\n * }),\n * )(request),\n * };\n * ```\n */\nexport const makeHandlerWith = <RAssets = never>(\n options: SvelteKitNeutralHandlerOptions<RAssets>,\n): Effect.Effect<SvelteKitHttpApp<RAssets>, never, never> =>\n Effect.gen(function* () {\n const config = yield* handlerConfig(options.envPrefix ?? \"\").pipe(Effect.orDie);\n\n // Initialise the SvelteKit server. `read` backs `read()` from `$app/server`.\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: options.read,\n }),\n );\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 / prerendered pages, via the injected source.\n // Only GET/HEAD reach assets; build a body-less request for the source.\n if (request.method === \"GET\" || request.method === \"HEAD\") {\n const assetRequest = new Request(url, {\n method: request.method,\n headers: { ...request.headers },\n });\n const asset = yield* options.assets.serveAsset(assetRequest, pathname);\n if (Option.isSome(asset)) return asset.value;\n }\n\n // 2. 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\n/**\n * Convert a SvelteKit `HttpApp` to a Web `fetch` handler, providing the asset\n * source's services with `layer`. Use this for a Workers `fetch` export or any\n * runtime that speaks the Web platform.\n *\n * @example\n * ```ts\n * const fetchHandler = toWebHandler(\n * makeHandlerWith({ ..., assets: AssetSource.cloudflareAssets(env.ASSETS) }),\n * Layer.empty,\n * );\n * ```\n */\nexport const toWebHandler = <RAssets, E>(\n app: Effect.Effect<SvelteKitHttpApp<RAssets>, never, never>,\n layer: Layer.Layer<RAssets, E>,\n): ((request: Request) => Promise<Response>) => {\n // Build the app (server.init) and the per-request web handler once, lazily on\n // the first request, then dispatch each request through it.\n let built: Promise<(request: Request) => Promise<Response>> | undefined;\n const build = (): Promise<(request: Request) => Promise<Response>> =>\n (built ??= Effect.runPromise(app).then(\n (inner) =>\n HttpEffect.toWebHandlerLayer(inner, layer).handler as (\n request: Request,\n ) => Promise<Response>,\n ));\n return async (request) => (await build())(request);\n};\n"],"mappings":"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"}
|
|
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` instance the adapter\n * generates with `builder.generateServerInstance`) into an Effect HTTP application that serves\n * static client assets, prerendered pages, and server-side rendered routes from\n * disk — the Node default.\n *\n * {@link makeHandlerWith} is the platform-neutral core: it takes an injected\n * {@link AssetSource} (where assets come from) and a `read` capability (how to\n * read server assets), so the same SvelteKit `HttpApp` can run on a Node server\n * or on Cloudflare Workers (assets from `env.ASSETS`).\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`), mounted inside any\n * existing `effect/http` HTTP server, or converted to a `fetch` handler\n * (see {@link toWebHandler}).\n */\n\nimport type { Server as SvelteKitServer } from \"@sveltejs/kit\";\nimport * as FileSystem from \"effect/FileSystem\";\nimport * as HttpMethod from \"effect/http/HttpMethod\";\nimport * as HttpEffect from \"effect/http/HttpEffect\";\nimport * as HttpServerRequest from \"effect/http/HttpServerRequest\";\nimport * as HttpServerResponse from \"effect/http/HttpServerResponse\";\nimport * as Path from \"effect/Path\";\nimport { Config, Context, Effect, Layer, Option, Stream } from \"effect\";\nimport { AssetSource, type FileSystemAssetServices } from \"./assets.js\";\n\nexport { AssetSource, type FileSystemAssetServices } from \"./assets.js\";\n\n/**\n * The services required by the SvelteKit HTTP application when serving assets\n * from the file system (the Node default). All of them are provided by\n * `NodeHttpServer.layer` from `@effect/platform-node`.\n */\nexport type SvelteKitHttpAppServices = FileSystemAssetServices;\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. `R`\n * is the asset source's services (defaults to the file-system services).\n */\nexport type SvelteKitHttpApp<R = SvelteKitHttpAppServices> = Effect.Effect<\n HttpServerResponse.HttpServerResponse,\n never,\n R | HttpServerRequest.HttpServerRequest\n>;\n\n/** The SvelteKit build output {@link makeHandler} serves. */\nexport interface SvelteKitHandlerOptions {\n /** The SvelteKit `server` instance exported by the build output (`./server.js`). */\n readonly server: SvelteKitServer;\n /**\n * The application path, including any `paths.base` without its leading\n * slash (`\"_app\"`, or `\"my-app/_app\"` under a base), exported by the build\n * output (`./manifest.js`). Its `immutable/` subtree gets immutable cache\n * headers.\n */\n readonly appPath: string;\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/** Options for the platform-neutral SSR core, {@link makeHandlerWith}. */\nexport interface SvelteKitNeutralHandlerOptions<RAssets> {\n /** The SvelteKit `server` instance exported by the build output (`./server.js`). */\n readonly server: SvelteKitServer;\n /** Environment variables passed to the SvelteKit server. */\n readonly env?: Record<string, string | undefined> | undefined;\n /** Prefix for the runtime configuration environment variables (ORIGIN, ...). */\n readonly envPrefix?: string | undefined;\n /** Where static client assets and prerendered pages come from. */\n readonly assets: AssetSource<RAssets>;\n /**\n * How to read a server asset (backs `read()` from `$app/server`). On Node\n * this streams from `<dir>/server`; on Workers, back it with the platform's\n * own mechanism.\n */\n readonly read: (file: string) => ReadableStream<Uint8Array>;\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\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/http server:\n * import { handler } from \"./build/handler.js\";\n * import * as HttpServer from \"effect/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 fs = yield* FileSystem.FileSystem;\n const path = yield* Path.Path;\n const context = yield* Effect.context<never>();\n\n const serverDir = path.join(options.dir, \"server\");\n\n return yield* makeHandlerWith<SvelteKitHttpAppServices>({\n server: options.server,\n env: options.env,\n envPrefix: options.envPrefix,\n // `read` backs the `read()` helper from `$app/server`, streaming server\n // assets from the build output.\n read: (file) => Stream.toReadableStreamWith(fs.stream(path.join(serverDir, file)), context),\n assets: AssetSource.fileSystem({\n dir: options.dir,\n appPath: options.appPath,\n prerendered: options.prerendered,\n }),\n });\n });\n\n/**\n * The platform-neutral SSR core. Like {@link makeHandler}, but the asset source\n * and the server-asset `read` capability are injected, so the same SvelteKit\n * `HttpApp` can run on Node (file-system assets) or on Cloudflare Workers\n * (`AssetSource.cloudflareAssets(env.ASSETS)`).\n *\n * The returned app:\n *\n * 1. falls through to the injected {@link AssetSource} for static client assets\n * and prerendered pages (GET/HEAD), then\n * 2. server-side renders via the SvelteKit server.\n *\n * @example\n * ```ts\n * // Workers: a fetch handler with assets from env.ASSETS\n * import { makeHandlerWith, AssetSource, toWebHandler } from \"...adapter/handler\";\n * import { server } from \"./server.js\";\n *\n * export default {\n * fetch: (request, env, ctx) =>\n * toWebHandler(\n * makeHandlerWith({\n * server, env,\n * assets: AssetSource.cloudflareAssets(env.ASSETS),\n * read: () => { throw new Error(\"read() not supported on this deployment\"); },\n * }),\n * )(request),\n * };\n * ```\n */\nexport const makeHandlerWith = <RAssets = never>(\n options: SvelteKitNeutralHandlerOptions<RAssets>,\n): Effect.Effect<SvelteKitHttpApp<RAssets>, never, never> =>\n Effect.gen(function* () {\n const config = yield* handlerConfig(options.envPrefix ?? \"\").pipe(Effect.orDie);\n\n // Initialise the SvelteKit server. `read` backs `read()` from `$app/server`.\n const server = options.server;\n yield* Effect.promise(() =>\n server.init({\n env: options.env ?? {},\n read: options.read,\n }),\n );\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 / prerendered pages, via the injected source.\n // Only GET/HEAD reach assets; build a body-less request for the source.\n if (request.method === \"GET\" || request.method === \"HEAD\") {\n const assetRequest = new Request(url, {\n method: request.method,\n headers: { ...request.headers },\n });\n const asset = yield* options.assets.serveAsset(assetRequest, pathname);\n if (Option.isSome(asset)) return asset.value;\n }\n\n // 2. 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\n/**\n * Convert a SvelteKit `HttpApp` to a Web `fetch` handler, providing the asset\n * source's services with `layer`. Use this for a Workers `fetch` export or any\n * runtime that speaks the Web platform.\n *\n * @example\n * ```ts\n * const fetchHandler = toWebHandler(\n * makeHandlerWith({ ..., assets: AssetSource.cloudflareAssets(env.ASSETS) }),\n * Layer.empty,\n * );\n * ```\n */\nexport const toWebHandler = <RAssets, E>(\n app: Effect.Effect<SvelteKitHttpApp<RAssets>, never, never>,\n layer: Layer.Layer<RAssets, E>,\n): ((request: Request) => Promise<Response>) => {\n // Build the app (server.init) and the per-request web handler once, lazily on\n // the first request, then dispatch each request through it.\n let built: Promise<(request: Request) => Promise<Response>> | undefined;\n const build = (): Promise<(request: Request) => Promise<Response>> =>\n (built ??= Effect.runPromise(app).then(\n (inner) =>\n HttpEffect.toWebHandlerLayer(inner, layer).handler as (\n request: Request,\n ) => Promise<Response>,\n ));\n return async (request) => (await build())(request);\n};\n"],"mappings":"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"}
|
package/dist/adapter/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { writeFileSync } from "node:fs";
|
|
1
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
//#region src/adapter/index.ts
|
|
3
3
|
/**
|
|
4
4
|
* Effect deployment adapter for SvelteKit.
|
|
@@ -10,14 +10,14 @@ import { writeFileSync } from "node:fs";
|
|
|
10
10
|
* shutdown that runs all finalizers).
|
|
11
11
|
*
|
|
12
12
|
* ```js
|
|
13
|
-
* //
|
|
13
|
+
* // vite.config.ts
|
|
14
|
+
* import { sveltekit } from "@sveltejs/kit/vite";
|
|
14
15
|
* import adapter from "@thomasfosterau/effect-sveltekit/adapter";
|
|
16
|
+
* import { defineConfig } from "vite";
|
|
15
17
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* };
|
|
19
|
-
*
|
|
20
|
-
* export default config;
|
|
18
|
+
* export default defineConfig({
|
|
19
|
+
* plugins: [sveltekit({ adapter: adapter() })],
|
|
20
|
+
* });
|
|
21
21
|
* ```
|
|
22
22
|
*
|
|
23
23
|
* The build output (`build/` by default) contains:
|
|
@@ -25,7 +25,9 @@ import { writeFileSync } from "node:fs";
|
|
|
25
25
|
* - `index.js` — runs the production server: `node build`
|
|
26
26
|
* - `handler.js` — exports the SvelteKit app as a composable Effect HTTP
|
|
27
27
|
* handler, for mounting inside an existing `effect/http` server
|
|
28
|
-
* - `client/`, `server/`, `prerendered/`, `
|
|
28
|
+
* - `client/`, `server/`, `prerendered/`, `server.js` (the SvelteKit
|
|
29
|
+
* `server` instance), `manifest.js` (prerendered paths, base and app path) —
|
|
30
|
+
* the built app
|
|
29
31
|
*
|
|
30
32
|
* Unlike `adapter-node`, the server output is not bundled: the deployment
|
|
31
33
|
* needs `node_modules` with the app's production dependencies installed
|
|
@@ -39,8 +41,8 @@ import { dirname } from "node:path";
|
|
|
39
41
|
import { fileURLToPath } from "node:url";
|
|
40
42
|
import process from "node:process";
|
|
41
43
|
import { makeHandler } from "${PACKAGE}/adapter/handler";
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
+
import { server } from "./server.js";
|
|
45
|
+
import { prerendered, base, appPath } from "./manifest.js";
|
|
44
46
|
|
|
45
47
|
const dir = dirname(fileURLToPath(import.meta.url));
|
|
46
48
|
|
|
@@ -51,8 +53,8 @@ const dir = dirname(fileURLToPath(import.meta.url));
|
|
|
51
53
|
* \`effect/http\` HTTP server.
|
|
52
54
|
*/
|
|
53
55
|
export const handler = makeHandler({
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
server,
|
|
57
|
+
appPath,
|
|
56
58
|
prerendered,
|
|
57
59
|
base,
|
|
58
60
|
dir,
|
|
@@ -77,8 +79,7 @@ serve(handler, { envPrefix: ${JSON.stringify(envPrefix)} });
|
|
|
77
79
|
const fetchEntryTemplate = (envPrefix) => `\
|
|
78
80
|
import { Layer } from "effect";
|
|
79
81
|
import { makeHandlerWith, AssetSource, toWebHandler } from "${PACKAGE}/adapter/handler";
|
|
80
|
-
import {
|
|
81
|
-
import { manifest } from "./manifest.js";
|
|
82
|
+
import { server } from "./server.js";
|
|
82
83
|
|
|
83
84
|
let fetchHandler;
|
|
84
85
|
|
|
@@ -87,8 +88,7 @@ export default {
|
|
|
87
88
|
fetch(request, env, ctx) {
|
|
88
89
|
fetchHandler ??= toWebHandler(
|
|
89
90
|
makeHandlerWith({
|
|
90
|
-
|
|
91
|
-
manifest,
|
|
91
|
+
server,
|
|
92
92
|
env,
|
|
93
93
|
envPrefix: ${JSON.stringify(envPrefix)},
|
|
94
94
|
// Static assets come from the Workers ASSETS binding (configure it to
|
|
@@ -113,9 +113,12 @@ function effectAdapter(options = {}) {
|
|
|
113
113
|
return {
|
|
114
114
|
name: `${PACKAGE}/adapter`,
|
|
115
115
|
async adapt(builder) {
|
|
116
|
-
const base = builder.config.
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
const base = builder.config.paths.base;
|
|
117
|
+
rmSync(out, {
|
|
118
|
+
recursive: true,
|
|
119
|
+
force: true
|
|
120
|
+
});
|
|
121
|
+
mkdirSync(out, { recursive: true });
|
|
119
122
|
builder.log.minor("Copying assets");
|
|
120
123
|
builder.writeClient(`${out}/client${base}`);
|
|
121
124
|
builder.writePrerendered(`${out}/prerendered${base}`);
|
|
@@ -125,10 +128,11 @@ function effectAdapter(options = {}) {
|
|
|
125
128
|
}
|
|
126
129
|
builder.log.minor("Building server");
|
|
127
130
|
builder.writeServer(`${out}/server`);
|
|
131
|
+
builder.generateServerInstance(`${out}/server.js`, { serverDirectory: `${out}/server` });
|
|
128
132
|
writeFileSync(`${out}/manifest.js`, [
|
|
129
|
-
`export const manifest = ${builder.generateManifest({ relativePath: "./server" })};`,
|
|
130
133
|
`export const prerendered = new Set(${JSON.stringify(builder.prerendered.paths)});`,
|
|
131
|
-
`export const base = ${JSON.stringify(base)}
|
|
134
|
+
`export const base = ${JSON.stringify(base)};`,
|
|
135
|
+
`export const appPath = ${JSON.stringify(builder.getAppPath())};`
|
|
132
136
|
].join("\n\n"));
|
|
133
137
|
builder.log.minor("Writing server entry");
|
|
134
138
|
writeFileSync(`${out}/handler.js`, handlerTemplate(envPrefix));
|
|
@@ -1 +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/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 * //
|
|
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/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 * // vite.config.ts\n * import { sveltekit } from \"@sveltejs/kit/vite\";\n * import adapter from \"@thomasfosterau/effect-sveltekit/adapter\";\n * import { defineConfig } from \"vite\";\n *\n * export default defineConfig({\n * plugins: [sveltekit({ adapter: adapter() })],\n * });\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/http` server\n * - `client/`, `server/`, `prerendered/`, `server.js` (the SvelteKit\n * `server` instance), `manifest.js` (prerendered paths, base and app path) —\n * 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 { mkdirSync, rmSync, writeFileSync } from \"node:fs\";\nimport type { Adapter } from \"@sveltejs/kit\";\n\n/** Options for {@link effectAdapter}. */\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 * The deployment target for the generated entry:\n *\n * - `\"node\"` (default) — a production Node HTTP server (`node build`) on\n * `@effect/platform-node`, serving assets from disk.\n * - `\"fetch\"` — a Web `fetch` handler suitable for Cloudflare Workers\n * (`build/index.js` exports `default { fetch }`), serving assets from the\n * `env.ASSETS` binding via `AssetSource.cloudflareAssets`.\n *\n * Both targets also write `build/handler.js` (the SvelteKit `HttpApp`), so you\n * can compose your own entry for any `effect/http` runtime.\n *\n * @default \"node\"\n */\n readonly target?: \"node\" | \"fetch\";\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.js\";\nimport { prerendered, base, appPath } 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/http\\` HTTP server.\n */\nexport const handler = makeHandler({\n server,\n appPath,\n prerendered,\n base,\n dir,\n env: process.env,\n envPrefix: ${JSON.stringify(envPrefix)},\n});\n`;\n\n/** Template for the generated Node `<out>/index.js`. */\nconst nodeEntryTemplate = (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 * Template for the generated Cloudflare Workers `<out>/index.js`.\n *\n * Exports a module worker (`default { fetch }`). Static client assets and\n * prerendered pages are served from the `ASSETS` binding; everything else is\n * server-side rendered by the SvelteKit `HttpApp`.\n */\nconst fetchEntryTemplate = (envPrefix: string): string => `\\\nimport { Layer } from \"effect\";\nimport { makeHandlerWith, AssetSource, toWebHandler } from \"${PACKAGE}/adapter/handler\";\nimport { server } from \"./server.js\";\n\nlet fetchHandler;\n\nexport default {\n /** @param {Request} request @param {Record<string, any>} env @param {any} ctx */\n fetch(request, env, ctx) {\n fetchHandler ??= toWebHandler(\n makeHandlerWith({\n server,\n env,\n envPrefix: ${JSON.stringify(envPrefix)},\n // Static assets come from the Workers ASSETS binding (configure it to\n // serve the uploaded \\`client/\\` + \\`prerendered/\\` output).\n assets: AssetSource.cloudflareAssets(env.ASSETS),\n // \\`read()\\` from \\`$app/server\\` is not backed by a file system on workerd.\n read: (file) => {\n throw new Error(\\`read() is not supported on this deployment (\\${file})\\`);\n },\n }),\n Layer.empty,\n );\n return fetchHandler(request);\n },\n};\n`;\n\n/**\n * Creates the Effect SvelteKit adapter.\n */\nexport function effectAdapter(options: EffectAdapterOptions = {}): Adapter {\n const { out = \"build\", precompress = true, envPrefix = \"\", target = \"node\" } = options;\n\n return {\n name: `${PACKAGE}/adapter`,\n\n async adapt(builder) {\n const base = builder.config.paths.base;\n\n rmSync(out, { recursive: true, force: true });\n mkdirSync(out, { recursive: true });\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 // `<out>/server.js` exports the SvelteKit `server` instance, with the\n // manifest of the (non-prerendered) routes baked in.\n builder.generateServerInstance(`${out}/server.js`, { serverDirectory: `${out}/server` });\n\n writeFileSync(\n `${out}/manifest.js`,\n [\n `export const prerendered = new Set(${JSON.stringify(builder.prerendered.paths)});`,\n `export const base = ${JSON.stringify(base)};`,\n `export const appPath = ${JSON.stringify(builder.getAppPath())};`,\n ].join(\"\\n\\n\"),\n );\n\n builder.log.minor(\"Writing server entry\");\n writeFileSync(`${out}/handler.js`, handlerTemplate(envPrefix));\n writeFileSync(\n `${out}/index.js`,\n target === \"fetch\" ? fetchEntryTemplate(envPrefix) : nodeEntryTemplate(envPrefix),\n );\n },\n\n supports: {\n // On Node, `read()` from `$app/server` is backed by the platform\n // FileSystem. On the `fetch` (Workers) target it has no file system, so\n // routes using `read()` are unsupported there.\n read: () => target !== \"fetch\",\n },\n };\n}\n\nexport default effectAdapter;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0EA,MAAM,UAAU;;AAGhB,MAAM,mBAAmB,cAA8B;;;;+BAIxB,QAAQ;;;;;;;;;;;;;;;;;;;eAmBxB,KAAK,UAAU,SAAS,EAAE;;;;AAKzC,MAAM,qBAAqB,cAA8B;yBAChC,QAAQ;;;8BAGH,KAAK,UAAU,SAAS,EAAE;;;;;;;;;AAUxD,MAAM,sBAAsB,cAA8B;;8DAEI,QAAQ;;;;;;;;;;;;qBAYjD,KAAK,UAAU,SAAS,EAAE;;;;;;;;;;;;;;;;;;AAmB/C,SAAgB,cAAc,UAAgC,CAAC,GAAY;CACzE,MAAM,EAAE,MAAM,SAAS,cAAc,MAAM,YAAY,IAAI,SAAS,WAAW;CAE/E,OAAO;EACL,MAAM,GAAG,QAAQ;EAEjB,MAAM,MAAM,SAAS;GACnB,MAAM,OAAO,QAAQ,OAAO,MAAM;GAElC,OAAO,KAAK;IAAE,WAAW;IAAM,OAAO;GAAK,CAAC;GAC5C,UAAU,KAAK,EAAE,WAAW,KAAK,CAAC;GAElC,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;GAGnC,QAAQ,uBAAuB,GAAG,IAAI,aAAa,EAAE,iBAAiB,GAAG,IAAI,SAAS,CAAC;GAEvF,cACE,GAAG,IAAI,eACP;IACE,sCAAsC,KAAK,UAAU,QAAQ,YAAY,KAAK,EAAE;IAChF,uBAAuB,KAAK,UAAU,IAAI,EAAE;IAC5C,0BAA0B,KAAK,UAAU,QAAQ,WAAW,CAAC,EAAE;GACjE,CAAC,CAAC,KAAK,MAAM,CACf;GAEA,QAAQ,IAAI,MAAM,sBAAsB;GACxC,cAAc,GAAG,IAAI,cAAc,gBAAgB,SAAS,CAAC;GAC7D,cACE,GAAG,IAAI,YACP,WAAW,UAAU,mBAAmB,SAAS,IAAI,kBAAkB,SAAS,CAClF;EACF;EAEA,UAAU,EAIR,YAAY,WAAW,QACzB;CACF;AACF"}
|
package/dist/api-client.js
CHANGED
|
@@ -17,7 +17,7 @@ import * as HttpApiClient from "effect/http-api/HttpApiClient";
|
|
|
17
17
|
* // src/routes/+page.ts — a universal load
|
|
18
18
|
* import { apiClient } from "@thomasfosterau/effect-sveltekit";
|
|
19
19
|
* import { Effect } from "effect";
|
|
20
|
-
* import { api } from "
|
|
20
|
+
* import { api } from "#lib/api.ts"; // the shared HttpApi definition
|
|
21
21
|
*
|
|
22
22
|
* export const load = (event) =>
|
|
23
23
|
* Effect.gen(function* () {
|
package/dist/api-client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-client.js","names":[],"sources":["../src/api-client.ts"],"sourcesContent":["/**\n * A typed client for an Effect [`HttpApi`](https://effect.website) served by\n * {@link Endpoint.fromApi}, closing the serve↔consume loop.\n *\n * {@link apiClient} wraps Effect's own `HttpApiClient.make` and provides the\n * `HttpClient` for you (via `FetchHttpClient`), so a browser or a universal\n * `load` can call the same-origin API with full end-to-end types derived from\n * the shared `HttpApi` definition. In a `load`, pass `event.fetch` so the call\n * participates in SvelteKit's SSR fetch (relative URLs, credentials, dedupe).\n *\n * @example\n * ```ts\n * // src/routes/+page.ts — a universal load\n * import { apiClient } from \"@thomasfosterau/effect-sveltekit\";\n * import { Effect } from \"effect\";\n * import { api } from \"
|
|
1
|
+
{"version":3,"file":"api-client.js","names":[],"sources":["../src/api-client.ts"],"sourcesContent":["/**\n * A typed client for an Effect [`HttpApi`](https://effect.website) served by\n * {@link Endpoint.fromApi}, closing the serve↔consume loop.\n *\n * {@link apiClient} wraps Effect's own `HttpApiClient.make` and provides the\n * `HttpClient` for you (via `FetchHttpClient`), so a browser or a universal\n * `load` can call the same-origin API with full end-to-end types derived from\n * the shared `HttpApi` definition. In a `load`, pass `event.fetch` so the call\n * participates in SvelteKit's SSR fetch (relative URLs, credentials, dedupe).\n *\n * @example\n * ```ts\n * // src/routes/+page.ts — a universal load\n * import { apiClient } from \"@thomasfosterau/effect-sveltekit\";\n * import { Effect } from \"effect\";\n * import { api } from \"#lib/api.ts\"; // the shared HttpApi definition\n *\n * export const load = (event) =>\n * Effect.gen(function* () {\n * const client = yield* apiClient(api, { baseUrl: \"/api\", fetch: event.fetch });\n * return { user: yield* client.users.get({ path: { id: event.params.id } }) };\n * });\n * ```\n */\n\nimport { Effect, Layer } from \"effect\";\nimport * as FetchHttpClient from \"effect/http/FetchHttpClient\";\nimport type * as HttpClient from \"effect/http/HttpClient\";\nimport type * as HttpApi from \"effect/http-api/HttpApi\";\nimport * as HttpApiClient from \"effect/http-api/HttpApiClient\";\nimport type * as HttpApiGroup from \"effect/http-api/HttpApiGroup\";\n\n/** Options for {@link apiClient}. */\nexport interface ApiClientOptions {\n /**\n * The base URL the API is mounted at. For an `Endpoint.fromApi` mounted at\n * `src/routes/api/[...path]` with `prefix: \"/api\"`, pass `\"/api\"`. Defaults to\n * the endpoint paths relative to the current origin.\n */\n readonly baseUrl?: URL | string;\n /**\n * The `fetch` implementation to use. In a `load`, pass `event.fetch` so the\n * request joins SvelteKit's SSR fetch. Defaults to the platform `fetch`.\n */\n readonly fetch?: typeof globalThis.fetch;\n /** Transform the underlying `HttpClient` (e.g. add headers, retries). */\n readonly transformClient?: (client: HttpClient.HttpClient) => HttpClient.HttpClient;\n}\n\n/**\n * Build a typed {@link HttpApiClient} for `api`, with the `HttpClient` provided\n * via `FetchHttpClient`. Yields the client from an `Effect`; the only remaining\n * requirement is any client middleware the API declares.\n */\nexport const apiClient = <ApiId extends string, Groups extends HttpApiGroup.Constraint>(\n api: HttpApi.HttpApi<ApiId, Groups>,\n options?: ApiClientOptions,\n): Effect.Effect<HttpApiClient.Client<Groups>, never, HttpApiGroup.MiddlewareClient<Groups>> => {\n const fetchLayer =\n options?.fetch === undefined\n ? FetchHttpClient.layer\n : FetchHttpClient.layer.pipe(\n Layer.provide(Layer.succeed(FetchHttpClient.Fetch, options.fetch)),\n );\n\n return HttpApiClient.make(api, {\n baseUrl: options?.baseUrl,\n transformClient: options?.transformClient,\n }).pipe(Effect.provide(fetchLayer));\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsDA,MAAa,aACX,KACA,YAC8F;CAC9F,MAAM,aACJ,SAAS,UAAU,KAAA,IACf,gBAAgB,QAChB,gBAAgB,MAAM,KACpB,MAAM,QAAQ,MAAM,QAAQ,gBAAgB,OAAO,QAAQ,KAAK,CAAC,CACnE;CAEN,OAAO,cAAc,KAAK,KAAK;EAC7B,SAAS,SAAS;EAClB,iBAAiB,SAAS;CAC5B,CAAC,CAAC,CAAC,KAAK,OAAO,QAAQ,UAAU,CAAC;AACpC"}
|
package/dist/app-runtime.d.ts
CHANGED
|
@@ -4,7 +4,8 @@ import { ErrorTranslator } from "./request-runtime.js";
|
|
|
4
4
|
import { SvelteKitCookies, SvelteKitLocals, SvelteKitResponseHeaders } from "./request-services.js";
|
|
5
5
|
import { Effect, Exit, Fiber, Layer, ManagedRuntime } from "effect";
|
|
6
6
|
import * as HttpClient from "effect/http/HttpClient";
|
|
7
|
-
import { Action, Actions,
|
|
7
|
+
import { Action, Actions, Load, RequestEvent, ServerLoad } from "@sveltejs/kit";
|
|
8
|
+
import { Handle, HandleFetch, HandleServerError, ServerInit } from "@sveltejs/kit/hooks";
|
|
8
9
|
//#region src/app-runtime.d.ts
|
|
9
10
|
/**
|
|
10
11
|
* The services provided to every wrapped effect on the server, in addition to
|
|
@@ -152,9 +153,9 @@ interface ServerRuntime<R = never, E = never> {
|
|
|
152
153
|
*
|
|
153
154
|
* ```ts
|
|
154
155
|
* // src/routes/items/+page.server.ts
|
|
155
|
-
* import { runtime } from '
|
|
156
|
+
* import { runtime } from '#lib/server/runtime.ts';
|
|
156
157
|
* import { Effect } from 'effect';
|
|
157
|
-
* import { Database } from '
|
|
158
|
+
* import { Database } from '#lib/server/database.ts';
|
|
158
159
|
*
|
|
159
160
|
* export const load = runtime.serverLoad((event) =>
|
|
160
161
|
* Effect.gen(function* () {
|
|
@@ -204,16 +205,16 @@ interface UniversalRuntime<R = never, E = never> {
|
|
|
204
205
|
* ```ts
|
|
205
206
|
* // src/lib/runtime.ts
|
|
206
207
|
* import { makeUniversalRuntime } from '@thomasfosterau/effect-sveltekit';
|
|
207
|
-
* import { ApiClient } from '
|
|
208
|
+
* import { ApiClient } from '#lib/api.ts';
|
|
208
209
|
*
|
|
209
210
|
* export const runtime = makeUniversalRuntime(ApiClient.layer);
|
|
210
211
|
* ```
|
|
211
212
|
*
|
|
212
213
|
* ```ts
|
|
213
214
|
* // src/routes/+page.ts
|
|
214
|
-
* import { runtime } from '
|
|
215
|
+
* import { runtime } from '#lib/runtime.ts';
|
|
215
216
|
* import { Effect } from 'effect';
|
|
216
|
-
* import { ApiClient } from '
|
|
217
|
+
* import { ApiClient } from '#lib/api.ts';
|
|
217
218
|
*
|
|
218
219
|
* export const load = runtime.load((event) =>
|
|
219
220
|
* Effect.gen(function* () {
|