@thomasfosterau/effect-sveltekit 0.0.0 → 0.1.1

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.
Files changed (40) hide show
  1. package/README.md +36 -13
  2. package/dist/Hooks.d.ts +2 -2
  3. package/dist/Hooks.js +2 -1
  4. package/dist/Hooks.js.map +1 -1
  5. package/dist/adapter/assets.d.ts +33 -0
  6. package/dist/adapter/assets.js +87 -0
  7. package/dist/adapter/assets.js.map +1 -0
  8. package/dist/adapter/handler.d.ts +75 -10
  9. package/dist/adapter/handler.js +73 -44
  10. package/dist/adapter/handler.js.map +1 -1
  11. package/dist/adapter/index.d.ts +15 -0
  12. package/dist/adapter/index.js +43 -5
  13. package/dist/adapter/index.js.map +1 -1
  14. package/dist/app-runtime.d.ts +24 -7
  15. package/dist/app-runtime.js +13 -10
  16. package/dist/app-runtime.js.map +1 -1
  17. package/dist/internal/remote-run.d.ts +17 -0
  18. package/dist/internal/remote-run.js +39 -0
  19. package/dist/internal/remote-run.js.map +1 -0
  20. package/dist/internal/remote-validators.js +23 -0
  21. package/dist/internal/remote-validators.js.map +1 -0
  22. package/dist/internal/route-params.d.ts +23 -0
  23. package/dist/remote.server.d.ts +105 -33
  24. package/dist/remote.server.js +96 -41
  25. package/dist/remote.server.js.map +1 -1
  26. package/dist/request-runtime.d.ts +139 -0
  27. package/dist/request-runtime.js +186 -0
  28. package/dist/request-runtime.js.map +1 -0
  29. package/dist/server/Endpoint.d.ts +35 -4
  30. package/dist/server/Endpoint.js +14 -5
  31. package/dist/server/Endpoint.js.map +1 -1
  32. package/dist/server.d.ts +8 -6
  33. package/dist/server.js +2 -1
  34. package/dist/server.js.map +1 -1
  35. package/dist/transport.d.ts +43 -5
  36. package/dist/transport.js +36 -6
  37. package/dist/transport.js.map +1 -1
  38. package/dist/universal.d.ts +2 -1
  39. package/dist/universal.js.map +1 -1
  40. package/package.json +2 -2
@@ -0,0 +1,186 @@
1
+ import { causeToThrowable } from "./internal/error.js";
2
+ import { Cause, Effect, Exit, ManagedRuntime } from "effect";
3
+ import { isHttpError, isRedirect } from "@sveltejs/kit";
4
+ //#region src/request-runtime.ts
5
+ /**
6
+ * A general, Effect-idiomatic way to provide the per-request execution context
7
+ * for SvelteKit server wrappers.
8
+ *
9
+ * Where {@link makeServerRuntime} bakes in "the package owns a runtime, built
10
+ * once per process," this module factors out the single decision *how to obtain
11
+ * the execution context for this request* behind one open abstraction — a
12
+ * {@link RequestRuntimeProvider}. Existing models (a build-once
13
+ * `ManagedRuntime`, a layer built once), a consumer-owned runtime resolved from
14
+ * `event.locals`, and a package-built per-request runtime are all just
15
+ * {@link RequestRuntime} constructors of the same provider, not separate code
16
+ * paths.
17
+ *
18
+ * {@link makeRuntimeAdapter} binds the `serverLoad` / `action` / `actions` /
19
+ * `endpoint` wrappers to a provider (and an optional boundary error
20
+ * translator), so the handler effect's requirement channel `R` is the
21
+ * *provider's* services — your own application catalogue — rather than the fixed
22
+ * {@link SvelteKitServices}.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * // The consumer owns a per-request ManagedRuntime on event.locals.runtime
27
+ * // (rebuilt mid-request); the package adopts it, resolved at call time.
28
+ * import { makeRuntimeAdapter, RequestRuntime } from "@thomasfosterau/effect-sveltekit/server";
29
+ *
30
+ * const { serverLoad, action, actions, endpoint } = makeRuntimeAdapter(
31
+ * RequestRuntime.fromLocals((event) => event.locals.runtime),
32
+ * { translateError: toSvelteKit },
33
+ * );
34
+ *
35
+ * export const load = serverLoad((event) =>
36
+ * Effect.gen(function* () {
37
+ * const db = yield* Database; // app service from locals.runtime
38
+ * return { items: yield* db.list() };
39
+ * }),
40
+ * );
41
+ * ```
42
+ */
43
+ /** Runners backed by an existing `ManagedRuntime`. */
44
+ const runnersFromManagedRuntime = (rt) => ({
45
+ runPromiseExit: (effect) => rt.runPromiseExit(effect),
46
+ runFork: (effect) => rt.runFork(effect)
47
+ });
48
+ /** Runners backed by a built service `Context` (run on the default scheduler). */
49
+ const runnersFromContext = (context) => ({
50
+ runPromiseExit: (effect) => Effect.runPromiseExitWith(context)(effect),
51
+ runFork: (effect) => Effect.runForkWith(context)(effect)
52
+ });
53
+ /** Runners for a target that is either a `ManagedRuntime` or a `Context`. */
54
+ const runnersFor = (target) => ManagedRuntime.isManagedRuntime(target) ? runnersFromManagedRuntime(target) : runnersFromContext(target);
55
+ /**
56
+ * Constructors for {@link RequestRuntimeProvider}. Each expresses one way to
57
+ * obtain a request's execution context; the wrappers don't care which.
58
+ */
59
+ const RequestRuntime = {
60
+ /**
61
+ * Adopt a `ManagedRuntime` the consumer already built (the build-once model,
62
+ * expressed as a provider).
63
+ */
64
+ fromManagedRuntime: (rt) => {
65
+ const runners = runnersFromManagedRuntime(rt);
66
+ return { run: () => runners };
67
+ },
68
+ /**
69
+ * Let the package build a single `ManagedRuntime` from a layer, lazily on
70
+ * first use and shared across requests (the existing build-once-per-process
71
+ * model).
72
+ */
73
+ fromLayer: (layer) => {
74
+ let runners;
75
+ return { run: () => runners ??= runnersFromManagedRuntime(ManagedRuntime.make(layer)) };
76
+ },
77
+ /**
78
+ * Adopt a runtime the consumer stashed on the request (e.g. on
79
+ * `event.locals`). Resolved at call time, so a runtime rebuilt mid-request is
80
+ * honoured automatically. The getter may return a `ManagedRuntime` or a
81
+ * built service `Context`.
82
+ */
83
+ fromLocals: (get) => ({ run: (event) => runnersFor(get(event)) }),
84
+ /**
85
+ * Let the package build *and dispose* a `ManagedRuntime` per request from a
86
+ * layer factory. Each handler invocation builds its own runtime from
87
+ * `layer(event)` and disposes it once the effect settles — so a per-request
88
+ * resource (a DB pool) dies with the request. Use `opts.afterResponse` to
89
+ * defer disposal to after the response (Workers `ctx.waitUntil`).
90
+ *
91
+ * For sharing one runtime across every load/action of a request, prefer
92
+ * {@link RequestRuntime.fromLocals} with a runtime the consumer builds in
93
+ * `hooks.server.ts`.
94
+ */
95
+ fromLayerPerRequest: (layer, opts) => ({ run: (event) => {
96
+ const dispose = (rt) => () => rt.dispose();
97
+ const teardown = (rt) => {
98
+ if (opts?.afterResponse) {
99
+ opts.afterResponse(event, dispose(rt));
100
+ return Promise.resolve();
101
+ }
102
+ return rt.dispose();
103
+ };
104
+ return {
105
+ runPromiseExit: async (effect) => {
106
+ const rt = ManagedRuntime.make(layer(event));
107
+ try {
108
+ return await rt.runPromiseExit(effect);
109
+ } finally {
110
+ await teardown(rt);
111
+ }
112
+ },
113
+ runFork: (effect) => {
114
+ const rt = ManagedRuntime.make(layer(event));
115
+ const fiber = rt.runFork(effect);
116
+ fiber.addObserver(() => void teardown(rt));
117
+ return fiber;
118
+ }
119
+ };
120
+ } })
121
+ };
122
+ /**
123
+ * Bind the SvelteKit server wrappers to a {@link RequestRuntimeProvider} (and an
124
+ * optional boundary error translator).
125
+ *
126
+ * Unlike the standalone `serverLoad` / `action` / `endpoint`, the handler
127
+ * effect's required services `R` come from the provider — your own application
128
+ * catalogue — not the fixed {@link SvelteKitServices}. The package does not
129
+ * merge `HttpClient` / `SvelteKitRequestEvent` onto this path; add them to your
130
+ * own layer if you want them.
131
+ *
132
+ * @example
133
+ * ```ts
134
+ * // Package owns the runtime per request on Workers, disposed via waitUntil.
135
+ * const { serverLoad } = makeRuntimeAdapter(
136
+ * RequestRuntime.fromLayerPerRequest((event) => AppLayer(event.platform!.env), {
137
+ * afterResponse: (event, dispose) => event.platform!.ctx.waitUntil(dispose()),
138
+ * }),
139
+ * );
140
+ * ```
141
+ */
142
+ const makeRuntimeAdapter = (provider, opts) => {
143
+ const translateError = opts?.translateError ?? ((cause) => {
144
+ throw causeToThrowable(cause);
145
+ });
146
+ const runSettle = async (event, effect) => {
147
+ const exit = await provider.run(event).runPromiseExit(effect);
148
+ try {
149
+ if (Exit.isSuccess(exit)) return exit.value;
150
+ return translateError(exit.cause);
151
+ } finally {
152
+ provider.disposeAfterResponse?.(event);
153
+ }
154
+ };
155
+ const action = (fn) => (event) => runSettle(event, fn(event));
156
+ return {
157
+ serverLoad: (fn) => (event) => runSettle(event, fn(event)),
158
+ action,
159
+ actions: (map) => {
160
+ const wrapped = {};
161
+ for (const [key, fn] of Object.entries(map)) wrapped[key] = action(fn);
162
+ return wrapped;
163
+ },
164
+ endpoint: (fn, options) => async (event) => {
165
+ const exit = await provider.run(event).runPromiseExit(fn(event));
166
+ try {
167
+ if (Exit.isSuccess(exit)) return exit.value;
168
+ if (opts?.translateError) return opts.translateError(exit.cause);
169
+ const squashed = Cause.squash(exit.cause);
170
+ if (isRedirect(squashed) || isHttpError(squashed)) throw squashed;
171
+ console.error("Endpoint error:", Cause.pretty(exit.cause));
172
+ const message = options?.exposeErrors ? Cause.pretty(exit.cause) : "Internal server error";
173
+ return new Response(JSON.stringify({ error: message }), {
174
+ status: 500,
175
+ headers: { "Content-Type": "application/json" }
176
+ });
177
+ } finally {
178
+ provider.disposeAfterResponse?.(event);
179
+ }
180
+ }
181
+ };
182
+ };
183
+ //#endregion
184
+ export { RequestRuntime, makeRuntimeAdapter };
185
+
186
+ //# sourceMappingURL=request-runtime.js.map
@@ -0,0 +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 { causeToThrowable } from \"./internal/error.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 =\n opts?.translateError ??\n ((cause) => {\n throw causeToThrowable(cause);\n });\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) => runSettle(event, fn(event));\n\n return {\n serverLoad: (fn) => (event) => runSettle(event, fn(event)),\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 exit = await provider.run(event as RequestEvent).runPromiseExit(fn(event));\n try {\n if (Exit.isSuccess(exit)) return exit.value;\n // A configured translator owns the whole failure path (control flow,\n // structured error bodies, ...).\n if (opts?.translateError) return opts.translateError(exit.cause);\n // Default: pass SvelteKit control flow through, otherwise a 500.\n const squashed = Cause.squash(exit.cause);\n if (isRedirect(squashed) || isHttpError(squashed)) throw squashed;\n console.error(\"Endpoint error:\", Cause.pretty(exit.cause));\n const message = options?.exposeErrors ? Cause.pretty(exit.cause) : \"Internal server error\";\n return new Response(JSON.stringify({ error: message }), {\n status: 500,\n headers: { \"Content-Type\": \"application/json\" },\n });\n } finally {\n provider.disposeAfterResponse?.(event as RequestEvent);\n }\n },\n };\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyFA,MAAM,6BACJ,QAC8B;CAC9B,iBAAiB,WAAW,GAAG,eAAe,MAAM;CACpD,UAAU,WAAW,GAAG,QAAQ,MAAM;AACxC;;AAGA,MAAM,sBAAyB,aAA2D;CACxF,iBAAiB,WAAW,OAAO,mBAAmB,OAAO,EAAE,MAAM;CACrE,UAAU,WAAW,OAAO,YAAY,OAAO,EAAE,MAAM;AACzD;;AAGA,MAAM,cACJ,WAEA,eAAe,iBAAiB,MAAM,IAClC,0BAA0B,MAAmD,IAC7E,mBAAmB,MAA4B;;;;;AAqBrD,MAAa,iBAAiB;;;;;CAK5B,qBACE,OACiC;EACjC,MAAM,UAAU,0BAA0B,EAA+C;EACzF,OAAO,EAAE,WAAW,QAAQ;CAC9B;;;;;;CAOA,YAAkB,UAA2D;EAC3E,IAAI;EACJ,OAAO,EACL,WACG,YAAY,0BACX,eAAe,KAAK,KAAK,CAC3B,EACJ;CACF;;;;;;;CAQA,aACE,SAC+B,EAC/B,MAAM,UAAU,WAAW,IAAI,KAAK,CAAC,EACvC;;;;;;;;;;;;CAaA,sBACE,OACA,UACkC,EAClC,MAAM,UAAU;EACd,MAAM,WAAW,aAAkD,GAAG,QAAQ;EAC9E,MAAM,YAAY,OAA2D;GAC3E,IAAI,MAAM,eAAe;IACvB,KAAK,cAAc,OAAO,QAAQ,EAAE,CAAC;IACrC,OAAO,QAAQ,QAAQ;GACzB;GACA,OAAO,GAAG,QAAQ;EACpB;EACA,OAAO;GACL,gBAAgB,OAAO,WAAW;IAChC,MAAM,KAAK,eAAe,KAAK,MAAM,KAAK,CAAC;IAC3C,IAAI;KACF,OAAQ,MAAM,GAAG,eAAe,MAAM;IACxC,UAAU;KACR,MAAM,SAAS,EAAE;IACnB;GACF;GACA,UAAU,WAAW;IACnB,MAAM,KAAK,eAAe,KAAK,MAAM,KAAK,CAAC;IAC3C,MAAM,QAAQ,GAAG,QAAQ,MAAM;IAC/B,MAAM,kBAAkB,KAAK,SAAS,EAAE,CAAC;IACzC,OAAO;GACT;EACF;CACF,EACF;AACF;;;;;;;;;;;;;;;;;;;;;AAkFA,MAAa,sBACX,UACA,SACsB;CACtB,MAAM,iBACJ,MAAM,oBACJ,UAAU;EACV,MAAM,iBAAiB,KAAK;CAC9B;CAEF,MAAM,YAAY,OAChB,OACA,WACe;EACf,MAAM,OAAO,MAAM,SAAS,IAAI,KAAK,EAAE,eAAe,MAAM;EAC5D,IAAI;GACF,IAAI,KAAK,UAAU,IAAI,GAAG,OAAO,KAAK;GACtC,OAAO,eAAe,KAAK,KAAK;EAClC,UAAU;GACR,SAAS,uBAAuB,KAAK;EACvC;CACF;CAEA,MAAM,UAAuC,QAAQ,UAAU,UAAU,OAAO,GAAG,KAAK,CAAC;CAEzF,OAAO;EACL,aAAa,QAAQ,UAAU,UAAU,OAAO,GAAG,KAAK,CAAC;EACzD;EACA,UAAU,QAAQ;GAChB,MAAM,UAAiD,CAAC;GACxD,KAAK,MAAM,CAAC,KAAK,OAAO,OAAO,QAAQ,GAAG,GAAG,QAAQ,OAAO,OAAO,EAAE;GACrE,OAAO;EACT;EACA,WAAW,IAAI,YAAY,OAAO,UAAU;GAC1C,MAAM,OAAO,MAAM,SAAS,IAAI,KAAqB,EAAE,eAAe,GAAG,KAAK,CAAC;GAC/E,IAAI;IACF,IAAI,KAAK,UAAU,IAAI,GAAG,OAAO,KAAK;IAGtC,IAAI,MAAM,gBAAgB,OAAO,KAAK,eAAe,KAAK,KAAK;IAE/D,MAAM,WAAW,MAAM,OAAO,KAAK,KAAK;IACxC,IAAI,WAAW,QAAQ,KAAK,YAAY,QAAQ,GAAG,MAAM;IACzD,QAAQ,MAAM,mBAAmB,MAAM,OAAO,KAAK,KAAK,CAAC;IACzD,MAAM,UAAU,SAAS,eAAe,MAAM,OAAO,KAAK,KAAK,IAAI;IACnE,OAAO,IAAI,SAAS,KAAK,UAAU,EAAE,OAAO,QAAQ,CAAC,GAAG;KACtD,QAAQ;KACR,SAAS,EAAE,gBAAgB,mBAAmB;IAChD,CAAC;GACH,UAAU;IACR,SAAS,uBAAuB,KAAqB;GACvD;EACF;CACF;AACF"}
@@ -1,4 +1,4 @@
1
- import { Effect, Layer, Scope } from "effect";
1
+ import { Context, Effect, Layer, Scope } from "effect";
2
2
  import { RequestHandler } from "@sveltejs/kit";
3
3
  import * as HttpRouter from "effect/unstable/http/HttpRouter";
4
4
  import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
@@ -8,8 +8,15 @@ import * as HttpApiGroup from "effect/unstable/httpapi/HttpApiGroup";
8
8
 
9
9
  //#region src/server/Endpoint.d.ts
10
10
  declare namespace Endpoint_d_exports {
11
- export { EndpointHandler, EndpointOptions, fromApi, fromHttpEffect, fromRouter };
11
+ export { EndpointHandler, EndpointOptions, RequestContext, fromApi, fromHttpEffect, fromRouter };
12
12
  }
13
+ /**
14
+ * Resolve a request-scoped service context, if a resolver was given. The
15
+ * resolver may return a `Context` directly or an `Effect` producing one (run
16
+ * with no requirements), so a consumer can read the request to build
17
+ * session-aware services per dispatch.
18
+ */
19
+ type RequestContext<RequestServices> = (request: Request) => Context.Context<RequestServices> | Effect.Effect<Context.Context<RequestServices>, never, never>;
13
20
  /**
14
21
  * A SvelteKit endpoint handler — the built-in
15
22
  * [`RequestHandler`](https://svelte.dev/docs/kit/@sveltejs-kit#RequestHandler) —
@@ -49,9 +56,27 @@ interface EndpointOptions {
49
56
  * export const fallback = Endpoint.fromApi(api, ApiLive, { prefix: "/api" });
50
57
  * ```
51
58
  */
52
- declare const fromApi: <Id extends string, Groups extends HttpApiGroup.Any, E>(api: HttpApi.HttpApi<Id, Groups>, layer: Layer.Layer<HttpApiGroup.ToService<Id, Groups>, E>, options?: EndpointOptions & {
59
+ declare const fromApi: <Id extends string, Groups extends HttpApiGroup.Any, E, LayerRequires = never, RequestServices = never>(api: HttpApi.HttpApi<Id, Groups>, layer: Layer.Layer<HttpApiGroup.ToService<Id, Groups>, E, LayerRequires>, options?: EndpointOptions & {
53
60
  /** Serve the OpenAPI spec for the API at this path (e.g. `"/openapi.json"`). */readonly openapiPath?: `/${string}`; /** Add Effect's request logger middleware. Defaults to `false` (quiet). */
54
61
  readonly logger?: boolean;
62
+ /**
63
+ * Provide the request-scoped services the API groups require, per request.
64
+ * The build-once platform layer (`HttpPlatform` / `FileSystem` / `Path` /
65
+ * `Etag`) stays shared; only this slice is resolved and merged for each
66
+ * dispatch — so a session-aware, request-pooled API can run on Workers
67
+ * without rebuilding its platform layer.
68
+ *
69
+ * The resolver may return a `Context` or an `Effect` producing one. Leave
70
+ * the matching services unprovided in `layer` and supply them here:
71
+ *
72
+ * ```ts
73
+ * Endpoint.fromApi(api, ApiLive, {
74
+ * prefix: "/api",
75
+ * context: (request) => Context.make(CurrentUser, userFor(request)),
76
+ * });
77
+ * ```
78
+ */
79
+ readonly context?: RequestContext<RequestServices>;
55
80
  }) => EndpointHandler;
56
81
  /**
57
82
  * Create a SvelteKit endpoint that serves an Effect `HttpRouter` (provided by
@@ -72,8 +97,14 @@ declare const fromApi: <Id extends string, Groups extends HttpApiGroup.Any, E>(a
72
97
  * export const fallback = Endpoint.fromRouter(RouterLive, { prefix: "/api" });
73
98
  * ```
74
99
  */
75
- 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>>(layer: Layer.Layer<A, E, R>, options?: EndpointOptions & {
100
+ 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 & {
76
101
  /** Add Effect's request logger middleware. Defaults to `false` (quiet). */readonly logger?: boolean;
102
+ /**
103
+ * Provide the request-scoped services the router's routes require, per
104
+ * request (the routes' `HttpRouter.Request<"Requires", ...>` services). The
105
+ * resolver may return a `Context` or an `Effect` producing one.
106
+ */
107
+ readonly context?: RequestContext<RequestServices>;
77
108
  }) => EndpointHandler;
78
109
  /**
79
110
  * Create a SvelteKit endpoint from a single Effect HTTP route — an effect
@@ -1,5 +1,5 @@
1
1
  import { __exportAll } from "../_virtual/_rolldown/runtime.js";
2
- import { Layer } from "effect";
2
+ import { Effect, Layer } from "effect";
3
3
  import * as FileSystem from "effect/FileSystem";
4
4
  import * as Path from "effect/Path";
5
5
  import * as Etag from "effect/unstable/http/Etag";
@@ -13,6 +13,11 @@ var Endpoint_exports = /* @__PURE__ */ __exportAll({
13
13
  fromHttpEffect: () => fromHttpEffect,
14
14
  fromRouter: () => fromRouter
15
15
  });
16
+ const resolveRequestContext = async (request, context) => {
17
+ if (context === void 0) return void 0;
18
+ const result = context(request);
19
+ return Effect.isEffect(result) ? Effect.runPromise(result) : result;
20
+ };
16
21
  const noopDispose = () => Promise.resolve();
17
22
  /** Remove `prefix` from the request's path (respecting `/` boundaries). */
18
23
  const stripPrefix = (request, prefix) => {
@@ -26,8 +31,12 @@ const stripPrefix = (request, prefix) => {
26
31
  return new Request(url, request);
27
32
  };
28
33
  /** Wrap a Web handler (and its dispose) as a SvelteKit endpoint handler. */
29
- const make = (webHandler, dispose, prefix) => {
30
- const handler = (event) => webHandler(prefix === void 0 ? event.request : stripPrefix(event.request, prefix));
34
+ const make = (webHandler, dispose, prefix, context) => {
35
+ const handler = async (event) => {
36
+ const request = prefix === void 0 ? event.request : stripPrefix(event.request, prefix);
37
+ if (context === void 0) return webHandler(request);
38
+ return webHandler(request, await resolveRequestContext(request, context));
39
+ };
31
40
  return Object.assign(handler, { dispose });
32
41
  };
33
42
  /** The platform services an `HttpApi` needs, with a no-op file system. */
@@ -53,7 +62,7 @@ const apiPlatform = Layer.mergeAll(Path.layer, Etag.layerWeak, HttpPlatform.laye
53
62
  const fromApi = (api, layer, options) => {
54
63
  const appLayer = HttpApiBuilder.layer(api, options?.openapiPath ? { openapiPath: options.openapiPath } : void 0).pipe(Layer.provide(layer), Layer.provide(apiPlatform));
55
64
  const { handler, dispose } = HttpRouter.toWebHandler(appLayer, { disableLogger: options?.logger !== true });
56
- return make(handler, dispose, options?.prefix);
65
+ return make(handler, dispose, options?.prefix, options?.context);
57
66
  };
58
67
  /**
59
68
  * Create a SvelteKit endpoint that serves an Effect `HttpRouter` (provided by
@@ -76,7 +85,7 @@ const fromApi = (api, layer, options) => {
76
85
  */
77
86
  const fromRouter = (layer, options) => {
78
87
  const { handler, dispose } = HttpRouter.toWebHandler(layer, { disableLogger: options?.logger !== true });
79
- return make(handler, dispose, options?.prefix);
88
+ return make(handler, dispose, options?.prefix, options?.context);
80
89
  };
81
90
  /**
82
91
  * Create a SvelteKit endpoint from a single Effect HTTP route — an effect
@@ -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\";\n * import { ApiLive } from \"$lib/server/handlers\"; // 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 { Layer } from \"effect\";\nimport type { Effect, Scope } from \"effect\";\nimport * as FileSystem from \"effect/FileSystem\";\nimport * as Path from \"effect/Path\";\nimport * as Etag from \"effect/unstable/http/Etag\";\nimport * as HttpEffect from \"effect/unstable/http/HttpEffect\";\nimport * as HttpPlatform from \"effect/unstable/http/HttpPlatform\";\nimport * as HttpRouter from \"effect/unstable/http/HttpRouter\";\nimport type * as HttpServerRequest from \"effect/unstable/http/HttpServerRequest\";\nimport type * as HttpServerResponse from \"effect/unstable/http/HttpServerResponse\";\nimport type * as HttpApi from \"effect/unstable/httpapi/HttpApi\";\nimport * as HttpApiBuilder from \"effect/unstable/httpapi/HttpApiBuilder\";\nimport type * as HttpApiGroup from \"effect/unstable/httpapi/HttpApiGroup\";\n\n/** A Web `fetch`-style handler, as produced by Effect's `toWebHandler` family. */\ntype WebHandler = (request: Request) => Promise<Response>;\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 = (\n webHandler: WebHandler,\n dispose: () => Promise<void>,\n prefix: string | undefined,\n): EndpointHandler => {\n const handler: RequestHandler = (event) =>\n webHandler(prefix === undefined ? event.request : stripPrefix(event.request, prefix));\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\";\n * import { ApiLive } from \"$lib/server/handlers\";\n *\n * export const fallback = Endpoint.fromApi(api, ApiLive, { prefix: \"/api\" });\n * ```\n */\nexport const fromApi = <Id extends string, Groups extends HttpApiGroup.Any, E>(\n api: HttpApi.HttpApi<Id, Groups>,\n layer: Layer.Layer<HttpApiGroup.ToService<Id, Groups>, E>,\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): 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 const { handler, dispose } = HttpRouter.toWebHandler(\n appLayer as Layer.Layer<never, E, HttpRouter.HttpRouter>,\n { disableLogger: options?.logger !== true },\n );\n return make(handler, dispose, options?.prefix);\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/unstable/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>(\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): EndpointHandler => {\n const { handler, dispose } = HttpRouter.toWebHandler(layer, {\n disableLogger: options?.logger !== true,\n });\n return make(handler as WebHandler, dispose, options?.prefix);\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/unstable/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"],"mappings":";;;;;;;;;;;;;;;AA0EA,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,WACoB;CACpB,MAAM,WAA2B,UAC/B,WAAW,WAAW,KAAA,IAAY,MAAM,UAAU,YAAY,MAAM,SAAS,MAAM,CAAC;CACtF,OAAO,OAAO,OAAO,SAAS,EAAE,QAAQ,CAAC;AAC3C;;AAGA,MAAM,cAAc,MAAM,SAAS,KAAK,OAAO,KAAK,WAAW,aAAa,KAAK,EAAE,KACjF,MAAM,aAAa,WAAW,UAAU,CAAC,CAAC,CAAC,CAC7C;;;;;;;;;;;;;;;;;;;AAoBA,MAAa,WACX,KACA,OACA,YAMoB;CACpB,MAAM,WAAW,eAAe,MAC9B,KACA,SAAS,cAAc,EAAE,aAAa,QAAQ,YAAY,IAAI,KAAA,CAChE,EAAE,KAAK,MAAM,QAAQ,KAAK,GAAG,MAAM,QAAQ,WAAW,CAAC;CAEvD,MAAM,EAAE,SAAS,YAAY,WAAW,aACtC,UACA,EAAE,eAAe,SAAS,WAAW,KAAK,CAC5C;CACA,OAAO,KAAK,SAAS,SAAS,SAAS,MAAM;AAC/C;;;;;;;;;;;;;;;;;;;;AAqBA,MAAa,cAUX,OACA,YAIoB;CACpB,MAAM,EAAE,SAAS,YAAY,WAAW,aAAa,OAAO,EAC1D,eAAe,SAAS,WAAW,KACrC,CAAC;CACD,OAAO,KAAK,SAAuB,SAAS,SAAS,MAAM;AAC7D;;;;;;;;;;;;;;;;;;;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;CAQA,OAAO,KAPS,WAAW,aACzB,MAMgB,GAAG,aAAa,SAAS,MAAM;AACnD"}
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\";\n * import { ApiLive } from \"$lib/server/handlers\"; // 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, Effect, Layer } from \"effect\";\nimport type { Scope } from \"effect\";\nimport * as FileSystem from \"effect/FileSystem\";\nimport * as Path from \"effect/Path\";\nimport * as Etag from \"effect/unstable/http/Etag\";\nimport * as HttpEffect from \"effect/unstable/http/HttpEffect\";\nimport * as HttpPlatform from \"effect/unstable/http/HttpPlatform\";\nimport * as HttpRouter from \"effect/unstable/http/HttpRouter\";\nimport type * as HttpServerRequest from \"effect/unstable/http/HttpServerRequest\";\nimport type * as HttpServerResponse from \"effect/unstable/http/HttpServerResponse\";\nimport type * as HttpApi from \"effect/unstable/httpapi/HttpApi\";\nimport * as HttpApiBuilder from \"effect/unstable/httpapi/HttpApiBuilder\";\nimport type * as HttpApiGroup from \"effect/unstable/httpapi/HttpApiGroup\";\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\";\n * import { ApiLive } from \"$lib/server/handlers\";\n *\n * export const fallback = Endpoint.fromApi(api, ApiLive, { prefix: \"/api\" });\n * ```\n */\nexport const fromApi = <\n Id extends string,\n Groups extends HttpApiGroup.Any,\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/unstable/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/unstable/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"],"mappings":";;;;;;;;;;;;;;;AAoEA,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,EAAE,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,EAAE,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;CAQA,OAAO,KAPS,WAAW,aACzB,MAMgB,GAAG,aAAa,SAAS,MAAM;AACnD"}
package/dist/server.d.ts CHANGED
@@ -1,5 +1,7 @@
1
+ import { SvelteRouteParams } from "./internal/route-params.js";
1
2
  import { SvelteKitRequestEvent, SvelteKitRuntime, runWithSvelteKitRuntime } from "./runtime.js";
2
- import { RequestRuntime, ServerRuntime, SvelteKitServices, makeServerRuntime } from "./app-runtime.js";
3
+ import { AdapterEndpointOptions, ErrorTranslator, FromLayerPerRequestOptions, RequestRuntime, RequestRuntimeProvider, RequestRuntimeRunners, RuntimeAdapter, RuntimeAdapterOptions, makeRuntimeAdapter } from "./request-runtime.js";
4
+ import { RequestRuntime as RequestRuntime$1, ServerRuntime, ServerRuntimeOptions, SvelteKitServices, makeServerRuntime } from "./app-runtime.js";
3
5
  import { sveltekitConfig, sveltekitConfigProvider } from "./config.js";
4
6
  import { Hooks_d_exports } from "./server/Hooks.js";
5
7
  import { Endpoint_d_exports } from "./server/Endpoint.js";
@@ -35,7 +37,7 @@ import { Action, Actions, RequestEvent, ServerLoad } from "@sveltejs/kit";
35
37
  * );
36
38
  * ```
37
39
  */
38
- declare function serverLoad<Params extends Record<string, string> = any, 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>;
40
+ 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>;
39
41
  /**
40
42
  * Wraps an Effect-based function to work as a SvelteKit action handler.
41
43
  * The effect has access to platform services (HttpClient, the RequestEvent, etc.)
@@ -57,7 +59,7 @@ declare function serverLoad<Params extends Record<string, string> = any, ParentD
57
59
  * };
58
60
  * ```
59
61
  */
60
- declare function action<Params extends Record<string, string> = any, 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>;
62
+ 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>;
61
63
  /**
62
64
  * Creates SvelteKit actions from an object of Effect-based action handlers.
63
65
  * Each action will have access to platform services via the SvelteKit runtime.
@@ -73,7 +75,7 @@ declare function action<Params extends Record<string, string> = any, OutputData
73
75
  * });
74
76
  * ```
75
77
  */
76
- declare function actions<Params extends Record<string, string> = any, 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>;
78
+ 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>;
77
79
  /**
78
80
  * Wraps an Effect-based function to work as a SvelteKit API endpoint handler.
79
81
  * The effect has access to platform services (HttpClient, the RequestEvent, etc.)
@@ -100,9 +102,9 @@ declare function actions<Params extends Record<string, string> = any, OutputData
100
102
  * );
101
103
  * ```
102
104
  */
103
- declare function endpoint<Params extends Record<string, string> = any, RouteId extends string | null = any>(fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<Response, unknown, SvelteKitServices>, options?: {
105
+ declare function endpoint<Params extends SvelteRouteParams = SvelteRouteParams, RouteId extends string | null = any>(fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<Response, unknown, SvelteKitServices>, options?: {
104
106
  exposeErrors?: boolean;
105
107
  }): (event: RequestEvent<Params, RouteId>) => Promise<Response>;
106
108
  //#endregion
107
- export { Endpoint_d_exports as Endpoint, Hooks_d_exports as Hooks, RemoteFunction, type RequestRuntime, type ServerRuntime, SvelteKitRequestEvent, SvelteKitRuntime, type SvelteKitServices, action, actions, endpoint, makeServerRuntime, runWithSvelteKitRuntime, serverLoad, sveltekitConfig, sveltekitConfigProvider };
109
+ export { type AdapterEndpointOptions, Endpoint_d_exports as Endpoint, type ErrorTranslator, type FromLayerPerRequestOptions, Hooks_d_exports as Hooks, RemoteFunction, RequestRuntime, type RequestRuntimeProvider, type RequestRuntimeRunners, type RuntimeAdapter, type RuntimeAdapterOptions, type RequestRuntime$1 as ScopedRequestRuntime, type ServerRuntime, type ServerRuntimeOptions, SvelteKitRequestEvent, SvelteKitRuntime, type SvelteKitServices, action, actions, endpoint, makeRuntimeAdapter, makeServerRuntime, runWithSvelteKitRuntime, serverLoad, sveltekitConfig, sveltekitConfigProvider };
108
110
  //# sourceMappingURL=server.d.ts.map
package/dist/server.js CHANGED
@@ -5,6 +5,7 @@ import { sveltekitConfig, sveltekitConfigProvider } from "./config.js";
5
5
  import { Hooks_exports } from "./server/Hooks.js";
6
6
  import { Endpoint_exports } from "./server/Endpoint.js";
7
7
  import { RemoteFunction } from "./remote.server.js";
8
+ import { RequestRuntime, makeRuntimeAdapter } from "./request-runtime.js";
8
9
  import { Cause, Exit } from "effect";
9
10
  import { isHttpError, isRedirect } from "@sveltejs/kit";
10
11
  //#region src/server.ts
@@ -126,6 +127,6 @@ function endpoint(fn, options) {
126
127
  };
127
128
  }
128
129
  //#endregion
129
- export { Endpoint_exports as Endpoint, Hooks_exports as Hooks, RemoteFunction, SvelteKitRequestEvent, SvelteKitRuntime, action, actions, endpoint, makeServerRuntime, runWithSvelteKitRuntime, serverLoad, sveltekitConfig, sveltekitConfigProvider };
130
+ export { Endpoint_exports as Endpoint, Hooks_exports as Hooks, RemoteFunction, RequestRuntime, SvelteKitRequestEvent, SvelteKitRuntime, action, actions, endpoint, makeRuntimeAdapter, makeServerRuntime, runWithSvelteKitRuntime, serverLoad, sveltekitConfig, sveltekitConfigProvider };
130
131
 
131
132
  //# sourceMappingURL=server.js.map
@@ -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 { 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/unstable/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 Record<string, string> = any,\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 Record<string, string> = 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): 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 Record<string, string> = any,\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/unstable/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 Record<string, string> = any,\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 { makeServerRuntime, type ServerRuntime, type RequestRuntime } 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.server.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"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,SAAgB,WAMd,IAGqD;CACrD,OAAOA,qBAAQ,EAAE,WAAW,EAAE;AAChC;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,OAKd,IAGqC;CACrC,OAAOA,qBAAQ,EAAE,OAAO,EAAE;AAC5B;;;;;;;;;;;;;;;;AAiBA,SAAgB,QAKd,YAIsC;CACtC,OAAOA,qBAAQ,EAAE,QAAQ,UAAU;AACrC;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,SAId,IACA,SAC6D;CAC7D,MAAM,EAAE,eAAe,UAAU,WAAW,CAAC;CAE7C,OAAO,OAAO,UAAU;EACtB,MAAM,OAAO,MAAMA,qBAAQ,EAAE,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 { 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/unstable/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/unstable/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.server.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"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,WAMd,IAGqD;CACrD,OAAOA,qBAAQ,EAAE,WAAW,EAAE;AAChC;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,OAKd,IAGqC;CACrC,OAAOA,qBAAQ,EAAE,OAAO,EAAE;AAC5B;;;;;;;;;;;;;;;;AAiBA,SAAgB,QAKd,YAIsC;CACtC,OAAOA,qBAAQ,EAAE,QAAQ,UAAU;AACrC;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,SAId,IACA,SAC6D;CAC7D,MAAM,EAAE,eAAe,UAAU,WAAW,CAAC;CAE7C,OAAO,OAAO,UAAU;EACtB,MAAM,OAAO,MAAMA,qBAAQ,EAAE,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"}
@@ -13,11 +13,49 @@ import { Transport, Transporter } from "@sveltejs/kit";
13
13
  */
14
14
  declare const transporter: <T, E>(schema: Schema.Codec<T, E>) => Transporter<T, readonly [E]>;
15
15
  /**
16
- * Assembles a SvelteKit `transport` object from a record of Effect schemas,
17
- * applying {@link transporter} to each. The keys are arbitrary labels (they
18
- * only need to be stable between server and client).
16
+ * Builds a deliberately-lossy {@link Transporter} for a non-POJO wrapper whose
17
+ * contents should *not* round-trip — the value is detected on the server and
18
+ * reconstructed fresh on the client.
19
+ *
20
+ * Unlike {@link transporter} (which needs a round-tripping {@link Schema.Codec}),
21
+ * this is for values that have no meaningful serialization but still need a
22
+ * `transport` entry so SvelteKit doesn't reject them: `encode` returns a truthy
23
+ * marker for matching values, and `decode` ignores it and produces a fresh value
24
+ * from {@link MarkerTransporterOptions.make}.
25
+ *
26
+ * ```ts
27
+ * // A non-serializable handle reconstructed as a fresh, empty instance:
28
+ * export const transport = Hooks.transport({
29
+ * Live: Hooks.markerTransporter({
30
+ * guard: (v): v is LiveHandle => v instanceof LiveHandle,
31
+ * make: () => LiveHandle.empty(),
32
+ * }),
33
+ * });
34
+ * ```
35
+ */
36
+ declare const markerTransporter: <T>(options: MarkerTransporterOptions<T>) => Transporter<T, unknown>;
37
+ /** Options for {@link markerTransporter}. */
38
+ interface MarkerTransporterOptions<T> {
39
+ /** Detect, on the server, values this transporter handles. */
40
+ readonly guard: (value: unknown) => value is T;
41
+ /** Produce a fresh value on the client (the payload is discarded). */
42
+ readonly make: () => T;
43
+ /**
44
+ * The truthy marker `encode` emits (SvelteKit treats a falsy `encode` result
45
+ * as "not this type"). Defaults to `1`.
46
+ */
47
+ readonly marker?: unknown;
48
+ }
49
+ /** An entry accepted by {@link transport}: an Effect schema or a ready Transporter. */
50
+ type TransportEntry = Schema.Codec<any, any> | Transporter<any, any>;
51
+ /**
52
+ * Assembles a SvelteKit `transport` object from a record of entries. Each entry
53
+ * is either an Effect {@link Schema.Codec} (wrapped with {@link transporter}) or
54
+ * a ready {@link Transporter} (e.g. from {@link markerTransporter}), so
55
+ * round-tripping and marker-style values can be mixed. The keys are arbitrary
56
+ * labels (they only need to be stable between server and client).
19
57
  */
20
- declare const transport: (schemas: Record<string, Schema.Codec<any, any>>) => Transport;
58
+ declare const transport: (entries: Record<string, TransportEntry>) => Transport;
21
59
  //#endregion
22
- export { transport, transporter };
60
+ export { MarkerTransporterOptions, TransportEntry, markerTransporter, transport, transporter };
23
61
  //# sourceMappingURL=transport.d.ts.map
package/dist/transport.js CHANGED
@@ -19,16 +19,46 @@ const transporter = (schema) => {
19
19
  };
20
20
  };
21
21
  /**
22
- * Assembles a SvelteKit `transport` object from a record of Effect schemas,
23
- * applying {@link transporter} to each. The keys are arbitrary labels (they
24
- * only need to be stable between server and client).
22
+ * Builds a deliberately-lossy {@link Transporter} for a non-POJO wrapper whose
23
+ * contents should *not* round-trip — the value is detected on the server and
24
+ * reconstructed fresh on the client.
25
+ *
26
+ * Unlike {@link transporter} (which needs a round-tripping {@link Schema.Codec}),
27
+ * this is for values that have no meaningful serialization but still need a
28
+ * `transport` entry so SvelteKit doesn't reject them: `encode` returns a truthy
29
+ * marker for matching values, and `decode` ignores it and produces a fresh value
30
+ * from {@link MarkerTransporterOptions.make}.
31
+ *
32
+ * ```ts
33
+ * // A non-serializable handle reconstructed as a fresh, empty instance:
34
+ * export const transport = Hooks.transport({
35
+ * Live: Hooks.markerTransporter({
36
+ * guard: (v): v is LiveHandle => v instanceof LiveHandle,
37
+ * make: () => LiveHandle.empty(),
38
+ * }),
39
+ * });
40
+ * ```
41
+ */
42
+ const markerTransporter = (options) => {
43
+ const marker = options.marker ?? 1;
44
+ return {
45
+ encode: (value) => options.guard(value) ? marker : false,
46
+ decode: () => options.make()
47
+ };
48
+ };
49
+ /**
50
+ * Assembles a SvelteKit `transport` object from a record of entries. Each entry
51
+ * is either an Effect {@link Schema.Codec} (wrapped with {@link transporter}) or
52
+ * a ready {@link Transporter} (e.g. from {@link markerTransporter}), so
53
+ * round-tripping and marker-style values can be mixed. The keys are arbitrary
54
+ * labels (they only need to be stable between server and client).
25
55
  */
26
- const transport = (schemas) => {
56
+ const transport = (entries) => {
27
57
  const result = {};
28
- for (const [name, schema] of Object.entries(schemas)) result[name] = transporter(schema);
58
+ for (const [name, entry] of Object.entries(entries)) result[name] = Schema.isSchema(entry) ? transporter(entry) : entry;
29
59
  return result;
30
60
  };
31
61
  //#endregion
32
- export { transport, transporter };
62
+ export { markerTransporter, transport, transporter };
33
63
 
34
64
  //# sourceMappingURL=transport.js.map