@thomasfosterau/effect-sveltekit 0.6.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/README.md +16 -13
- 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 +11 -11
- package/dist/Navigation.js +15 -2
- package/dist/Navigation.js.map +1 -1
- package/dist/Remote.d.ts +18 -17
- package/dist/Remote.js +2 -4
- package/dist/Remote.js.map +1 -1
- package/dist/State.d.ts +10 -6
- package/dist/State.js +2 -1
- 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 +9 -10
- package/dist/forms.js +5 -2
- package/dist/forms.js.map +1 -1
- package/dist/internal/app-state.d.ts +16 -14
- package/dist/internal/app-state.js +9 -3
- 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 +20 -18
- package/dist/internal/enhance.js +3 -1
- 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/kit.d.ts +12 -11
- package/dist/kit.js +6 -2
- package/dist/kit.js.map +1 -1
- package/dist/realtime.d.ts +29 -31
- package/dist/realtime.js +23 -4
- 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 +12 -9
package/dist/request-runtime.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { SvelteKit } from "./kit.js";
|
|
2
|
-
import
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Context from "effect/Context";
|
|
4
|
+
import * as Exit from "effect/Exit";
|
|
5
|
+
import "effect/Layer";
|
|
6
|
+
import * as ManagedRuntime from "effect/ManagedRuntime";
|
|
7
|
+
import * as Rec from "effect/Record";
|
|
8
|
+
import * as Result from "effect/Result";
|
|
3
9
|
import { isHttpError, isRedirect } from "@sveltejs/kit";
|
|
10
|
+
import * as Cause from "effect/Cause";
|
|
11
|
+
import * as Console from "effect/Console";
|
|
4
12
|
//#region src/request-runtime.ts
|
|
5
13
|
/**
|
|
6
14
|
* A general, Effect-idiomatic way to provide the per-request execution context
|
|
@@ -51,7 +59,7 @@ const runnersFromContext = (context) => ({
|
|
|
51
59
|
runFork: (effect) => Effect.runForkWith(context)(effect)
|
|
52
60
|
});
|
|
53
61
|
/** Runners for a target that is either a `ManagedRuntime` or a `Context`. */
|
|
54
|
-
const runnersFor = (target) =>
|
|
62
|
+
const runnersFor = (target) => Context.isContext(target) ? runnersFromContext(target) : runnersFromManagedRuntime(target);
|
|
55
63
|
/**
|
|
56
64
|
* Constructors for {@link RequestRuntimeProvider}. Each expresses one way to
|
|
57
65
|
* obtain a request's execution context; the wrappers don't care which.
|
|
@@ -104,11 +112,7 @@ const RequestRuntime = {
|
|
|
104
112
|
return {
|
|
105
113
|
runPromiseExit: async (effect) => {
|
|
106
114
|
const rt = ManagedRuntime.make(layer(event));
|
|
107
|
-
|
|
108
|
-
return await rt.runPromiseExit(effect);
|
|
109
|
-
} finally {
|
|
110
|
-
await teardown(rt);
|
|
111
|
-
}
|
|
115
|
+
return rt.runPromiseExit(effect).finally(() => teardown(rt));
|
|
112
116
|
},
|
|
113
117
|
runFork: (effect) => {
|
|
114
118
|
const rt = ManagedRuntime.make(layer(event));
|
|
@@ -143,41 +147,36 @@ const makeRuntimeAdapter = (provider, opts) => {
|
|
|
143
147
|
const translateError = opts?.translateError ?? SvelteKit.translate;
|
|
144
148
|
const runSettle = async (event, effect) => {
|
|
145
149
|
const exit = await provider.run(event).runPromiseExit(effect);
|
|
146
|
-
try
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
150
|
+
const settled = Result.try(() => Exit.isSuccess(exit) ? exit.value : translateError(exit.cause));
|
|
151
|
+
provider.disposeAfterResponse?.(event);
|
|
152
|
+
return Result.getOrElse(settled, (thrown) => {
|
|
153
|
+
throw thrown;
|
|
154
|
+
});
|
|
152
155
|
};
|
|
153
156
|
const action = (fn) => (event) => runSettle(event, fn(event).pipe(Effect.withSpan(`action ${event.route.id ?? "(unknown)"}`, { kind: "server" })));
|
|
154
157
|
return {
|
|
155
158
|
serverLoad: (fn) => (event) => runSettle(event, fn(event).pipe(Effect.withSpan(`serverLoad ${event.route.id ?? "(unknown)"}`, { kind: "server" }))),
|
|
156
159
|
action,
|
|
157
160
|
actions: (map) => {
|
|
158
|
-
|
|
159
|
-
for (const [key, fn] of Object.entries(map)) wrapped[key] = action(fn);
|
|
160
|
-
return wrapped;
|
|
161
|
+
return Rec.map(map, (fn) => action(fn));
|
|
161
162
|
},
|
|
162
163
|
endpoint: (fn, options) => async (event) => {
|
|
163
164
|
const effect = fn(event).pipe(Effect.withSpan(`${event.request.method} ${event.route.id ?? "(unknown)"}`, { kind: "server" }));
|
|
164
165
|
const exit = await provider.run(event).runPromiseExit(effect);
|
|
165
|
-
try {
|
|
166
|
+
const settled = Result.try(() => {
|
|
166
167
|
if (Exit.isSuccess(exit)) return exit.value;
|
|
167
|
-
try
|
|
168
|
-
|
|
169
|
-
} catch (err) {
|
|
168
|
+
const translated = Result.try(() => translateError(exit.cause));
|
|
169
|
+
return Result.getOrElse(translated, (err) => {
|
|
170
170
|
if (isRedirect(err) || isHttpError(err)) throw err;
|
|
171
|
-
|
|
171
|
+
Effect.runSync(Console.error("Endpoint error:", Cause.pretty(exit.cause)));
|
|
172
172
|
const message = options?.exposeErrors ? Cause.pretty(exit.cause) : "Internal server error";
|
|
173
|
-
return
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
173
|
+
return Response.json({ error: message }, { status: 500 });
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
provider.disposeAfterResponse?.(event);
|
|
177
|
+
return Result.getOrElse(settled, (thrown) => {
|
|
178
|
+
throw thrown;
|
|
179
|
+
});
|
|
181
180
|
}
|
|
182
181
|
};
|
|
183
182
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-runtime.js","names":[],"sources":["../src/request-runtime.ts"],"sourcesContent":["/**\n * A general, Effect-idiomatic way to provide the per-request execution context\n * for SvelteKit server wrappers.\n *\n * Where {@link makeServerRuntime} bakes in \"the package owns a runtime, built\n * once per process,\" this module factors out the single decision *how to obtain\n * the execution context for this request* behind one open abstraction — a\n * {@link RequestRuntimeProvider}. Existing models (a build-once\n * `ManagedRuntime`, a layer built once), a consumer-owned runtime resolved from\n * `event.locals`, and a package-built per-request runtime are all just\n * {@link RequestRuntime} constructors of the same provider, not separate code\n * paths.\n *\n * {@link makeRuntimeAdapter} binds the `serverLoad` / `action` / `actions` /\n * `endpoint` wrappers to a provider (and an optional boundary error\n * translator), so the handler effect's requirement channel `R` is the\n * *provider's* services — your own application catalogue — rather than the fixed\n * {@link SvelteKitServices}.\n *\n * @example\n * ```ts\n * // The consumer owns a per-request ManagedRuntime on event.locals.runtime\n * // (rebuilt mid-request); the package adopts it, resolved at call time.\n * import { makeRuntimeAdapter, RequestRuntime } from \"@thomasfosterau/effect-sveltekit/server\";\n *\n * const { serverLoad, action, actions, endpoint } = makeRuntimeAdapter(\n * RequestRuntime.fromLocals((event) => event.locals.runtime),\n * { translateError: toSvelteKit },\n * );\n *\n * export const load = serverLoad((event) =>\n * Effect.gen(function* () {\n * const db = yield* Database; // app service from locals.runtime\n * return { items: yield* db.list() };\n * }),\n * );\n * ```\n */\n\nimport {\n isHttpError,\n isRedirect,\n type Action,\n type Actions,\n type RequestEvent,\n type RequestHandler,\n type ServerLoad,\n} from \"@sveltejs/kit\";\nimport { Cause, Context, Effect, Exit, type Fiber, Layer, ManagedRuntime } from \"effect\";\nimport type { SvelteRouteParams } from \"./internal/route-params.js\";\nimport { SvelteKit } from \"./kit.js\";\n\n/**\n * The runners a {@link RequestRuntimeProvider} resolves for a single request:\n * a way to run an `Effect<A, unknown, R>` that requires the provider's services\n * `R`. The error channel is `unknown` because a boundary translator (see\n * {@link makeRuntimeAdapter}) decides how failures surface.\n */\nexport interface RequestRuntimeRunners<in R> {\n /** Run the request's effect, keeping its `Exit` (never rejects). */\n readonly runPromiseExit: <A>(\n effect: Effect.Effect<A, unknown, R>,\n ) => Promise<Exit.Exit<A, unknown>>;\n /** Fork the request's effect, returning the running fiber. */\n readonly runFork: <A>(effect: Effect.Effect<A, unknown, R>) => Fiber.Fiber<A, unknown>;\n}\n\n/**\n * How to execute a request's effects. Resolved per request, at call time, so a\n * runtime the consumer rebuilds mid-request is honoured automatically.\n *\n * Implementations own the scope/lifecycle of whatever they build. `R` is the\n * set of services the handler effect may require; `E` is the layer construction\n * error type (informational — failures surface through the translator).\n */\nexport interface RequestRuntimeProvider<R, E = never> {\n /** Resolve the runners for a request. Consulted per call, never cached. */\n readonly run: (event: RequestEvent) => RequestRuntimeRunners<R>;\n /**\n * Optional teardown to run after the response is produced. Called by\n * {@link makeRuntimeAdapter} once the handler has settled — e.g. to route a\n * per-request resource's disposal through `ctx.waitUntil` on Workers.\n */\n readonly disposeAfterResponse?: (event: RequestEvent) => void;\n /** Phantom marker so `E` participates in inference. */\n readonly _E?: (_: never) => E;\n}\n\n/** Runners backed by an existing `ManagedRuntime`. */\nconst runnersFromManagedRuntime = <R>(\n rt: ManagedRuntime.ManagedRuntime<R, unknown>,\n): RequestRuntimeRunners<R> => ({\n runPromiseExit: (effect) => rt.runPromiseExit(effect) as Promise<Exit.Exit<never, unknown>>,\n runFork: (effect) => rt.runFork(effect) as Fiber.Fiber<never, unknown>,\n});\n\n/** Runners backed by a built service `Context` (run on the default scheduler). */\nconst runnersFromContext = <R>(context: Context.Context<R>): RequestRuntimeRunners<R> => ({\n runPromiseExit: (effect) => Effect.runPromiseExitWith(context)(effect),\n runFork: (effect) => Effect.runForkWith(context)(effect),\n});\n\n/** Runners for a target that is either a `ManagedRuntime` or a `Context`. */\nconst runnersFor = <R>(\n target: ManagedRuntime.ManagedRuntime<R, unknown> | Context.Context<R>,\n): RequestRuntimeRunners<R> =>\n ManagedRuntime.isManagedRuntime(target)\n ? runnersFromManagedRuntime(target as ManagedRuntime.ManagedRuntime<R, unknown>)\n : runnersFromContext(target as Context.Context<R>);\n\n/** Options for {@link RequestRuntime.fromLayerPerRequest}. */\nexport interface FromLayerPerRequestOptions {\n /**\n * Defer teardown of the per-request runtime to after the response is\n * produced, instead of awaiting `dispose()` inline. On Workers, route it\n * through `ctx.waitUntil` so disposal does not fight response streaming or\n * socket pinning:\n *\n * ```ts\n * { afterResponse: (event, dispose) => event.platform!.ctx.waitUntil(dispose()) }\n * ```\n */\n readonly afterResponse?: (event: RequestEvent, dispose: () => Promise<void>) => void;\n}\n\n/**\n * Constructors for {@link RequestRuntimeProvider}. Each expresses one way to\n * obtain a request's execution context; the wrappers don't care which.\n */\nexport const RequestRuntime = {\n /**\n * Adopt a `ManagedRuntime` the consumer already built (the build-once model,\n * expressed as a provider).\n */\n fromManagedRuntime: <R, E>(\n rt: ManagedRuntime.ManagedRuntime<R, E>,\n ): RequestRuntimeProvider<R, E> => {\n const runners = runnersFromManagedRuntime(rt as ManagedRuntime.ManagedRuntime<R, unknown>);\n return { run: () => runners };\n },\n\n /**\n * Let the package build a single `ManagedRuntime` from a layer, lazily on\n * first use and shared across requests (the existing build-once-per-process\n * model).\n */\n fromLayer: <R, E>(layer: Layer.Layer<R, E>): RequestRuntimeProvider<R, E> => {\n let runners: RequestRuntimeRunners<R> | undefined;\n return {\n run: () =>\n (runners ??= runnersFromManagedRuntime(\n ManagedRuntime.make(layer) as ManagedRuntime.ManagedRuntime<R, unknown>,\n )),\n };\n },\n\n /**\n * Adopt a runtime the consumer stashed on the request (e.g. on\n * `event.locals`). Resolved at call time, so a runtime rebuilt mid-request is\n * honoured automatically. The getter may return a `ManagedRuntime` or a\n * built service `Context`.\n */\n fromLocals: <R>(\n get: (event: RequestEvent) => ManagedRuntime.ManagedRuntime<R, unknown> | Context.Context<R>,\n ): RequestRuntimeProvider<R> => ({\n run: (event) => runnersFor(get(event)),\n }),\n\n /**\n * Let the package build *and dispose* a `ManagedRuntime` per request from a\n * layer factory. Each handler invocation builds its own runtime from\n * `layer(event)` and disposes it once the effect settles — so a per-request\n * resource (a DB pool) dies with the request. Use `opts.afterResponse` to\n * defer disposal to after the response (Workers `ctx.waitUntil`).\n *\n * For sharing one runtime across every load/action of a request, prefer\n * {@link RequestRuntime.fromLocals} with a runtime the consumer builds in\n * `hooks.server.ts`.\n */\n fromLayerPerRequest: <R, E>(\n layer: (event: RequestEvent) => Layer.Layer<R, E>,\n opts?: FromLayerPerRequestOptions,\n ): RequestRuntimeProvider<R, E> => ({\n run: (event) => {\n const dispose = (rt: ManagedRuntime.ManagedRuntime<R, E>) => () => rt.dispose();\n const teardown = (rt: ManagedRuntime.ManagedRuntime<R, E>): Promise<void> => {\n if (opts?.afterResponse) {\n opts.afterResponse(event, dispose(rt));\n return Promise.resolve();\n }\n return rt.dispose();\n };\n return {\n runPromiseExit: async (effect) => {\n const rt = ManagedRuntime.make(layer(event));\n try {\n return (await rt.runPromiseExit(effect)) as Exit.Exit<never, unknown>;\n } finally {\n await teardown(rt);\n }\n },\n runFork: (effect) => {\n const rt = ManagedRuntime.make(layer(event));\n const fiber = rt.runFork(effect) as Fiber.Fiber<never, unknown>;\n fiber.addObserver(() => void teardown(rt));\n return fiber;\n },\n };\n },\n }),\n};\n\n/** A boundary translator: turn a failed `Cause` into a thrown value. */\nexport type ErrorTranslator = (cause: Cause.Cause<unknown>) => never;\n\n/** Options shared by {@link makeRuntimeAdapter}. */\nexport interface RuntimeAdapterOptions {\n /**\n * Translate a failed `Cause` into SvelteKit control flow at the boundary.\n * Called for `serverLoad` / `action` / `actions` failures; it may throw\n * `redirect()` / `error()` (and, for remote forms, call `invalid()`).\n * Defaults to the package's passthrough: SvelteKit's own `redirect()` /\n * `error()` are rethrown, anything else becomes an `EffectCauseError` (a 500).\n */\n readonly translateError?: ErrorTranslator;\n}\n\n/** Options for the adapter's `endpoint` wrapper. */\nexport interface AdapterEndpointOptions {\n /**\n * Include full error details (`Cause.pretty`) in the default 500 JSON body.\n * Ignored when a `translateError` is configured. Defaults to `false`.\n */\n readonly exposeErrors?: boolean;\n}\n\n/** The wrappers produced by {@link makeRuntimeAdapter}, bound to a provider. */\nexport interface RuntimeAdapter<R> {\n readonly 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: (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, R>,\n ) => ServerLoad<Params, ParentData, OutputData, RouteId>;\n readonly action: <\n Params extends SvelteRouteParams = SvelteRouteParams,\n OutputData extends Record<string, any> | void = any,\n RouteId extends string | null = any,\n >(\n fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, R>,\n ) => Action<Params, OutputData, RouteId>;\n readonly actions: <\n Params extends SvelteRouteParams = SvelteRouteParams,\n OutputData extends Record<string, any> | void = any,\n RouteId extends string | null = any,\n >(\n map: Record<\n string,\n (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, R>\n >,\n ) => Actions<Params, OutputData, RouteId>;\n readonly endpoint: <\n Params extends SvelteRouteParams = SvelteRouteParams,\n RouteId extends string | null = any,\n >(\n fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<Response, unknown, R>,\n options?: AdapterEndpointOptions,\n ) => RequestHandler<Params, RouteId>;\n}\n\n/**\n * Bind the SvelteKit server wrappers to a {@link RequestRuntimeProvider} (and an\n * optional boundary error translator).\n *\n * Unlike the standalone `serverLoad` / `action` / `endpoint`, the handler\n * effect's required services `R` come from the provider — your own application\n * catalogue — not the fixed {@link SvelteKitServices}. The package does not\n * merge `HttpClient` / `SvelteKitRequestEvent` onto this path; add them to your\n * own layer if you want them.\n *\n * @example\n * ```ts\n * // Package owns the runtime per request on Workers, disposed via waitUntil.\n * const { serverLoad } = makeRuntimeAdapter(\n * RequestRuntime.fromLayerPerRequest((event) => AppLayer(event.platform!.env), {\n * afterResponse: (event, dispose) => event.platform!.ctx.waitUntil(dispose()),\n * }),\n * );\n * ```\n */\nexport const makeRuntimeAdapter = <R>(\n provider: RequestRuntimeProvider<R, any>,\n opts?: RuntimeAdapterOptions,\n): RuntimeAdapter<R> => {\n const translateError: ErrorTranslator = opts?.translateError ?? SvelteKit.translate;\n\n const runSettle = async <A>(\n event: RequestEvent,\n effect: Effect.Effect<A, unknown, R>,\n ): Promise<A> => {\n const exit = await provider.run(event).runPromiseExit(effect);\n try {\n if (Exit.isSuccess(exit)) return exit.value;\n return translateError(exit.cause);\n } finally {\n provider.disposeAfterResponse?.(event);\n }\n };\n\n const action: RuntimeAdapter<R>[\"action\"] = (fn) => (event) =>\n runSettle(\n event,\n fn(event).pipe(\n Effect.withSpan(`action ${event.route.id ?? \"(unknown)\"}`, { kind: \"server\" }),\n ),\n );\n\n return {\n serverLoad: (fn) => (event) =>\n runSettle(\n event,\n fn(event).pipe(\n Effect.withSpan(`serverLoad ${event.route.id ?? \"(unknown)\"}`, { kind: \"server\" }),\n ),\n ),\n action,\n actions: (map) => {\n const wrapped: Record<string, Action<any, any, any>> = {};\n for (const [key, fn] of Object.entries(map)) wrapped[key] = action(fn);\n return wrapped as never;\n },\n endpoint: (fn, options) => async (event) => {\n const effect = fn(event).pipe(\n Effect.withSpan(\n `${(event as RequestEvent).request.method} ${(event as RequestEvent).route.id ?? \"(unknown)\"}`,\n { kind: \"server\" },\n ),\n );\n const exit = await provider.run(event as RequestEvent).runPromiseExit(effect);\n try {\n if (Exit.isSuccess(exit)) return exit.value;\n // Run the translator (default: SvelteKit.translate). SvelteKit\n // control-flow objects (redirect, error, KitRedirect, KitError) are\n // thrown so SvelteKit can handle them. Anything unrecognised is wrapped\n // in an EffectCauseError and caught below to produce a 500 Response.\n try {\n return translateError(exit.cause);\n } catch (err) {\n if (isRedirect(err) || isHttpError(err)) throw err;\n console.error(\"Endpoint error:\", Cause.pretty(exit.cause));\n const message = options?.exposeErrors\n ? Cause.pretty(exit.cause)\n : \"Internal server error\";\n return new Response(JSON.stringify({ error: message }), {\n status: 500,\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n } finally {\n provider.disposeAfterResponse?.(event as RequestEvent);\n }\n },\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"request-runtime.js","names":[],"sources":["../src/request-runtime.ts"],"sourcesContent":["/**\n * A general, Effect-idiomatic way to provide the per-request execution context\n * for SvelteKit server wrappers.\n *\n * Where {@link makeServerRuntime} bakes in \"the package owns a runtime, built\n * once per process,\" this module factors out the single decision *how to obtain\n * the execution context for this request* behind one open abstraction — a\n * {@link RequestRuntimeProvider}. Existing models (a build-once\n * `ManagedRuntime`, a layer built once), a consumer-owned runtime resolved from\n * `event.locals`, and a package-built per-request runtime are all just\n * {@link RequestRuntime} constructors of the same provider, not separate code\n * paths.\n *\n * {@link makeRuntimeAdapter} binds the `serverLoad` / `action` / `actions` /\n * `endpoint` wrappers to a provider (and an optional boundary error\n * translator), so the handler effect's requirement channel `R` is the\n * *provider's* services — your own application catalogue — rather than the fixed\n * {@link SvelteKitServices}.\n *\n * @example\n * ```ts\n * // The consumer owns a per-request ManagedRuntime on event.locals.runtime\n * // (rebuilt mid-request); the package adopts it, resolved at call time.\n * import { makeRuntimeAdapter, RequestRuntime } from \"@thomasfosterau/effect-sveltekit/server\";\n *\n * const { serverLoad, action, actions, endpoint } = makeRuntimeAdapter(\n * RequestRuntime.fromLocals((event) => event.locals.runtime),\n * { translateError: toSvelteKit },\n * );\n *\n * export const load = serverLoad((event) =>\n * Effect.gen(function* () {\n * const db = yield* Database; // app service from locals.runtime\n * return { items: yield* db.list() };\n * }),\n * );\n * ```\n */\n\nimport {\n isHttpError,\n isRedirect,\n type Action,\n type Actions,\n type RequestEvent,\n type RequestHandler,\n type ServerLoad,\n} from \"@sveltejs/kit\";\nimport * as Cause from \"effect/Cause\";\nimport * as Console from \"effect/Console\";\nimport * as Context from \"effect/Context\";\nimport * as Effect from \"effect/Effect\";\nimport * as Exit from \"effect/Exit\";\nimport type * as Fiber from \"effect/Fiber\";\nimport * as Layer from \"effect/Layer\";\nimport * as ManagedRuntime from \"effect/ManagedRuntime\";\nimport * as Rec from \"effect/Record\";\nimport * as Result from \"effect/Result\";\nimport type { SvelteRouteParams } from \"./internal/route-params.js\";\nimport { SvelteKit } from \"./kit.js\";\n\n/**\n * The runners a {@link RequestRuntimeProvider} resolves for a single request:\n * a way to run an `Effect<A, unknown, R>` that requires the provider's services\n * `R`. The error channel is `unknown` because a boundary translator (see\n * {@link makeRuntimeAdapter}) decides how failures surface.\n */\nexport interface RequestRuntimeRunners<in R> {\n /** Run the request's effect, keeping its `Exit` (never rejects). */\n readonly runPromiseExit: <A>(\n effect: Effect.Effect<A, unknown, R>,\n ) => Promise<Exit.Exit<A, unknown>>;\n /** Fork the request's effect, returning the running fiber. */\n readonly runFork: <A>(effect: Effect.Effect<A, unknown, R>) => Fiber.Fiber<A, unknown>;\n}\n\n/**\n * How to execute a request's effects. Resolved per request, at call time, so a\n * runtime the consumer rebuilds mid-request is honoured automatically.\n *\n * Implementations own the scope/lifecycle of whatever they build. `R` is the\n * set of services the handler effect may require; `E` is the layer construction\n * error type (informational — failures surface through the translator).\n */\nexport interface RequestRuntimeProvider<R, E = never> {\n /** Resolve the runners for a request. Consulted per call, never cached. */\n readonly run: (event: RequestEvent) => RequestRuntimeRunners<R>;\n /**\n * Optional teardown to run after the response is produced. Called by\n * {@link makeRuntimeAdapter} once the handler has settled — e.g. to route a\n * per-request resource's disposal through `ctx.waitUntil` on Workers.\n */\n readonly disposeAfterResponse?: (event: RequestEvent) => void;\n /** Phantom marker so `E` participates in inference. */\n readonly _E?: (_: never) => E;\n}\n\n/** Runners backed by an existing `ManagedRuntime`. */\nconst runnersFromManagedRuntime = <R>(\n rt: ManagedRuntime.ManagedRuntime<R, unknown>,\n): RequestRuntimeRunners<R> => ({\n runPromiseExit: (effect) => rt.runPromiseExit(effect),\n runFork: (effect) => rt.runFork(effect),\n});\n\n/** Runners backed by a built service `Context` (run on the default scheduler). */\nconst runnersFromContext = <R>(context: Context.Context<R>): RequestRuntimeRunners<R> => ({\n runPromiseExit: (effect) => Effect.runPromiseExitWith(context)(effect),\n runFork: (effect) => Effect.runForkWith(context)(effect),\n});\n\n/** Runners for a target that is either a `ManagedRuntime` or a `Context`. */\nconst runnersFor = <R>(\n target: ManagedRuntime.ManagedRuntime<R, unknown> | Context.Context<R>,\n): RequestRuntimeRunners<R> =>\n Context.isContext(target) ? runnersFromContext(target) : runnersFromManagedRuntime(target);\n\n/** Options for {@link RequestRuntime.fromLayerPerRequest}. */\nexport interface FromLayerPerRequestOptions {\n /**\n * Defer teardown of the per-request runtime to after the response is\n * produced, instead of awaiting `dispose()` inline. On Workers, route it\n * through `ctx.waitUntil` so disposal does not fight response streaming or\n * socket pinning:\n *\n * ```ts\n * { afterResponse: (event, dispose) => event.platform!.ctx.waitUntil(dispose()) }\n * ```\n */\n readonly afterResponse?: (event: RequestEvent, dispose: () => Promise<void>) => void;\n}\n\n/**\n * Constructors for {@link RequestRuntimeProvider}. Each expresses one way to\n * obtain a request's execution context; the wrappers don't care which.\n */\nexport const RequestRuntime = {\n /**\n * Adopt a `ManagedRuntime` the consumer already built (the build-once model,\n * expressed as a provider).\n */\n fromManagedRuntime: <R, E>(\n rt: ManagedRuntime.ManagedRuntime<R, E>,\n ): RequestRuntimeProvider<R, E> => {\n const runners = runnersFromManagedRuntime(rt);\n return { run: () => runners };\n },\n\n /**\n * Let the package build a single `ManagedRuntime` from a layer, lazily on\n * first use and shared across requests (the existing build-once-per-process\n * model).\n */\n fromLayer: <R, E>(layer: Layer.Layer<R, E>): RequestRuntimeProvider<R, E> => {\n let runners: RequestRuntimeRunners<R> | undefined;\n return {\n run: () => (runners ??= runnersFromManagedRuntime(ManagedRuntime.make(layer))),\n };\n },\n\n /**\n * Adopt a runtime the consumer stashed on the request (e.g. on\n * `event.locals`). Resolved at call time, so a runtime rebuilt mid-request is\n * honoured automatically. The getter may return a `ManagedRuntime` or a\n * built service `Context`.\n */\n fromLocals: <R>(\n get: (event: RequestEvent) => ManagedRuntime.ManagedRuntime<R, unknown> | Context.Context<R>,\n ): RequestRuntimeProvider<R> => ({\n run: (event) => runnersFor(get(event)),\n }),\n\n /**\n * Let the package build *and dispose* a `ManagedRuntime` per request from a\n * layer factory. Each handler invocation builds its own runtime from\n * `layer(event)` and disposes it once the effect settles — so a per-request\n * resource (a DB pool) dies with the request. Use `opts.afterResponse` to\n * defer disposal to after the response (Workers `ctx.waitUntil`).\n *\n * For sharing one runtime across every load/action of a request, prefer\n * {@link RequestRuntime.fromLocals} with a runtime the consumer builds in\n * `hooks.server.ts`.\n */\n fromLayerPerRequest: <R, E>(\n layer: (event: RequestEvent) => Layer.Layer<R, E>,\n opts?: FromLayerPerRequestOptions,\n ): RequestRuntimeProvider<R, E> => ({\n run: (event) => {\n const dispose = (rt: ManagedRuntime.ManagedRuntime<R, E>) => () => rt.dispose();\n const teardown = (rt: ManagedRuntime.ManagedRuntime<R, E>): Promise<void> => {\n if (opts?.afterResponse) {\n opts.afterResponse(event, dispose(rt));\n return Promise.resolve();\n }\n return rt.dispose();\n };\n return {\n runPromiseExit: async (effect) => {\n const rt = ManagedRuntime.make(layer(event));\n // Tear the runtime down once the effect settles; a teardown failure\n // rejects in place of the exit.\n return rt.runPromiseExit(effect).finally(() => teardown(rt));\n },\n runFork: (effect) => {\n const rt = ManagedRuntime.make(layer(event));\n const fiber = rt.runFork(effect);\n fiber.addObserver(() => void teardown(rt));\n return fiber;\n },\n };\n },\n }),\n};\n\n/** A boundary translator: turn a failed `Cause` into a thrown value. */\nexport type ErrorTranslator = (cause: Cause.Cause<unknown>) => never;\n\n/** Options shared by {@link makeRuntimeAdapter}. */\nexport interface RuntimeAdapterOptions {\n /**\n * Translate a failed `Cause` into SvelteKit control flow at the boundary.\n * Called for `serverLoad` / `action` / `actions` failures; it may throw\n * `redirect()` / `error()` (and, for remote forms, call `invalid()`).\n * Defaults to the package's passthrough: SvelteKit's own `redirect()` /\n * `error()` are rethrown, anything else becomes an `EffectCauseError` (a 500).\n */\n readonly translateError?: ErrorTranslator;\n}\n\n/** Options for the adapter's `endpoint` wrapper. */\nexport interface AdapterEndpointOptions {\n /**\n * Include full error details (`Cause.pretty`) in the default 500 JSON body.\n * Ignored when a `translateError` is configured. Defaults to `false`.\n */\n readonly exposeErrors?: boolean;\n}\n\n/** The wrappers produced by {@link makeRuntimeAdapter}, bound to a provider. */\nexport interface RuntimeAdapter<R> {\n readonly 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: (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, R>,\n ) => ServerLoad<Params, ParentData, OutputData, RouteId>;\n readonly action: <\n Params extends SvelteRouteParams = SvelteRouteParams,\n OutputData extends Record<string, any> | void = any,\n RouteId extends string | null = any,\n >(\n fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, R>,\n ) => Action<Params, OutputData, RouteId>;\n readonly actions: <\n Params extends SvelteRouteParams = SvelteRouteParams,\n OutputData extends Record<string, any> | void = any,\n RouteId extends string | null = any,\n >(\n map: Record<\n string,\n (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, R>\n >,\n ) => Actions<Params, OutputData, RouteId>;\n readonly endpoint: <\n Params extends SvelteRouteParams = SvelteRouteParams,\n RouteId extends string | null = any,\n >(\n fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<Response, unknown, R>,\n options?: AdapterEndpointOptions,\n ) => RequestHandler<Params, RouteId>;\n}\n\n/**\n * Bind the SvelteKit server wrappers to a {@link RequestRuntimeProvider} (and an\n * optional boundary error translator).\n *\n * Unlike the standalone `serverLoad` / `action` / `endpoint`, the handler\n * effect's required services `R` come from the provider — your own application\n * catalogue — not the fixed {@link SvelteKitServices}. The package does not\n * merge `HttpClient` / `SvelteKitRequestEvent` onto this path; add them to your\n * own layer if you want them.\n *\n * @example\n * ```ts\n * // Package owns the runtime per request on Workers, disposed via waitUntil.\n * const { serverLoad } = makeRuntimeAdapter(\n * RequestRuntime.fromLayerPerRequest((event) => AppLayer(event.platform!.env), {\n * afterResponse: (event, dispose) => event.platform!.ctx.waitUntil(dispose()),\n * }),\n * );\n * ```\n */\nexport const makeRuntimeAdapter = <R>(\n provider: RequestRuntimeProvider<R, any>,\n opts?: RuntimeAdapterOptions,\n): RuntimeAdapter<R> => {\n const translateError: ErrorTranslator = opts?.translateError ?? SvelteKit.translate;\n\n const runSettle = async <A>(\n event: RequestEvent,\n effect: Effect.Effect<A, unknown, R>,\n ): Promise<A> => {\n const exit = await provider.run(event).runPromiseExit(effect);\n // Settle (the translator throws for a failure), then dispose, then\n // surface the settled value or rethrow what the translator threw.\n const settled = Result.try(() =>\n Exit.isSuccess(exit) ? exit.value : translateError(exit.cause),\n );\n provider.disposeAfterResponse?.(event);\n return Result.getOrElse(settled, (thrown) => {\n throw thrown;\n });\n };\n\n const action: RuntimeAdapter<R>[\"action\"] = (fn) => (event) =>\n runSettle(\n event,\n fn(event).pipe(\n Effect.withSpan(`action ${event.route.id ?? \"(unknown)\"}`, { kind: \"server\" }),\n ),\n );\n\n return {\n serverLoad: (fn) => (event) =>\n runSettle(\n event,\n fn(event).pipe(\n Effect.withSpan(`serverLoad ${event.route.id ?? \"(unknown)\"}`, { kind: \"server\" }),\n ),\n ),\n action,\n actions: (map) => {\n return Rec.map(map, (fn) => action(fn));\n },\n endpoint: (fn, options) => async (event) => {\n const effect = fn(event).pipe(\n Effect.withSpan(`${event.request.method} ${event.route.id ?? \"(unknown)\"}`, {\n kind: \"server\",\n }),\n );\n const exit = await provider.run(event).runPromiseExit(effect);\n // Run the translator (default: SvelteKit.translate). SvelteKit\n // control-flow objects (redirect, error, KitRedirect, KitError) are\n // thrown so SvelteKit can handle them. Anything unrecognised is wrapped\n // in an EffectCauseError and becomes a 500 Response below.\n const settled = Result.try(() => {\n if (Exit.isSuccess(exit)) return exit.value;\n const translated = Result.try(() => translateError(exit.cause));\n return Result.getOrElse(translated, (err) => {\n if (isRedirect(err) || isHttpError(err)) throw err;\n Effect.runSync(Console.error(\"Endpoint error:\", Cause.pretty(exit.cause)));\n const message = options?.exposeErrors\n ? Cause.pretty(exit.cause)\n : \"Internal server error\";\n // `Response.json` serialises the body and sets `Content-Type: application/json`.\n return Response.json({ error: message }, { status: 500 });\n });\n });\n provider.disposeAfterResponse?.(event);\n return Result.getOrElse(settled, (thrown) => {\n throw thrown;\n });\n },\n };\n};\n"],"mappings":"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"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { SvelteKitRequestEvent } from "./runtime.js";
|
|
2
|
-
import
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Context from "effect/Context";
|
|
4
|
+
import * as Layer from "effect/Layer";
|
|
3
5
|
import { Cookies, RequestEvent } from "@sveltejs/kit";
|
|
4
6
|
//#region src/request-services.d.ts
|
|
5
7
|
/**
|
|
@@ -7,7 +9,7 @@ import { Cookies, RequestEvent } from "@sveltejs/kit";
|
|
|
7
9
|
* mirrors the corresponding `Cookies` method from `@sveltejs/kit` but returns
|
|
8
10
|
* an `Effect` rather than a raw value or `void`.
|
|
9
11
|
*/
|
|
10
|
-
interface SvelteKitCookiesInterface {
|
|
12
|
+
export interface SvelteKitCookiesInterface {
|
|
11
13
|
/** Get a cookie by name. Returns `undefined` when the cookie is absent. */
|
|
12
14
|
readonly get: (...args: Parameters<Cookies["get"]>) => Effect.Effect<ReturnType<Cookies["get"]>>;
|
|
13
15
|
/** Get all cookies as `{ name, value }` pairs. */
|
|
@@ -33,7 +35,7 @@ declare const SvelteKitCookies_base: Context.ServiceClass<SvelteKitCookies, "@th
|
|
|
33
35
|
* const session = yield* (yield* SvelteKitCookies).get("session");
|
|
34
36
|
* ```
|
|
35
37
|
*/
|
|
36
|
-
declare class SvelteKitCookies extends SvelteKitCookies_base {
|
|
38
|
+
export declare class SvelteKitCookies extends SvelteKitCookies_base {
|
|
37
39
|
/**
|
|
38
40
|
* Layer that builds the `SvelteKitCookies` service from the current
|
|
39
41
|
* {@link SvelteKitRequestEvent}. Included in the per-request context
|
|
@@ -44,7 +46,7 @@ declare class SvelteKitCookies extends SvelteKitCookies_base {
|
|
|
44
46
|
/**
|
|
45
47
|
* The Effect-native interface exposed by {@link SvelteKitResponseHeaders}.
|
|
46
48
|
*/
|
|
47
|
-
interface SvelteKitResponseHeadersInterface {
|
|
49
|
+
export interface SvelteKitResponseHeadersInterface {
|
|
48
50
|
/**
|
|
49
51
|
* Set one or more response headers. Equivalent to `event.setHeaders(headers)`.
|
|
50
52
|
*
|
|
@@ -67,7 +69,7 @@ declare const SvelteKitResponseHeaders_base: Context.ServiceClass<SvelteKitRespo
|
|
|
67
69
|
* yield* headers.set({ "cache-control": "max-age=60, s-maxage=300" });
|
|
68
70
|
* ```
|
|
69
71
|
*/
|
|
70
|
-
declare class SvelteKitResponseHeaders extends SvelteKitResponseHeaders_base {
|
|
72
|
+
export declare class SvelteKitResponseHeaders extends SvelteKitResponseHeaders_base {
|
|
71
73
|
/**
|
|
72
74
|
* Layer that builds the `SvelteKitResponseHeaders` service from the current
|
|
73
75
|
* {@link SvelteKitRequestEvent}.
|
|
@@ -96,7 +98,7 @@ declare const SvelteKitLocals_base: Context.ServiceClass<SvelteKitLocals, "@thom
|
|
|
96
98
|
* locals.user = yield* Auth.currentUser;
|
|
97
99
|
* ```
|
|
98
100
|
*/
|
|
99
|
-
declare class SvelteKitLocals extends SvelteKitLocals_base {
|
|
101
|
+
export declare class SvelteKitLocals extends SvelteKitLocals_base {
|
|
100
102
|
/**
|
|
101
103
|
* Layer that exposes `event.locals` (the mutable `App.Locals` reference)
|
|
102
104
|
* from the current {@link SvelteKitRequestEvent}.
|
|
@@ -113,7 +115,7 @@ declare class SvelteKitLocals extends SvelteKitLocals_base {
|
|
|
113
115
|
* {@link makeRuntimeAdapter}. Expose it here for use in custom setups that
|
|
114
116
|
* already have `SvelteKitRequestEvent` in scope.
|
|
115
117
|
*/
|
|
116
|
-
declare const requestServicesLayer: Layer.Layer<SvelteKitCookies | SvelteKitResponseHeaders | SvelteKitLocals, never, SvelteKitRequestEvent>;
|
|
118
|
+
export declare const requestServicesLayer: Layer.Layer<SvelteKitCookies | SvelteKitResponseHeaders | SvelteKitLocals, never, SvelteKitRequestEvent>;
|
|
117
119
|
//#endregion
|
|
118
|
-
export {
|
|
120
|
+
export {};
|
|
119
121
|
//# sourceMappingURL=request-services.d.ts.map
|
package/dist/request-services.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { SvelteKitRequestEvent } from "./runtime.js";
|
|
2
|
-
import
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Context from "effect/Context";
|
|
4
|
+
import * as Layer from "effect/Layer";
|
|
3
5
|
//#region src/request-services.ts
|
|
4
6
|
/**
|
|
5
7
|
* An Effect service wrapping the `RequestEvent.cookies` API.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-services.js","names":[],"sources":["../src/request-services.ts"],"sourcesContent":["/**\n * Effect-native services for the three per-request surfaces on a SvelteKit\n * `RequestEvent` — cookies, response headers, and locals — each as a tagged\n * `Context.Service` that can be `yield*`-ed in any handler effect and mocked\n * in tests with `Layer.succeed`.\n *\n * All three services are provided automatically as part of the\n * {@link SvelteKitServices} per-request context when using\n * {@link makeServerRuntime}, the standalone `serverLoad`/`action`/`endpoint`\n * wrappers, or {@link makeRuntimeAdapter}. No extra layer setup is required.\n *\n * @example\n * ```ts\n * import { Effect } from \"effect\";\n * import {\n * SvelteKitCookies,\n * SvelteKitResponseHeaders,\n * SvelteKitLocals,\n * } from \"@thomasfosterau/effect-sveltekit/server\";\n *\n * export const load = serverLoad(() =>\n * Effect.gen(function* () {\n * const cookies = yield* SvelteKitCookies;\n * const session = yield* cookies.get(\"session\");\n *\n * const headers = yield* SvelteKitResponseHeaders;\n * yield* headers.set({ \"cache-control\": \"max-age=60, s-maxage=300\" });\n *\n * const locals = yield* SvelteKitLocals;\n * // locals is App.Locals — typed to your augmented interface\n * const user = locals.user;\n *\n * return { user };\n * }),\n * );\n * ```\n *\n * ## Testing\n *\n * Because these are tagged services, they can be replaced with stubs via\n * `Layer.succeed` in unit tests without involving a real `RequestEvent`:\n *\n * ```ts\n * import { Layer } from \"effect\";\n * import { SvelteKitCookies } from \"@thomasfosterau/effect-sveltekit/server\";\n * import { runServerLoad, TestRequestEvent } from \"@thomasfosterau/effect-sveltekit/testing\";\n *\n * const MockCookies = Layer.succeed(SvelteKitCookies, {\n * get: (name) => Effect.succeed(name === \"session\" ? \"tok_test\" : undefined),\n * getAll: () => Effect.succeed([{ name: \"session\", value: \"tok_test\" }]),\n * set: () => Effect.void,\n * delete: () => Effect.void,\n * serialize: (n, v) => Effect.succeed(`${n}=${v}`),\n * });\n *\n * const data = await runServerLoad(myLoadFn, TestRequestEvent(), { layer: MockCookies });\n * ```\n */\n\nimport type { Cookies, RequestEvent } from \"@sveltejs/kit\";\nimport { Context, Effect, Layer } from \"effect\";\nimport { SvelteKitRequestEvent } from \"./runtime.js\";\n\n// ─── SvelteKitCookies ─────────────────────────────────────────────────────────\n\n/**\n * The Effect-native interface exposed by {@link SvelteKitCookies}. Each method\n * mirrors the corresponding `Cookies` method from `@sveltejs/kit` but returns\n * an `Effect` rather than a raw value or `void`.\n */\nexport interface SvelteKitCookiesInterface {\n /** Get a cookie by name. Returns `undefined` when the cookie is absent. */\n readonly get: (...args: Parameters<Cookies[\"get\"]>) => Effect.Effect<ReturnType<Cookies[\"get\"]>>;\n /** Get all cookies as `{ name, value }` pairs. */\n readonly getAll: (\n ...args: Parameters<Cookies[\"getAll\"]>\n ) => Effect.Effect<ReturnType<Cookies[\"getAll\"]>>;\n /** Set a cookie (adds a `set-cookie` response header). */\n readonly set: (...args: Parameters<Cookies[\"set\"]>) => Effect.Effect<void>;\n /** Delete a cookie by setting it to an empty value with a past expiry. */\n readonly delete: (...args: Parameters<Cookies[\"delete\"]>) => Effect.Effect<void>;\n /** Serialize a cookie into a `Set-Cookie` string without adding it to the response. */\n readonly serialize: (\n ...args: Parameters<Cookies[\"serialize\"]>\n ) => Effect.Effect<ReturnType<Cookies[\"serialize\"]>>;\n}\n\n/**\n * An Effect service wrapping the `RequestEvent.cookies` API.\n *\n * Available automatically in every server handler effect (it is part of\n * {@link SvelteKitServices}). Use `yield* SvelteKitCookies` to obtain the\n * service; call its methods (each returns an `Effect`) to read or write\n * cookies.\n *\n * @example\n * ```ts\n * const session = yield* (yield* SvelteKitCookies).get(\"session\");\n * ```\n */\nexport class SvelteKitCookies extends Context.Service<\n SvelteKitCookies,\n SvelteKitCookiesInterface\n>()(\"@thomasfosterau/effect-sveltekit/SvelteKitCookies\") {\n /**\n * Layer that builds the `SvelteKitCookies` service from the current\n * {@link SvelteKitRequestEvent}. Included in the per-request context\n * automatically; expose as a standalone layer for custom setups.\n */\n static readonly layer: Layer.Layer<SvelteKitCookies, never, SvelteKitRequestEvent> = Layer.effect(\n SvelteKitCookies,\n Effect.gen(function* () {\n const event = yield* SvelteKitRequestEvent;\n const { cookies } = event;\n return {\n get: (...args) => Effect.sync(() => cookies.get(...args)),\n getAll: (...args) => Effect.sync(() => cookies.getAll(...args)),\n set: (...args) => Effect.sync(() => cookies.set(...args)),\n delete: (...args) => Effect.sync(() => cookies.delete(...args)),\n serialize: (...args) => Effect.sync(() => cookies.serialize(...args)),\n };\n }),\n );\n}\n\n// ─── SvelteKitResponseHeaders ─────────────────────────────────────────────────\n\n/**\n * The Effect-native interface exposed by {@link SvelteKitResponseHeaders}.\n */\nexport interface SvelteKitResponseHeadersInterface {\n /**\n * Set one or more response headers. Equivalent to `event.setHeaders(headers)`.\n *\n * Note: You cannot set `set-cookie` headers here — use\n * {@link SvelteKitCookies} instead.\n */\n readonly set: (headers: Record<string, string>) => Effect.Effect<void>;\n}\n\n/**\n * An Effect service wrapping the `RequestEvent.setHeaders` API.\n *\n * Available automatically in every server handler effect. Use\n * `yield* SvelteKitResponseHeaders` to obtain the service, then call\n * `headers.set(...)`.\n *\n * @example\n * ```ts\n * const headers = yield* SvelteKitResponseHeaders;\n * yield* headers.set({ \"cache-control\": \"max-age=60, s-maxage=300\" });\n * ```\n */\nexport class SvelteKitResponseHeaders extends Context.Service<\n SvelteKitResponseHeaders,\n SvelteKitResponseHeadersInterface\n>()(\"@thomasfosterau/effect-sveltekit/SvelteKitResponseHeaders\") {\n /**\n * Layer that builds the `SvelteKitResponseHeaders` service from the current\n * {@link SvelteKitRequestEvent}.\n */\n static readonly layer: Layer.Layer<SvelteKitResponseHeaders, never, SvelteKitRequestEvent> =\n Layer.effect(\n SvelteKitResponseHeaders,\n Effect.gen(function* () {\n const event = yield* SvelteKitRequestEvent;\n return { set: (headers) => Effect.sync(() => event.setHeaders(headers)) };\n }),\n );\n}\n\n// ─── SvelteKitLocals ──────────────────────────────────────────────────────────\n\n/**\n * An Effect service that exposes the mutable `App.Locals` object for the\n * current request.\n *\n * `yield* SvelteKitLocals` returns the live `event.locals` reference — the\n * same object mutations in `hooks.server.ts` (e.g. setting `event.locals.user`\n * in a `handle` hook) write to. Mutations made to it inside handler effects\n * propagate back to subsequent hook middleware.\n *\n * Available automatically in every server handler effect.\n *\n * @example\n * ```ts\n * // Read a typed local (requires App.Locals augmentation in your app):\n * const { user } = yield* SvelteKitLocals;\n *\n * // Write a local (useful in handle hooks run via makeServerRuntime.handle):\n * const locals = yield* SvelteKitLocals;\n * locals.user = yield* Auth.currentUser;\n * ```\n */\nexport class SvelteKitLocals extends Context.Service<SvelteKitLocals, App.Locals>()(\n \"@thomasfosterau/effect-sveltekit/SvelteKitLocals\",\n) {\n /**\n * Layer that exposes `event.locals` (the mutable `App.Locals` reference)\n * from the current {@link SvelteKitRequestEvent}.\n */\n static readonly layer: Layer.Layer<SvelteKitLocals, never, SvelteKitRequestEvent> = Layer.effect(\n SvelteKitLocals,\n Effect.gen(function* () {\n const event = yield* SvelteKitRequestEvent;\n return event.locals;\n }),\n );\n}\n\n// ─── Combined layer ───────────────────────────────────────────────────────────\n\n/**\n * A convenience layer that provides all three request services\n * ({@link SvelteKitCookies}, {@link SvelteKitResponseHeaders},\n * {@link SvelteKitLocals}) from the current {@link SvelteKitRequestEvent}.\n *\n * This layer is merged into the per-request context automatically by\n * {@link makeServerRuntime}, the standalone wrappers, and\n * {@link makeRuntimeAdapter}. Expose it here for use in custom setups that\n * already have `SvelteKitRequestEvent` in scope.\n */\nexport const requestServicesLayer: Layer.Layer<\n SvelteKitCookies | SvelteKitResponseHeaders | SvelteKitLocals,\n never,\n SvelteKitRequestEvent\n> = Layer.mergeAll(SvelteKitCookies.layer, SvelteKitResponseHeaders.layer, SvelteKitLocals.layer);\n\n/**\n * Build all three request services directly from a {@link RequestEvent},\n * without going through {@link SvelteKitRequestEvent}. Used internally by\n * `requestLayer` in `app-runtime.ts` to avoid an extra indirection.\n */\nexport const requestServicesFrom = (\n event: RequestEvent,\n): {\n cookies: SvelteKitCookiesInterface;\n responseHeaders: SvelteKitResponseHeadersInterface;\n locals: App.Locals;\n} => {\n const { cookies } = event;\n return {\n cookies: {\n get: (...args) => Effect.sync(() => cookies.get(...args)),\n getAll: (...args) => Effect.sync(() => cookies.getAll(...args)),\n set: (...args) => Effect.sync(() => cookies.set(...args)),\n delete: (...args) => Effect.sync(() => cookies.delete(...args)),\n serialize: (...args) => Effect.sync(() => cookies.serialize(...args)),\n },\n responseHeaders: { set: (headers) => Effect.sync(() => event.setHeaders(headers)) },\n locals: event.locals,\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAoGA,IAAa,mBAAb,MAAa,yBAAyB,QAAQ,QAG5C,CAAC,CAAC,mDAAmD,CAAC,CAAC;;;;;;CAMvD,OAAgB,QAAqE,MAAM,OACzF,kBACA,OAAO,IAAI,aAAa;EAEtB,MAAM,EAAE,YAAY,OADC;EAErB,OAAO;GACL,MAAM,GAAG,SAAS,OAAO,WAAW,QAAQ,IAAI,GAAG,IAAI,CAAC;GACxD,SAAS,GAAG,SAAS,OAAO,WAAW,QAAQ,OAAO,GAAG,IAAI,CAAC;GAC9D,MAAM,GAAG,SAAS,OAAO,WAAW,QAAQ,IAAI,GAAG,IAAI,CAAC;GACxD,SAAS,GAAG,SAAS,OAAO,WAAW,QAAQ,OAAO,GAAG,IAAI,CAAC;GAC9D,YAAY,GAAG,SAAS,OAAO,WAAW,QAAQ,UAAU,GAAG,IAAI,CAAC;EACtE;CACF,CAAC,CACH;AACF;;;;;;;;;;;;;;AA8BA,IAAa,2BAAb,MAAa,iCAAiC,QAAQ,QAGpD,CAAC,CAAC,2DAA2D,CAAC,CAAC;;;;;CAK/D,OAAgB,QACd,MAAM,OACJ,0BACA,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO;EACrB,OAAO,EAAE,MAAM,YAAY,OAAO,WAAW,MAAM,WAAW,OAAO,CAAC,EAAE;CAC1E,CAAC,CACH;AACJ;;;;;;;;;;;;;;;;;;;;;;AAyBA,IAAa,kBAAb,MAAa,wBAAwB,QAAQ,QAAqC,CAAC,CACjF,kDACF,CAAC,CAAC;;;;;CAKA,OAAgB,QAAoE,MAAM,OACxF,iBACA,OAAO,IAAI,aAAa;EAEtB,QAAO,OADc,sBAAA,CACR;CACf,CAAC,CACH;AACF;;;;;;;;;;;AAcA,MAAa,uBAIT,MAAM,SAAS,iBAAiB,OAAO,yBAAyB,OAAO,gBAAgB,KAAK;;;;;;AAOhG,MAAa,uBACX,UAKG;CACH,MAAM,EAAE,YAAY;CACpB,OAAO;EACL,SAAS;GACP,MAAM,GAAG,SAAS,OAAO,WAAW,QAAQ,IAAI,GAAG,IAAI,CAAC;GACxD,SAAS,GAAG,SAAS,OAAO,WAAW,QAAQ,OAAO,GAAG,IAAI,CAAC;GAC9D,MAAM,GAAG,SAAS,OAAO,WAAW,QAAQ,IAAI,GAAG,IAAI,CAAC;GACxD,SAAS,GAAG,SAAS,OAAO,WAAW,QAAQ,OAAO,GAAG,IAAI,CAAC;GAC9D,YAAY,GAAG,SAAS,OAAO,WAAW,QAAQ,UAAU,GAAG,IAAI,CAAC;EACtE;EACA,iBAAiB,EAAE,MAAM,YAAY,OAAO,WAAW,MAAM,WAAW,OAAO,CAAC,EAAE;EAClF,QAAQ,MAAM;CAChB;AACF"}
|
|
1
|
+
{"version":3,"file":"request-services.js","names":[],"sources":["../src/request-services.ts"],"sourcesContent":["/**\n * Effect-native services for the three per-request surfaces on a SvelteKit\n * `RequestEvent` — cookies, response headers, and locals — each as a tagged\n * `Context.Service` that can be `yield*`-ed in any handler effect and mocked\n * in tests with `Layer.succeed`.\n *\n * All three services are provided automatically as part of the\n * {@link SvelteKitServices} per-request context when using\n * {@link makeServerRuntime}, the standalone `serverLoad`/`action`/`endpoint`\n * wrappers, or {@link makeRuntimeAdapter}. No extra layer setup is required.\n *\n * @example\n * ```ts\n * import { Effect } from \"effect\";\n * import {\n * SvelteKitCookies,\n * SvelteKitResponseHeaders,\n * SvelteKitLocals,\n * } from \"@thomasfosterau/effect-sveltekit/server\";\n *\n * export const load = serverLoad(() =>\n * Effect.gen(function* () {\n * const cookies = yield* SvelteKitCookies;\n * const session = yield* cookies.get(\"session\");\n *\n * const headers = yield* SvelteKitResponseHeaders;\n * yield* headers.set({ \"cache-control\": \"max-age=60, s-maxage=300\" });\n *\n * const locals = yield* SvelteKitLocals;\n * // locals is App.Locals — typed to your augmented interface\n * const user = locals.user;\n *\n * return { user };\n * }),\n * );\n * ```\n *\n * ## Testing\n *\n * Because these are tagged services, they can be replaced with stubs via\n * `Layer.succeed` in unit tests without involving a real `RequestEvent`:\n *\n * ```ts\n * import { Layer } from \"effect\";\n * import { SvelteKitCookies } from \"@thomasfosterau/effect-sveltekit/server\";\n * import { runServerLoad, TestRequestEvent } from \"@thomasfosterau/effect-sveltekit/testing\";\n *\n * const MockCookies = Layer.succeed(SvelteKitCookies, {\n * get: (name) => Effect.succeed(name === \"session\" ? \"tok_test\" : undefined),\n * getAll: () => Effect.succeed([{ name: \"session\", value: \"tok_test\" }]),\n * set: () => Effect.void,\n * delete: () => Effect.void,\n * serialize: (n, v) => Effect.succeed(`${n}=${v}`),\n * });\n *\n * const data = await runServerLoad(myLoadFn, TestRequestEvent(), { layer: MockCookies });\n * ```\n */\n\nimport type { Cookies, RequestEvent } from \"@sveltejs/kit\";\nimport * as Context from \"effect/Context\";\nimport * as Effect from \"effect/Effect\";\nimport * as Layer from \"effect/Layer\";\nimport { SvelteKitRequestEvent } from \"./runtime.js\";\n\n// ─── SvelteKitCookies ─────────────────────────────────────────────────────────\n\n/**\n * The Effect-native interface exposed by {@link SvelteKitCookies}. Each method\n * mirrors the corresponding `Cookies` method from `@sveltejs/kit` but returns\n * an `Effect` rather than a raw value or `void`.\n */\nexport interface SvelteKitCookiesInterface {\n /** Get a cookie by name. Returns `undefined` when the cookie is absent. */\n readonly get: (...args: Parameters<Cookies[\"get\"]>) => Effect.Effect<ReturnType<Cookies[\"get\"]>>;\n /** Get all cookies as `{ name, value }` pairs. */\n readonly getAll: (\n ...args: Parameters<Cookies[\"getAll\"]>\n ) => Effect.Effect<ReturnType<Cookies[\"getAll\"]>>;\n /** Set a cookie (adds a `set-cookie` response header). */\n readonly set: (...args: Parameters<Cookies[\"set\"]>) => Effect.Effect<void>;\n /** Delete a cookie by setting it to an empty value with a past expiry. */\n readonly delete: (...args: Parameters<Cookies[\"delete\"]>) => Effect.Effect<void>;\n /** Serialize a cookie into a `Set-Cookie` string without adding it to the response. */\n readonly serialize: (\n ...args: Parameters<Cookies[\"serialize\"]>\n ) => Effect.Effect<ReturnType<Cookies[\"serialize\"]>>;\n}\n\n/**\n * An Effect service wrapping the `RequestEvent.cookies` API.\n *\n * Available automatically in every server handler effect (it is part of\n * {@link SvelteKitServices}). Use `yield* SvelteKitCookies` to obtain the\n * service; call its methods (each returns an `Effect`) to read or write\n * cookies.\n *\n * @example\n * ```ts\n * const session = yield* (yield* SvelteKitCookies).get(\"session\");\n * ```\n */\nexport class SvelteKitCookies extends Context.Service<\n SvelteKitCookies,\n SvelteKitCookiesInterface\n>()(\"@thomasfosterau/effect-sveltekit/SvelteKitCookies\") {\n /**\n * Layer that builds the `SvelteKitCookies` service from the current\n * {@link SvelteKitRequestEvent}. Included in the per-request context\n * automatically; expose as a standalone layer for custom setups.\n */\n static readonly layer: Layer.Layer<SvelteKitCookies, never, SvelteKitRequestEvent> = Layer.effect(\n SvelteKitCookies,\n Effect.gen(function* () {\n const event = yield* SvelteKitRequestEvent;\n const { cookies } = event;\n return {\n get: (...args) => Effect.sync(() => cookies.get(...args)),\n getAll: (...args) => Effect.sync(() => cookies.getAll(...args)),\n set: (...args) => Effect.sync(() => cookies.set(...args)),\n delete: (...args) => Effect.sync(() => cookies.delete(...args)),\n serialize: (...args) => Effect.sync(() => cookies.serialize(...args)),\n };\n }),\n );\n}\n\n// ─── SvelteKitResponseHeaders ─────────────────────────────────────────────────\n\n/**\n * The Effect-native interface exposed by {@link SvelteKitResponseHeaders}.\n */\nexport interface SvelteKitResponseHeadersInterface {\n /**\n * Set one or more response headers. Equivalent to `event.setHeaders(headers)`.\n *\n * Note: You cannot set `set-cookie` headers here — use\n * {@link SvelteKitCookies} instead.\n */\n readonly set: (headers: Record<string, string>) => Effect.Effect<void>;\n}\n\n/**\n * An Effect service wrapping the `RequestEvent.setHeaders` API.\n *\n * Available automatically in every server handler effect. Use\n * `yield* SvelteKitResponseHeaders` to obtain the service, then call\n * `headers.set(...)`.\n *\n * @example\n * ```ts\n * const headers = yield* SvelteKitResponseHeaders;\n * yield* headers.set({ \"cache-control\": \"max-age=60, s-maxage=300\" });\n * ```\n */\nexport class SvelteKitResponseHeaders extends Context.Service<\n SvelteKitResponseHeaders,\n SvelteKitResponseHeadersInterface\n>()(\"@thomasfosterau/effect-sveltekit/SvelteKitResponseHeaders\") {\n /**\n * Layer that builds the `SvelteKitResponseHeaders` service from the current\n * {@link SvelteKitRequestEvent}.\n */\n static readonly layer: Layer.Layer<SvelteKitResponseHeaders, never, SvelteKitRequestEvent> =\n Layer.effect(\n SvelteKitResponseHeaders,\n Effect.gen(function* () {\n const event = yield* SvelteKitRequestEvent;\n return { set: (headers) => Effect.sync(() => event.setHeaders(headers)) };\n }),\n );\n}\n\n// ─── SvelteKitLocals ──────────────────────────────────────────────────────────\n\n/**\n * An Effect service that exposes the mutable `App.Locals` object for the\n * current request.\n *\n * `yield* SvelteKitLocals` returns the live `event.locals` reference — the\n * same object mutations in `hooks.server.ts` (e.g. setting `event.locals.user`\n * in a `handle` hook) write to. Mutations made to it inside handler effects\n * propagate back to subsequent hook middleware.\n *\n * Available automatically in every server handler effect.\n *\n * @example\n * ```ts\n * // Read a typed local (requires App.Locals augmentation in your app):\n * const { user } = yield* SvelteKitLocals;\n *\n * // Write a local (useful in handle hooks run via makeServerRuntime.handle):\n * const locals = yield* SvelteKitLocals;\n * locals.user = yield* Auth.currentUser;\n * ```\n */\nexport class SvelteKitLocals extends Context.Service<SvelteKitLocals, App.Locals>()(\n \"@thomasfosterau/effect-sveltekit/SvelteKitLocals\",\n) {\n /**\n * Layer that exposes `event.locals` (the mutable `App.Locals` reference)\n * from the current {@link SvelteKitRequestEvent}.\n */\n static readonly layer: Layer.Layer<SvelteKitLocals, never, SvelteKitRequestEvent> = Layer.effect(\n SvelteKitLocals,\n Effect.gen(function* () {\n const event = yield* SvelteKitRequestEvent;\n return event.locals;\n }),\n );\n}\n\n// ─── Combined layer ───────────────────────────────────────────────────────────\n\n/**\n * A convenience layer that provides all three request services\n * ({@link SvelteKitCookies}, {@link SvelteKitResponseHeaders},\n * {@link SvelteKitLocals}) from the current {@link SvelteKitRequestEvent}.\n *\n * This layer is merged into the per-request context automatically by\n * {@link makeServerRuntime}, the standalone wrappers, and\n * {@link makeRuntimeAdapter}. Expose it here for use in custom setups that\n * already have `SvelteKitRequestEvent` in scope.\n */\nexport const requestServicesLayer: Layer.Layer<\n SvelteKitCookies | SvelteKitResponseHeaders | SvelteKitLocals,\n never,\n SvelteKitRequestEvent\n> = Layer.mergeAll(SvelteKitCookies.layer, SvelteKitResponseHeaders.layer, SvelteKitLocals.layer);\n\n/** The three request services {@link requestServicesFrom} builds from an event. */\ninterface RequestServices {\n cookies: SvelteKitCookiesInterface;\n responseHeaders: SvelteKitResponseHeadersInterface;\n locals: App.Locals;\n}\n\n/**\n * Build all three request services directly from a {@link RequestEvent},\n * without going through {@link SvelteKitRequestEvent}. Used internally by\n * `requestLayer` in `app-runtime.ts` to avoid an extra indirection.\n */\nexport const requestServicesFrom = (event: RequestEvent): RequestServices => {\n const { cookies } = event;\n return {\n cookies: {\n get: (...args) => Effect.sync(() => cookies.get(...args)),\n getAll: (...args) => Effect.sync(() => cookies.getAll(...args)),\n set: (...args) => Effect.sync(() => cookies.set(...args)),\n delete: (...args) => Effect.sync(() => cookies.delete(...args)),\n serialize: (...args) => Effect.sync(() => cookies.serialize(...args)),\n },\n responseHeaders: { set: (headers) => Effect.sync(() => event.setHeaders(headers)) },\n locals: event.locals,\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAsGA,IAAa,mBAAb,MAAa,yBAAyB,QAAQ,QAG5C,CAAC,CAAC,mDAAmD,CAAC,CAAC;;;;;;CAMvD,OAAgB,QAAqE,MAAM,OACzF,kBACA,OAAO,IAAI,aAAa;EAEtB,MAAM,EAAE,YAAY,OADC;EAErB,OAAO;GACL,MAAM,GAAG,SAAS,OAAO,WAAW,QAAQ,IAAI,GAAG,IAAI,CAAC;GACxD,SAAS,GAAG,SAAS,OAAO,WAAW,QAAQ,OAAO,GAAG,IAAI,CAAC;GAC9D,MAAM,GAAG,SAAS,OAAO,WAAW,QAAQ,IAAI,GAAG,IAAI,CAAC;GACxD,SAAS,GAAG,SAAS,OAAO,WAAW,QAAQ,OAAO,GAAG,IAAI,CAAC;GAC9D,YAAY,GAAG,SAAS,OAAO,WAAW,QAAQ,UAAU,GAAG,IAAI,CAAC;EACtE;CACF,CAAC,CACH;AACF;;;;;;;;;;;;;;AA8BA,IAAa,2BAAb,MAAa,iCAAiC,QAAQ,QAGpD,CAAC,CAAC,2DAA2D,CAAC,CAAC;;;;;CAK/D,OAAgB,QACd,MAAM,OACJ,0BACA,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO;EACrB,OAAO,EAAE,MAAM,YAAY,OAAO,WAAW,MAAM,WAAW,OAAO,CAAC,EAAE;CAC1E,CAAC,CACH;AACJ;;;;;;;;;;;;;;;;;;;;;;AAyBA,IAAa,kBAAb,MAAa,wBAAwB,QAAQ,QAAqC,CAAC,CACjF,kDACF,CAAC,CAAC;;;;;CAKA,OAAgB,QAAoE,MAAM,OACxF,iBACA,OAAO,IAAI,aAAa;EAEtB,QAAO,OADc,sBAAA,CACR;CACf,CAAC,CACH;AACF;;;;;;;;;;;AAcA,MAAa,uBAIT,MAAM,SAAS,iBAAiB,OAAO,yBAAyB,OAAO,gBAAgB,KAAK;;;;;;AAchG,MAAa,uBAAuB,UAAyC;CAC3E,MAAM,EAAE,YAAY;CACpB,OAAO;EACL,SAAS;GACP,MAAM,GAAG,SAAS,OAAO,WAAW,QAAQ,IAAI,GAAG,IAAI,CAAC;GACxD,SAAS,GAAG,SAAS,OAAO,WAAW,QAAQ,OAAO,GAAG,IAAI,CAAC;GAC9D,MAAM,GAAG,SAAS,OAAO,WAAW,QAAQ,IAAI,GAAG,IAAI,CAAC;GACxD,SAAS,GAAG,SAAS,OAAO,WAAW,QAAQ,OAAO,GAAG,IAAI,CAAC;GAC9D,YAAY,GAAG,SAAS,OAAO,WAAW,QAAQ,UAAU,GAAG,IAAI,CAAC;EACtE;EACA,iBAAiB,EAAE,MAAM,YAAY,OAAO,WAAW,MAAM,WAAW,OAAO,CAAC,EAAE;EAClF,QAAQ,MAAM;CAChB;AACF"}
|
|
@@ -4,7 +4,7 @@ import { Transporter } from "@sveltejs/kit/hooks";
|
|
|
4
4
|
* A `Headers` that serialises to `{}` and knows how to copy itself onto a
|
|
5
5
|
* response. Everything else is the platform `Headers`.
|
|
6
6
|
*/
|
|
7
|
-
declare class ResponseHeaders extends globalThis.Headers {
|
|
7
|
+
export declare class ResponseHeaders extends globalThis.Headers {
|
|
8
8
|
/**
|
|
9
9
|
* Append every buffered header to `target` (typically `response.headers`
|
|
10
10
|
* after `resolve()`). Appends rather than sets, so repeated headers such as
|
|
@@ -18,7 +18,6 @@ declare class ResponseHeaders extends globalThis.Headers {
|
|
|
18
18
|
* The `transport` entry for {@link ResponseHeaders}: detected on the server,
|
|
19
19
|
* reconstructed on the client as a fresh, empty buffer.
|
|
20
20
|
*/
|
|
21
|
-
declare const responseHeadersTransporter: Transporter<ResponseHeaders, unknown>;
|
|
21
|
+
export declare const responseHeadersTransporter: Transporter<ResponseHeaders, unknown>;
|
|
22
22
|
//#endregion
|
|
23
|
-
export { ResponseHeaders, responseHeadersTransporter };
|
|
24
23
|
//# sourceMappingURL=response-headers.d.ts.map
|
package/dist/response-headers.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { markerTransporter } from "./transport.js";
|
|
2
|
+
import * as Arr from "effect/Array";
|
|
2
3
|
//#region src/response-headers.ts
|
|
3
4
|
/**
|
|
4
5
|
* A `Headers` that serialises to `{}` and knows how to copy itself onto a
|
|
@@ -11,7 +12,7 @@ var ResponseHeaders = class extends globalThis.Headers {
|
|
|
11
12
|
* `set-cookie` all survive.
|
|
12
13
|
*/
|
|
13
14
|
appendTo(target) {
|
|
14
|
-
|
|
15
|
+
Arr.forEach(this.getSetCookie(), (cookie) => target.append("set-cookie", cookie));
|
|
15
16
|
this.forEach((value, key) => {
|
|
16
17
|
if (key !== "set-cookie") target.append(key, value);
|
|
17
18
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response-headers.js","names":[],"sources":["../src/response-headers.ts"],"sourcesContent":["/**\n * A per-request **response-headers buffer** that can ride through `load` data.\n *\n * SvelteKit's `setHeaders` refuses `set-cookie` and throws when the same header\n * is set twice, and components rendering during SSR cannot reach it at all. The\n * pattern the applications converged on is a mutable `Headers` buffer on\n * `event.locals`: loads (and SSR components, via `page.data`) append to it, and\n * a server `handle` copies it onto the outgoing response after `resolve()`.\n *\n * Returning that buffer from a `load` needs a `transport` entry, or SvelteKit\n * refuses to serialise it (\"Cannot stringify arbitrary non-POJOs\"). Its contents\n * are deliberately *not* round-tripped — the server hook reads the live buffer\n * after rendering, so the client only needs a fresh, empty instance.\n * {@link ResponseHeaders} is the buffer, {@link responseHeadersTransporter} the\n * transport entry, and {@link ResponseHeaders.appendTo} the copy step.\n *\n * @example\n * ```ts\n * // src/hooks.ts\n * import { Hooks } from \"@thomasfosterau/effect-sveltekit\";\n *\n * export const transport = Hooks.transport({ ResponseHeaders: Hooks.responseHeadersTransporter });\n *\n * // src/hooks.server.ts\n * import { ResponseHeaders } from \"@thomasfosterau/effect-sveltekit\";\n * import type { Handle } from \"@sveltejs/kit/hooks\";\n *\n * export const handle: Handle = async ({ event, resolve }) => {\n * event.locals.headers = new ResponseHeaders();\n * const response = await resolve(event);\n * event.locals.headers.appendTo(response.headers);\n * return response;\n * };\n *\n * // src/routes/+layout.server.ts — expose the buffer to every page\n * export const load = ({ locals }) => ({ headers: locals.headers });\n * ```\n *\n * @module\n */\n\nimport type { Transporter } from \"@sveltejs/kit/hooks\";\nimport { markerTransporter } from \"./transport.js\";\n\n/**\n * A `Headers` that serialises to `{}` and knows how to copy itself onto a\n * response. Everything else is the platform `Headers`.\n */\nexport class ResponseHeaders extends globalThis.Headers {\n /**\n * Append every buffered header to `target` (typically `response.headers`\n * after `resolve()`). Appends rather than sets, so repeated headers such as\n * `set-cookie` all survive.\n */\n appendTo(target: globalThis.Headers): void {\n
|
|
1
|
+
{"version":3,"file":"response-headers.js","names":[],"sources":["../src/response-headers.ts"],"sourcesContent":["/**\n * A per-request **response-headers buffer** that can ride through `load` data.\n *\n * SvelteKit's `setHeaders` refuses `set-cookie` and throws when the same header\n * is set twice, and components rendering during SSR cannot reach it at all. The\n * pattern the applications converged on is a mutable `Headers` buffer on\n * `event.locals`: loads (and SSR components, via `page.data`) append to it, and\n * a server `handle` copies it onto the outgoing response after `resolve()`.\n *\n * Returning that buffer from a `load` needs a `transport` entry, or SvelteKit\n * refuses to serialise it (\"Cannot stringify arbitrary non-POJOs\"). Its contents\n * are deliberately *not* round-tripped — the server hook reads the live buffer\n * after rendering, so the client only needs a fresh, empty instance.\n * {@link ResponseHeaders} is the buffer, {@link responseHeadersTransporter} the\n * transport entry, and {@link ResponseHeaders.appendTo} the copy step.\n *\n * @example\n * ```ts\n * // src/hooks.ts\n * import { Hooks } from \"@thomasfosterau/effect-sveltekit\";\n *\n * export const transport = Hooks.transport({ ResponseHeaders: Hooks.responseHeadersTransporter });\n *\n * // src/hooks.server.ts\n * import { ResponseHeaders } from \"@thomasfosterau/effect-sveltekit\";\n * import type { Handle } from \"@sveltejs/kit/hooks\";\n *\n * export const handle: Handle = async ({ event, resolve }) => {\n * event.locals.headers = new ResponseHeaders();\n * const response = await resolve(event);\n * event.locals.headers.appendTo(response.headers);\n * return response;\n * };\n *\n * // src/routes/+layout.server.ts — expose the buffer to every page\n * export const load = ({ locals }) => ({ headers: locals.headers });\n * ```\n *\n * @module\n */\n\nimport type { Transporter } from \"@sveltejs/kit/hooks\";\nimport * as Arr from \"effect/Array\";\nimport { markerTransporter } from \"./transport.js\";\n\n/**\n * A `Headers` that serialises to `{}` and knows how to copy itself onto a\n * response. Everything else is the platform `Headers`.\n */\nexport class ResponseHeaders extends globalThis.Headers {\n /**\n * Append every buffered header to `target` (typically `response.headers`\n * after `resolve()`). Appends rather than sets, so repeated headers such as\n * `set-cookie` all survive.\n */\n appendTo(target: globalThis.Headers): void {\n Arr.forEach(this.getSetCookie(), (cookie) => target.append(\"set-cookie\", cookie));\n this.forEach((value, key) => {\n if (key !== \"set-cookie\") target.append(key, value);\n });\n }\n\n /** Serialises to an empty object: the contents never leave the server. */\n toJSON(): Record<string, never> {\n return {};\n }\n}\n\n/**\n * The `transport` entry for {@link ResponseHeaders}: detected on the server,\n * reconstructed on the client as a fresh, empty buffer.\n */\nexport const responseHeadersTransporter: Transporter<ResponseHeaders, unknown> = markerTransporter({\n guard: (value): value is ResponseHeaders => value instanceof ResponseHeaders,\n make: () => new ResponseHeaders(),\n});\n"],"mappings":";;;;;;;AAiDA,IAAa,kBAAb,cAAqC,WAAW,QAAQ;;;;;;CAMtD,SAAS,QAAkC;EACzC,IAAI,QAAQ,KAAK,aAAa,IAAI,WAAW,OAAO,OAAO,cAAc,MAAM,CAAC;EAChF,KAAK,SAAS,OAAO,QAAQ;GAC3B,IAAI,QAAQ,cAAc,OAAO,OAAO,KAAK,KAAK;EACpD,CAAC;CACH;;CAGA,SAAgC;EAC9B,OAAO,CAAC;CACV;AACF;;;;;AAMA,MAAa,6BAAoE,kBAAkB;CACjG,QAAQ,UAAoC,iBAAiB;CAC7D,YAAY,IAAI,gBAAgB;AAClC,CAAC"}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
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 ManagedRuntime from "effect/ManagedRuntime";
|
|
2
5
|
import * as HttpClient from "effect/http/HttpClient";
|
|
3
6
|
import { RequestEvent } from "@sveltejs/kit";
|
|
4
7
|
//#region src/runtime.d.ts
|
|
@@ -7,7 +10,7 @@ declare const SvelteKitRequestEvent_base: Context.ServiceClass<SvelteKitRequestE
|
|
|
7
10
|
* Service that provides access to the current SvelteKit RequestEvent.
|
|
8
11
|
* This allows Effect code to access request-specific data like cookies, headers, etc.
|
|
9
12
|
*/
|
|
10
|
-
declare class SvelteKitRequestEvent extends SvelteKitRequestEvent_base {}
|
|
13
|
+
export declare class SvelteKitRequestEvent extends SvelteKitRequestEvent_base {}
|
|
11
14
|
/**
|
|
12
15
|
* Custom runtime for SvelteKit server-side helpers.
|
|
13
16
|
*
|
|
@@ -35,7 +38,7 @@ declare class SvelteKitRequestEvent extends SvelteKitRequestEvent_base {}
|
|
|
35
38
|
* };
|
|
36
39
|
* ```
|
|
37
40
|
*/
|
|
38
|
-
declare class SvelteKitRuntime {
|
|
41
|
+
export declare class SvelteKitRuntime {
|
|
39
42
|
/**
|
|
40
43
|
* Creates a layer with platform services for a specific request.
|
|
41
44
|
* This includes FetchHttpClient and access to the RequestEvent.
|
|
@@ -123,7 +126,7 @@ declare class SvelteKitRuntime {
|
|
|
123
126
|
* };
|
|
124
127
|
* ```
|
|
125
128
|
*/
|
|
126
|
-
declare function runWithSvelteKitRuntime<A, E>(event: RequestEvent, effect: Effect.Effect<A, E, HttpClient.HttpClient | SvelteKitRequestEvent>): Promise<A>;
|
|
129
|
+
export declare function runWithSvelteKitRuntime<A, E>(event: RequestEvent, effect: Effect.Effect<A, E, HttpClient.HttpClient | SvelteKitRequestEvent>): Promise<A>;
|
|
127
130
|
//#endregion
|
|
128
|
-
export {
|
|
131
|
+
export {};
|
|
129
132
|
//# sourceMappingURL=runtime.d.ts.map
|
package/dist/runtime.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import "effect/Effect";
|
|
2
|
+
import * as Context from "effect/Context";
|
|
3
|
+
import * as Layer from "effect/Layer";
|
|
4
|
+
import * as ManagedRuntime from "effect/ManagedRuntime";
|
|
2
5
|
import * as FetchHttpClient from "effect/http/FetchHttpClient";
|
|
3
6
|
import "effect/http/HttpClient";
|
|
4
7
|
//#region src/runtime.ts
|
package/dist/runtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.js","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"file":"runtime.js","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["import * as Layer from \"effect/Layer\";\nimport * as ManagedRuntime from \"effect/ManagedRuntime\";\nimport * as Context from \"effect/Context\";\nimport * as Effect from \"effect/Effect\";\nimport * as HttpClient from \"effect/http/HttpClient\";\nimport * as FetchHttpClient from \"effect/http/FetchHttpClient\";\nimport type { RequestEvent } from \"@sveltejs/kit\";\n\n/**\n * Service that provides access to the current SvelteKit RequestEvent.\n * This allows Effect code to access request-specific data like cookies, headers, etc.\n */\nexport class SvelteKitRequestEvent extends Context.Service<SvelteKitRequestEvent, RequestEvent>()(\n \"SvelteKitRequestEvent\",\n) {}\n\n/**\n * Custom runtime for SvelteKit server-side helpers.\n *\n * This runtime is designed for SvelteKit server-side execution and provides:\n * - HttpClient for making HTTP requests\n * - FetchHttpClient using the platform's fetch implementation\n * - SvelteKitRequestEvent for accessing SvelteKit-specific request data\n *\n * @example\n * ```ts\n * import { SvelteKitRuntime } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n * import * as HttpClient from 'effect/http/HttpClient';\n *\n * export const load = (event) => {\n * const runtime = SvelteKitRuntime.forRequest(event);\n * return runtime.runPromise(\n * Effect.gen(function* () {\n * // Access HttpClient from the runtime\n * const client = yield* HttpClient.HttpClient;\n * const response = yield* client.get(\"https://api.example.com/data\");\n * return { data: response };\n * })\n * );\n * };\n * ```\n */\nexport class SvelteKitRuntime {\n /**\n * Creates a layer with platform services for a specific request.\n * This includes FetchHttpClient and access to the RequestEvent.\n *\n * @param event - The SvelteKit RequestEvent\n * @returns A layer providing platform services\n */\n private static createRequestLayer(\n event: RequestEvent,\n ): Layer.Layer<HttpClient.HttpClient | SvelteKitRequestEvent> {\n // Provide HttpClient using SvelteKit's fetch\n const httpClientLayer = FetchHttpClient.layer.pipe(\n Layer.provide(Layer.succeed(FetchHttpClient.Fetch, event.fetch)),\n );\n\n // Provide access to the RequestEvent\n const requestEventLayer = Layer.succeed(SvelteKitRequestEvent, event);\n\n return Layer.merge(httpClientLayer, requestEventLayer);\n }\n\n /**\n * Creates a runtime for a specific SvelteKit request.\n * This runtime includes all platform services configured for the request.\n *\n * @param event - The SvelteKit RequestEvent\n * @returns A managed runtime with platform services\n */\n static forRequest(\n event: RequestEvent,\n ): ManagedRuntime.ManagedRuntime<HttpClient.HttpClient | SvelteKitRequestEvent, never> {\n return ManagedRuntime.make(this.createRequestLayer(event));\n }\n\n /**\n * Extends the request runtime with additional services.\n *\n * @param event - The SvelteKit RequestEvent\n * @param layer - Additional layer to merge\n * @returns A managed runtime with both platform and custom services\n *\n * @example\n * ```ts\n * import { SvelteKitRuntime } from '@thomasfosterau/effect-sveltekit/server';\n * import { Layer, Context } from 'effect';\n *\n * class Database extends Context.Service<Database, {\n * query: (sql: string) => Effect.Effect<unknown>\n * }>()(\"Database\") {}\n *\n * const dbLayer = Layer.succeed(Database, {\n * query: (sql) => Effect.succeed([])\n * });\n *\n * export const load = (event) => {\n * const runtime = SvelteKitRuntime.extend(event, dbLayer);\n * // Use runtime...\n * };\n * ```\n */\n static extend<R, E>(\n event: RequestEvent,\n layer: Layer.Layer<R, E>,\n ): ManagedRuntime.ManagedRuntime<R | HttpClient.HttpClient | SvelteKitRequestEvent, E> {\n return ManagedRuntime.make(Layer.merge(this.createRequestLayer(event), layer));\n }\n\n /**\n * Creates a runtime that provides `HttpClient` backed by the given fetch\n * implementation, without a `SvelteKitRequestEvent`.\n *\n * This is used for universal `load` functions running on the client, where\n * there is no `RequestEvent` but the load event still exposes `fetch`.\n *\n * @param fetchFn - The fetch implementation to back HttpClient with\n * @returns A managed runtime providing HttpClient\n */\n static forFetch(\n fetchFn: typeof globalThis.fetch,\n ): ManagedRuntime.ManagedRuntime<HttpClient.HttpClient, never> {\n return ManagedRuntime.make(\n FetchHttpClient.layer.pipe(Layer.provide(Layer.succeed(FetchHttpClient.Fetch, fetchFn))),\n );\n }\n\n /**\n * The base layer for SvelteKit without request-specific services.\n * Use this for effects that don't need access to the request.\n */\n static readonly baseLayer = Layer.empty;\n\n /**\n * Creates a runtime from a custom layer.\n *\n * @param layer - The layer to use for the runtime\n * @returns A managed runtime\n */\n static make<R, E>(layer: Layer.Layer<R, E>): ManagedRuntime.ManagedRuntime<R, E> {\n return ManagedRuntime.make(layer);\n }\n}\n\n/**\n * Helper to run an effect with the SvelteKit runtime for a specific request.\n * This automatically sets up and tears down the runtime.\n *\n * @param event - The SvelteKit RequestEvent\n * @param effect - The effect to run\n * @returns A promise with the result\n *\n * @example\n * ```ts\n * import { runWithSvelteKitRuntime } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n *\n * export const load = async (event) => {\n * return await runWithSvelteKitRuntime(event,\n * Effect.succeed({ message: \"Hello!\" })\n * );\n * };\n * ```\n */\nexport function runWithSvelteKitRuntime<A, E>(\n event: RequestEvent,\n effect: Effect.Effect<A, E, HttpClient.HttpClient | SvelteKitRequestEvent>,\n): Promise<A> {\n const runtime = SvelteKitRuntime.forRequest(event);\n return runtime.runPromise(effect);\n}\n"],"mappings":";;;;;;;;;;;AAYA,IAAa,wBAAb,cAA2C,QAAQ,QAA6C,CAAC,CAC/F,uBACF,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BH,IAAa,mBAAb,MAA8B;;;;;;;;CAQ5B,OAAe,mBACb,OAC4D;EAE5D,MAAM,kBAAkB,gBAAgB,MAAM,KAC5C,MAAM,QAAQ,MAAM,QAAQ,gBAAgB,OAAO,MAAM,KAAK,CAAC,CACjE;EAGA,MAAM,oBAAoB,MAAM,QAAQ,uBAAuB,KAAK;EAEpE,OAAO,MAAM,MAAM,iBAAiB,iBAAiB;CACvD;;;;;;;;CASA,OAAO,WACL,OACqF;EACrF,OAAO,eAAe,KAAK,KAAK,mBAAmB,KAAK,CAAC;CAC3D;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BA,OAAO,OACL,OACA,OACqF;EACrF,OAAO,eAAe,KAAK,MAAM,MAAM,KAAK,mBAAmB,KAAK,GAAG,KAAK,CAAC;CAC/E;;;;;;;;;;;CAYA,OAAO,SACL,SAC6D;EAC7D,OAAO,eAAe,KACpB,gBAAgB,MAAM,KAAK,MAAM,QAAQ,MAAM,QAAQ,gBAAgB,OAAO,OAAO,CAAC,CAAC,CACzF;CACF;;;;;CAMA,OAAgB,YAAY,MAAM;;;;;;;CAQlC,OAAO,KAAW,OAA+D;EAC/E,OAAO,eAAe,KAAK,KAAK;CAClC;AACF;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,wBACd,OACA,QACY;CAEZ,OADgB,iBAAiB,WAAW,KAC/B,CAAC,CAAC,WAAW,MAAM;AAClC"}
|
|
@@ -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),
|