@thomasfosterau/effect-sveltekit 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +515 -30
- package/dist/Hooks.d.ts +1 -1
- package/dist/Hooks.js.map +1 -1
- package/dist/Navigation.d.ts +17 -6
- package/dist/Navigation.js +17 -5
- package/dist/Navigation.js.map +1 -1
- package/dist/Remote.d.ts +58 -1
- package/dist/Remote.js +16 -0
- package/dist/Remote.js.map +1 -1
- package/dist/State.d.ts +4 -4
- package/dist/State.js.map +1 -1
- package/dist/adapter/handler.d.ts +15 -12
- package/dist/adapter/handler.js +5 -7
- package/dist/adapter/handler.js.map +1 -1
- package/dist/adapter/index.d.ts +1 -0
- package/dist/adapter/index.js +25 -21
- package/dist/adapter/index.js.map +1 -1
- package/dist/adapter/serve.d.ts +1 -0
- package/dist/adapter/serve.js.map +1 -1
- package/dist/api-client.js +1 -1
- package/dist/api-client.js.map +1 -1
- package/dist/app-runtime.d.ts +7 -6
- package/dist/app-runtime.js +5 -5
- package/dist/app-runtime.js.map +1 -1
- package/dist/client/Hooks.d.ts +1 -1
- package/dist/client/Hooks.js.map +1 -1
- package/dist/errors.js +3 -2
- package/dist/errors.js.map +1 -1
- package/dist/forms.d.ts +6 -6
- package/dist/forms.js.map +1 -1
- package/dist/internal/app-state.d.ts +1 -1
- package/dist/internal/app-state.js.map +1 -1
- package/dist/internal/auto.d.ts +2 -1
- package/dist/internal/auto.js.map +1 -1
- package/dist/internal/enhance.d.ts +1 -1
- package/dist/internal/enhance.js.map +1 -1
- package/dist/internal/error.js +23 -2
- package/dist/internal/error.js.map +1 -1
- package/dist/internal/remote-run.d.ts +4 -3
- package/dist/internal/remote-run.js.map +1 -1
- package/dist/kit.js +3 -3
- package/dist/kit.js.map +1 -1
- package/dist/realtime.d.ts +2 -0
- package/dist/realtime.js.map +1 -1
- package/dist/remote-functions.d.ts +18 -4
- package/dist/remote-functions.js +43 -0
- package/dist/remote-functions.js.map +1 -1
- package/dist/remote-reactive.d.ts +1 -1
- package/dist/remote-reactive.js.map +1 -1
- package/dist/response-headers.d.ts +1 -1
- package/dist/response-headers.js.map +1 -1
- package/dist/server/Endpoint.d.ts +2 -2
- package/dist/server/Endpoint.js +2 -2
- package/dist/server/Endpoint.js.map +1 -1
- package/dist/server/Hooks.d.ts +1 -1
- package/dist/server/Hooks.js.map +1 -1
- package/dist/transport.d.ts +1 -1
- package/dist/transport.js.map +1 -1
- package/dist/vite.d.ts +4 -2
- package/dist/vite.js.map +1 -1
- package/package.json +7 -4
|
@@ -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, type Duration, Effect, Layer, Schedule, Stream } from \"effect\";\nimport type { Scope } from \"effect\";\nimport * as FileSystem from \"effect/FileSystem\";\nimport * as Path from \"effect/Path\";\nimport * as Etag from \"effect/http/Etag\";\nimport * as HttpEffect from \"effect/http/HttpEffect\";\nimport * as HttpPlatform from \"effect/http/HttpPlatform\";\nimport * as HttpRouter from \"effect/http/HttpRouter\";\nimport type * as HttpServerRequest from \"effect/http/HttpServerRequest\";\nimport type * as HttpServerResponse from \"effect/http/HttpServerResponse\";\nimport type * as HttpApi from \"effect/http-api/HttpApi\";\nimport * as HttpApiBuilder from \"effect/http-api/HttpApiBuilder\";\nimport type * as HttpApiGroup from \"effect/http-api/HttpApiGroup\";\nimport { encodeRetry, encodeSseFrame, HEARTBEAT_FRAME, SSE_HEADERS } from \"../internal/sse.js\";\n\n/** A Web `fetch`-style handler, as produced by Effect's `toWebHandler` family. */\ntype WebHandler = (request: Request) => Promise<Response>;\n\n/** A Web handler that also accepts a per-request service context. */\ntype WebHandlerWithContext = (\n request: Request,\n context?: Context.Context<never>,\n) => Promise<Response>;\n\n/**\n * Resolve a request-scoped service context, if a resolver was given. The\n * resolver may return a `Context` directly or an `Effect` producing one (run\n * with no requirements), so a consumer can read the request to build\n * session-aware services per dispatch.\n */\nexport type RequestContext<RequestServices> = (\n request: Request,\n) =>\n | Context.Context<RequestServices>\n | Effect.Effect<Context.Context<RequestServices>, never, never>;\n\nconst resolveRequestContext = async <RequestServices>(\n request: Request,\n context: RequestContext<RequestServices> | undefined,\n): Promise<Context.Context<RequestServices> | undefined> => {\n if (context === undefined) return undefined;\n const result = context(request);\n return Effect.isEffect(result) ? Effect.runPromise(result) : result;\n};\n\n/**\n * A SvelteKit endpoint handler — the built-in\n * [`RequestHandler`](https://svelte.dev/docs/kit/@sveltejs-kit#RequestHandler) —\n * backed by an Effect HTTP construct. Assign it to a `+server.ts` export\n * (`fallback`, `GET`, ...). `dispose()` closes the scope of the layer behind it.\n */\nexport type EndpointHandler = RequestHandler & {\n /** Close the scope of the layer backing the endpoint (runs its finalizers). */\n readonly dispose: () => Promise<void>;\n};\n\n/** Shared options for every `Endpoint.from*` helper. */\nexport interface EndpointOptions {\n /**\n * A leading path segment to strip from each request before it reaches the\n * Effect handler — the SvelteKit mount point. For an endpoint mounted at\n * `src/routes/api/[...path]/+server.ts` whose routes are defined relative to\n * the API root, pass `prefix: \"/api\"` so `/api/users` is dispatched as\n * `/users`. The match respects path boundaries (`/api` does not strip\n * `/apiary`). Defaults to no stripping.\n */\n readonly prefix?: string;\n}\n\nconst noopDispose = (): Promise<void> => Promise.resolve();\n\n/** Remove `prefix` from the request's path (respecting `/` boundaries). */\nconst stripPrefix = (request: Request, prefix: string): Request => {\n const normalized = prefix.endsWith(\"/\") ? prefix.slice(0, -1) : prefix;\n if (normalized === \"\") return request;\n\n const url = new URL(request.url);\n const path = url.pathname;\n if (path === normalized) {\n url.pathname = \"/\";\n } else if (path.startsWith(`${normalized}/`)) {\n url.pathname = path.slice(normalized.length);\n } else {\n return request;\n }\n return new Request(url, request);\n};\n\n/** Wrap a Web handler (and its dispose) as a SvelteKit endpoint handler. */\nconst make = <RequestServices = never>(\n webHandler: WebHandler | WebHandlerWithContext,\n dispose: () => Promise<void>,\n prefix: string | undefined,\n context?: RequestContext<RequestServices>,\n): EndpointHandler => {\n const handler: RequestHandler = async (event) => {\n const request = prefix === undefined ? event.request : stripPrefix(event.request, prefix);\n if (context === undefined) return (webHandler as WebHandler)(request);\n const ctx = await resolveRequestContext(request, context);\n return (webHandler as WebHandlerWithContext)(\n request,\n ctx as Context.Context<never> | undefined,\n );\n };\n return Object.assign(handler, { dispose });\n};\n\n/** The platform services an `HttpApi` needs, with a no-op file system. */\nconst apiPlatform = Layer.mergeAll(Path.layer, Etag.layerWeak, HttpPlatform.layer).pipe(\n Layer.provideMerge(FileSystem.layerNoop({})),\n);\n\n/**\n * Create a SvelteKit endpoint that serves an Effect\n * [`HttpApi`](https://effect.website).\n *\n * `layer` implements the API's groups (built with `HttpApiBuilder.group`), with\n * any application services already provided. The platform services an `HttpApi`\n * requires (`HttpPlatform`, `FileSystem`, `Path`, `Etag`) are supplied for you.\n *\n * @example\n * ```ts\n * // src/routes/api/[...path]/+server.ts\n * import { Endpoint } from \"@thomasfosterau/effect-sveltekit/server\";\n * import { api } from \"$lib/server/api\";\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.Constraint,\n E,\n LayerRequires = never,\n RequestServices = never,\n>(\n api: HttpApi.HttpApi<Id, Groups>,\n layer: Layer.Layer<HttpApiGroup.ToService<Id, Groups>, E, LayerRequires>,\n options?: EndpointOptions & {\n /** Serve the OpenAPI spec for the API at this path (e.g. `\"/openapi.json\"`). */\n readonly openapiPath?: `/${string}`;\n /** Add Effect's request logger middleware. Defaults to `false` (quiet). */\n readonly logger?: boolean;\n /**\n * Provide the request-scoped services the API groups require, per request.\n * The build-once platform layer (`HttpPlatform` / `FileSystem` / `Path` /\n * `Etag`) stays shared; only this slice is resolved and merged for each\n * dispatch — so a session-aware, request-pooled API can run on Workers\n * without rebuilding its platform layer.\n *\n * The resolver may return a `Context` or an `Effect` producing one. Leave\n * the matching services unprovided in `layer` and supply them here:\n *\n * ```ts\n * Endpoint.fromApi(api, ApiLive, {\n * prefix: \"/api\",\n * context: (request) => Context.make(CurrentUser, userFor(request)),\n * });\n * ```\n */\n readonly context?: RequestContext<RequestServices>;\n },\n): EndpointHandler => {\n const appLayer = HttpApiBuilder.layer(\n api,\n options?.openapiPath ? { openapiPath: options.openapiPath } : undefined,\n ).pipe(Layer.provide(layer), Layer.provide(apiPlatform));\n\n // The request-scoped services are supplied per dispatch via the web handler's\n // context argument, so they are hidden from the build-once layer here.\n const { handler, dispose } = HttpRouter.toWebHandler(\n appLayer as unknown as Layer.Layer<never, E, HttpRouter.HttpRouter>,\n { disableLogger: options?.logger !== true },\n );\n return make<RequestServices>(\n handler as WebHandlerWithContext,\n dispose,\n options?.prefix,\n options?.context,\n );\n};\n\n/**\n * Create a SvelteKit endpoint that serves an Effect `HttpRouter` (provided by\n * `layer`). The router's routes must be self-contained (any services they need\n * are provided within `layer`).\n *\n * @example\n * ```ts\n * // src/routes/api/[...path]/+server.ts\n * import { Endpoint } from \"@thomasfosterau/effect-sveltekit/server\";\n * import { Effect } from \"effect\";\n * import { HttpRouter, HttpServerResponse } from \"effect/http\";\n *\n * const RouterLive = HttpRouter.use((router) =>\n * router.add(\"GET\", \"/hello\", Effect.succeed(HttpServerResponse.text(\"hi\"))),\n * );\n *\n * export const fallback = Endpoint.fromRouter(RouterLive, { prefix: \"/api\" });\n * ```\n */\nexport const fromRouter = <\n A,\n E,\n R extends\n | HttpRouter.HttpRouter\n | HttpRouter.Request<\"Requires\", any>\n | HttpRouter.Request<\"GlobalRequires\", any>\n | HttpRouter.Request<\"Error\", any>\n | HttpRouter.Request<\"GlobalError\", any>,\n RequestServices = never,\n>(\n layer: Layer.Layer<A, E, R>,\n options?: EndpointOptions & {\n /** Add Effect's request logger middleware. Defaults to `false` (quiet). */\n readonly logger?: boolean;\n /**\n * Provide the request-scoped services the router's routes require, per\n * request (the routes' `HttpRouter.Request<\"Requires\", ...>` services). The\n * resolver may return a `Context` or an `Effect` producing one.\n */\n readonly context?: RequestContext<RequestServices>;\n },\n): EndpointHandler => {\n const { handler, dispose } = HttpRouter.toWebHandler(layer, {\n disableLogger: options?.logger !== true,\n });\n return make<RequestServices>(\n handler as WebHandlerWithContext,\n dispose,\n options?.prefix,\n options?.context,\n );\n};\n\n/**\n * Create a SvelteKit endpoint from a single Effect HTTP route — an effect\n * producing an `HttpServerResponse` for the current `HttpServerRequest`. Pass\n * `options.layer` to provide the route's services; the layer is built once and\n * shared.\n *\n * @example\n * ```ts\n * // src/routes/health/+server.ts\n * import { Endpoint } from \"@thomasfosterau/effect-sveltekit/server\";\n * import { Effect } from \"effect\";\n * import { HttpServerResponse } from \"effect/http\";\n *\n * export const GET = Endpoint.fromHttpEffect(\n * Effect.succeed(HttpServerResponse.text(\"ok\")),\n * );\n * ```\n */\nexport const fromHttpEffect = <E, R = never, ProvidedE = never>(\n effect: Effect.Effect<\n HttpServerResponse.HttpServerResponse,\n E,\n R | HttpServerRequest.HttpServerRequest | Scope.Scope\n >,\n options?: EndpointOptions & {\n /** A layer providing the route's services (`R`), built once and shared. */\n readonly layer?: Layer.Layer<R, ProvidedE>;\n },\n): EndpointHandler => {\n if (options?.layer) {\n const { handler, dispose } = HttpEffect.toWebHandlerLayer(effect, options.layer);\n return make(handler as WebHandler, dispose, options.prefix);\n }\n const handler = HttpEffect.toWebHandler(\n effect as Effect.Effect<\n HttpServerResponse.HttpServerResponse,\n E,\n HttpServerRequest.HttpServerRequest | Scope.Scope\n >,\n );\n return make(handler, noopDispose, options?.prefix);\n};\n\n/** Options for {@link fromStream}: how to frame each element as an SSE event. */\nexport interface FromStreamOptions<A, R = never> {\n /**\n * The event name (`event:` field). A string applies to every element, or a\n * function derives it per element (return `undefined` for an unnamed event).\n * Omit for the default unnamed `message` events.\n */\n readonly event?: string | ((value: A) => string | undefined);\n /**\n * Encode an element into the `data:` payload. Defaults to `JSON.stringify`.\n * Multi-line output is split across multiple `data:` lines automatically.\n */\n readonly data?: (value: A) => string;\n /** Derive an event id (`id:` field) the browser echoes on reconnect. */\n readonly id?: (value: A) => string | undefined;\n /**\n * The client reconnection delay in milliseconds, sent once as a leading\n * `retry:` field. The browser's `EventSource` reconnects automatically after\n * this delay when the connection drops.\n */\n readonly retry?: number;\n /**\n * Send a comment heartbeat on this interval to keep the connection alive\n * through proxies that time out idle streams. Accepts any `Duration.Input`\n * (e.g. `\"30 seconds\"`).\n */\n readonly heartbeat?: Duration.Input;\n /**\n * A layer (or context) providing the stream's services `R`, applied per\n * connection. Omit when the stream is self-contained (`R = never`).\n */\n readonly provide?: Layer.Layer<R> | Context.Context<R>;\n}\n\n/**\n * Create a SvelteKit endpoint that streams an Effect `Stream` to the browser as\n * [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)\n * (`text/event-stream`). Consume it client-side with `Realtime.fromEventSource`\n * (from `@thomasfosterau/effect-sveltekit/client`) or the native `EventSource`.\n *\n * Each element is framed as one SSE event (`event` / `id` optional, `data`\n * defaults to JSON). A `retry` sets the reconnect delay; a `heartbeat` keeps\n * idle connections alive. The response closes when the stream ends; the stream\n * is interrupted (running its finalizers) when the client disconnects.\n *\n * @example\n * ```ts\n * // src/routes/events/+server.ts\n * import { Endpoint } from \"@thomasfosterau/effect-sveltekit/server\";\n * import { Schedule, Stream } from \"effect\";\n *\n * const ticks = Stream.fromSchedule(Schedule.spaced(\"1 second\"));\n *\n * export const GET = Endpoint.fromStream(ticks, {\n * event: \"tick\",\n * heartbeat: \"30 seconds\",\n * retry: 3000,\n * });\n * ```\n */\nexport const fromStream = <A, E, R = never>(\n stream: Stream.Stream<A, E, R>,\n options?: FromStreamOptions<A, R>,\n): RequestHandler => {\n const encodeData = options?.data ?? ((value: A): string => JSON.stringify(value));\n const eventOf = (value: A): string | undefined =>\n typeof options?.event === \"function\" ? options.event(value) : options?.event;\n\n return (event) => {\n // Frame each element, then encode the resulting strings to bytes.\n const events = stream.pipe(\n Stream.map((value) =>\n encodeSseFrame({\n data: encodeData(value),\n event: eventOf(value),\n id: options?.id?.(value),\n }),\n ),\n );\n\n // A leading `retry:` field, if configured.\n const withRetry =\n options?.retry === undefined ? events : Stream.prepend(events, [encodeRetry(options.retry)]);\n\n // Merge in a heartbeat that halts as soon as the data stream ends.\n const framed =\n options?.heartbeat === undefined\n ? withRetry\n : Stream.merge(\n withRetry,\n Stream.fromSchedule(Schedule.spaced(options.heartbeat)).pipe(\n Stream.map(() => HEARTBEAT_FRAME),\n ),\n { haltStrategy: \"left\" },\n );\n\n const bytes = Stream.encodeText(framed);\n const provided =\n options?.provide === undefined ? bytes : Stream.provide(bytes, options.provide);\n\n const body = Stream.toReadableStream(provided as Stream.Stream<Uint8Array, E>);\n\n // SvelteKit forwards a streaming body Response as-is.\n void event;\n return new Response(body, { headers: { ...SSE_HEADERS } });\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Endpoint.js","names":[],"sources":["../../src/server/Endpoint.ts"],"sourcesContent":["/**\n * Create SvelteKit server endpoints (`+server.ts`) backed by Effect — the\n * `Endpoint` namespace of `@thomasfosterau/effect-sveltekit/server`.\n *\n * A SvelteKit endpoint is a `RequestHandler` (`(event) => Response`). Effect can\n * turn an [`HttpApi`](https://effect.website), an `HttpRouter`, or a single\n * route effect into a Web `fetch` handler; these helpers wrap one as a SvelteKit\n * endpoint handler so a route can be served by Effect. Mount it on a catch-all\n * route (`[...path]`) and assign it to `fallback` so every method and sub-path\n * reaches it:\n *\n * @example\n * ```ts\n * // src/routes/api/[...path]/+server.ts\n * import { Endpoint } from \"@thomasfosterau/effect-sveltekit/server\";\n * import { api } from \"#lib/server/api.ts\";\n * import { ApiLive } from \"#lib/server/handlers.ts\"; // HttpApiBuilder.group(...) + your services\n *\n * // `prefix` strips the SvelteKit mount path so the API's own paths\n * // (e.g. \"/users\") match.\n * export const fallback = Endpoint.fromApi(api, ApiLive, { prefix: \"/api\" });\n * ```\n *\n * The layer backing an endpoint is built once, lazily, on the first request and\n * shared across subsequent requests (like `makeServerRuntime`). The returned\n * handler carries a `dispose()` that closes the layer's scope — call it on\n * shutdown if the endpoint holds resources.\n *\n * The Effect handlers receive an Effect-native `HttpServerRequest` (built from\n * `event.request`), not the SvelteKit `RequestEvent`.\n */\n\nimport type { RequestHandler } from \"@sveltejs/kit\";\nimport { Context, type Duration, Effect, Layer, Schedule, Stream } from \"effect\";\nimport type { Scope } from \"effect\";\nimport * as FileSystem from \"effect/FileSystem\";\nimport * as Path from \"effect/Path\";\nimport * as Etag from \"effect/http/Etag\";\nimport * as HttpEffect from \"effect/http/HttpEffect\";\nimport * as HttpPlatform from \"effect/http/HttpPlatform\";\nimport * as HttpRouter from \"effect/http/HttpRouter\";\nimport type * as HttpServerRequest from \"effect/http/HttpServerRequest\";\nimport type * as HttpServerResponse from \"effect/http/HttpServerResponse\";\nimport type * as HttpApi from \"effect/http-api/HttpApi\";\nimport * as HttpApiBuilder from \"effect/http-api/HttpApiBuilder\";\nimport type * as HttpApiGroup from \"effect/http-api/HttpApiGroup\";\nimport { encodeRetry, encodeSseFrame, HEARTBEAT_FRAME, SSE_HEADERS } from \"../internal/sse.js\";\n\n/** A Web `fetch`-style handler, as produced by Effect's `toWebHandler` family. */\ntype WebHandler = (request: Request) => Promise<Response>;\n\n/** A Web handler that also accepts a per-request service context. */\ntype WebHandlerWithContext = (\n request: Request,\n context?: Context.Context<never>,\n) => Promise<Response>;\n\n/**\n * Resolve a request-scoped service context, if a resolver was given. The\n * resolver may return a `Context` directly or an `Effect` producing one (run\n * with no requirements), so a consumer can read the request to build\n * session-aware services per dispatch.\n */\nexport type RequestContext<RequestServices> = (\n request: Request,\n) =>\n | Context.Context<RequestServices>\n | Effect.Effect<Context.Context<RequestServices>, never, never>;\n\nconst resolveRequestContext = async <RequestServices>(\n request: Request,\n context: RequestContext<RequestServices> | undefined,\n): Promise<Context.Context<RequestServices> | undefined> => {\n if (context === undefined) return undefined;\n const result = context(request);\n return Effect.isEffect(result) ? Effect.runPromise(result) : result;\n};\n\n/**\n * A SvelteKit endpoint handler — the built-in\n * [`RequestHandler`](https://svelte.dev/docs/kit/@sveltejs-kit#RequestHandler) —\n * backed by an Effect HTTP construct. Assign it to a `+server.ts` export\n * (`fallback`, `GET`, ...). `dispose()` closes the scope of the layer behind it.\n */\nexport type EndpointHandler = RequestHandler & {\n /** Close the scope of the layer backing the endpoint (runs its finalizers). */\n readonly dispose: () => Promise<void>;\n};\n\n/** Shared options for every `Endpoint.from*` helper. */\nexport interface EndpointOptions {\n /**\n * A leading path segment to strip from each request before it reaches the\n * Effect handler — the SvelteKit mount point. For an endpoint mounted at\n * `src/routes/api/[...path]/+server.ts` whose routes are defined relative to\n * the API root, pass `prefix: \"/api\"` so `/api/users` is dispatched as\n * `/users`. The match respects path boundaries (`/api` does not strip\n * `/apiary`). Defaults to no stripping.\n */\n readonly prefix?: string;\n}\n\nconst noopDispose = (): Promise<void> => Promise.resolve();\n\n/** Remove `prefix` from the request's path (respecting `/` boundaries). */\nconst stripPrefix = (request: Request, prefix: string): Request => {\n const normalized = prefix.endsWith(\"/\") ? prefix.slice(0, -1) : prefix;\n if (normalized === \"\") return request;\n\n const url = new URL(request.url);\n const path = url.pathname;\n if (path === normalized) {\n url.pathname = \"/\";\n } else if (path.startsWith(`${normalized}/`)) {\n url.pathname = path.slice(normalized.length);\n } else {\n return request;\n }\n return new Request(url, request);\n};\n\n/** Wrap a Web handler (and its dispose) as a SvelteKit endpoint handler. */\nconst make = <RequestServices = never>(\n webHandler: WebHandler | WebHandlerWithContext,\n dispose: () => Promise<void>,\n prefix: string | undefined,\n context?: RequestContext<RequestServices>,\n): EndpointHandler => {\n const handler: RequestHandler = async (event) => {\n const request = prefix === undefined ? event.request : stripPrefix(event.request, prefix);\n if (context === undefined) return (webHandler as WebHandler)(request);\n const ctx = await resolveRequestContext(request, context);\n return (webHandler as WebHandlerWithContext)(\n request,\n ctx as Context.Context<never> | undefined,\n );\n };\n return Object.assign(handler, { dispose });\n};\n\n/** The platform services an `HttpApi` needs, with a no-op file system. */\nconst apiPlatform = Layer.mergeAll(Path.layer, Etag.layerWeak, HttpPlatform.layer).pipe(\n Layer.provideMerge(FileSystem.layerNoop({})),\n);\n\n/**\n * Create a SvelteKit endpoint that serves an Effect\n * [`HttpApi`](https://effect.website).\n *\n * `layer` implements the API's groups (built with `HttpApiBuilder.group`), with\n * any application services already provided. The platform services an `HttpApi`\n * requires (`HttpPlatform`, `FileSystem`, `Path`, `Etag`) are supplied for you.\n *\n * @example\n * ```ts\n * // src/routes/api/[...path]/+server.ts\n * import { Endpoint } from \"@thomasfosterau/effect-sveltekit/server\";\n * import { api } from \"#lib/server/api.ts\";\n * import { ApiLive } from \"#lib/server/handlers.ts\";\n *\n * export const fallback = Endpoint.fromApi(api, ApiLive, { prefix: \"/api\" });\n * ```\n */\nexport const fromApi = <\n Id extends string,\n Groups extends HttpApiGroup.Constraint,\n E,\n LayerRequires = never,\n RequestServices = never,\n>(\n api: HttpApi.HttpApi<Id, Groups>,\n layer: Layer.Layer<HttpApiGroup.ToService<Id, Groups>, E, LayerRequires>,\n options?: EndpointOptions & {\n /** Serve the OpenAPI spec for the API at this path (e.g. `\"/openapi.json\"`). */\n readonly openapiPath?: `/${string}`;\n /** Add Effect's request logger middleware. Defaults to `false` (quiet). */\n readonly logger?: boolean;\n /**\n * Provide the request-scoped services the API groups require, per request.\n * The build-once platform layer (`HttpPlatform` / `FileSystem` / `Path` /\n * `Etag`) stays shared; only this slice is resolved and merged for each\n * dispatch — so a session-aware, request-pooled API can run on Workers\n * without rebuilding its platform layer.\n *\n * The resolver may return a `Context` or an `Effect` producing one. Leave\n * the matching services unprovided in `layer` and supply them here:\n *\n * ```ts\n * Endpoint.fromApi(api, ApiLive, {\n * prefix: \"/api\",\n * context: (request) => Context.make(CurrentUser, userFor(request)),\n * });\n * ```\n */\n readonly context?: RequestContext<RequestServices>;\n },\n): EndpointHandler => {\n const appLayer = HttpApiBuilder.layer(\n api,\n options?.openapiPath ? { openapiPath: options.openapiPath } : undefined,\n ).pipe(Layer.provide(layer), Layer.provide(apiPlatform));\n\n // The request-scoped services are supplied per dispatch via the web handler's\n // context argument, so they are hidden from the build-once layer here.\n const { handler, dispose } = HttpRouter.toWebHandler(\n appLayer as unknown as Layer.Layer<never, E, HttpRouter.HttpRouter>,\n { disableLogger: options?.logger !== true },\n );\n return make<RequestServices>(\n handler as WebHandlerWithContext,\n dispose,\n options?.prefix,\n options?.context,\n );\n};\n\n/**\n * Create a SvelteKit endpoint that serves an Effect `HttpRouter` (provided by\n * `layer`). The router's routes must be self-contained (any services they need\n * are provided within `layer`).\n *\n * @example\n * ```ts\n * // src/routes/api/[...path]/+server.ts\n * import { Endpoint } from \"@thomasfosterau/effect-sveltekit/server\";\n * import { Effect } from \"effect\";\n * import { HttpRouter, HttpServerResponse } from \"effect/http\";\n *\n * const RouterLive = HttpRouter.use((router) =>\n * router.add(\"GET\", \"/hello\", Effect.succeed(HttpServerResponse.text(\"hi\"))),\n * );\n *\n * export const fallback = Endpoint.fromRouter(RouterLive, { prefix: \"/api\" });\n * ```\n */\nexport const fromRouter = <\n A,\n E,\n R extends\n | HttpRouter.HttpRouter\n | HttpRouter.Request<\"Requires\", any>\n | HttpRouter.Request<\"GlobalRequires\", any>\n | HttpRouter.Request<\"Error\", any>\n | HttpRouter.Request<\"GlobalError\", any>,\n RequestServices = never,\n>(\n layer: Layer.Layer<A, E, R>,\n options?: EndpointOptions & {\n /** Add Effect's request logger middleware. Defaults to `false` (quiet). */\n readonly logger?: boolean;\n /**\n * Provide the request-scoped services the router's routes require, per\n * request (the routes' `HttpRouter.Request<\"Requires\", ...>` services). The\n * resolver may return a `Context` or an `Effect` producing one.\n */\n readonly context?: RequestContext<RequestServices>;\n },\n): EndpointHandler => {\n const { handler, dispose } = HttpRouter.toWebHandler(layer, {\n disableLogger: options?.logger !== true,\n });\n return make<RequestServices>(\n handler as WebHandlerWithContext,\n dispose,\n options?.prefix,\n options?.context,\n );\n};\n\n/**\n * Create a SvelteKit endpoint from a single Effect HTTP route — an effect\n * producing an `HttpServerResponse` for the current `HttpServerRequest`. Pass\n * `options.layer` to provide the route's services; the layer is built once and\n * shared.\n *\n * @example\n * ```ts\n * // src/routes/health/+server.ts\n * import { Endpoint } from \"@thomasfosterau/effect-sveltekit/server\";\n * import { Effect } from \"effect\";\n * import { HttpServerResponse } from \"effect/http\";\n *\n * export const GET = Endpoint.fromHttpEffect(\n * Effect.succeed(HttpServerResponse.text(\"ok\")),\n * );\n * ```\n */\nexport const fromHttpEffect = <E, R = never, ProvidedE = never>(\n effect: Effect.Effect<\n HttpServerResponse.HttpServerResponse,\n E,\n R | HttpServerRequest.HttpServerRequest | Scope.Scope\n >,\n options?: EndpointOptions & {\n /** A layer providing the route's services (`R`), built once and shared. */\n readonly layer?: Layer.Layer<R, ProvidedE>;\n },\n): EndpointHandler => {\n if (options?.layer) {\n const { handler, dispose } = HttpEffect.toWebHandlerLayer(effect, options.layer);\n return make(handler as WebHandler, dispose, options.prefix);\n }\n const handler = HttpEffect.toWebHandler(\n effect as Effect.Effect<\n HttpServerResponse.HttpServerResponse,\n E,\n HttpServerRequest.HttpServerRequest | Scope.Scope\n >,\n );\n return make(handler, noopDispose, options?.prefix);\n};\n\n/** Options for {@link fromStream}: how to frame each element as an SSE event. */\nexport interface FromStreamOptions<A, R = never> {\n /**\n * The event name (`event:` field). A string applies to every element, or a\n * function derives it per element (return `undefined` for an unnamed event).\n * Omit for the default unnamed `message` events.\n */\n readonly event?: string | ((value: A) => string | undefined);\n /**\n * Encode an element into the `data:` payload. Defaults to `JSON.stringify`.\n * Multi-line output is split across multiple `data:` lines automatically.\n */\n readonly data?: (value: A) => string;\n /** Derive an event id (`id:` field) the browser echoes on reconnect. */\n readonly id?: (value: A) => string | undefined;\n /**\n * The client reconnection delay in milliseconds, sent once as a leading\n * `retry:` field. The browser's `EventSource` reconnects automatically after\n * this delay when the connection drops.\n */\n readonly retry?: number;\n /**\n * Send a comment heartbeat on this interval to keep the connection alive\n * through proxies that time out idle streams. Accepts any `Duration.Input`\n * (e.g. `\"30 seconds\"`).\n */\n readonly heartbeat?: Duration.Input;\n /**\n * A layer (or context) providing the stream's services `R`, applied per\n * connection. Omit when the stream is self-contained (`R = never`).\n */\n readonly provide?: Layer.Layer<R> | Context.Context<R>;\n}\n\n/**\n * Create a SvelteKit endpoint that streams an Effect `Stream` to the browser as\n * [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)\n * (`text/event-stream`). Consume it client-side with `Realtime.fromEventSource`\n * (from `@thomasfosterau/effect-sveltekit/client`) or the native `EventSource`.\n *\n * Each element is framed as one SSE event (`event` / `id` optional, `data`\n * defaults to JSON). A `retry` sets the reconnect delay; a `heartbeat` keeps\n * idle connections alive. The response closes when the stream ends; the stream\n * is interrupted (running its finalizers) when the client disconnects.\n *\n * @example\n * ```ts\n * // src/routes/events/+server.ts\n * import { Endpoint } from \"@thomasfosterau/effect-sveltekit/server\";\n * import { Schedule, Stream } from \"effect\";\n *\n * const ticks = Stream.fromSchedule(Schedule.spaced(\"1 second\"));\n *\n * export const GET = Endpoint.fromStream(ticks, {\n * event: \"tick\",\n * heartbeat: \"30 seconds\",\n * retry: 3000,\n * });\n * ```\n */\nexport const fromStream = <A, E, R = never>(\n stream: Stream.Stream<A, E, R>,\n options?: FromStreamOptions<A, R>,\n): RequestHandler => {\n const encodeData = options?.data ?? ((value: A): string => JSON.stringify(value));\n const eventOf = (value: A): string | undefined =>\n typeof options?.event === \"function\" ? options.event(value) : options?.event;\n\n return (event) => {\n // Frame each element, then encode the resulting strings to bytes.\n const events = stream.pipe(\n Stream.map((value) =>\n encodeSseFrame({\n data: encodeData(value),\n event: eventOf(value),\n id: options?.id?.(value),\n }),\n ),\n );\n\n // A leading `retry:` field, if configured.\n const withRetry =\n options?.retry === undefined ? events : Stream.prepend(events, [encodeRetry(options.retry)]);\n\n // Merge in a heartbeat that halts as soon as the data stream ends.\n const framed =\n options?.heartbeat === undefined\n ? withRetry\n : Stream.merge(\n withRetry,\n Stream.fromSchedule(Schedule.spaced(options.heartbeat)).pipe(\n Stream.map(() => HEARTBEAT_FRAME),\n ),\n { haltStrategy: \"left\" },\n );\n\n const bytes = Stream.encodeText(framed);\n const provided =\n options?.provide === undefined ? bytes : Stream.provide(bytes, options.provide);\n\n const body = Stream.toReadableStream(provided as Stream.Stream<Uint8Array, E>);\n\n // SvelteKit forwards a streaming body Response as-is.\n void event;\n return new Response(body, { headers: { ...SSE_HEADERS } });\n };\n};\n"],"mappings":"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"}
|
package/dist/server/Hooks.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SvelteKitServices } from "../app-runtime.js";
|
|
2
2
|
import { Effect } from "effect";
|
|
3
|
-
import { Handle, HandleFetch, HandleServerError, ServerInit } from "@sveltejs/kit";
|
|
3
|
+
import { Handle, HandleFetch, HandleServerError, ServerInit } from "@sveltejs/kit/hooks";
|
|
4
4
|
declare namespace Hooks_d_exports {
|
|
5
5
|
export { handle, handleError, handleFetch, init };
|
|
6
6
|
}
|
package/dist/server/Hooks.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Hooks.js","names":[],"sources":["../../src/server/Hooks.ts"],"sourcesContent":["/**\n * Effect wrappers for SvelteKit's server-side hooks (`src/hooks.server.ts`),\n * exposed as the `Hooks` namespace of `@thomasfosterau/effect-sveltekit/server`.\n *\n * Each helper turns an Effect (or a function returning one) into the\n * function-shaped export SvelteKit expects. Effects run on the SvelteKit server\n * runtime, so they have access to platform services (`HttpClient`, the\n * `RequestEvent`, ...). To additionally provide your application's services,\n * use the matching methods on an app-wide runtime from `makeServerRuntime`.\n *\n * @example\n * ```ts\n * // src/hooks.server.ts\n * import { Hooks } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n *\n * export const handle = Hooks.handle(({ event, resolve }) =>\n * Effect.gen(function* () {\n * const response = yield* Effect.promise(() => resolve(event));\n * yield* Effect.logInfo(`${event.url.pathname} -> ${response.status}`);\n * return response;\n * })\n * );\n * ```\n */\n\nimport type { Handle, HandleFetch, HandleServerError, ServerInit } from \"@sveltejs/kit\";\nimport { Effect } from \"effect\";\nimport type { SvelteKitServices } from \"../app-runtime.js\";\nimport { defaultServerRuntime } from \"../internal/default-runtime.js\";\n\n/**\n * Wraps an Effect-based function as a SvelteKit\n * [`handle`](https://svelte.dev/docs/kit/hooks#Server-hooks-handle) hook, which\n * runs on every request and controls how the response is produced.\n *\n * SvelteKit control flow (`redirect()`, `error()`) raised inside the effect is\n * passed through to SvelteKit.\n *\n * @example\n * ```ts\n * // src/hooks.server.ts\n * import { Hooks } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n *\n * export const handle = Hooks.handle(({ event, resolve }) =>\n * Effect.gen(function* () {\n * // custom logic before resolve\n * const response = yield* Effect.promise(() => resolve(event));\n * // custom logic after resolve\n * return response;\n * })\n * );\n * ```\n */\nexport function handle(\n fn: (args: Parameters<Handle>[0]) => Effect.Effect<Response, unknown, SvelteKitServices>,\n): Handle {\n return defaultServerRuntime().handle(fn);\n}\n\n/**\n * Wraps an Effect-based function as a SvelteKit\n * [`handleFetch`](https://svelte.dev/docs/kit/hooks#Server-hooks-handleFetch)\n * hook, which intercepts `event.fetch` calls made on the server (in `load`,\n * actions, endpoints, ...). The effect produces the `Response` to use.\n *\n * @example\n * ```ts\n * // src/hooks.server.ts\n * import { Hooks } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n *\n * export const handleFetch = Hooks.handleFetch(({ request, fetch }) =>\n * Effect.gen(function* () {\n * // Redirect internal API calls to the upstream service.\n * const url = request.url.replace('https://api.internal', 'http://localhost:9999');\n * return yield* Effect.promise(() => fetch(new Request(url, request)));\n * })\n * );\n * ```\n */\nexport function handleFetch(\n fn: (args: Parameters<HandleFetch>[0]) => Effect.Effect<Response, unknown, SvelteKitServices>,\n): HandleFetch {\n return defaultServerRuntime().handleFetch(fn);\n}\n\n/**\n * Wraps an Effect-based function as the SvelteKit server\n * [`handleError`](https://svelte.dev/docs/kit/hooks#Shared-hooks-handleError)\n * hook, which runs when an unexpected error is thrown while responding to a\n * request. The effect receives the error and request event and produces the\n * shape exposed to the client (your `App.Error`, or nothing).\n *\n * @example\n * ```ts\n * // src/hooks.server.ts\n * import { Hooks } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n *\n * export const handleError = Hooks.handleError(({ error, event }) =>\n * Effect.gen(function* () {\n * const id = crypto.randomUUID();\n * yield* Effect.logError(`${event.url.pathname} (${id})`, error);\n * return { message: 'Internal Error', id };\n * })\n * );\n * ```\n */\nexport function handleError(\n fn: (\n args: Parameters<HandleServerError>[0],\n ) => Effect.Effect<Awaited<ReturnType<HandleServerError>>, unknown, SvelteKitServices>,\n): HandleServerError {\n return defaultServerRuntime().handleError(fn);\n}\n\n/**\n * Wraps an Effect as the SvelteKit server\n * [`init`](https://svelte.dev/docs/kit/hooks#Shared-hooks-init) hook, which\n * runs once before the server responds to its first request. There is no\n * request event available, so the effect runs without per-request services.\n *\n * @example\n * ```ts\n * // src/hooks.server.ts\n * import { Hooks } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n *\n * export const init = Hooks.init(Effect.logInfo('server starting up'));\n * ```\n */\nexport function init(effect: Effect.Effect<void, unknown>): ServerInit {\n return defaultServerRuntime().init(effect);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuDA,SAAgB,OACd,IACQ;CACR,OAAO,qBAAqB,CAAC,CAAC,OAAO,EAAE;AACzC;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,YACd,IACa;CACb,OAAO,qBAAqB,CAAC,CAAC,YAAY,EAAE;AAC9C;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,YACd,IAGmB;CACnB,OAAO,qBAAqB,CAAC,CAAC,YAAY,EAAE;AAC9C;;;;;;;;;;;;;;;;AAiBA,SAAgB,KAAK,QAAkD;CACrE,OAAO,qBAAqB,CAAC,CAAC,KAAK,MAAM;AAC3C"}
|
|
1
|
+
{"version":3,"file":"Hooks.js","names":[],"sources":["../../src/server/Hooks.ts"],"sourcesContent":["/**\n * Effect wrappers for SvelteKit's server-side hooks (`src/hooks.server.ts`),\n * exposed as the `Hooks` namespace of `@thomasfosterau/effect-sveltekit/server`.\n *\n * Each helper turns an Effect (or a function returning one) into the\n * function-shaped export SvelteKit expects. Effects run on the SvelteKit server\n * runtime, so they have access to platform services (`HttpClient`, the\n * `RequestEvent`, ...). To additionally provide your application's services,\n * use the matching methods on an app-wide runtime from `makeServerRuntime`.\n *\n * @example\n * ```ts\n * // src/hooks.server.ts\n * import { Hooks } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n *\n * export const handle = Hooks.handle(({ event, resolve }) =>\n * Effect.gen(function* () {\n * const response = yield* Effect.promise(() => resolve(event));\n * yield* Effect.logInfo(`${event.url.pathname} -> ${response.status}`);\n * return response;\n * })\n * );\n * ```\n */\n\nimport type { Handle, HandleFetch, HandleServerError, ServerInit } from \"@sveltejs/kit/hooks\";\nimport { Effect } from \"effect\";\nimport type { SvelteKitServices } from \"../app-runtime.js\";\nimport { defaultServerRuntime } from \"../internal/default-runtime.js\";\n\n/**\n * Wraps an Effect-based function as a SvelteKit\n * [`handle`](https://svelte.dev/docs/kit/hooks#Server-hooks-handle) hook, which\n * runs on every request and controls how the response is produced.\n *\n * SvelteKit control flow (`redirect()`, `error()`) raised inside the effect is\n * passed through to SvelteKit.\n *\n * @example\n * ```ts\n * // src/hooks.server.ts\n * import { Hooks } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n *\n * export const handle = Hooks.handle(({ event, resolve }) =>\n * Effect.gen(function* () {\n * // custom logic before resolve\n * const response = yield* Effect.promise(() => resolve(event));\n * // custom logic after resolve\n * return response;\n * })\n * );\n * ```\n */\nexport function handle(\n fn: (args: Parameters<Handle>[0]) => Effect.Effect<Response, unknown, SvelteKitServices>,\n): Handle {\n return defaultServerRuntime().handle(fn);\n}\n\n/**\n * Wraps an Effect-based function as a SvelteKit\n * [`handleFetch`](https://svelte.dev/docs/kit/hooks#Server-hooks-handleFetch)\n * hook, which intercepts `event.fetch` calls made on the server (in `load`,\n * actions, endpoints, ...). The effect produces the `Response` to use.\n *\n * @example\n * ```ts\n * // src/hooks.server.ts\n * import { Hooks } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n *\n * export const handleFetch = Hooks.handleFetch(({ request, fetch }) =>\n * Effect.gen(function* () {\n * // Redirect internal API calls to the upstream service.\n * const url = request.url.replace('https://api.internal', 'http://localhost:9999');\n * return yield* Effect.promise(() => fetch(new Request(url, request)));\n * })\n * );\n * ```\n */\nexport function handleFetch(\n fn: (args: Parameters<HandleFetch>[0]) => Effect.Effect<Response, unknown, SvelteKitServices>,\n): HandleFetch {\n return defaultServerRuntime().handleFetch(fn);\n}\n\n/**\n * Wraps an Effect-based function as the SvelteKit server\n * [`handleError`](https://svelte.dev/docs/kit/hooks#Shared-hooks-handleError)\n * hook, which runs when an unexpected error is thrown while responding to a\n * request. The effect receives the error and request event and produces the\n * shape exposed to the client (your `App.Error`, or nothing).\n *\n * @example\n * ```ts\n * // src/hooks.server.ts\n * import { Hooks } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n *\n * export const handleError = Hooks.handleError(({ error, event }) =>\n * Effect.gen(function* () {\n * const id = crypto.randomUUID();\n * yield* Effect.logError(`${event.url.pathname} (${id})`, error);\n * return { message: 'Internal Error', id };\n * })\n * );\n * ```\n */\nexport function handleError(\n fn: (\n args: Parameters<HandleServerError>[0],\n ) => Effect.Effect<Awaited<ReturnType<HandleServerError>>, unknown, SvelteKitServices>,\n): HandleServerError {\n return defaultServerRuntime().handleError(fn);\n}\n\n/**\n * Wraps an Effect as the SvelteKit server\n * [`init`](https://svelte.dev/docs/kit/hooks#Shared-hooks-init) hook, which\n * runs once before the server responds to its first request. There is no\n * request event available, so the effect runs without per-request services.\n *\n * @example\n * ```ts\n * // src/hooks.server.ts\n * import { Hooks } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n *\n * export const init = Hooks.init(Effect.logInfo('server starting up'));\n * ```\n */\nexport function init(effect: Effect.Effect<void, unknown>): ServerInit {\n return defaultServerRuntime().init(effect);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuDA,SAAgB,OACd,IACQ;CACR,OAAO,qBAAqB,CAAC,CAAC,OAAO,EAAE;AACzC;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,YACd,IACa;CACb,OAAO,qBAAqB,CAAC,CAAC,YAAY,EAAE;AAC9C;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,YACd,IAGmB;CACnB,OAAO,qBAAqB,CAAC,CAAC,YAAY,EAAE;AAC9C;;;;;;;;;;;;;;;;AAiBA,SAAgB,KAAK,QAAkD;CACrE,OAAO,qBAAqB,CAAC,CAAC,KAAK,MAAM;AAC3C"}
|
package/dist/transport.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Schema } from "effect";
|
|
2
|
-
import { Transport, Transporter } from "@sveltejs/kit";
|
|
2
|
+
import { Transport, Transporter } from "@sveltejs/kit/hooks";
|
|
3
3
|
//#region src/transport.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Builds a SvelteKit {@link Transporter} from an Effect {@link Schema.Codec}.
|
package/dist/transport.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.js","names":[],"sources":["../src/transport.ts"],"sourcesContent":["/**\n * Effect Schema ⇄ SvelteKit `transport` hook.\n *\n * SvelteKit's universal [`transport`](https://svelte.dev/docs/kit/hooks#Universal-hooks-transport)\n * hook moves custom (non-POJO) values across the server/client boundary — for\n * data returned from `load` functions and from remote functions. Each entry is\n * a {@link Transporter}: a sync `encode` (server) / `decode` (client) pair,\n * where `encode` returns `false` for values it does not handle.\n *\n * An Effect {@link Schema.Codec} is exactly such a pair. {@link transporter}\n * turns a schema into a `Transporter`, and {@link transport} assembles a whole\n * `transport` object from a record of schemas — so `Option`, `DateTime`,\n * branded types, `Schema.Class` instances, tagged errors, etc. round-trip with\n * no hand-written serialization.\n *\n * ```ts\n * // src/hooks.ts\n * import { Hooks } from \"@thomasfosterau/effect-sveltekit\";\n * import { Schema } from \"effect\";\n *\n * export const transport = Hooks.transport({\n * Option: Schema.Option(Schema.Number),\n * Date: Schema.Date,\n * });\n * ```\n */\n\nimport type { Transport, Transporter } from \"@sveltejs/kit\";\nimport { Schema } from \"effect\";\n\n/**\n * Builds a SvelteKit {@link Transporter} from an Effect {@link Schema.Codec}.\n *\n * The decoded `Type` is detected with `Schema.is`; matching values are encoded\n * with `Schema.encodeSync` and wrapped in a single-element tuple. The wrapper\n * guarantees the encoding is truthy — SvelteKit treats a falsy `encode` result\n * (e.g. an encoded `0`, `\"\"`, `false`, or `null`) as \"not this type\" — and is\n * unwrapped again by `decode` on the client.\n */\nexport const transporter = <T, E>(schema: Schema.Codec<T, E>): Transporter<T, readonly [E]> => {\n const guard = Schema.is(schema);\n const encode = Schema.encodeSync(schema);\n const decode = Schema.decodeSync(schema);\n return {\n encode: (value) => (guard(value) ? ([encode(value)] as const) : false),\n decode: ([data]) => decode(data),\n };\n};\n\n/**\n * Builds a deliberately-lossy {@link Transporter} for a non-POJO wrapper whose\n * contents should *not* round-trip — the value is detected on the server and\n * reconstructed fresh on the client.\n *\n * Unlike {@link transporter} (which needs a round-tripping {@link Schema.Codec}),\n * this is for values that have no meaningful serialization but still need a\n * `transport` entry so SvelteKit doesn't reject them: `encode` returns a truthy\n * marker for matching values, and `decode` ignores it and produces a fresh value\n * from {@link MarkerTransporterOptions.make}.\n *\n * ```ts\n * // A non-serializable handle reconstructed as a fresh, empty instance:\n * export const transport = Hooks.transport({\n * Live: Hooks.markerTransporter({\n * guard: (v): v is LiveHandle => v instanceof LiveHandle,\n * make: () => LiveHandle.empty(),\n * }),\n * });\n * ```\n */\nexport const markerTransporter = <T>(\n options: MarkerTransporterOptions<T>,\n): Transporter<T, unknown> => {\n const marker = options.marker ?? 1;\n return {\n encode: (value) => (options.guard(value) ? marker : false),\n decode: () => options.make(),\n };\n};\n\n/** Options for {@link markerTransporter}. */\nexport interface MarkerTransporterOptions<T> {\n /** Detect, on the server, values this transporter handles. */\n readonly guard: (value: unknown) => value is T;\n /** Produce a fresh value on the client (the payload is discarded). */\n readonly make: () => T;\n /**\n * The truthy marker `encode` emits (SvelteKit treats a falsy `encode` result\n * as \"not this type\"). Defaults to `1`.\n */\n readonly marker?: unknown;\n}\n\n/** An entry accepted by {@link transport}: an Effect schema or a ready Transporter. */\nexport type TransportEntry = Schema.Codec<any, any> | Transporter<any, any>;\n\n/**\n * Assembles a SvelteKit `transport` object from a record of entries. Each entry\n * is either an Effect {@link Schema.Codec} (wrapped with {@link transporter}) or\n * a ready {@link Transporter} (e.g. from {@link markerTransporter}), so\n * round-tripping and marker-style values can be mixed. The keys are arbitrary\n * labels (they only need to be stable between server and client).\n */\nexport const transport = (entries: Record<string, TransportEntry>): Transport => {\n const result: Transport = {};\n for (const [name, entry] of Object.entries(entries)) {\n result[name] = Schema.isSchema(entry)\n ? transporter(entry as Schema.Codec<any, any>)\n : (entry as Transporter<any, any>);\n }\n return result;\n};\n"],"mappings":";;;;;;;;;;;AAuCA,MAAa,eAAqB,WAA6D;CAC7F,MAAM,QAAQ,OAAO,GAAG,MAAM;CAC9B,MAAM,SAAS,OAAO,WAAW,MAAM;CACvC,MAAM,SAAS,OAAO,WAAW,MAAM;CACvC,OAAO;EACL,SAAS,UAAW,MAAM,KAAK,IAAK,CAAC,OAAO,KAAK,CAAC,IAAc;EAChE,SAAS,CAAC,UAAU,OAAO,IAAI;CACjC;AACF;;;;;;;;;;;;;;;;;;;;;;AAuBA,MAAa,qBACX,YAC4B;CAC5B,MAAM,SAAS,QAAQ,UAAU;CACjC,OAAO;EACL,SAAS,UAAW,QAAQ,MAAM,KAAK,IAAI,SAAS;EACpD,cAAc,QAAQ,KAAK;CAC7B;AACF;;;;;;;;AAyBA,MAAa,aAAa,YAAuD;CAC/E,MAAM,SAAoB,CAAC;CAC3B,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,OAAO,GAChD,OAAO,QAAQ,OAAO,SAAS,KAAK,IAChC,YAAY,KAA+B,IAC1C;CAEP,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"transport.js","names":[],"sources":["../src/transport.ts"],"sourcesContent":["/**\n * Effect Schema ⇄ SvelteKit `transport` hook.\n *\n * SvelteKit's universal [`transport`](https://svelte.dev/docs/kit/hooks#Universal-hooks-transport)\n * hook moves custom (non-POJO) values across the server/client boundary — for\n * data returned from `load` functions and from remote functions. Each entry is\n * a {@link Transporter}: a sync `encode` (server) / `decode` (client) pair,\n * where `encode` returns `false` for values it does not handle.\n *\n * An Effect {@link Schema.Codec} is exactly such a pair. {@link transporter}\n * turns a schema into a `Transporter`, and {@link transport} assembles a whole\n * `transport` object from a record of schemas — so `Option`, `DateTime`,\n * branded types, `Schema.Class` instances, tagged errors, etc. round-trip with\n * no hand-written serialization.\n *\n * ```ts\n * // src/hooks.ts\n * import { Hooks } from \"@thomasfosterau/effect-sveltekit\";\n * import { Schema } from \"effect\";\n *\n * export const transport = Hooks.transport({\n * Option: Schema.Option(Schema.Number),\n * Date: Schema.Date,\n * });\n * ```\n */\n\nimport type { Transport, Transporter } from \"@sveltejs/kit/hooks\";\nimport { Schema } from \"effect\";\n\n/**\n * Builds a SvelteKit {@link Transporter} from an Effect {@link Schema.Codec}.\n *\n * The decoded `Type` is detected with `Schema.is`; matching values are encoded\n * with `Schema.encodeSync` and wrapped in a single-element tuple. The wrapper\n * guarantees the encoding is truthy — SvelteKit treats a falsy `encode` result\n * (e.g. an encoded `0`, `\"\"`, `false`, or `null`) as \"not this type\" — and is\n * unwrapped again by `decode` on the client.\n */\nexport const transporter = <T, E>(schema: Schema.Codec<T, E>): Transporter<T, readonly [E]> => {\n const guard = Schema.is(schema);\n const encode = Schema.encodeSync(schema);\n const decode = Schema.decodeSync(schema);\n return {\n encode: (value) => (guard(value) ? ([encode(value)] as const) : false),\n decode: ([data]) => decode(data),\n };\n};\n\n/**\n * Builds a deliberately-lossy {@link Transporter} for a non-POJO wrapper whose\n * contents should *not* round-trip — the value is detected on the server and\n * reconstructed fresh on the client.\n *\n * Unlike {@link transporter} (which needs a round-tripping {@link Schema.Codec}),\n * this is for values that have no meaningful serialization but still need a\n * `transport` entry so SvelteKit doesn't reject them: `encode` returns a truthy\n * marker for matching values, and `decode` ignores it and produces a fresh value\n * from {@link MarkerTransporterOptions.make}.\n *\n * ```ts\n * // A non-serializable handle reconstructed as a fresh, empty instance:\n * export const transport = Hooks.transport({\n * Live: Hooks.markerTransporter({\n * guard: (v): v is LiveHandle => v instanceof LiveHandle,\n * make: () => LiveHandle.empty(),\n * }),\n * });\n * ```\n */\nexport const markerTransporter = <T>(\n options: MarkerTransporterOptions<T>,\n): Transporter<T, unknown> => {\n const marker = options.marker ?? 1;\n return {\n encode: (value) => (options.guard(value) ? marker : false),\n decode: () => options.make(),\n };\n};\n\n/** Options for {@link markerTransporter}. */\nexport interface MarkerTransporterOptions<T> {\n /** Detect, on the server, values this transporter handles. */\n readonly guard: (value: unknown) => value is T;\n /** Produce a fresh value on the client (the payload is discarded). */\n readonly make: () => T;\n /**\n * The truthy marker `encode` emits (SvelteKit treats a falsy `encode` result\n * as \"not this type\"). Defaults to `1`.\n */\n readonly marker?: unknown;\n}\n\n/** An entry accepted by {@link transport}: an Effect schema or a ready Transporter. */\nexport type TransportEntry = Schema.Codec<any, any> | Transporter<any, any>;\n\n/**\n * Assembles a SvelteKit `transport` object from a record of entries. Each entry\n * is either an Effect {@link Schema.Codec} (wrapped with {@link transporter}) or\n * a ready {@link Transporter} (e.g. from {@link markerTransporter}), so\n * round-tripping and marker-style values can be mixed. The keys are arbitrary\n * labels (they only need to be stable between server and client).\n */\nexport const transport = (entries: Record<string, TransportEntry>): Transport => {\n const result: Transport = {};\n for (const [name, entry] of Object.entries(entries)) {\n result[name] = Schema.isSchema(entry)\n ? transporter(entry as Schema.Codec<any, any>)\n : (entry as Transporter<any, any>);\n }\n return result;\n};\n"],"mappings":";;;;;;;;;;;AAuCA,MAAa,eAAqB,WAA6D;CAC7F,MAAM,QAAQ,OAAO,GAAG,MAAM;CAC9B,MAAM,SAAS,OAAO,WAAW,MAAM;CACvC,MAAM,SAAS,OAAO,WAAW,MAAM;CACvC,OAAO;EACL,SAAS,UAAW,MAAM,KAAK,IAAK,CAAC,OAAO,KAAK,CAAC,IAAc;EAChE,SAAS,CAAC,UAAU,OAAO,IAAI;CACjC;AACF;;;;;;;;;;;;;;;;;;;;;;AAuBA,MAAa,qBACX,YAC4B;CAC5B,MAAM,SAAS,QAAQ,UAAU;CACjC,OAAO;EACL,SAAS,UAAW,QAAQ,MAAM,KAAK,IAAI,SAAS;EACpD,cAAc,QAAQ,KAAK;CAC7B;AACF;;;;;;;;AAyBA,MAAa,aAAa,YAAuD;CAC/E,MAAM,SAAoB,CAAC;CAC3B,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,OAAO,GAChD,OAAO,QAAQ,OAAO,SAAS,KAAK,IAChC,YAAY,KAA+B,IAC1C;CAEP,OAAO;AACT"}
|
package/dist/vite.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Plugin } from "vite";
|
|
2
2
|
//#region src/vite.d.ts
|
|
3
|
+
/** The SvelteKit route and hook file kinds `effectKit()` can rewrite. */
|
|
3
4
|
type EffectKitFileKind = "pageServer" | "page" | "layoutServer" | "layout" | "server" | "hooks" | "hooksServer" | "hooksClient";
|
|
5
|
+
/** Options for {@link effectKit}. */
|
|
4
6
|
interface EffectKitPluginOptions {
|
|
5
7
|
/**
|
|
6
8
|
* Restrict transformation to a subset of SvelteKit file kinds.
|
|
@@ -15,7 +17,7 @@ interface EffectKitPluginOptions {
|
|
|
15
17
|
* `+layout.server.ts`, `+server.ts`, `hooks.server.ts`) have access to the
|
|
16
18
|
* services of that runtime's layer.
|
|
17
19
|
*
|
|
18
|
-
* @example "
|
|
20
|
+
* @example "#lib/server/runtime.ts"
|
|
19
21
|
*/
|
|
20
22
|
serverRuntime?: string;
|
|
21
23
|
/**
|
|
@@ -26,7 +28,7 @@ interface EffectKitPluginOptions {
|
|
|
26
28
|
* `+layout.ts`, `hooks.client.ts`) have access to the services of that
|
|
27
29
|
* runtime's layer. The module must be safe to import in the browser.
|
|
28
30
|
*
|
|
29
|
-
* @example "
|
|
31
|
+
* @example "#lib/runtime.ts"
|
|
30
32
|
*/
|
|
31
33
|
clientRuntime?: string;
|
|
32
34
|
}
|
package/dist/vite.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite.js","names":[],"sources":["../src/vite.ts"],"sourcesContent":["/**\n * Vite plugin that lets you write SvelteKit route exports as Effects (or\n * functions returning Effects) without manually wrapping them.\n *\n * ```ts\n * // vite.config.ts\n * import { sveltekit } from \"@sveltejs/kit/vite\";\n * import { effectKit } from \"@thomasfosterau/effect-sveltekit/vite\";\n *\n * export default {\n * plugins: [effectKit(), sveltekit()],\n * };\n * ```\n *\n * ```ts\n * // +page.server.ts\n * import { Effect } from \"effect\";\n * import * as HttpClient from \"effect/http/HttpClient\";\n *\n * export const load = (event) =>\n * Effect.gen(function* () {\n * const client = yield* HttpClient.HttpClient;\n * return { hello: \"world\" };\n * });\n * ```\n *\n * Under the hood the plugin rewrites the export to call the same wrappers\n * that the explicit API (`serverLoad`, `actions`, `handle`, `endpoint`, ...)\n * uses. Plain functions that return promises or values pass through\n * unchanged, so the plugin is safe to enable on existing projects.\n */\n\nimport MagicString from \"magic-string\";\nimport type { Plugin } from \"vite\";\n\nconst INTERNAL_IMPORT = \"@thomasfosterau/effect-sveltekit/internal/auto\";\nconst ORIG_PREFIX = \"__effect_kit_orig_\";\nconst RUNTIME_IDENT = \"__effect_kit_runtime\";\n\nexport type EffectKitFileKind =\n | \"pageServer\"\n | \"page\"\n | \"layoutServer\"\n | \"layout\"\n | \"server\"\n | \"hooks\"\n | \"hooksServer\"\n | \"hooksClient\";\n\n/** File kinds whose exports only ever run on the server. */\nconst serverKinds: ReadonlySet<EffectKitFileKind> = new Set([\n \"pageServer\",\n \"layoutServer\",\n \"server\",\n \"hooksServer\",\n]);\n\nconst wrapperByKind: Record<EffectKitFileKind, Record<string, string>> = {\n pageServer: { load: \"wrapServerLoad\", actions: \"wrapActions\" },\n page: { load: \"wrapLoad\" },\n layoutServer: { load: \"wrapServerLoad\" },\n layout: { load: \"wrapLoad\" },\n server: {\n GET: \"wrapEndpoint\",\n POST: \"wrapEndpoint\",\n PUT: \"wrapEndpoint\",\n PATCH: \"wrapEndpoint\",\n DELETE: \"wrapEndpoint\",\n OPTIONS: \"wrapEndpoint\",\n HEAD: \"wrapEndpoint\",\n fallback: \"wrapEndpoint\",\n },\n hooksServer: {\n handle: \"wrapHandle\",\n handleFetch: \"wrapHandleFetch\",\n handleError: \"wrapHandleError\",\n init: \"wrapInit\",\n },\n hooksClient: {\n handleError: \"wrapHandleError\",\n init: \"wrapInit\",\n },\n // Universal `src/hooks.ts`: only `reroute` is function-shaped (the `transport`\n // export is plain data and is intentionally left untouched).\n hooks: {\n reroute: \"wrapReroute\",\n },\n};\n\nfunction getFileKind(id: string): EffectKitFileKind | null {\n const path = id.split(\"?\")[0]!.replace(/\\\\/g, \"/\");\n if (/\\/\\+page\\.server\\.(?:ts|js|mts|mjs)$/.test(path)) return \"pageServer\";\n if (/\\/\\+page\\.(?:ts|js|mts|mjs)$/.test(path)) return \"page\";\n if (/\\/\\+layout\\.server\\.(?:ts|js|mts|mjs)$/.test(path)) return \"layoutServer\";\n if (/\\/\\+layout\\.(?:ts|js|mts|mjs)$/.test(path)) return \"layout\";\n if (/\\/\\+server\\.(?:ts|js|mts|mjs)$/.test(path)) return \"server\";\n if (/\\/hooks\\.server\\.(?:ts|js|mts|mjs)$/.test(path)) return \"hooksServer\";\n if (/\\/hooks\\.client\\.(?:ts|js|mts|mjs)$/.test(path)) return \"hooksClient\";\n // Plain `hooks.ts` is the universal hooks file; checked last so the\n // `.server.`/`.client.` variants above take precedence.\n if (/\\/hooks\\.(?:ts|js|mts|mjs)$/.test(path)) return \"hooks\";\n return null;\n}\n\n/**\n * Returns a copy of `source` with line/block comments and string/template\n * contents replaced by spaces (newlines preserved). Byte offsets in the\n * returned string line up with the original, so regex scans don't trip on\n * tokens inside comments or strings.\n */\nfunction maskCommentsAndStrings(source: string): string {\n const out: string[] = [];\n let i = 0;\n const len = source.length;\n\n while (i < len) {\n const c = source[i]!;\n const next = source[i + 1];\n\n if (c === \"/\" && next === \"/\") {\n out.push(\" \");\n i += 2;\n while (i < len && source[i] !== \"\\n\") {\n out.push(\" \");\n i++;\n }\n continue;\n }\n\n if (c === \"/\" && next === \"*\") {\n out.push(\" \");\n i += 2;\n while (i < len && !(source[i] === \"*\" && source[i + 1] === \"/\")) {\n out.push(source[i] === \"\\n\" ? \"\\n\" : \" \");\n i++;\n }\n if (i < len) {\n out.push(\" \");\n i += 2;\n }\n continue;\n }\n\n if (c === '\"' || c === \"'\") {\n const quote = c;\n out.push(quote);\n i++;\n while (i < len && source[i] !== quote) {\n if (source[i] === \"\\\\\" && i + 1 < len) {\n out.push(\" \");\n i += 2;\n continue;\n }\n out.push(source[i] === \"\\n\" ? \"\\n\" : \" \");\n i++;\n }\n if (i < len) {\n out.push(quote);\n i++;\n }\n continue;\n }\n\n if (c === \"`\") {\n out.push(\"`\");\n i++;\n while (i < len && source[i] !== \"`\") {\n if (source[i] === \"\\\\\" && i + 1 < len) {\n out.push(\" \");\n i += 2;\n continue;\n }\n if (source[i] === \"$\" && source[i + 1] === \"{\") {\n // Preserve template expression so nested strings are masked by\n // the outer loop.\n out.push(\"${\");\n i += 2;\n let depth = 1;\n while (i < len && depth > 0) {\n out.push(source[i]!);\n if (source[i] === \"{\") depth++;\n else if (source[i] === \"}\") depth--;\n i++;\n }\n continue;\n }\n out.push(source[i] === \"\\n\" ? \"\\n\" : \" \");\n i++;\n }\n if (i < len) {\n out.push(\"`\");\n i++;\n }\n continue;\n }\n\n out.push(c);\n i++;\n }\n\n return out.join(\"\");\n}\n\ninterface ExportMatch {\n name: string;\n start: number;\n end: number;\n replacement: string;\n}\n\n/**\n * Find top-level `export const|let|var|function NAME` declarations whose\n * NAME is in `targetNames`. Returns the slice of the original source to\n * replace, along with a renamed declaration to put in its place.\n *\n * `export { NAME }`, `export default`, and destructuring forms are not\n * supported — users who need them should use the explicit wrappers.\n */\nfunction findExports(masked: string, targetNames: ReadonlySet<string>): ExportMatch[] {\n const matches: ExportMatch[] = [];\n const pattern =\n /^[ \\t]*(export)[ \\t]+(?:async[ \\t]+)?(?:const|let|var|function)[ \\t]+([A-Za-z_$][\\w$]*)/gm;\n\n let m: RegExpExecArray | null;\n while ((m = pattern.exec(masked))) {\n const [full, , name] = m;\n if (!targetNames.has(name!)) continue;\n\n const exportStart = m.index + full.indexOf(\"export\");\n const nameEnd = m.index + full.length;\n const renamed = `${ORIG_PREFIX}${name}`;\n\n const slice = masked.slice(exportStart, nameEnd);\n const replacement = slice\n .replace(/^export[ \\t]+/, \"\")\n .replace(new RegExp(`\\\\b${name}$`), renamed);\n\n matches.push({ name: name!, start: exportStart, end: nameEnd, replacement });\n }\n\n return matches;\n}\n\nexport interface EffectKitPluginOptions {\n /**\n * Restrict transformation to a subset of SvelteKit file kinds.\n * Defaults to all standard route files.\n */\n include?: ReadonlyArray<EffectKitFileKind>;\n\n /**\n * Import specifier of a server-only module whose `runtime` (or default)\n * export is an app-wide runtime created with `makeServerRuntime`.\n *\n * When set, Effects returned from server route files (`+page.server.ts`,\n * `+layout.server.ts`, `+server.ts`, `hooks.server.ts`) have access to the\n * services of that runtime's layer.\n *\n * @example \"$lib/server/runtime\"\n */\n serverRuntime?: string;\n\n /**\n * Import specifier of a universal module whose `runtime` (or default)\n * export is an app-wide runtime created with `makeUniversalRuntime`.\n *\n * When set, Effects returned from universal route files (`+page.ts`,\n * `+layout.ts`, `hooks.client.ts`) have access to the services of that\n * runtime's layer. The module must be safe to import in the browser.\n *\n * @example \"$lib/runtime\"\n */\n clientRuntime?: string;\n}\n\n/**\n * Vite plugin that rewrites SvelteKit route exports so that Effects, or\n * functions returning Effects, are accepted wherever SvelteKit expects a\n * function-shaped export.\n */\nexport function effectKit(options: EffectKitPluginOptions = {}): Plugin {\n const include = options.include ? new Set(options.include) : null;\n\n return {\n name: \"effect-kit\",\n enforce: \"pre\",\n transform(code, id) {\n const kind = getFileKind(id);\n if (!kind) return null;\n if (include && !include.has(kind)) return null;\n\n const wrappers = wrapperByKind[kind];\n const targetNames = new Set(Object.keys(wrappers));\n\n const masked = maskCommentsAndStrings(code);\n const found = findExports(masked, targetNames);\n if (found.length === 0) return null;\n\n const s = new MagicString(code);\n\n for (const match of found) {\n s.overwrite(match.start, match.end, match.replacement);\n }\n\n const wrapperImports = Array.from(new Set(found.map((m) => wrappers[m.name]!)));\n\n // Server-only files get the server runtime module; universal/client\n // files get the client runtime module (it must be browser-safe).\n const runtimeSpecifier = serverKinds.has(kind)\n ? options.serverRuntime\n : options.clientRuntime;\n\n const imports =\n `\\nimport { ${wrapperImports.join(\", \")} } from ${JSON.stringify(INTERNAL_IMPORT)};\\n` +\n (runtimeSpecifier\n ? `import * as ${RUNTIME_IDENT} from ${JSON.stringify(runtimeSpecifier)};\\n`\n : \"\");\n const runtimeArg = runtimeSpecifier ? `, ${RUNTIME_IDENT}` : \"\";\n const exports = found\n .map(\n (m) =>\n `export const ${m.name} = ${wrappers[m.name]}(${ORIG_PREFIX}${m.name}${runtimeArg});`,\n )\n .join(\"\\n\");\n\n s.append(`\\n${imports}${exports}\\n`);\n\n return {\n code: s.toString(),\n map: s.generateMap({ hires: \"boundary\", source: id }),\n };\n },\n };\n}\n\nexport default effectKit;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"vite.js","names":[],"sources":["../src/vite.ts"],"sourcesContent":["/**\n * Vite plugin that lets you write SvelteKit route exports as Effects (or\n * functions returning Effects) without manually wrapping them.\n *\n * ```ts\n * // vite.config.ts\n * import { sveltekit } from \"@sveltejs/kit/vite\";\n * import { effectKit } from \"@thomasfosterau/effect-sveltekit/vite\";\n *\n * export default {\n * plugins: [effectKit(), sveltekit()],\n * };\n * ```\n *\n * ```ts\n * // +page.server.ts\n * import { Effect } from \"effect\";\n * import * as HttpClient from \"effect/http/HttpClient\";\n *\n * export const load = (event) =>\n * Effect.gen(function* () {\n * const client = yield* HttpClient.HttpClient;\n * return { hello: \"world\" };\n * });\n * ```\n *\n * Under the hood the plugin rewrites the export to call the same wrappers\n * that the explicit API (`serverLoad`, `actions`, `handle`, `endpoint`, ...)\n * uses. Plain functions that return promises or values pass through\n * unchanged, so the plugin is safe to enable on existing projects.\n */\n\nimport MagicString from \"magic-string\";\nimport type { Plugin } from \"vite\";\n\nconst INTERNAL_IMPORT = \"@thomasfosterau/effect-sveltekit/internal/auto\";\nconst ORIG_PREFIX = \"__effect_kit_orig_\";\nconst RUNTIME_IDENT = \"__effect_kit_runtime\";\n\n/** The SvelteKit route and hook file kinds `effectKit()` can rewrite. */\nexport type EffectKitFileKind =\n | \"pageServer\"\n | \"page\"\n | \"layoutServer\"\n | \"layout\"\n | \"server\"\n | \"hooks\"\n | \"hooksServer\"\n | \"hooksClient\";\n\n/** File kinds whose exports only ever run on the server. */\nconst serverKinds: ReadonlySet<EffectKitFileKind> = new Set([\n \"pageServer\",\n \"layoutServer\",\n \"server\",\n \"hooksServer\",\n]);\n\nconst wrapperByKind: Record<EffectKitFileKind, Record<string, string>> = {\n pageServer: { load: \"wrapServerLoad\", actions: \"wrapActions\" },\n page: { load: \"wrapLoad\" },\n layoutServer: { load: \"wrapServerLoad\" },\n layout: { load: \"wrapLoad\" },\n server: {\n GET: \"wrapEndpoint\",\n POST: \"wrapEndpoint\",\n PUT: \"wrapEndpoint\",\n PATCH: \"wrapEndpoint\",\n DELETE: \"wrapEndpoint\",\n OPTIONS: \"wrapEndpoint\",\n HEAD: \"wrapEndpoint\",\n fallback: \"wrapEndpoint\",\n },\n hooksServer: {\n handle: \"wrapHandle\",\n handleFetch: \"wrapHandleFetch\",\n handleError: \"wrapHandleError\",\n init: \"wrapInit\",\n },\n hooksClient: {\n handleError: \"wrapHandleError\",\n init: \"wrapInit\",\n },\n // Universal `src/hooks.ts`: only `reroute` is function-shaped (the `transport`\n // export is plain data and is intentionally left untouched).\n hooks: {\n reroute: \"wrapReroute\",\n },\n};\n\nfunction getFileKind(id: string): EffectKitFileKind | null {\n const path = id.split(\"?\")[0]!.replace(/\\\\/g, \"/\");\n if (/\\/\\+page\\.server\\.(?:ts|js|mts|mjs)$/.test(path)) return \"pageServer\";\n if (/\\/\\+page\\.(?:ts|js|mts|mjs)$/.test(path)) return \"page\";\n if (/\\/\\+layout\\.server\\.(?:ts|js|mts|mjs)$/.test(path)) return \"layoutServer\";\n if (/\\/\\+layout\\.(?:ts|js|mts|mjs)$/.test(path)) return \"layout\";\n if (/\\/\\+server\\.(?:ts|js|mts|mjs)$/.test(path)) return \"server\";\n if (/\\/hooks\\.server\\.(?:ts|js|mts|mjs)$/.test(path)) return \"hooksServer\";\n if (/\\/hooks\\.client\\.(?:ts|js|mts|mjs)$/.test(path)) return \"hooksClient\";\n // Plain `hooks.ts` is the universal hooks file; checked last so the\n // `.server.`/`.client.` variants above take precedence.\n if (/\\/hooks\\.(?:ts|js|mts|mjs)$/.test(path)) return \"hooks\";\n return null;\n}\n\n/**\n * Returns a copy of `source` with line/block comments and string/template\n * contents replaced by spaces (newlines preserved). Byte offsets in the\n * returned string line up with the original, so regex scans don't trip on\n * tokens inside comments or strings.\n */\nfunction maskCommentsAndStrings(source: string): string {\n const out: string[] = [];\n let i = 0;\n const len = source.length;\n\n while (i < len) {\n const c = source[i]!;\n const next = source[i + 1];\n\n if (c === \"/\" && next === \"/\") {\n out.push(\" \");\n i += 2;\n while (i < len && source[i] !== \"\\n\") {\n out.push(\" \");\n i++;\n }\n continue;\n }\n\n if (c === \"/\" && next === \"*\") {\n out.push(\" \");\n i += 2;\n while (i < len && !(source[i] === \"*\" && source[i + 1] === \"/\")) {\n out.push(source[i] === \"\\n\" ? \"\\n\" : \" \");\n i++;\n }\n if (i < len) {\n out.push(\" \");\n i += 2;\n }\n continue;\n }\n\n if (c === '\"' || c === \"'\") {\n const quote = c;\n out.push(quote);\n i++;\n while (i < len && source[i] !== quote) {\n if (source[i] === \"\\\\\" && i + 1 < len) {\n out.push(\" \");\n i += 2;\n continue;\n }\n out.push(source[i] === \"\\n\" ? \"\\n\" : \" \");\n i++;\n }\n if (i < len) {\n out.push(quote);\n i++;\n }\n continue;\n }\n\n if (c === \"`\") {\n out.push(\"`\");\n i++;\n while (i < len && source[i] !== \"`\") {\n if (source[i] === \"\\\\\" && i + 1 < len) {\n out.push(\" \");\n i += 2;\n continue;\n }\n if (source[i] === \"$\" && source[i + 1] === \"{\") {\n // Preserve template expression so nested strings are masked by\n // the outer loop.\n out.push(\"${\");\n i += 2;\n let depth = 1;\n while (i < len && depth > 0) {\n out.push(source[i]!);\n if (source[i] === \"{\") depth++;\n else if (source[i] === \"}\") depth--;\n i++;\n }\n continue;\n }\n out.push(source[i] === \"\\n\" ? \"\\n\" : \" \");\n i++;\n }\n if (i < len) {\n out.push(\"`\");\n i++;\n }\n continue;\n }\n\n out.push(c);\n i++;\n }\n\n return out.join(\"\");\n}\n\ninterface ExportMatch {\n name: string;\n start: number;\n end: number;\n replacement: string;\n}\n\n/**\n * Find top-level `export const|let|var|function NAME` declarations whose\n * NAME is in `targetNames`. Returns the slice of the original source to\n * replace, along with a renamed declaration to put in its place.\n *\n * `export { NAME }`, `export default`, and destructuring forms are not\n * supported — users who need them should use the explicit wrappers.\n */\nfunction findExports(masked: string, targetNames: ReadonlySet<string>): ExportMatch[] {\n const matches: ExportMatch[] = [];\n const pattern =\n /^[ \\t]*(export)[ \\t]+(?:async[ \\t]+)?(?:const|let|var|function)[ \\t]+([A-Za-z_$][\\w$]*)/gm;\n\n let m: RegExpExecArray | null;\n while ((m = pattern.exec(masked))) {\n const [full, , name] = m;\n if (!targetNames.has(name!)) continue;\n\n const exportStart = m.index + full.indexOf(\"export\");\n const nameEnd = m.index + full.length;\n const renamed = `${ORIG_PREFIX}${name}`;\n\n const slice = masked.slice(exportStart, nameEnd);\n const replacement = slice\n .replace(/^export[ \\t]+/, \"\")\n .replace(new RegExp(`\\\\b${name}$`), renamed);\n\n matches.push({ name: name!, start: exportStart, end: nameEnd, replacement });\n }\n\n return matches;\n}\n\n/** Options for {@link effectKit}. */\nexport interface EffectKitPluginOptions {\n /**\n * Restrict transformation to a subset of SvelteKit file kinds.\n * Defaults to all standard route files.\n */\n include?: ReadonlyArray<EffectKitFileKind>;\n\n /**\n * Import specifier of a server-only module whose `runtime` (or default)\n * export is an app-wide runtime created with `makeServerRuntime`.\n *\n * When set, Effects returned from server route files (`+page.server.ts`,\n * `+layout.server.ts`, `+server.ts`, `hooks.server.ts`) have access to the\n * services of that runtime's layer.\n *\n * @example \"#lib/server/runtime.ts\"\n */\n serverRuntime?: string;\n\n /**\n * Import specifier of a universal module whose `runtime` (or default)\n * export is an app-wide runtime created with `makeUniversalRuntime`.\n *\n * When set, Effects returned from universal route files (`+page.ts`,\n * `+layout.ts`, `hooks.client.ts`) have access to the services of that\n * runtime's layer. The module must be safe to import in the browser.\n *\n * @example \"#lib/runtime.ts\"\n */\n clientRuntime?: string;\n}\n\n/**\n * Vite plugin that rewrites SvelteKit route exports so that Effects, or\n * functions returning Effects, are accepted wherever SvelteKit expects a\n * function-shaped export.\n */\nexport function effectKit(options: EffectKitPluginOptions = {}): Plugin {\n const include = options.include ? new Set(options.include) : null;\n\n return {\n name: \"effect-kit\",\n enforce: \"pre\",\n transform(code, id) {\n const kind = getFileKind(id);\n if (!kind) return null;\n if (include && !include.has(kind)) return null;\n\n const wrappers = wrapperByKind[kind];\n const targetNames = new Set(Object.keys(wrappers));\n\n const masked = maskCommentsAndStrings(code);\n const found = findExports(masked, targetNames);\n if (found.length === 0) return null;\n\n const s = new MagicString(code);\n\n for (const match of found) {\n s.overwrite(match.start, match.end, match.replacement);\n }\n\n const wrapperImports = Array.from(new Set(found.map((m) => wrappers[m.name]!)));\n\n // Server-only files get the server runtime module; universal/client\n // files get the client runtime module (it must be browser-safe).\n const runtimeSpecifier = serverKinds.has(kind)\n ? options.serverRuntime\n : options.clientRuntime;\n\n const imports =\n `\\nimport { ${wrapperImports.join(\", \")} } from ${JSON.stringify(INTERNAL_IMPORT)};\\n` +\n (runtimeSpecifier\n ? `import * as ${RUNTIME_IDENT} from ${JSON.stringify(runtimeSpecifier)};\\n`\n : \"\");\n const runtimeArg = runtimeSpecifier ? `, ${RUNTIME_IDENT}` : \"\";\n const exports = found\n .map(\n (m) =>\n `export const ${m.name} = ${wrappers[m.name]}(${ORIG_PREFIX}${m.name}${runtimeArg});`,\n )\n .join(\"\\n\");\n\n s.append(`\\n${imports}${exports}\\n`);\n\n return {\n code: s.toString(),\n map: s.generateMap({ hires: \"boundary\", source: id }),\n };\n },\n };\n}\n\nexport default effectKit;\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thomasfosterau/effect-sveltekit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Effect integration for SvelteKit: load/action/endpoint wrappers, remote functions, an effectKit() Vite plugin, and an Effect HTTP deployment adapter",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"effect",
|
|
@@ -74,14 +74,17 @@
|
|
|
74
74
|
"dependencies": {
|
|
75
75
|
"@standard-schema/spec": "^1.1.0",
|
|
76
76
|
"magic-string": "^1.4.2",
|
|
77
|
-
"@thomasfosterau/effect-svelte": "^0.4.
|
|
77
|
+
"@thomasfosterau/effect-svelte": "^0.4.2"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
|
+
"@effect/platform-node": "4.0.0",
|
|
81
|
+
"@sveltejs/kit": "^3.0.0",
|
|
82
|
+
"svelte": "^5.57.1",
|
|
80
83
|
"typescript": "npm:@typescript/typescript6@6.0.2"
|
|
81
84
|
},
|
|
82
85
|
"peerDependencies": {
|
|
83
86
|
"@effect/platform-node": ">=4.0.0-rc.118",
|
|
84
|
-
"@sveltejs/kit": "^
|
|
87
|
+
"@sveltejs/kit": "^3.0.0",
|
|
85
88
|
"effect": ">=4.0.0-rc.118",
|
|
86
89
|
"svelte": "^5.40.0",
|
|
87
90
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
|
|
@@ -98,7 +101,7 @@
|
|
|
98
101
|
}
|
|
99
102
|
},
|
|
100
103
|
"engines": {
|
|
101
|
-
"node": "
|
|
104
|
+
"node": ">=22.17"
|
|
102
105
|
},
|
|
103
106
|
"scripts": {
|
|
104
107
|
"build": "vp pack",
|