Consolidate docs and format runtime cleanup

master
dev 5 months ago
parent 6555c3b23e
commit 7b54bdac4c

@ -4,14 +4,20 @@ Estado al cierre:
- Suite unitaria verde: `npm test` -> 103 archivos, 1189 tests. - Suite unitaria verde: `npm test` -> 103 archivos, 1189 tests.
- `fmts` verde: `npx vitest run src/arts/fmts` -> 14 archivos, 39 tests. - `fmts` verde: `npx vitest run src/arts/fmts` -> 14 archivos, 39 tests.
- `conn` verde: `npx vitest run src/arts/conn` -> 5 archivos, 28 tests.
- `auth` verde: `npx vitest run src/arts/auth src/svrs/auth src/libs/auth` -> 4 archivos, 14 tests.
- `/test/ecosystem` revisado en navegador: carga sin errores de consola, `ar` cambia a `rtl`, Formats se actualiza por locale, Perm cambia con rol `viewer`, Cach re-scopea por locale y Conn loopback publica/recibe.
- `src/arts/aapp/test/ecosystem.integration.test.ts` ampliado para cubrir rol `viewer` no-allow y cache re-scoped por locale.
- Referencias residuales de marca anterior eliminadas de `src/` fuera de rutas temporales: docs, páginas de test y constantes de cookies/headers auth usan ahora `Active/active`.
- `src/arts/conn/README.md` ampliado: contrato de raíz/conexión/canal, estados, transportes, request/reply, reconnect, heartbeat, sesión, diagnostics/logger, errores y testing.
- `fmts` redujo boilerplate activo con helpers `readFrom` / `writeTo` en `active-runtime.svelte.ts`; APIs públicas sin cambios.
- No tocar `src/web/routes/temp/` hasta decidir que hacer con esa pagina. - No tocar `src/web/routes/temp/` hasta decidir que hacer con esa pagina.
- No commitear `.idea/`, `.claude/` ni `.opencode/`. - No commitear `.idea/`, `.claude/` ni `.opencode/`.
Pendiente para manana: Pendiente para manana:
- Ejecutar una pasada completa sobre `/test/ecosystem` en navegador y corregir cualquier fallo real de integracion. - Revisar documentacion restante de `cach`, `perm`, `auth` y `fmts` con ojo de consumidor externo, no solo tecnico.
- Revisar la adopcion final del contrato comun `Logger` / diagnostics en todos los modulos, sin acoplar artefactos a `arts/logr`. - Continuar la reduccion de archivos grandes: prioridad `conn/connection.ts`, `libs/cach/engine.ts`, `svrs/auth/engine-auth.ts` y `svrs/auth/adapters/memory-store.ts`.
- Continuar la reduccion de archivos grandes y boilerplate: prioridad `conn`, `auth`, `cach` y cualquier wrapper activo repetitivo.
- Ampliar tests de integracion cruzada: `auth + sess + perm + cach + http + stor + fmts + conn + timr + logr`. - Ampliar tests de integracion cruzada: `auth + sess + perm + cach + http + stor + fmts + conn + timr + logr`.
- Revisar documentacion raiz de `arts`, `aapp`, `auth`, `cach`, `perm`, `conn` y `fmts` para que refleje el estado real del framework. - Revisar la adopcion final del contrato comun `Logger` / diagnostics en todos los modulos, sin acoplar artefactos a `arts/logr`; primera pasada limpia salvo `aapp` como composition root, `arts/logr` y tests.
- Decidir que hacer con la pagina temporal que bloquea `npm run check`; mientras tanto, validar con `npm test` y tests focalizados. - Decidir que hacer con la pagina temporal que bloquea `npm run check`; mientras tanto, validar con `npm test` y tests focalizados.

@ -42,6 +42,7 @@ App.dispose();
| `App.Cache` | yes | `ActiveCache` backed by memory by default. Configure `cache: { adapter, policies, scopeResolver }` for persistence, custom policies or tenant/actor/permission-aware keys | | `App.Cache` | yes | `ActiveCache` backed by memory by default. Configure `cache: { adapter, policies, scopeResolver }` for persistence, custom policies or tenant/actor/permission-aware keys |
| `App.Sess` | no | created lazily through `App.createActiveSession(...)`. Logger is injected automatically; storage, refresh/revoke handlers and HTTP hooks remain explicit so auth policy does not become hidden magic | | `App.Sess` | no | created lazily through `App.createActiveSession(...)`. Logger is injected automatically; storage, refresh/revoke handlers and HTTP hooks remain explicit so auth policy does not become hidden magic |
| `App.Auth` | no | created lazily through `App.createActiveAuth(...)`. App injects `Http` and `Timers`; the server authority remains `$svrs/auth` | | `App.Auth` | no | created lazily through `App.createActiveAuth(...)`. App injects `Http` and `Timers`; the server authority remains `$svrs/auth` |
| `App.Permissions` | no | created lazily through `App.createActivePermissions(...)`. App injects `Http` and `Logger`; the authoritative policy engine remains `$svrs/perm` |
| `Connections` | no | created lazily through `App.createActiveConnections(...)`. App injects Logger, Timers and a structural session bridge; each connection opts into session behavior independently | | `Connections` | no | created lazily through `App.createActiveConnections(...)`. App injects Logger, Timers and a structural session bridge; each connection opts into session behavior independently |
Server-authoritative engines that have a browser reflector live under Server-authoritative engines that have a browser reflector live under
@ -129,6 +130,13 @@ option.
10. **Sess** — created lazily via `App.createActiveSession(...)`, not from 10. **Sess** — created lazily via `App.createActiveSession(...)`, not from
`createActiveApp(...)` options. App injects Logger, but the consumer keeps `createActiveApp(...)` options. App injects Logger, but the consumer keeps
auth policy explicit (`storage`, `onRefresh`, `onRevoke`, HTTP hooks). auth policy explicit (`storage`, `onRefresh`, `onRevoke`, HTTP hooks).
11. **Connections** — created lazily via `App.createActiveConnections(...)`.
App injects Logger, Timers and a structural session bridge.
12. **Permissions** — created lazily via `App.createActivePermissions(...)`.
App injects Logger and Http; endpoint/defaults can be provided either in
`createActiveApp({ permissions })` or at the factory call site.
13. **Auth** — created lazily via `App.createActiveAuth(...)`. App injects
Http, Cache and Logger; `$svrs/auth` remains the server authority.
`dispose()` runs in reverse order. `dispose()` runs in reverse order.
@ -464,6 +472,8 @@ interface ActiveAppOptions<S extends LangNode> {
timers?: Omit<EngineTimersOptions, 'logger'>; timers?: Omit<EngineTimersOptions, 'logger'>;
cache?: Omit<ActiveCacheOptions, 'logger'>; cache?: Omit<ActiveCacheOptions, 'logger'>;
connections?: Omit<ActiveConnectionsOptions, 'logger' | 'timers' | 'session'>; connections?: Omit<ActiveConnectionsOptions, 'logger' | 'timers' | 'session'>;
permissions?: Omit<ActivePermissionsOptions, 'logger' | 'http'>;
auth?: Omit<ActiveAuthOptions, 'http' | 'cach' | 'logger'>;
} }
interface ActiveApp<S extends LangNode = LangNode> { interface ActiveApp<S extends LangNode = LangNode> {
@ -476,7 +486,6 @@ interface ActiveApp<S extends LangNode = LangNode> {
readonly Http: EngineHttp; readonly Http: EngineHttp;
readonly Timers: ActiveTimers; readonly Timers: ActiveTimers;
readonly Cache: ActiveCache; readonly Cache: ActiveCache;
readonly Sess: ActiveSession<unknown, unknown, unknown> | undefined;
getLocale(): SupportedLocale; getLocale(): SupportedLocale;
setLocale(locale: SupportedLocale): void; setLocale(locale: SupportedLocale): void;
@ -489,6 +498,14 @@ interface ActiveApp<S extends LangNode = LangNode> {
createActiveConnections<TConnections extends ConnectionMap = ConnectionMap>( createActiveConnections<TConnections extends ConnectionMap = ConnectionMap>(
options?: Omit<ActiveConnectionsOptions, 'logger' | 'timers' | 'session'> options?: Omit<ActiveConnectionsOptions, 'logger' | 'timers' | 'session'>
): ActiveConnections<TConnections>; ): ActiveConnections<TConnections>;
createActivePermissions(
options?: Partial<Omit<ActivePermissionsOptions, 'logger' | 'http'>>
): ActivePermissions;
createActiveAuth(options?: Omit<ActiveAuthOptions, 'http' | 'cach' | 'logger'>): ActiveAuth;
readonly Sess: ActiveSession<unknown, unknown, unknown> | undefined;
readonly Permissions: ActivePermissions | undefined;
readonly Auth: ActiveAuth | undefined;
dispose(): void; dispose(): void;
} }
@ -509,5 +526,5 @@ just to use Lang or Formats. Keeping Sium page-scoped means:
The standard pattern is one line at the top of the page module: The standard pattern is one line at the top of the page module:
```ts ```ts
const sium = createEngineSium({ lang: App.Lang, logger: App.Logger }); const sium = App.createSiumEngine();
``` ```

@ -22,6 +22,7 @@ import {
import { LogLevel, type LogEntry } from '$logr'; import { LogLevel, type LogEntry } from '$logr';
import { import {
PERMISSION_EFFECT_ALLOW, PERMISSION_EFFECT_ALLOW,
PERMISSION_EFFECT_NOT_APPLICABLE,
actor, actor,
allow, allow,
and, and,
@ -44,6 +45,7 @@ const PROJECT_ACTION_UPDATE = 'project.update';
const PROJECT_RELATION_MEMBER = 'project.member'; const PROJECT_RELATION_MEMBER = 'project.member';
const PROJECT_SCHEMA_VERSION = 'ProjectPayload:v1'; const PROJECT_SCHEMA_VERSION = 'ProjectPayload:v1';
const ROLE_ADMIN = 'admin'; const ROLE_ADMIN = 'admin';
const ROLE_VIEWER = 'viewer';
const TIMER_KEY = 'ecosystem:test'; const TIMER_KEY = 'ecosystem:test';
const CONNECTION_NAME = 'updates'; const CONNECTION_NAME = 'updates';
const LOG_CATEGORY = 'test.ecosystem'; const LOG_CATEGORY = 'test.ecosystem';
@ -85,20 +87,21 @@ describe('ActiveApp — total ecosystem integration', () => {
it('wires auth, sess, perm, cach, http, stor, sium, fmts, fend, adom, timr, conn and logr', async () => { it('wires auth, sess, perm, cach, http, stor, sium, fmts, fend, adom, timr, conn and logr', async () => {
const entries: LogEntry[] = []; const entries: LogEntry[] = [];
const authCurrent = createAuthCurrent(); const authCurrent = createAuthCurrent();
const actorRef: SubjectRef = { let actorRole = ROLE_ADMIN;
const actorRef = (): SubjectRef => ({
type: 'user', type: 'user',
id: ACTOR_ID, id: ACTOR_ID,
role: ROLE_ADMIN, role: actorRole,
tenantIds: [TENANT_ID] tenantIds: [TENANT_ID]
}; });
const resource: ProjectResource = { const resource: ProjectResource = {
type: 'project', type: 'project',
id: PROJECT_ID, id: PROJECT_ID,
tenantId: TENANT_ID, tenantId: TENANT_ID,
locked: false locked: false
}; };
const permissionEngine = createPermissionEngine(actorRef); const permissionEngine = createPermissionEngine();
const permissionHandlers = createPermissionHttpHandlers(permissionEngine, () => actorRef); const permissionHandlers = createPermissionHttpHandlers(permissionEngine, actorRef);
let projectFetches = 0; let projectFetches = 0;
const appFetch = (async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => { const appFetch = (async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
@ -164,7 +167,7 @@ describe('ActiveApp — total ecosystem integration', () => {
scopeResolver: (): ResolvedScopeValues => ({ scopeResolver: (): ResolvedScopeValues => ({
tenantId: TENANT_ID, tenantId: TENANT_ID,
actorId: ACTOR_ID, actorId: ACTOR_ID,
permissionHash: ROLE_ADMIN, permissionHash: actorRole,
locale: App.getLocale() locale: App.getLocale()
}), }),
policies: { policies: {
@ -218,7 +221,7 @@ describe('ActiveApp — total ecosystem integration', () => {
const Permissions = App.createActivePermissions({ const Permissions = App.createActivePermissions({
endpoint: PERM_ENDPOINT, endpoint: PERM_ENDPOINT,
scopeKey: ACTOR_ID scopeKey: () => `${ACTOR_ID}:${actorRole}`
}); });
const decision = await Permissions.check({ const decision = await Permissions.check({
action: PROJECT_ACTION_UPDATE, action: PROJECT_ACTION_UPDATE,
@ -227,6 +230,18 @@ describe('ActiveApp — total ecosystem integration', () => {
}); });
expect(decision.effect).toBe(PERMISSION_EFFECT_ALLOW); expect(decision.effect).toBe(PERMISSION_EFFECT_ALLOW);
actorRole = ROLE_VIEWER;
Permissions.invalidate();
const viewerDecision = await Permissions.check({
action: PROJECT_ACTION_UPDATE,
resource,
context: { risk: { mfa: true } }
});
expect(viewerDecision.effect).toBe(PERMISSION_EFFECT_NOT_APPLICABLE);
actorRole = ROLE_ADMIN;
Permissions.invalidate();
const project = await App.Cache.query<ProjectPayload>({ const project = await App.Cache.query<ProjectPayload>({
key: ['project', PROJECT_ID], key: ['project', PROJECT_ID],
scope: CACHE_SCOPE_TENANT, scope: CACHE_SCOPE_TENANT,
@ -255,6 +270,24 @@ describe('ActiveApp — total ecosystem integration', () => {
expect(cached.version).toBe(1); expect(cached.version).toBe(1);
expect(projectFetches).toBe(1); expect(projectFetches).toBe(1);
App.setLocale('es');
const localizedProject = await App.Cache.query<ProjectPayload>({
key: ['project', PROJECT_ID],
scope: CACHE_SCOPE_TENANT,
policy: CACHE_POLICY_INTERACTIVE,
schemaVersion: PROJECT_SCHEMA_VERSION,
tags: [{ type: 'project', id: PROJECT_ID }],
fetcher: async () => {
const response = await App.Http.get(HTTP_PROJECT_PATH, {
schema: JSON_PASSTHROUGH_SCHEMA
});
if (!response.ok) throw new Error('localized project request failed');
return response.value as ProjectPayload;
}
});
expect(localizedProject.version).toBe(2);
expect(projectFetches).toBe(2);
let timerRan = false; let timerRan = false;
App.Timers.schedule(TIMER_KEY, 0, () => { App.Timers.schedule(TIMER_KEY, 0, () => {
timerRan = true; timerRan = true;
@ -280,7 +313,7 @@ describe('ActiveApp — total ecosystem integration', () => {
}); });
}); });
function createPermissionEngine(actorRef: SubjectRef) { function createPermissionEngine() {
const schema = definePermSchema({ const schema = definePermSchema({
actors: { actors: {
user: { user: {
@ -323,7 +356,7 @@ function createPermissionEngine(actorRef: SubjectRef) {
relations: { relations: {
hasRelation({ relation, resource, subject }) { hasRelation({ relation, resource, subject }) {
if (relation !== PROJECT_RELATION_MEMBER) return 'unknown'; if (relation !== PROJECT_RELATION_MEMBER) return 'unknown';
expect(subject.id).toBe(actorRef.id); expect(subject.id).toBe(ACTOR_ID);
return Array.isArray(subject.tenantIds) && subject.tenantIds.includes(resource.tenantId); return Array.isArray(subject.tenantIds) && subject.tenantIds.includes(resource.tenantId);
} }
} }

@ -19,7 +19,7 @@ Su responsabilidad actual es más pequeña y más concreta:
En otras palabras: En otras palabras:
```text ```text
libs/dom -> arts/adom -> app.dom / soma.dom libs/dom -> arts/adom -> App.Dom / app.dom
puro reactivo consumo de app puro reactivo consumo de app
``` ```
@ -68,8 +68,8 @@ Runtime reactivo de DOM:
`ActiveDom` vive a nivel de aplicación: `ActiveDom` vive a nivel de aplicación:
```ts ```ts
App.Dom;
app.dom; app.dom;
soma.dom;
``` ```
La implementación se consume desde la capa de aplicación y desde artefactos que La implementación se consume desde la capa de aplicación y desde artefactos que
@ -183,7 +183,7 @@ top-level `window`).
Además, `uix/adom` puede alojar helpers DOM con estado global real, como Además, `uix/adom` puede alojar helpers DOM con estado global real, como
`BodyScrollLock`, o helpers scoped de runtime como `DOMContext`, cuando ya no `BodyScrollLock`, o helpers scoped de runtime como `DOMContext`, cuando ya no
son primitives puras de `libs/dom` pero tampoco pertenecen a `Soma`. son primitives puras de `libs/dom` pero tampoco pertenecen a un componente UI concreto.
También caben aquí helpers runtime de foco con estado propio, como También caben aquí helpers runtime de foco con estado propio, como
`RovingFocusGroup`, que reutilizan `uix/lib/dom` por debajo pero ya no son `RovingFocusGroup`, que reutilizan `uix/lib/dom` por debajo pero ya no son
@ -230,7 +230,7 @@ Su código actual sirve como referencia histórica para extraer utilidades hacia
Lo que ya está cerrado: Lo que ya está cerrado:
- `app.dom` - `app.dom`
- `soma.dom` - `App.Dom`
- `viewport` - `viewport`
- `breakpoints` - `breakpoints`
- `currentBreakpoint` - `currentBreakpoint`

@ -1,7 +1,7 @@
/** /**
* `apply` / `remove` — the only mutation surface of `ActiveDom`. * `apply` / `remove` — the only mutation surface of `ActiveDom`.
* *
* `ActiveDom` doesn't know Morfo, Sema, Soma, or Eidos. It receives instructions * `ActiveDom` doesn't know product frameworks. It receives instructions
* already resolved by upper layers and applies them to the DOM. Nothing else. * already resolved by upper layers and applies them to the DOM. Nothing else.
* *
* Contract: * Contract:

@ -1,6 +1,24 @@
# `arts/conn` # conn
`conn` manages runtime realtime connections without coupling the app to WebSocket as a concept. The root artifact is a registry: `conn` es el artefacto de conexiones realtime. No es "un wrapper de
WebSocket": es un registro de conexiones con transporte intercambiable,
reconexión, heartbeat, request/reply, canales, integración opcional con sesión
y diagnósticos estructurados.
La regla de nombres es importante:
| Concepto | Nombre |
| -------- | ------ |
| Raíz imperativa | `createEngineConnections()` / `EngineConnections` |
| Raíz reactiva | `createActiveConnections()` / `ActiveConnections` |
| Unidad individual | `Connection` |
| Topic lógico dentro de una conexión | `ConnectionChannel` |
No existe `EngineConnection` ni `ActiveConnection`. `Engine*` y `Active*`
quedan reservados para raíces de artefacto; una conexión individual no es una
raíz, es una entidad gestionada por `EngineConnections`.
## Uso Mínimo
```ts ```ts
import { createEngineConnections, createWebSocketTransport } from '$conn'; import { createEngineConnections, createWebSocketTransport } from '$conn';
@ -15,27 +33,59 @@ const Main = Connections.createConnection('main', {
await Main.connect(); await Main.connect();
``` ```
## Naming La raíz mantiene el mapa de conexiones:
```ts
Connections.names();
Connections.connection('main');
await Connections.openConnection('main');
Connections.closeConnection('main', 'manual');
await Connections.reconnectAll('network-restored');
Connections.dispose();
```
`dispose()` de la raíz cierra conexiones, cancela timers propios y limpia los
listeners. Después de `dispose()`, las operaciones públicas lanzan errores
tipados `Conn*`.
- Root engine: `createEngineConnections()` / `EngineConnections`. ## ActiveConnections
- Reactive root: `createActiveConnections()` / `ActiveConnections`.
- Individual unit: `Connection`.
- Logical topic: `ConnectionChannel`.
There is intentionally no `EngineConnection` or `ActiveConnection`; `Engine*` and `Active*` are reserved for artifact roots. La capa activa añade estado derivado para UI:
## App Integration ```ts
const Connections = createActiveConnections();
Connections.size;
Connections.activeNames;
Connections.states;
Connections.connectedNames;
Connections.failedNames;
Connections.anyConnected;
Connections.anyFailed;
```
`ActiveConnections` conserva la misma API de creación/consulta que el engine,
pero sus colecciones reflejan los cambios de estado de cada conexión.
`aapp` exposes a factory instead of a permanent property: ## Integración Con App
`aapp` expone una factory porque las conexiones son app-scoped y suelen tener
tipos específicos del proyecto:
```ts ```ts
const Connections = App.createActiveConnections<AppConnections>(); const Connections = App.createActiveConnections<AppConnections>();
``` ```
App injects `Logger`, `Timers` and a structural session event bridge. A connection reacts to session events only when it opts in: App inyecta:
- `App.Logger`, como `Logger` común de `$libs/logr`.
- `App.Timers`, para reconexión, heartbeat y timeouts de ack.
- Un bridge estructural de sesión, si `App.Sess` existe.
Cada conexión decide si usa la sesión:
```ts ```ts
Connections.createConnection('main', { const Main = Connections.createConnection('main', {
transport: createWebSocketTransport({ url: '/realtime' }), transport: createWebSocketTransport({ url: '/realtime' }),
auth: () => ({ token: App.Sess?.current?.credential }), auth: () => ({ token: App.Sess?.current?.credential }),
session: { session: {
@ -46,33 +96,325 @@ Connections.createConnection('main', {
}); });
``` ```
When `Timers` is not injected, `createEngineConnections()` creates a private Cuando `Timers` no se inyecta, `createEngineConnections()` crea un scheduler
timer engine with the same logger. Those scheduler diagnostics are emitted privado con el mismo logger. Los diagnósticos del scheduler salen bajo la
under the `timr` logger category; connection-specific logs stay scoped as categoría `timr`; los de conexiones salen bajo `conn` o `conn:<name>`.
`conn:<name>`.
## Estados
Estados de conexión:
```txt
idle -> connecting -> open
open -> reconnecting -> open
open -> closing -> closed
connecting/reconnecting -> failed
```
Campos útiles:
```ts
Main.state;
Main.connected;
Main.generation;
Main.error;
Main.openedAt;
Main.closedAt;
Main.lastMessageAt;
Main.reconnectAttempt;
```
`generation` cambia cuando se abre una conexión nueva. Los timeouts de ack,
heartbeat y reconexión usan esa generación para no resolver trabajo viejo sobre
una conexión nueva.
## Transports
El contrato mínimo es `ConnectionTransport`:
```ts
interface ConnectionTransport {
readonly kind: string;
readonly state: ConnectionTransportState;
readonly bufferedAmount: number;
readonly canSend: boolean;
open(): Promise<void>;
send(data: string | ArrayBuffer): Promise<void> | void;
close(code?: number, reason?: string): void;
onOpen(listener: () => void): () => void;
onMessage(listener: (message: string | ArrayBuffer) => void): () => void;
onClose(listener: (event: ConnectionCloseEvent) => void): () => void;
onError(listener: (error: unknown) => void): () => void;
}
```
Incluidos:
- `createWebSocketTransport()` para navegador/runtime con WebSocket.
- `createMockTransport()` para tests, loopback y páginas de diagnóstico.
El transporte no decide reconexión, auth, heartbeat ni canales. Solo abre,
envía, cierra y emite eventos.
## Frames
El frame canónico:
```ts
interface ConnectionFrame<TType extends string = string, TPayload = unknown> {
readonly id?: string;
readonly topic?: string;
readonly type: TType;
readonly payload: TPayload;
readonly ts?: number;
readonly ack?: boolean;
readonly replyTo?: string;
readonly error?: ConnectionFrameError;
}
```
El serializer por defecto es JSON y valida la forma mínima del frame. Errores
de encode/decode no se lanzan como strings dispersos: devuelven resultados
tagged o errores `ConnInvalidFrameError` según el punto de entrada.
## Channels ## Send Y Request/Reply
Channels are cached by name and route frames by `topic`. `send()` devuelve un resultado tagged:
```ts ```ts
const Orders = Main.channel<{ 'order.updated': { id: string } }>('orders'); const result = await Main.send('project.updated', { id: 'p1' });
if (!result.ok) {
console.log(result.reason);
}
```
Orders.on('order.updated', (payload) => { `request()` usa `ack: true`, genera un `id` y espera un frame entrante con
console.log(payload.id); `replyTo` igual a ese id:
```ts
const reply = await Main.request<{ id: string }, { ok: boolean }>('project.sync', {
id: 'p1'
}); });
await Orders.join({ tenant: 'acme' }); if (reply.ok) {
reply.payload.ok;
}
``` ```
## Request / Reply Razones de fallo principales:
- `timeout`
- `closed`
- `rejected`
- `transport_error`
- `invalid_reply`
`request()` creates a frame id, sends `ack: true`, and resolves when an incoming frame carries `replyTo` with that id. ## Canales
Los canales son topics nombrados dentro de una conexión. Se cachean por nombre:
```ts ```ts
const result = await Main.request<{ id: string }, { ok: boolean }>('order.sync', { type ProjectEvents = {
id: 'o1' 'project.updated': { id: string; version: number };
};
const Projects = Main.channel<ProjectEvents>('tenant:projects');
Projects.on('project.updated', (payload, meta) => {
console.log(payload.id, meta.receivedAt);
}); });
await Projects.join({ tenantId: 'acme' });
await Projects.send('project.updated', { id: 'p1', version: 2 });
await Projects.leave();
``` ```
Runtime failures return tagged results. Programmer errors such as duplicated names or invalid connection names throw typed `Conn*` errors. Estados de canal:
```txt
idle -> joining -> joined -> leaving -> left
joining -> failed
```
`dispose()` del canal limpia listeners y deja el canal en estado terminal
`left`.
## Reconexión
La reconexión usa `timr` y backoff configurable:
```ts
Connections.createConnection('main', {
transport,
reconnect: {
enabled: true,
minDelayMs: 500,
maxDelayMs: 15_000,
factor: 1.8,
jitterMs: 500,
maxAttempts: 8,
reconnectOnVisible: true,
reconnectOnOnline: true
}
});
```
Si `reconnectOnVisible` o `reconnectOnOnline` están activos, el módulo escucha
eventos del navegador y pide reconexión cuando la conexión está cerrada o
fallida. Esa capa no recibe una función `logDebug`; recibe `Diagnostics`, que
incluye el `Logger` completo y emite eventos catalogados.
## Heartbeat
```ts
Connections.createConnection('main', {
transport,
heartbeat: {
enabled: true,
intervalMs: 25_000,
timeoutMs: 10_000,
pingType: 'conn.ping',
pongType: 'conn.pong'
}
});
```
El heartbeat envía `pingType` periódicamente y espera `pongType`. Si vence el
timeout, cierra la conexión con `heartbeat_timeout` y deja que la política de
reconexión decida el siguiente paso.
## Auth Y Sesión
Auth de conexión:
```ts
Connections.createConnection('main', {
transport,
auth: {
getAuth: () => ({ token }),
authType: 'conn.auth',
timeoutMs: 10_000
}
});
```
El resultado de auth es tagged:
```ts
await Main.reauthenticate(); // { ok: true } | { ok: false, reason, error? }
```
Con `session.enabled`, el bridge de sesión puede:
- reautenticar cuando `sess` emite refresh;
- desconectar cuando la sesión expira;
- desconectar cuando la sesión se revoca.
`conn` no crea sesiones ni decide permisos. En servidor, los joins/sends de un
canal deben validarse con `auth/sess/perm`.
## Buffer
Cuando la conexión no está abierta, `send()` puede comportarse según policy:
```ts
buffer: {
policy: 'buffer', // 'buffer' | 'drop' | 'fail'
maxMessages: 100,
maxBytes: 1_000_000
}
```
- `buffer`: encola y drena al abrir.
- `drop`: acepta la llamada pero descarta.
- `fail`: devuelve `{ ok: false, reason: 'closed' }`.
## Diagnostics Y Logger
Las opciones públicas aceptan `logger?: Logger` desde `$libs/logr`. No existe
un `ConnectionLogger` propio.
```ts
import type { Logger } from '$libs/logr';
```
Internamente `conn` usa `createConnectionDiagnostics(logger)` y eventos
catalogados en `CONNECTION_DIAGNOSTIC_EVENTS`:
- `connect_failed`
- `transport_error`
- `send_failed`
- `frame_decode_failed`
- `frame_encode_failed`
- `auth_failed`
- `reauth_failed`
- `heartbeat_timeout`
- `reconnect_exhausted`
- `browser_reconnect`
- `session_refreshed`
- `session_expired`
- `session_revoked`
- `listener_threw`
Los mensajes y categorías viven en `consts.ts`. Si quieres enviar eventos a
Sentry, Loki o Datadog, inyecta un `EngineLogger` con el transporte adecuado;
`conn` solo emite al contrato común.
## Errores
Programmer errors lanzan clases tipadas:
- `ConnDisposedError`
- `ConnConnectionAlreadyExistsError`
- `ConnConnectionNotFoundError`
- `ConnInvalidConnectionNameError`
- `ConnInvalidFrameError`
- `ConnChannelAlreadyExistsError`
- `ConnChannelNotFoundError`
- `ConnWebSocketUnavailableError`
Fallos runtime de transporte/envío/auth/request devuelven resultados tagged
para que el consumidor pueda decidir sin `try/catch` obligatorio.
## Página De Prueba
La página interactiva está en:
```txt
/test/conn
```
Incluye chat WebSocket real usando `scripts/conn-chat-server.mjs`:
```txt
npm run dev:conn-chat
```
También `/test/ecosystem` usa `conn` dentro de la demo total con transporte
mock loopback para probar integración con `aapp`, `timr`, `logr`, `perm` y
`cach`.
## Testing
Tests principales:
```txt
src/arts/conn/test/engine-connections.test.ts
src/arts/conn/test/active-connections.test.ts
src/arts/conn/test/connection.test.ts
src/arts/conn/test/connection-state.test.ts
src/arts/conn/test/websocket.test.ts
```
Casos que deben mantenerse cubiertos:
- creación duplicada y lookup inexistente;
- `this` no requerido al desestructurar métodos del engine;
- reloj inyectado para timestamps deterministas;
- reconnect con fake timers;
- heartbeat timeout;
- request/reply con timeout y rechazo;
- channel join/leave/dispose;
- bridge de sesión refresh/expire/revoke;
- serializer inválido y transport errors.

@ -1,7 +1,7 @@
# Formats # Formats
`Formats` es la API publica del artefacto `fmts`, que agrupa formatos `Formats` es la API publica del artefacto `fmts`, que agrupa formatos
localizados de Soma. localizados de Active.
Agrupa los subdominios que dependen de locale: Agrupa los subdominios que dependen de locale:

@ -96,3 +96,24 @@ export function createActiveFormatsRuntime(
} }
}; };
} }
export function readFrom<TArgs extends unknown[], TResult>(
runtime: Pick<ActiveFormatsRuntime, 'read'>,
fn: (...args: TArgs) => TResult
): (...args: TArgs) => TResult {
return (...args) => {
runtime.read();
return fn(...args);
};
}
export function writeTo<TArgs extends unknown[], TResult>(
runtime: Pick<ActiveFormatsRuntime, 'notifyChange'>,
fn: (...args: TArgs) => TResult
): (...args: TArgs) => TResult {
return (...args) => {
const result = fn(...args);
runtime.notifyChange();
return result;
};
}

@ -1,6 +1,6 @@
# Currency # Currency
Currency es la API publica de moneda de Soma: formato, moneda por locale y conversion Currency es la API publica de moneda de Active: formato, moneda por locale y conversion
mediante factores cacheados. mediante factores cacheados.
La API publica sigue el mismo patron que el resto de artefactos: La API publica sigue el mismo patron que el resto de artefactos:

@ -1,4 +1,4 @@
import { createActiveFormatsRuntime } from '../active-runtime.svelte'; import { createActiveFormatsRuntime, readFrom, writeTo } from '../active-runtime.svelte';
import { createEngineDates } from './engine-dates'; import { createEngineDates } from './engine-dates';
import type { ActiveDates, ActiveDatesOptions } from './types'; import type { ActiveDates, ActiveDatesOptions } from './types';
@ -16,67 +16,31 @@ export function createActiveDates(options: ActiveDatesOptions = {}): ActiveDates
}); });
return { return {
getLocale() { getLocale: readFrom(runtime, engine.getLocale),
runtime.read();
return engine.getLocale();
},
setLocale: runtime.syncLocale, setLocale: runtime.syncLocale,
getDateOrder() { getDateOrder: readFrom(runtime, engine.getDateOrder),
runtime.read();
return engine.getDateOrder(); setDateOrder: writeTo(runtime, engine.setDateOrder),
},
clearDateOrder: writeTo(runtime, engine.clearDateOrder),
setDateOrder(order) {
engine.setDateOrder(order); isDateOrderAuto: readFrom(runtime, engine.isDateOrderAuto),
runtime.notifyChange();
}, getHourCycle: readFrom(runtime, engine.getHourCycle),
clearDateOrder() { setHourCycle: writeTo(runtime, engine.setHourCycle),
engine.clearDateOrder();
runtime.notifyChange(); clearHourCycle: writeTo(runtime, engine.clearHourCycle),
},
isHourCycleAuto: readFrom(runtime, engine.isHourCycleAuto),
isDateOrderAuto() {
runtime.read(); formatDate: readFrom(runtime, engine.formatDate),
return engine.isDateOrderAuto();
}, formatTime: readFrom(runtime, engine.formatTime),
getHourCycle() { formatDateTime: readFrom(runtime, engine.formatDateTime),
runtime.read();
return engine.getHourCycle();
},
setHourCycle(hourCycle) {
engine.setHourCycle(hourCycle);
runtime.notifyChange();
},
clearHourCycle() {
engine.clearHourCycle();
runtime.notifyChange();
},
isHourCycleAuto() {
runtime.read();
return engine.isHourCycleAuto();
},
formatDate(value, options) {
runtime.read();
return engine.formatDate(value, options);
},
formatTime(value, options) {
runtime.read();
return engine.formatTime(value, options);
},
formatDateTime(value, options) {
runtime.read();
return engine.formatDateTime(value, options);
},
onPreferenceChange: runtime.onChange, onPreferenceChange: runtime.onChange,

@ -1,6 +1,6 @@
import { createActiveFormatsRuntime } from '../active-runtime.svelte'; import { createActiveFormatsRuntime, readFrom, writeTo } from '../active-runtime.svelte';
import { createEngineNumbers } from './engine-numbers'; import { createEngineNumbers } from './engine-numbers';
import type { ActiveNumbers, ActiveNumbersOptions, NumbersFormatOptions } from './types'; import type { ActiveNumbers, ActiveNumbersOptions } from './types';
export function createActiveNumbers(options: ActiveNumbersOptions = {}): ActiveNumbers { export function createActiveNumbers(options: ActiveNumbersOptions = {}): ActiveNumbers {
const localeSource = options.localeSource; const localeSource = options.localeSource;
@ -16,102 +16,45 @@ export function createActiveNumbers(options: ActiveNumbersOptions = {}): ActiveN
}); });
return { return {
getLocale() { getLocale: readFrom(runtime, engine.getLocale),
runtime.read();
return engine.getLocale();
},
setLocale: runtime.syncLocale, setLocale: runtime.syncLocale,
format(value: number, options?: NumbersFormatOptions): string { format: readFrom(runtime, engine.format) as ActiveNumbers['format'],
runtime.read();
return engine.format(value, options); formatPercent: readFrom(runtime, engine.formatPercent),
},
formatCompact: readFrom(runtime, engine.formatCompact),
formatPercent(value, options) {
runtime.read(); formatCurrency: readFrom(runtime, engine.formatCurrency),
return engine.formatPercent(value, options);
}, formatUnit: readFrom(runtime, engine.formatUnit),
formatCompact(value, options) { parse: readFrom(runtime, engine.parse),
runtime.read();
return engine.formatCompact(value, options); getDecimalSeparator: readFrom(runtime, engine.getDecimalSeparator),
},
getGroupSeparator: readFrom(runtime, engine.getGroupSeparator),
formatCurrency(value, currency, options) {
runtime.read(); getGrouping: readFrom(runtime, engine.getGrouping),
return engine.formatCurrency(value, currency, options);
}, setDecimalSeparator: writeTo(runtime, engine.setDecimalSeparator),
formatUnit(value, unit, options) { clearDecimalSeparator: writeTo(runtime, engine.clearDecimalSeparator),
runtime.read();
return engine.formatUnit(value, unit, options); isDecimalSeparatorAuto: readFrom(runtime, engine.isDecimalSeparatorAuto),
},
setGroupSeparator: writeTo(runtime, engine.setGroupSeparator),
parse(value) {
runtime.read(); clearGroupSeparator: writeTo(runtime, engine.clearGroupSeparator),
return engine.parse(value);
}, isGroupSeparatorAuto: readFrom(runtime, engine.isGroupSeparatorAuto),
getDecimalSeparator() { setGrouping: writeTo(runtime, engine.setGrouping),
runtime.read();
return engine.getDecimalSeparator(); clearGrouping: writeTo(runtime, engine.clearGrouping),
},
isGroupingAuto: readFrom(runtime, engine.isGroupingAuto),
getGroupSeparator() {
runtime.read();
return engine.getGroupSeparator();
},
getGrouping() {
runtime.read();
return engine.getGrouping();
},
setDecimalSeparator(separator) {
engine.setDecimalSeparator(separator);
runtime.notifyChange();
},
clearDecimalSeparator() {
engine.clearDecimalSeparator();
runtime.notifyChange();
},
isDecimalSeparatorAuto() {
runtime.read();
return engine.isDecimalSeparatorAuto();
},
setGroupSeparator(separator) {
engine.setGroupSeparator(separator);
runtime.notifyChange();
},
clearGroupSeparator() {
engine.clearGroupSeparator();
runtime.notifyChange();
},
isGroupSeparatorAuto() {
runtime.read();
return engine.isGroupSeparatorAuto();
},
setGrouping(enabled) {
engine.setGrouping(enabled);
runtime.notifyChange();
},
clearGrouping() {
engine.clearGrouping();
runtime.notifyChange();
},
isGroupingAuto() {
runtime.read();
return engine.isGroupingAuto();
},
onPreferenceChange: runtime.onChange, onPreferenceChange: runtime.onChange,

@ -1,6 +1,6 @@
import { createActiveFormatsRuntime } from '../active-runtime.svelte'; import { createActiveFormatsRuntime, readFrom, writeTo } from '../active-runtime.svelte';
import { createEngineUnits } from './engine-units'; import { createEngineUnits } from './engine-units';
import type { ActiveUnits, ActiveUnitsOptions, UnitId, UnitKind, UnitSystemMode } from './types'; import type { ActiveUnits, ActiveUnitsOptions } from './types';
export function createActiveUnits(options: ActiveUnitsOptions = {}): ActiveUnits { export function createActiveUnits(options: ActiveUnitsOptions = {}): ActiveUnits {
const localeSource = options.localeSource; const localeSource = options.localeSource;
@ -16,52 +16,25 @@ export function createActiveUnits(options: ActiveUnitsOptions = {}): ActiveUnits
}); });
return { return {
getLocale() { getLocale: readFrom(runtime, engine.getLocale),
runtime.read();
return engine.getLocale();
},
setLocale: runtime.syncLocale, setLocale: runtime.syncLocale,
getSystem() { getSystem: readFrom(runtime, engine.getSystem),
runtime.read();
return engine.getSystem(); setSystem: writeTo(runtime, engine.setSystem),
},
clearSystem: writeTo(runtime, engine.clearSystem),
setSystem(system: UnitSystemMode) {
engine.setSystem(system); isSystemAuto: readFrom(runtime, engine.isSystemAuto),
runtime.notifyChange();
}, getDefaultUnit: readFrom(runtime, engine.getDefaultUnit),
clearSystem() { isDefaultUnit: readFrom(runtime, engine.isDefaultUnit),
engine.clearSystem();
runtime.notifyChange(); format: readFrom(runtime, engine.format),
},
formatDefault: readFrom(runtime, engine.formatDefault),
isSystemAuto() {
runtime.read();
return engine.isSystemAuto();
},
getDefaultUnit(kind: UnitKind, system) {
runtime.read();
return engine.getDefaultUnit(kind, system);
},
isDefaultUnit(unit: UnitId, kind, system) {
runtime.read();
return engine.isDefaultUnit(unit, kind, system);
},
format(value, unit, options) {
runtime.read();
return engine.format(value, unit, options);
},
formatDefault(value, kind, options) {
runtime.read();
return engine.formatDefault(value, kind, options);
},
convert: engine.convert, convert: engine.convert,
convertToDefault: engine.convertToDefault, convertToDefault: engine.convertToDefault,

@ -1,8 +1,8 @@
# Sium # Sium
Sium es la capa de contratos, validacion e introspeccion de Soma. Sium es la capa de contratos, validacion e introspeccion de Active.
Su objetivo no es competir con Zod, Valibot o ArkType como libreria generalista. Sium existe para que Soma tenga un contrato nativo entre datos, validacion, errores traducibles, tipos de dominio y UI auto-generada. Su objetivo no es competir con Zod, Valibot o ArkType como libreria generalista. Sium existe para que Active tenga un contrato nativo entre datos, validacion, errores traducibles, tipos de dominio y UI auto-generada.
Este archivo es la unica documentacion Markdown del modulo. Si cambia el comportamiento de Sium, actualiza este README en lugar de crear documentos paralelos. Este archivo es la unica documentacion Markdown del modulo. Si cambia el comportamiento de Sium, actualiza este README en lugar de crear documentos paralelos.
@ -98,9 +98,9 @@ interface Schema<I = unknown, O = I> {
## Por Que Existe ## Por Que Existe
Sium cubre cuatro huecos que las librerias externas no resuelven de forma nativa dentro de Soma. Sium cubre cuatro huecos que las librerias externas no resuelven de forma nativa dentro de Active.
- Tipos de dominio: `ColorValue`, `DateValue`, `TimeValue` y sus segmentos ya existen en Soma. - Tipos de dominio: `ColorValue`, `DateValue`, `TimeValue` y sus segmentos ya existen en Active.
- UI metadata: `meta.widget`, `meta.channel`, labels y opciones viajan dentro del schema. - UI metadata: `meta.widget`, `meta.channel`, labels y opciones viajan dentro del schema.
- Errores traducibles: los issues llevan `code`, `message` idlangref, `params` y `path`. - Errores traducibles: los issues llevan `code`, `message` idlangref, `params` y `path`.
- Interop: cada schema tambien habla Standard Schema v1. - Interop: cada schema tambien habla Standard Schema v1.
@ -649,7 +649,7 @@ Estado actual:
- El core puede usarse sin Svelte. - El core puede usarse sin Svelte.
- Los schemas exponen `~standard`, asi que pueden conectarse a consumidores Standard Schema. - Los schemas exponen `~standard`, asi que pueden conectarse a consumidores Standard Schema.
- El adapter `svelte/` no esta presente en este arbol porque depende de piezas de Soma UI pendientes de port. - El adapter `svelte/` no esta presente en este arbol porque depende de piezas UI pendientes de port.
- Hasta completar esa migracion, las pruebas y auditorias de Sium deben omitir `src/arts/sium/svelte`. - Hasta completar esa migracion, las pruebas y auditorias de Sium deben omitir `src/arts/sium/svelte`.
## Recetas Rapidas ## Recetas Rapidas
@ -744,7 +744,7 @@ Los errores emitidos dentro de schemas hijos deben usar `ctx.path`. Sium pasa pa
### `npm run check` falla en `sium/svelte` ### `npm run check` falla en `sium/svelte`
Es esperado mientras `$uix/soma/provider` no este portado. El core de Sium se valida con la suite sin Svelte. Es esperado mientras el provider UI no este portado. El core de Sium se valida con la suite sin Svelte.
## Verificacion Recomendada ## Verificacion Recomendada

@ -4,7 +4,7 @@
* Pure TypeScript — no Svelte runtime, no runes, no DOM. Runnable in Node, * Pure TypeScript — no Svelte runtime, no runes, no DOM. Runnable in Node,
* Bun, Deno, Cloudflare Workers, or a browser. The Svelte adapter lives at * Bun, Deno, Cloudflare Workers, or a browser. The Svelte adapter lives at
* `$lib/sium/svelte/` and the UI renderer (`Form.AutoFields`) at * `$lib/sium/svelte/` and the UI renderer (`Form.AutoFields`) at
* `$uix/soma/form/`. * the framework UI form package.
* *
* Contract and design rules are frozen in the module README. Key invariants: * Contract and design rules are frozen in the module README. Key invariants:
* *
@ -79,8 +79,8 @@ export type SchemaEffect = 'refine' | 'transform' | 'codec';
* — the semantic role lives here so `Form.AutoFields` can optionally * — the semantic role lives here so `Form.AutoFields` can optionally
* route to a specialized widget keyed by channel without inflating the * route to a specialized widget keyed by channel without inflating the
* `kind` vocabulary. * `kind` vocabulary.
* - `label` / `description`: free-form text or a resolution key. The soma * - `label` / `description`: free-form text or a resolution key. The UI
* adapter routes these through `soma.langs.ts()`. * adapter routes these through the active language engine.
* - `example`: a canonical example value (used in docs or placeholder fallback). * - `example`: a canonical example value (used in docs or placeholder fallback).
* - `readOnly` / `deprecated`: behavioural flags. * - `readOnly` / `deprecated`: behavioural flags.
*/ */
@ -191,7 +191,7 @@ export type Result<T> =
* fields have decoded). Inside **field-level** refines it is partial or * fields have decoded). Inside **field-level** refines it is partial or
* `undefined` (the sibling fields may not be decoded yet). * `undefined` (the sibling fields may not be decoded yet).
* - `locale` — set by adapters (`sium/svelte` injects it from * - `locale` — set by adapters (`sium/svelte` injects it from
* `soma.langs.getLocale()`). Used by refinements that need locale-aware * the active language engine). Used by refinements that need locale-aware
* validation (date parsing, collation, etc.). * validation (date parsing, collation, etc.).
* - `meta` — adapter-specific extras. * - `meta` — adapter-specific extras.
*/ */
@ -240,7 +240,7 @@ export interface RefineFailure {
* ### Standard Schema v1 interop * ### Standard Schema v1 interop
* *
* Every sium schema exposes `'~standard'` — consumers that accept * Every sium schema exposes `'~standard'` — consumers that accept
* `StandardSchemaV1` (including soma's own `createForm`) can use sium * `StandardSchemaV1` (including Active form builders) can use sium
* schemas as-is, and vice versa (sium schemas interoperate with Zod / * schemas as-is, and vice versa (sium schemas interoperate with Zod /
* Valibot / ArkType via the same contract). * Valibot / ArkType via the same contract).
*/ */
@ -324,4 +324,3 @@ export class SiumAsyncSchemaError extends Error {
} }
} }

@ -39,7 +39,7 @@ const maxLengthPlural = p({
* *
* const langs = createLang({ * const langs = createLang({
* sium: siumLangs, * sium: siumLangs,
* components: somaComponentLangs, * components: componentLangs,
* ... * ...
* }, 'es'); * }, 'es');
* *

@ -51,10 +51,10 @@ export const AUTH_CONTENT_TYPES = {
} as const; } as const;
export const AUTH_COOKIE_NAMES = { export const AUTH_COOKIE_NAMES = {
CSRF: '__Host-asoma.auth.csrf', CSRF: '__Host-active.auth.csrf',
FLOW: '__Host-asoma.auth.flow', FLOW: '__Host-active.auth.flow',
DEVICE: '__Host-asoma.auth.device', DEVICE: '__Host-active.auth.device',
REFRESH: '__Host-asoma.auth.refresh' REFRESH: '__Host-active.auth.refresh'
} as const; } as const;
export const AUTH_COOKIE_ATTRIBUTES = { export const AUTH_COOKIE_ATTRIBUTES = {
@ -74,8 +74,8 @@ export const AUTH_HEADER_NAMES = {
CONTENT_TYPE: 'content-type', CONTENT_TYPE: 'content-type',
COOKIE: 'cookie', COOKIE: 'cookie',
SET_COOKIE: 'set-cookie', SET_COOKIE: 'set-cookie',
CSRF: 'x-asoma-auth-csrf', CSRF: 'x-active-auth-csrf',
REQUEST_ID: 'x-asoma-request-id', REQUEST_ID: 'x-active-request-id',
FETCH_SITE: 'sec-fetch-site', FETCH_SITE: 'sec-fetch-site',
FETCH_MODE: 'sec-fetch-mode', FETCH_MODE: 'sec-fetch-mode',
FETCH_DEST: 'sec-fetch-dest', FETCH_DEST: 'sec-fetch-dest',
@ -352,7 +352,7 @@ export const AUTH_TEST_IDS = {
} as const; } as const;
export const AUTH_TEST_COOKIE_NAMES = { export const AUTH_TEST_COOKIE_NAMES = {
SESSION: 'asoma.auth.test.session' SESSION: 'active.auth.test.session'
} as const; } as const;
export const AUTH_TEST_ACTIONS = { export const AUTH_TEST_ACTIONS = {

@ -101,7 +101,7 @@ export function isValidColor(value: string): boolean {
} }
/** /**
* Type guard for the normalized `ColorValue` shape used across air/soma. * Type guard for the normalized `ColorValue` shape used across Active UI layers.
*/ */
export function isColorValue(value: unknown): value is ColorValue { export function isColorValue(value: unknown): value is ColorValue {
if (!isRecord(value)) return false; if (!isRecord(value)) return false;

@ -4,7 +4,7 @@
* *
* Everything in this module is **pure** — no DOM, no reactivity, no framework. * Everything in this module is **pure** — no DOM, no reactivity, no framework.
* UI/DOM concerns (segment navigation, keyboard handling, screen reader * UI/DOM concerns (segment navigation, keyboard handling, screen reader
* announcements) live next to the consumer (e.g. `soma/datetime/`). * announcements) live next to the consumer (for example a datetime component).
* *
* The surface is organised as: * The surface is organised as:
* *

@ -74,12 +74,12 @@
</script> </script>
<svelte:head> <svelte:head>
<title>Active/Soma auth test</title> <title>Active auth test</title>
</svelte:head> </svelte:head>
<main> <main>
<header> <header>
<p class="eyebrow">Active/Soma artifact lab</p> <p class="eyebrow">Active artifact lab</p>
<h1>auth test surface</h1> <h1>auth test surface</h1>
<p> <p>
Validación funcional del engine server-authoritative: password flow, sesión de test, CSRF, Validación funcional del engine server-authoritative: password flow, sesión de test, CSRF,

@ -765,7 +765,7 @@
</script> </script>
<svelte:head> <svelte:head>
<title>Active/Soma ecosystem test</title> <title>Active ecosystem test</title>
</svelte:head> </svelte:head>
<main bind:this={appRoot} class="shell" data-dir={shellView.dir}> <main bind:this={appRoot} class="shell" data-dir={shellView.dir}>

@ -12,7 +12,7 @@
"strict": true, "strict": true,
"moduleResolution": "bundler" "moduleResolution": "bundler"
}, },
"exclude": ["src/cach-v1/**", "src/active-soma-auth-implementation/**"] "exclude": []
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
// //

@ -17,7 +17,7 @@ export default defineConfig({
instances: [{ browser: 'chromium', headless: true }] instances: [{ browser: 'chromium', headless: true }]
}, },
include: ['src/**/*.svelte.{test,spec}.{js,ts}'], include: ['src/**/*.svelte.{test,spec}.{js,ts}'],
exclude: ['src/libs/server/**', 'src/active-soma-auth-implementation/**'] exclude: ['src/libs/server/**']
} }
}, },
@ -27,7 +27,7 @@ export default defineConfig({
name: 'server', name: 'server',
environment: 'node', environment: 'node',
include: ['src/**/*.{test,spec}.{js,ts}'], include: ['src/**/*.{test,spec}.{js,ts}'],
exclude: ['src/**/*.svelte.{test,spec}.{js,ts}', 'src/active-soma-auth-implementation/**'] exclude: ['src/**/*.svelte.{test,spec}.{js,ts}']
} }
} }
] ]

Loading…
Cancel
Save

Powered by TurnKey Linux.