notes:'Injected only when the app enables those flows.'
},
{
name:'ports.rateLimit',
purpose:'Server-side abuse protection.',
notes:'Cuts password, recovery and OAuth flows before touching store/provider/mailer.'
},
{
name:'providers',
purpose:'OAuth/OIDC provider adapters.',
notes:'Provider tokens are not persisted unless an adapter does so explicitly.'
},
{
name:'hooks.beforeEvent / hooks.afterEvent',
purpose:'Security event interception.',
notes:'Useful for audit, metrics and custom side effects.'
}
]
}
],
buss:[
{
title:'EngineBus',
body:[
'The bus is a mechanical typed event engine. It creates envelopes, invokes listeners and reports listener failures; it does not decide application orchestration by itself.'
],
table:[
{
name:'publish(type, payload, options?)',
purpose:'Synchronously publish an event.',
notes:'Returns the envelope and collected listener failures.'
},
{
name:'publishAsync(type, payload, options?)',
purpose:'Publish and await async listeners.',
notes:'Useful when tests or orchestration need deterministic completion.'
},
{
name:'on(type, listener, options?)',
purpose:'Subscribe to one event type.',
notes:'Options support signal, once and listener id.'
},
{
name:'once(type, listener, options?)',
purpose:'Subscribe for one delivery.',
notes:'Removes the listener before invoking it.'
},
{
name:'onAny(listener, options?)',
purpose:'Subscribe to every event.',
notes:'For diagnostics, test capture and devtools-like surfaces.'
},
{
name:'listenerCount(type?)',
purpose:'Inspect listener counts.',
notes:'Helps detect leaks in long-lived apps and tests.'
},
{
name:'dispose()',
purpose:'Remove listeners and close the bus.',
notes:'Further public operations throw BusDisposedError.'
}
]
},
{
title:'EngineBusOptions',
table:[
{
name:'logger',
purpose:'Shared Logger contract.',
notes:'App injects App.logger into App.bus.'
},
{
name:'clock',
purpose:'Timestamp source.',
notes:'App injects Timers.clock for deterministic tests.'
},
{
name:'idFactory',
purpose:'Envelope id generation.',
notes:'Defaults to incremental bus-* ids.'
},
{
name:'maxListenersPerEvent',
purpose:'Leak warning threshold.',
notes:'Default is BUS_DEFAULT_MAX_LISTENERS_PER_EVENT.'
},
{
name:'listenerErrorMode',
purpose:'Default listener failure policy.',
notes:'log-and-continue, throw or collect; publish options can override it per event.'
}
]
},
{
title:'BusEnvelope',
table:[
{
name:'id / type / payload / at',
purpose:'Core event facts.',
notes:'Every event is wrapped before listeners receive it.'
},
{
name:'source',
purpose:'Publisher identity.',
notes:'Defaults to bus; module publishers can provide their own source.'
},
{
name:'correlationId / causationId',
purpose:'Trace event chains.',
notes:'Useful for cross-module integration tests and diagnostics.'
},
{
name:'context / tags',
purpose:'Non-secret metadata.',
notes:'Do not put credentials, tokens or authorization headers in public app events.'
}
]
}
],
sess:[
{
title:'EngineSession / ActiveSession',
body:[
'ActiveSession is the reactive facade over the same session contract; both expose the same lifecycle methods.'
],
table:[
{
name:'current',
purpose:'Current session object or null.',
notes:'Contains user, credential and data slots.'
},
{name:'identity',purpose:'Identity state.',notes:'none, anonymous or identified.'},
{
name:'generation',
purpose:'Monotonic local change counter.',
notes:'Useful for cache keys and UI invalidation.'
},
{
name:'adopt(session)',
purpose:'Adopt a client-provided session after schema validation.',
notes:'Use for client-side session updates.'
},
{
name:'adoptServer(session)',
purpose:'Adopt SSR-trusted session.',
notes:'Bypasses client schema distrust because server already validated.'
},
{
name:'refresh()',
purpose:'Deduped refresh operation.',
notes:'Keeps current session if refresh throws; clears if refresh returns null.'
},
{
name:'revoke(options?)',
purpose:'End local/global session.',
notes:'Calls onRevoke when configured.'
},
{
name:'clearLocal(reason?)',
purpose:'Clear local state without server revoke.',
notes:'Use when server already invalidated the session.'
},
{
name:'onChange(listener)',
purpose:'Subscribe to lifecycle changes.',
notes:'Local lifecycle stream. App.bus integration uses safe session.* events when bus is injected.'
},
{name:'dispose()',purpose:'Stop timers/listeners.',notes:'Called by App.dispose().'}
]
},
{
title:'EngineSessionOptions',
table:[
{
name:'schemas',
purpose:'Optional Standard Schema validation for user/credential/data.',
notes:'Protects client-provided adoption.'
},
{
name:'storage',
purpose:'Storage entry config.',
notes:'Typically App.storage with local/session/cookie adapter.'
},
{
name:'onRefresh',
purpose:'Server refresh callback.',
notes:'Returns next session or null.'
},
{
name:'onRevoke',
purpose:'Server revoke callback.',
notes:'Can degrade to local revoke if remote fails.'
},
{name:'logger',purpose:'Shared Logger contract.',notes:'Injected by App.'},
{
name:'bus',
purpose:'Optional EventPublisher<SessEventMap>.',
notes:'App injects App.bus so session.* events can be published directly; cross-module reactions are handled by Orca presets.'
},
{
name:'broadcastChannel',
purpose:'Cross-tab session propagation.',
notes:'Optional browser integration.'
}
]
}
],
cache:[
{
title:'EngineCache / CacheRuntime',
body:[
'The server engine extends the pure CacheRuntime from $libs/cache and adds disposal/diagnostics.'
],
table:[
{
name:'query(options)',
purpose:'Read through cache with fetcher.',
notes:'Evaluates freshness, scope, policy, epochs and stale-if-error.'
},
{
name:'get(key, options)',
purpose:'Read cached value only.',
notes:'Returns undefined on miss/expired/invalid.'
},
{
name:'set(key, value, options)',
purpose:'Write an envelope.',
notes:'Stores scope, tags, policy windows and schemaVersion.'
},
{
name:'invalidate(options)',
purpose:'Invalidate by key, keyPrefix or tag.',
notes:'Uses epoch bumping instead of scanning every entry.'
},
{
name:'mutate(options)',
purpose:'Run commit plus cache updates/invalidations.',
notes:'Useful after writes.'
},
{
name:'explain(key, options)',
purpose:'Debug cache decision.',
notes:'Shows action, reason, scope, timings and epoch comparison.'
},
{
name:'stats()',
purpose:'Read event counters.',
notes:'Hit/miss/stale/refresh/error counters.'
},
{
name:'on(type, handler)',
purpose:'Subscribe to cache events.',
notes:'Use CACHE_EVENT_ALL for every event.'
},
{
name:'clear()',
purpose:'Clear adapter if supported.',
notes:'Falls back according to adapter capability.'
},
{
name:'dispose()',
purpose:'Close engine and reject future calls.',
notes:'ActiveCache calls this automatically.'
}
]
},
{
title:'ActiveCache',
table:[
{
name:'orca presets',
purpose:'Cross-module reactions to lifecycle events.',
notes:'Cache itself is passive. Wire applyCacheClearOnIdentityChange / applyCacheClearOnRevoke (or applyStandardOrca) at the App level to clear on session events.'
},
{
name:'lastEvent / eventCount',
purpose:'Reactive event summary.',
notes:'Useful for debug panels.'
},
{
name:'loading / lastError / disposed',
purpose:'ActiveEngine state.',
notes:'Tracks active operations.'
},
{
name:'entry(options)',
purpose:'Create an ActiveCacheEntry.',
notes:'Entry wraps query/set/invalidate with local status.'
},
{
name:'snapshot() / onChange() / clearError()',
purpose:'Active lifecycle.',
notes:'Same convention as other active roots.'
}
]
},
{
title:'ActiveCacheEntry',
table:[
{
name:'data / error / status / loading / updatedAt',
purpose:'Reactive entry state.',
notes:'Status: idle, loading, success, stale, refreshing, degraded or error.'
},
{name:'load()',purpose:'Initial query.',notes:'Uses the entry QueryOptions.'},
{
name:'refresh()',
purpose:'Force reload through query.',
notes:'Keeps entry state coordinated.'
},
{
name:'set(value, options?)',
purpose:'Write entry value.',
notes:'Scope comes from the entry options.'
},
{
name:'invalidate()',
purpose:'Invalidate this key.',
notes:'Keeps tags/prefix policies in the runtime.'
},
{
name:'snapshot() / onChange() / dispose()',
purpose:'Entry lifecycle.',
notes:'Dispose removes it from the ActiveCache registry.'
}
]
}
],
stor:[
{
title:'EngineStorage',
table:[
{
name:'adapter',
purpose:'Default SyncStorageAdapter.',
notes:'localAdapter, sessionAdapter, cookieAdapter or custom.'
},
{
name:'namespace',
purpose:'Optional key prefix.',
notes:'Entry options can override with namespace or false.'
},
{
name:'entry(key, defaults, options?)',
purpose:'Create a StorageEntry.',
notes:'Defaults can be a value or factory.'
},
{
name:'entries()',
purpose:'List created entries.',
notes:'Engine-owned registry, not adapter scan.'
},
{
name:'clear()',
purpose:'Remove known entries.',
notes:'Does not blindly wipe unrelated storage.'
},
{
name:'dispose()',
purpose:'Dispose root and entries.',
notes:'Rejects future root operations.'
}
]
},
{
title:'StorageEntry / ActiveStorageEntry',
table:[
{
name:'key / fullKey',
purpose:'Logical and adapter key.',
notes:'fullKey includes namespace unless disabled.'
},
{
name:'get()',
purpose:'Read parsed value.',
notes:'Applies envelope, ttl, version, migrate, mergeDefaults and validate.'
},
{
name:'set(value)',
purpose:'Serialize and write.',
notes:'Active entry also updates current.'
},
{
name:'update(fn)',
purpose:'Read-modify-write.',
notes:'Preferred over deep mutations.'
},
{
name:'remove()',
purpose:'Delete adapter value and memory returns to default.',
notes:'Different from reset().'
},
{
name:'reset()',
purpose:'Write default value to adapter.',
notes:'Useful for explicit user reset.'
},
{
name:'has()',
purpose:'Check whether adapter has a stored value.',
notes:'Independent from current default.'
},
{
name:'current',
purpose:'Reactive ActiveStorageEntry value.',
notes:'Not a deep persistence proxy; use set/update.'
},
{
name:'onChange(listener)',
purpose:'Subscribe to active value changes.',
notes:'Active entries only.'
},
{
name:'dispose()',
purpose:'Detach sync listeners.',
notes:'Important for per-page entries.'
}
]
},
{
title:'StorageEntryOptions',
table:[
{
name:'adapter / namespace',
purpose:'Override root storage per entry.',
notes:'Use cookies for locale/theme, local for drafts, session for wizards.'
},
{
name:'serializer',
purpose:'Custom parse/stringify.',
notes:'Auto-selected for primitives, Date, Set, Map and JSON objects.'
},
{
name:'version / migrate',
purpose:'Envelope versioning.',
notes:'If version differs, migrate must return the new shape.'
},
{
name:'validate',
purpose:'Function or Standard Schema validation.',
notes:'Rejected values fall back safely and report onError.'
},
{
name:'mergeDefaults',
purpose:'Evolve object shapes.',
notes:'Boolean shallow merge or custom merge function.'
},
{
name:'ttlMs / raw / writeDefaults / syncTabs',
purpose:'Expiry and persistence behavior.',
notes:'raw disables envelope features by design.'
}
]
}
],
http:[
{
title:'EngineHttp',
table:[
{
name:'with(options)',
purpose:'Create scoped child client.',
notes:'Use with event.fetch in SvelteKit server loads.'
},
{
name:'get(url, options?) / head() / options()',
purpose:'Read-oriented methods.',
notes:'GET can use query, schema, timeout, hooks.'
},
{
name:'post() / put() / patch() / delete()',
purpose:'Mutation methods.',
notes:'Body, bodySchema and schema are validated through Standard Schema.'
},
{
name:'hooks',
purpose:'Before/after request/result hooks.',
notes:'Session rescue, tracing and auth headers live here.'
},
{
name:'retry / timeout / totalTimeout',
purpose:'Resilience controls.',
notes:'Retry respects method/idempotency and Retry-After.'
}
]
},
{
title:'HttpResult',
table:[
{
name:'{ ok: true, value, response }',
purpose:'Successful validated response.',
notes:'value is parsed/validated payload.'
},
{
name:'http_error',
purpose:'Non-2xx response.',
notes:'Status, headers and parsed body stay available.'
},
{
name:'validation_error',
purpose:'Schema rejected payload.',
notes:'Contains validation issues.'
},
{
name:'network_error',
purpose:'Fetch threw before response.',
notes:'Original error normalized.'
},
{
name:'timeout',
purpose:'Abort by timeout.',
notes:'Per-attempt and total timeouts are separate.'
}
]
},
{
title:'EngineHttpOptions',
table:[
{
name:'baseUrl / headers / fetch',
purpose:'Request defaults.',
notes:'Pass SvelteKit event.fetch on the server.'
},
{
name:'timeout / totalTimeout / retry',
purpose:'Failure policy.',
notes:'Avoid per-call magic constants.'
},
{
name:'hooks',
purpose:'Composable request/result middleware.',
notes:'No direct coupling to session/auth/perm.'
},
{name:'logger',purpose:'Shared Logger contract.',notes:'Injected by App.'}
]
}
],
fmts:[
{
title:'EngineFormat / ActiveFormat',
table:[
{
name:'numbers',
purpose:'Numbers sub-engine.',
notes:'format, parse, percent, compact and unit helpers.'
},
{
name:'currency',
purpose:'Currency sub-engine.',
notes:'Currency resolution, formatting and optional conversion.'
},
{
name:'units',
purpose:'Units sub-engine.',
notes:'System defaults, conversion and default-unit formatting.'
},
{
name:'dates',
purpose:'Dates sub-engine.',
notes:'Date order, hour cycle and Intl DateTime formatting.'
},
{
name:'getLocale() / setLocale(locale)',
purpose:'Shared locale control.',
notes:'Active version usually receives localeSource from App.'
notes:'App injects Logger and Timers automatically.'
},
{
name:'session',
purpose:'ConnectionSessionSource (abstract { onChange } interface) for advanced/manual wiring.',
notes:'Optional. The canonical pattern wires reauth/close as orca actions via applyConnectionsReauthOnIdentityChange / applyConnectionsCloseOnRevoke (or applyStandardOrca) — this option is for standalone / per-connection setups outside the App composition.'
}
]
},
{
title:'Connection',
table:[
{
name:'state / connected / error / generation',
purpose:'Connection state.',
notes:'generation changes on lifecycle transitions.'
body:'Auth proves identity. Session keeps continuity. Perms decide access. Storage must not persist secrets.'
},
overview:[
'Auth is split into a shared language package, a server-authoritative engine and an active Svelte client. The server engine owns identity proof, CSRF, password/recovery flows, OAuth/MFA primitives, device methods and security events. The active client currently exposes current, password, recovery, email verification, sign-out and device methods over HTTP.',
'Use Auth when the app needs to sign users in or out, load the current actor, request verification or reset flows, and bind that identity proof to session state. Do not use Auth to decide permissions or to store long-lived credentials in the browser.',
'In an App composition, Auth receives App.logger from the core and talks to server auth routes through the HTTP client supplied in its options. Cache invalidation is not wired inside Auth; it belongs to Orca presets.'
],
dynamics:[
'The server creates an EngineAuth with ports. Those ports are the real integration points: store persists auth records, actors maps credentials to actor refs, session starts or ends sessions, cache clears identity-scoped data, and logger records security events.',
'The browser creates ActiveAuth only as a reflector. It loads /current, sends CSRF-protected commands to server routes, updates current after successful responses and emits local state changes for UI. A protected server action must never trust ActiveAuth state.',
'The normal request path is: server hook resolves current auth, page load serializes a safe AuthCurrentView, ActiveAuth hydrates that snapshot, user triggers sign-in/out, server mutates session, and any cross-module reactions run only when the app registers the corresponding Orca presets.'
],
commonMistakes:[
{
name:'checking Auth.authenticated on the server',
purpose:'ActiveAuth is browser state and can be stale or manipulated.',
notes:'Resolve current auth in server hooks/load/actions through $svrs/auth.'
},
{
name:'putting permissions inside auth callbacks',
purpose:'It mixes identity proof with authorization and becomes impossible to audit.',
purpose:'Creates the server-side authority for auth flows.',
notes:
'Lives in $svrs/auth and receives store, actors, session, cache, logger, crypto and hasher ports.'
},
{
name:'createActiveAuth(options)',
purpose:'Creates the reactive browser client (raw factory).',
notes:
'Direct factory for tests or non-App contexts. App-wired apps use defineActiveAuth instead.'
},
{
name:'defineActiveAuth(options)',
purpose:'Service factory for the App schema.',
notes:
'Registered as services.auth in createActiveApp; the builder injects Http, Cache and Logger from the core/services automatically.'
}
],
api: artifactApis.auth,
sections:[
{
title:'Creation and route wiring',
body:[
'Auth has two creation points. The server creates EngineAuth from $svrs/auth with ports for storage, actors, session, cache, crypto and logging. The browser declares the auth slot via defineActiveAuth in the App service schema, which talks to the server routes and mirrors the safe AuthCurrentView through App.auth.',
'Do not declare the auth service before the server routes exist. The client cannot prove identity by itself; every sign-in, sign-out, CSRF and recovery operation is a server command.'
'ActiveAuth follows the ActiveEngine convention: direct getters, snapshot(), onChange(), clearError() and dispose().'
],
table:[
{
name:'current',
purpose:'Latest AuthCurrentView.',
notes:'Contains session status and public actor snapshot.'
},
{
name:'authenticated',
purpose:'Convenience boolean.',
notes:'Derived from current.session.status.'
},
{
name:'loading',
purpose:'True while a client operation is in flight.',
notes:'Shared active-root naming.'
},
{
name:'lastError',
purpose:'Safe client error.',
notes:'Secrets and raw backend errors are normalized.'
}
]
},
{
title:'Server Wiring',
body:[
'The server engine exposes route handlers and ports instead of importing a specific database, mailer or framework. That keeps auth portable and testable.'
],
code:{
title:'Server engine shape',
code:`const Auth = createEngineAuth({
security,
ports:{
store,
actors,
sess,
cache,
logger,
timer: App.timers,
crypto,
passwordHasher,
mailer
}
});`
}
},
{
title:'Database Persistence',
body:[
'Production auth should not use memory adapters. Use createDbAuthAdapter(repos) from $svrs/auth and map your ORM or SQL repositories to the AuthRepository contract.',
'The reference PostgreSQL schema lives in src/svrs/auth/sql/postgres.sql. It models credentials, flows, linked accounts, devices, session bindings and refresh token rotation without forcing a concrete ORM.'
],
table:[
{
name:'auth_credentials',
purpose:'Credential and factor records.',
notes:'Stores identifier hashes and password hashes, never raw passwords.'
},
{
name:'auth_flows',
purpose:'Expiring server flows.',
notes:'Email verification, password reset, OAuth state and MFA/WebAuthn challenges.'
},
{
name:'auth_session_bindings',
purpose:'Auth-to-session binding.',
notes:'Lets auth revoke by actor, device, password change, refresh reuse or logout.'
notes:'Requires transactional row locks; tokens are stored only as hashes.'
}
],
code:{
title:'Repository-backed store',
code:`const store = createDbAuthAdapter({
credentials,
flows,
linkedAccounts,
devices,
sessionBindings,
refreshFamilies,
refreshTokens,
transaction:(run)=>db.transaction(run)
});`
}
},
{
title:'Flows',
bullets:[
'Password sign-up and sign-in bind a successful identity proof to session state through the session port.',
'CSRF is requested and sent automatically by ActiveAuth for state-changing client calls.',
'Email verification and password reset use expiring server flows; tokens are verified on the server.',
'Device listing and revoke are exposed by the engine/client contracts; route wiring must expose AUTH_ROUTE_PATHS.DEVICES and DEVICE_REVOKE explicitly because the default handler map currently covers current, CSRF, password, recovery and sign-out routes.'
]
},
{
title:'Integration Rules',
bullets:[
'Auth proves identity and mutates session through explicit server ports; Cache and Perms react to public app events only when their own consumer options opt in.',
'Auth never stores refresh tokens, passwords, OTPs or CSRF secrets in Storage.',
'Perms receives actor context from Auth/Session but remains the authorization authority.',
'Client Auth is UX, not a security boundary.'
]
}
],
tests:[
{
name:'src/arts/auth/test',
purpose:'Active client behavior.',
notes:'Load current, sign-in/out and safe client state.'
},
{
name:'src/svrs/auth/test',
purpose:'Server flows.',
notes:'Password, CSRF, recovery, device primitives and handlers.'
},
{
name:'/test/auth',
purpose:'Interactive auth lab.',
notes:'Password flow, CSRF and event stream.'
}
]
},
buss:{
section:'Infrastructure',
title:'Bus',
alias:'$bus',
summary:
'Typed event engine used by App.bus for cross-artifact facts. Pure contracts live in $libs/bus; the engine implementation lives in $bus.',
body:'Modules import interfaces, error classes, SILENT_BUS and helpers from $libs/bus. The composition root ($active-app) and tests import the createEngineBus implementation from $bus. Modules must never import from $bus directly — that mirrors the Logger / SILENT_LOGGER pattern in $libs/logger.'
},
overview:[
'Bus is the low-level event engine. It owns typed subscriptions, envelopes, listener error handling, listener leak warnings and disposal.',
'The contract surface (EngineBus, EventPublisher, BusEnvelope, BusListener, error classes, SILENT_BUS, generic constants) lives in $libs/bus as pure types and helpers — no runtime state. The engine factory createEngineBus() lives in $bus and imports its types from $libs/bus.',
'The only event App owns is APP_EVENT_DISPOSE_STARTING (in arts/active-app/events.ts). Every other public event is owned by its module — SESSION_EVENT_IDENTITY_CHANGED, SESSION_EVENT_REVOKED, etc. Modules publish their own events directly on App.bus.',
'Cross-module reactions are explicit: cache.clear / perm.invalidate / connection.reauth on identity change live as orca actions registered through applyStandardOrca(App) or the cherry-picked apply* presets in $active-app/presets. The bus stays inert — it carries events; orca runs reactions.'
],
dynamics:[
'Modules do not create their own buses and do not import from $bus. They accept an injected EngineBus or EventPublisher (interfaces from $libs/bus) and publish/listen through constants exported by the event owner.',
'Module events are local facts such as session.identity-changed; the publishing module owns the constant and the payload type. There is no "translator" layer — the canonical event names are the module ones, never republished as APP_EVENT_*.',
'Consumers (UI components, orca actions) subscribe to module events directly via their canonical names. The "modules do not know each other" invariant holds because the module that subscribes is in arts/active-app/presets/, not inside another art.',
'App publishes APP_EVENT_DISPOSE_STARTING during App.dispose() so subscribers can flush before service teardown begins. That is the only App-owned event.'
],
commonMistakes:[
{
name:'importing from $bus in module code',
purpose:'$bus exposes the concrete engine; module code must depend only on the contract layer.',
notes:'Import EngineBus, EventPublisher, BusSubscription, BusEnvelope, SILENT_BUS, etc. from $libs/bus. Only $active-app and tests touch $bus.'
},
{
name:'putting secrets in app events',
purpose:'Public events can be logged, captured or inspected by diagnostics.',
notes:'Use actor ids, tenant ids, causes and correlation ids; never tokens, passwords or authorization headers.'
},
{
name:'expecting publish to clear data',
purpose:'Publishing is observable and does not mutate other artifacts by default.',
notes:'Register an orca action via applyStandardOrca(App) or a cherry-picked apply* preset in $active-app/presets so the desired reactions run on the published event.'
},
{
name:'using bus for private in-module events',
purpose:'It creates needless coupling and noise.',
notes:'Keep private event emitters inside the artifact; use App.bus for cross-artifact facts.'
},
{
name:'creating createEngineBus() inside modules',
purpose:'It fragments the event graph and makes cross-artifact behavior invisible.',
notes:'Use App.bus in application code; modules should accept an injected bus/publisher.'
},
{
name:'publishing inline event strings',
purpose:'Magic strings drift and break refactors.',
notes:'Declare event constants in uppercase at the owner boundary — for example SESSION_EVENT_IDENTITY_CHANGED in arts/session/consts.ts.'
}
],
quickStart:{
title:'Central App.bus',
code:`import { SESSION_EVENT_IDENTITY_CHANGED } from '$session';
notes:'Use as a default for modules that take an optional bus, mirror of SILENT_LOGGER. Importable from $libs/bus.'
},
{
name:'createEngineBus(options?)',
purpose:'Build a real bus engine. The implementation factory.',
notes:'Importable only from $bus (the artifact). Used by App and tests; never inside an artifact module.'
},
{
name:'App.bus',
purpose:'Application bus instance, always-present.',
notes:'Created by createActiveApp with Logger and Timers.clock injected. Modules receive it through their factory options.'
}
],
api: artifactApis.buss,
sections:[
{
title:'Layer split — Where to import from',
body:[
'libs/bus owns the pure contract: interfaces, generic constants, error classes, SILENT_BUS, helpers. Nothing here has runtime state. Module code (cache, session, perm, connection, …) imports types from $libs/bus exclusively — that is the rule.',
'arts/bus owns the engine implementation (createEngineBus, createSvelteEngineBus) and the Svelte-context bridge (setBus / getBus, exported from $bus). The composition root (active-app) and tests import the engine from $bus; nothing else does. The split mirrors the Logger / SILENT_LOGGER pattern in $libs/logger.',
'A module that needs to publish or subscribe accepts a bus through its factory options, typed against the interface from $libs/bus. The App service builder passes App.bus when constructing services that declare bus as a coreDependency; otherwise the module falls back to SILENT_BUS so the publish path stays unconditional.'
],
table:[
{
name:'arts/<module> code',
purpose:'Always import from $libs/bus.',
notes:'Types, SILENT_BUS, error classes, helpers. Never createEngineBus.'
},
{
name:'arts/active-app/active-app.svelte.ts',
purpose:'Imports createSvelteEngineBus from $bus.',
notes:'The single legitimate consumer of the engine implementation in production code.'
},
{
name:'Tests that build their own bus',
purpose:'Import createEngineBus from $bus.',
notes:'Acceptable; bus engines are cheap and self-contained.'
},
{
name:'arts/active-app/events.ts',
purpose:'Imports interfaces from $libs/bus.',
notes:'Defines APP_EVENT_DISPOSE_STARTING + safety helpers; never touches the engine.'
'Every event constant value across the framework is scoped with the artifact prefix to disambiguate aggregated logs, devtools and any future cross-bus serialization. A bare "delete" or "hit" leaves observers guessing which module emitted it; "cache.delete" or "cache.hit" does not.',
'Format: <artifact>.<concept> with snake_case for compound terms inside a level (cache.stale_if_error) and dots for hierarchy (cache.refresh.start). Same convention auth has used since v0 with AUTH_EVENT_NAMES.',
'Method labels passed to ensureLive(method) follow the same rule: session.adopt, cache.invalidate, auth.signOut. Bare names like "adopt" or "invalidate" never appear in error messages.'
notes:'Stays "*" because the framework bus owns the symbol. Per-emitter wildcards are scoped (CACHE_EVENT_ALL = "cache.*", etc.).'
}
]
},
{
title:'App-event contract',
body:[
'Only one event is owned by arts/active-app: APP_EVENT_DISPOSE_STARTING (in arts/active-app/events.ts). It fires once at the start of App.dispose() before any service teardown begins.',
'Every other public event is owned by its module. Examples: SESSION_EVENT_IDENTITY_CHANGED and SESSION_EVENT_REVOKED in arts/session/consts.ts; CONNECTION_* events in arts/connection. There is no longer a translator layer that re-publishes them as APP_EVENT_*.',
'App events should flow through their typed publisher (publishAppDisposeStarting, publish<Module>Event*) so payload typing, runtime guards and unsafe-payload checks stay in one place. Raw bus.on(...) is acceptable for low-level tests.'
]
},
{
title:'Centralization rule',
body:[
'There is one app-level bus per application: App.bus, built by createActiveApp. Modules do not call createEngineBus() for their own private island. They accept an injected bus, EventPublisher or EventSubscriber from the composition root.',
'createEngineBus() remains public because App, isolated services and unit tests need to build a bus engine, but it is not the normal usage pattern inside framework modules.'
]
},
{
title:'Reactions live in orca, not on the bus',
body:[
'Earlier drafts of this art proposed a translator layer that turned module events into app events plus per-consumer autoInvalidateOn / autoReauthOn flags. That machinery was removed. Reactions now live as orca actions registered through the apply* presets in $active-app/presets.',
'applyStandardOrca(App) registers every standard preset whose required services are declared on App. Apps that want a tailored set cherry-pick individual apply* functions instead.'
],
table:[
{
name:'applyCacheClearOnIdentityChange(App)',
purpose:'Identity-change reaction.',
notes:'Listens to SESSION_EVENT_IDENTITY_CHANGED and calls App.cache.clear().'
},
{
name:'applyCacheClearOnRevoke(App)',
purpose:'Revoke reaction.',
notes:'Listens to SESSION_EVENT_REVOKED and calls App.cache.clear().'
},
{
name:'applyPermInvalidateOnIdentityChange(App)',
purpose:'Permission cache reaction.',
notes:'Listens to SESSION_EVENT_IDENTITY_CHANGED and calls App.perm.invalidate().'
},
{
name:'applyStandardOrca(App)',
purpose:'Aggregator.',
notes:'Registers every standard preset whose required services are declared on App.'
}
]
},
{
title:'Identity-change flow',
body:[
'This example shows the complete chain. The bus carries the SESSION_EVENT_IDENTITY_CHANGED event; orca runs every action registered for it; each action calls the imperative API of the affected service.'
'Session is not authentication. It does not verify passwords, OAuth callbacks or permissions. It keeps continuity once another layer has established identity.',
'The session shape has three slots: user, credential and data. User is identity, credential is how the client can refresh or authenticate to the server, and data is session-scoped application state such as tenantId or cartId.',
'The engine is deterministic and testable: refresh can be deduped, storage is pluggable, timers can be injected and lifecycle changes can emit safe session.* events when a bus is injected.'
],
dynamics:[
'Session starts from a trusted source: SSR data, an auth success response or a refresh callback. adoptServer() is for data already validated on the server; adopt() validates client-provided values through configured schemas.',
'Refresh is a controlled lifecycle operation. Multiple concurrent refresh calls dedupe into one remote call; a successful refresh replaces current, a null result clears the session, and a thrown refresh keeps the previous value while reporting the error.',
'Revoke is different from clearLocal(). revoke() calls the configured server revoke callback and then clears state. clearLocal() only wipes local memory/storage when the server already invalidated the session.'
],
commonMistakes:[
{
name:'using Session as Auth',
purpose:
'Session cannot prove identity; it only stores continuity after identity was proven.',
notes:'Use $auth for login/proof, $session for lifecycle and propagation.'
},
{
name:'calling adopt() with SSR data',
purpose:
'It treats trusted server data like untrusted browser input and can produce confusing validation paths.',
notes:'Use adoptServer(data.session) for server-validated payloads.'
},
{
name:'mutating nested current data directly',
purpose:'Deep mutation can skip persistence and event propagation depending on shape.',
notes:'Adopt a new session object or use the exposed lifecycle methods.'
},
{
name:'not clearing caches on revoke',
purpose:'Actor-scoped data can remain visible after logout.',
notes:'Wire applyStandardOrca(App) (or applyCacheClearOnRevoke directly) so SESSION_EVENT_REVOKED triggers App.cache.clear() automatically.'
notes:'Registered as services.session in createActiveApp; the builder injects Logger and Bus from the core.'
}
],
api: artifactApis.sess,
sections:[
{
title:'Creation and wiring',
body:[
'In an application, declare the session slot via defineActiveSession(...) in the createActiveApp({ services }) schema. The builder injects Logger and Bus from the core. Use createEngineSession()/createActiveSession() directly only in tests, isolated services or when you deliberately do not use App.',
'The storage option decides where the local session snapshot lives. The callbacks onRefresh and onRevoke are the only places that should call the server. Session itself does not know your endpoint shape.'
{name:'none',purpose:'No local session exists.',notes:'current is null.'},
{
name:'anonymous',
purpose:'Tracked but unidentified session.',
notes:'Useful for carts or anonymous journeys.'
},
{
name:'identified',
purpose:'Session has a user.',
notes:'Authorization can now evaluate an actor.'
}
]
},
{
title:'Lifecycle',
bullets:[
'adopt() validates client-provided sessions against optional Standard Schema contracts.',
'adoptServer() trusts the server and is the SSR hydration path.',
'refresh() preserves the current session if the refresh call throws, but expires it if refresh returns null.',
'revoke() can be local or global; global degrades to local if the server revocation fails.'
]
},
{
title:'HTTP Integration',
body:[
'The HTTP integration can intercept 401 responses, trigger a deduped refresh and retry once with fresh credentials. A sentinel header prevents infinite retry loops.'
'The session art publishes its own SESSION_EVENT_* events on App.bus directly. There are no longer any republished APP_EVENT_* shadows; the canonical events are the module ones.',
'Cross-module reactions (cache.clear on revoke, perm.invalidate on identity change, connection reauth) live as orca actions registered through applyStandardOrca(App) or the cherry-picked apply* presets in $active-app/presets.'
]
}
],
tests:[
{
name:'src/arts/session/test',
purpose:'Lifecycle and integrations.',
notes:'Refresh, revoke, actor metadata, HTTP and auto-refresh.'
body:'The server engine is the authority. ActivePerms is for UX, cache and rendering helpers only.'
},
overview:[
'Perms is not a simple RBAC helper. It models authorization as explicit decisions using roles, attributes, relations and context in the same runtime.',
'The server engine evaluates policies and returns rich decisions: allow, deny, not applicable or indeterminate. The client reflector batches remote checks, caches snapshots, can invalidate from public app events and exposes a Can component for UI gates.',
'Policies can be explained and list queries can be filtered or compiled into query plans when the provider supports it.'
'ActivePerms is a passive runtime. It does not subscribe to the bus on its own; cross-module reactions live in orca presets registered at the App level.',
'applyPermInvalidateOnIdentityChange(App) registers an orca action that calls App.perm.invalidate() when the session art emits SESSION_EVENT_IDENTITY_CHANGED. The standard aggregator applyStandardOrca(App) wires it together with the cache presets.',
'Tenant switches and "permissions refreshed" notifications are app-defined events; register a custom orca action that calls App.perm.invalidate() when those fire.'
],
code:{
title:'Wire the standard reactions',
code:`import { applyStandardOrca } from '$active-app/presets';
applyStandardOrca(App);// runs perm.invalidate() on SESSION_EVENT_IDENTITY_CHANGED`
}
},
{
title:'Database Persistence',
body:[
'$svrs/perm ships a reference PostgreSQL model in src/svrs/perm/sql/postgres.sql. It creates permission_policies, permission_relations and permission_decision_audit.',
'The framework does not own your ORM. Map the SQL to Prisma, Drizzle, Kysely or raw SQL and expose a tiny repository that returns PolicyIR rows and relation facts.',
'Policy rows are versioned per tenant and namespace. Store validated PolicyIR JSON, publish exactly one active version per policy id, and invalidate permission/cache scopes after publishing.'
'permission_relations is the generic ReBAC table. Use it for facts like project.owner, team.member or invoice.approver when the domain does not already have a stronger table.',
'Relation providers must fail closed. If tenant id, resource id or backend state is unavailable, return unknown instead of false unless the database definitely says the relation does not exist.'
],
table:[
{
name:'hasRelation(input)',
purpose:'Check one actor/resource/relation tuple.',
notes:'Used by rel(...).is(actor()) policy conditions.'
},
{
name:'listSubjects(input)',
purpose:'Reverse lookup actors for a resource.',
notes:'Used by who() and admin/audit views.'
},
{
name:'listResources(input)',
purpose:'Find resources reachable by a subject.',
notes:'Useful for list pages and reverse queries.'
}
]
},
{
title:'Can Component',
body:[
'Can is a UI convenience component. It asks ActivePerms whether content should render, but it never replaces server checks. Protected mutations and reads must still call the server engine.'
'Cache answers more than "do I have this value?". It decides freshness, scope safety, invalidation status, stale serving and what to do if the origin fails.',
'The pure runtime lives in libs/cache. Server and active wrappers compose that runtime with adapters, diagnostics and Svelte state.',
'Scopes are part of the key. Private data should be cached under tenant, actor or permission scopes, never as public.'
],
dynamics:[
'A query normalizes its key, resolves scope, reads the adapter, evaluates policy windows and tag epochs, then decides whether to return fresh data, serve stale data while refreshing, serve stale-if-error or call the fetcher.',
'Invalidation does not have to delete every entry immediately. Tag epochs mark entries as stale/invalidated lazily, which keeps distributed adapters cheap while explain() can still tell why a value was rejected.',
'ActiveCache wraps the same runtime with entry state. The UI reads data/status/loading/error from ActiveCacheEntry; the server or service layer uses EngineCache for request handlers, jobs and SSR.',
'ActiveCache is passive: it does not subscribe to the bus on its own. Cross-module reactions live in orca presets registered at the App level — applyCacheClearOnIdentityChange, applyCacheClearOnRevoke, or applyStandardOrca for the bundled set.'
],
commonMistakes:[
{
name:'public scope for private data',
purpose:
'Different users or tenants can observe cached values that were not meant for them.',
notes:'Use scope: actor, tenant or permission and provide a scopeResolver.'
},
{
name:'using raw strings as keys',
purpose:'Ad-hoc keys collide and cannot be invalidated by structure.',
notes:"Use deterministic array keys such as ['project', projectId]."
},
{
name:'forgetting tags on queries',
purpose:'Mutations cannot invalidate related reads cleanly.',
notes:'Add stable tags to reads and invalidate those tags after writes.'
},
{
name:'hiding cache bugs',
purpose:'Stale data failures are difficult to debug from UI symptoms.',
notes:'Use explain(), stats() and diagnostics when behavior surprises you.'
notes:'Adds entry state and active loading/error surface.'
},
{
name:'App.cache',
purpose:'Schema-declared cache via defineActiveCache.',
notes:
'Memory adapter by default; configure scopeResolver and adapter for production. Reactions to identity/revoke events live in orca presets, not here.'
}
],
api: artifactApis.cache,
sections:[
{
title:'Creation and adapter wiring',
body:[
'When cache is declared with defineActiveCache(), App.cache is an ActiveCache service. Omitting adapter uses the in-memory default, which is fine for first use, tests and local UI state; production private data should configure scopeResolver and an intentional adapter strategy.',
'Server-side cache roots should be created from $svrs/cache. Client active cache roots are UI helpers and should not become the source of truth for permission-sensitive data.'
],
code:{
title:'Scoped cache root',
code:`const Cache = createEngineCache({
namespace:'app',
adapter: memoryCacheAdapter({maxEntries: 5_000}),
defaultPolicy:'interactive',
scopeResolver:()=>({
tenantId: Sess.current?.data.tenantId,
actorId: Sess.current?.user?.id,
permissionHash: Perms.snapshot().version
}),
logger
});`
}
},
{
title:'Server and Client Boundary',
body:[
'$libs/cache contains the pure runtime. $svrs/cache is the server-side entry point for request handlers, jobs and shared services. $cache/createActiveCache is the Svelte-facing wrapper for UI state.',
'The same policy/key/scope semantics apply in both places, but server caches normally use process/distributed adapters while client caches use memory or Storage-backed adapters.'
],
table:[
{
name:'defaultMemoryAdapter',
purpose:'Options for the implicit memory adapter.',
notes:
'Only applies when adapter is omitted; demos/tests can suppress the production warning explicitly.'
},
{
name:'server request cache',
purpose:'Deduplicate work during one request.',
notes:'Good for SSR loaders and backend services.'
},
{
name:'process memory cache',
purpose:'Fast L1 cache.',
notes:'Use max entries/size and clear by actor/tenant on identity changes.'
},
{
name:'storage/tiered/distributed adapter',
purpose:'Durable or shared L2.',
notes:'Redis/KV adapters should honor epochs, TTL and namespace boundaries.'
},
{
name:'ActiveCacheEntry',
purpose:'UI state around one query.',
notes:'Status/loading/error live here; security decisions still belong server-side.'
notes:'Short windows and no persistence by default.'
},
{
name:'realtime',
purpose:'Almost no cache.',
notes:'Use for data that must always revalidate.'
},
{
name:'immutable',
purpose:'Versioned immutable data.',
notes:'Can be cached indefinitely.'
}
]
},
{
title:'Invalidation',
body:[
'Entries can be invalidated by exact key, key prefix, tag, scope or predicate. Tags are the recommended high-level contract between mutations and cached reads.'
],
code:{
title:'Tag invalidation',
code:`await App.cache.invalidate({
tags:[{type:'project',id: projectId}]
});`
}
},
{
title:'Reacting to identity changes',
body:[
'ActiveCache is a passive runtime — it never subscribes to the bus on its own. Cross-module reactions live in orca presets registered at the App level.',
'applyCacheClearOnIdentityChange wires SESSION_EVENT_IDENTITY_CHANGED to App.cache.clear(); applyCacheClearOnRevoke does the same for SESSION_EVENT_REVOKED. applyStandardOrca(App) registers both at once when the corresponding services are declared.',
'Cache stays decoupled from session, auth and perm internals: it only exposes the imperative API (clear, invalidate); the orca preset decides when to call it.'
],
code:{
title:'Wire reactions at the App level',
code:`import { createActiveApp } from '$active-app';
'explain() tells why the cache served, missed, refreshed or rejected an entry. This is intentionally part of the public surface because cache bugs are otherwise invisible.'
]
}
],
tests:[
{
name:'src/libs/cache/test',
purpose:'Pure runtime.',
notes:'Keys, policies, query, mutation, invalidation and refresh.'
},
{
name:'src/arts/cache/test',
purpose:'Active wrapper.',
notes:'Reactive entries and operation state.'
},
{
name:'/test/cach',
purpose:'Interactive cache lab.',
notes:'Policies, scopes, tags and active entries.'
}
]
},
stor:{
section:'Data',
title:'Storage',
alias:'$storage',
summary:
'Reactive synchronous key/value storage with pluggable adapters, envelopes, versioning, TTL, validation and cross-tab sync.',
'Storage is the framework primitive for client-safe persistence: preferences, drafts, non-secret state and SSR-readable cookies.',
'Each entry has defaults, serializer, optional version/migration, optional validation, TTL and remove/reset semantics. Active entries expose current as reactive state.',
'Storage is intentionally synchronous in v1. Async stores such as IndexedDB can be added later through a separate async contract.',
'Only createEngineStorage() and createActiveStorage() are root factories. Adapters are storage backends: they are passed to a root through options.adapter or overridden per entry.'
],
dynamics:[
'A root owns the adapter default, namespace, entry registry, bus and diagnostics. Every call to entry() creates a handle for one logical key and stores strings through the selected adapter.',
'Reads always pass through serializer, envelope, TTL, migration, mergeDefaults and validation. If a stored value is expired, corrupt or invalid, the entry falls back to its default and reports through diagnostics/onError.',
'Active entries add a reactive current property and an onChange stream. current is not a deep persistence proxy: assigning a nested field does not write to the adapter unless you reassign the value or call update().'
],
commonMistakes:[
{
name:'treating adapters as root factories',
purpose:
'Adapters only store strings; they do not own entries, lifecycle, namespaces or diagnostics.',
notes:
'Create a root with createActiveStorage/createEngineStorage and pass adapters into it.'
},
{
name:'storing secrets',
purpose:'localStorage/sessionStorage/cookies used here are not a secret vault.',
notes:'Do not store passwords, OTPs, refresh tokens or private provider tokens in $storage.'
},
{
name:'entry.current.foo = value',
purpose:'Nested mutation can skip persistence because current is not a deep proxy.',
notes:
'Use entry.update(prev => ({ ...prev, foo: value })) or assign entry.current to a new object.'
},
{
name:'raw with version or ttlMs',
purpose:'Raw mode deliberately bypasses the envelope where version/TTL live.',
notes:'Use raw for readable cookies; use envelope mode for migration and TTL.'
}
],
quickStart:{
title:'Active entry',
code:`import { createActiveStorage, localAdapter, cookieAdapter } from '$storage';
// Root created directly.
constStorage=createActiveStorage({
adapter: localAdapter,
namespace:'app'
});
// In an application root, App.storage exists when storage is declared.
purpose:'Root factory: creates an imperative EngineStorage.',
notes:'Pass adapter, namespace, logger and onError here.'
},
{
name:'createActiveStorage(options)',
purpose:'Root factory: creates a reactive ActiveStorage.',
notes:'Wraps EngineStorage and adds reactive entries.'
},
{
name:'defineActiveStorage(options)',
purpose:'Service factory for App.storage.',
notes:'Declared under createActiveApp({ services }).'
}
],
api: artifactApis.stor,
sections:[
{
title:'Creation Model',
body:[
'Storage has one root and many entries. The root is created with createEngineStorage() or createActiveStorage(); entries are created through Storage.entry(key, defaults, options?).',
'Adapters are not roots. localAdapter, sessionAdapter, cookieAdapter and createMemoryAdapter() implement SyncStorageAdapter. They decide where strings are stored; the root decides namespaces, entry registry, diagnostics and lifecycle.',
'Inside App, storage is an opt-in service declared with defineActiveStorage(). Feature code should use App.storage.entry(...) only when that service exists. Create your own root in tests, SSR helpers or isolated subsystems.'
'Frontend no longer persists preferences directly. User intent belongs to prefs (now part of the core); persist it by passing a storage adapter into the createActiveApp({ prefs: { storage } }) option, or by calling createPrefsStorageBridge() yourself.'
'Http wraps fetch with a tagged result model. Callers receive ok/value for success and structured non-ok results for HTTP, network, timeout and validation failures.',
'It centralizes shared HTTP constants in libs/http so methods, headers and content types are not magic strings scattered through modules.',
'The engine supports retry, Retry-After, abort signals, request/response hooks, body validation and response validation through Standard Schema.'
],
dynamics:[
'Every call builds a request from root defaults plus per-call options, runs before hooks, serializes query/body, starts timeout control, executes fetch, parses the response, validates it when a schema is supplied and returns a tagged result instead of throwing for normal HTTP failures.',
'Retry is policy-driven. The engine can retry safe/idempotent calls, respect Retry-After and stop on total timeout. Callers still inspect the final tagged result.',
'with(options) creates a scoped child client. This is the preferred way to bind SvelteKit event.fetch, request-specific headers or tenant-specific baseUrl without mutating the root client.'
],
commonMistakes:[
{
name:'try/catch around every non-2xx',
purpose:
'Http returns tagged results for expected HTTP failures, so catch blocks hide useful status/body metadata.',
notes:'Check response.ok and switch on response.type.'
},
{
name:'using global fetch in SvelteKit server code',
purpose:'Cookies and internal routing can be lost.',
notes:'Create a scoped client with Http.with({ fetch: event.fetch }).'
},
{
name:'retrying unsafe mutations blindly',
purpose:'POST/PATCH side effects can be duplicated.',
notes:
'Configure retry explicitly and only for idempotent operations or idempotency-key protected calls.'
},
{
name:'parsing bodies outside Http',
purpose:'Validation and error normalization become inconsistent.',
notes:'Pass schema/bodySchema and consume value from the tagged result.'
notes:'No Active wrapper because request state belongs to callers.'
},
{
name:'createEngineHttpAuthClient(Http)',
purpose:'Auth adapter.',
notes:'Adapts EngineHttp to ActiveAuth route calls.'
},
{
name:'App.http',
purpose:'Schema-declared HTTP engine via defineEngineHttp.',
notes:'The App builder injects App.logger when the service is declared.'
}
],
api: artifactApis.http,
sections:[
{
title:'Creation and request scoping',
body:[
'When http is declared with defineEngineHttp(), App.http is the normal browser/client client. On the server, create a scoped child with event.fetch so SvelteKit cookies, internal routes and platform behavior are preserved.',
'Do not mutate a global Http instance with request-specific headers. Use with() to create a child client for one request, tenant or backend integration.'
'Format centralizes everything that depends on locale but is not text translation: numeric separators, currency, units and date/time conventions.',
'It deliberately does not depend on Lang. When composed through App, Format prefers prefs.effective.locale; Lang prefers prefs.effective.language. Apps that do not declare prefs can still wire an explicit LocaleSource or fall back to Lang.',
'Each submodule can run as an engine or active wrapper. Auto values derive from locale until the user sets an explicit override.'
],
dynamics:[
'ActiveFormat listens to a LocaleSource. When the locale changes, every submodule recomputes values that are still auto: numeric separators, currency, unit system, date order and hour cycle.',
'Manual values are sticky. If a user calls setCurrency(), setSystem(), setDateOrder() or similar setters, later locale changes do not overwrite that choice. clearX() returns that setting to auto mode.',
'Numbers, Currency, Units and Dates can run independently, but the aggregate Format root is the normal application surface because it keeps one locale and one auto/manual contract across every formatter.'
],
commonMistakes:[
{
name:'deriving currency from base language',
purpose:'Locales like es-AR and es-ES do not share currency.',
notes:'Use explicit locale mappings and avoid locale.split("-")[0] currency fallbacks.'
},
{
name:'overwriting manual user choices on locale change',
purpose:'Translations and Intl formatting have different responsibilities.',
notes:'Use $langs for text, $format for numbers/currency/units/dates.'
},
{
name:'assuming conversion rates exist',
purpose:'Formatting money is not the same as converting money.',
notes:'Configure rates before using convert()/convertAs().'
}
],
quickStart:{
title:'App formats',
code:`App.prefs.locale.set('es-AR');
App.format.numbers.format(1234.5);
App.format.currency.getCurrency();// ARS
App.format.units.getSystem();// metric
App.format.dates.getDateOrder();`
},
factoryRows:[
{
name:'createEngineFormat(options)',
purpose:'Pure aggregate engine.',
notes:'Groups numbers, currency, units and dates.'
},
{
name:'createActiveFormat(options)',
purpose:'Reactive aggregate wrapper.',
notes:'Subscribes to LocaleSource.'
},
{
name:'createEngineNumbers/Currency/Units/Dates',
purpose:'Standalone sub-engines.',
notes:'Use when only one formatting domain is needed.'
}
],
api: artifactApis.fmts,
sections:[
{
title:'Creation and locale source',
body:[
'When Format is created through App, it receives its LocaleSource from core App.prefs. That keeps regional formatting locale separate from translation language while still using one preference snapshot.',
'Create standalone sub-engines only when a non-UI service needs one formatting domain. UI code should prefer App.format so auto/manual state stays consistent.'
purpose:'Number, percent, compact and parse helpers.',
notes:'Backed by Intl.NumberFormat.'
},
{
name:'currency',
purpose:'Currency selection, formatting and conversion rates.',
notes:'Currency can be auto from locale or fixed.'
},
{
name:'units',
purpose:'Metric/imperial defaults and conversions.',
notes:'Defaults are locale-aware but overridable.'
},
{
name:'dates',
purpose:'Date order, hour cycle and date/time formatting.',
notes:'Uses locale conventions with explicit overrides.'
}
]
},
{
title:'Auto / Manual',
body:[
'If a setting is auto, locale changes can update it. If the user sets a value explicitly, locale changes do not override it. clearX() returns to auto.'
]
},
{
title:'Custom Currency',
code:{
title:'Fixed currency',
code:`const Format = createActiveFormat({
locale:'es-ES',
currency:{currency:'USD'}
});
Format.setLocale('fr-FR');
Format.currency.getCurrency();// USD, explicit user choice`
}
}
],
tests:[
{
name:'src/arts/format/test',
purpose:'Formatting engines.',
notes:'Numbers, currency, units, dates and auto-state.'
'Legacy Frontend reads locale and environment preferences, resolves auto-capable values, and writes the result to DOM attributes through Dom.apply().',
'The auto/manual dynamic matches Format. While dir/mode/reducedMotion are auto, locale or media-query changes can update them. Once the user sets a value explicitly, later auto sources stop overriding it until clearX() is called.',
'Persistence is not owned by Frontend. User intent belongs to prefs, and persistence is handled by the prefs storage bridge or application code.'
],
commonMistakes:[
{
name:'setting document attributes by hand',
purpose:'It bypasses the central writer and can fight Frontend updates.',
notes:'Use Frontend setters or Dom.apply() through the framework.'
'Frontend no longer owns preference persistence. User intent belongs to prefs; persist it by passing a storage adapter into the core prefs option, or by wiring createPrefsStorageBridge() yourself.'
'In the browser, ActiveDom installs viewport/media listeners and updates a reactive viewport snapshot. During SSR those listeners are inert so imports stay safe.',
'Responsive values are resolved from the current breakpoint. Consumers can pass a scalar or a breakpoint map and receive the best value for the active viewport.',
'DOM writes are centralized through apply(). Prefs projection, Eidos and feature modules use the same writer for controlled attribute/class/style updates.'
'When dom is declared with defineActiveDom(), App.dom is the shared DOM service for the application. It should own viewport tracking, responsive resolution and global writes that other artifacts depend on.',
'When using createActiveDom() directly, decide the target boundary explicitly: document-level app shell, an embedded widget root, or a test DOM. Do not let unrelated feature modules write global attributes independently.'
'Sium is page/feature scoped by design. Forms live in pages and features, so use createEngineSium() directly or declare sium with defineEngineSium() only where the App needs a shared validator service.',
'The engine creates schemas, validates values, returns structured issues and carries metadata for UI generation. It supports Standard Schema interoperability.',
'When declared through App, Sium receives App.logger from the core and App.langs when the lang service exists. If Lang is not provided, its local resolver is only a fallback.'
],
dynamics:[
'Create a Sium engine close to the form or feature that needs it. Define schemas once, then call validate() for submitted values or Standard Schema consumers such as HTTP body validation.',
'Validation returns structured issues with paths. UI code should render issues by path instead of flattening everything into one string, otherwise nested forms become hard to explain.',
'Translations flow through the injected Lang engine when present. Sium should not copy Lang behavior; its local resolver exists only so validation still has fallback messages without i18n.'
],
commonMistakes:[
{
name:'creating one global validator for every page',
purpose:'Forms are page/feature scoped and global validators load unnecessary schemas.',
notes:'Use defineEngineSium({}) where the form lives.'
},
{
name:'throwing on normal validation failure',
purpose:'Invalid user input is data, not an exception path.',
notes:'Return/inspect result.ok and render result.issues.'
},
{
name:'hard-coded issue messages',
purpose:'Messages bypass Lang and cannot localize consistently.',
notes:'Use injected Lang and constants for message keys/fallbacks.'
},
{
name:'discarding issue paths',
purpose:'The UI cannot attach messages to fields.',
notes:'Keep structured issues and group/render by path.'
purpose:'Creates a validation engine (raw factory).',
notes:'Direct factory for tests or non-App contexts.'
},
{
name:'defineEngineSium(options)',
purpose:'Service factory for the App schema.',
notes:'Registered as services.sium; the builder injects logger from the core and lang from services when both are declared.'
}
],
api: artifactApis.sium,
sections:[
{
title:'Creation and schema ownership',
body:[
'Sium is declared as a service via defineEngineSium({}) in the createActiveApp({ services }) schema. The builder injects logger from the core and lang from services automatically when both are declared.',
'Schemas should be owned by the feature that validates the data. Share schemas only when multiple boundaries validate the exact same shape, for example a form and an HTTP endpoint.'
],
code:{
title:'Feature-owned validator',
code:`// app.svelte.ts — schema declared once at the App level
'Structured logger with shared Logger contract, EngineLogger runtime, transports, filters, failure routing and diagnostics support.',
factories:['createEngineLogger'],
dependsOn:['$libs/logger'],
layer:'EngineLogger / Logger contract',
overview:[
'The minimal Logger interface lives in libs/logger and is what all modules receive. EngineLogger lives in arts/logger and extends that contract with transports, history, child loggers, timers and lifecycle.',
'Diagnostics are a cataloged layer above Logger. They map internal framework events to normal logger calls without forcing every log to become an event.',
'Transports can be filtered per level, buffered, throttled on failure and adapted to Sentry, Datadog, Loki, Logtail or OpenTelemetry.'
],
dynamics:[
'Application code and modules receive the small Logger contract from $libs/logger: trace, debug, info, warn, error and fatal. They do not need to know whether the concrete logger is EngineLogger, Sentry, a test spy or a custom adapter.',
'EngineLogger is the runtime implementation. It normalizes entries, applies level enablement, routes to transports, handles transport failures and emits synthetic failure entries to the remaining transports without cascading into the failed one.',
'Diagnostics sit above Logger. A module can define a catalog of internal events and map each one to a normal logger call. Free-form info/debug logs still go directly through Logger.'
],
commonMistakes:[
{
name:'defining local Logger interfaces',
purpose:'Every module drifts and integrations become incompatible.',
notes:'Import Logger and LogFn from $libs/logger.'
},
{
name:'hard-coded categories/messages',
purpose:'Search, routing and audits become unreliable.',
notes:'Keep categories, diagnostic names and standard messages in consts.ts.'
notes:'Shared helper, not an EngineLogger factory.'
}
],
api: artifactApis.logr,
sections:[
{
title:'Creation and injection',
body:[
'Create one EngineLogger at the application root and inject its Logger contract into other artifacts. Modules should depend on $libs/logger.Logger, not on EngineLogger internals.',
'Use child/context helpers for module scopes if needed, but keep category names and standard messages in module consts.ts.'
'The framework uses an explicit per-level enablement map for routing, not module-specific severity systems. EngineLogger still performs final filtering and transport dispatch.'
]
},
{
title:'Failure Routing',
body:[
'If a transport fails, EngineLogger emits a synthetic failure entry to the remaining transports and marks deniedFor so the failing sink does not receive its own failure. Throttling prevents cascades.'
]
},
{
title:'Diagnostics',
code:{
title:'Diagnostic catalog from $libs/logger',
code:`import { createCatalogDiagnostics, LogLevel } from '$libs/logger';
constdiagnostics=createCatalogDiagnostics({
logger,
defaultCategory:'connection',
catalog:{
reconnect_exhausted:{
level: LogLevel.WARN,
message:'reconnect attempts exhausted'
}
}
});
diagnostics.emit({
artifact:'connection',
type:'reconnect_exhausted',
meta:{attempts: 5}
});`
}
}
],
tests:[
{
name:'src/arts/logger/test',
purpose:'Engine logger.',
notes:'Levels, transports, failures, buffers and adapters.'
},
{
name:'src/libs/logger/test',
purpose:'Shared diagnostics.',
notes:'Catalog routing and error normalization.'
},
{
name:'/test/logr',
purpose:'Interactive logger lab.',
notes:'Levels, transports and web vitals.'
}
]
},
timer:{
section:'Infrastructure',
title:'Timers',
alias:'$timer',
summary:
'Deterministic timer scheduler with injectable clock, one-shots, intervals, cancellation, snapshots and backoff helpers.',
'Timers centralizes scheduling so modules do not scatter setTimeout and setInterval logic. Reconnect, heartbeat, auto-refresh and cache-like workflows can all share a deterministic scheduler.',
'The engine supports keyed timers, replacement, cancellation by scope, intervals, snapshots and injected clocks for deterministic tests.',
'ActiveTimers exposes reactive snapshots for debug panels and test pages.'
],
dynamics:[
'Every timer has a stable key and optional scope. Scheduling with replace cancels the previous entry for that key before creating the next one, which prevents duplicate refresh/reconnect loops.',
'The engine uses the injected clock for timestamps and backoff calculations, so tests can advance time deterministically instead of waiting for real time.',
'ActiveTimers wraps the engine and exposes entries() as reactive snapshots. That is for observability and test pages; business logic should keep using schedule(), interval(), cancel() and cancelScope().'
],
commonMistakes:[
{
name:'using setTimeout directly in modules',
purpose:'Timers become impossible to cancel, inspect or fake in tests.',
notes:'Use $timer for reconnect, refresh, heartbeat and delayed jobs.'
},
{
name:'anonymous timer keys',
purpose:'Duplicate timers accumulate and cause repeated work.',
notes:'Use stable keys and replace when the latest task should win.'
},
{
name:'not canceling by scope on dispose',
purpose:'Page/module timers can run after their owner is gone.',
notes:'Use scopes and cancelScope() or dispose the owner root.'
},
{
name:'using Date.now() beside fake timers',
purpose:'Tests advance scheduler time but metadata remains real-time.',
notes:'Use the injected clock or TimerClock for related timestamps.'
notes:'Use in services, tests and non-Svelte contexts.'
},
{
name:'createActiveTimers(options)',
purpose:'Reactive scheduler.',
notes:'Exposes entries() snapshots through Svelte state.'
},
{
name:'App.timers',
purpose:'Always-present App root.',
notes:'Injected into Connections and available to consumers.'
}
],
api: artifactApis.timer,
sections:[
{
title:'Creation and ownership',
body:[
'App.timers is the shared scheduler for browser-side artifacts. Connections, auto-refresh and debug panels should use this root instead of creating their own timer islands.',
'Create EngineTimers directly for deterministic unit tests, workers or server utilities that need an injected clock and do not need Svelte state.'
'Connections is a registry of named realtime connections. A connection is not the engine: EngineConnections owns all connection state; each Connection owns transport, channels, heartbeat, reconnect and request/reply.',
'The transport contract is pluggable. Browser WebSocket is one transport; tests and demos can use mock transports without changing the connection runtime.',
'When composed through App via defineActiveConnections, the registry receives Timers and Logger from the core. Identity tracking is decoupled: the canonical wiring uses orca presets (applyConnectionsReauthOnIdentityChange / applyConnectionsCloseOnRevoke) to bridge App.session events to the registry. Per-connection ConnectionSessionSource remains available for standalone or manual setups.'
],
dynamics:[
'Create one registry, then create named connections inside it. The registry tracks all names and aggregate state; each connection owns its transport lifecycle, channel collection, send buffer, heartbeat timers and reconnect strategy.',
'Transports emit open/message/close/failure signals. The connection translates those into framework states, schedules heartbeat and reconnect through Timers, and routes logs through the shared Logger/diagnostic constants.',
'Channels are scoped streams over a connection. They can join, leave, send and request. After reconnect, auto-join channels rejoin so feature code does not rebuild subscriptions manually.',
'Identity reauth is wired through the orca presets in $active-app/presets — applyStandardOrca(App) registers the canonical reactions (cache clear, perm invalidate, connections reauth on identity change, connections close on revoke). Each connection still needs its own auth() callback to produce a credential frame. Per-connection session options remain available for standalone or manual setups outside the App composition.'
],
commonMistakes:[
{
name:'treating Connection as the root',
purpose:'You lose registry-level lifecycle, aggregate state and disposal.',
notes:
'Create EngineConnections/ActiveConnections first, then createConnection(name, options).'
},
{
name:'using mock transports for real demos',
purpose:'It hides network ordering, close and reconnect behavior.',
notes:'Use createWebSocketTransport for demos intended to validate realtime behavior.'
},
{
name:'logging through ad-hoc callbacks',
purpose:'Reconnect/heartbeat/channel logs bypass the framework logger pipeline.',
notes:'Use the injected Logger and module diagnostics/constants.'
},
{
name:'forgetting Orca reauth behavior',
purpose:'Connections can keep old identity after login/logout/refresh.',
notes:'Wire the orca preset (applyStandardOrca or applyConnectionsReauthOnIdentityChange / applyConnectionsCloseOnRevoke) so identity changes flow through reauthenticateAll/closeAll. For standalone connections without orca, use the per-connection session option.'
notes:'Direct factory for tests or non-App contexts.'
},
{
name:'defineActiveConnections(options)',
purpose:'Service factory for the App schema.',
notes:'Registered as services.connections; the builder injects logger and timers from the core.'
},
{
name:'createWebSocketTransport(options)',
purpose:'Browser WebSocket transport.',
notes:'Real network transport for production and demos.'
},
{
name:'createMockTransport(options)',
purpose:'Test transport.',
notes:'No network; useful for deterministic tests.'
}
],
api: artifactApis.conn,
sections:[
{
title:'Creation and registry ownership',
body:[
'Declare the connections registry via defineActiveConnections in the App service schema. The registry is the root; individual connections are children owned by that registry.',
'Use one registry for related realtime connections so aggregate state, disposal and identity reauth reactions stay coordinated.'
notes:'Can reconnect on online/visible browser events.'
},
{
name:'request()',
purpose:'Request/reply over frames.',
notes:'ACK registry handles timeouts and replies.'
},
{
name:'channels',
purpose:'Topic-like scoped streams.',
notes:'Can auto-join and rejoin after reconnect.'
},
{
name:'buffer',
purpose:'Buffer/drop/fail sends while closed.',
notes:'Configurable max messages and bytes.'
}
]
},
{
title:'Session-aware reauth',
body:[
'The canonical pattern wires reauth through orca: applyStandardOrca(App) (or the individual applyConnectionsReauthOnIdentityChange / applyConnectionsCloseOnRevoke) calls reauthenticateAll() / closeAll() on App.connections when session events fire. Each connection still needs its own auth() callback to produce the credential frame.',
'For standalone or per-connection setups outside the App composition, the registry / connection still accepts ConnectionSessionSource ({ onChange }) via the session option. The internal session-wiring listens to onChange and reacts directly. This is the manual / advanced path.'
]
},
{
title:'Active State',
body:[
'ActiveConnections tracks names and states reactively: connectedNames, reconnectingNames, failedNames, allConnected and anyConnected are ready for UI panels.'
]
}
],
tests:[
{
name:'src/arts/connection/test',
purpose:'Connection runtime.',
notes:'States, channels, websocket transport and reauth behavior.'