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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026-present Nikhil S
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/LLMS.md ADDED
@@ -0,0 +1,382 @@
1
+ <!-- Generated by scripts/generate-docs.ts. Do not edit directly. -->
2
+
3
+ # effective-rsc documentation
4
+
5
+ Use the React and Effect documentation for their underlying concepts. ERSC conventions:
6
+
7
+ - Only `src/application.tsx` has framework filename semantics.
8
+ - Create application values from one ERSC identity and its derived middleware views.
9
+ - Provide application services and export the result at `ERSC.make`.
10
+ - Import the package root only from the RSC graph.
11
+
12
+ ## Getting started
13
+
14
+ Create an application with compatible dependencies and Tailwind support:
15
+
16
+ ```sh
17
+ bunx create-ersc-app my-application
18
+ cd my-application
19
+ bun run dev
20
+ ```
21
+
22
+ Open `http://localhost:18193`.
23
+
24
+ In `src/application.tsx`, create one ERSC identity, define a root Layout and Page, compose Routes,
25
+ and export `ERSC.make(...)`.
26
+
27
+ For a production check, run `bun run check`, `bun run build`, and `bun run start`. Both
28
+ `ersc dev` and `ersc start` accept `--hostname` and `--port`; flags take precedence over
29
+ `HOST` and `PORT`. See the package README for requirements and manual installation.
30
+
31
+ Files in `public/` are served from `/` with `Cache-Control: public, max-age=0`.
32
+
33
+ ### A minimal application
34
+
35
+ Create values from one ERSC identity and close it with ERSC.make.
36
+
37
+ ```tsx
38
+ import { Effect } from 'effect';
39
+ import { Application } from 'effective-rsc';
40
+
41
+ const ERSC = Application.ersc();
42
+
43
+ const RootLayout = ERSC.Layout.make({
44
+ render: ({ children }) =>
45
+ Effect.succeed(
46
+ <html lang='en'>
47
+ <body>{children}</body>
48
+ </html>,
49
+ ),
50
+ });
51
+
52
+ const HomePage = ERSC.Page.make({
53
+ render: () => Effect.succeed(<h1>Hello from effective-rsc</h1>),
54
+ });
55
+
56
+ export default ERSC.make({
57
+ routes: ERSC.Routes.make({ layout: RootLayout }).page('/', HomePage),
58
+ });
59
+ ```
60
+
61
+ ### More examples
62
+
63
+ - **[Importing styles](./docs/01-getting-started/20_styling.tsx)**: ERSC has no magic stylesheet entry; import styles from their owning module.
64
+
65
+ ## Guides
66
+
67
+ Familiarity with React Server Components and Effect is assumed.
68
+
69
+ ## Server Functions
70
+
71
+ `ERSC.ServerFn.make` decodes Schema input and runs an Effect handler with application services.
72
+
73
+ Callers pass the Schema's encoded type and the handler receives its decoded type. Use an ordinary
74
+ `Schema.Struct(...)` for object input. Use `Schema.fromFormData(...)` when a native form supplies the
75
+ input; a function returning `void` can then be passed directly to `<form action>`. Let the Schema
76
+ infer the handler parameter.
77
+
78
+ A successful invocation refreshes the current route.
79
+
80
+ - **[Creating the Server Function authoring module](./docs/02-guides/01-server-functions/10_ersc.ts)**
81
+ - **[Defining a Server Function](./docs/02-guides/01-server-functions/20_follow-author.ts)**: ERSC decodes FormData before running the Effect handler.
82
+ - **[Rendering a direct form action](./docs/02-guides/01-server-functions/30_follow-author-button.tsx)**: A FormData Server Function can be passed directly to form action.
83
+ - **[Closing the Server Function application](./docs/02-guides/01-server-functions/40_application.tsx)**
84
+
85
+ ## Services
86
+
87
+ Define services with Effect, then follow the ERSC composition convention:
88
+
89
+ 1. Declare the complete service union with `Application.ersc<Services>()`.
90
+ 2. Let Pages, Layouts, Components, and Server Functions require members of that union.
91
+ 3. Provide the complete Layer once with `ERSC.make({ layer })`.
92
+
93
+ This keeps implementations at the application composition boundary while preserving each
94
+ renderer's inferred service requirements.
95
+
96
+ - **[An application-owned service](./docs/02-guides/02-services/10_catalog.ts)**: ERSC consumes the service contract and Layer; their construction is ordinary Effect code.
97
+ - **[Providing services at the composition boundary](./docs/02-guides/02-services/20_application.tsx)**: Declare the service union on ERSC and provide its Layer once at ERSC.make.
98
+
99
+ ## Routing, parameters, and loading
100
+
101
+ - Routes are immutable and belong to one ERSC identity.
102
+ - `page(path, page)` attaches a Page; `mount(prefix, routes)` nests a route scope.
103
+ - Mounted scopes retain their Layout and Loading ancestry.
104
+ - Page parameter Schemas decode Effect HTTP path captures before rendering.
105
+ - Effect HTTP owns route matching; ERSC rejects duplicate shapes and invalid composition while
106
+ building the graph.
107
+
108
+ - **[Creating one routing authoring module](./docs/02-guides/03-routing/10_ersc.ts)**
109
+ - **[Layout and Loading concerns](./docs/02-guides/03-routing/10_layouts.tsx)**: Layout is Effectful; Loading is synchronous and service-free.
110
+ - **[Static and parameterized Pages](./docs/02-guides/03-routing/20_pages.tsx)**: A Page Schema decodes captured path strings for render.
111
+ - **[Composing and mounting Routes](./docs/02-guides/03-routing/30_routes.tsx)**: Mounting retains the child graph's Layout and Loading ancestry.
112
+ - **[Closing the route graph](./docs/02-guides/03-routing/40_application.ts)**
113
+
114
+ ## Middleware
115
+
116
+ Create middleware from the base ERSC view, then derive a view with
117
+ `ERSC.withMiddleware(middleware)`. The derived view has the same ERSC identity and retains the
118
+ middleware scope.
119
+
120
+ Routes and Server Functions created from the derived view activate that scope. Pages, Layouts, and
121
+ Components created from it may require the services declared by the middleware and consume them only
122
+ while rendered inside an active scope.
123
+
124
+ Use `ERSC.Middleware.make<{ provides: CurrentUser }>(handler)` when a middleware provides a
125
+ request-scoped service. The handler must provide that service to the downstream Effect. Chain
126
+ `withMiddleware` in request order; responses unwind in reverse.
127
+
128
+ Scoped middleware does not wrap userland HTTP, assets, or unmatched requests. Put server-wide policy
129
+ in native global Effect HTTP middleware supplied through the application Layer.
130
+
131
+ - **[Defining an authenticated view](./docs/02-guides/04-middleware/10_auth.ts)**: Middleware can short-circuit a request and provide typed services downstream.
132
+ - **[Consuming middleware data in a Page](./docs/02-guides/04-middleware/20_account-page.tsx)**
133
+ - **[Consuming middleware data in a Server Function](./docs/02-guides/04-middleware/30_update-profile.ts)**
134
+ - **[Activating middleware with Routes](./docs/02-guides/04-middleware/40_application.tsx)**
135
+
136
+ ## Userland HTTP
137
+
138
+ Register native Effect `HttpRouter`, `HttpApi`, or RPC layers in the Layer passed to
139
+ `ERSC.make({ layer })`. They share the framework's HTTP server, application services, and shutdown
140
+ scope.
141
+
142
+ Register routes that require application services with `HttpRouter.use`, then retain those services
143
+ with `Layer.provideMerge`.
144
+
145
+ Native global middleware belongs in the same application Layer. It observes Page requests, Server
146
+ Function requests, userland HTTP, assets, and unmatched requests. ERSC-scoped middleware has narrower
147
+ reach.
148
+
149
+ - **[Composing ERSC and userland HTTP](./docs/02-guides/05-http/10_application-layer.tsx)**: ERSC concerns and native HTTP routes share one application Layer.
150
+
151
+ ## Advanced
152
+
153
+ These guides describe ERSC's runtime guarantees. See the
154
+ [current limitations](https://github.com/nikhilsnayak/effective-rsc/blob/main/docs/ARCHITECTURE.md#known-limitations)
155
+ before adopting them.
156
+
157
+ ## Request runtime and lifetimes
158
+
159
+ The server builds the Layer passed to `ERSC.make` once and releases it at shutdown. Its services have
160
+ application lifetime.
161
+
162
+ Each HTTP request has an independent Effect scope. Server Function handlers run in the HTTP request
163
+ fiber. Page, Layout, and Component render Effects run in a request-owned render scope.
164
+
165
+ Closing the response interrupts unfinished request work and runs its finalizers. Acquire
166
+ request-local resources inside the request Effect so their lifetime follows the request
167
+ automatically.
168
+
169
+ Give work that must outlive a request an explicit application-owned scope.
170
+
171
+ ## Client navigation
172
+
173
+ ERSC handles eligible document navigations through the browser Navigation API and
174
+ `NavigationPrecommitController`. There is no History API fallback. A browser missing either one
175
+ never hydrates: the streamed document stays as served and the application behaves as a plain
176
+ multi-page application, with document navigations and natively submitted forms but no interactive
177
+ Client Components.
178
+
179
+ An intercepted Page navigation has two milestones:
180
+
181
+ - **Native commit:** ERSC starts the Flight request in a React Transition, retains the common Layout
182
+ prefix, and publishes the destination in another Transition after the asynchronous load. The
183
+ precommit handler settles at the destination's first UI commit. The Navigation API can
184
+ then commit the URL and history entry, apply focus and default scroll, and finish any React View
185
+ Transition without waiting for Flight EOF.
186
+ - **Stream completion:** after native commit, the client router owns any remaining Flight stream
187
+ until EOF or until React confirms that another render retired it. A completed tree is cached for
188
+ the exact Navigation API history-entry id that committed for that navigation.
189
+
190
+ Canceling or superseding before commit interrupts the client transport and server request Effects.
191
+ A scheduled destination is discarded before its stream is released, so no rollback is needed. The
192
+ current UI and stream remain live while a successor prepares and retire only after React confirms a
193
+ different render. After native commit, Browser Stop no longer owns the stream; later Flight failures
194
+ use React's Error Boundary handling.
195
+
196
+ Back/Forward traversal reuses a completed cached payload. Push, replace, and uncached traversal
197
+ fetch fresh Flight. Disposing a history entry evicts its payload; a Server Function refresh clears
198
+ the traversal cache because a mutation may affect any route.
199
+
200
+ Flight redirects use the response's final URL; a non-success or non-Flight response becomes a
201
+ full-document navigation. Native focus and scroll remain enabled. Because Suspense content may
202
+ continue after native commit, history can remember an intermediate fallback's scroll position;
203
+ stream-aware restoration is not yet implemented.
204
+
205
+ ### React View Transitions
206
+
207
+ Applications own React `<ViewTransition>` boundaries and all animation CSS. ERSC does not wrap the
208
+ route tree or call `document.startViewTransition()`. It calls React's `addTransitionType()` inside
209
+ the same Transition that publishes an initial navigation or refresh render, so application
210
+ boundaries can select animation policy without delaying native navigation until Flight EOF.
211
+
212
+ The types are additive:
213
+
214
+ | Publication | Added types |
215
+ | ------------------------------------------------------ | -------------------------------------------------- |
216
+ | Every routed navigation | `navigation`, `navigation-${event.navigationType}` |
217
+ | Push navigation | `navigation-forward` |
218
+ | Backward traversal | `navigation-backward` |
219
+ | Forward traversal | `navigation-forward` |
220
+ | Navigation with `event.hasUAVisualTransition` | `navigation-ua-visual-transition` |
221
+ | Server Function response tree or current-route refresh | `server-function` |
222
+ | HMR current-route refresh | `hmr-refresh` |
223
+
224
+ `event.navigationType` is `push`, `replace`, or `traverse`. Replace has no direction type. A
225
+ traversal has no direction type when either history index is unavailable or the indices are equal.
226
+ Applications may suppress author animation for `navigation-ua-visual-transition` and `hmr-refresh`,
227
+ but ERSC does not impose that policy.
228
+
229
+ These types describe only the first publication. Suspense content that resolves later renders in a
230
+ separate, untyped React Transition. Applications should use their own Suspense-specific
231
+ `<ViewTransition>` boundaries and styling for those reveals.
232
+
233
+ ## Server Function execution and refresh
234
+
235
+ Hydrated invocations and progressively enhanced forms execute the same request-scoped Effect
236
+ handler. A hydrated response contains the Server Function result and a refreshed route tree; a
237
+ progressively enhanced response contains a complete document with the refreshed tree and form state.
238
+
239
+ For hydrated calls, the result Promise settles independently from the route refresh. ERSC commits
240
+ the refreshed tree in a React transition and keeps the request active through React commit and
241
+ Flight EOF. Disconnecting interrupts unfinished request work and the response stream.
242
+
243
+ Hydrated invocations may execute concurrently. Only the latest invocation may apply its response's
244
+ route tree while its original history entry remains current and no navigation is active. Other
245
+ responses trigger a fresh current-route refresh. A response tree interrupts any older current-route
246
+ refresh before rendering.
247
+
248
+ After a successful mutation, ERSC clears the Back/Forward traversal cache because any route may have
249
+ changed.
250
+
251
+ ## API reference
252
+
253
+ Under the `react-server` condition, the package root exports `Application`.
254
+ `Application.ersc<Services>()` returns `Component`, `Layout`, `Loading`, `Page`, `Middleware`,
255
+ `Routes`, `ServerFn`, `withMiddleware`, and `make`. Values from different ERSC identities cannot
256
+ be composed.
257
+
258
+ ## Application
259
+
260
+ `Application.ersc<Services>()` creates one application-scoped ERSC identity and its base authoring
261
+ view. `Services` is the complete server-service union; omit it for a service-free application.
262
+
263
+ `ERSC.make({ routes, layer })` closes the route graph and application runtime. Export its result from
264
+ `src/application.tsx`. `layer` is required unless `Services` is `never`; it may provide the declared
265
+ services and register native Effect HTTP on the framework router.
266
+
267
+ ## Page
268
+
269
+ - `ERSC.Page.make({ render })` creates a static route leaf.
270
+ - `ERSC.Page.make({ params, render })` creates a parameterized route leaf.
271
+
272
+ `render` returns an Effect whose requirements fit the ERSC service union. For parameterized Pages,
273
+ the Schema's encoded keys must exactly match the path parameters and accept strings. Compose the
274
+ Page with `Routes.page`.
275
+
276
+ Pages produce React output. Only an unmatched route receives a native `404`. The mapping from a
277
+ matched Page's parameter rejection to an expected HTTP outcome remains a
278
+ [known limitation](https://github.com/nikhilsnayak/effective-rsc/blob/main/docs/ARCHITECTURE.md#known-limitations).
279
+
280
+ ## Layout
281
+
282
+ `ERSC.Layout.make({ render })` creates an Effectful wrapper with one `children` outlet. It may require
283
+ ERSC application services. The root Layout owns the HTML document; nested Layouts own route scopes.
284
+
285
+ ## Loading
286
+
287
+ `ERSC.Loading.make({ render })` creates a Routes-scope fallback. `render` is synchronous and cannot
288
+ require services. A scope accepts at most one Loading value.
289
+
290
+ ## Component
291
+
292
+ `ERSC.Component.make({ render })` creates a non-route Effectful Server Component. Props are inferred
293
+ from `render`; requirements must fit the ERSC service union. Use it only in the RSC graph.
294
+
295
+ - **[An Effectful Server Component](./docs/04-api-reference/05-component/10_component.tsx)**: Component runs its render Effect in the current ERSC render scope.
296
+
297
+ ## Middleware
298
+
299
+ `ERSC.Middleware.make(handler)` adapts an Effect HTTP middleware to the current ERSC identity.
300
+ `ERSC.withMiddleware(middleware)` returns a derived authoring view of that same identity.
301
+
302
+ Use `ERSC.Middleware.make<{ provides: CurrentUser }>(handler)` when the handler provides a service to
303
+ the downstream Effect. Multiple services use a union. The derived view adds those services to the
304
+ requirements available to Page, Layout, Component, ServerFn, Routes, Middleware, and further derived
305
+ views.
306
+
307
+ Routes and ServerFn activate retained middleware. Page, Layout, and Component consume its services
308
+ only while React renders them inside an active scope. Rendering one outside its required scope is a
309
+ programmer error and throws `TypeError`.
310
+
311
+ Chain `withMiddleware` in request order. Ancestors run before descendants; response transformations
312
+ unwind in reverse. A middleware repeated in one resolved mounted route chain is rejected. Shared
313
+ middleware across mounted scopes runs once.
314
+
315
+ ## Reach
316
+
317
+ | Request | Route scope | Server Function scope | Native global middleware |
318
+ | -------------------------------- | ------------------------------------ | --------------------- | ------------------------ |
319
+ | Page GET/HEAD | Matched chain | No | Yes |
320
+ | Hydrated Server Function POST | Remaining route chain around refresh | Server Function chain | Yes |
321
+ | Progressive Server Function POST | No route refresh in the POST | Server Function chain | Yes |
322
+ | Userland HTTP, assets, unmatched | No | No | Yes |
323
+
324
+ During a hydrated Server Function request, middleware already active for the Server Function is not
325
+ executed again for the refreshed route, even if it appears at another position in that route chain.
326
+ Remaining route middleware wraps refreshed rendering.
327
+
328
+ Native global Effect HTTP middleware is separate. Register it through the application Layer for
329
+ server-wide policy.
330
+
331
+ ## Routes
332
+
333
+ `ERSC.Routes.make({ layout?, loading? })` creates an immutable route scope.
334
+
335
+ - `routes.page(path, page)` adds a Page at an absolute Effect HTTP pattern. Parameter Schema keys
336
+ must exactly match path parameters.
337
+ - `routes.mount(prefix, childRoutes)` mounts a non-empty graph of the same ERSC identity below an
338
+ absolute, parameter-free prefix.
339
+ - Mounted scopes retain their Layout, Loading, and middleware ancestry.
340
+
341
+ Both operations return new Routes values. Conflicting matcher shapes and `/_ersc/assets` are
342
+ rejected. Root Routes require a Layout and at least one Page.
343
+
344
+ Routes created from a derived authoring view activate its middleware.
345
+
346
+ ## ServerFn
347
+
348
+ `ERSC.ServerFn.make({ input, handler })` creates a native React Server Function reference. `input`
349
+ decodes the invocation payload and infers the handler parameter; do not annotate it. The handler
350
+ returns an Effect whose requirements fit the ERSC service universe. The client reference accepts the
351
+ Schema's encoded type and resolves `Promise<Output>`; the handler receives its decoded type.
352
+
353
+ ```ts
354
+ const followAuthor = ERSC.ServerFn.make({
355
+ input: Schema.Struct({ authorId: Schema.NonEmptyString }),
356
+ handler: ({ authorId }) => Effect.succeed({ authorId, following: true }),
357
+ });
358
+ ```
359
+
360
+ Schema transformations may use a different encoded type. To pass a Server Function directly to
361
+ `form.action`, decode `FormData` and return `void`:
362
+
363
+ ```tsx
364
+ const followAuthorForm = ERSC.ServerFn.make({
365
+ input: Schema.fromFormData(Schema.Struct({ authorId: Schema.NonEmptyString })),
366
+ handler: ({ authorId }) => Effect.logInfo('Followed author', { authorId }),
367
+ });
368
+
369
+ <form action={followAuthorForm}>
370
+ <input name='authorId' />
371
+ <button type='submit'>Follow</button>
372
+ </form>;
373
+ ```
374
+
375
+ A ServerFn created from a derived view activates its middleware for the POST. The Middleware
376
+ reference defines refresh reach and ordering.
377
+
378
+ Direct server invocation throws. Encode expected failure in a discriminated output union; unexpected
379
+ failures reject the Promise. Browser requests require an Origin matching the application host and
380
+ may contain at most 10 MiB. See the
381
+ [known limitations](https://github.com/nikhilsnayak/effective-rsc/blob/main/docs/ARCHITECTURE.md#known-limitations)
382
+ for the typed failure channel and progressive bound arguments.
package/README.md ADDED
@@ -0,0 +1,235 @@
1
+ <picture>
2
+ <source
3
+ media="(prefers-color-scheme: dark)"
4
+ srcset="https://raw.githubusercontent.com/nikhilsnayak/effective-rsc/main/packages/effective-rsc/logo-dark.svg"
5
+ />
6
+ <img
7
+ src="https://raw.githubusercontent.com/nikhilsnayak/effective-rsc/main/packages/effective-rsc/logo.svg"
8
+ alt=""
9
+ width="72"
10
+ height="72"
11
+ />
12
+ </picture>
13
+
14
+ # effective-rsc
15
+
16
+ **React owns the UI. Effect owns the runtime.**
17
+
18
+ An experimental, Effect-native React Server Components framework for Bun. Built on Rspack's
19
+ native RSC support.
20
+
21
+ > Experimental. Uses React Canary, Effect v4 RC, TypeScript 7, Rspack's RSC support, and modern
22
+ > browser APIs. [Current limitations](https://github.com/nikhilsnayak/effective-rsc/blob/main/docs/ARCHITECTURE.md#known-limitations).
23
+
24
+ ## Requirements
25
+
26
+ - Bun 1.4 or newer is the only supported server runtime.
27
+ - Client navigation and hydration require the Navigation API and `NavigationPrecommitController`;
28
+ there is no History API fallback. Browsers without them receive the server-rendered document as a
29
+ plain multi-page application, with working links and natively submitted forms.
30
+ - React, React DOM, Effect, Effect's browser and Bun platforms, and
31
+ `react-server-dom-rspack` must use the exact compatible versions shown below.
32
+
33
+ ## Create an application
34
+
35
+ ```sh
36
+ bunx create-ersc-app my-effective-rsc-app
37
+ cd my-effective-rsc-app
38
+ bun run dev
39
+ ```
40
+
41
+ Run `bunx create-ersc-app` without a directory for the interactive flow.
42
+
43
+ ## Why effective-rsc
44
+
45
+ effective-rsc is built around three deliberate constraints:
46
+
47
+ - **Effect owns the application runtime.** Pages, Layouts, Components, and Server Functions retain
48
+ inferred service requirements and run as request-scoped Effects. One application Layer provides
49
+ services and native Effect HTTP; Effect scopes own resources, interruption, and shutdown.
50
+ - **Routes and ownership are explicit.** One application-scoped ERSC identity composes an immutable
51
+ route graph in `src/application.tsx`. Concern identity, middleware reach, and service requirements
52
+ remain visible in that composition.
53
+ - **Navigation is browser-native.** ERSC intercepts the Navigation API and settles it at the
54
+ destination's first UI commit, so URL/history, focus, scroll, and React View Transitions are not
55
+ blocked by Flight EOF. The router retains the remaining stream until EOF or render retirement,
56
+ and tags publication with navigation, direction, UA visual-transition, Server Function, and HMR
57
+ transition types while applications own the `<ViewTransition>` boundaries and CSS.
58
+
59
+ ## Manual installation
60
+
61
+ Create a Bun package and install the framework with its exact compatible peers:
62
+
63
+ ```sh
64
+ mkdir my-effective-rsc-app
65
+ cd my-effective-rsc-app
66
+ bun init -y
67
+ bun add effective-rsc \
68
+ effect@4.0.0-rc.112 \
69
+ @effect/platform-browser@4.0.0-rc.112 \
70
+ @effect/platform-bun@4.0.0-rc.112 \
71
+ react@19.3.0-canary-8425b691-20260904 \
72
+ react-dom@19.3.0-canary-8425b691-20260904 \
73
+ react-server-dom-rspack@0.1.0
74
+ bun add --dev \
75
+ typescript@7.0.2 \
76
+ @types/bun@^1.4.0 \
77
+ @types/react@19.2.18 \
78
+ @types/react-dom@19.2.7
79
+ ```
80
+
81
+ Add the framework commands to `package.json`:
82
+
83
+ ```json
84
+ {
85
+ "type": "module",
86
+ "scripts": {
87
+ "dev": "ersc dev",
88
+ "check": "tsc --noEmit",
89
+ "build": "ersc build",
90
+ "start": "ersc start"
91
+ }
92
+ }
93
+ ```
94
+
95
+ Create `tsconfig.json`:
96
+
97
+ ```json
98
+ {
99
+ "compilerOptions": {
100
+ "target": "ESNext",
101
+ "module": "ESNext",
102
+ "moduleResolution": "Bundler",
103
+ "jsx": "react-jsx",
104
+ "noEmit": true,
105
+ "strict": true,
106
+ "erasableSyntaxOnly": true,
107
+ "exactOptionalPropertyTypes": true,
108
+ "noUncheckedIndexedAccess": true,
109
+ "noUncheckedSideEffectImports": true,
110
+ "types": ["bun", "react", "react-dom", "react/canary"],
111
+ "lib": ["ESNext", "DOM", "DOM.Iterable"]
112
+ },
113
+ "include": ["src"]
114
+ }
115
+ ```
116
+
117
+ Create `src/environment.d.ts` so TypeScript accepts stylesheet imports:
118
+
119
+ ```ts
120
+ declare module '*.css' {}
121
+ ```
122
+
123
+ ## Quick start
124
+
125
+ Create `src/application.tsx`:
126
+
127
+ ```tsx
128
+ import { Effect } from 'effect';
129
+ import { Application } from 'effective-rsc';
130
+
131
+ const ERSC = Application.ersc();
132
+
133
+ const RootLayout = ERSC.Layout.make({
134
+ render: ({ children }) =>
135
+ Effect.succeed(
136
+ <html lang='en'>
137
+ <body>{children}</body>
138
+ </html>,
139
+ ),
140
+ });
141
+
142
+ const HomePage = ERSC.Page.make({
143
+ render: () => Effect.succeed(<h1>Hello from effective-rsc</h1>),
144
+ });
145
+
146
+ export default ERSC.make({
147
+ routes: ERSC.Routes.make({ layout: RootLayout }).page('/', HomePage),
148
+ });
149
+ ```
150
+
151
+ Run `bun run dev`, then open `http://localhost:18193`. For a production run, use `bun run check`,
152
+ `bun run build`, and `bun run start`.
153
+
154
+ For deployment, `ersc start` accepts `--hostname` and `--port`. Command-line flags take precedence
155
+ over `HOST` and `PORT`; the defaults are `localhost` and `18193`.
156
+
157
+ ## Styling
158
+
159
+ Import stylesheets from the modules that use them; there is no framework stylesheet entry point:
160
+
161
+ ```tsx
162
+ import './styles.css';
163
+ ```
164
+
165
+ Tailwind uses the same CSS pipeline. Install it:
166
+
167
+ ```sh
168
+ bun add --dev tailwindcss@4.3.3
169
+ ```
170
+
171
+ Then create a stylesheet, such as `src/styles.css`:
172
+
173
+ ```css
174
+ @import 'tailwindcss';
175
+ ```
176
+
177
+ ## Authoring model
178
+
179
+ `Application.ersc<Services>()` creates one application-scoped ERSC identity and its base authoring
180
+ view:
181
+
182
+ - `Page` is an Effectful route leaf and may decode typed path parameters with Schema.
183
+ - `Layout` is an Effectful wrapper with one `children` outlet; the root Layout owns the document.
184
+ - `Loading` is a synchronous, service-free Suspense fallback.
185
+ - `Component` defines an Effectful Server Component that is not itself a route.
186
+ - `ServerFn` adds Effect and Schema to React's native Server Function protocol.
187
+ - `Middleware` adapts Effect HTTP middleware and may provide typed request services.
188
+ - `Routes` immutably composes Pages and nested Layout/Loading scopes and activates middleware
189
+ retained by its authoring view.
190
+
191
+ Create application values from one ERSC identity and its derived middleware views.
192
+ `ERSC.withMiddleware(middleware)` derives a view whose values retain that middleware scope. Declare
193
+ the complete service universe through `Services` and provide the application Layer at
194
+ `ERSC.make({ layer })`.
195
+
196
+ ## Runtime boundary
197
+
198
+ The package-root API is available only under the `react-server` condition. The framework build
199
+ enables that condition for application authoring modules; importing `effective-rsc` from another
200
+ runtime, including a Client Component, throws immediately.
201
+
202
+ ## Example and documentation
203
+
204
+ The [event platform](https://github.com/nikhilsnayak/effective-rsc/tree/main/examples/event-platform)
205
+ is the complete application example.
206
+
207
+ - [Getting started](https://github.com/nikhilsnayak/effective-rsc/blob/main/packages/effective-rsc/docs/01-getting-started/index.md)
208
+ - [Guides](https://github.com/nikhilsnayak/effective-rsc/blob/main/packages/effective-rsc/docs/02-guides/index.md)
209
+ - [Advanced](https://github.com/nikhilsnayak/effective-rsc/blob/main/packages/effective-rsc/docs/03-advanced/index.md)
210
+ - [API reference](https://github.com/nikhilsnayak/effective-rsc/blob/main/packages/effective-rsc/docs/04-api-reference/index.md)
211
+ - [Combined LLM reference](https://github.com/nikhilsnayak/effective-rsc/blob/main/packages/effective-rsc/LLMS.md)
212
+
213
+ ## Credits
214
+
215
+ ### Key dependencies
216
+
217
+ - [Rspack](https://github.com/web-infra-dev/rspack) and
218
+ [react-server-dom-rspack](https://www.npmjs.com/package/react-server-dom-rspack) provide native
219
+ RSC compilation and transport. Special thanks to [Cong-Cong Pan](https://github.com/SyMind) for
220
+ their work on Rspack's RSC implementation.
221
+
222
+ ### Prior art
223
+
224
+ - [rsc-html-stream](https://github.com/devongovett/rsc-html-stream) by
225
+ [Devon Govett](https://x.com/devongovett) established the compact streamed-HTML Flight embedding
226
+ shape adapted by ERSC's injector.
227
+ - [Next.js](https://github.com/vercel/next.js) is a reference for production RSC conventions and
228
+ protocol behavior.
229
+ - [Waku](https://github.com/wakujs/waku) by [Daishi Kato](https://x.com/dai_shi) and
230
+ [Twofold](https://github.com/twofold-rsc/twofold) by
231
+ [Ryan Toronto](https://x.com/ryantotweets) demonstrated compact RSC framework design.
232
+ - [rspack-rsc](https://github.com/rstackjs/rstack-examples/tree/main/rspack/rspack-rsc),
233
+ [rsbuild-plugin-rsc](https://github.com/rstackjs/rsbuild-plugin-rsc), and
234
+ [Vite RSC](https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-rsc) provide
235
+ reference implementations for RSC bundling and Server Function integration.
package/bin/ersc.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import '../dist/cli.js';
@@ -0,0 +1,15 @@
1
+ import { Effect } from 'effect';
2
+ import type { ReactNode } from 'react';
3
+ import { type ERSCIdentity, type ERSCMember } from './ersc-identity.js';
4
+ import type { AnyMiddleware } from './middleware.js';
5
+ export interface EffectComponent<Props, ApplicationServices> extends ERSCMember<ApplicationServices, 'Component'> {
6
+ (props: Props): Promise<Awaited<ReactNode>>;
7
+ }
8
+ type ComponentOptions<Props, Error, AvailableServices> = {
9
+ readonly render: (props: Props) => Effect.Effect<Awaited<ReactNode>, Error, AvailableServices>;
10
+ };
11
+ export type ComponentFactory<ApplicationServices, AvailableServices> = {
12
+ readonly make: <Props, Error>(options: ComponentOptions<Props, Error, AvailableServices>) => EffectComponent<Props, ApplicationServices>;
13
+ };
14
+ export declare const makeComponentFactory: <ApplicationServices, AvailableServices>(identity: ERSCIdentity<ApplicationServices>, middleware: ReadonlyArray<AnyMiddleware<ApplicationServices>>) => ComponentFactory<ApplicationServices, AvailableServices>;
15
+ export {};
@@ -0,0 +1,20 @@
1
+ import { Effect } from "effect";
2
+ import { attachERSCMember } from "./ersc-identity.js";
3
+
4
+
5
+
6
+
7
+
8
+ const makeComponentFactory = (identity, middleware)=>{
9
+ const make = ({ render })=>{
10
+ const EffectComponent = (props)=>identity.renderRuntime.run('Component', Effect.suspend(()=>render(props)), middleware);
11
+ return attachERSCMember(EffectComponent, identity, 'Component');
12
+ };
13
+ return {
14
+ make
15
+ };
16
+ };
17
+
18
+ export { makeComponentFactory };
19
+
20
+ //# sourceMappingURL=component.js.map