| 130 | export function createApp(opts: CreateAppRoutesOnlyOptions): App<Record<string, never>>; |
| 131 | export function createApp<S>(opts: CreateAppStateOptions<S>): App<S>; |
| 132 | export function createApp<S>(opts: CreateAppStateOptions<S> | CreateAppRoutesOnlyOptions): App<S> { |
| 133 | const backend = opts.backend; |
| 134 | const config = resolveAppConfig(opts.config); |
| 135 | |
| 136 | const backendDrawlistVersion = readBackendDrawlistVersionMarker(backend); |
| 137 | if (backendDrawlistVersion !== null && backendDrawlistVersion !== 1) { |
| 138 | invalidProps( |
| 139 | `backend drawlistVersion=${String( |
| 140 | backendDrawlistVersion, |
| 141 | )} is invalid. Fix: set backend drawlist version marker to 1.`, |
| 142 | ); |
| 143 | } |
| 144 | |
| 145 | const backendMaxEventBytes = readBackendPositiveIntMarker( |
| 146 | backend, |
| 147 | BACKEND_MAX_EVENT_BYTES_MARKER, |
| 148 | ); |
| 149 | if (backendMaxEventBytes !== null && backendMaxEventBytes !== config.maxEventBytes) { |
| 150 | invalidProps( |
| 151 | `config.maxEventBytes=${String(config.maxEventBytes)} must match backend maxEventBytes=${String( |
| 152 | backendMaxEventBytes, |
| 153 | )}. Fix: align maxEventBytes between app config and backend, or prefer createNodeApp({ config }) for Node/Bun apps to keep them aligned automatically.`, |
| 154 | ); |
| 155 | } |
| 156 | |
| 157 | const backendFpsCap = readBackendPositiveIntMarker(backend, BACKEND_FPS_CAP_MARKER); |
| 158 | if (backendFpsCap !== null && backendFpsCap !== config.fpsCap) { |
| 159 | invalidProps( |
| 160 | `config.fpsCap=${String(config.fpsCap)} must match backend fpsCap=${String( |
| 161 | backendFpsCap, |
| 162 | )}. Fix: align fpsCap between app config and backend, or prefer createNodeApp({ config }) for Node/Bun apps to keep them aligned automatically.`, |
| 163 | ); |
| 164 | } |
| 165 | |
| 166 | let theme = compileTheme(opts.theme ?? defaultTheme.definition); |
| 167 | let themeTransition: ThemeTransitionState | null = null; |
| 168 | let terminalProfile: TerminalProfile = DEFAULT_TERMINAL_PROFILE; |
| 169 | |
| 170 | const sm = new AppStateMachine(); |
| 171 | |
| 172 | const routes = opts.routes as readonly RouteDefinition<S>[] | undefined; |
| 173 | if (routes !== undefined && routes.length === 0) { |
| 174 | invalidProps("routes must contain at least one route"); |
| 175 | } |
| 176 | if (routes === undefined && opts.initialRoute !== undefined) { |
| 177 | invalidProps("initialRoute requires routes"); |
| 178 | } |
| 179 | if (routes !== undefined && opts.initialRoute === undefined) { |
| 180 | invalidProps("initialRoute is required when routes are provided"); |
| 181 | } |
| 182 | if (opts.routeHistoryMaxDepth !== undefined) { |
| 183 | requirePositiveInt("routeHistoryMaxDepth", opts.routeHistoryMaxDepth); |
| 184 | } |
| 185 | |
| 186 | let mode: Mode | null = null; |
| 187 | let drawFn: DrawFn | null = null; |
| 188 | let viewFn: ViewFn<S> | null = null; |
| 189 | let topLevelViewError: TopLevelViewError | null = null; |