|
|
|
|
|
# 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 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`:
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
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:
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
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` with
|
|
|
|
|
|
`create<Artifact>Diagnostics(logger?)` and emits catalogued events for
|
|
|
|
|
|
internal diagnostics.
|
|
|
|
|
|
- Diagnostic event names live in the artifact `consts.ts` as
|
|
|
|
|
|
`*_DIAGNOSTIC_EVENTS`. Messages live in `errors.ts` or `consts.ts`, never as
|
|
|
|
|
|
inline strings in 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:
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
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.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`, `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`](./lang/README.md) | `EngineLang`, `ActiveLang`, `ActiveMonoLang` | i18n: type-safe translations, BCP 47 resolution, plurals, refs, JSON round-trip | — |
|
|
|
|
|
|
| [`logr`](./logr/README.md) | `EngineLogger` | Structured logger: levels, transports, filters, vitals, dispose | — |
|
|
|
|
|
|
| [`timr`](./timr/README.md) | `EngineTimers`, `ActiveTimers` | Deterministic timer scheduler: clock injection, one-shots, intervals, cancellation, snapshots, backoff | `$libs/timers`, `$logger` (optional) |
|
|
|
|
|
|
| [`fmts`](./fmts/README.md) | `EngineFormat`, `ActiveFormat` | Localized formatting: numbers, currency, units, dates | `$logger` (currency) |
|
|
|
|
|
|
| [`adom`](./adom/README.md) | `ActiveDom` | Reactive DOM service: viewport, breakpoints, attribute writes, scroll lock | `$libs/dom`, `$reactive` |
|
|
|
|
|
|
| [`fend`](./fend/README.md) | `ActiveFrontend` | Frontend preferences: theme, mode, dir, density, applied via DOM attrs | `$adom` |
|
|
|
|
|
|
| [`sium`](./sium/README.md) | `EngineSium` | Validation contracts: schemas, issues, introspection, Standard Schema interop | `$lang` (optional), `$logger` (optional), `$libs/days`, `$libs/color` |
|
|
|
|
|
|
| [`stor`](./stor/README.md) | `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`](./http/README.md) | `EngineHttp` | HTTP client: tagged `HttpResult`, Standard Schema validation, retry, timeouts, hooks, SvelteKit `event.fetch` integration | `$libs/http`, `$libs/standard-schema` (type-only), `$logger` |
|
|
|
|
|
|
| [`sess`](./sess/README.md) | `EngineSession`, `ActiveSession` | Session lifecycle: adopt/revoke/refresh, auto-refresh, 401-rescue hook, SvelteKit SSR via `adoptServer` + cookie reader | `$storage`, `$timer`, `$http`, `$logger` (optional) |
|
|
|
|
|
|
| [`conn`](./conn/README.md) | `EngineConnections`, `ActiveConnections` | Realtime connection registry: transports, reconnect, heartbeat, request/reply, channels, session bridge | `$timer`, `$logger` (optional), `$session` bridge (optional) |
|
|
|
|
|
|
| [`auth`](./auth/README.md) | `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`](./perm/README.md) | `ActivePerms` (`EnginePerms` in `$svrs/perm`) | Authorization: policy runtime adapter, HTTP client/handlers, cache snapshot, `<Can />` guard | `$libs/perm`, `$libs/svrs`, `$http`, `$logger` (optional) |
|
|
|
|
|
|
| [`cach`](./cach/README.md) | `ActiveCache` (`EngineCache` in `$svrs/cache`) | Data cache: deterministic keys, policies, scopes, stale/revalidate, tags, memory/storage adapters | `$libs/cache`, `$storage` (adapter), `$logger` (optional) |
|
|
|
|
|
|
| [`aapp`](./aapp/README.md) | `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`:
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
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.lang.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.
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
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
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
- `lang` and `logr` are the dependency-free roots (`zero-dep` libraries).
|
|
|
|
|
|
- `fmts` consumes `logr` only inside `currency` (rate fetcher diagnostics).
|
|
|
|
|
|
- `sium` accepts `lang` and `logr` via injection; without them it falls back
|
|
|
|
|
|
to local message interpolation.
|
|
|
|
|
|
- `timr` is the deterministic scheduler consumed by `sess` and `conn`.
|
|
|
|
|
|
- `http` accepts `logr` via injection (auto-wired through `aapp`) and keeps
|
|
|
|
|
|
shared HTTP literals/types in `$libs/http`.
|
|
|
|
|
|
- `sess` consumes `stor` for persistence, `timr` for auto-refresh, and `http`
|
|
|
|
|
|
for 401-rescue integration.
|
|
|
|
|
|
- `conn` consumes `timr` for reconnect/heartbeat/ack timeouts and accepts the
|
|
|
|
|
|
App session bridge when composed through `aapp`.
|
|
|
|
|
|
- `auth` splits cleanly: `$svrs/auth` owns identity proof, CSRF and server
|
|
|
|
|
|
handlers; `$auth` owns the active client reflector. It feeds `sess`, `perm`
|
|
|
|
|
|
and `cach` 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 />`.
|
|
|
|
|
|
- `cach` splits cleanly: `$svrs/cache` owns the imperative engine, while
|
|
|
|
|
|
`$cache` owns the active Svelte wrapper and can consume `stor` through its
|
|
|
|
|
|
storage adapter.
|
|
|
|
|
|
- `adom` depends only on the pure helpers in `libs/dom` and on `libs/reactive`.
|
|
|
|
|
|
- `fend` depends on `adom` for DOM attribute writes.
|
|
|
|
|
|
- `aapp` composes 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
|
|
|
|
|
|
|
|
|
|
|
|
```js
|
|
|
|
|
|
// 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.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({ 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, tagged `HttpResult`
|
|
|
|
|
|
- `/test/sess` — session lifecycle: adopt/revoke/refresh with generation guard + dedup, auto-refresh, tagged `RevokeResult`, 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`.
|