@bleedingdev/modern-js-runtime-extensions 3.9.0-ultramodern.4 → 3.9.0-ultramodern.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/contextExtensions.js +74 -0
- package/dist/cjs/headRuntime.js +91 -0
- package/dist/cjs/helmetContext.js +54 -0
- package/dist/cjs/index.js +18 -4
- package/dist/cjs/prefetchPolicy.js +52 -0
- package/dist/cjs/request-policy/browser.js +139 -0
- package/dist/cjs/request-policy/client.js +234 -0
- package/dist/cjs/request-policy/node.js +149 -0
- package/dist/cjs/request-policy/policyCore.js +266 -0
- package/dist/cjs/request-policy/requestContext.js +137 -0
- package/dist/cjs/request-policy/traceparent.js +56 -0
- package/dist/cjs/request-policy/transport.js +234 -0
- package/dist/cjs/request-policy/types.js +57 -0
- package/dist/cjs/routeHydration.js +127 -0
- package/dist/cjs/routerPrefetchPolicy.js +153 -0
- package/dist/cjs/routerProvider.js +105 -0
- package/dist/cjs/routerState.js +167 -0
- package/dist/cjs/routerStatePlugin.js +86 -0
- package/dist/cjs/routerStateTypes.js +18 -0
- package/dist/cjs/runtimeContextProjection.js +112 -0
- package/dist/cjs/server-config/bffRuntime.js +18 -0
- package/dist/cjs/server-config/index.js +18 -0
- package/dist/cjs/server-config/serverTelemetry.js +18 -0
- package/dist/cjs/shellStream.js +34 -2
- package/dist/cjs/workspaceEvents.js +159 -0
- package/dist/esm/contextExtensions.mjs +33 -0
- package/dist/esm/headRuntime.mjs +43 -0
- package/dist/esm/helmetContext.mjs +13 -0
- package/dist/esm/index.mjs +2 -0
- package/dist/esm/prefetchPolicy.mjs +11 -0
- package/dist/esm/request-policy/browser.mjs +11 -0
- package/dist/esm/request-policy/client.mjs +195 -0
- package/dist/esm/request-policy/policyCore.mjs +171 -0
- package/dist/esm/request-policy/requestContext.mjs +75 -0
- package/dist/esm/request-policy/traceparent.mjs +18 -0
- package/dist/esm/request-policy/transport.mjs +196 -0
- package/dist/esm/request-policy/types.mjs +10 -0
- package/dist/esm/routeHydration.mjs +77 -0
- package/dist/esm/routerPrefetchPolicy.mjs +115 -0
- package/dist/esm/routerProvider.mjs +58 -0
- package/dist/esm/routerState.mjs +105 -0
- package/dist/esm/routerStatePlugin.mjs +48 -0
- package/dist/esm/routerStateTypes.mjs +0 -0
- package/dist/esm/runtimeContextProjection.mjs +74 -0
- package/dist/esm/server-config/bffRuntime.mjs +0 -0
- package/dist/esm/server-config/index.mjs +0 -0
- package/dist/esm/server-config/serverTelemetry.mjs +0 -0
- package/dist/esm/shellStream.mjs +34 -2
- package/dist/esm/workspaceEvents.mjs +66 -0
- package/dist/esm-node/contextExtensions.mjs +34 -0
- package/dist/esm-node/headRuntime.mjs +44 -0
- package/dist/esm-node/helmetContext.mjs +14 -0
- package/dist/esm-node/index.mjs +2 -0
- package/dist/esm-node/prefetchPolicy.mjs +12 -0
- package/dist/esm-node/request-policy/browser.mjs +12 -0
- package/dist/esm-node/request-policy/client.mjs +196 -0
- package/dist/esm-node/request-policy/node.mjs +22 -0
- package/dist/esm-node/request-policy/policyCore.mjs +172 -0
- package/dist/esm-node/request-policy/requestContext.mjs +76 -0
- package/dist/esm-node/request-policy/traceparent.mjs +19 -0
- package/dist/esm-node/request-policy/transport.mjs +197 -0
- package/dist/esm-node/request-policy/types.mjs +11 -0
- package/dist/esm-node/routeHydration.mjs +78 -0
- package/dist/esm-node/routerPrefetchPolicy.mjs +116 -0
- package/dist/esm-node/routerProvider.mjs +59 -0
- package/dist/esm-node/routerState.mjs +106 -0
- package/dist/esm-node/routerStatePlugin.mjs +49 -0
- package/dist/esm-node/routerStateTypes.mjs +1 -0
- package/dist/esm-node/runtimeContextProjection.mjs +75 -0
- package/dist/esm-node/server-config/bffRuntime.mjs +1 -0
- package/dist/esm-node/server-config/index.mjs +1 -0
- package/dist/esm-node/server-config/serverTelemetry.mjs +1 -0
- package/dist/esm-node/shellStream.mjs +34 -2
- package/dist/esm-node/workspaceEvents.mjs +67 -0
- package/dist/types/contextExtensions.d.ts +34 -0
- package/dist/types/headRuntime.d.ts +12 -0
- package/dist/types/helmetContext.d.ts +10 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/prefetchPolicy.d.ts +4 -0
- package/dist/types/request-policy/browser.d.ts +10 -0
- package/dist/types/request-policy/client.d.ts +14 -0
- package/dist/types/request-policy/node.d.ts +10 -0
- package/dist/types/request-policy/policyCore.d.ts +107 -0
- package/dist/types/request-policy/requestContext.d.ts +22 -0
- package/dist/types/request-policy/traceparent.d.ts +10 -0
- package/dist/types/request-policy/transport.d.ts +12 -0
- package/dist/types/request-policy/types.d.ts +144 -0
- package/dist/types/routeHydration.d.ts +24 -0
- package/dist/types/routerPrefetchPolicy.d.ts +28 -0
- package/dist/types/routerProvider.d.ts +55 -0
- package/dist/types/routerState.d.ts +12 -0
- package/dist/types/routerStatePlugin.d.ts +19 -0
- package/dist/types/routerStateTypes.d.ts +40 -0
- package/dist/types/runtimeContextProjection.d.ts +30 -0
- package/dist/types/server-config/bffRuntime.d.ts +122 -0
- package/dist/types/server-config/index.d.ts +2 -0
- package/dist/types/server-config/serverTelemetry.d.ts +322 -0
- package/dist/types/workspaceEvents.d.ts +62 -0
- package/package.json +198 -13
- package/rslib.config.mts +17 -2
- package/rstest.config.mts +33 -2
- package/src/contextExtensions.ts +81 -0
- package/src/headRuntime.tsx +77 -0
- package/src/helmetContext.ts +31 -0
- package/src/index.ts +2 -0
- package/src/prefetchPolicy.ts +33 -0
- package/src/request-policy/browser.ts +22 -0
- package/src/request-policy/client.ts +404 -0
- package/src/request-policy/node.ts +41 -0
- package/src/request-policy/policyCore.ts +424 -0
- package/src/request-policy/requestContext.ts +149 -0
- package/src/request-policy/traceparent.ts +43 -0
- package/src/request-policy/transport.ts +349 -0
- package/src/request-policy/types.ts +229 -0
- package/src/routeHydration.ts +208 -0
- package/src/routerPrefetchPolicy.ts +195 -0
- package/src/routerProvider.ts +160 -0
- package/src/routerState.ts +208 -0
- package/src/routerStatePlugin.ts +86 -0
- package/src/routerStateTypes.ts +46 -0
- package/src/runtimeContextProjection.ts +105 -0
- package/src/server-config/bffRuntime.ts +136 -0
- package/src/server-config/index.ts +2 -0
- package/src/server-config/serverTelemetry.ts +337 -0
- package/src/shellStream.ts +55 -25
- package/src/workspaceEvents.ts +324 -0
- package/tests/buildIdentity.test.ts +2 -4
- package/tests/contextExtensions.test.ts +71 -0
- package/tests/headRuntime.test.tsx +369 -0
- package/tests/nativeSafeFailureCompatibility.test.ts +117 -0
- package/tests/prefetchPolicy.test.ts +51 -0
- package/tests/rendererHead.test.ts +1 -72
- package/tests/request-policy/browser-policy.test.ts +608 -0
- package/tests/request-policy/client-isolation.test.ts +149 -0
- package/tests/request-policy/identityBinding.test.ts +75 -0
- package/tests/request-policy/node-policy.test.ts +741 -0
- package/tests/request-policy/policyCore.test.ts +250 -0
- package/tests/request-policy/requestContext.test.ts +80 -0
- package/tests/request-policy/requestFactory.outbound-contract.test.ts +691 -0
- package/tests/request-policy/traceparent.test.ts +55 -0
- package/tests/request-policy/transport-retry.test.ts +370 -0
- package/tests/routeHydration.test.ts +180 -0
- package/tests/routerPrefetchPolicy.test.ts +102 -0
- package/tests/routerProvider.test.ts +29 -0
- package/tests/routerState.test.ts +157 -0
- package/tests/routerStatePlugin.test.ts +108 -0
- package/tests/runtimeContextProjection.test.ts +135 -0
- package/tests/shellStream.test.ts +37 -0
- package/tests/workspaceEvents.test.ts +391 -0
- package/dist/cjs/rscHtmlStream.js +0 -128
- package/dist/esm/rscHtmlStream.mjs +0 -86
- package/dist/esm-node/rscHtmlStream.mjs +0 -87
- package/dist/types/rscHtmlStream.d.ts +0 -4
- package/src/rscHtmlStream.ts +0 -142
- package/tests/rscHtmlStream.test.ts +0 -153
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// @effect-diagnostics asyncFunction:off strictBooleanExpressions:off
|
|
2
|
+
import { createRuntimeContextExtension } from './contextExtensions';
|
|
3
|
+
import type {
|
|
4
|
+
InternalRouterRuntimeState,
|
|
5
|
+
InternalRouterServerSnapshot,
|
|
6
|
+
RouterRouteMatchSnapshot,
|
|
7
|
+
RouterServerPrepareResult,
|
|
8
|
+
} from './routerStateTypes';
|
|
9
|
+
|
|
10
|
+
export type {
|
|
11
|
+
BuiltInRouterFramework,
|
|
12
|
+
InternalRouterRuntimeState,
|
|
13
|
+
InternalRouterServerSnapshot,
|
|
14
|
+
RouterFramework,
|
|
15
|
+
RouterLifecyclePhase,
|
|
16
|
+
RouterRouteMatchSnapshot,
|
|
17
|
+
RouterServerPrepareResult,
|
|
18
|
+
} from './routerStateTypes';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Router runtime state is shared by every router provider (react-router,
|
|
22
|
+
* @modern-js/plugin-tanstack, ...) and consumed by the SSR pipeline. It lives
|
|
23
|
+
* in the runtime-context extension slot instead of ad-hoc fields on
|
|
24
|
+
* `TInternalRuntimeContext`.
|
|
25
|
+
*/
|
|
26
|
+
const routerRuntimeStateExtension =
|
|
27
|
+
createRuntimeContextExtension<InternalRouterRuntimeState>(
|
|
28
|
+
'@modern-js/runtime:router-runtime-state',
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The server snapshot is tracked separately: a later
|
|
33
|
+
* `applyRouterRuntimeState` call without a snapshot must not clear a
|
|
34
|
+
* previously captured one.
|
|
35
|
+
*/
|
|
36
|
+
const routerServerSnapshotExtension =
|
|
37
|
+
createRuntimeContextExtension<InternalRouterServerSnapshot>(
|
|
38
|
+
'@modern-js/runtime:router-server-snapshot',
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
export function getRouterRuntimeState(
|
|
42
|
+
runtimeContext: object,
|
|
43
|
+
): InternalRouterRuntimeState | undefined {
|
|
44
|
+
return routerRuntimeStateExtension.get(runtimeContext);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function getRouterServerSnapshot(
|
|
48
|
+
runtimeContext: object,
|
|
49
|
+
): InternalRouterServerSnapshot | undefined {
|
|
50
|
+
return routerServerSnapshotExtension.get(runtimeContext);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type RouterSnapshotLike = Partial<InternalRouterServerSnapshot>;
|
|
54
|
+
|
|
55
|
+
function toHydrationScripts(state: {
|
|
56
|
+
hydrationScript?: string;
|
|
57
|
+
hydrationScripts?: string[];
|
|
58
|
+
}) {
|
|
59
|
+
if (state.hydrationScripts?.length) {
|
|
60
|
+
return state.hydrationScripts;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return state.hydrationScript ? [state.hydrationScript] : undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getMatchedRouteIdsFromMatches(matches?: RouterRouteMatchSnapshot[]) {
|
|
67
|
+
const routeIds = matches
|
|
68
|
+
?.map(match => match.assetRouteId ?? match.routeId)
|
|
69
|
+
.filter((routeId): routeId is string => typeof routeId === 'string');
|
|
70
|
+
|
|
71
|
+
return routeIds?.length ? routeIds : undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function createRouterServerSnapshot(
|
|
75
|
+
state: RouterSnapshotLike,
|
|
76
|
+
): InternalRouterServerSnapshot {
|
|
77
|
+
const hydrationScripts = toHydrationScripts(state);
|
|
78
|
+
const matchedRouteIds =
|
|
79
|
+
state.matchedRouteIds ?? getMatchedRouteIdsFromMatches(state.matches);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
...state,
|
|
83
|
+
...(hydrationScripts?.length
|
|
84
|
+
? {
|
|
85
|
+
hydrationScript: state.hydrationScript ?? hydrationScripts[0],
|
|
86
|
+
hydrationScripts,
|
|
87
|
+
}
|
|
88
|
+
: {}),
|
|
89
|
+
...(matchedRouteIds ? { matchedRouteIds } : {}),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function createRouterRuntimeState(
|
|
94
|
+
state: InternalRouterRuntimeState,
|
|
95
|
+
): InternalRouterRuntimeState {
|
|
96
|
+
const hasSnapshotState =
|
|
97
|
+
Boolean(state.serverSnapshot) ||
|
|
98
|
+
Boolean(state.hydrationScript) ||
|
|
99
|
+
Boolean(state.hydrationScripts?.length) ||
|
|
100
|
+
Boolean(state.matchedRouteIds?.length) ||
|
|
101
|
+
Boolean(state.matches?.length);
|
|
102
|
+
const serverSnapshot = state.serverSnapshot
|
|
103
|
+
? createRouterServerSnapshot({
|
|
104
|
+
...state.serverSnapshot,
|
|
105
|
+
framework: state.serverSnapshot.framework ?? state.framework,
|
|
106
|
+
basename: state.serverSnapshot.basename ?? state.basename,
|
|
107
|
+
hydrationScript:
|
|
108
|
+
state.serverSnapshot.hydrationScript ?? state.hydrationScript,
|
|
109
|
+
hydrationScripts:
|
|
110
|
+
state.serverSnapshot.hydrationScripts ?? state.hydrationScripts,
|
|
111
|
+
matchedRouteIds:
|
|
112
|
+
state.serverSnapshot.matchedRouteIds ?? state.matchedRouteIds,
|
|
113
|
+
matches: state.serverSnapshot.matches ?? state.matches,
|
|
114
|
+
})
|
|
115
|
+
: hasSnapshotState
|
|
116
|
+
? createRouterServerSnapshot({
|
|
117
|
+
framework: state.framework,
|
|
118
|
+
basename: state.basename,
|
|
119
|
+
hydrationScript: state.hydrationScript,
|
|
120
|
+
hydrationScripts: state.hydrationScripts,
|
|
121
|
+
matchedRouteIds: state.matchedRouteIds,
|
|
122
|
+
matches: state.matches,
|
|
123
|
+
})
|
|
124
|
+
: undefined;
|
|
125
|
+
const hydrationScripts = toHydrationScripts({
|
|
126
|
+
hydrationScript: state.hydrationScript ?? serverSnapshot?.hydrationScript,
|
|
127
|
+
hydrationScripts:
|
|
128
|
+
state.hydrationScripts ?? serverSnapshot?.hydrationScripts,
|
|
129
|
+
});
|
|
130
|
+
const matchedRouteIds =
|
|
131
|
+
state.matchedRouteIds ??
|
|
132
|
+
serverSnapshot?.matchedRouteIds ??
|
|
133
|
+
getMatchedRouteIdsFromMatches(state.matches);
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
...state,
|
|
137
|
+
...(hydrationScripts?.length
|
|
138
|
+
? {
|
|
139
|
+
hydrationScript: state.hydrationScript ?? hydrationScripts[0],
|
|
140
|
+
hydrationScripts,
|
|
141
|
+
}
|
|
142
|
+
: {}),
|
|
143
|
+
...(matchedRouteIds ? { matchedRouteIds } : {}),
|
|
144
|
+
...(serverSnapshot ? { serverSnapshot } : {}),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function applyRouterRuntimeState<Context extends object>(
|
|
149
|
+
runtimeContext: Context,
|
|
150
|
+
state: InternalRouterRuntimeState,
|
|
151
|
+
) {
|
|
152
|
+
const normalized = createRouterRuntimeState(state);
|
|
153
|
+
routerRuntimeStateExtension.set(runtimeContext, normalized);
|
|
154
|
+
if (normalized.serverSnapshot) {
|
|
155
|
+
routerServerSnapshotExtension.set(
|
|
156
|
+
runtimeContext,
|
|
157
|
+
normalized.serverSnapshot,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return runtimeContext;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function applyRouterServerPrepareResult<Context extends object>(
|
|
165
|
+
runtimeContext: Context,
|
|
166
|
+
result: RouterServerPrepareResult,
|
|
167
|
+
) {
|
|
168
|
+
const state = createRouterRuntimeState({
|
|
169
|
+
...result.state,
|
|
170
|
+
cleanup: result.cleanup ?? result.state.cleanup,
|
|
171
|
+
serverSnapshot: result.snapshot ?? result.state.serverSnapshot,
|
|
172
|
+
});
|
|
173
|
+
applyRouterRuntimeState(runtimeContext, state);
|
|
174
|
+
return runtimeContext;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function getRouterHydrationScripts(runtimeContext: object) {
|
|
178
|
+
const serverSnapshot = getRouterServerSnapshot(runtimeContext);
|
|
179
|
+
const runtimeState = getRouterRuntimeState(runtimeContext);
|
|
180
|
+
return (
|
|
181
|
+
serverSnapshot?.hydrationScripts ??
|
|
182
|
+
toHydrationScripts({
|
|
183
|
+
hydrationScript: serverSnapshot?.hydrationScript,
|
|
184
|
+
}) ??
|
|
185
|
+
runtimeState?.hydrationScripts ??
|
|
186
|
+
toHydrationScripts({
|
|
187
|
+
hydrationScript: runtimeState?.hydrationScript,
|
|
188
|
+
}) ??
|
|
189
|
+
[]
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function getRouterMatchedRouteIds(runtimeContext: object) {
|
|
194
|
+
const serverSnapshot = getRouterServerSnapshot(runtimeContext);
|
|
195
|
+
const runtimeState = getRouterRuntimeState(runtimeContext);
|
|
196
|
+
return (
|
|
197
|
+
serverSnapshot?.matchedRouteIds ??
|
|
198
|
+
getMatchedRouteIdsFromMatches(serverSnapshot?.matches) ??
|
|
199
|
+
runtimeState?.matchedRouteIds ??
|
|
200
|
+
getMatchedRouteIdsFromMatches(runtimeState?.matches)
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export async function cleanupRouterRuntimeState(runtimeContext: object) {
|
|
205
|
+
try {
|
|
206
|
+
await getRouterRuntimeState(runtimeContext)?.cleanup?.();
|
|
207
|
+
} catch {}
|
|
208
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { createRouterPrefetchPolicy } from './routerPrefetchPolicy';
|
|
2
|
+
import {
|
|
3
|
+
applyRouterRuntimeState,
|
|
4
|
+
createRouterServerSnapshot,
|
|
5
|
+
} from './routerState';
|
|
6
|
+
|
|
7
|
+
type ReactRouterServerContext = {
|
|
8
|
+
statusCode: number;
|
|
9
|
+
errors?: Record<string, unknown> | null;
|
|
10
|
+
loaderData: Record<string, unknown>;
|
|
11
|
+
matches: { route: { id?: string } }[];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type RouterLifecycleEvent = {
|
|
15
|
+
framework: string;
|
|
16
|
+
phase: string;
|
|
17
|
+
runtimeContext: object;
|
|
18
|
+
basename?: string;
|
|
19
|
+
router?: unknown;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** Install fork policy through lifecycle hooks supplied by the native owner. */
|
|
23
|
+
export function createRouterStatePlugin<Hooks extends Record<string, unknown>>({
|
|
24
|
+
registryHooks,
|
|
25
|
+
}: {
|
|
26
|
+
registryHooks: Hooks;
|
|
27
|
+
}) {
|
|
28
|
+
return {
|
|
29
|
+
name: '@modern-js/router-runtime-policy',
|
|
30
|
+
registryHooks,
|
|
31
|
+
setup(api: {
|
|
32
|
+
onBeforeRender: (callback: (context: object) => void) => unknown;
|
|
33
|
+
onAfterCreateRouter: (
|
|
34
|
+
callback: (event: RouterLifecycleEvent) => void,
|
|
35
|
+
) => unknown;
|
|
36
|
+
}) {
|
|
37
|
+
api.onBeforeRender(context => {
|
|
38
|
+
const runtimeContext = context as {
|
|
39
|
+
linkPrefetchPolicy?: ReturnType<typeof createRouterPrefetchPolicy>;
|
|
40
|
+
};
|
|
41
|
+
runtimeContext.linkPrefetchPolicy ??= createRouterPrefetchPolicy();
|
|
42
|
+
});
|
|
43
|
+
api.onAfterCreateRouter(event => {
|
|
44
|
+
if (event.framework !== 'react-router') {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (event.phase === 'ssr-prepare') {
|
|
48
|
+
const { routerContext } = event.runtimeContext as {
|
|
49
|
+
routerContext?: ReactRouterServerContext;
|
|
50
|
+
};
|
|
51
|
+
if (!routerContext) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const serverSnapshot = createRouterServerSnapshot({
|
|
55
|
+
framework: event.framework,
|
|
56
|
+
basename: event.basename,
|
|
57
|
+
statusCode: routerContext.statusCode,
|
|
58
|
+
errors: routerContext.errors ?? undefined,
|
|
59
|
+
routerData: {
|
|
60
|
+
loaderData: routerContext.loaderData,
|
|
61
|
+
errors: routerContext.errors ?? undefined,
|
|
62
|
+
},
|
|
63
|
+
matches: routerContext.matches.flatMap(match =>
|
|
64
|
+
typeof match.route.id === 'string'
|
|
65
|
+
? [{ routeId: match.route.id }]
|
|
66
|
+
: [],
|
|
67
|
+
),
|
|
68
|
+
});
|
|
69
|
+
applyRouterRuntimeState(event.runtimeContext, {
|
|
70
|
+
framework: event.framework,
|
|
71
|
+
basename: event.basename,
|
|
72
|
+
instance: event.router,
|
|
73
|
+
matchedRouteIds: serverSnapshot.matchedRouteIds,
|
|
74
|
+
serverSnapshot,
|
|
75
|
+
});
|
|
76
|
+
} else if (event.phase === 'client-create') {
|
|
77
|
+
applyRouterRuntimeState(event.runtimeContext, {
|
|
78
|
+
framework: event.framework,
|
|
79
|
+
basename: event.basename,
|
|
80
|
+
instance: event.router,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type BuiltInRouterFramework = 'react-router' | 'tanstack';
|
|
2
|
+
|
|
3
|
+
export type RouterFramework = BuiltInRouterFramework | (string & {});
|
|
4
|
+
|
|
5
|
+
export interface RouterRouteMatchSnapshot {
|
|
6
|
+
routeId: string;
|
|
7
|
+
assetRouteId?: string;
|
|
8
|
+
pathname?: string;
|
|
9
|
+
params?: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface InternalRouterServerSnapshot {
|
|
13
|
+
framework?: RouterFramework;
|
|
14
|
+
basename?: string;
|
|
15
|
+
statusCode?: number;
|
|
16
|
+
errors?: Record<string, unknown>;
|
|
17
|
+
routerData?: {
|
|
18
|
+
loaderData?: Record<string, unknown>;
|
|
19
|
+
errors?: Record<string, unknown>;
|
|
20
|
+
};
|
|
21
|
+
hydrationScript?: string;
|
|
22
|
+
hydrationScripts?: string[];
|
|
23
|
+
matchedRouteIds?: string[];
|
|
24
|
+
matches?: RouterRouteMatchSnapshot[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface InternalRouterRuntimeState {
|
|
28
|
+
framework: RouterFramework;
|
|
29
|
+
basename?: string;
|
|
30
|
+
instance?: unknown;
|
|
31
|
+
hydrationScript?: string;
|
|
32
|
+
hydrationScripts?: string[];
|
|
33
|
+
matchedRouteIds?: string[];
|
|
34
|
+
matches?: RouterRouteMatchSnapshot[];
|
|
35
|
+
serverSnapshot?: InternalRouterServerSnapshot;
|
|
36
|
+
cleanup?: () => void | Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface RouterServerPrepareResult {
|
|
40
|
+
state: InternalRouterRuntimeState;
|
|
41
|
+
snapshot?: InternalRouterServerSnapshot;
|
|
42
|
+
redirect?: Response;
|
|
43
|
+
cleanup?: () => void | Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type RouterLifecyclePhase = 'ssr-prepare' | 'client-create' | 'hydrate';
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { stripRuntimeContextExtensions } from './contextExtensions';
|
|
2
|
+
|
|
3
|
+
// Only the transport fields consumed by this projection are required. This
|
|
4
|
+
// policy module does not depend on the native runtime or plugin API types.
|
|
5
|
+
interface ProjectionContext {
|
|
6
|
+
isBrowser?: boolean;
|
|
7
|
+
ssrContext?: {
|
|
8
|
+
request: {
|
|
9
|
+
url?: unknown;
|
|
10
|
+
userAgent?: unknown;
|
|
11
|
+
cookie?: unknown;
|
|
12
|
+
pathname?: unknown;
|
|
13
|
+
query?: object;
|
|
14
|
+
params?: object;
|
|
15
|
+
headers?: object;
|
|
16
|
+
host?: unknown;
|
|
17
|
+
referer?: unknown;
|
|
18
|
+
};
|
|
19
|
+
response: { locals?: unknown };
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function createRscSafeRequestContext(
|
|
24
|
+
ssrContext: ProjectionContext['ssrContext'],
|
|
25
|
+
) {
|
|
26
|
+
const requestContext = {
|
|
27
|
+
request: {
|
|
28
|
+
params: {},
|
|
29
|
+
pathname: '',
|
|
30
|
+
query: {},
|
|
31
|
+
headers: {},
|
|
32
|
+
host: '',
|
|
33
|
+
url: '',
|
|
34
|
+
},
|
|
35
|
+
response: {
|
|
36
|
+
setHeader() {},
|
|
37
|
+
status() {},
|
|
38
|
+
locals: {} as unknown,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
Object.defineProperty(requestContext.response, 'setHeader', {
|
|
42
|
+
enumerable: false,
|
|
43
|
+
});
|
|
44
|
+
Object.defineProperty(requestContext.response, 'status', {
|
|
45
|
+
enumerable: false,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (ssrContext === undefined) {
|
|
49
|
+
return requestContext;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const { request, response } = ssrContext;
|
|
53
|
+
return {
|
|
54
|
+
request: {
|
|
55
|
+
url: request.url,
|
|
56
|
+
userAgent: request.userAgent,
|
|
57
|
+
cookie: request.cookie,
|
|
58
|
+
pathname: request.pathname,
|
|
59
|
+
query: { ...request.query },
|
|
60
|
+
params: { ...request.params },
|
|
61
|
+
headers: { ...request.headers },
|
|
62
|
+
host: request.host,
|
|
63
|
+
referer: request.referer,
|
|
64
|
+
},
|
|
65
|
+
response: Object.assign(requestContext.response, {
|
|
66
|
+
locals: response.locals ?? {},
|
|
67
|
+
}),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function projectRuntimeContext<
|
|
72
|
+
InternalContext extends ProjectionContext,
|
|
73
|
+
PublicContext extends ProjectionContext,
|
|
74
|
+
>(
|
|
75
|
+
projection: {
|
|
76
|
+
internalContext: InternalContext;
|
|
77
|
+
publicContext: PublicContext;
|
|
78
|
+
},
|
|
79
|
+
options: { context: InternalContext; isRsc: boolean },
|
|
80
|
+
) {
|
|
81
|
+
let internalContext = projection.internalContext;
|
|
82
|
+
let publicContext = { ...projection.publicContext };
|
|
83
|
+
// Enumerable symbol slots deliberately survive internal SSR copies. Remove
|
|
84
|
+
// them only from the public projection, never from the original request.
|
|
85
|
+
stripRuntimeContextExtensions(publicContext);
|
|
86
|
+
|
|
87
|
+
if (options.isRsc && options.context.isBrowser === false) {
|
|
88
|
+
const requestContext = createRscSafeRequestContext(
|
|
89
|
+
options.context.ssrContext,
|
|
90
|
+
);
|
|
91
|
+
internalContext = {
|
|
92
|
+
...internalContext,
|
|
93
|
+
context: requestContext,
|
|
94
|
+
requestContext,
|
|
95
|
+
};
|
|
96
|
+
delete internalContext.ssrContext;
|
|
97
|
+
publicContext = {
|
|
98
|
+
...publicContext,
|
|
99
|
+
context: requestContext,
|
|
100
|
+
requestContext,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { internalContext, publicContext };
|
|
105
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
export type BffRuntimeFramework = 'hono' | 'effect';
|
|
2
|
+
|
|
3
|
+
/** Fork BFF options, independent of any native server config interface. */
|
|
4
|
+
export interface BffRuntimeUserConfig {
|
|
5
|
+
runtimeFramework?: BffRuntimeFramework;
|
|
6
|
+
effect?: BffEffectUserConfig;
|
|
7
|
+
crossProjectPolicy?: BffCrossProjectPolicyUserConfig;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface BffCrossProjectPolicyUserConfig {
|
|
11
|
+
/**
|
|
12
|
+
* Enable cross-project envelope and operation-context policy checks.
|
|
13
|
+
*
|
|
14
|
+
* @default false
|
|
15
|
+
*/
|
|
16
|
+
enabled?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Require cross-project envelope header when policy is enabled.
|
|
19
|
+
*
|
|
20
|
+
* @default true
|
|
21
|
+
*/
|
|
22
|
+
requireEnvelope?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Require operation-context header when policy is enabled.
|
|
25
|
+
*
|
|
26
|
+
* @default true
|
|
27
|
+
*/
|
|
28
|
+
requireOperationContext?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Require operation-context detail header carrying schema/version metadata.
|
|
31
|
+
*
|
|
32
|
+
* @default true
|
|
33
|
+
*/
|
|
34
|
+
requireOperationContextDetails?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Require operation schema hash in operation-context details.
|
|
37
|
+
*
|
|
38
|
+
* @default true
|
|
39
|
+
*/
|
|
40
|
+
requireOperationSchemaHash?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Require operation version in operation-context details.
|
|
43
|
+
*
|
|
44
|
+
* @default true
|
|
45
|
+
*/
|
|
46
|
+
requireOperationVersion?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Optional allowlist of producer namespaces derived from requestId.
|
|
49
|
+
*/
|
|
50
|
+
allowedNamespaces?: string[];
|
|
51
|
+
/**
|
|
52
|
+
* Optional hook deriving a verified producer identity (namespace) from
|
|
53
|
+
* request headers, e.g. an mTLS subject or gateway-verified JWT claim.
|
|
54
|
+
* When provided, namespace checks bind to this verified value instead of
|
|
55
|
+
* the client-asserted requestId namespace; returning `undefined` denies
|
|
56
|
+
* the request. Without it the namespace checks are advisory only.
|
|
57
|
+
*/
|
|
58
|
+
verifyProducerIdentity?: (
|
|
59
|
+
headers: Record<string, unknown>,
|
|
60
|
+
) => string | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Optional operation-contract map keyed by:
|
|
63
|
+
* - `${METHOD}:${routePath}`
|
|
64
|
+
* - `operation:${requestId}:${operationId}`
|
|
65
|
+
*/
|
|
66
|
+
expectedOperationContracts?: Record<
|
|
67
|
+
string,
|
|
68
|
+
{
|
|
69
|
+
schemaHash?: string;
|
|
70
|
+
operationVersion?: number;
|
|
71
|
+
}
|
|
72
|
+
>;
|
|
73
|
+
/**
|
|
74
|
+
* Allow operations missing from expectedOperationContracts.
|
|
75
|
+
*
|
|
76
|
+
* @default false
|
|
77
|
+
*/
|
|
78
|
+
allowUnknownOperations?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* HTTP status code used for denied requests.
|
|
81
|
+
*
|
|
82
|
+
* @default 403
|
|
83
|
+
*/
|
|
84
|
+
denyStatus?: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type BffEffectOpenApiUserConfig =
|
|
88
|
+
| boolean
|
|
89
|
+
| {
|
|
90
|
+
path?: string;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export interface BffEffectDataPlatformSelectionUserConfig {
|
|
94
|
+
maxDepth?: number;
|
|
95
|
+
maxFields?: number;
|
|
96
|
+
allowedLeafPaths?: string[];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface BffEffectDataPlatformBatchUserConfig {
|
|
100
|
+
enabled?: boolean;
|
|
101
|
+
endpoint?: `/${string}`;
|
|
102
|
+
maxBatchSize?: number;
|
|
103
|
+
maxBatchBytes?: number;
|
|
104
|
+
flushIntervalMs?: number;
|
|
105
|
+
maxConcurrency?: number;
|
|
106
|
+
requestTimeoutMs?: number;
|
|
107
|
+
allowedMethods?: string[];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface BffEffectDataPlatformUserConfig {
|
|
111
|
+
enabled?: boolean;
|
|
112
|
+
requireEnvelope?: boolean;
|
|
113
|
+
envelopeHeader?: string;
|
|
114
|
+
expectedNamespace?: string;
|
|
115
|
+
validateOrigin?: boolean;
|
|
116
|
+
requireTraceContext?: boolean;
|
|
117
|
+
selection?: BffEffectDataPlatformSelectionUserConfig;
|
|
118
|
+
batch?: BffEffectDataPlatformBatchUserConfig;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface BffEffectUserConfig {
|
|
122
|
+
entry?: string;
|
|
123
|
+
/**
|
|
124
|
+
* Enforce Effect-native API/runtime modules instead of raw request handlers.
|
|
125
|
+
*
|
|
126
|
+
* Effect API entries must export a `defineEffectBff(...)`
|
|
127
|
+
* definition or a `{ api, layer }` HttpApi module. Raw `handler` exports,
|
|
128
|
+
* default request handlers, and unbranded custom `createHandler` factories
|
|
129
|
+
* are rejected.
|
|
130
|
+
*
|
|
131
|
+
* @default true
|
|
132
|
+
*/
|
|
133
|
+
strictEffectApproach?: true;
|
|
134
|
+
openapi?: BffEffectOpenApiUserConfig;
|
|
135
|
+
dataPlatform?: BffEffectDataPlatformUserConfig;
|
|
136
|
+
}
|