/** * `createDatingApp` — opinionated `createActiveApp` composition for the * Nexo demo. Picks a fixed service schema so consumer components can * type their `App` prop as `DatingApp` and get autocomplete on * `App.lang`, `App.session`, `App.cache`, … without falling back to * `unknown`. * * Contract intentionally narrower than the README's full list: `auth`, * `perm` and `connection` need port wiring that lives outside Phase 0 * scope (auth flows go through `DatingApiClient` directly while the * artifact-level `App.auth` waits for a Nexo-shaped HTTP port). They * will be added by their respective phases — see * `src/web/routes/dating/plan-implementacion.md`. */ import { createActiveApp, type ActiveApp } from '$active-app'; import { defineActiveCache, defineActiveFormat, defineActiveFrontend, defineActiveLang, defineActivePrefs, defineActiveSession, defineActiveStorage, defineEngineHttp, defineEngineSium } from '$active-app/services'; import type { PrefsCapabilities } from '$libs/prefs'; import type { DatingUser } from './types.ts'; import { createDatingApiClient, type DatingApiClient } from './api.ts'; /** * Bootstrap lang schema. Phase 0 ships only the strings the shell * needs; later phases register their own namespaces via * `App.lang.extend(...)` rather than threading more keys through * here. */ export const NEXO_LANG_SCHEMA = { nexo: { title: { es: 'Nexo', en: 'Nexo' }, tagline: { es: 'Demo del ecosistema Active sobre un producto real.', en: 'Active ecosystem demo running on a real product.' }, nav: { discover: { es: 'Descubre', en: 'Discover' }, matches: { es: 'Matches', en: 'Matches' }, profile: { es: 'Perfil', en: 'Profile' }, safety: { es: 'Seguridad', en: 'Safety' }, devtools: { es: 'Devtools', en: 'Devtools' } }, auth: { login: { es: 'Iniciar sesión', en: 'Sign in' }, register: { es: 'Crear cuenta', en: 'Sign up' }, logout: { es: 'Cerrar sesión', en: 'Sign out' } } } } as const; export const NEXO_PREFS_CAPABILITIES: PrefsCapabilities = { languages: ['es', 'en'], locales: ['es-ES', 'en-US'], currencies: ['EUR', 'USD'], unitSystems: ['metric', 'imperial'], themes: ['light', 'dark', 'system'], densities: ['compact', 'comfortable', 'spacious'], motions: ['allow', 'reduce', 'system'], defaults: { language: 'es', locale: 'es-ES', currency: 'EUR', timezone: 'Europe/Madrid', unitSystem: 'metric', theme: 'light', density: 'comfortable', motion: 'allow', direction: 'ltr' } }; export interface CreateDatingAppOptions { /** * Override the Nexo API base URL. Defaults to the standalone server * at `http://127.0.0.1:8787` (see `servers/dating/README.md`). */ readonly apiBase?: string; /** * Optional `fetch` override — typically `event.fetch` during SSR or * a mocked transport in tests. */ readonly fetch?: typeof fetch; /** * Frontend mount target. SvelteKit pages pass the layout root here * so theme / density / direction get applied to the right node. */ readonly frontendTarget?: () => HTMLElement | null; } /** * Concrete `ActiveApp` shape exposed by `createDatingApp`. Components * accept `App: DatingApp` to keep autocomplete and type-checked * service access; using a generic `ActiveApp` here would degrade * every service slot to `unknown`. */ export type DatingApp = ReturnType; function composeApp(options: CreateDatingAppOptions) { return createActiveApp({ services: { lang: defineActiveLang({ schema: NEXO_LANG_SCHEMA, defaultLocale: 'es', fallbackChain: ['en'] }), prefs: defineActivePrefs({ capabilities: NEXO_PREFS_CAPABILITIES }), storage: defineActiveStorage({ namespace: 'nexo' }), frontend: defineActiveFrontend({ target: options.frontendTarget, theme: 'base', mode: 'auto', density: 'comfortable' }), format: defineActiveFormat(), cache: defineActiveCache({ defaultMemoryAdapter: { suppressProductionWarning: true } }), http: defineEngineHttp({ fetch: options.fetch, timeout: 10_000, retry: { limit: 1 } }), session: defineActiveSession({ schemas: {} }), sium: defineEngineSium() } }); } /** * Compose a `DatingApp` plus the Nexo HTTP client. The two are * returned together because almost every component needs both — * `App` for ecosystem state and `api` for typed server calls — and * keeping the construction in one helper means the demo only has one * canonical wiring point. * * Disposing the wrapper tears the App down; the API client is * stateless and needs no lifecycle. */ export interface DatingAppHandle { readonly App: DatingApp; readonly api: DatingApiClient; dispose(): void; } export function createDatingApp(options: CreateDatingAppOptions = {}): DatingAppHandle { const App = composeApp(options); const api = createDatingApiClient({ base: options.apiBase, fetch: options.fetch }); return { App, api, dispose: () => App.dispose() }; } /** * Marker re-export so consumers that only need the type don't pull * the runtime in just for `ActiveApp`. */ export type { ActiveApp };