@bleedingdev/modern-js-renderer-core 0.0.0-bootstrap → 3.9.3-ultramodern.1
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/dist/cjs/abort.js +65 -0
- package/dist/cjs/adapter.js +62 -0
- package/dist/cjs/data/body.js +78 -0
- package/dist/cjs/data/client.js +409 -0
- package/dist/cjs/data/codec.js +168 -0
- package/dist/cjs/data/index.js +125 -0
- package/dist/cjs/data/private.js +77 -0
- package/dist/cjs/data/routes.js +149 -0
- package/dist/cjs/data/server.js +791 -0
- package/dist/cjs/data/static.js +110 -0
- package/dist/cjs/data/tree.js +231 -0
- package/dist/cjs/data/types.js +56 -0
- package/dist/cjs/document.js +184 -0
- package/dist/cjs/entry-client.js +142 -0
- package/dist/cjs/entry-server.js +253 -0
- package/dist/cjs/federation.js +80 -0
- package/dist/cjs/identity.js +120 -0
- package/dist/cjs/index.js +46 -0
- package/dist/cjs/localization.js +48 -0
- package/dist/cjs/router.js +293 -0
- package/dist/cjs/server/assets.js +56 -0
- package/dist/cjs/server/dispatch.js +351 -0
- package/dist/cjs/server/index.js +57 -0
- package/dist/cjs/server/types.js +18 -0
- package/dist/cjs/server/worker.js +73 -0
- package/dist/cjs/session/cache.js +172 -0
- package/dist/cjs/session/index.js +63 -0
- package/dist/cjs/session/request.js +269 -0
- package/dist/cjs/session/types.js +18 -0
- package/dist/esm/abort.mjs +27 -0
- package/dist/esm/adapter.mjs +21 -0
- package/dist/esm/data/body.mjs +40 -0
- package/dist/esm/data/client.mjs +368 -0
- package/dist/esm/data/codec.mjs +112 -0
- package/dist/esm/data/index.mjs +7 -0
- package/dist/esm/data/private.mjs +39 -0
- package/dist/esm/data/routes.mjs +108 -0
- package/dist/esm/data/server.mjs +723 -0
- package/dist/esm/data/static.mjs +51 -0
- package/dist/esm/data/tree.mjs +193 -0
- package/dist/esm/data/types.mjs +6 -0
- package/dist/esm/document.mjs +131 -0
- package/dist/esm/entry-client.mjs +104 -0
- package/dist/esm/entry-server.mjs +215 -0
- package/dist/esm/federation.mjs +36 -0
- package/dist/esm/identity.mjs +63 -0
- package/dist/esm/index.mjs +1 -0
- package/dist/esm/localization.mjs +10 -0
- package/dist/esm/router.mjs +225 -0
- package/dist/esm/server/assets.mjs +15 -0
- package/dist/esm/server/dispatch.mjs +307 -0
- package/dist/esm/server/index.mjs +3 -0
- package/dist/esm/server/types.mjs +0 -0
- package/dist/esm/server/worker.mjs +35 -0
- package/dist/esm/session/cache.mjs +116 -0
- package/dist/esm/session/index.mjs +3 -0
- package/dist/esm/session/request.mjs +231 -0
- package/dist/esm/session/types.mjs +0 -0
- package/dist/esm-node/abort.mjs +28 -0
- package/dist/esm-node/adapter.mjs +22 -0
- package/dist/esm-node/data/body.mjs +41 -0
- package/dist/esm-node/data/client.mjs +369 -0
- package/dist/esm-node/data/codec.mjs +113 -0
- package/dist/esm-node/data/index.mjs +8 -0
- package/dist/esm-node/data/private.mjs +40 -0
- package/dist/esm-node/data/routes.mjs +109 -0
- package/dist/esm-node/data/server.mjs +724 -0
- package/dist/esm-node/data/static.mjs +52 -0
- package/dist/esm-node/data/tree.mjs +194 -0
- package/dist/esm-node/data/types.mjs +7 -0
- package/dist/esm-node/document.mjs +132 -0
- package/dist/esm-node/entry-client.mjs +105 -0
- package/dist/esm-node/entry-server.mjs +216 -0
- package/dist/esm-node/federation.mjs +37 -0
- package/dist/esm-node/identity.mjs +64 -0
- package/dist/esm-node/index.mjs +2 -0
- package/dist/esm-node/localization.mjs +11 -0
- package/dist/esm-node/router.mjs +226 -0
- package/dist/esm-node/server/assets.mjs +16 -0
- package/dist/esm-node/server/dispatch.mjs +308 -0
- package/dist/esm-node/server/index.mjs +4 -0
- package/dist/esm-node/server/types.mjs +1 -0
- package/dist/esm-node/server/worker.mjs +36 -0
- package/dist/esm-node/session/cache.mjs +117 -0
- package/dist/esm-node/session/index.mjs +4 -0
- package/dist/esm-node/session/request.mjs +232 -0
- package/dist/esm-node/session/types.mjs +1 -0
- package/dist/types/abort.d.ts +6 -0
- package/dist/types/adapter.d.ts +195 -0
- package/dist/types/data/body.d.ts +2 -0
- package/dist/types/data/client.d.ts +17 -0
- package/dist/types/data/codec.d.ts +14 -0
- package/dist/types/data/index.d.ts +8 -0
- package/dist/types/data/private.d.ts +6 -0
- package/dist/types/data/routes.d.ts +28 -0
- package/dist/types/data/server.d.ts +59 -0
- package/dist/types/data/static.d.ts +31 -0
- package/dist/types/data/tree.d.ts +3 -0
- package/dist/types/data/types.d.ts +104 -0
- package/dist/types/document.d.ts +76 -0
- package/dist/types/entry-client.d.ts +53 -0
- package/dist/types/entry-server.d.ts +63 -0
- package/dist/types/federation.d.ts +56 -0
- package/dist/types/identity.d.ts +27 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/localization.d.ts +58 -0
- package/dist/types/router.d.ts +201 -0
- package/dist/types/server/assets.d.ts +14 -0
- package/dist/types/server/dispatch.d.ts +11 -0
- package/dist/types/server/index.d.ts +4 -0
- package/dist/types/server/types.d.ts +64 -0
- package/dist/types/server/worker.d.ts +25 -0
- package/dist/types/session/cache.d.ts +25 -0
- package/dist/types/session/index.d.ts +4 -0
- package/dist/types/session/request.d.ts +8 -0
- package/dist/types/session/types.d.ts +48 -0
- package/package.json +159 -4
- package/src/abort.ts +40 -0
- package/src/adapter.ts +255 -0
- package/src/data/body.ts +43 -0
- package/src/data/client.ts +501 -0
- package/src/data/codec.ts +247 -0
- package/src/data/index.ts +8 -0
- package/src/data/private.ts +68 -0
- package/src/data/routes.ts +201 -0
- package/src/data/server.ts +977 -0
- package/src/data/static.ts +105 -0
- package/src/data/tree.ts +244 -0
- package/src/data/types.ts +98 -0
- package/src/document.ts +338 -0
- package/src/entry-client.ts +236 -0
- package/src/entry-server.ts +393 -0
- package/src/federation.ts +111 -0
- package/src/identity.ts +128 -0
- package/src/index.ts +7 -0
- package/src/localization.ts +77 -0
- package/src/router.ts +475 -0
- package/src/server/assets.ts +60 -0
- package/src/server/dispatch.ts +523 -0
- package/src/server/index.ts +16 -0
- package/src/server/types.ts +94 -0
- package/src/server/worker.ts +94 -0
- package/src/session/cache.ts +177 -0
- package/src/session/index.ts +11 -0
- package/src/session/request.ts +324 -0
- package/src/session/types.ts +60 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { untilAborted } from "./abort.mjs";
|
|
2
|
+
import { handleDataRequest, invokeRouteData, normalizeDataResult } from "./data/index.mjs";
|
|
3
|
+
import { assertRendererIdentity } from "./identity.mjs";
|
|
4
|
+
import { nativeI18nView } from "./localization.mjs";
|
|
5
|
+
import { isRoutedApplication, matchApplicationRouteIds, selectApplicationDataRoute } from "./router.mjs";
|
|
6
|
+
function serverRouteLoader(application) {
|
|
7
|
+
return async (route, input)=>{
|
|
8
|
+
const loader = application.dataModules[route.id]?.loader;
|
|
9
|
+
if (!loader) return {
|
|
10
|
+
kind: 'success',
|
|
11
|
+
value: void 0,
|
|
12
|
+
status: 200
|
|
13
|
+
};
|
|
14
|
+
return invokeRouteData(loader, input, {
|
|
15
|
+
production: 'development' !== process.env.NODE_ENV
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function resolveDocumentPolicy(session, vary) {
|
|
20
|
+
if (!session.responsePolicy) session.resolveResponse({
|
|
21
|
+
kind: 'document',
|
|
22
|
+
status: 200,
|
|
23
|
+
headers: [
|
|
24
|
+
[
|
|
25
|
+
'content-type',
|
|
26
|
+
'text/html; charset=utf-8'
|
|
27
|
+
],
|
|
28
|
+
...vary?.length ? [
|
|
29
|
+
[
|
|
30
|
+
'vary',
|
|
31
|
+
vary.join(', ')
|
|
32
|
+
]
|
|
33
|
+
] : []
|
|
34
|
+
],
|
|
35
|
+
cache: {
|
|
36
|
+
mode: 'no-store'
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const detectedVary = (language)=>language?.kind === 'language' ? language.vary : void 0;
|
|
41
|
+
function createNativeServerEntry(options, adapter) {
|
|
42
|
+
const rendererIdentity = Object.freeze({
|
|
43
|
+
...options.identity
|
|
44
|
+
});
|
|
45
|
+
const { i18n } = options;
|
|
46
|
+
const own = (request, context)=>{
|
|
47
|
+
assertRendererIdentity(context.entry, rendererIdentity);
|
|
48
|
+
assertRendererIdentity(context.session.identity, rendererIdentity);
|
|
49
|
+
if (request !== context.session.request) throw new Error('Native request/session ownership mismatch');
|
|
50
|
+
};
|
|
51
|
+
const prepare = (request, context)=>{
|
|
52
|
+
own(request, context);
|
|
53
|
+
return adapter.document({
|
|
54
|
+
documentId: crypto.randomUUID(),
|
|
55
|
+
rootId: options.rootId ?? 'root',
|
|
56
|
+
...void 0 === context.nonce ? {} : {
|
|
57
|
+
nonce: context.nonce
|
|
58
|
+
},
|
|
59
|
+
...void 0 === context.assets ? {} : {
|
|
60
|
+
assets: context.assets
|
|
61
|
+
}
|
|
62
|
+
}, context.nativeManifest, rendererIdentity);
|
|
63
|
+
};
|
|
64
|
+
const routedApplication = async (signal)=>{
|
|
65
|
+
const application = await untilAborted(options.app(), signal);
|
|
66
|
+
if (!isRoutedApplication(application)) throw new Error('unsupported-renderer-capability: ssrByRouteIds requires native route matching.');
|
|
67
|
+
return application;
|
|
68
|
+
};
|
|
69
|
+
const nativeCSRRequestHandler = (request, context)=>adapter.run(context.session, async ()=>{
|
|
70
|
+
const document = prepare(request, context);
|
|
71
|
+
if (!i18n) {
|
|
72
|
+
resolveDocumentPolicy(context.session);
|
|
73
|
+
return adapter.renderCSR(context, document);
|
|
74
|
+
}
|
|
75
|
+
const language = i18n.resolveRequest(request);
|
|
76
|
+
if ('redirect' === language.kind) return adapter.respond(context.session, i18n.redirect(language.location));
|
|
77
|
+
resolveDocumentPolicy(context.session, detectedVary(language));
|
|
78
|
+
return adapter.renderCSR(context, {
|
|
79
|
+
...document,
|
|
80
|
+
lang: language.language,
|
|
81
|
+
inlineData: [
|
|
82
|
+
i18n.handoff(language.language)
|
|
83
|
+
]
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
const nativeMatchRouteIds = (request, context)=>adapter.run(context.session, async ()=>{
|
|
87
|
+
own(request, context);
|
|
88
|
+
const application = await routedApplication(context.session.signal);
|
|
89
|
+
const router = adapter.createRouter(application, {
|
|
90
|
+
identity: rendererIdentity,
|
|
91
|
+
loadRoute: serverRouteLoader(application),
|
|
92
|
+
request,
|
|
93
|
+
context: context.session.platform.bindings,
|
|
94
|
+
session: context.session,
|
|
95
|
+
...i18n ? {
|
|
96
|
+
rewrite: i18n.rewrite(()=>i18n.resolveRequest(request).language)
|
|
97
|
+
} : {}
|
|
98
|
+
});
|
|
99
|
+
return matchApplicationRouteIds(router, new URL(request.url));
|
|
100
|
+
});
|
|
101
|
+
const nativeRequestHandler = (request, context)=>adapter.run(context.session, async ()=>{
|
|
102
|
+
const document = prepare(request, context);
|
|
103
|
+
const { session } = context;
|
|
104
|
+
const application = await untilAborted(options.app(), session.signal);
|
|
105
|
+
const routed = isRoutedApplication(application);
|
|
106
|
+
const nativeRequest = routed ? new Request(request, {
|
|
107
|
+
signal: session.signal
|
|
108
|
+
}) : request;
|
|
109
|
+
const language = i18n?.resolveRequest(nativeRequest);
|
|
110
|
+
if (!routed && i18n && language?.kind === 'redirect') return adapter.respond(session, i18n.redirect(language.location));
|
|
111
|
+
const localize = async (inlineData = [])=>{
|
|
112
|
+
if (!i18n || !language) return {
|
|
113
|
+
document,
|
|
114
|
+
localization: void 0
|
|
115
|
+
};
|
|
116
|
+
const instance = await untilAborted(i18n.create(language.language), session.signal);
|
|
117
|
+
return {
|
|
118
|
+
document: {
|
|
119
|
+
...document,
|
|
120
|
+
lang: instance.language,
|
|
121
|
+
inlineData: [
|
|
122
|
+
...inlineData,
|
|
123
|
+
i18n.handoff(instance.language, instance)
|
|
124
|
+
]
|
|
125
|
+
},
|
|
126
|
+
localization: nativeI18nView(i18n, instance)
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
if (!routed) {
|
|
130
|
+
const localized = await localize(document.inlineData);
|
|
131
|
+
resolveDocumentPolicy(session, detectedVary(language));
|
|
132
|
+
return adapter.renderComponent(context, application.default, localized.document, localized.localization);
|
|
133
|
+
}
|
|
134
|
+
const bindings = session.platform.bindings;
|
|
135
|
+
const outcomes = [];
|
|
136
|
+
const outcomeRanks = [];
|
|
137
|
+
let routeOrder;
|
|
138
|
+
const addOutcome = (rank, outcome)=>{
|
|
139
|
+
const at = outcomeRanks.findIndex((other)=>other > rank);
|
|
140
|
+
const index = at < 0 ? outcomes.length : at;
|
|
141
|
+
outcomes.splice(index, 0, outcome);
|
|
142
|
+
outcomeRanks.splice(index, 0, rank);
|
|
143
|
+
};
|
|
144
|
+
const router = adapter.createRouter(application, {
|
|
145
|
+
identity: rendererIdentity,
|
|
146
|
+
loadRoute: serverRouteLoader(application),
|
|
147
|
+
request: nativeRequest,
|
|
148
|
+
context: bindings,
|
|
149
|
+
onOutcome: (routeId, outcome)=>{
|
|
150
|
+
if ('response' in outcome) {
|
|
151
|
+
routeOrder ??= matchApplicationRouteIds(router, new URL(nativeRequest.url));
|
|
152
|
+
const rank = routeOrder.indexOf(routeId);
|
|
153
|
+
addOutcome(rank < 0 ? 1 / 0 : rank, outcome);
|
|
154
|
+
} else if (outcome.completion) {
|
|
155
|
+
const completion = outcome.completion;
|
|
156
|
+
completion.catch((error)=>session.fail(error));
|
|
157
|
+
session.registerCleanup(()=>completion);
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
session,
|
|
161
|
+
...void 0 === context.nonce ? {} : {
|
|
162
|
+
nonce: context.nonce
|
|
163
|
+
},
|
|
164
|
+
...i18n && language ? {
|
|
165
|
+
rewrite: i18n.rewrite(()=>language.language)
|
|
166
|
+
} : {}
|
|
167
|
+
});
|
|
168
|
+
const dataResponse = await handleDataRequest({
|
|
169
|
+
request: nativeRequest,
|
|
170
|
+
identity: rendererIdentity,
|
|
171
|
+
context: bindings,
|
|
172
|
+
privateValues: [
|
|
173
|
+
context,
|
|
174
|
+
session,
|
|
175
|
+
session.platform,
|
|
176
|
+
bindings
|
|
177
|
+
],
|
|
178
|
+
production: 'development' !== process.env.NODE_ENV,
|
|
179
|
+
selectRoute: (dataRequest, routeId, operation)=>selectApplicationDataRoute(router, dataRequest, routeId, operation, application.dataModules)
|
|
180
|
+
});
|
|
181
|
+
if (dataResponse) return adapter.respond(session, dataResponse);
|
|
182
|
+
if (i18n && language?.kind === 'redirect') return adapter.respond(session, i18n.redirect(language.location));
|
|
183
|
+
const vary = detectedVary(language);
|
|
184
|
+
if (vary?.length) addOutcome(1 / 0, await normalizeDataResult(new Response(null, {
|
|
185
|
+
headers: {
|
|
186
|
+
vary: vary.join(', ')
|
|
187
|
+
}
|
|
188
|
+
})));
|
|
189
|
+
const localized = await localize();
|
|
190
|
+
return adapter.renderRoutes({
|
|
191
|
+
request: nativeRequest,
|
|
192
|
+
context,
|
|
193
|
+
router,
|
|
194
|
+
outcomes,
|
|
195
|
+
document: localized.document,
|
|
196
|
+
forbiddenValues: [
|
|
197
|
+
context,
|
|
198
|
+
session,
|
|
199
|
+
session.platform,
|
|
200
|
+
bindings,
|
|
201
|
+
nativeRequest
|
|
202
|
+
],
|
|
203
|
+
...localized.localization ? {
|
|
204
|
+
i18n: localized.localization
|
|
205
|
+
} : {}
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
return {
|
|
209
|
+
rendererIdentity,
|
|
210
|
+
nativeRequestHandler,
|
|
211
|
+
nativeCSRRequestHandler,
|
|
212
|
+
nativeMatchRouteIds
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
export { createNativeServerEntry };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
function getFederationHost(binding) {
|
|
2
|
+
if (void 0 === binding) return;
|
|
3
|
+
if ('function' != typeof binding?.instance) throw new TypeError('The native federation host binding is invalid.');
|
|
4
|
+
const instance = binding.instance();
|
|
5
|
+
if (!instance || 'function' != typeof instance.loadRemote) throw new TypeError('The application federation runtime has not started.');
|
|
6
|
+
return instance;
|
|
7
|
+
}
|
|
8
|
+
async function loadFederatedModule(instance, id) {
|
|
9
|
+
if (!instance) throw new Error(`Cannot load ${id}: this application has no Module Federation runtime. Add module-federation.config.ts with its remotes.`);
|
|
10
|
+
const module = await instance.loadRemote(id);
|
|
11
|
+
if (!module) throw new Error(`Remote module ${id} is unavailable`);
|
|
12
|
+
return module;
|
|
13
|
+
}
|
|
14
|
+
function remoteBrowserAssets(instance, id) {
|
|
15
|
+
const target = instance.remoteHandler?.idToRemoteMap?.[id];
|
|
16
|
+
const loaded = target && instance.moduleCache?.get(target.name);
|
|
17
|
+
const snapshot = loaded && instance.snapshotHandler?.getGlobalRemoteInfo(loaded.remoteInfo).remoteSnapshot;
|
|
18
|
+
if (!snapshot || 'string' != typeof snapshot.publicPath || !snapshot.publicPath || 'string' != typeof snapshot.remoteEntry || !snapshot.remoteEntry) return;
|
|
19
|
+
const expose = snapshot.modules?.find((module)=>module.modulePath === target.expose);
|
|
20
|
+
if (!expose?.assets) return;
|
|
21
|
+
const publicPath = snapshot.publicPath;
|
|
22
|
+
const url = (file)=>/^(?:https?:)?\/\//iu.test(file) ? file : `${publicPath.replace(/\/$/u, '')}/${file.replace(/^\//u, '')}`;
|
|
23
|
+
return {
|
|
24
|
+
js: [
|
|
25
|
+
url(snapshot.remoteEntry),
|
|
26
|
+
...(expose.assets.js?.sync ?? []).map(url)
|
|
27
|
+
],
|
|
28
|
+
css: [
|
|
29
|
+
...new Set([
|
|
30
|
+
...expose.assets.css?.sync ?? [],
|
|
31
|
+
...expose.assets.css?.async ?? []
|
|
32
|
+
].map(url))
|
|
33
|
+
]
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export { getFederationHost, loadFederatedModule, remoteBrowserAssets };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
function resolveRenderer(value = 'react') {
|
|
2
|
+
if ('string' == typeof value && value.trim() === value && /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(value)) return value;
|
|
3
|
+
throw new Error(`Unsupported UltraModern renderer: ${String(value)}`);
|
|
4
|
+
}
|
|
5
|
+
function identityCacheKey(identity) {
|
|
6
|
+
resolveRenderer(identity.renderer);
|
|
7
|
+
for (const key of [
|
|
8
|
+
'appId',
|
|
9
|
+
'entryName',
|
|
10
|
+
'buildId'
|
|
11
|
+
])if ('string' != typeof identity[key] || 0 === identity[key].trim().length) throw new Error(`Renderer identity requires a nonempty ${key}`);
|
|
12
|
+
if (1 !== identity.protocolVersion) throw new Error('Unsupported renderer data protocol version');
|
|
13
|
+
return JSON.stringify([
|
|
14
|
+
identity.renderer,
|
|
15
|
+
identity.appId,
|
|
16
|
+
identity.entryName,
|
|
17
|
+
identity.protocolVersion,
|
|
18
|
+
identity.buildId
|
|
19
|
+
]);
|
|
20
|
+
}
|
|
21
|
+
function assertRendererIdentity(actual, expected) {
|
|
22
|
+
if (identityCacheKey(actual) !== identityCacheKey(expected)) throw new Error('Renderer identity conflicts with the application build');
|
|
23
|
+
}
|
|
24
|
+
const IDENTITY_FIELDS = [
|
|
25
|
+
'renderer',
|
|
26
|
+
'appId',
|
|
27
|
+
'entryName',
|
|
28
|
+
'protocolVersion',
|
|
29
|
+
'buildId'
|
|
30
|
+
];
|
|
31
|
+
function readRendererIdentity(value, expected) {
|
|
32
|
+
if (!value || 'object' != typeof value || ![
|
|
33
|
+
Object.prototype,
|
|
34
|
+
null
|
|
35
|
+
].includes(Object.getPrototypeOf(value)) || Object.keys(value).some((key)=>!IDENTITY_FIELDS.includes(key))) throw new Error('A renderer identity must contain only identity fields.');
|
|
36
|
+
const identity = value;
|
|
37
|
+
assertRendererIdentity(identity, expected);
|
|
38
|
+
return Object.freeze({
|
|
39
|
+
renderer: identity.renderer,
|
|
40
|
+
appId: identity.appId,
|
|
41
|
+
entryName: identity.entryName,
|
|
42
|
+
protocolVersion: identity.protocolVersion,
|
|
43
|
+
buildId: identity.buildId
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function nativeModuleManifestFilename(renderer, entryName) {
|
|
47
|
+
if ('string' != typeof entryName || !entryName.trim()) throw new Error('Native module manifests require an application entry name.');
|
|
48
|
+
return `${resolveRenderer(renderer)}-module-manifest.${encodeURIComponent(entryName)}.json`;
|
|
49
|
+
}
|
|
50
|
+
const RENDERER_IDENTITY_HEADER = 'x-ultramodern-renderer-identity';
|
|
51
|
+
function serializeRendererIdentityHeader(identity) {
|
|
52
|
+
identityCacheKey(identity);
|
|
53
|
+
return JSON.stringify({
|
|
54
|
+
renderer: identity.renderer,
|
|
55
|
+
appId: identity.appId,
|
|
56
|
+
entryName: identity.entryName,
|
|
57
|
+
protocolVersion: identity.protocolVersion,
|
|
58
|
+
buildId: identity.buildId
|
|
59
|
+
}).replace(/[\u007f-\u{10ffff}]/gu, (character)=>Array.from({
|
|
60
|
+
length: character.length
|
|
61
|
+
}, (_, index)=>`\\u${character.charCodeAt(index).toString(16).padStart(4, '0')}`).join(''));
|
|
62
|
+
}
|
|
63
|
+
export { RENDERER_IDENTITY_HEADER, assertRendererIdentity, identityCacheKey, nativeModuleManifestFilename, readRendererIdentity, resolveRenderer, serializeRendererIdentityHeader };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { assertRendererIdentity, identityCacheKey, resolveRenderer } from "./identity.mjs";
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { untilAborted } from "./abort.mjs";
|
|
2
|
+
import { DataProtocolError, parsePublicData, serializePublicData, toTanstackPath } from "./data/index.mjs";
|
|
3
|
+
class RouteDataError extends Error {
|
|
4
|
+
constructor(routeId, outcome){
|
|
5
|
+
super(outcome.error.message);
|
|
6
|
+
this.name = outcome.error.name;
|
|
7
|
+
this.routeId = routeId;
|
|
8
|
+
this.status = 'response' in outcome ? outcome.response.status : outcome.status;
|
|
9
|
+
this.data = outcome.data;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function resolveRouteData(native, routeId, outcome, project = (value)=>value) {
|
|
13
|
+
switch(outcome.kind){
|
|
14
|
+
case 'success':
|
|
15
|
+
return project(outcome.value);
|
|
16
|
+
case 'deferred':
|
|
17
|
+
{
|
|
18
|
+
const value = parsePublicData(serializePublicData(outcome.critical));
|
|
19
|
+
for (const [key, promise] of Object.entries(outcome.deferred))Object.defineProperty(value, key, {
|
|
20
|
+
value: promise,
|
|
21
|
+
enumerable: true,
|
|
22
|
+
writable: true,
|
|
23
|
+
configurable: true
|
|
24
|
+
});
|
|
25
|
+
return project(value);
|
|
26
|
+
}
|
|
27
|
+
case 'redirect':
|
|
28
|
+
throw native.redirect({
|
|
29
|
+
href: outcome.location,
|
|
30
|
+
...'response' in outcome ? {
|
|
31
|
+
headers: outcome.response.headers
|
|
32
|
+
} : {},
|
|
33
|
+
statusCode: 'response' in outcome ? outcome.response.status : outcome.status
|
|
34
|
+
});
|
|
35
|
+
case 'not-found':
|
|
36
|
+
throw native.notFound({
|
|
37
|
+
data: project(outcome.value)
|
|
38
|
+
});
|
|
39
|
+
case 'error':
|
|
40
|
+
{
|
|
41
|
+
const error = project(outcome.error);
|
|
42
|
+
if (!error || 'object' != typeof error || !('name' in error) || 'string' != typeof error.name || !('message' in error) || 'string' != typeof error.message) throw new DataProtocolError('Invalid public route error');
|
|
43
|
+
throw Object.freeze(new RouteDataError(routeId, {
|
|
44
|
+
...outcome,
|
|
45
|
+
error: {
|
|
46
|
+
name: error.name,
|
|
47
|
+
message: error.message
|
|
48
|
+
},
|
|
49
|
+
data: project(outcome.data)
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function splitFileSystemRoutes(routes) {
|
|
55
|
+
const roots = routes.filter((route)=>route.isRoot);
|
|
56
|
+
if (roots.length > 1) throw new Error('A filesystem route tree requires one application root');
|
|
57
|
+
const root = roots[0];
|
|
58
|
+
const ids = new Set();
|
|
59
|
+
const visit = (route, nested)=>{
|
|
60
|
+
if (nested && route.isRoot) throw new Error('An application root cannot be a child route');
|
|
61
|
+
if (ids.has(route.id)) throw new Error(`Duplicate filesystem route id: ${route.id}`);
|
|
62
|
+
ids.add(route.id);
|
|
63
|
+
for (const child of route.children)visit(child, true);
|
|
64
|
+
};
|
|
65
|
+
for (const route of routes)visit(route, false);
|
|
66
|
+
return {
|
|
67
|
+
root,
|
|
68
|
+
children: root ? [
|
|
69
|
+
...root.children,
|
|
70
|
+
...routes.filter((route)=>route !== root)
|
|
71
|
+
] : routes
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function nativeRoutePath(route) {
|
|
75
|
+
const path = route.index ? '/' : route.path;
|
|
76
|
+
return path ? {
|
|
77
|
+
path: toTanstackPath(path)
|
|
78
|
+
} : {
|
|
79
|
+
id: route.id
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
async function loadFileSystemRoute(route, loadRoute, options, { params, location, abortController }) {
|
|
83
|
+
const base = options.request?.url ?? ("u" < typeof window ? void 0 : window.location.href);
|
|
84
|
+
if (!base) throw new Error('A server route loader requires its request');
|
|
85
|
+
const owners = [
|
|
86
|
+
abortController.signal,
|
|
87
|
+
...options.request ? [
|
|
88
|
+
options.request.signal
|
|
89
|
+
] : [],
|
|
90
|
+
...options.signal ? [
|
|
91
|
+
options.signal
|
|
92
|
+
] : []
|
|
93
|
+
];
|
|
94
|
+
const signal = owners.length > 1 ? AbortSignal.any(owners) : owners[0];
|
|
95
|
+
signal.throwIfAborted();
|
|
96
|
+
const request = new Request(new URL(location.publicHref, base), {
|
|
97
|
+
headers: options.request?.headers,
|
|
98
|
+
signal
|
|
99
|
+
});
|
|
100
|
+
const outcome = await untilAborted(loadRoute(route, {
|
|
101
|
+
request,
|
|
102
|
+
routeId: route.id,
|
|
103
|
+
params,
|
|
104
|
+
context: options.context
|
|
105
|
+
}), signal);
|
|
106
|
+
return {
|
|
107
|
+
outcome,
|
|
108
|
+
request,
|
|
109
|
+
signal
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function matchApplicationRoutes(router, url) {
|
|
113
|
+
return router.matchRoutes(router.parseLocation({
|
|
114
|
+
href: url.pathname + url.search + url.hash,
|
|
115
|
+
pathname: url.pathname,
|
|
116
|
+
search: url.search,
|
|
117
|
+
hash: url.hash,
|
|
118
|
+
state: {
|
|
119
|
+
__TSR_index: 0
|
|
120
|
+
}
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
function applicationRouteId(router, nativeRouteId) {
|
|
124
|
+
const staticData = router.routesById[nativeRouteId]?.options.staticData;
|
|
125
|
+
const id = staticData && 'object' == typeof staticData ? Reflect.get(staticData, 'ultramodernRouteId') : void 0;
|
|
126
|
+
return 'string' == typeof id ? id : void 0;
|
|
127
|
+
}
|
|
128
|
+
function matchApplicationRouteIds(router, url) {
|
|
129
|
+
return matchApplicationRoutes(router, url).flatMap((match)=>{
|
|
130
|
+
const id = applicationRouteId(router, match.routeId);
|
|
131
|
+
return void 0 === id ? [] : [
|
|
132
|
+
id
|
|
133
|
+
];
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function selectApplicationDataRoute(router, request, requestedRouteId, operation, handlers) {
|
|
137
|
+
const match = matchApplicationRoutes(router, new URL(request.url)).find((candidate)=>applicationRouteId(router, candidate.routeId) === requestedRouteId);
|
|
138
|
+
const handler = handlers[requestedRouteId]?.[operation];
|
|
139
|
+
if (!match || 'function' != typeof handler) return;
|
|
140
|
+
return {
|
|
141
|
+
routeId: requestedRouteId,
|
|
142
|
+
params: match.params,
|
|
143
|
+
handler
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function isRoutedApplication(application) {
|
|
147
|
+
return 'routeIR' in application;
|
|
148
|
+
}
|
|
149
|
+
const viewFields = [
|
|
150
|
+
'component',
|
|
151
|
+
'pendingComponent',
|
|
152
|
+
'errorComponent',
|
|
153
|
+
'notFoundComponent'
|
|
154
|
+
];
|
|
155
|
+
function resolveRouteSources(sources) {
|
|
156
|
+
const module = {};
|
|
157
|
+
for (const field of viewFields){
|
|
158
|
+
const source = sources[field];
|
|
159
|
+
if (source) module[field] = source.default;
|
|
160
|
+
}
|
|
161
|
+
if (sources.head) module.head = sources.head.head ?? sources.head.default;
|
|
162
|
+
if (sources.search) {
|
|
163
|
+
const validateSearch = sources.search.validateSearch ?? sources.search.default;
|
|
164
|
+
if ('function' != typeof validateSearch) throw new Error('A native search module must export validateSearch or a default validator');
|
|
165
|
+
module.validateSearch = validateSearch;
|
|
166
|
+
}
|
|
167
|
+
return module;
|
|
168
|
+
}
|
|
169
|
+
function createNativeRouter(application, options, factory) {
|
|
170
|
+
const context = options.context ?? {};
|
|
171
|
+
if (Object.hasOwn(context, 'ultramodern')) throw new Error('The native router context reserves ultramodern metadata');
|
|
172
|
+
const modules = Object.fromEntries(Object.entries(application.routeModules).map(([id, sources])=>[
|
|
173
|
+
id,
|
|
174
|
+
resolveRouteSources(sources)
|
|
175
|
+
]));
|
|
176
|
+
let router;
|
|
177
|
+
const routeTree = factory.routeTree(application.routeIR, modules, {
|
|
178
|
+
...options.request ? {
|
|
179
|
+
request: options.request
|
|
180
|
+
} : {},
|
|
181
|
+
...options.signal ? {
|
|
182
|
+
signal: options.signal
|
|
183
|
+
} : {},
|
|
184
|
+
context,
|
|
185
|
+
...options.onOutcome ? {
|
|
186
|
+
onOutcome: options.onOutcome
|
|
187
|
+
} : {},
|
|
188
|
+
...options.session ? {
|
|
189
|
+
session: options.session
|
|
190
|
+
} : {},
|
|
191
|
+
getRouter: ()=>{
|
|
192
|
+
if (!router) throw new Error('The native router is not created yet');
|
|
193
|
+
return router;
|
|
194
|
+
},
|
|
195
|
+
loadRoute: options.loadRoute
|
|
196
|
+
});
|
|
197
|
+
const url = options.request ? new URL(options.request.url) : void 0;
|
|
198
|
+
router = factory.router({
|
|
199
|
+
routeTree,
|
|
200
|
+
basepath: application.basePath,
|
|
201
|
+
context: {
|
|
202
|
+
ultramodern: Object.freeze({
|
|
203
|
+
rendererIdentity: Object.freeze({
|
|
204
|
+
...options.identity
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
},
|
|
208
|
+
...options.rewrite ? {
|
|
209
|
+
rewrite: options.rewrite
|
|
210
|
+
} : {},
|
|
211
|
+
...void 0 === options.nonce ? {} : {
|
|
212
|
+
ssr: {
|
|
213
|
+
nonce: options.nonce
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
...url ? {
|
|
217
|
+
location: {
|
|
218
|
+
origin: url.origin,
|
|
219
|
+
href: url.pathname + url.search + url.hash
|
|
220
|
+
}
|
|
221
|
+
} : {}
|
|
222
|
+
});
|
|
223
|
+
return router;
|
|
224
|
+
}
|
|
225
|
+
export { RouteDataError, createNativeRouter, isRoutedApplication, loadFileSystemRoute, matchApplicationRouteIds, matchApplicationRoutes, nativeRoutePath, resolveRouteData, selectApplicationDataRoute, splitFileSystemRoutes, untilAborted };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { collectDocumentAssets } from "../document.mjs";
|
|
2
|
+
import { assertRendererIdentity } from "../identity.mjs";
|
|
3
|
+
const RENDERER_ASSET_MANIFEST_FILE = 'renderer-assets.json';
|
|
4
|
+
function validateNativeClientAssetManifest(value, expectedIdentity) {
|
|
5
|
+
if (!value || 'object' != typeof value || Array.isArray(value)) throw new Error('Missing native client asset manifest. Rebuild the application before serving.');
|
|
6
|
+
const manifest = value;
|
|
7
|
+
if ('ultramodern-renderer-assets' !== manifest.schema || 1 !== manifest.version || 'react' === expectedIdentity.renderer || manifest.renderer !== expectedIdentity.renderer) throw new Error('Native client asset manifest conflicts with the selected renderer or schema.');
|
|
8
|
+
const entry = manifest.entries?.[expectedIdentity.entryName];
|
|
9
|
+
if (!entry?.rendererIdentity || !Array.isArray(entry.assets)) throw new Error(`Native client asset manifest is missing entry ${expectedIdentity.entryName}.`);
|
|
10
|
+
assertRendererIdentity(entry.rendererIdentity, expectedIdentity);
|
|
11
|
+
const assets = collectDocumentAssets(entry.assets);
|
|
12
|
+
if (!assets.some((asset)=>"script" === asset.kind)) throw new Error(`Native client asset manifest has no application script for ${expectedIdentity.entryName}.`);
|
|
13
|
+
return assets;
|
|
14
|
+
}
|
|
15
|
+
export { RENDERER_ASSET_MANIFEST_FILE, validateNativeClientAssetManifest };
|