|
|
5 months ago | |
|---|---|---|
| .. | ||
| active-app | 5 months ago | |
| adom | 5 months ago | |
| auth | 5 months ago | |
| bus | 5 months ago | |
| cache | 5 months ago | |
| connection | 5 months ago | |
| format | 5 months ago | |
| frontend | 5 months ago | |
| http | 5 months ago | |
| lang | 5 months ago | |
| logger | 5 months ago | |
| orca | 5 months ago | |
| perm | 5 months ago | |
| prefs | 5 months ago | |
| session | 5 months ago | |
| sium | 5 months ago | |
| storage | 5 months ago | |
| timer | 5 months ago | |
| README.md | 5 months ago | |
README.md
arts — runtime artifacts
src/arts/ contains the runtime building blocks of the application. Each
artifact is independent, has its own README, and follows two consistent
naming conventions:
Engine*— public methods over private state (or no state at all). Pure factory; the locale, logger or any volatile input is passed as argument on every call. When an artifact has a true server-authoritative counterpart (perm,cach), the engine lives undersrc/svrs/.Active*— anEngine*that exposes public reactive state. Lives in a.svelte.tsfile because it owns$state. Imports must target the file directly, not the barrel, to keep the rest of the artifact runes-free.
ActiveEngine Contract
Root active artifacts implement the shared ActiveEngine<TSnapshot, TError>
contract from $libs/active:
interface ActiveEngine<TSnapshot, TError> {
readonly loading: boolean;
readonly lastError: TError | null;
readonly disposed: boolean;
snapshot(): TSnapshot;
clearError(): void;
onChange(listener: (snapshot: TSnapshot) => void): () => void;
dispose(): void;
}
Conventions:
- Use direct getters (
Auth.current,Cache.loading,Perms.lastError), not a module-specific.stateobject. - Use
loading, neverpending, for in-flight work. - Use
onChange()for snapshot subscriptions. Lower-level clients may expose their own event buses, but active roots keep this name. dispose()is idempotent, clears owned listeners/entries/resources, and subsequent public operations throw the artifact'sXxxDisposedError.- Root active artifacts that create entries (
ActiveCache.entry(),ActiveConnections.connection(), etc.) own those entries and dispose them when the root is disposed.
Logger And Diagnostics Contract
Every artifact that emits runtime information follows the same two-layer contract:
import type { DiagnosticEvent, Diagnostics, Logger } from '$libs/logger';
- Public options use
logger?: Logger. Do not create artifact-local logger interfaces or narrowed aliases for individual modules. - The root logger implementation is
EngineLoggerfrom$logger; it extends the sharedLoggercontract from$libs/logger. - Artifact code defines
<Artifact>Diagnosticswithcreate<Artifact>Diagnostics(logger?)and emits catalogued events for internal diagnostics. - Diagnostic event names live in the artifact
consts.tsas*_DIAGNOSTIC_EVENTS. Messages live inerrors.tsorconsts.ts, never as inline strings in runtime logic. Diagnostics<TEvent>always exposes{ logger, emit(event) }. Theloggerproperty is the commonLogger, so modules that need an ad-hocinfoorerrorstill have the full logger without inventing a second interface.- Level routing is controlled by the logger/transports via the existing per-level enablement map, not by module-specific severity systems.
Typical shape:
export const HTTP_DIAGNOSTIC_EVENTS = {
REQUEST: 'http.request',
NETWORK_ERROR: 'http.network_error'
} as const;
export function createHttpDiagnostics(logger?: Logger): HttpDiagnostics {
return createCatalogDiagnostics({
logger,
defaultCategory: LOGGER_CATEGORY,
catalog: HTTP_DIAGNOSTIC_LOGS
});
}
This gives every module the same path to Sentry, Loki, Datadog, console,
test-capture transports or any future sink: inject one Logger, emit typed
diagnostic events, let logr route.
Error Contract
Errors follow the same rule: strings are centralized, and public programmer errors are typed.
- Error messages and error names live in
errors.tsorconsts.ts. - Runtime code must not throw inline string/template errors outside tests or vendored code.
- Public programmer errors use artifact-specific classes and guards:
SessionDisposedError,ConnInvalidNameError,UnitsUnknownUnitError, etc. - Expected runtime failures should be returned as tagged data/results when the
artifact already has such a contract (
http,conn,perm,cach). - Validation failures are data (
SiumValidationError.issues) and diagnostics are emitted separately when a logger is injected.
Map
| Artifact | Layer(s) | Purpose | Depends on |
|---|---|---|---|
lang |
EngineLang, ActiveLang, ActiveMonoLang |
i18n: type-safe translations, BCP 47 resolution, plurals, refs, JSON round-trip | — |
logr |
EngineLogger |
Structured logger: levels, transports, filters, vitals, dispose | — |
timr |
EngineTimers, ActiveTimers |
Deterministic timer scheduler: clock injection, one-shots, intervals, cancellation, snapshots, backoff | $libs/timers, $logger (optional) |
fmts |
EngineFormat, ActiveFormat |
Localized formatting: numbers, currency, units, dates | $logger (currency) |
adom |
ActiveDom |
Reactive DOM service: viewport, breakpoints, attribute writes, scroll lock | $libs/dom, $reactive |
fend |
ActiveFrontend |
Frontend preferences: theme, mode, dir, density, applied via DOM attrs | $adom |
sium |
EngineSium |
Validation contracts: schemas, issues, introspection, Standard Schema interop | $lang (optional), $logger (optional), $libs/days, $libs/color |
stor |
EngineStorage, ActiveStorage |
Reactive sync key/value: pluggable adapters, version+migrate, TTL, validation, intra-tab + cross-tab sync, reactive keys | $sium (Standard Schema interop, optional) |
http |
EngineHttp |
HTTP client: tagged HttpResult, Standard Schema validation, retry, timeouts, hooks, SvelteKit event.fetch integration |
$libs/http, $libs/standard-schema (type-only), $logger |
sess |
EngineSession, ActiveSession |
Session lifecycle: adopt/revoke/refresh, auto-refresh, 401-rescue hook, SvelteKit SSR via adoptServer + cookie reader |
$storage, $timer, $http, $logger (optional) |
conn |
EngineConnections, ActiveConnections |
Realtime connection registry: transports, reconnect, heartbeat, request/reply, channels, session bridge | $timer, $logger (optional), $session bridge (optional) |
auth |
ActiveAuth (EngineAuth in $svrs/auth) |
Authentication: password flows, CSRF, current session reflector, devices, logout, server-authoritative auth handlers | $libs/auth, $http, $cache, $svrs/auth |
perm |
ActivePerms (EnginePerms in $svrs/perm) |
Authorization: policy runtime adapter, HTTP client/handlers, cache snapshot, <Can /> guard |
$libs/perm, $libs/svrs, $http, $logger (optional) |
cach |
ActiveCache (EngineCache in $svrs/cache) |
Data cache: deterministic keys, policies, scopes, stale/revalidate, tags, memory/storage adapters | $libs/cache, $storage (adapter), $logger (optional) |
aapp |
ActiveApp |
App composition: wires Logger + Lang + Format + Frontend + Dom + Storage + Http + Timers + Cache; factories for Sess, Conn, Auth, Perm, Sium | every artifact above |
Composition
Most apps consume the artifacts through aapp:
import { createActiveApp } from '$active-app';
const App = createActiveApp({
lang: { schema, defaultLocale: 'es', fallbackChain: ['en'] },
logger: { level: LogLevel.INFO, transports: [consoleTransport()] },
frontend: { theme: 'base' }
});
App.lang.t('common.ok');
App.format.currency.format(99.5);
App.setLocale('es-MX'); // propagates to Lang, Format, Frontend
Every member of App is always present. When the caller does not
configure lang/logger/formats, App provides a structurally identical
adapter (mono lang, console logger, default-locale formats). Call sites stay
uniform: App.lang.t(...) and App.format.currency.format(...) work
whether or not i18n was configured.
Sium, Session, Connections, Auth and Perms are exposed as
factories because they are feature/page-scoped: App injects shared
services, but construction is explicit at the call site.
const App = createActiveApp({
services: {
sium: defineEngineSium({}),
connections: defineActiveConnections({}),
auth: defineActiveAuth({ initial: data.auth }),
perm: defineActivePerm({ endpoint: '/perm' })
}
});
applyStandardOrca(App);
See aapp/README.md for the full composition contract.
Cross-artifact dependencies
lang logr
\ / | \
\ / | \
fmts http timr
\ | /|\
adom ─── fend \ | / | conn
\ \ \ | / | \
\ \ \ | / auth perm
──────────────── aapp ─ stor ─ cach
:
sium
langandlograre the dependency-free roots (zero-deplibraries).fmtsconsumeslogronly insidecurrency(rate fetcher diagnostics).siumacceptslangandlogrvia injection; without them it falls back to local message interpolation.timris the deterministic scheduler consumed bysessandconn.httpacceptslogrvia injection (auto-wired throughaapp) and keeps shared HTTP literals/types in$libs/http.sessconsumesstorfor persistence,timrfor auto-refresh, andhttpfor 401-rescue integration.connconsumestimrfor reconnect/heartbeat/ack timeouts and accepts the App session bridge when composed throughaapp.authsplits cleanly:$svrs/authowns identity proof, CSRF and server handlers;$authowns the active client reflector. It feedssess,permandcachthrough ports rather than owning their state.permsplits cleanly:$svrs/permowns the authoritative engine/HTTP handlers, while$permowns the active UI reflector and<Can />.cachsplits cleanly:$svrs/cacheowns the imperative engine, while$cacheowns the active Svelte wrapper and can consumestorthrough its storage adapter.adomdepends only on the pure helpers inlibs/domand onlibs/reactive.fenddepends onadomfor DOM attribute writes.aappcomposes always-present roots and exposes factories for scoped artifacts (sium,sess,conn,auth,perm).
Shared types
| Module | Type | Used by |
|---|---|---|
$locale |
LocaleSource |
fmts.localeSource, fend.localeSource, aapp wiring |
$lang |
SupportedLocale (LangBase | ${LangBase}-${string}) |
lang, consumers that want type-safe locales |
Aliases
// svelte.config.js
alias: {
$active-app: 'src/arts/aapp',
$adom: 'src/arts/adom',
$auth: 'src/arts/auth',
$cache: 'src/arts/cach',
$connection: 'src/arts/conn',
$frontend: 'src/arts/fend',
$format: 'src/arts/fmts',
$http: 'src/arts/http',
$lang: 'src/arts/lang',
$logger: 'src/arts/logr',
$perm: 'src/arts/perm',
$session: 'src/arts/sess',
$sium: 'src/arts/sium',
$storage: 'src/arts/stor',
$svrs: 'src/svrs',
$timer: 'src/arts/timr',
$libs: 'src/libs',
$locale: 'src/libs/locale',
$reactive: 'src/libs/reactive'
}
Bundle policy
Every artifact is designed to tree-shake cleanly:
package.jsondeclares"sideEffects": ["**/*.css", "**/*.svelte"], so any.ts/.svelte.tsmodule that the bundler does not statically reach is dropped from the production bundle.- All barrels use named re-exports (
export { a, b } from './x') instead ofexport *. This lets the bundler prove which symbols are reached from a given import and drop the rest of the source module. .svelte.tsfiles defer module-level state (viewport.svelte.ts,body-scroll-lock.svelte.ts) so importing the barrel does not allocate Svelte runes runtime for unused features.- External adapters (
$logger/adapters/*) and dev helpers ($active-app/testing,$sium/_examples/) live outside the main barrel. A consumer that does not reference them never pays for them.
A consumer that builds createActiveApp({ lang: { schema } }) and only
calls App.lang.t(...) should land roughly in the 40–50 KB minified range.
A consumer that wires every artifact (Sium + Storage cookies + Frontend
preferences + Web Vitals) lands in the ~120 KB range. The difference is
the per-feature surface, paid only when reached.
Test pages
Each artifact ships an interactive page under src/web/routes/test/<artifact>:
/test/aapp— full composition end-to-end/test/ecosystem— total integration demo: auth, sess, perm, cach, http, stor, sium, fmts, fend, adom, timr, conn, lang and logr in one app flow/test/lang— i18n with reactive locale switching, plurals, BCP 47/test/logr— log levels, transports, vitals, Sentry integration/test/fmts— numbers / currency / units / dates with shared locale/test/fend— theme, mode, dir, density applied to a target/test/adom— viewport, breakpoints, scroll lock, roving focus/test/sium— login / signup / profile schemas with translated issues/test/stor— adapters (memory / local / session / cookie), envelope versioning + migrate, raw mode, TTL, mergeDefaults, cross-tab sync/test/http— GET/POST with Sium validation, retry + Retry-After, timeout, cancellation, lifecycle hooks, taggedHttpResult/test/sess— session lifecycle: adopt/revoke/refresh with generation guard + dedup, auto-refresh, taggedRevokeResult, permission checks, event stream/test/timr— scheduler snapshots, intervals, cancellation and deterministic clocks/test/conn— websocket chat and connection/channel lifecycle/test/auth— server-authoritative auth surface: password flow, CSRF, devices, routes and security events/test/perm— authorization checks,<Can />, HTTP handlers and client cache/test/cach— cache policies, scopes, tags and active entries
Index at /test.