@molecule/app-bonds-default-react
v3.0.0
Published
Default app-side bond wirings for React+Tailwind+react-router+localStorage+Arimo font+molecule icons. 5 setup functions consolidating 665 byte-identical per-app bond wiring files across the fleet.
Readme
@molecule/app-bonds-default-react
@molecule/app-bonds-default-react — default app-side bond wirings for the
React fleet stack, replacing the byte-identical per-app files:
bootstrapApp({ App, authClient, setupProviders, registerPWA? })— the whole scaffoldedsrc/main.tsx: awaitssetupProviders()BEFORE the first render, kicks offauthClient.initialize()(best-effort), mounts<App />in StrictMode on#root, then registers the PWA.setupAllDefaultBonds()— wires the EIGHT universal bonds in one call: state (the vanillasimpleProvider, socreateStore()works from the first render — the documentedcreateStore()+useStore()pattern needs a provider bonded and nothing else in a scaffold wires one), fonts-arimo, routing-react-router, storage-localstorage, styling-tailwind (registers the tailwind-merge class merger), theme-css-variables (light + dark viagetDefaultThemeProvider()), ui-tailwind ClassMap, icons-molecule. Each also exists as an individualsetupApp*()for apps that wire a la carte (per-appapp/src/bonds/<name>.tsfiles stay 1-line re-exports of these).- Optional provider wirings live behind subpaths, one module per pair:
@molecule/app-bonds-default-react/optional/<pair>.js—realtime-socketio,keyboard-shortcuts-hotkeys,command-palette-cmdk,client-search-minisearch,code-editor-monaco,virtual-scroll-tanstack,drag-drop-dndkit,charts-chartjs,maps-leaflet,video-hls. Import ONLY the ones the app installs. - Auth/http factories —
createDefaultAuthClient(authConfig)returns{ authClient, setupAuthDefault }; the...WithHttpSync/...WithFetchClientvariants also keep the bonded http client's bearer token in sync with auth events. All three accept any JSON object profile (createDefaultAuthClient<SessionUser>(config)for an API whose user has noid) and fall back toUserProfile— with itsid: string— when no type argument is given. The profile is never inferred fromauthConfig, so an untyped call keeps the default contract.
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Quick Start
// src/main.tsx
import {
bootstrapApp,
createDefaultAuthClientWithHttpSync,
setupAllDefaultBonds,
} from '@molecule/app-bonds-default-react'
// Optional providers come from their own subpath — see @remarks.
import { setupAppCodeEditorMonaco } from '@molecule/app-bonds-default-react/optional/code-editor-monaco.js'
import { App } from './App.js'
import { authConfig } from './config.js'
const { authClient, setupAuthDefault } = createDefaultAuthClientWithHttpSync(authConfig)
bootstrapApp({
App,
authClient,
// Async so optional async bonds are AWAITED before the first render.
setupProviders: async () => {
setupAllDefaultBonds()
setupAuthDefault()
await setupAppCodeEditorMonaco() // only if the app uses the code editor
},
})Type
feature
Installation
npm install @molecule/app-bonds-default-react @molecule/app-auth @molecule/app-charts @molecule/app-charts-chartjs @molecule/app-client-search @molecule/app-client-search-minisearch @molecule/app-code-editor @molecule/app-code-editor-monaco @molecule/app-command-palette @molecule/app-command-palette-cmdk @molecule/app-drag-drop @molecule/app-drag-drop-dndkit @molecule/app-fonts @molecule/app-fonts-arimo @molecule/app-http @molecule/app-icons @molecule/app-icons-molecule @molecule/app-keyboard-shortcuts @molecule/app-keyboard-shortcuts-hotkeys @molecule/app-maps @molecule/app-maps-leaflet @molecule/app-realtime @molecule/app-realtime-socketio @molecule/app-routing @molecule/app-routing-react-router @molecule/app-state @molecule/app-storage @molecule/app-storage-localstorage @molecule/app-styling-tailwind @molecule/app-theme @molecule/app-theme-css-variables @molecule/app-ui @molecule/app-ui-tailwind @molecule/app-video @molecule/app-video-hls @molecule/app-virtual-scroll @molecule/app-virtual-scroll-tanstack react react-dom
npm install -D @types/react @types/react-domAPI
Functions
createDefaultHttpClient(baseURL)
Builds the default fetch-based HTTP client + wires it into
@molecule/app-http. Replaces the per-app bonds/http-default.ts
shipped by ~52 fleet apps.
function createDefaultHttpClient(baseURL: string): {
httpClient: HttpClient
setupHttpDefault: () => void
}createDefaultHttpClientWithAuthBearer(opts)
Variant of createDefaultHttpClient that adds a request interceptor
injecting the JWT bearer token from the auth client AND (optionally)
stripping a leading /api/ from request URLs.
Replaces ~20 per-app bonds/http-default.ts files that hand-roll
this same wiring. Pass stripApiPrefix: true when the app uses
baseURL: '/api' to handle pages that pass /api/-prefixed paths
(would otherwise resolve to /api/api/... and 404). Pass authScheme
when the API reads another scheme than Bearer: it is an option of THIS
constructor (createDefaultHttpClientWithAuthBearer({ authScheme: 'Token', … }),
or createFetchClient({ baseURL, authScheme: 'Token' }) directly), never a
property of the client it returns. The client is built with the same scheme, so
a token the client also holds — the one a default auth bond's token sync gives
it — is sent with that scheme too, not as Bearer.
function createDefaultHttpClientWithAuthBearer(opts: {
baseURL: string
withCredentials?: boolean
stripApiPrefix?: boolean
getToken: () => string | null | undefined
authScheme?: string
}): { httpClient: HttpClient; setupHttpDefault: () => void }createDefaultAuthClient(authConfig)
Builds the default JWT auth client + wires it into @molecule/app-auth.
Apps pass their own authConfig (which lives in src/config.ts).
TUser is the profile the app's pages read, and may be any JSON object type —
including a profile with NO id ({ username, email, bio, image }), as in the
factory it wraps. Never add a synthetic id to satisfy a type: nothing in the
client reads it, and the fabricated value then travels into storage, getUser()
and every page. Omit the type parameter and the default is UserProfile, whose
id: string stays required — the profile is never inferred from authConfig
(not even from an annotated config variable), so an untyped wrapper call keeps
the default contract.
Type the config with the SAME type (const authConfig: AuthClientConfig<MyUser> = { … })
so its mapAuthResponse is checked against MyUser, and name that type at the
call (createDefaultAuthClient<MyUser>(authConfig)): a mapper that omits a
required custom field then fails at that call instead of shipping a signed-in
page that reads undefined. An annotated config alone does not supply the type
argument — that is a 1.8 caller shape this major changes (see
AuthClientConfig's migration note in @molecule/app-auth).
TRegister is the app's own registration fields ({ username: string } for a
RealWorld-shaped API), passed on to @molecule/app-auth's factory so
authClient.register({ username, email, password }) type-checks with no cast.
function createDefaultAuthClient(authConfig: NoInfer<AuthClientConfigOptions<TUser>>): {
authClient: AuthClient<TUser, TRegister>
setupAuthDefault: () => void
}createDefaultAuthClientWithHttpSync(authConfig)
Variant of createDefaultAuthClient that also keeps the bonded
@molecule/app-http client's bearer token in sync with the auth client's session.
Required by apps whose http client must carry the JWT on every request — without
this wiring, authed endpoints return 401 after page reloads or token refresh.
The sync is a LIVE read: @molecule/app-http's setAuthTokenSource registers the auth
client's token, and every client the app bonds — one bonded after this setup too (the
scaffold wires auth BEFORE http, so a token copied into the client present at setup was
never sent) — reads it on each request. A session adopted with no
auth event (useAuth().setSession(), an OAuth callback's setAccessToken(), the same
profile under a new token) is therefore sent by the next request, not the previous
account's bearer. A transport without setAuthTokenResolver is followed through a request
interceptor (see setAuthTokenSource for which request first carries the change), and
auth events still push the token to it.
A cookie session (no body token) resolves to null, so the request carries NO
Authorization header — a kept bearer would answer as the PRIOR session.
Requests also wait for the session restore, so a page that fetches viewer data on mount
cannot race it (see {@link registerAuthGate}).
The profile type is createDefaultAuthClient's: a JSON object type of named
fields (see AuthProfileShape), including an app profile with no id.
function createDefaultAuthClientWithHttpSync(authConfig: NoInfer<AuthClientConfigOptions<TUser>>): {
authClient: AuthClient<TUser, TRegister>
setupAuthDefault: () => void
}createDefaultAuthClientWithFetchClient(authConfig, fetchClientOptions)
Most aggressive variant — bonds a fetch-based HTTP client with the
given baseURL (so molecule pkg useGet('/path') calls hit
${baseURL}/path instead of the SPA's catch-all route), AND keeps
its bearer token in sync with the auth client's session, read on every
request: a session adopted with no auth event (setSession(),
setAccessToken()) is sent by the next request. The token uses the auth
config's authScheme (default Bearer); a client bonded after this setup
gets the same source and auth events.
The auth client restores a stored token AND user SYNCHRONOUSLY, so the first
render is the signed-in UI — do not gate a protected page (or a provider of
yours) on a current-user request. This bond registers whenRestored() as the
HTTP gate, so requests still wait for the background restore/verification to
settle; only useAuth().status === 'loading' means "no decision yet"
(a page that waits on its own /me pays a round trip on every navigation).
Used by apps where molecule packages render pricing / billing / other authed JSON-fetching screens that need both behaviors.
Requests are also held until the session restore settles (see {@link registerAuthGate}), so a page that fetches viewer-dependent data on mount cannot race it and answer for an anonymous viewer.
The profile type is createDefaultAuthClient's: a JSON object type of named
fields (see AuthProfileShape), including an app profile with no id.
function createDefaultAuthClientWithFetchClient(
authConfig: NoInfer<AuthClientConfigOptions<TUser>>,
fetchClientOptions: { baseURL: string; withCredentials?: boolean },
): { authClient: AuthClient<TUser, TRegister>; setupAuthDefault: () => void }bootstrapApp(opts)
Boot the React app: wire bonds, kick off auth initialization, mount
<App /> inside <StrictMode> to #root, and (optionally) register
the PWA service worker.
Replaces the 20-line per-app src/main.tsx that 97 fleet apps shipped
byte-identically.
function bootstrapApp(opts: {
App: ComponentType
authClient: { initialize: () => Promise<void> }
setupProviders: () => void | Promise<void>
registerPWA?: () => void
}): voidgetDefaultThemeProvider()
Returns (and lazily constructs) the shared default CSS-variables theme provider.
function getDefaultThemeProvider(): ThemeProvidersetupAppFontsArimo()
Wires @molecule/app-fonts-arimo to @molecule/app-fonts.
function setupAppFontsArimo(): voidsetupAppIconsMolecule()
Wires @molecule/app-icons-molecule to @molecule/app-icons.
function setupAppIconsMolecule(): voidsetupAppRoutingReactRouter()
Wires @molecule/app-routing-react-router to @molecule/app-routing.
function setupAppRoutingReactRouter(): voidsetupAppStateSimple()
Wires the framework-agnostic simpleProvider from @molecule/app-state as
the active state provider.
WITHOUT this, createStore() throws
@molecule/app-state: No provider set. Call setProvider() first — nothing
else in a scaffolded app wires the state bond, so the documented
createStore() + useStore() pattern failed at the first render and every
page came up blank while tsc passed.
function setupAppStateSimple(): voidsetupAppStorageLocalstorage()
Wires @molecule/app-storage-localstorage to @molecule/app-storage.
function setupAppStorageLocalstorage(): voidsetupAppStylingTailwind()
Wires @molecule/app-styling-tailwind: registers tailwind-merge as the
class merger for @molecule/app-styling's cn(), so conflicting Tailwind
utilities resolve (last wins). Tailwind itself is configured via env vars +
the Vite plugin; this is the one runtime hook the framework-agnostic styling
core needs so it carries no Tailwind dependency of its own.
function setupAppStylingTailwind(): voidsetupAppThemeCssVariables()
Wires the default light + dark CSS-variables theme provider to @molecule/app-theme.
function setupAppThemeCssVariables(): voidsetupAppUiTailwind()
Wires @molecule/app-ui-tailwind classMap to @molecule/app-ui.
function setupAppUiTailwind(): voidsetupAllDefaultBonds()
Wires all 8 universal app-side bonds in one call — state (the vanilla
simpleProvider, so createStore() works), fonts, routing, storage,
styling, theme, UI ClassMap, icons (in that order). Auth + i18n stay
per-app because they need app-specific config.
Replaces the individual setupX() calls in per-app bonds/index.ts.
function setupAllDefaultBonds(): voidInjection Notes
Requirements
Peer dependencies:
@molecule/app-auth^3.0.0@molecule/app-charts^1.0.1@molecule/app-charts-chartjs^1.0.1@molecule/app-client-search^1.0.0@molecule/app-client-search-minisearch^1.0.0@molecule/app-code-editor^1.0.1@molecule/app-code-editor-monaco^1.0.1@molecule/app-command-palette^1.0.1@molecule/app-command-palette-cmdk^1.0.1@molecule/app-drag-drop^1.0.1@molecule/app-drag-drop-dndkit^1.0.1@molecule/app-fonts^1.0.1@molecule/app-fonts-arimo^1.0.1@molecule/app-http^1.5.0@molecule/app-icons^1.0.1@molecule/app-icons-molecule^1.0.1@molecule/app-keyboard-shortcuts^1.0.1@molecule/app-keyboard-shortcuts-hotkeys^1.0.1@molecule/app-maps^1.0.1@molecule/app-maps-leaflet^1.0.1@molecule/app-realtime^1.0.1@molecule/app-realtime-socketio^1.0.1@molecule/app-routing^1.0.1@molecule/app-routing-react-router^1.0.1@molecule/app-state^1.0.1@molecule/app-storage^1.0.1@molecule/app-storage-localstorage^1.0.1@molecule/app-styling-tailwind^1.0.1@molecule/app-theme^1.0.1@molecule/app-theme-css-variables^1.0.1@molecule/app-ui^1.0.1@molecule/app-ui-tailwind^1.0.1@molecule/app-video^1.0.1@molecule/app-video-hls^1.0.1@molecule/app-virtual-scroll^1.0.1@molecule/app-virtual-scroll-tanstack^1.0.1react^18.0.0 || ^19.0.0react-dom^18.0.0 || ^19.0.0
Runtime Dependencies
@molecule/app-auth@molecule/app-charts@molecule/app-charts-chartjs@molecule/app-client-search@molecule/app-client-search-minisearch@molecule/app-code-editor@molecule/app-code-editor-monaco@molecule/app-command-palette@molecule/app-command-palette-cmdk@molecule/app-drag-drop@molecule/app-drag-drop-dndkit@molecule/app-fonts@molecule/app-fonts-arimo@molecule/app-http@molecule/app-icons@molecule/app-icons-molecule@molecule/app-keyboard-shortcuts@molecule/app-keyboard-shortcuts-hotkeys@molecule/app-maps@molecule/app-maps-leaflet@molecule/app-realtime@molecule/app-realtime-socketio@molecule/app-routing@molecule/app-routing-react-router@molecule/app-state@molecule/app-storage@molecule/app-storage-localstorage@molecule/app-styling-tailwind@molecule/app-theme@molecule/app-theme-css-variables@molecule/app-ui@molecule/app-ui-tailwind@molecule/app-video@molecule/app-video-hls@molecule/app-virtual-scroll@molecule/app-virtual-scroll-tanstackreactreact-domOptional setups are NOT exported from the package root. Import each from its own subpath —
@molecule/app-bonds-default-react/optional/maps-leaflet.js, notfrom '@molecule/app-bonds-default-react'. A bundler must RESOLVE everyimport()in a module it pulls into the graph, before tree-shaking can drop anything, so while these lived in the barrel every app inherited all 18 optional providers and any app that had not installed all of them failed to build withRolldown failed to resolve import "@molecule/app-maps". Only import a subpath whose provider pair the app actually installs.setupAllDefaultBonds()does NOT wire the optional bonds. Those ship as separate ASYNC setups —setupAppRealtimeSocketio,setupAppKeyboardShortcutsHotkeys,setupAppCommandPaletteCmdk,setupAppCodeEditorMonaco,setupAppVirtualScrollTanstack,setupAppDragDropDndkit,setupAppChartsChartjs,setupAppMapsLeaflet,setupAppVideoHls— and MUST be awaited insidesetupProviders(make it async).bootstrapAppawaitssetupProviders()before mounting so bonded providers exist by a component's first effect; a fire-and-forget async setup races the mount and intermittently loses.Plain
createDefaultAuthClientdoes NOT attach the JWT to the bonded http client. If molecule packages call authed/apiendpoints, usecreateDefaultAuthClientWithHttpSync(or...WithFetchClientto also bond a fetch client with abaseURL) — otherwise those endpoints return 401 after a page reload or token refresh.getDefaultThemeProvider()constructs lazily because the CSS-variables theme provider toucheslocalStorageat construction — importing this package is SSR/test-safe, but only CALL it in a DOM environment. Apps with custom themes build their own provider and skipsetupAppThemeCssVariables().
