@bleedingdev/modern-js-runtime-extensions 3.9.0-ultramodern.1 → 3.9.0-ultramodern.10

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 (198) hide show
  1. package/dist/cjs/buildIdentity.js +73 -0
  2. package/dist/cjs/contextExtensions.js +74 -0
  3. package/dist/cjs/headRuntime.js +91 -0
  4. package/dist/cjs/helmetContext.js +54 -0
  5. package/dist/cjs/index.js +18 -4
  6. package/dist/cjs/localisedLoaderIdentity.js +51 -0
  7. package/dist/cjs/prefetchPolicy.js +52 -0
  8. package/dist/cjs/reactContext.js +45 -0
  9. package/dist/cjs/request-policy/browser.js +139 -0
  10. package/dist/cjs/request-policy/client.js +234 -0
  11. package/dist/cjs/request-policy/node.js +149 -0
  12. package/dist/cjs/request-policy/policyCore.js +266 -0
  13. package/dist/cjs/request-policy/requestContext.js +137 -0
  14. package/dist/cjs/request-policy/traceparent.js +56 -0
  15. package/dist/cjs/request-policy/transport.js +234 -0
  16. package/dist/cjs/request-policy/types.js +57 -0
  17. package/dist/cjs/routeHydration.js +127 -0
  18. package/dist/cjs/routerPrefetchPolicy.js +153 -0
  19. package/dist/cjs/routerProvider.js +105 -0
  20. package/dist/cjs/routerState.js +204 -0
  21. package/dist/cjs/routerStatePlugin.js +86 -0
  22. package/dist/cjs/routerStateTypes.js +18 -0
  23. package/dist/cjs/rsc-disabled/rscDisabledClientBrowserRuntime.js +69 -0
  24. package/dist/cjs/rsc-disabled/rscDisabledClientEdgeRuntime.js +61 -0
  25. package/dist/cjs/rsc-disabled/rscDisabledClientNodeRuntime.js +65 -0
  26. package/dist/cjs/rsc-disabled/rscDisabledRuntimeError.js +42 -0
  27. package/dist/cjs/rsc-disabled/rscDisabledServerEdgeRuntime.js +93 -0
  28. package/dist/cjs/rsc-disabled/rscDisabledServerNodeRuntime.js +101 -0
  29. package/dist/cjs/runtimeContextProjection.js +112 -0
  30. package/dist/cjs/server-config/bffRuntime.js +18 -0
  31. package/dist/cjs/server-config/index.js +18 -0
  32. package/dist/cjs/server-config/serverTelemetry.js +18 -0
  33. package/dist/cjs/shellStream.js +34 -2
  34. package/dist/cjs/workspaceEvents.js +159 -0
  35. package/dist/esm/buildIdentity.mjs +35 -0
  36. package/dist/esm/contextExtensions.mjs +33 -0
  37. package/dist/esm/headRuntime.mjs +43 -0
  38. package/dist/esm/helmetContext.mjs +13 -0
  39. package/dist/esm/index.mjs +2 -0
  40. package/dist/esm/localisedLoaderIdentity.mjs +13 -0
  41. package/dist/esm/prefetchPolicy.mjs +11 -0
  42. package/dist/esm/reactContext.mjs +7 -0
  43. package/dist/esm/request-policy/browser.mjs +11 -0
  44. package/dist/esm/request-policy/client.mjs +195 -0
  45. package/dist/esm/request-policy/policyCore.mjs +171 -0
  46. package/dist/esm/request-policy/requestContext.mjs +75 -0
  47. package/dist/esm/request-policy/traceparent.mjs +18 -0
  48. package/dist/esm/request-policy/transport.mjs +196 -0
  49. package/dist/esm/request-policy/types.mjs +10 -0
  50. package/dist/esm/routeHydration.mjs +77 -0
  51. package/dist/esm/routerPrefetchPolicy.mjs +115 -0
  52. package/dist/esm/routerProvider.mjs +58 -0
  53. package/dist/esm/routerState.mjs +139 -0
  54. package/dist/esm/routerStatePlugin.mjs +48 -0
  55. package/dist/esm/routerStateTypes.mjs +0 -0
  56. package/dist/esm/rsc-disabled/rscDisabledClientBrowserRuntime.mjs +10 -0
  57. package/dist/esm/rsc-disabled/rscDisabledClientEdgeRuntime.mjs +8 -0
  58. package/dist/esm/rsc-disabled/rscDisabledClientNodeRuntime.mjs +9 -0
  59. package/dist/esm/rsc-disabled/rscDisabledRuntimeError.mjs +4 -0
  60. package/dist/esm/rsc-disabled/rscDisabledServerEdgeRuntime.mjs +16 -0
  61. package/dist/esm/rsc-disabled/rscDisabledServerNodeRuntime.mjs +18 -0
  62. package/dist/esm/runtimeContextProjection.mjs +74 -0
  63. package/dist/esm/server-config/bffRuntime.mjs +0 -0
  64. package/dist/esm/server-config/index.mjs +0 -0
  65. package/dist/esm/server-config/serverTelemetry.mjs +0 -0
  66. package/dist/esm/shellStream.mjs +34 -2
  67. package/dist/esm/workspaceEvents.mjs +66 -0
  68. package/dist/esm-node/buildIdentity.mjs +36 -0
  69. package/dist/esm-node/contextExtensions.mjs +34 -0
  70. package/dist/esm-node/headRuntime.mjs +44 -0
  71. package/dist/esm-node/helmetContext.mjs +14 -0
  72. package/dist/esm-node/index.mjs +2 -0
  73. package/dist/esm-node/localisedLoaderIdentity.mjs +14 -0
  74. package/dist/esm-node/prefetchPolicy.mjs +12 -0
  75. package/dist/esm-node/reactContext.mjs +8 -0
  76. package/dist/esm-node/request-policy/browser.mjs +12 -0
  77. package/dist/esm-node/request-policy/client.mjs +196 -0
  78. package/dist/esm-node/request-policy/node.mjs +22 -0
  79. package/dist/esm-node/request-policy/policyCore.mjs +172 -0
  80. package/dist/esm-node/request-policy/requestContext.mjs +76 -0
  81. package/dist/esm-node/request-policy/traceparent.mjs +19 -0
  82. package/dist/esm-node/request-policy/transport.mjs +197 -0
  83. package/dist/esm-node/request-policy/types.mjs +11 -0
  84. package/dist/esm-node/routeHydration.mjs +78 -0
  85. package/dist/esm-node/routerPrefetchPolicy.mjs +116 -0
  86. package/dist/esm-node/routerProvider.mjs +59 -0
  87. package/dist/esm-node/routerState.mjs +140 -0
  88. package/dist/esm-node/routerStatePlugin.mjs +49 -0
  89. package/dist/esm-node/routerStateTypes.mjs +1 -0
  90. package/dist/esm-node/rsc-disabled/rscDisabledClientBrowserRuntime.mjs +11 -0
  91. package/dist/esm-node/rsc-disabled/rscDisabledClientEdgeRuntime.mjs +9 -0
  92. package/dist/esm-node/rsc-disabled/rscDisabledClientNodeRuntime.mjs +10 -0
  93. package/dist/esm-node/rsc-disabled/rscDisabledRuntimeError.mjs +5 -0
  94. package/dist/esm-node/rsc-disabled/rscDisabledServerEdgeRuntime.mjs +17 -0
  95. package/dist/esm-node/rsc-disabled/rscDisabledServerNodeRuntime.mjs +19 -0
  96. package/dist/esm-node/runtimeContextProjection.mjs +75 -0
  97. package/dist/esm-node/server-config/bffRuntime.mjs +1 -0
  98. package/dist/esm-node/server-config/index.mjs +1 -0
  99. package/dist/esm-node/server-config/serverTelemetry.mjs +1 -0
  100. package/dist/esm-node/shellStream.mjs +34 -2
  101. package/dist/esm-node/workspaceEvents.mjs +67 -0
  102. package/dist/types/buildIdentity.d.ts +37 -0
  103. package/dist/types/contextExtensions.d.ts +34 -0
  104. package/dist/types/headRuntime.d.ts +12 -0
  105. package/dist/types/helmetContext.d.ts +10 -0
  106. package/dist/types/index.d.ts +2 -0
  107. package/dist/types/localisedLoaderIdentity.d.ts +10 -0
  108. package/dist/types/prefetchPolicy.d.ts +4 -0
  109. package/dist/types/reactContext.d.ts +6 -0
  110. package/dist/types/request-policy/browser.d.ts +10 -0
  111. package/dist/types/request-policy/client.d.ts +14 -0
  112. package/dist/types/request-policy/node.d.ts +10 -0
  113. package/dist/types/request-policy/policyCore.d.ts +107 -0
  114. package/dist/types/request-policy/requestContext.d.ts +22 -0
  115. package/dist/types/request-policy/traceparent.d.ts +10 -0
  116. package/dist/types/request-policy/transport.d.ts +12 -0
  117. package/dist/types/request-policy/types.d.ts +144 -0
  118. package/dist/types/routeHydration.d.ts +24 -0
  119. package/dist/types/routerPrefetchPolicy.d.ts +28 -0
  120. package/dist/types/routerProvider.d.ts +55 -0
  121. package/dist/types/routerState.d.ts +18 -0
  122. package/dist/types/routerStatePlugin.d.ts +19 -0
  123. package/dist/types/routerStateTypes.d.ts +40 -0
  124. package/dist/types/rsc-disabled/rscDisabledClientBrowserRuntime.d.ts +9 -0
  125. package/dist/types/rsc-disabled/rscDisabledClientEdgeRuntime.d.ts +7 -0
  126. package/dist/types/rsc-disabled/rscDisabledClientNodeRuntime.d.ts +8 -0
  127. package/dist/types/rsc-disabled/rscDisabledRuntimeError.d.ts +1 -0
  128. package/dist/types/rsc-disabled/rscDisabledServerEdgeRuntime.d.ts +15 -0
  129. package/dist/types/rsc-disabled/rscDisabledServerNodeRuntime.d.ts +17 -0
  130. package/dist/types/runtimeContextProjection.d.ts +30 -0
  131. package/dist/types/server-config/bffRuntime.d.ts +122 -0
  132. package/dist/types/server-config/index.d.ts +2 -0
  133. package/dist/types/server-config/serverTelemetry.d.ts +322 -0
  134. package/dist/types/workspaceEvents.d.ts +62 -0
  135. package/package.json +239 -14
  136. package/rslib.config.mts +17 -2
  137. package/rstest.config.mts +33 -2
  138. package/src/buildIdentity.ts +61 -0
  139. package/src/contextExtensions.ts +81 -0
  140. package/src/headRuntime.tsx +77 -0
  141. package/src/helmetContext.ts +31 -0
  142. package/src/index.ts +2 -0
  143. package/src/localisedLoaderIdentity.ts +30 -0
  144. package/src/prefetchPolicy.ts +33 -0
  145. package/src/reactContext.ts +16 -0
  146. package/src/request-policy/browser.ts +22 -0
  147. package/src/request-policy/client.ts +404 -0
  148. package/src/request-policy/node.ts +41 -0
  149. package/src/request-policy/policyCore.ts +424 -0
  150. package/src/request-policy/requestContext.ts +149 -0
  151. package/src/request-policy/traceparent.ts +43 -0
  152. package/src/request-policy/transport.ts +349 -0
  153. package/src/request-policy/types.ts +229 -0
  154. package/src/routeHydration.ts +208 -0
  155. package/src/routerPrefetchPolicy.ts +195 -0
  156. package/src/routerProvider.ts +160 -0
  157. package/src/routerState.ts +290 -0
  158. package/src/routerStatePlugin.ts +86 -0
  159. package/src/routerStateTypes.ts +46 -0
  160. package/src/rsc-disabled/rscDisabledClientBrowserRuntime.ts +10 -0
  161. package/src/rsc-disabled/rscDisabledClientEdgeRuntime.ts +8 -0
  162. package/src/rsc-disabled/rscDisabledClientNodeRuntime.ts +9 -0
  163. package/src/rsc-disabled/rscDisabledRuntimeError.ts +3 -0
  164. package/src/rsc-disabled/rscDisabledServerEdgeRuntime.ts +16 -0
  165. package/src/rsc-disabled/rscDisabledServerNodeRuntime.ts +18 -0
  166. package/src/runtimeContextProjection.ts +105 -0
  167. package/src/server-config/bffRuntime.ts +136 -0
  168. package/src/server-config/index.ts +2 -0
  169. package/src/server-config/serverTelemetry.ts +337 -0
  170. package/src/shellStream.ts +55 -25
  171. package/src/workspaceEvents.ts +324 -0
  172. package/tests/buildIdentity.test.ts +51 -0
  173. package/tests/contextExtensions.test.ts +27 -0
  174. package/tests/headRuntime.test.tsx +229 -0
  175. package/tests/rendererHead.test.ts +1 -113
  176. package/tests/request-policy/browser-policy.test.ts +108 -0
  177. package/tests/request-policy/client-isolation.test.ts +109 -0
  178. package/tests/request-policy/node-policy.test.ts +447 -0
  179. package/tests/request-policy/policyCore.test.ts +129 -0
  180. package/tests/request-policy/requestContext.test.ts +66 -0
  181. package/tests/request-policy/requestFactory.outbound-contract.test.ts +290 -0
  182. package/tests/request-policy/traceparent.test.ts +52 -0
  183. package/tests/request-policy/transport-retry.test.ts +367 -0
  184. package/tests/routeHydration.test.ts +180 -0
  185. package/tests/routerPrefetchPolicy.test.ts +102 -0
  186. package/tests/routerProvider.test.ts +29 -0
  187. package/tests/routerState.test.ts +78 -0
  188. package/tests/routerStatePlugin.test.ts +63 -0
  189. package/tests/runtimeContextProjection.test.ts +83 -0
  190. package/tests/safeFailure.test.ts +10 -28
  191. package/tests/shellStream.test.ts +37 -0
  192. package/tests/workspaceEvents.test.ts +221 -0
  193. package/dist/cjs/rscHtmlStream.js +0 -128
  194. package/dist/esm/rscHtmlStream.mjs +0 -86
  195. package/dist/esm-node/rscHtmlStream.mjs +0 -87
  196. package/dist/types/rscHtmlStream.d.ts +0 -4
  197. package/src/rscHtmlStream.ts +0 -142
  198. package/tests/rscHtmlStream.test.ts +0 -153
@@ -0,0 +1,324 @@
1
+ export type UltramodernPerformanceReadinessSignalId =
2
+ | 'bfcache'
3
+ | 'core-web-vitals-rum'
4
+ | 'duplicate-prefetch-warmup'
5
+ | 'cache-policy-sanity'
6
+ | 'save-data-behavior'
7
+ | 'cloudflare-ssr-cache-hints';
8
+
9
+ export type UltramodernWorkspaceLocale = 'en' | 'cs';
10
+
11
+ export type UltramodernPerformanceReadinessSignalStatus =
12
+ | 'pass'
13
+ | 'warn'
14
+ | 'fail';
15
+
16
+ export const ultramodernWorkspaceEventNames = {
17
+ navigate: 'ultramodern:navigate',
18
+ performanceSignal: 'ultramodern:performance-signal',
19
+ remoteReady: 'ultramodern:remote-ready',
20
+ routeSettled: 'ultramodern:route-settled',
21
+ } as const;
22
+
23
+ export type UltramodernWorkspaceEventName =
24
+ (typeof ultramodernWorkspaceEventNames)[keyof typeof ultramodernWorkspaceEventNames];
25
+
26
+ export interface UltramodernNavigatePayload {
27
+ to: string;
28
+ replace?: boolean;
29
+ state?: Record<string, unknown>;
30
+ }
31
+
32
+ export interface UltramodernRouteSettledPayload {
33
+ pathname: string;
34
+ locale?: UltramodernWorkspaceLocale;
35
+ title?: string;
36
+ }
37
+
38
+ export interface UltramodernRemoteReadyPayload {
39
+ appId: string;
40
+ build?: string;
41
+ surface?: string;
42
+ version?: string;
43
+ }
44
+
45
+ export interface UltramodernPerformanceSignalPayload {
46
+ signalId: UltramodernPerformanceReadinessSignalId;
47
+ status: UltramodernPerformanceReadinessSignalStatus;
48
+ durationMs?: number;
49
+ detail?: Record<string, unknown>;
50
+ }
51
+
52
+ export interface UltramodernWorkspaceEventPayloadMap {
53
+ 'ultramodern:navigate': UltramodernNavigatePayload;
54
+ 'ultramodern:performance-signal': UltramodernPerformanceSignalPayload;
55
+ 'ultramodern:remote-ready': UltramodernRemoteReadyPayload;
56
+ 'ultramodern:route-settled': UltramodernRouteSettledPayload;
57
+ }
58
+
59
+ export class UltramodernWorkspaceEventValidationError {
60
+ readonly message: string;
61
+ readonly name = 'UltramodernWorkspaceEventValidationError';
62
+ readonly eventName: UltramodernWorkspaceEventName;
63
+ readonly payload: unknown;
64
+
65
+ constructor(eventName: UltramodernWorkspaceEventName, payload: unknown) {
66
+ this.message = `Invalid payload for UltraModern workspace event "${eventName}"`;
67
+ this.eventName = eventName;
68
+ this.payload = payload;
69
+ }
70
+ }
71
+
72
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
73
+ typeof value === 'object' && value !== null && !Array.isArray(value);
74
+
75
+ const isNonEmptyString = (value: unknown): value is string =>
76
+ typeof value === 'string' && value.trim().length > 0;
77
+
78
+ const isNonNegativeNumber = (value: unknown): value is number =>
79
+ typeof value === 'number' && Number.isFinite(value) && value >= 0;
80
+
81
+ const isUltramodernWorkspaceLocale = (
82
+ value: unknown,
83
+ ): value is UltramodernWorkspaceLocale => value === 'en' || value === 'cs';
84
+
85
+ const isPerformanceReadinessSignalId = (
86
+ value: unknown,
87
+ ): value is UltramodernPerformanceReadinessSignalId =>
88
+ value === 'bfcache' ||
89
+ value === 'core-web-vitals-rum' ||
90
+ value === 'duplicate-prefetch-warmup' ||
91
+ value === 'cache-policy-sanity' ||
92
+ value === 'save-data-behavior' ||
93
+ value === 'cloudflare-ssr-cache-hints';
94
+
95
+ const isPerformanceReadinessSignalStatus = (
96
+ value: unknown,
97
+ ): value is UltramodernPerformanceReadinessSignalStatus =>
98
+ value === 'pass' || value === 'warn' || value === 'fail';
99
+
100
+ const hasOptionalString = (value: Record<string, unknown>, key: string) =>
101
+ value[key] === undefined || isNonEmptyString(value[key]);
102
+
103
+ const hasOptionalBoolean = (value: Record<string, unknown>, key: string) =>
104
+ value[key] === undefined || typeof value[key] === 'boolean';
105
+
106
+ const hasOptionalRecord = (value: Record<string, unknown>, key: string) =>
107
+ value[key] === undefined || isRecord(value[key]);
108
+
109
+ const hasOptionalNonNegativeNumber = (
110
+ value: Record<string, unknown>,
111
+ key: string,
112
+ ) => value[key] === undefined || isNonNegativeNumber(value[key]);
113
+
114
+ const hasOptionalLocale = (value: Record<string, unknown>, key: string) =>
115
+ value[key] === undefined || isUltramodernWorkspaceLocale(value[key]);
116
+
117
+ export const isUltramodernNavigatePayload = (
118
+ payload: unknown,
119
+ ): payload is UltramodernNavigatePayload =>
120
+ isRecord(payload) &&
121
+ isNonEmptyString(payload.to) &&
122
+ hasOptionalBoolean(payload, 'replace') &&
123
+ hasOptionalRecord(payload, 'state');
124
+
125
+ export const isUltramodernRouteSettledPayload = (
126
+ payload: unknown,
127
+ ): payload is UltramodernRouteSettledPayload =>
128
+ isRecord(payload) &&
129
+ isNonEmptyString(payload.pathname) &&
130
+ hasOptionalLocale(payload, 'locale') &&
131
+ hasOptionalString(payload, 'title');
132
+
133
+ export const isUltramodernRemoteReadyPayload = (
134
+ payload: unknown,
135
+ ): payload is UltramodernRemoteReadyPayload =>
136
+ isRecord(payload) &&
137
+ isNonEmptyString(payload.appId) &&
138
+ hasOptionalString(payload, 'build') &&
139
+ hasOptionalString(payload, 'surface') &&
140
+ hasOptionalString(payload, 'version');
141
+
142
+ export const isUltramodernPerformanceSignalPayload = (
143
+ payload: unknown,
144
+ ): payload is UltramodernPerformanceSignalPayload =>
145
+ isRecord(payload) &&
146
+ isPerformanceReadinessSignalId(payload.signalId) &&
147
+ isPerformanceReadinessSignalStatus(payload.status) &&
148
+ hasOptionalNonNegativeNumber(payload, 'durationMs') &&
149
+ hasOptionalRecord(payload, 'detail');
150
+
151
+ const ultramodernWorkspaceEventValidators: {
152
+ [Name in UltramodernWorkspaceEventName]: (
153
+ payload: unknown,
154
+ ) => payload is UltramodernWorkspaceEventPayloadMap[Name];
155
+ } = {
156
+ [ultramodernWorkspaceEventNames.navigate]: isUltramodernNavigatePayload,
157
+ [ultramodernWorkspaceEventNames.performanceSignal]:
158
+ isUltramodernPerformanceSignalPayload,
159
+ [ultramodernWorkspaceEventNames.remoteReady]: isUltramodernRemoteReadyPayload,
160
+ [ultramodernWorkspaceEventNames.routeSettled]:
161
+ isUltramodernRouteSettledPayload,
162
+ };
163
+
164
+ export const isUltramodernWorkspaceEventPayload = <
165
+ Name extends UltramodernWorkspaceEventName,
166
+ >(
167
+ eventName: Name,
168
+ payload: unknown,
169
+ ): payload is UltramodernWorkspaceEventPayloadMap[Name] =>
170
+ ultramodernWorkspaceEventValidators[eventName](payload);
171
+
172
+ export const assertUltramodernWorkspaceEventPayload = <
173
+ Name extends UltramodernWorkspaceEventName,
174
+ >(
175
+ eventName: Name,
176
+ payload: unknown,
177
+ ): UltramodernWorkspaceEventPayloadMap[Name] => {
178
+ if (!isUltramodernWorkspaceEventPayload(eventName, payload)) {
179
+ throw new UltramodernWorkspaceEventValidationError(eventName, payload);
180
+ }
181
+
182
+ return payload;
183
+ };
184
+
185
+ export const createUltramodernWorkspaceEvent = <
186
+ Name extends UltramodernWorkspaceEventName,
187
+ >(
188
+ eventName: Name,
189
+ payload: UltramodernWorkspaceEventPayloadMap[Name],
190
+ ): CustomEvent<UltramodernWorkspaceEventPayloadMap[Name]> =>
191
+ new CustomEvent(eventName, {
192
+ bubbles: true,
193
+ composed: true,
194
+ detail: assertUltramodernWorkspaceEventPayload(eventName, payload),
195
+ });
196
+
197
+ export const dispatchUltramodernWorkspaceEvent = <
198
+ Name extends UltramodernWorkspaceEventName,
199
+ >(
200
+ target: EventTarget,
201
+ eventName: Name,
202
+ payload: UltramodernWorkspaceEventPayloadMap[Name],
203
+ ) => target.dispatchEvent(createUltramodernWorkspaceEvent(eventName, payload));
204
+
205
+ export const onUltramodernWorkspaceEvent = <
206
+ Name extends UltramodernWorkspaceEventName,
207
+ >(
208
+ target: EventTarget,
209
+ eventName: Name,
210
+ handler: (
211
+ payload: UltramodernWorkspaceEventPayloadMap[Name],
212
+ event: CustomEvent<UltramodernWorkspaceEventPayloadMap[Name]>,
213
+ ) => void,
214
+ ) => {
215
+ const listener = (event: Event) => {
216
+ if (!('detail' in event)) {
217
+ throw new UltramodernWorkspaceEventValidationError(eventName, undefined);
218
+ }
219
+
220
+ const customEvent = event as CustomEvent<unknown>;
221
+ handler(
222
+ assertUltramodernWorkspaceEventPayload(eventName, customEvent.detail),
223
+ customEvent as CustomEvent<UltramodernWorkspaceEventPayloadMap[Name]>,
224
+ );
225
+ };
226
+
227
+ target.addEventListener(eventName, listener);
228
+
229
+ return () => {
230
+ target.removeEventListener(eventName, listener);
231
+ };
232
+ };
233
+
234
+ export const dispatchUltramodernNavigate = (
235
+ target: EventTarget,
236
+ payload: UltramodernNavigatePayload,
237
+ ) =>
238
+ dispatchUltramodernWorkspaceEvent(
239
+ target,
240
+ ultramodernWorkspaceEventNames.navigate,
241
+ payload,
242
+ );
243
+
244
+ export const dispatchUltramodernRouteSettled = (
245
+ target: EventTarget,
246
+ payload: UltramodernRouteSettledPayload,
247
+ ) =>
248
+ dispatchUltramodernWorkspaceEvent(
249
+ target,
250
+ ultramodernWorkspaceEventNames.routeSettled,
251
+ payload,
252
+ );
253
+
254
+ export const dispatchUltramodernRemoteReady = (
255
+ target: EventTarget,
256
+ payload: UltramodernRemoteReadyPayload,
257
+ ) =>
258
+ dispatchUltramodernWorkspaceEvent(
259
+ target,
260
+ ultramodernWorkspaceEventNames.remoteReady,
261
+ payload,
262
+ );
263
+
264
+ export const dispatchUltramodernPerformanceSignal = (
265
+ target: EventTarget,
266
+ payload: UltramodernPerformanceSignalPayload,
267
+ ) =>
268
+ dispatchUltramodernWorkspaceEvent(
269
+ target,
270
+ ultramodernWorkspaceEventNames.performanceSignal,
271
+ payload,
272
+ );
273
+
274
+ export const onUltramodernNavigate = (
275
+ target: EventTarget,
276
+ handler: (
277
+ payload: UltramodernNavigatePayload,
278
+ event: CustomEvent<UltramodernNavigatePayload>,
279
+ ) => void,
280
+ ) =>
281
+ onUltramodernWorkspaceEvent(
282
+ target,
283
+ ultramodernWorkspaceEventNames.navigate,
284
+ handler,
285
+ );
286
+
287
+ export const onUltramodernRouteSettled = (
288
+ target: EventTarget,
289
+ handler: (
290
+ payload: UltramodernRouteSettledPayload,
291
+ event: CustomEvent<UltramodernRouteSettledPayload>,
292
+ ) => void,
293
+ ) =>
294
+ onUltramodernWorkspaceEvent(
295
+ target,
296
+ ultramodernWorkspaceEventNames.routeSettled,
297
+ handler,
298
+ );
299
+
300
+ export const onUltramodernRemoteReady = (
301
+ target: EventTarget,
302
+ handler: (
303
+ payload: UltramodernRemoteReadyPayload,
304
+ event: CustomEvent<UltramodernRemoteReadyPayload>,
305
+ ) => void,
306
+ ) =>
307
+ onUltramodernWorkspaceEvent(
308
+ target,
309
+ ultramodernWorkspaceEventNames.remoteReady,
310
+ handler,
311
+ );
312
+
313
+ export const onUltramodernPerformanceSignal = (
314
+ target: EventTarget,
315
+ handler: (
316
+ payload: UltramodernPerformanceSignalPayload,
317
+ event: CustomEvent<UltramodernPerformanceSignalPayload>,
318
+ ) => void,
319
+ ) =>
320
+ onUltramodernWorkspaceEvent(
321
+ target,
322
+ ultramodernWorkspaceEventNames.performanceSignal,
323
+ handler,
324
+ );
@@ -0,0 +1,51 @@
1
+ import { resolveUltramodernBuildArtifact } from '../src/buildIdentity';
2
+
3
+ const artifact = {
4
+ deliveryUnit: {
5
+ build: 'original',
6
+ buildMarker: 'original',
7
+ sourceRevision: 'workspace',
8
+ unitId: 'shop/catalog',
9
+ },
10
+ surfaces: {
11
+ api: {
12
+ build: 'original',
13
+ buildMarker: 'original',
14
+ sourceRevision: 'workspace',
15
+ surface: 'api',
16
+ },
17
+ ui: {
18
+ build: 'original',
19
+ buildMarker: 'original',
20
+ sourceRevision: 'workspace',
21
+ surface: 'ui',
22
+ },
23
+ },
24
+ } as const;
25
+
26
+ const readers = {
27
+ buildMarker: () => 'compiled-build',
28
+ sourceRevision: () => 'compiled-revision',
29
+ };
30
+
31
+ it('falls back only for missing compiler references and preserves each supplied value', () => {
32
+ expect(resolveUltramodernBuildArtifact(artifact)).toEqual(artifact);
33
+
34
+ const result = resolveUltramodernBuildArtifact(artifact, {
35
+ buildMarker: readers.buildMarker,
36
+ sourceRevision: () => {
37
+ throw new ReferenceError('ULTRAMODERN_SOURCE_REVISION is not defined');
38
+ },
39
+ });
40
+ expect(result.deliveryUnit.buildMarker).toBe('compiled-build');
41
+ expect(result.deliveryUnit.sourceRevision).toBe('workspace');
42
+ const failure = new Error('reader failed');
43
+ expect(() =>
44
+ resolveUltramodernBuildArtifact(artifact, {
45
+ ...readers,
46
+ buildMarker: () => {
47
+ throw failure;
48
+ },
49
+ }),
50
+ ).toThrow(failure);
51
+ });
@@ -0,0 +1,27 @@
1
+ import { createRuntimeContextExtension } from '../src/contextExtensions';
2
+
3
+ describe('runtime context extensions', () => {
4
+ it('does not leak into string-key enumeration of the context', () => {
5
+ const extension = createRuntimeContextExtension<string>('test:hidden');
6
+ const context = {};
7
+ const keysBefore = Object.keys(context);
8
+
9
+ extension.set(context, 'secret');
10
+
11
+ expect(Object.keys(context)).toEqual(keysBefore);
12
+ expect(JSON.stringify(context)).not.toContain('secret');
13
+ for (const key in context) {
14
+ expect(typeof key).toBe('string');
15
+ expect((context as Record<string, unknown>)[key]).not.toBe('secret');
16
+ }
17
+ });
18
+
19
+ it('survives object spreads so SSR context copies keep their extensions', () => {
20
+ const extension = createRuntimeContextExtension<string>('test:spread');
21
+ const context = {};
22
+ extension.set(context, 'carried');
23
+
24
+ const copy = { ...context };
25
+ expect(extension.get(copy)).toBe('carried');
26
+ });
27
+ });
@@ -0,0 +1,229 @@
1
+ import React from 'react';
2
+ import { renderToString } from 'react-dom/server';
3
+ import { createHeadRuntime } from '../src/headRuntime';
4
+ import { ensureHelmetContext, getHelmetContext } from '../src/helmetContext';
5
+ import * as rendererHead from '../src/rendererHead';
6
+
7
+ const { Head: Helmet, wrapServerRoot } = createHeadRuntime();
8
+ const getHelmetData = (context: object) => getHelmetContext(context)?.helmet;
9
+
10
+ const createServerContext = () => ({ isBrowser: false }) as any;
11
+
12
+ const renderWithContext = (context: any, node: React.ReactNode) => {
13
+ rendererHead.beginHeadRender(context);
14
+ try {
15
+ const html = renderToString(
16
+ wrapServerRoot(node, {
17
+ runtimeContext: context,
18
+ helmetContext: ensureHelmetContext(context),
19
+ }),
20
+ );
21
+ return rendererHead.completeHeadRender(context, html);
22
+ } catch (error) {
23
+ rendererHead.abortHeadRender(context);
24
+ throw error;
25
+ }
26
+ };
27
+
28
+ describe('server Helmet collection', () => {
29
+ it('keeps independently registered adapters scoped to their own providers', () => {
30
+ const outer = createHeadRuntime();
31
+ const inner = createHeadRuntime();
32
+ const outerContext = {};
33
+ const innerContext = {};
34
+
35
+ renderToString(
36
+ outer.wrapServerRoot(
37
+ inner.wrapServerRoot(
38
+ <>
39
+ <outer.Head>
40
+ <title>outer application</title>
41
+ </outer.Head>
42
+ <inner.Head>
43
+ <title>inner application</title>
44
+ </inner.Head>
45
+ </>,
46
+ {
47
+ runtimeContext: innerContext,
48
+ helmetContext: ensureHelmetContext(innerContext),
49
+ },
50
+ ),
51
+ {
52
+ runtimeContext: outerContext,
53
+ helmetContext: ensureHelmetContext(outerContext),
54
+ },
55
+ ),
56
+ );
57
+
58
+ expect(getHelmetData(outerContext)?.title.toString()).toBe(
59
+ '<title data-rh="true">outer application</title>',
60
+ );
61
+ expect(getHelmetData(innerContext)?.title.toString()).toBe(
62
+ '<title data-rh="true">inner application</title>',
63
+ );
64
+ });
65
+
66
+ it('excludes Helmet records from a discarded Suspense primary branch', () => {
67
+ const context = createServerContext();
68
+ const never = new Promise<never>(() => {});
69
+
70
+ const SuspendForever = (): null => {
71
+ throw never;
72
+ };
73
+
74
+ const html = renderWithContext(
75
+ context,
76
+ <>
77
+ <Helmet>
78
+ <meta name="outside" content="committed" />
79
+ </Helmet>
80
+ <React.Suspense
81
+ fallback={
82
+ <>
83
+ <Helmet>
84
+ <meta name="fallback" content="committed" />
85
+ </Helmet>
86
+ <span>fallback rendered</span>
87
+ </>
88
+ }
89
+ >
90
+ <Helmet>
91
+ <meta name="abandoned-unique" content="ghost" />
92
+ </Helmet>
93
+ <SuspendForever />
94
+ </React.Suspense>
95
+ </>,
96
+ );
97
+
98
+ expect(html).toContain('fallback rendered');
99
+ const meta = getHelmetData(context)!.meta.toString();
100
+ expect(meta).toContain('name="outside"');
101
+ expect(meta).toContain('name="fallback"');
102
+ expect(meta).not.toContain('name="abandoned-unique"');
103
+ });
104
+
105
+ it('collects title/meta during SSR and renders nothing inline', () => {
106
+ const context = createServerContext();
107
+ const html = renderWithContext(
108
+ context,
109
+ <main>
110
+ <Helmet>
111
+ <title>Server Title</title>
112
+ <meta name="description" content="hello" />
113
+ </Helmet>
114
+ body
115
+ </main>,
116
+ );
117
+
118
+ expect(html).not.toContain('Server Title');
119
+ const helmet = getHelmetData(context)!;
120
+ expect(helmet.title.toString()).toBe(
121
+ '<title data-rh="true">Server Title</title>',
122
+ );
123
+ expect(helmet.meta.toString()).toBe(
124
+ '<meta data-rh="true" name="description" content="hello">',
125
+ );
126
+ });
127
+
128
+ it('dedupes meta by primary attribute across nested Helmets, inner wins', () => {
129
+ const context = createServerContext();
130
+ renderWithContext(
131
+ context,
132
+ <>
133
+ <Helmet>
134
+ <meta name="description" content="outer" />
135
+ <meta name="keywords" content="modernjs" />
136
+ </Helmet>
137
+ <section>
138
+ <Helmet>
139
+ <meta name="description" content="inner" />
140
+ </Helmet>
141
+ </section>
142
+ </>,
143
+ );
144
+
145
+ const meta = getHelmetData(context)!.meta.toString();
146
+ expect(meta).toContain('content="inner"');
147
+ expect(meta).not.toContain('content="outer"');
148
+ expect(meta).toContain('content="modernjs"');
149
+ });
150
+
151
+ it('applies the innermost title and the innermost titleTemplate', () => {
152
+ const context = createServerContext();
153
+ renderWithContext(
154
+ context,
155
+ <>
156
+ <Helmet titleTemplate="%s | Site">
157
+ <title>Outer</title>
158
+ </Helmet>
159
+ <Helmet>
160
+ <title>Inner</title>
161
+ </Helmet>
162
+ </>,
163
+ );
164
+
165
+ expect(getHelmetData(context)!.title.toString()).toBe(
166
+ '<title data-rh="true">Inner | Site</title>',
167
+ );
168
+ });
169
+
170
+ it('publishes prioritized SEO tags separately with component parity', () => {
171
+ const context = createServerContext();
172
+ renderWithContext(
173
+ context,
174
+ <Helmet prioritizeSeoTags htmlAttributes={{ class: 'app' } as any}>
175
+ <title>Priority Page</title>
176
+ <meta name="description" content="priority description" />
177
+ <meta name="keywords" content="ordinary keywords" />
178
+ <link rel="canonical" href="https://example.com/page" />
179
+ <link rel="stylesheet" href="/page.css" />
180
+ <script type="application/ld+json">{'{"name":"page"}'}</script>
181
+ <script src="/ordinary.js" />
182
+ </Helmet>,
183
+ );
184
+
185
+ const helmet = getHelmetData(context)!;
186
+ expect(helmet.priority.toString()).toContain('priority description');
187
+ expect(helmet.priority.toString()).toContain('https://example.com/page');
188
+ expect(helmet.priority.toString()).toContain('application/ld+json');
189
+ expect(helmet.meta.toString()).toContain('ordinary keywords');
190
+ expect(helmet.meta.toString()).not.toContain('priority description');
191
+ expect(helmet.link.toString()).toContain('/page.css');
192
+ expect(helmet.link.toString()).not.toContain('https://example.com/page');
193
+ expect(helmet.script.toString()).toContain('/ordinary.js');
194
+ expect(helmet.script.toString()).not.toContain('application/ld+json');
195
+ });
196
+
197
+ it('is idempotent when React replays the tree (streaming SSR retry)', () => {
198
+ const context = createServerContext();
199
+ const app = (
200
+ <>
201
+ <Helmet titleTemplate="%s | Site">
202
+ <title>Page</title>
203
+ <meta name="description" content="stable" />
204
+ <html lang="en" />
205
+ </Helmet>
206
+ <Helmet>
207
+ <meta property="og:title" content="Page" />
208
+ </Helmet>
209
+ </>
210
+ );
211
+
212
+ renderWithContext(context, app);
213
+ const first = getHelmetData(context)!;
214
+ const firstSnapshot = {
215
+ title: first.title.toString(),
216
+ meta: first.meta.toString(),
217
+ htmlAttributes: first.htmlAttributes.toString(),
218
+ };
219
+
220
+ // Simulate a replayed render against the same per-request context.
221
+ renderWithContext(context, app);
222
+ const second = getHelmetData(context)!;
223
+
224
+ expect(second.title.toString()).toBe(firstSnapshot.title);
225
+ expect(second.meta.toString()).toBe(firstSnapshot.meta);
226
+ expect(second.htmlAttributes.toString()).toBe(firstSnapshot.htmlAttributes);
227
+ expect(second.meta.toString().match(/description/g)).toHaveLength(1);
228
+ });
229
+ });