akanjs 2.3.0 → 2.3.1-rc.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 (180) hide show
  1. package/base/primitiveRegistry.ts +19 -12
  2. package/client/csrTypes.ts +16 -0
  3. package/constant/fieldInfo.ts +11 -9
  4. package/constant/getDefault.ts +1 -1
  5. package/fetch/requestStorage.ts +5 -0
  6. package/package.json +4 -4
  7. package/server/akanApp.ts +26 -3
  8. package/server/akanServer.ts +5 -1
  9. package/server/cachePolicy.ts +99 -5
  10. package/server/imageOptimizer.ts +14 -1
  11. package/server/metadata.tsx +117 -33
  12. package/server/resolver/database.resolver.ts +4 -4
  13. package/server/routeElementComposer.tsx +46 -14
  14. package/server/routeState.ts +379 -0
  15. package/server/routeTreeBuilder.ts +3 -2
  16. package/server/rscClient.tsx +316 -46
  17. package/server/rscClientFetch.ts +57 -0
  18. package/server/rscClientPatch.ts +157 -0
  19. package/server/rscHeadPatch.ts +80 -0
  20. package/server/rscNavigationState.ts +315 -0
  21. package/server/rscPartialCommit.ts +3 -0
  22. package/server/rscPatchSafety.ts +57 -0
  23. package/server/rscSegmentOutlet.tsx +69 -0
  24. package/server/rscSegmentOutletReference.ts +24 -0
  25. package/server/rscWorker.tsx +380 -53
  26. package/server/rscWorkerCache.ts +180 -0
  27. package/server/rscWorkerHost.ts +40 -12
  28. package/server/rscWorkerReplay.ts +11 -2
  29. package/server/ssrFromRscRenderer.tsx +15 -10
  30. package/server/ssrTypes.ts +18 -0
  31. package/server/types.tsx +4 -0
  32. package/server/webRouter.ts +198 -42
  33. package/service/predefinedAdaptor/database.adaptor.ts +72 -25
  34. package/signal/signalContext.ts +1 -1
  35. package/types/base/primitiveRegistry.d.ts +6 -6
  36. package/types/client/csrTypes.d.ts +16 -0
  37. package/types/constant/fieldInfo.d.ts +8 -7
  38. package/types/fetch/requestStorage.d.ts +2 -0
  39. package/types/server/cachePolicy.d.ts +36 -0
  40. package/types/server/metadata.d.ts +10 -1
  41. package/types/server/routeElementComposer.d.ts +9 -1
  42. package/types/server/routeState.d.ts +94 -0
  43. package/types/server/rscClient.d.ts +1 -0
  44. package/types/server/rscClientFetch.d.ts +24 -0
  45. package/types/server/rscClientPatch.d.ts +21 -0
  46. package/types/server/rscHeadPatch.d.ts +12 -0
  47. package/types/server/rscNavigationState.d.ts +78 -0
  48. package/types/server/rscPartialCommit.d.ts +1 -0
  49. package/types/server/rscPatchSafety.d.ts +8 -0
  50. package/types/server/rscSegmentOutlet.d.ts +17 -0
  51. package/types/server/rscSegmentOutletReference.d.ts +2 -0
  52. package/types/server/rscWorker.d.ts +5 -0
  53. package/types/server/rscWorkerCache.d.ts +63 -0
  54. package/types/server/rscWorkerHost.d.ts +8 -4
  55. package/types/server/rscWorkerReplay.d.ts +3 -0
  56. package/types/server/ssrFromRscRenderer.d.ts +1 -0
  57. package/types/server/ssrTypes.d.ts +17 -0
  58. package/types/server/types.d.ts +4 -0
  59. package/types/server/webRouter.d.ts +7 -3
  60. package/types/service/predefinedAdaptor/database.adaptor.d.ts +6 -0
  61. package/types/ui/Button.d.ts +1 -1
  62. package/types/ui/ClientSide.d.ts +1 -1
  63. package/types/ui/Constant/Doc.d.ts +6 -6
  64. package/types/ui/Constant/Mermaid.d.ts +1 -1
  65. package/types/ui/Constant/index.d.ts +1 -1
  66. package/types/ui/Constant/schemaDoc.d.ts +1 -1
  67. package/types/ui/Copy.d.ts +1 -1
  68. package/types/ui/CsrImage.d.ts +1 -1
  69. package/types/ui/Data/CardList.d.ts +1 -1
  70. package/types/ui/Data/Dashboard.d.ts +1 -1
  71. package/types/ui/Data/Insight.d.ts +1 -1
  72. package/types/ui/Data/Item.d.ts +6 -6
  73. package/types/ui/Data/ListContainer.d.ts +1 -1
  74. package/types/ui/Data/Pagination.d.ts +1 -1
  75. package/types/ui/Data/TableList.d.ts +1 -1
  76. package/types/ui/DatePicker.d.ts +3 -3
  77. package/types/ui/Dialog/Close.d.ts +1 -1
  78. package/types/ui/Dialog/Content.d.ts +1 -1
  79. package/types/ui/Dialog/Provider.d.ts +1 -1
  80. package/types/ui/Dialog/Trigger.d.ts +1 -1
  81. package/types/ui/Dialog/index.d.ts +3 -3
  82. package/types/ui/DragAction.d.ts +4 -4
  83. package/types/ui/DraggableList.d.ts +3 -3
  84. package/types/ui/Dropdown.d.ts +1 -1
  85. package/types/ui/Empty.d.ts +1 -1
  86. package/types/ui/Field.d.ts +22 -22
  87. package/types/ui/Image.d.ts +1 -1
  88. package/types/ui/InfiniteScroll.d.ts +1 -1
  89. package/types/ui/Input.d.ts +6 -6
  90. package/types/ui/KeyboardAvoiding.d.ts +1 -1
  91. package/types/ui/Layout/BottomAction.d.ts +1 -1
  92. package/types/ui/Layout/BottomInset.d.ts +1 -1
  93. package/types/ui/Layout/BottomTab.d.ts +1 -1
  94. package/types/ui/Layout/Header.d.ts +1 -1
  95. package/types/ui/Layout/LeftSider.d.ts +1 -1
  96. package/types/ui/Layout/Navbar.d.ts +1 -1
  97. package/types/ui/Layout/RightSider.d.ts +1 -1
  98. package/types/ui/Layout/Sider.d.ts +1 -1
  99. package/types/ui/Layout/Template.d.ts +1 -1
  100. package/types/ui/Layout/TopLeftAction.d.ts +1 -1
  101. package/types/ui/Layout/Unit.d.ts +1 -1
  102. package/types/ui/Layout/View.d.ts +1 -1
  103. package/types/ui/Layout/Zone.d.ts +1 -1
  104. package/types/ui/Layout/index.d.ts +12 -12
  105. package/types/ui/Link/Back.d.ts +1 -1
  106. package/types/ui/Link/Close.d.ts +1 -1
  107. package/types/ui/Link/CsrLink.d.ts +1 -1
  108. package/types/ui/Link/Lang.d.ts +1 -1
  109. package/types/ui/Link/SsrLink.d.ts +1 -1
  110. package/types/ui/Link/index.d.ts +1 -1
  111. package/types/ui/Load/Edit.d.ts +1 -1
  112. package/types/ui/Load/Edit_Client.d.ts +1 -1
  113. package/types/ui/Load/PageCSR.d.ts +1 -1
  114. package/types/ui/Load/Pagination.d.ts +1 -1
  115. package/types/ui/Load/Units.d.ts +1 -1
  116. package/types/ui/Load/View.d.ts +1 -1
  117. package/types/ui/Loading/Area.d.ts +1 -1
  118. package/types/ui/Loading/Button.d.ts +1 -1
  119. package/types/ui/Loading/Input.d.ts +1 -1
  120. package/types/ui/Loading/ProgressBar.d.ts +1 -1
  121. package/types/ui/Loading/Skeleton.d.ts +1 -1
  122. package/types/ui/Loading/Spin.d.ts +1 -1
  123. package/types/ui/Loading/index.d.ts +6 -6
  124. package/types/ui/Menu.d.ts +1 -1
  125. package/types/ui/Modal.d.ts +1 -1
  126. package/types/ui/Model/AdminPanel.d.ts +1 -1
  127. package/types/ui/Model/Edit.d.ts +1 -1
  128. package/types/ui/Model/EditModal.d.ts +1 -1
  129. package/types/ui/Model/EditWrapper.d.ts +1 -1
  130. package/types/ui/Model/LoadInit.d.ts +1 -1
  131. package/types/ui/Model/New.d.ts +1 -1
  132. package/types/ui/Model/NewWrapper.d.ts +1 -1
  133. package/types/ui/Model/NewWrapper_Client.d.ts +1 -1
  134. package/types/ui/Model/Remove.d.ts +1 -1
  135. package/types/ui/Model/RemoveWrapper.d.ts +1 -1
  136. package/types/ui/Model/SureToRemove.d.ts +1 -1
  137. package/types/ui/Model/View.d.ts +1 -1
  138. package/types/ui/Model/ViewEditModal.d.ts +1 -1
  139. package/types/ui/Model/ViewModal.d.ts +1 -1
  140. package/types/ui/Model/ViewWrapper.d.ts +1 -1
  141. package/types/ui/More.d.ts +1 -1
  142. package/types/ui/ObjectId.d.ts +1 -1
  143. package/types/ui/Popconfirm.d.ts +1 -1
  144. package/types/ui/Radio.d.ts +2 -2
  145. package/types/ui/RecentTime.d.ts +1 -1
  146. package/types/ui/Refresh.d.ts +1 -1
  147. package/types/ui/ScreenNavigator.d.ts +3 -3
  148. package/types/ui/Select.d.ts +1 -1
  149. package/types/ui/Signal/Arg.d.ts +13 -13
  150. package/types/ui/Signal/Doc.d.ts +6 -6
  151. package/types/ui/Signal/Listener.d.ts +2 -2
  152. package/types/ui/Signal/Message.d.ts +4 -4
  153. package/types/ui/Signal/Object.d.ts +4 -4
  154. package/types/ui/Signal/PubSub.d.ts +4 -4
  155. package/types/ui/Signal/Request.d.ts +2 -2
  156. package/types/ui/Signal/Response.d.ts +3 -3
  157. package/types/ui/Signal/RestApi.d.ts +5 -5
  158. package/types/ui/Signal/WebSocket.d.ts +2 -2
  159. package/types/ui/System/CSR.d.ts +5 -5
  160. package/types/ui/System/Client.d.ts +8 -8
  161. package/types/ui/System/Common.d.ts +2 -2
  162. package/types/ui/System/DevModeToggle.d.ts +1 -1
  163. package/types/ui/System/Gtag.d.ts +1 -1
  164. package/types/ui/System/Messages.d.ts +1 -1
  165. package/types/ui/System/Reconnect.d.ts +1 -1
  166. package/types/ui/System/Root.d.ts +1 -1
  167. package/types/ui/System/SSR.d.ts +4 -4
  168. package/types/ui/System/SelectLanguage.d.ts +1 -1
  169. package/types/ui/System/ThemeToggle.d.ts +1 -1
  170. package/types/ui/System/index.d.ts +7 -7
  171. package/types/ui/Tab/Menu.d.ts +1 -1
  172. package/types/ui/Tab/Menus.d.ts +1 -1
  173. package/types/ui/Tab/Panel.d.ts +1 -1
  174. package/types/ui/Tab/Provider.d.ts +1 -1
  175. package/types/ui/Tab/index.d.ts +4 -4
  176. package/types/ui/Table.d.ts +1 -1
  177. package/types/ui/ToggleSelect.d.ts +2 -2
  178. package/types/ui/Unauthorized.d.ts +1 -1
  179. package/ui/Constant/schemaDoc.ts +1 -1
  180. package/server/resolver/resolver.contract.fixture.ts +0 -222
@@ -0,0 +1,180 @@
1
+ import {
2
+ hasRouteCacheInvalidationScope,
3
+ type LruTtlCache,
4
+ type RouteCacheEntry,
5
+ type RouteCacheInvalidation,
6
+ type RouteCacheRenderState,
7
+ shouldInvalidateRouteCacheEntry,
8
+ } from "./cachePolicy";
9
+ import type { AkanRouterStateV1, AkanRscPatchDecision, AkanRscPatchMetadata } from "./routeState";
10
+
11
+ export interface CachedRscResult {
12
+ chunks: Uint8Array[];
13
+ bytes: number;
14
+ chunksCount: number;
15
+ pathname: string;
16
+ routeId?: string;
17
+ tags?: string[];
18
+ theme?: string;
19
+ cacheState: RouteCacheRenderState;
20
+ patch?: CachedRscPatchMetadata;
21
+ }
22
+
23
+ export interface CachedRscPatchMetadata {
24
+ targetRouterState: AkanRouterStateV1;
25
+ patch: AkanRscPatchMetadata;
26
+ }
27
+
28
+ export interface RscPatchCacheKeyInput {
29
+ baseEntry: RouteCacheEntry;
30
+ targetRouterState: AkanRouterStateV1;
31
+ patch: AkanRscPatchMetadata;
32
+ }
33
+
34
+ export function createRscPatchCacheKey({ baseEntry, targetRouterState, patch }: RscPatchCacheKeyInput): string {
35
+ return [
36
+ "patch-v1",
37
+ baseEntry.key,
38
+ targetRouterState.buildId ?? "",
39
+ targetRouterState.href,
40
+ targetRouterState.routeId,
41
+ JSON.stringify(targetRouterState.segments),
42
+ patch.patchStartIndex,
43
+ patch.patchStartSegmentKey,
44
+ JSON.stringify(patch.segmentPath),
45
+ patch.headSafe === true ? "head-safe" : "head-unsafe",
46
+ ].join("\n");
47
+ }
48
+
49
+ export function createRscPatchCacheEntry(input: RscPatchCacheKeyInput): RouteCacheEntry {
50
+ return { key: createRscPatchCacheKey(input), ttl: input.baseEntry.ttl };
51
+ }
52
+
53
+ export function isRscPatchResultCacheEligible(input: {
54
+ partialCommitEnabled: boolean;
55
+ patch?: AkanRscPatchMetadata;
56
+ }): boolean {
57
+ return (
58
+ input.partialCommitEnabled &&
59
+ input.patch?.headSafe === true &&
60
+ input.patch.headSnapshot !== undefined &&
61
+ !input.patch.headSnapshotFailure
62
+ );
63
+ }
64
+
65
+ export function createCachedRscPatchMetadata(input: {
66
+ targetRouterState: AkanRouterStateV1;
67
+ patch: AkanRscPatchMetadata;
68
+ }): CachedRscPatchMetadata {
69
+ return {
70
+ targetRouterState: input.targetRouterState,
71
+ patch: input.patch,
72
+ };
73
+ }
74
+
75
+ export function isCachedRscPatchMetadataCompatible(input: {
76
+ cached?: CachedRscPatchMetadata;
77
+ targetRouterState: AkanRouterStateV1 | null;
78
+ safePatchDecision: AkanRscPatchDecision;
79
+ }): boolean {
80
+ if (!input.cached || !input.targetRouterState || input.safePatchDecision.status !== "patch") return false;
81
+ const currentPatch = input.safePatchDecision.patch;
82
+ if (!currentPatch) return false;
83
+ const cachedPatch = input.cached.patch;
84
+ return (
85
+ input.cached.targetRouterState.buildId === input.targetRouterState.buildId &&
86
+ input.cached.targetRouterState.href === input.targetRouterState.href &&
87
+ input.cached.targetRouterState.routeId === input.targetRouterState.routeId &&
88
+ JSON.stringify(input.cached.targetRouterState.segments) === JSON.stringify(input.targetRouterState.segments) &&
89
+ cachedPatch.patchStartIndex === currentPatch.patchStartIndex &&
90
+ cachedPatch.patchStartSegmentKey === currentPatch.patchStartSegmentKey &&
91
+ JSON.stringify(cachedPatch.segmentPath) === JSON.stringify(currentPatch.segmentPath) &&
92
+ cachedPatch.headSafe === true &&
93
+ cachedPatch.headSnapshot !== undefined &&
94
+ !cachedPatch.headSnapshotFailure
95
+ );
96
+ }
97
+
98
+ export function createRscWorkerCachedPatchReplayDecision(input: {
99
+ cached: CachedRscPatchMetadata;
100
+ safePatchDecision: AkanRscPatchDecision;
101
+ }): AkanRscPatchDecision {
102
+ return {
103
+ ...input.safePatchDecision,
104
+ patch: input.cached.patch,
105
+ };
106
+ }
107
+
108
+ export function resolveRscWorkerPatchCacheEntry(input: {
109
+ cacheEntry: RouteCacheEntry | null;
110
+ targetRouterState: AkanRouterStateV1 | null;
111
+ safePatchDecision: AkanRscPatchDecision;
112
+ partialCommitEnabled: boolean;
113
+ }): RouteCacheEntry | null {
114
+ const patch = input.safePatchDecision.status === "patch" ? input.safePatchDecision.patch : undefined;
115
+ if (
116
+ !input.cacheEntry ||
117
+ !input.targetRouterState ||
118
+ input.safePatchDecision.status !== "patch" ||
119
+ !patch ||
120
+ !isRscPatchResultCacheEligible({ partialCommitEnabled: input.partialCommitEnabled, patch })
121
+ ) {
122
+ return null;
123
+ }
124
+ return createRscPatchCacheEntry({
125
+ baseEntry: input.cacheEntry,
126
+ targetRouterState: input.targetRouterState,
127
+ patch,
128
+ });
129
+ }
130
+
131
+ export function shouldCollectRscWorkerRenderChunks(input: {
132
+ cacheEntry: RouteCacheEntry | null;
133
+ effectivePatchDecision: AkanRscPatchDecision;
134
+ patchCacheEntry: RouteCacheEntry | null;
135
+ }): boolean {
136
+ return (
137
+ input.cacheEntry !== null && (input.effectivePatchDecision.status !== "patch" || input.patchCacheEntry !== null)
138
+ );
139
+ }
140
+
141
+ export function shouldStoreRscWorkerPatchResult(input: {
142
+ cacheEntry: RouteCacheEntry | null;
143
+ patchCacheEntry: RouteCacheEntry | null;
144
+ effectivePatchDecision: AkanRscPatchDecision;
145
+ storeTtl: number | null;
146
+ }): boolean {
147
+ return (
148
+ input.cacheEntry !== null &&
149
+ input.patchCacheEntry !== null &&
150
+ input.storeTtl !== null &&
151
+ input.effectivePatchDecision.status === "patch"
152
+ );
153
+ }
154
+
155
+ export function shouldUseRscWorkerFullResultCache(input: {
156
+ cacheEntry: RouteCacheEntry | null;
157
+ patchCacheEntry: RouteCacheEntry | null;
158
+ }): boolean {
159
+ return input.cacheEntry !== null && input.patchCacheEntry === null;
160
+ }
161
+
162
+ export function invalidateCachedRscResults(
163
+ cache: LruTtlCache<CachedRscResult>,
164
+ invalidation: RouteCacheInvalidation,
165
+ ): void {
166
+ if (!hasRouteCacheInvalidationScope(invalidation)) {
167
+ cache.clear();
168
+ return;
169
+ }
170
+ cache.invalidate((_key, result) =>
171
+ shouldInvalidateRouteCacheEntry(
172
+ {
173
+ pathname: result.pathname,
174
+ routeId: result.routeId,
175
+ tags: result.tags,
176
+ },
177
+ invalidation,
178
+ ),
179
+ );
180
+ }
@@ -5,8 +5,8 @@ import { type AkanI18nConfig, DEFAULT_AKAN_I18N, Logger } from "akanjs/common";
5
5
  import type { AkanTheme } from "akanjs/fetch";
6
6
  import type { AkanMetricsReport } from "akanjs/service";
7
7
  import type { ClientManifest } from "./artifact";
8
- import type { RouteCacheRenderState } from "./cachePolicy";
9
- import type { SsrLateRedirect } from "./ssrTypes";
8
+ import type { RouteCacheInvalidation, RouteCacheRenderState } from "./cachePolicy";
9
+ import type { RscTraceMetadata, SsrLateRedirect } from "./ssrTypes";
10
10
  import type { BaseBuildArtifact, CssAsset } from "./types";
11
11
 
12
12
  const DEFAULT_RSC_HOST_MAX_PENDING_CHUNKS = 256;
@@ -15,7 +15,7 @@ export interface RscPending {
15
15
  onChunk: (data: Uint8Array) => void;
16
16
  onEnd: () => void;
17
17
  onError: (message: string) => void;
18
- onMeta?: (meta: { theme?: AkanTheme; status?: number }) => void;
18
+ onMeta?: (meta: { theme?: AkanTheme; status?: number; trace?: RscTraceMetadata }) => void;
19
19
  onCacheState?: (state: RouteCacheRenderState) => void;
20
20
  onRedirect?: (location: string, method: RscRedirectMethod, status: RscRedirectStatus) => void;
21
21
  onLateRedirect?: (location: string, method: RscRedirectMethod, status: RscRedirectStatus) => void;
@@ -28,6 +28,8 @@ export type RscRedirectStatus = 303 | 307 | 308;
28
28
  export interface RscWorkerInvalidateCacheMessage {
29
29
  type: "invalidate-cache";
30
30
  reason?: string;
31
+ tags?: string[];
32
+ paths?: string[];
31
33
  }
32
34
 
33
35
  export type RscRenderResult =
@@ -36,6 +38,7 @@ export type RscRenderResult =
36
38
  stream: ReadableStream<Uint8Array>;
37
39
  theme?: AkanTheme;
38
40
  status?: number;
41
+ trace?: RscTraceMetadata;
39
42
  lateControl: Promise<SsrLateRedirect | null>;
40
43
  cacheState: Promise<RouteCacheRenderState>;
41
44
  cancel: (reason?: unknown) => void;
@@ -72,8 +75,17 @@ export function createIdempotentRscRenderCancel(onCancel: (reason?: unknown) =>
72
75
  };
73
76
  }
74
77
 
75
- export function createRscWorkerInvalidateCacheMessage(reason?: string): RscWorkerInvalidateCacheMessage {
76
- return reason ? { type: "invalidate-cache", reason } : { type: "invalidate-cache" };
78
+ export function createRscWorkerInvalidateCacheMessage(
79
+ invalidation?: string | RouteCacheInvalidation,
80
+ ): RscWorkerInvalidateCacheMessage {
81
+ if (!invalidation) return { type: "invalidate-cache" };
82
+ if (typeof invalidation === "string") return { type: "invalidate-cache", reason: invalidation };
83
+ return {
84
+ type: "invalidate-cache",
85
+ reason: invalidation.reason,
86
+ tags: invalidation.tags,
87
+ paths: invalidation.paths,
88
+ };
77
89
  }
78
90
 
79
91
  export function createRscHostRenderStream(input: {
@@ -89,6 +101,7 @@ export function createRscHostRenderStream(input: {
89
101
  let stream!: ReadableStream<Uint8Array>;
90
102
  let theme: AkanTheme | undefined;
91
103
  let status: number | undefined;
104
+ let trace: RscTraceMetadata | undefined;
92
105
  let resolveLateControl!: (control: SsrLateRedirect | null) => void;
93
106
  let resolveCacheState!: (state: RouteCacheRenderState) => void;
94
107
  const lateControl = new Promise<SsrLateRedirect | null>((resolve) => {
@@ -140,12 +153,13 @@ export function createRscHostRenderStream(input: {
140
153
  const settleStream = () => {
141
154
  if (settled) return;
142
155
  settled = true;
143
- resolve({ type: "stream", stream, theme, status, lateControl, cacheState, cancel: cancelRender });
156
+ resolve({ type: "stream", stream, theme, status, trace, lateControl, cacheState, cancel: cancelRender });
144
157
  };
145
158
  input.setPending({
146
159
  onMeta: (meta) => {
147
160
  theme = meta.theme;
148
161
  status = meta.status;
162
+ trace = meta.trace;
149
163
  settleStream();
150
164
  },
151
165
  onChunk: (data) => {
@@ -234,7 +248,7 @@ type RscInMsg =
234
248
  | { type: "hello" }
235
249
  | { type: "ready" }
236
250
  | { type: "reloaded"; buildId: number }
237
- | { type: "meta"; requestId: string; theme?: AkanTheme; status?: number }
251
+ | { type: "meta"; requestId: string; theme?: AkanTheme; status?: number; trace?: RscTraceMetadata }
238
252
  | { type: "cache-state"; requestId: string; state: RouteCacheRenderState }
239
253
  | { type: "chunk"; requestId: string; data: Uint8Array }
240
254
  | { type: "end"; requestId: string }
@@ -329,7 +343,7 @@ export class RscWorker {
329
343
  };
330
344
 
331
345
  constructor(artifact: BaseBuildArtifact) {
332
- this.#clientManifest = {};
346
+ this.#clientManifest = artifact.rscRuntimeClientManifest ?? {};
333
347
  this.#pagesBundlePath = artifact.pagesBundlePath;
334
348
  this.#pagesBundleBuildId = artifact.pagesBundleBuildId;
335
349
  this.#cssAssets = artifact.cssAssets ?? {};
@@ -417,10 +431,10 @@ export class RscWorker {
417
431
  });
418
432
  }
419
433
 
420
- invalidateRouteResultCache(reason?: string): void {
434
+ invalidateRouteResultCache(invalidation?: string | RouteCacheInvalidation): void {
421
435
  if (this.#status !== "ready") return;
422
436
  try {
423
- this.#proc.send(createRscWorkerInvalidateCacheMessage(reason));
437
+ this.#proc.send(createRscWorkerInvalidateCacheMessage(invalidation));
424
438
  } catch (error) {
425
439
  this.#logger.warn(
426
440
  `rsc worker cache invalidate send failed: ${error instanceof Error ? error.message : String(error)}`,
@@ -544,12 +558,24 @@ export class RscWorker {
544
558
  this.#status = "starting";
545
559
  const workerPath = this.#resolveWorkerPath();
546
560
  let proc!: Bun.Subprocess<"ignore", "inherit", "inherit">;
561
+ const earlyMessages: RscInMsg[] = [];
547
562
  proc = Bun.spawn(["bun", "--conditions", "react-server", workerPath], {
548
- ipc: (message: RscInMsg) => this.#handleMessage(message, proc),
563
+ ipc: (message: RscInMsg) => {
564
+ if (!proc) {
565
+ earlyMessages.push(message);
566
+ return;
567
+ }
568
+ this.#handleMessage(message, proc);
569
+ },
549
570
  stdio: ["ignore", "inherit", "inherit"],
550
571
  serialization: "advanced",
551
572
  env: { ...process.env },
552
573
  });
574
+ if (earlyMessages.length > 0) {
575
+ setTimeout(() => {
576
+ for (const message of earlyMessages.splice(0)) this.#handleMessage(message, proc);
577
+ }, 0);
578
+ }
553
579
  proc.exited.then((code) => this.#handleExit(proc, code));
554
580
  return proc;
555
581
  }
@@ -603,7 +629,9 @@ export class RscWorker {
603
629
  this.#pending.get(message.requestId)?.onChunk(message.data);
604
630
  return;
605
631
  case "meta":
606
- this.#pending.get(message.requestId)?.onMeta?.({ theme: message.theme, status: message.status });
632
+ this.#pending
633
+ .get(message.requestId)
634
+ ?.onMeta?.({ theme: message.theme, status: message.status, trace: message.trace });
607
635
  return;
608
636
  case "end":
609
637
  this.#resolvePending(message.requestId, (p) => p.onEnd());
@@ -1,7 +1,8 @@
1
1
  import type { RouteCacheRenderState } from "./cachePolicy";
2
+ import type { RscTraceMetadata } from "./ssrTypes";
2
3
 
3
4
  export type CachedRscReplayMessage =
4
- | { type: "meta"; requestId: string; theme?: string; status?: number }
5
+ | { type: "meta"; requestId: string; theme?: string; status?: number; trace?: RscTraceMetadata }
5
6
  | { type: "cache-state"; requestId: string; state: RouteCacheRenderState }
6
7
  | { type: "chunk"; requestId: string; data: Uint8Array }
7
8
  | { type: "end"; requestId: string };
@@ -15,6 +16,7 @@ export async function replayCachedRscResult(input: {
15
16
  chunks: readonly Uint8Array[];
16
17
  theme?: string;
17
18
  status?: number;
19
+ trace?: RscTraceMetadata;
18
20
  cacheState?: RouteCacheRenderState;
19
21
  send: (message: CachedRscReplayMessage) => void;
20
22
  isCancelled: () => boolean;
@@ -27,7 +29,14 @@ export async function replayCachedRscResult(input: {
27
29
  : 1;
28
30
  const yieldToHost = input.yieldToHost ?? yieldToHostEventLoop;
29
31
  if (input.isCancelled()) return false;
30
- input.send({ type: "meta", requestId: input.requestId, theme: input.theme, status: input.status });
32
+ const metaMessage: CachedRscReplayMessage = {
33
+ type: "meta",
34
+ requestId: input.requestId,
35
+ theme: input.theme,
36
+ status: input.status,
37
+ };
38
+ if (input.trace) metaMessage.trace = input.trace;
39
+ input.send(metaMessage);
31
40
  input.send({ type: "cache-state", requestId: input.requestId, state: input.cacheState ?? { cacheable: true } });
32
41
  for (let index = 0; index < input.chunks.length; index += 1) {
33
42
  if (input.isCancelled()) return false;
@@ -1,6 +1,6 @@
1
1
  import { Readable } from "node:stream";
2
2
  import { type AkanRequestStore, type AkanTheme, pushRequestFallback, requestStorage } from "akanjs/fetch";
3
- import { type ReactNode, use } from "react";
3
+ import type { ReactNode } from "react";
4
4
  import { renderToReadableStream } from "react-dom/server.browser";
5
5
  import { createFromNodeStream } from "react-server-dom-webpack/client.node";
6
6
  import type { SsrChunkRegistryStats, SsrFromRscInput, SsrLateRedirect } from "./ssrTypes";
@@ -121,12 +121,13 @@ export function createSoftRedirectScript(redirect: SsrLateRedirect): string {
121
121
 
122
122
  function sanitizeFlightRows(
123
123
  stream: ReadableStream<Uint8Array>,
124
- options: { rewriteStylesheetHints?: boolean } = {},
124
+ options: { rewriteStylesheetHints?: boolean; dropDebugInfoRows?: boolean } = {},
125
125
  ): ReadableStream<Uint8Array> {
126
126
  const decoder = new TextDecoder("utf-8", { fatal: true });
127
127
  const encoder = new TextEncoder();
128
128
  const hlStylesheetRe = /(:HL\["[^"\\]*(?:\\.[^"\\]*)*",)"stylesheet"(\])/g;
129
129
  const redirectErrorRowRe = /^([0-9a-z]+):E(\{[^\n]*"digest":"AKAN_REDIRECT(?:;[^"]*)?"[^\n]*\})(\n?)$/;
130
+ const debugInfoRowRe = /^[0-9a-z]+:D/;
130
131
  let buffered: Uint8Array<ArrayBuffer> = new Uint8Array(0);
131
132
 
132
133
  const concatBytes = (left: Uint8Array, right: Uint8Array): Uint8Array<ArrayBuffer> => {
@@ -143,6 +144,7 @@ function sanitizeFlightRows(
143
144
  } catch {
144
145
  return row;
145
146
  }
147
+ if (options.dropDebugInfoRows && debugInfoRowRe.test(text)) return new Uint8Array(0);
146
148
  const sanitized = (options.rewriteStylesheetHints ? text.replace(hlStylesheetRe, `$1"style"$2`) : text).replace(
147
149
  redirectErrorRowRe,
148
150
  "$1:null$3",
@@ -180,6 +182,10 @@ export function sanitizeFlightForClientStream(stream: ReadableStream<Uint8Array>
180
182
  return sanitizeFlightRows(stream, { rewriteStylesheetHints: true });
181
183
  }
182
184
 
185
+ export function sanitizeFlightForSsrStream(stream: ReadableStream<Uint8Array>): ReadableStream<Uint8Array> {
186
+ return sanitizeFlightRows(stream, { dropDebugInfoRows: true });
187
+ }
188
+
183
189
  type StderrWrite = typeof process.stderr.write;
184
190
 
185
191
  export class ExpectedLateRedirectStderrSuppressor {
@@ -445,7 +451,6 @@ export function interleaveRscScriptsWithHtml(
445
451
  if (done || errored) break;
446
452
  controller.enqueue(value);
447
453
  bootstrapDetector.observe(value);
448
- flushPendingRscScripts();
449
454
  }
450
455
  } finally {
451
456
  htmlReader.releaseLock();
@@ -578,25 +583,25 @@ export class SsrFromRscRenderer {
578
583
 
579
584
  const [rscForSsr, rscForClient] = input.rscStream.tee();
580
585
 
581
- const ssrNodeStream = Readable.fromWeb(sanitizeFlightRows(rscForSsr) as never);
586
+ const ssrNodeStream = Readable.fromWeb(sanitizeFlightForSsrStream(rscForSsr) as never);
582
587
  const stderrSuppressor = ExpectedLateRedirectStderrSuppressor.start(input.lateControl);
583
588
  const thenable = SsrFromRscRenderer.#suppressExpectedLateRedirectError(
584
589
  createFromNodeStream(ssrNodeStream, input.ssrManifest),
585
590
  input.lateControl,
586
591
  );
587
592
 
588
- function Root(): ReactNode {
589
- return use(thenable);
590
- }
591
-
592
593
  const bootstrap = input.extraBootstrapInline
593
594
  ? `${SsrFromRscRenderer.#clientBootstrap}\n${input.extraBootstrapInline}`
594
595
  : SsrFromRscRenderer.#clientBootstrap;
595
596
 
596
- const renderHtml = () =>
597
- renderToReadableStream(<Root />, {
597
+ const renderHtml = async () => {
598
+ const root = await thenable;
599
+ const stream = await renderToReadableStream(root, {
598
600
  bootstrapScriptContent: bootstrap,
599
601
  });
602
+ await stream.allReady;
603
+ return stream;
604
+ };
600
605
  const requestContext = input.requestStore ?? input.request;
601
606
  const htmlStream =
602
607
  requestContext && requestStorage ? await requestStorage.run(requestContext, renderHtml) : await renderHtml();
@@ -26,6 +26,24 @@ export interface SsrLateRedirect {
26
26
  status: 303 | 307 | 308;
27
27
  }
28
28
 
29
+ export interface RscTraceMetadata {
30
+ navId?: string;
31
+ pathname: string;
32
+ routeId: string;
33
+ cache: "hit" | "miss" | "bypass";
34
+ cacheReason?: string;
35
+ cacheKeyHash?: string;
36
+ partial?: "full" | "candidate" | "patch" | "fallback";
37
+ partialReason?: string;
38
+ partialCommonPrefixLength?: number;
39
+ patchStartIndex?: number;
40
+ patchSegmentPath?: string;
41
+ patchStartSegment?: string;
42
+ patchHeadSafe?: boolean;
43
+ patchHeadSnapshot?: string;
44
+ routeState?: string;
45
+ }
46
+
29
47
  export interface SsrFromRscInput {
30
48
  request?: Request;
31
49
  requestStore?: AkanRequestStore;
package/server/types.tsx CHANGED
@@ -1,5 +1,7 @@
1
1
  import type { PromiseOrObject } from "akanjs/base";
2
2
  import type { AkanI18nConfig } from "akanjs/common";
3
+ import type { ClientManifest } from "./artifact";
4
+ import type { SsrManifest } from "./ssrTypes";
3
5
 
4
6
  export type WebsocketRoute = (
5
7
  ws: Bun.ServerWebSocket<unknown>,
@@ -85,6 +87,8 @@ export function getAkanImageWidths(config: Pick<AkanImageConfig, "deviceSizes" |
85
87
 
86
88
  export type BaseBuildArtifact = {
87
89
  rscClientUrl: string;
90
+ rscRuntimeClientManifest?: ClientManifest;
91
+ rscRuntimeSsrManifest?: SsrManifest;
88
92
  vendorMap: Record<string, string>;
89
93
  pagesBundlePath: string;
90
94
  pagesBundleBuildId: number;