effective-rsc 0.1.0 → 0.1.1

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 (52) hide show
  1. package/LLMS.md +48 -7
  2. package/dist/application/page.d.ts +14 -7
  3. package/dist/application/page.js +5 -3
  4. package/dist/application/page.js.map +1 -1
  5. package/dist/application/route-graph.d.ts +1 -1
  6. package/dist/application/route-graph.js.map +1 -1
  7. package/dist/application/server-fn.d.ts +10 -6
  8. package/dist/application/server-fn.js +9 -4
  9. package/dist/application/server-fn.js.map +1 -1
  10. package/dist/build/dev.d.ts +11 -11
  11. package/dist/build/dev.js +21 -5
  12. package/dist/build/dev.js.map +1 -1
  13. package/dist/cli.js +2 -0
  14. package/dist/cli.js.map +1 -1
  15. package/dist/client/application.js +4 -1
  16. package/dist/client/application.js.map +1 -1
  17. package/dist/client/browser-render-status.d.ts +25 -0
  18. package/dist/client/browser-render-status.js +20 -0
  19. package/dist/client/browser-render-status.js.map +1 -0
  20. package/dist/client/call-server.js +7 -2
  21. package/dist/client/call-server.js.map +1 -1
  22. package/dist/client/react-dom-renderer.d.ts +3 -2
  23. package/dist/client/react-dom-renderer.js +43 -2
  24. package/dist/client/react-dom-renderer.js.map +1 -1
  25. package/dist/client/route-refresh.d.ts +1 -2
  26. package/dist/client/route-refresh.js +5 -18
  27. package/dist/client/route-refresh.js.map +1 -1
  28. package/dist/dev/client.d.ts +2 -1
  29. package/dist/dev/client.js +32 -5
  30. package/dist/dev/client.js.map +1 -1
  31. package/dist/dev/panel.d.ts +6 -1
  32. package/dist/dev/panel.js +18 -3
  33. package/dist/dev/panel.js.map +1 -1
  34. package/dist/dev/runtime-failure.d.ts +1 -0
  35. package/dist/dev/runtime-failure.js +7 -1
  36. package/dist/dev/runtime-failure.js.map +1 -1
  37. package/dist/rsc/render-route-tree.d.ts +3 -3
  38. package/dist/rsc/render-route-tree.js +2 -2
  39. package/dist/rsc/render-route-tree.js.map +1 -1
  40. package/dist/server/application.js +54 -35
  41. package/dist/server/application.js.map +1 -1
  42. package/dist/server/server-fn-request.js +3 -1
  43. package/dist/server/server-fn-request.js.map +1 -1
  44. package/docs/02-guides/01-server-functions/50_greet.ts +18 -0
  45. package/docs/02-guides/01-server-functions/60_greeting-form.tsx +23 -0
  46. package/docs/02-guides/01-server-functions/index.md +7 -0
  47. package/docs/02-guides/03-routing/index.md +5 -1
  48. package/docs/03-advanced/03-server-function-execution-and-refresh/index.md +3 -1
  49. package/docs/04-api-reference/02-page/index.md +8 -3
  50. package/docs/04-api-reference/06-middleware/index.md +2 -2
  51. package/docs/04-api-reference/08-server-fn/index.md +23 -0
  52. package/package.json +1 -1
@@ -1,6 +1,7 @@
1
1
  import { Effect, Layer } from "effect";
2
2
  import { checkBrowserCapabilities } from "./browser-capabilities.js";
3
3
  import { BrowserEffectRunner } from "./browser-effect-runner.js";
4
+ import { BrowserRenderStatus } from "./browser-render-status.js";
4
5
  import { BrowserRenderer } from "./browser-renderer.js";
5
6
  import { showBrowserFailure } from "./browser-screen.js";
6
7
  import { installCallServer } from "./call-server.js";
@@ -27,6 +28,7 @@ import * as __rspack_external__effect_platform_browser_BrowserHttpClient_04160dc
27
28
 
28
29
 
29
30
 
31
+
30
32
  // oxlint-disable effecttsgo/process-env, effecttsgo/process-env-in-effect -- Rspack replaces NODE_ENV at compile time.
31
33
 
32
34
 
@@ -42,7 +44,8 @@ import * as __rspack_external__effect_platform_browser_BrowserHttpClient_04160dc
42
44
 
43
45
 
44
46
 
45
- const BrowserServicesLayer = Layer.mergeAll(BrowserEffectRunner.layer, BrowserRenderer.layer, FlightClient.layer, NavigationApi.layer).pipe(Layer.provide(InitialFlightStream.layer));
47
+
48
+ const BrowserServicesLayer = Layer.mergeAll(BrowserEffectRunner.layer, BrowserRenderer.layer, BrowserRenderStatus.layer, FlightClient.layer, NavigationApi.layer).pipe(Layer.provide(InitialFlightStream.layer));
46
49
  const BrowserLayer = Layer.mergeAll(ReactDOMRenderer.layer, RouteLoader.layer, RouteRefresher.layer).pipe(Layer.provideMerge(BrowserServicesLayer), Layer.provide(__rspack_external__effect_platform_browser_BrowserHttpClient_04160dce.layerFetch));
47
50
  const renderBrowserFailure = Effect.sync(showBrowserFailure);
48
51
  const skipHydration = (missingApi)=>process.env.NODE_ENV === 'development' ? Effect.logWarning(`effective-rsc did not hydrate because this browser does not provide ${missingApi}. The server-rendered document remains a plain multi-page application.`) : Effect["void"];
@@ -1 +1 @@
1
- {"version":3,"file":"client/application.js","sources":["../../src/client/application.ts"],"sourcesContent":["// oxlint-disable effecttsgo/process-env, effecttsgo/process-env-in-effect -- Rspack replaces NODE_ENV at compile time.\nimport * as BrowserHttpClient from '@effect/platform-browser/BrowserHttpClient';\nimport { Effect, Layer } from 'effect';\n\nimport { checkBrowserCapabilities } from './browser-capabilities';\nimport { BrowserEffectRunner } from './browser-effect-runner';\nimport { BrowserRenderer } from './browser-renderer';\nimport { showBrowserFailure } from './browser-screen';\nimport { installCallServer } from './call-server';\nimport { installClientRouter } from './client-router';\nimport { FlightClient } from './flight-client';\nimport { InitialFlightStream } from './initial-flight-stream';\nimport { NavigationApi } from './navigation-api';\nimport { ReactDOMRenderer } from './react-dom-renderer';\nimport { RouteLoader } from './route-loader';\nimport { installRouteRefresh, RouteRefresher } from './route-refresh';\n\nconst BrowserServicesLayer = Layer.mergeAll(\n BrowserEffectRunner.layer,\n BrowserRenderer.layer,\n FlightClient.layer,\n NavigationApi.layer,\n).pipe(Layer.provide(InitialFlightStream.layer));\n\nconst BrowserLayer = Layer.mergeAll(\n ReactDOMRenderer.layer,\n RouteLoader.layer,\n RouteRefresher.layer,\n).pipe(Layer.provideMerge(BrowserServicesLayer), Layer.provide(BrowserHttpClient.layerFetch));\n\nconst renderBrowserFailure = Effect.sync(showBrowserFailure);\n\nconst skipHydration = (missingApi: string) =>\n process.env.NODE_ENV === 'development'\n ? Effect.logWarning(\n `effective-rsc did not hydrate because this browser does not provide ${missingApi}. The server-rendered document remains a plain multi-page application.`,\n )\n : Effect.void;\n\nconst activateClientNavigation = Effect.gen(function* () {\n const routeLoader = yield* RouteLoader;\n const reactDOMRenderer = yield* ReactDOMRenderer;\n\n const initialPayload = yield* routeLoader.loadInitial;\n yield* reactDOMRenderer.hydrate(document, initialPayload);\n yield* installClientRouter;\n yield* installRouteRefresh;\n yield* installCallServer;\n});\n\nexport const browserMain = Effect.scoped(\n Effect.gen(function* () {\n if (process.env.NODE_ENV === 'development') {\n yield* Effect.tryPromise(() => import('../dev/client')).pipe(\n Effect.flatMap(({ startDevClient }) => startDevClient),\n Effect.catch((cause) => Effect.logError('Development client stopped.', cause)),\n Effect.forkScoped,\n );\n }\n\n yield* checkBrowserCapabilities.pipe(\n Effect.andThen(activateClientNavigation),\n Effect.catchTags({\n FlightLoadError: () => renderBrowserFailure,\n ReactDOMHydrationError: () => renderBrowserFailure,\n NavigationApiUnavailableError: () => skipHydration('the Navigation API'),\n NavigationPrecommitUnavailableError: () => skipHydration('NavigationPrecommitController'),\n }),\n );\n return yield* Effect.never;\n }),\n).pipe(Effect.provide(BrowserLayer));\n"],"names":["BrowserHttpClient","Effect","Layer","checkBrowserCapabilities","BrowserEffectRunner","BrowserRenderer","showBrowserFailure","installCallServer","installClientRouter","FlightClient","InitialFlightStream","NavigationApi","ReactDOMRenderer","RouteLoader","installRouteRefresh","RouteRefresher","BrowserServicesLayer","BrowserLayer","renderBrowserFailure","skipHydration","missingApi","process","activateClientNavigation","routeLoader","reactDOMRenderer","initialPayload","document","browserMain","startDevClient","cause"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uHAAuH;AACvC;AACzC;AAE2B;AACJ;AACT;AACC;AACJ;AACI;AACP;AACe;AACb;AACO;AACX;AACyB;AAEtE,MAAMgB,oBAAoBA,GAAGd,cAAc,CACzCE,yBAAyB,EACzBC,qBAAqB,EACrBI,kBAAkB,EAClBE,mBAAmB,EACnB,IAAI,CAACT,aAAa,CAACQ,yBAAyB;AAE9C,MAAMO,YAAYA,GAAGf,cAAc,CACjCU,sBAAsB,EACtBC,iBAAiB,EACjBE,oBAAoB,EACpB,IAAI,CAACb,kBAAkB,CAACc,oBAAoBA,GAAGd,aAAa,CAACF,gFAA4B;AAE3F,MAAMkB,oBAAoBA,GAAGjB,WAAW,CAACK,kBAAkBA;AAE3D,MAAMa,aAAaA,GAAG,CAACC,aACrBC,QAAQ,GAAG,CAAC,QAAQ,KAAK,gBACrBpB,iBAAiB,CACf,CAAC,oEAAoE,EAAEmB,WAAW,sEAAsE,CAAC,IAE3JnB,cAAW;AAEjB,MAAMqB,wBAAwBA,GAAGrB,UAAU,CAAC;IAC1C,MAAMsB,cAAc,OAAOV,WAAWA;IACtC,MAAMW,mBAAmB,OAAOZ,gBAAgBA;IAEhD,MAAMa,iBAAiB,OAAOF,YAAY,WAAW;IACrD,OAAOC,iBAAiB,OAAO,CAACE,UAAUD;IAC1C,OAAOjB,mBAAmBA;IAC1B,OAAOM,mBAAmBA;IAC1B,OAAOP,iBAAiBA;AAC1B;AAEO,MAAMoB,WAAWA,GAAG1B,aAAa,CACtCA,UAAU,CAAC;IACT,IAAIoB,QAAQ,GAAG,CAAC,QAAQ,KAAK,eAAe;QAC1C,OAAOpB,iBAAiB,CAAC,IAAM,0BAAuB,EAAE,IAAI,CAC1DA,cAAc,CAAC,CAAC,EAAE2B,cAAc,EAAE,GAAKA,iBACvC3B,eAAY,CAAC,CAAC4B,QAAU5B,eAAe,CAAC,+BAA+B4B,SACvE5B,iBAAiB;IAErB;IAEA,OAAOE,6BAA6B,CAClCF,cAAc,CAACqB,wBAAwBA,GACvCrB,gBAAgB,CAAC;QACf,iBAAiB,IAAMiB,oBAAoBA;QAC3C,wBAAwB,IAAMA,oBAAoBA;QAClD,+BAA+B,IAAMC,aAAaA,CAAC;QACnD,qCAAqC,IAAMA,aAAaA,CAAC;IAC3D;IAEF,OAAO,OAAOlB,YAAY;AAC5B,IACA,IAAI,CAACA,cAAc,CAACgB,YAAYA,GAAG"}
1
+ {"version":3,"file":"client/application.js","sources":["../../src/client/application.ts"],"sourcesContent":["// oxlint-disable effecttsgo/process-env, effecttsgo/process-env-in-effect -- Rspack replaces NODE_ENV at compile time.\nimport * as BrowserHttpClient from '@effect/platform-browser/BrowserHttpClient';\nimport { Effect, Layer } from 'effect';\n\nimport { checkBrowserCapabilities } from './browser-capabilities';\nimport { BrowserEffectRunner } from './browser-effect-runner';\nimport { BrowserRenderStatus } from './browser-render-status';\nimport { BrowserRenderer } from './browser-renderer';\nimport { showBrowserFailure } from './browser-screen';\nimport { installCallServer } from './call-server';\nimport { installClientRouter } from './client-router';\nimport { FlightClient } from './flight-client';\nimport { InitialFlightStream } from './initial-flight-stream';\nimport { NavigationApi } from './navigation-api';\nimport { ReactDOMRenderer } from './react-dom-renderer';\nimport { RouteLoader } from './route-loader';\nimport { installRouteRefresh, RouteRefresher } from './route-refresh';\n\nconst BrowserServicesLayer = Layer.mergeAll(\n BrowserEffectRunner.layer,\n BrowserRenderer.layer,\n BrowserRenderStatus.layer,\n FlightClient.layer,\n NavigationApi.layer,\n).pipe(Layer.provide(InitialFlightStream.layer));\n\nconst BrowserLayer = Layer.mergeAll(\n ReactDOMRenderer.layer,\n RouteLoader.layer,\n RouteRefresher.layer,\n).pipe(Layer.provideMerge(BrowserServicesLayer), Layer.provide(BrowserHttpClient.layerFetch));\n\nconst renderBrowserFailure = Effect.sync(showBrowserFailure);\n\nconst skipHydration = (missingApi: string) =>\n process.env.NODE_ENV === 'development'\n ? Effect.logWarning(\n `effective-rsc did not hydrate because this browser does not provide ${missingApi}. The server-rendered document remains a plain multi-page application.`,\n )\n : Effect.void;\n\nconst activateClientNavigation = Effect.gen(function* () {\n const routeLoader = yield* RouteLoader;\n const reactDOMRenderer = yield* ReactDOMRenderer;\n\n const initialPayload = yield* routeLoader.loadInitial;\n yield* reactDOMRenderer.hydrate(document, initialPayload);\n yield* installClientRouter;\n yield* installRouteRefresh;\n yield* installCallServer;\n});\n\nexport const browserMain = Effect.scoped(\n Effect.gen(function* () {\n if (process.env.NODE_ENV === 'development') {\n yield* Effect.tryPromise(() => import('../dev/client')).pipe(\n Effect.flatMap(({ startDevClient }) => startDevClient),\n Effect.catch((cause) => Effect.logError('Development client stopped.', cause)),\n Effect.forkScoped,\n );\n }\n\n yield* checkBrowserCapabilities.pipe(\n Effect.andThen(activateClientNavigation),\n Effect.catchTags({\n FlightLoadError: () => renderBrowserFailure,\n ReactDOMHydrationError: () => renderBrowserFailure,\n NavigationApiUnavailableError: () => skipHydration('the Navigation API'),\n NavigationPrecommitUnavailableError: () => skipHydration('NavigationPrecommitController'),\n }),\n );\n return yield* Effect.never;\n }),\n).pipe(Effect.provide(BrowserLayer));\n"],"names":["BrowserHttpClient","Effect","Layer","checkBrowserCapabilities","BrowserEffectRunner","BrowserRenderStatus","BrowserRenderer","showBrowserFailure","installCallServer","installClientRouter","FlightClient","InitialFlightStream","NavigationApi","ReactDOMRenderer","RouteLoader","installRouteRefresh","RouteRefresher","BrowserServicesLayer","BrowserLayer","renderBrowserFailure","skipHydration","missingApi","process","activateClientNavigation","routeLoader","reactDOMRenderer","initialPayload","document","browserMain","startDevClient","cause"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uHAAuH;AACvC;AACzC;AAE2B;AACJ;AACA;AACT;AACC;AACJ;AACI;AACP;AACe;AACb;AACO;AACX;AACyB;AAEtE,MAAMiB,oBAAoBA,GAAGf,cAAc,CACzCE,yBAAyB,EACzBE,qBAAqB,EACrBD,yBAAyB,EACzBK,kBAAkB,EAClBE,mBAAmB,EACnB,IAAI,CAACV,aAAa,CAACS,yBAAyB;AAE9C,MAAMO,YAAYA,GAAGhB,cAAc,CACjCW,sBAAsB,EACtBC,iBAAiB,EACjBE,oBAAoB,EACpB,IAAI,CAACd,kBAAkB,CAACe,oBAAoBA,GAAGf,aAAa,CAACF,gFAA4B;AAE3F,MAAMmB,oBAAoBA,GAAGlB,WAAW,CAACM,kBAAkBA;AAE3D,MAAMa,aAAaA,GAAG,CAACC,aACrBC,QAAQ,GAAG,CAAC,QAAQ,KAAK,gBACrBrB,iBAAiB,CACf,CAAC,oEAAoE,EAAEoB,WAAW,sEAAsE,CAAC,IAE3JpB,cAAW;AAEjB,MAAMsB,wBAAwBA,GAAGtB,UAAU,CAAC;IAC1C,MAAMuB,cAAc,OAAOV,WAAWA;IACtC,MAAMW,mBAAmB,OAAOZ,gBAAgBA;IAEhD,MAAMa,iBAAiB,OAAOF,YAAY,WAAW;IACrD,OAAOC,iBAAiB,OAAO,CAACE,UAAUD;IAC1C,OAAOjB,mBAAmBA;IAC1B,OAAOM,mBAAmBA;IAC1B,OAAOP,iBAAiBA;AAC1B;AAEO,MAAMoB,WAAWA,GAAG3B,aAAa,CACtCA,UAAU,CAAC;IACT,IAAIqB,QAAQ,GAAG,CAAC,QAAQ,KAAK,eAAe;QAC1C,OAAOrB,iBAAiB,CAAC,IAAM,0BAAuB,EAAE,IAAI,CAC1DA,cAAc,CAAC,CAAC,EAAE4B,cAAc,EAAE,GAAKA,iBACvC5B,eAAY,CAAC,CAAC6B,QAAU7B,eAAe,CAAC,+BAA+B6B,SACvE7B,iBAAiB;IAErB;IAEA,OAAOE,6BAA6B,CAClCF,cAAc,CAACsB,wBAAwBA,GACvCtB,gBAAgB,CAAC;QACf,iBAAiB,IAAMkB,oBAAoBA;QAC3C,wBAAwB,IAAMA,oBAAoBA;QAClD,+BAA+B,IAAMC,aAAaA,CAAC;QACnD,qCAAqC,IAAMA,aAAaA,CAAC;IAC3D;IAEF,OAAO,OAAOnB,YAAY;AAC5B,IACA,IAAI,CAACA,cAAc,CAACiB,YAAYA,GAAG"}
@@ -0,0 +1,25 @@
1
+ import { Context, Effect, Layer } from 'effect';
2
+ type RenderStatus = {
3
+ readonly _tag: 'Waiting';
4
+ } | {
5
+ readonly _tag: 'Rendered';
6
+ } | {
7
+ readonly _tag: 'Failed';
8
+ readonly error: unknown;
9
+ readonly componentStack: string | null;
10
+ };
11
+ declare const BrowserRenderStatus_base: Context.ServiceClass<BrowserRenderStatus, "ersc/client/BrowserRenderStatus", {
12
+ changes: import("effect/Stream").Stream<RenderStatus, never, never>;
13
+ get: Effect.Effect<RenderStatus, never, never>;
14
+ report: (value: RenderStatus) => Effect.Effect<void, never, never>;
15
+ }> & {
16
+ readonly make: Effect.Effect<{
17
+ changes: import("effect/Stream").Stream<RenderStatus, never, never>;
18
+ get: Effect.Effect<RenderStatus, never, never>;
19
+ report: (value: RenderStatus) => Effect.Effect<void, never, never>;
20
+ }, never, never>;
21
+ };
22
+ export declare class BrowserRenderStatus extends BrowserRenderStatus_base {
23
+ static readonly layer: Layer.Layer<BrowserRenderStatus, never, never>;
24
+ }
25
+ export {};
@@ -0,0 +1,20 @@
1
+ import { Context, Effect, Layer, SubscriptionRef } from "effect";
2
+
3
+
4
+
5
+ // Replay the latest status: diagnostics may start after hydration has already failed.
6
+ class BrowserRenderStatus extends Context.Service()('ersc/client/BrowserRenderStatus', {
7
+ make: SubscriptionRef.make({
8
+ _tag: 'Waiting'
9
+ }).pipe(Effect.map((status)=>({
10
+ changes: SubscriptionRef.changes(status),
11
+ get: SubscriptionRef.get(status),
12
+ report: (value)=>SubscriptionRef.set(status, value)
13
+ })))
14
+ }) {
15
+ static layer = Layer.effect(this, this.make);
16
+ }
17
+
18
+ export { BrowserRenderStatus };
19
+
20
+ //# sourceMappingURL=browser-render-status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client/browser-render-status.js","sources":["../../src/client/browser-render-status.ts"],"sourcesContent":["import { Context, Effect, Layer, SubscriptionRef } from 'effect';\n\ntype RenderStatus =\n | { readonly _tag: 'Waiting' }\n | { readonly _tag: 'Rendered' }\n | { readonly _tag: 'Failed'; readonly error: unknown; readonly componentStack: string | null };\n\n// Replay the latest status: diagnostics may start after hydration has already failed.\nexport class BrowserRenderStatus extends Context.Service<BrowserRenderStatus>()(\n 'ersc/client/BrowserRenderStatus',\n {\n make: SubscriptionRef.make<RenderStatus>({ _tag: 'Waiting' }).pipe(\n Effect.map((status) => ({\n changes: SubscriptionRef.changes(status),\n get: SubscriptionRef.get(status),\n report: (value: RenderStatus) => SubscriptionRef.set(status, value),\n })),\n ),\n },\n) {\n static readonly layer = Layer.effect(this, this.make);\n}\n"],"names":["Context","Effect","Layer","SubscriptionRef","BrowserRenderStatus","status","value"],"mappings":";;;AAAiE;AAOjE,sFAAsF;AAC/E,MAAMI,mBAAmBA,SAASJ,eAAe,GACtD,mCACA;IACE,MAAMG,oBAAoB,CAAe;QAAE,MAAM;IAAU,GAAG,IAAI,CAChEF,UAAU,CAAC,CAACI,SAAY;YACtB,SAASF,uBAAuB,CAACE;YACjC,KAAKF,mBAAmB,CAACE;YACzB,QAAQ,CAACC,QAAwBH,mBAAmB,CAACE,QAAQC;QAC/D;AAEJ;IAEA,OAAgB,QAAQJ,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;AACxD"}
@@ -113,12 +113,17 @@ const installCallServer = Effect.gen(function*() {
113
113
  // React registered its Action reactions before the request completed. Registering ERSC's
114
114
  // continuation after settlement lets React close that Action before the refresh Transition.
115
115
  yield* Effect.promise(()=>invocationResult.promise.then(()=>undefined, ()=>undefined));
116
- if (selectRefreshSource(invocation) === 'CurrentRoute') {
116
+ let refreshSource = selectRefreshSource(invocation);
117
+ if (refreshSource === 'Response') {
118
+ yield* routeRefresher.interruptCurrentRouteRefresh;
119
+ // Interruption awaits cleanup, during which navigation or another invocation can win.
120
+ refreshSource = selectRefreshSource(invocation);
121
+ }
122
+ if (refreshSource === 'CurrentRoute') {
117
123
  yield* resource.release;
118
124
  yield* routeRefresher.refreshCurrentRoute('server-function');
119
125
  return;
120
126
  }
121
- yield* routeRefresher.interruptCurrentRouteRefresh;
122
127
  const commitRefresh = routeLoader.prepareRefresh(resource.payload.routeTree);
123
128
  let renderCommitted;
124
129
  yield* Effect.sync(()=>{
@@ -1 +1 @@
1
- {"version":3,"file":"client/call-server.js","sources":["../../src/client/call-server.ts"],"sourcesContent":["import { Effect, MutableRef, Schema } from 'effect';\nimport { addTransitionType, startTransition } from 'react';\nimport {\n createTemporaryReferenceSet,\n encodeReply,\n setServerCallback,\n} from 'react-server-dom-rspack/client.browser';\n\nimport { BrowserEffectRunner } from './browser-effect-runner';\nimport { BrowserRenderer } from './browser-renderer';\nimport { FlightClient } from './flight-client';\nimport { NavigationApi } from './navigation-api';\nimport { RouteLoader } from './route-loader';\nimport { RouteRefresher } from './route-refresh';\n\nclass ServerFnCallError extends Schema.TaggedError<ServerFnCallError>()('ServerFnCallError', {\n cause: Schema.Defect(),\n message: Schema.String,\n}) {}\n\ntype ServerFnInvocation =\n | {\n readonly _tag: 'HistoryEntry';\n readonly id: string;\n readonly order: number;\n readonly url: string;\n }\n | { readonly _tag: 'CurrentUrl'; readonly order: number; readonly url: string };\n\ntype ServerFnRefreshSource = 'Response' | 'CurrentRoute';\n\nexport const installCallServer = Effect.gen(function* () {\n const browserRenderer = yield* BrowserRenderer;\n const navigationApi = yield* NavigationApi;\n const run = yield* BrowserEffectRunner;\n const flightClient = yield* FlightClient;\n const routeLoader = yield* RouteLoader;\n const routeRefresher = yield* RouteRefresher;\n const latestInvocationOrder = MutableRef.make(0);\n const selectRefreshSource = (invocation: ServerFnInvocation): ServerFnRefreshSource => {\n if (\n MutableRef.get(latestInvocationOrder) !== invocation.order ||\n navigationApi.getTransition() !== null\n ) {\n return 'CurrentRoute';\n }\n\n const currentEntry = navigationApi.getCurrentEntry();\n switch (invocation._tag) {\n case 'HistoryEntry':\n return currentEntry?.id === invocation.id ? 'Response' : 'CurrentRoute';\n case 'CurrentUrl':\n return currentEntry === null && navigationApi.getCurrentUrl() === invocation.url\n ? 'Response'\n : 'CurrentRoute';\n }\n };\n const callServer = Effect.fnUntraced(function* (\n id: string,\n args: ReadonlyArray<unknown>,\n invocationResult: PromiseWithResolvers<unknown>,\n ) {\n const currentEntry = navigationApi.getCurrentEntry();\n const order = MutableRef.incrementAndGet(latestInvocationOrder);\n const invocation: ServerFnInvocation =\n currentEntry === null\n ? { _tag: 'CurrentUrl', order, url: navigationApi.getCurrentUrl() }\n : {\n _tag: 'HistoryEntry',\n id: currentEntry.id,\n order,\n url: currentEntry.url ?? navigationApi.getCurrentUrl(),\n };\n const temporaryReferences = createTemporaryReferenceSet();\n const body = yield* Effect.tryPromise({\n try: () => encodeReply(args, { temporaryReferences }),\n catch: (cause) => new ServerFnCallError({ cause, message: 'Failed to encode arguments.' }),\n });\n const resource = yield* flightClient\n .load({\n _tag: 'ServerFunction',\n body,\n destination: new URL(invocation.url),\n id,\n temporaryReferences,\n })\n .pipe(\n Effect.mapError(\n (cause) => new ServerFnCallError({ cause, message: 'Server Function request failed.' }),\n ),\n );\n if (resource._tag === 'Document') {\n yield* resource.release;\n return yield* new ServerFnCallError({\n cause: new Error('A Server Function response cannot request document navigation.'),\n message: 'Server Function response was incompatible with Flight.',\n });\n }\n const serverFnResult = resource.payload.serverFnResult;\n if (serverFnResult === null) {\n yield* resource.release;\n return yield* new ServerFnCallError({\n cause: new Error('The Flight payload omitted the Server Function return value.'),\n message: 'Server Function response was incomplete.',\n });\n }\n switch (serverFnResult._tag) {\n case 'Failure':\n invocationResult.reject(\n new ServerFnCallError({\n cause: serverFnResult.error,\n message: 'Server Function execution failed.',\n }),\n );\n break;\n case 'Success':\n invocationResult.resolve(serverFnResult.value);\n break;\n }\n // React registered its Action reactions before the request completed. Registering ERSC's\n // continuation after settlement lets React close that Action before the refresh Transition.\n yield* Effect.promise(() =>\n invocationResult.promise.then(\n () => undefined,\n () => undefined,\n ),\n );\n if (selectRefreshSource(invocation) === 'CurrentRoute') {\n yield* resource.release;\n yield* routeRefresher.refreshCurrentRoute('server-function');\n return;\n }\n\n yield* routeRefresher.interruptCurrentRouteRefresh;\n const commitRefresh = routeLoader.prepareRefresh(resource.payload.routeTree);\n let renderCommitted!: Promise<void>;\n yield* Effect.sync(() => {\n startTransition(() => {\n addTransitionType('server-function');\n // Do not return the commit Promise from React's Transition Action. React cannot commit the\n // render until that Action ends.\n renderCommitted = browserRenderer.refresh(resource.payload.routeTree);\n });\n });\n yield* Effect.all([resource.completed, Effect.promise(() => renderCommitted)], {\n concurrency: 'unbounded',\n discard: true,\n }).pipe(\n Effect.andThen(Effect.sync(commitRefresh)),\n Effect.onError(() => resource.release),\n Effect.forkScoped,\n );\n });\n\n yield* Effect.sync(() => {\n setServerCallback((id, args) => {\n const invocationResult = Promise.withResolvers<unknown>();\n void run(callServer(id, args, invocationResult)).catch(invocationResult.reject);\n return invocationResult.promise;\n });\n });\n});\n"],"names":["Effect","MutableRef","Schema","addTransitionType","startTransition","createTemporaryReferenceSet","encodeReply","setServerCallback","BrowserEffectRunner","BrowserRenderer","FlightClient","NavigationApi","RouteLoader","RouteRefresher","ServerFnCallError","installCallServer","browserRenderer","navigationApi","run","flightClient","routeLoader","routeRefresher","latestInvocationOrder","selectRefreshSource","invocation","currentEntry","callServer","id","args","invocationResult","order","temporaryReferences","body","cause","resource","URL","Error","serverFnResult","undefined","commitRefresh","renderCommitted","Promise"],"mappings":"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"}
1
+ {"version":3,"file":"client/call-server.js","sources":["../../src/client/call-server.ts"],"sourcesContent":["import { Effect, MutableRef, Schema } from 'effect';\nimport { addTransitionType, startTransition } from 'react';\nimport {\n createTemporaryReferenceSet,\n encodeReply,\n setServerCallback,\n} from 'react-server-dom-rspack/client.browser';\n\nimport { BrowserEffectRunner } from './browser-effect-runner';\nimport { BrowserRenderer } from './browser-renderer';\nimport { FlightClient } from './flight-client';\nimport { NavigationApi } from './navigation-api';\nimport { RouteLoader } from './route-loader';\nimport { RouteRefresher } from './route-refresh';\n\nclass ServerFnCallError extends Schema.TaggedError<ServerFnCallError>()('ServerFnCallError', {\n cause: Schema.Defect(),\n message: Schema.String,\n}) {}\n\ntype ServerFnInvocation =\n | {\n readonly _tag: 'HistoryEntry';\n readonly id: string;\n readonly order: number;\n readonly url: string;\n }\n | { readonly _tag: 'CurrentUrl'; readonly order: number; readonly url: string };\n\ntype ServerFnRefreshSource = 'Response' | 'CurrentRoute';\n\nexport const installCallServer = Effect.gen(function* () {\n const browserRenderer = yield* BrowserRenderer;\n const navigationApi = yield* NavigationApi;\n const run = yield* BrowserEffectRunner;\n const flightClient = yield* FlightClient;\n const routeLoader = yield* RouteLoader;\n const routeRefresher = yield* RouteRefresher;\n const latestInvocationOrder = MutableRef.make(0);\n const selectRefreshSource = (invocation: ServerFnInvocation): ServerFnRefreshSource => {\n if (\n MutableRef.get(latestInvocationOrder) !== invocation.order ||\n navigationApi.getTransition() !== null\n ) {\n return 'CurrentRoute';\n }\n\n const currentEntry = navigationApi.getCurrentEntry();\n switch (invocation._tag) {\n case 'HistoryEntry':\n return currentEntry?.id === invocation.id ? 'Response' : 'CurrentRoute';\n case 'CurrentUrl':\n return currentEntry === null && navigationApi.getCurrentUrl() === invocation.url\n ? 'Response'\n : 'CurrentRoute';\n }\n };\n const callServer = Effect.fnUntraced(function* (\n id: string,\n args: ReadonlyArray<unknown>,\n invocationResult: PromiseWithResolvers<unknown>,\n ) {\n const currentEntry = navigationApi.getCurrentEntry();\n const order = MutableRef.incrementAndGet(latestInvocationOrder);\n const invocation: ServerFnInvocation =\n currentEntry === null\n ? { _tag: 'CurrentUrl', order, url: navigationApi.getCurrentUrl() }\n : {\n _tag: 'HistoryEntry',\n id: currentEntry.id,\n order,\n url: currentEntry.url ?? navigationApi.getCurrentUrl(),\n };\n const temporaryReferences = createTemporaryReferenceSet();\n const body = yield* Effect.tryPromise({\n try: () => encodeReply(args, { temporaryReferences }),\n catch: (cause) => new ServerFnCallError({ cause, message: 'Failed to encode arguments.' }),\n });\n const resource = yield* flightClient\n .load({\n _tag: 'ServerFunction',\n body,\n destination: new URL(invocation.url),\n id,\n temporaryReferences,\n })\n .pipe(\n Effect.mapError(\n (cause) => new ServerFnCallError({ cause, message: 'Server Function request failed.' }),\n ),\n );\n if (resource._tag === 'Document') {\n yield* resource.release;\n return yield* new ServerFnCallError({\n cause: new Error('A Server Function response cannot request document navigation.'),\n message: 'Server Function response was incompatible with Flight.',\n });\n }\n const serverFnResult = resource.payload.serverFnResult;\n if (serverFnResult === null) {\n yield* resource.release;\n return yield* new ServerFnCallError({\n cause: new Error('The Flight payload omitted the Server Function return value.'),\n message: 'Server Function response was incomplete.',\n });\n }\n switch (serverFnResult._tag) {\n case 'Failure':\n invocationResult.reject(\n new ServerFnCallError({\n cause: serverFnResult.error,\n message: 'Server Function execution failed.',\n }),\n );\n break;\n case 'Success':\n invocationResult.resolve(serverFnResult.value);\n break;\n }\n // React registered its Action reactions before the request completed. Registering ERSC's\n // continuation after settlement lets React close that Action before the refresh Transition.\n yield* Effect.promise(() =>\n invocationResult.promise.then(\n () => undefined,\n () => undefined,\n ),\n );\n let refreshSource = selectRefreshSource(invocation);\n if (refreshSource === 'Response') {\n yield* routeRefresher.interruptCurrentRouteRefresh;\n // Interruption awaits cleanup, during which navigation or another invocation can win.\n refreshSource = selectRefreshSource(invocation);\n }\n\n if (refreshSource === 'CurrentRoute') {\n yield* resource.release;\n yield* routeRefresher.refreshCurrentRoute('server-function');\n return;\n }\n const commitRefresh = routeLoader.prepareRefresh(resource.payload.routeTree);\n let renderCommitted!: Promise<void>;\n yield* Effect.sync(() => {\n startTransition(() => {\n addTransitionType('server-function');\n // Do not return the commit Promise from React's Transition Action. React cannot commit the\n // render until that Action ends.\n renderCommitted = browserRenderer.refresh(resource.payload.routeTree);\n });\n });\n yield* Effect.all([resource.completed, Effect.promise(() => renderCommitted)], {\n concurrency: 'unbounded',\n discard: true,\n }).pipe(\n Effect.andThen(Effect.sync(commitRefresh)),\n Effect.onError(() => resource.release),\n Effect.forkScoped,\n );\n });\n\n yield* Effect.sync(() => {\n setServerCallback((id, args) => {\n const invocationResult = Promise.withResolvers<unknown>();\n void run(callServer(id, args, invocationResult)).catch(invocationResult.reject);\n return invocationResult.promise;\n });\n });\n});\n"],"names":["Effect","MutableRef","Schema","addTransitionType","startTransition","createTemporaryReferenceSet","encodeReply","setServerCallback","BrowserEffectRunner","BrowserRenderer","FlightClient","NavigationApi","RouteLoader","RouteRefresher","ServerFnCallError","installCallServer","browserRenderer","navigationApi","run","flightClient","routeLoader","routeRefresher","latestInvocationOrder","selectRefreshSource","invocation","currentEntry","callServer","id","args","invocationResult","order","temporaryReferences","body","cause","resource","URL","Error","serverFnResult","undefined","refreshSource","commitRefresh","renderCommitted","Promise"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { Context, Effect, Layer, Schema } from 'effect';
2
2
  import type { FlightPayload } from '../rsc/flight.js';
3
3
  import { BrowserEffectRunner } from './browser-effect-runner.js';
4
+ import { BrowserRenderStatus } from './browser-render-status.js';
4
5
  import { BrowserRenderer } from './browser-renderer.js';
5
6
  declare const ReactDOMHydrationError_base: Schema.Class<ReactDOMHydrationError, Schema.TaggedStruct<"ReactDOMHydrationError", {
6
7
  readonly cause: Schema.Defect;
@@ -12,9 +13,9 @@ declare const ReactDOMRenderer_base: Context.ServiceClass<ReactDOMRenderer, "ers
12
13
  }> & {
13
14
  readonly make: Effect.Effect<{
14
15
  hydrate: (container: Document | Element, initialPayload: FlightPayload) => Effect.Effect<void, ReactDOMHydrationError, import("effect/Scope").Scope>;
15
- }, never, BrowserEffectRunner | BrowserRenderer>;
16
+ }, never, BrowserEffectRunner | BrowserRenderStatus | BrowserRenderer>;
16
17
  };
17
18
  export declare class ReactDOMRenderer extends ReactDOMRenderer_base {
18
- static readonly layer: Layer.Layer<ReactDOMRenderer, never, BrowserEffectRunner | BrowserRenderer>;
19
+ static readonly layer: Layer.Layer<ReactDOMRenderer, never, BrowserEffectRunner | BrowserRenderStatus | BrowserRenderer>;
19
20
  }
20
21
  export {};
@@ -3,6 +3,7 @@ import { Context, Effect, Layer, Schema } from "effect";
3
3
  import { Component, StrictMode, useLayoutEffect, useState } from "react";
4
4
  import { hydrateRoot } from "react-dom/client";
5
5
  import { BrowserEffectRunner } from "./browser-effect-runner.js";
6
+ import { BrowserRenderStatus } from "./browser-render-status.js";
6
7
  import { BrowserRenderer } from "./browser-renderer.js";
7
8
  import { BrowserFailureScreen } from "./browser-screen.js";
8
9
  import { RouteTree } from "./route-tree.js";
@@ -23,22 +24,47 @@ import { RouteTree } from "./route-tree.js";
23
24
 
24
25
 
25
26
 
27
+
28
+
26
29
  class ReactDOMHydrationError extends Schema.TaggedError()('ReactDOMHydrationError', {
27
30
  cause: Schema.Defect()
28
31
  }) {
29
32
  }
30
33
  class BrowserErrorBoundary extends Component {
31
34
  state = {
32
- _tag: 'Ready'
35
+ _tag: 'Uninitialized'
33
36
  };
34
37
  static getDerivedStateFromError() {
35
38
  return {
36
39
  _tag: 'Failed'
37
40
  };
38
41
  }
42
+ static getDerivedStateFromProps(props, state) {
43
+ if (state._tag !== 'Uninitialized' && props.render === state.render) {
44
+ return null;
45
+ }
46
+ // Discard acknowledges a cancelled candidate, not a request to retry the failed tree.
47
+ return state._tag === 'Failed' && props.render._tag === 'Discard' ? {
48
+ _tag: 'Failed',
49
+ render: props.render
50
+ } : {
51
+ _tag: 'Ready',
52
+ render: props.render
53
+ };
54
+ }
39
55
  componentDidCatch(error, info) {
40
56
  this.props.onError(error, info);
41
57
  }
58
+ componentDidMount() {
59
+ if (this.state._tag === 'Ready') {
60
+ this.props.onRendered();
61
+ }
62
+ }
63
+ componentDidUpdate() {
64
+ if (this.state._tag === 'Ready') {
65
+ this.props.onRendered();
66
+ }
67
+ }
42
68
  render() {
43
69
  if (this.state._tag === 'Failed') {
44
70
  return /*#__PURE__*/ jsx(BrowserFailureScreen, {});
@@ -50,10 +76,23 @@ class ReactDOMRenderer extends Context.Service()('ersc/client/ReactDOMRenderer',
50
76
  make: Effect.gen(function*() {
51
77
  const browserRenderer = yield* BrowserRenderer;
52
78
  const run = yield* BrowserEffectRunner;
79
+ const renderStatus = yield* BrowserRenderStatus;
53
80
  const hydrate = Effect.fnUntraced(function*(container, initialPayload) {
54
81
  const browserRendererReady = Promise.withResolvers();
55
82
  const reportError = (error, info)=>{
56
- void run(Effect.logError('Uncaught client render error.', error, info.componentStack));
83
+ void run(Effect.gen(function*() {
84
+ yield* renderStatus.report({
85
+ _tag: 'Failed',
86
+ error,
87
+ componentStack: info.componentStack ?? null
88
+ });
89
+ yield* Effect.logError('Uncaught client render error.', error, info.componentStack);
90
+ }));
91
+ };
92
+ const reportRendered = ()=>{
93
+ void run(renderStatus.report({
94
+ _tag: 'Rendered'
95
+ }));
57
96
  };
58
97
  function Root() {
59
98
  const [render, setRender] = useState(()=>({
@@ -71,6 +110,8 @@ class ReactDOMRenderer extends Context.Service()('ersc/client/ReactDOMRenderer',
71
110
  ]);
72
111
  return /*#__PURE__*/ jsx(BrowserErrorBoundary, {
73
112
  onError: reportError,
113
+ onRendered: reportRendered,
114
+ render: render,
74
115
  children: /*#__PURE__*/ jsx(RouteTree, {
75
116
  root: render.routeTree
76
117
  })
@@ -1 +1 @@
1
- {"version":3,"file":"client/react-dom-renderer.js","sources":["../../src/client/react-dom-renderer.tsx"],"sourcesContent":["import { Context, Effect, Layer, Schema } from 'effect';\nimport {\n Component,\n type ErrorInfo,\n type ReactNode,\n StrictMode,\n useLayoutEffect,\n useState,\n} from 'react';\nimport { hydrateRoot } from 'react-dom/client';\n\nimport type { FlightPayload } from '../rsc/flight';\nimport { BrowserEffectRunner } from './browser-effect-runner';\nimport { type BrowserRender, BrowserRenderer } from './browser-renderer';\nimport { BrowserFailureScreen } from './browser-screen';\nimport { RouteTree } from './route-tree';\n\nexport class ReactDOMHydrationError extends Schema.TaggedError<ReactDOMHydrationError>()(\n 'ReactDOMHydrationError',\n { cause: Schema.Defect() },\n) {}\n\ntype BrowserErrorBoundaryState = { readonly _tag: 'Ready' } | { readonly _tag: 'Failed' };\ntype BrowserErrorBoundaryProps = {\n readonly children: ReactNode;\n readonly onError: (error: unknown, info: ErrorInfo) => void;\n};\n\nclass BrowserErrorBoundary extends Component<BrowserErrorBoundaryProps, BrowserErrorBoundaryState> {\n override readonly state: BrowserErrorBoundaryState = { _tag: 'Ready' };\n\n static getDerivedStateFromError(): BrowserErrorBoundaryState {\n return { _tag: 'Failed' };\n }\n\n override componentDidCatch(error: unknown, info: ErrorInfo) {\n this.props.onError(error, info);\n }\n\n override render() {\n if (this.state._tag === 'Failed') {\n return <BrowserFailureScreen />;\n }\n\n return this.props.children;\n }\n}\n\nexport class ReactDOMRenderer extends Context.Service<ReactDOMRenderer>()(\n 'ersc/client/ReactDOMRenderer',\n {\n make: Effect.gen(function* () {\n const browserRenderer = yield* BrowserRenderer;\n const run = yield* BrowserEffectRunner;\n\n const hydrate = Effect.fnUntraced(function* (\n container: Element | Document,\n initialPayload: FlightPayload,\n ) {\n const browserRendererReady = Promise.withResolvers<void>();\n const reportError = (error: unknown, info: ErrorInfo) => {\n void run(Effect.logError('Uncaught client render error.', error, info.componentStack));\n };\n\n function Root() {\n const [render, setRender] = useState<BrowserRender>(() => ({\n _tag: 'Initial',\n routeTree: initialPayload.routeTree,\n }));\n\n useLayoutEffect(() => {\n browserRenderer.initialize(initialPayload.routeTree, setRender);\n browserRendererReady.resolve();\n }, []);\n\n useLayoutEffect(() => {\n browserRenderer.commit(render);\n }, [render]);\n\n return (\n <BrowserErrorBoundary onError={reportError}>\n <RouteTree root={render.routeTree} />\n </BrowserErrorBoundary>\n );\n }\n\n yield* Effect.acquireRelease(\n Effect.try({\n try: () =>\n hydrateRoot(\n container,\n <StrictMode>\n <Root />\n </StrictMode>,\n { formState: initialPayload.formState },\n ),\n catch: (cause) => new ReactDOMHydrationError({ cause }),\n }),\n (root) => Effect.sync(() => root.unmount()),\n );\n yield* Effect.promise(() => browserRendererReady.promise);\n });\n\n return { hydrate };\n }),\n },\n) {\n static readonly layer = Layer.effect(this, this.make);\n}\n"],"names":["Context","Effect","Layer","Schema","Component","StrictMode","useLayoutEffect","useState","hydrateRoot","BrowserEffectRunner","BrowserRenderer","BrowserFailureScreen","RouteTree","ReactDOMHydrationError","BrowserErrorBoundary","error","info","ReactDOMRenderer","browserRenderer","run","hydrate","container","initialPayload","browserRendererReady","Promise","reportError","Root","render","setRender","cause","root"],"mappings":";;;;;;;;;;;;;;;;;;AAAwD;AAQzC;AACgC;AAGe;AACW;AACjB;AACf;AAElC,MAAMa,sBAAsBA,SAASV,kBAAkB,GAC5D,0BACA;IAAE,OAAOA,aAAa;AAAG;AACxB;AAQH,MAAMW,oBAAoBA,SAASV,SAASA;IACxB,QAAmC;QAAE,MAAM;IAAQ,EAAE;IAEvE,OAAO,2BAAsD;QAC3D,OAAO;YAAE,MAAM;QAAS;IAC1B;IAES,kBAAkBW,KAAc,EAAEC,IAAe,EAAE;QAC1D,IAAI,CAAC,KAAK,CAAC,OAAO,CAACD,OAAOC;IAC5B;IAES,SAAS;QAChB,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,UAAU;YAChC,qBAAO,IAACL,oBAAoBA;QAC9B;QAEA,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ;IAC5B;AACF;AAEO,MAAMM,gBAAgBA,SAASjB,eAAe,GACnD,gCACA;IACE,MAAMC,UAAU,CAAC;QACf,MAAMiB,kBAAkB,OAAOR,eAAeA;QAC9C,MAAMS,MAAM,OAAOV,mBAAmBA;QAEtC,MAAMW,UAAUnB,iBAAiB,CAAC,UAChCoB,SAA6B,EAC7BC,cAA6B;YAE7B,MAAMC,uBAAuBC,QAAQ,aAAa;YAClD,MAAMC,cAAc,CAACV,OAAgBC;gBACnC,KAAKG,IAAIlB,eAAe,CAAC,iCAAiCc,OAAOC,KAAK,cAAc;YACtF;YAEA,SAASU;gBACP,MAAM,CAACC,QAAQC,UAAU,GAAGrB,QAAQA,CAAgB,IAAO;wBACzD,MAAM;wBACN,WAAWe,eAAe,SAAS;oBACrC;gBAEAhB,eAAeA,CAAC;oBACdY,gBAAgB,UAAU,CAACI,eAAe,SAAS,EAAEM;oBACrDL,qBAAqB,OAAO;gBAC9B,GAAG,EAAE;gBAELjB,eAAeA,CAAC;oBACdY,gBAAgB,MAAM,CAACS;gBACzB,GAAG;oBAACA;iBAAO;gBAEX,qBACE,IAACb,oBAAoBA;oBAAC,SAASW;8BAC7B,kBAACb,SAASA;wBAAC,MAAMe,OAAO,SAAS;;;YAGvC;YAEA,OAAO1B,qBAAqB,CAC1BA,aAAU,CAAC;gBACT,KAAK,IACHO,WAAWA,CACTa,yBACA,IAAChB,UAAUA;kCACT,kBAACqB;wBAEH;wBAAE,WAAWJ,eAAe,SAAS;oBAAC;gBAE1C,OAAO,CAACO,QAAU,IAAIhB,sBAAsBA,CAAC;wBAAEgB;oBAAM;YACvD,IACA,CAACC,OAAS7B,WAAW,CAAC,IAAM6B,KAAK,OAAO;YAE1C,OAAO7B,cAAc,CAAC,IAAMsB,qBAAqB,OAAO;QAC1D;QAEA,OAAO;YAAEH;QAAQ;IACnB;AACF;IAEA,OAAgB,QAAQlB,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;AACxD"}
1
+ {"version":3,"file":"client/react-dom-renderer.js","sources":["../../src/client/react-dom-renderer.tsx"],"sourcesContent":["import { Context, Effect, Layer, Schema } from 'effect';\nimport {\n Component,\n type ErrorInfo,\n type ReactNode,\n StrictMode,\n useLayoutEffect,\n useState,\n} from 'react';\nimport { hydrateRoot } from 'react-dom/client';\n\nimport type { FlightPayload } from '../rsc/flight';\nimport { BrowserEffectRunner } from './browser-effect-runner';\nimport { BrowserRenderStatus } from './browser-render-status';\nimport { type BrowserRender, BrowserRenderer } from './browser-renderer';\nimport { BrowserFailureScreen } from './browser-screen';\nimport { RouteTree } from './route-tree';\n\nexport class ReactDOMHydrationError extends Schema.TaggedError<ReactDOMHydrationError>()(\n 'ReactDOMHydrationError',\n { cause: Schema.Defect() },\n) {}\n\ntype BrowserErrorBoundaryState =\n | { readonly _tag: 'Uninitialized' }\n | { readonly _tag: 'Ready'; readonly render: BrowserRender }\n | { readonly _tag: 'Failed'; readonly render: BrowserRender };\ntype BrowserErrorBoundaryProps = {\n readonly children: ReactNode;\n readonly onError: (error: unknown, info: ErrorInfo) => void;\n readonly onRendered: () => void;\n readonly render: BrowserRender;\n};\n\nclass BrowserErrorBoundary extends Component<BrowserErrorBoundaryProps, BrowserErrorBoundaryState> {\n override readonly state: BrowserErrorBoundaryState = { _tag: 'Uninitialized' };\n\n static getDerivedStateFromError() {\n return { _tag: 'Failed' } as const;\n }\n\n static getDerivedStateFromProps(\n props: BrowserErrorBoundaryProps,\n state: BrowserErrorBoundaryState,\n ): BrowserErrorBoundaryState | null {\n if (state._tag !== 'Uninitialized' && props.render === state.render) {\n return null;\n }\n // Discard acknowledges a cancelled candidate, not a request to retry the failed tree.\n return state._tag === 'Failed' && props.render._tag === 'Discard'\n ? { _tag: 'Failed', render: props.render }\n : { _tag: 'Ready', render: props.render };\n }\n\n override componentDidCatch(error: unknown, info: ErrorInfo) {\n this.props.onError(error, info);\n }\n\n override componentDidMount() {\n if (this.state._tag === 'Ready') {\n this.props.onRendered();\n }\n }\n\n override componentDidUpdate() {\n if (this.state._tag === 'Ready') {\n this.props.onRendered();\n }\n }\n\n override render() {\n if (this.state._tag === 'Failed') {\n return <BrowserFailureScreen />;\n }\n\n return this.props.children;\n }\n}\n\nexport class ReactDOMRenderer extends Context.Service<ReactDOMRenderer>()(\n 'ersc/client/ReactDOMRenderer',\n {\n make: Effect.gen(function* () {\n const browserRenderer = yield* BrowserRenderer;\n const run = yield* BrowserEffectRunner;\n const renderStatus = yield* BrowserRenderStatus;\n\n const hydrate = Effect.fnUntraced(function* (\n container: Element | Document,\n initialPayload: FlightPayload,\n ) {\n const browserRendererReady = Promise.withResolvers<void>();\n const reportError = (error: unknown, info: ErrorInfo) => {\n void run(\n Effect.gen(function* () {\n yield* renderStatus.report({\n _tag: 'Failed',\n error,\n componentStack: info.componentStack ?? null,\n });\n yield* Effect.logError('Uncaught client render error.', error, info.componentStack);\n }),\n );\n };\n\n const reportRendered = () => {\n void run(renderStatus.report({ _tag: 'Rendered' }));\n };\n\n function Root() {\n const [render, setRender] = useState<BrowserRender>(() => ({\n _tag: 'Initial',\n routeTree: initialPayload.routeTree,\n }));\n\n useLayoutEffect(() => {\n browserRenderer.initialize(initialPayload.routeTree, setRender);\n browserRendererReady.resolve();\n }, []);\n\n useLayoutEffect(() => {\n browserRenderer.commit(render);\n }, [render]);\n\n return (\n <BrowserErrorBoundary onError={reportError} onRendered={reportRendered} render={render}>\n <RouteTree root={render.routeTree} />\n </BrowserErrorBoundary>\n );\n }\n\n yield* Effect.acquireRelease(\n Effect.try({\n try: () =>\n hydrateRoot(\n container,\n <StrictMode>\n <Root />\n </StrictMode>,\n { formState: initialPayload.formState },\n ),\n catch: (cause) => new ReactDOMHydrationError({ cause }),\n }),\n (root) => Effect.sync(() => root.unmount()),\n );\n yield* Effect.promise(() => browserRendererReady.promise);\n });\n\n return { hydrate };\n }),\n },\n) {\n static readonly layer = Layer.effect(this, this.make);\n}\n"],"names":["Context","Effect","Layer","Schema","Component","StrictMode","useLayoutEffect","useState","hydrateRoot","BrowserEffectRunner","BrowserRenderStatus","BrowserRenderer","BrowserFailureScreen","RouteTree","ReactDOMHydrationError","BrowserErrorBoundary","props","state","error","info","ReactDOMRenderer","browserRenderer","run","renderStatus","hydrate","container","initialPayload","browserRendererReady","Promise","reportError","reportRendered","Root","render","setRender","cause","root"],"mappings":"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"}
@@ -1,5 +1,4 @@
1
1
  import { Context, Effect, Layer } from 'effect';
2
- import { BrowserEffectRunner } from './browser-effect-runner.js';
3
2
  import { BrowserRenderer } from './browser-renderer.js';
4
3
  import { NavigationApi } from './navigation-api.js';
5
4
  import { RouteLoader } from './route-loader.js';
@@ -27,5 +26,5 @@ export declare class RouteRefresher extends RouteRefresher_base {
27
26
  replace?: (replacement: RouteRefreshImplementation) => Effect.Effect<void, never, never>;
28
27
  }) => Layer.Layer<RouteRefresher, never, never>;
29
28
  }
30
- export declare const installRouteRefresh: Effect.Effect<void, never, BrowserEffectRunner | BrowserRenderer | NavigationApi | RouteLoader | RouteRefresher | import("effect/Scope").Scope>;
29
+ export declare const installRouteRefresh: Effect.Effect<void, never, BrowserRenderer | NavigationApi | RouteLoader | RouteRefresher | import("effect/Scope").Scope>;
31
30
  export {};
@@ -1,6 +1,5 @@
1
- import { Context, Effect, FiberMap, Layer, Ref, Schema } from "effect";
1
+ import { Context, Effect, FiberMap, Layer, Ref } from "effect";
2
2
  import { addTransitionType, startTransition } from "react";
3
- import { BrowserEffectRunner } from "./browser-effect-runner.js";
4
3
  import { BrowserRenderer } from "./browser-renderer.js";
5
4
  import { NavigationApi } from "./navigation-api.js";
6
5
  import { isRoutedNavigation, preserveRequestedHash } from "./navigation-routing.js";
@@ -18,13 +17,7 @@ import { RouteLoader } from "./route-loader.js";
18
17
 
19
18
 
20
19
 
21
-
22
-
23
20
  const CurrentRouteRefreshKey = 'CurrentRouteRefresh';
24
- class RouteRefreshError extends Schema.TaggedError()('RouteRefreshError', {
25
- cause: Schema.Defect()
26
- }) {
27
- }
28
21
  class RouteRefresher extends Context.Service()('ersc/client/RouteRefresher', {
29
22
  make: Effect.gen(function*() {
30
23
  const navigationApi = yield* NavigationApi;
@@ -55,7 +48,6 @@ const installRouteRefresh = Effect.gen(function*() {
55
48
  const navigationApi = yield* NavigationApi;
56
49
  const routeLoader = yield* RouteLoader;
57
50
  const routeRefresher = yield* RouteRefresher;
58
- const run = yield* BrowserEffectRunner;
59
51
  const refreshes = yield* FiberMap.make();
60
52
  const waitForNavigationIdle = Effect.suspend(()=>{
61
53
  const transition = navigationApi.getTransition();
@@ -109,18 +101,13 @@ const installRouteRefresh = Effect.gen(function*() {
109
101
  discard: true
110
102
  }).pipe(Effect.andThen(Effect.sync(commitRefresh)), Effect.ensuring(resource.release));
111
103
  });
112
- const refreshInReactTransition = (transitionType)=>Effect.callback((resume, signal)=>{
113
- startTransition(()=>run(refreshRoute(transitionType), {
114
- signal
115
- }).then(()=>resume(Effect["void"]), (cause)=>resume(Effect.fail(new RouteRefreshError({
116
- cause
117
- })))));
118
- });
119
104
  const refreshCurrentRoute = Effect.fnUntraced(function*(transitionType) {
120
105
  routeLoader.invalidate();
121
106
  yield* waitForNavigationIdle;
122
- yield* Effect.raceFirst(refreshInReactTransition(transitionType), waitForRoutedNavigation);
123
- }, Effect["catch"]((cause)=>Effect.logError('Failed to refresh the current route.', cause)));
107
+ // Only publication belongs to a React Transition. An async Action waiting for this
108
+ // effect would prevent the very UI commit that completes the refresh.
109
+ yield* Effect.raceFirst(refreshRoute(transitionType), waitForRoutedNavigation);
110
+ }, Effect.scoped, Effect["catch"]((cause)=>Effect.logError('Failed to refresh the current route.', cause)));
124
111
  yield* routeRefresher.replace({
125
112
  interruptCurrentRouteRefresh: FiberMap.remove(refreshes, CurrentRouteRefreshKey),
126
113
  refreshCurrentRoute: (transitionType)=>FiberMap.run(refreshes, CurrentRouteRefreshKey, refreshCurrentRoute(transitionType)).pipe(Effect.asVoid)
@@ -1 +1 @@
1
- {"version":3,"file":"client/route-refresh.js","sources":["../../src/client/route-refresh.ts"],"sourcesContent":["import { Context, Effect, FiberMap, Layer, Ref, Schema } from 'effect';\nimport { addTransitionType, startTransition } from 'react';\n\nimport { BrowserEffectRunner } from './browser-effect-runner';\nimport { BrowserRenderer } from './browser-renderer';\nimport { NavigationApi } from './navigation-api';\nimport { isRoutedNavigation, preserveRequestedHash } from './navigation-routing';\nimport { RouteLoader } from './route-loader';\n\nconst CurrentRouteRefreshKey = 'CurrentRouteRefresh';\n\nclass RouteRefreshError extends Schema.TaggedError<RouteRefreshError>()('RouteRefreshError', {\n cause: Schema.Defect(),\n}) {}\n\nexport type RouteRefreshTransitionType = 'hmr-refresh' | 'server-function';\n\ntype RouteRefreshImplementation = {\n readonly interruptCurrentRouteRefresh: Effect.Effect<void>;\n readonly refreshCurrentRoute: (transitionType: RouteRefreshTransitionType) => Effect.Effect<void>;\n};\n\nexport class RouteRefresher extends Context.Service<RouteRefresher>()(\n 'ersc/client/RouteRefresher',\n {\n make: Effect.gen(function* () {\n const navigationApi = yield* NavigationApi;\n const implementation = yield* Ref.make<RouteRefreshImplementation>({\n interruptCurrentRouteRefresh: Effect.void,\n refreshCurrentRoute: () => Effect.sync(navigationApi.reloadDocument),\n });\n\n const interruptCurrentRouteRefresh = Effect.gen(function* () {\n const current = yield* Ref.get(implementation);\n yield* current.interruptCurrentRouteRefresh;\n });\n\n const refreshCurrentRoute = Effect.fn('RouteRefresher.refreshCurrentRoute')(function* (\n transitionType: RouteRefreshTransitionType,\n ) {\n const current = yield* Ref.get(implementation);\n yield* current.refreshCurrentRoute(transitionType);\n });\n\n return {\n interruptCurrentRouteRefresh,\n refreshCurrentRoute,\n replace: (replacement: RouteRefreshImplementation) => Ref.set(implementation, replacement),\n };\n }),\n },\n) {\n static readonly layer = Layer.effect(this, this.make);\n\n static readonly layerTest = Layer.mock(this);\n}\n\nexport const installRouteRefresh = Effect.gen(function* () {\n const browserRenderer = yield* BrowserRenderer;\n const navigationApi = yield* NavigationApi;\n const routeLoader = yield* RouteLoader;\n const routeRefresher = yield* RouteRefresher;\n const run = yield* BrowserEffectRunner;\n const refreshes = yield* FiberMap.make<typeof CurrentRouteRefreshKey>();\n\n const waitForNavigationIdle = Effect.suspend(() => {\n const transition = navigationApi.getTransition();\n return transition === null\n ? Effect.succeed('Idle' as const)\n : Effect.promise(() =>\n transition.finished.then(\n () => undefined,\n () => undefined,\n ),\n ).pipe(Effect.as('Settled' as const));\n }).pipe(Effect.repeat({ while: (state) => state === 'Settled' }), Effect.asVoid);\n\n const waitForRoutedNavigation = Effect.callback<void>((resume) => {\n const onNavigate = (event: NavigateEvent) => {\n if (isRoutedNavigation(event)) {\n resume(Effect.void);\n }\n };\n const unsubscribe = navigationApi.subscribe(onNavigate);\n return Effect.sync(unsubscribe);\n });\n\n const refreshRoute = Effect.fnUntraced(function* (transitionType: RouteRefreshTransitionType) {\n const currentEntry = navigationApi.getCurrentEntry();\n const destination = new URL(currentEntry?.url ?? navigationApi.getCurrentUrl());\n const resource = yield* routeLoader.load({\n destination: {\n id: currentEntry?.id ?? '',\n url: destination.href,\n },\n navigationType: 'replace',\n });\n\n if (resource._tag === 'Document') {\n yield* resource.release;\n yield* Effect.sync(navigationApi.reloadDocument);\n return;\n }\n\n const resolvedDestination = preserveRequestedHash(destination, resource.resolvedUrl);\n if (resolvedDestination.href !== destination.href) {\n yield* resource.release;\n yield* Effect.sync(() => navigationApi.replaceDocument(resolvedDestination.href));\n return;\n }\n\n const commitRefresh = routeLoader.prepareRefresh(resource.routeTree);\n let renderCommitted!: Promise<void>;\n yield* Effect.sync(() => {\n startTransition(() => {\n addTransitionType(transitionType);\n renderCommitted = browserRenderer.refresh(resource.routeTree);\n });\n });\n yield* Effect.all([Effect.promise(() => renderCommitted), resource.completed], {\n concurrency: 'unbounded',\n discard: true,\n }).pipe(Effect.andThen(Effect.sync(commitRefresh)), Effect.ensuring(resource.release));\n });\n\n const refreshInReactTransition = (transitionType: RouteRefreshTransitionType) =>\n Effect.callback<void, RouteRefreshError>((resume, signal) => {\n startTransition(() =>\n run(refreshRoute(transitionType), { signal }).then(\n () => resume(Effect.void),\n (cause) => resume(Effect.fail(new RouteRefreshError({ cause }))),\n ),\n );\n });\n\n const refreshCurrentRoute = Effect.fnUntraced(\n function* (transitionType: RouteRefreshTransitionType) {\n routeLoader.invalidate();\n yield* waitForNavigationIdle;\n yield* Effect.raceFirst(refreshInReactTransition(transitionType), waitForRoutedNavigation);\n },\n Effect.catch((cause) => Effect.logError('Failed to refresh the current route.', cause)),\n );\n\n yield* routeRefresher.replace({\n interruptCurrentRouteRefresh: FiberMap.remove(refreshes, CurrentRouteRefreshKey),\n refreshCurrentRoute: (transitionType) =>\n FiberMap.run(refreshes, CurrentRouteRefreshKey, refreshCurrentRoute(transitionType)).pipe(\n Effect.asVoid,\n ),\n });\n});\n"],"names":["Context","Effect","FiberMap","Layer","Ref","Schema","addTransitionType","startTransition","BrowserEffectRunner","BrowserRenderer","NavigationApi","isRoutedNavigation","preserveRequestedHash","RouteLoader","CurrentRouteRefreshKey","RouteRefreshError","RouteRefresher","navigationApi","implementation","interruptCurrentRouteRefresh","current","refreshCurrentRoute","transitionType","replacement","installRouteRefresh","browserRenderer","routeLoader","routeRefresher","run","refreshes","waitForNavigationIdle","transition","undefined","state","waitForRoutedNavigation","resume","onNavigate","event","unsubscribe","refreshRoute","currentEntry","destination","URL","resource","resolvedDestination","commitRefresh","renderCommitted","refreshInReactTransition","signal","cause"],"mappings":"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"}
1
+ {"version":3,"file":"client/route-refresh.js","sources":["../../src/client/route-refresh.ts"],"sourcesContent":["import { Context, Effect, FiberMap, Layer, Ref } from 'effect';\nimport { addTransitionType, startTransition } from 'react';\n\nimport { BrowserRenderer } from './browser-renderer';\nimport { NavigationApi } from './navigation-api';\nimport { isRoutedNavigation, preserveRequestedHash } from './navigation-routing';\nimport { RouteLoader } from './route-loader';\n\nconst CurrentRouteRefreshKey = 'CurrentRouteRefresh';\n\nexport type RouteRefreshTransitionType = 'hmr-refresh' | 'server-function';\n\ntype RouteRefreshImplementation = {\n readonly interruptCurrentRouteRefresh: Effect.Effect<void>;\n readonly refreshCurrentRoute: (transitionType: RouteRefreshTransitionType) => Effect.Effect<void>;\n};\n\nexport class RouteRefresher extends Context.Service<RouteRefresher>()(\n 'ersc/client/RouteRefresher',\n {\n make: Effect.gen(function* () {\n const navigationApi = yield* NavigationApi;\n const implementation = yield* Ref.make<RouteRefreshImplementation>({\n interruptCurrentRouteRefresh: Effect.void,\n refreshCurrentRoute: () => Effect.sync(navigationApi.reloadDocument),\n });\n\n const interruptCurrentRouteRefresh = Effect.gen(function* () {\n const current = yield* Ref.get(implementation);\n yield* current.interruptCurrentRouteRefresh;\n });\n\n const refreshCurrentRoute = Effect.fn('RouteRefresher.refreshCurrentRoute')(function* (\n transitionType: RouteRefreshTransitionType,\n ) {\n const current = yield* Ref.get(implementation);\n yield* current.refreshCurrentRoute(transitionType);\n });\n\n return {\n interruptCurrentRouteRefresh,\n refreshCurrentRoute,\n replace: (replacement: RouteRefreshImplementation) => Ref.set(implementation, replacement),\n };\n }),\n },\n) {\n static readonly layer = Layer.effect(this, this.make);\n\n static readonly layerTest = Layer.mock(this);\n}\n\nexport const installRouteRefresh = Effect.gen(function* () {\n const browserRenderer = yield* BrowserRenderer;\n const navigationApi = yield* NavigationApi;\n const routeLoader = yield* RouteLoader;\n const routeRefresher = yield* RouteRefresher;\n const refreshes = yield* FiberMap.make<typeof CurrentRouteRefreshKey>();\n\n const waitForNavigationIdle = Effect.suspend(() => {\n const transition = navigationApi.getTransition();\n return transition === null\n ? Effect.succeed('Idle' as const)\n : Effect.promise(() =>\n transition.finished.then(\n () => undefined,\n () => undefined,\n ),\n ).pipe(Effect.as('Settled' as const));\n }).pipe(Effect.repeat({ while: (state) => state === 'Settled' }), Effect.asVoid);\n\n const waitForRoutedNavigation = Effect.callback<void>((resume) => {\n const onNavigate = (event: NavigateEvent) => {\n if (isRoutedNavigation(event)) {\n resume(Effect.void);\n }\n };\n const unsubscribe = navigationApi.subscribe(onNavigate);\n return Effect.sync(unsubscribe);\n });\n\n const refreshRoute = Effect.fnUntraced(function* (transitionType: RouteRefreshTransitionType) {\n const currentEntry = navigationApi.getCurrentEntry();\n const destination = new URL(currentEntry?.url ?? navigationApi.getCurrentUrl());\n const resource = yield* routeLoader.load({\n destination: {\n id: currentEntry?.id ?? '',\n url: destination.href,\n },\n navigationType: 'replace',\n });\n\n if (resource._tag === 'Document') {\n yield* resource.release;\n yield* Effect.sync(navigationApi.reloadDocument);\n return;\n }\n\n const resolvedDestination = preserveRequestedHash(destination, resource.resolvedUrl);\n if (resolvedDestination.href !== destination.href) {\n yield* resource.release;\n yield* Effect.sync(() => navigationApi.replaceDocument(resolvedDestination.href));\n return;\n }\n\n const commitRefresh = routeLoader.prepareRefresh(resource.routeTree);\n let renderCommitted!: Promise<void>;\n yield* Effect.sync(() => {\n startTransition(() => {\n addTransitionType(transitionType);\n renderCommitted = browserRenderer.refresh(resource.routeTree);\n });\n });\n yield* Effect.all([Effect.promise(() => renderCommitted), resource.completed], {\n concurrency: 'unbounded',\n discard: true,\n }).pipe(Effect.andThen(Effect.sync(commitRefresh)), Effect.ensuring(resource.release));\n });\n\n const refreshCurrentRoute = Effect.fnUntraced(\n function* (transitionType: RouteRefreshTransitionType) {\n routeLoader.invalidate();\n yield* waitForNavigationIdle;\n // Only publication belongs to a React Transition. An async Action waiting for this\n // effect would prevent the very UI commit that completes the refresh.\n yield* Effect.raceFirst(refreshRoute(transitionType), waitForRoutedNavigation);\n },\n Effect.scoped,\n Effect.catch((cause) => Effect.logError('Failed to refresh the current route.', cause)),\n );\n\n yield* routeRefresher.replace({\n interruptCurrentRouteRefresh: FiberMap.remove(refreshes, CurrentRouteRefreshKey),\n refreshCurrentRoute: (transitionType) =>\n FiberMap.run(refreshes, CurrentRouteRefreshKey, refreshCurrentRoute(transitionType)).pipe(\n Effect.asVoid,\n ),\n });\n});\n"],"names":["Context","Effect","FiberMap","Layer","Ref","addTransitionType","startTransition","BrowserRenderer","NavigationApi","isRoutedNavigation","preserveRequestedHash","RouteLoader","CurrentRouteRefreshKey","RouteRefresher","navigationApi","implementation","interruptCurrentRouteRefresh","current","refreshCurrentRoute","transitionType","replacement","installRouteRefresh","browserRenderer","routeLoader","routeRefresher","refreshes","waitForNavigationIdle","transition","undefined","state","waitForRoutedNavigation","resume","onNavigate","event","unsubscribe","refreshRoute","currentEntry","destination","URL","resource","resolvedDestination","commitRefresh","renderCommitted","cause"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
1
  import { Effect } from 'effect';
2
+ import { BrowserRenderStatus } from '../client/browser-render-status.js';
2
3
  import { RouteRefresher } from '../client/route-refresh.js';
3
- export declare const startDevClient: Effect.Effect<void, import("effect/unstable/rpc/RpcClientError").RpcClientError, RouteRefresher | import("effect/Scope").Scope>;
4
+ export declare const startDevClient: Effect.Effect<void, import("effect/unstable/rpc/RpcClientError").RpcClientError, BrowserRenderStatus | RouteRefresher | import("effect/Scope").Scope>;
@@ -1,10 +1,11 @@
1
1
  import { Effect, Layer, Ref, Semaphore, Stream } from "effect";
2
2
  import { RpcClient, RpcSerialization } from "effect/unstable/rpc";
3
+ import { BrowserRenderStatus } from "../client/browser-render-status.js";
3
4
  import { RouteRefresher } from "../client/route-refresh.js";
4
5
  import { DevChannelPath, DevRpcs } from "./channel.js";
5
6
  import { decideHotUpdate } from "./hmr-update.js";
6
7
  import { makeDevPanel } from "./panel.js";
7
- import { reportBrowserFailures } from "./runtime-failure.js";
8
+ import { fromRenderError, reportBrowserFailures } from "./runtime-failure.js";
8
9
 
9
10
  import * as __rspack_external__effect_platform_browser_BrowserSocket_31178d5b from "@effect/platform-browser/BrowserSocket";
10
11
 
@@ -23,6 +24,8 @@ import * as __rspack_external__effect_platform_browser_BrowserSocket_31178d5b fr
23
24
 
24
25
 
25
26
 
27
+
28
+
26
29
  const socketProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
27
30
  const socketUrl = `${socketProtocol}//${location.host}${DevChannelPath}`;
28
31
  const DevRpcProtocolLayer = RpcClient.layerProtocolSocket({
@@ -88,6 +91,7 @@ const settlePendingUpdate = Effect.fnUntraced(function*(pendingUpdate) {
88
91
  });
89
92
  const startDevClient = Effect.gen(function*() {
90
93
  const routeRefresher = yield* RouteRefresher;
94
+ const renderStatus = yield* BrowserRenderStatus;
91
95
  const panel = yield* makeDevPanel;
92
96
  const pendingUpdate = yield* Ref.make({
93
97
  acknowledgedClientHash: import.meta.rspackHash,
@@ -101,13 +105,31 @@ const startDevClient = Effect.gen(function*() {
101
105
  if (action === 'Reloading') {
102
106
  return;
103
107
  }
104
- if (action === 'Refresh') {
108
+ const status = yield* renderStatus.get;
109
+ if (action === 'Refresh' || status._tag === 'Failed') {
105
110
  yield* routeRefresher.refreshCurrentRoute('hmr-refresh');
111
+ // Starting a refresh is not evidence that the replacement rendered successfully.
112
+ return;
106
113
  }
107
114
  yield* panel.dispatch({
108
- _tag: 'Reconciled'
115
+ _tag: 'RuntimeReconciled'
109
116
  });
110
117
  });
118
+ yield* renderStatus.changes.pipe(Stream.runForEach((status)=>{
119
+ switch(status._tag){
120
+ case 'Waiting':
121
+ return Effect["void"];
122
+ case 'Rendered':
123
+ return panel.dispatch({
124
+ _tag: 'RuntimeReconciled'
125
+ });
126
+ case 'Failed':
127
+ return panel.dispatch({
128
+ _tag: 'RenderFailed',
129
+ failure: fromRenderError(status.error, status.componentStack)
130
+ });
131
+ }
132
+ }), Effect.forkScoped);
111
133
  yield* reportBrowserFailures((failure)=>panel.dispatch({
112
134
  _tag: 'RuntimeFailed',
113
135
  failure
@@ -117,8 +139,13 @@ const startDevClient = Effect.gen(function*() {
117
139
  const handleUpdate = Effect.fnUntraced(function*(phase, message) {
118
140
  if (message._tag === 'BuildFailed') {
119
141
  yield* panel.dispatch(message);
120
- } else if (phase === 'Live') {
121
- yield* handleHotUpdate(message);
142
+ } else {
143
+ yield* panel.dispatch({
144
+ _tag: 'BuildSucceeded'
145
+ });
146
+ if (phase === 'Live' || message.clientHash !== import.meta.rspackHash) {
147
+ yield* handleHotUpdate(message);
148
+ }
122
149
  }
123
150
  return 'Live';
124
151
  });
@@ -1 +1 @@
1
- {"version":3,"file":"dev/client.js","sources":["../../src/dev/client.ts"],"sourcesContent":["import * as BrowserSocket from '@effect/platform-browser/BrowserSocket';\nimport { Effect, Layer, Ref, Semaphore, Stream } from 'effect';\nimport { RpcClient, RpcSerialization } from 'effect/unstable/rpc';\n\nimport { RouteRefresher } from '../client/route-refresh';\nimport { DevChannelPath, DevRpcs, type DevUpdate } from './channel';\nimport { decideHotUpdate, type HotUpdateCheck, type PendingDevUpdate } from './hmr-update';\nimport { makeDevPanel } from './panel';\nimport { reportBrowserFailures } from './runtime-failure';\n\nconst socketProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';\nconst socketUrl = `${socketProtocol}//${location.host}${DevChannelPath}`;\n\nconst DevRpcProtocolLayer = RpcClient.layerProtocolSocket({ retryTransientErrors: true }).pipe(\n Layer.provide(BrowserSocket.layerWebSocket(socketUrl)),\n Layer.provide(RpcSerialization.layerJson),\n);\n\ntype DevHotUpdate = Exclude<DevUpdate, { readonly _tag: 'BuildFailed' }>;\ntype DevUpdatePhase = 'Snapshot' | 'Live';\n\nconst recordUpdate = (pending: PendingDevUpdate, message: DevHotUpdate): PendingDevUpdate => ({\n acknowledgedClientHash: pending.acknowledgedClientHash,\n clientHash: message.clientHash,\n rscRefresh:\n pending.rscRefresh === 'Pending' || message._tag === 'RscUpdate' ? 'Pending' : 'Current',\n});\n\nconst settlePendingUpdate = Effect.fnUntraced(function* (pendingUpdate: Ref.Ref<PendingDevUpdate>) {\n const reconcile = Effect.gen(function* () {\n const pending = yield* Ref.get(pendingUpdate);\n if (pending.acknowledgedClientHash !== pending.clientHash) {\n const previousHash = import.meta.rspackHash;\n const check = yield* Effect.tryPromise(() => import.meta.webpackHot!.check(true)).pipe(\n Effect.match({\n onFailure: (): HotUpdateCheck => ({ _tag: 'Failed' }),\n onSuccess: (updatedModules): HotUpdateCheck => ({\n _tag: 'Completed',\n currentHash: import.meta.rspackHash,\n previousHash,\n updatedModules,\n }),\n }),\n );\n const decision = decideHotUpdate(pending, check);\n if (decision._tag === 'Reload') {\n yield* Effect.sync(() => location.reload());\n return 'Reloading' as const;\n }\n\n yield* Ref.update(pendingUpdate, (current) => ({\n ...current,\n acknowledgedClientHash: decision.acknowledgedClientHash,\n }));\n return 'Retry' as const;\n }\n\n return yield* Ref.modify(pendingUpdate, (current) => {\n if (current.acknowledgedClientHash !== current.clientHash) {\n return ['Retry', current] as const;\n }\n if (current.rscRefresh === 'Current') {\n return ['Current', current] as const;\n }\n return ['Refresh', { ...current, rscRefresh: 'Current' }] as const;\n });\n });\n\n return yield* reconcile.pipe(Effect.repeat({ while: (action) => action === 'Retry' }));\n});\n\nexport const startDevClient = Effect.gen(function* () {\n const routeRefresher = yield* RouteRefresher;\n const panel = yield* makeDevPanel;\n const pendingUpdate = yield* Ref.make<PendingDevUpdate>({\n acknowledgedClientHash: import.meta.rspackHash,\n clientHash: import.meta.rspackHash,\n rscRefresh: 'Current',\n });\n const updateLock = yield* Semaphore.make(1);\n const handleHotUpdate = Effect.fnUntraced(function* (message: DevHotUpdate) {\n yield* Ref.update(pendingUpdate, (pending) => recordUpdate(pending, message));\n const action = yield* updateLock.withPermit(settlePendingUpdate(pendingUpdate));\n if (action === 'Reloading') {\n return;\n }\n if (action === 'Refresh') {\n yield* routeRefresher.refreshCurrentRoute('hmr-refresh');\n }\n yield* panel.dispatch({ _tag: 'Reconciled' });\n });\n yield* reportBrowserFailures((failure) =>\n panel.dispatch({ _tag: 'RuntimeFailed', failure }),\n ).pipe(Effect.forkScoped);\n\n const observeUpdates = Effect.gen(function* () {\n const client = yield* RpcClient.make(DevRpcs);\n const handleUpdate = Effect.fnUntraced(function* (phase: DevUpdatePhase, message: DevUpdate) {\n if (message._tag === 'BuildFailed') {\n yield* panel.dispatch(message);\n } else if (phase === 'Live') {\n yield* handleHotUpdate(message);\n }\n\n return 'Live' as const;\n });\n\n yield* client.ObserveDevUpdates().pipe(\n Stream.runFoldEffect((): DevUpdatePhase => 'Snapshot', handleUpdate),\n Effect.asVoid,\n );\n });\n\n yield* observeUpdates.pipe(Effect.provide(DevRpcProtocolLayer));\n});\n"],"names":["BrowserSocket","Effect","Layer","Ref","Semaphore","Stream","RpcClient","RpcSerialization","RouteRefresher","DevChannelPath","DevRpcs","decideHotUpdate","makeDevPanel","reportBrowserFailures","socketProtocol","location","socketUrl","DevRpcProtocolLayer","recordUpdate","pending","message","settlePendingUpdate","pendingUpdate","reconcile","previousHash","check","updatedModules","decision","current","action","startDevClient","routeRefresher","panel","updateLock","handleHotUpdate","failure","observeUpdates","client","handleUpdate","phase"],"mappings":"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"}
1
+ {"version":3,"file":"dev/client.js","sources":["../../src/dev/client.ts"],"sourcesContent":["import * as BrowserSocket from '@effect/platform-browser/BrowserSocket';\nimport { Effect, Layer, Ref, Semaphore, Stream } from 'effect';\nimport { RpcClient, RpcSerialization } from 'effect/unstable/rpc';\n\nimport { BrowserRenderStatus } from '../client/browser-render-status';\nimport { RouteRefresher } from '../client/route-refresh';\nimport { DevChannelPath, DevRpcs, type DevUpdate } from './channel';\nimport { decideHotUpdate, type HotUpdateCheck, type PendingDevUpdate } from './hmr-update';\nimport { makeDevPanel } from './panel';\nimport { fromRenderError, reportBrowserFailures } from './runtime-failure';\n\nconst socketProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';\nconst socketUrl = `${socketProtocol}//${location.host}${DevChannelPath}`;\n\nconst DevRpcProtocolLayer = RpcClient.layerProtocolSocket({ retryTransientErrors: true }).pipe(\n Layer.provide(BrowserSocket.layerWebSocket(socketUrl)),\n Layer.provide(RpcSerialization.layerJson),\n);\n\ntype DevHotUpdate = Exclude<DevUpdate, { readonly _tag: 'BuildFailed' }>;\ntype DevUpdatePhase = 'Snapshot' | 'Live';\n\nconst recordUpdate = (pending: PendingDevUpdate, message: DevHotUpdate): PendingDevUpdate => ({\n acknowledgedClientHash: pending.acknowledgedClientHash,\n clientHash: message.clientHash,\n rscRefresh:\n pending.rscRefresh === 'Pending' || message._tag === 'RscUpdate' ? 'Pending' : 'Current',\n});\n\nconst settlePendingUpdate = Effect.fnUntraced(function* (pendingUpdate: Ref.Ref<PendingDevUpdate>) {\n const reconcile = Effect.gen(function* () {\n const pending = yield* Ref.get(pendingUpdate);\n if (pending.acknowledgedClientHash !== pending.clientHash) {\n const previousHash = import.meta.rspackHash;\n const check = yield* Effect.tryPromise(() => import.meta.webpackHot!.check(true)).pipe(\n Effect.match({\n onFailure: (): HotUpdateCheck => ({ _tag: 'Failed' }),\n onSuccess: (updatedModules): HotUpdateCheck => ({\n _tag: 'Completed',\n currentHash: import.meta.rspackHash,\n previousHash,\n updatedModules,\n }),\n }),\n );\n const decision = decideHotUpdate(pending, check);\n if (decision._tag === 'Reload') {\n yield* Effect.sync(() => location.reload());\n return 'Reloading' as const;\n }\n\n yield* Ref.update(pendingUpdate, (current) => ({\n ...current,\n acknowledgedClientHash: decision.acknowledgedClientHash,\n }));\n return 'Retry' as const;\n }\n\n return yield* Ref.modify(pendingUpdate, (current) => {\n if (current.acknowledgedClientHash !== current.clientHash) {\n return ['Retry', current] as const;\n }\n if (current.rscRefresh === 'Current') {\n return ['Current', current] as const;\n }\n return ['Refresh', { ...current, rscRefresh: 'Current' }] as const;\n });\n });\n\n return yield* reconcile.pipe(Effect.repeat({ while: (action) => action === 'Retry' }));\n});\n\nexport const startDevClient = Effect.gen(function* () {\n const routeRefresher = yield* RouteRefresher;\n const renderStatus = yield* BrowserRenderStatus;\n const panel = yield* makeDevPanel;\n const pendingUpdate = yield* Ref.make<PendingDevUpdate>({\n acknowledgedClientHash: import.meta.rspackHash,\n clientHash: import.meta.rspackHash,\n rscRefresh: 'Current',\n });\n const updateLock = yield* Semaphore.make(1);\n const handleHotUpdate = Effect.fnUntraced(function* (message: DevHotUpdate) {\n yield* Ref.update(pendingUpdate, (pending) => recordUpdate(pending, message));\n const action = yield* updateLock.withPermit(settlePendingUpdate(pendingUpdate));\n if (action === 'Reloading') {\n return;\n }\n const status = yield* renderStatus.get;\n if (action === 'Refresh' || status._tag === 'Failed') {\n yield* routeRefresher.refreshCurrentRoute('hmr-refresh');\n // Starting a refresh is not evidence that the replacement rendered successfully.\n return;\n }\n yield* panel.dispatch({ _tag: 'RuntimeReconciled' });\n });\n yield* renderStatus.changes.pipe(\n Stream.runForEach((status) => {\n switch (status._tag) {\n case 'Waiting':\n return Effect.void;\n case 'Rendered':\n return panel.dispatch({ _tag: 'RuntimeReconciled' });\n case 'Failed':\n return panel.dispatch({\n _tag: 'RenderFailed',\n failure: fromRenderError(status.error, status.componentStack),\n });\n }\n }),\n Effect.forkScoped,\n );\n yield* reportBrowserFailures((failure) =>\n panel.dispatch({ _tag: 'RuntimeFailed', failure }),\n ).pipe(Effect.forkScoped);\n\n const observeUpdates = Effect.gen(function* () {\n const client = yield* RpcClient.make(DevRpcs);\n const handleUpdate = Effect.fnUntraced(function* (phase: DevUpdatePhase, message: DevUpdate) {\n if (message._tag === 'BuildFailed') {\n yield* panel.dispatch(message);\n } else {\n yield* panel.dispatch({ _tag: 'BuildSucceeded' });\n if (phase === 'Live' || message.clientHash !== import.meta.rspackHash) {\n yield* handleHotUpdate(message);\n }\n }\n\n return 'Live' as const;\n });\n\n yield* client.ObserveDevUpdates().pipe(\n Stream.runFoldEffect((): DevUpdatePhase => 'Snapshot', handleUpdate),\n Effect.asVoid,\n );\n });\n\n yield* observeUpdates.pipe(Effect.provide(DevRpcProtocolLayer));\n});\n"],"names":["BrowserSocket","Effect","Layer","Ref","Semaphore","Stream","RpcClient","RpcSerialization","BrowserRenderStatus","RouteRefresher","DevChannelPath","DevRpcs","decideHotUpdate","makeDevPanel","fromRenderError","reportBrowserFailures","socketProtocol","location","socketUrl","DevRpcProtocolLayer","recordUpdate","pending","message","settlePendingUpdate","pendingUpdate","reconcile","previousHash","check","updatedModules","decision","current","action","startDevClient","routeRefresher","renderStatus","panel","updateLock","handleHotUpdate","status","failure","observeUpdates","client","handleUpdate","phase"],"mappings":"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"}
@@ -25,7 +25,12 @@ export type DevPanelEvent = {
25
25
  readonly _tag: 'RuntimeFailed';
26
26
  readonly failure: DevRuntimeFailure;
27
27
  } | {
28
- readonly _tag: 'Reconciled';
28
+ readonly _tag: 'RenderFailed';
29
+ readonly failure: DevRuntimeFailure;
30
+ } | {
31
+ readonly _tag: 'BuildSucceeded';
32
+ } | {
33
+ readonly _tag: 'RuntimeReconciled';
29
34
  } | {
30
35
  readonly _tag: 'Opened';
31
36
  } | {