You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/web/routes/active/docs/aapp/+page.svelte

338 lines
14 KiB

<script lang="ts">
import CodeBlock from '../../_components/CodeBlock.svelte';
import Callout from '../../_components/Callout.svelte';
import AiAgentsBox from '../../_components/AiAgentsBox.svelte';
import ModuleHeader from '../../_components/ModuleHeader.svelte';
import PageNav from '../../_components/PageNav.svelte';
const composition = `import { createActiveApp } from '$active-app';
import {
defineActiveLangs,
defineActiveFormat,
defineActiveDom,
defineActiveCache,
defineActiveSession,
defineActivePerm,
defineActiveConnections
} from '$active-app/services';
import { createActivePrefsDomProjection } from '$prefs';
import { applyStandardOrca } from '$active-app/presets';
import { LogLevel, consoleTransport } from '$logger';
export const App = createActiveApp({
logger: { level: LogLevel.INFO, transports: [consoleTransport()] },
orca: { maxDepth: 24 },
prefs: { capabilities, environment, intent },
services: {
dom: defineActiveDom(),
langs: defineActiveLangs({ schema, defaultLocale: 'es', fallbackChain: ['en'] }),
format: defineActiveFormat(),
cache: defineActiveCache(),
session: defineActiveSession({ schemas, storage, onRefresh, onRevoke }),
perm: defineActivePerm({ endpoint: '/api/perm' }),
connections: defineActiveConnections()
}
});
export const prefsProjection = createActivePrefsDomProjection({
prefs: App.prefs,
dom: App.dom
});
applyStandardOrca(App);`;
const access = `App.logger.info('checkout.paid', { orderId });
App.prefs.locale.set('es-MX');
App.langs.t('common.ok');
App.format.currency.format(99.5);
App.cache.clear();`;
const factories = `import {
defineActiveLangs,
defineActiveFormat,
defineActiveDom,
defineActiveStorage,
defineEngineHttp,
defineEngineSium,
defineActiveCache,
defineActiveSession,
defineActiveAuth,
defineActivePerm,
defineActiveConnections
} from '$active-app/services';
const App = createActiveApp({
prefs: { capabilities, environment },
services: {
storage: defineActiveStorage(),
http: defineEngineHttp({ baseUrl: '/api' }),
dom: defineActiveDom(),
langs: defineActiveLangs({ schema }),
format: defineActiveFormat(),
sium: defineEngineSium({}),
cache: defineActiveCache(),
session: defineActiveSession({ schemas, storage, onRefresh, onRevoke }),
auth: defineActiveAuth({ endpoint: '/api/auth' }),
perm: defineActivePerm({ endpoint: '/api/perm' }),
connections: defineActiveConnections()
}
});`;
const eventFlow = `App.session publishes SESSION_EVENT_IDENTITY_CHANGED on App.bus
App.orca receives the event through registered presets
-> applyCacheClearOnIdentityChange calls App.cache.clear()
-> applyPermInvalidateOnIdentityChange calls App.perm.invalidate()
-> applyConnectionsReauthOnIdentityChange calls App.connections.reauthenticateAll()
No preset registered, no destructive reaction runs.`;
const disposal = `import { onDestroy } from 'svelte';
onDestroy(() => App.dispose());`;
const commonMistakes = [
{
name: 'Treating App as a domain service',
why: 'The composition root becomes a mixed business object and module ownership disappears.',
fix: 'Keep business behavior inside the artifact that owns it; App only wires runtime pieces.'
},
{
name: 'Expecting undeclared services to exist',
why: 'Only core members are always present. Services are exposed only when declared in the schema.',
fix: 'Declare each required module under services and let TypeScript enforce the App shape.'
},
{
name: 'Bypassing prefs for user intent',
why: 'Lang, Format and DOM projection can disagree about language, locale or direction.',
fix: 'Use App.prefs.<dim>.set(value) (e.g. App.prefs.locale.set("es-ES")); downstream services follow the effective snapshot.'
},
{
name: 'Putting reactions inside Bus listeners by hand',
why: 'Lifecycle behavior becomes invisible and hard to test.',
fix: 'Register orca presets in $active-app/presets or add explicit App.orca actions.'
},
{
name: 'Importing $active-app as server authority',
why: 'App is client/runtime composition; server trust belongs to engines and $svrs.',
fix: 'Use $svrs/auth, $svrs/perm, $svrs/cache and pure Engine factories from server files.'
}
] as const;
const aiAgentRows = [
{
step: 'Composition surface',
where: 'src/arts/active-app/types.ts and src/arts/active-app/active-app.svelte.ts',
rule: 'Update root options, core getters and service-schema typing together.'
},
{
step: 'Fixed core',
where: 'createActiveApp()',
rule: 'Logger, Bus, Timers, Orca and Prefs are always present and never declared as services.'
},
{
step: 'Service factories',
where: 'src/arts/active-app/service-factories/*.ts',
rule: 'Each factory adapts one artifact to App; artifacts must not import App.'
},
{
step: 'Orchestration',
where: 'src/arts/active-app/presets/*.ts',
rule: 'Cross-module reactions belong to Orca presets, not hidden auto-subscribers inside Cache, Perm or Connections.'
},
{
step: 'Server boundary',
where: 'src/svrs/*',
rule: '$active-app is the client composition root. Server authority belongs in $svrs and shared contracts belong in $libs.'
},
{
step: 'Tests',
where: 'src/arts/active-app/test',
rule: 'Composition, service ordering, prefs consumer wiring and orca presets need integration coverage.'
}
] as const;
</script>
<svelte:head>
<title>App ($active-app) - Active</title>
</svelte:head>
<article class="article">
<ModuleHeader
section="Composition"
title="App"
alias="$active-app"
summary="Client composition root: fixed Logger, Bus, Timers, Orca and Prefs core plus a typed opt-in service schema."
factories={['createActiveApp']}
dependsOn={['$logger', '$bus', '$timer', '$orca', '$prefs', '$active-app/services']}
layer="ActiveApp"
/>
<h2>Overview</h2>
<p>
<code>$active-app</code> is the runtime composition root. It always builds five core
members - <code>App.logger</code>, <code>App.bus</code>, <code>App.timers</code> and
<code>App.orca</code>, plus <code>App.prefs</code> - and then exposes only the services declared by the application.
The old model where Lang, Format, Frontend, Dom, Storage, Http, Cache and Prefs were
always-present roots is gone.
</p>
<h2>Mental model</h2>
<p>
App is wiring, not business logic. It creates the core, adapts artifacts through service
factories, computes the service dependency order, exposes typed getters, and owns teardown.
Cross-module behavior is explicit: modules publish events on <code>App.bus</code>, while
<code>App.orca</code> runs the registered reactions.
</p>
<Callout variant="info" title="The schema is the App contract">
<p>
If a service is not declared in <code>services</code>, it is not part of the App type.
This keeps feature surfaces honest: a checkout app can declare cache and session, while
a static marketing page can keep only the fixed core.
</p>
</Callout>
<h2>Quick start</h2>
<CodeBlock code={composition} lang="ts" title="src/lib/app.ts" />
<p>Then read or mutate declared services directly:</p>
<CodeBlock code={access} lang="ts" />
<h2>Fixed core</h2>
<table>
<thead>
<tr>
<th>Member</th>
<th>Configured from</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr><td><code>App.logger</code></td><td><code>logger</code></td><td>Engine logger. Injected into core-aware service factories.</td></tr>
<tr><td><code>App.bus</code></td><td><code>bus</code></td><td>Typed Svelte-safe event bus. App injects logger and clock.</td></tr>
<tr><td><code>App.timers</code></td><td><code>timers</code></td><td>Active timer scheduler. Used by services and Orca.</td></tr>
<tr><td><code>App.orca</code></td><td><code>orca</code></td><td>Orchestration engine. App injects bus, timers and logger.</td></tr>
<tr><td><code>App.prefs</code></td><td><code>prefs</code></td><td>Preference engine. Always present; configured from root <code>prefs</code> options or neutral defaults.</td></tr>
</tbody>
</table>
<h2>Service schema</h2>
<p>
Services are adapted by <code>$active-app/services</code>. Factories declare the core
dependencies they consume, the services they can read, and whether they build lazily or
immediately. The builder validates names, detects cycles, builds dependencies first and
disposes constructed services in reverse construction order.
</p>
<CodeBlock code={factories} lang="ts" />
<table>
<thead>
<tr>
<th>Slot</th>
<th>Factory</th>
<th>Dependency behavior</th>
</tr>
</thead>
<tbody>
<tr><td><code>lang</code></td><td><code>defineActiveLangs()</code></td><td>Consumes <code>logger</code> and <code>prefs</code> from the core; follows <code>App.prefs.language.get()</code>.</td></tr>
<tr><td><code>format</code></td><td><code>defineActiveFormat()</code></td><td>Consumes <code>timers</code> and <code>prefs</code> from the core; resolves locale from explicit source or prefs.</td></tr>
<tr><td><code>frontend</code></td><td><code>defineActiveFrontend()</code></td><td>Legacy opt-in presentation shell. New UIX code uses prefs projection plus Eidos.</td></tr>
<tr><td><code>dom</code></td><td><code>defineActiveDom()</code></td><td>DOM integration, isolated as a service.</td></tr>
<tr><td><code>storage</code></td><td><code>defineActiveStorage()</code></td><td>Storage runtime, typically used by session and prefs persistence bridges.</td></tr>
<tr><td><code>http</code></td><td><code>defineEngineHttp()</code></td><td>Pure HTTP engine adapted into the schema.</td></tr>
<tr><td><code>sium</code></td><td><code>defineEngineSium()</code></td><td>Validation engine; uses lang when present.</td></tr>
<tr><td><code>cache</code></td><td><code>defineActiveCache()</code></td><td>Cache runtime. Identity clears are Orca presets.</td></tr>
<tr><td><code>session</code></td><td><code>defineActiveSession()</code></td><td>Publishes typed session lifecycle events on App.bus.</td></tr>
<tr><td><code>auth</code></td><td><code>defineActiveAuth()</code></td><td>Client auth reflector for server-backed flows.</td></tr>
<tr><td><code>perm</code></td><td><code>defineActivePerm()</code></td><td>Permission reflector. Identity invalidation is an Orca preset.</td></tr>
<tr><td><code>connections</code></td><td><code>defineActiveConnections()</code></td><td>Realtime connection registry. Identity reauth and revoke close are Orca presets.</td></tr>
</tbody>
</table>
<h2>Prefs propagation</h2>
<p>
<code>App.prefs</code> is always present and is the core source for user intent.
<code>lang</code> follows <code>App.prefs.language.get()</code>,
<code>format</code> follows <code>App.prefs.locale.get()</code>, and
shared prefs such as direction, motion, sound and haptic can be projected explicitly
through <code>createActivePrefsDomProjection()</code>.
</p>
<h2>Bus and Orca</h2>
<p>
<code>App.bus</code> is the event transport. <code>App.orca</code> is the policy runner.
This separation matters: a module can publish a lifecycle event without silently clearing
cache, invalidating permissions or reconnecting sockets. Those reactions exist only when
the application registers presets from <code>$active-app/presets</code>.
</p>
<CodeBlock code={eventFlow} lang="txt" title="Identity event flow" />
<table>
<thead>
<tr>
<th>Preset</th>
<th>Event</th>
<th>Effect</th>
</tr>
</thead>
<tbody>
<tr><td><code>applyCacheClearOnIdentityChange</code></td><td><code>SESSION_EVENT_IDENTITY_CHANGED</code></td><td><code>App.cache.clear()</code></td></tr>
<tr><td><code>applyCacheClearOnRevoke</code></td><td><code>SESSION_EVENT_REVOKED</code></td><td><code>App.cache.clear()</code></td></tr>
<tr><td><code>applyPermInvalidateOnIdentityChange</code></td><td><code>SESSION_EVENT_IDENTITY_CHANGED</code></td><td><code>App.perm.invalidate()</code></td></tr>
<tr><td><code>applyConnectionsReauthOnIdentityChange</code></td><td><code>SESSION_EVENT_IDENTITY_CHANGED</code></td><td><code>App.connections.reauthenticateAll()</code></td></tr>
<tr><td><code>applyConnectionsCloseOnRevoke</code></td><td><code>SESSION_EVENT_REVOKED</code></td><td><code>App.connections.closeAll()</code></td></tr>
<tr><td><code>applySessionAutoRefresh</code></td><td>Timer action</td><td>Schedules session refresh through <code>App.orca</code>.</td></tr>
</tbody>
</table>
<Callout variant="warn" title="Public payloads only">
<p>
Bus payloads are observable contracts. Do not put tokens, passwords, authorization
headers, refresh secrets or sensitive hashes in them. Use actor ids, tenant ids,
causes and correlation ids.
</p>
</Callout>
<h2>Disposal</h2>
<p>
<code>App.dispose()</code> publishes the dispose-starting event, disposes all constructed
services in reverse construction order, tears down the prefs storage bridge, then disposes
<code>Prefs</code>, <code>Orca</code>, <code>Bus</code>, <code>Timers</code> and
<code>Logger</code>. Subsequent calls are no-ops.
</p>
<CodeBlock code={disposal} lang="ts" />
<h2>Common mistakes</h2>
<table>
<thead>
<tr>
<th>Mistake</th>
<th>Why it hurts</th>
<th>Correct pattern</th>
</tr>
</thead>
<tbody>
{#each commonMistakes as mistake (mistake.name)}
<tr>
<td><code>{mistake.name}</code></td>
<td>{mistake.why}</td>
<td>{mistake.fix}</td>
</tr>
{/each}
</tbody>
</table>
<h2>Testing</h2>
<p>
Suites under <code>src/arts/active-app/test</code> cover schema construction, service
ordering, prefs consumer wiring, orca presets, session auto refresh and cross-actor
isolation.
</p>
<AiAgentsBox
intro="Before editing $active-app, verify the fixed core, the service schema and the Orca preset boundary."
rows={aiAgentRows}
/>
<PageNav />
</article>

Powered by TurnKey Linux.