@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
@@ -0,0 +1,111 @@
1
+ import { __exportAll } from "./_virtual/_rolldown/runtime.js";
2
+ import { Cause, Data, Effect, Queue, Stream } from "effect";
3
+ //#region src/realtime.ts
4
+ /**
5
+ * Client-side realtime bridges: consume a Server-Sent-Events endpoint or a
6
+ * WebSocket as an Effect `Stream`. The counterpart to `Endpoint.fromStream`
7
+ * (server) on the `@thomasfosterau/effect-sveltekit/client` `Realtime` namespace.
8
+ *
9
+ * These use the browser globals `EventSource` / `WebSocket`; both can be
10
+ * overridden (e.g. a Node polyfill, or a fake in tests) via the `implementation`
11
+ * option. The source is opened when the stream is run and closed when its scope
12
+ * closes, so a `Stream.take` / component teardown tears down the connection.
13
+ */
14
+ var realtime_exports = /* @__PURE__ */ __exportAll({
15
+ EventSourceError: () => EventSourceError,
16
+ WebSocketError: () => WebSocketError,
17
+ fromEventSource: () => fromEventSource,
18
+ fromWebSocket: () => fromWebSocket
19
+ });
20
+ /** Raised when an `EventSource` connection fails fatally (its `readyState` is `CLOSED`). */
21
+ var EventSourceError = class extends Data.TaggedError("EventSourceError") {};
22
+ /**
23
+ * Consume a Server-Sent-Events endpoint as a `Stream` of {@link SseMessage}.
24
+ *
25
+ * The browser's `EventSource` reconnects automatically after transient drops,
26
+ * so the stream stays live across reconnects; it fails with
27
+ * {@link EventSourceError} only when the connection closes fatally.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * import { Realtime } from "@thomasfosterau/effect-sveltekit/client";
32
+ * import { Effect, Stream } from "effect";
33
+ *
34
+ * const program = Realtime.fromEventSource("/events", { events: ["tick"] }).pipe(
35
+ * Stream.runForEach((msg) => Effect.log(`${msg.event}: ${msg.data}`)),
36
+ * );
37
+ * ```
38
+ */
39
+ const fromEventSource = (url, options) => {
40
+ const Impl = options?.implementation ?? globalThis.EventSource;
41
+ return Stream.callback((queue) => Effect.sync(() => {
42
+ const source = options?.withCredentials === void 0 ? new Impl(url) : new Impl(url, { withCredentials: options.withCredentials });
43
+ const onMessage = (e) => {
44
+ Queue.offerUnsafe(queue, {
45
+ event: e.type,
46
+ data: e.data,
47
+ lastEventId: e.lastEventId
48
+ });
49
+ };
50
+ source.addEventListener("message", onMessage);
51
+ for (const name of options?.events ?? []) source.addEventListener(name, onMessage);
52
+ source.addEventListener("error", () => {
53
+ if (source.readyState === 2) Queue.failCauseUnsafe(queue, Cause.fail(new EventSourceError({ url })));
54
+ });
55
+ return Effect.sync(() => source.close());
56
+ }), {
57
+ bufferSize: options?.bufferSize ?? 16,
58
+ strategy: options?.strategy ?? "sliding"
59
+ });
60
+ };
61
+ /** Raised when a `WebSocket` errors or closes uncleanly. */
62
+ var WebSocketError = class extends Data.TaggedError("WebSocketError") {};
63
+ /**
64
+ * Consume a `WebSocket` as a `Stream` of its message payloads
65
+ * (`string | ArrayBuffer | Blob`, depending on `binaryType`).
66
+ *
67
+ * The stream ends when the socket closes cleanly and fails with
68
+ * {@link WebSocketError} on an error or unclean close. The socket is closed when
69
+ * the stream's scope closes.
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * import { Realtime } from "@thomasfosterau/effect-sveltekit/client";
74
+ * import { Effect, Stream } from "effect";
75
+ *
76
+ * const program = Realtime.fromWebSocket("wss://example.com/ws").pipe(
77
+ * Stream.runForEach((data) => Effect.log(String(data))),
78
+ * );
79
+ * ```
80
+ */
81
+ const fromWebSocket = (url, options) => {
82
+ const Impl = options?.implementation ?? globalThis.WebSocket;
83
+ return Stream.callback((queue) => Effect.sync(() => {
84
+ const socket = new Impl(url, options?.protocols);
85
+ if (options?.binaryType !== void 0) socket.binaryType = options.binaryType;
86
+ socket.addEventListener("message", (e) => {
87
+ Queue.offerUnsafe(queue, e.data);
88
+ });
89
+ socket.addEventListener("error", () => {
90
+ Queue.failCauseUnsafe(queue, Cause.fail(new WebSocketError({ url })));
91
+ });
92
+ socket.addEventListener("close", (e) => {
93
+ if (e.wasClean) Queue.endUnsafe(queue);
94
+ else Queue.failCauseUnsafe(queue, Cause.fail(new WebSocketError({
95
+ url,
96
+ code: e.code,
97
+ reason: e.reason
98
+ })));
99
+ });
100
+ return Effect.sync(() => {
101
+ if (socket.readyState === 0 || socket.readyState === 1) socket.close();
102
+ });
103
+ }), {
104
+ bufferSize: options?.bufferSize ?? 16,
105
+ strategy: options?.strategy ?? "suspend"
106
+ });
107
+ };
108
+ //#endregion
109
+ export { realtime_exports };
110
+
111
+ //# sourceMappingURL=realtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime.js","names":[],"sources":["../src/realtime.ts"],"sourcesContent":["/**\n * Client-side realtime bridges: consume a Server-Sent-Events endpoint or a\n * WebSocket as an Effect `Stream`. The counterpart to `Endpoint.fromStream`\n * (server) on the `@thomasfosterau/effect-sveltekit/client` `Realtime` namespace.\n *\n * These use the browser globals `EventSource` / `WebSocket`; both can be\n * overridden (e.g. a Node polyfill, or a fake in tests) via the `implementation`\n * option. The source is opened when the stream is run and closed when its scope\n * closes, so a `Stream.take` / component teardown tears down the connection.\n */\n\nimport { Cause, Data, Effect, Queue, Stream } from \"effect\";\n\n// ---------------------------------------------------------------------------\n// EventSource\n// ---------------------------------------------------------------------------\n\n/** A decoded Server-Sent Event. */\nexport interface SseMessage {\n /** The event name (`\"message\"` for unnamed events). */\n readonly event: string;\n /** The event payload (the joined `data:` lines). */\n readonly data: string;\n /** The last event id seen, or `\"\"` if none. */\n readonly lastEventId: string;\n}\n\n/** Raised when an `EventSource` connection fails fatally (its `readyState` is `CLOSED`). */\nexport class EventSourceError extends Data.TaggedError(\"EventSourceError\")<{\n readonly url: string;\n}> {}\n\n/** The subset of the `EventSource` constructor this module relies on. */\nexport type EventSourceConstructor = new (\n url: string,\n eventSourceInitDict?: { withCredentials?: boolean },\n) => EventSource;\n\nexport interface FromEventSourceOptions {\n /** Send credentials (cookies) with the request. Defaults to `false`. */\n readonly withCredentials?: boolean;\n /**\n * Named events to listen for in addition to the default unnamed `message`\n * event. An `Endpoint.fromStream({ event: \"tick\" })` server needs\n * `events: [\"tick\"]` here.\n */\n readonly events?: ReadonlyArray<string>;\n /** Buffer size for the backing queue. Defaults to 16. */\n readonly bufferSize?: number;\n /** Backpressure strategy when the buffer is full. Defaults to `\"sliding\"`. */\n readonly strategy?: \"sliding\" | \"dropping\" | \"suspend\";\n /** Override the `EventSource` constructor (Node polyfill, tests). */\n readonly implementation?: EventSourceConstructor;\n}\n\n/**\n * Consume a Server-Sent-Events endpoint as a `Stream` of {@link SseMessage}.\n *\n * The browser's `EventSource` reconnects automatically after transient drops,\n * so the stream stays live across reconnects; it fails with\n * {@link EventSourceError} only when the connection closes fatally.\n *\n * @example\n * ```ts\n * import { Realtime } from \"@thomasfosterau/effect-sveltekit/client\";\n * import { Effect, Stream } from \"effect\";\n *\n * const program = Realtime.fromEventSource(\"/events\", { events: [\"tick\"] }).pipe(\n * Stream.runForEach((msg) => Effect.log(`${msg.event}: ${msg.data}`)),\n * );\n * ```\n */\nexport const fromEventSource = (\n url: string,\n options?: FromEventSourceOptions,\n): Stream.Stream<SseMessage, EventSourceError> => {\n const Impl = options?.implementation ?? (globalThis.EventSource as EventSourceConstructor);\n return Stream.callback<SseMessage, EventSourceError>(\n (queue) =>\n Effect.sync(() => {\n const source =\n options?.withCredentials === undefined\n ? new Impl(url)\n : new Impl(url, { withCredentials: options.withCredentials });\n\n const onMessage = (e: MessageEvent): void => {\n Queue.offerUnsafe(queue, { event: e.type, data: e.data, lastEventId: e.lastEventId });\n };\n\n source.addEventListener(\"message\", onMessage);\n for (const name of options?.events ?? []) source.addEventListener(name, onMessage);\n\n source.addEventListener(\"error\", () => {\n // EventSource reconnects on its own unless it has closed for good.\n if (source.readyState === 2 /* CLOSED */) {\n Queue.failCauseUnsafe(queue, Cause.fail(new EventSourceError({ url })));\n }\n });\n\n return Effect.sync(() => source.close());\n }),\n { bufferSize: options?.bufferSize ?? 16, strategy: options?.strategy ?? \"sliding\" },\n );\n};\n\n// ---------------------------------------------------------------------------\n// WebSocket\n// ---------------------------------------------------------------------------\n\n/** Raised when a `WebSocket` errors or closes uncleanly. */\nexport class WebSocketError extends Data.TaggedError(\"WebSocketError\")<{\n readonly url: string;\n readonly code?: number;\n readonly reason?: string;\n}> {}\n\n/** The subset of the `WebSocket` constructor this module relies on. */\nexport type WebSocketConstructor = new (\n url: string,\n protocols?: string | ReadonlyArray<string>,\n) => WebSocket;\n\nexport interface FromWebSocketOptions {\n /** Sub-protocols passed to the `WebSocket` constructor. */\n readonly protocols?: string | ReadonlyArray<string>;\n /** `arraybuffer` or `blob` for binary frames. Defaults to the platform default. */\n readonly binaryType?: BinaryType;\n /** Buffer size for the backing queue. Defaults to 16. */\n readonly bufferSize?: number;\n /** Backpressure strategy when the buffer is full. Defaults to `\"suspend\"`. */\n readonly strategy?: \"sliding\" | \"dropping\" | \"suspend\";\n /** Override the `WebSocket` constructor (Node polyfill, tests). */\n readonly implementation?: WebSocketConstructor;\n}\n\n/**\n * Consume a `WebSocket` as a `Stream` of its message payloads\n * (`string | ArrayBuffer | Blob`, depending on `binaryType`).\n *\n * The stream ends when the socket closes cleanly and fails with\n * {@link WebSocketError} on an error or unclean close. The socket is closed when\n * the stream's scope closes.\n *\n * @example\n * ```ts\n * import { Realtime } from \"@thomasfosterau/effect-sveltekit/client\";\n * import { Effect, Stream } from \"effect\";\n *\n * const program = Realtime.fromWebSocket(\"wss://example.com/ws\").pipe(\n * Stream.runForEach((data) => Effect.log(String(data))),\n * );\n * ```\n */\nexport const fromWebSocket = (\n url: string,\n options?: FromWebSocketOptions,\n): Stream.Stream<string | ArrayBuffer | Blob, WebSocketError> => {\n const Impl = options?.implementation ?? (globalThis.WebSocket as WebSocketConstructor);\n return Stream.callback<string | ArrayBuffer | Blob, WebSocketError>(\n (queue) =>\n Effect.sync(() => {\n const socket = new Impl(url, options?.protocols);\n if (options?.binaryType !== undefined) socket.binaryType = options.binaryType;\n\n socket.addEventListener(\"message\", (e: MessageEvent) => {\n Queue.offerUnsafe(queue, e.data);\n });\n\n socket.addEventListener(\"error\", () => {\n Queue.failCauseUnsafe(queue, Cause.fail(new WebSocketError({ url })));\n });\n\n socket.addEventListener(\"close\", (e: CloseEvent) => {\n if (e.wasClean) {\n Queue.endUnsafe(queue);\n } else {\n Queue.failCauseUnsafe(\n queue,\n Cause.fail(new WebSocketError({ url, code: e.code, reason: e.reason })),\n );\n }\n });\n\n return Effect.sync(() => {\n // Only close a socket that is still connecting/open.\n if (socket.readyState === 0 || socket.readyState === 1) socket.close();\n });\n }),\n { bufferSize: options?.bufferSize ?? 16, strategy: options?.strategy ?? \"suspend\" },\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA4BA,IAAa,mBAAb,cAAsC,KAAK,YAAY,kBAAkB,EAEtE,CAAC;;;;;;;;;;;;;;;;;;AA0CJ,MAAa,mBACX,KACA,YACgD;CAChD,MAAM,OAAO,SAAS,kBAAmB,WAAW;CACpD,OAAO,OAAO,UACX,UACC,OAAO,WAAW;EAChB,MAAM,SACJ,SAAS,oBAAoB,KAAA,IACzB,IAAI,KAAK,GAAG,IACZ,IAAI,KAAK,KAAK,EAAE,iBAAiB,QAAQ,gBAAgB,CAAC;EAEhE,MAAM,aAAa,MAA0B;GAC3C,MAAM,YAAY,OAAO;IAAE,OAAO,EAAE;IAAM,MAAM,EAAE;IAAM,aAAa,EAAE;GAAY,CAAC;EACtF;EAEA,OAAO,iBAAiB,WAAW,SAAS;EAC5C,KAAK,MAAM,QAAQ,SAAS,UAAU,CAAC,GAAG,OAAO,iBAAiB,MAAM,SAAS;EAEjF,OAAO,iBAAiB,eAAe;GAErC,IAAI,OAAO,eAAe,GACxB,MAAM,gBAAgB,OAAO,MAAM,KAAK,IAAI,iBAAiB,EAAE,IAAI,CAAC,CAAC,CAAC;EAE1E,CAAC;EAED,OAAO,OAAO,WAAW,OAAO,MAAM,CAAC;CACzC,CAAC,GACH;EAAE,YAAY,SAAS,cAAc;EAAI,UAAU,SAAS,YAAY;CAAU,CACpF;AACF;;AAOA,IAAa,iBAAb,cAAoC,KAAK,YAAY,gBAAgB,EAIlE,CAAC;;;;;;;;;;;;;;;;;;;AAuCJ,MAAa,iBACX,KACA,YAC+D;CAC/D,MAAM,OAAO,SAAS,kBAAmB,WAAW;CACpD,OAAO,OAAO,UACX,UACC,OAAO,WAAW;EAChB,MAAM,SAAS,IAAI,KAAK,KAAK,SAAS,SAAS;EAC/C,IAAI,SAAS,eAAe,KAAA,GAAW,OAAO,aAAa,QAAQ;EAEnE,OAAO,iBAAiB,YAAY,MAAoB;GACtD,MAAM,YAAY,OAAO,EAAE,IAAI;EACjC,CAAC;EAED,OAAO,iBAAiB,eAAe;GACrC,MAAM,gBAAgB,OAAO,MAAM,KAAK,IAAI,eAAe,EAAE,IAAI,CAAC,CAAC,CAAC;EACtE,CAAC;EAED,OAAO,iBAAiB,UAAU,MAAkB;GAClD,IAAI,EAAE,UACJ,MAAM,UAAU,KAAK;QAErB,MAAM,gBACJ,OACA,MAAM,KAAK,IAAI,eAAe;IAAE;IAAK,MAAM,EAAE;IAAM,QAAQ,EAAE;GAAO,CAAC,CAAC,CACxE;EAEJ,CAAC;EAED,OAAO,OAAO,WAAW;GAEvB,IAAI,OAAO,eAAe,KAAK,OAAO,eAAe,GAAG,OAAO,MAAM;EACvE,CAAC;CACH,CAAC,GACH;EAAE,YAAY,SAAS,cAAc;EAAI,UAAU,SAAS,YAAY;CAAU,CACpF;AACF"}
@@ -27,11 +27,9 @@ interface QueryLiveFn<Env> {
27
27
  }
28
28
  interface FormFn<Env> {
29
29
  <Output>(fn: () => Effect.Effect<Output, unknown, Env>): RemoteForm<void, Output>;
30
- <Input extends RemoteFormInput, Output>(validate: "unchecked", fn: (data: Input, issue: InvalidField<Input>) => Effect.Effect<Output, unknown, Env>): RemoteForm<Input, Output>;
31
- <Sch extends StandardSchemaV1<RemoteFormInput, Record<string, any>>, Output>(validate: Sch, fn: (data: StandardSchemaV1.InferOutput<Sch>, issue: InvalidField<StandardSchemaV1.InferInput<Sch>>) => Effect.Effect<Output, unknown, Env>): RemoteForm<StandardSchemaV1.InferInput<Sch>, Output>;
32
- <S extends Schema.Top & {
33
- readonly Encoded: RemoteFormInput;
34
- }, Output>(validate: S, fn: (data: S["Type"], issue: InvalidField<S["Encoded"]>) => Effect.Effect<Output, unknown, Env>): RemoteForm<S["Encoded"], Output>;
30
+ <Input, Output>(validate: "unchecked", fn: (data: Input, issue: InvalidField<Input & RemoteFormInput>) => Effect.Effect<Output, unknown, Env>): RemoteForm<Input & RemoteFormInput, Output>;
31
+ <Sch extends StandardSchemaV1, Output>(validate: Sch, fn: (data: StandardSchemaV1.InferOutput<Sch>, issue: InvalidField<StandardSchemaV1.InferInput<Sch> & RemoteFormInput>) => Effect.Effect<Output, unknown, Env>): RemoteForm<StandardSchemaV1.InferInput<Sch> & RemoteFormInput, Output>;
32
+ <S extends Schema.Top, Output>(validate: S, fn: (data: S["Type"], issue: InvalidField<S["Encoded"] & RemoteFormInput>) => Effect.Effect<Output, unknown, Env>): RemoteForm<S["Encoded"] & RemoteFormInput, Output>;
35
33
  }
36
34
  interface CommandFn<Env> {
37
35
  <Output>(fn: () => Effect.Effect<Output, unknown, Env>): RemoteCommand<void, Output>;
@@ -1 +1 @@
1
- {"version":3,"file":"remote.server.js","names":["isBareHandlerValue"],"sources":["../src/remote.server.ts"],"sourcesContent":["import * as AppServer from \"$app/server\";\nimport type {\n RemoteCommand,\n RemoteForm,\n RemoteFormInput,\n RemotePrerenderFunction,\n RemoteQueryFunction,\n InvalidField,\n} from \"@sveltejs/kit\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport * as HttpClient from \"effect/unstable/http/HttpClient\";\nimport { Effect, type Schema } from \"effect\";\nimport { SvelteKitRuntime, SvelteKitRequestEvent } from \"./runtime.js\";\nimport {\n isBareHandler as isBareHandlerValue,\n toStandardValidator,\n} from \"./internal/remote-validators.js\";\nimport {\n defaultTranslate,\n makeRunHelpers,\n type FormErrorMapper,\n type RunHelpers,\n type Runner,\n} from \"./internal/remote-run.js\";\nimport type { ErrorTranslator, RequestRuntimeProvider } from \"./request-runtime.js\";\n\nexport type { FormErrorMapper } from \"./internal/remote-run.js\";\n\ntype SvelteKitEffectEnv = HttpClient.HttpClient | SvelteKitRequestEvent;\n\n/** The default helpers: a fresh SvelteKit per-request runtime, passthrough translation. */\nconst defaultHelpers: RunHelpers = makeRunHelpers(() => {\n const event = AppServer.getRequestEvent();\n return { runExit: (effect) => SvelteKitRuntime.forRequest(event).runPromiseExit(effect) };\n}, defaultTranslate);\n\n/** Build helpers from a {@link RequestRuntimeProvider}, translator, and form-error mapper. */\nconst helpersFromProvider = (\n provider: RequestRuntimeProvider<any, any>,\n translate: ErrorTranslator,\n formError?: FormErrorMapper,\n): RunHelpers =>\n makeRunHelpers(\n () => {\n const event = AppServer.getRequestEvent();\n return {\n runExit: (effect) => provider.run(event).runPromiseExit(effect),\n dispose: () => provider.disposeAfterResponse?.(event),\n };\n },\n translate,\n formError,\n );\n\nfunction unavailable(name: string): never {\n throw new Error(\n `${name} is not available in the installed SvelteKit version. Update @sveltejs/kit to use this API.`,\n );\n}\n\ntype EffectHandler<Env> = (arg: unknown) => Effect.Effect<unknown, unknown, Env>;\ntype EffectFormHandler<Env> = (\n data: unknown,\n issue: unknown,\n) => Effect.Effect<unknown, unknown, Env>;\ntype EffectVoidHandler<Env> = () => Effect.Effect<unknown, unknown, Env>;\n\n/** True when the argument is the no-validation handler overload (typed narrowing). */\nconst isBareHandler = <Env>(value: unknown): value is EffectVoidHandler<Env> =>\n isBareHandlerValue(value);\n\n/** The public shape of the `query` helper, parameterised by its handler env. */\nexport interface QueryFn<Env> {\n <Output>(fn: () => Effect.Effect<Output, unknown, Env>): RemoteQueryFunction<void, Output>;\n <Input, Output>(\n validate: \"unchecked\",\n fn: (arg: Input) => Effect.Effect<Output, unknown, Env>,\n ): RemoteQueryFunction<Input, Output>;\n <Sch extends StandardSchemaV1, Output>(\n schema: Sch,\n fn: (arg: StandardSchemaV1.InferOutput<Sch>) => Effect.Effect<Output, unknown, Env>,\n ): RemoteQueryFunction<StandardSchemaV1.InferInput<Sch>, Output>;\n <S extends Schema.Top, Output>(\n schema: S,\n fn: (arg: S[\"Type\"]) => Effect.Effect<Output, unknown, Env>,\n ): RemoteQueryFunction<S[\"Encoded\"], Output>;\n}\n\nconst makeQuery = <Env>(run: Runner): QueryFn<Env> =>\n ((arg1: unknown, arg2?: (arg: unknown) => Effect.Effect<unknown, unknown, Env>): any => {\n if (isBareHandler<Env>(arg1)) {\n const handler = arg1;\n return AppServer.query(() => run(handler()));\n }\n\n if (!arg2) {\n throw new Error(\"query requires a handler function when a schema is provided\");\n }\n\n return AppServer.query(toStandardValidator(arg1) as \"unchecked\", (arg: unknown) =>\n run(arg2(arg)),\n );\n }) as QueryFn<Env>;\n\nexport interface QueryBatchFn<Env> {\n <Input, Output>(\n validate: \"unchecked\",\n fn: (args: Input[]) => Effect.Effect<(arg: Input, idx: number) => Output, unknown, Env>,\n ): RemoteQueryFunction<Input, Output>;\n <Sch extends StandardSchemaV1, Output>(\n schema: Sch,\n fn: (\n args: StandardSchemaV1.InferOutput<Sch>[],\n ) => Effect.Effect<\n (arg: StandardSchemaV1.InferOutput<Sch>, idx: number) => Output,\n unknown,\n Env\n >,\n ): RemoteQueryFunction<StandardSchemaV1.InferInput<Sch>, Output>;\n <S extends Schema.Top, Output>(\n schema: S,\n fn: (args: S[\"Type\"][]) => Effect.Effect<(arg: S[\"Type\"], idx: number) => Output, unknown, Env>,\n ): RemoteQueryFunction<S[\"Encoded\"], Output>;\n}\n\nconst makeQueryBatch = <Env>(run: Runner): QueryBatchFn<Env> =>\n ((\n validate: unknown,\n fn: (args: unknown[]) => Effect.Effect<(arg: unknown, idx: number) => unknown, unknown, Env>,\n ): any =>\n AppServer.query.batch(toStandardValidator(validate) as \"unchecked\", (args: unknown[]) =>\n run(fn(args)),\n )) as QueryBatchFn<Env>;\n\ntype QueryLiveFactory = (\n validate: unknown,\n fn: (arg: unknown) => Promise<AsyncIterable<unknown>> | AsyncIterable<unknown>,\n) => unknown;\n\ntype QueryWithCompat = typeof AppServer.query & {\n live?: QueryLiveFactory;\n};\n\ntype AppServerWithCompat = typeof AppServer & {\n requested?: (...args: unknown[]) => unknown;\n};\n\nconst appServerCompat = AppServer as AppServerWithCompat;\nconst queryCompat = AppServer.query as QueryWithCompat;\n\nfunction getQueryLive(): QueryLiveFactory {\n const live = queryCompat.live;\n\n if (typeof live !== \"function\") {\n unavailable(\"query.live\");\n }\n\n return live;\n}\n\nfunction getRequested(): (...args: unknown[]) => unknown {\n const requestedFn = appServerCompat.requested;\n\n if (typeof requestedFn !== \"function\") {\n unavailable(\"requested\");\n }\n\n return requestedFn;\n}\n\nexport interface QueryLiveFn<Env> {\n <Input, Output>(\n validate: \"unchecked\",\n fn: (arg: Input) => Effect.Effect<AsyncIterable<Output>, unknown, Env>,\n ): RemoteQueryFunction<Input, Output>;\n <Sch extends StandardSchemaV1, Output>(\n schema: Sch,\n fn: (\n arg: StandardSchemaV1.InferOutput<Sch>,\n ) => Effect.Effect<AsyncIterable<Output>, unknown, Env>,\n ): RemoteQueryFunction<StandardSchemaV1.InferInput<Sch>, Output>;\n <S extends Schema.Top, Output>(\n schema: S,\n fn: (arg: S[\"Type\"]) => Effect.Effect<AsyncIterable<Output>, unknown, Env>,\n ): RemoteQueryFunction<S[\"Encoded\"], Output>;\n}\n\nconst makeQueryLive = <Env>(run: Runner): QueryLiveFn<Env> =>\n ((\n validate: unknown,\n fn: (arg: unknown) => Effect.Effect<AsyncIterable<unknown>, unknown, Env>,\n ): any => {\n const live = getQueryLive();\n return live(toStandardValidator(validate), (arg) => run(fn(arg)));\n }) as QueryLiveFn<Env>;\n\nexport interface FormFn<Env> {\n <Output>(fn: () => Effect.Effect<Output, unknown, Env>): RemoteForm<void, Output>;\n <Input extends RemoteFormInput, Output>(\n validate: \"unchecked\",\n fn: (data: Input, issue: InvalidField<Input>) => Effect.Effect<Output, unknown, Env>,\n ): RemoteForm<Input, Output>;\n <Sch extends StandardSchemaV1<RemoteFormInput, Record<string, any>>, Output>(\n validate: Sch,\n fn: (\n data: StandardSchemaV1.InferOutput<Sch>,\n issue: InvalidField<StandardSchemaV1.InferInput<Sch>>,\n ) => Effect.Effect<Output, unknown, Env>,\n ): RemoteForm<StandardSchemaV1.InferInput<Sch>, Output>;\n <S extends Schema.Top & { readonly Encoded: RemoteFormInput }, Output>(\n validate: S,\n fn: (data: S[\"Type\"], issue: InvalidField<S[\"Encoded\"]>) => Effect.Effect<Output, unknown, Env>,\n ): RemoteForm<S[\"Encoded\"], Output>;\n}\n\nconst makeForm = <Env>(helpers: RunHelpers): FormFn<Env> =>\n ((arg1: unknown, arg2?: unknown): any => {\n if (isBareHandler<Env>(arg1)) {\n const handler = arg1;\n return AppServer.form(() => helpers.run(handler()));\n }\n\n if (!arg2) {\n throw new Error(\"form requires a handler function when a schema is provided\");\n }\n\n return AppServer.form(\n toStandardValidator(arg1) as \"unchecked\",\n (data: unknown, issue: unknown) =>\n helpers.runForm((arg2 as EffectFormHandler<Env>)(data, issue), {\n issue: issue as InvalidField<any>,\n data,\n }),\n );\n }) as FormFn<Env>;\n\nexport interface CommandFn<Env> {\n <Output>(fn: () => Effect.Effect<Output, unknown, Env>): RemoteCommand<void, Output>;\n <Input, Output>(\n validate: \"unchecked\",\n fn: (arg: Input) => Effect.Effect<Output, unknown, Env>,\n ): RemoteCommand<Input, Output>;\n <Sch extends StandardSchemaV1, Output>(\n validate: Sch,\n fn: (arg: StandardSchemaV1.InferOutput<Sch>) => Effect.Effect<Output, unknown, Env>,\n ): RemoteCommand<StandardSchemaV1.InferInput<Sch>, Output>;\n <S extends Schema.Top, Output>(\n validate: S,\n fn: (arg: S[\"Type\"]) => Effect.Effect<Output, unknown, Env>,\n ): RemoteCommand<S[\"Encoded\"], Output>;\n}\n\nconst makeCommand = <Env>(run: Runner): CommandFn<Env> =>\n ((arg1: unknown, arg2?: (arg: unknown) => Effect.Effect<unknown, unknown, Env>): any => {\n if (isBareHandler<Env>(arg1)) {\n const handler = arg1;\n return AppServer.command(() => run(handler()));\n }\n\n if (!arg2) {\n throw new Error(\"command requires a handler function when a schema is provided\");\n }\n\n return AppServer.command(toStandardValidator(arg1) as \"unchecked\", (arg: unknown) =>\n run(arg2(arg)),\n );\n }) as CommandFn<Env>;\n\nexport interface PrerenderFn<Env> {\n <Output>(\n fn: () => Effect.Effect<Output, unknown, Env>,\n options?: {\n inputs?: () => Promise<void[]> | void[];\n dynamic?: boolean;\n },\n ): RemotePrerenderFunction<void, Output>;\n <Input, Output>(\n validate: \"unchecked\",\n fn: (arg: Input) => Effect.Effect<Output, unknown, Env>,\n options?: {\n inputs?: () => Promise<Input[]> | Input[];\n dynamic?: boolean;\n },\n ): RemotePrerenderFunction<Input, Output>;\n <Sch extends StandardSchemaV1, Output>(\n schema: Sch,\n fn: (arg: StandardSchemaV1.InferOutput<Sch>) => Effect.Effect<Output, unknown, Env>,\n options?: {\n inputs?: () =>\n | Promise<StandardSchemaV1.InferInput<Sch>[]>\n | StandardSchemaV1.InferInput<Sch>[];\n dynamic?: boolean;\n },\n ): RemotePrerenderFunction<StandardSchemaV1.InferInput<Sch>, Output>;\n <S extends Schema.Top, Output>(\n schema: S,\n fn: (arg: S[\"Type\"]) => Effect.Effect<Output, unknown, Env>,\n options?: {\n inputs?: () => Promise<S[\"Encoded\"][]> | S[\"Encoded\"][];\n dynamic?: boolean;\n },\n ): RemotePrerenderFunction<S[\"Encoded\"], Output>;\n}\n\nconst makePrerender = <Env>(run: Runner): PrerenderFn<Env> =>\n ((\n arg1: unknown,\n arg2?:\n | ((arg: unknown) => Effect.Effect<unknown, unknown, Env>)\n | {\n inputs?: () => Promise<unknown[]> | unknown[];\n dynamic?: boolean;\n },\n arg3?: {\n inputs?: () => Promise<unknown[]> | unknown[];\n dynamic?: boolean;\n },\n ): any => {\n if (isBareHandler<Env>(arg1)) {\n const handler = arg1;\n return AppServer.prerender(\n () => run(handler()),\n arg2 as { inputs?: () => Promise<void[]> | void[]; dynamic?: boolean } | undefined,\n );\n }\n\n if (typeof arg2 !== \"function\") {\n throw new Error(\"prerender requires a handler function when a schema is provided\");\n }\n\n return AppServer.prerender(\n toStandardValidator(arg1) as \"unchecked\",\n (arg: unknown) => run((arg2 as EffectHandler<Env>)(arg)),\n arg3,\n );\n }) as PrerenderFn<Env>;\n\nexport function requested<Result = unknown>(...args: unknown[]): Result {\n const requestedFn = getRequested();\n return requestedFn(...args) as Result;\n}\n\n/** A `query` with its `batch` / `live` helpers attached. */\nexport type QueryNamespace<Env> = QueryFn<Env> & {\n batch: QueryBatchFn<Env>;\n live: QueryLiveFn<Env>;\n};\n\nconst makeQueryNamespace = <Env>(run: Runner): QueryNamespace<Env> =>\n Object.assign(makeQuery<Env>(run), {\n batch: makeQueryBatch<Env>(run),\n live: makeQueryLive<Env>(run),\n }) as QueryNamespace<Env>;\n\n// ── Standalone exports (default SvelteKit per-request runtime) ──────────────\n\nexport const query: QueryNamespace<SvelteKitEffectEnv> = makeQueryNamespace(defaultHelpers.run);\nexport const queryBatch: QueryBatchFn<SvelteKitEffectEnv> = query.batch;\nexport const queryLive: QueryLiveFn<SvelteKitEffectEnv> = query.live;\nexport const form: FormFn<SvelteKitEffectEnv> = makeForm(defaultHelpers);\nexport const command: CommandFn<SvelteKitEffectEnv> = makeCommand(defaultHelpers.run);\nexport const prerender: PrerenderFn<SvelteKitEffectEnv> = makePrerender(defaultHelpers.run);\n\n/** The set of remote-function helpers bound to a particular runner. */\nexport interface RemoteFunctions<Env> {\n readonly query: QueryNamespace<Env>;\n readonly form: FormFn<Env>;\n readonly command: CommandFn<Env>;\n readonly prerender: PrerenderFn<Env>;\n}\n\n/**\n * Bind the remote-function helpers to a {@link RequestRuntimeProvider} (and an\n * optional boundary error translator), so their handlers run on the provider's\n * services `Env` — your own application catalogue — instead of the fixed\n * {@link SvelteKitServices}. The Standard-Schema / `\"unchecked\"` validation\n * argument and the native return types (`RemoteQueryFunction`, `RemoteForm`,\n * ...) are exactly as the standalone helpers.\n *\n * For remote forms, the failure path runs `opts.formError` when provided —\n * mapping a structured/nested error shape into SvelteKit's field issues (via the\n * `issue` helper / `invalid()`) so a domain validation failure drives\n * field-level invalidation rather than a generic failure. Otherwise the form\n * falls back to `translateError`.\n *\n * @example\n * ```ts\n * const { query, form, command } = RemoteFunction.withRuntime(\n * RequestRuntime.fromLocals((event) => event.locals.runtime),\n * {\n * translateError: toSvelteKit,\n * formError: (error, { issue }) => {\n * if (error instanceof ValidationFailure) issue.field(error.field, error.message);\n * invalid();\n * },\n * },\n * );\n *\n * export const items = query(() =>\n * Effect.gen(function* () {\n * const db = yield* Database;\n * return yield* db.list();\n * }),\n * );\n * ```\n */\nexport function withRuntime<R>(\n provider: RequestRuntimeProvider<R, any>,\n opts?: { translateError?: ErrorTranslator; formError?: FormErrorMapper },\n): RemoteFunctions<R> {\n const helpers = helpersFromProvider(\n provider,\n opts?.translateError ?? defaultTranslate,\n opts?.formError,\n );\n return {\n query: makeQueryNamespace<R>(helpers.run),\n form: makeForm<R>(helpers),\n command: makeCommand<R>(helpers.run),\n prerender: makePrerender<R>(helpers.run),\n };\n}\n\nexport const RemoteFunction = {\n query,\n form,\n command,\n prerender,\n requested,\n withRuntime,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"remote.server.js","names":["isBareHandlerValue"],"sources":["../src/remote.server.ts"],"sourcesContent":["import * as AppServer from \"$app/server\";\nimport type {\n RemoteCommand,\n RemoteForm,\n RemoteFormInput,\n RemotePrerenderFunction,\n RemoteQueryFunction,\n InvalidField,\n} from \"@sveltejs/kit\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport * as HttpClient from \"effect/unstable/http/HttpClient\";\nimport { Effect, type Schema } from \"effect\";\nimport { SvelteKitRuntime, SvelteKitRequestEvent } from \"./runtime.js\";\nimport {\n isBareHandler as isBareHandlerValue,\n toStandardValidator,\n} from \"./internal/remote-validators.js\";\nimport {\n defaultTranslate,\n makeRunHelpers,\n type FormErrorMapper,\n type RunHelpers,\n type Runner,\n} from \"./internal/remote-run.js\";\nimport type { ErrorTranslator, RequestRuntimeProvider } from \"./request-runtime.js\";\n\nexport type { FormErrorMapper } from \"./internal/remote-run.js\";\n\ntype SvelteKitEffectEnv = HttpClient.HttpClient | SvelteKitRequestEvent;\n\n/** The default helpers: a fresh SvelteKit per-request runtime, passthrough translation. */\nconst defaultHelpers: RunHelpers = makeRunHelpers(() => {\n const event = AppServer.getRequestEvent();\n return { runExit: (effect) => SvelteKitRuntime.forRequest(event).runPromiseExit(effect) };\n}, defaultTranslate);\n\n/** Build helpers from a {@link RequestRuntimeProvider}, translator, and form-error mapper. */\nconst helpersFromProvider = (\n provider: RequestRuntimeProvider<any, any>,\n translate: ErrorTranslator,\n formError?: FormErrorMapper,\n): RunHelpers =>\n makeRunHelpers(\n () => {\n const event = AppServer.getRequestEvent();\n return {\n runExit: (effect) => provider.run(event).runPromiseExit(effect),\n dispose: () => provider.disposeAfterResponse?.(event),\n };\n },\n translate,\n formError,\n );\n\nfunction unavailable(name: string): never {\n throw new Error(\n `${name} is not available in the installed SvelteKit version. Update @sveltejs/kit to use this API.`,\n );\n}\n\ntype EffectHandler<Env> = (arg: unknown) => Effect.Effect<unknown, unknown, Env>;\ntype EffectFormHandler<Env> = (\n data: unknown,\n issue: unknown,\n) => Effect.Effect<unknown, unknown, Env>;\ntype EffectVoidHandler<Env> = () => Effect.Effect<unknown, unknown, Env>;\n\n/** True when the argument is the no-validation handler overload (typed narrowing). */\nconst isBareHandler = <Env>(value: unknown): value is EffectVoidHandler<Env> =>\n isBareHandlerValue(value);\n\n/** The public shape of the `query` helper, parameterised by its handler env. */\nexport interface QueryFn<Env> {\n <Output>(fn: () => Effect.Effect<Output, unknown, Env>): RemoteQueryFunction<void, Output>;\n <Input, Output>(\n validate: \"unchecked\",\n fn: (arg: Input) => Effect.Effect<Output, unknown, Env>,\n ): RemoteQueryFunction<Input, Output>;\n <Sch extends StandardSchemaV1, Output>(\n schema: Sch,\n fn: (arg: StandardSchemaV1.InferOutput<Sch>) => Effect.Effect<Output, unknown, Env>,\n ): RemoteQueryFunction<StandardSchemaV1.InferInput<Sch>, Output>;\n <S extends Schema.Top, Output>(\n schema: S,\n fn: (arg: S[\"Type\"]) => Effect.Effect<Output, unknown, Env>,\n ): RemoteQueryFunction<S[\"Encoded\"], Output>;\n}\n\nconst makeQuery = <Env>(run: Runner): QueryFn<Env> =>\n ((arg1: unknown, arg2?: (arg: unknown) => Effect.Effect<unknown, unknown, Env>): any => {\n if (isBareHandler<Env>(arg1)) {\n const handler = arg1;\n return AppServer.query(() => run(handler()));\n }\n\n if (!arg2) {\n throw new Error(\"query requires a handler function when a schema is provided\");\n }\n\n return AppServer.query(toStandardValidator(arg1) as \"unchecked\", (arg: unknown) =>\n run(arg2(arg)),\n );\n }) as QueryFn<Env>;\n\nexport interface QueryBatchFn<Env> {\n <Input, Output>(\n validate: \"unchecked\",\n fn: (args: Input[]) => Effect.Effect<(arg: Input, idx: number) => Output, unknown, Env>,\n ): RemoteQueryFunction<Input, Output>;\n <Sch extends StandardSchemaV1, Output>(\n schema: Sch,\n fn: (\n args: StandardSchemaV1.InferOutput<Sch>[],\n ) => Effect.Effect<\n (arg: StandardSchemaV1.InferOutput<Sch>, idx: number) => Output,\n unknown,\n Env\n >,\n ): RemoteQueryFunction<StandardSchemaV1.InferInput<Sch>, Output>;\n <S extends Schema.Top, Output>(\n schema: S,\n fn: (args: S[\"Type\"][]) => Effect.Effect<(arg: S[\"Type\"], idx: number) => Output, unknown, Env>,\n ): RemoteQueryFunction<S[\"Encoded\"], Output>;\n}\n\nconst makeQueryBatch = <Env>(run: Runner): QueryBatchFn<Env> =>\n ((\n validate: unknown,\n fn: (args: unknown[]) => Effect.Effect<(arg: unknown, idx: number) => unknown, unknown, Env>,\n ): any =>\n AppServer.query.batch(toStandardValidator(validate) as \"unchecked\", (args: unknown[]) =>\n run(fn(args)),\n )) as QueryBatchFn<Env>;\n\ntype QueryLiveFactory = (\n validate: unknown,\n fn: (arg: unknown) => Promise<AsyncIterable<unknown>> | AsyncIterable<unknown>,\n) => unknown;\n\ntype QueryWithCompat = typeof AppServer.query & {\n live?: QueryLiveFactory;\n};\n\ntype AppServerWithCompat = typeof AppServer & {\n requested?: (...args: unknown[]) => unknown;\n};\n\nconst appServerCompat = AppServer as AppServerWithCompat;\nconst queryCompat = AppServer.query as QueryWithCompat;\n\nfunction getQueryLive(): QueryLiveFactory {\n const live = queryCompat.live;\n\n if (typeof live !== \"function\") {\n unavailable(\"query.live\");\n }\n\n return live;\n}\n\nfunction getRequested(): (...args: unknown[]) => unknown {\n const requestedFn = appServerCompat.requested;\n\n if (typeof requestedFn !== \"function\") {\n unavailable(\"requested\");\n }\n\n return requestedFn;\n}\n\nexport interface QueryLiveFn<Env> {\n <Input, Output>(\n validate: \"unchecked\",\n fn: (arg: Input) => Effect.Effect<AsyncIterable<Output>, unknown, Env>,\n ): RemoteQueryFunction<Input, Output>;\n <Sch extends StandardSchemaV1, Output>(\n schema: Sch,\n fn: (\n arg: StandardSchemaV1.InferOutput<Sch>,\n ) => Effect.Effect<AsyncIterable<Output>, unknown, Env>,\n ): RemoteQueryFunction<StandardSchemaV1.InferInput<Sch>, Output>;\n <S extends Schema.Top, Output>(\n schema: S,\n fn: (arg: S[\"Type\"]) => Effect.Effect<AsyncIterable<Output>, unknown, Env>,\n ): RemoteQueryFunction<S[\"Encoded\"], Output>;\n}\n\nconst makeQueryLive = <Env>(run: Runner): QueryLiveFn<Env> =>\n ((\n validate: unknown,\n fn: (arg: unknown) => Effect.Effect<AsyncIterable<unknown>, unknown, Env>,\n ): any => {\n const live = getQueryLive();\n return live(toStandardValidator(validate), (arg) => run(fn(arg)));\n }) as QueryLiveFn<Env>;\n\nexport interface FormFn<Env> {\n <Output>(fn: () => Effect.Effect<Output, unknown, Env>): RemoteForm<void, Output>;\n <Input, Output>(\n validate: \"unchecked\",\n fn: (\n data: Input,\n issue: InvalidField<Input & RemoteFormInput>,\n ) => Effect.Effect<Output, unknown, Env>,\n ): RemoteForm<Input & RemoteFormInput, Output>;\n <Sch extends StandardSchemaV1, Output>(\n validate: Sch,\n fn: (\n data: StandardSchemaV1.InferOutput<Sch>,\n issue: InvalidField<StandardSchemaV1.InferInput<Sch> & RemoteFormInput>,\n ) => Effect.Effect<Output, unknown, Env>,\n ): RemoteForm<StandardSchemaV1.InferInput<Sch> & RemoteFormInput, Output>;\n <S extends Schema.Top, Output>(\n validate: S,\n fn: (\n data: S[\"Type\"],\n issue: InvalidField<S[\"Encoded\"] & RemoteFormInput>,\n ) => Effect.Effect<Output, unknown, Env>,\n ): RemoteForm<S[\"Encoded\"] & RemoteFormInput, Output>;\n}\n\nconst makeForm = <Env>(helpers: RunHelpers): FormFn<Env> =>\n ((arg1: unknown, arg2?: unknown): any => {\n if (isBareHandler<Env>(arg1)) {\n const handler = arg1;\n return AppServer.form(() => helpers.run(handler()));\n }\n\n if (!arg2) {\n throw new Error(\"form requires a handler function when a schema is provided\");\n }\n\n return AppServer.form(\n toStandardValidator(arg1) as \"unchecked\",\n (data: unknown, issue: unknown) =>\n helpers.runForm((arg2 as EffectFormHandler<Env>)(data, issue), {\n issue: issue as InvalidField<any>,\n data,\n }),\n );\n }) as FormFn<Env>;\n\nexport interface CommandFn<Env> {\n <Output>(fn: () => Effect.Effect<Output, unknown, Env>): RemoteCommand<void, Output>;\n <Input, Output>(\n validate: \"unchecked\",\n fn: (arg: Input) => Effect.Effect<Output, unknown, Env>,\n ): RemoteCommand<Input, Output>;\n <Sch extends StandardSchemaV1, Output>(\n validate: Sch,\n fn: (arg: StandardSchemaV1.InferOutput<Sch>) => Effect.Effect<Output, unknown, Env>,\n ): RemoteCommand<StandardSchemaV1.InferInput<Sch>, Output>;\n <S extends Schema.Top, Output>(\n validate: S,\n fn: (arg: S[\"Type\"]) => Effect.Effect<Output, unknown, Env>,\n ): RemoteCommand<S[\"Encoded\"], Output>;\n}\n\nconst makeCommand = <Env>(run: Runner): CommandFn<Env> =>\n ((arg1: unknown, arg2?: (arg: unknown) => Effect.Effect<unknown, unknown, Env>): any => {\n if (isBareHandler<Env>(arg1)) {\n const handler = arg1;\n return AppServer.command(() => run(handler()));\n }\n\n if (!arg2) {\n throw new Error(\"command requires a handler function when a schema is provided\");\n }\n\n return AppServer.command(toStandardValidator(arg1) as \"unchecked\", (arg: unknown) =>\n run(arg2(arg)),\n );\n }) as CommandFn<Env>;\n\nexport interface PrerenderFn<Env> {\n <Output>(\n fn: () => Effect.Effect<Output, unknown, Env>,\n options?: {\n inputs?: () => Promise<void[]> | void[];\n dynamic?: boolean;\n },\n ): RemotePrerenderFunction<void, Output>;\n <Input, Output>(\n validate: \"unchecked\",\n fn: (arg: Input) => Effect.Effect<Output, unknown, Env>,\n options?: {\n inputs?: () => Promise<Input[]> | Input[];\n dynamic?: boolean;\n },\n ): RemotePrerenderFunction<Input, Output>;\n <Sch extends StandardSchemaV1, Output>(\n schema: Sch,\n fn: (arg: StandardSchemaV1.InferOutput<Sch>) => Effect.Effect<Output, unknown, Env>,\n options?: {\n inputs?: () =>\n | Promise<StandardSchemaV1.InferInput<Sch>[]>\n | StandardSchemaV1.InferInput<Sch>[];\n dynamic?: boolean;\n },\n ): RemotePrerenderFunction<StandardSchemaV1.InferInput<Sch>, Output>;\n <S extends Schema.Top, Output>(\n schema: S,\n fn: (arg: S[\"Type\"]) => Effect.Effect<Output, unknown, Env>,\n options?: {\n inputs?: () => Promise<S[\"Encoded\"][]> | S[\"Encoded\"][];\n dynamic?: boolean;\n },\n ): RemotePrerenderFunction<S[\"Encoded\"], Output>;\n}\n\nconst makePrerender = <Env>(run: Runner): PrerenderFn<Env> =>\n ((\n arg1: unknown,\n arg2?:\n | ((arg: unknown) => Effect.Effect<unknown, unknown, Env>)\n | {\n inputs?: () => Promise<unknown[]> | unknown[];\n dynamic?: boolean;\n },\n arg3?: {\n inputs?: () => Promise<unknown[]> | unknown[];\n dynamic?: boolean;\n },\n ): any => {\n if (isBareHandler<Env>(arg1)) {\n const handler = arg1;\n return AppServer.prerender(\n () => run(handler()),\n arg2 as { inputs?: () => Promise<void[]> | void[]; dynamic?: boolean } | undefined,\n );\n }\n\n if (typeof arg2 !== \"function\") {\n throw new Error(\"prerender requires a handler function when a schema is provided\");\n }\n\n return AppServer.prerender(\n toStandardValidator(arg1) as \"unchecked\",\n (arg: unknown) => run((arg2 as EffectHandler<Env>)(arg)),\n arg3,\n );\n }) as PrerenderFn<Env>;\n\nexport function requested<Result = unknown>(...args: unknown[]): Result {\n const requestedFn = getRequested();\n return requestedFn(...args) as Result;\n}\n\n/** A `query` with its `batch` / `live` helpers attached. */\nexport type QueryNamespace<Env> = QueryFn<Env> & {\n batch: QueryBatchFn<Env>;\n live: QueryLiveFn<Env>;\n};\n\nconst makeQueryNamespace = <Env>(run: Runner): QueryNamespace<Env> =>\n Object.assign(makeQuery<Env>(run), {\n batch: makeQueryBatch<Env>(run),\n live: makeQueryLive<Env>(run),\n }) as QueryNamespace<Env>;\n\n// ── Standalone exports (default SvelteKit per-request runtime) ──────────────\n\nexport const query: QueryNamespace<SvelteKitEffectEnv> = makeQueryNamespace(defaultHelpers.run);\nexport const queryBatch: QueryBatchFn<SvelteKitEffectEnv> = query.batch;\nexport const queryLive: QueryLiveFn<SvelteKitEffectEnv> = query.live;\nexport const form: FormFn<SvelteKitEffectEnv> = makeForm(defaultHelpers);\nexport const command: CommandFn<SvelteKitEffectEnv> = makeCommand(defaultHelpers.run);\nexport const prerender: PrerenderFn<SvelteKitEffectEnv> = makePrerender(defaultHelpers.run);\n\n/** The set of remote-function helpers bound to a particular runner. */\nexport interface RemoteFunctions<Env> {\n readonly query: QueryNamespace<Env>;\n readonly form: FormFn<Env>;\n readonly command: CommandFn<Env>;\n readonly prerender: PrerenderFn<Env>;\n}\n\n/**\n * Bind the remote-function helpers to a {@link RequestRuntimeProvider} (and an\n * optional boundary error translator), so their handlers run on the provider's\n * services `Env` — your own application catalogue — instead of the fixed\n * {@link SvelteKitServices}. The Standard-Schema / `\"unchecked\"` validation\n * argument and the native return types (`RemoteQueryFunction`, `RemoteForm`,\n * ...) are exactly as the standalone helpers.\n *\n * For remote forms, the failure path runs `opts.formError` when provided —\n * mapping a structured/nested error shape into SvelteKit's field issues (via the\n * `issue` helper / `invalid()`) so a domain validation failure drives\n * field-level invalidation rather than a generic failure. Otherwise the form\n * falls back to `translateError`.\n *\n * @example\n * ```ts\n * const { query, form, command } = RemoteFunction.withRuntime(\n * RequestRuntime.fromLocals((event) => event.locals.runtime),\n * {\n * translateError: toSvelteKit,\n * formError: (error, { issue }) => {\n * if (error instanceof ValidationFailure) issue.field(error.field, error.message);\n * invalid();\n * },\n * },\n * );\n *\n * export const items = query(() =>\n * Effect.gen(function* () {\n * const db = yield* Database;\n * return yield* db.list();\n * }),\n * );\n * ```\n */\nexport function withRuntime<R>(\n provider: RequestRuntimeProvider<R, any>,\n opts?: { translateError?: ErrorTranslator; formError?: FormErrorMapper },\n): RemoteFunctions<R> {\n const helpers = helpersFromProvider(\n provider,\n opts?.translateError ?? defaultTranslate,\n opts?.formError,\n );\n return {\n query: makeQueryNamespace<R>(helpers.run),\n form: makeForm<R>(helpers),\n command: makeCommand<R>(helpers.run),\n prerender: makePrerender<R>(helpers.run),\n };\n}\n\nexport const RemoteFunction = {\n query,\n form,\n command,\n prerender,\n requested,\n withRuntime,\n};\n"],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ import { SvelteKitRequestEvent } from "./runtime.js";
2
+ import { Effect, Schema } from "effect";
3
+ import { StandardSchemaV1 } from "@standard-schema/spec";
4
+
5
+ //#region src/request-parse.d.ts
6
+ /** A single issue reported by a Standard Schema validator. */
7
+ interface ParseIssue {
8
+ readonly message: string;
9
+ readonly path?: ReadonlyArray<PropertyKey | {
10
+ readonly key: PropertyKey;
11
+ }>;
12
+ }
13
+ declare const ParseError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
14
+ readonly _tag: "ParseError";
15
+ } & Readonly<A>;
16
+ /**
17
+ * Raised when a schema validator rejects the request data.
18
+ *
19
+ * Carry the raw `issues` array from the Standard Schema validator so callers
20
+ * can pattern-match on specific field failures.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * import { ParseError } from '@thomasfosterau/effect-sveltekit/server';
25
+ * import { Effect } from 'effect';
26
+ *
27
+ * const result = yield* decodeJson(MySchema).pipe(
28
+ * Effect.catchTag("ParseError", (e) => Effect.succeed({ fallback: true })),
29
+ * );
30
+ * ```
31
+ */
32
+ declare class ParseError extends ParseError_base<{
33
+ readonly issues: ReadonlyArray<ParseIssue>;
34
+ }> {}
35
+ /**
36
+ * Decode the route `params` object against `schema`.
37
+ *
38
+ * `params` is a plain `Record<string, string>` from the matched URL segments;
39
+ * the schema is free to coerce values (e.g. `Schema.NumberFromString`).
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * const Params = Schema.Struct({ id: Schema.String });
44
+ * const { id } = yield* decodeParams(Params);
45
+ * ```
46
+ */
47
+ declare const decodeParams: <O>(schema: Schema.Schema<O> | StandardSchemaV1<unknown, O>) => Effect.Effect<O, ParseError, SvelteKitRequestEvent>;
48
+ /**
49
+ * Decode the URL search / query parameters against `schema`.
50
+ *
51
+ * The raw value passed to the validator is a plain object built from
52
+ * `event.url.searchParams` (multiple values for the same key become an array).
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * const Query = Schema.Struct({ page: Schema.NumberFromString });
57
+ * const { page } = yield* decodeSearchParams(Query);
58
+ * ```
59
+ */
60
+ declare const decodeSearchParams: <O>(schema: Schema.Schema<O> | StandardSchemaV1<unknown, O>) => Effect.Effect<O, ParseError, SvelteKitRequestEvent>;
61
+ /**
62
+ * Parse the request body as JSON and decode it against `schema`.
63
+ *
64
+ * Fails with `ParseError` if the body is not valid JSON or if the schema
65
+ * rejects the parsed value.
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * const Body = Schema.Struct({ name: Schema.String });
70
+ * const { name } = yield* decodeJson(Body);
71
+ * ```
72
+ */
73
+ declare const decodeJson: <O>(schema: Schema.Schema<O> | StandardSchemaV1<unknown, O>) => Effect.Effect<O, ParseError, SvelteKitRequestEvent>;
74
+ /**
75
+ * Parse the request body as `FormData` and decode it against `schema`.
76
+ *
77
+ * The raw value passed to the validator is a plain object; fields with
78
+ * multiple values (same name) become arrays.
79
+ *
80
+ * @example
81
+ * ```ts
82
+ * const Form = Schema.Struct({ email: Schema.String });
83
+ * const { email } = yield* decodeFormData(Form);
84
+ * ```
85
+ */
86
+ declare const decodeFormData: <O>(schema: Schema.Schema<O> | StandardSchemaV1<unknown, O>) => Effect.Effect<O, ParseError, SvelteKitRequestEvent>;
87
+ //#endregion
88
+ export { ParseError, ParseIssue, decodeFormData, decodeJson, decodeParams, decodeSearchParams };
89
+ //# sourceMappingURL=request-parse.d.ts.map
@@ -0,0 +1,121 @@
1
+ import { SvelteKitRequestEvent } from "./runtime.js";
2
+ import { toStandardValidator } from "./internal/remote-validators.js";
3
+ import { Data, Effect } from "effect";
4
+ //#region src/request-parse.ts
5
+ /**
6
+ * Raised when a schema validator rejects the request data.
7
+ *
8
+ * Carry the raw `issues` array from the Standard Schema validator so callers
9
+ * can pattern-match on specific field failures.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { ParseError } from '@thomasfosterau/effect-sveltekit/server';
14
+ * import { Effect } from 'effect';
15
+ *
16
+ * const result = yield* decodeJson(MySchema).pipe(
17
+ * Effect.catchTag("ParseError", (e) => Effect.succeed({ fallback: true })),
18
+ * );
19
+ * ```
20
+ */
21
+ var ParseError = class extends Data.TaggedError("ParseError") {};
22
+ /** Validate `value` against `schema` and return the typed output or fail. */
23
+ const validate = (schema, value) => Effect.callback((resume) => {
24
+ const resultOrPromise = schema["~standard"].validate(value);
25
+ const settle = (result) => {
26
+ if ("issues" in result && result.issues !== void 0) resume(Effect.fail(new ParseError({ issues: result.issues })));
27
+ else resume(Effect.succeed(result.value));
28
+ };
29
+ if (resultOrPromise instanceof Promise) resultOrPromise.then(settle, (err) => {
30
+ resume(Effect.fail(new ParseError({ issues: [{ message: String(err) }] })));
31
+ });
32
+ else settle(resultOrPromise);
33
+ });
34
+ /** Normalise an Effect Schema or Standard Schema V1 to Standard Schema V1. */
35
+ const normalise = (schema) => toStandardValidator(schema);
36
+ /**
37
+ * Decode the route `params` object against `schema`.
38
+ *
39
+ * `params` is a plain `Record<string, string>` from the matched URL segments;
40
+ * the schema is free to coerce values (e.g. `Schema.NumberFromString`).
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * const Params = Schema.Struct({ id: Schema.String });
45
+ * const { id } = yield* decodeParams(Params);
46
+ * ```
47
+ */
48
+ const decodeParams = (schema) => Effect.gen(function* () {
49
+ const event = yield* SvelteKitRequestEvent;
50
+ return yield* validate(normalise(schema), event.params);
51
+ });
52
+ /**
53
+ * Decode the URL search / query parameters against `schema`.
54
+ *
55
+ * The raw value passed to the validator is a plain object built from
56
+ * `event.url.searchParams` (multiple values for the same key become an array).
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * const Query = Schema.Struct({ page: Schema.NumberFromString });
61
+ * const { page } = yield* decodeSearchParams(Query);
62
+ * ```
63
+ */
64
+ const decodeSearchParams = (schema) => Effect.gen(function* () {
65
+ const event = yield* SvelteKitRequestEvent;
66
+ const raw = {};
67
+ for (const key of event.url.searchParams.keys()) {
68
+ const values = event.url.searchParams.getAll(key);
69
+ raw[key] = values.length === 1 ? values[0] : values;
70
+ }
71
+ return yield* validate(normalise(schema), raw);
72
+ });
73
+ /**
74
+ * Parse the request body as JSON and decode it against `schema`.
75
+ *
76
+ * Fails with `ParseError` if the body is not valid JSON or if the schema
77
+ * rejects the parsed value.
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * const Body = Schema.Struct({ name: Schema.String });
82
+ * const { name } = yield* decodeJson(Body);
83
+ * ```
84
+ */
85
+ const decodeJson = (schema) => Effect.gen(function* () {
86
+ const event = yield* SvelteKitRequestEvent;
87
+ const json = yield* Effect.tryPromise({
88
+ try: () => event.request.json(),
89
+ catch: (err) => new ParseError({ issues: [{ message: `Invalid JSON: ${String(err)}` }] })
90
+ });
91
+ return yield* validate(normalise(schema), json);
92
+ });
93
+ /**
94
+ * Parse the request body as `FormData` and decode it against `schema`.
95
+ *
96
+ * The raw value passed to the validator is a plain object; fields with
97
+ * multiple values (same name) become arrays.
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * const Form = Schema.Struct({ email: Schema.String });
102
+ * const { email } = yield* decodeFormData(Form);
103
+ * ```
104
+ */
105
+ const decodeFormData = (schema) => Effect.gen(function* () {
106
+ const event = yield* SvelteKitRequestEvent;
107
+ const formData = yield* Effect.tryPromise({
108
+ try: () => event.request.formData(),
109
+ catch: (err) => new ParseError({ issues: [{ message: `Failed to read form data: ${String(err)}` }] })
110
+ });
111
+ const raw = {};
112
+ for (const key of formData.keys()) {
113
+ const values = formData.getAll(key).map(String);
114
+ raw[key] = values.length === 1 ? values[0] : values;
115
+ }
116
+ return yield* validate(normalise(schema), raw);
117
+ });
118
+ //#endregion
119
+ export { ParseError, decodeFormData, decodeJson, decodeParams, decodeSearchParams };
120
+
121
+ //# sourceMappingURL=request-parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-parse.js","names":[],"sources":["../src/request-parse.ts"],"sourcesContent":["/**\n * Schema-driven request parsing utilities for SvelteKit server handlers.\n *\n * Each helper reads a part of the current request (route params, search params,\n * JSON body, or form data), validates it against a schema (Effect `Schema` or\n * any Standard Schema V1 validator), and returns the typed output as an\n * `Effect`. A validation failure surfaces as {@link ParseError}.\n *\n * All four utilities require `SvelteKitRequestEvent` in the environment so they\n * compose naturally inside `serverLoad` / `action` / `endpoint` handlers.\n *\n * @example\n * ```ts\n * import { serverLoad } from '@thomasfosterau/effect-sveltekit/server';\n * import { decodeParams, decodeSearchParams } from '@thomasfosterau/effect-sveltekit/server';\n * import { Schema } from 'effect';\n *\n * const Params = Schema.Struct({ id: Schema.String });\n * const Query = Schema.Struct({ page: Schema.NumberFromString });\n *\n * export const load = serverLoad(() =>\n * Effect.gen(function* () {\n * const { id } = yield* decodeParams(Params);\n * const { page } = yield* decodeSearchParams(Query);\n * return { id, page };\n * }),\n * );\n * ```\n */\n\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { Data, Effect, Schema } from \"effect\";\nimport { SvelteKitRequestEvent } from \"./runtime.js\";\nimport { toStandardValidator } from \"./internal/remote-validators.js\";\n\n// ---------------------------------------------------------------------------\n// ParseError\n// ---------------------------------------------------------------------------\n\n/** A single issue reported by a Standard Schema validator. */\nexport interface ParseIssue {\n readonly message: string;\n readonly path?: ReadonlyArray<PropertyKey | { readonly key: PropertyKey }>;\n}\n\n/**\n * Raised when a schema validator rejects the request data.\n *\n * Carry the raw `issues` array from the Standard Schema validator so callers\n * can pattern-match on specific field failures.\n *\n * @example\n * ```ts\n * import { ParseError } from '@thomasfosterau/effect-sveltekit/server';\n * import { Effect } from 'effect';\n *\n * const result = yield* decodeJson(MySchema).pipe(\n * Effect.catchTag(\"ParseError\", (e) => Effect.succeed({ fallback: true })),\n * );\n * ```\n */\nexport class ParseError extends Data.TaggedError(\"ParseError\")<{\n readonly issues: ReadonlyArray<ParseIssue>;\n}> {}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Validate `value` against `schema` and return the typed output or fail. */\nconst validate = <O>(\n schema: StandardSchemaV1<unknown, O>,\n value: unknown,\n): Effect.Effect<O, ParseError> =>\n Effect.callback<O, ParseError>((resume) => {\n const resultOrPromise = schema[\"~standard\"].validate(value);\n const settle = (result: StandardSchemaV1.Result<O>) => {\n if (\"issues\" in result && result.issues !== undefined) {\n resume(Effect.fail(new ParseError({ issues: result.issues as ReadonlyArray<ParseIssue> })));\n } else {\n resume(Effect.succeed((result as StandardSchemaV1.SuccessResult<O>).value));\n }\n };\n if (resultOrPromise instanceof Promise) {\n resultOrPromise.then(settle, (err: unknown) => {\n resume(Effect.fail(new ParseError({ issues: [{ message: String(err) }] })));\n });\n } else {\n settle(resultOrPromise);\n }\n });\n\n/** Normalise an Effect Schema or Standard Schema V1 to Standard Schema V1. */\nconst normalise = <O>(schema: unknown): StandardSchemaV1<unknown, O> =>\n toStandardValidator(schema) as StandardSchemaV1<unknown, O>;\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Decode the route `params` object against `schema`.\n *\n * `params` is a plain `Record<string, string>` from the matched URL segments;\n * the schema is free to coerce values (e.g. `Schema.NumberFromString`).\n *\n * @example\n * ```ts\n * const Params = Schema.Struct({ id: Schema.String });\n * const { id } = yield* decodeParams(Params);\n * ```\n */\nexport const decodeParams = <O>(\n schema: Schema.Schema<O> | StandardSchemaV1<unknown, O>,\n): Effect.Effect<O, ParseError, SvelteKitRequestEvent> =>\n Effect.gen(function* () {\n const event = yield* SvelteKitRequestEvent;\n return yield* validate(normalise<O>(schema), event.params);\n });\n\n/**\n * Decode the URL search / query parameters against `schema`.\n *\n * The raw value passed to the validator is a plain object built from\n * `event.url.searchParams` (multiple values for the same key become an array).\n *\n * @example\n * ```ts\n * const Query = Schema.Struct({ page: Schema.NumberFromString });\n * const { page } = yield* decodeSearchParams(Query);\n * ```\n */\nexport const decodeSearchParams = <O>(\n schema: Schema.Schema<O> | StandardSchemaV1<unknown, O>,\n): Effect.Effect<O, ParseError, SvelteKitRequestEvent> =>\n Effect.gen(function* () {\n const event = yield* SvelteKitRequestEvent;\n const raw: Record<string, string | string[]> = {};\n for (const key of event.url.searchParams.keys()) {\n const values = event.url.searchParams.getAll(key);\n raw[key] = values.length === 1 ? values[0]! : values;\n }\n return yield* validate(normalise<O>(schema), raw);\n });\n\n/**\n * Parse the request body as JSON and decode it against `schema`.\n *\n * Fails with `ParseError` if the body is not valid JSON or if the schema\n * rejects the parsed value.\n *\n * @example\n * ```ts\n * const Body = Schema.Struct({ name: Schema.String });\n * const { name } = yield* decodeJson(Body);\n * ```\n */\nexport const decodeJson = <O>(\n schema: Schema.Schema<O> | StandardSchemaV1<unknown, O>,\n): Effect.Effect<O, ParseError, SvelteKitRequestEvent> =>\n Effect.gen(function* () {\n const event = yield* SvelteKitRequestEvent;\n const json = yield* Effect.tryPromise({\n try: () => event.request.json() as Promise<unknown>,\n catch: (err) => new ParseError({ issues: [{ message: `Invalid JSON: ${String(err)}` }] }),\n });\n return yield* validate(normalise<O>(schema), json);\n });\n\n/**\n * Parse the request body as `FormData` and decode it against `schema`.\n *\n * The raw value passed to the validator is a plain object; fields with\n * multiple values (same name) become arrays.\n *\n * @example\n * ```ts\n * const Form = Schema.Struct({ email: Schema.String });\n * const { email } = yield* decodeFormData(Form);\n * ```\n */\nexport const decodeFormData = <O>(\n schema: Schema.Schema<O> | StandardSchemaV1<unknown, O>,\n): Effect.Effect<O, ParseError, SvelteKitRequestEvent> =>\n Effect.gen(function* () {\n const event = yield* SvelteKitRequestEvent;\n const formData = yield* Effect.tryPromise({\n try: () => event.request.formData(),\n catch: (err) =>\n new ParseError({ issues: [{ message: `Failed to read form data: ${String(err)}` }] }),\n });\n const raw: Record<string, string | string[]> = {};\n for (const key of formData.keys()) {\n const values = formData.getAll(key).map(String);\n raw[key] = values.length === 1 ? values[0]! : values;\n }\n return yield* validate(normalise<O>(schema), raw);\n });\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA6DA,IAAa,aAAb,cAAgC,KAAK,YAAY,YAAY,EAE1D,CAAC;;AAOJ,MAAM,YACJ,QACA,UAEA,OAAO,UAAyB,WAAW;CACzC,MAAM,kBAAkB,OAAO,aAAa,SAAS,KAAK;CAC1D,MAAM,UAAU,WAAuC;EACrD,IAAI,YAAY,UAAU,OAAO,WAAW,KAAA,GAC1C,OAAO,OAAO,KAAK,IAAI,WAAW,EAAE,QAAQ,OAAO,OAAoC,CAAC,CAAC,CAAC;OAE1F,OAAO,OAAO,QAAS,OAA6C,KAAK,CAAC;CAE9E;CACA,IAAI,2BAA2B,SAC7B,gBAAgB,KAAK,SAAS,QAAiB;EAC7C,OAAO,OAAO,KAAK,IAAI,WAAW,EAAE,QAAQ,CAAC,EAAE,SAAS,OAAO,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;CAC5E,CAAC;MAED,OAAO,eAAe;AAE1B,CAAC;;AAGH,MAAM,aAAgB,WACpB,oBAAoB,MAAM;;;;;;;;;;;;;AAkB5B,MAAa,gBACX,WAEA,OAAO,IAAI,aAAa;CACtB,MAAM,QAAQ,OAAO;CACrB,OAAO,OAAO,SAAS,UAAa,MAAM,GAAG,MAAM,MAAM;AAC3D,CAAC;;;;;;;;;;;;;AAcH,MAAa,sBACX,WAEA,OAAO,IAAI,aAAa;CACtB,MAAM,QAAQ,OAAO;CACrB,MAAM,MAAyC,CAAC;CAChD,KAAK,MAAM,OAAO,MAAM,IAAI,aAAa,KAAK,GAAG;EAC/C,MAAM,SAAS,MAAM,IAAI,aAAa,OAAO,GAAG;EAChD,IAAI,OAAO,OAAO,WAAW,IAAI,OAAO,KAAM;CAChD;CACA,OAAO,OAAO,SAAS,UAAa,MAAM,GAAG,GAAG;AAClD,CAAC;;;;;;;;;;;;;AAcH,MAAa,cACX,WAEA,OAAO,IAAI,aAAa;CACtB,MAAM,QAAQ,OAAO;CACrB,MAAM,OAAO,OAAO,OAAO,WAAW;EACpC,WAAW,MAAM,QAAQ,KAAK;EAC9B,QAAQ,QAAQ,IAAI,WAAW,EAAE,QAAQ,CAAC,EAAE,SAAS,iBAAiB,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC;CAC1F,CAAC;CACD,OAAO,OAAO,SAAS,UAAa,MAAM,GAAG,IAAI;AACnD,CAAC;;;;;;;;;;;;;AAcH,MAAa,kBACX,WAEA,OAAO,IAAI,aAAa;CACtB,MAAM,QAAQ,OAAO;CACrB,MAAM,WAAW,OAAO,OAAO,WAAW;EACxC,WAAW,MAAM,QAAQ,SAAS;EAClC,QAAQ,QACN,IAAI,WAAW,EAAE,QAAQ,CAAC,EAAE,SAAS,6BAA6B,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC;CACxF,CAAC;CACD,MAAM,MAAyC,CAAC;CAChD,KAAK,MAAM,OAAO,SAAS,KAAK,GAAG;EACjC,MAAM,SAAS,SAAS,OAAO,GAAG,EAAE,IAAI,MAAM;EAC9C,IAAI,OAAO,OAAO,WAAW,IAAI,OAAO,KAAM;CAChD;CACA,OAAO,OAAO,SAAS,UAAa,MAAM,GAAG,GAAG;AAClD,CAAC"}
@@ -1,3 +1,4 @@
1
+ import { SvelteRouteParams } from "./internal/route-params.js";
1
2
  import { Cause, Context, Effect, Exit, Fiber, Layer, ManagedRuntime } from "effect";
2
3
  import { Action, Actions, RequestEvent, RequestHandler, ServerLoad } from "@sveltejs/kit";
3
4
 
@@ -107,10 +108,10 @@ interface AdapterEndpointOptions {
107
108
  }
108
109
  /** The wrappers produced by {@link makeRuntimeAdapter}, bound to a provider. */
109
110
  interface RuntimeAdapter<R> {
110
- readonly serverLoad: <Params extends Record<string, string> = any, ParentData extends Record<string, any> = any, OutputData extends Record<string, any> | void = any, RouteId extends string | null = any>(fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, R>) => ServerLoad<Params, ParentData, OutputData, RouteId>;
111
- readonly action: <Params extends Record<string, string> = any, OutputData extends Record<string, any> | void = any, RouteId extends string | null = any>(fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, R>) => Action<Params, OutputData, RouteId>;
112
- readonly actions: <Params extends Record<string, string> = any, OutputData extends Record<string, any> | void = any, RouteId extends string | null = any>(map: Record<string, (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, R>>) => Actions<Params, OutputData, RouteId>;
113
- readonly endpoint: <Params extends Record<string, string> = any, RouteId extends string | null = any>(fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<Response, unknown, R>, options?: AdapterEndpointOptions) => RequestHandler<Params, RouteId>;
111
+ readonly serverLoad: <Params extends SvelteRouteParams = SvelteRouteParams, ParentData extends Record<string, any> = any, OutputData extends Record<string, any> | void = any, RouteId extends string | null = any>(fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, R>) => ServerLoad<Params, ParentData, OutputData, RouteId>;
112
+ readonly action: <Params extends SvelteRouteParams = SvelteRouteParams, OutputData extends Record<string, any> | void = any, RouteId extends string | null = any>(fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, R>) => Action<Params, OutputData, RouteId>;
113
+ readonly actions: <Params extends SvelteRouteParams = SvelteRouteParams, OutputData extends Record<string, any> | void = any, RouteId extends string | null = any>(map: Record<string, (event: RequestEvent<Params, RouteId>) => Effect.Effect<OutputData, unknown, R>>) => Actions<Params, OutputData, RouteId>;
114
+ readonly endpoint: <Params extends SvelteRouteParams = SvelteRouteParams, RouteId extends string | null = any>(fn: (event: RequestEvent<Params, RouteId>) => Effect.Effect<Response, unknown, R>, options?: AdapterEndpointOptions) => RequestHandler<Params, RouteId>;
114
115
  }
115
116
  /**
116
117
  * Bind the SvelteKit server wrappers to a {@link RequestRuntimeProvider} (and an
@@ -1,4 +1,4 @@
1
- import { causeToThrowable } from "./internal/error.js";
1
+ import { SvelteKit } from "./kit.js";
2
2
  import { Cause, Effect, Exit, ManagedRuntime } from "effect";
3
3
  import { isHttpError, isRedirect } from "@sveltejs/kit";
4
4
  //#region src/request-runtime.ts
@@ -140,9 +140,7 @@ const RequestRuntime = {
140
140
  * ```
141
141
  */
142
142
  const makeRuntimeAdapter = (provider, opts) => {
143
- const translateError = opts?.translateError ?? ((cause) => {
144
- throw causeToThrowable(cause);
145
- });
143
+ const translateError = opts?.translateError ?? SvelteKit.translate;
146
144
  const runSettle = async (event, effect) => {
147
145
  const exit = await provider.run(event).runPromiseExit(effect);
148
146
  try {
@@ -152,9 +150,9 @@ const makeRuntimeAdapter = (provider, opts) => {
152
150
  provider.disposeAfterResponse?.(event);
153
151
  }
154
152
  };
155
- const action = (fn) => (event) => runSettle(event, fn(event));
153
+ const action = (fn) => (event) => runSettle(event, fn(event).pipe(Effect.withSpan(`action ${event.route.id ?? "(unknown)"}`, { kind: "server" })));
156
154
  return {
157
- serverLoad: (fn) => (event) => runSettle(event, fn(event)),
155
+ serverLoad: (fn) => (event) => runSettle(event, fn(event).pipe(Effect.withSpan(`serverLoad ${event.route.id ?? "(unknown)"}`, { kind: "server" }))),
158
156
  action,
159
157
  actions: (map) => {
160
158
  const wrapped = {};
@@ -162,18 +160,21 @@ const makeRuntimeAdapter = (provider, opts) => {
162
160
  return wrapped;
163
161
  },
164
162
  endpoint: (fn, options) => async (event) => {
165
- const exit = await provider.run(event).runPromiseExit(fn(event));
163
+ const effect = fn(event).pipe(Effect.withSpan(`${event.request.method} ${event.route.id ?? "(unknown)"}`, { kind: "server" }));
164
+ const exit = await provider.run(event).runPromiseExit(effect);
166
165
  try {
167
166
  if (Exit.isSuccess(exit)) return exit.value;
168
- if (opts?.translateError) return opts.translateError(exit.cause);
169
- const squashed = Cause.squash(exit.cause);
170
- if (isRedirect(squashed) || isHttpError(squashed)) throw squashed;
171
- console.error("Endpoint error:", Cause.pretty(exit.cause));
172
- const message = options?.exposeErrors ? Cause.pretty(exit.cause) : "Internal server error";
173
- return new Response(JSON.stringify({ error: message }), {
174
- status: 500,
175
- headers: { "Content-Type": "application/json" }
176
- });
167
+ try {
168
+ return translateError(exit.cause);
169
+ } catch (err) {
170
+ if (isRedirect(err) || isHttpError(err)) throw err;
171
+ console.error("Endpoint error:", Cause.pretty(exit.cause));
172
+ const message = options?.exposeErrors ? Cause.pretty(exit.cause) : "Internal server error";
173
+ return new Response(JSON.stringify({ error: message }), {
174
+ status: 500,
175
+ headers: { "Content-Type": "application/json" }
176
+ });
177
+ }
177
178
  } finally {
178
179
  provider.disposeAfterResponse?.(event);
179
180
  }