@bleedingdev/modern-js-plugin-tanstack 3.9.0-ultramodern.2 → 3.9.0-ultramodern.21
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/cli/artifacts.js +15 -1
- package/dist/cjs/cli/index.js +12 -0
- package/dist/cjs/cli/routeMetadata.js +131 -0
- package/dist/cjs/cli/tanstackTypes/canonicalRoutes.js +7 -7
- package/dist/cjs/cli/tanstackTypes/shared.js +0 -14
- package/dist/cjs/cli/tanstackTypes/source.js +27 -60
- package/dist/cjs/runtime/basepathRewrite.js +13 -6
- package/dist/cjs/runtime/lifecycle.js +6 -9
- package/dist/cjs/runtime/loaderBridge.js +10 -4
- package/dist/cjs/runtime/navigation.js +102 -0
- package/dist/cjs/runtime/plugin.js +7 -2
- package/dist/cjs/runtime/plugin.node.js +86 -64
- package/dist/cjs/runtime/prefetchLink.js +2 -7
- package/dist/cjs/runtime/register.js +0 -2
- package/dist/cjs/runtime/routeTree/construction.js +19 -59
- package/dist/cjs/runtime/routeTree/loaders.js +1 -1
- package/dist/cjs/runtime/routeTree/paths.js +0 -6
- package/dist/cjs/runtime/router.js +25 -9
- package/dist/cjs/runtime/ssrManagedTags.js +2 -12
- package/dist/cjs/runtime/state.js +2 -2
- package/dist/cjs/runtime/utils.js +3 -0
- package/dist/cjs/shared/routeDescriptor.js +60 -0
- package/dist/esm/cli/artifacts.mjs +13 -2
- package/dist/esm/cli/index.mjs +12 -9
- package/dist/esm/cli/routeMetadata.mjs +71 -0
- package/dist/esm/cli/tanstackTypes/canonicalRoutes.mjs +7 -7
- package/dist/esm/cli/tanstackTypes/shared.mjs +1 -9
- package/dist/esm/cli/tanstackTypes/source.mjs +28 -61
- package/dist/esm/runtime/basepathRewrite.mjs +10 -3
- package/dist/esm/runtime/lifecycle.mjs +1 -1
- package/dist/esm/runtime/loaderBridge.mjs +8 -5
- package/dist/esm/runtime/navigation.mjs +64 -0
- package/dist/esm/runtime/plugin.mjs +7 -2
- package/dist/esm/runtime/plugin.node.mjs +88 -66
- package/dist/esm/runtime/prefetchLink.mjs +1 -6
- package/dist/esm/runtime/register.mjs +0 -2
- package/dist/esm/runtime/routeTree/construction.mjs +19 -59
- package/dist/esm/runtime/routeTree/loaders.mjs +2 -2
- package/dist/esm/runtime/routeTree/paths.mjs +1 -4
- package/dist/esm/runtime/router.mjs +27 -11
- package/dist/esm/runtime/ssrManagedTags.mjs +1 -8
- package/dist/esm/runtime/state.mjs +1 -1
- package/dist/esm/runtime/utils.mjs +3 -0
- package/dist/esm/shared/routeDescriptor.mjs +22 -0
- package/dist/esm-node/cli/artifacts.mjs +13 -2
- package/dist/esm-node/cli/index.mjs +12 -9
- package/dist/esm-node/cli/routeMetadata.mjs +72 -0
- package/dist/esm-node/cli/tanstackTypes/canonicalRoutes.mjs +7 -7
- package/dist/esm-node/cli/tanstackTypes/shared.mjs +1 -9
- package/dist/esm-node/cli/tanstackTypes/source.mjs +28 -61
- package/dist/esm-node/runtime/basepathRewrite.mjs +10 -3
- package/dist/esm-node/runtime/lifecycle.mjs +1 -1
- package/dist/esm-node/runtime/loaderBridge.mjs +8 -5
- package/dist/esm-node/runtime/navigation.mjs +65 -0
- package/dist/esm-node/runtime/plugin.mjs +7 -2
- package/dist/esm-node/runtime/plugin.node.mjs +88 -66
- package/dist/esm-node/runtime/prefetchLink.mjs +1 -6
- package/dist/esm-node/runtime/register.mjs +0 -2
- package/dist/esm-node/runtime/routeTree/construction.mjs +19 -59
- package/dist/esm-node/runtime/routeTree/loaders.mjs +2 -2
- package/dist/esm-node/runtime/routeTree/paths.mjs +1 -4
- package/dist/esm-node/runtime/router.mjs +27 -11
- package/dist/esm-node/runtime/ssrManagedTags.mjs +1 -8
- package/dist/esm-node/runtime/state.mjs +1 -1
- package/dist/esm-node/runtime/utils.mjs +3 -0
- package/dist/esm-node/shared/routeDescriptor.mjs +23 -0
- package/dist/types/cli/artifacts.d.ts +13 -5
- package/dist/types/cli/index.d.ts +1 -1
- package/dist/types/cli/routeMetadata.d.ts +18 -0
- package/dist/types/cli/tanstackTypes/shared.d.ts +0 -2
- package/dist/types/runtime/basepathRewrite.d.ts +2 -1
- package/dist/types/runtime/lifecycle.d.ts +9 -2
- package/dist/types/runtime/loaderBridge.d.ts +1 -0
- package/dist/types/runtime/navigation.d.ts +4 -0
- package/dist/types/runtime/register.d.ts +1 -9
- package/dist/types/runtime/routeTree/paths.d.ts +0 -2
- package/dist/types/runtime/router.d.ts +2 -13
- package/dist/types/runtime/ssrManagedTags.d.ts +0 -1
- package/dist/types/runtime/ssrTypes.d.ts +0 -7
- package/dist/types/runtime/state.d.ts +1 -1
- package/dist/types/runtime/types.d.ts +2 -1
- package/dist/types/shared/routeDescriptor.d.ts +26 -0
- package/package.json +22 -19
- package/rstest.config.mts +2 -24
- package/src/cli/artifacts.ts +53 -6
- package/src/cli/index.ts +11 -2
- package/src/cli/routeMetadata.ts +121 -0
- package/src/cli/tanstackTypes/canonicalRoutes.ts +17 -12
- package/src/cli/tanstackTypes/shared.ts +0 -14
- package/src/cli/tanstackTypes/source.ts +42 -116
- package/src/runtime/basepathRewrite.ts +11 -2
- package/src/runtime/lifecycle.ts +14 -4
- package/src/runtime/loaderBridge.ts +9 -9
- package/src/runtime/navigation.ts +78 -0
- package/src/runtime/plugin.node.tsx +120 -93
- package/src/runtime/plugin.tsx +11 -1
- package/src/runtime/prefetchLink.tsx +1 -23
- package/src/runtime/register.ts +1 -12
- package/src/runtime/routeTree/construction.ts +32 -110
- package/src/runtime/routeTree/loaders.ts +2 -4
- package/src/runtime/routeTree/paths.ts +0 -6
- package/src/runtime/router.ts +25 -40
- package/src/runtime/ssrManagedTags.ts +0 -17
- package/src/runtime/ssrTypes.ts +0 -7
- package/src/runtime/state.ts +1 -1
- package/src/runtime/types.ts +2 -2
- package/src/runtime/utils.tsx +4 -1
- package/src/shared/routeDescriptor.ts +46 -0
- package/tests/router/cli.test.ts +148 -824
- package/tests/router/clientNavigation.ts +13 -0
- package/tests/router/dataMutation.test.tsx +17 -85
- package/tests/router/flightSerialization.roundtrip.test.tsx +9 -6
- package/tests/router/generateRouteArtifacts.test.ts +139 -27
- package/tests/router/loaderBridge.test.ts +0 -25
- package/tests/router/localisedIdentity.test.tsx +100 -0
- package/tests/router/prefetchLink.test.tsx +46 -21
- package/tests/router/prefetchLinkPreload.test.tsx +11 -32
- package/tests/router/routeHooks.test.ts +42 -105
- package/tests/router/routeMetadata.test.ts +233 -0
- package/tests/router/routeTree.test.ts +144 -836
- package/tests/router/rsc.test.tsx +131 -166
- package/tests/router/serverPlugin.test.ts +193 -1
- package/tests/router/tanstackTypes.test.ts +218 -963
- package/dist/esm/rslib-runtime~1.mjs +0 -18
- package/dist/esm-node/rslib-runtime~0.mjs +0 -19
- package/tests/realm-isolation.test.ts +0 -114
- package/tests/router/fastDefaults.test.ts +0 -32
- package/tests/router/fixtures/legacyRuntimeRouter.ts +0 -10
- package/tests/router/fixtures/rscDecoderSecurityChild.cjs +0 -162
- package/tests/router/hooks.test.ts +0 -40
- package/tests/router/hydrationBoundary.test.tsx +0 -23
- package/tests/router/packageSurface.test.ts +0 -24
- package/tests/router/preloadRedirect.test.ts +0 -64
- package/tests/router/register.test.ts +0 -8
- package/tests/router/routerCompatibility.test.ts +0 -239
- package/tests/router/rscDecoderSecurity.test.ts +0 -113
- package/tests/router/rscPayloadRouterMatrix.test.ts +0 -197
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from 'node:child_process';
|
|
2
|
-
import {
|
|
3
|
-
mkdirSync,
|
|
4
|
-
mkdtempSync,
|
|
5
|
-
readFileSync,
|
|
6
|
-
rmSync,
|
|
7
|
-
writeFileSync,
|
|
8
|
-
} from 'node:fs';
|
|
9
|
-
import { tmpdir } from 'node:os';
|
|
10
|
-
import path from 'node:path';
|
|
11
|
-
|
|
12
|
-
const REGISTRY_SLOT: unique symbol = Symbol.for(
|
|
13
|
-
'@modern-js/runtime:router-providers:v3',
|
|
14
|
-
);
|
|
15
|
-
const registryHost = globalThis as { [REGISTRY_SLOT]?: unknown };
|
|
16
|
-
const webpackHost = globalThis as typeof globalThis & {
|
|
17
|
-
__webpack_require__?: { u: (chunkId: unknown) => string };
|
|
18
|
-
};
|
|
19
|
-
const installWebpackRequire = () => {
|
|
20
|
-
webpackHost.__webpack_require__ = { u: chunkId => String(chunkId) };
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
type LegacyRuntimeRouterModule = {
|
|
24
|
-
getLegacyRouterPluginInvocationCount: () => number;
|
|
25
|
-
routerPlugin: (...args: unknown[]) => unknown;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
type CompatibilityGraph = {
|
|
29
|
-
factory: (...args: any[]) => any;
|
|
30
|
-
legacyRuntimeRouter: LegacyRuntimeRouterModule;
|
|
31
|
-
resolveRouterProvider: (
|
|
32
|
-
framework?: string,
|
|
33
|
-
) => (...args: unknown[]) => unknown;
|
|
34
|
-
routerPlugin: (...args: any[]) => any;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
async function loadCompatibilityGraph(): Promise<CompatibilityGraph> {
|
|
38
|
-
rstest.resetModules();
|
|
39
|
-
installWebpackRequire();
|
|
40
|
-
|
|
41
|
-
const legacyRuntimeRouter = (await import(
|
|
42
|
-
'@modern-js/runtime/router/internal'
|
|
43
|
-
)) as unknown as LegacyRuntimeRouterModule;
|
|
44
|
-
const { routerPlugin } = await import('../../src/runtime/router');
|
|
45
|
-
const { tanstackRouterProviderFactory } = await import(
|
|
46
|
-
'../../src/runtime/register'
|
|
47
|
-
);
|
|
48
|
-
const { resolveRouterProvider } = await import('@modern-js/runtime/context');
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
factory: tanstackRouterProviderFactory,
|
|
52
|
-
legacyRuntimeRouter,
|
|
53
|
-
resolveRouterProvider,
|
|
54
|
-
routerPlugin,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function resolveTsgoBin(): string {
|
|
59
|
-
const packageJsonPath = require.resolve(
|
|
60
|
-
'@typescript/native-preview/package.json',
|
|
61
|
-
);
|
|
62
|
-
const packageDirectory = path.dirname(packageJsonPath);
|
|
63
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as {
|
|
64
|
-
bin?: string | { tsgo?: string };
|
|
65
|
-
};
|
|
66
|
-
const binEntry =
|
|
67
|
-
typeof packageJson.bin === 'string'
|
|
68
|
-
? packageJson.bin
|
|
69
|
-
: packageJson.bin?.tsgo;
|
|
70
|
-
|
|
71
|
-
return path.resolve(packageDirectory, binEntry ?? 'bin/tsgo.js');
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function runTsgo(tsgoBin: string, configPath: string): void {
|
|
75
|
-
try {
|
|
76
|
-
execFileSync(process.execPath, [tsgoBin, '-p', configPath], {
|
|
77
|
-
stdio: 'pipe',
|
|
78
|
-
});
|
|
79
|
-
} catch (error) {
|
|
80
|
-
const result = error as { stderr?: Buffer; stdout?: Buffer };
|
|
81
|
-
throw new Error(
|
|
82
|
-
`TS-Go compatibility check failed:\n${String(result.stdout ?? '')}${String(
|
|
83
|
-
result.stderr ?? '',
|
|
84
|
-
)}`,
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
installWebpackRequire();
|
|
90
|
-
|
|
91
|
-
describe('runtime router compatibility', () => {
|
|
92
|
-
beforeEach(() => {
|
|
93
|
-
delete registryHost[REGISTRY_SLOT];
|
|
94
|
-
rstest.resetModules();
|
|
95
|
-
installWebpackRequire();
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
afterEach(() => {
|
|
99
|
-
delete registryHost[REGISTRY_SLOT];
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('uses the app-local factory instead of the legacy global wrapper', async () => {
|
|
103
|
-
const graph = await loadCompatibilityGraph();
|
|
104
|
-
|
|
105
|
-
expect('createRouterPlugin' in graph.legacyRuntimeRouter).toBe(false);
|
|
106
|
-
expect(graph.routerPlugin).toBe(graph.factory);
|
|
107
|
-
expect(graph.routerPlugin({ framework: 'tanstack' })).toMatchObject({
|
|
108
|
-
name: '@modern-js/plugin-router-tanstack',
|
|
109
|
-
});
|
|
110
|
-
expect(
|
|
111
|
-
graph.legacyRuntimeRouter.getLegacyRouterPluginInvocationCount(),
|
|
112
|
-
).toBe(0);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('keeps independently evaluated legacy module graphs app-local', async () => {
|
|
116
|
-
const graphA = await loadCompatibilityGraph();
|
|
117
|
-
const graphB = await loadCompatibilityGraph();
|
|
118
|
-
|
|
119
|
-
expect(graphB.factory).not.toBe(graphA.factory);
|
|
120
|
-
expect(graphB.routerPlugin).toBe(graphB.factory);
|
|
121
|
-
expect(graphB.resolveRouterProvider('tanstack')).toBe(graphA.factory);
|
|
122
|
-
expect(graphB.routerPlugin({ framework: 'tanstack' })).toMatchObject({
|
|
123
|
-
name: '@modern-js/plugin-router-tanstack',
|
|
124
|
-
});
|
|
125
|
-
expect(
|
|
126
|
-
graphA.legacyRuntimeRouter.getLegacyRouterPluginInvocationCount(),
|
|
127
|
-
).toBe(0);
|
|
128
|
-
expect(
|
|
129
|
-
graphB.legacyRuntimeRouter.getLegacyRouterPluginInvocationCount(),
|
|
130
|
-
).toBe(0);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('emits declarations compatible with the oldest runtime router shape', () => {
|
|
134
|
-
const packageRoot = path.resolve(__dirname, '../..');
|
|
135
|
-
const sourcePath = path.join(packageRoot, 'src/runtime/router.ts');
|
|
136
|
-
const legacyFixturePath = path.join(
|
|
137
|
-
__dirname,
|
|
138
|
-
'fixtures/legacyRuntimeRouter.ts',
|
|
139
|
-
);
|
|
140
|
-
const tempRoot = mkdtempSync(
|
|
141
|
-
path.join(tmpdir(), 'plugin-tanstack-router-compatibility-'),
|
|
142
|
-
);
|
|
143
|
-
const sourceRoot = path.join(tempRoot, 'src');
|
|
144
|
-
const emittedRoot = path.join(tempRoot, 'types');
|
|
145
|
-
const tsgoBin = resolveTsgoBin();
|
|
146
|
-
|
|
147
|
-
try {
|
|
148
|
-
mkdirSync(sourceRoot, { recursive: true });
|
|
149
|
-
writeFileSync(
|
|
150
|
-
path.join(sourceRoot, 'router.ts'),
|
|
151
|
-
readFileSync(sourcePath, 'utf8'),
|
|
152
|
-
);
|
|
153
|
-
writeFileSync(
|
|
154
|
-
path.join(sourceRoot, 'register.ts'),
|
|
155
|
-
[
|
|
156
|
-
'export declare const tanstackRouterProviderFactory:',
|
|
157
|
-
' (userConfig?: Record<string, unknown>) => { name?: string };',
|
|
158
|
-
].join('\n'),
|
|
159
|
-
);
|
|
160
|
-
writeFileSync(
|
|
161
|
-
path.join(tempRoot, 'currentRuntimeRouter.d.ts'),
|
|
162
|
-
[
|
|
163
|
-
'export type RouterPluginFactory =',
|
|
164
|
-
' (userConfig?: Record<string, unknown>) => { name?: string };',
|
|
165
|
-
'export declare const routerPlugin: RouterPluginFactory;',
|
|
166
|
-
'export declare const createRouterPlugin:',
|
|
167
|
-
' (providers: readonly unknown[]) => RouterPluginFactory;',
|
|
168
|
-
].join('\n'),
|
|
169
|
-
);
|
|
170
|
-
writeFileSync(
|
|
171
|
-
path.join(tempRoot, 'emit.json'),
|
|
172
|
-
JSON.stringify(
|
|
173
|
-
{
|
|
174
|
-
compilerOptions: {
|
|
175
|
-
declaration: true,
|
|
176
|
-
emitDeclarationOnly: true,
|
|
177
|
-
module: 'Preserve',
|
|
178
|
-
moduleResolution: 'Bundler',
|
|
179
|
-
noCheck: true,
|
|
180
|
-
outDir: emittedRoot,
|
|
181
|
-
paths: {
|
|
182
|
-
'@modern-js/runtime/router/internal': [
|
|
183
|
-
'./currentRuntimeRouter.d.ts',
|
|
184
|
-
],
|
|
185
|
-
},
|
|
186
|
-
rootDir: sourceRoot,
|
|
187
|
-
skipLibCheck: false,
|
|
188
|
-
strict: true,
|
|
189
|
-
target: 'ESNext',
|
|
190
|
-
types: [],
|
|
191
|
-
},
|
|
192
|
-
files: [path.join(sourceRoot, 'router.ts')],
|
|
193
|
-
},
|
|
194
|
-
null,
|
|
195
|
-
2,
|
|
196
|
-
),
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
runTsgo(tsgoBin, path.join(tempRoot, 'emit.json'));
|
|
200
|
-
|
|
201
|
-
const declarationPath = path.join(emittedRoot, 'router.d.ts');
|
|
202
|
-
writeFileSync(
|
|
203
|
-
path.join(tempRoot, 'legacyRuntimeRouter.ts'),
|
|
204
|
-
readFileSync(legacyFixturePath, 'utf8'),
|
|
205
|
-
);
|
|
206
|
-
writeFileSync(
|
|
207
|
-
path.join(tempRoot, 'compatibility.json'),
|
|
208
|
-
JSON.stringify(
|
|
209
|
-
{
|
|
210
|
-
compilerOptions: {
|
|
211
|
-
module: 'Preserve',
|
|
212
|
-
moduleResolution: 'Bundler',
|
|
213
|
-
noEmit: true,
|
|
214
|
-
paths: {
|
|
215
|
-
'@modern-js/runtime/router/internal': [
|
|
216
|
-
'./legacyRuntimeRouter.ts',
|
|
217
|
-
],
|
|
218
|
-
},
|
|
219
|
-
skipLibCheck: false,
|
|
220
|
-
strict: true,
|
|
221
|
-
target: 'ESNext',
|
|
222
|
-
types: [],
|
|
223
|
-
},
|
|
224
|
-
files: [
|
|
225
|
-
declarationPath,
|
|
226
|
-
path.join(tempRoot, 'legacyRuntimeRouter.ts'),
|
|
227
|
-
],
|
|
228
|
-
},
|
|
229
|
-
null,
|
|
230
|
-
2,
|
|
231
|
-
),
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
runTsgo(tsgoBin, path.join(tempRoot, 'compatibility.json'));
|
|
235
|
-
} finally {
|
|
236
|
-
rmSync(tempRoot, { force: true, recursive: true });
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
});
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
|
|
5
|
-
type DecoderScenario =
|
|
6
|
-
| 'action-load'
|
|
7
|
-
| 'async-iterator'
|
|
8
|
-
| 'blob-type'
|
|
9
|
-
| 'cycle'
|
|
10
|
-
| 'form-data-roundtrip'
|
|
11
|
-
| 'referenced-form-data';
|
|
12
|
-
|
|
13
|
-
const resolvePackage = createRequire(
|
|
14
|
-
path.join(__dirname, 'rsc-decoder-security-resolver.cjs'),
|
|
15
|
-
).resolve;
|
|
16
|
-
const childPath = path.join(
|
|
17
|
-
__dirname,
|
|
18
|
-
'fixtures',
|
|
19
|
-
'rscDecoderSecurityChild.cjs',
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
function runDecoderScenario(options: {
|
|
23
|
-
entry: 'edge' | 'node';
|
|
24
|
-
nodeEnv: 'development' | 'production';
|
|
25
|
-
scenario: DecoderScenario;
|
|
26
|
-
}): Record<string, unknown> {
|
|
27
|
-
const result = spawnSync(
|
|
28
|
-
process.execPath,
|
|
29
|
-
[
|
|
30
|
-
'--conditions=react-server',
|
|
31
|
-
childPath,
|
|
32
|
-
options.scenario,
|
|
33
|
-
resolvePackage(`react-server-dom-rspack/server.${options.entry}`),
|
|
34
|
-
resolvePackage(`react-server-dom-rspack/client.${options.entry}`),
|
|
35
|
-
],
|
|
36
|
-
{
|
|
37
|
-
encoding: 'utf8',
|
|
38
|
-
env: { ...process.env, NODE_ENV: options.nodeEnv },
|
|
39
|
-
timeout: 5_000,
|
|
40
|
-
},
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
if (result.error || result.status !== 0) {
|
|
44
|
-
throw new Error(
|
|
45
|
-
[
|
|
46
|
-
`RSC decoder child failed for ${options.entry}/${options.nodeEnv}/${options.scenario}`,
|
|
47
|
-
result.stdout,
|
|
48
|
-
result.stderr,
|
|
49
|
-
]
|
|
50
|
-
.filter(Boolean)
|
|
51
|
-
.join('\n'),
|
|
52
|
-
{ cause: result.error },
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return JSON.parse(result.stdout) as Record<string, unknown>;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
for (const entry of ['node', 'edge'] as const) {
|
|
60
|
-
for (const nodeEnv of ['development', 'production'] as const) {
|
|
61
|
-
describe(`${entry} ${nodeEnv} Flight decoder security`, () => {
|
|
62
|
-
test('bounds cyclic Map reconstruction before invoking constructors', () => {
|
|
63
|
-
expect(
|
|
64
|
-
runDecoderScenario({ entry, nodeEnv, scenario: 'cycle' }),
|
|
65
|
-
).toEqual({
|
|
66
|
-
mapArrayConstructions: 2,
|
|
67
|
-
rejected: true,
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test('decodes referenced FormData with one backing-store enumeration', () => {
|
|
72
|
-
expect(
|
|
73
|
-
runDecoderScenario({
|
|
74
|
-
entry,
|
|
75
|
-
nodeEnv,
|
|
76
|
-
scenario: 'referenced-form-data',
|
|
77
|
-
}),
|
|
78
|
-
).toEqual({ keyIterations: 1, values: ['first', 'second'] });
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test('round-trips referenced FormData through the public client and server APIs', () => {
|
|
82
|
-
expect(
|
|
83
|
-
runDecoderScenario({
|
|
84
|
-
entry,
|
|
85
|
-
nodeEnv,
|
|
86
|
-
scenario: 'form-data-roundtrip',
|
|
87
|
-
}),
|
|
88
|
-
).toEqual({ values: ['first', 'second'] });
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test('rejects a Blob reference backed by a string', () => {
|
|
92
|
-
expect(
|
|
93
|
-
runDecoderScenario({ entry, nodeEnv, scenario: 'blob-type' }),
|
|
94
|
-
).toEqual({
|
|
95
|
-
rejected: true,
|
|
96
|
-
returnedString: false,
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test('does not recursively throw into a rejecting async iterator', () => {
|
|
101
|
-
expect(
|
|
102
|
-
runDecoderScenario({ entry, nodeEnv, scenario: 'async-iterator' }),
|
|
103
|
-
).toEqual({ throwCalls: 1 });
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test('loads one server module for eight distinct action identifiers', () => {
|
|
107
|
-
expect(
|
|
108
|
-
runDecoderScenario({ entry, nodeEnv, scenario: 'action-load' }),
|
|
109
|
-
).toEqual({ moduleLoads: 1, submittedEntries: [] });
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
}
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
__setTanstackRscPayloadDecoderForTests,
|
|
3
|
-
createTanstackRscServerPayload,
|
|
4
|
-
handleTanstackRscRedirect,
|
|
5
|
-
loadTanstackRscRouteData,
|
|
6
|
-
} from '../../src/runtime/rsc/payloadRouter';
|
|
7
|
-
|
|
8
|
-
function createPayload(
|
|
9
|
-
routeId: string,
|
|
10
|
-
loaderData: unknown,
|
|
11
|
-
options: { errors?: Record<string, unknown> | null } = {},
|
|
12
|
-
) {
|
|
13
|
-
return {
|
|
14
|
-
type: 'render',
|
|
15
|
-
actionData: null,
|
|
16
|
-
errors: options.errors ?? null,
|
|
17
|
-
loaderData: loaderData === undefined ? {} : { [routeId]: loaderData },
|
|
18
|
-
location: { href: routeId },
|
|
19
|
-
routes: [{ id: routeId, hasLoader: true }],
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function decodeJsonPayload(stream: ReadableStream<Uint8Array>) {
|
|
24
|
-
return JSON.parse(await new Response(stream).text());
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
describe('tanstack rsc payload router matrix', () => {
|
|
28
|
-
afterEach(() => {
|
|
29
|
-
__setTanstackRscPayloadDecoderForTests();
|
|
30
|
-
rstest.restoreAllMocks();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('normalizes redirect basenames while preserving status and non-location headers', () => {
|
|
34
|
-
const scenarios = [
|
|
35
|
-
{
|
|
36
|
-
location: '/base',
|
|
37
|
-
name: 'exact basename',
|
|
38
|
-
redirect: '/',
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
location: '/base/login?from=%2Fbase',
|
|
42
|
-
name: 'leading basename',
|
|
43
|
-
redirect: '/login?from=%2Fbase',
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
location: '/shop/base/login',
|
|
47
|
-
name: 'mid-path basename',
|
|
48
|
-
redirect: '/shop/base/login',
|
|
49
|
-
},
|
|
50
|
-
] as const;
|
|
51
|
-
|
|
52
|
-
for (const scenario of scenarios) {
|
|
53
|
-
const response = handleTanstackRscRedirect(
|
|
54
|
-
new Headers({
|
|
55
|
-
Location: scenario.location,
|
|
56
|
-
'X-Trace': scenario.name,
|
|
57
|
-
}),
|
|
58
|
-
'/base',
|
|
59
|
-
307,
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
expect(response.status).toBe(307);
|
|
63
|
-
expect(response.headers.get('Location')).toBeNull();
|
|
64
|
-
expect(response.headers.get('X-Modernjs-BaseUrl')).toBe('/base');
|
|
65
|
-
expect(response.headers.get('X-Modernjs-Redirect')).toBe(
|
|
66
|
-
scenario.redirect,
|
|
67
|
-
);
|
|
68
|
-
expect(response.headers.get('X-Trace')).toBe(scenario.name);
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test('separates payload fetch cache entries by URL and request method', async () => {
|
|
73
|
-
const payloads = [
|
|
74
|
-
createPayload('/resource', { method: 'GET' }),
|
|
75
|
-
createPayload('/resource', { method: 'POST' }),
|
|
76
|
-
];
|
|
77
|
-
let payloadIndex = 0;
|
|
78
|
-
const fetchMock = rstest.fn(() =>
|
|
79
|
-
Promise.resolve(new Response(JSON.stringify(payloads[payloadIndex++]))),
|
|
80
|
-
);
|
|
81
|
-
rstest.stubGlobal('fetch', fetchMock);
|
|
82
|
-
__setTanstackRscPayloadDecoderForTests(decodeJsonPayload);
|
|
83
|
-
|
|
84
|
-
const [getData, postData] = await Promise.all([
|
|
85
|
-
loadTanstackRscRouteData({
|
|
86
|
-
loadClientData: async () => ({ fallback: 'get' }),
|
|
87
|
-
request: new Request('http://localhost/resource', { method: 'GET' }),
|
|
88
|
-
routeId: '/resource',
|
|
89
|
-
}),
|
|
90
|
-
loadTanstackRscRouteData({
|
|
91
|
-
loadClientData: async () => ({ fallback: 'post' }),
|
|
92
|
-
request: new Request('http://localhost/resource', { method: 'POST' }),
|
|
93
|
-
routeId: '/resource',
|
|
94
|
-
}),
|
|
95
|
-
]);
|
|
96
|
-
|
|
97
|
-
expect(getData).toEqual({ method: 'GET' });
|
|
98
|
-
expect(postData).toEqual({ method: 'POST' });
|
|
99
|
-
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
100
|
-
expect(fetchMock.mock.calls.map(call => call[0])).toEqual([
|
|
101
|
-
'http://localhost/resource',
|
|
102
|
-
'http://localhost/resource',
|
|
103
|
-
]);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test('rethrows matched route errors from the decoded payload', async () => {
|
|
107
|
-
const routeError = new Error('route loader failed');
|
|
108
|
-
const payload = createPayload('/broken', undefined, {
|
|
109
|
-
errors: { '/broken': routeError },
|
|
110
|
-
});
|
|
111
|
-
rstest.stubGlobal(
|
|
112
|
-
'fetch',
|
|
113
|
-
rstest.fn(() => Promise.resolve(new Response('payload'))),
|
|
114
|
-
);
|
|
115
|
-
__setTanstackRscPayloadDecoderForTests(async () => payload);
|
|
116
|
-
|
|
117
|
-
await expect(
|
|
118
|
-
loadTanstackRscRouteData({
|
|
119
|
-
loadClientData: async () => ({ fallback: true }),
|
|
120
|
-
request: new Request('http://localhost/broken'),
|
|
121
|
-
routeId: '/broken',
|
|
122
|
-
}),
|
|
123
|
-
).rejects.toBe(routeError);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
test('returns undefined for route ids missing from the decoded payload', async () => {
|
|
127
|
-
const loadClientData = rstest.fn(async () => ({ fallback: true }));
|
|
128
|
-
const payload = createPayload('/known', { known: true });
|
|
129
|
-
rstest.stubGlobal(
|
|
130
|
-
'fetch',
|
|
131
|
-
rstest.fn(() => Promise.resolve(new Response('payload'))),
|
|
132
|
-
);
|
|
133
|
-
__setTanstackRscPayloadDecoderForTests(async () => payload);
|
|
134
|
-
|
|
135
|
-
await expect(
|
|
136
|
-
loadTanstackRscRouteData({
|
|
137
|
-
loadClientData,
|
|
138
|
-
request: new Request('http://localhost/missing'),
|
|
139
|
-
routeId: '/missing',
|
|
140
|
-
}),
|
|
141
|
-
).resolves.toBeUndefined();
|
|
142
|
-
expect(loadClientData).not.toHaveBeenCalled();
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
test('omits server payload matches with no route identity', () => {
|
|
146
|
-
const payload = createTanstackRscServerPayload({
|
|
147
|
-
state: {
|
|
148
|
-
location: { href: '/kept' },
|
|
149
|
-
matches: [
|
|
150
|
-
{
|
|
151
|
-
loaderData: { omitted: true },
|
|
152
|
-
params: {},
|
|
153
|
-
pathname: '/missing-id',
|
|
154
|
-
pathnameBase: '/missing-id',
|
|
155
|
-
route: {
|
|
156
|
-
options: {
|
|
157
|
-
path: 'missing-id',
|
|
158
|
-
staticData: { modernRouteHasLoader: true },
|
|
159
|
-
},
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
loaderData: { kept: true },
|
|
164
|
-
params: {},
|
|
165
|
-
pathname: '/kept',
|
|
166
|
-
pathnameBase: '/kept',
|
|
167
|
-
route: {
|
|
168
|
-
id: '/kept',
|
|
169
|
-
options: {
|
|
170
|
-
path: 'kept',
|
|
171
|
-
staticData: { modernRouteHasLoader: true },
|
|
172
|
-
},
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
],
|
|
176
|
-
},
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
expect(payload.loaderData).toEqual({ '/kept': { kept: true } });
|
|
180
|
-
expect(payload.routes).toEqual([
|
|
181
|
-
{
|
|
182
|
-
handle: undefined,
|
|
183
|
-
hasAction: false,
|
|
184
|
-
hasClientLoader: false,
|
|
185
|
-
hasErrorBoundary: false,
|
|
186
|
-
hasLoader: true,
|
|
187
|
-
id: '/kept',
|
|
188
|
-
index: undefined,
|
|
189
|
-
params: {},
|
|
190
|
-
parentId: undefined,
|
|
191
|
-
path: 'kept',
|
|
192
|
-
pathname: '/kept',
|
|
193
|
-
pathnameBase: '/kept',
|
|
194
|
-
},
|
|
195
|
-
]);
|
|
196
|
-
});
|
|
197
|
-
});
|