1212 * https://github.com/shakacode/react_on_rails/blob/master/REACT-ON-RAILS-PRO-LICENSE.md
1313 */
1414
15- import { createBaseClientObject } from 'react-on-rails/@internal/base/client' ;
15+ import { createBaseClientObject , type BaseClientObjectType } from 'react-on-rails/@internal/base/client' ;
1616import { createBaseFullObject } from 'react-on-rails/@internal/base/full' ;
1717import { onPageLoaded , onPageUnloaded } from 'react-on-rails/pageLifecycle' ;
1818import { debugTurbolinks } from 'react-on-rails/turbolinksUtils' ;
19- import type { Store , StoreGenerator , RegisteredComponent } from 'react-on-rails/types' ;
19+ import type { ReactOnRailsInternal , RegisteredComponent , Store , StoreGenerator } from 'react-on-rails/types' ;
2020import * as ProComponentRegistry from './ComponentRegistry.ts' ;
2121import * as ProStoreRegistry from './StoreRegistry.ts' ;
2222import {
@@ -31,6 +31,22 @@ import {
3131
3232type BaseObjectCreator = typeof createBaseClientObject | typeof createBaseFullObject ;
3333
34+ /**
35+ * Pro-specific functions that override base/core stubs with real implementations.
36+ * Typed explicitly to ensure type safety when mutating the base object.
37+ */
38+ type ReactOnRailsProSpecificFunctions = Pick <
39+ ReactOnRailsInternal ,
40+ | 'reactOnRailsPageLoaded'
41+ | 'reactOnRailsComponentLoaded'
42+ | 'getOrWaitForComponent'
43+ | 'getOrWaitForStore'
44+ | 'getOrWaitForStoreGenerator'
45+ | 'reactOnRailsStoreLoaded'
46+ | 'streamServerRenderedReactComponent'
47+ | 'serverRenderRSCReactComponent'
48+ > ;
49+
3450// Pro client startup with immediate hydration support
3551async function reactOnRailsPageLoaded ( ) {
3652 debugTurbolinks ( 'reactOnRailsPageLoaded [PRO]' ) ;
@@ -62,19 +78,23 @@ function clientStartup() {
6278 onPageUnloaded ( reactOnRailsPageUnloaded ) ;
6379}
6480
65- // eslint-disable-next-line import/prefer-default-export
66- export function createReactOnRailsPro ( baseObjectCreator : BaseObjectCreator ) {
67- // Create base object with Pro registries
68- const baseObject = baseObjectCreator ( {
69- ComponentRegistry : ProComponentRegistry ,
70- StoreRegistry : ProStoreRegistry ,
71- } ) ;
72-
73- // Add Pro-specific implementations
74- const ReactOnRails = {
75- ...baseObject ,
81+ export default function createReactOnRailsPro (
82+ baseObjectCreator : BaseObjectCreator ,
83+ currentGlobal : BaseClientObjectType | null = null ,
84+ ) : ReactOnRailsInternal {
85+ // Create base object with Pro registries, passing currentGlobal for caching/validation
86+ const baseObject = baseObjectCreator (
87+ {
88+ ComponentRegistry : ProComponentRegistry ,
89+ StoreRegistry : ProStoreRegistry ,
90+ } ,
91+ currentGlobal ,
92+ ) ;
7693
77- // Override client-side rendering stubs with Pro implementations
94+ // Define Pro-specific functions with proper types
95+ // This object acts as a type-safe specification of what we're adding/overriding on the base object
96+ const reactOnRailsProSpecificFunctions : ReactOnRailsProSpecificFunctions = {
97+ // Override core implementations with Pro implementations
7898 reactOnRailsPageLoaded ( ) : Promise < void > {
7999 return reactOnRailsPageLoaded ( ) ;
80100 } ,
@@ -83,11 +103,7 @@ export function createReactOnRailsPro(baseObjectCreator: BaseObjectCreator) {
83103 return renderOrHydrateComponent ( domId ) ;
84104 } ,
85105
86- // ===================================================================
87- // PRO-ONLY METHOD IMPLEMENTATIONS
88- // These methods don't exist in base, add them here
89- // ===================================================================
90-
106+ // Pro-only method implementations (override core stubs)
91107 getOrWaitForComponent ( name : string ) : Promise < RegisteredComponent > {
92108 return ProComponentRegistry . getOrWaitForComponent ( name ) ;
93109 } ,
@@ -105,28 +121,42 @@ export function createReactOnRailsPro(baseObjectCreator: BaseObjectCreator) {
105121 } ,
106122
107123 // streamServerRenderedReactComponent is added in ReactOnRails.node.ts
108- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
109- streamServerRenderedReactComponent ( ... _args : any [ ] ) : any {
124+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
125+ streamServerRenderedReactComponent ( ) : any {
110126 throw new Error (
111127 'streamServerRenderedReactComponent requires importing from react-on-rails-pro in Node.js environment' ,
112128 ) ;
113129 } ,
114130
115131 // serverRenderRSCReactComponent is added in ReactOnRailsRSC.ts
116- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
117- serverRenderRSCReactComponent ( ... _args : any [ ] ) : any {
132+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
133+ serverRenderRSCReactComponent ( ) : any {
118134 throw new Error ( 'serverRenderRSCReactComponent is supported in RSC bundle only' ) ;
119135 } ,
120136 } ;
121137
122- // Assign to global
123- globalThis . ReactOnRails = ReactOnRails ;
138+ // Type assertion is safe here because:
139+ // 1. We start with BaseClientObjectType or BaseFullObjectType (from baseObjectCreator)
140+ // 2. We add exactly the methods defined in ReactOnRailsProSpecificFunctions
141+ // 3. ReactOnRailsInternal = Base + ReactOnRailsProSpecificFunctions
142+ // TypeScript can't track the mutation, but we ensure type safety by explicitly typing
143+ // the functions object above
144+ const reactOnRailsPro = baseObject as unknown as ReactOnRailsInternal ;
145+
146+ // Assign Pro-specific functions to the ReactOnRailsPro object using Object.assign
147+ // This pattern ensures we add exactly what's defined in the type, nothing more, nothing less
148+ Object . assign ( reactOnRailsPro , reactOnRailsProSpecificFunctions ) ;
124149
125- // Reset options to defaults
126- ReactOnRails . resetOptions ( ) ;
150+ // Assign to global if not already assigned
151+ if ( ! globalThis . ReactOnRails ) {
152+ globalThis . ReactOnRails = reactOnRailsPro ;
127153
128- // Run Pro client startup with immediate hydration support
129- clientStartup ( ) ;
154+ // Reset options to defaults (only on first initialization)
155+ reactOnRailsPro . resetOptions ( ) ;
156+
157+ // Run Pro client startup with immediate hydration support (only on first initialization)
158+ clientStartup ( ) ;
159+ }
130160
131- return ReactOnRails ;
161+ return reactOnRailsPro ;
132162}
0 commit comments