@bleedingdev/modern-js-renderer-octane 3.9.3-ultramodern.7 → 3.9.3-ultramodern.9

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.
Files changed (96) hide show
  1. package/dist/cjs/client.js +56 -6
  2. package/dist/cjs/entry-application.js +7 -2
  3. package/dist/cjs/entry-client.js +3 -0
  4. package/dist/cjs/entry-server.js +13 -5
  5. package/dist/cjs/federation.js +108 -6
  6. package/dist/cjs/i18n/I18nProvider.js +6 -14
  7. package/dist/cjs/i18n/LocalizedLink.js +24 -33
  8. package/dist/cjs/i18n/index.js +0 -3
  9. package/dist/cjs/i18n-external-snapshot.js +645 -0
  10. package/dist/cjs/index.js +4 -4
  11. package/dist/cjs/manifest.js +2 -2
  12. package/dist/cjs/plugin/compiler.js +2 -5
  13. package/dist/cjs/plugin/index.js +2 -2
  14. package/dist/cjs/plugin/profile.js +12 -10
  15. package/dist/cjs/router-client.js +1 -1
  16. package/dist/cjs/router-external-snapshot.js +263 -0
  17. package/dist/cjs/router-handler.js +11 -8
  18. package/dist/cjs/router-server.js +3 -11
  19. package/dist/cjs/router.js +1 -1
  20. package/dist/cjs/routes.js +11 -17
  21. package/dist/cjs/server.js +28 -7
  22. package/dist/cjs/worker-federation-context.js +63 -0
  23. package/dist/cjs/worker-federation-contexts.js +97 -0
  24. package/dist/cjs/worker-federation.js +97 -0
  25. package/dist/esm/client.mjs +56 -6
  26. package/dist/esm/entry-application.mjs +7 -2
  27. package/dist/esm/entry-client.mjs +3 -0
  28. package/dist/esm/entry-server.mjs +11 -6
  29. package/dist/esm/federation.mjs +110 -8
  30. package/dist/esm/i18n/I18nProvider.mjs +7 -15
  31. package/dist/esm/i18n/LocalizedLink.mjs +24 -30
  32. package/dist/esm/i18n/index.mjs +1 -1
  33. package/dist/esm/i18n-external-snapshot.mjs +576 -0
  34. package/dist/esm/index.mjs +4 -4
  35. package/dist/esm/manifest.mjs +2 -2
  36. package/dist/esm/router-client.mjs +1 -1
  37. package/dist/esm/router-external-snapshot.mjs +216 -0
  38. package/dist/esm/router-handler.mjs +11 -8
  39. package/dist/esm/router-server.mjs +0 -1
  40. package/dist/esm/router.mjs +2 -2
  41. package/dist/esm/routes.mjs +11 -17
  42. package/dist/esm/server.mjs +24 -3
  43. package/dist/esm/worker-federation-context.mjs +19 -0
  44. package/dist/esm/worker-federation-contexts.mjs +47 -0
  45. package/dist/esm/worker-federation.mjs +59 -0
  46. package/dist/esm-node/manifest.mjs +2 -2
  47. package/dist/esm-node/plugin/compiler.mjs +2 -5
  48. package/dist/esm-node/plugin/index.mjs +2 -2
  49. package/dist/esm-node/plugin/profile.mjs +12 -10
  50. package/dist/types/client.d.ts +5 -1
  51. package/dist/types/entry-client.d.ts +2 -0
  52. package/dist/types/entry-server.d.ts +4 -1
  53. package/dist/types/federation.d.ts +2 -0
  54. package/dist/types/i18n/LocalizedLink.d.ts +5 -32
  55. package/dist/types/i18n/context.d.ts +1 -1
  56. package/dist/types/i18n/index.d.ts +1 -1
  57. package/dist/types/i18n-external-snapshot.d.ts +43 -0
  58. package/dist/types/index.d.ts +4 -4
  59. package/dist/types/manifest.d.ts +2 -2
  60. package/dist/types/router-external-snapshot.d.ts +10 -0
  61. package/dist/types/router-server.d.ts +0 -2
  62. package/dist/types/server.d.ts +6 -0
  63. package/dist/types/worker-federation-context.d.ts +34 -0
  64. package/dist/types/worker-federation-contexts.d.ts +25 -0
  65. package/dist/types/worker-federation.d.ts +20 -0
  66. package/package.json +84 -22
  67. package/src/client.ts +99 -16
  68. package/src/entry-application.ts +7 -3
  69. package/src/entry-client.ts +10 -0
  70. package/src/entry-server.ts +20 -9
  71. package/src/federation.ts +159 -15
  72. package/src/i18n/I18nProvider.ts +11 -20
  73. package/src/i18n/LocalizedLink.ts +46 -72
  74. package/src/i18n/context.ts +1 -1
  75. package/src/i18n/index.ts +1 -5
  76. package/src/i18n-external-snapshot.ts +803 -0
  77. package/src/index.ts +4 -4
  78. package/src/manifest.ts +2 -2
  79. package/src/plugin/compiler-manifest.cjs +27 -1
  80. package/src/plugin/compiler.ts +6 -14
  81. package/src/plugin/index.ts +4 -2
  82. package/src/plugin/profile.ts +14 -10
  83. package/src/router-client.ts +1 -1
  84. package/src/router-external-snapshot.ts +306 -0
  85. package/src/router-handler.ts +11 -8
  86. package/src/router-server.ts +0 -2
  87. package/src/router.ts +2 -2
  88. package/src/routes.ts +28 -26
  89. package/src/server.ts +45 -5
  90. package/src/worker-federation-context.ts +51 -0
  91. package/src/worker-federation-contexts.ts +91 -0
  92. package/src/worker-federation.ts +120 -0
  93. package/dist/cjs/router-injection.js +0 -139
  94. package/dist/esm/router-injection.mjs +0 -101
  95. package/dist/types/router-injection.d.ts +0 -8
  96. package/src/router-injection.ts +0 -145
@@ -31,7 +31,7 @@ function isReactiveMatchIds(value) {
31
31
  }
32
32
  function waitForCommittedMatches(router, signal) {
33
33
  signal?.throwIfAborted();
34
- const matchesId = router.stores.matchesId;
34
+ const matchesId = router.stores.ids;
35
35
  if (matchesId.get().length > 0) return Promise.resolve();
36
36
  if (!isReactiveMatchIds(matchesId)) throw new Error('Octane router hydration requires its native reactive match store');
37
37
  return new Promise((resolve, reject)=>{
@@ -0,0 +1,216 @@
1
+ import { assertDefaultRouterRenderHost, associateRouterRenderProfile, captureRouterRenderSnapshot, createRouterRenderRouteTree, defaultParseSearch, defaultStringifySearch, disposeRouterRenderSnapshot, getRouterRenderProfile, matchContext, registerRouterRenderProfile, restoreRouterRenderSnapshot, routerContext } from "@octanejs/tanstack-router";
2
+ import { OCTANE_WORKER_CONTEXT_KEYS, getOctaneWorkerRouterRewrite, registerOctaneWorkerContext } from "./worker-federation-contexts.mjs";
3
+ const OCTANE_FILE_ROUTE_SNAPSHOT_PROFILE = Object.freeze({
4
+ key: 'ultramodern.file-routes',
5
+ version: '1'
6
+ });
7
+ const origins = new WeakMap();
8
+ let profileRegistered = false;
9
+ function plainRecord(value) {
10
+ return null !== value && 'object' == typeof value && [
11
+ Object.prototype,
12
+ null
13
+ ].includes(Object.getPrototypeOf(value));
14
+ }
15
+ function record(value, fields) {
16
+ if (!plainRecord(value)) throw new TypeError('Worker router context requires a plain record');
17
+ for (const key of Reflect.ownKeys(value)){
18
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
19
+ if ('string' != typeof key || fields && !fields.includes(key) || !descriptor || !descriptor.enumerable || !('value' in descriptor)) throw new TypeError('Unknown Worker router context field or accessor');
20
+ }
21
+ return value;
22
+ }
23
+ function nonempty(value) {
24
+ if ('string' != typeof value || !value.trim() || value.length > 1024) throw new TypeError('Invalid Worker router identity');
25
+ return value;
26
+ }
27
+ function publicContext(value) {
28
+ const context = record(value, [
29
+ 'ultramodern'
30
+ ]);
31
+ const metadata = record(context.ultramodern, [
32
+ 'rendererIdentity'
33
+ ]);
34
+ const identity = record(metadata.rendererIdentity, [
35
+ 'renderer',
36
+ 'appId',
37
+ 'entryName',
38
+ 'protocolVersion',
39
+ 'buildId'
40
+ ]);
41
+ if ('octane' !== identity.renderer || 1 !== identity.protocolVersion) throw new TypeError('Unsupported Worker router identity');
42
+ return Object.freeze({
43
+ ultramodern: Object.freeze({
44
+ rendererIdentity: Object.freeze({
45
+ renderer: 'octane',
46
+ appId: nonempty(identity.appId),
47
+ entryName: nonempty(identity.entryName),
48
+ protocolVersion: 1,
49
+ buildId: nonempty(identity.buildId)
50
+ })
51
+ })
52
+ });
53
+ }
54
+ function localizedUrls(value) {
55
+ if (void 0 === value || 'boolean' == typeof value) return value;
56
+ const input = record(value);
57
+ const result = {};
58
+ for (const [path, translations] of Object.entries(input)){
59
+ const names = {};
60
+ for (const [language, target] of Object.entries(record(translations)))names[nonempty(language)] = nonempty(target);
61
+ result[path] = names;
62
+ }
63
+ return result;
64
+ }
65
+ function rewriteDescriptor(value) {
66
+ if (void 0 === value) return;
67
+ const input = record(value, [
68
+ 'language',
69
+ 'languages',
70
+ 'localisedUrls'
71
+ ]);
72
+ if (!Array.isArray(input.languages) || !input.languages.length || input.languages.length > 64) throw new TypeError('Invalid Worker router i18n languages');
73
+ const languages = input.languages.map(nonempty);
74
+ const language = nonempty(input.language);
75
+ if (new Set(languages).size !== languages.length || !languages.includes(language)) throw new TypeError('Invalid Worker router i18n language');
76
+ const urls = localizedUrls(input.localisedUrls);
77
+ return {
78
+ language,
79
+ languages,
80
+ ...void 0 === urls ? {} : {
81
+ localisedUrls: urls
82
+ }
83
+ };
84
+ }
85
+ function nativeRewriteDescriptor(router) {
86
+ if (!router.options.rewrite) return;
87
+ const known = getOctaneWorkerRouterRewrite()?.capture(router.options.rewrite);
88
+ if (!known) throw new TypeError('Custom router rewrite requires an exact Worker profile');
89
+ const descriptor = rewriteDescriptor(known);
90
+ if (!descriptor) throw new TypeError('Missing native router rewrite provenance');
91
+ return {
92
+ language: descriptor.language,
93
+ languages: [
94
+ ...descriptor.languages
95
+ ],
96
+ ...void 0 === descriptor.localisedUrls ? {} : {
97
+ localisedUrls: descriptor.localisedUrls
98
+ }
99
+ };
100
+ }
101
+ function validateDefaultSnapshot(snapshot) {
102
+ publicContext(snapshot.publicContext);
103
+ rewriteDescriptor(snapshot.i18n);
104
+ }
105
+ function ensureDefaultProfile() {
106
+ if (profileRegistered) return;
107
+ registerRouterRenderProfile({
108
+ id: OCTANE_FILE_ROUTE_SNAPSHOT_PROFILE,
109
+ assertHost: (router, snapshot)=>{
110
+ const origin = origins.get(router);
111
+ if (!origin || origin.rewrite !== router.options.rewrite || JSON.stringify(publicContext(router.options.context)) !== JSON.stringify(origin.publicContext)) throw new TypeError('Generated Worker router origin changed');
112
+ assertDefaultRouterRenderHost(router, void 0 !== snapshot.i18n);
113
+ validateDefaultSnapshot(snapshot);
114
+ if (JSON.stringify(nativeRewriteDescriptor(router)) !== JSON.stringify(snapshot.i18n)) throw new TypeError('Worker router rewrite differs from its native provenance');
115
+ },
116
+ createRoutingTree: (snapshot)=>{
117
+ validateDefaultSnapshot(snapshot);
118
+ return createRouterRenderRouteTree(snapshot.routes);
119
+ },
120
+ routingOptions: (snapshot)=>{
121
+ const defaults = {
122
+ parseSearch: defaultParseSearch,
123
+ stringifySearch: defaultStringifySearch
124
+ };
125
+ const i18n = rewriteDescriptor(snapshot.i18n);
126
+ if (!i18n) return defaults;
127
+ const codec = getOctaneWorkerRouterRewrite();
128
+ if (!codec) throw new TypeError('Missing owning Worker router i18n codec');
129
+ return {
130
+ ...defaults,
131
+ rewrite: codec.restore(i18n)
132
+ };
133
+ }
134
+ });
135
+ profileRegistered = true;
136
+ }
137
+ function associateOctaneFileRouteSnapshot(router) {
138
+ ensureDefaultProfile();
139
+ const context = publicContext(router.options.context);
140
+ origins.set(router, {
141
+ publicContext: context,
142
+ rewrite: router.options.rewrite
143
+ });
144
+ associateRouterRenderProfile(router, {
145
+ id: OCTANE_FILE_ROUTE_SNAPSHOT_PROFILE,
146
+ publicContext: context
147
+ });
148
+ }
149
+ function captureOctaneRouterSnapshot(router) {
150
+ const origin = origins.get(router);
151
+ const declared = getRouterRenderProfile(router);
152
+ if (origin && declared?.key === OCTANE_FILE_ROUTE_SNAPSHOT_PROFILE.key && declared.version === OCTANE_FILE_ROUTE_SNAPSHOT_PROFILE.version) {
153
+ const i18n = nativeRewriteDescriptor(router);
154
+ associateRouterRenderProfile(router, {
155
+ id: OCTANE_FILE_ROUTE_SNAPSHOT_PROFILE,
156
+ publicContext: origin.publicContext,
157
+ ...void 0 === i18n ? {} : {
158
+ i18n
159
+ }
160
+ });
161
+ }
162
+ return captureRouterRenderSnapshot(router);
163
+ }
164
+ let registrations = 0;
165
+ let releaseContexts;
166
+ function registerOctaneRouterWorkerContexts() {
167
+ ensureDefaultProfile();
168
+ if (!releaseContexts) {
169
+ const releaseRouter = registerOctaneWorkerContext(routerContext, {
170
+ key: OCTANE_WORKER_CONTEXT_KEYS.router,
171
+ encode: (value)=>{
172
+ if (!value) throw new TypeError('Missing native router provider');
173
+ return captureOctaneRouterSnapshot(value);
174
+ },
175
+ decode: (value, signal)=>restoreRouterRenderSnapshot(value, {
176
+ signal
177
+ }),
178
+ dispose: (value)=>{
179
+ if (value) disposeRouterRenderSnapshot(value);
180
+ },
181
+ isProvided: (value)=>void 0 !== value
182
+ });
183
+ try {
184
+ const releaseMatch = registerOctaneWorkerContext(matchContext, {
185
+ key: OCTANE_WORKER_CONTEXT_KEYS.match,
186
+ encode: nonempty,
187
+ decode: nonempty,
188
+ validate: (value, readContext)=>{
189
+ const selected = readContext(routerContext);
190
+ const router = selected.present ? selected.value : void 0;
191
+ const current = router?.state.matches.find((match)=>match.routeId === value);
192
+ if (!router || !current || 'string' != typeof value || router.stores.getMatchStore(value).get() !== current) throw new TypeError('Worker match context requires its selected native router and a current match');
193
+ },
194
+ isProvided: (value)=>void 0 !== value
195
+ });
196
+ releaseContexts = ()=>{
197
+ releaseMatch();
198
+ releaseRouter();
199
+ };
200
+ } catch (error) {
201
+ releaseRouter();
202
+ throw error;
203
+ }
204
+ }
205
+ registrations++;
206
+ let released = false;
207
+ return ()=>{
208
+ if (released) return;
209
+ released = true;
210
+ if (0 === --registrations) {
211
+ releaseContexts?.();
212
+ releaseContexts = void 0;
213
+ }
214
+ };
215
+ }
216
+ export { OCTANE_FILE_ROUTE_SNAPSHOT_PROFILE, associateOctaneFileRouteSnapshot, captureOctaneRouterSnapshot, registerOctaneRouterWorkerContexts };
@@ -52,8 +52,8 @@ function requestHeaders(router) {
52
52
  if (field?.get || field?.set) throw new TypeError('Native Octane response headers must be owned values');
53
53
  appendHeaders(headers, field?.value);
54
54
  }
55
- const redirect = router.stores.redirect.get();
56
- if (redirect) appendHeaders(headers, redirect.headers);
55
+ const result = router._serverResult;
56
+ if (result?.type === 'redirect') appendHeaders(headers, result.redirect.headers);
57
57
  return headers;
58
58
  }
59
59
  function createOctaneRequestHandler({ createRouter, request, session, getRouterManifest, serialization }) {
@@ -74,7 +74,6 @@ function createOctaneRequestHandler({ createRouter, request, session, getRouterM
74
74
  ...serialization?.forbiddenValues ?? []
75
75
  ]
76
76
  });
77
- session.registerCleanup(()=>router.cancelMatches());
78
77
  const { url } = getNormalizedURL(request.url, 'http://localhost');
79
78
  const href = url.href.replace(url.origin, '');
80
79
  router.update({
@@ -86,11 +85,13 @@ function createOctaneRequestHandler({ createRouter, request, session, getRouterM
86
85
  }),
87
86
  origin: router.options.origin ?? getOrigin(request)
88
87
  });
89
- await withAbort(router.load(), session.signal);
88
+ await withAbort(router.load({
89
+ _signal: session.signal
90
+ }), session.signal);
90
91
  guard.assertActive();
91
- const redirect = router.stores.redirect.get();
92
- if (redirect) {
93
- const resolved = router.resolveRedirect(redirect);
92
+ const loadResult = router._serverResult;
93
+ if (loadResult?.type === 'redirect') {
94
+ const resolved = loadResult.redirect;
94
95
  const headers = requestHeaders(router);
95
96
  return new Response(null, {
96
97
  status: resolved.status,
@@ -101,7 +102,9 @@ function createOctaneRequestHandler({ createRouter, request, session, getRouterM
101
102
  guard.assertMatches();
102
103
  const serverSsr = router.serverSsr;
103
104
  if (!serverSsr) throw new Error('Native Octane router SSR state is required');
104
- await withAbort(serverSsr.dehydrate(), session.signal);
105
+ await withAbort(serverSsr.dehydrate({
106
+ signal: session.signal
107
+ }), session.signal);
105
108
  guard.assertActive();
106
109
  const responseHeaders = requestHeaders(router);
107
110
  const rendered = Promise.resolve(callback({
@@ -7,6 +7,5 @@ function OctaneRouterServer({ router }) {
7
7
  });
8
8
  }
9
9
  export { createOctaneRequestHandler } from "./router-handler.mjs";
10
- export { createOctaneRouterInjection } from "./router-injection.mjs";
11
10
  export { prepareOctaneRouterSerialization } from "./router-server-snapshot.mjs";
12
11
  export { OctaneRouterServer };
@@ -1,6 +1,6 @@
1
1
  import { createDataClient } from "@modern-js/renderer-core/data";
2
2
  import { RouterProvider, createRouter, redirect, useMatch, useRouter } from "@octanejs/tanstack-router";
3
- import { createSubSlot, useCallback, useEffect, useRef } from "octane";
3
+ import { createSubSlot, useCallback, useLayoutEffect, useRef } from "octane";
4
4
  import { assertOctaneIdentity } from "./bootstrap.mjs";
5
5
  import { RouteDataError, createFileSystemRouteTree, matchApplicationRouteIds, matchApplicationRoutes, resolveRouteData, selectApplicationDataRoute } from "./routes.mjs";
6
6
  export * from "@octanejs/tanstack-router";
@@ -69,7 +69,7 @@ function useOctaneRouteAction(...args) {
69
69
  const identity = useApplicationIdentity();
70
70
  const routeId = useApplicationRouteId();
71
71
  const owner = useRef(null, actionSlot(slot, 'owner'));
72
- useEffect(()=>{
72
+ useLayoutEffect(()=>{
73
73
  const controller = new AbortController();
74
74
  owner.current = controller;
75
75
  return ()=>controller.abort(new DOMException('The route action owner unmounted', 'AbortError'));
@@ -10,21 +10,12 @@ function routes_resolveRouteData(routeId, outcome) {
10
10
  function isReactiveNativeMatch(value) {
11
11
  return 'object' == typeof value && null !== value && 'function' == typeof Reflect.get(value, 'get') && 'function' == typeof Reflect.get(value, 'subscribe');
12
12
  }
13
- function observeRouteCompletion(router, controller, signal, completion) {
13
+ function observeRouteCompletion(router, routeId, controller, signal, completion) {
14
14
  completion.catch((error)=>{
15
15
  if (signal.aborted) return;
16
- const match = [
17
- ...router.stores.pendingMatches.get(),
18
- ...router.stores.matches.get(),
19
- ...router.stores.cachedMatches.get()
20
- ].find((candidate)=>candidate.abortController === controller);
21
- if (!match) return;
22
- const store = [
23
- router.stores.pendingMatchStores.get(match.id),
24
- router.stores.matchStores.get(match.id),
25
- router.stores.cachedMatchStores.get(match.id)
26
- ].find((candidate)=>candidate?.get().abortController === controller);
27
- if (!store) return;
16
+ const route = Object.values(router.routesById).find((candidate)=>candidate.options.staticData?.ultramodernRouteId === routeId);
17
+ if (!route) return;
18
+ const store = router.stores.getMatchStore(route.id);
28
19
  let subscription;
29
20
  let settled = false;
30
21
  const cleanup = ()=>{
@@ -35,9 +26,10 @@ function observeRouteCompletion(router, controller, signal, completion) {
35
26
  settled = true;
36
27
  cleanup();
37
28
  };
38
- const publish = ()=>{
29
+ const publish = async ()=>{
39
30
  if (settled) return;
40
31
  const current = store.get();
32
+ if (!current) return;
41
33
  if (current.abortController !== controller || signal.aborted || 'pending' !== current.status && 'success' !== current.status) return void onAbort();
42
34
  if ('success' !== current.status || current.isFetching) return;
43
35
  settled = true;
@@ -45,17 +37,19 @@ function observeRouteCompletion(router, controller, signal, completion) {
45
37
  const route = router.routesById[current.routeId];
46
38
  let failure = error;
47
39
  try {
40
+ await route?.options.errorComponent?.preload?.();
41
+ if (signal.aborted || store.get()?.abortController !== controller) return;
48
42
  route?.options.onError?.(failure);
49
43
  } catch (onErrorFailure) {
50
44
  failure = onErrorFailure;
51
45
  }
52
46
  if (signal.aborted) return;
53
- router.updateMatch(match.id, (previous)=>previous.abortController !== controller || 'success' !== previous.status || previous.isFetching ? previous : {
47
+ router.stores.setMatches(router.stores.matches.get().map((previous)=>previous.routeId !== route.id || previous.abortController !== controller || 'success' !== previous.status || previous.isFetching ? previous : {
54
48
  ...previous,
55
49
  error: failure,
56
50
  status: 'error',
57
51
  isFetching: false
58
- });
52
+ }));
59
53
  };
60
54
  signal.addEventListener('abort', onAbort, {
61
55
  once: true
@@ -99,7 +93,7 @@ function createFileSystemRouteTree(routes, modules, options = {}) {
99
93
  await Promise.resolve();
100
94
  signal.throwIfAborted();
101
95
  if (failed) throw failure;
102
- observeRouteCompletion(router, match.abortController, signal, outcome.completion);
96
+ observeRouteCompletion(router, route.id, match.abortController, signal, outcome.completion);
103
97
  }
104
98
  options.onOutcome?.(route.id, outcome);
105
99
  return routes_resolveRouteData(route.id, outcome);
@@ -1,8 +1,10 @@
1
1
  import { prepareDocument } from "@modern-js/renderer-core/document";
2
2
  import { untilAborted } from "@modern-js/renderer-core/session";
3
+ import { transformReadableStreamWithRouter } from "@octanejs/tanstack-router/ssr/server";
3
4
  import { createElement, earlySignalBootstrapScript, renderToReadableStream } from "octane/server";
4
5
  import { assertNativeHydrationBuildId, assertOctaneIdentity } from "./bootstrap.mjs";
5
6
  import { FederationRoot, createFederationScope } from "./federation-context.mjs";
7
+ import { WorkerFederationRoot } from "./worker-federation-context.mjs";
6
8
  function prepareOctaneDocument(session, document, hydrating) {
7
9
  assertOctaneIdentity(session.identity);
8
10
  if ('node' !== session.platform.kind && 'worker' !== session.platform.kind) throw new Error('An Octane document requires a Node or worker request platform.');
@@ -73,12 +75,21 @@ async function renderOctaneApplication(input) {
73
75
  if (session.responsePolicy?.kind === 'terminal') return session.respond(null);
74
76
  session.startRendering();
75
77
  let head = '';
76
- const federation = input.federation ? createFederationScope(input.federation, true, document.nonce) : void 0;
78
+ const federation = input.federation || input.workerFederation ? createFederationScope(input.federation, true, document.nonce) : void 0;
77
79
  const App = federation ? ()=>createElement(FederationRoot, {
78
80
  scope: federation,
79
81
  children: 'function' == typeof input.App ? createElement(input.App, input.props) : input.App
80
82
  }) : input.App;
81
- const nativeStream = await renderToReadableStream(App, input.props, {
83
+ const workerFederation = input.workerFederation ? {
84
+ transport: input.workerFederation,
85
+ locale: input.workerFederationLocale ?? 'default',
86
+ signal: session.signal
87
+ } : void 0;
88
+ const NativeApp = workerFederation ? ()=>createElement(WorkerFederationRoot, {
89
+ scope: workerFederation,
90
+ children: 'function' == typeof App ? createElement(App, input.props) : App
91
+ }) : App;
92
+ const nativeStream = await renderToReadableStream(NativeApp, input.props, {
82
93
  signal: session.signal,
83
94
  ...void 0 === document.nonce ? {} : {
84
95
  nonce: document.nonce
@@ -91,6 +102,9 @@ async function renderOctaneApplication(input) {
91
102
  buildId: document.nativeHydrationBuildId,
92
103
  documentId: document.documentId
93
104
  },
105
+ externalSnapshots: {
106
+ documentId: document.documentId
107
+ },
94
108
  headChannel: 'separate',
95
109
  onHeadReady (value) {
96
110
  head = value;
@@ -174,7 +188,14 @@ async function renderOctaneApplication(input) {
174
188
  }, {
175
189
  highWaterMark: 0
176
190
  });
177
- return session.respond(body);
191
+ return session.respond(input.router ? transformReadableStreamWithRouter(input.router, body, {
192
+ signal: session.signal,
193
+ rendererSafePoint: "script-close",
194
+ timeoutMs: input.routerSerializationTimeoutMs,
195
+ onAbort: (reason)=>{
196
+ session.fail(reason);
197
+ }
198
+ }) : body);
178
199
  } catch (error) {
179
200
  await session.fail(error);
180
201
  throw error;
@@ -0,0 +1,19 @@
1
+ import { createContext, createElement } from "octane";
2
+ import { FederationApplication } from "./federation-context.mjs";
3
+ const WorkerFederationContext = createContext(null);
4
+ function WorkerFederationRoot(props) {
5
+ return createElement(WorkerFederationContext, {
6
+ value: props.scope,
7
+ children: props.children
8
+ });
9
+ }
10
+ function WorkerFederationApplication(props) {
11
+ return createElement(WorkerFederationRoot, {
12
+ scope: props.scope,
13
+ children: props.federation ? createElement(FederationApplication, {
14
+ scope: props.federation,
15
+ application: props.application
16
+ }) : createElement(props.application.default, props.application.props)
17
+ });
18
+ }
19
+ export { WorkerFederationApplication, WorkerFederationContext, WorkerFederationRoot };
@@ -0,0 +1,47 @@
1
+ import { registerExternalSnapshotContext, useContext } from "octane";
2
+ const OCTANE_WORKER_CONTEXT_KEYS = Object.freeze({
3
+ router: 'ultramodern.octane.router-context.v1',
4
+ match: 'ultramodern.octane.match-context.v1',
5
+ i18n: 'ultramodern.octane.i18n-context.v1'
6
+ });
7
+ const builtinKeys = Object.values(OCTANE_WORKER_CONTEXT_KEYS);
8
+ const contexts = new Map();
9
+ function registerOctaneWorkerContext(context, codec) {
10
+ const { key, isProvided } = codec;
11
+ if (!builtinKeys.includes(key) || 'function' != typeof isProvided) throw new TypeError('Invalid Octane Worker builtin context codec');
12
+ const unregister = registerExternalSnapshotContext(context, codec);
13
+ const hasProvider = ()=>isProvided(useContext(context));
14
+ contexts.set(key, hasProvider);
15
+ return ()=>{
16
+ if (contexts.get(key) !== hasProvider) return;
17
+ unregister();
18
+ contexts.delete(key);
19
+ };
20
+ }
21
+ function useOctaneWorkerContextKeys(explicit = []) {
22
+ const selected = [];
23
+ for (const key of builtinKeys){
24
+ const hasProvider = contexts.get(key);
25
+ if (hasProvider?.() && !explicit.includes(key)) selected.push(key);
26
+ }
27
+ return [
28
+ ...selected,
29
+ ...explicit
30
+ ];
31
+ }
32
+ let routerRewrite;
33
+ function registerOctaneWorkerRouterRewrite(codec) {
34
+ if (routerRewrite || 'function' != typeof codec.capture || 'function' != typeof codec.restore) throw new TypeError('Invalid or duplicate Octane Worker router rewrite codec');
35
+ const captured = Object.freeze({
36
+ capture: codec.capture,
37
+ restore: codec.restore
38
+ });
39
+ routerRewrite = captured;
40
+ return ()=>{
41
+ if (routerRewrite === captured) routerRewrite = void 0;
42
+ };
43
+ }
44
+ function getOctaneWorkerRouterRewrite() {
45
+ return routerRewrite;
46
+ }
47
+ export { OCTANE_WORKER_CONTEXT_KEYS, getOctaneWorkerRouterRewrite, registerOctaneWorkerContext, registerOctaneWorkerRouterRewrite, useOctaneWorkerContextKeys };
@@ -0,0 +1,59 @@
1
+ import { createWorkerFederationTransport } from "@modern-js/renderer-core/worker-federation";
2
+ import { prepareExternalSnapshotRequest, releasePreparedExternalSnapshotRequest, renderExternalSnapshot, serializeExternalSnapshot } from "octane/server";
3
+ import { FederationContext, createFederationScope } from "./federation-context.mjs";
4
+ import { WorkerFederationContext } from "./worker-federation-context.mjs";
5
+ function isNativeWorkerComponent(value) {
6
+ return 'function' == typeof value;
7
+ }
8
+ function createNativeWorkerFederationRuntime(options, compiler) {
9
+ const { resources } = options;
10
+ const bindings = options.bindings;
11
+ const transport = resources.workerFederationServices?.length ? createWorkerFederationTransport({
12
+ admitRendererContract: compiler.admitRendererContract,
13
+ services: resources.workerFederationServices.map((service)=>{
14
+ const binding = bindings && Reflect.get(bindings, service.binding);
15
+ if (!binding || 'object' != typeof binding && 'function' != typeof binding || !('fetch' in binding) || 'function' != typeof binding.fetch) throw new Error(`Native Worker federation requires service binding ${service.binding}.`);
16
+ return {
17
+ ...service,
18
+ fetch: binding.fetch.bind(binding)
19
+ };
20
+ })
21
+ }) : void 0;
22
+ let handle;
23
+ if (resources.workerFederation) {
24
+ if (!compiler.registry || !resources.workerFederationManifestUrl) throw new Error('Native Worker federation requires its compiled expose registry and pinned manifest URL.');
25
+ handle = compiler.registry.initialize({
26
+ publication: resources.workerFederation.worker,
27
+ manifestUrl: resources.workerFederationManifestUrl,
28
+ admitRendererContract: compiler.admitRendererContract,
29
+ admitNativeRequest (payload, { admitted, signal }) {
30
+ const initializeContexts = transport ? (provide, request)=>{
31
+ provide(FederationContext, createFederationScope(void 0, true, request.nonce));
32
+ provide(WorkerFederationContext, {
33
+ transport,
34
+ locale: admitted.locale,
35
+ signal
36
+ });
37
+ } : void 0;
38
+ return prepareExternalSnapshotRequest(payload, {
39
+ authority: admitted.coordinate.native,
40
+ signal,
41
+ ...initializeContexts ? {
42
+ initializeContexts
43
+ } : {}
44
+ });
45
+ },
46
+ releaseNativeRequest: releasePreparedExternalSnapshotRequest,
47
+ async renderNativeSnapshot (module, request, { admitted }) {
48
+ const component = module && 'object' == typeof module && 'default' in module ? module.default : void 0;
49
+ if (!isNativeWorkerComponent(component)) throw new TypeError(`Native Worker expose ${admitted.expose} requires a default Octane component.`);
50
+ return serializeExternalSnapshot(await renderExternalSnapshot(component, request));
51
+ }
52
+ });
53
+ }
54
+ return {
55
+ transport,
56
+ handleRequest: handle ?? (()=>Promise.resolve(void 0))
57
+ };
58
+ }
59
+ export { createNativeWorkerFederationRuntime };
@@ -1,7 +1,7 @@
1
1
  import { nativeModuleManifestFilename, readRendererIdentity } from "@modern-js/renderer-core/identity";
2
2
  import { assertNativeHydrationBuildId, assertOctaneIdentity } from "./bootstrap.mjs";
3
- const OCTANE_RUNTIME_VERSION = '0.7.1+ultramodern.1331985ea3b0';
4
- const OCTANE_COMPILER_VERSION = '0.1.55';
3
+ const OCTANE_RUNTIME_VERSION = '0.13.8';
4
+ const OCTANE_COMPILER_VERSION = '0.2.4';
5
5
  function octaneModuleManifestFileName(entryName) {
6
6
  return nativeModuleManifestFilename('octane', entryName);
7
7
  }
@@ -20,7 +20,8 @@ function createOctaneCompilerPlugin(options) {
20
20
  const directory = pluginSourceDirectory();
21
21
  const require = createRequire(node_path.join(directory, 'index.cjs'));
22
22
  const { OctaneCompilerManifestPlugin } = require('./compiler-manifest.cjs');
23
- const { inferRspackEnvironment, OctaneRspackPlugin } = require('@octanejs/rspack-plugin');
23
+ const appRequire = createRequire(node_path.join(api.context.rootPath, 'package.json'));
24
+ const { inferRspackEnvironment, OctaneRspackPlugin } = appRequire('@octanejs/rspack-plugin');
24
25
  api.modifyRsbuildConfig((config, { mergeRsbuildConfig })=>mergeRsbuildConfig(config, {
25
26
  source: {
26
27
  include: [
@@ -30,10 +31,6 @@ function createOctaneCompilerPlugin(options) {
30
31
  }));
31
32
  api.modifyRspackConfig((config, { environment: rsbuildEnvironment })=>{
32
33
  const environment = rsbuildEnvironment.name === options.workerEnvironmentName ? 'server' : inferRspackEnvironment(config.target);
33
- config.resolve ??= {};
34
- const extensions = (config.resolve.extensions ?? []).filter((extension)=>'.tsrx' !== extension);
35
- extensions.splice(Math.max(extensions.indexOf('.ts'), extensions.indexOf('.tsx')) + 1, 0, '.tsrx');
36
- config.resolve.extensions = extensions;
37
34
  config.plugins ??= [];
38
35
  config.plugins.push(new OctaneRspackPlugin({
39
36
  root: api.context.rootPath,
@@ -77,7 +77,7 @@ const rendererAdapter = defineRendererAdapter({
77
77
  create: {
78
78
  templates: {
79
79
  federation: true,
80
- workers: false
80
+ workers: true
81
81
  },
82
82
  dependencies: (profile, { typescriptVersion })=>({
83
83
  frameworkDependencies: [
@@ -86,7 +86,7 @@ const rendererAdapter = defineRendererAdapter({
86
86
  ],
87
87
  dependencies: nativeRendererDependencies(profile),
88
88
  devDependencies: {
89
- [profile.compiler.name]: profile.compiler.version,
89
+ [profile.compiler.name]: profile.dependencies[profile.compiler.name] ?? profile.compiler.version,
90
90
  typescript: typescriptVersion
91
91
  },
92
92
  typecheckCommand: 'octane-tsc --noEmit --project tsconfig.json',
@@ -2,7 +2,7 @@ const octaneProfile = {
2
2
  renderer: 'octane',
3
3
  status: 'preview',
4
4
  protocolVersion: 1,
5
- minimumNode: '26.10.0',
5
+ minimumNode: '26.12.0',
6
6
  hmr: {
7
7
  editedBoundary: 'may-reset',
8
8
  unaffectedComponents: 'preserved',
@@ -12,17 +12,17 @@ const octaneProfile = {
12
12
  },
13
13
  compiler: {
14
14
  name: '@octanejs/rspack-plugin',
15
- version: '0.1.55'
15
+ version: '0.2.4'
16
16
  },
17
17
  hydration: {
18
18
  name: 'octane',
19
- version: '0.7.1+ultramodern.1331985ea3b0'
19
+ version: '0.13.8'
20
20
  },
21
21
  router: {
22
22
  name: '@octanejs/tanstack-router',
23
- version: '0.1.60+ultramodern.0b9f76ee3003',
23
+ version: '0.2.10',
24
24
  coreName: '@tanstack/router-core',
25
- coreVersion: '1.171.15'
25
+ coreVersion: '1.171.38'
26
26
  },
27
27
  sourceExtensions: [
28
28
  '.tsx',
@@ -32,12 +32,14 @@ const octaneProfile = {
32
32
  ],
33
33
  jsxImportSource: 'octane',
34
34
  dependencies: {
35
- octane: 'https://github.com/bleedingdev/octane/releases/download/octane%400.7.1%2Bultramodern.1331985ea3b0/octane-0.7.1%2Bultramodern.1331985ea3b0.tgz',
36
- '@octanejs/tanstack-router': 'https://github.com/bleedingdev/octane/releases/download/%40octanejs%2Ftanstack-router%400.1.60%2Bultramodern.0b9f76ee3003/octanejs-tanstack-router-0.1.60%2Bultramodern.0b9f76ee3003.tgz',
37
- '@octanejs/rspack-plugin': '0.1.55',
35
+ '@tsrx/oxc': '0.20.0',
36
+ octane: 'https://github.com/BleedingDev/octane/releases/download/ultramodern-octane-0bf2313bbdfb/octane-0.13.8.tgz',
37
+ '@octanejs/tanstack-router': 'https://github.com/BleedingDev/octane/releases/download/ultramodern-octane-0bf2313bbdfb/octanejs-tanstack-router-0.2.10.tgz',
38
+ '@octanejs/rspack-plugin': 'https://github.com/BleedingDev/octane/releases/download/ultramodern-octane-0bf2313bbdfb/octanejs-rspack-plugin-0.2.4.tgz',
39
+ '@tanstack/router-core': 'https://github.com/BleedingDev/router/releases/download/ultramodern-router-core-585d3233f7f5/tanstack-router-core-1.171.38.tgz',
38
40
  '@modern-js/renderer-octane': '3.8.3',
39
- seroval: '1.6.8',
40
- 'seroval-plugins': '1.6.8'
41
+ seroval: '1.6.9',
42
+ 'seroval-plugins': '1.6.9'
41
43
  },
42
44
  capabilities: {
43
45
  worker: true,