@bleedingdev/modern-js-plugin-tanstack 3.5.0-ultramodern.32 → 3.5.0-ultramodern.34
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/runtime/rsc/slotUsageSanitizer.js +1 -0
- package/dist/cjs/runtime/state.js +1 -1
- package/dist/esm/runtime/rsc/slotUsageSanitizer.mjs +1 -0
- package/dist/esm/runtime/state.mjs +1 -1
- package/dist/esm-node/runtime/rsc/slotUsageSanitizer.mjs +1 -0
- package/dist/esm-node/runtime/state.mjs +1 -1
- package/package.json +9 -9
- package/rstest.config.mts +2 -0
- package/src/runtime/rsc/slotUsageSanitizer.ts +4 -0
- package/src/runtime/state.ts +5 -1
- package/tests/router/flightSerialization.roundtrip.test.tsx +137 -0
- package/tests/router/hooks.test.ts +14 -0
- package/tests/router/slotUsageSanitizer.test.ts +23 -0
|
@@ -45,6 +45,7 @@ function sanitizeSlotArg(value, seen) {
|
|
|
45
45
|
if (null == value) return value;
|
|
46
46
|
if ('string' == typeof value || 'number' == typeof value || 'boolean' == typeof value || 'bigint' == typeof value) return value;
|
|
47
47
|
if ('object' != typeof value && 'function' != typeof value) return String(value);
|
|
48
|
+
if ('function' == typeof value) return '[Function]';
|
|
48
49
|
if (seen.has(value)) return '[Circular]';
|
|
49
50
|
seen.add(value);
|
|
50
51
|
if (Array.isArray(value)) return value.map((item)=>sanitizeSlotArg(item, seen));
|
|
@@ -33,7 +33,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
33
33
|
const context_namespaceObject = require("@modern-js/runtime/context");
|
|
34
34
|
function getTanstackRouterState(context) {
|
|
35
35
|
const state = (0, context_namespaceObject.getRouterRuntimeState)(context);
|
|
36
|
-
if (void 0 === state || 'tanstack' !== state.framework) return;
|
|
36
|
+
if (void 0 === state || 'tanstack' !== state.framework || void 0 === state.instance) return;
|
|
37
37
|
return state;
|
|
38
38
|
}
|
|
39
39
|
exports.getTanstackRouterState = __webpack_exports__.getTanstackRouterState;
|
|
@@ -13,6 +13,7 @@ function sanitizeSlotArg(value, seen) {
|
|
|
13
13
|
if (null == value) return value;
|
|
14
14
|
if ('string' == typeof value || 'number' == typeof value || 'boolean' == typeof value || 'bigint' == typeof value) return value;
|
|
15
15
|
if ('object' != typeof value && 'function' != typeof value) return String(value);
|
|
16
|
+
if ('function' == typeof value) return '[Function]';
|
|
16
17
|
if (seen.has(value)) return '[Circular]';
|
|
17
18
|
seen.add(value);
|
|
18
19
|
if (Array.isArray(value)) return value.map((item)=>sanitizeSlotArg(item, seen));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getRouterRuntimeState } from "@modern-js/runtime/context";
|
|
2
2
|
function getTanstackRouterState(context) {
|
|
3
3
|
const state = getRouterRuntimeState(context);
|
|
4
|
-
if (void 0 === state || 'tanstack' !== state.framework) return;
|
|
4
|
+
if (void 0 === state || 'tanstack' !== state.framework || void 0 === state.instance) return;
|
|
5
5
|
return state;
|
|
6
6
|
}
|
|
7
7
|
export { getTanstackRouterState };
|
|
@@ -14,6 +14,7 @@ function sanitizeSlotArg(value, seen) {
|
|
|
14
14
|
if (null == value) return value;
|
|
15
15
|
if ('string' == typeof value || 'number' == typeof value || 'boolean' == typeof value || 'bigint' == typeof value) return value;
|
|
16
16
|
if ('object' != typeof value && 'function' != typeof value) return String(value);
|
|
17
|
+
if ('function' == typeof value) return '[Function]';
|
|
17
18
|
if (seen.has(value)) return '[Circular]';
|
|
18
19
|
seen.add(value);
|
|
19
20
|
if (Array.isArray(value)) return value.map((item)=>sanitizeSlotArg(item, seen));
|
|
@@ -2,7 +2,7 @@ import "node:module";
|
|
|
2
2
|
import { getRouterRuntimeState } from "@modern-js/runtime/context";
|
|
3
3
|
function getTanstackRouterState(context) {
|
|
4
4
|
const state = getRouterRuntimeState(context);
|
|
5
|
-
if (void 0 === state || 'tanstack' !== state.framework) return;
|
|
5
|
+
if (void 0 === state || 'tanstack' !== state.framework || void 0 === state.instance) return;
|
|
6
6
|
return state;
|
|
7
7
|
}
|
|
8
8
|
export { getTanstackRouterState };
|
package/package.json
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"modern.js",
|
|
19
19
|
"tanstack-router"
|
|
20
20
|
],
|
|
21
|
-
"version": "3.5.0-ultramodern.
|
|
21
|
+
"version": "3.5.0-ultramodern.34",
|
|
22
22
|
"engines": {
|
|
23
23
|
"node": ">=20"
|
|
24
24
|
},
|
|
@@ -98,14 +98,14 @@
|
|
|
98
98
|
"@tanstack/react-router": "1.170.16",
|
|
99
99
|
"@tanstack/router-core": "1.171.13",
|
|
100
100
|
"react-server-dom-rspack": "0.0.2",
|
|
101
|
-
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.5.0-ultramodern.
|
|
102
|
-
"@modern-js/
|
|
103
|
-
"@modern-js/
|
|
104
|
-
"@modern-js/
|
|
101
|
+
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.5.0-ultramodern.34",
|
|
102
|
+
"@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.5.0-ultramodern.34",
|
|
103
|
+
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.5.0-ultramodern.34",
|
|
104
|
+
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.5.0-ultramodern.34"
|
|
105
105
|
},
|
|
106
106
|
"peerDependencies": {
|
|
107
|
-
"@modern-js/app-tools": "3.5.0-ultramodern.
|
|
108
|
-
"@modern-js/runtime": "3.5.0-ultramodern.
|
|
107
|
+
"@modern-js/app-tools": "3.5.0-ultramodern.34",
|
|
108
|
+
"@modern-js/runtime": "3.5.0-ultramodern.34",
|
|
109
109
|
"react": "^19.2.7",
|
|
110
110
|
"react-dom": "^19.2.7"
|
|
111
111
|
},
|
|
@@ -120,8 +120,8 @@
|
|
|
120
120
|
"@typescript/native-preview": "7.0.0-dev.20260628.1",
|
|
121
121
|
"react": "^19.2.7",
|
|
122
122
|
"react-dom": "^19.2.7",
|
|
123
|
-
"@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.5.0-ultramodern.
|
|
124
|
-
"@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.5.0-ultramodern.
|
|
123
|
+
"@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.5.0-ultramodern.34",
|
|
124
|
+
"@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.5.0-ultramodern.34",
|
|
125
125
|
"@scripts/rstest-config": "2.66.0"
|
|
126
126
|
},
|
|
127
127
|
"sideEffects": [
|
package/rstest.config.mts
CHANGED
|
@@ -25,6 +25,7 @@ export default {
|
|
|
25
25
|
include: [
|
|
26
26
|
'tests/router/cli.test.ts',
|
|
27
27
|
'tests/router/fastDefaults.test.ts',
|
|
28
|
+
'tests/router/flightSerialization.roundtrip.test.tsx',
|
|
28
29
|
'tests/router/generateRouteArtifacts.test.ts',
|
|
29
30
|
'tests/router/hooks.test.ts',
|
|
30
31
|
'tests/router/loaderBridge.test.ts',
|
|
@@ -33,6 +34,7 @@ export default {
|
|
|
33
34
|
'tests/router/register.test.ts',
|
|
34
35
|
'tests/router/routeHooks.test.ts',
|
|
35
36
|
'tests/router/rsc.test.tsx',
|
|
37
|
+
'tests/router/slotUsageSanitizer.test.ts',
|
|
36
38
|
'tests/router/ssrPreload.test.ts',
|
|
37
39
|
'tests/router/tanstackTypes.test.ts',
|
|
38
40
|
'tests/router/routeTree.test.ts',
|
package/src/runtime/state.ts
CHANGED
|
@@ -23,7 +23,11 @@ export function getTanstackRouterState(
|
|
|
23
23
|
context: object,
|
|
24
24
|
): TanstackRouterState | undefined {
|
|
25
25
|
const state = getRouterRuntimeState(context);
|
|
26
|
-
if (
|
|
26
|
+
if (
|
|
27
|
+
state === undefined ||
|
|
28
|
+
state.framework !== 'tanstack' ||
|
|
29
|
+
state.instance === undefined
|
|
30
|
+
) {
|
|
27
31
|
return undefined;
|
|
28
32
|
}
|
|
29
33
|
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import { isValidElement } from 'react';
|
|
3
|
+
import { createRscProxy } from '../../src/runtime/rsc/createRscProxy';
|
|
4
|
+
import {
|
|
5
|
+
reviveTanstackRscFlightValues,
|
|
6
|
+
serializeTanstackRscFlightValues,
|
|
7
|
+
} from '../../src/runtime/rsc/flightSerialization';
|
|
8
|
+
import {
|
|
9
|
+
RENDERABLE_RSC,
|
|
10
|
+
SERVER_COMPONENT_STREAM,
|
|
11
|
+
type ServerComponentStream,
|
|
12
|
+
} from '../../src/runtime/rsc/symbols';
|
|
13
|
+
|
|
14
|
+
type RoundTripFixture = {
|
|
15
|
+
bigintValue: bigint;
|
|
16
|
+
booleanValue: boolean;
|
|
17
|
+
circular: Record<string, unknown>;
|
|
18
|
+
dateValue: Date;
|
|
19
|
+
falseValue: boolean;
|
|
20
|
+
mapValue: Map<unknown, unknown>;
|
|
21
|
+
nested: {
|
|
22
|
+
array: unknown[];
|
|
23
|
+
object: {
|
|
24
|
+
child: unknown;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
nullValue: null;
|
|
28
|
+
numberValue: number;
|
|
29
|
+
rscProxy: React.ReactElement & Record<PropertyKey, unknown>;
|
|
30
|
+
setValue: Set<unknown>;
|
|
31
|
+
sharedA: Record<string, unknown>;
|
|
32
|
+
sharedB: Record<string, unknown>;
|
|
33
|
+
stringValue: string;
|
|
34
|
+
undefinedValue: undefined;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function createStream(): ServerComponentStream {
|
|
38
|
+
return {
|
|
39
|
+
createReplayStream: () => new ReadableStream<Uint8Array>(),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
test('round-trips TanStack RSC flight values across supported value types', async () => {
|
|
44
|
+
const shared = { marker: 'shared' };
|
|
45
|
+
const circular: Record<string, unknown> = { marker: 'circular' };
|
|
46
|
+
circular.self = circular;
|
|
47
|
+
|
|
48
|
+
const mapValue = new Map<unknown, unknown>([
|
|
49
|
+
['primitive', 1],
|
|
50
|
+
['shared', shared],
|
|
51
|
+
[shared, circular],
|
|
52
|
+
]);
|
|
53
|
+
mapValue.set('self', mapValue);
|
|
54
|
+
|
|
55
|
+
const setValue = new Set<unknown>(['set-item', shared, circular]);
|
|
56
|
+
setValue.add(setValue);
|
|
57
|
+
|
|
58
|
+
const rscProxy = createRscProxy(() => ({ slot: 'server' }), {
|
|
59
|
+
renderable: true,
|
|
60
|
+
stream: createStream(),
|
|
61
|
+
}) as React.ReactElement & Record<PropertyKey, unknown>;
|
|
62
|
+
|
|
63
|
+
const fixture: RoundTripFixture = {
|
|
64
|
+
bigintValue: 9007199254740993n,
|
|
65
|
+
booleanValue: true,
|
|
66
|
+
circular,
|
|
67
|
+
dateValue: new Date('2026-07-08T12:34:56.789Z'),
|
|
68
|
+
falseValue: false,
|
|
69
|
+
mapValue,
|
|
70
|
+
nested: {
|
|
71
|
+
array: [shared, [null, undefined, 7n]],
|
|
72
|
+
object: {
|
|
73
|
+
child: shared,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
nullValue: null,
|
|
77
|
+
numberValue: 42,
|
|
78
|
+
rscProxy,
|
|
79
|
+
setValue,
|
|
80
|
+
sharedA: shared,
|
|
81
|
+
sharedB: shared,
|
|
82
|
+
stringValue: 'flight',
|
|
83
|
+
undefinedValue: undefined,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const serialized = serializeTanstackRscFlightValues(
|
|
87
|
+
fixture,
|
|
88
|
+
) as RoundTripFixture;
|
|
89
|
+
const revived = (await reviveTanstackRscFlightValues(
|
|
90
|
+
serialized,
|
|
91
|
+
)) as RoundTripFixture;
|
|
92
|
+
|
|
93
|
+
expect(revived).not.toBe(fixture);
|
|
94
|
+
expect(revived.stringValue).toBe('flight');
|
|
95
|
+
expect(revived.numberValue).toBe(42);
|
|
96
|
+
expect(revived.booleanValue).toBe(true);
|
|
97
|
+
expect(revived.falseValue).toBe(false);
|
|
98
|
+
expect(revived.nullValue).toBeNull();
|
|
99
|
+
expect(Object.hasOwn(revived, 'undefinedValue')).toBe(true);
|
|
100
|
+
expect(revived.undefinedValue).toBeUndefined();
|
|
101
|
+
expect(revived.bigintValue).toBe(9007199254740993n);
|
|
102
|
+
expect(revived.dateValue).toBeInstanceOf(Date);
|
|
103
|
+
expect(revived.dateValue.toISOString()).toBe('2026-07-08T12:34:56.789Z');
|
|
104
|
+
|
|
105
|
+
expect(revived.nested.array[0]).toBe(revived.sharedA);
|
|
106
|
+
expect(revived.nested.object.child).toBe(revived.sharedA);
|
|
107
|
+
expect(revived.sharedB).toBe(revived.sharedA);
|
|
108
|
+
|
|
109
|
+
expect(revived.circular).not.toBe(circular);
|
|
110
|
+
expect(revived.circular.self).toBe(revived.circular);
|
|
111
|
+
|
|
112
|
+
expect(revived.mapValue).toBeInstanceOf(Map);
|
|
113
|
+
expect(revived.mapValue).not.toBe(mapValue);
|
|
114
|
+
expect(revived.mapValue.get('primitive')).toBe(1);
|
|
115
|
+
expect(revived.mapValue.get('shared')).toBe(revived.sharedA);
|
|
116
|
+
expect(revived.mapValue.get(revived.sharedA)).toBe(revived.circular);
|
|
117
|
+
expect(revived.mapValue.get('self')).toBe(revived.mapValue);
|
|
118
|
+
|
|
119
|
+
expect(revived.setValue).toBeInstanceOf(Set);
|
|
120
|
+
expect(revived.setValue).not.toBe(setValue);
|
|
121
|
+
expect(revived.setValue.has('set-item')).toBe(true);
|
|
122
|
+
expect(revived.setValue.has(revived.sharedA)).toBe(true);
|
|
123
|
+
expect(revived.setValue.has(revived.circular)).toBe(true);
|
|
124
|
+
expect(revived.setValue.has(revived.setValue)).toBe(true);
|
|
125
|
+
|
|
126
|
+
expect(serialized.rscProxy).not.toBe(rscProxy);
|
|
127
|
+
expect(serialized.rscProxy).toMatchObject({
|
|
128
|
+
__modernTanstackRsc: true,
|
|
129
|
+
kind: 'renderable',
|
|
130
|
+
});
|
|
131
|
+
expect(serialized.rscProxy.stream).toBeInstanceOf(ReadableStream);
|
|
132
|
+
|
|
133
|
+
expect(revived.rscProxy).not.toBe(rscProxy);
|
|
134
|
+
expect(isValidElement(revived.rscProxy)).toBe(true);
|
|
135
|
+
expect(revived.rscProxy[RENDERABLE_RSC]).toBe(true);
|
|
136
|
+
expect(revived.rscProxy[SERVER_COMPONENT_STREAM]).toBeDefined();
|
|
137
|
+
});
|
|
@@ -3,8 +3,10 @@ import {
|
|
|
3
3
|
routerProviderRegistryHooks,
|
|
4
4
|
} from '@modern-js/runtime/context';
|
|
5
5
|
import * as tanstackHooks from '../../src/runtime/hooks';
|
|
6
|
+
import { applyRouterRuntimeState } from '../../src/runtime/lifecycle';
|
|
6
7
|
import { tanstackRouterPlugin as browserPlugin } from '../../src/runtime/plugin';
|
|
7
8
|
import { tanstackRouterPlugin as nodePlugin } from '../../src/runtime/plugin.node';
|
|
9
|
+
import { getTanstackRouterState } from '../../src/runtime/state';
|
|
8
10
|
|
|
9
11
|
describe('tanstack router hooks single declaration source', () => {
|
|
10
12
|
test('re-exports the canonical hook instances owned by @modern-js/runtime', () => {
|
|
@@ -24,3 +26,15 @@ describe('tanstack router hooks single declaration source', () => {
|
|
|
24
26
|
expect(nodePlugin().registryHooks).toBe(routerProviderRegistryHooks);
|
|
25
27
|
});
|
|
26
28
|
});
|
|
29
|
+
|
|
30
|
+
describe('tanstack router runtime state accessor', () => {
|
|
31
|
+
test('returns undefined before a tanstack router instance is created', () => {
|
|
32
|
+
const context = {};
|
|
33
|
+
|
|
34
|
+
applyRouterRuntimeState(context as never, {
|
|
35
|
+
framework: 'tanstack',
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(getTanstackRouterState(context)).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { sanitizeSlotArgs } from '../../src/runtime/rsc/slotUsageSanitizer';
|
|
2
|
+
|
|
3
|
+
describe('RSC slot usage sanitizer', () => {
|
|
4
|
+
test('redacts callable slot args without serializing function source', () => {
|
|
5
|
+
function tokenFactory() {
|
|
6
|
+
return 'token-secret';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const args = sanitizeSlotArgs([
|
|
10
|
+
tokenFactory,
|
|
11
|
+
{ nested: tokenFactory },
|
|
12
|
+
[tokenFactory],
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
expect(args).toEqual([
|
|
16
|
+
'[Function]',
|
|
17
|
+
{ nested: '[Function]' },
|
|
18
|
+
['[Function]'],
|
|
19
|
+
]);
|
|
20
|
+
expect(JSON.stringify(args)).not.toContain('token-secret');
|
|
21
|
+
expect(JSON.stringify(args)).not.toContain('tokenFactory');
|
|
22
|
+
});
|
|
23
|
+
});
|