@thomasfosterau/effect-sveltekit 0.5.0 → 0.6.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/dist/forms.d.ts CHANGED
@@ -1,29 +1,92 @@
1
- import { EnhanceHandlers, EnhanceOptions, EnhanceRuntime, ResultOptions, SubmitInput } from "./internal/enhance.js";
1
+ import { EnhanceHandlerKey, EnhanceHandlers, EnhanceHandlersBy, EnhanceOptions, EnhanceOptionsArgs, EnhanceRuntime, HandlerErrors, HandlerKeys, OnErrorAccepts, OnErrorInput, RequiredServices, ResultOptions, SubmitInput } from "./internal/enhance.js";
2
2
  import "./forms.js";
3
3
  import { Effect } from "effect";
4
4
  import { ActionResult } from "$app/forms";
5
5
  declare namespace forms_d_exports {
6
- export { EnhanceHandlers, EnhanceOptions, EnhanceRuntime, ResultOptions, SubmitInput, applyAction$1 as applyAction, deserialize$1 as deserialize, enhance };
6
+ export { ApplyActionError, EnhanceHandlers, EnhanceOptions, EnhanceOptionsArgs, EnhanceRuntime, ResultOptions, SubmitInput, applyAction$1 as applyAction, deserialize$1 as deserialize, enhance };
7
7
  }
8
8
  /**
9
9
  * Build a `SubmitFunction` for `use:enhance` from Effect-based handlers.
10
10
  *
11
11
  * `onSubmit` runs before the request (call `input.cancel()` to abort);
12
12
  * `onResult` runs on the response with SvelteKit's `update()`. Omit `onResult`
13
- * to keep SvelteKit's default behaviour. Pass `{ runtime }` when the handlers
14
- * require application services.
13
+ * to keep SvelteKit's default behaviour. When the handlers (or `onError`)
14
+ * require application services, pass `{ runtime }` (a `Context` or
15
+ * `ManagedRuntime` providing them): it is then required, and a call without it
16
+ * does not type-check. Each handler may require different services; the runtime
17
+ * must provide all of them. A missing runtime is reported on the call, and one
18
+ * that lacks a service on the runtime, never on a handler; the message may at
19
+ * first name only the services of the handlers TypeScript types first (such as
20
+ * an `onError` passed by name), and the rest once the runtime provides those.
21
+ * Handlers that require no services run on the default runtime, and the options
22
+ * may be omitted; an effect typed as requiring `any` or `unknown` is treated as
23
+ * requiring no services, so no runtime is demanded. A function that wraps
24
+ * `enhance` for handlers requiring any `R` forwards its options as
25
+ * `...options: EnhanceOptionsArgs<R>` (or takes a required
26
+ * `{ runtime: EnhanceRuntime<R> }`): an `EnhanceOptions<R>`, whose runtime is
27
+ * optional, does not type-check there.
28
+ *
29
+ * Either handler may fail. Add an `onError` handler to handle the error
30
+ * (typed as the union of the handlers' error types when written after them;
31
+ * `unknown` when written before a handler with unannotated parameters): a
32
+ * failed `onSubmit` also cancels the submission, and a failed `onResult` does
33
+ * not fall back to SvelteKit's default behaviour. Without `onError`, or for a
34
+ * defect or interruption, the promise SvelteKit awaits rejects (with the error
35
+ * itself for a failure), which SvelteKit leaves unhandled.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * const submit = Forms.enhance({
40
+ * onResult: ({ result }) => Forms.applyAction(result),
41
+ * // error: ApplyActionError
42
+ * onError: (error) => Effect.logWarning("applyAction failed", error.cause),
43
+ * });
44
+ * ```
45
+ */
46
+ declare const enhance: <RSubmit = unknown, RResult = unknown, RError = unknown, ESubmit = any, EResult = any, Keys extends EnhanceHandlerKey = never, E = OnErrorInput<HandlerErrors<Keys, ESubmit, EResult>>>(handlers: EnhanceHandlersBy<RSubmit, RResult, RError, ESubmit, EResult, E> & HandlerKeys<Keys> & NoInfer<OnErrorAccepts<RError, HandlerErrors<Keys, ESubmit, EResult>, E>>, ...rest: NoInfer<EnhanceOptionsArgs<RequiredServices<RSubmit> | RequiredServices<RResult> | RequiredServices<RError>>>) => import("$app/forms").SubmitFunction;
47
+ declare const ApplyActionError_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 & {
48
+ readonly _tag: "ApplyActionError";
49
+ } & Readonly<A>;
50
+ /**
51
+ * {@link applyAction} rejected or threw. `cause` is SvelteKit's original
52
+ * reason, unchanged.
53
+ *
54
+ * SvelteKit 3 throws when `applyAction` is called during server rendering, and
55
+ * follows a `redirect` result with a client-side navigation that rejects if it
56
+ * fails: for example, a `location` that cannot be parsed even relative to the
57
+ * page's base URL, or a `beforeNavigate` / `onNavigate` callback that throws. Recover with
58
+ * `Effect.catchTag("ApplyActionError", ...)`.
15
59
  */
16
- declare const enhance: <R = never>(handlers: EnhanceHandlers<R>, options?: EnhanceOptions<R>) => import("$app/forms").SubmitFunction;
60
+ declare class ApplyActionError extends ApplyActionError_base<{
61
+ /** The rejection reason, or the value thrown, as SvelteKit produced it. */
62
+ readonly cause: unknown;
63
+ }> {}
17
64
  /**
18
65
  * Apply an `ActionResult` to the page (updating `page.form` / `page.status`,
19
66
  * resetting the form, etc.) as an Effect. Wraps `$app/forms`' `applyAction`.
67
+ *
68
+ * Fails with an {@link ApplyActionError} if SvelteKit rejects or throws: for
69
+ * example, when the navigation that follows a `redirect` result fails, or when
70
+ * called during server rendering.
71
+ *
72
+ * {@link enhance}'s `onResult` can return it directly; its `onError` handler
73
+ * then receives the `ApplyActionError`. Without `onError`, the error rejects
74
+ * the promise SvelteKit awaits.
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * const submit = Forms.enhance({
79
+ * onResult: ({ result }) => Forms.applyAction(result),
80
+ * onError: (error) => Effect.logWarning("applyAction failed", error.cause),
81
+ * });
82
+ * ```
20
83
  */
21
- declare const applyAction$1: <Success extends Record<string, unknown> | undefined = Record<string, unknown>, Failure extends Record<string, unknown> | undefined = Record<string, unknown>>(result: ActionResult<Success, Failure>) => Effect.Effect<void>;
84
+ declare const applyAction$1: <Success extends Record<string, unknown> | undefined = Record<string, unknown>, Failure extends Record<string, unknown> | undefined = Record<string, unknown>>(result: ActionResult<Success, Failure>) => Effect.Effect<void, ApplyActionError>;
22
85
  /**
23
86
  * Deserialize a raw form-action response body into an `ActionResult`, as an
24
87
  * Effect. Wraps `$app/forms`' `deserialize`; a malformed body fails the effect.
25
88
  */
26
89
  declare const deserialize$1: <Success extends Record<string, unknown> | undefined = Record<string, unknown>, Failure extends Record<string, unknown> | undefined = Record<string, unknown>>(result: string) => Effect.Effect<ActionResult<Success, Failure>, unknown>;
27
90
  //#endregion
28
- export { type EnhanceHandlers, type EnhanceOptions, type EnhanceRuntime, type ResultOptions, type SubmitInput, applyAction$1 as applyAction, deserialize$1 as deserialize, enhance, forms_d_exports };
91
+ export { ApplyActionError, type EnhanceHandlers, type EnhanceOptions, type EnhanceOptionsArgs, type EnhanceRuntime, type ResultOptions, type SubmitInput, applyAction$1 as applyAction, deserialize$1 as deserialize, enhance, forms_d_exports };
29
92
  //# sourceMappingURL=forms.d.ts.map
package/dist/forms.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { __exportAll } from "./_virtual/_rolldown/runtime.js";
2
2
  import { makeSubmitFunction } from "./internal/enhance.js";
3
- import { Effect } from "effect";
3
+ import { Data, Effect } from "effect";
4
4
  import { applyAction, deserialize } from "$app/forms";
5
5
  //#region src/forms.ts
6
6
  /**
@@ -34,6 +34,7 @@ import { applyAction, deserialize } from "$app/forms";
34
34
  * ```
35
35
  */
36
36
  var forms_exports = /* @__PURE__ */ __exportAll({
37
+ ApplyActionError: () => ApplyActionError,
37
38
  applyAction: () => applyAction$1,
38
39
  deserialize: () => deserialize$1,
39
40
  enhance: () => enhance
@@ -43,15 +44,75 @@ var forms_exports = /* @__PURE__ */ __exportAll({
43
44
  *
44
45
  * `onSubmit` runs before the request (call `input.cancel()` to abort);
45
46
  * `onResult` runs on the response with SvelteKit's `update()`. Omit `onResult`
46
- * to keep SvelteKit's default behaviour. Pass `{ runtime }` when the handlers
47
- * require application services.
47
+ * to keep SvelteKit's default behaviour. When the handlers (or `onError`)
48
+ * require application services, pass `{ runtime }` (a `Context` or
49
+ * `ManagedRuntime` providing them): it is then required, and a call without it
50
+ * does not type-check. Each handler may require different services; the runtime
51
+ * must provide all of them. A missing runtime is reported on the call, and one
52
+ * that lacks a service on the runtime, never on a handler; the message may at
53
+ * first name only the services of the handlers TypeScript types first (such as
54
+ * an `onError` passed by name), and the rest once the runtime provides those.
55
+ * Handlers that require no services run on the default runtime, and the options
56
+ * may be omitted; an effect typed as requiring `any` or `unknown` is treated as
57
+ * requiring no services, so no runtime is demanded. A function that wraps
58
+ * `enhance` for handlers requiring any `R` forwards its options as
59
+ * `...options: EnhanceOptionsArgs<R>` (or takes a required
60
+ * `{ runtime: EnhanceRuntime<R> }`): an `EnhanceOptions<R>`, whose runtime is
61
+ * optional, does not type-check there.
62
+ *
63
+ * Either handler may fail. Add an `onError` handler to handle the error
64
+ * (typed as the union of the handlers' error types when written after them;
65
+ * `unknown` when written before a handler with unannotated parameters): a
66
+ * failed `onSubmit` also cancels the submission, and a failed `onResult` does
67
+ * not fall back to SvelteKit's default behaviour. Without `onError`, or for a
68
+ * defect or interruption, the promise SvelteKit awaits rejects (with the error
69
+ * itself for a failure), which SvelteKit leaves unhandled.
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * const submit = Forms.enhance({
74
+ * onResult: ({ result }) => Forms.applyAction(result),
75
+ * // error: ApplyActionError
76
+ * onError: (error) => Effect.logWarning("applyAction failed", error.cause),
77
+ * });
78
+ * ```
48
79
  */
49
80
  const enhance = makeSubmitFunction;
50
81
  /**
82
+ * {@link applyAction} rejected or threw. `cause` is SvelteKit's original
83
+ * reason, unchanged.
84
+ *
85
+ * SvelteKit 3 throws when `applyAction` is called during server rendering, and
86
+ * follows a `redirect` result with a client-side navigation that rejects if it
87
+ * fails: for example, a `location` that cannot be parsed even relative to the
88
+ * page's base URL, or a `beforeNavigate` / `onNavigate` callback that throws. Recover with
89
+ * `Effect.catchTag("ApplyActionError", ...)`.
90
+ */
91
+ var ApplyActionError = class extends Data.TaggedError("ApplyActionError") {};
92
+ /**
51
93
  * Apply an `ActionResult` to the page (updating `page.form` / `page.status`,
52
94
  * resetting the form, etc.) as an Effect. Wraps `$app/forms`' `applyAction`.
95
+ *
96
+ * Fails with an {@link ApplyActionError} if SvelteKit rejects or throws: for
97
+ * example, when the navigation that follows a `redirect` result fails, or when
98
+ * called during server rendering.
99
+ *
100
+ * {@link enhance}'s `onResult` can return it directly; its `onError` handler
101
+ * then receives the `ApplyActionError`. Without `onError`, the error rejects
102
+ * the promise SvelteKit awaits.
103
+ *
104
+ * @example
105
+ * ```ts
106
+ * const submit = Forms.enhance({
107
+ * onResult: ({ result }) => Forms.applyAction(result),
108
+ * onError: (error) => Effect.logWarning("applyAction failed", error.cause),
109
+ * });
110
+ * ```
53
111
  */
54
- const applyAction$1 = (result) => Effect.promise(() => applyAction(result));
112
+ const applyAction$1 = (result) => Effect.tryPromise({
113
+ try: () => applyAction(result),
114
+ catch: (cause) => new ApplyActionError({ cause })
115
+ });
55
116
  /**
56
117
  * Deserialize a raw form-action response body into an `ActionResult`, as an
57
118
  * Effect. Wraps `$app/forms`' `deserialize`; a malformed body fails the effect.
@@ -61,6 +122,6 @@ const deserialize$1 = (result) => Effect.try({
61
122
  catch: (error) => error
62
123
  });
63
124
  //#endregion
64
- export { applyAction$1 as applyAction, deserialize$1 as deserialize, enhance, forms_exports };
125
+ export { ApplyActionError, applyAction$1 as applyAction, deserialize$1 as deserialize, enhance, forms_exports };
65
126
 
66
127
  //# sourceMappingURL=forms.js.map
package/dist/forms.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"forms.js","names":["applyAction","kitApplyAction","deserialize","kitDeserialize"],"sources":["../src/forms.ts"],"sourcesContent":["/**\n * Effect bridge for SvelteKit's native form actions\n * ([`$app/forms`](https://svelte.dev/docs/kit/$app-forms)) — the `Forms`\n * namespace of `@thomasfosterau/effect-sveltekit/client`.\n *\n * This complements `Remote.submitForm` (which covers *remote* forms): here the\n * progressive-enhancement `use:enhance` flow for page `actions` is expressed\n * with Effects, and `applyAction` / `deserialize` are wrapped so a hand-rolled\n * `fetch` submission can be finished off inside an Effect pipeline.\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { enhance } from '$app/forms';\n * import { Forms } from '@thomasfosterau/effect-sveltekit/client';\n * import { Effect } from 'effect';\n *\n * const submit = Forms.enhance({\n * onSubmit: () => Effect.sync(() => { saving = true; }),\n * onResult: ({ result, update }) =>\n * Effect.gen(function* () {\n * yield* Effect.promise(() => update());\n * saving = false;\n * }),\n * });\n * </script>\n *\n * <form method=\"POST\" use:enhance={submit}>...</form>\n * ```\n */\n\nimport { applyAction as kitApplyAction, deserialize as kitDeserialize } from \"$app/forms\";\nimport type { ActionResult } from \"$app/forms\";\nimport { Effect } from \"effect\";\nimport { makeSubmitFunction } from \"./internal/enhance.js\";\n\nexport type {\n EnhanceHandlers,\n EnhanceOptions,\n EnhanceRuntime,\n SubmitInput,\n ResultOptions,\n} from \"./internal/enhance.js\";\n\n/**\n * Build a `SubmitFunction` for `use:enhance` from Effect-based handlers.\n *\n * `onSubmit` runs before the request (call `input.cancel()` to abort);\n * `onResult` runs on the response with SvelteKit's `update()`. Omit `onResult`\n * to keep SvelteKit's default behaviour. Pass `{ runtime }` when the handlers\n * require application services.\n */\nexport const enhance = makeSubmitFunction;\n\n/**\n * Apply an `ActionResult` to the page (updating `page.form` / `page.status`,\n * resetting the form, etc.) as an Effect. Wraps `$app/forms`' `applyAction`.\n */\nexport const applyAction = <\n Success extends Record<string, unknown> | undefined = Record<string, unknown>,\n Failure extends Record<string, unknown> | undefined = Record<string, unknown>,\n>(\n result: ActionResult<Success, Failure>,\n): Effect.Effect<void> => Effect.promise(() => kitApplyAction(result));\n\n/**\n * Deserialize a raw form-action response body into an `ActionResult`, as an\n * Effect. Wraps `$app/forms`' `deserialize`; a malformed body fails the effect.\n */\nexport const deserialize = <\n Success extends Record<string, unknown> | undefined = Record<string, unknown>,\n Failure extends Record<string, unknown> | undefined = Record<string, unknown>,\n>(\n result: string,\n): Effect.Effect<ActionResult<Success, Failure>, unknown> =>\n Effect.try({\n try: () => kitDeserialize<Success, Failure>(result),\n catch: (error) => error,\n });\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,MAAa,UAAU;;;;;AAMvB,MAAaA,iBAIX,WACwB,OAAO,cAAcC,YAAe,MAAM,CAAC;;;;;AAMrE,MAAaC,iBAIX,WAEA,OAAO,IAAI;CACT,WAAWC,YAAiC,MAAM;CAClD,QAAQ,UAAU;AACpB,CAAC"}
1
+ {"version":3,"file":"forms.js","names":["applyAction","kitApplyAction","deserialize","kitDeserialize"],"sources":["../src/forms.ts"],"sourcesContent":["/**\n * Effect bridge for SvelteKit's native form actions\n * ([`$app/forms`](https://svelte.dev/docs/kit/$app-forms)) — the `Forms`\n * namespace of `@thomasfosterau/effect-sveltekit/client`.\n *\n * This complements `Remote.submitForm` (which covers *remote* forms): here the\n * progressive-enhancement `use:enhance` flow for page `actions` is expressed\n * with Effects, and `applyAction` / `deserialize` are wrapped so a hand-rolled\n * `fetch` submission can be finished off inside an Effect pipeline.\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { enhance } from '$app/forms';\n * import { Forms } from '@thomasfosterau/effect-sveltekit/client';\n * import { Effect } from 'effect';\n *\n * const submit = Forms.enhance({\n * onSubmit: () => Effect.sync(() => { saving = true; }),\n * onResult: ({ result, update }) =>\n * Effect.gen(function* () {\n * yield* Effect.promise(() => update());\n * saving = false;\n * }),\n * });\n * </script>\n *\n * <form method=\"POST\" use:enhance={submit}>...</form>\n * ```\n */\n\nimport { applyAction as kitApplyAction, deserialize as kitDeserialize } from \"$app/forms\";\nimport type { ActionResult } from \"$app/forms\";\nimport { Data, Effect } from \"effect\";\nimport { makeSubmitFunction } from \"./internal/enhance.js\";\n\nexport type {\n EnhanceHandlers,\n EnhanceOptions,\n EnhanceOptionsArgs,\n EnhanceRuntime,\n SubmitInput,\n ResultOptions,\n} from \"./internal/enhance.js\";\n\n/**\n * Build a `SubmitFunction` for `use:enhance` from Effect-based handlers.\n *\n * `onSubmit` runs before the request (call `input.cancel()` to abort);\n * `onResult` runs on the response with SvelteKit's `update()`. Omit `onResult`\n * to keep SvelteKit's default behaviour. When the handlers (or `onError`)\n * require application services, pass `{ runtime }` (a `Context` or\n * `ManagedRuntime` providing them): it is then required, and a call without it\n * does not type-check. Each handler may require different services; the runtime\n * must provide all of them. A missing runtime is reported on the call, and one\n * that lacks a service on the runtime, never on a handler; the message may at\n * first name only the services of the handlers TypeScript types first (such as\n * an `onError` passed by name), and the rest once the runtime provides those.\n * Handlers that require no services run on the default runtime, and the options\n * may be omitted; an effect typed as requiring `any` or `unknown` is treated as\n * requiring no services, so no runtime is demanded. A function that wraps\n * `enhance` for handlers requiring any `R` forwards its options as\n * `...options: EnhanceOptionsArgs<R>` (or takes a required\n * `{ runtime: EnhanceRuntime<R> }`): an `EnhanceOptions<R>`, whose runtime is\n * optional, does not type-check there.\n *\n * Either handler may fail. Add an `onError` handler to handle the error\n * (typed as the union of the handlers' error types when written after them;\n * `unknown` when written before a handler with unannotated parameters): a\n * failed `onSubmit` also cancels the submission, and a failed `onResult` does\n * not fall back to SvelteKit's default behaviour. Without `onError`, or for a\n * defect or interruption, the promise SvelteKit awaits rejects (with the error\n * itself for a failure), which SvelteKit leaves unhandled.\n *\n * @example\n * ```ts\n * const submit = Forms.enhance({\n * onResult: ({ result }) => Forms.applyAction(result),\n * // error: ApplyActionError\n * onError: (error) => Effect.logWarning(\"applyAction failed\", error.cause),\n * });\n * ```\n */\nexport const enhance = makeSubmitFunction;\n\n/**\n * {@link applyAction} rejected or threw. `cause` is SvelteKit's original\n * reason, unchanged.\n *\n * SvelteKit 3 throws when `applyAction` is called during server rendering, and\n * follows a `redirect` result with a client-side navigation that rejects if it\n * fails: for example, a `location` that cannot be parsed even relative to the\n * page's base URL, or a `beforeNavigate` / `onNavigate` callback that throws. Recover with\n * `Effect.catchTag(\"ApplyActionError\", ...)`.\n */\nexport class ApplyActionError extends Data.TaggedError(\"ApplyActionError\")<{\n /** The rejection reason, or the value thrown, as SvelteKit produced it. */\n readonly cause: unknown;\n}> {}\n\n/**\n * Apply an `ActionResult` to the page (updating `page.form` / `page.status`,\n * resetting the form, etc.) as an Effect. Wraps `$app/forms`' `applyAction`.\n *\n * Fails with an {@link ApplyActionError} if SvelteKit rejects or throws: for\n * example, when the navigation that follows a `redirect` result fails, or when\n * called during server rendering.\n *\n * {@link enhance}'s `onResult` can return it directly; its `onError` handler\n * then receives the `ApplyActionError`. Without `onError`, the error rejects\n * the promise SvelteKit awaits.\n *\n * @example\n * ```ts\n * const submit = Forms.enhance({\n * onResult: ({ result }) => Forms.applyAction(result),\n * onError: (error) => Effect.logWarning(\"applyAction failed\", error.cause),\n * });\n * ```\n */\nexport const applyAction = <\n Success extends Record<string, unknown> | undefined = Record<string, unknown>,\n Failure extends Record<string, unknown> | undefined = Record<string, unknown>,\n>(\n result: ActionResult<Success, Failure>,\n): Effect.Effect<void, ApplyActionError> =>\n Effect.tryPromise({\n try: () => kitApplyAction(result),\n catch: (cause) => new ApplyActionError({ cause }),\n });\n\n/**\n * Deserialize a raw form-action response body into an `ActionResult`, as an\n * Effect. Wraps `$app/forms`' `deserialize`; a malformed body fails the effect.\n */\nexport const deserialize = <\n Success extends Record<string, unknown> | undefined = Record<string, unknown>,\n Failure extends Record<string, unknown> | undefined = Record<string, unknown>,\n>(\n result: string,\n): Effect.Effect<ActionResult<Success, Failure>, unknown> =>\n Effect.try({\n try: () => kitDeserialize<Success, Failure>(result),\n catch: (error) => error,\n });\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmFA,MAAa,UAAU;;;;;;;;;;;AAYvB,IAAa,mBAAb,cAAsC,KAAK,YAAY,kBAAkB,CAAC,CAGvE,CAAC;;;;;;;;;;;;;;;;;;;;;AAsBJ,MAAaA,iBAIX,WAEA,OAAO,WAAW;CAChB,WAAWC,YAAe,MAAM;CAChC,QAAQ,UAAU,IAAI,iBAAiB,EAAE,MAAM,CAAC;AAClD,CAAC;;;;;AAMH,MAAaC,iBAIX,WAEA,OAAO,IAAI;CACT,WAAWC,YAAiC,MAAM;CAClD,QAAQ,UAAU;AACpB,CAAC"}
@@ -1,4 +1,5 @@
1
- import { Stream } from "effect";
1
+ import { Effect, Option, Stream } from "effect";
2
+ import { NavigationTarget, NavigationType } from "$app/navigation";
2
3
  //#region src/internal/app-state.d.ts
3
4
  /**
4
5
  * The fields of SvelteKit's `page` we snapshot. Mirrors the shape of
@@ -14,6 +15,7 @@ interface PageSnapshot<P extends ReadonlyPage = ReadonlyPage> {
14
15
  readonly data: P["data"];
15
16
  readonly state: P["state"];
16
17
  readonly form: P["form"];
18
+ readonly shallow: P["shallow"];
17
19
  }
18
20
  /**
19
21
  * The subset of `Page` fields the snapshot reads. Deliberately loose (the value
@@ -32,7 +34,40 @@ interface ReadonlyPage {
32
34
  readonly data: Record<string, unknown>;
33
35
  readonly state: unknown;
34
36
  readonly form: unknown;
37
+ readonly shallow: unknown;
38
+ }
39
+ /**
40
+ * A snapshot of an in-flight SvelteKit navigation, as `$app/state`'s
41
+ * `navigating` exposes it. `delta` is the history step count for a `popstate`
42
+ * navigation and `null` otherwise; `complete` resolves when the navigation
43
+ * finishes and rejects if it fails or is aborted.
44
+ */
45
+ interface NavigatingSnapshot {
46
+ readonly type: Exclude<NavigationType, "enter">;
47
+ readonly from: NavigationTarget | null;
48
+ readonly to: NavigationTarget | null;
49
+ readonly willUnload: boolean;
50
+ readonly delta: number | null;
51
+ readonly complete: Promise<void>;
52
+ }
53
+ declare const UpdateCheckError_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 & {
54
+ readonly _tag: "UpdateCheckError";
55
+ } & Readonly<A>;
56
+ /**
57
+ * `updated.check()` rejected. SvelteKit resolves `false` when the version
58
+ * request fails, so this signals misuse (e.g. calling it during server
59
+ * rendering, where there is no `window`) rather than a network error.
60
+ */
61
+ declare class UpdateCheckError extends UpdateCheckError_base<{
62
+ readonly cause: unknown;
63
+ }> {}
64
+ /** The service shape behind `State.CurrentPage`. */
65
+ interface CurrentPageShape<P extends ReadonlyPage = ReadonlyPage> {
66
+ /** Snapshots the current page when run. */
67
+ readonly get: Effect.Effect<PageSnapshot<P>>;
68
+ /** The current page, then a fresh snapshot whenever any of its fields changes. */
69
+ readonly changes: Stream.Stream<PageSnapshot<P>>;
35
70
  }
36
71
  //#endregion
37
- export { PageSnapshot, ReadonlyPage };
72
+ export { CurrentPageShape, NavigatingSnapshot, PageSnapshot, ReadonlyPage, UpdateCheckError };
38
73
  //# sourceMappingURL=app-state.d.ts.map
@@ -1,18 +1,6 @@
1
- import { Effect, Queue, Stream } from "effect";
1
+ import { Data, Effect, Option, Queue, Stream } from "effect";
2
2
  //#region src/internal/app-state.ts
3
3
  /**
4
- * Pure, `$app/*`-free core for the `$app/state` bridge in `../State.ts`.
5
- *
6
- * The public `page` can't be unit-tested directly because importing it
7
- * pulls in `$app/navigation` and `$app/state` (SvelteKit virtual modules that
8
- * only exist inside a build). The snapshotting and the "emit now, then after
9
- * each navigation" plumbing live here instead, parameterised over the
10
- * navigation registrar and a `readPage` thunk, so they can be driven with
11
- * stand-ins in tests while the public wrapper stays a thin pass-through.
12
- *
13
- * @internal
14
- */
15
- /**
16
4
  * Builds a shallow snapshot of the `page` fields that matter. We copy the
17
5
  * fields rather than forwarding the live `$app/state` proxy so downstream Effect
18
6
  * consumers see a stable value (the proxy mutates in place on every
@@ -29,7 +17,8 @@ function snapshotPage(page) {
29
17
  error: page.error,
30
18
  data: page.data,
31
19
  state: page.state,
32
- form: page.form
20
+ form: page.form,
21
+ shallow: page.shallow
33
22
  };
34
23
  }
35
24
  /**
@@ -46,7 +35,61 @@ function pageSnapshotStream(registerAfterNavigate, readPage) {
46
35
  });
47
36
  return Stream.fromQueue(queue);
48
37
  }
38
+ /**
39
+ * A `Stream` of `read()`: the current value, then a new one each time `watch`
40
+ * reports a change. Cold and lazy — each run opens its own watcher and disposes
41
+ * it when the stream ends or is interrupted. Never fails; a throwing `watch`
42
+ * surfaces as a defect (the stream dies with what it threw, with no watcher
43
+ * left to dispose).
44
+ */
45
+ function watchedStream(watch, read) {
46
+ return Stream.callback((queue) => Effect.acquireRelease(Effect.sync(() => watch(read, (value) => {
47
+ Queue.offerUnsafe(queue, value);
48
+ })), (dispose) => Effect.sync(dispose)));
49
+ }
50
+ /**
51
+ * Copies `navigating` into a plain {@link NavigatingSnapshot}, or `None` when
52
+ * no navigation is in flight (`type` is `null`). Reads every field, so a
53
+ * watcher tracks the whole object.
54
+ */
55
+ function snapshotNavigating(navigating) {
56
+ const { type, from, to, willUnload, delta, complete } = navigating;
57
+ if (type === null || willUnload === null || complete === null) return Option.none();
58
+ return Option.some({
59
+ type,
60
+ from,
61
+ to,
62
+ willUnload,
63
+ delta: delta ?? null,
64
+ complete
65
+ });
66
+ }
67
+ /**
68
+ * `updated.check()` rejected. SvelteKit resolves `false` when the version
69
+ * request fails, so this signals misuse (e.g. calling it during server
70
+ * rendering, where there is no `window`) rather than a network error.
71
+ */
72
+ var UpdateCheckError = class extends Data.TaggedError("UpdateCheckError") {};
73
+ /**
74
+ * Runs `check` (SvelteKit's `updated.check`) as an Effect that succeeds with
75
+ * whether a new version is available and fails with {@link UpdateCheckError}
76
+ * if the promise rejects (or `check` throws).
77
+ */
78
+ function checkForUpdateWith(check) {
79
+ return Effect.tryPromise({
80
+ try: () => check(),
81
+ catch: (cause) => new UpdateCheckError({ cause })
82
+ });
83
+ }
84
+ /** Builds a {@link CurrentPageShape} reading `readPage()` and watching it with `watch`. */
85
+ function makeCurrentPage(readPage, watch) {
86
+ const read = () => snapshotPage(readPage());
87
+ return {
88
+ get: Effect.sync(read),
89
+ changes: watchedStream(watch, read)
90
+ };
91
+ }
49
92
  //#endregion
50
- export { pageSnapshotStream, snapshotPage };
93
+ export { UpdateCheckError, checkForUpdateWith, makeCurrentPage, pageSnapshotStream, snapshotNavigating, snapshotPage, watchedStream };
51
94
 
52
95
  //# sourceMappingURL=app-state.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-state.js","names":[],"sources":["../../src/internal/app-state.ts"],"sourcesContent":["/**\n * Pure, `$app/*`-free core for the `$app/state` bridge in `../State.ts`.\n *\n * The public `page` can't be unit-tested directly because importing it\n * pulls in `$app/navigation` and `$app/state` (SvelteKit virtual modules that\n * only exist inside a build). The snapshotting and the \"emit now, then after\n * each navigation\" plumbing live here instead, parameterised over the\n * navigation registrar and a `readPage` thunk, so they can be driven with\n * stand-ins in tests while the public wrapper stays a thin pass-through.\n *\n * @internal\n */\n\nimport { Effect, Queue, Stream } from \"effect\";\n\n/**\n * The fields of SvelteKit's `page` we snapshot. Mirrors the shape of\n * `@sveltejs/kit`'s `Page`, but as a plain readonly record rather than the live\n * mutating proxy.\n */\nexport interface PageSnapshot<P extends ReadonlyPage = ReadonlyPage> {\n readonly url: P[\"url\"];\n readonly params: P[\"params\"];\n readonly route: P[\"route\"];\n readonly status: P[\"status\"];\n readonly error: P[\"error\"];\n readonly data: P[\"data\"];\n readonly state: P[\"state\"];\n readonly form: P[\"form\"];\n}\n\n/**\n * The subset of `Page` fields the snapshot reads. Deliberately loose (the value\n * types are `unknown` / broad) so SvelteKit's `Page` — whose `params` / `data`\n * / `error` / `state` are app-specific — satisfies the constraint, while\n * `PageSnapshot<P>` recovers the precise field types from `P`.\n */\nexport interface ReadonlyPage {\n // SvelteKit 3 types `page.url` as an immutable `ReadonlyURL` (from\n // `$app/state`); everything but `searchParams` is enough here.\n readonly url: Readonly<Omit<URL, \"searchParams\">>;\n readonly params: Record<string, string | undefined>;\n readonly route: { readonly id: string | null };\n readonly status: number;\n readonly error: unknown;\n readonly data: Record<string, unknown>;\n readonly state: unknown;\n readonly form: unknown;\n}\n\n/**\n * Builds a shallow snapshot of the `page` fields that matter. We copy the\n * fields rather than forwarding the live `$app/state` proxy so downstream Effect\n * consumers see a stable value (the proxy mutates in place on every\n * navigation, which would make a captured reference unreliable). Nested values\n * (`url`, `data`, ...) are *not* deep-cloned: they're replaced wholesale on each\n * navigation, so a shallow copy is enough to distinguish snapshots.\n */\nexport function snapshotPage<P extends ReadonlyPage>(page: P): PageSnapshot<P> {\n return {\n url: page.url,\n params: page.params,\n route: page.route,\n status: page.status,\n error: page.error,\n data: page.data,\n state: page.state,\n form: page.form,\n };\n}\n\n/**\n * Emits a snapshot of `readPage()` immediately, then again every time\n * `registerAfterNavigate`'s callback fires (i.e. after each navigation). Backed\n * by an unbounded queue, exposed as a `Stream` — the same push bridge as the\n * navigation-event helpers.\n */\nexport function pageSnapshotStream<P extends ReadonlyPage>(\n registerAfterNavigate: (callback: () => void) => void,\n readPage: () => P,\n): Stream.Stream<PageSnapshot<P>> {\n const queue = Effect.runSync(Queue.unbounded<PageSnapshot<P>>());\n // Initial value: the current page, so consumers start from a known state.\n Queue.offerUnsafe(queue, snapshotPage(readPage()));\n // afterNavigate fires once on mount too, but emitting up front means a\n // late-subscribing consumer still sees the current page without waiting for a\n // navigation.\n registerAfterNavigate(() => {\n Queue.offerUnsafe(queue, snapshotPage(readPage()));\n });\n return Stream.fromQueue(queue);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA0DA,SAAgB,aAAqC,MAA0B;CAC7E,OAAO;EACL,KAAK,KAAK;EACV,QAAQ,KAAK;EACb,OAAO,KAAK;EACZ,QAAQ,KAAK;EACb,OAAO,KAAK;EACZ,MAAM,KAAK;EACX,OAAO,KAAK;EACZ,MAAM,KAAK;CACb;AACF;;;;;;;AAQA,SAAgB,mBACd,uBACA,UACgC;CAChC,MAAM,QAAQ,OAAO,QAAQ,MAAM,UAA2B,CAAC;CAE/D,MAAM,YAAY,OAAO,aAAa,SAAS,CAAC,CAAC;CAIjD,4BAA4B;EAC1B,MAAM,YAAY,OAAO,aAAa,SAAS,CAAC,CAAC;CACnD,CAAC;CACD,OAAO,OAAO,UAAU,KAAK;AAC/B"}
1
+ {"version":3,"file":"app-state.js","names":[],"sources":["../../src/internal/app-state.ts"],"sourcesContent":["/**\n * Pure, `$app/*`-free core for the `$app/state` bridge in `../State.ts`.\n *\n * The public helpers can't be unit-tested in plain Node because importing them\n * pulls in `$app/navigation` and `$app/state` (SvelteKit virtual modules that\n * only exist inside a build). The snapshotting, the \"emit now, then after each\n * navigation\" plumbing and the watch-to-`Stream` bridge live here instead,\n * parameterised over the navigation registrar, a {@link Watch} function and\n * read thunks, so they can be driven with stand-ins in tests while the public\n * wrappers stay thin pass-throughs. The only runes code is `watch.svelte.ts`.\n *\n * @internal\n */\n\nimport type { NavigationTarget, NavigationType } from \"$app/navigation\";\nimport { Data, Effect, Option, Queue, Stream } from \"effect\";\n\n/**\n * The fields of SvelteKit's `page` we snapshot. Mirrors the shape of\n * `@sveltejs/kit`'s `Page`, but as a plain readonly record rather than the live\n * mutating proxy.\n */\nexport interface PageSnapshot<P extends ReadonlyPage = ReadonlyPage> {\n readonly url: P[\"url\"];\n readonly params: P[\"params\"];\n readonly route: P[\"route\"];\n readonly status: P[\"status\"];\n readonly error: P[\"error\"];\n readonly data: P[\"data\"];\n readonly state: P[\"state\"];\n readonly form: P[\"form\"];\n readonly shallow: P[\"shallow\"];\n}\n\n/**\n * The subset of `Page` fields the snapshot reads. Deliberately loose (the value\n * types are `unknown` / broad) so SvelteKit's `Page` — whose `params` / `data`\n * / `error` / `state` are app-specific — satisfies the constraint, while\n * `PageSnapshot<P>` recovers the precise field types from `P`.\n */\nexport interface ReadonlyPage {\n // SvelteKit 3 types `page.url` as an immutable `ReadonlyURL` (from\n // `$app/state`); everything but `searchParams` is enough here.\n readonly url: Readonly<Omit<URL, \"searchParams\">>;\n readonly params: Record<string, string | undefined>;\n readonly route: { readonly id: string | null };\n readonly status: number;\n readonly error: unknown;\n readonly data: Record<string, unknown>;\n readonly state: unknown;\n readonly form: unknown;\n // SvelteKit 3: the target of the current shallow navigation, or `null`.\n readonly shallow: unknown;\n}\n\n/**\n * Builds a shallow snapshot of the `page` fields that matter. We copy the\n * fields rather than forwarding the live `$app/state` proxy so downstream Effect\n * consumers see a stable value (the proxy mutates in place on every\n * navigation, which would make a captured reference unreliable). Nested values\n * (`url`, `data`, ...) are *not* deep-cloned: they're replaced wholesale on each\n * navigation, so a shallow copy is enough to distinguish snapshots.\n */\nexport function snapshotPage<P extends ReadonlyPage>(page: P): PageSnapshot<P> {\n return {\n url: page.url,\n params: page.params,\n route: page.route,\n status: page.status,\n error: page.error,\n data: page.data,\n state: page.state,\n form: page.form,\n shallow: page.shallow,\n };\n}\n\n/**\n * Emits a snapshot of `readPage()` immediately, then again every time\n * `registerAfterNavigate`'s callback fires (i.e. after each navigation). Backed\n * by an unbounded queue, exposed as a `Stream` — the same push bridge as the\n * navigation-event helpers.\n */\nexport function pageSnapshotStream<P extends ReadonlyPage>(\n registerAfterNavigate: (callback: () => void) => void,\n readPage: () => P,\n): Stream.Stream<PageSnapshot<P>> {\n const queue = Effect.runSync(Queue.unbounded<PageSnapshot<P>>());\n // Initial value: the current page, so consumers start from a known state.\n Queue.offerUnsafe(queue, snapshotPage(readPage()));\n // afterNavigate fires once on mount too, but emitting up front means a\n // late-subscribing consumer still sees the current page without waiting for a\n // navigation.\n registerAfterNavigate(() => {\n Queue.offerUnsafe(queue, snapshotPage(readPage()));\n });\n return Stream.fromQueue(queue);\n}\n\n// ─── Watching rune state ─────────────────────────────────────────────────────\n\n/**\n * Observes a reactive read: calls `onValue` with `read()` once, then again\n * whenever something `read` touched changes, until the returned disposer runs.\n * The real implementation is `watch` in `./watch.svelte.ts`.\n */\nexport type Watch = <A>(read: () => A, onValue: (value: A) => void) => () => void;\n\n/**\n * A `Stream` of `read()`: the current value, then a new one each time `watch`\n * reports a change. Cold and lazy — each run opens its own watcher and disposes\n * it when the stream ends or is interrupted. Never fails; a throwing `watch`\n * surfaces as a defect (the stream dies with what it threw, with no watcher\n * left to dispose).\n */\nexport function watchedStream<A>(watch: Watch, read: () => A): Stream.Stream<A> {\n return Stream.callback<A>((queue) =>\n Effect.acquireRelease(\n Effect.sync(() =>\n watch(read, (value) => {\n Queue.offerUnsafe(queue, value);\n }),\n ),\n (dispose) => Effect.sync(dispose),\n ),\n );\n}\n\n// ─── navigating ──────────────────────────────────────────────────────────────\n\n/**\n * A snapshot of an in-flight SvelteKit navigation, as `$app/state`'s\n * `navigating` exposes it. `delta` is the history step count for a `popstate`\n * navigation and `null` otherwise; `complete` resolves when the navigation\n * finishes and rejects if it fails or is aborted.\n */\nexport interface NavigatingSnapshot {\n readonly type: Exclude<NavigationType, \"enter\">;\n readonly from: NavigationTarget | null;\n readonly to: NavigationTarget | null;\n readonly willUnload: boolean;\n readonly delta: number | null;\n readonly complete: Promise<void>;\n}\n\n/**\n * The shape of `$app/state`'s `navigating`: every field is `null` while no\n * navigation is in flight.\n */\nexport interface ReadonlyNavigating {\n readonly type: NavigatingSnapshot[\"type\"] | null;\n readonly from: NavigationTarget | null;\n readonly to: NavigationTarget | null;\n readonly willUnload: boolean | null;\n readonly delta?: number | null | undefined;\n readonly complete: Promise<void> | null;\n}\n\n/**\n * Copies `navigating` into a plain {@link NavigatingSnapshot}, or `None` when\n * no navigation is in flight (`type` is `null`). Reads every field, so a\n * watcher tracks the whole object.\n */\nexport function snapshotNavigating(\n navigating: ReadonlyNavigating,\n): Option.Option<NavigatingSnapshot> {\n const { type, from, to, willUnload, delta, complete } = navigating;\n if (type === null || willUnload === null || complete === null) return Option.none();\n return Option.some({ type, from, to, willUnload, delta: delta ?? null, complete });\n}\n\n// ─── updated ─────────────────────────────────────────────────────────────────\n\n/**\n * `updated.check()` rejected. SvelteKit resolves `false` when the version\n * request fails, so this signals misuse (e.g. calling it during server\n * rendering, where there is no `window`) rather than a network error.\n */\nexport class UpdateCheckError extends Data.TaggedError(\"UpdateCheckError\")<{\n readonly cause: unknown;\n}> {}\n\n/**\n * Runs `check` (SvelteKit's `updated.check`) as an Effect that succeeds with\n * whether a new version is available and fails with {@link UpdateCheckError}\n * if the promise rejects (or `check` throws).\n */\nexport function checkForUpdateWith(\n check: () => Promise<boolean>,\n): Effect.Effect<boolean, UpdateCheckError> {\n return Effect.tryPromise({\n try: () => check(),\n catch: (cause) => new UpdateCheckError({ cause }),\n });\n}\n\n// ─── CurrentPage ─────────────────────────────────────────────────────────────\n\n/** The service shape behind `State.CurrentPage`. */\nexport interface CurrentPageShape<P extends ReadonlyPage = ReadonlyPage> {\n /** Snapshots the current page when run. */\n readonly get: Effect.Effect<PageSnapshot<P>>;\n /** The current page, then a fresh snapshot whenever any of its fields changes. */\n readonly changes: Stream.Stream<PageSnapshot<P>>;\n}\n\n/** Builds a {@link CurrentPageShape} reading `readPage()` and watching it with `watch`. */\nexport function makeCurrentPage<P extends ReadonlyPage>(\n readPage: () => P,\n watch: Watch,\n): CurrentPageShape<P> {\n const read = (): PageSnapshot<P> => snapshotPage(readPage());\n return { get: Effect.sync(read), changes: watchedStream(watch, read) };\n}\n"],"mappings":";;;;;;;;;;AA+DA,SAAgB,aAAqC,MAA0B;CAC7E,OAAO;EACL,KAAK,KAAK;EACV,QAAQ,KAAK;EACb,OAAO,KAAK;EACZ,QAAQ,KAAK;EACb,OAAO,KAAK;EACZ,MAAM,KAAK;EACX,OAAO,KAAK;EACZ,MAAM,KAAK;EACX,SAAS,KAAK;CAChB;AACF;;;;;;;AAQA,SAAgB,mBACd,uBACA,UACgC;CAChC,MAAM,QAAQ,OAAO,QAAQ,MAAM,UAA2B,CAAC;CAE/D,MAAM,YAAY,OAAO,aAAa,SAAS,CAAC,CAAC;CAIjD,4BAA4B;EAC1B,MAAM,YAAY,OAAO,aAAa,SAAS,CAAC,CAAC;CACnD,CAAC;CACD,OAAO,OAAO,UAAU,KAAK;AAC/B;;;;;;;;AAkBA,SAAgB,cAAiB,OAAc,MAAiC;CAC9E,OAAO,OAAO,UAAa,UACzB,OAAO,eACL,OAAO,WACL,MAAM,OAAO,UAAU;EACrB,MAAM,YAAY,OAAO,KAAK;CAChC,CAAC,CACH,IACC,YAAY,OAAO,KAAK,OAAO,CAClC,CACF;AACF;;;;;;AAqCA,SAAgB,mBACd,YACmC;CACnC,MAAM,EAAE,MAAM,MAAM,IAAI,YAAY,OAAO,aAAa;CACxD,IAAI,SAAS,QAAQ,eAAe,QAAQ,aAAa,MAAM,OAAO,OAAO,KAAK;CAClF,OAAO,OAAO,KAAK;EAAE;EAAM;EAAM;EAAI;EAAY,OAAO,SAAS;EAAM;CAAS,CAAC;AACnF;;;;;;AASA,IAAa,mBAAb,cAAsC,KAAK,YAAY,kBAAkB,CAAC,CAEvE,CAAC;;;;;;AAOJ,SAAgB,mBACd,OAC0C;CAC1C,OAAO,OAAO,WAAW;EACvB,WAAW,MAAM;EACjB,QAAQ,UAAU,IAAI,iBAAiB,EAAE,MAAM,CAAC;CAClD,CAAC;AACH;;AAaA,SAAgB,gBACd,UACA,OACqB;CACrB,MAAM,aAA8B,aAAa,SAAS,CAAC;CAC3D,OAAO;EAAE,KAAK,OAAO,KAAK,IAAI;EAAG,SAAS,cAAc,OAAO,IAAI;CAAE;AACvE"}
@@ -9,30 +9,128 @@ type ResultCallback = Extract<Awaited<ReturnType<SubmitFunction>>, (...args: nev
9
9
  type ResultOptions = Parameters<ResultCallback>[0];
10
10
  /** A runtime the handler effects can run against (a `Context` or `ManagedRuntime`). */
11
11
  type EnhanceRuntime<R> = Context.Context<R> | ManagedRuntime.ManagedRuntime<R, never>;
12
- /** Effect-based handlers for the two phases of an enhanced form submission. */
13
- interface EnhanceHandlers<R = never> {
12
+ /**
13
+ * Effect-based handlers for the two phases of an enhanced form submission,
14
+ * plus `onError` for the errors they fail with. `ESubmit` / `EResult` are the
15
+ * errors `onSubmit` / `onResult` may fail with, and `E` (their union by
16
+ * default) the error `onError` receives. Each handler is called as a method of
17
+ * the handlers object, so `this` in a handler written as a method is that
18
+ * object.
19
+ */
20
+ interface EnhanceHandlers<R = never, ESubmit = never, EResult = never, E = ESubmit | EResult> {
14
21
  /**
15
22
  * Runs before the request is sent. Call `input.cancel()` to abort the
16
23
  * submission (the result phase then never runs). Use for optimistic UI or
17
24
  * validation.
25
+ *
26
+ * If it fails with `ESubmit`, the submission is cancelled and the error goes
27
+ * to {@link EnhanceHandlers.onError | onError}.
18
28
  */
19
- readonly onSubmit?: (input: SubmitInput) => Effect.Effect<void, never, R>;
29
+ readonly onSubmit?: ((input: SubmitInput) => Effect.Effect<void, ESubmit, R>) | undefined;
20
30
  /**
21
31
  * Runs once the action responds, with the `ActionResult` and SvelteKit's
22
32
  * `update()` (which applies the default behaviour — updating `page.form`,
23
33
  * resetting the form, invalidating). If omitted, SvelteKit's default
24
34
  * behaviour applies automatically.
35
+ *
36
+ * If it fails with `EResult`, the error goes to
37
+ * {@link EnhanceHandlers.onError | onError}; SvelteKit's default behaviour
38
+ * is not applied in its place.
25
39
  */
26
- readonly onResult?: (options: ResultOptions) => Effect.Effect<void, never, R>;
40
+ readonly onResult?: ((options: ResultOptions) => Effect.Effect<void, EResult, R>) | undefined;
41
+ /**
42
+ * Handles an error `onSubmit` or `onResult` failed with (`E`, the union of
43
+ * their error types), as `Effect.catch` would. It runs on the same runtime
44
+ * and must not fail itself.
45
+ *
46
+ * Write it after `onSubmit` / `onResult` for `error` to be inferred
47
+ * precisely. Written before a handler whose parameters are not annotated
48
+ * (such as `onResult: ({ result }) => ...`), it still type-checks, but
49
+ * `error` is `unknown`: TypeScript types `onError`'s parameter before it has
50
+ * seen that handler's error type. An annotated parameter (or a function
51
+ * passed by name) must accept every error the handlers fail with, in either
52
+ * order.
53
+ *
54
+ * Only failures reach it: a defect or interruption (in a handler or in
55
+ * `onError`) still rejects the promise SvelteKit awaits, as does a failure
56
+ * when `onError` is omitted (rejecting with the error itself, as if the
57
+ * handler ended in `Effect.orDie`). SvelteKit does not catch that rejection:
58
+ * it sends no request after a rejected `onSubmit`, applies no default result
59
+ * after a rejected `onResult`, and the browser reports an unhandled
60
+ * rejection.
61
+ */
62
+ readonly onError?: ((error: E) => Effect.Effect<void, never, R>) | undefined;
27
63
  }
28
64
  /** Options for {@link makeSubmitFunction}. */
29
65
  interface EnhanceOptions<R = never> {
30
66
  /**
31
- * The runtime to run the handler effects against. Omit when the handlers
32
- * require no services (`R = never`); they then run on the default runtime.
67
+ * The runtime to run the handler effects against, providing every service
68
+ * `R` they require. Required when the handlers (or `onError`) require
69
+ * services; omit it when they require none (`R = never`), and they run on
70
+ * the default runtime.
33
71
  */
34
- readonly runtime?: EnhanceRuntime<R>;
72
+ readonly runtime?: EnhanceRuntime<R> | undefined;
73
+ }
74
+ /**
75
+ * The options argument of `Forms.enhance`, as a rest tuple: optional when the
76
+ * handlers require no services (`R = never`), and otherwise required, with a
77
+ * `runtime` that provides `R`. There is no ambient runtime to fall back on, so
78
+ * handlers that require services and run without one would die with a
79
+ * missing-service defect. A function that wraps `Forms.enhance` for a generic
80
+ * `R` takes its options as `...options: EnhanceOptionsArgs<R>` and forwards
81
+ * them unchanged.
82
+ */
83
+ type EnhanceOptionsArgs<R> = [R] extends [never] ? [options?: EnhanceOptions<never> | undefined] : [options: EnhanceOptions<R> & {
84
+ readonly runtime: EnhanceRuntime<R>;
85
+ }];
86
+ /** The handler names {@link makeSubmitFunction} accepts. */
87
+ type EnhanceHandlerKey = keyof EnhanceHandlers;
88
+ /**
89
+ * Records which handlers a handlers object names: `Keys` is inferred from the
90
+ * object's keys, which TypeScript knows before it types any handler.
91
+ */
92
+ type HandlerKeys<Keys extends EnhanceHandlerKey> = { readonly [Key in Keys]?: unknown; };
93
+ /** Whether `Errors` includes a handler error not inferred yet ({@link ErrorNotInferred}). */
94
+ type IsErrorNotInferred<Errors> = 0 extends 1 & Errors ? true : false;
95
+ /**
96
+ * The services a handler requires: `R`, or none while it is not inferred
97
+ * ({@link ServicesNotInferred}), as for an absent handler.
98
+ *
99
+ * Since `unknown` must count as none, so does every requirements type that
100
+ * `unknown` is assignable to: an effect typed as requiring `any` or `unknown`
101
+ * (to which a union with `unknown` collapses), such as one annotated
102
+ * `Effect.Effect<void, never, any>`, is treated as requiring no services, so
103
+ * no runtime is demanded for it. Such a type names no service a runtime could
104
+ * be checked against, and only a top type fits every handler in TypeScript's
105
+ * first inference pass.
106
+ */
107
+ type RequiredServices<R> = unknown extends R ? never : R;
108
+ /** The errors of the handlers named in `Keys`. */
109
+ type HandlerErrors<Keys extends EnhanceHandlerKey, ESubmit, EResult> = ("onSubmit" extends Keys ? ESubmit : never) | ("onResult" extends Keys ? EResult : never);
110
+ /**
111
+ * The error an unannotated `onError` receives: the handlers' errors, or
112
+ * `unknown` while one of them is not inferred yet (`onError` written before a
113
+ * handler whose parameters are not annotated).
114
+ */
115
+ type OnErrorInput<Errors> = IsErrorNotInferred<Errors> extends true ? unknown : Errors;
116
+ /**
117
+ * Requires `onError` to accept every error in `Errors` when its parameter is
118
+ * annotated (`E` is then inferred from the annotation). The check waits while
119
+ * a handler's error is not inferred yet, and applies once it is.
120
+ */
121
+ type OnErrorAccepts<R, Errors, E> = IsErrorNotInferred<Errors> extends true ? unknown : [Errors] extends [E] ? unknown : OnErrorMustAccept<R, Errors>;
122
+ /** The `onError` that {@link OnErrorAccepts} requires. */
123
+ interface OnErrorMustAccept<R, Errors> {
124
+ readonly onError?: ((error: Errors) => Effect.Effect<void, never, R>) | undefined;
35
125
  }
126
+ /**
127
+ * The handlers {@link makeSubmitFunction} accepts: {@link EnhanceHandlers},
128
+ * with each handler's requirements in a parameter of its own (`RSubmit`,
129
+ * `RResult`, `RError`). TypeScript infers one candidate per handler and does
130
+ * not union them, so a single requirements parameter would reject handlers
131
+ * that require different services even when the runtime provides them all.
132
+ */
133
+ type EnhanceHandlersBy<RSubmit, RResult, RError, ESubmit, EResult, E> = Pick<EnhanceHandlers<RSubmit, ESubmit, EResult, E>, "onSubmit"> & Pick<EnhanceHandlers<RResult, ESubmit, EResult, E>, "onResult"> & Pick<EnhanceHandlers<RError, ESubmit, EResult, E>, "onError">;
36
134
  //#endregion
37
- export { EnhanceHandlers, EnhanceOptions, EnhanceRuntime, ResultOptions, SubmitInput };
135
+ export { EnhanceHandlerKey, EnhanceHandlers, EnhanceHandlersBy, EnhanceOptions, EnhanceOptionsArgs, EnhanceRuntime, HandlerErrors, HandlerKeys, IsErrorNotInferred, OnErrorAccepts, OnErrorInput, OnErrorMustAccept, RequiredServices, ResultOptions, SubmitInput };
38
136
  //# sourceMappingURL=enhance.d.ts.map
@@ -1,6 +1,6 @@
1
1
  import { Context, Effect } from "effect";
2
2
  //#region src/internal/enhance.ts
3
- /** Run an `Effect<A, never, R>` on the given runtime (or the default when none). */
3
+ /** Run an `Effect<A, E, R>` on the given runtime (or the default when none). */
4
4
  const runPromiseWith = (runtime) => (effect) => {
5
5
  if (runtime === void 0) return Effect.runPromise(effect);
6
6
  return Context.isContext(runtime) ? Effect.runPromiseWith(runtime)(effect) : runtime.runPromise(effect);
@@ -8,14 +8,38 @@ const runPromiseWith = (runtime) => (effect) => {
8
8
  /**
9
9
  * Build a SvelteKit `SubmitFunction` from Effect-based handlers. See
10
10
  * `Forms.enhance` for the public entry point and documentation.
11
+ *
12
+ * The type parameters are inferred, never written. `RSubmit`, `RResult` and
13
+ * `RError` are the services each handler requires (see
14
+ * {@link EnhanceHandlersBy}). `Keys` names the handlers present, and `E` (the
15
+ * error `onError` receives) is computed from their errors, or inferred from
16
+ * `onError`'s parameter annotation and then checked against them, so a
17
+ * handlers object type-checks whatever its property order (see
18
+ * {@link EnhanceHandlers.onError}). The options argument, and its `runtime`,
19
+ * are required when the handlers require any service, and the runtime must
20
+ * provide all of them (see {@link EnhanceOptionsArgs}).
21
+ *
22
+ * When a call does not type-check, TypeScript may report it against its first
23
+ * inference pass, which skips handlers whose parameters are not annotated. The
24
+ * defaults of the handler parameters ({@link ErrorNotInferred},
25
+ * {@link ServicesNotInferred}) accept any handler, so a missing or wrong
26
+ * runtime is reported on the call or on the runtime, never on a handler.
11
27
  */
12
- const makeSubmitFunction = (handlers, options) => {
28
+ const makeSubmitFunction = (handlers, ...rest) => {
29
+ const options = rest[0];
13
30
  const run = runPromiseWith(options?.runtime);
31
+ const phases = handlers;
32
+ const onError = handlers.onError;
33
+ const handleError = onError === void 0 ? void 0 : (error) => onError.call(handlers, error);
14
34
  return async (input) => {
15
- if (handlers.onSubmit) await run(handlers.onSubmit(input));
16
- if (!handlers.onResult) return void 0;
17
- const onResult = handlers.onResult;
18
- return (resultOptions) => run(onResult(resultOptions));
35
+ const submit = phases.onSubmit?.(input);
36
+ if (submit !== void 0) await run(handleError === void 0 ? submit : submit.pipe(Effect.catch((error) => Effect.sync(() => input.cancel()).pipe(Effect.andThen(handleError(error))))));
37
+ const { onResult } = phases;
38
+ if (!onResult) return void 0;
39
+ return (resultOptions) => {
40
+ const result = onResult.call(handlers, resultOptions);
41
+ return run(handleError === void 0 ? result : result.pipe(Effect.catch(handleError)));
42
+ };
19
43
  };
20
44
  };
21
45
  //#endregion