@thomasfosterau/effect-sveltekit 0.1.0 → 0.2.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.
Files changed (57) hide show
  1. package/dist/api-client.d.ts +32 -0
  2. package/dist/api-client.js +44 -0
  3. package/dist/api-client.js.map +1 -0
  4. package/dist/app-runtime.d.ts +8 -6
  5. package/dist/app-runtime.js +13 -11
  6. package/dist/app-runtime.js.map +1 -1
  7. package/dist/client.d.ts +3 -1
  8. package/dist/client.js +3 -1
  9. package/dist/config.d.ts +33 -2
  10. package/dist/config.js +33 -2
  11. package/dist/config.js.map +1 -1
  12. package/dist/forms.d.ts +30 -0
  13. package/dist/forms.js +66 -0
  14. package/dist/forms.js.map +1 -0
  15. package/dist/index.d.ts +3 -2
  16. package/dist/index.js +3 -2
  17. package/dist/internal/enhance.d.ts +39 -0
  18. package/dist/internal/enhance.js +24 -0
  19. package/dist/internal/enhance.js.map +1 -0
  20. package/dist/internal/remote-run.d.ts +12 -1
  21. package/dist/internal/remote-run.js +3 -5
  22. package/dist/internal/remote-run.js.map +1 -1
  23. package/dist/internal/route-params.d.ts +23 -0
  24. package/dist/internal/sse.js +37 -0
  25. package/dist/internal/sse.js.map +1 -0
  26. package/dist/kit.d.ts +130 -0
  27. package/dist/kit.js +185 -0
  28. package/dist/kit.js.map +1 -0
  29. package/dist/realtime.d.ts +105 -0
  30. package/dist/realtime.js +111 -0
  31. package/dist/realtime.js.map +1 -0
  32. package/dist/remote.server.d.ts +3 -5
  33. package/dist/remote.server.js.map +1 -1
  34. package/dist/request-parse.d.ts +89 -0
  35. package/dist/request-parse.js +121 -0
  36. package/dist/request-parse.js.map +1 -0
  37. package/dist/request-runtime.d.ts +5 -4
  38. package/dist/request-runtime.js +17 -16
  39. package/dist/request-runtime.js.map +1 -1
  40. package/dist/request-services.d.ts +120 -0
  41. package/dist/request-services.js +120 -0
  42. package/dist/request-services.js.map +1 -0
  43. package/dist/server/Endpoint.d.ts +62 -2
  44. package/dist/server/Endpoint.js +47 -2
  45. package/dist/server/Endpoint.js.map +1 -1
  46. package/dist/server.d.ts +10 -5
  47. package/dist/server.js +5 -1
  48. package/dist/server.js.map +1 -1
  49. package/dist/streamed-load.d.ts +23 -0
  50. package/dist/streamed-load.js +74 -0
  51. package/dist/streamed-load.js.map +1 -0
  52. package/dist/testing.d.ts +175 -0
  53. package/dist/testing.js +153 -0
  54. package/dist/testing.js.map +1 -0
  55. package/dist/universal.d.ts +2 -1
  56. package/dist/universal.js.map +1 -1
  57. package/package.json +6 -2
@@ -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 { 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 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: (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, R>,\n ) => ServerLoad<Params, ParentData, OutputData, RouteId>;\n readonly 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: (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, R>,\n ) => Action<Params, OutputData, RouteId>;\n readonly actions: <\n Params extends Record<string, string> = any,\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 Record<string, string> = any,\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":"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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 { 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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"}
@@ -0,0 +1,120 @@
1
+ import { SvelteKitRequestEvent } from "./runtime.js";
2
+ import { Context, Effect, Layer } from "effect";
3
+ import { Cookies, RequestEvent } from "@sveltejs/kit";
4
+
5
+ //#region src/request-services.d.ts
6
+ /**
7
+ * The Effect-native interface exposed by {@link SvelteKitCookies}. Each method
8
+ * mirrors the corresponding `Cookies` method from `@sveltejs/kit` but returns
9
+ * an `Effect` rather than a raw value or `void`.
10
+ */
11
+ interface SvelteKitCookiesInterface {
12
+ /** Get a cookie by name. Returns `undefined` when the cookie is absent. */
13
+ readonly get: (...args: Parameters<Cookies["get"]>) => Effect.Effect<ReturnType<Cookies["get"]>>;
14
+ /** Get all cookies as `{ name, value }` pairs. */
15
+ readonly getAll: (...args: Parameters<Cookies["getAll"]>) => Effect.Effect<ReturnType<Cookies["getAll"]>>;
16
+ /** Set a cookie (adds a `set-cookie` response header). */
17
+ readonly set: (...args: Parameters<Cookies["set"]>) => Effect.Effect<void>;
18
+ /** Delete a cookie by setting it to an empty value with a past expiry. */
19
+ readonly delete: (...args: Parameters<Cookies["delete"]>) => Effect.Effect<void>;
20
+ /** Serialize a cookie into a `Set-Cookie` string without adding it to the response. */
21
+ readonly serialize: (...args: Parameters<Cookies["serialize"]>) => Effect.Effect<ReturnType<Cookies["serialize"]>>;
22
+ }
23
+ declare const SvelteKitCookies_base: Context.ServiceClass<SvelteKitCookies, "@thomasfosterau/effect-sveltekit/SvelteKitCookies", SvelteKitCookiesInterface>;
24
+ /**
25
+ * An Effect service wrapping the `RequestEvent.cookies` API.
26
+ *
27
+ * Available automatically in every server handler effect (it is part of
28
+ * {@link SvelteKitServices}). Use `yield* SvelteKitCookies` to obtain the
29
+ * service; call its methods (each returns an `Effect`) to read or write
30
+ * cookies.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * const session = yield* (yield* SvelteKitCookies).get("session");
35
+ * ```
36
+ */
37
+ declare class SvelteKitCookies extends SvelteKitCookies_base {
38
+ /**
39
+ * Layer that builds the `SvelteKitCookies` service from the current
40
+ * {@link SvelteKitRequestEvent}. Included in the per-request context
41
+ * automatically; expose as a standalone layer for custom setups.
42
+ */
43
+ static readonly layer: Layer.Layer<SvelteKitCookies, never, SvelteKitRequestEvent>;
44
+ }
45
+ /**
46
+ * The Effect-native interface exposed by {@link SvelteKitResponseHeaders}.
47
+ */
48
+ interface SvelteKitResponseHeadersInterface {
49
+ /**
50
+ * Set one or more response headers. Equivalent to `event.setHeaders(headers)`.
51
+ *
52
+ * Note: You cannot set `set-cookie` headers here — use
53
+ * {@link SvelteKitCookies} instead.
54
+ */
55
+ readonly set: (headers: Record<string, string>) => Effect.Effect<void>;
56
+ }
57
+ declare const SvelteKitResponseHeaders_base: Context.ServiceClass<SvelteKitResponseHeaders, "@thomasfosterau/effect-sveltekit/SvelteKitResponseHeaders", SvelteKitResponseHeadersInterface>;
58
+ /**
59
+ * An Effect service wrapping the `RequestEvent.setHeaders` API.
60
+ *
61
+ * Available automatically in every server handler effect. Use
62
+ * `yield* SvelteKitResponseHeaders` to obtain the service, then call
63
+ * `headers.set(...)`.
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * const headers = yield* SvelteKitResponseHeaders;
68
+ * yield* headers.set({ "cache-control": "max-age=60, s-maxage=300" });
69
+ * ```
70
+ */
71
+ declare class SvelteKitResponseHeaders extends SvelteKitResponseHeaders_base {
72
+ /**
73
+ * Layer that builds the `SvelteKitResponseHeaders` service from the current
74
+ * {@link SvelteKitRequestEvent}.
75
+ */
76
+ static readonly layer: Layer.Layer<SvelteKitResponseHeaders, never, SvelteKitRequestEvent>;
77
+ }
78
+ declare const SvelteKitLocals_base: Context.ServiceClass<SvelteKitLocals, "@thomasfosterau/effect-sveltekit/SvelteKitLocals", App.Locals>;
79
+ /**
80
+ * An Effect service that exposes the mutable `App.Locals` object for the
81
+ * current request.
82
+ *
83
+ * `yield* SvelteKitLocals` returns the live `event.locals` reference — the
84
+ * same object mutations in `hooks.server.ts` (e.g. setting `event.locals.user`
85
+ * in a `handle` hook) write to. Mutations made to it inside handler effects
86
+ * propagate back to subsequent hook middleware.
87
+ *
88
+ * Available automatically in every server handler effect.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * // Read a typed local (requires App.Locals augmentation in your app):
93
+ * const { user } = yield* SvelteKitLocals;
94
+ *
95
+ * // Write a local (useful in handle hooks run via makeServerRuntime.handle):
96
+ * const locals = yield* SvelteKitLocals;
97
+ * locals.user = yield* Auth.currentUser;
98
+ * ```
99
+ */
100
+ declare class SvelteKitLocals extends SvelteKitLocals_base {
101
+ /**
102
+ * Layer that exposes `event.locals` (the mutable `App.Locals` reference)
103
+ * from the current {@link SvelteKitRequestEvent}.
104
+ */
105
+ static readonly layer: Layer.Layer<SvelteKitLocals, never, SvelteKitRequestEvent>;
106
+ }
107
+ /**
108
+ * A convenience layer that provides all three request services
109
+ * ({@link SvelteKitCookies}, {@link SvelteKitResponseHeaders},
110
+ * {@link SvelteKitLocals}) from the current {@link SvelteKitRequestEvent}.
111
+ *
112
+ * This layer is merged into the per-request context automatically by
113
+ * {@link makeServerRuntime}, the standalone wrappers, and
114
+ * {@link makeRuntimeAdapter}. Expose it here for use in custom setups that
115
+ * already have `SvelteKitRequestEvent` in scope.
116
+ */
117
+ declare const requestServicesLayer: Layer.Layer<SvelteKitCookies | SvelteKitResponseHeaders | SvelteKitLocals, never, SvelteKitRequestEvent>;
118
+ //#endregion
119
+ export { SvelteKitCookies, SvelteKitCookiesInterface, SvelteKitLocals, SvelteKitResponseHeaders, SvelteKitResponseHeadersInterface, requestServicesLayer };
120
+ //# sourceMappingURL=request-services.d.ts.map
@@ -0,0 +1,120 @@
1
+ import { SvelteKitRequestEvent } from "./runtime.js";
2
+ import { Context, Effect, Layer } from "effect";
3
+ //#region src/request-services.ts
4
+ /**
5
+ * An Effect service wrapping the `RequestEvent.cookies` API.
6
+ *
7
+ * Available automatically in every server handler effect (it is part of
8
+ * {@link SvelteKitServices}). Use `yield* SvelteKitCookies` to obtain the
9
+ * service; call its methods (each returns an `Effect`) to read or write
10
+ * cookies.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const session = yield* (yield* SvelteKitCookies).get("session");
15
+ * ```
16
+ */
17
+ var SvelteKitCookies = class SvelteKitCookies extends Context.Service()("@thomasfosterau/effect-sveltekit/SvelteKitCookies") {
18
+ /**
19
+ * Layer that builds the `SvelteKitCookies` service from the current
20
+ * {@link SvelteKitRequestEvent}. Included in the per-request context
21
+ * automatically; expose as a standalone layer for custom setups.
22
+ */
23
+ static layer = Layer.effect(SvelteKitCookies, Effect.gen(function* () {
24
+ const { cookies } = yield* SvelteKitRequestEvent;
25
+ return {
26
+ get: (...args) => Effect.sync(() => cookies.get(...args)),
27
+ getAll: (...args) => Effect.sync(() => cookies.getAll(...args)),
28
+ set: (...args) => Effect.sync(() => cookies.set(...args)),
29
+ delete: (...args) => Effect.sync(() => cookies.delete(...args)),
30
+ serialize: (...args) => Effect.sync(() => cookies.serialize(...args))
31
+ };
32
+ }));
33
+ };
34
+ /**
35
+ * An Effect service wrapping the `RequestEvent.setHeaders` API.
36
+ *
37
+ * Available automatically in every server handler effect. Use
38
+ * `yield* SvelteKitResponseHeaders` to obtain the service, then call
39
+ * `headers.set(...)`.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * const headers = yield* SvelteKitResponseHeaders;
44
+ * yield* headers.set({ "cache-control": "max-age=60, s-maxage=300" });
45
+ * ```
46
+ */
47
+ var SvelteKitResponseHeaders = class SvelteKitResponseHeaders extends Context.Service()("@thomasfosterau/effect-sveltekit/SvelteKitResponseHeaders") {
48
+ /**
49
+ * Layer that builds the `SvelteKitResponseHeaders` service from the current
50
+ * {@link SvelteKitRequestEvent}.
51
+ */
52
+ static layer = Layer.effect(SvelteKitResponseHeaders, Effect.gen(function* () {
53
+ const event = yield* SvelteKitRequestEvent;
54
+ return { set: (headers) => Effect.sync(() => event.setHeaders(headers)) };
55
+ }));
56
+ };
57
+ /**
58
+ * An Effect service that exposes the mutable `App.Locals` object for the
59
+ * current request.
60
+ *
61
+ * `yield* SvelteKitLocals` returns the live `event.locals` reference — the
62
+ * same object mutations in `hooks.server.ts` (e.g. setting `event.locals.user`
63
+ * in a `handle` hook) write to. Mutations made to it inside handler effects
64
+ * propagate back to subsequent hook middleware.
65
+ *
66
+ * Available automatically in every server handler effect.
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * // Read a typed local (requires App.Locals augmentation in your app):
71
+ * const { user } = yield* SvelteKitLocals;
72
+ *
73
+ * // Write a local (useful in handle hooks run via makeServerRuntime.handle):
74
+ * const locals = yield* SvelteKitLocals;
75
+ * locals.user = yield* Auth.currentUser;
76
+ * ```
77
+ */
78
+ var SvelteKitLocals = class SvelteKitLocals extends Context.Service()("@thomasfosterau/effect-sveltekit/SvelteKitLocals") {
79
+ /**
80
+ * Layer that exposes `event.locals` (the mutable `App.Locals` reference)
81
+ * from the current {@link SvelteKitRequestEvent}.
82
+ */
83
+ static layer = Layer.effect(SvelteKitLocals, Effect.gen(function* () {
84
+ return (yield* SvelteKitRequestEvent).locals;
85
+ }));
86
+ };
87
+ /**
88
+ * A convenience layer that provides all three request services
89
+ * ({@link SvelteKitCookies}, {@link SvelteKitResponseHeaders},
90
+ * {@link SvelteKitLocals}) from the current {@link SvelteKitRequestEvent}.
91
+ *
92
+ * This layer is merged into the per-request context automatically by
93
+ * {@link makeServerRuntime}, the standalone wrappers, and
94
+ * {@link makeRuntimeAdapter}. Expose it here for use in custom setups that
95
+ * already have `SvelteKitRequestEvent` in scope.
96
+ */
97
+ const requestServicesLayer = Layer.mergeAll(SvelteKitCookies.layer, SvelteKitResponseHeaders.layer, SvelteKitLocals.layer);
98
+ /**
99
+ * Build all three request services directly from a {@link RequestEvent},
100
+ * without going through {@link SvelteKitRequestEvent}. Used internally by
101
+ * `requestLayer` in `app-runtime.ts` to avoid an extra indirection.
102
+ */
103
+ const requestServicesFrom = (event) => {
104
+ const { cookies } = event;
105
+ return {
106
+ cookies: {
107
+ get: (...args) => Effect.sync(() => cookies.get(...args)),
108
+ getAll: (...args) => Effect.sync(() => cookies.getAll(...args)),
109
+ set: (...args) => Effect.sync(() => cookies.set(...args)),
110
+ delete: (...args) => Effect.sync(() => cookies.delete(...args)),
111
+ serialize: (...args) => Effect.sync(() => cookies.serialize(...args))
112
+ },
113
+ responseHeaders: { set: (headers) => Effect.sync(() => event.setHeaders(headers)) },
114
+ locals: event.locals
115
+ };
116
+ };
117
+ //#endregion
118
+ export { SvelteKitCookies, SvelteKitLocals, SvelteKitResponseHeaders, requestServicesFrom, requestServicesLayer };
119
+
120
+ //# sourceMappingURL=request-services.js.map
@@ -0,0 +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,EAAE,mDAAmD,EAAE;;;;;;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,EAAE,2DAA2D,EAAE;;;;;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,EAChF,kDACF,EAAE;;;;;CAKA,OAAgB,QAAoE,MAAM,OACxF,iBACA,OAAO,IAAI,aAAa;EAEtB,QAAO,OADc,uBACR;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,4 +1,4 @@
1
- import { Context, Effect, Layer, Scope } from "effect";
1
+ import { Context, Duration, Effect, Layer, Scope, Stream } 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,7 +8,7 @@ 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, RequestContext, fromApi, fromHttpEffect, fromRouter };
11
+ export { EndpointHandler, EndpointOptions, FromStreamOptions, RequestContext, fromApi, fromHttpEffect, fromRouter, fromStream };
12
12
  }
13
13
  /**
14
14
  * Resolve a request-scoped service context, if a resolver was given. The
@@ -127,6 +127,66 @@ declare const fromRouter: <A, E, R extends HttpRouter.HttpRouter | HttpRouter.Re
127
127
  declare const fromHttpEffect: <E, R = never, ProvidedE = never>(effect: Effect.Effect<HttpServerResponse.HttpServerResponse, E, R | HttpServerRequest.HttpServerRequest | Scope.Scope>, options?: EndpointOptions & {
128
128
  /** A layer providing the route's services (`R`), built once and shared. */readonly layer?: Layer.Layer<R, ProvidedE>;
129
129
  }) => EndpointHandler;
130
+ /** Options for {@link fromStream}: how to frame each element as an SSE event. */
131
+ interface FromStreamOptions<A, R = never> {
132
+ /**
133
+ * The event name (`event:` field). A string applies to every element, or a
134
+ * function derives it per element (return `undefined` for an unnamed event).
135
+ * Omit for the default unnamed `message` events.
136
+ */
137
+ readonly event?: string | ((value: A) => string | undefined);
138
+ /**
139
+ * Encode an element into the `data:` payload. Defaults to `JSON.stringify`.
140
+ * Multi-line output is split across multiple `data:` lines automatically.
141
+ */
142
+ readonly data?: (value: A) => string;
143
+ /** Derive an event id (`id:` field) the browser echoes on reconnect. */
144
+ readonly id?: (value: A) => string | undefined;
145
+ /**
146
+ * The client reconnection delay in milliseconds, sent once as a leading
147
+ * `retry:` field. The browser's `EventSource` reconnects automatically after
148
+ * this delay when the connection drops.
149
+ */
150
+ readonly retry?: number;
151
+ /**
152
+ * Send a comment heartbeat on this interval to keep the connection alive
153
+ * through proxies that time out idle streams. Accepts any `Duration.Input`
154
+ * (e.g. `"30 seconds"`).
155
+ */
156
+ readonly heartbeat?: Duration.Input;
157
+ /**
158
+ * A layer (or context) providing the stream's services `R`, applied per
159
+ * connection. Omit when the stream is self-contained (`R = never`).
160
+ */
161
+ readonly provide?: Layer.Layer<R> | Context.Context<R>;
162
+ }
163
+ /**
164
+ * Create a SvelteKit endpoint that streams an Effect `Stream` to the browser as
165
+ * [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)
166
+ * (`text/event-stream`). Consume it client-side with `Realtime.fromEventSource`
167
+ * (from `@thomasfosterau/effect-sveltekit/client`) or the native `EventSource`.
168
+ *
169
+ * Each element is framed as one SSE event (`event` / `id` optional, `data`
170
+ * defaults to JSON). A `retry` sets the reconnect delay; a `heartbeat` keeps
171
+ * idle connections alive. The response closes when the stream ends; the stream
172
+ * is interrupted (running its finalizers) when the client disconnects.
173
+ *
174
+ * @example
175
+ * ```ts
176
+ * // src/routes/events/+server.ts
177
+ * import { Endpoint } from "@thomasfosterau/effect-sveltekit/server";
178
+ * import { Schedule, Stream } from "effect";
179
+ *
180
+ * const ticks = Stream.fromSchedule(Schedule.spaced("1 second"));
181
+ *
182
+ * export const GET = Endpoint.fromStream(ticks, {
183
+ * event: "tick",
184
+ * heartbeat: "30 seconds",
185
+ * retry: 3000,
186
+ * });
187
+ * ```
188
+ */
189
+ declare const fromStream: <A, E, R = never>(stream: Stream.Stream<A, E, R>, options?: FromStreamOptions<A, R>) => RequestHandler;
130
190
  //#endregion
131
191
  export { Endpoint_d_exports };
132
192
  //# sourceMappingURL=Endpoint.d.ts.map
@@ -1,5 +1,6 @@
1
1
  import { __exportAll } from "../_virtual/_rolldown/runtime.js";
2
- import { Effect, Layer } from "effect";
2
+ import { HEARTBEAT_FRAME, SSE_HEADERS, encodeRetry, encodeSseFrame } from "../internal/sse.js";
3
+ import { Effect, Layer, Schedule, Stream } from "effect";
3
4
  import * as FileSystem from "effect/FileSystem";
4
5
  import * as Path from "effect/Path";
5
6
  import * as Etag from "effect/unstable/http/Etag";
@@ -11,7 +12,8 @@ import * as HttpApiBuilder from "effect/unstable/httpapi/HttpApiBuilder";
11
12
  var Endpoint_exports = /* @__PURE__ */ __exportAll({
12
13
  fromApi: () => fromApi,
13
14
  fromHttpEffect: () => fromHttpEffect,
14
- fromRouter: () => fromRouter
15
+ fromRouter: () => fromRouter,
16
+ fromStream: () => fromStream
15
17
  });
16
18
  const resolveRequestContext = async (request, context) => {
17
19
  if (context === void 0) return void 0;
@@ -112,6 +114,49 @@ const fromHttpEffect = (effect, options) => {
112
114
  }
113
115
  return make(HttpEffect.toWebHandler(effect), noopDispose, options?.prefix);
114
116
  };
117
+ /**
118
+ * Create a SvelteKit endpoint that streams an Effect `Stream` to the browser as
119
+ * [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)
120
+ * (`text/event-stream`). Consume it client-side with `Realtime.fromEventSource`
121
+ * (from `@thomasfosterau/effect-sveltekit/client`) or the native `EventSource`.
122
+ *
123
+ * Each element is framed as one SSE event (`event` / `id` optional, `data`
124
+ * defaults to JSON). A `retry` sets the reconnect delay; a `heartbeat` keeps
125
+ * idle connections alive. The response closes when the stream ends; the stream
126
+ * is interrupted (running its finalizers) when the client disconnects.
127
+ *
128
+ * @example
129
+ * ```ts
130
+ * // src/routes/events/+server.ts
131
+ * import { Endpoint } from "@thomasfosterau/effect-sveltekit/server";
132
+ * import { Schedule, Stream } from "effect";
133
+ *
134
+ * const ticks = Stream.fromSchedule(Schedule.spaced("1 second"));
135
+ *
136
+ * export const GET = Endpoint.fromStream(ticks, {
137
+ * event: "tick",
138
+ * heartbeat: "30 seconds",
139
+ * retry: 3000,
140
+ * });
141
+ * ```
142
+ */
143
+ const fromStream = (stream, options) => {
144
+ const encodeData = options?.data ?? ((value) => JSON.stringify(value));
145
+ const eventOf = (value) => typeof options?.event === "function" ? options.event(value) : options?.event;
146
+ return (event) => {
147
+ const events = stream.pipe(Stream.map((value) => encodeSseFrame({
148
+ data: encodeData(value),
149
+ event: eventOf(value),
150
+ id: options?.id?.(value)
151
+ })));
152
+ const withRetry = options?.retry === void 0 ? events : Stream.prepend(events, [encodeRetry(options.retry)]);
153
+ const framed = options?.heartbeat === void 0 ? withRetry : Stream.merge(withRetry, Stream.fromSchedule(Schedule.spaced(options.heartbeat)).pipe(Stream.map(() => HEARTBEAT_FRAME)), { haltStrategy: "left" });
154
+ const bytes = Stream.encodeText(framed);
155
+ const provided = options?.provide === void 0 ? bytes : Stream.provide(bytes, options.provide);
156
+ const body = Stream.toReadableStream(provided);
157
+ return new Response(body, { headers: { ...SSE_HEADERS } });
158
+ };
159
+ };
115
160
  //#endregion
116
161
  export { Endpoint_exports };
117
162
 
@@ -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 { 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"}
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, type Duration, Effect, Layer, Schedule, Stream } from \"effect\";\nimport type { Scope } from \"effect\";\nimport * as FileSystem from \"effect/FileSystem\";\nimport * as Path from \"effect/Path\";\nimport * as Etag from \"effect/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\";\nimport { encodeRetry, encodeSseFrame, HEARTBEAT_FRAME, SSE_HEADERS } from \"../internal/sse.js\";\n\n/** A Web `fetch`-style handler, as produced by Effect's `toWebHandler` family. */\ntype WebHandler = (request: Request) => Promise<Response>;\n\n/** A Web handler that also accepts a per-request service context. */\ntype WebHandlerWithContext = (\n request: Request,\n context?: Context.Context<never>,\n) => Promise<Response>;\n\n/**\n * Resolve a request-scoped service context, if a resolver was given. The\n * resolver may return a `Context` directly or an `Effect` producing one (run\n * with no requirements), so a consumer can read the request to build\n * session-aware services per dispatch.\n */\nexport type RequestContext<RequestServices> = (\n request: Request,\n) =>\n | Context.Context<RequestServices>\n | Effect.Effect<Context.Context<RequestServices>, never, never>;\n\nconst resolveRequestContext = async <RequestServices>(\n request: Request,\n context: RequestContext<RequestServices> | undefined,\n): Promise<Context.Context<RequestServices> | undefined> => {\n if (context === undefined) return undefined;\n const result = context(request);\n return Effect.isEffect(result) ? Effect.runPromise(result) : result;\n};\n\n/**\n * A SvelteKit endpoint handler — the built-in\n * [`RequestHandler`](https://svelte.dev/docs/kit/@sveltejs-kit#RequestHandler) —\n * backed by an Effect HTTP construct. Assign it to a `+server.ts` export\n * (`fallback`, `GET`, ...). `dispose()` closes the scope of the layer behind it.\n */\nexport type EndpointHandler = RequestHandler & {\n /** Close the scope of the layer backing the endpoint (runs its finalizers). */\n readonly dispose: () => Promise<void>;\n};\n\n/** Shared options for every `Endpoint.from*` helper. */\nexport interface EndpointOptions {\n /**\n * A leading path segment to strip from each request before it reaches the\n * Effect handler — the SvelteKit mount point. For an endpoint mounted at\n * `src/routes/api/[...path]/+server.ts` whose routes are defined relative to\n * the API root, pass `prefix: \"/api\"` so `/api/users` is dispatched as\n * `/users`. The match respects path boundaries (`/api` does not strip\n * `/apiary`). Defaults to no stripping.\n */\n readonly prefix?: string;\n}\n\nconst noopDispose = (): Promise<void> => Promise.resolve();\n\n/** Remove `prefix` from the request's path (respecting `/` boundaries). */\nconst stripPrefix = (request: Request, prefix: string): Request => {\n const normalized = prefix.endsWith(\"/\") ? prefix.slice(0, -1) : prefix;\n if (normalized === \"\") return request;\n\n const url = new URL(request.url);\n const path = url.pathname;\n if (path === normalized) {\n url.pathname = \"/\";\n } else if (path.startsWith(`${normalized}/`)) {\n url.pathname = path.slice(normalized.length);\n } else {\n return request;\n }\n return new Request(url, request);\n};\n\n/** Wrap a Web handler (and its dispose) as a SvelteKit endpoint handler. */\nconst make = <RequestServices = never>(\n webHandler: WebHandler | WebHandlerWithContext,\n dispose: () => Promise<void>,\n prefix: string | undefined,\n context?: RequestContext<RequestServices>,\n): EndpointHandler => {\n const handler: RequestHandler = async (event) => {\n const request = prefix === undefined ? event.request : stripPrefix(event.request, prefix);\n if (context === undefined) return (webHandler as WebHandler)(request);\n const ctx = await resolveRequestContext(request, context);\n return (webHandler as WebHandlerWithContext)(\n request,\n ctx as Context.Context<never> | undefined,\n );\n };\n return Object.assign(handler, { dispose });\n};\n\n/** The platform services an `HttpApi` needs, with a no-op file system. */\nconst apiPlatform = Layer.mergeAll(Path.layer, Etag.layerWeak, HttpPlatform.layer).pipe(\n Layer.provideMerge(FileSystem.layerNoop({})),\n);\n\n/**\n * Create a SvelteKit endpoint that serves an Effect\n * [`HttpApi`](https://effect.website).\n *\n * `layer` implements the API's groups (built with `HttpApiBuilder.group`), with\n * any application services already provided. The platform services an `HttpApi`\n * requires (`HttpPlatform`, `FileSystem`, `Path`, `Etag`) are supplied for you.\n *\n * @example\n * ```ts\n * // src/routes/api/[...path]/+server.ts\n * import { Endpoint } from \"@thomasfosterau/effect-sveltekit/server\";\n * import { api } from \"$lib/server/api\";\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\n/** Options for {@link fromStream}: how to frame each element as an SSE event. */\nexport interface FromStreamOptions<A, R = never> {\n /**\n * The event name (`event:` field). A string applies to every element, or a\n * function derives it per element (return `undefined` for an unnamed event).\n * Omit for the default unnamed `message` events.\n */\n readonly event?: string | ((value: A) => string | undefined);\n /**\n * Encode an element into the `data:` payload. Defaults to `JSON.stringify`.\n * Multi-line output is split across multiple `data:` lines automatically.\n */\n readonly data?: (value: A) => string;\n /** Derive an event id (`id:` field) the browser echoes on reconnect. */\n readonly id?: (value: A) => string | undefined;\n /**\n * The client reconnection delay in milliseconds, sent once as a leading\n * `retry:` field. The browser's `EventSource` reconnects automatically after\n * this delay when the connection drops.\n */\n readonly retry?: number;\n /**\n * Send a comment heartbeat on this interval to keep the connection alive\n * through proxies that time out idle streams. Accepts any `Duration.Input`\n * (e.g. `\"30 seconds\"`).\n */\n readonly heartbeat?: Duration.Input;\n /**\n * A layer (or context) providing the stream's services `R`, applied per\n * connection. Omit when the stream is self-contained (`R = never`).\n */\n readonly provide?: Layer.Layer<R> | Context.Context<R>;\n}\n\n/**\n * Create a SvelteKit endpoint that streams an Effect `Stream` to the browser as\n * [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)\n * (`text/event-stream`). Consume it client-side with `Realtime.fromEventSource`\n * (from `@thomasfosterau/effect-sveltekit/client`) or the native `EventSource`.\n *\n * Each element is framed as one SSE event (`event` / `id` optional, `data`\n * defaults to JSON). A `retry` sets the reconnect delay; a `heartbeat` keeps\n * idle connections alive. The response closes when the stream ends; the stream\n * is interrupted (running its finalizers) when the client disconnects.\n *\n * @example\n * ```ts\n * // src/routes/events/+server.ts\n * import { Endpoint } from \"@thomasfosterau/effect-sveltekit/server\";\n * import { Schedule, Stream } from \"effect\";\n *\n * const ticks = Stream.fromSchedule(Schedule.spaced(\"1 second\"));\n *\n * export const GET = Endpoint.fromStream(ticks, {\n * event: \"tick\",\n * heartbeat: \"30 seconds\",\n * retry: 3000,\n * });\n * ```\n */\nexport const fromStream = <A, E, R = never>(\n stream: Stream.Stream<A, E, R>,\n options?: FromStreamOptions<A, R>,\n): RequestHandler => {\n const encodeData = options?.data ?? ((value: A): string => JSON.stringify(value));\n const eventOf = (value: A): string | undefined =>\n typeof options?.event === \"function\" ? options.event(value) : options?.event;\n\n return (event) => {\n // Frame each element, then encode the resulting strings to bytes.\n const events = stream.pipe(\n Stream.map((value) =>\n encodeSseFrame({\n data: encodeData(value),\n event: eventOf(value),\n id: options?.id?.(value),\n }),\n ),\n );\n\n // A leading `retry:` field, if configured.\n const withRetry =\n options?.retry === undefined ? events : Stream.prepend(events, [encodeRetry(options.retry)]);\n\n // Merge in a heartbeat that halts as soon as the data stream ends.\n const framed =\n options?.heartbeat === undefined\n ? withRetry\n : Stream.merge(\n withRetry,\n Stream.fromSchedule(Schedule.spaced(options.heartbeat)).pipe(\n Stream.map(() => HEARTBEAT_FRAME),\n ),\n { haltStrategy: \"left\" },\n );\n\n const bytes = Stream.encodeText(framed);\n const provided =\n options?.provide === undefined ? bytes : Stream.provide(bytes, options.provide);\n\n const body = Stream.toReadableStream(provided as Stream.Stream<Uint8Array, E>);\n\n // SvelteKit forwards a streaming body Response as-is.\n void event;\n return new Response(body, { headers: { ...SSE_HEADERS } });\n };\n};\n"],"mappings":"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"}