@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/dist/State.js CHANGED
@@ -1,34 +1,49 @@
1
1
  import { __exportAll } from "./_virtual/_rolldown/runtime.js";
2
- import { pageSnapshotStream } from "./internal/app-state.js";
2
+ import { UpdateCheckError, checkForUpdateWith, makeCurrentPage, pageSnapshotStream, snapshotNavigating, watchedStream } from "./internal/app-state.js";
3
+ import { watch } from "./internal/watch.svelte.js";
4
+ import * as Context from "effect/Context";
5
+ import * as Layer from "effect/Layer";
3
6
  import { afterNavigate } from "$app/navigation";
4
- import { page } from "$app/state";
7
+ import { navigating, page, updated } from "$app/state";
5
8
  //#region src/State.ts
6
9
  /**
7
- * A minimal Effect-side bridge for SvelteKit's
8
- * [`$app/state`](https://svelte.dev/docs/kit/$app-state) `page`.
10
+ * An Effect-side bridge for SvelteKit's
11
+ * [`$app/state`](https://svelte.dev/docs/kit/$app-state): `page`, `navigating`
12
+ * and `updated`.
9
13
  *
10
- * `$app/state` exposes rune-reactive `page` / `navigating` / `updated` for use
11
- * inside components. This bridges `page` to an Effect `Stream` so navigation can
12
- * drive Effect pipelines without touching Svelte runes: it emits the current
13
- * `page` immediately, then again after each navigation, by registering
14
- * `afterNavigate` (from `$app/navigation`) and reading `page` (from
15
- * `$app/state`) at callback time.
14
+ * `$app/state` exposes rune-reactive objects for use inside components. This
15
+ * module turns them into Effect values so navigation and version state can
16
+ * drive Effect pipelines without touching Svelte runes:
16
17
  *
17
- * Each emission is a *shallow snapshot* (`url`, `params`, `route`, `data`,
18
- * `status`, `error`, `form`, `state`) rather than the live `$app/state` proxy:
19
- * the proxy mutates in place on every navigation, so a captured reference is
20
- * unreliable for downstream consumers — the snapshot gives each emission a
21
- * stable value. Reading at `afterNavigate` time also avoids needing a `.svelte`
22
- * runes file.
18
+ * - {@link page} — a `Stream` of page snapshots, emitted now and after each
19
+ * navigation (via `afterNavigate`, so call it during component
20
+ * initialization).
21
+ * - {@link navigating} — a `Stream` of the in-flight navigation as an
22
+ * `Option` (`None` when idle).
23
+ * - {@link updated} / {@link checkForUpdate} — whether a new app version has
24
+ * been deployed, as a `Stream<boolean>` and as an on-demand check.
25
+ * - {@link CurrentPage} — a `Context` service exposing the current page to any
26
+ * client Effect; provide {@link CurrentPage.layer} in your client runtime.
23
27
  *
24
- * Like the navigation-event helpers, `page` registers its callback during
25
- * component initialization (where `$app/navigation` requires), so call it at the
26
- * top level of `<script>`. Client-only.
28
+ * Each page emission is a *shallow snapshot* (`url`, `params`, `route`, `data`,
29
+ * `status`, `error`, `form`, `state`, `shallow`) rather than the live
30
+ * `$app/state` proxy: the proxy mutates in place on every navigation, so a
31
+ * captured reference is unreliable for downstream consumers — the snapshot
32
+ * gives each emission a stable value. `navigating` emissions are snapshots for
33
+ * the same reason.
27
34
  *
28
- * NOTE: this is intentionally minimal. A richer reactive bridge (a `CurrentPage`
29
- * service readable from any Effect, `navigating` / `updated` streams) may later
30
- * compose with the official `@effect/atom-svelte` integration (see issue #23)
31
- * rather than re-implementing runes plumbing here.
35
+ * `navigating`, `updated` and `CurrentPage.changes` watch the rune state with a
36
+ * root `$effect`, so they work anywhere on the client (not only during
37
+ * component initialization). Each stream is cold: a run opens its own watcher,
38
+ * emits the current value on the next Svelte flush and again whenever it
39
+ * changes, and disposes the watcher when the stream ends or is interrupted.
40
+ * All of this is client-only: during server rendering the watched streams
41
+ * never emit, so consuming one there (for example with `Stream.runHead`)
42
+ * hangs rather than failing.
43
+ *
44
+ * These are streams and a service rather than atoms; an atom-based variant
45
+ * may follow once the official `@effect/atom-svelte` integration lands (see
46
+ * issue #23).
32
47
  *
33
48
  * @example
34
49
  * ```svelte
@@ -46,11 +61,20 @@ import { page } from "$app/state";
46
61
  * <\/script>
47
62
  * ```
48
63
  */
49
- var State_exports = /* @__PURE__ */ __exportAll({ page: () => page$1 });
64
+ var State_exports = /* @__PURE__ */ __exportAll({
65
+ CurrentPage: () => CurrentPage,
66
+ UpdateCheckError: () => UpdateCheckError,
67
+ checkForUpdate: () => checkForUpdate,
68
+ navigating: () => navigating$1,
69
+ page: () => page$1,
70
+ updated: () => updated$1
71
+ });
50
72
  /**
51
73
  * A `Stream` of the current SvelteKit `page`, emitting a shallow snapshot
52
- * immediately and again after every navigation. Call during component
53
- * initialization.
74
+ * immediately and again after every navigation; never fails. Call during
75
+ * component initialization. It follows `afterNavigate`, so page changes
76
+ * without a navigation (shallow routing, form results) don't emit — use
77
+ * {@link CurrentPage}'s `changes` for those.
54
78
  */
55
79
  function page$1() {
56
80
  return pageSnapshotStream((callback) => {
@@ -59,7 +83,73 @@ function page$1() {
59
83
  });
60
84
  }, () => page);
61
85
  }
86
+ /**
87
+ * A `Stream` of SvelteKit's in-flight navigation: `Option.some` with a
88
+ * {@link NavigatingSnapshot} while a navigation is in progress, `Option.none()`
89
+ * when idle. Emits the current state on the next Svelte flush, then on every
90
+ * change; never fails. Client-only, but callable anywhere (not only during
91
+ * component initialization).
92
+ *
93
+ * @example
94
+ * ```ts
95
+ * const spinner = Stream.runForEach(State.navigating(), (nav) =>
96
+ * Effect.sync(() => (busy = Option.isSome(nav))),
97
+ * );
98
+ * ```
99
+ */
100
+ function navigating$1() {
101
+ return watchedStream(watch, () => snapshotNavigating(navigating));
102
+ }
103
+ /**
104
+ * A `Stream` of SvelteKit's `updated.current`: whether a new version of the app
105
+ * has been deployed since this one loaded. Emits the current value on the next
106
+ * Svelte flush, then on every change (SvelteKit only ever flips it from `false`
107
+ * to `true`); never fails. Client-only, but callable anywhere.
108
+ */
109
+ function updated$1() {
110
+ return watchedStream(watch, () => updated.current);
111
+ }
112
+ /**
113
+ * Asks SvelteKit to check for a new app version now (`updated.check()`),
114
+ * succeeding with whether one is available; a positive result also flips
115
+ * {@link updated}. Always succeeds with `false` in development. Fails with
116
+ * {@link UpdateCheckError} if the check rejects. Client-only.
117
+ */
118
+ function checkForUpdate() {
119
+ return checkForUpdateWith(() => updated.check());
120
+ }
121
+ /**
122
+ * The current SvelteKit page as an Effect service, so any client Effect can read
123
+ * it with `yield* State.CurrentPage` instead of taking it as an argument.
124
+ * `get` snapshots the page when run; `changes` emits the current page on the
125
+ * next Svelte flush, then a fresh snapshot whenever any field changes
126
+ * (navigations, but also shallow routing and form results). Neither fails on
127
+ * the client; on the server, Kit's `page` throws outside synchronous component
128
+ * rendering (including after an async boundary in an Effect forked during
129
+ * render), so `get` dies with a defect there.
130
+ *
131
+ * Provide {@link CurrentPage.layer} (backed by `$app/state`) in your client
132
+ * runtime, or `Layer.succeed(State.CurrentPage, …)` in tests.
133
+ *
134
+ * @example
135
+ * ```ts
136
+ * const trackView = Effect.gen(function* () {
137
+ * const page = yield* (yield* State.CurrentPage).get;
138
+ * yield* analytics.view(page.url.pathname);
139
+ * });
140
+ *
141
+ * runtime.runFork(trackView.pipe(Effect.provide(State.CurrentPage.layer)));
142
+ * ```
143
+ */
144
+ var CurrentPage = class CurrentPage extends Context.Service()("@thomasfosterau/effect-sveltekit/CurrentPage") {
145
+ /**
146
+ * A layer providing {@link CurrentPage} from `$app/state`'s `page`. Builds
147
+ * instantly and never fails; client-only (during server rendering `changes`
148
+ * never emits, so consuming it there hangs).
149
+ */
150
+ static layer = Layer.succeed(CurrentPage, makeCurrentPage(() => page, watch));
151
+ };
62
152
  //#endregion
63
- export { State_exports, page$1 as page };
153
+ export { CurrentPage, State_exports, UpdateCheckError, checkForUpdate, navigating$1 as navigating, page$1 as page, updated$1 as updated };
64
154
 
65
155
  //# sourceMappingURL=State.js.map
package/dist/State.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"State.js","names":["page","kitPage"],"sources":["../src/State.ts"],"sourcesContent":["/**\n * A minimal Effect-side bridge for SvelteKit's\n * [`$app/state`](https://svelte.dev/docs/kit/$app-state) `page`.\n *\n * `$app/state` exposes rune-reactive `page` / `navigating` / `updated` for use\n * inside components. This bridges `page` to an Effect `Stream` so navigation can\n * drive Effect pipelines without touching Svelte runes: it emits the current\n * `page` immediately, then again after each navigation, by registering\n * `afterNavigate` (from `$app/navigation`) and reading `page` (from\n * `$app/state`) at callback time.\n *\n * Each emission is a *shallow snapshot* (`url`, `params`, `route`, `data`,\n * `status`, `error`, `form`, `state`) rather than the live `$app/state` proxy:\n * the proxy mutates in place on every navigation, so a captured reference is\n * unreliable for downstream consumers — the snapshot gives each emission a\n * stable value. Reading at `afterNavigate` time also avoids needing a `.svelte`\n * runes file.\n *\n * Like the navigation-event helpers, `page` registers its callback during\n * component initialization (where `$app/navigation` requires), so call it at the\n * top level of `<script>`. Client-only.\n *\n * NOTE: this is intentionally minimal. A richer reactive bridge (a `CurrentPage`\n * service readable from any Effect, `navigating` / `updated` streams) may later\n * compose with the official `@effect/atom-svelte` integration (see issue #23)\n * rather than re-implementing runes plumbing here.\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { State } from '@thomasfosterau/effect-sveltekit/client';\n * import { Stream, Effect } from 'effect';\n * import { getRuntime } from '@thomasfosterau/effect-svelte';\n *\n * const pages = State.page();\n * getRuntime().runFork(\n * Stream.runForEach(pages, (page) =>\n * Effect.logInfo(`page: ${page.url.pathname}`),\n * ),\n * );\n * </script>\n * ```\n */\n\nimport { afterNavigate as kitAfterNavigate } from \"$app/navigation\";\nimport { page as kitPage } from \"$app/state\";\nimport type { Page } from \"$app/state\";\nimport type { Stream } from \"effect\";\nimport { type PageSnapshot, pageSnapshotStream } from \"./internal/app-state.js\";\n\n/** A shallow snapshot of SvelteKit's `page` — see {@link page}. */\nexport type { PageSnapshot } from \"./internal/app-state.js\";\n\n/**\n * A `Stream` of the current SvelteKit `page`, emitting a shallow snapshot\n * immediately and again after every navigation. Call during component\n * initialization.\n */\nexport function page(): Stream.Stream<PageSnapshot<Page>> {\n return pageSnapshotStream<Page>(\n (callback) => {\n kitAfterNavigate(() => {\n callback();\n });\n },\n () => kitPage,\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0DA,SAAgBA,SAA0C;CACxD,OAAO,oBACJ,aAAa;EACZ,oBAAuB;GACrB,SAAS;EACX,CAAC;CACH,SACMC,IACR;AACF"}
1
+ {"version":3,"file":"State.js","names":["page","kitPage","navigating","kitNavigating","updated","kitUpdated"],"sources":["../src/State.ts"],"sourcesContent":["/**\n * An Effect-side bridge for SvelteKit's\n * [`$app/state`](https://svelte.dev/docs/kit/$app-state): `page`, `navigating`\n * and `updated`.\n *\n * `$app/state` exposes rune-reactive objects for use inside components. This\n * module turns them into Effect values so navigation and version state can\n * drive Effect pipelines without touching Svelte runes:\n *\n * - {@link page} — a `Stream` of page snapshots, emitted now and after each\n * navigation (via `afterNavigate`, so call it during component\n * initialization).\n * - {@link navigating} — a `Stream` of the in-flight navigation as an\n * `Option` (`None` when idle).\n * - {@link updated} / {@link checkForUpdate} — whether a new app version has\n * been deployed, as a `Stream<boolean>` and as an on-demand check.\n * - {@link CurrentPage} — a `Context` service exposing the current page to any\n * client Effect; provide {@link CurrentPage.layer} in your client runtime.\n *\n * Each page emission is a *shallow snapshot* (`url`, `params`, `route`, `data`,\n * `status`, `error`, `form`, `state`, `shallow`) rather than the live\n * `$app/state` proxy: the proxy mutates in place on every navigation, so a\n * captured reference is unreliable for downstream consumers — the snapshot\n * gives each emission a stable value. `navigating` emissions are snapshots for\n * the same reason.\n *\n * `navigating`, `updated` and `CurrentPage.changes` watch the rune state with a\n * root `$effect`, so they work anywhere on the client (not only during\n * component initialization). Each stream is cold: a run opens its own watcher,\n * emits the current value on the next Svelte flush and again whenever it\n * changes, and disposes the watcher when the stream ends or is interrupted.\n * All of this is client-only: during server rendering the watched streams\n * never emit, so consuming one there (for example with `Stream.runHead`)\n * hangs rather than failing.\n *\n * These are streams and a service rather than atoms; an atom-based variant\n * may follow once the official `@effect/atom-svelte` integration lands (see\n * issue #23).\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { State } from '@thomasfosterau/effect-sveltekit/client';\n * import { Stream, Effect } from 'effect';\n * import { getRuntime } from '@thomasfosterau/effect-svelte';\n *\n * const pages = State.page();\n * getRuntime().runFork(\n * Stream.runForEach(pages, (page) =>\n * Effect.logInfo(`page: ${page.url.pathname}`),\n * ),\n * );\n * </script>\n * ```\n */\n\nimport { afterNavigate as kitAfterNavigate } from \"$app/navigation\";\nimport { navigating as kitNavigating, page as kitPage, updated as kitUpdated } from \"$app/state\";\nimport type { Page } from \"$app/state\";\nimport * as Context from \"effect/Context\";\nimport * as Layer from \"effect/Layer\";\nimport type * as Effect from \"effect/Effect\";\nimport type * as Option from \"effect/Option\";\nimport type * as Stream from \"effect/Stream\";\nimport {\n type CurrentPageService,\n type NavigatingSnapshot,\n type PageSnapshot,\n type UpdateCheckError,\n checkForUpdateWith,\n makeCurrentPage,\n pageSnapshotStream,\n snapshotNavigating,\n watchedStream,\n} from \"./internal/app-state.js\";\nimport { watch } from \"./internal/watch.svelte.js\";\n\n/** A shallow snapshot of SvelteKit's `page` — see {@link page}. */\nexport type { PageSnapshot } from \"./internal/app-state.js\";\n\n/**\n * A snapshot of an in-flight SvelteKit navigation (`type`, `from`, `to`,\n * `willUnload`, `delta`, `complete`) — see {@link navigating}.\n */\nexport type { NavigatingSnapshot } from \"./internal/app-state.js\";\n\n/**\n * The error {@link checkForUpdate} fails with when SvelteKit's\n * `updated.check()` rejects (misuse such as calling it during server\n * rendering; a failed version request resolves `false` instead). The original\n * error is on `cause`.\n */\nexport { UpdateCheckError } from \"./internal/app-state.js\";\n\n/**\n * A `Stream` of the current SvelteKit `page`, emitting a shallow snapshot\n * immediately and again after every navigation; never fails. Call during\n * component initialization. It follows `afterNavigate`, so page changes\n * without a navigation (shallow routing, form results) don't emit — use\n * {@link CurrentPage}'s `changes` for those.\n */\nexport function page(): Stream.Stream<PageSnapshot<Page>> {\n return pageSnapshotStream<Page>(\n (callback) => {\n kitAfterNavigate(() => {\n callback();\n });\n },\n () => kitPage,\n );\n}\n\n/**\n * A `Stream` of SvelteKit's in-flight navigation: `Option.some` with a\n * {@link NavigatingSnapshot} while a navigation is in progress, `Option.none()`\n * when idle. Emits the current state on the next Svelte flush, then on every\n * change; never fails. Client-only, but callable anywhere (not only during\n * component initialization).\n *\n * @example\n * ```ts\n * const spinner = Stream.runForEach(State.navigating(), (nav) =>\n * Effect.sync(() => (busy = Option.isSome(nav))),\n * );\n * ```\n */\nexport function navigating(): Stream.Stream<Option.Option<NavigatingSnapshot>> {\n return watchedStream(watch, () => snapshotNavigating(kitNavigating));\n}\n\n/**\n * A `Stream` of SvelteKit's `updated.current`: whether a new version of the app\n * has been deployed since this one loaded. Emits the current value on the next\n * Svelte flush, then on every change (SvelteKit only ever flips it from `false`\n * to `true`); never fails. Client-only, but callable anywhere.\n */\nexport function updated(): Stream.Stream<boolean> {\n return watchedStream(watch, () => kitUpdated.current);\n}\n\n/**\n * Asks SvelteKit to check for a new app version now (`updated.check()`),\n * succeeding with whether one is available; a positive result also flips\n * {@link updated}. Always succeeds with `false` in development. Fails with\n * {@link UpdateCheckError} if the check rejects. Client-only.\n */\nexport function checkForUpdate(): Effect.Effect<boolean, UpdateCheckError> {\n return checkForUpdateWith(() => kitUpdated.check());\n}\n\n/**\n * The service shape of {@link CurrentPage}: `get` snapshots the current page\n * and `changes` streams it. Neither fails on the client; see\n * {@link CurrentPage} for the server.\n */\nexport interface CurrentPageInterface extends CurrentPageService<Page> {}\n\n/**\n * The current SvelteKit page as an Effect service, so any client Effect can read\n * it with `yield* State.CurrentPage` instead of taking it as an argument.\n * `get` snapshots the page when run; `changes` emits the current page on the\n * next Svelte flush, then a fresh snapshot whenever any field changes\n * (navigations, but also shallow routing and form results). Neither fails on\n * the client; on the server, Kit's `page` throws outside synchronous component\n * rendering (including after an async boundary in an Effect forked during\n * render), so `get` dies with a defect there.\n *\n * Provide {@link CurrentPage.layer} (backed by `$app/state`) in your client\n * runtime, or `Layer.succeed(State.CurrentPage, …)` in tests.\n *\n * @example\n * ```ts\n * const trackView = Effect.gen(function* () {\n * const page = yield* (yield* State.CurrentPage).get;\n * yield* analytics.view(page.url.pathname);\n * });\n *\n * runtime.runFork(trackView.pipe(Effect.provide(State.CurrentPage.layer)));\n * ```\n */\nexport class CurrentPage extends Context.Service<CurrentPage, CurrentPageInterface>()(\n \"@thomasfosterau/effect-sveltekit/CurrentPage\",\n) {\n /**\n * A layer providing {@link CurrentPage} from `$app/state`'s `page`. Builds\n * instantly and never fails; client-only (during server rendering `changes`\n * never emits, so consuming it there hangs).\n */\n static readonly layer: Layer.Layer<CurrentPage> = Layer.succeed(\n CurrentPage,\n makeCurrentPage(() => kitPage, watch),\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqGA,SAAgBA,SAA0C;CACxD,OAAO,oBACJ,aAAa;EACZ,oBAAuB;GACrB,SAAS;EACX,CAAC;CACH,SACMC,IACR;AACF;;;;;;;;;;;;;;;AAgBA,SAAgBC,eAA+D;CAC7E,OAAO,cAAc,aAAa,mBAAmBC,UAAa,CAAC;AACrE;;;;;;;AAQA,SAAgBC,YAAkC;CAChD,OAAO,cAAc,aAAaC,QAAW,OAAO;AACtD;;;;;;;AAQA,SAAgB,iBAA2D;CACzE,OAAO,yBAAyBA,QAAW,MAAM,CAAC;AACpD;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAa,cAAb,MAAa,oBAAoB,QAAQ,QAA2C,CAAC,CACnF,8CACF,CAAC,CAAC;;;;;;CAMA,OAAgB,QAAkC,MAAM,QACtD,aACA,sBAAsBJ,MAAS,KAAK,CACtC;AACF"}
@@ -1,7 +1,9 @@
1
- import { Effect, Option } from "effect";
1
+ import * as Effect from "effect/Effect";
2
+ import * as Schema from "effect/Schema";
2
3
  import * as FileSystem from "effect/FileSystem";
3
4
  import * as Path from "effect/Path";
4
5
  import * as HttpPlatform from "effect/http/HttpPlatform";
6
+ import * as Option from "effect/Option";
5
7
  import * as HttpServerResponse from "effect/http/HttpServerResponse";
6
8
  //#region src/adapter/assets.d.ts
7
9
  /**
@@ -11,25 +13,40 @@ import * as HttpServerResponse from "effect/http/HttpServerResponse";
11
13
  * needs (e.g. `FileSystem | Path | HttpPlatform` for {@link fileSystem}, or
12
14
  * `never` for {@link cloudflareAssets}).
13
15
  */
14
- interface AssetSource<R = never> {
16
+ export interface AssetSource<R = never> {
15
17
  readonly serveAsset: (request: Request, pathname: string) => Effect.Effect<Option.Option<HttpServerResponse.HttpServerResponse>, never, R>;
16
18
  }
17
19
  /** The services a file-system {@link AssetSource} requires. */
18
- type FileSystemAssetServices = FileSystem.FileSystem | Path.Path | HttpPlatform.HttpPlatform;
20
+ export type FileSystemAssetServices = FileSystem.FileSystem | Path.Path | HttpPlatform.HttpPlatform;
21
+ declare const AssetFetchError_base: Schema.Class<AssetFetchError, Schema.TaggedStruct<"AssetFetchError", {
22
+ /** The request path the binding was asked for. */
23
+ readonly url: Schema.String;
24
+ /** What the binding's `fetch` rejected with, or threw. */
25
+ readonly cause: Schema.Defect;
26
+ }>, import("effect/Cause").YieldableError>;
27
+ /**
28
+ * A {@link cloudflareAssets} binding's `fetch` rejected. The asset source's
29
+ * error channel is `never`, so this reaches the handler as a DEFECT (a 500),
30
+ * carrying what `fetch` rejected with as `cause`.
31
+ */
32
+ export declare class AssetFetchError extends AssetFetchError_base {}
19
33
  /** The {@link AssetSource} constructors, grouped for the adapter API. */
20
- declare const AssetSource: {
34
+ export declare const AssetSource: {
21
35
  fileSystem: (config: {
22
36
  /** Absolute path of the build output directory (contains `client/`, `prerendered/`). */
23
37
  readonly dir: string;
24
38
  /** The configured `manifest.appPath` — its `immutable/` subtree gets immutable caching. */
25
39
  readonly appPath: string;
26
- /** The set of prerendered paths exported by the build output. */
27
- readonly prerendered: ReadonlySet<string>;
40
+ /**
41
+ * The prerendered paths exported by the build output (the build's
42
+ * `prerendered` `Set`, or any iterable of paths).
43
+ */
44
+ readonly prerendered: Iterable<string>;
28
45
  }) => AssetSource<FileSystemAssetServices>;
29
46
  cloudflareAssets: (assets: {
30
47
  fetch: (request: Request) => Promise<Response> | Response;
31
48
  }) => AssetSource;
32
49
  };
33
50
  //#endregion
34
- export { AssetSource, FileSystemAssetServices };
51
+ export {};
35
52
  //# sourceMappingURL=assets.d.ts.map
@@ -1,8 +1,12 @@
1
- import { Effect, Option } from "effect";
1
+ import * as Effect from "effect/Effect";
2
+ import * as Result from "effect/Result";
3
+ import * as Schema from "effect/Schema";
2
4
  import * as FileSystem from "effect/FileSystem";
3
5
  import * as Path from "effect/Path";
4
6
  import "effect/http/HttpPlatform";
7
+ import * as Option from "effect/Option";
5
8
  import * as HttpServerResponse from "effect/http/HttpServerResponse";
9
+ import * as HashSet from "effect/HashSet";
6
10
  //#region src/adapter/assets.ts
7
11
  /**
8
12
  * Pluggable asset serving for the Effect SvelteKit adapter.
@@ -26,41 +30,52 @@ const IMMUTABLE_HEADERS = { "cache-control": "public, max-age=31536000, immutabl
26
30
  * Serve static client assets and prerendered pages from a build directory on
27
31
  * disk (the Node default). `dir` contains `client/` and `prerendered/`.
28
32
  */
29
- const fileSystem = (config) => ({ serveAsset: (request, pathname) => Effect.gen(function* () {
30
- if (request.method !== "GET" && request.method !== "HEAD") return Option.none();
31
- const path = yield* Path.Path;
32
- const fs = yield* FileSystem.FileSystem;
33
- const clientDir = path.join(config.dir, "client");
34
- const prerenderedDir = path.join(config.dir, "prerendered");
35
- const immutablePrefix = `/${config.appPath}/immutable/`;
36
- /**
37
- * Serve a file from `root` if `pathname` resolves to an existing file
38
- * inside it (rejecting path traversal). `Option.none()` otherwise.
39
- */
40
- const serveFile = (root, candidate, headers) => Effect.gen(function* () {
41
- const filePath = path.join(root, candidate);
42
- const relative = path.relative(root, filePath);
43
- if (relative.startsWith("..") || path.isAbsolute(relative)) return Option.none();
44
- const stat = yield* fs.stat(filePath).pipe(Effect.option);
45
- if (Option.isNone(stat) || stat.value.type !== "File") return Option.none();
46
- const response = yield* HttpServerResponse.file(filePath, { headers }).pipe(Effect.orDie);
47
- return Option.some(response);
48
- });
49
- const asset = yield* serveFile(clientDir, pathname, pathname.startsWith(immutablePrefix) ? IMMUTABLE_HEADERS : void 0);
50
- if (Option.isSome(asset)) return asset;
51
- if (config.prerendered.has(pathname) || config.prerendered.has(pathname + "/")) {
52
- const candidates = pathname === "/" ? ["/index.html"] : pathname.endsWith("/") ? [`${pathname}index.html`, `${pathname.slice(0, -1)}.html`] : [
53
- pathname,
54
- `${pathname}.html`,
55
- `${pathname}/index.html`
56
- ];
57
- for (const candidate of candidates) {
58
- const page = yield* serveFile(prerenderedDir, candidate);
59
- if (Option.isSome(page)) return page;
33
+ const fileSystem = (config) => {
34
+ const prerendered = HashSet.fromIterable(config.prerendered);
35
+ return { serveAsset: (request, pathname) => Effect.gen(function* () {
36
+ if (request.method !== "GET" && request.method !== "HEAD") return Option.none();
37
+ const path = yield* Path.Path;
38
+ const fs = yield* FileSystem.FileSystem;
39
+ const clientDir = path.join(config.dir, "client");
40
+ const prerenderedDir = path.join(config.dir, "prerendered");
41
+ const immutablePrefix = `/${config.appPath}/immutable/`;
42
+ /**
43
+ * Serve a file from `root` if `pathname` resolves to an existing file
44
+ * inside it (rejecting path traversal). `Option.none()` otherwise.
45
+ */
46
+ const serveFile = (root, candidate, headers) => Effect.gen(function* () {
47
+ const filePath = path.join(root, candidate);
48
+ const relative = path.relative(root, filePath);
49
+ if (relative.startsWith("..") || path.isAbsolute(relative)) return Option.none();
50
+ const stat = yield* fs.stat(filePath).pipe(Effect.option);
51
+ if (Option.isNone(stat) || stat.value.type !== "File") return Option.none();
52
+ const response = yield* HttpServerResponse.file(filePath, { headers }).pipe(Effect.orDie);
53
+ return Option.some(response);
54
+ });
55
+ const asset = yield* serveFile(clientDir, pathname, pathname.startsWith(immutablePrefix) ? IMMUTABLE_HEADERS : void 0);
56
+ if (Option.isSome(asset)) return asset;
57
+ if (HashSet.has(prerendered, pathname) || HashSet.has(prerendered, pathname + "/")) {
58
+ const candidates = pathname === "/" ? ["/index.html"] : pathname.endsWith("/") ? [`${pathname}index.html`, `${pathname.slice(0, -1)}.html`] : [
59
+ pathname,
60
+ `${pathname}.html`,
61
+ `${pathname}/index.html`
62
+ ];
63
+ return yield* Effect.findFirstFilter(candidates, (candidate) => serveFile(prerenderedDir, candidate).pipe(Effect.map(Result.fromOption(() => candidate))));
60
64
  }
61
- }
62
- return Option.none();
63
- }) });
65
+ return Option.none();
66
+ }) };
67
+ };
68
+ /**
69
+ * A {@link cloudflareAssets} binding's `fetch` rejected. The asset source's
70
+ * error channel is `never`, so this reaches the handler as a DEFECT (a 500),
71
+ * carrying what `fetch` rejected with as `cause`.
72
+ */
73
+ var AssetFetchError = class extends Schema.TaggedError()("AssetFetchError", {
74
+ /** The request path the binding was asked for. */
75
+ url: Schema.String,
76
+ /** What the binding's `fetch` rejected with, or threw. */
77
+ cause: Schema.Defect()
78
+ }) {};
64
79
  /**
65
80
  * Serve static assets from a Cloudflare Workers static-assets binding (commonly
66
81
  * `env.ASSETS`). The binding's `fetch` resolves client assets and prerendered
@@ -72,7 +87,13 @@ const fileSystem = (config) => ({ serveAsset: (request, pathname) => Effect.gen(
72
87
  */
73
88
  const cloudflareAssets = (assets) => ({ serveAsset: (request) => Effect.gen(function* () {
74
89
  if (request.method !== "GET" && request.method !== "HEAD") return Option.none();
75
- const response = yield* Effect.promise(async () => assets.fetch(request));
90
+ const response = yield* Effect.tryPromise({
91
+ try: async () => assets.fetch(request),
92
+ catch: (cause) => new AssetFetchError({
93
+ url: request.url,
94
+ cause
95
+ })
96
+ }).pipe(Effect.orDie);
76
97
  if (response.status === 404) return Option.none();
77
98
  return Option.some(HttpServerResponse.fromWeb(response));
78
99
  }) });
@@ -82,6 +103,6 @@ const AssetSource = {
82
103
  cloudflareAssets
83
104
  };
84
105
  //#endregion
85
- export { AssetSource, cloudflareAssets, fileSystem };
106
+ export { AssetFetchError, AssetSource, cloudflareAssets, fileSystem };
86
107
 
87
108
  //# sourceMappingURL=assets.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"assets.js","names":[],"sources":["../../src/adapter/assets.ts"],"sourcesContent":["/**\n * Pluggable asset serving for the Effect SvelteKit adapter.\n *\n * The SSR core ({@link makeHandler}) is platform-neutral: it falls through to an\n * injected {@link AssetSource} for static client assets and prerendered pages,\n * rather than reading the file system directly. This lets the same SvelteKit\n * `HttpApp` run on a Node server (assets from disk) or on Cloudflare Workers\n * (assets from the `env.ASSETS` binding, or any custom source — R2, KV, a CDN\n * passthrough).\n *\n * Ship several sources as equals and let the adapter (or the caller) pick:\n *\n * - {@link fileSystem} — serve `client/` + `prerendered/` from disk (Node\n * default). Requires `FileSystem` / `Path` / `HttpPlatform`.\n * - {@link cloudflareAssets} — serve from a Workers static-assets binding\n * (`env.ASSETS`). Requires nothing.\n */\n\nimport { Effect, Option } from \"effect\";\nimport * as FileSystem from \"effect/FileSystem\";\nimport * as Path from \"effect/Path\";\nimport * as HttpPlatform from \"effect/http/HttpPlatform\";\nimport * as HttpServerResponse from \"effect/http/HttpServerResponse\";\n\n/**\n * A source of static assets. `serveAsset` returns the response for an asset\n * request, or `Option.none()` when the request is not an asset (so the SSR core\n * continues to server-side rendering). `R` is the set of services the source\n * needs (e.g. `FileSystem | Path | HttpPlatform` for {@link fileSystem}, or\n * `never` for {@link cloudflareAssets}).\n */\nexport interface AssetSource<R = never> {\n readonly serveAsset: (\n request: Request,\n pathname: string,\n ) => Effect.Effect<Option.Option<HttpServerResponse.HttpServerResponse>, never, R>;\n}\n\n/** The services a file-system {@link AssetSource} requires. */\nexport type FileSystemAssetServices = FileSystem.FileSystem | Path.Path | HttpPlatform.HttpPlatform;\n\nconst IMMUTABLE_HEADERS = { \"cache-control\": \"public, max-age=31536000, immutable\" };\n\n/**\n * Serve static client assets and prerendered pages from a build directory on\n * disk (the Node default). `dir` contains `client/` and `prerendered/`.\n */\nexport const fileSystem = (config: {\n /** Absolute path of the build output directory (contains `client/`, `prerendered/`). */\n readonly dir: string;\n /** The configured `manifest.appPath` — its `immutable/` subtree gets immutable caching. */\n readonly appPath: string;\n /** The set of prerendered paths exported by the build output. */\n readonly prerendered: ReadonlySet<string>;\n}): AssetSource<FileSystemAssetServices> => ({\n serveAsset: (request, pathname) =>\n Effect.gen(function* () {\n // Only GET/HEAD reach static assets.\n if (request.method !== \"GET\" && request.method !== \"HEAD\") return Option.none();\n\n const path = yield* Path.Path;\n const fs = yield* FileSystem.FileSystem;\n\n const clientDir = path.join(config.dir, \"client\");\n const prerenderedDir = path.join(config.dir, \"prerendered\");\n const immutablePrefix = `/${config.appPath}/immutable/`;\n\n /**\n * Serve a file from `root` if `pathname` resolves to an existing file\n * inside it (rejecting path traversal). `Option.none()` otherwise.\n */\n const serveFile = (\n root: string,\n candidate: string,\n headers?: Record<string, string>,\n ): Effect.Effect<\n Option.Option<HttpServerResponse.HttpServerResponse>,\n never,\n FileSystemAssetServices\n > =>\n Effect.gen(function* () {\n const filePath = path.join(root, candidate);\n const relative = path.relative(root, filePath);\n if (relative.startsWith(\"..\") || path.isAbsolute(relative)) return Option.none();\n\n const stat = yield* fs.stat(filePath).pipe(Effect.option);\n if (Option.isNone(stat) || stat.value.type !== \"File\") return Option.none();\n\n const response = yield* HttpServerResponse.file(filePath, { headers }).pipe(Effect.orDie);\n return Option.some(response);\n });\n\n // 1. Static client assets (hashed build assets get immutable caching).\n const isImmutable = pathname.startsWith(immutablePrefix);\n const asset = yield* serveFile(\n clientDir,\n pathname,\n isImmutable ? IMMUTABLE_HEADERS : undefined,\n );\n if (Option.isSome(asset)) return asset;\n\n // 2. Prerendered pages.\n if (config.prerendered.has(pathname) || config.prerendered.has(pathname + \"/\")) {\n const candidates =\n pathname === \"/\"\n ? [\"/index.html\"]\n : pathname.endsWith(\"/\")\n ? [`${pathname}index.html`, `${pathname.slice(0, -1)}.html`]\n : [pathname, `${pathname}.html`, `${pathname}/index.html`];\n for (const candidate of candidates) {\n const page = yield* serveFile(prerenderedDir, candidate);\n if (Option.isSome(page)) return page;\n }\n }\n\n return Option.none();\n }),\n});\n\n/**\n * Serve static assets from a Cloudflare Workers static-assets binding (commonly\n * `env.ASSETS`). The binding's `fetch` resolves client assets and prerendered\n * pages uploaded by the deployment; a 404 from it means \"not an asset\", so the\n * SSR core continues to server-side rendering.\n *\n * This is one provided convenience, not the assumption: pass any object with a\n * `fetch` (R2, KV, a CDN passthrough) or a custom {@link AssetSource}.\n */\nexport const cloudflareAssets = (assets: {\n fetch: (request: Request) => Promise<Response> | Response;\n}): AssetSource => ({\n serveAsset: (request) =>\n Effect.gen(function* () {\n if (request.method !== \"GET\" && request.method !== \"HEAD\") return Option.none();\n const response = yield* Effect.promise(async () => assets.fetch(request));\n if (response.status === 404) return Option.none();\n return Option.some(HttpServerResponse.fromWeb(response));\n }),\n});\n\n/** The {@link AssetSource} constructors, grouped for the adapter API. */\nexport const AssetSource = {\n fileSystem,\n cloudflareAssets,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAyCA,MAAM,oBAAoB,EAAE,iBAAiB,sCAAsC;;;;;AAMnF,MAAa,cAAc,YAOkB,EAC3C,aAAa,SAAS,aACpB,OAAO,IAAI,aAAa;CAEtB,IAAI,QAAQ,WAAW,SAAS,QAAQ,WAAW,QAAQ,OAAO,OAAO,KAAK;CAE9E,MAAM,OAAO,OAAO,KAAK;CACzB,MAAM,KAAK,OAAO,WAAW;CAE7B,MAAM,YAAY,KAAK,KAAK,OAAO,KAAK,QAAQ;CAChD,MAAM,iBAAiB,KAAK,KAAK,OAAO,KAAK,aAAa;CAC1D,MAAM,kBAAkB,IAAI,OAAO,QAAQ;;;;;CAM3C,MAAM,aACJ,MACA,WACA,YAMA,OAAO,IAAI,aAAa;EACtB,MAAM,WAAW,KAAK,KAAK,MAAM,SAAS;EAC1C,MAAM,WAAW,KAAK,SAAS,MAAM,QAAQ;EAC7C,IAAI,SAAS,WAAW,IAAI,KAAK,KAAK,WAAW,QAAQ,GAAG,OAAO,OAAO,KAAK;EAE/E,MAAM,OAAO,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,KAAK,OAAO,MAAM;EACxD,IAAI,OAAO,OAAO,IAAI,KAAK,KAAK,MAAM,SAAS,QAAQ,OAAO,OAAO,KAAK;EAE1E,MAAM,WAAW,OAAO,mBAAmB,KAAK,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,OAAO,KAAK;EACxF,OAAO,OAAO,KAAK,QAAQ;CAC7B,CAAC;CAIH,MAAM,QAAQ,OAAO,UACnB,WACA,UAHkB,SAAS,WAAW,eAI5B,IAAI,oBAAoB,KAAA,CACpC;CACA,IAAI,OAAO,OAAO,KAAK,GAAG,OAAO;CAGjC,IAAI,OAAO,YAAY,IAAI,QAAQ,KAAK,OAAO,YAAY,IAAI,WAAW,GAAG,GAAG;EAC9E,MAAM,aACJ,aAAa,MACT,CAAC,aAAa,IACd,SAAS,SAAS,GAAG,IACnB,CAAC,GAAG,SAAS,aAAa,GAAG,SAAS,MAAM,GAAG,EAAE,EAAE,MAAM,IACzD;GAAC;GAAU,GAAG,SAAS;GAAQ,GAAG,SAAS;EAAY;EAC/D,KAAK,MAAM,aAAa,YAAY;GAClC,MAAM,OAAO,OAAO,UAAU,gBAAgB,SAAS;GACvD,IAAI,OAAO,OAAO,IAAI,GAAG,OAAO;EAClC;CACF;CAEA,OAAO,OAAO,KAAK;AACrB,CAAC,EACL;;;;;;;;;;AAWA,MAAa,oBAAoB,YAEb,EAClB,aAAa,YACX,OAAO,IAAI,aAAa;CACtB,IAAI,QAAQ,WAAW,SAAS,QAAQ,WAAW,QAAQ,OAAO,OAAO,KAAK;CAC9E,MAAM,WAAW,OAAO,OAAO,QAAQ,YAAY,OAAO,MAAM,OAAO,CAAC;CACxE,IAAI,SAAS,WAAW,KAAK,OAAO,OAAO,KAAK;CAChD,OAAO,OAAO,KAAK,mBAAmB,QAAQ,QAAQ,CAAC;AACzD,CAAC,EACL;;AAGA,MAAa,cAAc;CACzB;CACA;AACF"}
1
+ {"version":3,"file":"assets.js","names":[],"sources":["../../src/adapter/assets.ts"],"sourcesContent":["/**\n * Pluggable asset serving for the Effect SvelteKit adapter.\n *\n * The SSR core ({@link makeHandler}) is platform-neutral: it falls through to an\n * injected {@link AssetSource} for static client assets and prerendered pages,\n * rather than reading the file system directly. This lets the same SvelteKit\n * `HttpApp` run on a Node server (assets from disk) or on Cloudflare Workers\n * (assets from the `env.ASSETS` binding, or any custom source — R2, KV, a CDN\n * passthrough).\n *\n * Ship several sources as equals and let the adapter (or the caller) pick:\n *\n * - {@link fileSystem} — serve `client/` + `prerendered/` from disk (Node\n * default). Requires `FileSystem` / `Path` / `HttpPlatform`.\n * - {@link cloudflareAssets} — serve from a Workers static-assets binding\n * (`env.ASSETS`). Requires nothing.\n */\n\nimport * as Effect from \"effect/Effect\";\nimport * as HashSet from \"effect/HashSet\";\nimport * as Option from \"effect/Option\";\nimport * as Result from \"effect/Result\";\nimport * as Schema from \"effect/Schema\";\nimport * as FileSystem from \"effect/FileSystem\";\nimport * as Path from \"effect/Path\";\nimport * as HttpPlatform from \"effect/http/HttpPlatform\";\nimport * as HttpServerResponse from \"effect/http/HttpServerResponse\";\n\n/**\n * A source of static assets. `serveAsset` returns the response for an asset\n * request, or `Option.none()` when the request is not an asset (so the SSR core\n * continues to server-side rendering). `R` is the set of services the source\n * needs (e.g. `FileSystem | Path | HttpPlatform` for {@link fileSystem}, or\n * `never` for {@link cloudflareAssets}).\n */\nexport interface AssetSource<R = never> {\n readonly serveAsset: (\n request: Request,\n pathname: string,\n ) => Effect.Effect<Option.Option<HttpServerResponse.HttpServerResponse>, never, R>;\n}\n\n/** The services a file-system {@link AssetSource} requires. */\nexport type FileSystemAssetServices = FileSystem.FileSystem | Path.Path | HttpPlatform.HttpPlatform;\n\nconst IMMUTABLE_HEADERS = { \"cache-control\": \"public, max-age=31536000, immutable\" };\n\n/**\n * Serve static client assets and prerendered pages from a build directory on\n * disk (the Node default). `dir` contains `client/` and `prerendered/`.\n */\nexport const fileSystem = (config: {\n /** Absolute path of the build output directory (contains `client/`, `prerendered/`). */\n readonly dir: string;\n /** The configured `manifest.appPath` — its `immutable/` subtree gets immutable caching. */\n readonly appPath: string;\n /**\n * The prerendered paths exported by the build output (the build's\n * `prerendered` `Set`, or any iterable of paths).\n */\n readonly prerendered: Iterable<string>;\n}): AssetSource<FileSystemAssetServices> => {\n const prerendered = HashSet.fromIterable(config.prerendered);\n return {\n serveAsset: (request, pathname) =>\n Effect.gen(function* () {\n // Only GET/HEAD reach static assets.\n if (request.method !== \"GET\" && request.method !== \"HEAD\") return Option.none();\n\n const path = yield* Path.Path;\n const fs = yield* FileSystem.FileSystem;\n\n const clientDir = path.join(config.dir, \"client\");\n const prerenderedDir = path.join(config.dir, \"prerendered\");\n const immutablePrefix = `/${config.appPath}/immutable/`;\n\n /**\n * Serve a file from `root` if `pathname` resolves to an existing file\n * inside it (rejecting path traversal). `Option.none()` otherwise.\n */\n const serveFile = (\n root: string,\n candidate: string,\n headers?: Record<string, string>,\n ): Effect.Effect<\n Option.Option<HttpServerResponse.HttpServerResponse>,\n never,\n FileSystemAssetServices\n > =>\n Effect.gen(function* () {\n const filePath = path.join(root, candidate);\n const relative = path.relative(root, filePath);\n if (relative.startsWith(\"..\") || path.isAbsolute(relative)) return Option.none();\n\n const stat = yield* fs.stat(filePath).pipe(Effect.option);\n if (Option.isNone(stat) || stat.value.type !== \"File\") return Option.none();\n\n const response = yield* HttpServerResponse.file(filePath, { headers }).pipe(\n Effect.orDie,\n );\n return Option.some(response);\n });\n\n // 1. Static client assets (hashed build assets get immutable caching).\n const isImmutable = pathname.startsWith(immutablePrefix);\n const asset = yield* serveFile(\n clientDir,\n pathname,\n isImmutable ? IMMUTABLE_HEADERS : undefined,\n );\n if (Option.isSome(asset)) return asset;\n\n // 2. Prerendered pages.\n if (HashSet.has(prerendered, pathname) || HashSet.has(prerendered, pathname + \"/\")) {\n const candidates =\n pathname === \"/\"\n ? [\"/index.html\"]\n : pathname.endsWith(\"/\")\n ? [`${pathname}index.html`, `${pathname.slice(0, -1)}.html`]\n : [pathname, `${pathname}.html`, `${pathname}/index.html`];\n // The first candidate that exists, tried in order.\n return yield* Effect.findFirstFilter(candidates, (candidate) =>\n serveFile(prerenderedDir, candidate).pipe(\n Effect.map(Result.fromOption(() => candidate)),\n ),\n );\n }\n\n return Option.none();\n }),\n };\n};\n\n/**\n * A {@link cloudflareAssets} binding's `fetch` rejected. The asset source's\n * error channel is `never`, so this reaches the handler as a DEFECT (a 500),\n * carrying what `fetch` rejected with as `cause`.\n */\nexport class AssetFetchError extends Schema.TaggedError<AssetFetchError>()(\"AssetFetchError\", {\n /** The request path the binding was asked for. */\n url: Schema.String,\n /** What the binding's `fetch` rejected with, or threw. */\n cause: Schema.Defect(),\n}) {}\n\n/**\n * Serve static assets from a Cloudflare Workers static-assets binding (commonly\n * `env.ASSETS`). The binding's `fetch` resolves client assets and prerendered\n * pages uploaded by the deployment; a 404 from it means \"not an asset\", so the\n * SSR core continues to server-side rendering.\n *\n * This is one provided convenience, not the assumption: pass any object with a\n * `fetch` (R2, KV, a CDN passthrough) or a custom {@link AssetSource}.\n */\nexport const cloudflareAssets = (assets: {\n fetch: (request: Request) => Promise<Response> | Response;\n}): AssetSource => ({\n serveAsset: (request) =>\n Effect.gen(function* () {\n if (request.method !== \"GET\" && request.method !== \"HEAD\") return Option.none();\n const response = yield* Effect.tryPromise({\n try: async () => assets.fetch(request),\n catch: (cause) => new AssetFetchError({ url: request.url, cause }),\n }).pipe(Effect.orDie);\n if (response.status === 404) return Option.none();\n return Option.some(HttpServerResponse.fromWeb(response));\n }),\n});\n\n/** The {@link AssetSource} constructors, grouped for the adapter API. */\nexport const AssetSource = {\n fileSystem,\n cloudflareAssets,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,MAAM,oBAAoB,EAAE,iBAAiB,sCAAsC;;;;;AAMnF,MAAa,cAAc,WAUiB;CAC1C,MAAM,cAAc,QAAQ,aAAa,OAAO,WAAW;CAC3D,OAAO,EACL,aAAa,SAAS,aACpB,OAAO,IAAI,aAAa;EAEtB,IAAI,QAAQ,WAAW,SAAS,QAAQ,WAAW,QAAQ,OAAO,OAAO,KAAK;EAE9E,MAAM,OAAO,OAAO,KAAK;EACzB,MAAM,KAAK,OAAO,WAAW;EAE7B,MAAM,YAAY,KAAK,KAAK,OAAO,KAAK,QAAQ;EAChD,MAAM,iBAAiB,KAAK,KAAK,OAAO,KAAK,aAAa;EAC1D,MAAM,kBAAkB,IAAI,OAAO,QAAQ;;;;;EAM3C,MAAM,aACJ,MACA,WACA,YAMA,OAAO,IAAI,aAAa;GACtB,MAAM,WAAW,KAAK,KAAK,MAAM,SAAS;GAC1C,MAAM,WAAW,KAAK,SAAS,MAAM,QAAQ;GAC7C,IAAI,SAAS,WAAW,IAAI,KAAK,KAAK,WAAW,QAAQ,GAAG,OAAO,OAAO,KAAK;GAE/E,MAAM,OAAO,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,KAAK,OAAO,MAAM;GACxD,IAAI,OAAO,OAAO,IAAI,KAAK,KAAK,MAAM,SAAS,QAAQ,OAAO,OAAO,KAAK;GAE1E,MAAM,WAAW,OAAO,mBAAmB,KAAK,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC,KACrE,OAAO,KACT;GACA,OAAO,OAAO,KAAK,QAAQ;EAC7B,CAAC;EAIH,MAAM,QAAQ,OAAO,UACnB,WACA,UAHkB,SAAS,WAAW,eAI5B,IAAI,oBAAoB,KAAA,CACpC;EACA,IAAI,OAAO,OAAO,KAAK,GAAG,OAAO;EAGjC,IAAI,QAAQ,IAAI,aAAa,QAAQ,KAAK,QAAQ,IAAI,aAAa,WAAW,GAAG,GAAG;GAClF,MAAM,aACJ,aAAa,MACT,CAAC,aAAa,IACd,SAAS,SAAS,GAAG,IACnB,CAAC,GAAG,SAAS,aAAa,GAAG,SAAS,MAAM,GAAG,EAAE,EAAE,MAAM,IACzD;IAAC;IAAU,GAAG,SAAS;IAAQ,GAAG,SAAS;GAAY;GAE/D,OAAO,OAAO,OAAO,gBAAgB,aAAa,cAChD,UAAU,gBAAgB,SAAS,CAAC,CAAC,KACnC,OAAO,IAAI,OAAO,iBAAiB,SAAS,CAAC,CAC/C,CACF;EACF;EAEA,OAAO,OAAO,KAAK;CACrB,CAAC,EACL;AACF;;;;;;AAOA,IAAa,kBAAb,cAAqC,OAAO,YAA6B,CAAC,CAAC,mBAAmB;;CAE5F,KAAK,OAAO;;CAEZ,OAAO,OAAO,OAAO;AACvB,CAAC,CAAC,CAAC,CAAC;;;;;;;;;;AAWJ,MAAa,oBAAoB,YAEb,EAClB,aAAa,YACX,OAAO,IAAI,aAAa;CACtB,IAAI,QAAQ,WAAW,SAAS,QAAQ,WAAW,QAAQ,OAAO,OAAO,KAAK;CAC9E,MAAM,WAAW,OAAO,OAAO,WAAW;EACxC,KAAK,YAAY,OAAO,MAAM,OAAO;EACrC,QAAQ,UAAU,IAAI,gBAAgB;GAAE,KAAK,QAAQ;GAAK;EAAM,CAAC;CACnE,CAAC,CAAC,CAAC,KAAK,OAAO,KAAK;CACpB,IAAI,SAAS,WAAW,KAAK,OAAO,OAAO,KAAK;CAChD,OAAO,OAAO,KAAK,mBAAmB,QAAQ,QAAQ,CAAC;AACzD,CAAC,EACL;;AAGA,MAAa,cAAc;CACzB;CACA;AACF"}
@@ -1,6 +1,8 @@
1
- import { AssetSource, FileSystemAssetServices } from "./assets.js";
2
- import { Effect, Layer } from "effect";
1
+ import { AssetFetchError, AssetSource, FileSystemAssetServices } from "./assets.js";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
3
4
  import { Server } from "@sveltejs/kit";
5
+ import * as Schema from "effect/Schema";
4
6
  import * as HttpServerRequest from "effect/http/HttpServerRequest";
5
7
  import * as HttpServerResponse from "effect/http/HttpServerResponse";
6
8
  //#region src/adapter/handler.d.ts
@@ -9,16 +11,16 @@ import * as HttpServerResponse from "effect/http/HttpServerResponse";
9
11
  * from the file system (the Node default). All of them are provided by
10
12
  * `NodeHttpServer.layer` from `@effect/platform-node`.
11
13
  */
12
- type SvelteKitHttpAppServices = FileSystemAssetServices;
14
+ export type SvelteKitHttpAppServices = FileSystemAssetServices;
13
15
  /**
14
16
  * The SvelteKit application as a composable Effect HTTP handler: an effect that
15
17
  * produces an {@link HttpServerResponse.HttpServerResponse} for the current
16
18
  * request. The `HttpServerRequest` is supplied per request by the server. `R`
17
19
  * is the asset source's services (defaults to the file-system services).
18
20
  */
19
- type SvelteKitHttpApp<R = SvelteKitHttpAppServices> = Effect.Effect<HttpServerResponse.HttpServerResponse, never, R | HttpServerRequest.HttpServerRequest>;
21
+ export type SvelteKitHttpApp<R = SvelteKitHttpAppServices> = Effect.Effect<HttpServerResponse.HttpServerResponse, never, R | HttpServerRequest.HttpServerRequest>;
20
22
  /** The SvelteKit build output {@link makeHandler} serves. */
21
- interface SvelteKitHandlerOptions {
23
+ export interface SvelteKitHandlerOptions {
22
24
  /** The SvelteKit `server` instance exported by the build output (`./server.js`). */
23
25
  readonly server: Server;
24
26
  /**
@@ -28,8 +30,8 @@ interface SvelteKitHandlerOptions {
28
30
  * headers.
29
31
  */
30
32
  readonly appPath: string;
31
- /** The set of prerendered paths exported by the build output (`./manifest.js`). */
32
- readonly prerendered: ReadonlySet<string>;
33
+ /** The prerendered paths exported by the build output (`./manifest.js`'s `Set`, or any iterable). */
34
+ readonly prerendered: Iterable<string>;
33
35
  /** The configured `paths.base`, exported by the build output (`./manifest.js`). */
34
36
  readonly base: string;
35
37
  /** Absolute path of the build output directory (contains `client/`, `server/`, `prerendered/`). */
@@ -40,7 +42,7 @@ interface SvelteKitHandlerOptions {
40
42
  readonly envPrefix?: string;
41
43
  }
42
44
  /** Options for the platform-neutral SSR core, {@link makeHandlerWith}. */
43
- interface SvelteKitNeutralHandlerOptions<RAssets> {
45
+ export interface SvelteKitNeutralHandlerOptions<RAssets> {
44
46
  /** The SvelteKit `server` instance exported by the build output (`./server.js`). */
45
47
  readonly server: Server;
46
48
  /** Environment variables passed to the SvelteKit server. */
@@ -83,7 +85,20 @@ interface SvelteKitNeutralHandlerOptions<RAssets> {
83
85
  * NodeRuntime.runMain(Layer.launch(serverLayer));
84
86
  * ```
85
87
  */
86
- declare const makeHandler: (options: SvelteKitHandlerOptions) => Effect.Effect<SvelteKitHttpApp, never, SvelteKitHttpAppServices>;
88
+ export declare const makeHandler: (options: SvelteKitHandlerOptions) => Effect.Effect<SvelteKitHttpApp, never, SvelteKitHttpAppServices>;
89
+ declare const SvelteKitServerError_base: Schema.Class<SvelteKitServerError, Schema.TaggedStruct<"SvelteKitServerError", {
90
+ /** Which `Server` method rejected. */
91
+ readonly operation: Schema.Literals<readonly ["init", "respond"]>;
92
+ /** What the method rejected with, or threw. */
93
+ readonly cause: Schema.Defect;
94
+ }>, import("effect/Cause").YieldableError>;
95
+ /**
96
+ * The SvelteKit server's `init` or `respond` rejected. SvelteKit turns a
97
+ * route's own errors into responses, so a rejection is a bug in the server
98
+ * build; the handler's error channel is `never`, so this reaches the platform
99
+ * as a DEFECT (a 500), carrying what SvelteKit rejected with as `cause`.
100
+ */
101
+ export declare class SvelteKitServerError extends SvelteKitServerError_base {}
87
102
  /**
88
103
  * The platform-neutral SSR core. Like {@link makeHandler}, but the asset source
89
104
  * and the server-asset `read` capability are injected, so the same SvelteKit
@@ -114,7 +129,7 @@ declare const makeHandler: (options: SvelteKitHandlerOptions) => Effect.Effect<S
114
129
  * };
115
130
  * ```
116
131
  */
117
- declare const makeHandlerWith: <RAssets = never>(options: SvelteKitNeutralHandlerOptions<RAssets>) => Effect.Effect<SvelteKitHttpApp<RAssets>, never, never>;
132
+ export declare const makeHandlerWith: <RAssets = never>(options: SvelteKitNeutralHandlerOptions<RAssets>) => Effect.Effect<SvelteKitHttpApp<RAssets>, never, never>;
118
133
  /**
119
134
  * Convert a SvelteKit `HttpApp` to a Web `fetch` handler, providing the asset
120
135
  * source's services with `layer`. Use this for a Workers `fetch` export or any
@@ -128,7 +143,7 @@ declare const makeHandlerWith: <RAssets = never>(options: SvelteKitNeutralHandle
128
143
  * );
129
144
  * ```
130
145
  */
131
- declare const toWebHandler: <RAssets, E>(app: Effect.Effect<SvelteKitHttpApp<RAssets>, never, never>, layer: Layer.Layer<RAssets, E>) => ((request: Request) => Promise<Response>);
146
+ export declare const toWebHandler: <RAssets, E>(app: Effect.Effect<SvelteKitHttpApp<RAssets>, never, never>, layer: Layer.Layer<RAssets, E>) => ((request: Request) => Promise<Response>);
132
147
  //#endregion
133
- export { AssetSource, type FileSystemAssetServices, SvelteKitHandlerOptions, SvelteKitHttpApp, SvelteKitHttpAppServices, SvelteKitNeutralHandlerOptions, makeHandler, makeHandlerWith, toWebHandler };
148
+ export { AssetFetchError, AssetSource, type FileSystemAssetServices };
134
149
  //# sourceMappingURL=handler.d.ts.map