@bleedingdev/modern-js-renderer-octane 0.0.0-stage → 3.9.3-ultramodern.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/bin/octane-tsc.mjs +4 -0
- package/dist/cjs/bootstrap.js +68 -0
- package/dist/cjs/client.js +421 -0
- package/dist/cjs/entry-application.js +88 -0
- package/dist/cjs/entry-client.js +95 -0
- package/dist/cjs/entry-server.js +135 -0
- package/dist/cjs/federation-context.js +72 -0
- package/dist/cjs/federation.js +205 -0
- package/dist/cjs/i18n/I18nProvider.js +59 -0
- package/dist/cjs/i18n/LocalizedLink.js +80 -0
- package/dist/cjs/i18n/context.js +41 -0
- package/dist/cjs/i18n/index.js +83 -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/i18n-external-snapshot.js +645 -0
- package/dist/cjs/index.js +46 -0
- package/dist/cjs/manifest.js +167 -0
- package/dist/cjs/plugin/compiler.js +122 -0
- package/dist/cjs/plugin/create.js +266 -0
- package/dist/cjs/plugin/index.js +151 -0
- package/dist/cjs/plugin/profile.js +91 -0
- package/dist/cjs/plugin/source.js +56 -0
- package/dist/cjs/router-client.js +139 -0
- package/dist/cjs/router-external-snapshot.js +291 -0
- package/dist/cjs/router-handler.js +171 -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 +100 -0
- package/dist/cjs/router-snapshot.js +270 -0
- package/dist/cjs/router.js +218 -0
- package/dist/cjs/routes.js +190 -0
- package/dist/cjs/server.js +245 -0
- package/dist/cjs/typecheck.js +615 -0
- package/dist/cjs/worker-federation-context.js +63 -0
- package/dist/cjs/worker-federation-contexts.js +97 -0
- package/dist/cjs/worker-federation.js +97 -0
- package/dist/esm/bootstrap.mjs +24 -0
- package/dist/esm/client.mjs +377 -0
- package/dist/esm/entry-application.mjs +44 -0
- package/dist/esm/entry-client.mjs +57 -0
- package/dist/esm/entry-server.mjs +91 -0
- package/dist/esm/federation-context.mjs +25 -0
- package/dist/esm/federation.mjs +167 -0
- package/dist/esm/i18n/I18nProvider.mjs +21 -0
- package/dist/esm/i18n/LocalizedLink.mjs +42 -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/i18n-external-snapshot.mjs +576 -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-external-snapshot.mjs +244 -0
- package/dist/esm/router-handler.mjs +133 -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 +11 -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 +204 -0
- package/dist/esm/worker-federation-context.mjs +19 -0
- package/dist/esm/worker-federation-contexts.mjs +47 -0
- package/dist/esm/worker-federation.mjs +59 -0
- package/dist/esm-node/bootstrap.mjs +24 -0
- package/dist/esm-node/manifest.mjs +117 -0
- package/dist/esm-node/plugin/compiler.mjs +67 -0
- package/dist/esm-node/plugin/create.mjs +228 -0
- package/dist/esm-node/plugin/index.mjs +103 -0
- package/dist/esm-node/plugin/profile.mjs +53 -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 +40 -0
- package/dist/types/entry-application.d.ts +19 -0
- package/dist/types/entry-client.d.ts +10 -0
- package/dist/types/entry-server.d.ts +14 -0
- package/dist/types/federation-context.d.ts +28 -0
- package/dist/types/federation.d.ts +20 -0
- package/dist/types/i18n/I18nProvider.d.ts +8 -0
- package/dist/types/i18n/LocalizedLink.d.ts +14 -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/i18n-external-snapshot.d.ts +43 -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-external-snapshot.d.ts +10 -0
- package/dist/types/router-handler.d.ts +13 -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 +11 -0
- package/dist/types/router-snapshot.d.ts +14 -0
- package/dist/types/router.d.ts +30 -0
- package/dist/types/routes.d.ts +20 -0
- package/dist/types/server.d.ts +47 -0
- package/dist/types/typecheck.d.ts +31 -0
- package/dist/types/worker-federation-context.d.ts +34 -0
- package/dist/types/worker-federation-contexts.d.ts +25 -0
- package/dist/types/worker-federation.d.ts +20 -0
- package/package.json +278 -4
- package/src/bootstrap.ts +60 -0
- package/src/client.ts +568 -0
- package/src/entry-application.ts +76 -0
- package/src/entry-client.ts +90 -0
- package/src/entry-server.ts +153 -0
- package/src/federation-context.ts +53 -0
- package/src/federation.ts +307 -0
- package/src/i18n/I18nProvider.ts +47 -0
- package/src/i18n/LocalizedLink.ts +86 -0
- package/src/i18n/context.ts +12 -0
- package/src/i18n/index.ts +31 -0
- package/src/i18n/languageSync.ts +132 -0
- package/src/i18n/types.ts +36 -0
- package/src/i18n/useI18n.ts +119 -0
- package/src/i18n-external-snapshot.ts +803 -0
- package/src/index.ts +10 -0
- package/src/manifest.ts +253 -0
- package/src/plugin/compiler-manifest.cjs +204 -0
- package/src/plugin/compiler.ts +104 -0
- package/src/plugin/create.ts +273 -0
- package/src/plugin/index.ts +107 -0
- package/src/plugin/profile.ts +50 -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-external-snapshot.ts +363 -0
- package/src/router-handler.ts +185 -0
- package/src/router-server-cleanup.ts +11 -0
- package/src/router-server-snapshot.ts +564 -0
- package/src/router-server.ts +21 -0
- package/src/router-snapshot.ts +251 -0
- package/src/router.ts +192 -0
- package/src/routes.ts +274 -0
- package/src/server.ts +340 -0
- package/src/typecheck.ts +723 -0
- package/src/worker-federation-context.ts +51 -0
- package/src/worker-federation-contexts.ts +91 -0
- package/src/worker-federation.ts +120 -0
- package/README.md +0 -3
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import { createRequestDataPolicy, publicDataError } from "@modern-js/renderer-core/data";
|
|
2
|
+
import { untilAborted } from "@modern-js/renderer-core/session";
|
|
3
|
+
import { createSerializationAdapter } from "@octanejs/tanstack-router";
|
|
4
|
+
import { cleanupOctaneRouterSSR } from "./router-server-cleanup.mjs";
|
|
5
|
+
import { ADAPTER_KEY, MATCH_FIELDS, NATIVE_ERROR_KIND, NATIVE_ROOT_FIELDS, ROUTE_ERROR_KIND, descriptors, invalid, nativePromise, octaneRouterSnapshotAdapter, record, stringField } from "./router-snapshot.mjs";
|
|
6
|
+
import { RouteDataError } from "./routes.mjs";
|
|
7
|
+
const ERROR_FIELDS = new Set([
|
|
8
|
+
'name',
|
|
9
|
+
'message',
|
|
10
|
+
'stack',
|
|
11
|
+
'cause'
|
|
12
|
+
]);
|
|
13
|
+
const ROUTE_ERROR_FIELDS = new Set([
|
|
14
|
+
...ERROR_FIELDS,
|
|
15
|
+
'routeId',
|
|
16
|
+
'status',
|
|
17
|
+
'data'
|
|
18
|
+
]);
|
|
19
|
+
const errorConstructors = new Map([
|
|
20
|
+
[
|
|
21
|
+
Error.prototype,
|
|
22
|
+
Error
|
|
23
|
+
],
|
|
24
|
+
[
|
|
25
|
+
TypeError.prototype,
|
|
26
|
+
TypeError
|
|
27
|
+
],
|
|
28
|
+
[
|
|
29
|
+
RangeError.prototype,
|
|
30
|
+
RangeError
|
|
31
|
+
],
|
|
32
|
+
[
|
|
33
|
+
SyntaxError.prototype,
|
|
34
|
+
SyntaxError
|
|
35
|
+
],
|
|
36
|
+
[
|
|
37
|
+
ReferenceError.prototype,
|
|
38
|
+
ReferenceError
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
URIError.prototype,
|
|
42
|
+
URIError
|
|
43
|
+
],
|
|
44
|
+
[
|
|
45
|
+
EvalError.prototype,
|
|
46
|
+
EvalError
|
|
47
|
+
]
|
|
48
|
+
]);
|
|
49
|
+
const owners = new WeakMap();
|
|
50
|
+
function prepareOctaneRouterSerialization(router, session, options = {}) {
|
|
51
|
+
session.signal.throwIfAborted();
|
|
52
|
+
if (owners.has(router)) throw invalid('a router already has a request serialization owner');
|
|
53
|
+
const adapterProperty = Object.getOwnPropertyDescriptor(router.options, 'serializationAdapters');
|
|
54
|
+
const dehydrateProperty = Object.getOwnPropertyDescriptor(router.options, 'dehydrate');
|
|
55
|
+
if (adapterProperty?.get || adapterProperty?.set || dehydrateProperty?.get || dehydrateProperty?.set) throw invalid('native serializer options must be owned data properties');
|
|
56
|
+
const consumerAdapters = adapterProperty?.value ?? [];
|
|
57
|
+
if (!Array.isArray(consumerAdapters) || Object.getPrototypeOf(consumerAdapters) !== Array.prototype) throw invalid('native serializationAdapters must be an owned array');
|
|
58
|
+
const adapterFields = descriptors(consumerAdapters);
|
|
59
|
+
if (consumerAdapters.length > 1 || 1 === consumerAdapters.length && adapterFields['0']?.value !== octaneRouterSnapshotAdapter || Object.keys(adapterFields).length !== consumerAdapters.length + 1) throw invalid('custom serializationAdapters are not admitted in framework SSR; return supported public data');
|
|
60
|
+
const consumerDehydrate = dehydrateProperty?.value;
|
|
61
|
+
if (void 0 !== consumerDehydrate && 'function' != typeof consumerDehydrate) throw invalid('native dehydrate must be an owned function');
|
|
62
|
+
owners.set(router, session);
|
|
63
|
+
const production = options.production ?? 'production' === process.env['NODE_ENV'];
|
|
64
|
+
const policy = createRequestDataPolicy([
|
|
65
|
+
session,
|
|
66
|
+
session.request,
|
|
67
|
+
session.platform,
|
|
68
|
+
session.platform.bindings,
|
|
69
|
+
...options.forbiddenValues ?? []
|
|
70
|
+
]);
|
|
71
|
+
const copies = new WeakMap();
|
|
72
|
+
const projectedRoots = new WeakSet();
|
|
73
|
+
let nativeRoot;
|
|
74
|
+
let released = false;
|
|
75
|
+
let failure;
|
|
76
|
+
const fail = (error)=>{
|
|
77
|
+
failure ??= error;
|
|
78
|
+
session.fail(failure);
|
|
79
|
+
cleanupOctaneRouterSSR(router);
|
|
80
|
+
throw failure;
|
|
81
|
+
};
|
|
82
|
+
const assertActive = ()=>{
|
|
83
|
+
if (void 0 !== failure) throw failure;
|
|
84
|
+
session.signal.throwIfAborted();
|
|
85
|
+
};
|
|
86
|
+
function copyPublic(value) {
|
|
87
|
+
policy.assertValue(value);
|
|
88
|
+
return copyValue(value);
|
|
89
|
+
}
|
|
90
|
+
function copyValue(value) {
|
|
91
|
+
if (null === value || 'object' != typeof value) return value;
|
|
92
|
+
const fields = descriptors(value);
|
|
93
|
+
const previous = copies.get(value);
|
|
94
|
+
if (previous) return previous;
|
|
95
|
+
const prototype = Object.getPrototypeOf(value);
|
|
96
|
+
let result;
|
|
97
|
+
if (prototype === Date.prototype) result = new Date(Date.prototype.valueOf.call(value));
|
|
98
|
+
else if (prototype === RegExp.prototype) {
|
|
99
|
+
const source = Object.getOwnPropertyDescriptor(RegExp.prototype, 'source').get.call(value);
|
|
100
|
+
const flags = [
|
|
101
|
+
'hasIndices',
|
|
102
|
+
'global',
|
|
103
|
+
'ignoreCase',
|
|
104
|
+
'multiline',
|
|
105
|
+
'dotAll',
|
|
106
|
+
'unicode',
|
|
107
|
+
'unicodeSets',
|
|
108
|
+
'sticky'
|
|
109
|
+
];
|
|
110
|
+
const letters = [
|
|
111
|
+
'd',
|
|
112
|
+
'g',
|
|
113
|
+
'i',
|
|
114
|
+
'm',
|
|
115
|
+
's',
|
|
116
|
+
'u',
|
|
117
|
+
'v',
|
|
118
|
+
'y'
|
|
119
|
+
];
|
|
120
|
+
const enabled = flags.map((flag, index)=>Object.getOwnPropertyDescriptor(RegExp.prototype, flag)?.get?.call(value) ? letters[index] : '').join('');
|
|
121
|
+
result = new RegExp(source, enabled);
|
|
122
|
+
} else result = prototype === Map.prototype ? new Map() : prototype === Set.prototype ? new Set() : Array.isArray(value) ? [] : Object.create(prototype);
|
|
123
|
+
copies.set(value, result);
|
|
124
|
+
if (prototype === Map.prototype) for (const [key, item] of Map.prototype.entries.call(value))Map.prototype.set.call(result, copyValue(key), copyValue(item));
|
|
125
|
+
else if (prototype === Set.prototype) for (const item of Set.prototype.values.call(value))Set.prototype.add.call(result, copyValue(item));
|
|
126
|
+
for (const [key, field] of Object.entries(fields))if (!Array.isArray(value) || 'length' !== key) Object.defineProperty(result, key, {
|
|
127
|
+
...field,
|
|
128
|
+
value: copyValue(field.value)
|
|
129
|
+
});
|
|
130
|
+
if (Array.isArray(value)) Object.defineProperty(result, 'length', fields['length']);
|
|
131
|
+
if (!Object.isExtensible(value)) Object.preventExtensions(result);
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
function copyError(value) {
|
|
135
|
+
if (null === value || 'object' != typeof value) return copyPublic(value);
|
|
136
|
+
policy.assertRoot(value);
|
|
137
|
+
const prototype = Object.getPrototypeOf(value);
|
|
138
|
+
const routeError = prototype === RouteDataError.prototype;
|
|
139
|
+
const Constructor = errorConstructors.get(prototype);
|
|
140
|
+
if (!routeError && !Constructor) return copyPublic(value);
|
|
141
|
+
const fields = descriptors(value, routeError ? ROUTE_ERROR_FIELDS : ERROR_FIELDS, true);
|
|
142
|
+
if ('[object Error]' !== Object.prototype.toString.call(value)) throw invalid('an error requires genuine native Error slots');
|
|
143
|
+
const name = fields['name'] ? stringField(fields, 'name') : routeError ? 'Error' : Constructor.name;
|
|
144
|
+
const message = fields['message'] ? stringField(fields, 'message') : '';
|
|
145
|
+
const stack = production ? void 0 : fields['stack']?.value;
|
|
146
|
+
if (void 0 !== stack && 'string' != typeof stack) throw invalid('stack must be a data string');
|
|
147
|
+
const cause = fields['cause'] ? copyPublic(fields['cause'].value) : void 0;
|
|
148
|
+
const projectedData = routeError ? copyPublic(fields['data']?.value) : void 0;
|
|
149
|
+
const previous = copies.get(value);
|
|
150
|
+
if (previous) return previous;
|
|
151
|
+
if (routeError) {
|
|
152
|
+
const routeId = stringField(fields, 'routeId');
|
|
153
|
+
const status = fields['status']?.value;
|
|
154
|
+
if (!routeId || !Number.isInteger(status) || status < 400 || status > 599) throw invalid('a route error has invalid identity or status');
|
|
155
|
+
const result = {
|
|
156
|
+
kind: ROUTE_ERROR_KIND,
|
|
157
|
+
routeId,
|
|
158
|
+
status,
|
|
159
|
+
name,
|
|
160
|
+
message,
|
|
161
|
+
data: projectedData,
|
|
162
|
+
...void 0 === stack ? {} : {
|
|
163
|
+
stack
|
|
164
|
+
},
|
|
165
|
+
...fields['cause'] ? {
|
|
166
|
+
cause
|
|
167
|
+
} : {}
|
|
168
|
+
};
|
|
169
|
+
copies.set(value, result);
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
const publicError = production ? publicDataError(value, true) : {
|
|
173
|
+
name,
|
|
174
|
+
message
|
|
175
|
+
};
|
|
176
|
+
const result = {
|
|
177
|
+
kind: NATIVE_ERROR_KIND,
|
|
178
|
+
type: production ? 'Error' : Constructor.name,
|
|
179
|
+
name: publicError.name,
|
|
180
|
+
message: publicError.message,
|
|
181
|
+
...void 0 === stack ? {} : {
|
|
182
|
+
stack
|
|
183
|
+
},
|
|
184
|
+
...!production && fields['cause'] ? {
|
|
185
|
+
cause
|
|
186
|
+
} : {}
|
|
187
|
+
};
|
|
188
|
+
copies.set(value, result);
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
function copyPromise(value) {
|
|
192
|
+
policy.assertRoot(value);
|
|
193
|
+
if (Reflect.ownKeys(value).length > 0) throw invalid('deferred values require native Promises without own fields');
|
|
194
|
+
const previous = copies.get(value);
|
|
195
|
+
if (previous) return previous;
|
|
196
|
+
const checked = Promise.prototype.then.call(value, (settled)=>{
|
|
197
|
+
try {
|
|
198
|
+
if (released || session.signal.aborted) throw invalid('the request serialization owner was released');
|
|
199
|
+
policy.assertRoot(value);
|
|
200
|
+
return copyPublic(settled);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
return fail(error);
|
|
203
|
+
}
|
|
204
|
+
}, (reason)=>{
|
|
205
|
+
if (released || session.signal.aborted) throw invalid('the request serialization owner was released');
|
|
206
|
+
let projected;
|
|
207
|
+
try {
|
|
208
|
+
policy.assertRoot(value);
|
|
209
|
+
projected = copyError(reason);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
return fail(error);
|
|
212
|
+
}
|
|
213
|
+
throw projected;
|
|
214
|
+
});
|
|
215
|
+
copies.set(value, checked);
|
|
216
|
+
Promise.prototype.then.call(checked, void 0, ()=>{});
|
|
217
|
+
return checked;
|
|
218
|
+
}
|
|
219
|
+
function copyLoader(value) {
|
|
220
|
+
if (nativePromise(value)) return copyPromise(value);
|
|
221
|
+
if (!record(value)) return copyPublic(value);
|
|
222
|
+
policy.assertRoot(value);
|
|
223
|
+
const fields = descriptors(value);
|
|
224
|
+
if (!Object.values(fields).some((field)=>nativePromise(field.value))) return copyPublic(value);
|
|
225
|
+
const previous = copies.get(value);
|
|
226
|
+
if (previous) return previous;
|
|
227
|
+
const result = Object.create(Object.getPrototypeOf(value));
|
|
228
|
+
copies.set(value, result);
|
|
229
|
+
for (const [key, field] of Object.entries(fields))Object.defineProperty(result, key, {
|
|
230
|
+
...field,
|
|
231
|
+
value: nativePromise(field.value) ? copyPromise(field.value) : copyPublic(field.value)
|
|
232
|
+
});
|
|
233
|
+
if (!Object.isExtensible(value)) Object.preventExtensions(result);
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
function projectRoot(value) {
|
|
237
|
+
assertActive();
|
|
238
|
+
assertMatches();
|
|
239
|
+
if (!record(value) || Object.getPrototypeOf(value) !== Object.prototype) throw invalid('the first native serializer value must be its fresh outer record');
|
|
240
|
+
const fields = descriptors(value, NATIVE_ROOT_FIELDS);
|
|
241
|
+
const matches = fields['matches']?.value;
|
|
242
|
+
if (!fields['manifest'] || !Array.isArray(matches)) throw invalid('the native outer record shape changed');
|
|
243
|
+
const arrayFields = descriptors(matches);
|
|
244
|
+
const projectedMatches = [];
|
|
245
|
+
for(let index = 0; index < matches.length; index++){
|
|
246
|
+
const match = arrayFields[String(index)]?.value;
|
|
247
|
+
if (!record(match)) throw invalid('native matches must be dense records');
|
|
248
|
+
const matchFields = descriptors(match, MATCH_FIELDS);
|
|
249
|
+
const projected = {};
|
|
250
|
+
const id = stringField(matchFields, 'i');
|
|
251
|
+
const status = stringField(matchFields, 's');
|
|
252
|
+
const updatedAt = matchFields['u']?.value;
|
|
253
|
+
if (!id || 'number' != typeof updatedAt || !Number.isFinite(updatedAt)) throw invalid('native match metadata changed');
|
|
254
|
+
if (![
|
|
255
|
+
'pending',
|
|
256
|
+
'success',
|
|
257
|
+
'error',
|
|
258
|
+
'notFound',
|
|
259
|
+
'redirected'
|
|
260
|
+
].includes(status)) throw invalid('native match status changed');
|
|
261
|
+
projected['i'] = id;
|
|
262
|
+
projected['u'] = updatedAt;
|
|
263
|
+
projected['s'] = status;
|
|
264
|
+
for (const [key, field] of Object.entries(matchFields))if ('i' !== key && 'u' !== key && 's' !== key) projected[key] = 'l' === key ? copyLoader(field.value) : 'e' === key ? copyError(field.value) : copyPublic(field.value);
|
|
265
|
+
projectedMatches.push(projected);
|
|
266
|
+
}
|
|
267
|
+
const result = {
|
|
268
|
+
manifest: copyPublic(fields['manifest'].value),
|
|
269
|
+
matches: projectedMatches
|
|
270
|
+
};
|
|
271
|
+
if (fields['lastMatchId']) result['lastMatchId'] = stringField(fields, 'lastMatchId');
|
|
272
|
+
if (fields['dehydratedData']) result['dehydratedData'] = copyPublic(fields['dehydratedData'].value);
|
|
273
|
+
projectedRoots.add(result);
|
|
274
|
+
return result;
|
|
275
|
+
}
|
|
276
|
+
const adapter = Object.freeze(createSerializationAdapter({
|
|
277
|
+
key: ADAPTER_KEY,
|
|
278
|
+
test (value) {
|
|
279
|
+
if (null === value || 'object' != typeof value || projectedRoots.has(value)) return false;
|
|
280
|
+
if (nativeRoot) return false;
|
|
281
|
+
nativeRoot = value;
|
|
282
|
+
return true;
|
|
283
|
+
},
|
|
284
|
+
toSerializable (value) {
|
|
285
|
+
try {
|
|
286
|
+
return projectRoot(value);
|
|
287
|
+
} catch (error) {
|
|
288
|
+
return fail(error);
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
fromSerializable: octaneRouterSnapshotAdapter.fromSerializable
|
|
292
|
+
}));
|
|
293
|
+
const adapters = Object.freeze([
|
|
294
|
+
adapter
|
|
295
|
+
]);
|
|
296
|
+
const wrappedDehydrate = async ()=>{
|
|
297
|
+
try {
|
|
298
|
+
assertMatches();
|
|
299
|
+
const result = 'function' == typeof consumerDehydrate ? await untilAborted(Reflect.apply(consumerDehydrate, router.options, []), session.signal) : void 0;
|
|
300
|
+
assertMatches();
|
|
301
|
+
return result;
|
|
302
|
+
} catch (error) {
|
|
303
|
+
return fail(error);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
const nativeOptions = {
|
|
307
|
+
...router.options,
|
|
308
|
+
serializationAdapters: adapters,
|
|
309
|
+
dehydrate: wrappedDehydrate
|
|
310
|
+
};
|
|
311
|
+
router.update(nativeOptions);
|
|
312
|
+
session.registerCleanup(()=>{
|
|
313
|
+
released = true;
|
|
314
|
+
cleanupOctaneRouterSSR(router);
|
|
315
|
+
});
|
|
316
|
+
function assertMatches() {
|
|
317
|
+
try {
|
|
318
|
+
assertActive();
|
|
319
|
+
const currentAdapters = Object.getOwnPropertyDescriptor(router.options, 'serializationAdapters');
|
|
320
|
+
const currentDehydrate = Object.getOwnPropertyDescriptor(router.options, 'dehydrate');
|
|
321
|
+
if (currentAdapters?.get || currentAdapters?.set || currentAdapters?.value !== adapters || currentDehydrate?.get || currentDehydrate?.set || currentDehydrate?.value !== wrappedDehydrate) throw invalid('the request serializer owner was replaced');
|
|
322
|
+
for (const match of router.stores.matches.get()){
|
|
323
|
+
const prototype = Object.getPrototypeOf(match);
|
|
324
|
+
if (prototype !== Object.prototype && null !== prototype) throw invalid('native matches require owned plain records');
|
|
325
|
+
for (const key of [
|
|
326
|
+
'id',
|
|
327
|
+
'routeId',
|
|
328
|
+
'updatedAt',
|
|
329
|
+
'status',
|
|
330
|
+
'__beforeLoadContext',
|
|
331
|
+
'loaderData',
|
|
332
|
+
'error',
|
|
333
|
+
'ssr',
|
|
334
|
+
'globalNotFound',
|
|
335
|
+
'styles',
|
|
336
|
+
'headScripts'
|
|
337
|
+
]){
|
|
338
|
+
const field = Object.getOwnPropertyDescriptor(match, key);
|
|
339
|
+
if (field?.get || field?.set) throw invalid('native match fields must be owned values before dehydrate');
|
|
340
|
+
if (!field && prototype && Object.getOwnPropertyDescriptor(prototype, key)) throw invalid('native match fields cannot be inherited');
|
|
341
|
+
if (('id' === key || 'routeId' === key) && ('string' != typeof field?.value || !field.value)) throw invalid('native match identity must be an owned nonempty string');
|
|
342
|
+
if ('updatedAt' === key && ('number' != typeof field?.value || !Number.isFinite(field.value))) throw invalid('native updatedAt must be an owned finite number');
|
|
343
|
+
if ('status' === key && ('string' != typeof field?.value || ![
|
|
344
|
+
'pending',
|
|
345
|
+
'success',
|
|
346
|
+
'error',
|
|
347
|
+
'notFound',
|
|
348
|
+
'redirected'
|
|
349
|
+
].includes(field.value))) throw invalid('native status must be an owned supported string');
|
|
350
|
+
if (('styles' === key || 'headScripts' === key) && field?.value !== void 0) {
|
|
351
|
+
const value = field.value;
|
|
352
|
+
if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) throw invalid('native styles/headScripts are not admitted by the fragment host');
|
|
353
|
+
const fields = descriptors(value);
|
|
354
|
+
if (fields['length']?.value !== 0 || 1 !== Reflect.ownKeys(value).length) throw invalid('native styles/headScripts are not admitted by the fragment host');
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
} catch (error) {
|
|
359
|
+
fail(error);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
assertActive,
|
|
364
|
+
assertMatches
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
export { prepareOctaneRouterSerialization };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { RouterProvider } from "@octanejs/tanstack-router";
|
|
2
|
+
import { createElement } from "octane/server";
|
|
3
|
+
export * from "@octanejs/tanstack-router/ssr/server";
|
|
4
|
+
function OctaneRouterServer({ router }) {
|
|
5
|
+
return createElement(RouterProvider, {
|
|
6
|
+
router
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
export { createOctaneRequestHandler } from "./router-handler.mjs";
|
|
10
|
+
export { prepareOctaneRouterSerialization } from "./router-server-snapshot.mjs";
|
|
11
|
+
export { OctaneRouterServer };
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { DataProtocolError, assertPublicData } from "@modern-js/renderer-core/data";
|
|
2
|
+
import { createSerializationAdapter } from "@octanejs/tanstack-router";
|
|
3
|
+
import { RouteDataError } from "./routes.mjs";
|
|
4
|
+
const ADAPTER_KEY = 'ultramodern.octane-public-snapshot.v1';
|
|
5
|
+
const ROUTE_ERROR_KIND = 'ultramodern-route-error';
|
|
6
|
+
const NATIVE_ERROR_KIND = 'ultramodern-native-error';
|
|
7
|
+
const nativeErrorTypes = Object.freeze({
|
|
8
|
+
Error,
|
|
9
|
+
TypeError,
|
|
10
|
+
RangeError,
|
|
11
|
+
SyntaxError,
|
|
12
|
+
ReferenceError,
|
|
13
|
+
URIError,
|
|
14
|
+
EvalError
|
|
15
|
+
});
|
|
16
|
+
const NATIVE_ROOT_FIELDS = new Set([
|
|
17
|
+
'manifest',
|
|
18
|
+
'matches',
|
|
19
|
+
'lastMatchId',
|
|
20
|
+
'dehydratedData'
|
|
21
|
+
]);
|
|
22
|
+
const MATCH_FIELDS = new Set([
|
|
23
|
+
'i',
|
|
24
|
+
'u',
|
|
25
|
+
's',
|
|
26
|
+
'b',
|
|
27
|
+
'l',
|
|
28
|
+
'e',
|
|
29
|
+
'ssr',
|
|
30
|
+
'g'
|
|
31
|
+
]);
|
|
32
|
+
function invalid(message) {
|
|
33
|
+
const error = new DataProtocolError(`Octane router snapshot: ${message}`);
|
|
34
|
+
delete error.stack;
|
|
35
|
+
return error;
|
|
36
|
+
}
|
|
37
|
+
function descriptors(value, allowed, allowLazyStack = false) {
|
|
38
|
+
const result = Object.create(null);
|
|
39
|
+
for (const key of Reflect.ownKeys(value)){
|
|
40
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
41
|
+
if ('string' != typeof key || allowed && !allowed.has(key)) throw invalid('an unsupported own field cannot be transferred');
|
|
42
|
+
if (descriptor.get || descriptor.set) {
|
|
43
|
+
if ('stack' === key && allowLazyStack) continue;
|
|
44
|
+
throw invalid('accessors cannot be transferred');
|
|
45
|
+
}
|
|
46
|
+
result[key] = descriptor;
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
function record(value) {
|
|
51
|
+
return null !== value && 'object' == typeof value && (Object.getPrototypeOf(value) === Object.prototype || null === Object.getPrototypeOf(value));
|
|
52
|
+
}
|
|
53
|
+
function stringField(fields, key) {
|
|
54
|
+
const value = fields[key]?.value;
|
|
55
|
+
if ('string' != typeof value) throw invalid(`${key} must be a string`);
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
function nativePromise(value) {
|
|
59
|
+
return null !== value && 'object' == typeof value && Object.getPrototypeOf(value) === Promise.prototype;
|
|
60
|
+
}
|
|
61
|
+
function clearStack(error, stack) {
|
|
62
|
+
delete error.stack;
|
|
63
|
+
if ('string' == typeof stack) Object.defineProperty(error, 'stack', {
|
|
64
|
+
value: stack,
|
|
65
|
+
writable: true,
|
|
66
|
+
configurable: true
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function decodeRouteError(error) {
|
|
70
|
+
if (!record(error)) return error;
|
|
71
|
+
const errorFields = descriptors(error);
|
|
72
|
+
const kind = errorFields['kind']?.value;
|
|
73
|
+
if (kind !== ROUTE_ERROR_KIND && kind !== NATIVE_ERROR_KIND) return error;
|
|
74
|
+
if (kind === NATIVE_ERROR_KIND) {
|
|
75
|
+
descriptors(error, new Set([
|
|
76
|
+
'kind',
|
|
77
|
+
'type',
|
|
78
|
+
'name',
|
|
79
|
+
'message',
|
|
80
|
+
'stack',
|
|
81
|
+
'cause'
|
|
82
|
+
]));
|
|
83
|
+
assertPublicData(error);
|
|
84
|
+
const type = stringField(errorFields, 'type');
|
|
85
|
+
const Constructor = Object.getOwnPropertyDescriptor(nativeErrorTypes, type)?.value;
|
|
86
|
+
if (!Constructor) throw invalid('the native Error type is unsupported');
|
|
87
|
+
const restored = new Constructor(stringField(errorFields, 'message'));
|
|
88
|
+
clearStack(restored, errorFields['stack']?.value);
|
|
89
|
+
Object.defineProperty(restored, 'name', {
|
|
90
|
+
value: stringField(errorFields, 'name'),
|
|
91
|
+
writable: true,
|
|
92
|
+
configurable: true
|
|
93
|
+
});
|
|
94
|
+
if (errorFields['cause']) Object.defineProperty(restored, 'cause', {
|
|
95
|
+
value: errorFields['cause'].value,
|
|
96
|
+
writable: true,
|
|
97
|
+
configurable: true
|
|
98
|
+
});
|
|
99
|
+
return restored;
|
|
100
|
+
}
|
|
101
|
+
const expected = new Set([
|
|
102
|
+
'kind',
|
|
103
|
+
'routeId',
|
|
104
|
+
'status',
|
|
105
|
+
'data',
|
|
106
|
+
'name',
|
|
107
|
+
'message',
|
|
108
|
+
'stack',
|
|
109
|
+
'cause'
|
|
110
|
+
]);
|
|
111
|
+
descriptors(error, expected);
|
|
112
|
+
const routeId = stringField(errorFields, 'routeId');
|
|
113
|
+
const name = stringField(errorFields, 'name');
|
|
114
|
+
const message = stringField(errorFields, 'message');
|
|
115
|
+
const status = errorFields['status']?.value;
|
|
116
|
+
if (!routeId || !Number.isInteger(status) || status < 400 || status > 599) throw invalid('a route error has invalid identity or status');
|
|
117
|
+
assertPublicData(error);
|
|
118
|
+
const restored = new RouteDataError(routeId, {
|
|
119
|
+
kind: 'error',
|
|
120
|
+
status: status,
|
|
121
|
+
error: {
|
|
122
|
+
name,
|
|
123
|
+
message
|
|
124
|
+
},
|
|
125
|
+
data: errorFields['data']?.value,
|
|
126
|
+
thrown: true
|
|
127
|
+
});
|
|
128
|
+
clearStack(restored, errorFields['stack']?.value);
|
|
129
|
+
if (errorFields['cause']) Object.defineProperty(restored, 'cause', {
|
|
130
|
+
value: errorFields['cause'].value,
|
|
131
|
+
writable: true,
|
|
132
|
+
configurable: true
|
|
133
|
+
});
|
|
134
|
+
return restored;
|
|
135
|
+
}
|
|
136
|
+
function decodeNativeSnapshot(value) {
|
|
137
|
+
if (!record(value)) throw invalid('the native root must be a record');
|
|
138
|
+
const fields = descriptors(value, NATIVE_ROOT_FIELDS);
|
|
139
|
+
const matches = fields['matches']?.value;
|
|
140
|
+
if (!Array.isArray(matches)) throw invalid('native matches must be an array');
|
|
141
|
+
const matchFields = descriptors(matches);
|
|
142
|
+
const promises = new WeakMap();
|
|
143
|
+
const loaders = new WeakMap();
|
|
144
|
+
const errors = new WeakMap();
|
|
145
|
+
const decodeError = (error)=>{
|
|
146
|
+
if (null === error || 'object' != typeof error) return error;
|
|
147
|
+
if (errors.has(error)) return errors.get(error);
|
|
148
|
+
const restored = decodeRouteError(error);
|
|
149
|
+
errors.set(error, restored);
|
|
150
|
+
return restored;
|
|
151
|
+
};
|
|
152
|
+
const decodePromise = (promise)=>{
|
|
153
|
+
const previous = promises.get(promise);
|
|
154
|
+
if (previous) return previous;
|
|
155
|
+
const restored = Promise.prototype.then.call(promise, void 0, (error)=>{
|
|
156
|
+
throw decodeError(error);
|
|
157
|
+
});
|
|
158
|
+
promises.set(promise, restored);
|
|
159
|
+
Promise.prototype.then.call(restored, void 0, ()=>{});
|
|
160
|
+
return restored;
|
|
161
|
+
};
|
|
162
|
+
const decodeLoader = (loader)=>{
|
|
163
|
+
if (nativePromise(loader)) return decodePromise(loader);
|
|
164
|
+
if (!record(loader)) return loader;
|
|
165
|
+
const properties = descriptors(loader);
|
|
166
|
+
if (!Object.values(properties).some((field)=>nativePromise(field.value))) return loader;
|
|
167
|
+
const previous = loaders.get(loader);
|
|
168
|
+
if (previous) return previous;
|
|
169
|
+
const restored = Object.create(Object.getPrototypeOf(loader));
|
|
170
|
+
loaders.set(loader, restored);
|
|
171
|
+
for (const [key, field] of Object.entries(properties))Object.defineProperty(restored, key, {
|
|
172
|
+
...field,
|
|
173
|
+
value: nativePromise(field.value) ? decodePromise(field.value) : field.value
|
|
174
|
+
});
|
|
175
|
+
if (!Object.isExtensible(loader)) Object.preventExtensions(restored);
|
|
176
|
+
return restored;
|
|
177
|
+
};
|
|
178
|
+
const restoredMatches = [];
|
|
179
|
+
for(let index = 0; index < matches.length; index++){
|
|
180
|
+
const match = matchFields[String(index)]?.value;
|
|
181
|
+
if (!record(match)) throw invalid('a native match must be a record');
|
|
182
|
+
const properties = descriptors(match, MATCH_FIELDS);
|
|
183
|
+
const restored = {};
|
|
184
|
+
for (const [key, field] of Object.entries(properties))restored[key] = 'e' === key ? decodeError(field.value) : 'l' === key ? decodeLoader(field.value) : field.value;
|
|
185
|
+
restoredMatches.push(restored);
|
|
186
|
+
}
|
|
187
|
+
const restored = {};
|
|
188
|
+
for (const [key, field] of Object.entries(fields))restored[key] = 'matches' === key ? restoredMatches : field.value;
|
|
189
|
+
return restored;
|
|
190
|
+
}
|
|
191
|
+
const octaneRouterSnapshotAdapter = createSerializationAdapter({
|
|
192
|
+
key: ADAPTER_KEY,
|
|
193
|
+
test: (_value)=>false,
|
|
194
|
+
toSerializable () {
|
|
195
|
+
throw invalid('the client decoder cannot serialize a server snapshot');
|
|
196
|
+
},
|
|
197
|
+
fromSerializable: decodeNativeSnapshot
|
|
198
|
+
});
|
|
199
|
+
export { ADAPTER_KEY, MATCH_FIELDS, NATIVE_ERROR_KIND, NATIVE_ROOT_FIELDS, ROUTE_ERROR_KIND, clearStack, descriptors, invalid, nativePromise, octaneRouterSnapshotAdapter, record, stringField };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { createDataClient } from "@modern-js/renderer-core/data";
|
|
2
|
+
import { RouterProvider, createRouter, redirect, useMatch, useRouter } from "@octanejs/tanstack-router";
|
|
3
|
+
import { createSubSlot, useCallback, useLayoutEffect, useRef } from "octane";
|
|
4
|
+
import { assertOctaneIdentity } from "./bootstrap.mjs";
|
|
5
|
+
import { RouteDataError, createFileSystemRouteTree, matchApplicationRouteIds, matchApplicationRoutes, resolveRouteData, selectApplicationDataRoute } from "./routes.mjs";
|
|
6
|
+
export * from "@octanejs/tanstack-router";
|
|
7
|
+
function useApplicationIdentity() {
|
|
8
|
+
const identity = useRouter().options.context?.ultramodern?.rendererIdentity;
|
|
9
|
+
if (!identity || 'object' != typeof identity || Array.isArray(identity)) throw new Error('The Octane application router is missing its renderer identity');
|
|
10
|
+
assertOctaneIdentity(identity);
|
|
11
|
+
return Object.freeze({
|
|
12
|
+
...identity
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function useApplicationRouteId() {
|
|
16
|
+
const router = useRouter();
|
|
17
|
+
const routeId = useMatch({
|
|
18
|
+
strict: false,
|
|
19
|
+
select: (match)=>match.routeId
|
|
20
|
+
});
|
|
21
|
+
if ('string' != typeof routeId) throw new Error('The Octane application requires an active native route match');
|
|
22
|
+
const id = router.routesById[routeId]?.options.staticData?.ultramodernRouteId;
|
|
23
|
+
if ('string' != typeof id || 0 === id.length) throw new Error('The Octane application route is missing its filesystem identity');
|
|
24
|
+
return id;
|
|
25
|
+
}
|
|
26
|
+
function assertNavigableActionRedirect(status, method) {
|
|
27
|
+
if (307 === status || 308 === status || (301 === status || 302 === status) && 'POST' !== method) throw new Error(`A native action redirect must not preserve the method (HTTP ${status} after ${method}); redirect with 303 to navigate after the mutation`);
|
|
28
|
+
}
|
|
29
|
+
function createOctaneRouteAction(input) {
|
|
30
|
+
const data = createDataClient(input.routeId, input.identity, {
|
|
31
|
+
fetch: input.fetch
|
|
32
|
+
});
|
|
33
|
+
return async (_previous, formData)=>{
|
|
34
|
+
input.signal?.throwIfAborted();
|
|
35
|
+
const target = 'function' == typeof input.url ? input.url() : input.url ?? input.router.latestLocation.publicHref;
|
|
36
|
+
const origin = input.router.options.origin ?? globalThis.location?.origin;
|
|
37
|
+
const request = new Request(new URL(target, origin), {
|
|
38
|
+
method: input.method ?? 'POST',
|
|
39
|
+
body: formData,
|
|
40
|
+
signal: input.signal
|
|
41
|
+
});
|
|
42
|
+
const outcome = await data.action({
|
|
43
|
+
request
|
|
44
|
+
});
|
|
45
|
+
await outcome.completion;
|
|
46
|
+
input.signal?.throwIfAborted();
|
|
47
|
+
if ('redirect' === outcome.kind) {
|
|
48
|
+
assertNavigableActionRedirect(outcome.status, request.method);
|
|
49
|
+
const resolved = input.router.resolveRedirect(redirect({
|
|
50
|
+
href: new URL(outcome.location, request.url).href,
|
|
51
|
+
statusCode: outcome.status
|
|
52
|
+
}));
|
|
53
|
+
await input.router.navigate({
|
|
54
|
+
href: resolved.options.href
|
|
55
|
+
});
|
|
56
|
+
} else if (('error' === outcome.kind || 'not-found' === outcome.kind) && outcome.thrown) resolveRouteData(input.routeId, outcome);
|
|
57
|
+
else if ('success' === outcome.kind) await input.router.invalidate();
|
|
58
|
+
return outcome;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const actionSlot = createSubSlot({
|
|
62
|
+
slotlessPrefix: 'ultramodern-action:'
|
|
63
|
+
});
|
|
64
|
+
function useOctaneRouteAction(...args) {
|
|
65
|
+
const tail = args.at(-1);
|
|
66
|
+
const slot = 'symbol' == typeof tail ? tail : void 0;
|
|
67
|
+
const binding = 'object' == typeof args[0] && null !== args[0] ? args[0] : {};
|
|
68
|
+
const router = useRouter();
|
|
69
|
+
const identity = useApplicationIdentity();
|
|
70
|
+
const routeId = useApplicationRouteId();
|
|
71
|
+
const owner = useRef(null, actionSlot(slot, 'owner'));
|
|
72
|
+
useLayoutEffect(()=>{
|
|
73
|
+
const controller = new AbortController();
|
|
74
|
+
owner.current = controller;
|
|
75
|
+
return ()=>controller.abort(new DOMException('The route action owner unmounted', 'AbortError'));
|
|
76
|
+
}, [], actionSlot(slot, 'lifetime'));
|
|
77
|
+
const { url, method, fetch } = binding;
|
|
78
|
+
return useCallback((previous, formData)=>createOctaneRouteAction({
|
|
79
|
+
router,
|
|
80
|
+
routeId,
|
|
81
|
+
identity,
|
|
82
|
+
url,
|
|
83
|
+
method,
|
|
84
|
+
fetch,
|
|
85
|
+
signal: owner.current?.signal
|
|
86
|
+
})(previous, formData), [
|
|
87
|
+
router,
|
|
88
|
+
routeId,
|
|
89
|
+
url,
|
|
90
|
+
method,
|
|
91
|
+
fetch
|
|
92
|
+
], actionSlot(slot, 'action'));
|
|
93
|
+
}
|
|
94
|
+
export { RouteDataError, RouterProvider as ApplicationRouter, createFileSystemRouteTree, createOctaneRouteAction, createRouter as createApplicationRouter, matchApplicationRouteIds, matchApplicationRoutes, resolveRouteData, selectApplicationDataRoute, useApplicationIdentity, useApplicationRouteId, useOctaneRouteAction };
|