@thomasfosterau/effect-sveltekit 0.5.0 → 0.7.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 (121) hide show
  1. package/LICENSE.md +9 -0
  2. package/README.md +178 -83
  3. package/dist/Hooks.d.ts +3 -3
  4. package/dist/Hooks.js +1 -1
  5. package/dist/Hooks.js.map +1 -1
  6. package/dist/Navigation.d.ts +70 -17
  7. package/dist/Navigation.js +93 -19
  8. package/dist/Navigation.js.map +1 -1
  9. package/dist/Remote.d.ts +54 -35
  10. package/dist/Remote.js +22 -11
  11. package/dist/Remote.js.map +1 -1
  12. package/dist/State.d.ts +79 -6
  13. package/dist/State.js +117 -27
  14. package/dist/State.js.map +1 -1
  15. package/dist/adapter/assets.d.ts +24 -7
  16. package/dist/adapter/assets.js +58 -37
  17. package/dist/adapter/assets.js.map +1 -1
  18. package/dist/adapter/handler.d.ts +27 -12
  19. package/dist/adapter/handler.js +53 -26
  20. package/dist/adapter/handler.js.map +1 -1
  21. package/dist/adapter/index.d.ts +3 -3
  22. package/dist/adapter/index.js +1 -1
  23. package/dist/adapter/index.js.map +1 -1
  24. package/dist/adapter/serve.d.ts +6 -5
  25. package/dist/adapter/serve.js +3 -1
  26. package/dist/adapter/serve.js.map +1 -1
  27. package/dist/api-client.d.ts +3 -4
  28. package/dist/api-client.js +2 -1
  29. package/dist/api-client.js.map +1 -1
  30. package/dist/app-runtime.d.ts +12 -9
  31. package/dist/app-runtime.js +23 -14
  32. package/dist/app-runtime.js.map +1 -1
  33. package/dist/client/Hooks.d.ts +4 -4
  34. package/dist/client/Hooks.js +2 -1
  35. package/dist/client/Hooks.js.map +1 -1
  36. package/dist/client.d.ts +2 -2
  37. package/dist/config.d.ts +7 -5
  38. package/dist/config.js +5 -34
  39. package/dist/config.js.map +1 -1
  40. package/dist/errors.d.ts +14 -16
  41. package/dist/errors.js +20 -12
  42. package/dist/errors.js.map +1 -1
  43. package/dist/forms.d.ts +70 -8
  44. package/dist/forms.js +69 -5
  45. package/dist/forms.js.map +1 -1
  46. package/dist/internal/app-state.d.ts +42 -5
  47. package/dist/internal/app-state.js +64 -15
  48. package/dist/internal/app-state.js.map +1 -1
  49. package/dist/internal/auto.d.ts +23 -14
  50. package/dist/internal/auto.js +50 -40
  51. package/dist/internal/auto.js.map +1 -1
  52. package/dist/internal/enhance.d.ts +114 -14
  53. package/dist/internal/enhance.js +33 -7
  54. package/dist/internal/enhance.js.map +1 -1
  55. package/dist/internal/error.d.ts +13 -0
  56. package/dist/internal/error.js +4 -3
  57. package/dist/internal/error.js.map +1 -1
  58. package/dist/internal/navigation-events.js +3 -1
  59. package/dist/internal/navigation-events.js.map +1 -1
  60. package/dist/internal/remote-run.d.ts +6 -6
  61. package/dist/internal/remote-run.js +13 -16
  62. package/dist/internal/remote-run.js.map +1 -1
  63. package/dist/internal/remote-validators.js +4 -3
  64. package/dist/internal/remote-validators.js.map +1 -1
  65. package/dist/internal/route-params.d.ts +1 -2
  66. package/dist/internal/sse.js +2 -1
  67. package/dist/internal/sse.js.map +1 -1
  68. package/dist/internal/watch.svelte.js +33 -0
  69. package/dist/internal/watch.svelte.js.map +1 -0
  70. package/dist/kit.d.ts +12 -11
  71. package/dist/kit.js +6 -2
  72. package/dist/kit.js.map +1 -1
  73. package/dist/realtime.d.ts +49 -30
  74. package/dist/realtime.js +90 -46
  75. package/dist/realtime.js.map +1 -1
  76. package/dist/remote-functions.d.ts +34 -18
  77. package/dist/remote-functions.js +78 -46
  78. package/dist/remote-functions.js.map +1 -1
  79. package/dist/remote-reactive.d.ts +8 -4
  80. package/dist/remote-reactive.js.map +1 -1
  81. package/dist/request-parse.d.ts +13 -13
  82. package/dist/request-parse.js +29 -15
  83. package/dist/request-parse.js.map +1 -1
  84. package/dist/request-runtime.d.ts +16 -11
  85. package/dist/request-runtime.js +28 -29
  86. package/dist/request-runtime.js.map +1 -1
  87. package/dist/request-services.d.ts +10 -8
  88. package/dist/request-services.js +3 -1
  89. package/dist/request-services.js.map +1 -1
  90. package/dist/response-headers.d.ts +2 -3
  91. package/dist/response-headers.js +2 -1
  92. package/dist/response-headers.js.map +1 -1
  93. package/dist/runtime.d.ts +8 -5
  94. package/dist/runtime.js +4 -1
  95. package/dist/runtime.js.map +1 -1
  96. package/dist/server/Endpoint.d.ts +17 -11
  97. package/dist/server/Endpoint.js +18 -4
  98. package/dist/server/Endpoint.js.map +1 -1
  99. package/dist/server/Hooks.d.ts +6 -6
  100. package/dist/server/Hooks.js +1 -1
  101. package/dist/server/Hooks.js.map +1 -1
  102. package/dist/server.d.ts +8 -7
  103. package/dist/server.js +8 -8
  104. package/dist/server.js.map +1 -1
  105. package/dist/streamed-load.d.ts +2 -3
  106. package/dist/streamed-load.js +1 -1
  107. package/dist/streamed-load.js.map +1 -1
  108. package/dist/testing.d.ts +15 -14
  109. package/dist/testing.js +27 -7
  110. package/dist/testing.js.map +1 -1
  111. package/dist/transport.d.ts +6 -7
  112. package/dist/transport.js +3 -6
  113. package/dist/transport.js.map +1 -1
  114. package/dist/universal.d.ts +3 -3
  115. package/dist/universal.js +1 -1
  116. package/dist/universal.js.map +1 -1
  117. package/dist/vite.d.ts +4 -4
  118. package/dist/vite.js +102 -103
  119. package/dist/vite.js.map +1 -1
  120. package/package.json +14 -11
  121. package/LICENSE +0 -21
package/LICENSE.md ADDED
@@ -0,0 +1,9 @@
1
+ # MIT License
2
+
3
+ Copyright © 2026 [Thomas Foster](https://thomasfoster.au/)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -3,21 +3,24 @@
3
3
  Effect integration for SvelteKit: load/action/endpoint wrappers, remote
4
4
  functions, an `effectKit()` Vite plugin, and an Effect HTTP deployment adapter.
5
5
 
6
- It depends on
6
+ It lists
7
7
  [`@thomasfosterau/effect-svelte`](https://www.npmjs.com/package/@thomasfosterau/effect-svelte)
8
- for the Svelte runes hooks.
8
+ as a dependency, so a compatible version is always installed alongside it. To
9
+ import its Svelte runes hooks (`useDerived`, `AsyncResult`, …) in your own
10
+ code, add it to your app's dependencies too: pnpm only lets a package import
11
+ what it lists itself.
9
12
 
10
13
  ## Install
11
14
 
12
15
  ```bash
13
- pnpm add @thomasfosterau/effect-sveltekit effect@rc svelte @sveltejs/kit
16
+ pnpm add @thomasfosterau/effect-sveltekit effect svelte @sveltejs/kit
14
17
  # the Effect deployment adapter additionally needs:
15
- pnpm add @effect/platform-node@rc
18
+ pnpm add @effect/platform-node
16
19
  ```
17
20
 
18
- `effect` (`>=4.0.0-rc.118`) and `svelte` (`^5.40.0`) are required peers;
21
+ `effect` (`>=4.0.2`) and `svelte` (`^5.40.0`) are required peers;
19
22
  `@sveltejs/kit` (`^3.0.0`), `vite` (for `./vite`) and
20
- `@effect/platform-node` (`>=4.0.0-rc.118`, for `./adapter`) are optional. The
23
+ `@effect/platform-node` (`>=4.0.2`, for `./adapter`) are optional. The
21
24
  HTTP modules that lived in `@effect/platform` in v3 are part of `effect` itself
22
25
  in v4 (`effect/http`), so no other Effect package is needed. Keep a single copy
23
26
  of `effect`, and keep `effect` and `@effect/platform-node` on the same
@@ -38,26 +41,26 @@ export const load = serverLoad((event) => Effect.succeed({ id: event.params.id }
38
41
  The same wrappers exist for actions, endpoints and hooks, on the default
39
42
  per-request runtime or on your own services (see
40
43
  [App-wide runtime](#app-wide-runtime-share-your-services-across-requests)); the
41
- `effectKit()` Vite plugin lets route files export bare Effects instead.
44
+ optional `effectKit()` Vite plugin lets route files export bare Effects instead.
42
45
 
43
46
  ## API
44
47
 
45
- | Entry point | What it provides |
46
- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
47
- | `@thomasfosterau/effect-sveltekit` | Universal helpers: `load` / `makeUniversalRuntime`, the `Remote` call namespace, `Hooks` (`reroute`, `transport`), `ResponseHeaders`, `sveltekitConfig` / `readConfig`, `apiClient` |
48
- | `…/server` | Server wrappers (`serverLoad`, `action`, `actions`, `endpoint`), `Hooks`, `Endpoint`, `makeServerRuntime`, the per-request runtime seam (`makeRuntimeAdapter` / `RequestRuntime`), `Errors`, `RemoteFunction`, request decoding (`decodeParams`, `decodeJson`, …) |
49
- | `…/client` | Browser-only `Navigation`, `State`, `Remote`, `Realtime`, `Forms` and `Hooks` namespaces, and `useRemoteQuery` |
50
- | `…/vite` | The `effectKit()` Vite plugin (auto-wraps bare-Effect route exports) |
51
- | `…/adapter` | The Effect HTTP deployment adapter (`effectAdapter`) |
52
- | `…/adapter/handler` | The SvelteKit app as an Effect HTTP app (`makeHandler`, `makeHandlerWith`, `AssetSource`, `toWebHandler`) |
53
- | `…/adapter/serve` | The Node server the adapter's `index.js` runs (`serve`, `layer`) |
54
- | `…/testing` | A fake `RequestEvent` and runners for unit-testing server wrappers and remote functions (`TestRequestEvent`, `runServerLoad`, `runAction`, `runEndpoint`, `runRemote`) |
55
- | `…/internal/auto` | Referenced by `effectKit()`'s generated code; not a public API |
48
+ | Entry point | What it provides |
49
+ | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
50
+ | `@thomasfosterau/effect-sveltekit` | Universal helpers: `load` / `makeUniversalRuntime`, the `Remote` call namespace, `Hooks` (`reroute`, `transport`), `ResponseHeaders`, `sveltekitConfig` / `readConfig`, `apiClient` |
51
+ | `…/server` | Server wrappers (`serverLoad`, `action`, `actions`, `endpoint`), `Hooks`, `Endpoint`, `makeServerRuntime`, the per-request runtime seam (`makeRuntimeAdapter` / `RequestRuntime`), `Errors`, `RemoteFunction` / `RemoteFunctionUsageError`, `KitError` / `KitErrorBody`, request decoding (`decodeParams`, `decodeJson`, …) |
52
+ | `…/client` | Browser-only `Navigation`, `State` (incl. the `CurrentPage` service), `Remote`, `Realtime`, `Forms` and `Hooks` namespaces, and `useRemoteQuery` / `RemoteQueryResult` |
53
+ | `…/vite` | The `effectKit()` Vite plugin (auto-wraps bare-Effect route exports) |
54
+ | `…/adapter` | The Effect HTTP deployment adapter (`effectAdapter`) |
55
+ | `…/adapter/handler` | The SvelteKit app as an Effect HTTP app (`makeHandler`, `makeHandlerWith`, `AssetSource`, `toWebHandler`, the `SvelteKitServerError` / `AssetFetchError` defects) |
56
+ | `…/adapter/serve` | The Node server the adapter's `index.js` runs (`serve`, `layer`) |
57
+ | `…/testing` | A fake `RequestEvent` and runners for unit-testing server wrappers and remote functions (`TestRequestEvent`, `runServerLoad`, `runAction`, `runEndpoint`, `runRemote`) |
58
+ | `…/internal/auto` | Referenced by `effectKit()`'s generated code; not a public API |
56
59
 
57
60
  ### At a glance
58
61
 
59
62
  - **Effects as route exports** — explicit wrappers, app-wide runtimes (services
60
- built once per process), or the `effectKit()` plugin.
63
+ built once per process), or, optionally, the `effectKit()` plugin.
61
64
  - **Bring-your-own per-request runtime** — `makeRuntimeAdapter(provider, opts)`
62
65
  binds the server wrappers to a `RequestRuntime` provider, so the handler
63
66
  effect's services are your own application catalogue (not `SvelteKitServices`).
@@ -81,8 +84,9 @@ per-request runtime or on your own services (see
81
84
  `Hooks.markerTransporter` for non-round-tripping wrappers) and **`$env` ⇄
82
85
  Effect `Config`**.
83
86
  - **Client navigation/state** — the `Navigation` (`goto`/`invalidate`/… plus
84
- `beforeNavigate`/`afterNavigate`/`onNavigate` streams) and `State` (`page`)
85
- namespaces.
87
+ `beforeNavigate`/`afterNavigate`/`onNavigate` streams) and `State` (`page`,
88
+ `navigating` and `updated` streams, `checkForUpdate`, and the `CurrentPage`
89
+ service) namespaces.
86
90
  - **Effect deployment adapter** — the generated app is a platform-neutral
87
91
  Effect `HttpApp` (`makeHandlerWith`): serve it as a production Node HTTP server
88
92
  (`effect/http` + `@effect/platform-node`, the default) or as a Web
@@ -91,51 +95,6 @@ per-request runtime or on your own services (see
91
95
  (`AssetSource.fileSystem` for Node, `AssetSource.cloudflareAssets(env.ASSETS)`
92
96
  for Workers, or any custom source — R2, KV, a CDN passthrough).
93
97
 
94
- ### SvelteKit Vite plugin (recommended)
95
-
96
- ```ts
97
- // vite.config.ts
98
- import { sveltekit } from "@sveltejs/kit/vite";
99
- import { effectKit } from "@thomasfosterau/effect-sveltekit/vite";
100
-
101
- export default {
102
- plugins: [effectKit(), sveltekit()],
103
- };
104
- ```
105
-
106
- With the plugin installed, you can return an Effect (or a function returning
107
- an Effect) directly from any standard SvelteKit route file (`+page.ts`,
108
- `+page.server.ts`, `+layout(.server).ts`, `+server.ts`,
109
- `hooks.(server|client).ts`):
110
-
111
- ```ts
112
- // +page.server.ts
113
- import { Effect } from "effect";
114
-
115
- export const load = (event) => Effect.succeed({ id: event.params.id });
116
- ```
117
-
118
- The plugin rewrites the export so it goes through the same runtime as the
119
- explicit wrappers below; plain promise/value-returning functions pass through
120
- unchanged.
121
-
122
- #### Supported exports
123
-
124
- | File | Exports auto-wrapped |
125
- | ------------------- | ---------------------------------------------------------------------- |
126
- | `+page.ts` | `load` |
127
- | `+page.server.ts` | `load`, `actions` |
128
- | `+layout.ts` | `load` |
129
- | `+layout.server.ts` | `load` |
130
- | `+server.ts` | `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`, `HEAD`, `fallback` |
131
- | `hooks.ts` | `reroute` |
132
- | `hooks.server.ts` | `handle`, `handleFetch`, `handleError`, `init` |
133
- | `hooks.client.ts` | `handleError`, `init` |
134
-
135
- Config exports like `prerender`, `ssr`, `csr`, and `trailingSlash` — and the
136
- universal `transport` hook (which is plain data, not a function) — are never
137
- touched.
138
-
139
98
  ### App-wide runtime (share your services across requests)
140
99
 
141
100
  Define your application's Effect services once as a `Layer`; the layer is
@@ -170,20 +129,13 @@ The runtime exposes `serverLoad`, `action`, `actions`, `handle`, `handleFetch`,
170
129
  For universal `load` functions there is a browser-safe counterpart,
171
130
  `makeUniversalRuntime`, in `@thomasfosterau/effect-sveltekit`.
172
131
 
173
- To make the same services available to bare-Effect exports handled by the
174
- Vite plugin, point the plugin at the runtime module:
175
-
176
- ```ts
177
- // vite.config.ts
178
- effectKit({ serverRuntime: "#lib/server/runtime.ts" });
179
- ```
180
-
181
132
  #### One runtime per request
182
133
 
183
- Wire the runtime's `handle` hook (or the auto-wrapped `handle` from the Vite
184
- plugin) in `src/hooks.server.ts`. It creates a **per-request runtime** — the
185
- application layer's services plus the request's `HttpClient`/`SvelteKitRequestEvent`,
186
- built once and sharing a single request scope — and stores it on `event.locals`:
134
+ Wire the runtime's `handle` hook (or the auto-wrapped `handle` from the
135
+ [Vite plugin](#sveltekit-vite-plugin-optional)) in `src/hooks.server.ts`. It
136
+ creates a **per-request runtime** — the application layer's services plus the
137
+ request's `HttpClient`/`SvelteKitRequestEvent`, built once and sharing a single
138
+ request scope — and stores it on `event.locals`:
187
139
 
188
140
  ```ts
189
141
  // src/hooks.server.ts
@@ -215,6 +167,64 @@ export const handle = runtime.handle(({ event, resolve }) =>
215
167
  Wrappers from a _different_ runtime, or requests without `handle` wired, fall
216
168
  back to building the request services per call, so this is backward compatible.
217
169
 
170
+ ### SvelteKit Vite plugin (optional)
171
+
172
+ The explicit wrappers above are the primary API. The `effectKit()` Vite plugin
173
+ is an optional convenience on top of them:
174
+
175
+ ```ts
176
+ // vite.config.ts
177
+ import { sveltekit } from "@sveltejs/kit/vite";
178
+ import { effectKit } from "@thomasfosterau/effect-sveltekit/vite";
179
+
180
+ export default {
181
+ plugins: [effectKit(), sveltekit()],
182
+ };
183
+ ```
184
+
185
+ With the plugin installed, you can return an Effect (or a function returning
186
+ an Effect) directly from any standard SvelteKit route file (`+page.ts`,
187
+ `+page.server.ts`, `+layout(.server).ts`, `+server.ts`,
188
+ `hooks.(server|client).ts`):
189
+
190
+ ```ts
191
+ // +page.server.ts
192
+ import { Effect } from "effect";
193
+
194
+ export const load = (event) => Effect.succeed({ id: event.params.id });
195
+ ```
196
+
197
+ The plugin rewrites the export so it goes through the same runtime as the
198
+ explicit wrappers; plain promise/value-returning functions pass through
199
+ unchanged. SvelteKit's generated `$types` cannot see through the rewrite, so
200
+ type a plugin-wrapped route's `data` prop yourself; explicit wrappers return
201
+ real SvelteKit functions, so route `$types` work as usual.
202
+
203
+ To make an app-wide runtime's services available to bare-Effect exports, point
204
+ the plugin at the runtime module:
205
+
206
+ ```ts
207
+ // vite.config.ts
208
+ effectKit({ serverRuntime: "#lib/server/runtime.ts" });
209
+ ```
210
+
211
+ #### Supported exports
212
+
213
+ | File | Exports auto-wrapped |
214
+ | ------------------- | ---------------------------------------------------------------------- |
215
+ | `+page.ts` | `load` |
216
+ | `+page.server.ts` | `load`, `actions` |
217
+ | `+layout.ts` | `load` |
218
+ | `+layout.server.ts` | `load` |
219
+ | `+server.ts` | `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`, `HEAD`, `fallback` |
220
+ | `hooks.ts` | `reroute` |
221
+ | `hooks.server.ts` | `handle`, `handleFetch`, `handleError`, `init` |
222
+ | `hooks.client.ts` | `handleError`, `init` |
223
+
224
+ Config exports like `prerender`, `ssr`, `csr`, and `trailingSlash` — and the
225
+ universal `transport` hook (which is plain data, not a function) — are never
226
+ touched.
227
+
218
228
  ### Effect deployment adapter
219
229
 
220
230
  A SvelteKit [adapter](https://svelte.dev/docs/kit/adapters) (like
@@ -363,7 +373,8 @@ import {
363
373
  // $app/navigation: promise wrappers (goto, invalidate, ...) and lifecycle
364
374
  // events as Effect Streams (beforeNavigate/afterNavigate/onNavigate)
365
375
  Navigation,
366
- // $app/state: the current page as a Stream of snapshots
376
+ // $app/state: page / navigating / updated as Streams, plus the
377
+ // CurrentPage service
367
378
  State,
368
379
  // remote-function helpers (callQuery, callCommand, liveQueryStream, ...)
369
380
  Remote,
@@ -377,6 +388,14 @@ import {
377
388
  // Remote.callQuery(getTodos), State.page()
378
389
  ```
379
390
 
391
+ `Navigation.goto`, `invalidate`, `invalidateAll`, `refreshAll`, `preloadData`
392
+ and `preloadCode` fail with `Navigation.NavigationError` when SvelteKit rejects
393
+ or throws: for example, `goto` to a URL outside the app, `preloadData` for an
394
+ `href` outside the app, a navigation whose `beforeNavigate` / `onNavigate`
395
+ callback throws, or any of them run during server rendering. The error's
396
+ `operation` names the wrapper and its `cause` is SvelteKit's original reason;
397
+ recover with `Effect.catchTag("NavigationError", ...)`.
398
+
380
399
  `Navigation.beforeNavigate()` / `Navigation.afterNavigate()` bridge
381
400
  `$app/navigation`'s callback-registration functions to `Stream`s,
382
401
  `Navigation.onNavigate(fn => Effect)` runs an Effect per navigation (forwarding
@@ -385,6 +404,31 @@ any cleanup the Effect yields), and `State.page()` emits a shallow snapshot of
385
404
  SvelteKit functions they wrap, these must be called during component
386
405
  initialization (at the top of `<script>`).
387
406
 
407
+ The rest of `State` watches `$app/state` directly, so it works anywhere on the
408
+ client (not only during component initialization):
409
+
410
+ | Export | What it is |
411
+ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
412
+ | `State.navigating()` | `Stream<Option<NavigatingSnapshot>>` — `Some` while a navigation is in flight (`type`, `from`, `to`, `willUnload`, `delta`, `complete`), `None` when idle |
413
+ | `State.updated()` | `Stream<boolean>` — `updated.current`: whether a new app version has been deployed |
414
+ | `State.checkForUpdate()` | `Effect<boolean, UpdateCheckError>` — `updated.check()` now; fails with `UpdateCheckError` if the check rejects |
415
+ | `State.CurrentPage` | A `Context` service: `get` (snapshot the page now) and `changes` (a stream that also emits on shallow routing and form results); provide `CurrentPage.layer` |
416
+
417
+ Each stream emits the current value on the next Svelte flush, then on every
418
+ change, and stops watching when it ends or is interrupted. None of them fail.
419
+
420
+ ```ts
421
+ import { State } from "@thomasfosterau/effect-sveltekit/client";
422
+ import { Effect } from "effect";
423
+
424
+ // Any client Effect can read the page once CurrentPage.layer is provided
425
+ // (e.g. in the layer behind your client runtime).
426
+ const logPath = Effect.gen(function* () {
427
+ const page = yield* (yield* State.CurrentPage).get;
428
+ yield* Effect.logInfo(`at ${page.url.pathname}`);
429
+ }).pipe(Effect.provide(State.CurrentPage.layer));
430
+ ```
431
+
388
432
  ### `transport` hook from Effect Schema
389
433
 
390
434
  Move custom types (`Option`, `DateTime`, branded types, `Schema.Class`
@@ -528,10 +572,61 @@ Effects (no explicit wrapper) when the `effectKit()` Vite plugin is enabled.
528
572
 
529
573
  For SvelteKit page `actions`, the `Forms` namespace
530
574
  (`@thomasfosterau/effect-sveltekit/client`) expresses progressive enhancement
531
- with Effects: `Forms.enhance({ onSubmit, onResult })` builds a `SubmitFunction`
532
- for `use:enhance`, and `Forms.applyAction` / `Forms.deserialize` wrap the
533
- `$app/forms` helpers. (This complements `Remote.submitForm`, which covers remote
534
- forms.)
575
+ with Effects: `Forms.enhance({ onSubmit, onResult, onError }, { runtime })`
576
+ builds a `SubmitFunction` for `use:enhance`, and `Forms.applyAction` /
577
+ `Forms.deserialize` wrap the `$app/forms` helpers. `Forms.applyAction` fails
578
+ with `Forms.ApplyActionError` (SvelteKit's original reason as its `cause`) when
579
+ SvelteKit rejects or throws: for example, when the navigation that follows a
580
+ `redirect` result fails, or when it runs during server rendering.
581
+
582
+ The handlers (and `onError`) run on `runtime`, a `Context` or `ManagedRuntime`.
583
+ It is required, and checked to provide every service they require, as soon as
584
+ any handler requires one: there is no ambient runtime to fall back on, so such a
585
+ call without it does not type-check. Each handler may require different
586
+ services; the runtime must provide all of them. A missing runtime is reported on
587
+ the `Forms.enhance` call, and one that lacks a service on the runtime, never on
588
+ a handler. The message may at first name only the services of the handlers
589
+ TypeScript types first (such as an `onError` passed by name, before handlers
590
+ whose parameters are not annotated), and the rest once the runtime provides
591
+ those. Handlers that require no services run on the default runtime, and the
592
+ options may be omitted; an effect typed as requiring `any` or `unknown` is
593
+ treated as requiring no services, so no runtime is demanded. A function that
594
+ wraps `Forms.enhance` for handlers requiring any `R` forwards its options as
595
+ `...options: Forms.EnhanceOptionsArgs<R>`, which keeps them optional exactly
596
+ when `R` is `never` (an `EnhanceOptions<R>`, whose runtime is optional, does not
597
+ type-check there).
598
+
599
+ `onSubmit` and `onResult` may fail. The `onError` handler receives the error,
600
+ typed as the union of their error types, and runs on the same runtime; it must
601
+ not fail itself. Write `onError` after the other handlers: written before one
602
+ whose parameters are not annotated, it still type-checks, but its `error`
603
+ parameter is `unknown`. A failed `onSubmit` also cancels the submission, and a
604
+ failed `onResult` does not fall back to SvelteKit's default behaviour. Without
605
+ `onError`, a failure is treated like a defect: the promise SvelteKit awaits
606
+ rejects with the error, SvelteKit sends no request after a failed `onSubmit`
607
+ and applies no default result after a failed `onResult`, and the browser
608
+ reports an unhandled rejection. Defects and interruptions always take that path
609
+ rather than reaching `onError`.
610
+
611
+ ```ts
612
+ const submit = Forms.enhance({
613
+ onResult: ({ result }) => Forms.applyAction(result),
614
+ // error: ApplyActionError
615
+ onError: (error) => Effect.logWarning("applyAction failed", error.cause),
616
+ });
617
+
618
+ const submitAndRecover = Forms.enhance({
619
+ // Or recover inside the handler, so onResult cannot fail.
620
+ onResult: ({ result }) =>
621
+ Forms.applyAction(result).pipe(
622
+ Effect.catchTag("ApplyActionError", (error) =>
623
+ Effect.logWarning("applyAction failed", error.cause),
624
+ ),
625
+ ),
626
+ });
627
+ ```
628
+
629
+ (This complements `Remote.submitForm`, which covers remote forms.)
535
630
 
536
631
  ## Recipes
537
632
 
package/dist/Hooks.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { MarkerTransporterOptions, TransportEntry, markerTransporter, transport, transporter } from "./transport.js";
2
2
  import { responseHeadersTransporter } from "./response-headers.js";
3
- import { Effect } from "effect";
3
+ import * as Effect from "effect/Effect";
4
4
  import * as HttpClient from "effect/http/HttpClient";
5
5
  import { Reroute } from "@sveltejs/kit/hooks";
6
6
  declare namespace Hooks_d_exports {
@@ -23,10 +23,10 @@ declare namespace Hooks_d_exports {
23
23
  * );
24
24
  * ```
25
25
  */
26
- declare function reroute(fn: (event: {
26
+ export declare function reroute(fn: (event: {
27
27
  url: URL;
28
28
  fetch: typeof globalThis.fetch;
29
29
  }) => Effect.Effect<string | void, unknown, HttpClient.HttpClient>): Reroute;
30
30
  //#endregion
31
- export { Hooks_d_exports, type MarkerTransporterOptions, type TransportEntry, markerTransporter, reroute, responseHeadersTransporter, transport, transporter };
31
+ export { Hooks_d_exports, type MarkerTransporterOptions, type TransportEntry, markerTransporter, responseHeadersTransporter, transport, transporter };
32
32
  //# sourceMappingURL=Hooks.d.ts.map
package/dist/Hooks.js CHANGED
@@ -2,7 +2,7 @@ import { __exportAll } from "./_virtual/_rolldown/runtime.js";
2
2
  import { defaultUniversalRuntime } from "./internal/default-runtime.js";
3
3
  import { markerTransporter, transport, transporter } from "./transport.js";
4
4
  import { responseHeadersTransporter } from "./response-headers.js";
5
- import "effect";
5
+ import "effect/Effect";
6
6
  //#region src/Hooks.ts
7
7
  var Hooks_exports = /* @__PURE__ */ __exportAll({
8
8
  markerTransporter: () => markerTransporter,
package/dist/Hooks.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Hooks.js","names":[],"sources":["../src/Hooks.ts"],"sourcesContent":["/**\n * Effect helpers for SvelteKit's universal hooks (`src/hooks.ts`), exposed as\n * the `Hooks` namespace of `@thomasfosterau/effect-sveltekit`.\n *\n * The `reroute` hook is expressed as an Effect (running on the universal\n * runtime, with an `HttpClient` backed by the event's `fetch`), and the\n * universal `transport` hook is built from Effect schemas via {@link transport}\n * / {@link transporter}.\n *\n * @example\n * ```ts\n * // src/hooks.ts\n * import { Hooks } from '@thomasfosterau/effect-sveltekit';\n * import { Effect, Schema } from 'effect';\n *\n * export const reroute = Hooks.reroute((event) =>\n * Effect.succeed(event.url.pathname === '/old' ? '/new' : undefined),\n * );\n *\n * export const transport = Hooks.transport({\n * Option: Schema.Option(Schema.Number),\n * Date: Schema.Date,\n * });\n * ```\n */\n\nimport type { Reroute } from \"@sveltejs/kit/hooks\";\nimport { Effect } from \"effect\";\nimport type * as HttpClient from \"effect/http/HttpClient\";\nimport { defaultUniversalRuntime } from \"./internal/default-runtime.js\";\n\n/**\n * Wraps an Effect-based function as the SvelteKit universal\n * [`reroute`](https://svelte.dev/docs/kit/hooks#Universal-hooks-reroute) hook,\n * which rewrites the URL before routing. The effect runs on both the server and\n * the client and gets an `HttpClient` backed by the event's `fetch`.\n *\n * @example\n * ```ts\n * // src/hooks.ts\n * import { Hooks } from '@thomasfosterau/effect-sveltekit';\n * import { Effect } from 'effect';\n *\n * export const reroute = Hooks.reroute((event) =>\n * Effect.succeed(event.url.pathname === '/old' ? '/new' : undefined),\n * );\n * ```\n */\nexport function reroute(\n fn: (event: {\n url: URL;\n fetch: typeof globalThis.fetch;\n }) => Effect.Effect<string | void, unknown, HttpClient.HttpClient>,\n): Reroute {\n return (event) => defaultUniversalRuntime().runPromise(event.fetch, fn(event));\n}\n\n/**\n * The universal `transport` hook built from Effect schemas: `transport`\n * assembles a whole `transport` object from a record of entries, `transporter`\n * builds a single round-tripping entry from a `Schema.Codec`, and\n * `markerTransporter` builds a deliberately-lossy entry for a non-POJO wrapper\n * whose contents should not round-trip. See {@link transport} for details.\n */\nexport {\n transport,\n transporter,\n markerTransporter,\n type MarkerTransporterOptions,\n type TransportEntry,\n} from \"./transport.js\";\n\n/**\n * The `transport` entry for the {@link ResponseHeaders} response-headers buffer\n * (`Hooks.transport({ ResponseHeaders: Hooks.responseHeadersTransporter })`).\n */\nexport { responseHeadersTransporter } from \"./response-headers.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,SAAgB,QACd,IAIS;CACT,QAAQ,UAAU,wBAAwB,CAAC,CAAC,WAAW,MAAM,OAAO,GAAG,KAAK,CAAC;AAC/E"}
1
+ {"version":3,"file":"Hooks.js","names":[],"sources":["../src/Hooks.ts"],"sourcesContent":["/**\n * Effect helpers for SvelteKit's universal hooks (`src/hooks.ts`), exposed as\n * the `Hooks` namespace of `@thomasfosterau/effect-sveltekit`.\n *\n * The `reroute` hook is expressed as an Effect (running on the universal\n * runtime, with an `HttpClient` backed by the event's `fetch`), and the\n * universal `transport` hook is built from Effect schemas via {@link transport}\n * / {@link transporter}.\n *\n * @example\n * ```ts\n * // src/hooks.ts\n * import { Hooks } from '@thomasfosterau/effect-sveltekit';\n * import { Effect, Schema } from 'effect';\n *\n * export const reroute = Hooks.reroute((event) =>\n * Effect.succeed(event.url.pathname === '/old' ? '/new' : undefined),\n * );\n *\n * export const transport = Hooks.transport({\n * Option: Schema.Option(Schema.Number),\n * Date: Schema.Date,\n * });\n * ```\n */\n\nimport type { Reroute } from \"@sveltejs/kit/hooks\";\nimport * as Effect from \"effect/Effect\";\nimport type * as HttpClient from \"effect/http/HttpClient\";\nimport { defaultUniversalRuntime } from \"./internal/default-runtime.js\";\n\n/**\n * Wraps an Effect-based function as the SvelteKit universal\n * [`reroute`](https://svelte.dev/docs/kit/hooks#Universal-hooks-reroute) hook,\n * which rewrites the URL before routing. The effect runs on both the server and\n * the client and gets an `HttpClient` backed by the event's `fetch`.\n *\n * @example\n * ```ts\n * // src/hooks.ts\n * import { Hooks } from '@thomasfosterau/effect-sveltekit';\n * import { Effect } from 'effect';\n *\n * export const reroute = Hooks.reroute((event) =>\n * Effect.succeed(event.url.pathname === '/old' ? '/new' : undefined),\n * );\n * ```\n */\nexport function reroute(\n fn: (event: {\n url: URL;\n fetch: typeof globalThis.fetch;\n }) => Effect.Effect<string | void, unknown, HttpClient.HttpClient>,\n): Reroute {\n return (event) => defaultUniversalRuntime().runPromise(event.fetch, fn(event));\n}\n\n/**\n * The universal `transport` hook built from Effect schemas: `transport`\n * assembles a whole `transport` object from a record of entries, `transporter`\n * builds a single round-tripping entry from a `Schema.Codec`, and\n * `markerTransporter` builds a deliberately-lossy entry for a non-POJO wrapper\n * whose contents should not round-trip. See {@link transport} for details.\n */\nexport {\n transport,\n transporter,\n markerTransporter,\n type MarkerTransporterOptions,\n type TransportEntry,\n} from \"./transport.js\";\n\n/**\n * The `transport` entry for the {@link ResponseHeaders} response-headers buffer\n * (`Hooks.transport({ ResponseHeaders: Hooks.responseHeadersTransporter })`).\n */\nexport { responseHeadersTransporter } from \"./response-headers.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,SAAgB,QACd,IAIS;CACT,QAAQ,UAAU,wBAAwB,CAAC,CAAC,WAAW,MAAM,OAAO,GAAG,KAAK,CAAC;AAC/E"}
@@ -1,32 +1,85 @@
1
- import { Effect, Stream } from "effect";
1
+ import * as Effect from "effect/Effect";
2
+ import * as Stream from "effect/Stream";
3
+ import * as Schema from "effect/Schema";
2
4
  import { AfterNavigate, BeforeNavigate, OnNavigate, goto, invalidate, preloadCode, preloadData, refreshAll } from "$app/navigation";
3
5
  declare namespace Navigation_d_exports {
4
- export { afterNavigate$1 as afterNavigate, beforeNavigate$1 as beforeNavigate, goto$1 as goto, invalidate$1 as invalidate, invalidateAll$1 as invalidateAll, onNavigate$1 as onNavigate, preloadCode$1 as preloadCode, preloadData$1 as preloadData, refreshAll$1 as refreshAll };
6
+ export { NavigationError, afterNavigate$1 as afterNavigate, beforeNavigate$1 as beforeNavigate, goto$1 as goto, invalidate$1 as invalidate, invalidateAll$1 as invalidateAll, onNavigate$1 as onNavigate, preloadCode$1 as preloadCode, preloadData$1 as preloadData, refreshAll$1 as refreshAll };
5
7
  }
8
+ declare const NavigationError_base: Schema.Class<NavigationError, Schema.TaggedStruct<"NavigationError", {
9
+ /** The wrapper whose SvelteKit call failed. */
10
+ readonly operation: Schema.Literals<readonly ["goto", "invalidate", "invalidateAll", "refreshAll", "preloadData", "preloadCode"]>;
11
+ /** The rejection reason, or the value thrown, as SvelteKit produced it. */
12
+ readonly cause: Schema.Defect;
13
+ }>, import("effect/Cause").YieldableError>;
6
14
  /**
7
- * Navigate to `url`. Wraps `$app/navigation`'s `goto`. SvelteKit rejects a URL
8
- * outside the app (external, or not matching a route); a rejection surfaces as
9
- * a defect.
15
+ * A `$app/navigation` promise wrapper ({@link goto}, {@link invalidate},
16
+ * {@link invalidateAll}, {@link refreshAll}, {@link preloadData},
17
+ * {@link preloadCode}) rejected or threw. `operation` names the wrapper and
18
+ * `cause` is SvelteKit's original reason, unchanged.
19
+ *
20
+ * SvelteKit 3 rejects `goto` with a URL outside the app (external, or matching
21
+ * no route) and `preloadData` with a URL outside the app; `invalidate` throws on
22
+ * a `resource` that cannot be parsed even relative to the current URL;
23
+ * `preloadCode` rejects when a route's code fails to load; a navigation (including one that follows a `load` redirect during
24
+ * `invalidate` / `refreshAll`) rejects when a `beforeNavigate` / `onNavigate`
25
+ * callback throws; and every one of them throws when called during server
26
+ * rendering. Recover with `Effect.catchTag("NavigationError", ...)`.
27
+ */
28
+ export declare class NavigationError extends NavigationError_base {}
29
+ /**
30
+ * Navigate to `url`. Wraps `$app/navigation`'s `goto`.
31
+ *
32
+ * Fails with a {@link NavigationError} if SvelteKit rejects: a URL outside the
33
+ * app (external, or not matching a route) rejects, as does a navigation whose
34
+ * `beforeNavigate` / `onNavigate` callback throws, or a call during server
35
+ * rendering. For an external URL, set `window.location` instead.
10
36
  */
11
- declare const goto$1: (...args: Parameters<typeof goto>) => Effect.Effect<void>;
12
- /** Re-run `load` functions that depend on `resource`. Wraps `invalidate`. */
13
- declare const invalidate$1: (...args: Parameters<typeof invalidate>) => Effect.Effect<void>;
37
+ declare const goto$1: (...args: Parameters<typeof goto>) => Effect.Effect<void, NavigationError>;
38
+ /**
39
+ * Re-run `load` functions that depend on `resource`. Wraps `invalidate`.
40
+ *
41
+ * Fails with a {@link NavigationError} if SvelteKit rejects or throws: a
42
+ * `resource` string that cannot be parsed even relative to the current URL
43
+ * throws, as does a call during server rendering, and the navigation that
44
+ * follows a `load` redirect can reject.
45
+ */
46
+ declare const invalidate$1: (...args: Parameters<typeof invalidate>) => Effect.Effect<void, NavigationError>;
14
47
  /**
15
48
  * Re-run every `load` function for the active page. Wraps `invalidateAll`.
16
49
  *
50
+ * Fails with a {@link NavigationError} if SvelteKit rejects or throws: for
51
+ * example, when the navigation that follows a `load` redirect fails, or when
52
+ * called during server rendering.
53
+ *
17
54
  * @deprecated SvelteKit 3 deprecates `invalidateAll`; use {@link refreshAll},
18
55
  * which also refreshes active remote functions and keeps `page.state`.
19
56
  */
20
- declare const invalidateAll$1: () => Effect.Effect<void>;
21
- /** Refresh active remote functions (and, by default, `load`s). Wraps `refreshAll`. */
22
- declare const refreshAll$1: (...args: Parameters<typeof refreshAll>) => Effect.Effect<void>;
23
- /** Preload the data for `href`. Wraps `preloadData`. */
24
- declare const preloadData$1: (href: string) => Effect.Effect<Awaited<ReturnType<typeof preloadData>>>;
57
+ declare const invalidateAll$1: () => Effect.Effect<void, NavigationError>;
58
+ /**
59
+ * Refresh active remote functions (and, by default, `load`s). Wraps `refreshAll`.
60
+ *
61
+ * Fails with a {@link NavigationError} if SvelteKit rejects or throws: for
62
+ * example, when the navigation that follows a `load` redirect fails, or when
63
+ * called during server rendering.
64
+ */
65
+ declare const refreshAll$1: (...args: Parameters<typeof refreshAll>) => Effect.Effect<void, NavigationError>;
66
+ /**
67
+ * Preload the data for `href`. Wraps `preloadData`, succeeding with SvelteKit's
68
+ * result (`loaded`, `redirect`, or `error` when a `load` fails).
69
+ *
70
+ * Fails with a {@link NavigationError} if SvelteKit rejects: an `href` outside
71
+ * the app rejects, as does a call during server rendering.
72
+ */
73
+ declare const preloadData$1: (href: string) => Effect.Effect<Awaited<ReturnType<typeof preloadData>>, NavigationError>;
25
74
  /**
26
75
  * Preload the code for the route `id` (e.g. `/blog/[slug]`, without
27
76
  * `paths.base`). Wraps `preloadCode`.
77
+ *
78
+ * Fails with a {@link NavigationError} if SvelteKit rejects: a route module
79
+ * that fails to load rejects, as does a call during server rendering (and, in
80
+ * development, an `id` not starting with `/`).
28
81
  */
29
- declare const preloadCode$1: (...args: Parameters<typeof preloadCode>) => Effect.Effect<void>;
82
+ declare const preloadCode$1: (...args: Parameters<typeof preloadCode>) => Effect.Effect<void, NavigationError>;
30
83
  /**
31
84
  * Registers a `beforeNavigate` callback and exposes every navigation about to
32
85
  * happen as an Effect `Stream`. Call during component initialization.
@@ -34,9 +87,9 @@ declare const preloadCode$1: (...args: Parameters<typeof preloadCode>) => Effect
34
87
  * Unlike the promise-returning navigation functions above, this is a
35
88
  * **callback-registration** function: it must be called *synchronously during
36
89
  * component initialization* (SvelteKit wires it to the component's lifecycle).
37
- * It registers its callback eagerly and feeds an internal `Queue`, returning a
38
- * `Stream` backed by that queue — the same push-based bridge as `SignalEmitter`
39
- * in `@thomasfosterau/effect-svelte`. Call it at the top level of `<script>`,
90
+ * It registers its callback eagerly and feeds an internal unbounded `Queue`,
91
+ * returning a `Stream` backed by that queue: a push-based bridge that buffers
92
+ * every event for its single consumer. Call it at the top level of `<script>`,
40
93
  * exactly where you'd call the bare `beforeNavigate`.
41
94
  *
42
95
  * @example