effective-rsc 0.1.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 (220) hide show
  1. package/LICENSE +21 -0
  2. package/LLMS.md +382 -0
  3. package/README.md +235 -0
  4. package/bin/ersc.js +3 -0
  5. package/dist/application/component.d.ts +15 -0
  6. package/dist/application/component.js +20 -0
  7. package/dist/application/component.js.map +1 -0
  8. package/dist/application/definition.d.ts +31 -0
  9. package/dist/application/definition.js +57 -0
  10. package/dist/application/definition.js.map +1 -0
  11. package/dist/application/ersc-identity.d.ts +25 -0
  12. package/dist/application/ersc-identity.js +23 -0
  13. package/dist/application/ersc-identity.js.map +1 -0
  14. package/dist/application/ersc.d.ts +28 -0
  15. package/dist/application/ersc.js +72 -0
  16. package/dist/application/ersc.js.map +1 -0
  17. package/dist/application/layout.d.ts +18 -0
  18. package/dist/application/layout.js +17 -0
  19. package/dist/application/layout.js.map +1 -0
  20. package/dist/application/loading.d.ts +17 -0
  21. package/dist/application/loading.js +14 -0
  22. package/dist/application/loading.js.map +1 -0
  23. package/dist/application/middleware.d.ts +36 -0
  24. package/dist/application/middleware.js +46 -0
  25. package/dist/application/middleware.js.map +1 -0
  26. package/dist/application/page.d.ts +58 -0
  27. package/dist/application/page.js +50 -0
  28. package/dist/application/page.js.map +1 -0
  29. package/dist/application/render-runtime.d.ts +9 -0
  30. package/dist/application/render-runtime.js +28 -0
  31. package/dist/application/render-runtime.js.map +1 -0
  32. package/dist/application/route-graph.d.ts +22 -0
  33. package/dist/application/route-graph.js +68 -0
  34. package/dist/application/route-graph.js.map +1 -0
  35. package/dist/application/route-path.d.ts +31 -0
  36. package/dist/application/route-path.js +67 -0
  37. package/dist/application/route-path.js.map +1 -0
  38. package/dist/application/routes.d.ts +66 -0
  39. package/dist/application/routes.js +159 -0
  40. package/dist/application/routes.js.map +1 -0
  41. package/dist/application/server-fn.d.ts +30 -0
  42. package/dist/application/server-fn.js +58 -0
  43. package/dist/application/server-fn.js.map +1 -0
  44. package/dist/build/build.d.ts +26 -0
  45. package/dist/build/build.js +59 -0
  46. package/dist/build/build.js.map +1 -0
  47. package/dist/build/compiled-server.d.ts +53 -0
  48. package/dist/build/compiled-server.js +77 -0
  49. package/dist/build/compiled-server.js.map +1 -0
  50. package/dist/build/contract.d.ts +23 -0
  51. package/dist/build/contract.js +41 -0
  52. package/dist/build/contract.js.map +1 -0
  53. package/dist/build/dev-channel.d.ts +20 -0
  54. package/dist/build/dev-channel.js +71 -0
  55. package/dist/build/dev-channel.js.map +1 -0
  56. package/dist/build/dev.d.ts +41 -0
  57. package/dist/build/dev.js +186 -0
  58. package/dist/build/dev.js.map +1 -0
  59. package/dist/build/rsc-entry.d.ts +4 -0
  60. package/dist/build/rsc-entry.js +17 -0
  61. package/dist/build/rsc-entry.js.map +1 -0
  62. package/dist/build/rspack-config.d.ts +17 -0
  63. package/dist/build/rspack-config.js +259 -0
  64. package/dist/build/rspack-config.js.map +1 -0
  65. package/dist/build/rspack.d.ts +45 -0
  66. package/dist/build/rspack.js +231 -0
  67. package/dist/build/rspack.js.map +1 -0
  68. package/dist/build/terminal.d.ts +9 -0
  69. package/dist/build/terminal.js +17 -0
  70. package/dist/build/terminal.js.map +1 -0
  71. package/dist/cli.d.ts +14 -0
  72. package/dist/cli.js +116 -0
  73. package/dist/cli.js.map +1 -0
  74. package/dist/client/application.d.ts +2 -0
  75. package/dist/client/application.js +73 -0
  76. package/dist/client/application.js.map +1 -0
  77. package/dist/client/browser-capabilities.d.ts +9 -0
  78. package/dist/client/browser-capabilities.js +20 -0
  79. package/dist/client/browser-capabilities.js.map +1 -0
  80. package/dist/client/browser-effect-runner.d.ts +13 -0
  81. package/dist/client/browser-effect-runner.js +13 -0
  82. package/dist/client/browser-effect-runner.js.map +1 -0
  83. package/dist/client/browser-renderer.d.ts +70 -0
  84. package/dist/client/browser-renderer.js +159 -0
  85. package/dist/client/browser-renderer.js.map +1 -0
  86. package/dist/client/browser-screen.d.ts +2 -0
  87. package/dist/client/browser-screen.js +215 -0
  88. package/dist/client/browser-screen.js.map +1 -0
  89. package/dist/client/call-server.d.ts +8 -0
  90. package/dist/client/call-server.js +151 -0
  91. package/dist/client/call-server.js.map +1 -0
  92. package/dist/client/client-router.d.ts +6 -0
  93. package/dist/client/client-router.js +588 -0
  94. package/dist/client/client-router.js.map +1 -0
  95. package/dist/client/entry.d.ts +1 -0
  96. package/dist/client/entry.js +12 -0
  97. package/dist/client/entry.js.map +1 -0
  98. package/dist/client/flight-client.d.ts +83 -0
  99. package/dist/client/flight-client.js +146 -0
  100. package/dist/client/flight-client.js.map +1 -0
  101. package/dist/client/initial-flight-stream.d.ts +18 -0
  102. package/dist/client/initial-flight-stream.js +60 -0
  103. package/dist/client/initial-flight-stream.js.map +1 -0
  104. package/dist/client/navigation-api.d.ts +34 -0
  105. package/dist/client/navigation-api.js +25 -0
  106. package/dist/client/navigation-api.js.map +1 -0
  107. package/dist/client/navigation-routing.d.ts +3 -0
  108. package/dist/client/navigation-routing.js +8 -0
  109. package/dist/client/navigation-routing.js.map +1 -0
  110. package/dist/client/react-dom-renderer.d.ts +20 -0
  111. package/dist/client/react-dom-renderer.js +101 -0
  112. package/dist/client/react-dom-renderer.js.map +1 -0
  113. package/dist/client/route-loader.d.ts +81 -0
  114. package/dist/client/route-loader.js +128 -0
  115. package/dist/client/route-loader.js.map +1 -0
  116. package/dist/client/route-refresh.d.ts +31 -0
  117. package/dist/client/route-refresh.js +132 -0
  118. package/dist/client/route-refresh.js.map +1 -0
  119. package/dist/client/route-tree.d.ts +7 -0
  120. package/dist/client/route-tree.js +34 -0
  121. package/dist/client/route-tree.js.map +1 -0
  122. package/dist/dev/channel.d.ts +18 -0
  123. package/dist/dev/channel.js +30 -0
  124. package/dist/dev/channel.js.map +1 -0
  125. package/dist/dev/client.d.ts +3 -0
  126. package/dist/dev/client.js +132 -0
  127. package/dist/dev/client.js.map +1 -0
  128. package/dist/dev/hmr-update.d.ts +20 -0
  129. package/dist/dev/hmr-update.js +28 -0
  130. package/dist/dev/hmr-update.js.map +1 -0
  131. package/dist/dev/panel.d.ts +41 -0
  132. package/dist/dev/panel.js +9946 -0
  133. package/dist/dev/panel.js.map +1 -0
  134. package/dist/dev/runtime-failure.d.ts +11 -0
  135. package/dist/dev/runtime-failure.js +32 -0
  136. package/dist/dev/runtime-failure.js.map +1 -0
  137. package/dist/index.d.ts +1 -0
  138. package/dist/index.js +6 -0
  139. package/dist/index.js.map +1 -0
  140. package/dist/rsc/flight.d.ts +16 -0
  141. package/dist/rsc/flight.js +6 -0
  142. package/dist/rsc/flight.js.map +1 -0
  143. package/dist/rsc/render-route-tree.d.ts +11 -0
  144. package/dist/rsc/render-route-tree.js +52 -0
  145. package/dist/rsc/render-route-tree.js.map +1 -0
  146. package/dist/rsc/route-tree.d.ts +6 -0
  147. package/dist/rsc/route-tree.js +5 -0
  148. package/dist/rsc/route-tree.js.map +1 -0
  149. package/dist/server/application.d.ts +17 -0
  150. package/dist/server/application.js +178 -0
  151. package/dist/server/application.js.map +1 -0
  152. package/dist/server/flight-html-stream.d.ts +19 -0
  153. package/dist/server/flight-html-stream.js +193 -0
  154. package/dist/server/flight-html-stream.js.map +1 -0
  155. package/dist/server/flight-renderer.d.ts +31 -0
  156. package/dist/server/flight-renderer.js +47 -0
  157. package/dist/server/flight-renderer.js.map +1 -0
  158. package/dist/server/html-renderer.d.ts +28 -0
  159. package/dist/server/html-renderer.js +82 -0
  160. package/dist/server/html-renderer.js.map +1 -0
  161. package/dist/server/request-outcome.d.ts +8 -0
  162. package/dist/server/request-outcome.js +5 -0
  163. package/dist/server/request-outcome.js.map +1 -0
  164. package/dist/server/server-config.d.ts +17 -0
  165. package/dist/server/server-config.js +18 -0
  166. package/dist/server/server-config.js.map +1 -0
  167. package/dist/server/server-fn-outcome.d.ts +13 -0
  168. package/dist/server/server-fn-outcome.js +24 -0
  169. package/dist/server/server-fn-outcome.js.map +1 -0
  170. package/dist/server/server-fn-request.d.ts +24 -0
  171. package/dist/server/server-fn-request.js +160 -0
  172. package/dist/server/server-fn-request.js.map +1 -0
  173. package/dist/unsupported.d.ts +0 -0
  174. package/dist/unsupported.js +5 -0
  175. package/dist/unsupported.js.map +1 -0
  176. package/docs/01-getting-started/01_first-application.tsx +26 -0
  177. package/docs/01-getting-started/20_styling.tsx +6 -0
  178. package/docs/01-getting-started/index.md +27 -0
  179. package/docs/01-getting-started/styles.css +1 -0
  180. package/docs/02-guides/01-server-functions/10_ersc.ts +6 -0
  181. package/docs/02-guides/01-server-functions/20_follow-author.ts +15 -0
  182. package/docs/02-guides/01-server-functions/30_follow-author-button.tsx +19 -0
  183. package/docs/02-guides/01-server-functions/40_application.tsx +30 -0
  184. package/docs/02-guides/01-server-functions/index.md +19 -0
  185. package/docs/02-guides/02-services/10_catalog.ts +22 -0
  186. package/docs/02-guides/02-services/20_application.tsx +44 -0
  187. package/docs/02-guides/02-services/index.md +17 -0
  188. package/docs/02-guides/03-routing/10_ersc.ts +6 -0
  189. package/docs/02-guides/03-routing/10_layouts.tsx +36 -0
  190. package/docs/02-guides/03-routing/20_pages.tsx +23 -0
  191. package/docs/02-guides/03-routing/30_routes.tsx +17 -0
  192. package/docs/02-guides/03-routing/40_application.ts +7 -0
  193. package/docs/02-guides/03-routing/index.md +22 -0
  194. package/docs/02-guides/04-middleware/10_auth.ts +27 -0
  195. package/docs/02-guides/04-middleware/20_account-page.tsx +13 -0
  196. package/docs/02-guides/04-middleware/30_update-profile.ts +16 -0
  197. package/docs/02-guides/04-middleware/40_application.tsx +20 -0
  198. package/docs/02-guides/04-middleware/index.md +25 -0
  199. package/docs/02-guides/05-http/10_application-layer.tsx +60 -0
  200. package/docs/02-guides/05-http/index.md +23 -0
  201. package/docs/02-guides/index.md +11 -0
  202. package/docs/03-advanced/01-request-runtime-and-lifetimes/index.md +13 -0
  203. package/docs/03-advanced/02-client-navigation/index.md +61 -0
  204. package/docs/03-advanced/03-server-function-execution-and-refresh/index.md +17 -0
  205. package/docs/03-advanced/index.md +11 -0
  206. package/docs/04-api-reference/01-application/index.md +8 -0
  207. package/docs/04-api-reference/02-page/index.md +12 -0
  208. package/docs/04-api-reference/03-layout/index.md +4 -0
  209. package/docs/04-api-reference/04-loading/index.md +4 -0
  210. package/docs/04-api-reference/05-component/10_component.tsx +18 -0
  211. package/docs/04-api-reference/05-component/index.md +10 -0
  212. package/docs/04-api-reference/06-middleware/index.md +33 -0
  213. package/docs/04-api-reference/07-routes/index.md +20 -0
  214. package/docs/04-api-reference/08-server-fn/index.md +43 -0
  215. package/docs/04-api-reference/index.md +17 -0
  216. package/docs/environment.d.ts +1 -0
  217. package/docs/index.md +15 -0
  218. package/logo-dark.svg +6 -0
  219. package/logo.svg +6 -0
  220. package/package.json +87 -0
@@ -0,0 +1,27 @@
1
+ ## Getting started
2
+
3
+ Create an application with compatible dependencies and Tailwind support:
4
+
5
+ ```sh
6
+ bunx create-ersc-app my-application
7
+ cd my-application
8
+ bun run dev
9
+ ```
10
+
11
+ Open `http://localhost:18193`.
12
+
13
+ In `src/application.tsx`, create one ERSC identity, define a root Layout and Page, compose Routes,
14
+ and export `ERSC.make(...)`.
15
+
16
+ For a production check, run `bun run check`, `bun run build`, and `bun run start`. Both
17
+ `ersc dev` and `ersc start` accept `--hostname` and `--port`; flags take precedence over
18
+ `HOST` and `PORT`. See the package README for requirements and manual installation.
19
+
20
+ Files in `public/` are served from `/` with `Cache-Control: public, max-age=0`.
21
+
22
+ <!-- source-navigation -->
23
+
24
+ ### Examples
25
+
26
+ - [Minimal application](./01_first-application.tsx)
27
+ - [Stylesheet import](./20_styling.tsx)
@@ -0,0 +1 @@
1
+ @import 'tailwindcss';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @title Creating the Server Function authoring module
3
+ */
4
+ import { Application } from 'effective-rsc';
5
+
6
+ export const ERSC = Application.ersc();
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @title Defining a Server Function
3
+ *
4
+ * ERSC decodes FormData before running the Effect handler.
5
+ */
6
+ 'use server';
7
+
8
+ import { Effect, Schema } from 'effect';
9
+
10
+ import { ERSC } from './10_ersc';
11
+
12
+ export const followAuthor = ERSC.ServerFn.make({
13
+ input: Schema.fromFormData(Schema.Struct({ authorId: Schema.NonEmptyString })),
14
+ handler: ({ authorId }) => Effect.logInfo('Followed author', { authorId }),
15
+ });
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @title Rendering a direct form action
3
+ *
4
+ * A FormData Server Function can be passed directly to form action.
5
+ */
6
+ import { Effect } from 'effect';
7
+
8
+ import { ERSC } from './10_ersc';
9
+ import { followAuthor } from './20_follow-author';
10
+
11
+ export const FollowAuthorButton = ERSC.Component.make({
12
+ render: ({ authorId }: { readonly authorId: string }) =>
13
+ Effect.succeed(
14
+ <form action={followAuthor}>
15
+ <input name='authorId' type='hidden' value={authorId} />
16
+ <button type='submit'>Follow author</button>
17
+ </form>,
18
+ ),
19
+ });
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @title Closing the Server Function application
3
+ */
4
+ import { Effect } from 'effect';
5
+
6
+ import { ERSC } from './10_ersc';
7
+ import { FollowAuthorButton } from './30_follow-author-button';
8
+
9
+ const RootLayout = ERSC.Layout.make({
10
+ render: ({ children }) =>
11
+ Effect.succeed(
12
+ <html lang='en'>
13
+ <body>{children}</body>
14
+ </html>,
15
+ ),
16
+ });
17
+
18
+ const AuthorPage = ERSC.Page.make({
19
+ render: () =>
20
+ Effect.succeed(
21
+ <main>
22
+ <h1>Grace Hopper</h1>
23
+ <FollowAuthorButton authorId='grace-hopper' />
24
+ </main>,
25
+ ),
26
+ });
27
+
28
+ export default ERSC.make({
29
+ routes: ERSC.Routes.make({ layout: RootLayout }).page('/', AuthorPage),
30
+ });
@@ -0,0 +1,19 @@
1
+ ## Server Functions
2
+
3
+ `ERSC.ServerFn.make` decodes Schema input and runs an Effect handler with application services.
4
+
5
+ Callers pass the Schema's encoded type and the handler receives its decoded type. Use an ordinary
6
+ `Schema.Struct(...)` for object input. Use `Schema.fromFormData(...)` when a native form supplies the
7
+ input; a function returning `void` can then be passed directly to `<form action>`. Let the Schema
8
+ infer the handler parameter.
9
+
10
+ A successful invocation refreshes the current route.
11
+
12
+ <!-- source-navigation -->
13
+
14
+ ### Examples
15
+
16
+ - [Create the ERSC identity](./10_ersc.ts)
17
+ - [Define a Server Function](./20_follow-author.ts)
18
+ - [Use a direct form action](./30_follow-author-button.tsx)
19
+ - [Compose the application](./40_application.tsx)
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @title An application-owned service
3
+ *
4
+ * ERSC consumes the service contract and Layer; their construction is ordinary Effect code.
5
+ */
6
+ import { Context, Effect, Layer } from 'effect';
7
+
8
+ export type CatalogItem = {
9
+ readonly name: string;
10
+ readonly price: number;
11
+ };
12
+
13
+ export class Catalog extends Context.Service<Catalog>()('shop/services/Catalog', {
14
+ make: Effect.succeed({
15
+ featured: Effect.succeed([
16
+ { name: 'Mechanical keyboard', price: 129 },
17
+ { name: 'Studio headphones', price: 249 },
18
+ ] satisfies ReadonlyArray<CatalogItem>),
19
+ }),
20
+ }) {
21
+ static readonly layer = Layer.effect(this, this.make);
22
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @title Providing services at the composition boundary
3
+ *
4
+ * Declare the service union on ERSC and provide its Layer once at ERSC.make.
5
+ */
6
+ import { Effect } from 'effect';
7
+ import { Application } from 'effective-rsc';
8
+
9
+ import { Catalog } from './10_catalog';
10
+
11
+ const ERSC = Application.ersc<Catalog>();
12
+
13
+ const RootLayout = ERSC.Layout.make({
14
+ render: ({ children }) =>
15
+ Effect.succeed(
16
+ <html lang='en'>
17
+ <body>{children}</body>
18
+ </html>,
19
+ ),
20
+ });
21
+
22
+ const FeaturedPage = ERSC.Page.make({
23
+ render: Effect.fn('FeaturedPage.render')(function* () {
24
+ const catalog = yield* Catalog;
25
+ const items = yield* catalog.featured;
26
+ return (
27
+ <main>
28
+ <h1>Featured products</h1>
29
+ <ul>
30
+ {items.map((item) => (
31
+ <li key={item.name}>
32
+ {item.name}: ${item.price}
33
+ </li>
34
+ ))}
35
+ </ul>
36
+ </main>
37
+ );
38
+ }),
39
+ });
40
+
41
+ export default ERSC.make({
42
+ routes: ERSC.Routes.make({ layout: RootLayout }).page('/', FeaturedPage),
43
+ layer: Catalog.layer,
44
+ });
@@ -0,0 +1,17 @@
1
+ ## Services
2
+
3
+ Define services with Effect, then follow the ERSC composition convention:
4
+
5
+ 1. Declare the complete service union with `Application.ersc<Services>()`.
6
+ 2. Let Pages, Layouts, Components, and Server Functions require members of that union.
7
+ 3. Provide the complete Layer once with `ERSC.make({ layer })`.
8
+
9
+ This keeps implementations at the application composition boundary while preserving each
10
+ renderer's inferred service requirements.
11
+
12
+ <!-- source-navigation -->
13
+
14
+ ### Examples
15
+
16
+ - [Define a service](./10_catalog.ts)
17
+ - [Provide services](./20_application.tsx)
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @title Creating one routing authoring module
3
+ */
4
+ import { Application } from 'effective-rsc';
5
+
6
+ export const ERSC = Application.ersc();
@@ -0,0 +1,36 @@
1
+ /**
2
+ * @title Layout and Loading concerns
3
+ *
4
+ * Layout is Effectful; Loading is synchronous and service-free.
5
+ */
6
+ import { Effect } from 'effect';
7
+
8
+ import { ERSC } from './10_ersc';
9
+
10
+ export const RootLayout = ERSC.Layout.make({
11
+ render: ({ children }) =>
12
+ Effect.succeed(
13
+ <html lang='en'>
14
+ <body>
15
+ <nav>
16
+ <a href='/'>Home</a> · <a href='/articles/effectful-rsc'>Article</a>
17
+ </nav>
18
+ {children}
19
+ </body>
20
+ </html>,
21
+ ),
22
+ });
23
+
24
+ export const ArticleLayout = ERSC.Layout.make({
25
+ render: ({ children }) =>
26
+ Effect.succeed(
27
+ <section>
28
+ <h1>Articles</h1>
29
+ {children}
30
+ </section>,
31
+ ),
32
+ });
33
+
34
+ export const ArticleLoading = ERSC.Loading.make({
35
+ render: () => <p>Loading article…</p>,
36
+ });
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @title Static and parameterized Pages
3
+ *
4
+ * A Page Schema decodes captured path strings for render.
5
+ */
6
+ import { Effect, Schema } from 'effect';
7
+
8
+ import { ERSC } from './10_ersc';
9
+
10
+ export const HomePage = ERSC.Page.make({
11
+ render: () => Effect.succeed(<h1>Publishing home</h1>),
12
+ });
13
+
14
+ export const ArticlePage = ERSC.Page.make({
15
+ params: Schema.Struct({ slug: Schema.NonEmptyString }),
16
+ render: ({ params }) =>
17
+ Effect.succeed(
18
+ <article>
19
+ <h2>{params.slug}</h2>
20
+ <p>This article belongs to the mounted article route scope.</p>
21
+ </article>,
22
+ ),
23
+ });
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @title Composing and mounting Routes
3
+ *
4
+ * Mounting retains the child graph's Layout and Loading ancestry.
5
+ */
6
+ import { ERSC } from './10_ersc';
7
+ import { ArticleLayout, ArticleLoading, RootLayout } from './10_layouts';
8
+ import { ArticlePage, HomePage } from './20_pages';
9
+
10
+ const articleRoutes = ERSC.Routes.make({
11
+ layout: ArticleLayout,
12
+ loading: ArticleLoading,
13
+ }).page('/:slug', ArticlePage);
14
+
15
+ export const applicationRoutes = ERSC.Routes.make({ layout: RootLayout })
16
+ .page('/', HomePage)
17
+ .mount('/articles', articleRoutes);
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @title Closing the route graph
3
+ */
4
+ import { ERSC } from './10_ersc';
5
+ import { applicationRoutes } from './30_routes';
6
+
7
+ export default ERSC.make({ routes: applicationRoutes });
@@ -0,0 +1,22 @@
1
+ ## Routing, parameters, and loading
2
+
3
+ - Routes are immutable and belong to one ERSC identity.
4
+ - `page(path, page)` attaches a Page; `mount(prefix, routes)` nests a route scope.
5
+ - Mounted scopes retain their Layout and Loading ancestry.
6
+ - Page parameter Schemas decode Effect HTTP path captures before rendering.
7
+ - Effect HTTP owns route matching; ERSC rejects duplicate shapes and invalid composition while
8
+ building the graph.
9
+
10
+ <!-- source-navigation -->
11
+
12
+ ### Examples
13
+
14
+ - [Create the ERSC identity](./10_ersc.ts)
15
+ - [Define Layout and Loading concerns](./10_layouts.tsx)
16
+ - [Define Pages](./20_pages.tsx)
17
+ - [Compose and mount Routes](./30_routes.tsx)
18
+ - [Compose the application](./40_application.ts)
19
+
20
+ ### Related
21
+
22
+ - [Middleware](../04-middleware/index.md)
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @title Defining an authenticated view
3
+ *
4
+ * Middleware can short-circuit a request and provide typed services downstream.
5
+ */
6
+ import { Context, Effect } from 'effect';
7
+ import { HttpServerRequest, HttpServerResponse } from 'effect/unstable/http';
8
+ import { Application } from 'effective-rsc';
9
+
10
+ export class CurrentUser extends Context.Service<CurrentUser, { readonly name: string }>()(
11
+ 'docs/auth/CurrentUser',
12
+ ) {}
13
+
14
+ export const ERSC = Application.ersc();
15
+
16
+ const RequireCurrentUser = ERSC.Middleware.make<{ provides: CurrentUser }>(
17
+ Effect.fnUntraced(function* (httpEffect) {
18
+ const request = yield* HttpServerRequest.HttpServerRequest;
19
+ const name = request.cookies['user']?.trim();
20
+ if (name === undefined || name === '') {
21
+ return HttpServerResponse.text('Unauthorized', { status: 401 });
22
+ }
23
+ return yield* httpEffect.pipe(Effect.provideService(CurrentUser, { name }));
24
+ }),
25
+ );
26
+
27
+ export const AuthenticatedERSC = ERSC.withMiddleware(RequireCurrentUser);
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @title Consuming middleware data in a Page
3
+ */
4
+ import { Effect } from 'effect';
5
+
6
+ import { AuthenticatedERSC, CurrentUser } from './10_auth';
7
+
8
+ export const AccountPage = AuthenticatedERSC.Page.make({
9
+ render: Effect.fn('AccountPage.render')(function* () {
10
+ const user = yield* CurrentUser;
11
+ return <h1>Welcome, {user.name}</h1>;
12
+ }),
13
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @title Consuming middleware data in a Server Function
3
+ */
4
+ 'use server';
5
+
6
+ import { Effect, Schema } from 'effect';
7
+
8
+ import { AuthenticatedERSC, CurrentUser } from './10_auth';
9
+
10
+ export const updateDisplayName = AuthenticatedERSC.ServerFn.make({
11
+ input: Schema.Struct({ name: Schema.NonEmptyString }),
12
+ handler: Effect.fn('updateDisplayName')(function* ({ name }) {
13
+ const user = yield* CurrentUser;
14
+ yield* Effect.logInfo('Updating profile', { from: user.name, to: name });
15
+ }),
16
+ });
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @title Activating middleware with Routes
3
+ */
4
+ import { Effect } from 'effect';
5
+
6
+ import { ERSC, AuthenticatedERSC } from './10_auth';
7
+ import { AccountPage } from './20_account-page';
8
+
9
+ const RootLayout = ERSC.Layout.make({
10
+ render: ({ children }) =>
11
+ Effect.succeed(
12
+ <html lang='en'>
13
+ <body>{children}</body>
14
+ </html>,
15
+ ),
16
+ });
17
+
18
+ const routes = AuthenticatedERSC.Routes.make({ layout: RootLayout }).page('/account', AccountPage);
19
+
20
+ export default ERSC.make({ routes });
@@ -0,0 +1,25 @@
1
+ ## Middleware
2
+
3
+ Create middleware from the base ERSC view, then derive a view with
4
+ `ERSC.withMiddleware(middleware)`. The derived view has the same ERSC identity and retains the
5
+ middleware scope.
6
+
7
+ Routes and Server Functions created from the derived view activate that scope. Pages, Layouts, and
8
+ Components created from it may require the services declared by the middleware and consume them only
9
+ while rendered inside an active scope.
10
+
11
+ Use `ERSC.Middleware.make<{ provides: CurrentUser }>(handler)` when a middleware provides a
12
+ request-scoped service. The handler must provide that service to the downstream Effect. Chain
13
+ `withMiddleware` in request order; responses unwind in reverse.
14
+
15
+ Scoped middleware does not wrap userland HTTP, assets, or unmatched requests. Put server-wide policy
16
+ in native global Effect HTTP middleware supplied through the application Layer.
17
+
18
+ <!-- source-navigation -->
19
+
20
+ ### Examples
21
+
22
+ - [Define an authenticated view](./10_auth.ts)
23
+ - [Consume the service in a Page](./20_account-page.tsx)
24
+ - [Consume the service in a Server Function](./30_update-profile.ts)
25
+ - [Activate the scope with Routes](./40_application.tsx)
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @title Composing ERSC and userland HTTP
3
+ *
4
+ * ERSC concerns and native HTTP routes share one application Layer.
5
+ */
6
+ import { Effect, Layer } from 'effect';
7
+ import { HttpRouter, HttpServerResponse } from 'effect/unstable/http';
8
+ import { Application } from 'effective-rsc';
9
+
10
+ import { Catalog } from '../02-services/10_catalog';
11
+
12
+ const CatalogApi = HttpRouter.use(
13
+ Effect.fnUntraced(function* (router) {
14
+ const catalog = yield* Catalog;
15
+ const response = Effect.map(catalog.featured, HttpServerResponse.jsonUnsafe);
16
+ yield* router.add('GET', '/api/catalog', response);
17
+ }),
18
+ );
19
+
20
+ const GlobalHeaders = HttpRouter.middleware(
21
+ (httpEffect) =>
22
+ Effect.map(httpEffect, HttpServerResponse.setHeader('x-content-type-options', 'nosniff')),
23
+ { global: true },
24
+ );
25
+
26
+ const ApplicationLayer = Layer.mergeAll(CatalogApi, GlobalHeaders).pipe(
27
+ Layer.provideMerge(Catalog.layer),
28
+ );
29
+
30
+ const ERSC = Application.ersc<Catalog>();
31
+
32
+ const RootLayout = ERSC.Layout.make({
33
+ render: ({ children }) =>
34
+ Effect.succeed(
35
+ <html lang='en'>
36
+ <body>{children}</body>
37
+ </html>,
38
+ ),
39
+ });
40
+
41
+ const CatalogPage = ERSC.Page.make({
42
+ render: Effect.fn('CatalogPage.render')(function* () {
43
+ const catalog = yield* Catalog;
44
+ const items = yield* catalog.featured;
45
+ return (
46
+ <ul>
47
+ {items.map((item) => (
48
+ <li key={item.name}>
49
+ {item.name}: ${item.price}
50
+ </li>
51
+ ))}
52
+ </ul>
53
+ );
54
+ }),
55
+ });
56
+
57
+ export default ERSC.make({
58
+ routes: ERSC.Routes.make({ layout: RootLayout }).page('/catalog', CatalogPage),
59
+ layer: ApplicationLayer,
60
+ });
@@ -0,0 +1,23 @@
1
+ ## Userland HTTP
2
+
3
+ Register native Effect `HttpRouter`, `HttpApi`, or RPC layers in the Layer passed to
4
+ `ERSC.make({ layer })`. They share the framework's HTTP server, application services, and shutdown
5
+ scope.
6
+
7
+ Register routes that require application services with `HttpRouter.use`, then retain those services
8
+ with `Layer.provideMerge`.
9
+
10
+ Native global middleware belongs in the same application Layer. It observes Page requests, Server
11
+ Function requests, userland HTTP, assets, and unmatched requests. ERSC-scoped middleware has narrower
12
+ reach.
13
+
14
+ <!-- source-navigation -->
15
+
16
+ ### Examples
17
+
18
+ - [Compose ERSC and userland HTTP](./10_application-layer.tsx)
19
+
20
+ ### Related
21
+
22
+ - [Services](../02-services/index.md)
23
+ - [Middleware](../04-middleware/index.md)
@@ -0,0 +1,11 @@
1
+ ## Guides
2
+
3
+ Familiarity with React Server Components and Effect is assumed.
4
+
5
+ <!-- source-navigation -->
6
+
7
+ - [Server Functions](./01-server-functions/index.md)
8
+ - [Services](./02-services/index.md)
9
+ - [Routing](./03-routing/index.md)
10
+ - [Middleware](./04-middleware/index.md)
11
+ - [Userland HTTP](./05-http/index.md)
@@ -0,0 +1,13 @@
1
+ ## Request runtime and lifetimes
2
+
3
+ The server builds the Layer passed to `ERSC.make` once and releases it at shutdown. Its services have
4
+ application lifetime.
5
+
6
+ Each HTTP request has an independent Effect scope. Server Function handlers run in the HTTP request
7
+ fiber. Page, Layout, and Component render Effects run in a request-owned render scope.
8
+
9
+ Closing the response interrupts unfinished request work and runs its finalizers. Acquire
10
+ request-local resources inside the request Effect so their lifetime follows the request
11
+ automatically.
12
+
13
+ Give work that must outlive a request an explicit application-owned scope.
@@ -0,0 +1,61 @@
1
+ ## Client navigation
2
+
3
+ ERSC handles eligible document navigations through the browser Navigation API and
4
+ `NavigationPrecommitController`. There is no History API fallback. A browser missing either one
5
+ never hydrates: the streamed document stays as served and the application behaves as a plain
6
+ multi-page application, with document navigations and natively submitted forms but no interactive
7
+ Client Components.
8
+
9
+ An intercepted Page navigation has two milestones:
10
+
11
+ - **Native commit:** ERSC starts the Flight request in a React Transition, retains the common Layout
12
+ prefix, and publishes the destination in another Transition after the asynchronous load. The
13
+ precommit handler settles at the destination's first UI commit. The Navigation API can
14
+ then commit the URL and history entry, apply focus and default scroll, and finish any React View
15
+ Transition without waiting for Flight EOF.
16
+ - **Stream completion:** after native commit, the client router owns any remaining Flight stream
17
+ until EOF or until React confirms that another render retired it. A completed tree is cached for
18
+ the exact Navigation API history-entry id that committed for that navigation.
19
+
20
+ Canceling or superseding before commit interrupts the client transport and server request Effects.
21
+ A scheduled destination is discarded before its stream is released, so no rollback is needed. The
22
+ current UI and stream remain live while a successor prepares and retire only after React confirms a
23
+ different render. After native commit, Browser Stop no longer owns the stream; later Flight failures
24
+ use React's Error Boundary handling.
25
+
26
+ Back/Forward traversal reuses a completed cached payload. Push, replace, and uncached traversal
27
+ fetch fresh Flight. Disposing a history entry evicts its payload; a Server Function refresh clears
28
+ the traversal cache because a mutation may affect any route.
29
+
30
+ Flight redirects use the response's final URL; a non-success or non-Flight response becomes a
31
+ full-document navigation. Native focus and scroll remain enabled. Because Suspense content may
32
+ continue after native commit, history can remember an intermediate fallback's scroll position;
33
+ stream-aware restoration is not yet implemented.
34
+
35
+ ### React View Transitions
36
+
37
+ Applications own React `<ViewTransition>` boundaries and all animation CSS. ERSC does not wrap the
38
+ route tree or call `document.startViewTransition()`. It calls React's `addTransitionType()` inside
39
+ the same Transition that publishes an initial navigation or refresh render, so application
40
+ boundaries can select animation policy without delaying native navigation until Flight EOF.
41
+
42
+ The types are additive:
43
+
44
+ | Publication | Added types |
45
+ | ------------------------------------------------------ | -------------------------------------------------- |
46
+ | Every routed navigation | `navigation`, `navigation-${event.navigationType}` |
47
+ | Push navigation | `navigation-forward` |
48
+ | Backward traversal | `navigation-backward` |
49
+ | Forward traversal | `navigation-forward` |
50
+ | Navigation with `event.hasUAVisualTransition` | `navigation-ua-visual-transition` |
51
+ | Server Function response tree or current-route refresh | `server-function` |
52
+ | HMR current-route refresh | `hmr-refresh` |
53
+
54
+ `event.navigationType` is `push`, `replace`, or `traverse`. Replace has no direction type. A
55
+ traversal has no direction type when either history index is unavailable or the indices are equal.
56
+ Applications may suppress author animation for `navigation-ua-visual-transition` and `hmr-refresh`,
57
+ but ERSC does not impose that policy.
58
+
59
+ These types describe only the first publication. Suspense content that resolves later renders in a
60
+ separate, untyped React Transition. Applications should use their own Suspense-specific
61
+ `<ViewTransition>` boundaries and styling for those reveals.
@@ -0,0 +1,17 @@
1
+ ## Server Function execution and refresh
2
+
3
+ Hydrated invocations and progressively enhanced forms execute the same request-scoped Effect
4
+ handler. A hydrated response contains the Server Function result and a refreshed route tree; a
5
+ progressively enhanced response contains a complete document with the refreshed tree and form state.
6
+
7
+ For hydrated calls, the result Promise settles independently from the route refresh. ERSC commits
8
+ the refreshed tree in a React transition and keeps the request active through React commit and
9
+ Flight EOF. Disconnecting interrupts unfinished request work and the response stream.
10
+
11
+ Hydrated invocations may execute concurrently. Only the latest invocation may apply its response's
12
+ route tree while its original history entry remains current and no navigation is active. Other
13
+ responses trigger a fresh current-route refresh. A response tree interrupts any older current-route
14
+ refresh before rendering.
15
+
16
+ After a successful mutation, ERSC clears the Back/Forward traversal cache because any route may have
17
+ changed.
@@ -0,0 +1,11 @@
1
+ ## Advanced
2
+
3
+ These guides describe ERSC's runtime guarantees. See the
4
+ [current limitations](https://github.com/nikhilsnayak/effective-rsc/blob/main/docs/ARCHITECTURE.md#known-limitations)
5
+ before adopting them.
6
+
7
+ <!-- source-navigation -->
8
+
9
+ - [Request runtime and lifetimes](./01-request-runtime-and-lifetimes/index.md)
10
+ - [Client navigation](./02-client-navigation/index.md)
11
+ - [Server Function execution and refresh](./03-server-function-execution-and-refresh/index.md)
@@ -0,0 +1,8 @@
1
+ ## Application
2
+
3
+ `Application.ersc<Services>()` creates one application-scoped ERSC identity and its base authoring
4
+ view. `Services` is the complete server-service union; omit it for a service-free application.
5
+
6
+ `ERSC.make({ routes, layer })` closes the route graph and application runtime. Export its result from
7
+ `src/application.tsx`. `layer` is required unless `Services` is `never`; it may provide the declared
8
+ services and register native Effect HTTP on the framework router.