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/src/arts/README.md

17 KiB

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, cache), the engine lives under src/svrs/.
  • Active* — an Engine* that exposes public reactive state. Lives in a .svelte.ts file 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 .state object.
  • Use loading, never pending, 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's XxxDisposedError.
  • 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 EngineLogger from $logger; it extends the shared Logger contract from $libs/logger.
  • Artifact code defines <Artifact>Diagnostics in a dedicated diagnostics.ts file — the canonical home for the DiagnosticCatalog, create<Artifact>Diagnostics(logger?) and emit<Artifact>Diagnostic(...). Every artifact that emits diagnostics ships this file.
  • Diagnostic event names live in the artifact consts.ts as *_DIAGNOSTIC_EVENTS. Message strings are named constants in consts.ts or errors.ts (or co-located in diagnostics.ts when only the catalog reads them) — never inline string literals in the catalog or runtime logic.
  • Diagnostics<TEvent> always exposes { logger, emit(event) }. The logger property is the common Logger, so modules that need an ad-hoc info or error still 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 logger 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.ts or consts.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, connection, perm, cache).
  • Validation failures are data (SiumValidationError.issues) and diagnostics are emitted separately when a logger is injected.

Map

Artifact Layer(s) Purpose Depends on
langs EngineLangs, ActiveLangs, ActiveMonoLangs i18n: type-safe translations, BCP 47 resolution, plurals, refs, JSON round-trip —
logger EngineLogger Structured logger: levels, transports, filters, vitals, dispose —
timer EngineTimers, ActiveTimers Deterministic timer scheduler: clock injection, one-shots, intervals, cancellation, snapshots, backoff $libs/timers, $logger (optional)
format EngineFormat, ActiveFormat Localized formatting: numbers, currency, units, dates $logger (currency)
adom ActiveDom Reactive DOM service: viewport, breakpoints, attribute writes, scroll lock $libs/dom, $reactive
motion EngineMotion Animation runtime: registers + runs --state presets (CSS settle / JS drivers — spring / waapi / rect FLIP); the bridge BOTH UIX layers consume via uix.motion MotionDom port (injected; adom satisfies it)
clipboard ActiveClipboard Clipboard write capability with injectable writer and explicit unavailable errors browser navigator.clipboard or injected writer
sium EngineSium Validation contracts: schemas, issues, introspection, Standard Schema interop $langs (optional), $logger (optional), $libs/days, $libs/color
storage 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
session EngineSession, ActiveSession Session lifecycle: adopt/revoke/refresh, auto-refresh, 401-rescue hook, SvelteKit SSR via adoptServer + cookie reader $storage, $timer, $http, $logger (optional)
connection 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)
cache ActiveCache (EngineCache in $svrs/cache) Data cache: deterministic keys, policies, scopes, stale/revalidate, tags, memory/storage adapters $libs/cache, $storage (adapter), $logger (optional)
active-app ActiveApp App composition: core Logger + Bus + Timers + Orca + Prefs, plus declared services via factories every artifact above

Composition

Most apps consume the artifacts through active-app:

import { createActiveApp } from '$active-app';
import {
	defineActiveClipboard,
	defineActiveDom,
	defineActiveFormat,
	defineActiveLangs
} from '$active-app/service-factories';

const App = createActiveApp({
	logger: { level: LogLevel.INFO, transports: [consoleTransport()] },
	services: {
		langs: defineActiveLangs({ schema, defaultLocale: 'es', fallbackChain: ['en'] }),
		clipboard: defineActiveClipboard(),
		format: defineActiveFormat(),
		dom: defineActiveDom()
	}
});

App.langs.t('common.ok');
App.format.currency.format(99.5);
App.prefs.language.set('es-MX'); // propagates to langs when wired by the factory

App always exposes the fixed core (logger, bus, timers, orca, prefs). Feature services exist only when the application declares their slot. For translations that means App.langs exists when services.langs is declared with defineActiveLangs(...); otherwise the property is not part of the typed surface.

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 active-app/README.md for the full composition contract.

Cross-artifact dependencies

                 langs     logger
                   \      / |  \
                    \    /  |   \
                   format  http  timer
                      \      |    /|\
adom ──────────────────\     |   / | connection
   \                    \    |  /  |  \
    \                    \   | /  auth perm
     ───────────── active-app ─ storage ─ cache
                       :
                      sium
  • langs and logger are the dependency-free roots (zero-dep libraries).
  • format consumes logger only inside currency (rate fetcher diagnostics).
  • sium accepts langs and logger via injection; without them it falls back to local message interpolation.
  • timer is the deterministic scheduler consumed by session and connection.
  • http accepts logger via injection (auto-wired through active-app) and keeps shared HTTP literals/types in $libs/http.
  • session consumes storage for persistence, timer for auto-refresh, and http for 401-rescue integration.
  • connection consumes timer for reconnect/heartbeat/ack timeouts and accepts the App session bridge when composed through active-app.
  • auth splits cleanly: $svrs/auth owns identity proof, CSRF and server handlers; $auth owns the active client reflector. It feeds session, perm and cache through ports rather than owning their state.
  • perm splits cleanly: $svrs/perm owns the authoritative engine/HTTP handlers, while $perm owns the active UI reflector and <Can />.
  • cache splits cleanly: $svrs/cache owns the imperative engine, while $cache owns the active Svelte wrapper and can consume storage through its storage adapter.
  • adom depends only on the pure helpers in libs/dom and on libs/reactive.
  • motion is the animation engine consumed by BOTH UIX layers via uix.motion (soma's Presence + eidos wrappers), which dissolves the would-be soma→eidos coupling. It imports no other art — the DOM dependency arrives injected via the structural MotionDom port, which adom satisfies.
  • active-app composes always-present roots and exposes factories for scoped artifacts (sium, session, connection, auth, perm).

Shared types

Module Type Used by
$locale LocaleSource format.localeSource, active-app wiring
$langs SupportedLocale (LangBase | ${LangBase}-${string}) langs, consumers that want type-safe locales

Aliases

// svelte.config.js
alias: {
    $active-app:     'src/arts/active-app',
    $adom:     'src/arts/adom',
    $auth:     'src/arts/auth',
    $cache:     'src/arts/cache',
    $clipboard:     'src/arts/clipboard',
    $connection:     'src/arts/connection',
    $format:     'src/arts/format',
    $http:     'src/arts/http',
    $langs:     'src/arts/langs',
    $logger:     'src/arts/logger',
    $motion:     'src/arts/motion',
    $perm:     'src/arts/perm',
    $session:     'src/arts/session',
    $sium:     'src/arts/sium',
    $storage:     'src/arts/storage',
    $svrs:     'src/svrs',
    $timer:     'src/arts/timer',
    $libs:     'src/libs',
    $locale:   'src/libs/locale',
    $reactive: 'src/libs/reactive'
}

Bundle policy

Every artifact is designed to tree-shake cleanly:

  • package.json declares "sideEffects": ["**/*.css", "**/*.svelte"], so any .ts / .svelte.ts module 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 of export *. This lets the bundler prove which symbols are reached from a given import and drop the rest of the source module.
  • .svelte.ts files 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({ services: { langs: defineActiveLangs({ schema }) } }) and only calls App.langs.t(...) should land roughly in the 40–50 KB minified range. A consumer that wires every artifact (Sium + Storage cookies + DOM runtime + Web Vitals) lands in the ~120 KB range. The difference is the per-feature surface, paid only when reached.

Test pages

Interactive docs now live under web/routes/active and web/routes/uix. Older /test/* pages may still exist in local branches, but the canonical artifact names are the directory names listed in the map above:

  • active-app — full composition end-to-end
  • ecosystem — total integration demo: auth, session, perm, cache, http, storage, sium, format, adom, timer, connection, langs and logger in one app flow
  • langs — i18n with reactive locale switching, plurals, BCP 47
  • logger — log levels, transports, vitals, Sentry integration
  • format — numbers / currency / units / dates with shared locale
  • adom — viewport, breakpoints, scroll lock, roving focus
  • sium — login / signup / profile schemas with translated issues
  • storage — adapters (memory / local / session / cookie), envelope versioning + migrate, raw mode, TTL, mergeDefaults, cross-tab sync
  • http — GET/POST with Sium validation, retry + Retry-After, timeout, cancellation, lifecycle hooks, tagged HttpResult
  • session — session lifecycle: adopt/revoke/refresh with generation guard + dedup, auto-refresh, tagged RevokeResult, permission checks, event stream
  • timer — scheduler snapshots, intervals, cancellation and deterministic clocks
  • connection — websocket chat and connection/channel lifecycle
  • auth — server-authoritative auth surface: password flow, CSRF, devices, routes and security events
  • perm — authorization checks, <Can />, HTTP handlers and client cache
  • cache — cache policies, scopes, tags and active entries

Use /active for the current application/runtime docs and /uix for the UIX component system docs.

Powered by TurnKey Linux.