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.
- package/LLMS.md +54 -10
- package/README.md +3 -3
- package/dist/application/page.d.ts +14 -7
- package/dist/application/page.js +5 -3
- package/dist/application/page.js.map +1 -1
- package/dist/application/route-graph.d.ts +1 -1
- package/dist/application/route-graph.js.map +1 -1
- package/dist/application/server-fn.d.ts +10 -6
- package/dist/application/server-fn.js +9 -4
- package/dist/application/server-fn.js.map +1 -1
- package/dist/build/dev.d.ts +11 -11
- package/dist/build/dev.js +21 -5
- package/dist/build/dev.js.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/client/application.js +12 -9
- package/dist/client/application.js.map +1 -1
- package/dist/client/browser-capabilities.d.ts +2 -9
- package/dist/client/browser-capabilities.js +3 -14
- package/dist/client/browser-capabilities.js.map +1 -1
- package/dist/client/browser-render-status.d.ts +25 -0
- package/dist/client/browser-render-status.js +20 -0
- package/dist/client/browser-render-status.js.map +1 -0
- package/dist/client/call-server.js +7 -2
- package/dist/client/call-server.js.map +1 -1
- package/dist/client/navigation-api.js +5 -4
- package/dist/client/navigation-api.js.map +1 -1
- package/dist/client/react-dom-renderer.d.ts +3 -2
- package/dist/client/react-dom-renderer.js +43 -2
- package/dist/client/react-dom-renderer.js.map +1 -1
- package/dist/client/route-refresh.d.ts +1 -2
- package/dist/client/route-refresh.js +5 -18
- package/dist/client/route-refresh.js.map +1 -1
- package/dist/dev/client.d.ts +2 -1
- package/dist/dev/client.js +46 -6
- package/dist/dev/client.js.map +1 -1
- package/dist/dev/panel.d.ts +12 -1
- package/dist/dev/panel.js +73 -5
- package/dist/dev/panel.js.map +1 -1
- package/dist/dev/runtime-failure.d.ts +1 -0
- package/dist/dev/runtime-failure.js +7 -1
- package/dist/dev/runtime-failure.js.map +1 -1
- package/dist/rsc/render-route-tree.d.ts +3 -3
- package/dist/rsc/render-route-tree.js +2 -2
- package/dist/rsc/render-route-tree.js.map +1 -1
- package/dist/server/application.js +54 -35
- package/dist/server/application.js.map +1 -1
- package/dist/server/server-fn-request.js +3 -1
- package/dist/server/server-fn-request.js.map +1 -1
- package/docs/02-guides/01-server-functions/50_greet.ts +18 -0
- package/docs/02-guides/01-server-functions/60_greeting-form.tsx +23 -0
- package/docs/02-guides/01-server-functions/index.md +7 -0
- package/docs/02-guides/03-routing/index.md +5 -1
- package/docs/03-advanced/02-client-navigation/index.md +6 -3
- package/docs/03-advanced/03-server-function-execution-and-refresh/index.md +3 -1
- package/docs/04-api-reference/02-page/index.md +8 -3
- package/docs/04-api-reference/06-middleware/index.md +2 -2
- package/docs/04-api-reference/08-server-fn/index.md +23 -0
- 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,
|
|
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 {
|
|
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
|
|
8
|
+
readonly params: PageParams;
|
|
9
9
|
};
|
|
10
|
-
export declare const renderRouteTree: <Services>({ destination, pathname,
|
|
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,
|
|
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:
|
|
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 {
|
|
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
|
|
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
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
|
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":";;;;;;;;;;;;;AAA+C;AACU;AAOZ;AAIsB;AAClB;AAEK;AAEtD,MAAMY,sBAAsBA,GAAG;AAC/B,MAAMC,eAAeA,GAAGX,0BAA0B,CAACA,YAAY,CAACA,cAAc;AAC9E,MAAMY,YAAYA,GAAGC,OAAO,MAAM,CAAC,EAAE;AAE9B,MAAMC,oBAAoBA,SAASd,kBAAkB,GAC1D,wBACA;IACE,OAAOA,aAAa;IACpB,SAASA,aAAa;IACtB,QAAQA,eAAe,CAAC;QAAC;QAAK;QAAK;KAAI;AACzC;AACC;AAEH,MAAMe,sBAAsBA,SAASf,kBAAkB,GACrD,0BACA;IAAE,OAAOA,aAAa;AAAG;AACxB;AAEH,MAAMgB,YAAYA,GAAG,CAACC,SAAiBC,QAAyBC,QAC9D,IAAIL,oBAAoBA,CAAC;QAAEK;QAAOF;QAASC;IAAO;AAEpD,MAAME,aAAaA,GAAG,CAACH,SAAiBE,QAAmBH,YAAYA,CAACC,SAAS,KAAKE;AAEtF,MAAME,wBAAwBA,GAAG,CAC/BC,SAEAA,OAAO,IAAI,CACTvB,iBAAiB,CAAC,CAACoB,QACjBrB,mBAAmB,CAACqB,SAChBpB,gBAAgB,GAChBA,WAAW,CAAC,IAAIgB,sBAAsBA,CAAC;YAAE,OAAOjB,YAAY,CAACqB;QAAO;AAc9E,MAAMI,cAAcA,GAAG,CAACC,UACtBzB,aAAU,CAAC;QACT,KAAK;YACH,MAAM0B,SAASD,QAAQ,OAAO,CAAC,SAAS;YACxC,IAAIC,WAAWC,WAAW;gBACxB,MAAM,IAAIC,MAAM;YAClB;YAEA,MAAMC,eAAeJ,QAAQ,OAAO,CAAC,OAAO;YAC5C,IAAII,iBAAiBF,aAAa,IAAIG,IAAIJ,QAAQ,IAAI,KAAKG,aAAa,WAAW,IAAI;gBACrF,MAAM,IAAID,MAAM,CAAC,QAAQ,EAAEF,OAAO,uBAAuB,EAAEG,gBAAgB,GAAG,EAAE,CAAC;YACnF;QACF;QACA,OAAO,CAACT,QAAUH,YAAYA,CAAC,oDAAoD,KAAKG;IAC1F;AAEF,MAAMW,QAAQA,GAAG/B,iBAAiB,CAAC,UAAWyB,OAAgB;IAC5D,IAAIA,QAAQ,OAAO,CAAC,GAAG,CAAC,iBAAiB,cAAc,WAAW,wBAAwB;QACxF,OAAO,OAAOzB,iBAAiB,CAAC;YAC9B,KAAK,IAAMyB,QAAQ,QAAQ;YAC3B,OAAO,CAACL,QAAUC,aAAaA,CAAC,oDAAoDD;QACtF;IACF;IAEA,OAAO,OAAOpB,iBAAiB,CAAC;QAC9B,KAAK,IAAMyB,QAAQ,IAAI;QACvB,OAAO,CAACL,QAAUC,aAAaA,CAAC,oDAAoDD;IACtF;AACF;AAEA,MAAMY,wBAAwBA,GAAG,CAC/BC,UACAC,QACAC,OAEAnC,WAAW,CAAC;QACV,MAAMoC,aAAaF,UAAUC;QAC7B,MAAME,QAAQ7B,uBAAuBA,CAAC4B,YAAYH;QAClD,OAAQI,MAAM,IAAI;YAChB,KAAK;gBACH,OAAO;oBACL,QAAQf,wBAAwBA,CAACe,MAAM,MAAM;oBAC7C,YAAYA,MAAM,UAAU;gBAC9B;YACF,KAAK;gBACH,OAAO;oBACL,QAAQrC,UAAU,CAChB,IAAIsC,UAAU;oBAEhB,YAAYzB,YAAYA;gBAC1B;YACF,KAAK;gBACH,OAAO;oBACL,QAAQS,wBAAwBA,CAACtB,cAAc,CAAC,IAAMuC,QAAQ,OAAO,CAACH;oBACtE,YAAYvB,YAAYA;gBAC1B;QACJ;IACF,GAAG,IAAI,CAACS,wBAAwBA;AAElC,MAAMkB,qBAAqBA,GAAGxC,iBAAiB,CAAC,UAC9CyB,OAAgB,EAChBgB,QAAgB,EAChBR,QAA2C;IAE3C,MAAMS,sBAAsBvC,2BAA2BA;IACvD,MAAMwC,OAAO,OAAOZ,QAAQA,CAACN;IAC7B,MAAMmB,UAAU,OAAO5C,iBAAiB,CAAC;QACvC,KAAK,IACHM,WAAWA,CAACqC,MAAM;gBAChB,gBAAgBhC,sBAAsBA;gBACtC+B;YACF;QACF,OAAO,CAACtB,QAAUH,YAAYA,CAAC,+CAA+C,KAAKG;IACrF;IACA,MAAMe,OAAO,OAAOvB,eAAeA,CAACgC,SAAS,IAAI,CAC/C5C,eAAe,CAAC,CAACoB,QACfH,YAAYA,CAAC,8CAA8C,KAAKG;IAGpE,MAAMc,SAAS,OAAOlC,aAAU,CAAC;QAC/B,KAAK,IAAMO,gBAAgBA,CAACkC;QAC5B,OAAO,CAACrB,QAAUH,YAAYA,CAAC,iDAAiD,KAAKG;IACvF;IACA,MAAMyB,YAAY,OAAOb,wBAAwBA,CAACC,UAAUC,QAAQC;IAEpE,MAAMW,WAAyD;QAC7D,SAASpC,eAAeA,CAACmC,UAAU,MAAM,EAAE,IAAI,CAC7C7C,UAAU,CACR,CAAC+C,UACE;gBACC,WAAW;gBACX,GAAGA,OAAO;gBACVL;YACF;QAGN,YAAYG,UAAU,UAAU;IAClC;IACA,OAAOC;AACT;AAEA,MAAME,0BAA0BA,GAAGhD,iBAAiB,CAAC,UACnDyB,OAAgB,EAChBQ,QAA2C;IAE3C,MAAMgB,WAAW,OAAOjD,iBAAiB,CAAC;QACxC,KAAK,IAAMyB,QAAQ,QAAQ;QAC3B,OAAO,CAACL,QAAUC,aAAaA,CAAC,iDAAiDD;IACnF;IACA,MAAM8B,gBAAgB,OAAOlD,iBAAiB,CAAC;QAC7C,KAAK,IAAMuC,QAAQ,OAAO,CAACnC,YAAYA,CAAC6C;QACxC,OAAO,CAAC7B,QAAUH,YAAYA,CAAC,qDAAqD,KAAKG;IAC3F;IACA,IAAI8B,kBAAkB,MAAM;QAC1B,OAAO,OAAOjC,YAAYA,CACxB,iEACA,KACA,IAAIW,MAAM;IAEd;IAEA,MAAMiB,YAAY,OAAOb,wBAAwBA,CAACC,UAAUiB,eAAe,EAAE;IAC7E,MAAMC,UAAUnD,UAAU,CAAC;QACzB,MAAMoD,eAAe,OAAOP,UAAU,MAAM,CAAC,IAAI,CAC/C7C,eAAe,CAAC,CAACqD,QACfpC,YAAYA,CAAC,2CAA2C,KAAKoC,MAAM,KAAK;QAG5E,MAAMC,mBAAmB,OAAOtD,iBAAiB,CAAC;YAChD,KAAK,IAAMK,eAAeA,CAAC+C,cAAcH;YACzC,OAAO,CAAC7B,QAAUH,YAAYA,CAAC,sCAAsC,KAAKG;QAC5E;QAEA,OAAO;YACL,WAAWkC;YACX,gBAAgB;YAChB,QAAQ;QACV;IACF;IAEA,MAAMR,WAAyD;QAC7DK;QACA,YAAYN,UAAU,UAAU;IAClC;IACA,OAAOC;AACT;AAEO,MAAMS,sBAAsBA,GAAGvD,iBAAiB,CAAC,UACtDyB,OAA4C,EAC5CQ,QAAgC;IAEhC,OAAOT,cAAcA,CAACC;IACtB,MAAM+B,SAAS,OAAOxD,kBAAkB;IACxC,MAAMyD,aAAa,OAAOvD,uBAAuB,CAACuB,SAAS;QAAE+B;IAAO;IACpE,MAAMf,WAAWhB,QAAQ,OAAO,CAAChB,gBAAgBA,CAAC;IAClD,MAAMiD,iBACJjB,aAAad,YACTqB,0BAA0BA,CAACS,YAAYxB,YACvCO,qBAAqBA,CAACiB,YAAYhB,UAAUR;IAElD,OAAO,OAAOyB;AAChB,GAAG"}
|
|
@@ -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
|
-
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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.
|
|
11
|
-
|
|
12
|
-
|
|
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 |
|
|
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
|
|
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
|