effective-rsc 0.1.0 → 0.1.2

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 (59) hide show
  1. package/LLMS.md +54 -10
  2. package/README.md +3 -3
  3. package/dist/application/page.d.ts +14 -7
  4. package/dist/application/page.js +5 -3
  5. package/dist/application/page.js.map +1 -1
  6. package/dist/application/route-graph.d.ts +1 -1
  7. package/dist/application/route-graph.js.map +1 -1
  8. package/dist/application/server-fn.d.ts +10 -6
  9. package/dist/application/server-fn.js +9 -4
  10. package/dist/application/server-fn.js.map +1 -1
  11. package/dist/build/dev.d.ts +11 -11
  12. package/dist/build/dev.js +21 -5
  13. package/dist/build/dev.js.map +1 -1
  14. package/dist/cli.js +2 -0
  15. package/dist/cli.js.map +1 -1
  16. package/dist/client/application.js +12 -9
  17. package/dist/client/application.js.map +1 -1
  18. package/dist/client/browser-capabilities.d.ts +2 -9
  19. package/dist/client/browser-capabilities.js +3 -14
  20. package/dist/client/browser-capabilities.js.map +1 -1
  21. package/dist/client/browser-render-status.d.ts +25 -0
  22. package/dist/client/browser-render-status.js +20 -0
  23. package/dist/client/browser-render-status.js.map +1 -0
  24. package/dist/client/call-server.js +7 -2
  25. package/dist/client/call-server.js.map +1 -1
  26. package/dist/client/navigation-api.js +5 -4
  27. package/dist/client/navigation-api.js.map +1 -1
  28. package/dist/client/react-dom-renderer.d.ts +3 -2
  29. package/dist/client/react-dom-renderer.js +43 -2
  30. package/dist/client/react-dom-renderer.js.map +1 -1
  31. package/dist/client/route-refresh.d.ts +1 -2
  32. package/dist/client/route-refresh.js +5 -18
  33. package/dist/client/route-refresh.js.map +1 -1
  34. package/dist/dev/client.d.ts +2 -1
  35. package/dist/dev/client.js +46 -6
  36. package/dist/dev/client.js.map +1 -1
  37. package/dist/dev/panel.d.ts +12 -1
  38. package/dist/dev/panel.js +73 -5
  39. package/dist/dev/panel.js.map +1 -1
  40. package/dist/dev/runtime-failure.d.ts +1 -0
  41. package/dist/dev/runtime-failure.js +7 -1
  42. package/dist/dev/runtime-failure.js.map +1 -1
  43. package/dist/rsc/render-route-tree.d.ts +3 -3
  44. package/dist/rsc/render-route-tree.js +2 -2
  45. package/dist/rsc/render-route-tree.js.map +1 -1
  46. package/dist/server/application.js +54 -35
  47. package/dist/server/application.js.map +1 -1
  48. package/dist/server/server-fn-request.js +3 -1
  49. package/dist/server/server-fn-request.js.map +1 -1
  50. package/docs/02-guides/01-server-functions/50_greet.ts +18 -0
  51. package/docs/02-guides/01-server-functions/60_greeting-form.tsx +23 -0
  52. package/docs/02-guides/01-server-functions/index.md +7 -0
  53. package/docs/02-guides/03-routing/index.md +5 -1
  54. package/docs/03-advanced/02-client-navigation/index.md +6 -3
  55. package/docs/03-advanced/03-server-function-execution-and-refresh/index.md +3 -1
  56. package/docs/04-api-reference/02-page/index.md +8 -3
  57. package/docs/04-api-reference/06-middleware/index.md +2 -2
  58. package/docs/04-api-reference/08-server-fn/index.md +23 -0
  59. package/package.json +1 -1
@@ -8,4 +8,5 @@ export type DevRuntimeFailure = {
8
8
  };
9
9
  export declare const fromBrowserError: (event: Pick<ErrorEvent, 'error' | 'message'>) => DevRuntimeFailure;
10
10
  export declare const fromUnhandledRejection: (event: Pick<PromiseRejectionEvent, 'reason'>) => DevRuntimeFailure;
11
+ export declare const fromRenderError: (error: unknown, componentStack: string | null) => DevRuntimeFailure;
11
12
  export declare const reportBrowserFailures: (report: (failure: DevRuntimeFailure) => Effect.Effect<void>) => Effect.Effect<void, never, never>;
@@ -21,12 +21,18 @@ const fromUnknown = (_tag, value, fallbackMessage)=>{
21
21
  };
22
22
  const fromBrowserError = (event)=>fromUnknown('RuntimeError', event.error === null ? undefined : event.error, event.message || 'Unknown browser error.');
23
23
  const fromUnhandledRejection = (event)=>fromUnknown('UnhandledRejection', event.reason, 'Unhandled promise rejection.');
24
+ const fromRenderError = (error, componentStack)=>({
25
+ ...fromUnknown('RuntimeError', error, 'Unknown React render error.'),
26
+ ...componentStack === null ? {} : {
27
+ componentStack
28
+ }
29
+ });
24
30
  const reportBrowserFailures = Effect.fnUntraced(function*(report) {
25
31
  const errors = Stream.fromEventListener(window, 'error').pipe(Stream.map(fromBrowserError));
26
32
  const rejections = Stream.fromEventListener(window, 'unhandledrejection').pipe(Stream.map(fromUnhandledRejection));
27
33
  yield* Stream.merge(errors, rejections).pipe(Stream.runForEach(report));
28
34
  });
29
35
 
30
- export { fromBrowserError, fromUnhandledRejection, reportBrowserFailures };
36
+ export { fromBrowserError, fromRenderError, fromUnhandledRejection, reportBrowserFailures };
31
37
 
32
38
  //# sourceMappingURL=runtime-failure.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"dev/runtime-failure.js","sources":["../../src/dev/runtime-failure.ts"],"sourcesContent":["import { Effect, Inspectable, Predicate, Stream } from 'effect';\n\nexport type DevRuntimeFailure = {\n readonly _tag: 'RuntimeError' | 'UnhandledRejection';\n readonly name: string;\n readonly message: string;\n readonly stack?: string;\n readonly componentStack?: string;\n};\n\nconst fromUnknown = (\n _tag: DevRuntimeFailure['_tag'],\n value: unknown,\n fallbackMessage: string,\n): DevRuntimeFailure => {\n if (Predicate.isError(value)) {\n return {\n _tag,\n name: value.name,\n message: value.message,\n ...(value.stack === undefined ? {} : { stack: value.stack }),\n };\n }\n\n return {\n _tag,\n name: _tag === 'UnhandledRejection' ? 'UnhandledRejection' : 'Error',\n message: value === undefined ? fallbackMessage : Inspectable.toStringUnknown(value),\n };\n};\n\nexport const fromBrowserError = (event: Pick<ErrorEvent, 'error' | 'message'>): DevRuntimeFailure =>\n fromUnknown(\n 'RuntimeError',\n event.error === null ? undefined : event.error,\n event.message || 'Unknown browser error.',\n );\n\nexport const fromUnhandledRejection = (\n event: Pick<PromiseRejectionEvent, 'reason'>,\n): DevRuntimeFailure =>\n fromUnknown('UnhandledRejection', event.reason, 'Unhandled promise rejection.');\n\nexport const reportBrowserFailures = Effect.fnUntraced(function* (\n report: (failure: DevRuntimeFailure) => Effect.Effect<void>,\n) {\n const errors = Stream.fromEventListener<ErrorEvent>(window, 'error').pipe(\n Stream.map(fromBrowserError),\n );\n const rejections = Stream.fromEventListener<PromiseRejectionEvent>(\n window,\n 'unhandledrejection',\n ).pipe(Stream.map(fromUnhandledRejection));\n\n yield* Stream.merge(errors, rejections).pipe(Stream.runForEach(report));\n});\n"],"names":["Effect","Inspectable","Predicate","Stream","fromUnknown","_tag","value","fallbackMessage","undefined","fromBrowserError","event","fromUnhandledRejection","reportBrowserFailures","report","errors","window","rejections"],"mappings":";;;AAAgE;AAUhE,MAAMI,WAAWA,GAAG,CAClBC,MACAC,OACAC;IAEA,IAAIL,iBAAiB,CAACI,QAAQ;QAC5B,OAAO;YACLD;YACA,MAAMC,MAAM,IAAI;YAChB,SAASA,MAAM,OAAO;YACtB,GAAIA,MAAM,KAAK,KAAKE,YAAY,CAAC,IAAI;gBAAE,OAAOF,MAAM,KAAK;YAAC,CAAC;QAC7D;IACF;IAEA,OAAO;QACLD;QACA,MAAMA,SAAS,uBAAuB,uBAAuB;QAC7D,SAASC,UAAUE,YAAYD,kBAAkBN,2BAA2B,CAACK;IAC/E;AACF;AAEO,MAAMG,gBAAgBA,GAAG,CAACC,QAC/BN,WAAWA,CACT,gBACAM,MAAM,KAAK,KAAK,OAAOF,YAAYE,MAAM,KAAK,EAC9CA,MAAM,OAAO,IAAI,0BACjB;AAEG,MAAMC,sBAAsBA,GAAG,CACpCD,QAEAN,WAAWA,CAAC,sBAAsBM,MAAM,MAAM,EAAE,gCAAgC;AAE3E,MAAME,qBAAqBA,GAAGZ,iBAAiB,CAAC,UACrDa,MAA2D;IAE3D,MAAMC,SAASX,wBAAwB,CAAaY,QAAQ,SAAS,IAAI,CACvEZ,UAAU,CAACM,gBAAgBA;IAE7B,MAAMO,aAAab,wBAAwB,CACzCY,QACA,sBACA,IAAI,CAACZ,UAAU,CAACQ,sBAAsBA;IAExC,OAAOR,YAAY,CAACW,QAAQE,YAAY,IAAI,CAACb,iBAAiB,CAACU;AACjE,GAAG"}
1
+ {"version":3,"file":"dev/runtime-failure.js","sources":["../../src/dev/runtime-failure.ts"],"sourcesContent":["import { Effect, Inspectable, Predicate, Stream } from 'effect';\n\nexport type DevRuntimeFailure = {\n readonly _tag: 'RuntimeError' | 'UnhandledRejection';\n readonly name: string;\n readonly message: string;\n readonly stack?: string;\n readonly componentStack?: string;\n};\n\nconst fromUnknown = (\n _tag: DevRuntimeFailure['_tag'],\n value: unknown,\n fallbackMessage: string,\n): DevRuntimeFailure => {\n if (Predicate.isError(value)) {\n return {\n _tag,\n name: value.name,\n message: value.message,\n ...(value.stack === undefined ? {} : { stack: value.stack }),\n };\n }\n\n return {\n _tag,\n name: _tag === 'UnhandledRejection' ? 'UnhandledRejection' : 'Error',\n message: value === undefined ? fallbackMessage : Inspectable.toStringUnknown(value),\n };\n};\n\nexport const fromBrowserError = (event: Pick<ErrorEvent, 'error' | 'message'>): DevRuntimeFailure =>\n fromUnknown(\n 'RuntimeError',\n event.error === null ? undefined : event.error,\n event.message || 'Unknown browser error.',\n );\n\nexport const fromUnhandledRejection = (\n event: Pick<PromiseRejectionEvent, 'reason'>,\n): DevRuntimeFailure =>\n fromUnknown('UnhandledRejection', event.reason, 'Unhandled promise rejection.');\n\nexport const fromRenderError = (\n error: unknown,\n componentStack: string | null,\n): DevRuntimeFailure => ({\n ...fromUnknown('RuntimeError', error, 'Unknown React render error.'),\n ...(componentStack === null ? {} : { componentStack }),\n});\n\nexport const reportBrowserFailures = Effect.fnUntraced(function* (\n report: (failure: DevRuntimeFailure) => Effect.Effect<void>,\n) {\n const errors = Stream.fromEventListener<ErrorEvent>(window, 'error').pipe(\n Stream.map(fromBrowserError),\n );\n const rejections = Stream.fromEventListener<PromiseRejectionEvent>(\n window,\n 'unhandledrejection',\n ).pipe(Stream.map(fromUnhandledRejection));\n\n yield* Stream.merge(errors, rejections).pipe(Stream.runForEach(report));\n});\n"],"names":["Effect","Inspectable","Predicate","Stream","fromUnknown","_tag","value","fallbackMessage","undefined","fromBrowserError","event","fromUnhandledRejection","fromRenderError","error","componentStack","reportBrowserFailures","report","errors","window","rejections"],"mappings":";;;AAAgE;AAUhE,MAAMI,WAAWA,GAAG,CAClBC,MACAC,OACAC;IAEA,IAAIL,iBAAiB,CAACI,QAAQ;QAC5B,OAAO;YACLD;YACA,MAAMC,MAAM,IAAI;YAChB,SAASA,MAAM,OAAO;YACtB,GAAIA,MAAM,KAAK,KAAKE,YAAY,CAAC,IAAI;gBAAE,OAAOF,MAAM,KAAK;YAAC,CAAC;QAC7D;IACF;IAEA,OAAO;QACLD;QACA,MAAMA,SAAS,uBAAuB,uBAAuB;QAC7D,SAASC,UAAUE,YAAYD,kBAAkBN,2BAA2B,CAACK;IAC/E;AACF;AAEO,MAAMG,gBAAgBA,GAAG,CAACC,QAC/BN,WAAWA,CACT,gBACAM,MAAM,KAAK,KAAK,OAAOF,YAAYE,MAAM,KAAK,EAC9CA,MAAM,OAAO,IAAI,0BACjB;AAEG,MAAMC,sBAAsBA,GAAG,CACpCD,QAEAN,WAAWA,CAAC,sBAAsBM,MAAM,MAAM,EAAE,gCAAgC;AAE3E,MAAME,eAAeA,GAAG,CAC7BC,OACAC,iBACuB;QACvB,GAAGV,WAAWA,CAAC,gBAAgBS,OAAO,8BAA8B;QACpE,GAAIC,mBAAmB,OAAO,CAAC,IAAI;YAAEA;QAAe,CAAC;IACvD,GAAG;AAEI,MAAMC,qBAAqBA,GAAGf,iBAAiB,CAAC,UACrDgB,MAA2D;IAE3D,MAAMC,SAASd,wBAAwB,CAAae,QAAQ,SAAS,IAAI,CACvEf,UAAU,CAACM,gBAAgBA;IAE7B,MAAMU,aAAahB,wBAAwB,CACzCe,QACA,sBACA,IAAI,CAACf,UAAU,CAACQ,sBAAsBA;IAExC,OAAOR,YAAY,CAACc,QAAQE,YAAY,IAAI,CAAChB,iBAAiB,CAACa;AACjE,GAAG"}
@@ -1,11 +1,11 @@
1
- import type { PagePathParams } from '../application/page.js';
1
+ import type { PageParams } from '../application/page.js';
2
2
  import type { CompiledDestination } from '../application/route-graph.js';
3
3
  import type { AbsolutePath } from '../application/route-path.js';
4
4
  import type { RouteTreeModel } from './route-tree.js';
5
5
  type RenderRouteTreeOptions<Services> = {
6
6
  readonly destination: CompiledDestination<Services>;
7
7
  readonly pathname: AbsolutePath;
8
- readonly pathParams: PagePathParams;
8
+ readonly params: PageParams;
9
9
  };
10
- export declare const renderRouteTree: <Services>({ destination, pathname, pathParams, }: RenderRouteTreeOptions<Services>) => RouteTreeModel;
10
+ export declare const renderRouteTree: <Services>({ destination, pathname, params, }: RenderRouteTreeOptions<Services>) => RouteTreeModel;
11
11
  export {};
@@ -8,12 +8,12 @@ import { RouteOutlet } from "../client/route-tree.js";
8
8
 
9
9
 
10
10
 
11
- const renderRouteTree = ({ destination, pathname, pathParams })=>{
11
+ const renderRouteTree = ({ destination, pathname, params })=>{
12
12
  const Page = destination.page.component;
13
13
  let tree = {
14
14
  child: null,
15
15
  content: /*#__PURE__*/ jsx(Page, {
16
- params: pathParams
16
+ params: params
17
17
  }),
18
18
  id: `page:${pathname}`
19
19
  };
@@ -1 +1 @@
1
- {"version":3,"file":"rsc/render-route-tree.js","sources":["../../src/rsc/render-route-tree.tsx"],"sourcesContent":["import { Suspense } from 'react';\n\nimport type { PagePathParams } from '../application/page';\nimport type { CompiledDestination } from '../application/route-graph';\nimport type { AbsolutePath } from '../application/route-path';\nimport { RouteOutlet } from '../client/route-tree';\nimport type { RouteTreeModel } from './route-tree';\n\ntype RenderRouteTreeOptions<Services> = {\n readonly destination: CompiledDestination<Services>;\n readonly pathname: AbsolutePath;\n readonly pathParams: PagePathParams;\n};\n\nexport const renderRouteTree = <Services,>({\n destination,\n pathname,\n pathParams,\n}: RenderRouteTreeOptions<Services>): RouteTreeModel => {\n const Page = destination.page.component;\n let tree: RouteTreeModel = {\n child: null,\n content: <Page params={pathParams} />,\n id: `page:${pathname}`,\n };\n\n for (let index = destination.scopes.length - 1; index >= 0; index--) {\n const scope = destination.scopes[index];\n if (scope === undefined) {\n continue;\n }\n\n if (scope.loading !== null) {\n const Loading = scope.loading;\n tree = {\n child: tree,\n content: (\n <Suspense fallback={<Loading />}>\n <RouteOutlet />\n </Suspense>\n ),\n id: `loading:${scope.id}:${pathname}`,\n };\n }\n\n if (scope.layout !== null) {\n const Layout = scope.layout;\n tree = {\n child: tree,\n content: (\n <Layout>\n <RouteOutlet />\n </Layout>\n ),\n id: `layout:${scope.id}`,\n };\n }\n }\n\n return tree;\n};\n"],"names":["Suspense","RouteOutlet","renderRouteTree","destination","pathname","pathParams","Page","tree","index","scope","undefined","Loading","Layout"],"mappings":";;;;;;;;AAAiC;AAKkB;AAS5C,MAAME,eAAeA,GAAG,CAAY,EACzCC,WAAW,EACXC,QAAQ,EACRC,UAAU,EACuB;IACjC,MAAMC,OAAOH,YAAY,IAAI,CAAC,SAAS;IACvC,IAAII,OAAuB;QACzB,OAAO;QACP,uBAAS,IAACD;YAAK,QAAQD;;QACvB,IAAI,CAAC,KAAK,EAAED,UAAU;IACxB;IAEA,IAAK,IAAII,QAAQL,YAAY,MAAM,CAAC,MAAM,GAAG,GAAGK,SAAS,GAAGA,QAAS;QACnE,MAAMC,QAAQN,YAAY,MAAM,CAACK,MAAM;QACvC,IAAIC,UAAUC,WAAW;YACvB;QACF;QAEA,IAAID,MAAM,OAAO,KAAK,MAAM;YAC1B,MAAME,UAAUF,MAAM,OAAO;YAC7BF,OAAO;gBACL,OAAOA;gBACP,uBACE,IAACP,QAAQA;oBAAC,wBAAU,IAACW;8BACnB,kBAACV,WAAWA;;gBAGhB,IAAI,CAAC,QAAQ,EAAEQ,MAAM,EAAE,CAAC,CAAC,EAAEL,UAAU;YACvC;QACF;QAEA,IAAIK,MAAM,MAAM,KAAK,MAAM;YACzB,MAAMG,SAASH,MAAM,MAAM;YAC3BF,OAAO;gBACL,OAAOA;gBACP,uBACE,IAACK;8BACC,kBAACX,WAAWA;;gBAGhB,IAAI,CAAC,OAAO,EAAEQ,MAAM,EAAE,EAAE;YAC1B;QACF;IACF;IAEA,OAAOF;AACT,EAAE"}
1
+ {"version":3,"file":"rsc/render-route-tree.js","sources":["../../src/rsc/render-route-tree.tsx"],"sourcesContent":["import { Suspense } from 'react';\n\nimport type { PageParams } from '../application/page';\nimport type { CompiledDestination } from '../application/route-graph';\nimport type { AbsolutePath } from '../application/route-path';\nimport { RouteOutlet } from '../client/route-tree';\nimport type { RouteTreeModel } from './route-tree';\n\ntype RenderRouteTreeOptions<Services> = {\n readonly destination: CompiledDestination<Services>;\n readonly pathname: AbsolutePath;\n readonly params: PageParams;\n};\n\nexport const renderRouteTree = <Services,>({\n destination,\n pathname,\n params,\n}: RenderRouteTreeOptions<Services>): RouteTreeModel => {\n const Page = destination.page.component;\n let tree: RouteTreeModel = {\n child: null,\n content: <Page params={params} />,\n id: `page:${pathname}`,\n };\n\n for (let index = destination.scopes.length - 1; index >= 0; index--) {\n const scope = destination.scopes[index];\n if (scope === undefined) {\n continue;\n }\n\n if (scope.loading !== null) {\n const Loading = scope.loading;\n tree = {\n child: tree,\n content: (\n <Suspense fallback={<Loading />}>\n <RouteOutlet />\n </Suspense>\n ),\n id: `loading:${scope.id}:${pathname}`,\n };\n }\n\n if (scope.layout !== null) {\n const Layout = scope.layout;\n tree = {\n child: tree,\n content: (\n <Layout>\n <RouteOutlet />\n </Layout>\n ),\n id: `layout:${scope.id}`,\n };\n }\n }\n\n return tree;\n};\n"],"names":["Suspense","RouteOutlet","renderRouteTree","destination","pathname","params","Page","tree","index","scope","undefined","Loading","Layout"],"mappings":";;;;;;;;AAAiC;AAKkB;AAS5C,MAAME,eAAeA,GAAG,CAAY,EACzCC,WAAW,EACXC,QAAQ,EACRC,MAAM,EAC2B;IACjC,MAAMC,OAAOH,YAAY,IAAI,CAAC,SAAS;IACvC,IAAII,OAAuB;QACzB,OAAO;QACP,uBAAS,IAACD;YAAK,QAAQD;;QACvB,IAAI,CAAC,KAAK,EAAED,UAAU;IACxB;IAEA,IAAK,IAAII,QAAQL,YAAY,MAAM,CAAC,MAAM,GAAG,GAAGK,SAAS,GAAGA,QAAS;QACnE,MAAMC,QAAQN,YAAY,MAAM,CAACK,MAAM;QACvC,IAAIC,UAAUC,WAAW;YACvB;QACF;QAEA,IAAID,MAAM,OAAO,KAAK,MAAM;YAC1B,MAAME,UAAUF,MAAM,OAAO;YAC7BF,OAAO;gBACL,OAAOA;gBACP,uBACE,IAACP,QAAQA;oBAAC,wBAAU,IAACW;8BACnB,kBAACV,WAAWA;;gBAGhB,IAAI,CAAC,QAAQ,EAAEQ,MAAM,EAAE,CAAC,CAAC,EAAEL,UAAU;YACvC;QACF;QAEA,IAAIK,MAAM,MAAM,KAAK,MAAM;YACzB,MAAMG,SAASH,MAAM,MAAM;YAC3BF,OAAO;gBACL,OAAOA;gBACP,uBACE,IAACK;8BACC,kBAACX,WAAWA;;gBAGhB,IAAI,CAAC,OAAO,EAAEQ,MAAM,EAAE,EAAE;YAC1B;QACF;IACF;IAEA,OAAOF;AACT,EAAE"}
@@ -1,4 +1,4 @@
1
- import { Effect, Layer, Option, Stream } from "effect";
1
+ import { Effect, Layer, Option, Schema, Stream } from "effect";
2
2
  import { HttpEffect, HttpRouter, HttpServerRequest, HttpServerResponse, HttpStaticServer } from "effect/unstable/http";
3
3
  import { getApplicationState } from "../application/definition.js";
4
4
  import { getERSCIdentity } from "../application/ersc-identity.js";
@@ -60,7 +60,7 @@ const BunServerLayer = Layer.unwrap(Effect.map(ServerConfig, ({ hostname, port }
60
60
  maxRequestBodySize: ApplicationMaxRequestBodySizeBytes,
61
61
  port
62
62
  })));
63
- const EmptyPathParams = Object.freeze({});
63
+ const EmptyEncodedPageParams = Object.freeze({});
64
64
  const DynamicResponseHeaders = {
65
65
  'cache-control': 'private, no-store'
66
66
  };
@@ -86,42 +86,61 @@ const httpLayer = (application)=>{
86
86
  if (Option.isNone(requestUrl) || !isAbsolutePath(requestUrl.value.pathname)) {
87
87
  return yield* Effect.die(new TypeError(`Expected request URL "${request.originalUrl}" to contain an absolute path.`));
88
88
  }
89
- const pathParams = yield* destination.page.paramsSchema === null ? Effect.succeed(EmptyPathParams) : HttpRouter.params;
90
- const routeTree = renderRouteTree({
91
- destination,
92
- pathParams,
93
- pathname: requestUrl.value.pathname
94
- });
95
- const flightRenderer = yield* FlightRenderer;
96
- const flight = yield* flightRenderer.render({
97
- formState,
98
- middleware,
99
- renderRuntime: identity.renderRuntime,
100
- routeTree,
101
- serverFnResult,
102
- temporaryReferences
103
- });
104
- if (request.headers['accept'] === FlightMediaType) {
105
- return HttpServerResponse.stream(fromWebStream(flight.stream, {
106
- releaseLockOnEnd: true
107
- }).pipe(Stream.ensuring(flight.release)), {
108
- contentType: `${FlightMediaType};charset=utf-8`,
109
- headers: {
110
- ...DynamicResponseHeaders,
111
- 'content-location': requestUrl.value.href
112
- },
89
+ const pathname = requestUrl.value.pathname;
90
+ const encodedParams = yield* destination.page.paramsSchema === null ? Effect.succeed(EmptyEncodedPageParams) : HttpRouter.params;
91
+ const renderResponse = Effect.fnUntraced(function*(params) {
92
+ const routeTree = renderRouteTree({
93
+ destination,
94
+ params,
95
+ pathname
96
+ });
97
+ const flightRenderer = yield* FlightRenderer;
98
+ const flight = yield* flightRenderer.render({
99
+ formState,
100
+ middleware,
101
+ renderRuntime: identity.renderRuntime,
102
+ routeTree,
103
+ serverFnResult,
104
+ temporaryReferences
105
+ });
106
+ if (request.headers['accept'] === FlightMediaType) {
107
+ return HttpServerResponse.stream(fromWebStream(flight.stream, {
108
+ releaseLockOnEnd: true
109
+ }).pipe(Stream.ensuring(flight.release)), {
110
+ contentType: `${FlightMediaType};charset=utf-8`,
111
+ headers: {
112
+ ...DynamicResponseHeaders,
113
+ 'content-location': requestUrl.value.href
114
+ },
115
+ status
116
+ });
117
+ }
118
+ const htmlRenderer = yield* HtmlRenderer;
119
+ const htmlStream = yield* htmlRenderer.render({
120
+ flight,
121
+ formState
122
+ }).pipe(Effect.onError(()=>flight.release));
123
+ return HttpServerResponse.stream(fromWebStream(htmlStream).pipe(Stream.ensuring(flight.release)), {
124
+ contentType: 'text/html;charset=utf-8',
125
+ headers: DynamicResponseHeaders,
113
126
  status
114
127
  });
128
+ });
129
+ if (request.method !== 'POST' && destination.page.paramsSchema !== null) {
130
+ return yield* Schema.decodeEffect(destination.page.paramsSchema)(encodedParams).pipe(Effect.matchEffect({
131
+ onFailure: ()=>Effect.succeed(HttpServerResponse.empty({
132
+ status: 404,
133
+ headers: DynamicResponseHeaders
134
+ })),
135
+ onSuccess: (value)=>renderResponse({
136
+ _tag: 'Decoded',
137
+ value
138
+ })
139
+ }));
115
140
  }
116
- const htmlRenderer = yield* HtmlRenderer;
117
- const htmlStream = yield* htmlRenderer.render({
118
- flight,
119
- formState
120
- }).pipe(Effect.onError(()=>flight.release));
121
- return HttpServerResponse.stream(fromWebStream(htmlStream).pipe(Stream.ensuring(flight.release)), {
122
- contentType: 'text/html;charset=utf-8',
123
- headers: DynamicResponseHeaders,
124
- status
141
+ return yield* renderResponse({
142
+ _tag: 'Encoded',
143
+ value: encodedParams
125
144
  });
126
145
  });
127
146
  const executeServerFnAndRefresh = (prepared, destination, request)=>{
@@ -1 +1 @@
1
- {"version":3,"file":"server/application.js","sources":["../../src/server/application.ts"],"sourcesContent":["import * as BunHttpServer from '@effect/platform-bun/BunHttpServer';\nimport { Effect, Layer, Option, Stream, type Types } from 'effect';\nimport type { PlatformError } from 'effect/PlatformError';\nimport {\n HttpEffect,\n HttpRouter,\n HttpServerRequest,\n HttpServerResponse,\n HttpStaticServer,\n} from 'effect/unstable/http';\n\nimport { type ApplicationDefinition, getApplicationState } from '../application/definition';\nimport { getERSCIdentity } from '../application/ersc-identity';\nimport {\n applyMiddleware,\n type AnyMiddleware,\n getScopedHttpMiddleware,\n} from '../application/middleware';\nimport type { PagePathParams } from '../application/page';\nimport type { CompiledDestination } from '../application/route-graph';\nimport { FrameworkAssetNamespace, isAbsolutePath } from '../application/route-path';\nimport { FlightMediaType } from '../rsc/flight';\nimport { renderRouteTree } from '../rsc/render-route-tree';\nimport { FlightHtmlInjector } from './flight-html-stream';\nimport { FlightRenderer } from './flight-renderer';\nimport { HtmlRenderError, HtmlRenderer } from './html-renderer';\nimport type { RequestOutcome } from './request-outcome';\nimport {\n ApplicationIdleTimeoutSeconds,\n ApplicationMaxRequestBodySizeBytes,\n ServerConfig,\n} from './server-config';\nimport {\n prepareServerFnRequest,\n type PreparedServerFnRequest,\n type ServerFnRequestFailure,\n} from './server-fn-request';\n\nconst RenderersLayer = Layer.mergeAll(FlightRenderer.layer, HtmlRenderer.layer).pipe(\n Layer.provide(FlightHtmlInjector.layer),\n);\n\nconst StaticAssetsLayer = Layer.unwrap(\n Effect.map(ServerConfig, ({ clientAssetsCacheControl, clientAssetsRoot }) =>\n HttpStaticServer.layer({\n cacheControl: clientAssetsCacheControl,\n prefix: FrameworkAssetNamespace,\n root: clientAssetsRoot,\n }).pipe(Layer.provide(HttpRouter.disableLogger)),\n ),\n);\n\nconst PublicAssetsLayer = Layer.unwrap(\n Effect.map(ServerConfig, ({ publicAssetsRoot }) =>\n HttpStaticServer.layer({\n cacheControl: 'public, max-age=0',\n root: publicAssetsRoot,\n }),\n ),\n);\n\n// Bun derives `development` from NODE_ENV, which `ersc start` does not set, and its error page\n// carries the failure message and source stack.\nconst BunServerLayer = Layer.unwrap(\n Effect.map(ServerConfig, ({ hostname, port }) =>\n BunHttpServer.layer({\n development: false,\n hostname,\n idleTimeout: ApplicationIdleTimeoutSeconds,\n maxRequestBodySize: ApplicationMaxRequestBodySizeBytes,\n port,\n }),\n ),\n);\n\nconst EmptyPathParams: PagePathParams = Object.freeze({});\nconst DynamicResponseHeaders = {\n 'cache-control': 'private, no-store',\n} as const;\n\nconst appendAcceptVary = (vary: string | undefined) => {\n if (vary === undefined || vary.trim() === '') {\n return 'Accept';\n }\n\n const fields = vary.split(',').map((field) => field.trim().toLowerCase());\n return fields.includes('*') || fields.includes('accept') ? vary : `${vary}, Accept`;\n};\n\nconst acceptVaryPreResponseHandler: HttpEffect.PreResponseHandler = (_request, response) =>\n Effect.succeed(\n HttpServerResponse.setHeader(response, 'vary', appendAcceptVary(response.headers['vary'])),\n );\n\ntype RenderOptions<Services> = RequestOutcome & {\n readonly destination: CompiledDestination<Services>;\n readonly middleware: ReadonlyArray<AnyMiddleware<Services>>;\n readonly request: HttpServerRequest.HttpServerRequest;\n};\n\nconst fromWebStream = (\n stream: ReadableStream<Uint8Array>,\n options?: { readonly releaseLockOnEnd?: boolean },\n) =>\n Stream.fromReadableStream({\n evaluate: () => stream,\n onError: (cause) => cause,\n releaseLockOnEnd: options?.releaseLockOnEnd,\n });\n\nconst combinePageMiddleware = <Services>(\n middleware: ReadonlyArray<AnyMiddleware<Services>>,\n last: AnyMiddleware<Services>,\n) =>\n middleware\n .slice(0, -1)\n .reduceRight(\n (combined, current) => combined.combine(getScopedHttpMiddleware(current)),\n getScopedHttpMiddleware(last),\n );\n\ntype HttpApplicationRequirements =\n | ServerConfig\n | Layer.Services<ReturnType<typeof HttpStaticServer.layer>>\n | HttpRouter.Request.From<'Error', HtmlRenderError | ServerFnRequestFailure>;\n\nexport type HttpApplicationLayer<ApplicationError> = Layer.Layer<\n never,\n ApplicationError | PlatformError,\n HttpApplicationRequirements\n>;\n\nexport type ServerApplicationLayer<ApplicationError> = Layer.Layer<\n never,\n ApplicationError | PlatformError,\n ServerConfig\n>;\n\nconst httpLayer = <Services, ApplicationError>(\n application: ApplicationDefinition<Services, ApplicationError>,\n): HttpApplicationLayer<ApplicationError> => {\n const applicationState = getApplicationState(application);\n const identity = getERSCIdentity(application);\n const render = Effect.fnUntraced(function* ({\n destination,\n formState,\n middleware,\n request,\n serverFnResult,\n status,\n temporaryReferences,\n }: RenderOptions<Services>) {\n const requestUrl = HttpServerRequest.toURL(request);\n if (Option.isNone(requestUrl) || !isAbsolutePath(requestUrl.value.pathname)) {\n return yield* Effect.die(\n new TypeError(`Expected request URL \"${request.originalUrl}\" to contain an absolute path.`),\n );\n }\n\n const pathParams = yield* destination.page.paramsSchema === null\n ? Effect.succeed(EmptyPathParams)\n : HttpRouter.params;\n const routeTree = renderRouteTree({\n destination,\n pathParams,\n pathname: requestUrl.value.pathname,\n });\n const flightRenderer = yield* FlightRenderer;\n const flight = yield* flightRenderer.render({\n formState,\n middleware,\n renderRuntime: identity.renderRuntime,\n routeTree,\n serverFnResult,\n temporaryReferences,\n });\n\n if (request.headers['accept'] === FlightMediaType) {\n return HttpServerResponse.stream(\n fromWebStream(flight.stream, { releaseLockOnEnd: true }).pipe(\n Stream.ensuring(flight.release),\n ),\n {\n contentType: `${FlightMediaType};charset=utf-8`,\n headers: {\n ...DynamicResponseHeaders,\n 'content-location': requestUrl.value.href,\n },\n status,\n },\n );\n }\n\n const htmlRenderer = yield* HtmlRenderer;\n const htmlStream = yield* htmlRenderer\n .render({ flight, formState })\n .pipe(Effect.onError(() => flight.release));\n\n return HttpServerResponse.stream(\n fromWebStream(htmlStream).pipe(Stream.ensuring(flight.release)),\n {\n contentType: 'text/html;charset=utf-8',\n headers: DynamicResponseHeaders,\n status,\n },\n );\n });\n\n const executeServerFnAndRefresh = (\n prepared: PreparedServerFnRequest<Services>,\n destination: CompiledDestination<Services>,\n request: HttpServerRequest.HttpServerRequest,\n ) => {\n const refreshMiddleware = destination.middleware.filter(\n (middleware) => !prepared.middleware.includes(middleware),\n );\n const renderMiddleware =\n refreshMiddleware.length === 0\n ? prepared.middleware\n : Object.freeze([...prepared.middleware, ...refreshMiddleware]);\n const response = prepared.execute.pipe(\n Effect.flatMap((outcome) => {\n const refresh = render({\n ...outcome,\n destination,\n middleware: renderMiddleware,\n request,\n });\n return applyMiddleware(refreshMiddleware, refresh);\n }),\n );\n\n return applyMiddleware(prepared.middleware, response);\n };\n\n const RequestLayer = Layer.mergeAll(RenderersLayer, applicationState.layer);\n const ApplicationRoutesLayer = Layer.unwrap(\n Effect.map(Effect.context<Services | FlightRenderer | HtmlRenderer>(), (requestContext) => {\n const RequestContextMiddleware = HttpRouter.middleware<{\n provides: Services | FlightRenderer | HtmlRenderer;\n }>()((httpEffect): Effect.Effect<HttpServerResponse.HttpServerResponse, Types.unhandled> =>\n httpEffect.pipe(Effect.provideContext(requestContext)),\n );\n const makeRouteLayer = (destination: CompiledDestination<Services>) => {\n const GetLayer = HttpRouter.add('GET', destination.pattern, (request) =>\n render({\n destination,\n formState: null,\n middleware: destination.middleware,\n request,\n serverFnResult: null,\n status: 200,\n }).pipe(HttpEffect.withPreResponseHandler(acceptVaryPreResponseHandler)),\n );\n const lastPageMiddleware = destination.middleware.at(-1);\n const PageMiddleware =\n lastPageMiddleware === undefined\n ? RequestContextMiddleware\n : combinePageMiddleware(destination.middleware, lastPageMiddleware).combine(\n RequestContextMiddleware,\n );\n const PageLayer = GetLayer.pipe(Layer.provide(PageMiddleware.layer));\n const ServerFnLayer = HttpRouter.add('POST', destination.pattern, (request) =>\n prepareServerFnRequest(request, identity).pipe(\n Effect.flatMap((prepared) => executeServerFnAndRefresh(prepared, destination, request)),\n Effect.catchTag('ServerFnRequestError', (error) =>\n Effect.succeed(\n HttpServerResponse.text(error.message, {\n headers: DynamicResponseHeaders,\n status: error.status,\n }),\n ),\n ),\n HttpEffect.withPreResponseHandler(acceptVaryPreResponseHandler),\n ),\n ).pipe(Layer.provide(RequestContextMiddleware.layer));\n\n return Layer.mergeAll(PageLayer, ServerFnLayer);\n };\n const [firstDestination, ...remainingDestinations] = applicationState.routes;\n return Layer.mergeAll(\n makeRouteLayer(firstDestination),\n ...remainingDestinations.map(makeRouteLayer),\n );\n }),\n ).pipe(Layer.provide(RequestLayer));\n\n return Layer.mergeAll(StaticAssetsLayer, PublicAssetsLayer).pipe(\n Layer.provideMerge(ApplicationRoutesLayer),\n );\n};\n\nconst serverLayer = <Services, ApplicationError>(\n application: ApplicationDefinition<Services, ApplicationError>,\n): ServerApplicationLayer<ApplicationError> =>\n HttpRouter.serve(httpLayer(application)).pipe(Layer.provide(BunServerLayer));\n\nexport const ServerApplication = { httpLayer, serverLayer };\n"],"names":["BunHttpServer","Effect","Layer","Option","Stream","HttpEffect","HttpRouter","HttpServerRequest","HttpServerResponse","HttpStaticServer","getApplicationState","getERSCIdentity","applyMiddleware","getScopedHttpMiddleware","FrameworkAssetNamespace","isAbsolutePath","FlightMediaType","renderRouteTree","FlightHtmlInjector","FlightRenderer","HtmlRenderer","ApplicationIdleTimeoutSeconds","ApplicationMaxRequestBodySizeBytes","ServerConfig","prepareServerFnRequest","RenderersLayer","StaticAssetsLayer","clientAssetsCacheControl","clientAssetsRoot","PublicAssetsLayer","publicAssetsRoot","BunServerLayer","hostname","port","EmptyPathParams","Object","DynamicResponseHeaders","appendAcceptVary","vary","undefined","fields","field","acceptVaryPreResponseHandler","_request","response","fromWebStream","stream","options","cause","combinePageMiddleware","middleware","last","combined","current","httpLayer","application","applicationState","identity","render","destination","formState","request","serverFnResult","status","temporaryReferences","requestUrl","TypeError","pathParams","routeTree","flightRenderer","flight","htmlRenderer","htmlStream","executeServerFnAndRefresh","prepared","refreshMiddleware","renderMiddleware","outcome","refresh","RequestLayer","ApplicationRoutesLayer","requestContext","RequestContextMiddleware","httpEffect","makeRouteLayer","GetLayer","lastPageMiddleware","PageMiddleware","PageLayer","ServerFnLayer","error","firstDestination","remainingDestinations","serverLayer","ServerApplication"],"mappings":"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"}
1
+ {"version":3,"file":"server/application.js","sources":["../../src/server/application.ts"],"sourcesContent":["import * as BunHttpServer from '@effect/platform-bun/BunHttpServer';\nimport { Effect, Layer, Option, Schema, Stream, type Types } from 'effect';\nimport type { PlatformError } from 'effect/PlatformError';\nimport {\n HttpEffect,\n HttpRouter,\n HttpServerRequest,\n HttpServerResponse,\n HttpStaticServer,\n} from 'effect/unstable/http';\n\nimport { type ApplicationDefinition, getApplicationState } from '../application/definition';\nimport { getERSCIdentity } from '../application/ersc-identity';\nimport {\n applyMiddleware,\n type AnyMiddleware,\n getScopedHttpMiddleware,\n} from '../application/middleware';\nimport type { EncodedPageParams, PageParams } from '../application/page';\nimport type { CompiledDestination } from '../application/route-graph';\nimport { FrameworkAssetNamespace, isAbsolutePath } from '../application/route-path';\nimport { FlightMediaType } from '../rsc/flight';\nimport { renderRouteTree } from '../rsc/render-route-tree';\nimport { FlightHtmlInjector } from './flight-html-stream';\nimport { FlightRenderer } from './flight-renderer';\nimport { HtmlRenderError, HtmlRenderer } from './html-renderer';\nimport type { RequestOutcome } from './request-outcome';\nimport {\n ApplicationIdleTimeoutSeconds,\n ApplicationMaxRequestBodySizeBytes,\n ServerConfig,\n} from './server-config';\nimport {\n prepareServerFnRequest,\n type PreparedServerFnRequest,\n type ServerFnRequestFailure,\n} from './server-fn-request';\n\nconst RenderersLayer = Layer.mergeAll(FlightRenderer.layer, HtmlRenderer.layer).pipe(\n Layer.provide(FlightHtmlInjector.layer),\n);\n\nconst StaticAssetsLayer = Layer.unwrap(\n Effect.map(ServerConfig, ({ clientAssetsCacheControl, clientAssetsRoot }) =>\n HttpStaticServer.layer({\n cacheControl: clientAssetsCacheControl,\n prefix: FrameworkAssetNamespace,\n root: clientAssetsRoot,\n }).pipe(Layer.provide(HttpRouter.disableLogger)),\n ),\n);\n\nconst PublicAssetsLayer = Layer.unwrap(\n Effect.map(ServerConfig, ({ publicAssetsRoot }) =>\n HttpStaticServer.layer({\n cacheControl: 'public, max-age=0',\n root: publicAssetsRoot,\n }),\n ),\n);\n\n// Bun derives `development` from NODE_ENV, which `ersc start` does not set, and its error page\n// carries the failure message and source stack.\nconst BunServerLayer = Layer.unwrap(\n Effect.map(ServerConfig, ({ hostname, port }) =>\n BunHttpServer.layer({\n development: false,\n hostname,\n idleTimeout: ApplicationIdleTimeoutSeconds,\n maxRequestBodySize: ApplicationMaxRequestBodySizeBytes,\n port,\n }),\n ),\n);\n\nconst EmptyEncodedPageParams: EncodedPageParams = Object.freeze({});\nconst DynamicResponseHeaders = {\n 'cache-control': 'private, no-store',\n} as const;\n\nconst appendAcceptVary = (vary: string | undefined) => {\n if (vary === undefined || vary.trim() === '') {\n return 'Accept';\n }\n\n const fields = vary.split(',').map((field) => field.trim().toLowerCase());\n return fields.includes('*') || fields.includes('accept') ? vary : `${vary}, Accept`;\n};\n\nconst acceptVaryPreResponseHandler: HttpEffect.PreResponseHandler = (_request, response) =>\n Effect.succeed(\n HttpServerResponse.setHeader(response, 'vary', appendAcceptVary(response.headers['vary'])),\n );\n\ntype RenderOptions<Services> = RequestOutcome & {\n readonly destination: CompiledDestination<Services>;\n readonly middleware: ReadonlyArray<AnyMiddleware<Services>>;\n readonly request: HttpServerRequest.HttpServerRequest;\n};\n\nconst fromWebStream = (\n stream: ReadableStream<Uint8Array>,\n options?: { readonly releaseLockOnEnd?: boolean },\n) =>\n Stream.fromReadableStream({\n evaluate: () => stream,\n onError: (cause) => cause,\n releaseLockOnEnd: options?.releaseLockOnEnd,\n });\n\nconst combinePageMiddleware = <Services>(\n middleware: ReadonlyArray<AnyMiddleware<Services>>,\n last: AnyMiddleware<Services>,\n) =>\n middleware\n .slice(0, -1)\n .reduceRight(\n (combined, current) => combined.combine(getScopedHttpMiddleware(current)),\n getScopedHttpMiddleware(last),\n );\n\ntype HttpApplicationRequirements =\n | ServerConfig\n | Layer.Services<ReturnType<typeof HttpStaticServer.layer>>\n | HttpRouter.Request.From<'Error', HtmlRenderError | ServerFnRequestFailure>;\n\nexport type HttpApplicationLayer<ApplicationError> = Layer.Layer<\n never,\n ApplicationError | PlatformError,\n HttpApplicationRequirements\n>;\n\nexport type ServerApplicationLayer<ApplicationError> = Layer.Layer<\n never,\n ApplicationError | PlatformError,\n ServerConfig\n>;\n\nconst httpLayer = <Services, ApplicationError>(\n application: ApplicationDefinition<Services, ApplicationError>,\n): HttpApplicationLayer<ApplicationError> => {\n const applicationState = getApplicationState(application);\n const identity = getERSCIdentity(application);\n const render = Effect.fnUntraced(function* ({\n destination,\n formState,\n middleware,\n request,\n serverFnResult,\n status,\n temporaryReferences,\n }: RenderOptions<Services>) {\n const requestUrl = HttpServerRequest.toURL(request);\n if (Option.isNone(requestUrl) || !isAbsolutePath(requestUrl.value.pathname)) {\n return yield* Effect.die(\n new TypeError(`Expected request URL \"${request.originalUrl}\" to contain an absolute path.`),\n );\n }\n\n const pathname = requestUrl.value.pathname;\n const encodedParams = yield* destination.page.paramsSchema === null\n ? Effect.succeed(EmptyEncodedPageParams)\n : HttpRouter.params;\n const renderResponse = Effect.fnUntraced(function* (params: PageParams) {\n const routeTree = renderRouteTree({\n destination,\n params,\n pathname,\n });\n const flightRenderer = yield* FlightRenderer;\n const flight = yield* flightRenderer.render({\n formState,\n middleware,\n renderRuntime: identity.renderRuntime,\n routeTree,\n serverFnResult,\n temporaryReferences,\n });\n\n if (request.headers['accept'] === FlightMediaType) {\n return HttpServerResponse.stream(\n fromWebStream(flight.stream, { releaseLockOnEnd: true }).pipe(\n Stream.ensuring(flight.release),\n ),\n {\n contentType: `${FlightMediaType};charset=utf-8`,\n headers: {\n ...DynamicResponseHeaders,\n 'content-location': requestUrl.value.href,\n },\n status,\n },\n );\n }\n\n const htmlRenderer = yield* HtmlRenderer;\n const htmlStream = yield* htmlRenderer\n .render({ flight, formState })\n .pipe(Effect.onError(() => flight.release));\n\n return HttpServerResponse.stream(\n fromWebStream(htmlStream).pipe(Stream.ensuring(flight.release)),\n {\n contentType: 'text/html;charset=utf-8',\n headers: DynamicResponseHeaders,\n status,\n },\n );\n });\n\n if (request.method !== 'POST' && destination.page.paramsSchema !== null) {\n return yield* Schema.decodeEffect(destination.page.paramsSchema)(encodedParams).pipe(\n Effect.matchEffect({\n onFailure: () =>\n Effect.succeed(\n HttpServerResponse.empty({ status: 404, headers: DynamicResponseHeaders }),\n ),\n onSuccess: (value) => renderResponse({ _tag: 'Decoded', value }),\n }),\n );\n }\n return yield* renderResponse({ _tag: 'Encoded', value: encodedParams });\n });\n\n const executeServerFnAndRefresh = (\n prepared: PreparedServerFnRequest<Services>,\n destination: CompiledDestination<Services>,\n request: HttpServerRequest.HttpServerRequest,\n ) => {\n const refreshMiddleware = destination.middleware.filter(\n (middleware) => !prepared.middleware.includes(middleware),\n );\n const renderMiddleware =\n refreshMiddleware.length === 0\n ? prepared.middleware\n : Object.freeze([...prepared.middleware, ...refreshMiddleware]);\n const response = prepared.execute.pipe(\n Effect.flatMap((outcome) => {\n const refresh = render({\n ...outcome,\n destination,\n middleware: renderMiddleware,\n request,\n });\n return applyMiddleware(refreshMiddleware, refresh);\n }),\n );\n\n return applyMiddleware(prepared.middleware, response);\n };\n\n const RequestLayer = Layer.mergeAll(RenderersLayer, applicationState.layer);\n const ApplicationRoutesLayer = Layer.unwrap(\n Effect.map(Effect.context<Services | FlightRenderer | HtmlRenderer>(), (requestContext) => {\n const RequestContextMiddleware = HttpRouter.middleware<{\n provides: Services | FlightRenderer | HtmlRenderer;\n }>()((httpEffect): Effect.Effect<HttpServerResponse.HttpServerResponse, Types.unhandled> =>\n httpEffect.pipe(Effect.provideContext(requestContext)),\n );\n const makeRouteLayer = (destination: CompiledDestination<Services>) => {\n const GetLayer = HttpRouter.add('GET', destination.pattern, (request) =>\n render({\n destination,\n formState: null,\n middleware: destination.middleware,\n request,\n serverFnResult: null,\n status: 200,\n }).pipe(HttpEffect.withPreResponseHandler(acceptVaryPreResponseHandler)),\n );\n const lastPageMiddleware = destination.middleware.at(-1);\n const PageMiddleware =\n lastPageMiddleware === undefined\n ? RequestContextMiddleware\n : combinePageMiddleware(destination.middleware, lastPageMiddleware).combine(\n RequestContextMiddleware,\n );\n const PageLayer = GetLayer.pipe(Layer.provide(PageMiddleware.layer));\n const ServerFnLayer = HttpRouter.add('POST', destination.pattern, (request) =>\n prepareServerFnRequest(request, identity).pipe(\n Effect.flatMap((prepared) => executeServerFnAndRefresh(prepared, destination, request)),\n Effect.catchTag('ServerFnRequestError', (error) =>\n Effect.succeed(\n HttpServerResponse.text(error.message, {\n headers: DynamicResponseHeaders,\n status: error.status,\n }),\n ),\n ),\n HttpEffect.withPreResponseHandler(acceptVaryPreResponseHandler),\n ),\n ).pipe(Layer.provide(RequestContextMiddleware.layer));\n\n return Layer.mergeAll(PageLayer, ServerFnLayer);\n };\n const [firstDestination, ...remainingDestinations] = applicationState.routes;\n return Layer.mergeAll(\n makeRouteLayer(firstDestination),\n ...remainingDestinations.map(makeRouteLayer),\n );\n }),\n ).pipe(Layer.provide(RequestLayer));\n\n return Layer.mergeAll(StaticAssetsLayer, PublicAssetsLayer).pipe(\n Layer.provideMerge(ApplicationRoutesLayer),\n );\n};\n\nconst serverLayer = <Services, ApplicationError>(\n application: ApplicationDefinition<Services, ApplicationError>,\n): ServerApplicationLayer<ApplicationError> =>\n HttpRouter.serve(httpLayer(application)).pipe(Layer.provide(BunServerLayer));\n\nexport const ServerApplication = { httpLayer, serverLayer };\n"],"names":["BunHttpServer","Effect","Layer","Option","Schema","Stream","HttpEffect","HttpRouter","HttpServerRequest","HttpServerResponse","HttpStaticServer","getApplicationState","getERSCIdentity","applyMiddleware","getScopedHttpMiddleware","FrameworkAssetNamespace","isAbsolutePath","FlightMediaType","renderRouteTree","FlightHtmlInjector","FlightRenderer","HtmlRenderer","ApplicationIdleTimeoutSeconds","ApplicationMaxRequestBodySizeBytes","ServerConfig","prepareServerFnRequest","RenderersLayer","StaticAssetsLayer","clientAssetsCacheControl","clientAssetsRoot","PublicAssetsLayer","publicAssetsRoot","BunServerLayer","hostname","port","EmptyEncodedPageParams","Object","DynamicResponseHeaders","appendAcceptVary","vary","undefined","fields","field","acceptVaryPreResponseHandler","_request","response","fromWebStream","stream","options","cause","combinePageMiddleware","middleware","last","combined","current","httpLayer","application","applicationState","identity","render","destination","formState","request","serverFnResult","status","temporaryReferences","requestUrl","TypeError","pathname","encodedParams","renderResponse","params","routeTree","flightRenderer","flight","htmlRenderer","htmlStream","value","executeServerFnAndRefresh","prepared","refreshMiddleware","renderMiddleware","outcome","refresh","RequestLayer","ApplicationRoutesLayer","requestContext","RequestContextMiddleware","httpEffect","makeRouteLayer","GetLayer","lastPageMiddleware","PageMiddleware","PageLayer","ServerFnLayer","error","firstDestination","remainingDestinations","serverLayer","ServerApplication"],"mappings":"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"}
@@ -18,6 +18,7 @@ import { serverFnOutcome } from "./server-fn-outcome.js";
18
18
 
19
19
 
20
20
  const ServerFnArraySizeLimit = 10000;
21
+ const decodeArguments = Schema.decodeUnknownEffect(Schema.Array(Schema.Unknown));
21
22
  const NoMiddleware = Object.freeze([]);
22
23
  class ServerFnRequestError extends Schema.TaggedError()('ServerFnRequestError', {
23
24
  cause: Schema.Defect(),
@@ -91,13 +92,14 @@ const prepareServerFnOperation = (identity, action, args)=>Effect.sync(()=>{
91
92
  const prepareClientServerFn = Effect.fnUntraced(function*(request, actionId, identity) {
92
93
  const temporaryReferences = createTemporaryReferenceSet();
93
94
  const body = yield* readBody(request);
94
- const args = yield* Effect.tryPromise({
95
+ const decoded = yield* Effect.tryPromise({
95
96
  try: ()=>decodeReply(body, {
96
97
  arraySizeLimit: ServerFnArraySizeLimit,
97
98
  temporaryReferences
98
99
  }),
99
100
  catch: (cause)=>requestError('Failed to decode Server Function arguments.', 400, cause)
100
101
  });
102
+ const args = yield* decodeArguments(decoded).pipe(Effect.mapError((cause)=>requestError('Expected a Server Function argument array.', 400, cause)));
101
103
  const action = yield* Effect["try"]({
102
104
  try: ()=>loadServerAction(actionId),
103
105
  catch: (cause)=>requestError('The requested Server Function does not exist.', 400, cause)
@@ -1 +1 @@
1
- {"version":3,"file":"server/server-fn-request.js","sources":["../../src/server/server-fn-request.ts"],"sourcesContent":["import { Cause, Effect, Schema } from 'effect';\nimport { HttpServerRequest } from 'effect/unstable/http';\nimport {\n createTemporaryReferenceSet,\n decodeAction,\n decodeFormState,\n decodeReply,\n loadServerAction,\n} from 'react-server-dom-rspack/server.node';\n\nimport type { ERSCIdentity } from '../application/ersc-identity';\nimport type { AnyMiddleware } from '../application/middleware';\nimport { matchServerFnInvocation } from '../application/server-fn';\nimport { ServerFnIdHeader } from '../rsc/flight';\nimport type { RequestOutcome } from './request-outcome';\nimport { serverFnOutcome } from './server-fn-outcome';\n\nconst ServerFnArraySizeLimit = 10_000;\nconst NoMiddleware = Object.freeze([]);\n\nexport class ServerFnRequestError extends Schema.TaggedError<ServerFnRequestError>()(\n 'ServerFnRequestError',\n {\n cause: Schema.Defect(),\n message: Schema.String,\n status: Schema.Literals([400, 403, 500]),\n },\n) {}\n\nclass ServerFnExecutionError extends Schema.TaggedError<ServerFnExecutionError>()(\n 'ServerFnExecutionError',\n { cause: Schema.Defect() },\n) {}\n\nconst requestError = (message: string, status: 400 | 403 | 500, cause: unknown) =>\n new ServerFnRequestError({ cause, message, status });\n\nconst bodyReadError = (message: string, cause: unknown) => requestError(message, 400, cause);\n\nconst normalizeServerFnFailure = <Output, Error, Services>(\n effect: Effect.Effect<Output, Error, Services>,\n): Effect.Effect<Output, ServerFnExecutionError, Services> =>\n effect.pipe(\n Effect.catchCause((cause) =>\n Cause.hasInterrupts(cause)\n ? Effect.interrupt\n : Effect.fail(new ServerFnExecutionError({ cause: Cause.squash(cause) })),\n ),\n );\n\ntype ServerFnOperation<ApplicationServices> = {\n readonly effect: Effect.Effect<unknown, ServerFnExecutionError, ApplicationServices>;\n readonly middleware: ReadonlyArray<AnyMiddleware<ApplicationServices>>;\n};\n\nexport type PreparedServerFnRequest<ApplicationServices> = {\n readonly execute: Effect.Effect<RequestOutcome, ServerFnRequestError, ApplicationServices>;\n readonly middleware: ReadonlyArray<AnyMiddleware<ApplicationServices>>;\n};\n\nconst validateOrigin = (request: HttpServerRequest.HttpServerRequest) =>\n Effect.try({\n try: () => {\n const origin = request.headers['origin'];\n if (origin === undefined) {\n throw new Error('The Origin header is missing.');\n }\n\n const expectedHost = request.headers['host'];\n if (expectedHost === undefined || new URL(origin).host !== expectedHost.toLowerCase()) {\n throw new Error(`Origin \"${origin}\" does not match host \"${expectedHost ?? ''}\".`);\n }\n },\n catch: (cause) => requestError('Rejected a cross-origin Server Function request.', 403, cause),\n });\n\nconst readBody = Effect.fnUntraced(function* (request: Request) {\n if (request.headers.get('content-type')?.toLowerCase().startsWith('multipart/form-data')) {\n return yield* Effect.tryPromise({\n try: () => request.formData(),\n catch: (cause) => bodyReadError('Failed to read the Server Function request body.', cause),\n });\n }\n\n return yield* Effect.tryPromise({\n try: () => request.text(),\n catch: (cause) => bodyReadError('Failed to read the Server Function request body.', cause),\n });\n});\n\nconst prepareServerFnOperation = <ApplicationServices>(\n identity: ERSCIdentity<ApplicationServices>,\n action: (...args: ReadonlyArray<unknown>) => unknown,\n args: ReadonlyArray<unknown>,\n) =>\n Effect.sync((): ServerFnOperation<ApplicationServices> => {\n const invocation = action(...args);\n const match = matchServerFnInvocation(invocation, identity);\n switch (match._tag) {\n case 'Match':\n return {\n effect: normalizeServerFnFailure(match.effect),\n middleware: match.middleware,\n };\n case 'IdentityMismatch':\n return {\n effect: Effect.die(\n new TypeError('Server Function was created by a different ERSC module.'),\n ),\n middleware: NoMiddleware,\n };\n case 'Native':\n return {\n effect: normalizeServerFnFailure(Effect.promise(() => Promise.resolve(invocation))),\n middleware: NoMiddleware,\n };\n }\n }).pipe(normalizeServerFnFailure);\n\nconst prepareClientServerFn = Effect.fnUntraced(function* <ApplicationServices>(\n request: Request,\n actionId: string,\n identity: ERSCIdentity<ApplicationServices>,\n) {\n const temporaryReferences = createTemporaryReferenceSet();\n const body = yield* readBody(request);\n const args = yield* Effect.tryPromise({\n try: () =>\n decodeReply(body, {\n arraySizeLimit: ServerFnArraySizeLimit,\n temporaryReferences,\n }),\n catch: (cause) => requestError('Failed to decode Server Function arguments.', 400, cause),\n });\n const action = yield* Effect.try({\n try: () => loadServerAction(actionId),\n catch: (cause) => requestError('The requested Server Function does not exist.', 400, cause),\n });\n const operation = yield* prepareServerFnOperation(identity, action, args);\n\n const prepared: PreparedServerFnRequest<ApplicationServices> = {\n execute: serverFnOutcome(operation.effect).pipe(\n Effect.map(\n (outcome) =>\n ({\n formState: null,\n ...outcome,\n temporaryReferences,\n }) satisfies RequestOutcome,\n ),\n ),\n middleware: operation.middleware,\n };\n return prepared;\n});\n\nconst prepareProgressiveServerFn = Effect.fnUntraced(function* <ApplicationServices>(\n request: Request,\n identity: ERSCIdentity<ApplicationServices>,\n) {\n const formData = yield* Effect.tryPromise({\n try: () => request.formData(),\n catch: (cause) => bodyReadError('Failed to read the Server Function form body.', cause),\n });\n const decodedAction = yield* Effect.tryPromise({\n try: () => Promise.resolve(decodeAction(formData)),\n catch: (cause) => requestError('Failed to decode the Server Function form action.', 400, cause),\n });\n if (decodedAction === null) {\n return yield* requestError(\n 'The submitted form does not contain a Server Function action.',\n 400,\n new Error('decodeAction returned null.'),\n );\n }\n\n const operation = yield* prepareServerFnOperation(identity, decodedAction, []);\n const execute = Effect.gen(function* () {\n const actionResult = yield* operation.effect.pipe(\n Effect.mapError((error) =>\n requestError('The Server Function form action failed.', 500, error.cause),\n ),\n );\n const decodedFormState = yield* Effect.tryPromise({\n try: () => decodeFormState(actionResult, formData),\n catch: (cause) => requestError('Failed to decode React form state.', 500, cause),\n });\n\n return {\n formState: decodedFormState,\n serverFnResult: null,\n status: 200,\n } satisfies RequestOutcome;\n });\n\n const prepared: PreparedServerFnRequest<ApplicationServices> = {\n execute,\n middleware: operation.middleware,\n };\n return prepared;\n});\n\nexport const prepareServerFnRequest = Effect.fnUntraced(function* <Services>(\n request: HttpServerRequest.HttpServerRequest,\n identity: ERSCIdentity<Services>,\n) {\n yield* validateOrigin(request);\n const signal = yield* Effect.abortSignal;\n const webRequest = yield* HttpServerRequest.toWeb(request, { signal });\n const actionId = request.headers[ServerFnIdHeader];\n const prepareRequest =\n actionId === undefined\n ? prepareProgressiveServerFn(webRequest, identity)\n : prepareClientServerFn(webRequest, actionId, identity);\n\n return yield* prepareRequest;\n});\n\nexport type ServerFnRequestFailure = Effect.Error<ReturnType<typeof prepareServerFnRequest>>;\n"],"names":["Cause","Effect","Schema","HttpServerRequest","createTemporaryReferenceSet","decodeAction","decodeFormState","decodeReply","loadServerAction","matchServerFnInvocation","ServerFnIdHeader","serverFnOutcome","ServerFnArraySizeLimit","NoMiddleware","Object","ServerFnRequestError","ServerFnExecutionError","requestError","message","status","cause","bodyReadError","normalizeServerFnFailure","effect","validateOrigin","request","origin","undefined","Error","expectedHost","URL","readBody","prepareServerFnOperation","identity","action","args","invocation","match","TypeError","Promise","prepareClientServerFn","actionId","temporaryReferences","body","operation","prepared","outcome","prepareProgressiveServerFn","formData","decodedAction","execute","actionResult","error","decodedFormState","prepareServerFnRequest","signal","webRequest","prepareRequest"],"mappings":"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"}
1
+ {"version":3,"file":"server/server-fn-request.js","sources":["../../src/server/server-fn-request.ts"],"sourcesContent":["import { Cause, Effect, Schema } from 'effect';\nimport { HttpServerRequest } from 'effect/unstable/http';\nimport {\n createTemporaryReferenceSet,\n decodeAction,\n decodeFormState,\n decodeReply,\n loadServerAction,\n} from 'react-server-dom-rspack/server.node';\n\nimport type { ERSCIdentity } from '../application/ersc-identity';\nimport type { AnyMiddleware } from '../application/middleware';\nimport { matchServerFnInvocation } from '../application/server-fn';\nimport { ServerFnIdHeader } from '../rsc/flight';\nimport type { RequestOutcome } from './request-outcome';\nimport { serverFnOutcome } from './server-fn-outcome';\n\nconst ServerFnArraySizeLimit = 10_000;\nconst decodeArguments = Schema.decodeUnknownEffect(Schema.Array(Schema.Unknown));\nconst NoMiddleware = Object.freeze([]);\n\nexport class ServerFnRequestError extends Schema.TaggedError<ServerFnRequestError>()(\n 'ServerFnRequestError',\n {\n cause: Schema.Defect(),\n message: Schema.String,\n status: Schema.Literals([400, 403, 500]),\n },\n) {}\n\nclass ServerFnExecutionError extends Schema.TaggedError<ServerFnExecutionError>()(\n 'ServerFnExecutionError',\n { cause: Schema.Defect() },\n) {}\n\nconst requestError = (message: string, status: 400 | 403 | 500, cause: unknown) =>\n new ServerFnRequestError({ cause, message, status });\n\nconst bodyReadError = (message: string, cause: unknown) => requestError(message, 400, cause);\n\nconst normalizeServerFnFailure = <Output, Error, Services>(\n effect: Effect.Effect<Output, Error, Services>,\n): Effect.Effect<Output, ServerFnExecutionError, Services> =>\n effect.pipe(\n Effect.catchCause((cause) =>\n Cause.hasInterrupts(cause)\n ? Effect.interrupt\n : Effect.fail(new ServerFnExecutionError({ cause: Cause.squash(cause) })),\n ),\n );\n\ntype ServerFnOperation<ApplicationServices> = {\n readonly effect: Effect.Effect<unknown, ServerFnExecutionError, ApplicationServices>;\n readonly middleware: ReadonlyArray<AnyMiddleware<ApplicationServices>>;\n};\n\nexport type PreparedServerFnRequest<ApplicationServices> = {\n readonly execute: Effect.Effect<RequestOutcome, ServerFnRequestError, ApplicationServices>;\n readonly middleware: ReadonlyArray<AnyMiddleware<ApplicationServices>>;\n};\n\nconst validateOrigin = (request: HttpServerRequest.HttpServerRequest) =>\n Effect.try({\n try: () => {\n const origin = request.headers['origin'];\n if (origin === undefined) {\n throw new Error('The Origin header is missing.');\n }\n\n const expectedHost = request.headers['host'];\n if (expectedHost === undefined || new URL(origin).host !== expectedHost.toLowerCase()) {\n throw new Error(`Origin \"${origin}\" does not match host \"${expectedHost ?? ''}\".`);\n }\n },\n catch: (cause) => requestError('Rejected a cross-origin Server Function request.', 403, cause),\n });\n\nconst readBody = Effect.fnUntraced(function* (request: Request) {\n if (request.headers.get('content-type')?.toLowerCase().startsWith('multipart/form-data')) {\n return yield* Effect.tryPromise({\n try: () => request.formData(),\n catch: (cause) => bodyReadError('Failed to read the Server Function request body.', cause),\n });\n }\n\n return yield* Effect.tryPromise({\n try: () => request.text(),\n catch: (cause) => bodyReadError('Failed to read the Server Function request body.', cause),\n });\n});\n\nconst prepareServerFnOperation = <ApplicationServices>(\n identity: ERSCIdentity<ApplicationServices>,\n action: (...args: ReadonlyArray<unknown>) => unknown,\n args: ReadonlyArray<unknown>,\n) =>\n Effect.sync((): ServerFnOperation<ApplicationServices> => {\n const invocation = action(...args);\n const match = matchServerFnInvocation(invocation, identity);\n switch (match._tag) {\n case 'Match':\n return {\n effect: normalizeServerFnFailure(match.effect),\n middleware: match.middleware,\n };\n case 'IdentityMismatch':\n return {\n effect: Effect.die(\n new TypeError('Server Function was created by a different ERSC module.'),\n ),\n middleware: NoMiddleware,\n };\n case 'Native':\n return {\n effect: normalizeServerFnFailure(Effect.promise(() => Promise.resolve(invocation))),\n middleware: NoMiddleware,\n };\n }\n }).pipe(normalizeServerFnFailure);\n\nconst prepareClientServerFn = Effect.fnUntraced(function* <ApplicationServices>(\n request: Request,\n actionId: string,\n identity: ERSCIdentity<ApplicationServices>,\n) {\n const temporaryReferences = createTemporaryReferenceSet();\n const body = yield* readBody(request);\n const decoded = yield* Effect.tryPromise({\n try: () =>\n decodeReply(body, {\n arraySizeLimit: ServerFnArraySizeLimit,\n temporaryReferences,\n }),\n catch: (cause) => requestError('Failed to decode Server Function arguments.', 400, cause),\n });\n const args = yield* decodeArguments(decoded).pipe(\n Effect.mapError((cause) =>\n requestError('Expected a Server Function argument array.', 400, cause),\n ),\n );\n const action = yield* Effect.try({\n try: () => loadServerAction(actionId),\n catch: (cause) => requestError('The requested Server Function does not exist.', 400, cause),\n });\n const operation = yield* prepareServerFnOperation(identity, action, args);\n\n const prepared: PreparedServerFnRequest<ApplicationServices> = {\n execute: serverFnOutcome(operation.effect).pipe(\n Effect.map(\n (outcome) =>\n ({\n formState: null,\n ...outcome,\n temporaryReferences,\n }) satisfies RequestOutcome,\n ),\n ),\n middleware: operation.middleware,\n };\n return prepared;\n});\n\nconst prepareProgressiveServerFn = Effect.fnUntraced(function* <ApplicationServices>(\n request: Request,\n identity: ERSCIdentity<ApplicationServices>,\n) {\n const formData = yield* Effect.tryPromise({\n try: () => request.formData(),\n catch: (cause) => bodyReadError('Failed to read the Server Function form body.', cause),\n });\n const decodedAction = yield* Effect.tryPromise({\n try: () => Promise.resolve(decodeAction(formData)),\n catch: (cause) => requestError('Failed to decode the Server Function form action.', 400, cause),\n });\n if (decodedAction === null) {\n return yield* requestError(\n 'The submitted form does not contain a Server Function action.',\n 400,\n new Error('decodeAction returned null.'),\n );\n }\n\n const operation = yield* prepareServerFnOperation(identity, decodedAction, []);\n const execute = Effect.gen(function* () {\n const actionResult = yield* operation.effect.pipe(\n Effect.mapError((error) =>\n requestError('The Server Function form action failed.', 500, error.cause),\n ),\n );\n const decodedFormState = yield* Effect.tryPromise({\n try: () => decodeFormState(actionResult, formData),\n catch: (cause) => requestError('Failed to decode React form state.', 500, cause),\n });\n\n return {\n formState: decodedFormState,\n serverFnResult: null,\n status: 200,\n } satisfies RequestOutcome;\n });\n\n const prepared: PreparedServerFnRequest<ApplicationServices> = {\n execute,\n middleware: operation.middleware,\n };\n return prepared;\n});\n\nexport const prepareServerFnRequest = Effect.fnUntraced(function* <Services>(\n request: HttpServerRequest.HttpServerRequest,\n identity: ERSCIdentity<Services>,\n) {\n yield* validateOrigin(request);\n const signal = yield* Effect.abortSignal;\n const webRequest = yield* HttpServerRequest.toWeb(request, { signal });\n const actionId = request.headers[ServerFnIdHeader];\n const prepareRequest =\n actionId === undefined\n ? prepareProgressiveServerFn(webRequest, identity)\n : prepareClientServerFn(webRequest, actionId, identity);\n\n return yield* prepareRequest;\n});\n\nexport type ServerFnRequestFailure = Effect.Error<ReturnType<typeof prepareServerFnRequest>>;\n"],"names":["Cause","Effect","Schema","HttpServerRequest","createTemporaryReferenceSet","decodeAction","decodeFormState","decodeReply","loadServerAction","matchServerFnInvocation","ServerFnIdHeader","serverFnOutcome","ServerFnArraySizeLimit","decodeArguments","NoMiddleware","Object","ServerFnRequestError","ServerFnExecutionError","requestError","message","status","cause","bodyReadError","normalizeServerFnFailure","effect","validateOrigin","request","origin","undefined","Error","expectedHost","URL","readBody","prepareServerFnOperation","identity","action","args","invocation","match","TypeError","Promise","prepareClientServerFn","actionId","temporaryReferences","body","decoded","operation","prepared","outcome","prepareProgressiveServerFn","formData","decodedAction","execute","actionResult","error","decodedFormState","prepareServerFnRequest","signal","webRequest","prepareRequest"],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @title Defining a stateful form action
3
+ *
4
+ * A schema list decodes React's previous state and submitted FormData separately.
5
+ */
6
+ 'use server';
7
+
8
+ import { Effect, Schema } from 'effect';
9
+
10
+ import { ERSC } from './10_ersc';
11
+
12
+ const StateSchema = Schema.Struct({ message: Schema.String });
13
+ const FormSchema = Schema.fromFormData(Schema.Struct({ name: Schema.NonEmptyString }));
14
+
15
+ export const greet = ERSC.ServerFn.make({
16
+ input: [StateSchema, FormSchema],
17
+ handler: (_previousState, { name }) => Effect.succeed({ message: `Hello, ${name}` }),
18
+ });
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @title Rendering a stateful form
3
+ *
4
+ * Pass the native reference to useActionState so React also owns progressive form submission.
5
+ */
6
+ 'use client';
7
+
8
+ import { useActionState } from 'react';
9
+
10
+ import { greet } from './50_greet';
11
+
12
+ export function GreetingForm() {
13
+ const [state, formAction, pending] = useActionState(greet, { message: '' });
14
+ return (
15
+ <form action={formAction}>
16
+ <input name='name' required />
17
+ <button disabled={pending} type='submit'>
18
+ Greet
19
+ </button>
20
+ <p aria-live='polite'>{state.message}</p>
21
+ </form>
22
+ );
23
+ }
@@ -7,6 +7,11 @@ Callers pass the Schema's encoded type and the handler receives its decoded type
7
7
  input; a function returning `void` can then be passed directly to `<form action>`. Let the Schema
8
8
  infer the handler parameter.
9
9
 
10
+ For form feedback with `useActionState`, use `input: [StateSchema, FormSchema]` and
11
+ `handler: (previousState, form) => ...`. React supplies both arguments; ERSC validates and decodes
12
+ each one. Keep the native Server Function reference intact when passing it to `useActionState`
13
+ to retain progressive enhancement. A single Array or Tuple Schema still describes one argument.
14
+
10
15
  A successful invocation refreshes the current route.
11
16
 
12
17
  <!-- source-navigation -->
@@ -17,3 +22,5 @@ A successful invocation refreshes the current route.
17
22
  - [Define a Server Function](./20_follow-author.ts)
18
23
  - [Use a direct form action](./30_follow-author-button.tsx)
19
24
  - [Compose the application](./40_application.tsx)
25
+ - [Define a stateful form action](./50_greet.ts)
26
+ - [Render a stateful form](./60_greeting-form.tsx)
@@ -3,7 +3,11 @@
3
3
  - Routes are immutable and belong to one ERSC identity.
4
4
  - `page(path, page)` attaches a Page; `mount(prefix, routes)` nests a route scope.
5
5
  - Mounted scopes retain their Layout and Loading ancestry.
6
- - Page parameter Schemas decode Effect HTTP path captures before rendering.
6
+ - On GET/HEAD, the request handler decodes Page parameters once before rendering, with services
7
+ from existing route middleware available. Rejected parameters return an empty `404`, including
8
+ navigation Flight.
9
+ - Server Function POST refreshes keep parameter rejection in React's render-error path, preserving
10
+ the completed action result.
7
11
  - Effect HTTP owns route matching; ERSC rejects duplicate shapes and invalid composition while
8
12
  building the graph.
9
13
 
@@ -2,9 +2,12 @@
2
2
 
3
3
  ERSC handles eligible document navigations through the browser Navigation API and
4
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.
5
+ still hydrates Client Components and supports Server Functions and streamed current-page refreshes,
6
+ including HMR. Links use full-page navigation instead of the client router. Without JavaScript,
7
+ the server-rendered document retains working links and natively submitted forms.
8
+
9
+ Development reports a missing navigation API in the console and a dismissible development-panel
10
+ warning. The warning does not block interaction and is absent in production.
8
11
 
9
12
  An intercepted Page navigation has two milestones:
10
13
 
@@ -11,7 +11,9 @@ Flight EOF. Disconnecting interrupts unfinished request work and the response st
11
11
  Hydrated invocations may execute concurrently. Only the latest invocation may apply its response's
12
12
  route tree while its original history entry remains current and no navigation is active. Other
13
13
  responses trigger a fresh current-route refresh. A response tree interrupts any older current-route
14
- refresh before rendering.
14
+ refresh before rendering, then rechecks applicability after cleanup completes. Effect owns refresh
15
+ loading and cancellation; the React Transition publishes the tree without waiting for its own
16
+ commit inside an async Action.
15
17
 
16
18
  After a successful mutation, ERSC clears the Back/Forward traversal cache because any route may have
17
19
  changed.
@@ -7,6 +7,11 @@
7
7
  the Schema's encoded keys must exactly match the path parameters and accept strings. Compose the
8
8
  Page with `Routes.page`.
9
9
 
10
- Pages produce React output. Only an unmatched route receives a native `404`. The mapping from a
11
- matched Page's parameter rejection to an expected HTTP outcome remains a
12
- [known limitation](https://github.com/nikhilsnayak/effective-rsc/blob/main/docs/ARCHITECTURE.md#known-limitations).
10
+ Pages produce React output. On GET/HEAD, the request handler decodes parameters once before
11
+ rendering, with services from existing route middleware available. Rejected parameters receive
12
+ an empty `404`, including navigation Flight requests; unmatched routes also receive native `404`
13
+ responses. Other failures keep their existing
14
+ error behavior.
15
+
16
+ Server Function POST refreshes decode parameters inside Page rendering. A rejection follows React's
17
+ render-error path without replacing the completed Server Function result with a `404`.
@@ -22,10 +22,10 @@ middleware across mounted scopes runs once.
22
22
  | -------------------------------- | ------------------------------------ | --------------------- | ------------------------ |
23
23
  | Page GET/HEAD | Matched chain | No | Yes |
24
24
  | Hydrated Server Function POST | Remaining route chain around refresh | Server Function chain | Yes |
25
- | Progressive Server Function POST | No route refresh in the POST | Server Function chain | Yes |
25
+ | Progressive Server Function POST | Remaining route chain around refresh | Server Function chain | Yes |
26
26
  | Userland HTTP, assets, unmatched | No | No | Yes |
27
27
 
28
- During a hydrated Server Function request, middleware already active for the Server Function is not
28
+ During a Server Function request, middleware already active for the Server Function is not
29
29
  executed again for the refreshed route, even if it appears at another position in that route chain.
30
30
  Remaining route middleware wraps refreshed rendering.
31
31
 
@@ -5,6 +5,12 @@ decodes the invocation payload and infers the handler parameter; do not annotate
5
5
  returns an Effect whose requirements fit the ERSC service universe. The client reference accepts the
6
6
  Schema's encoded type and resolves `Promise<Output>`; the handler receives its decoded type.
7
7
 
8
+ For multiple positional arguments, supply a readonly schema list as `input`. Each caller argument
9
+ uses its Schema's encoded type; each handler argument uses its decoded type, in the same order.
10
+ Inline lists infer their tuple shape without `as const`. Use `input: []` for no arguments.
11
+ `input: Schema.Array(...)` and `input: Schema.Tuple(...)` still describe one argument, not a
12
+ positional argument list.
13
+
8
14
  ```ts
9
15
  const followAuthor = ERSC.ServerFn.make({
10
16
  input: Schema.Struct({ authorId: Schema.NonEmptyString }),
@@ -30,6 +36,23 @@ const followAuthorForm = ERSC.ServerFn.make({
30
36
  A ServerFn created from a derived view activates its middleware for the POST. The Middleware
31
37
  reference defines refresh reach and ordering.
32
38
 
39
+ For a `useActionState` form, declare both the previous state and submitted FormData:
40
+
41
+ ```ts
42
+ const StateSchema = Schema.Struct({ message: Schema.String });
43
+ const FormSchema = Schema.fromFormData(Schema.Struct({ name: Schema.NonEmptyString }));
44
+
45
+ const greet = ERSC.ServerFn.make({
46
+ input: [StateSchema, FormSchema],
47
+ handler: (_previousState, { name }) => Effect.succeed({ message: `Hello, ${name}` }),
48
+ });
49
+ ```
50
+
51
+ Pass the native reference directly to `useActionState(greet, { message: '' })` and its returned
52
+ action to `<form action>`. React supplies previous state and FormData for hydrated and progressive
53
+ submissions. Previous state is client input: validate it, but never trust it for authorization or
54
+ authoritative application state. Native `.bind` can prefill leading arguments.
55
+
33
56
  Direct server invocation throws. Encode expected failure in a discriminated output union; unexpected
34
57
  failures reject the Promise. Browser requests require an Origin matching the application host and
35
58
  may contain at most 10 MiB. See the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effective-rsc",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "An experimental, Effect-native React Server Components framework for Bun.",
5
5
  "keywords": [
6
6
  "bun",