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

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 (49) hide show
  1. package/dist/cjs/entry-server.js +2 -2
  2. package/dist/cjs/i18n/I18nProvider.js +6 -14
  3. package/dist/cjs/index.js +4 -4
  4. package/dist/cjs/manifest.js +2 -2
  5. package/dist/cjs/plugin/compiler.js +2 -5
  6. package/dist/cjs/plugin/index.js +1 -1
  7. package/dist/cjs/plugin/profile.js +11 -10
  8. package/dist/cjs/router-client.js +1 -1
  9. package/dist/cjs/router-handler.js +11 -8
  10. package/dist/cjs/router-server.js +3 -11
  11. package/dist/cjs/router.js +1 -1
  12. package/dist/cjs/routes.js +11 -17
  13. package/dist/cjs/server.js +14 -6
  14. package/dist/esm/entry-server.mjs +3 -3
  15. package/dist/esm/i18n/I18nProvider.mjs +7 -15
  16. package/dist/esm/index.mjs +4 -4
  17. package/dist/esm/manifest.mjs +2 -2
  18. package/dist/esm/router-client.mjs +1 -1
  19. package/dist/esm/router-handler.mjs +11 -8
  20. package/dist/esm/router-server.mjs +0 -1
  21. package/dist/esm/router.mjs +2 -2
  22. package/dist/esm/routes.mjs +11 -17
  23. package/dist/esm/server.mjs +9 -1
  24. package/dist/esm-node/manifest.mjs +2 -2
  25. package/dist/esm-node/plugin/compiler.mjs +2 -5
  26. package/dist/esm-node/plugin/index.mjs +1 -1
  27. package/dist/esm-node/plugin/profile.mjs +11 -10
  28. package/dist/types/index.d.ts +4 -4
  29. package/dist/types/manifest.d.ts +2 -2
  30. package/dist/types/router-server.d.ts +0 -2
  31. package/dist/types/server.d.ts +3 -0
  32. package/package.json +23 -18
  33. package/src/entry-server.ts +3 -3
  34. package/src/i18n/I18nProvider.ts +11 -20
  35. package/src/index.ts +4 -4
  36. package/src/manifest.ts +2 -2
  37. package/src/plugin/compiler.ts +6 -14
  38. package/src/plugin/index.ts +3 -1
  39. package/src/plugin/profile.ts +12 -10
  40. package/src/router-client.ts +1 -1
  41. package/src/router-handler.ts +11 -8
  42. package/src/router-server.ts +0 -2
  43. package/src/router.ts +2 -2
  44. package/src/routes.ts +28 -26
  45. package/src/server.ts +16 -1
  46. package/dist/cjs/router-injection.js +0 -139
  47. package/dist/esm/router-injection.mjs +0 -101
  48. package/dist/types/router-injection.d.ts +0 -8
  49. package/src/router-injection.ts +0 -145
@@ -1,5 +1,6 @@
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";
@@ -174,7 +175,14 @@ async function renderOctaneApplication(input) {
174
175
  }, {
175
176
  highWaterMark: 0
176
177
  });
177
- return session.respond(body);
178
+ return session.respond(input.router ? transformReadableStreamWithRouter(input.router, body, {
179
+ signal: session.signal,
180
+ rendererSafePoint: "script-close",
181
+ timeoutMs: input.routerSerializationTimeoutMs,
182
+ onAbort: (reason)=>{
183
+ session.fail(reason);
184
+ }
185
+ }) : body);
178
186
  } catch (error) {
179
187
  await session.fail(error);
180
188
  throw error;
@@ -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,
@@ -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.34'
26
26
  },
27
27
  sourceExtensions: [
28
28
  '.tsx',
@@ -32,12 +32,13 @@ 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-d5988dd93614/octane-0.13.8.tgz',
37
+ '@octanejs/tanstack-router': 'https://github.com/BleedingDev/octane/releases/download/ultramodern-octane-d5988dd93614/octanejs-tanstack-router-0.2.10.tgz',
38
+ '@octanejs/rspack-plugin': 'https://github.com/BleedingDev/octane/releases/download/ultramodern-octane-d5988dd93614/octanejs-rspack-plugin-0.2.4.tgz',
38
39
  '@modern-js/renderer-octane': '3.8.3',
39
- seroval: '1.6.8',
40
- 'seroval-plugins': '1.6.8'
40
+ seroval: '1.6.9',
41
+ 'seroval-plugins': '1.6.9'
41
42
  },
42
43
  capabilities: {
43
44
  worker: true,
@@ -2,8 +2,8 @@ export type { RendererIdentity } from '@modern-js/renderer-core/identity';
2
2
  export type { OctaneDocumentBootstrap } from './bootstrap';
3
3
  export declare const OCTANE_RUNTIME_PROFILE: Readonly<{
4
4
  renderer: "octane";
5
- runtime: "0.7.1+ultramodern.1331985ea3b0";
6
- router: "0.1.60+ultramodern.0b9f76ee3003";
7
- routerCore: "1.171.15";
8
- history: "1.162.0";
5
+ runtime: "0.13.8";
6
+ router: "0.2.10";
7
+ routerCore: "1.171.34";
8
+ history: "1.162.4";
9
9
  }>;
@@ -1,7 +1,7 @@
1
1
  import type { NativeCompilerArtifacts } from '@modern-js/renderer-core/adapter';
2
2
  import { type RendererIdentity } from '@modern-js/renderer-core/identity';
3
- export declare const OCTANE_RUNTIME_VERSION = "0.7.1+ultramodern.1331985ea3b0";
4
- export declare const OCTANE_COMPILER_VERSION = "0.1.55";
3
+ export declare const OCTANE_RUNTIME_VERSION = "0.13.8";
4
+ export declare const OCTANE_COMPILER_VERSION = "0.2.4";
5
5
  export interface OctaneCompiledSource {
6
6
  readonly resource: string;
7
7
  readonly canonicalId: string;
@@ -3,8 +3,6 @@ import { type ServerRenderNode } from 'octane/server';
3
3
  export * from '@octanejs/tanstack-router/ssr/server';
4
4
  export type { OctaneRequestHandlerOptions } from './router-handler';
5
5
  export { createOctaneRequestHandler } from './router-handler';
6
- export type { OctaneRouterInjectionOptions } from './router-injection';
7
- export { createOctaneRouterInjection } from './router-injection';
8
6
  export type { OctaneRouterSerializationGuard, OctaneRouterSerializationOptions, } from './router-server-snapshot';
9
7
  export { prepareOctaneRouterSerialization } from './router-server-snapshot';
10
8
  /** Render the native provider using the released Start server root contract. */
@@ -1,6 +1,7 @@
1
1
  import { type DocumentAsset, type DocumentInlineData } from '@modern-js/renderer-core/document';
2
2
  import type { NativeFederationBinding } from '@modern-js/renderer-core/federation';
3
3
  import { type RequestSession, type ResponsePolicy } from '@modern-js/renderer-core/session';
4
+ import type { AnyRouter } from '@octanejs/tanstack-router';
4
5
  import { type ServerRenderNode, type StreamOptions } from 'octane/server';
5
6
  export interface OctaneDocumentOptions {
6
7
  /** One identity for this response, shared with the client's signal receiver. */
@@ -27,6 +28,8 @@ export interface RenderOctaneApplicationOptions<Bindings extends object = object
27
28
  readonly props?: unknown;
28
29
  /** Native router serialization, owned by this request's router SSR context. */
29
30
  readonly injection?: StreamOptions['injection'];
31
+ readonly router?: AnyRouter;
32
+ readonly routerSerializationTimeoutMs?: number;
30
33
  /** This server compilation's native Module Federation runtime. */
31
34
  readonly federation?: NativeFederationBinding;
32
35
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bleedingdev/modern-js-renderer-octane",
3
- "version": "3.9.3-ultramodern.7",
3
+ "version": "3.9.3-ultramodern.8",
4
4
  "description": "UltraModern native Octane application adapter.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -149,23 +149,24 @@
149
149
  "octane-tsc": "bin/octane-tsc.mjs"
150
150
  },
151
151
  "engines": {
152
- "node": ">=26.10.0"
152
+ "node": ">=26.12.0"
153
153
  },
154
154
  "dependencies": {
155
- "@modern-js/renderer-core": "npm:@bleedingdev/modern-js-renderer-core@3.9.3-ultramodern.7",
155
+ "@modern-js/renderer-core": "npm:@bleedingdev/modern-js-renderer-core@3.9.3-ultramodern.8",
156
156
  "@volar/language-core": "2.4.28",
157
157
  "jsonc-parser": "3.3.1",
158
- "seroval": "1.6.8",
159
- "seroval-plugins": "1.6.8",
158
+ "seroval": "1.6.9",
159
+ "seroval-plugins": "1.6.9",
160
160
  "typescript": "7.0.2"
161
161
  },
162
162
  "peerDependencies": {
163
- "@modern-js/i18n-runtime-extensions": "3.9.3-ultramodern.7",
164
- "@octanejs/rspack-plugin": "0.1.55",
165
- "@octanejs/tanstack-router": "0.1.60+ultramodern.0b9f76ee3003",
166
- "@rsbuild/core": "2.2.11",
163
+ "@modern-js/i18n-runtime-extensions": "3.9.3-ultramodern.8",
164
+ "@octanejs/rspack-plugin": "0.2.4",
165
+ "@octanejs/tanstack-router": "0.2.10",
166
+ "@rsbuild/core": "2.2.13",
167
+ "@tsrx/oxc": "0.20.0",
167
168
  "i18next": ">=25.7.4",
168
- "octane": "0.7.1+ultramodern.1331985ea3b0"
169
+ "octane": "0.13.8"
169
170
  },
170
171
  "peerDependenciesMeta": {
171
172
  "@modern-js/i18n-runtime-extensions": {
@@ -180,6 +181,9 @@
180
181
  "@rsbuild/core": {
181
182
  "optional": true
182
183
  },
184
+ "@tsrx/oxc": {
185
+ "optional": true
186
+ },
183
187
  "i18next": {
184
188
  "optional": true
185
189
  },
@@ -188,16 +192,17 @@
188
192
  }
189
193
  },
190
194
  "devDependencies": {
191
- "@modern-js/i18n-runtime-extensions": "npm:@bleedingdev/modern-js-i18n-runtime-extensions@3.9.3-ultramodern.7",
192
- "@modern-js/tsconfig": "npm:@bleedingdev/modern-js-tsconfig@3.9.3-ultramodern.7",
193
- "@octanejs/rspack-plugin": "0.1.55",
194
- "@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",
195
- "@rsbuild/core": "2.2.11",
195
+ "@modern-js/i18n-runtime-extensions": "npm:@bleedingdev/modern-js-i18n-runtime-extensions@3.9.3-ultramodern.8",
196
+ "@modern-js/tsconfig": "npm:@bleedingdev/modern-js-tsconfig@3.9.3-ultramodern.8",
197
+ "@octanejs/rspack-plugin": "https://github.com/BleedingDev/octane/releases/download/ultramodern-octane-d5988dd93614/octanejs-rspack-plugin-0.2.4.tgz",
198
+ "@octanejs/tanstack-router": "https://github.com/BleedingDev/octane/releases/download/ultramodern-octane-d5988dd93614/octanejs-tanstack-router-0.2.10.tgz",
199
+ "@rsbuild/core": "2.2.13",
196
200
  "@rslib/core": "1.0.3",
197
201
  "@scripts/rstest-config": "2.66.0",
198
- "@types/node": "^26.6.3",
199
- "happy-dom": "^20.14.5",
200
- "octane": "https://github.com/bleedingdev/octane/releases/download/octane%400.7.1%2Bultramodern.1331985ea3b0/octane-0.7.1%2Bultramodern.1331985ea3b0.tgz",
202
+ "@tsrx/oxc": "0.20.0",
203
+ "@types/node": "^26.6.5",
204
+ "happy-dom": "^21.0.3",
205
+ "octane": "https://github.com/BleedingDev/octane/releases/download/ultramodern-octane-d5988dd93614/octane-0.13.8.tgz",
201
206
  "typescript": "7.0.2"
202
207
  },
203
208
  "sideEffects": false,
@@ -23,8 +23,8 @@ import {
23
23
  import { validateOctaneModuleManifest } from './manifest';
24
24
  import {
25
25
  createOctaneRequestHandler,
26
- createOctaneRouterInjection,
27
26
  createSsrStreamResponse,
27
+ getSsrStatus,
28
28
  OctaneRouterServer,
29
29
  } from './router-server';
30
30
  import { renderOctaneApplication, renderOctaneCSRDocument } from './server';
@@ -100,7 +100,7 @@ export function createNativeServerEntry(
100
100
  serialization: { forbiddenValues },
101
101
  })(async ({ router, responseHeaders }) => {
102
102
  const metadata = mergeDataResponseMetadata(outcomes, {
103
- status: router.state.statusCode ?? 200,
103
+ status: getSsrStatus(router),
104
104
  });
105
105
  // Route headers() take precedence over loader singleton fields; list
106
106
  // fields keep both.
@@ -127,7 +127,7 @@ export function createNativeServerEntry(
127
127
  }),
128
128
  document,
129
129
  federation: options.federation,
130
- injection: createOctaneRouterInjection(router, session),
130
+ router,
131
131
  });
132
132
  return createSsrStreamResponse(router, rendered);
133
133
  });
@@ -1,6 +1,6 @@
1
- import { createElement, hookSlots, useEffect, useState } from 'octane';
1
+ import { createElement, hookSlots, useSyncExternalStore } from 'octane';
2
2
  import { I18nContext } from './context';
3
- import type { I18nContextValue, I18nInstanceLike } from './types';
3
+ import type { I18nContextValue } from './types';
4
4
 
5
5
  export interface I18nProviderProps extends Omit<I18nContextValue, 'language'> {
6
6
  children?: unknown;
@@ -22,27 +22,18 @@ export interface I18nProviderProps extends Omit<I18nContextValue, 'language'> {
22
22
  */
23
23
  // Plain TypeScript is not rewritten by the Octane compiler, so each hook call
24
24
  // site carries its own stable slot, like `OctaneRouterRoot`.
25
- const languageStateSlot = Symbol(hookSlots(1));
26
- const languageSubscriptionSlot = Symbol(hookSlots(1));
25
+ const languageStoreSlot = Symbol(hookSlots(1));
27
26
 
28
27
  export function I18nProvider(props: I18nProviderProps) {
29
- const [language, setLanguage] = useState(
30
- () => props.instance.language,
31
- languageStateSlot,
32
- );
33
-
34
- useEffect(
35
- () => {
36
- const instance: I18nInstanceLike = props.instance;
37
- setLanguage(instance.language);
38
- const handleLanguageChanged = (lng: string) => setLanguage(lng);
39
- instance.on?.('languageChanged', handleLanguageChanged);
40
- return () => {
41
- instance.off?.('languageChanged', handleLanguageChanged);
42
- };
28
+ const instance = props.instance;
29
+ const language = useSyncExternalStore(
30
+ notify => {
31
+ instance.on?.('languageChanged', notify);
32
+ return () => instance.off?.('languageChanged', notify);
43
33
  },
44
- [props.instance],
45
- languageSubscriptionSlot,
34
+ () => instance.language,
35
+ () => instance.language,
36
+ languageStoreSlot,
46
37
  );
47
38
 
48
39
  const value: I18nContextValue = {
package/src/index.ts CHANGED
@@ -3,8 +3,8 @@ export type { OctaneDocumentBootstrap } from './bootstrap';
3
3
 
4
4
  export const OCTANE_RUNTIME_PROFILE = Object.freeze({
5
5
  renderer: 'octane',
6
- runtime: '0.7.1+ultramodern.1331985ea3b0',
7
- router: '0.1.60+ultramodern.0b9f76ee3003',
8
- routerCore: '1.171.15',
9
- history: '1.162.0',
6
+ runtime: '0.13.8',
7
+ router: '0.2.10',
8
+ routerCore: '1.171.34',
9
+ history: '1.162.4',
10
10
  });
package/src/manifest.ts CHANGED
@@ -9,8 +9,8 @@ import {
9
9
  assertOctaneIdentity,
10
10
  } from './bootstrap';
11
11
 
12
- export const OCTANE_RUNTIME_VERSION = '0.7.1+ultramodern.1331985ea3b0';
13
- export const OCTANE_COMPILER_VERSION = '0.1.55';
12
+ export const OCTANE_RUNTIME_VERSION = '0.13.8';
13
+ export const OCTANE_COMPILER_VERSION = '0.2.4';
14
14
 
15
15
  export interface OctaneCompiledSource {
16
16
  readonly resource: string;
@@ -41,10 +41,15 @@ export function createOctaneCompilerPlugin(
41
41
  } = require('./compiler-manifest.cjs');
42
42
  // The application installs the Octane compiler; resolve it lazily so
43
43
  // descriptor readers never load it.
44
+ const appRequire = createRequire(
45
+ path.join(api.context.rootPath, 'package.json'),
46
+ );
44
47
  const {
45
48
  inferRspackEnvironment,
46
49
  OctaneRspackPlugin,
47
- }: typeof import('@octanejs/rspack-plugin') = require('@octanejs/rspack-plugin');
50
+ }: typeof import('@octanejs/rspack-plugin') = appRequire(
51
+ '@octanejs/rspack-plugin',
52
+ );
48
53
  api.modifyRsbuildConfig((config, { mergeRsbuildConfig }) =>
49
54
  mergeRsbuildConfig(config, {
50
55
  source: {
@@ -60,19 +65,6 @@ export function createOctaneCompilerPlugin(
60
65
  rsbuildEnvironment.name === options.workerEnvironmentName
61
66
  ? 'server'
62
67
  : inferRspackEnvironment(config.target);
63
- // Octane prepends '.tsrx' only when it is absent. Keep TypeScript
64
- // modules first so an extensionless './link' cannot select a
65
- // case-colliding Link.tsrx on case-insensitive file systems.
66
- config.resolve ??= {};
67
- const extensions = (config.resolve.extensions ?? []).filter(
68
- extension => extension !== '.tsrx',
69
- );
70
- extensions.splice(
71
- Math.max(extensions.indexOf('.ts'), extensions.indexOf('.tsx')) + 1,
72
- 0,
73
- '.tsrx',
74
- );
75
- config.resolve.extensions = extensions;
76
68
  config.plugins ??= [];
77
69
  config.plugins.push(
78
70
  new OctaneRspackPlugin({
@@ -89,7 +89,9 @@ export const rendererAdapter: NativeRendererAdapter<'octane'> =
89
89
  ],
90
90
  dependencies: nativeRendererDependencies(profile),
91
91
  devDependencies: {
92
- [profile.compiler.name]: profile.compiler.version,
92
+ [profile.compiler.name]:
93
+ profile.dependencies[profile.compiler.name] ??
94
+ profile.compiler.version,
93
95
  typescript: typescriptVersion,
94
96
  },
95
97
  typecheckCommand: 'octane-tsc --noEmit --project tsconfig.json',
@@ -4,7 +4,7 @@ export const octaneProfile: RendererBuildProfile<'octane'> = {
4
4
  renderer: 'octane',
5
5
  status: 'preview',
6
6
  protocolVersion: 1,
7
- minimumNode: '26.10.0',
7
+ minimumNode: '26.12.0',
8
8
  hmr: {
9
9
  editedBoundary: 'may-reset',
10
10
  unaffectedComponents: 'preserved',
@@ -12,28 +12,30 @@ export const octaneProfile: RendererBuildProfile<'octane'> = {
12
12
  roots: 'single',
13
13
  cleanup: 'exactly-once',
14
14
  },
15
- compiler: { name: '@octanejs/rspack-plugin', version: '0.1.55' },
15
+ compiler: { name: '@octanejs/rspack-plugin', version: '0.2.4' },
16
16
  hydration: {
17
17
  name: 'octane',
18
- version: '0.7.1+ultramodern.1331985ea3b0',
18
+ version: '0.13.8',
19
19
  },
20
20
  router: {
21
21
  name: '@octanejs/tanstack-router',
22
- version: '0.1.60+ultramodern.0b9f76ee3003',
22
+ version: '0.2.10',
23
23
  coreName: '@tanstack/router-core',
24
- coreVersion: '1.171.15',
24
+ coreVersion: '1.171.34',
25
25
  },
26
26
  sourceExtensions: ['.tsx', '.tsrx', '.ts', '.js'],
27
27
  jsxImportSource: 'octane',
28
28
  dependencies: {
29
+ '@tsrx/oxc': '0.20.0',
29
30
  octane:
30
- 'https://github.com/bleedingdev/octane/releases/download/octane%400.7.1%2Bultramodern.1331985ea3b0/octane-0.7.1%2Bultramodern.1331985ea3b0.tgz',
31
+ 'https://github.com/BleedingDev/octane/releases/download/ultramodern-octane-d5988dd93614/octane-0.13.8.tgz',
31
32
  '@octanejs/tanstack-router':
32
- 'https://github.com/bleedingdev/octane/releases/download/%40octanejs%2Ftanstack-router%400.1.60%2Bultramodern.0b9f76ee3003/octanejs-tanstack-router-0.1.60%2Bultramodern.0b9f76ee3003.tgz',
33
- '@octanejs/rspack-plugin': '0.1.55',
33
+ 'https://github.com/BleedingDev/octane/releases/download/ultramodern-octane-d5988dd93614/octanejs-tanstack-router-0.2.10.tgz',
34
+ '@octanejs/rspack-plugin':
35
+ 'https://github.com/BleedingDev/octane/releases/download/ultramodern-octane-d5988dd93614/octanejs-rspack-plugin-0.2.4.tgz',
34
36
  '@modern-js/renderer-octane': '3.8.3',
35
- seroval: '1.6.8',
36
- 'seroval-plugins': '1.6.8',
37
+ seroval: '1.6.9',
38
+ 'seroval-plugins': '1.6.9',
37
39
  },
38
40
  capabilities: {
39
41
  worker: true,
@@ -64,7 +64,7 @@ function waitForCommittedMatches(
64
64
  signal?: AbortSignal,
65
65
  ): Promise<void> {
66
66
  signal?.throwIfAborted();
67
- const matchesId = router.stores.matchesId;
67
+ const matchesId = router.stores.ids;
68
68
  if (matchesId.get().length > 0) return Promise.resolve();
69
69
  if (!isReactiveMatchIds(matchesId)) {
70
70
  throw new Error(
@@ -91,8 +91,9 @@ function requestHeaders(router: AnyRouter): Headers {
91
91
  );
92
92
  appendHeaders(headers, field?.value);
93
93
  }
94
- const redirect = router.stores.redirect.get();
95
- if (redirect) appendHeaders(headers, redirect.headers);
94
+ const result = router._serverResult;
95
+ if (result?.type === 'redirect')
96
+ appendHeaders(headers, result.redirect.headers);
96
97
  return headers;
97
98
  }
98
99
 
@@ -117,7 +118,6 @@ export function createOctaneRequestHandler<TRouter extends AnyRouter>({
117
118
  ...serialization,
118
119
  forbiddenValues: [request, ...(serialization?.forbiddenValues ?? [])],
119
120
  });
120
- session.registerCleanup(() => router.cancelMatches());
121
121
 
122
122
  const { url } = getNormalizedURL(request.url, 'http://localhost');
123
123
  const href = url.href.replace(url.origin, '');
@@ -126,14 +126,14 @@ export function createOctaneRequestHandler<TRouter extends AnyRouter>({
126
126
  history: createMemoryHistory({ initialEntries: [href] }),
127
127
  origin: router.options.origin ?? getOrigin(request),
128
128
  });
129
- await withAbort(router.load(), session.signal);
129
+ await withAbort(router.load({ _signal: session.signal }), session.signal);
130
130
  guard.assertActive();
131
131
 
132
132
  // A native redirect is a terminal HTTP Response. It must never enter
133
133
  // the public serializer as a match error or document payload.
134
- const redirect = router.stores.redirect.get();
135
- if (redirect) {
136
- const resolved = router.resolveRedirect(redirect);
134
+ const loadResult = router._serverResult;
135
+ if (loadResult?.type === 'redirect') {
136
+ const resolved = loadResult.redirect;
137
137
  const headers = requestHeaders(router);
138
138
  return new Response(null, {
139
139
  status: resolved.status,
@@ -146,7 +146,10 @@ export function createOctaneRequestHandler<TRouter extends AnyRouter>({
146
146
  const serverSsr = router.serverSsr;
147
147
  if (!serverSsr)
148
148
  throw new Error('Native Octane router SSR state is required');
149
- await withAbort(serverSsr.dehydrate(), session.signal);
149
+ await withAbort(
150
+ serverSsr.dehydrate({ signal: session.signal }),
151
+ session.signal,
152
+ );
150
153
  guard.assertActive();
151
154
  const responseHeaders = requestHeaders(router);
152
155
  // A callback that ignores cancellation must not hold the router SSR
@@ -5,8 +5,6 @@ import { createElement, type ServerRenderNode } from 'octane/server';
5
5
  export * from '@octanejs/tanstack-router/ssr/server';
6
6
  export type { OctaneRequestHandlerOptions } from './router-handler';
7
7
  export { createOctaneRequestHandler } from './router-handler';
8
- export type { OctaneRouterInjectionOptions } from './router-injection';
9
- export { createOctaneRouterInjection } from './router-injection';
10
8
  export type {
11
9
  OctaneRouterSerializationGuard,
12
10
  OctaneRouterSerializationOptions,
package/src/router.ts CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  useMatch,
10
10
  useRouter,
11
11
  } from '@octanejs/tanstack-router';
12
- import { createSubSlot, useCallback, useEffect, useRef } from 'octane';
12
+ import { createSubSlot, useCallback, useLayoutEffect, useRef } from 'octane';
13
13
  import { assertOctaneIdentity } from './bootstrap';
14
14
  import { resolveRouteData } from './routes';
15
15
 
@@ -161,7 +161,7 @@ export function useOctaneRouteAction(...args: unknown[]) {
161
161
  const identity = useApplicationIdentity();
162
162
  const routeId = useApplicationRouteId();
163
163
  const owner = useRef<AbortController | null>(null, actionSlot(slot, 'owner'));
164
- useEffect(
164
+ useLayoutEffect(
165
165
  () => {
166
166
  const controller = new AbortController();
167
167
  owner.current = controller;
package/src/routes.ts CHANGED
@@ -59,7 +59,7 @@ export function resolveRouteData(
59
59
  }
60
60
 
61
61
  interface ReactiveNativeMatch {
62
- get(): AnyRouteMatch;
62
+ get(): AnyRouteMatch | undefined;
63
63
  subscribe(callback: () => void): { unsubscribe(): void };
64
64
  }
65
65
 
@@ -75,24 +75,18 @@ function isReactiveNativeMatch(value: unknown): value is ReactiveNativeMatch {
75
75
  /** A transport failure belongs to the native generation that fetched it. */
76
76
  function observeRouteCompletion(
77
77
  router: AnyRouter,
78
+ routeId: string,
78
79
  controller: AbortController,
79
80
  signal: AbortSignal,
80
81
  completion: Promise<void>,
81
82
  ) {
82
83
  void completion.catch(error => {
83
84
  if (signal.aborted) return;
84
- const match = [
85
- ...router.stores.pendingMatches.get(),
86
- ...router.stores.matches.get(),
87
- ...router.stores.cachedMatches.get(),
88
- ].find(candidate => candidate.abortController === controller);
89
- if (!match) return;
90
- const store = [
91
- router.stores.pendingMatchStores.get(match.id),
92
- router.stores.matchStores.get(match.id),
93
- router.stores.cachedMatchStores.get(match.id),
94
- ].find(candidate => candidate?.get().abortController === controller);
95
- if (!store) return;
85
+ const route = Object.values(router.routesById).find(
86
+ candidate => candidate.options.staticData?.ultramodernRouteId === routeId,
87
+ );
88
+ if (!route) return;
89
+ const store = router.stores.getMatchStore(route.id);
96
90
  let subscription: { unsubscribe(): void } | undefined;
97
91
  let settled = false;
98
92
  const cleanup = () => {
@@ -103,9 +97,10 @@ function observeRouteCompletion(
103
97
  settled = true;
104
98
  cleanup();
105
99
  };
106
- const publish = () => {
100
+ const publish = async () => {
107
101
  if (settled) return;
108
102
  const current = store.get();
103
+ if (!current) return;
109
104
  if (
110
105
  current.abortController !== controller ||
111
106
  signal.aborted ||
@@ -121,23 +116,29 @@ function observeRouteCompletion(
121
116
  const route = router.routesById[current.routeId];
122
117
  let failure = error;
123
118
  try {
119
+ await route?.options.errorComponent?.preload?.();
120
+ if (signal.aborted || store.get()?.abortController !== controller)
121
+ return;
124
122
  route?.options.onError?.(failure);
125
123
  } catch (onErrorFailure) {
126
124
  failure = onErrorFailure;
127
125
  }
128
- // Native successful loads have already preloaded their route components.
126
+ // The error component is ready before publishing a late transport failure.
129
127
  if (signal.aborted) return;
130
- router.updateMatch(match.id, previous =>
131
- previous.abortController === controller &&
132
- previous.status === 'success' &&
133
- !previous.isFetching
134
- ? {
135
- ...previous,
136
- error: failure,
137
- status: 'error',
138
- isFetching: false,
139
- }
140
- : previous,
128
+ router.stores.setMatches(
129
+ router.stores.matches.get().map(previous =>
130
+ previous.routeId === route.id &&
131
+ previous.abortController === controller &&
132
+ previous.status === 'success' &&
133
+ !previous.isFetching
134
+ ? {
135
+ ...previous,
136
+ error: failure,
137
+ status: 'error',
138
+ isFetching: false,
139
+ }
140
+ : previous,
141
+ ),
141
142
  );
142
143
  };
143
144
  signal.addEventListener('abort', onAbort, { once: true });
@@ -203,6 +204,7 @@ export function createFileSystemRouteTree<Context = unknown>(
203
204
  if (failed) throw failure;
204
205
  observeRouteCompletion(
205
206
  router,
207
+ route.id,
206
208
  match.abortController,
207
209
  signal,
208
210
  outcome.completion,