@bleedingdev/modern-js-renderer-octane 0.0.0-stage → 3.9.3-ultramodern.2
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/LICENSE +21 -0
- package/bin/octane-tsc.mjs +4 -0
- package/dist/cjs/bootstrap.js +68 -0
- package/dist/cjs/client.js +371 -0
- package/dist/cjs/entry-application.js +83 -0
- package/dist/cjs/entry-client.js +92 -0
- package/dist/cjs/entry-server.js +127 -0
- package/dist/cjs/federation-context.js +72 -0
- package/dist/cjs/federation.js +103 -0
- package/dist/cjs/i18n/I18nProvider.js +67 -0
- package/dist/cjs/i18n/LocalizedLink.js +89 -0
- package/dist/cjs/i18n/context.js +41 -0
- package/dist/cjs/i18n/index.js +86 -0
- package/dist/cjs/i18n/languageSync.js +99 -0
- package/dist/cjs/i18n/types.js +18 -0
- package/dist/cjs/i18n/useI18n.js +110 -0
- package/dist/cjs/index.js +46 -0
- package/dist/cjs/manifest.js +167 -0
- package/dist/cjs/plugin/compiler.js +125 -0
- package/dist/cjs/plugin/create.js +265 -0
- package/dist/cjs/plugin/index.js +151 -0
- package/dist/cjs/plugin/profile.js +89 -0
- package/dist/cjs/plugin/source.js +56 -0
- package/dist/cjs/router-client.js +139 -0
- package/dist/cjs/router-handler.js +168 -0
- package/dist/cjs/router-injection.js +139 -0
- package/dist/cjs/router-server-cleanup.js +46 -0
- package/dist/cjs/router-server-snapshot.js +405 -0
- package/dist/cjs/router-server.js +108 -0
- package/dist/cjs/router-snapshot.js +270 -0
- package/dist/cjs/router.js +218 -0
- package/dist/cjs/routes.js +187 -0
- package/dist/cjs/server.js +224 -0
- package/dist/cjs/typecheck.js +615 -0
- package/dist/esm/bootstrap.mjs +24 -0
- package/dist/esm/client.mjs +327 -0
- package/dist/esm/entry-application.mjs +39 -0
- package/dist/esm/entry-client.mjs +54 -0
- package/dist/esm/entry-server.mjs +86 -0
- package/dist/esm/federation-context.mjs +25 -0
- package/dist/esm/federation.mjs +65 -0
- package/dist/esm/i18n/I18nProvider.mjs +29 -0
- package/dist/esm/i18n/LocalizedLink.mjs +48 -0
- package/dist/esm/i18n/context.mjs +3 -0
- package/dist/esm/i18n/index.mjs +8 -0
- package/dist/esm/i18n/languageSync.mjs +61 -0
- package/dist/esm/i18n/types.mjs +0 -0
- package/dist/esm/i18n/useI18n.mjs +72 -0
- package/dist/esm/index.mjs +8 -0
- package/dist/esm/manifest.mjs +117 -0
- package/dist/esm/router-client.mjs +98 -0
- package/dist/esm/router-handler.mjs +130 -0
- package/dist/esm/router-injection.mjs +101 -0
- package/dist/esm/router-server-cleanup.mjs +8 -0
- package/dist/esm/router-server-snapshot.mjs +367 -0
- package/dist/esm/router-server.mjs +12 -0
- package/dist/esm/router-snapshot.mjs +199 -0
- package/dist/esm/router.mjs +94 -0
- package/dist/esm/routes.mjs +134 -0
- package/dist/esm/server.mjs +183 -0
- package/dist/esm-node/bootstrap.mjs +24 -0
- package/dist/esm-node/manifest.mjs +117 -0
- package/dist/esm-node/plugin/compiler.mjs +70 -0
- package/dist/esm-node/plugin/create.mjs +227 -0
- package/dist/esm-node/plugin/index.mjs +103 -0
- package/dist/esm-node/plugin/profile.mjs +51 -0
- package/dist/esm-node/plugin/source.mjs +7 -0
- package/dist/esm-node/typecheck.mjs +560 -0
- package/dist/types/bootstrap.d.ts +10 -0
- package/dist/types/client.d.ts +36 -0
- package/dist/types/entry-application.d.ts +19 -0
- package/dist/types/entry-client.d.ts +8 -0
- package/dist/types/entry-server.d.ts +11 -0
- package/dist/types/federation-context.d.ts +28 -0
- package/dist/types/federation.d.ts +18 -0
- package/dist/types/i18n/I18nProvider.d.ts +8 -0
- package/dist/types/i18n/LocalizedLink.d.ts +41 -0
- package/dist/types/i18n/context.d.ts +9 -0
- package/dist/types/i18n/index.d.ts +9 -0
- package/dist/types/i18n/languageSync.d.ts +26 -0
- package/dist/types/i18n/types.d.ts +34 -0
- package/dist/types/i18n/useI18n.d.ts +21 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/manifest.d.ts +34 -0
- package/dist/types/plugin/compiler-manifest.d.cts +10 -0
- package/dist/types/plugin/compiler.d.ts +5 -0
- package/dist/types/plugin/create.d.ts +23 -0
- package/dist/types/plugin/index.d.ts +3 -0
- package/dist/types/plugin/profile.d.ts +2 -0
- package/dist/types/plugin/source.d.ts +1 -0
- package/dist/types/plugin/svg-component-template.d.cts +6 -0
- package/dist/types/router-client.d.ts +12 -0
- package/dist/types/router-handler.d.ts +13 -0
- package/dist/types/router-injection.d.ts +8 -0
- package/dist/types/router-server-cleanup.d.ts +3 -0
- package/dist/types/router-server-snapshot.d.ts +15 -0
- package/dist/types/router-server.d.ts +13 -0
- package/dist/types/router-snapshot.d.ts +14 -0
- package/dist/types/router.d.ts +30 -0
- package/dist/types/routes.d.ts +18 -0
- package/dist/types/server.d.ts +41 -0
- package/dist/types/typecheck.d.ts +31 -0
- package/package.json +216 -4
- package/src/bootstrap.ts +60 -0
- package/src/client.ts +485 -0
- package/src/entry-application.ts +72 -0
- package/src/entry-client.ts +80 -0
- package/src/entry-server.ts +142 -0
- package/src/federation-context.ts +53 -0
- package/src/federation.ts +158 -0
- package/src/i18n/I18nProvider.ts +56 -0
- package/src/i18n/LocalizedLink.ts +112 -0
- package/src/i18n/context.ts +12 -0
- package/src/i18n/index.ts +35 -0
- package/src/i18n/languageSync.ts +132 -0
- package/src/i18n/types.ts +36 -0
- package/src/i18n/useI18n.ts +119 -0
- package/src/index.ts +10 -0
- package/src/manifest.ts +253 -0
- package/src/plugin/compiler-manifest.cjs +178 -0
- package/src/plugin/compiler.ts +112 -0
- package/src/plugin/create.ts +272 -0
- package/src/plugin/index.ts +105 -0
- package/src/plugin/profile.ts +46 -0
- package/src/plugin/source.ts +16 -0
- package/src/plugin/svg-component-template.cjs +17 -0
- package/src/router-client.ts +144 -0
- package/src/router-handler.ts +182 -0
- package/src/router-injection.ts +145 -0
- package/src/router-server-cleanup.ts +11 -0
- package/src/router-server-snapshot.ts +564 -0
- package/src/router-server.ts +23 -0
- package/src/router-snapshot.ts +251 -0
- package/src/router.ts +192 -0
- package/src/routes.ts +263 -0
- package/src/server.ts +300 -0
- package/src/typecheck.ts +723 -0
- package/README.md +0 -3
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createRequestDataPolicy,
|
|
3
|
+
publicDataError,
|
|
4
|
+
} from '@modern-js/renderer-core/data';
|
|
5
|
+
import {
|
|
6
|
+
type RequestSession,
|
|
7
|
+
untilAborted,
|
|
8
|
+
} from '@modern-js/renderer-core/session';
|
|
9
|
+
import {
|
|
10
|
+
type AnyRouter,
|
|
11
|
+
createSerializationAdapter,
|
|
12
|
+
} from '@octanejs/tanstack-router';
|
|
13
|
+
import { cleanupOctaneRouterSSR } from './router-server-cleanup';
|
|
14
|
+
import {
|
|
15
|
+
ADAPTER_KEY,
|
|
16
|
+
descriptors,
|
|
17
|
+
invalid,
|
|
18
|
+
MATCH_FIELDS,
|
|
19
|
+
NATIVE_ERROR_KIND,
|
|
20
|
+
NATIVE_ROOT_FIELDS,
|
|
21
|
+
nativePromise,
|
|
22
|
+
octaneRouterSnapshotAdapter,
|
|
23
|
+
ROUTE_ERROR_KIND,
|
|
24
|
+
record,
|
|
25
|
+
stringField,
|
|
26
|
+
} from './router-snapshot';
|
|
27
|
+
import { RouteDataError } from './routes';
|
|
28
|
+
|
|
29
|
+
const ERROR_FIELDS = new Set(['name', 'message', 'stack', 'cause']);
|
|
30
|
+
const ROUTE_ERROR_FIELDS = new Set([
|
|
31
|
+
...ERROR_FIELDS,
|
|
32
|
+
'routeId',
|
|
33
|
+
'status',
|
|
34
|
+
'data',
|
|
35
|
+
]);
|
|
36
|
+
const errorConstructors = new Map<object, typeof Error>([
|
|
37
|
+
[Error.prototype, Error],
|
|
38
|
+
[TypeError.prototype, TypeError],
|
|
39
|
+
[RangeError.prototype, RangeError],
|
|
40
|
+
[SyntaxError.prototype, SyntaxError],
|
|
41
|
+
[ReferenceError.prototype, ReferenceError],
|
|
42
|
+
[URIError.prototype, URIError],
|
|
43
|
+
[EvalError.prototype, EvalError],
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
export interface OctaneRouterSerializationOptions {
|
|
47
|
+
readonly production?: boolean;
|
|
48
|
+
/** Request-private records that are structurally ordinary JavaScript objects. */
|
|
49
|
+
readonly forbiddenValues?: readonly object[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface OctaneRouterSerializationGuard {
|
|
53
|
+
/** Call immediately before native dehydrateMatch reads the internal matches. */
|
|
54
|
+
assertMatches(): void;
|
|
55
|
+
/** Native Seroval swallows errors; call after dehydrate and before responding. */
|
|
56
|
+
assertActive(): void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const owners = new WeakMap<AnyRouter, RequestSession>();
|
|
60
|
+
|
|
61
|
+
/** Project the fresh native outer record before Seroval walks any application value. */
|
|
62
|
+
export function prepareOctaneRouterSerialization(
|
|
63
|
+
router: AnyRouter,
|
|
64
|
+
session: RequestSession,
|
|
65
|
+
options: OctaneRouterSerializationOptions = {},
|
|
66
|
+
): OctaneRouterSerializationGuard {
|
|
67
|
+
session.signal.throwIfAborted();
|
|
68
|
+
if (owners.has(router)) {
|
|
69
|
+
throw invalid('a router already has a request serialization owner');
|
|
70
|
+
}
|
|
71
|
+
const adapterProperty = Object.getOwnPropertyDescriptor(
|
|
72
|
+
router.options,
|
|
73
|
+
'serializationAdapters',
|
|
74
|
+
);
|
|
75
|
+
const dehydrateProperty = Object.getOwnPropertyDescriptor(
|
|
76
|
+
router.options,
|
|
77
|
+
'dehydrate',
|
|
78
|
+
);
|
|
79
|
+
if (
|
|
80
|
+
adapterProperty?.get ||
|
|
81
|
+
adapterProperty?.set ||
|
|
82
|
+
dehydrateProperty?.get ||
|
|
83
|
+
dehydrateProperty?.set
|
|
84
|
+
) {
|
|
85
|
+
throw invalid('native serializer options must be owned data properties');
|
|
86
|
+
}
|
|
87
|
+
const consumerAdapters: unknown = adapterProperty?.value ?? [];
|
|
88
|
+
if (
|
|
89
|
+
!Array.isArray(consumerAdapters) ||
|
|
90
|
+
Object.getPrototypeOf(consumerAdapters) !== Array.prototype
|
|
91
|
+
) {
|
|
92
|
+
throw invalid('native serializationAdapters must be an owned array');
|
|
93
|
+
}
|
|
94
|
+
const adapterFields = descriptors(consumerAdapters);
|
|
95
|
+
if (
|
|
96
|
+
consumerAdapters.length > 1 ||
|
|
97
|
+
(consumerAdapters.length === 1 &&
|
|
98
|
+
adapterFields['0']?.value !== octaneRouterSnapshotAdapter) ||
|
|
99
|
+
Object.keys(adapterFields).length !== consumerAdapters.length + 1
|
|
100
|
+
) {
|
|
101
|
+
throw invalid(
|
|
102
|
+
'custom serializationAdapters are not admitted in framework SSR; return supported public data',
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
const consumerDehydrate: unknown = dehydrateProperty?.value;
|
|
106
|
+
if (
|
|
107
|
+
consumerDehydrate !== undefined &&
|
|
108
|
+
typeof consumerDehydrate !== 'function'
|
|
109
|
+
) {
|
|
110
|
+
throw invalid('native dehydrate must be an owned function');
|
|
111
|
+
}
|
|
112
|
+
owners.set(router, session);
|
|
113
|
+
const production =
|
|
114
|
+
options.production ?? process.env['NODE_ENV'] === 'production';
|
|
115
|
+
const policy = createRequestDataPolicy([
|
|
116
|
+
session,
|
|
117
|
+
session.request,
|
|
118
|
+
session.platform,
|
|
119
|
+
session.platform.bindings,
|
|
120
|
+
...(options.forbiddenValues ?? []),
|
|
121
|
+
]);
|
|
122
|
+
const copies = new WeakMap<object, object>();
|
|
123
|
+
const projectedRoots = new WeakSet<object>();
|
|
124
|
+
let nativeRoot: object | undefined;
|
|
125
|
+
let released = false;
|
|
126
|
+
let failure: unknown;
|
|
127
|
+
|
|
128
|
+
const fail = (error: unknown): never => {
|
|
129
|
+
failure ??= error;
|
|
130
|
+
session.fail(failure);
|
|
131
|
+
// Native cleanup stops bytes, while checked transport promises prevent
|
|
132
|
+
// Seroval from traversing the invalid source value after cleanup.
|
|
133
|
+
cleanupOctaneRouterSSR(router);
|
|
134
|
+
throw failure;
|
|
135
|
+
};
|
|
136
|
+
const assertActive = () => {
|
|
137
|
+
if (failure !== undefined) throw failure;
|
|
138
|
+
session.signal.throwIfAborted();
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
function copyPublic(value: unknown): unknown {
|
|
142
|
+
// Repeat validation at each deferred settlement, even when an earlier
|
|
143
|
+
// snapshot already owns an alias of this source object.
|
|
144
|
+
policy.assertValue(value);
|
|
145
|
+
return copyValue(value);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function copyValue(value: unknown): unknown {
|
|
149
|
+
if (value === null || typeof value !== 'object') return value;
|
|
150
|
+
const fields = descriptors(value);
|
|
151
|
+
const previous = copies.get(value);
|
|
152
|
+
if (previous) return previous;
|
|
153
|
+
const prototype = Object.getPrototypeOf(value);
|
|
154
|
+
let result: object;
|
|
155
|
+
if (prototype === Date.prototype) {
|
|
156
|
+
result = new Date(Date.prototype.valueOf.call(value));
|
|
157
|
+
} else if (prototype === RegExp.prototype) {
|
|
158
|
+
const source = Object.getOwnPropertyDescriptor(
|
|
159
|
+
RegExp.prototype,
|
|
160
|
+
'source',
|
|
161
|
+
)!.get!.call(value);
|
|
162
|
+
const flags = [
|
|
163
|
+
'hasIndices',
|
|
164
|
+
'global',
|
|
165
|
+
'ignoreCase',
|
|
166
|
+
'multiline',
|
|
167
|
+
'dotAll',
|
|
168
|
+
'unicode',
|
|
169
|
+
'unicodeSets',
|
|
170
|
+
'sticky',
|
|
171
|
+
];
|
|
172
|
+
const letters = ['d', 'g', 'i', 'm', 's', 'u', 'v', 'y'];
|
|
173
|
+
const enabled = flags
|
|
174
|
+
.map((flag, index) =>
|
|
175
|
+
Object.getOwnPropertyDescriptor(RegExp.prototype, flag)?.get?.call(
|
|
176
|
+
value,
|
|
177
|
+
)
|
|
178
|
+
? letters[index]
|
|
179
|
+
: '',
|
|
180
|
+
)
|
|
181
|
+
.join('');
|
|
182
|
+
result = new RegExp(source, enabled);
|
|
183
|
+
} else if (prototype === Map.prototype) result = new Map();
|
|
184
|
+
else if (prototype === Set.prototype) result = new Set();
|
|
185
|
+
else if (Array.isArray(value)) result = [];
|
|
186
|
+
else result = Object.create(prototype);
|
|
187
|
+
copies.set(value, result);
|
|
188
|
+
if (prototype === Map.prototype) {
|
|
189
|
+
for (const [key, item] of Map.prototype.entries.call(value)) {
|
|
190
|
+
Map.prototype.set.call(result, copyValue(key), copyValue(item));
|
|
191
|
+
}
|
|
192
|
+
} else if (prototype === Set.prototype) {
|
|
193
|
+
for (const item of Set.prototype.values.call(value)) {
|
|
194
|
+
Set.prototype.add.call(result, copyValue(item));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Define array length last so frozen arrays retain their descriptor shape.
|
|
198
|
+
for (const [key, field] of Object.entries(fields)) {
|
|
199
|
+
if (Array.isArray(value) && key === 'length') continue;
|
|
200
|
+
Object.defineProperty(result, key, {
|
|
201
|
+
...field,
|
|
202
|
+
value: copyValue(field.value),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
if (Array.isArray(value))
|
|
206
|
+
Object.defineProperty(result, 'length', fields['length']!);
|
|
207
|
+
if (!Object.isExtensible(value)) Object.preventExtensions(result);
|
|
208
|
+
return result;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function copyError(value: unknown): unknown {
|
|
212
|
+
if (value === null || typeof value !== 'object') return copyPublic(value);
|
|
213
|
+
policy.assertRoot(value);
|
|
214
|
+
const prototype = Object.getPrototypeOf(value);
|
|
215
|
+
const routeError = prototype === RouteDataError.prototype;
|
|
216
|
+
const Constructor = errorConstructors.get(prototype);
|
|
217
|
+
if (!routeError && !Constructor) return copyPublic(value);
|
|
218
|
+
const fields = descriptors(
|
|
219
|
+
value,
|
|
220
|
+
routeError ? ROUTE_ERROR_FIELDS : ERROR_FIELDS,
|
|
221
|
+
true,
|
|
222
|
+
);
|
|
223
|
+
if (Object.prototype.toString.call(value) !== '[object Error]') {
|
|
224
|
+
throw invalid('an error requires genuine native Error slots');
|
|
225
|
+
}
|
|
226
|
+
const name = fields['name']
|
|
227
|
+
? stringField(fields, 'name')
|
|
228
|
+
: routeError
|
|
229
|
+
? 'Error'
|
|
230
|
+
: Constructor!.name;
|
|
231
|
+
const message = fields['message'] ? stringField(fields, 'message') : '';
|
|
232
|
+
const stack = production ? undefined : fields['stack']?.value;
|
|
233
|
+
if (stack !== undefined && typeof stack !== 'string')
|
|
234
|
+
throw invalid('stack must be a data string');
|
|
235
|
+
const cause = fields['cause']
|
|
236
|
+
? copyPublic(fields['cause'].value)
|
|
237
|
+
: undefined;
|
|
238
|
+
const projectedData = routeError
|
|
239
|
+
? copyPublic(fields['data']?.value)
|
|
240
|
+
: undefined;
|
|
241
|
+
const previous = copies.get(value);
|
|
242
|
+
if (previous) return previous;
|
|
243
|
+
if (routeError) {
|
|
244
|
+
const routeId = stringField(fields, 'routeId');
|
|
245
|
+
const status: unknown = fields['status']?.value;
|
|
246
|
+
if (
|
|
247
|
+
!routeId ||
|
|
248
|
+
!Number.isInteger(status) ||
|
|
249
|
+
(status as number) < 400 ||
|
|
250
|
+
(status as number) > 599
|
|
251
|
+
) {
|
|
252
|
+
throw invalid('a route error has invalid identity or status');
|
|
253
|
+
}
|
|
254
|
+
const result = {
|
|
255
|
+
kind: ROUTE_ERROR_KIND,
|
|
256
|
+
routeId,
|
|
257
|
+
status,
|
|
258
|
+
name,
|
|
259
|
+
message,
|
|
260
|
+
data: projectedData,
|
|
261
|
+
...(stack === undefined ? {} : { stack }),
|
|
262
|
+
...(fields['cause'] ? { cause } : {}),
|
|
263
|
+
};
|
|
264
|
+
copies.set(value, result);
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
267
|
+
const publicError = production
|
|
268
|
+
? publicDataError(value, true)
|
|
269
|
+
: { name, message };
|
|
270
|
+
const result = {
|
|
271
|
+
kind: NATIVE_ERROR_KIND,
|
|
272
|
+
type: production ? 'Error' : Constructor!.name,
|
|
273
|
+
name: publicError.name,
|
|
274
|
+
message: publicError.message,
|
|
275
|
+
...(stack === undefined ? {} : { stack }),
|
|
276
|
+
...(!production && fields['cause'] ? { cause } : {}),
|
|
277
|
+
};
|
|
278
|
+
copies.set(value, result);
|
|
279
|
+
return result;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function copyPromise(value: Promise<unknown>): Promise<unknown> {
|
|
283
|
+
policy.assertRoot(value);
|
|
284
|
+
if (Reflect.ownKeys(value).length > 0) {
|
|
285
|
+
// Promise.prototype.then performs SpeciesConstructor. An own constructor
|
|
286
|
+
// (even a data descriptor) can execute a nested @@species getter.
|
|
287
|
+
throw invalid(
|
|
288
|
+
'deferred values require native Promises without own fields',
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
const previous = copies.get(value);
|
|
292
|
+
if (previous) return previous as Promise<unknown>;
|
|
293
|
+
const checked = Promise.prototype.then.call(
|
|
294
|
+
value,
|
|
295
|
+
(settled: unknown) => {
|
|
296
|
+
try {
|
|
297
|
+
if (released || session.signal.aborted)
|
|
298
|
+
throw invalid('the request serialization owner was released');
|
|
299
|
+
policy.assertRoot(value);
|
|
300
|
+
return copyPublic(settled);
|
|
301
|
+
} catch (error) {
|
|
302
|
+
return fail(error);
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
(reason: unknown) => {
|
|
306
|
+
if (released || session.signal.aborted)
|
|
307
|
+
throw invalid('the request serialization owner was released');
|
|
308
|
+
let projected: unknown;
|
|
309
|
+
try {
|
|
310
|
+
policy.assertRoot(value);
|
|
311
|
+
projected = copyError(reason);
|
|
312
|
+
} catch (error) {
|
|
313
|
+
return fail(error);
|
|
314
|
+
}
|
|
315
|
+
throw projected;
|
|
316
|
+
},
|
|
317
|
+
) as Promise<unknown>;
|
|
318
|
+
copies.set(value, checked);
|
|
319
|
+
// An already settled source can reject before native Seroval subscribes.
|
|
320
|
+
void Promise.prototype.then.call(checked, undefined, () => {});
|
|
321
|
+
return checked;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function copyLoader(value: unknown): unknown {
|
|
325
|
+
if (nativePromise(value)) return copyPromise(value);
|
|
326
|
+
if (!record(value)) return copyPublic(value);
|
|
327
|
+
policy.assertRoot(value);
|
|
328
|
+
const fields = descriptors(value);
|
|
329
|
+
if (!Object.values(fields).some(field => nativePromise(field.value)))
|
|
330
|
+
return copyPublic(value);
|
|
331
|
+
const previous = copies.get(value);
|
|
332
|
+
if (previous) return previous;
|
|
333
|
+
const result = Object.create(Object.getPrototypeOf(value));
|
|
334
|
+
copies.set(value, result);
|
|
335
|
+
for (const [key, field] of Object.entries(fields)) {
|
|
336
|
+
Object.defineProperty(result, key, {
|
|
337
|
+
...field,
|
|
338
|
+
value: nativePromise(field.value)
|
|
339
|
+
? copyPromise(field.value)
|
|
340
|
+
: copyPublic(field.value),
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
if (!Object.isExtensible(value)) Object.preventExtensions(result);
|
|
344
|
+
return result;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function projectRoot(value: unknown): object {
|
|
348
|
+
assertActive();
|
|
349
|
+
assertMatches();
|
|
350
|
+
if (!record(value) || Object.getPrototypeOf(value) !== Object.prototype) {
|
|
351
|
+
throw invalid(
|
|
352
|
+
'the first native serializer value must be its fresh outer record',
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
const fields = descriptors(value, NATIVE_ROOT_FIELDS);
|
|
356
|
+
const matches: unknown = fields['matches']?.value;
|
|
357
|
+
if (!fields['manifest'] || !Array.isArray(matches))
|
|
358
|
+
throw invalid('the native outer record shape changed');
|
|
359
|
+
const arrayFields = descriptors(matches);
|
|
360
|
+
const projectedMatches: Record<string, unknown>[] = [];
|
|
361
|
+
for (let index = 0; index < matches.length; index++) {
|
|
362
|
+
const match: unknown = arrayFields[String(index)]?.value;
|
|
363
|
+
if (!record(match)) throw invalid('native matches must be dense records');
|
|
364
|
+
const matchFields = descriptors(match, MATCH_FIELDS);
|
|
365
|
+
const projected: Record<string, unknown> = {};
|
|
366
|
+
const id = stringField(matchFields, 'i');
|
|
367
|
+
const status = stringField(matchFields, 's');
|
|
368
|
+
const updatedAt: unknown = matchFields['u']?.value;
|
|
369
|
+
if (!id || typeof updatedAt !== 'number' || !Number.isFinite(updatedAt))
|
|
370
|
+
throw invalid('native match metadata changed');
|
|
371
|
+
if (
|
|
372
|
+
!['pending', 'success', 'error', 'notFound', 'redirected'].includes(
|
|
373
|
+
status,
|
|
374
|
+
)
|
|
375
|
+
)
|
|
376
|
+
throw invalid('native match status changed');
|
|
377
|
+
projected['i'] = id;
|
|
378
|
+
projected['u'] = updatedAt;
|
|
379
|
+
projected['s'] = status;
|
|
380
|
+
for (const [key, field] of Object.entries(matchFields)) {
|
|
381
|
+
if (key === 'i' || key === 'u' || key === 's') continue;
|
|
382
|
+
projected[key] =
|
|
383
|
+
key === 'l'
|
|
384
|
+
? copyLoader(field.value)
|
|
385
|
+
: key === 'e'
|
|
386
|
+
? copyError(field.value)
|
|
387
|
+
: copyPublic(field.value);
|
|
388
|
+
}
|
|
389
|
+
projectedMatches.push(projected);
|
|
390
|
+
}
|
|
391
|
+
const result: Record<string, unknown> = {
|
|
392
|
+
manifest: copyPublic(fields['manifest'].value),
|
|
393
|
+
matches: projectedMatches,
|
|
394
|
+
};
|
|
395
|
+
if (fields['lastMatchId'])
|
|
396
|
+
result['lastMatchId'] = stringField(fields, 'lastMatchId');
|
|
397
|
+
if (fields['dehydratedData'])
|
|
398
|
+
result['dehydratedData'] = copyPublic(fields['dehydratedData'].value);
|
|
399
|
+
projectedRoots.add(result);
|
|
400
|
+
return result;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const adapter = Object.freeze(
|
|
404
|
+
createSerializationAdapter<any, any>({
|
|
405
|
+
key: ADAPTER_KEY,
|
|
406
|
+
test(value): value is any {
|
|
407
|
+
if (
|
|
408
|
+
value === null ||
|
|
409
|
+
typeof value !== 'object' ||
|
|
410
|
+
projectedRoots.has(value)
|
|
411
|
+
)
|
|
412
|
+
return false;
|
|
413
|
+
if (nativeRoot) return false;
|
|
414
|
+
nativeRoot = value;
|
|
415
|
+
return true;
|
|
416
|
+
},
|
|
417
|
+
toSerializable(value) {
|
|
418
|
+
try {
|
|
419
|
+
return projectRoot(value);
|
|
420
|
+
} catch (error) {
|
|
421
|
+
return fail(error);
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
fromSerializable: octaneRouterSnapshotAdapter.fromSerializable,
|
|
425
|
+
}),
|
|
426
|
+
);
|
|
427
|
+
const adapters = Object.freeze([adapter]);
|
|
428
|
+
const wrappedDehydrate = async () => {
|
|
429
|
+
try {
|
|
430
|
+
assertMatches();
|
|
431
|
+
// An authored hook that ignores cancellation must not hold the router,
|
|
432
|
+
// session and data policy this continuation captures.
|
|
433
|
+
const result =
|
|
434
|
+
typeof consumerDehydrate === 'function'
|
|
435
|
+
? await untilAborted(
|
|
436
|
+
Reflect.apply(consumerDehydrate, router.options, []),
|
|
437
|
+
session.signal,
|
|
438
|
+
)
|
|
439
|
+
: undefined;
|
|
440
|
+
assertMatches();
|
|
441
|
+
return result;
|
|
442
|
+
} catch (error) {
|
|
443
|
+
return fail(error);
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
const nativeOptions = {
|
|
447
|
+
...router.options,
|
|
448
|
+
serializationAdapters: adapters,
|
|
449
|
+
dehydrate: wrappedDehydrate,
|
|
450
|
+
};
|
|
451
|
+
router.update(nativeOptions);
|
|
452
|
+
session.registerCleanup(() => {
|
|
453
|
+
released = true;
|
|
454
|
+
cleanupOctaneRouterSSR(router);
|
|
455
|
+
});
|
|
456
|
+
function assertMatches() {
|
|
457
|
+
try {
|
|
458
|
+
assertActive();
|
|
459
|
+
const currentAdapters = Object.getOwnPropertyDescriptor(
|
|
460
|
+
router.options,
|
|
461
|
+
'serializationAdapters',
|
|
462
|
+
);
|
|
463
|
+
const currentDehydrate = Object.getOwnPropertyDescriptor(
|
|
464
|
+
router.options,
|
|
465
|
+
'dehydrate',
|
|
466
|
+
);
|
|
467
|
+
if (
|
|
468
|
+
currentAdapters?.get ||
|
|
469
|
+
currentAdapters?.set ||
|
|
470
|
+
currentAdapters?.value !== adapters ||
|
|
471
|
+
currentDehydrate?.get ||
|
|
472
|
+
currentDehydrate?.set ||
|
|
473
|
+
currentDehydrate?.value !== wrappedDehydrate
|
|
474
|
+
) {
|
|
475
|
+
throw invalid('the request serializer owner was replaced');
|
|
476
|
+
}
|
|
477
|
+
for (const match of router.stores.matches.get()) {
|
|
478
|
+
const prototype = Object.getPrototypeOf(match);
|
|
479
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
480
|
+
throw invalid('native matches require owned plain records');
|
|
481
|
+
}
|
|
482
|
+
for (const key of [
|
|
483
|
+
'id',
|
|
484
|
+
'routeId',
|
|
485
|
+
'updatedAt',
|
|
486
|
+
'status',
|
|
487
|
+
'__beforeLoadContext',
|
|
488
|
+
'loaderData',
|
|
489
|
+
'error',
|
|
490
|
+
'ssr',
|
|
491
|
+
'globalNotFound',
|
|
492
|
+
'styles',
|
|
493
|
+
'headScripts',
|
|
494
|
+
]) {
|
|
495
|
+
const field = Object.getOwnPropertyDescriptor(match, key);
|
|
496
|
+
if (field?.get || field?.set)
|
|
497
|
+
throw invalid(
|
|
498
|
+
'native match fields must be owned values before dehydrate',
|
|
499
|
+
);
|
|
500
|
+
if (
|
|
501
|
+
!field &&
|
|
502
|
+
prototype &&
|
|
503
|
+
Object.getOwnPropertyDescriptor(prototype, key)
|
|
504
|
+
) {
|
|
505
|
+
throw invalid('native match fields cannot be inherited');
|
|
506
|
+
}
|
|
507
|
+
if (
|
|
508
|
+
(key === 'id' || key === 'routeId') &&
|
|
509
|
+
(typeof field?.value !== 'string' || !field.value)
|
|
510
|
+
) {
|
|
511
|
+
throw invalid(
|
|
512
|
+
'native match identity must be an owned nonempty string',
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
if (
|
|
516
|
+
key === 'updatedAt' &&
|
|
517
|
+
(typeof field?.value !== 'number' || !Number.isFinite(field.value))
|
|
518
|
+
) {
|
|
519
|
+
throw invalid('native updatedAt must be an owned finite number');
|
|
520
|
+
}
|
|
521
|
+
if (
|
|
522
|
+
key === 'status' &&
|
|
523
|
+
(typeof field?.value !== 'string' ||
|
|
524
|
+
![
|
|
525
|
+
'pending',
|
|
526
|
+
'success',
|
|
527
|
+
'error',
|
|
528
|
+
'notFound',
|
|
529
|
+
'redirected',
|
|
530
|
+
].includes(field.value))
|
|
531
|
+
) {
|
|
532
|
+
throw invalid('native status must be an owned supported string');
|
|
533
|
+
}
|
|
534
|
+
if (
|
|
535
|
+
(key === 'styles' || key === 'headScripts') &&
|
|
536
|
+
field?.value !== undefined
|
|
537
|
+
) {
|
|
538
|
+
const value: unknown = field.value;
|
|
539
|
+
if (
|
|
540
|
+
!Array.isArray(value) ||
|
|
541
|
+
Object.getPrototypeOf(value) !== Array.prototype
|
|
542
|
+
) {
|
|
543
|
+
throw invalid(
|
|
544
|
+
'native styles/headScripts are not admitted by the fragment host',
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
const fields = descriptors(value);
|
|
548
|
+
if (
|
|
549
|
+
fields['length']?.value !== 0 ||
|
|
550
|
+
Reflect.ownKeys(value).length !== 1
|
|
551
|
+
) {
|
|
552
|
+
throw invalid(
|
|
553
|
+
'native styles/headScripts are not admitted by the fragment host',
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
} catch (error) {
|
|
560
|
+
fail(error);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
return { assertActive, assertMatches };
|
|
564
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type AnyRouter, RouterProvider } from '@octanejs/tanstack-router';
|
|
2
|
+
import { createElement, type ServerRenderNode } from 'octane/server';
|
|
3
|
+
|
|
4
|
+
// Request handlers and serialization belong to the selected native router.
|
|
5
|
+
export * from '@octanejs/tanstack-router/ssr/server';
|
|
6
|
+
export type { OctaneRequestHandlerOptions } from './router-handler';
|
|
7
|
+
export { createOctaneRequestHandler } from './router-handler';
|
|
8
|
+
export type { OctaneRouterInjectionOptions } from './router-injection';
|
|
9
|
+
export { createOctaneRouterInjection } from './router-injection';
|
|
10
|
+
export type {
|
|
11
|
+
OctaneRouterSerializationGuard,
|
|
12
|
+
OctaneRouterSerializationOptions,
|
|
13
|
+
} from './router-server-snapshot';
|
|
14
|
+
export { prepareOctaneRouterSerialization } from './router-server-snapshot';
|
|
15
|
+
|
|
16
|
+
/** Render the native provider using the released Start server root contract. */
|
|
17
|
+
export function OctaneRouterServer({
|
|
18
|
+
router,
|
|
19
|
+
}: {
|
|
20
|
+
router: AnyRouter;
|
|
21
|
+
}): ServerRenderNode {
|
|
22
|
+
return createElement(RouterProvider, { router });
|
|
23
|
+
}
|