@thomasfosterau/effect-sveltekit 0.5.0 → 0.7.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 +178 -83
- package/dist/Hooks.d.ts +3 -3
- package/dist/Hooks.js +1 -1
- package/dist/Hooks.js.map +1 -1
- package/dist/Navigation.d.ts +70 -17
- package/dist/Navigation.js +93 -19
- package/dist/Navigation.js.map +1 -1
- package/dist/Remote.d.ts +54 -35
- package/dist/Remote.js +22 -11
- package/dist/Remote.js.map +1 -1
- package/dist/State.d.ts +79 -6
- package/dist/State.js +117 -27
- package/dist/State.js.map +1 -1
- package/dist/adapter/assets.d.ts +24 -7
- package/dist/adapter/assets.js +58 -37
- package/dist/adapter/assets.js.map +1 -1
- package/dist/adapter/handler.d.ts +27 -12
- package/dist/adapter/handler.js +53 -26
- package/dist/adapter/handler.js.map +1 -1
- package/dist/adapter/index.d.ts +3 -3
- package/dist/adapter/index.js +1 -1
- package/dist/adapter/index.js.map +1 -1
- package/dist/adapter/serve.d.ts +6 -5
- package/dist/adapter/serve.js +3 -1
- package/dist/adapter/serve.js.map +1 -1
- package/dist/api-client.d.ts +3 -4
- package/dist/api-client.js +2 -1
- package/dist/api-client.js.map +1 -1
- package/dist/app-runtime.d.ts +12 -9
- package/dist/app-runtime.js +23 -14
- package/dist/app-runtime.js.map +1 -1
- package/dist/client/Hooks.d.ts +4 -4
- package/dist/client/Hooks.js +2 -1
- package/dist/client/Hooks.js.map +1 -1
- package/dist/client.d.ts +2 -2
- package/dist/config.d.ts +7 -5
- package/dist/config.js +5 -34
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +14 -16
- package/dist/errors.js +20 -12
- package/dist/errors.js.map +1 -1
- package/dist/forms.d.ts +70 -8
- package/dist/forms.js +69 -5
- package/dist/forms.js.map +1 -1
- package/dist/internal/app-state.d.ts +42 -5
- package/dist/internal/app-state.js +64 -15
- package/dist/internal/app-state.js.map +1 -1
- package/dist/internal/auto.d.ts +23 -14
- package/dist/internal/auto.js +50 -40
- package/dist/internal/auto.js.map +1 -1
- package/dist/internal/enhance.d.ts +114 -14
- package/dist/internal/enhance.js +33 -7
- package/dist/internal/enhance.js.map +1 -1
- package/dist/internal/error.d.ts +13 -0
- package/dist/internal/error.js +4 -3
- package/dist/internal/error.js.map +1 -1
- package/dist/internal/navigation-events.js +3 -1
- package/dist/internal/navigation-events.js.map +1 -1
- package/dist/internal/remote-run.d.ts +6 -6
- package/dist/internal/remote-run.js +13 -16
- package/dist/internal/remote-run.js.map +1 -1
- package/dist/internal/remote-validators.js +4 -3
- package/dist/internal/remote-validators.js.map +1 -1
- package/dist/internal/route-params.d.ts +1 -2
- package/dist/internal/sse.js +2 -1
- package/dist/internal/sse.js.map +1 -1
- package/dist/internal/watch.svelte.js +33 -0
- package/dist/internal/watch.svelte.js.map +1 -0
- package/dist/kit.d.ts +12 -11
- package/dist/kit.js +6 -2
- package/dist/kit.js.map +1 -1
- package/dist/realtime.d.ts +49 -30
- package/dist/realtime.js +90 -46
- package/dist/realtime.js.map +1 -1
- package/dist/remote-functions.d.ts +34 -18
- package/dist/remote-functions.js +78 -46
- package/dist/remote-functions.js.map +1 -1
- package/dist/remote-reactive.d.ts +8 -4
- package/dist/remote-reactive.js.map +1 -1
- package/dist/request-parse.d.ts +13 -13
- package/dist/request-parse.js +29 -15
- package/dist/request-parse.js.map +1 -1
- package/dist/request-runtime.d.ts +16 -11
- package/dist/request-runtime.js +28 -29
- package/dist/request-runtime.js.map +1 -1
- package/dist/request-services.d.ts +10 -8
- package/dist/request-services.js +3 -1
- package/dist/request-services.js.map +1 -1
- package/dist/response-headers.d.ts +2 -3
- package/dist/response-headers.js +2 -1
- package/dist/response-headers.js.map +1 -1
- package/dist/runtime.d.ts +8 -5
- package/dist/runtime.js +4 -1
- package/dist/runtime.js.map +1 -1
- package/dist/server/Endpoint.d.ts +17 -11
- package/dist/server/Endpoint.js +18 -4
- package/dist/server/Endpoint.js.map +1 -1
- package/dist/server/Hooks.d.ts +6 -6
- package/dist/server/Hooks.js +1 -1
- package/dist/server/Hooks.js.map +1 -1
- package/dist/server.d.ts +8 -7
- package/dist/server.js +8 -8
- package/dist/server.js.map +1 -1
- package/dist/streamed-load.d.ts +2 -3
- package/dist/streamed-load.js +1 -1
- package/dist/streamed-load.js.map +1 -1
- package/dist/testing.d.ts +15 -14
- package/dist/testing.js +27 -7
- package/dist/testing.js.map +1 -1
- package/dist/transport.d.ts +6 -7
- package/dist/transport.js +3 -6
- package/dist/transport.js.map +1 -1
- package/dist/universal.d.ts +3 -3
- package/dist/universal.js +1 -1
- package/dist/universal.js.map +1 -1
- package/dist/vite.d.ts +4 -4
- package/dist/vite.js +102 -103
- package/dist/vite.js.map +1 -1
- package/package.json +14 -11
- package/LICENSE +0 -21
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Context from "effect/Context";
|
|
3
|
+
import * as Layer from "effect/Layer";
|
|
4
|
+
import * as Scope from "effect/Scope";
|
|
2
5
|
import { RequestHandler } from "@sveltejs/kit";
|
|
6
|
+
import * as Stream from "effect/Stream";
|
|
3
7
|
import * as HttpRouter from "effect/http/HttpRouter";
|
|
4
8
|
import * as HttpServerRequest from "effect/http/HttpServerRequest";
|
|
5
9
|
import * as HttpServerResponse from "effect/http/HttpServerResponse";
|
|
6
10
|
import * as HttpApi from "effect/http-api/HttpApi";
|
|
7
11
|
import * as HttpApiGroup from "effect/http-api/HttpApiGroup";
|
|
12
|
+
import * as Duration from "effect/Duration";
|
|
8
13
|
declare namespace Endpoint_d_exports {
|
|
9
14
|
export { EndpointHandler, EndpointOptions, FromStreamOptions, RequestContext, fromApi, fromHttpEffect, fromRouter, fromStream };
|
|
10
15
|
}
|
|
@@ -14,19 +19,19 @@ declare namespace Endpoint_d_exports {
|
|
|
14
19
|
* with no requirements), so a consumer can read the request to build
|
|
15
20
|
* session-aware services per dispatch.
|
|
16
21
|
*/
|
|
17
|
-
type RequestContext<RequestServices> = (request: Request) => Context.Context<RequestServices> | Effect.Effect<Context.Context<RequestServices>, never, never>;
|
|
22
|
+
export type RequestContext<RequestServices> = (request: Request) => Context.Context<RequestServices> | Effect.Effect<Context.Context<RequestServices>, never, never>;
|
|
18
23
|
/**
|
|
19
24
|
* A SvelteKit endpoint handler — the built-in
|
|
20
25
|
* [`RequestHandler`](https://svelte.dev/docs/kit/@sveltejs-kit#RequestHandler) —
|
|
21
26
|
* backed by an Effect HTTP construct. Assign it to a `+server.ts` export
|
|
22
27
|
* (`fallback`, `GET`, ...). `dispose()` closes the scope of the layer behind it.
|
|
23
28
|
*/
|
|
24
|
-
type EndpointHandler = RequestHandler & {
|
|
29
|
+
export type EndpointHandler = RequestHandler & {
|
|
25
30
|
/** Close the scope of the layer backing the endpoint (runs its finalizers). */
|
|
26
31
|
readonly dispose: () => Promise<void>;
|
|
27
32
|
};
|
|
28
33
|
/** Shared options for every `Endpoint.from*` helper. */
|
|
29
|
-
interface EndpointOptions {
|
|
34
|
+
export interface EndpointOptions {
|
|
30
35
|
/**
|
|
31
36
|
* A leading path segment to strip from each request before it reaches the
|
|
32
37
|
* Effect handler — the SvelteKit mount point. For an endpoint mounted at
|
|
@@ -55,7 +60,7 @@ interface EndpointOptions {
|
|
|
55
60
|
* export const fallback = Endpoint.fromApi(api, ApiLive, { prefix: "/api" });
|
|
56
61
|
* ```
|
|
57
62
|
*/
|
|
58
|
-
declare const fromApi: <Id extends string, Groups extends HttpApiGroup.Constraint, E, LayerRequires = never, RequestServices = never>(api: HttpApi.HttpApi<Id, Groups>, layer: Layer.Layer<HttpApiGroup.ToService<Id, Groups>, E, LayerRequires>, options?: EndpointOptions & {
|
|
63
|
+
export declare const fromApi: <Id extends string, Groups extends HttpApiGroup.Constraint, E, LayerRequires = never, RequestServices = never>(api: HttpApi.HttpApi<Id, Groups>, layer: Layer.Layer<HttpApiGroup.ToService<Id, Groups>, E, LayerRequires>, options?: EndpointOptions & {
|
|
59
64
|
/** Serve the OpenAPI spec for the API at this path (e.g. `"/openapi.json"`). */
|
|
60
65
|
readonly openapiPath?: `/${string}`;
|
|
61
66
|
/** Add Effect's request logger middleware. Defaults to `false` (quiet). */
|
|
@@ -98,7 +103,7 @@ declare const fromApi: <Id extends string, Groups extends HttpApiGroup.Constrain
|
|
|
98
103
|
* export const fallback = Endpoint.fromRouter(RouterLive, { prefix: "/api" });
|
|
99
104
|
* ```
|
|
100
105
|
*/
|
|
101
|
-
declare const fromRouter: <A, E, R extends HttpRouter.HttpRouter | HttpRouter.Request<"Requires", any> | HttpRouter.Request<"GlobalRequires", any> | HttpRouter.Request<"Error", any> | HttpRouter.Request<"GlobalError", any>, RequestServices = never>(layer: Layer.Layer<A, E, R>, options?: EndpointOptions & {
|
|
106
|
+
export declare const fromRouter: <A, E, R extends HttpRouter.HttpRouter | HttpRouter.Request<"Requires", any> | HttpRouter.Request<"GlobalRequires", any> | HttpRouter.Request<"Error", any> | HttpRouter.Request<"GlobalError", any>, RequestServices = never>(layer: Layer.Layer<A, E, R>, options?: EndpointOptions & {
|
|
102
107
|
/** Add Effect's request logger middleware. Defaults to `false` (quiet). */
|
|
103
108
|
readonly logger?: boolean;
|
|
104
109
|
/**
|
|
@@ -126,12 +131,12 @@ declare const fromRouter: <A, E, R extends HttpRouter.HttpRouter | HttpRouter.Re
|
|
|
126
131
|
* );
|
|
127
132
|
* ```
|
|
128
133
|
*/
|
|
129
|
-
declare const fromHttpEffect: <E, R = never, ProvidedE = never>(effect: Effect.Effect<HttpServerResponse.HttpServerResponse, E, R | HttpServerRequest.HttpServerRequest | Scope.Scope>, options?: EndpointOptions & {
|
|
134
|
+
export declare const fromHttpEffect: <E, R = never, ProvidedE = never>(effect: Effect.Effect<HttpServerResponse.HttpServerResponse, E, R | HttpServerRequest.HttpServerRequest | Scope.Scope>, options?: EndpointOptions & {
|
|
130
135
|
/** A layer providing the route's services (`R`), built once and shared. */
|
|
131
136
|
readonly layer?: Layer.Layer<R, ProvidedE>;
|
|
132
137
|
}) => EndpointHandler;
|
|
133
138
|
/** Options for {@link fromStream}: how to frame each element as an SSE event. */
|
|
134
|
-
interface FromStreamOptions<A, R = never> {
|
|
139
|
+
export interface FromStreamOptions<A, R = never> {
|
|
135
140
|
/**
|
|
136
141
|
* The event name (`event:` field). A string applies to every element, or a
|
|
137
142
|
* function derives it per element (return `undefined` for an unnamed event).
|
|
@@ -139,7 +144,8 @@ interface FromStreamOptions<A, R = never> {
|
|
|
139
144
|
*/
|
|
140
145
|
readonly event?: string | ((value: A) => string | undefined);
|
|
141
146
|
/**
|
|
142
|
-
* Encode an element into the `data:` payload. Defaults to
|
|
147
|
+
* Encode an element into the `data:` payload. Defaults to JSON (an element
|
|
148
|
+
* JSON cannot represent, such as a `bigint`, ends the stream with a defect).
|
|
143
149
|
* Multi-line output is split across multiple `data:` lines automatically.
|
|
144
150
|
*/
|
|
145
151
|
readonly data?: (value: A) => string;
|
|
@@ -189,7 +195,7 @@ interface FromStreamOptions<A, R = never> {
|
|
|
189
195
|
* });
|
|
190
196
|
* ```
|
|
191
197
|
*/
|
|
192
|
-
declare const fromStream: <A, E, R = never>(stream: Stream.Stream<A, E, R>, options?: FromStreamOptions<A, R>) => RequestHandler;
|
|
198
|
+
export declare const fromStream: <A, E, R = never>(stream: Stream.Stream<A, E, R>, options?: FromStreamOptions<A, R>) => RequestHandler;
|
|
193
199
|
//#endregion
|
|
194
|
-
export {
|
|
200
|
+
export { Endpoint_d_exports };
|
|
195
201
|
//# sourceMappingURL=Endpoint.d.ts.map
|
package/dist/server/Endpoint.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { __exportAll } from "../_virtual/_rolldown/runtime.js";
|
|
2
2
|
import { HEARTBEAT_FRAME, SSE_HEADERS, encodeRetry, encodeSseFrame } from "../internal/sse.js";
|
|
3
|
-
import
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import "effect/Context";
|
|
5
|
+
import * as Layer from "effect/Layer";
|
|
6
|
+
import * as Predicate from "effect/Predicate";
|
|
7
|
+
import * as Stream from "effect/Stream";
|
|
8
|
+
import * as Schema from "effect/Schema";
|
|
9
|
+
import * as Schedule from "effect/Schedule";
|
|
4
10
|
import * as FileSystem from "effect/FileSystem";
|
|
5
11
|
import * as Path from "effect/Path";
|
|
6
12
|
import * as Etag from "effect/http/Etag";
|
|
@@ -32,7 +38,10 @@ const stripPrefix = (request, prefix) => {
|
|
|
32
38
|
else return request;
|
|
33
39
|
return new Request(url, request);
|
|
34
40
|
};
|
|
35
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Wrap a Web handler (and its dispose) as a SvelteKit endpoint handler. A
|
|
43
|
+
* {@link WebHandler} is a {@link WebHandlerWithContext} that ignores the context.
|
|
44
|
+
*/
|
|
36
45
|
const make = (webHandler, dispose, prefix, context) => {
|
|
37
46
|
const handler = async (event) => {
|
|
38
47
|
const request = prefix === void 0 ? event.request : stripPrefix(event.request, prefix);
|
|
@@ -115,6 +124,8 @@ const fromHttpEffect = (effect, options) => {
|
|
|
115
124
|
const handler = HttpEffect.toWebHandler(effect);
|
|
116
125
|
return make(handler, noopDispose, options?.prefix);
|
|
117
126
|
};
|
|
127
|
+
/** `fromStream`'s default `data:` encoder: the element as a JSON string. */
|
|
128
|
+
const encodeJson = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown));
|
|
118
129
|
/**
|
|
119
130
|
* Create a SvelteKit endpoint that streams an Effect `Stream` to the browser as
|
|
120
131
|
* [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)
|
|
@@ -142,8 +153,11 @@ const fromHttpEffect = (effect, options) => {
|
|
|
142
153
|
* ```
|
|
143
154
|
*/
|
|
144
155
|
const fromStream = (stream, options) => {
|
|
145
|
-
const encodeData = options?.data ??
|
|
146
|
-
const eventOf = (value) =>
|
|
156
|
+
const encodeData = options?.data ?? encodeJson;
|
|
157
|
+
const eventOf = (value) => {
|
|
158
|
+
const name = options?.event;
|
|
159
|
+
return Predicate.isFunction(name) ? name(value) : name;
|
|
160
|
+
};
|
|
147
161
|
return (event) => {
|
|
148
162
|
const events = stream.pipe(Stream.map((value) => encodeSseFrame({
|
|
149
163
|
data: encodeData(value),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Endpoint.js","names":[],"sources":["../../src/server/Endpoint.ts"],"sourcesContent":["/**\n * Create SvelteKit server endpoints (`+server.ts`) backed by Effect — the\n * `Endpoint` namespace of `@thomasfosterau/effect-sveltekit/server`.\n *\n * A SvelteKit endpoint is a `RequestHandler` (`(event) => Response`). Effect can\n * turn an [`HttpApi`](https://effect.website), an `HttpRouter`, or a single\n * route effect into a Web `fetch` handler; these helpers wrap one as a SvelteKit\n * endpoint handler so a route can be served by Effect. Mount it on a catch-all\n * route (`[...path]`) and assign it to `fallback` so every method and sub-path\n * reaches it:\n *\n * @example\n * ```ts\n * // src/routes/api/[...path]/+server.ts\n * import { Endpoint } from \"@thomasfosterau/effect-sveltekit/server\";\n * import { api } from \"#lib/server/api.ts\";\n * import { ApiLive } from \"#lib/server/handlers.ts\"; // HttpApiBuilder.group(...) + your services\n *\n * // `prefix` strips the SvelteKit mount path so the API's own paths\n * // (e.g. \"/users\") match.\n * export const fallback = Endpoint.fromApi(api, ApiLive, { prefix: \"/api\" });\n * ```\n *\n * The layer backing an endpoint is built once, lazily, on the first request and\n * shared across subsequent requests (like `makeServerRuntime`). The returned\n * handler carries a `dispose()` that closes the layer's scope — call it on\n * shutdown if the endpoint holds resources.\n *\n * The Effect handlers receive an Effect-native `HttpServerRequest` (built from\n * `event.request`), not the SvelteKit `RequestEvent`.\n */\n\nimport type { RequestHandler } from \"@sveltejs/kit\";\nimport { Context, type Duration, Effect, Layer, Schedule, Stream } from \"effect\";\nimport type { Scope } from \"effect\";\nimport * as FileSystem from \"effect/FileSystem\";\nimport * as Path from \"effect/Path\";\nimport * as Etag from \"effect/http/Etag\";\nimport * as HttpEffect from \"effect/http/HttpEffect\";\nimport * as HttpPlatform from \"effect/http/HttpPlatform\";\nimport * as HttpRouter from \"effect/http/HttpRouter\";\nimport type * as HttpServerRequest from \"effect/http/HttpServerRequest\";\nimport type * as HttpServerResponse from \"effect/http/HttpServerResponse\";\nimport type * as HttpApi from \"effect/http-api/HttpApi\";\nimport * as HttpApiBuilder from \"effect/http-api/HttpApiBuilder\";\nimport type * as HttpApiGroup from \"effect/http-api/HttpApiGroup\";\nimport { encodeRetry, encodeSseFrame, HEARTBEAT_FRAME, SSE_HEADERS } from \"../internal/sse.js\";\n\n/** A Web `fetch`-style handler, as produced by Effect's `toWebHandler` family. */\ntype WebHandler = (request: Request) => Promise<Response>;\n\n/** A Web handler that also accepts a per-request service context. */\ntype WebHandlerWithContext = (\n request: Request,\n context?: Context.Context<never>,\n) => Promise<Response>;\n\n/**\n * Resolve a request-scoped service context, if a resolver was given. The\n * resolver may return a `Context` directly or an `Effect` producing one (run\n * with no requirements), so a consumer can read the request to build\n * session-aware services per dispatch.\n */\nexport type RequestContext<RequestServices> = (\n request: Request,\n) =>\n | Context.Context<RequestServices>\n | Effect.Effect<Context.Context<RequestServices>, never, never>;\n\nconst resolveRequestContext = async <RequestServices>(\n request: Request,\n context: RequestContext<RequestServices> | undefined,\n): Promise<Context.Context<RequestServices> | undefined> => {\n if (context === undefined) return undefined;\n const result = context(request);\n return Effect.isEffect(result) ? Effect.runPromise(result) : result;\n};\n\n/**\n * A SvelteKit endpoint handler — the built-in\n * [`RequestHandler`](https://svelte.dev/docs/kit/@sveltejs-kit#RequestHandler) —\n * backed by an Effect HTTP construct. Assign it to a `+server.ts` export\n * (`fallback`, `GET`, ...). `dispose()` closes the scope of the layer behind it.\n */\nexport type EndpointHandler = RequestHandler & {\n /** Close the scope of the layer backing the endpoint (runs its finalizers). */\n readonly dispose: () => Promise<void>;\n};\n\n/** Shared options for every `Endpoint.from*` helper. */\nexport interface EndpointOptions {\n /**\n * A leading path segment to strip from each request before it reaches the\n * Effect handler — the SvelteKit mount point. For an endpoint mounted at\n * `src/routes/api/[...path]/+server.ts` whose routes are defined relative to\n * the API root, pass `prefix: \"/api\"` so `/api/users` is dispatched as\n * `/users`. The match respects path boundaries (`/api` does not strip\n * `/apiary`). Defaults to no stripping.\n */\n readonly prefix?: string;\n}\n\nconst noopDispose = (): Promise<void> => Promise.resolve();\n\n/** Remove `prefix` from the request's path (respecting `/` boundaries). */\nconst stripPrefix = (request: Request, prefix: string): Request => {\n const normalized = prefix.endsWith(\"/\") ? prefix.slice(0, -1) : prefix;\n if (normalized === \"\") return request;\n\n const url = new URL(request.url);\n const path = url.pathname;\n if (path === normalized) {\n url.pathname = \"/\";\n } else if (path.startsWith(`${normalized}/`)) {\n url.pathname = path.slice(normalized.length);\n } else {\n return request;\n }\n return new Request(url, request);\n};\n\n/** Wrap a Web handler (and its dispose) as a SvelteKit endpoint handler. */\nconst make = <RequestServices = never>(\n webHandler: WebHandler | WebHandlerWithContext,\n dispose: () => Promise<void>,\n prefix: string | undefined,\n context?: RequestContext<RequestServices>,\n): EndpointHandler => {\n const handler: RequestHandler = async (event) => {\n const request = prefix === undefined ? event.request : stripPrefix(event.request, prefix);\n if (context === undefined) return (webHandler as WebHandler)(request);\n const ctx = await resolveRequestContext(request, context);\n return (webHandler as WebHandlerWithContext)(\n request,\n ctx as Context.Context<never> | undefined,\n );\n };\n return Object.assign(handler, { dispose });\n};\n\n/** The platform services an `HttpApi` needs, with a no-op file system. */\nconst apiPlatform = Layer.mergeAll(Path.layer, Etag.layerWeak, HttpPlatform.layer).pipe(\n Layer.provideMerge(FileSystem.layerNoop({})),\n);\n\n/**\n * Create a SvelteKit endpoint that serves an Effect\n * [`HttpApi`](https://effect.website).\n *\n * `layer` implements the API's groups (built with `HttpApiBuilder.group`), with\n * any application services already provided. The platform services an `HttpApi`\n * requires (`HttpPlatform`, `FileSystem`, `Path`, `Etag`) are supplied for you.\n *\n * @example\n * ```ts\n * // src/routes/api/[...path]/+server.ts\n * import { Endpoint } from \"@thomasfosterau/effect-sveltekit/server\";\n * import { api } from \"#lib/server/api.ts\";\n * import { ApiLive } from \"#lib/server/handlers.ts\";\n *\n * export const fallback = Endpoint.fromApi(api, ApiLive, { prefix: \"/api\" });\n * ```\n */\nexport const fromApi = <\n Id extends string,\n Groups extends HttpApiGroup.Constraint,\n E,\n LayerRequires = never,\n RequestServices = never,\n>(\n api: HttpApi.HttpApi<Id, Groups>,\n layer: Layer.Layer<HttpApiGroup.ToService<Id, Groups>, E, LayerRequires>,\n options?: EndpointOptions & {\n /** Serve the OpenAPI spec for the API at this path (e.g. `\"/openapi.json\"`). */\n readonly openapiPath?: `/${string}`;\n /** Add Effect's request logger middleware. Defaults to `false` (quiet). */\n readonly logger?: boolean;\n /**\n * Provide the request-scoped services the API groups require, per request.\n * The build-once platform layer (`HttpPlatform` / `FileSystem` / `Path` /\n * `Etag`) stays shared; only this slice is resolved and merged for each\n * dispatch — so a session-aware, request-pooled API can run on Workers\n * without rebuilding its platform layer.\n *\n * The resolver may return a `Context` or an `Effect` producing one. Leave\n * the matching services unprovided in `layer` and supply them here:\n *\n * ```ts\n * Endpoint.fromApi(api, ApiLive, {\n * prefix: \"/api\",\n * context: (request) => Context.make(CurrentUser, userFor(request)),\n * });\n * ```\n */\n readonly context?: RequestContext<RequestServices>;\n },\n): EndpointHandler => {\n const appLayer = HttpApiBuilder.layer(\n api,\n options?.openapiPath ? { openapiPath: options.openapiPath } : undefined,\n ).pipe(Layer.provide(layer), Layer.provide(apiPlatform));\n\n // The request-scoped services are supplied per dispatch via the web handler's\n // context argument, so they are hidden from the build-once layer here.\n const { handler, dispose } = HttpRouter.toWebHandler(\n appLayer as unknown as Layer.Layer<never, E, HttpRouter.HttpRouter>,\n { disableLogger: options?.logger !== true },\n );\n return make<RequestServices>(\n handler as WebHandlerWithContext,\n dispose,\n options?.prefix,\n options?.context,\n );\n};\n\n/**\n * Create a SvelteKit endpoint that serves an Effect `HttpRouter` (provided by\n * `layer`). The router's routes must be self-contained (any services they need\n * are provided within `layer`).\n *\n * @example\n * ```ts\n * // src/routes/api/[...path]/+server.ts\n * import { Endpoint } from \"@thomasfosterau/effect-sveltekit/server\";\n * import { Effect } from \"effect\";\n * import { HttpRouter, HttpServerResponse } from \"effect/http\";\n *\n * const RouterLive = HttpRouter.use((router) =>\n * router.add(\"GET\", \"/hello\", Effect.succeed(HttpServerResponse.text(\"hi\"))),\n * );\n *\n * export const fallback = Endpoint.fromRouter(RouterLive, { prefix: \"/api\" });\n * ```\n */\nexport const fromRouter = <\n A,\n E,\n R extends\n | HttpRouter.HttpRouter\n | HttpRouter.Request<\"Requires\", any>\n | HttpRouter.Request<\"GlobalRequires\", any>\n | HttpRouter.Request<\"Error\", any>\n | HttpRouter.Request<\"GlobalError\", any>,\n RequestServices = never,\n>(\n layer: Layer.Layer<A, E, R>,\n options?: EndpointOptions & {\n /** Add Effect's request logger middleware. Defaults to `false` (quiet). */\n readonly logger?: boolean;\n /**\n * Provide the request-scoped services the router's routes require, per\n * request (the routes' `HttpRouter.Request<\"Requires\", ...>` services). The\n * resolver may return a `Context` or an `Effect` producing one.\n */\n readonly context?: RequestContext<RequestServices>;\n },\n): EndpointHandler => {\n const { handler, dispose } = HttpRouter.toWebHandler(layer, {\n disableLogger: options?.logger !== true,\n });\n return make<RequestServices>(\n handler as WebHandlerWithContext,\n dispose,\n options?.prefix,\n options?.context,\n );\n};\n\n/**\n * Create a SvelteKit endpoint from a single Effect HTTP route — an effect\n * producing an `HttpServerResponse` for the current `HttpServerRequest`. Pass\n * `options.layer` to provide the route's services; the layer is built once and\n * shared.\n *\n * @example\n * ```ts\n * // src/routes/health/+server.ts\n * import { Endpoint } from \"@thomasfosterau/effect-sveltekit/server\";\n * import { Effect } from \"effect\";\n * import { HttpServerResponse } from \"effect/http\";\n *\n * export const GET = Endpoint.fromHttpEffect(\n * Effect.succeed(HttpServerResponse.text(\"ok\")),\n * );\n * ```\n */\nexport const fromHttpEffect = <E, R = never, ProvidedE = never>(\n effect: Effect.Effect<\n HttpServerResponse.HttpServerResponse,\n E,\n R | HttpServerRequest.HttpServerRequest | Scope.Scope\n >,\n options?: EndpointOptions & {\n /** A layer providing the route's services (`R`), built once and shared. */\n readonly layer?: Layer.Layer<R, ProvidedE>;\n },\n): EndpointHandler => {\n if (options?.layer) {\n const { handler, dispose } = HttpEffect.toWebHandlerLayer(effect, options.layer);\n return make(handler as WebHandler, dispose, options.prefix);\n }\n const handler = HttpEffect.toWebHandler(\n effect as Effect.Effect<\n HttpServerResponse.HttpServerResponse,\n E,\n HttpServerRequest.HttpServerRequest | Scope.Scope\n >,\n );\n return make(handler, noopDispose, options?.prefix);\n};\n\n/** Options for {@link fromStream}: how to frame each element as an SSE event. */\nexport interface FromStreamOptions<A, R = never> {\n /**\n * The event name (`event:` field). A string applies to every element, or a\n * function derives it per element (return `undefined` for an unnamed event).\n * Omit for the default unnamed `message` events.\n */\n readonly event?: string | ((value: A) => string | undefined);\n /**\n * Encode an element into the `data:` payload. Defaults to `JSON.stringify`.\n * Multi-line output is split across multiple `data:` lines automatically.\n */\n readonly data?: (value: A) => string;\n /** Derive an event id (`id:` field) the browser echoes on reconnect. */\n readonly id?: (value: A) => string | undefined;\n /**\n * The client reconnection delay in milliseconds, sent once as a leading\n * `retry:` field. The browser's `EventSource` reconnects automatically after\n * this delay when the connection drops.\n */\n readonly retry?: number;\n /**\n * Send a comment heartbeat on this interval to keep the connection alive\n * through proxies that time out idle streams. Accepts any `Duration.Input`\n * (e.g. `\"30 seconds\"`).\n */\n readonly heartbeat?: Duration.Input;\n /**\n * A layer (or context) providing the stream's services `R`, applied per\n * connection. Omit when the stream is self-contained (`R = never`).\n */\n readonly provide?: Layer.Layer<R> | Context.Context<R>;\n}\n\n/**\n * Create a SvelteKit endpoint that streams an Effect `Stream` to the browser as\n * [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)\n * (`text/event-stream`). Consume it client-side with `Realtime.fromEventSource`\n * (from `@thomasfosterau/effect-sveltekit/client`) or the native `EventSource`.\n *\n * Each element is framed as one SSE event (`event` / `id` optional, `data`\n * defaults to JSON). A `retry` sets the reconnect delay; a `heartbeat` keeps\n * idle connections alive. The response closes when the stream ends; the stream\n * is interrupted (running its finalizers) when the client disconnects.\n *\n * @example\n * ```ts\n * // src/routes/events/+server.ts\n * import { Endpoint } from \"@thomasfosterau/effect-sveltekit/server\";\n * import { Schedule, Stream } from \"effect\";\n *\n * const ticks = Stream.fromSchedule(Schedule.spaced(\"1 second\"));\n *\n * export const GET = Endpoint.fromStream(ticks, {\n * event: \"tick\",\n * heartbeat: \"30 seconds\",\n * retry: 3000,\n * });\n * ```\n */\nexport const fromStream = <A, E, R = never>(\n stream: Stream.Stream<A, E, R>,\n options?: FromStreamOptions<A, R>,\n): RequestHandler => {\n const encodeData = options?.data ?? ((value: A): string => JSON.stringify(value));\n const eventOf = (value: A): string | undefined =>\n typeof options?.event === \"function\" ? options.event(value) : options?.event;\n\n return (event) => {\n // Frame each element, then encode the resulting strings to bytes.\n const events = stream.pipe(\n Stream.map((value) =>\n encodeSseFrame({\n data: encodeData(value),\n event: eventOf(value),\n id: options?.id?.(value),\n }),\n ),\n );\n\n // A leading `retry:` field, if configured.\n const withRetry =\n options?.retry === undefined ? events : Stream.prepend(events, [encodeRetry(options.retry)]);\n\n // Merge in a heartbeat that halts as soon as the data stream ends.\n const framed =\n options?.heartbeat === undefined\n ? withRetry\n : Stream.merge(\n withRetry,\n Stream.fromSchedule(Schedule.spaced(options.heartbeat)).pipe(\n Stream.map(() => HEARTBEAT_FRAME),\n ),\n { haltStrategy: \"left\" },\n );\n\n const bytes = Stream.encodeText(framed);\n const provided =\n options?.provide === undefined ? bytes : Stream.provide(bytes, options.provide);\n\n const body = Stream.toReadableStream(provided as Stream.Stream<Uint8Array, E>);\n\n // SvelteKit forwards a streaming body Response as-is.\n void event;\n return new Response(body, { headers: { ...SSE_HEADERS } });\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AAqEA,MAAM,wBAAwB,OAC5B,SACA,YAC0D;CAC1D,IAAI,YAAY,KAAA,GAAW,OAAO,KAAA;CAClC,MAAM,SAAS,QAAQ,OAAO;CAC9B,OAAO,OAAO,SAAS,MAAM,IAAI,OAAO,WAAW,MAAM,IAAI;AAC/D;AA0BA,MAAM,oBAAmC,QAAQ,QAAQ;;AAGzD,MAAM,eAAe,SAAkB,WAA4B;CACjE,MAAM,aAAa,OAAO,SAAS,GAAG,IAAI,OAAO,MAAM,GAAG,EAAE,IAAI;CAChE,IAAI,eAAe,IAAI,OAAO;CAE9B,MAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;CAC/B,MAAM,OAAO,IAAI;CACjB,IAAI,SAAS,YACX,IAAI,WAAW;MACV,IAAI,KAAK,WAAW,GAAG,WAAW,EAAE,GACzC,IAAI,WAAW,KAAK,MAAM,WAAW,MAAM;MAE3C,OAAO;CAET,OAAO,IAAI,QAAQ,KAAK,OAAO;AACjC;;AAGA,MAAM,QACJ,YACA,SACA,QACA,YACoB;CACpB,MAAM,UAA0B,OAAO,UAAU;EAC/C,MAAM,UAAU,WAAW,KAAA,IAAY,MAAM,UAAU,YAAY,MAAM,SAAS,MAAM;EACxF,IAAI,YAAY,KAAA,GAAW,OAAQ,WAA0B,OAAO;EAEpE,OAAQ,WACN,SACA,MAHgB,sBAAsB,SAAS,OAAO,CAIxD;CACF;CACA,OAAO,OAAO,OAAO,SAAS,EAAE,QAAQ,CAAC;AAC3C;;AAGA,MAAM,cAAc,MAAM,SAAS,KAAK,OAAO,KAAK,WAAW,aAAa,KAAK,CAAC,CAAC,KACjF,MAAM,aAAa,WAAW,UAAU,CAAC,CAAC,CAAC,CAC7C;;;;;;;;;;;;;;;;;;;AAoBA,MAAa,WAOX,KACA,OACA,YAwBoB;CACpB,MAAM,WAAW,eAAe,MAC9B,KACA,SAAS,cAAc,EAAE,aAAa,QAAQ,YAAY,IAAI,KAAA,CAChE,CAAC,CAAC,KAAK,MAAM,QAAQ,KAAK,GAAG,MAAM,QAAQ,WAAW,CAAC;CAIvD,MAAM,EAAE,SAAS,YAAY,WAAW,aACtC,UACA,EAAE,eAAe,SAAS,WAAW,KAAK,CAC5C;CACA,OAAO,KACL,SACA,SACA,SAAS,QACT,SAAS,OACX;AACF;;;;;;;;;;;;;;;;;;;;AAqBA,MAAa,cAWX,OACA,YAUoB;CACpB,MAAM,EAAE,SAAS,YAAY,WAAW,aAAa,OAAO,EAC1D,eAAe,SAAS,WAAW,KACrC,CAAC;CACD,OAAO,KACL,SACA,SACA,SAAS,QACT,SAAS,OACX;AACF;;;;;;;;;;;;;;;;;;;AAoBA,MAAa,kBACX,QAKA,YAIoB;CACpB,IAAI,SAAS,OAAO;EAClB,MAAM,EAAE,SAAS,YAAY,WAAW,kBAAkB,QAAQ,QAAQ,KAAK;EAC/E,OAAO,KAAK,SAAuB,SAAS,QAAQ,MAAM;CAC5D;CACA,MAAM,UAAU,WAAW,aACzB,MAKF;CACA,OAAO,KAAK,SAAS,aAAa,SAAS,MAAM;AACnD;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8DA,MAAa,cACX,QACA,YACmB;CACnB,MAAM,aAAa,SAAS,UAAU,UAAqB,KAAK,UAAU,KAAK;CAC/E,MAAM,WAAW,UACf,OAAO,SAAS,UAAU,aAAa,QAAQ,MAAM,KAAK,IAAI,SAAS;CAEzE,QAAQ,UAAU;EAEhB,MAAM,SAAS,OAAO,KACpB,OAAO,KAAK,UACV,eAAe;GACb,MAAM,WAAW,KAAK;GACtB,OAAO,QAAQ,KAAK;GACpB,IAAI,SAAS,KAAK,KAAK;EACzB,CAAC,CACH,CACF;EAGA,MAAM,YACJ,SAAS,UAAU,KAAA,IAAY,SAAS,OAAO,QAAQ,QAAQ,CAAC,YAAY,QAAQ,KAAK,CAAC,CAAC;EAG7F,MAAM,SACJ,SAAS,cAAc,KAAA,IACnB,YACA,OAAO,MACL,WACA,OAAO,aAAa,SAAS,OAAO,QAAQ,SAAS,CAAC,CAAC,CAAC,KACtD,OAAO,UAAU,eAAe,CAClC,GACA,EAAE,cAAc,OAAO,CACzB;EAEN,MAAM,QAAQ,OAAO,WAAW,MAAM;EACtC,MAAM,WACJ,SAAS,YAAY,KAAA,IAAY,QAAQ,OAAO,QAAQ,OAAO,QAAQ,OAAO;EAEhF,MAAM,OAAO,OAAO,iBAAiB,QAAwC;EAI7E,OAAO,IAAI,SAAS,MAAM,EAAE,SAAS,EAAE,GAAG,YAAY,EAAE,CAAC;CAC3D;AACF"}
|
|
1
|
+
{"version":3,"file":"Endpoint.js","names":[],"sources":["../../src/server/Endpoint.ts"],"sourcesContent":["/**\n * Create SvelteKit server endpoints (`+server.ts`) backed by Effect — the\n * `Endpoint` namespace of `@thomasfosterau/effect-sveltekit/server`.\n *\n * A SvelteKit endpoint is a `RequestHandler` (`(event) => Response`). Effect can\n * turn an [`HttpApi`](https://effect.website), an `HttpRouter`, or a single\n * route effect into a Web `fetch` handler; these helpers wrap one as a SvelteKit\n * endpoint handler so a route can be served by Effect. Mount it on a catch-all\n * route (`[...path]`) and assign it to `fallback` so every method and sub-path\n * reaches it:\n *\n * @example\n * ```ts\n * // src/routes/api/[...path]/+server.ts\n * import { Endpoint } from \"@thomasfosterau/effect-sveltekit/server\";\n * import { api } from \"#lib/server/api.ts\";\n * import { ApiLive } from \"#lib/server/handlers.ts\"; // HttpApiBuilder.group(...) + your services\n *\n * // `prefix` strips the SvelteKit mount path so the API's own paths\n * // (e.g. \"/users\") match.\n * export const fallback = Endpoint.fromApi(api, ApiLive, { prefix: \"/api\" });\n * ```\n *\n * The layer backing an endpoint is built once, lazily, on the first request and\n * shared across subsequent requests (like `makeServerRuntime`). The returned\n * handler carries a `dispose()` that closes the layer's scope — call it on\n * shutdown if the endpoint holds resources.\n *\n * The Effect handlers receive an Effect-native `HttpServerRequest` (built from\n * `event.request`), not the SvelteKit `RequestEvent`.\n */\n\nimport type { RequestHandler } from \"@sveltejs/kit\";\nimport * as Context from \"effect/Context\";\nimport type * as Duration from \"effect/Duration\";\nimport * as Effect from \"effect/Effect\";\nimport * as Layer from \"effect/Layer\";\nimport * as Predicate from \"effect/Predicate\";\nimport * as Schema from \"effect/Schema\";\nimport * as Schedule from \"effect/Schedule\";\nimport * as Stream from \"effect/Stream\";\nimport type * as Scope from \"effect/Scope\";\nimport * as FileSystem from \"effect/FileSystem\";\nimport * as Path from \"effect/Path\";\nimport * as Etag from \"effect/http/Etag\";\nimport * as HttpEffect from \"effect/http/HttpEffect\";\nimport * as HttpPlatform from \"effect/http/HttpPlatform\";\nimport * as HttpRouter from \"effect/http/HttpRouter\";\nimport type * as HttpServerRequest from \"effect/http/HttpServerRequest\";\nimport type * as HttpServerResponse from \"effect/http/HttpServerResponse\";\nimport type * as HttpApi from \"effect/http-api/HttpApi\";\nimport * as HttpApiBuilder from \"effect/http-api/HttpApiBuilder\";\nimport type * as HttpApiGroup from \"effect/http-api/HttpApiGroup\";\nimport { encodeRetry, encodeSseFrame, HEARTBEAT_FRAME, SSE_HEADERS } from \"../internal/sse.js\";\n\n/** A Web `fetch`-style handler, as produced by Effect's `toWebHandler` family. */\ntype WebHandler = (request: Request) => Promise<Response>;\n\n/** A Web handler that also accepts a per-request service context. */\ntype WebHandlerWithContext = (\n request: Request,\n context?: Context.Context<never>,\n) => Promise<Response>;\n\n/**\n * Resolve a request-scoped service context, if a resolver was given. The\n * resolver may return a `Context` directly or an `Effect` producing one (run\n * with no requirements), so a consumer can read the request to build\n * session-aware services per dispatch.\n */\nexport type RequestContext<RequestServices> = (\n request: Request,\n) =>\n | Context.Context<RequestServices>\n | Effect.Effect<Context.Context<RequestServices>, never, never>;\n\nconst resolveRequestContext = async <RequestServices>(\n request: Request,\n context: RequestContext<RequestServices> | undefined,\n): Promise<Context.Context<RequestServices> | undefined> => {\n if (context === undefined) return undefined;\n const result = context(request);\n return Effect.isEffect(result) ? Effect.runPromise(result) : result;\n};\n\n/**\n * A SvelteKit endpoint handler — the built-in\n * [`RequestHandler`](https://svelte.dev/docs/kit/@sveltejs-kit#RequestHandler) —\n * backed by an Effect HTTP construct. Assign it to a `+server.ts` export\n * (`fallback`, `GET`, ...). `dispose()` closes the scope of the layer behind it.\n */\nexport type EndpointHandler = RequestHandler & {\n /** Close the scope of the layer backing the endpoint (runs its finalizers). */\n readonly dispose: () => Promise<void>;\n};\n\n/** Shared options for every `Endpoint.from*` helper. */\nexport interface EndpointOptions {\n /**\n * A leading path segment to strip from each request before it reaches the\n * Effect handler — the SvelteKit mount point. For an endpoint mounted at\n * `src/routes/api/[...path]/+server.ts` whose routes are defined relative to\n * the API root, pass `prefix: \"/api\"` so `/api/users` is dispatched as\n * `/users`. The match respects path boundaries (`/api` does not strip\n * `/apiary`). Defaults to no stripping.\n */\n readonly prefix?: string;\n}\n\nconst noopDispose = (): Promise<void> => Promise.resolve();\n\n/** Remove `prefix` from the request's path (respecting `/` boundaries). */\nconst stripPrefix = (request: Request, prefix: string): Request => {\n const normalized = prefix.endsWith(\"/\") ? prefix.slice(0, -1) : prefix;\n if (normalized === \"\") return request;\n\n const url = new URL(request.url);\n const path = url.pathname;\n if (path === normalized) {\n url.pathname = \"/\";\n } else if (path.startsWith(`${normalized}/`)) {\n url.pathname = path.slice(normalized.length);\n } else {\n return request;\n }\n return new Request(url, request);\n};\n\n/**\n * Wrap a Web handler (and its dispose) as a SvelteKit endpoint handler. A\n * {@link WebHandler} is a {@link WebHandlerWithContext} that ignores the context.\n */\nconst make = <RequestServices = never>(\n webHandler: WebHandlerWithContext,\n dispose: () => Promise<void>,\n prefix: string | undefined,\n context?: RequestContext<RequestServices>,\n): EndpointHandler => {\n const handler: RequestHandler = async (event) => {\n const request = prefix === undefined ? event.request : stripPrefix(event.request, prefix);\n if (context === undefined) return webHandler(request);\n const ctx = await resolveRequestContext(request, context);\n return webHandler(request, ctx);\n };\n return Object.assign(handler, { dispose });\n};\n\n/** The platform services an `HttpApi` needs, with a no-op file system. */\nconst apiPlatform = Layer.mergeAll(Path.layer, Etag.layerWeak, HttpPlatform.layer).pipe(\n Layer.provideMerge(FileSystem.layerNoop({})),\n);\n\n/**\n * Create a SvelteKit endpoint that serves an Effect\n * [`HttpApi`](https://effect.website).\n *\n * `layer` implements the API's groups (built with `HttpApiBuilder.group`), with\n * any application services already provided. The platform services an `HttpApi`\n * requires (`HttpPlatform`, `FileSystem`, `Path`, `Etag`) are supplied for you.\n *\n * @example\n * ```ts\n * // src/routes/api/[...path]/+server.ts\n * import { Endpoint } from \"@thomasfosterau/effect-sveltekit/server\";\n * import { api } from \"#lib/server/api.ts\";\n * import { ApiLive } from \"#lib/server/handlers.ts\";\n *\n * export const fallback = Endpoint.fromApi(api, ApiLive, { prefix: \"/api\" });\n * ```\n */\nexport const fromApi = <\n Id extends string,\n Groups extends HttpApiGroup.Constraint,\n E,\n LayerRequires = never,\n RequestServices = never,\n>(\n api: HttpApi.HttpApi<Id, Groups>,\n layer: Layer.Layer<HttpApiGroup.ToService<Id, Groups>, E, LayerRequires>,\n options?: EndpointOptions & {\n /** Serve the OpenAPI spec for the API at this path (e.g. `\"/openapi.json\"`). */\n readonly openapiPath?: `/${string}`;\n /** Add Effect's request logger middleware. Defaults to `false` (quiet). */\n readonly logger?: boolean;\n /**\n * Provide the request-scoped services the API groups require, per request.\n * The build-once platform layer (`HttpPlatform` / `FileSystem` / `Path` /\n * `Etag`) stays shared; only this slice is resolved and merged for each\n * dispatch — so a session-aware, request-pooled API can run on Workers\n * without rebuilding its platform layer.\n *\n * The resolver may return a `Context` or an `Effect` producing one. Leave\n * the matching services unprovided in `layer` and supply them here:\n *\n * ```ts\n * Endpoint.fromApi(api, ApiLive, {\n * prefix: \"/api\",\n * context: (request) => Context.make(CurrentUser, userFor(request)),\n * });\n * ```\n */\n readonly context?: RequestContext<RequestServices>;\n },\n): EndpointHandler => {\n const appLayer = HttpApiBuilder.layer(\n api,\n options?.openapiPath ? { openapiPath: options.openapiPath } : undefined,\n ).pipe(Layer.provide(layer), Layer.provide(apiPlatform));\n\n // The request-scoped services are supplied per dispatch via the web handler's\n // context argument, so they are hidden from the build-once layer here.\n const { handler, dispose } = HttpRouter.toWebHandler(\n // SAFETY: the layer's remaining requirements beyond `HttpRouter` are the\n // groups' request services, which `make` supplies on each dispatch from the\n // context `options.context` resolves.\n appLayer as Layer.Layer<never, E, HttpRouter.HttpRouter>,\n { disableLogger: options?.logger !== true },\n );\n return make<RequestServices>(handler, dispose, options?.prefix, options?.context);\n};\n\n/**\n * Create a SvelteKit endpoint that serves an Effect `HttpRouter` (provided by\n * `layer`). The router's routes must be self-contained (any services they need\n * are provided within `layer`).\n *\n * @example\n * ```ts\n * // src/routes/api/[...path]/+server.ts\n * import { Endpoint } from \"@thomasfosterau/effect-sveltekit/server\";\n * import { Effect } from \"effect\";\n * import { HttpRouter, HttpServerResponse } from \"effect/http\";\n *\n * const RouterLive = HttpRouter.use((router) =>\n * router.add(\"GET\", \"/hello\", Effect.succeed(HttpServerResponse.text(\"hi\"))),\n * );\n *\n * export const fallback = Endpoint.fromRouter(RouterLive, { prefix: \"/api\" });\n * ```\n */\nexport const fromRouter = <\n A,\n E,\n R extends\n | HttpRouter.HttpRouter\n | HttpRouter.Request<\"Requires\", any>\n | HttpRouter.Request<\"GlobalRequires\", any>\n | HttpRouter.Request<\"Error\", any>\n | HttpRouter.Request<\"GlobalError\", any>,\n RequestServices = never,\n>(\n layer: Layer.Layer<A, E, R>,\n options?: EndpointOptions & {\n /** Add Effect's request logger middleware. Defaults to `false` (quiet). */\n readonly logger?: boolean;\n /**\n * Provide the request-scoped services the router's routes require, per\n * request (the routes' `HttpRouter.Request<\"Requires\", ...>` services). The\n * resolver may return a `Context` or an `Effect` producing one.\n */\n readonly context?: RequestContext<RequestServices>;\n },\n): EndpointHandler => {\n const { handler, dispose } = HttpRouter.toWebHandler(layer, {\n disableLogger: options?.logger !== true,\n });\n return make<RequestServices>(\n // SAFETY: the routes' per-request services are the `RequestServices` that\n // `make` resolves from `options.context` and passes on each dispatch; the\n // handler's conditional type only stays unresolved while `R` is generic.\n handler as WebHandlerWithContext,\n dispose,\n options?.prefix,\n options?.context,\n );\n};\n\n/**\n * Create a SvelteKit endpoint from a single Effect HTTP route — an effect\n * producing an `HttpServerResponse` for the current `HttpServerRequest`. Pass\n * `options.layer` to provide the route's services; the layer is built once and\n * shared.\n *\n * @example\n * ```ts\n * // src/routes/health/+server.ts\n * import { Endpoint } from \"@thomasfosterau/effect-sveltekit/server\";\n * import { Effect } from \"effect\";\n * import { HttpServerResponse } from \"effect/http\";\n *\n * export const GET = Endpoint.fromHttpEffect(\n * Effect.succeed(HttpServerResponse.text(\"ok\")),\n * );\n * ```\n */\nexport const fromHttpEffect = <E, R = never, ProvidedE = never>(\n effect: Effect.Effect<\n HttpServerResponse.HttpServerResponse,\n E,\n R | HttpServerRequest.HttpServerRequest | Scope.Scope\n >,\n options?: EndpointOptions & {\n /** A layer providing the route's services (`R`), built once and shared. */\n readonly layer?: Layer.Layer<R, ProvidedE>;\n },\n): EndpointHandler => {\n if (options?.layer) {\n const { handler, dispose } = HttpEffect.toWebHandlerLayer(effect, options.layer);\n // SAFETY: `options.layer` provides `R` and the web handler provides the\n // request and scope, so no per-request context is needed; the conditional\n // type only stays unresolved while `R` is generic.\n return make(handler as WebHandler, dispose, options.prefix);\n }\n const handler = HttpEffect.toWebHandler(\n // SAFETY: without `options.layer` nothing can provide `R`, so the route\n // must be self-contained (`R` is `never`, its default).\n effect as Effect.Effect<\n HttpServerResponse.HttpServerResponse,\n E,\n HttpServerRequest.HttpServerRequest | Scope.Scope\n >,\n );\n return make(handler, noopDispose, options?.prefix);\n};\n\n/** Options for {@link fromStream}: how to frame each element as an SSE event. */\nexport interface FromStreamOptions<A, R = never> {\n /**\n * The event name (`event:` field). A string applies to every element, or a\n * function derives it per element (return `undefined` for an unnamed event).\n * Omit for the default unnamed `message` events.\n */\n readonly event?: string | ((value: A) => string | undefined);\n /**\n * Encode an element into the `data:` payload. Defaults to JSON (an element\n * JSON cannot represent, such as a `bigint`, ends the stream with a defect).\n * Multi-line output is split across multiple `data:` lines automatically.\n */\n readonly data?: (value: A) => string;\n /** Derive an event id (`id:` field) the browser echoes on reconnect. */\n readonly id?: (value: A) => string | undefined;\n /**\n * The client reconnection delay in milliseconds, sent once as a leading\n * `retry:` field. The browser's `EventSource` reconnects automatically after\n * this delay when the connection drops.\n */\n readonly retry?: number;\n /**\n * Send a comment heartbeat on this interval to keep the connection alive\n * through proxies that time out idle streams. Accepts any `Duration.Input`\n * (e.g. `\"30 seconds\"`).\n */\n readonly heartbeat?: Duration.Input;\n /**\n * A layer (or context) providing the stream's services `R`, applied per\n * connection. Omit when the stream is self-contained (`R = never`).\n */\n readonly provide?: Layer.Layer<R> | Context.Context<R>;\n}\n\n/** `fromStream`'s default `data:` encoder: the element as a JSON string. */\nconst encodeJson: <A>(value: A) => string = Schema.encodeSync(\n Schema.fromJsonString(Schema.Unknown),\n);\n\n/**\n * Create a SvelteKit endpoint that streams an Effect `Stream` to the browser as\n * [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)\n * (`text/event-stream`). Consume it client-side with `Realtime.fromEventSource`\n * (from `@thomasfosterau/effect-sveltekit/client`) or the native `EventSource`.\n *\n * Each element is framed as one SSE event (`event` / `id` optional, `data`\n * defaults to JSON). A `retry` sets the reconnect delay; a `heartbeat` keeps\n * idle connections alive. The response closes when the stream ends; the stream\n * is interrupted (running its finalizers) when the client disconnects.\n *\n * @example\n * ```ts\n * // src/routes/events/+server.ts\n * import { Endpoint } from \"@thomasfosterau/effect-sveltekit/server\";\n * import { Schedule, Stream } from \"effect\";\n *\n * const ticks = Stream.fromSchedule(Schedule.spaced(\"1 second\"));\n *\n * export const GET = Endpoint.fromStream(ticks, {\n * event: \"tick\",\n * heartbeat: \"30 seconds\",\n * retry: 3000,\n * });\n * ```\n */\nexport const fromStream = <A, E, R = never>(\n stream: Stream.Stream<A, E, R>,\n options?: FromStreamOptions<A, R>,\n): RequestHandler => {\n const encodeData = options?.data ?? encodeJson;\n const eventOf = (value: A): string | undefined => {\n const name = options?.event;\n return Predicate.isFunction(name) ? name(value) : name;\n };\n\n return (event) => {\n // Frame each element, then encode the resulting strings to bytes.\n const events = stream.pipe(\n Stream.map((value) =>\n encodeSseFrame({\n data: encodeData(value),\n event: eventOf(value),\n id: options?.id?.(value),\n }),\n ),\n );\n\n // A leading `retry:` field, if configured.\n const withRetry =\n options?.retry === undefined ? events : Stream.prepend(events, [encodeRetry(options.retry)]);\n\n // Merge in a heartbeat that halts as soon as the data stream ends.\n const framed =\n options?.heartbeat === undefined\n ? withRetry\n : Stream.merge(\n withRetry,\n Stream.fromSchedule(Schedule.spaced(options.heartbeat)).pipe(\n Stream.map(() => HEARTBEAT_FRAME),\n ),\n { haltStrategy: \"left\" },\n );\n\n const bytes = Stream.encodeText(framed);\n const provided =\n options?.provide === undefined ? bytes : Stream.provide(bytes, options.provide);\n\n // SAFETY: `options.provide` supplies `R`, and is omitted only for a\n // self-contained stream (`R` is `never`, its default).\n const body = Stream.toReadableStream(provided as Stream.Stream<Uint8Array, E>);\n\n // SvelteKit forwards a streaming body Response as-is.\n void event;\n return new Response(body, { headers: { ...SSE_HEADERS } });\n };\n};\n"],"mappings":"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"}
|
package/dist/server/Hooks.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SvelteKitServices } from "../app-runtime.js";
|
|
2
|
-
import
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
3
|
import { Handle, HandleFetch, HandleServerError, ServerInit } from "@sveltejs/kit/hooks";
|
|
4
4
|
declare namespace Hooks_d_exports {
|
|
5
5
|
export { handle, handleError, handleFetch, init };
|
|
@@ -28,7 +28,7 @@ declare namespace Hooks_d_exports {
|
|
|
28
28
|
* );
|
|
29
29
|
* ```
|
|
30
30
|
*/
|
|
31
|
-
declare function handle(fn: (args: Parameters<Handle>[0]) => Effect.Effect<Response, unknown, SvelteKitServices>): Handle;
|
|
31
|
+
export declare function handle(fn: (args: Parameters<Handle>[0]) => Effect.Effect<Response, unknown, SvelteKitServices>): Handle;
|
|
32
32
|
/**
|
|
33
33
|
* Wraps an Effect-based function as a SvelteKit
|
|
34
34
|
* [`handleFetch`](https://svelte.dev/docs/kit/hooks#Server-hooks-handleFetch)
|
|
@@ -50,7 +50,7 @@ declare function handle(fn: (args: Parameters<Handle>[0]) => Effect.Effect<Respo
|
|
|
50
50
|
* );
|
|
51
51
|
* ```
|
|
52
52
|
*/
|
|
53
|
-
declare function handleFetch(fn: (args: Parameters<HandleFetch>[0]) => Effect.Effect<Response, unknown, SvelteKitServices>): HandleFetch;
|
|
53
|
+
export declare function handleFetch(fn: (args: Parameters<HandleFetch>[0]) => Effect.Effect<Response, unknown, SvelteKitServices>): HandleFetch;
|
|
54
54
|
/**
|
|
55
55
|
* Wraps an Effect-based function as the SvelteKit server
|
|
56
56
|
* [`handleError`](https://svelte.dev/docs/kit/hooks#Shared-hooks-handleError)
|
|
@@ -73,7 +73,7 @@ declare function handleFetch(fn: (args: Parameters<HandleFetch>[0]) => Effect.Ef
|
|
|
73
73
|
* );
|
|
74
74
|
* ```
|
|
75
75
|
*/
|
|
76
|
-
declare function handleError(fn: (args: Parameters<HandleServerError>[0]) => Effect.Effect<Awaited<ReturnType<HandleServerError>>, unknown, SvelteKitServices>): HandleServerError;
|
|
76
|
+
export declare function handleError(fn: (args: Parameters<HandleServerError>[0]) => Effect.Effect<Awaited<ReturnType<HandleServerError>>, unknown, SvelteKitServices>): HandleServerError;
|
|
77
77
|
/**
|
|
78
78
|
* Wraps an Effect as the SvelteKit server
|
|
79
79
|
* [`init`](https://svelte.dev/docs/kit/hooks#Shared-hooks-init) hook, which
|
|
@@ -89,7 +89,7 @@ declare function handleError(fn: (args: Parameters<HandleServerError>[0]) => Eff
|
|
|
89
89
|
* export const init = Hooks.init(Effect.logInfo('server starting up'));
|
|
90
90
|
* ```
|
|
91
91
|
*/
|
|
92
|
-
declare function init(effect: Effect.Effect<void, unknown>): ServerInit;
|
|
92
|
+
export declare function init(effect: Effect.Effect<void, unknown>): ServerInit;
|
|
93
93
|
//#endregion
|
|
94
|
-
export { Hooks_d_exports
|
|
94
|
+
export { Hooks_d_exports };
|
|
95
95
|
//# sourceMappingURL=Hooks.d.ts.map
|
package/dist/server/Hooks.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { __exportAll } from "../_virtual/_rolldown/runtime.js";
|
|
2
2
|
import { defaultServerRuntime } from "../internal/default-runtime.js";
|
|
3
|
-
import "effect";
|
|
3
|
+
import "effect/Effect";
|
|
4
4
|
//#region src/server/Hooks.ts
|
|
5
5
|
var Hooks_exports = /* @__PURE__ */ __exportAll({
|
|
6
6
|
handle: () => handle,
|
package/dist/server/Hooks.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Hooks.js","names":[],"sources":["../../src/server/Hooks.ts"],"sourcesContent":["/**\n * Effect wrappers for SvelteKit's server-side hooks (`src/hooks.server.ts`),\n * exposed as the `Hooks` namespace of `@thomasfosterau/effect-sveltekit/server`.\n *\n * Each helper turns an Effect (or a function returning one) into the\n * function-shaped export SvelteKit expects. Effects run on the SvelteKit server\n * runtime, so they have access to platform services (`HttpClient`, the\n * `RequestEvent`, ...). To additionally provide your application's services,\n * use the matching methods on an app-wide runtime from `makeServerRuntime`.\n *\n * @example\n * ```ts\n * // src/hooks.server.ts\n * import { Hooks } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n *\n * export const handle = Hooks.handle(({ event, resolve }) =>\n * Effect.gen(function* () {\n * const response = yield* Effect.promise(() => resolve(event));\n * yield* Effect.logInfo(`${event.url.pathname} -> ${response.status}`);\n * return response;\n * })\n * );\n * ```\n */\n\nimport type { Handle, HandleFetch, HandleServerError, ServerInit } from \"@sveltejs/kit/hooks\";\nimport
|
|
1
|
+
{"version":3,"file":"Hooks.js","names":[],"sources":["../../src/server/Hooks.ts"],"sourcesContent":["/**\n * Effect wrappers for SvelteKit's server-side hooks (`src/hooks.server.ts`),\n * exposed as the `Hooks` namespace of `@thomasfosterau/effect-sveltekit/server`.\n *\n * Each helper turns an Effect (or a function returning one) into the\n * function-shaped export SvelteKit expects. Effects run on the SvelteKit server\n * runtime, so they have access to platform services (`HttpClient`, the\n * `RequestEvent`, ...). To additionally provide your application's services,\n * use the matching methods on an app-wide runtime from `makeServerRuntime`.\n *\n * @example\n * ```ts\n * // src/hooks.server.ts\n * import { Hooks } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n *\n * export const handle = Hooks.handle(({ event, resolve }) =>\n * Effect.gen(function* () {\n * const response = yield* Effect.promise(() => resolve(event));\n * yield* Effect.logInfo(`${event.url.pathname} -> ${response.status}`);\n * return response;\n * })\n * );\n * ```\n */\n\nimport type { Handle, HandleFetch, HandleServerError, ServerInit } from \"@sveltejs/kit/hooks\";\nimport * as Effect from \"effect/Effect\";\nimport type { SvelteKitServices } from \"../app-runtime.js\";\nimport { defaultServerRuntime } from \"../internal/default-runtime.js\";\n\n/**\n * Wraps an Effect-based function as a SvelteKit\n * [`handle`](https://svelte.dev/docs/kit/hooks#Server-hooks-handle) hook, which\n * runs on every request and controls how the response is produced.\n *\n * SvelteKit control flow (`redirect()`, `error()`) raised inside the effect is\n * passed through to SvelteKit.\n *\n * @example\n * ```ts\n * // src/hooks.server.ts\n * import { Hooks } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n *\n * export const handle = Hooks.handle(({ event, resolve }) =>\n * Effect.gen(function* () {\n * // custom logic before resolve\n * const response = yield* Effect.promise(() => resolve(event));\n * // custom logic after resolve\n * return response;\n * })\n * );\n * ```\n */\nexport function handle(\n fn: (args: Parameters<Handle>[0]) => Effect.Effect<Response, unknown, SvelteKitServices>,\n): Handle {\n return defaultServerRuntime().handle(fn);\n}\n\n/**\n * Wraps an Effect-based function as a SvelteKit\n * [`handleFetch`](https://svelte.dev/docs/kit/hooks#Server-hooks-handleFetch)\n * hook, which intercepts `event.fetch` calls made on the server (in `load`,\n * actions, endpoints, ...). The effect produces the `Response` to use.\n *\n * @example\n * ```ts\n * // src/hooks.server.ts\n * import { Hooks } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n *\n * export const handleFetch = Hooks.handleFetch(({ request, fetch }) =>\n * Effect.gen(function* () {\n * // Redirect internal API calls to the upstream service.\n * const url = request.url.replace('https://api.internal', 'http://localhost:9999');\n * return yield* Effect.promise(() => fetch(new Request(url, request)));\n * })\n * );\n * ```\n */\nexport function handleFetch(\n fn: (args: Parameters<HandleFetch>[0]) => Effect.Effect<Response, unknown, SvelteKitServices>,\n): HandleFetch {\n return defaultServerRuntime().handleFetch(fn);\n}\n\n/**\n * Wraps an Effect-based function as the SvelteKit server\n * [`handleError`](https://svelte.dev/docs/kit/hooks#Shared-hooks-handleError)\n * hook, which runs when an unexpected error is thrown while responding to a\n * request. The effect receives the error and request event and produces the\n * shape exposed to the client (your `App.Error`, or nothing).\n *\n * @example\n * ```ts\n * // src/hooks.server.ts\n * import { Hooks } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n *\n * export const handleError = Hooks.handleError(({ error, event }) =>\n * Effect.gen(function* () {\n * const id = crypto.randomUUID();\n * yield* Effect.logError(`${event.url.pathname} (${id})`, error);\n * return { message: 'Internal Error', id };\n * })\n * );\n * ```\n */\nexport function handleError(\n fn: (\n args: Parameters<HandleServerError>[0],\n ) => Effect.Effect<Awaited<ReturnType<HandleServerError>>, unknown, SvelteKitServices>,\n): HandleServerError {\n return defaultServerRuntime().handleError(fn);\n}\n\n/**\n * Wraps an Effect as the SvelteKit server\n * [`init`](https://svelte.dev/docs/kit/hooks#Shared-hooks-init) hook, which\n * runs once before the server responds to its first request. There is no\n * request event available, so the effect runs without per-request services.\n *\n * @example\n * ```ts\n * // src/hooks.server.ts\n * import { Hooks } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n *\n * export const init = Hooks.init(Effect.logInfo('server starting up'));\n * ```\n */\nexport function init(effect: Effect.Effect<void, unknown>): ServerInit {\n return defaultServerRuntime().init(effect);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuDA,SAAgB,OACd,IACQ;CACR,OAAO,qBAAqB,CAAC,CAAC,OAAO,EAAE;AACzC;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,YACd,IACa;CACb,OAAO,qBAAqB,CAAC,CAAC,YAAY,EAAE;AAC9C;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,YACd,IAGmB;CACnB,OAAO,qBAAqB,CAAC,CAAC,YAAY,EAAE;AAC9C;;;;;;;;;;;;;;;;AAiBA,SAAgB,KAAK,QAAkD;CACrE,OAAO,qBAAqB,CAAC,CAAC,KAAK,MAAM;AAC3C"}
|
package/dist/server.d.ts
CHANGED
|
@@ -7,12 +7,13 @@ import { ResponseHeaders } from "./response-headers.js";
|
|
|
7
7
|
import { sveltekitConfig, sveltekitConfigProvider } from "./config.js";
|
|
8
8
|
import { Hooks_d_exports } from "./server/Hooks.js";
|
|
9
9
|
import { Endpoint_d_exports } from "./server/Endpoint.js";
|
|
10
|
-
import { RemoteFunction } from "./remote-functions.js";
|
|
10
|
+
import { RemoteFunction, RemoteFunctionUsageError } from "./remote-functions.js";
|
|
11
11
|
import { ParseError, ParseIssue, decodeFormData, decodeJson, decodeParams, decodeSearchParams } from "./request-parse.js";
|
|
12
12
|
import { defer } from "./streamed-load.js";
|
|
13
|
+
import { KitErrorBody } from "./internal/error.js";
|
|
13
14
|
import { KitActionFailure, KitError, KitErrorTranslator, KitRedirect, KitTranslatorCase, SvelteKit } from "./kit.js";
|
|
14
15
|
import { errors_d_exports } from "./errors.js";
|
|
15
|
-
import
|
|
16
|
+
import * as Effect from "effect/Effect";
|
|
16
17
|
import { Action, Actions, RequestEvent, ServerLoad } from "@sveltejs/kit";
|
|
17
18
|
//#region src/server.d.ts
|
|
18
19
|
/**
|
|
@@ -42,7 +43,7 @@ import { Action, Actions, RequestEvent, ServerLoad } from "@sveltejs/kit";
|
|
|
42
43
|
* );
|
|
43
44
|
* ```
|
|
44
45
|
*/
|
|
45
|
-
declare function serverLoad<Params extends SvelteRouteParams = SvelteRouteParams, ParentData extends Record<string, any> = any, OutputData extends Record<string, any> | void = any, RouteId extends string | null = any>(fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, SvelteKitServices>): ServerLoad<Params, ParentData, OutputData, RouteId>;
|
|
46
|
+
export declare function serverLoad<Params extends SvelteRouteParams = SvelteRouteParams, ParentData extends Record<string, any> = any, OutputData extends Record<string, any> | void = any, RouteId extends string | null = any>(fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, SvelteKitServices>): ServerLoad<Params, ParentData, OutputData, RouteId>;
|
|
46
47
|
/**
|
|
47
48
|
* Wraps an Effect-based function to work as a SvelteKit action handler.
|
|
48
49
|
* The effect has access to platform services (HttpClient, the RequestEvent, etc.)
|
|
@@ -64,7 +65,7 @@ declare function serverLoad<Params extends SvelteRouteParams = SvelteRouteParams
|
|
|
64
65
|
* };
|
|
65
66
|
* ```
|
|
66
67
|
*/
|
|
67
|
-
declare function action<Params extends SvelteRouteParams = SvelteRouteParams, OutputData extends Record<string, any> | void = any, RouteId extends string | null = any>(fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, SvelteKitServices>): Action<Params, OutputData, RouteId>;
|
|
68
|
+
export declare function action<Params extends SvelteRouteParams = SvelteRouteParams, OutputData extends Record<string, any> | void = any, RouteId extends string | null = any>(fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, SvelteKitServices>): Action<Params, OutputData, RouteId>;
|
|
68
69
|
/**
|
|
69
70
|
* Creates SvelteKit actions from an object of Effect-based action handlers.
|
|
70
71
|
* Each action will have access to platform services via the SvelteKit runtime.
|
|
@@ -80,7 +81,7 @@ declare function action<Params extends SvelteRouteParams = SvelteRouteParams, Ou
|
|
|
80
81
|
* });
|
|
81
82
|
* ```
|
|
82
83
|
*/
|
|
83
|
-
declare function actions<Params extends SvelteRouteParams = SvelteRouteParams, OutputData extends Record<string, any> | void = any, RouteId extends string | null = any>(actionsMap: Record<string, (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, SvelteKitServices>>): Actions<Params, OutputData, RouteId>;
|
|
84
|
+
export declare function actions<Params extends SvelteRouteParams = SvelteRouteParams, OutputData extends Record<string, any> | void = any, RouteId extends string | null = any>(actionsMap: Record<string, (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, SvelteKitServices>>): Actions<Params, OutputData, RouteId>;
|
|
84
85
|
/**
|
|
85
86
|
* Wraps an Effect-based function to work as a SvelteKit API endpoint handler.
|
|
86
87
|
* The effect has access to platform services (HttpClient, the RequestEvent, etc.)
|
|
@@ -107,9 +108,9 @@ declare function actions<Params extends SvelteRouteParams = SvelteRouteParams, O
|
|
|
107
108
|
* );
|
|
108
109
|
* ```
|
|
109
110
|
*/
|
|
110
|
-
declare function endpoint<Params extends SvelteRouteParams = SvelteRouteParams, RouteId extends string | null = any>(fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<Response, unknown, SvelteKitServices>, options?: {
|
|
111
|
+
export declare function endpoint<Params extends SvelteRouteParams = SvelteRouteParams, RouteId extends string | null = any>(fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<Response, unknown, SvelteKitServices>, options?: {
|
|
111
112
|
exposeErrors?: boolean;
|
|
112
113
|
}): (event: RequestEvent<Params, RouteId>) => Promise<Response>;
|
|
113
114
|
//#endregion
|
|
114
|
-
export { type AdapterEndpointOptions, Endpoint_d_exports as Endpoint, type ErrorTranslator, errors_d_exports as Errors, type FromLayerPerRequestOptions, Hooks_d_exports as Hooks, KitActionFailure, KitError, type KitErrorTranslator, KitRedirect, type KitTranslatorCase, ParseError, type ParseIssue, RemoteFunction, RequestRuntime, type RequestRuntimeProvider, type RequestRuntimeRunners, ResponseHeaders, type RuntimeAdapter, type RuntimeAdapterOptions, type RequestRuntime$1 as ScopedRequestRuntime, type ServerRuntime, type ServerRuntimeOptions, SvelteKit, SvelteKitCookies, type SvelteKitCookiesInterface, SvelteKitLocals, SvelteKitRequestEvent, SvelteKitResponseHeaders, type SvelteKitResponseHeadersInterface, SvelteKitRuntime, type SvelteKitServices,
|
|
115
|
+
export { type AdapterEndpointOptions, Endpoint_d_exports as Endpoint, type ErrorTranslator, errors_d_exports as Errors, type FromLayerPerRequestOptions, Hooks_d_exports as Hooks, KitActionFailure, KitError, type KitErrorBody, type KitErrorTranslator, KitRedirect, type KitTranslatorCase, ParseError, type ParseIssue, RemoteFunction, RemoteFunctionUsageError, RequestRuntime, type RequestRuntimeProvider, type RequestRuntimeRunners, ResponseHeaders, type RuntimeAdapter, type RuntimeAdapterOptions, type RequestRuntime$1 as ScopedRequestRuntime, type ServerRuntime, type ServerRuntimeOptions, SvelteKit, SvelteKitCookies, type SvelteKitCookiesInterface, SvelteKitLocals, SvelteKitRequestEvent, SvelteKitResponseHeaders, type SvelteKitResponseHeadersInterface, SvelteKitRuntime, type SvelteKitServices, decodeFormData, decodeJson, decodeParams, decodeSearchParams, defer, makeRuntimeAdapter, makeServerRuntime, requestServicesLayer, runWithSvelteKitRuntime, sveltekitConfig, sveltekitConfigProvider };
|
|
115
116
|
//# sourceMappingURL=server.d.ts.map
|
package/dist/server.js
CHANGED
|
@@ -7,13 +7,16 @@ import { ResponseHeaders } from "./response-headers.js";
|
|
|
7
7
|
import { sveltekitConfig, sveltekitConfigProvider } from "./config.js";
|
|
8
8
|
import { Hooks_exports } from "./server/Hooks.js";
|
|
9
9
|
import { Endpoint_exports } from "./server/Endpoint.js";
|
|
10
|
-
import { RemoteFunction } from "./remote-functions.js";
|
|
10
|
+
import { RemoteFunction, RemoteFunctionUsageError } from "./remote-functions.js";
|
|
11
11
|
import { RequestRuntime, makeRuntimeAdapter } from "./request-runtime.js";
|
|
12
12
|
import { ParseError, decodeFormData, decodeJson, decodeParams, decodeSearchParams } from "./request-parse.js";
|
|
13
13
|
import { defer } from "./streamed-load.js";
|
|
14
14
|
import { errors_exports } from "./errors.js";
|
|
15
|
-
import
|
|
15
|
+
import * as Effect from "effect/Effect";
|
|
16
|
+
import * as Exit from "effect/Exit";
|
|
16
17
|
import { isHttpError, isRedirect } from "@sveltejs/kit";
|
|
18
|
+
import * as Cause from "effect/Cause";
|
|
19
|
+
import * as Console from "effect/Console";
|
|
17
20
|
//#region src/server.ts
|
|
18
21
|
/**
|
|
19
22
|
* Wraps an Effect-based function to work as a SvelteKit server load function.
|
|
@@ -121,18 +124,15 @@ function endpoint(fn, options) {
|
|
|
121
124
|
onFailure: (cause) => {
|
|
122
125
|
const squashed = Cause.squash(cause);
|
|
123
126
|
if (isRedirect(squashed) || isHttpError(squashed)) throw squashed;
|
|
124
|
-
|
|
127
|
+
Effect.runSync(Console.error("Endpoint error:", Cause.pretty(cause)));
|
|
125
128
|
const clientMessage = exposeErrors ? Cause.pretty(cause) : "Internal server error";
|
|
126
|
-
return
|
|
127
|
-
status: 500,
|
|
128
|
-
headers: { "Content-Type": "application/json" }
|
|
129
|
-
});
|
|
129
|
+
return Response.json({ error: clientMessage }, { status: 500 });
|
|
130
130
|
},
|
|
131
131
|
onSuccess: (value) => value
|
|
132
132
|
});
|
|
133
133
|
};
|
|
134
134
|
}
|
|
135
135
|
//#endregion
|
|
136
|
-
export { Endpoint_exports as Endpoint, errors_exports as Errors, Hooks_exports as Hooks, KitActionFailure, KitError, KitRedirect, ParseError, RemoteFunction, RequestRuntime, ResponseHeaders, SvelteKit, SvelteKitCookies, SvelteKitLocals, SvelteKitRequestEvent, SvelteKitResponseHeaders, SvelteKitRuntime, action, actions, decodeFormData, decodeJson, decodeParams, decodeSearchParams, defer, endpoint, makeRuntimeAdapter, makeServerRuntime, requestServicesLayer, runWithSvelteKitRuntime, serverLoad, sveltekitConfig, sveltekitConfigProvider };
|
|
136
|
+
export { Endpoint_exports as Endpoint, errors_exports as Errors, Hooks_exports as Hooks, KitActionFailure, KitError, KitRedirect, ParseError, RemoteFunction, RemoteFunctionUsageError, RequestRuntime, ResponseHeaders, SvelteKit, SvelteKitCookies, SvelteKitLocals, SvelteKitRequestEvent, SvelteKitResponseHeaders, SvelteKitRuntime, action, actions, decodeFormData, decodeJson, decodeParams, decodeSearchParams, defer, endpoint, makeRuntimeAdapter, makeServerRuntime, requestServicesLayer, runWithSvelteKitRuntime, serverLoad, sveltekitConfig, sveltekitConfigProvider };
|
|
137
137
|
|
|
138
138
|
//# sourceMappingURL=server.js.map
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","names":["runtime"],"sources":["../src/server.ts"],"sourcesContent":["import type { Action, Actions, RequestEvent, ServerLoad } from \"@sveltejs/kit\";\nimport { Effect, Exit, Cause } from \"effect\";\nimport { isHttpError, isRedirect } from \"@sveltejs/kit\";\nimport type { SvelteKitServices } from \"./app-runtime.js\";\nimport type { SvelteRouteParams } from \"./internal/route-params.js\";\nimport { defaultServerRuntime as runtime } from \"./internal/default-runtime.js\";\n\nexport type { SvelteKitServices };\n\n/**\n * Wraps an Effect-based function to work as a SvelteKit server load function.\n * The effect has access to platform services (HttpClient, the RequestEvent, etc.)\n * via the SvelteKit runtime.\n *\n * SvelteKit control flow (`redirect()`, `error()`) raised inside the effect is\n * passed through to SvelteKit.\n *\n * To give effects access to your own application services, create an\n * app-wide runtime with {@link makeServerRuntime} and use its `serverLoad`\n * instead.\n *\n * @example\n * ```ts\n * import { serverLoad } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n * import * as HttpClient from 'effect/http/HttpClient';\n *\n * export const load = serverLoad((event) =>\n * Effect.gen(function* () {\n * const client = yield* HttpClient.HttpClient;\n * const response = yield* client.get(\"https://api.example.com/data\");\n * return { data: yield* response.json };\n * })\n * );\n * ```\n */\nexport function serverLoad<\n Params extends SvelteRouteParams = SvelteRouteParams,\n ParentData extends Record<string, any> = any,\n OutputData extends Record<string, any> | void = any,\n RouteId extends string | null = any,\n>(\n fn: (\n event: RequestEvent<Params, RouteId>,\n ) => Effect.Effect<OutputData, unknown, SvelteKitServices>,\n): ServerLoad<Params, ParentData, OutputData, RouteId> {\n return runtime().serverLoad(fn);\n}\n\n/**\n * Wraps an Effect-based function to work as a SvelteKit action handler.\n * The effect has access to platform services (HttpClient, the RequestEvent, etc.)\n * via the SvelteKit runtime.\n *\n * @example\n * ```ts\n * import { action } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n *\n * export const actions = {\n * default: action((event) =>\n * Effect.gen(function* () {\n * const formData = yield* Effect.promise(() => event.request.formData());\n * // Process form data\n * return { success: true };\n * })\n * )\n * };\n * ```\n */\nexport function action<\n Params extends SvelteRouteParams = SvelteRouteParams,\n OutputData extends Record<string, any> | void = any,\n RouteId extends string | null = any,\n>(\n fn: (\n event: RequestEvent<Params, RouteId>,\n ) => Effect.Effect<OutputData, unknown, SvelteKitServices>,\n): Action<Params, OutputData, RouteId> {\n return runtime().action(fn);\n}\n\n/**\n * Creates SvelteKit actions from an object of Effect-based action handlers.\n * Each action will have access to platform services via the SvelteKit runtime.\n *\n * @example\n * ```ts\n * import { actions } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n *\n * export const actions = actions({\n * create: (event) => Effect.succeed({ created: true }),\n * delete: (event) => Effect.succeed({ deleted: true })\n * });\n * ```\n */\nexport function actions<\n Params extends SvelteRouteParams = SvelteRouteParams,\n OutputData extends Record<string, any> | void = any,\n RouteId extends string | null = any,\n>(\n actionsMap: Record<\n string,\n (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, SvelteKitServices>\n >,\n): Actions<Params, OutputData, RouteId> {\n return runtime().actions(actionsMap);\n}\n\n/**\n * Wraps an Effect-based function to work as a SvelteKit API endpoint handler.\n * The effect has access to platform services (HttpClient, the RequestEvent, etc.)\n * via the SvelteKit runtime.\n *\n * SvelteKit control flow (`redirect()`, `error()`) raised inside the effect is\n * passed through to SvelteKit. For any other failure, the error is logged\n * server-side and a generic error message is returned to clients. For\n * development/debugging, you can pass options to expose full error details.\n *\n * @example\n * ```ts\n * import { endpoint } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n * import { json } from '@sveltejs/kit';\n * import * as HttpClient from 'effect/http/HttpClient';\n *\n * export const GET = endpoint((event) =>\n * Effect.gen(function* () {\n * const client = yield* HttpClient.HttpClient;\n * const response = yield* client.get(\"https://api.example.com/data\");\n * return json(yield* response.json);\n * })\n * );\n * ```\n */\nexport function endpoint<\n Params extends SvelteRouteParams = SvelteRouteParams,\n RouteId extends string | null = any,\n>(\n fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<Response, unknown, SvelteKitServices>,\n options?: { exposeErrors?: boolean },\n): (event: RequestEvent<Params, RouteId>) => Promise<Response> {\n const { exposeErrors = false } = options ?? {};\n\n return async (event) => {\n const exit = await runtime().runPromiseExit(event as RequestEvent, fn(event));\n\n return Exit.match(exit, {\n onFailure: (cause) => {\n // Let SvelteKit handle its own control flow objects.\n const squashed = Cause.squash(cause);\n if (isRedirect(squashed) || isHttpError(squashed)) {\n throw squashed;\n }\n\n // Always log the full error server-side for debugging\n console.error(\"Endpoint error:\", Cause.pretty(cause));\n\n // Return generic error message to client unless explicitly enabled\n const clientMessage = exposeErrors ? Cause.pretty(cause) : \"Internal server error\";\n\n return new Response(JSON.stringify({ error: clientMessage }), {\n status: 500,\n headers: { \"Content-Type\": \"application/json\" },\n });\n },\n onSuccess: (value) => value,\n });\n };\n}\n\n/**\n * Effect wrappers for the server-side hooks (`hooks.server.ts`): the `Hooks`\n * namespace exposes `handle`, `handleFetch`, `handleError`, and `init`.\n */\nexport * as Hooks from \"./server/Hooks.js\";\n\n/**\n * Create SvelteKit `+server.ts` endpoints backed by Effect: the `Endpoint`\n * namespace exposes `fromApi` (an `HttpApi`), `fromRouter` (an `HttpRouter`),\n * and `fromHttpEffect` (a single HTTP route effect).\n */\nexport * as Endpoint from \"./server/Endpoint.js\";\n\n/**\n * App-wide runtime: define your application's Effect services once and share\n * them across every request.\n */\nexport {\n makeServerRuntime,\n type ServerRuntime,\n type ServerRuntimeOptions,\n type RequestRuntime as ScopedRequestRuntime,\n} from \"./app-runtime.js\";\n\n/**\n * Export the SvelteKit runtime utilities for advanced use cases.\n */\nexport { SvelteKitRuntime, runWithSvelteKitRuntime, SvelteKitRequestEvent } from \"./runtime.js\";\nexport { RemoteFunction } from \"./remote-functions.js\";\n\n/**\n * A general, Effect-idiomatic seam for *how the per-request execution context is\n * obtained*. {@link RequestRuntime} constructs a {@link RequestRuntimeProvider}\n * (from a consumer-owned runtime on `event.locals`, a package-built\n * per-request runtime, a build-once `ManagedRuntime`, or a `Layer`), and\n * {@link makeRuntimeAdapter} binds the `serverLoad` / `action` / `actions` /\n * `endpoint` wrappers to it — so the handler effect's services `R` are your own\n * application catalogue, not the fixed {@link SvelteKitServices}. A\n * {@link RuntimeAdapterOptions.translateError} maps a failed `Cause` to\n * SvelteKit control flow at the boundary.\n */\nexport {\n RequestRuntime,\n makeRuntimeAdapter,\n type RequestRuntimeProvider,\n type RequestRuntimeRunners,\n type RuntimeAdapter,\n type RuntimeAdapterOptions,\n type AdapterEndpointOptions,\n type FromLayerPerRequestOptions,\n type ErrorTranslator,\n} from \"./request-runtime.js\";\n\n/**\n * SvelteKit `$env` ⇄ Effect `Config` (see {@link sveltekitConfig}). The\n * Effect Schema ⇄ SvelteKit `transport` hook lives on the universal `Hooks`\n * namespace (`Hooks.transport`) in `@thomasfosterau/effect-sveltekit`.\n */\nexport { sveltekitConfig, sveltekitConfigProvider } from \"./config.js\";\n\n/**\n * Schema-driven request parsing. Each helper reads a part of the current\n * request (route params, search params, JSON body, or form data), validates it\n * against an Effect `Schema` or Standard Schema V1 validator, and returns the\n * typed output. A validation failure surfaces as {@link ParseError}.\n *\n * All helpers require `SvelteKitRequestEvent` in the environment and compose\n * naturally inside `serverLoad` / `action` / `endpoint` handlers.\n */\nexport {\n decodeParams,\n decodeSearchParams,\n decodeJson,\n decodeFormData,\n ParseError,\n type ParseIssue,\n} from \"./request-parse.js\";\n\n/**\n * Streamed load helper. {@link defer} captures the current service context and\n * runs a slow Effect in the background, returning the `Promise` so SvelteKit\n * can stream the result to the browser while sending the initial HTML\n * immediately. Use inside `serverLoad` alongside eagerly-computed fields.\n */\nexport { defer } from \"./streamed-load.js\";\n\n/**\n * Effect-native SvelteKit control-flow helpers and the default boundary\n * translator.\n *\n * - `SvelteKit.redirect` / `SvelteKit.error` / `SvelteKit.fail` — yield typed\n * Effect failures instead of throwing SvelteKit control-flow objects.\n * - `SvelteKit.translate` — the default `translateError` wired into every\n * server wrapper; maps the Kit error types to `redirect()` / `error()` /\n * `fail()` at the request boundary.\n * - `SvelteKit.makeTranslator([...cases])` — compose `translate` with your own\n * domain-error matchers (consumer cases run before the Kit base cases).\n * - `KitRedirect`, `KitError`, `KitActionFailure` — the tagged error classes\n * for use with `Effect.catchTag`, `instanceof`, etc.\n * - `KitTranslatorCase` — the argument type for `makeTranslator`.\n */\nexport {\n SvelteKit,\n KitRedirect,\n KitError,\n KitActionFailure,\n type KitTranslatorCase,\n type KitErrorTranslator,\n} from \"./kit.js\";\n\n/**\n * A `translateError` built from a tag-keyed catalogue:\n * `Errors.toSvelteKit({ NotFound: 404, Forbidden: 403, Redirect: \"redirect\", HttpError: \"status\" })`.\n * Pass it to `makeRuntimeAdapter` / `RemoteFunction.withRuntime`; see\n * {@link Errors.toSvelteKit}.\n */\nexport * as Errors from \"./errors.js\";\n\n/** The response-headers buffer, re-exported from the universal entry for server-only modules. */\nexport { ResponseHeaders } from \"./response-headers.js\";\n\n/**\n * Effect-native services for the three per-request surfaces of a SvelteKit\n * `RequestEvent`: cookies, response headers, and locals.\n *\n * Each is a tagged `Context.Service` that is provided automatically in every\n * server handler effect (part of {@link SvelteKitServices}). Use\n * `yield* SvelteKitCookies` / `yield* SvelteKitResponseHeaders` /\n * `yield* SvelteKitLocals` to access them; stub them with `Layer.succeed` in\n * tests without a real `RequestEvent`.\n */\nexport {\n SvelteKitCookies,\n SvelteKitResponseHeaders,\n SvelteKitLocals,\n requestServicesLayer,\n type SvelteKitCookiesInterface,\n type SvelteKitResponseHeadersInterface,\n} from \"./request-services.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,WAMd,IAGqD;CACrD,OAAOA,qBAAQ,CAAC,CAAC,WAAW,EAAE;AAChC;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,OAKd,IAGqC;CACrC,OAAOA,qBAAQ,CAAC,CAAC,OAAO,EAAE;AAC5B;;;;;;;;;;;;;;;;AAiBA,SAAgB,QAKd,YAIsC;CACtC,OAAOA,qBAAQ,CAAC,CAAC,QAAQ,UAAU;AACrC;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,SAId,IACA,SAC6D;CAC7D,MAAM,EAAE,eAAe,UAAU,WAAW,CAAC;CAE7C,OAAO,OAAO,UAAU;EACtB,MAAM,OAAO,MAAMA,qBAAQ,CAAC,CAAC,eAAe,OAAuB,GAAG,KAAK,CAAC;EAE5E,OAAO,KAAK,MAAM,MAAM;GACtB,YAAY,UAAU;IAEpB,MAAM,WAAW,MAAM,OAAO,KAAK;IACnC,IAAI,WAAW,QAAQ,KAAK,YAAY,QAAQ,GAC9C,MAAM;IAIR,QAAQ,MAAM,mBAAmB,MAAM,OAAO,KAAK,CAAC;IAGpD,MAAM,gBAAgB,eAAe,MAAM,OAAO,KAAK,IAAI;IAE3D,OAAO,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO,cAAc,CAAC,GAAG;KAC5D,QAAQ;KACR,SAAS,EAAE,gBAAgB,mBAAmB;IAChD,CAAC;GACH;GACA,YAAY,UAAU;EACxB,CAAC;CACH;AACF"}
|
|
1
|
+
{"version":3,"file":"server.js","names":["runtime"],"sources":["../src/server.ts"],"sourcesContent":["import type { Action, Actions, RequestEvent, ServerLoad } from \"@sveltejs/kit\";\nimport * as Effect from \"effect/Effect\";\nimport * as Exit from \"effect/Exit\";\nimport * as Cause from \"effect/Cause\";\nimport * as Console from \"effect/Console\";\nimport { isHttpError, isRedirect } from \"@sveltejs/kit\";\nimport type { SvelteKitServices } from \"./app-runtime.js\";\nimport type { SvelteRouteParams } from \"./internal/route-params.js\";\nimport { defaultServerRuntime as runtime } from \"./internal/default-runtime.js\";\n\nexport type { SvelteKitServices };\n\n/**\n * Wraps an Effect-based function to work as a SvelteKit server load function.\n * The effect has access to platform services (HttpClient, the RequestEvent, etc.)\n * via the SvelteKit runtime.\n *\n * SvelteKit control flow (`redirect()`, `error()`) raised inside the effect is\n * passed through to SvelteKit.\n *\n * To give effects access to your own application services, create an\n * app-wide runtime with {@link makeServerRuntime} and use its `serverLoad`\n * instead.\n *\n * @example\n * ```ts\n * import { serverLoad } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n * import * as HttpClient from 'effect/http/HttpClient';\n *\n * export const load = serverLoad((event) =>\n * Effect.gen(function* () {\n * const client = yield* HttpClient.HttpClient;\n * const response = yield* client.get(\"https://api.example.com/data\");\n * return { data: yield* response.json };\n * })\n * );\n * ```\n */\nexport function serverLoad<\n Params extends SvelteRouteParams = SvelteRouteParams,\n ParentData extends Record<string, any> = any,\n OutputData extends Record<string, any> | void = any,\n RouteId extends string | null = any,\n>(\n fn: (\n event: RequestEvent<Params, RouteId>,\n ) => Effect.Effect<OutputData, unknown, SvelteKitServices>,\n): ServerLoad<Params, ParentData, OutputData, RouteId> {\n return runtime().serverLoad(fn);\n}\n\n/**\n * Wraps an Effect-based function to work as a SvelteKit action handler.\n * The effect has access to platform services (HttpClient, the RequestEvent, etc.)\n * via the SvelteKit runtime.\n *\n * @example\n * ```ts\n * import { action } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n *\n * export const actions = {\n * default: action((event) =>\n * Effect.gen(function* () {\n * const formData = yield* Effect.promise(() => event.request.formData());\n * // Process form data\n * return { success: true };\n * })\n * )\n * };\n * ```\n */\nexport function action<\n Params extends SvelteRouteParams = SvelteRouteParams,\n OutputData extends Record<string, any> | void = any,\n RouteId extends string | null = any,\n>(\n fn: (\n event: RequestEvent<Params, RouteId>,\n ) => Effect.Effect<OutputData, unknown, SvelteKitServices>,\n): Action<Params, OutputData, RouteId> {\n return runtime().action(fn);\n}\n\n/**\n * Creates SvelteKit actions from an object of Effect-based action handlers.\n * Each action will have access to platform services via the SvelteKit runtime.\n *\n * @example\n * ```ts\n * import { actions } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n *\n * export const actions = actions({\n * create: (event) => Effect.succeed({ created: true }),\n * delete: (event) => Effect.succeed({ deleted: true })\n * });\n * ```\n */\nexport function actions<\n Params extends SvelteRouteParams = SvelteRouteParams,\n OutputData extends Record<string, any> | void = any,\n RouteId extends string | null = any,\n>(\n actionsMap: Record<\n string,\n (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, SvelteKitServices>\n >,\n): Actions<Params, OutputData, RouteId> {\n return runtime().actions(actionsMap);\n}\n\n/**\n * Wraps an Effect-based function to work as a SvelteKit API endpoint handler.\n * The effect has access to platform services (HttpClient, the RequestEvent, etc.)\n * via the SvelteKit runtime.\n *\n * SvelteKit control flow (`redirect()`, `error()`) raised inside the effect is\n * passed through to SvelteKit. For any other failure, the error is logged\n * server-side and a generic error message is returned to clients. For\n * development/debugging, you can pass options to expose full error details.\n *\n * @example\n * ```ts\n * import { endpoint } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n * import { json } from '@sveltejs/kit';\n * import * as HttpClient from 'effect/http/HttpClient';\n *\n * export const GET = endpoint((event) =>\n * Effect.gen(function* () {\n * const client = yield* HttpClient.HttpClient;\n * const response = yield* client.get(\"https://api.example.com/data\");\n * return json(yield* response.json);\n * })\n * );\n * ```\n */\nexport function endpoint<\n Params extends SvelteRouteParams = SvelteRouteParams,\n RouteId extends string | null = any,\n>(\n fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<Response, unknown, SvelteKitServices>,\n options?: { exposeErrors?: boolean },\n): (event: RequestEvent<Params, RouteId>) => Promise<Response> {\n const { exposeErrors = false } = options ?? {};\n\n return async (event) => {\n const exit = await runtime().runPromiseExit(event, fn(event));\n\n return Exit.match(exit, {\n onFailure: (cause) => {\n // Let SvelteKit handle its own control flow objects.\n const squashed = Cause.squash(cause);\n if (isRedirect(squashed) || isHttpError(squashed)) {\n throw squashed;\n }\n\n // Always log the full error server-side for debugging, through Effect's\n // default `Console` (the global console).\n Effect.runSync(Console.error(\"Endpoint error:\", Cause.pretty(cause)));\n\n // Return generic error message to client unless explicitly enabled\n const clientMessage = exposeErrors ? Cause.pretty(cause) : \"Internal server error\";\n\n // `Response.json` serialises the body and sets `Content-Type: application/json`.\n return Response.json({ error: clientMessage }, { status: 500 });\n },\n onSuccess: (value) => value,\n });\n };\n}\n\n/**\n * Effect wrappers for the server-side hooks (`hooks.server.ts`): the `Hooks`\n * namespace exposes `handle`, `handleFetch`, `handleError`, and `init`.\n */\nexport * as Hooks from \"./server/Hooks.js\";\n\n/**\n * Create SvelteKit `+server.ts` endpoints backed by Effect: the `Endpoint`\n * namespace exposes `fromApi` (an `HttpApi`), `fromRouter` (an `HttpRouter`),\n * and `fromHttpEffect` (a single HTTP route effect).\n */\nexport * as Endpoint from \"./server/Endpoint.js\";\n\n/**\n * App-wide runtime: define your application's Effect services once and share\n * them across every request.\n */\nexport {\n makeServerRuntime,\n type ServerRuntime,\n type ServerRuntimeOptions,\n type RequestRuntime as ScopedRequestRuntime,\n} from \"./app-runtime.js\";\n\n/**\n * Export the SvelteKit runtime utilities for advanced use cases.\n */\nexport { SvelteKitRuntime, runWithSvelteKitRuntime, SvelteKitRequestEvent } from \"./runtime.js\";\nexport { RemoteFunction, RemoteFunctionUsageError } from \"./remote-functions.js\";\n\n/**\n * A general, Effect-idiomatic seam for *how the per-request execution context is\n * obtained*. {@link RequestRuntime} constructs a {@link RequestRuntimeProvider}\n * (from a consumer-owned runtime on `event.locals`, a package-built\n * per-request runtime, a build-once `ManagedRuntime`, or a `Layer`), and\n * {@link makeRuntimeAdapter} binds the `serverLoad` / `action` / `actions` /\n * `endpoint` wrappers to it — so the handler effect's services `R` are your own\n * application catalogue, not the fixed {@link SvelteKitServices}. A\n * {@link RuntimeAdapterOptions.translateError} maps a failed `Cause` to\n * SvelteKit control flow at the boundary.\n */\nexport {\n RequestRuntime,\n makeRuntimeAdapter,\n type RequestRuntimeProvider,\n type RequestRuntimeRunners,\n type RuntimeAdapter,\n type RuntimeAdapterOptions,\n type AdapterEndpointOptions,\n type FromLayerPerRequestOptions,\n type ErrorTranslator,\n} from \"./request-runtime.js\";\n\n/**\n * SvelteKit `$env` ⇄ Effect `Config` (see {@link sveltekitConfig}). The\n * Effect Schema ⇄ SvelteKit `transport` hook lives on the universal `Hooks`\n * namespace (`Hooks.transport`) in `@thomasfosterau/effect-sveltekit`.\n */\nexport { sveltekitConfig, sveltekitConfigProvider } from \"./config.js\";\n\n/**\n * Schema-driven request parsing. Each helper reads a part of the current\n * request (route params, search params, JSON body, or form data), validates it\n * against an Effect `Schema` or Standard Schema V1 validator, and returns the\n * typed output. A validation failure surfaces as {@link ParseError}.\n *\n * All helpers require `SvelteKitRequestEvent` in the environment and compose\n * naturally inside `serverLoad` / `action` / `endpoint` handlers.\n */\nexport {\n decodeParams,\n decodeSearchParams,\n decodeJson,\n decodeFormData,\n ParseError,\n type ParseIssue,\n} from \"./request-parse.js\";\n\n/**\n * Streamed load helper. {@link defer} captures the current service context and\n * runs a slow Effect in the background, returning the `Promise` so SvelteKit\n * can stream the result to the browser while sending the initial HTML\n * immediately. Use inside `serverLoad` alongside eagerly-computed fields.\n */\nexport { defer } from \"./streamed-load.js\";\n\n/**\n * Effect-native SvelteKit control-flow helpers and the default boundary\n * translator.\n *\n * - `SvelteKit.redirect` / `SvelteKit.error` / `SvelteKit.fail` — yield typed\n * Effect failures instead of throwing SvelteKit control-flow objects.\n * - `SvelteKit.translate` — the default `translateError` wired into every\n * server wrapper; maps the Kit error types to `redirect()` / `error()` /\n * `fail()` at the request boundary.\n * - `SvelteKit.makeTranslator([...cases])` — compose `translate` with your own\n * domain-error matchers (consumer cases run before the Kit base cases).\n * - `KitRedirect`, `KitError`, `KitActionFailure` — the tagged error classes\n * for use with `Effect.catchTag`, `instanceof`, etc.\n * - `KitTranslatorCase` — the argument type for `makeTranslator`.\n * - `KitErrorBody` — the body `SvelteKit.error` / `KitError` carry.\n */\nexport {\n SvelteKit,\n KitRedirect,\n KitError,\n KitActionFailure,\n type KitTranslatorCase,\n type KitErrorTranslator,\n} from \"./kit.js\";\nexport type { KitErrorBody } from \"./internal/error.js\";\n\n/**\n * A `translateError` built from a tag-keyed catalogue:\n * `Errors.toSvelteKit({ NotFound: 404, Forbidden: 403, Redirect: \"redirect\", HttpError: \"status\" })`.\n * Pass it to `makeRuntimeAdapter` / `RemoteFunction.withRuntime`; see\n * {@link Errors.toSvelteKit}.\n */\nexport * as Errors from \"./errors.js\";\n\n/** The response-headers buffer, re-exported from the universal entry for server-only modules. */\nexport { ResponseHeaders } from \"./response-headers.js\";\n\n/**\n * Effect-native services for the three per-request surfaces of a SvelteKit\n * `RequestEvent`: cookies, response headers, and locals.\n *\n * Each is a tagged `Context.Service` that is provided automatically in every\n * server handler effect (part of {@link SvelteKitServices}). Use\n * `yield* SvelteKitCookies` / `yield* SvelteKitResponseHeaders` /\n * `yield* SvelteKitLocals` to access them; stub them with `Layer.succeed` in\n * tests without a real `RequestEvent`.\n */\nexport {\n SvelteKitCookies,\n SvelteKitResponseHeaders,\n SvelteKitLocals,\n requestServicesLayer,\n type SvelteKitCookiesInterface,\n type SvelteKitResponseHeadersInterface,\n} from \"./request-services.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,WAMd,IAGqD;CACrD,OAAOA,qBAAQ,CAAC,CAAC,WAAW,EAAE;AAChC;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,OAKd,IAGqC;CACrC,OAAOA,qBAAQ,CAAC,CAAC,OAAO,EAAE;AAC5B;;;;;;;;;;;;;;;;AAiBA,SAAgB,QAKd,YAIsC;CACtC,OAAOA,qBAAQ,CAAC,CAAC,QAAQ,UAAU;AACrC;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,SAId,IACA,SAC6D;CAC7D,MAAM,EAAE,eAAe,UAAU,WAAW,CAAC;CAE7C,OAAO,OAAO,UAAU;EACtB,MAAM,OAAO,MAAMA,qBAAQ,CAAC,CAAC,eAAe,OAAO,GAAG,KAAK,CAAC;EAE5D,OAAO,KAAK,MAAM,MAAM;GACtB,YAAY,UAAU;IAEpB,MAAM,WAAW,MAAM,OAAO,KAAK;IACnC,IAAI,WAAW,QAAQ,KAAK,YAAY,QAAQ,GAC9C,MAAM;IAKR,OAAO,QAAQ,QAAQ,MAAM,mBAAmB,MAAM,OAAO,KAAK,CAAC,CAAC;IAGpE,MAAM,gBAAgB,eAAe,MAAM,OAAO,KAAK,IAAI;IAG3D,OAAO,SAAS,KAAK,EAAE,OAAO,cAAc,GAAG,EAAE,QAAQ,IAAI,CAAC;GAChE;GACA,YAAY,UAAU;EACxB,CAAC;CACH;AACF"}
|
package/dist/streamed-load.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
2
|
//#region src/streamed-load.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* Capture the current service context and run `effect` in the background,
|
|
@@ -16,7 +16,6 @@ import { Effect } from "effect";
|
|
|
16
16
|
* return { title, comments: commentsPromise };
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
|
-
declare const defer: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<Promise<A>, never, R>;
|
|
19
|
+
export declare const defer: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<Promise<A>, never, R>;
|
|
20
20
|
//#endregion
|
|
21
|
-
export { defer };
|
|
22
21
|
//# sourceMappingURL=streamed-load.d.ts.map
|
package/dist/streamed-load.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streamed-load.js","names":[],"sources":["../src/streamed-load.ts"],"sourcesContent":["/**\n * Helpers for SvelteKit's streamed (deferred-promise) `serverLoad` data.\n *\n * SvelteKit can stream fields of a `load` return value to the browser as they\n * resolve when you return a `Promise` alongside eagerly-computed fields. This\n * module provides {@link defer} — an Effect that captures the current service\n * context and launches the slow effect in the background, handing SvelteKit\n * the resulting `Promise`.\n *\n * @example\n * ```ts\n * import { serverLoad, defer } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n *\n * export const load = serverLoad((event) =>\n * Effect.gen(function* () {\n * // Eager: resolved before the initial HTML is sent.\n * const title = yield* fetchTitle(event.params.id);\n *\n * // Deferred: SvelteKit streams this to the client as it resolves.\n * const commentsPromise = yield* defer(fetchComments(event.params.id));\n *\n * return { title, comments: commentsPromise };\n * }),\n * );\n * ```\n *\n * In `+page.svelte`, consume the deferred field with `{#await}`:\n *\n * ```svelte\n * <script>\n * let { data } = $props();\n * </script>\n *\n * <h1>{data.title}</h1>\n *\n * {#await data.comments}\n * <p>Loading comments…</p>\n * {:then comments}\n * {#each comments as c}<p>{c.body}</p>{/each}\n * {:catch}\n * <p>Failed to load comments.</p>\n * {/await}\n * ```\n *\n * **Error handling.** If the deferred Effect fails, the promise rejects.\n * SvelteKit surfaces the rejection in the `{:catch}` block of `{#await}` in\n * the component — it does *not* throw the page into its error boundary. To\n * provide a fallback value instead, apply `Effect.orElse` / `Effect.catchAll`\n * before passing the effect to `defer`.\n */\n\nimport
|
|
1
|
+
{"version":3,"file":"streamed-load.js","names":[],"sources":["../src/streamed-load.ts"],"sourcesContent":["/**\n * Helpers for SvelteKit's streamed (deferred-promise) `serverLoad` data.\n *\n * SvelteKit can stream fields of a `load` return value to the browser as they\n * resolve when you return a `Promise` alongside eagerly-computed fields. This\n * module provides {@link defer} — an Effect that captures the current service\n * context and launches the slow effect in the background, handing SvelteKit\n * the resulting `Promise`.\n *\n * @example\n * ```ts\n * import { serverLoad, defer } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n *\n * export const load = serverLoad((event) =>\n * Effect.gen(function* () {\n * // Eager: resolved before the initial HTML is sent.\n * const title = yield* fetchTitle(event.params.id);\n *\n * // Deferred: SvelteKit streams this to the client as it resolves.\n * const commentsPromise = yield* defer(fetchComments(event.params.id));\n *\n * return { title, comments: commentsPromise };\n * }),\n * );\n * ```\n *\n * In `+page.svelte`, consume the deferred field with `{#await}`:\n *\n * ```svelte\n * <script>\n * let { data } = $props();\n * </script>\n *\n * <h1>{data.title}</h1>\n *\n * {#await data.comments}\n * <p>Loading comments…</p>\n * {:then comments}\n * {#each comments as c}<p>{c.body}</p>{/each}\n * {:catch}\n * <p>Failed to load comments.</p>\n * {/await}\n * ```\n *\n * **Error handling.** If the deferred Effect fails, the promise rejects.\n * SvelteKit surfaces the rejection in the `{:catch}` block of `{#await}` in\n * the component — it does *not* throw the page into its error boundary. To\n * provide a fallback value instead, apply `Effect.orElse` / `Effect.catchAll`\n * before passing the effect to `defer`.\n */\n\nimport * as Effect from \"effect/Effect\";\n\n/**\n * Capture the current service context and run `effect` in the background,\n * returning the `Promise` immediately so SvelteKit can stream the result.\n *\n * The deferred effect runs with exactly the same services as the parent\n * `serverLoad` handler — you can `yield*` the same application services.\n *\n * @param effect - The slow Effect to run in the background. Failures cause the\n * returned Promise to reject; wrap with `Effect.orElse` for a fallback.\n *\n * @example\n * ```ts\n * const commentsPromise = yield* defer(fetchComments(id));\n * return { title, comments: commentsPromise };\n * ```\n */\nexport const defer = <A, E, R>(\n effect: Effect.Effect<A, E, R>,\n): Effect.Effect<Promise<A>, never, R> =>\n Effect.map(Effect.context<R>(), (ctx) => Effect.runPromiseWith(ctx)(effect));\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsEA,MAAa,SACX,WAEA,OAAO,IAAI,OAAO,QAAW,IAAI,QAAQ,OAAO,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC"}
|