# Sium 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 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. ## Sium no forma parte de active-app core `createActiveApp(...)` deliberadamente no construye Sium. La validacion es **page-scoped**: solo las paginas con formularios la necesitan. Cargar todo el core de schemas/types/issues en paginas sin formularios serìa coste innecesario. El patron estandar es una linea al inicio del modulo de la pagina: ```ts import { createEngineSium } from '$sium'; const sium = createEngineSium({ langs: App.langs, logger: App.logger }); ``` Asi cada formulario obtiene un engine afinado a sus necesidades (logger category, contexto de validacion, etc.) y comparte la traduccion de issues + el bus de logs con la app. ## Estado Core, tipos de dominio, lenguajes, Standard Schema e introspeccion estan activos. Sium no tiene adapter Svelte propio, por diseno: la integracion con UI es agnostica al validador. Los formularios (`createForm` / `Form.Provider`) consumen cualquier schema via `~standard`, `Form.AutoFields` auto-renderiza via la introspeccion `~sium`, y langs/logger se inyectan por `createEngineSium({ langs, logger })`. El core sigue siendo TypeScript puro. ## Novedades 2.0 Ampliacion alineada a lo que el ecosistema Active necesita (formularios, storage, http). NO busca paridad con Zod/Valibot. - **Refines de formato (string):** `uuid()`, `slug()`, `datetime()` (ISO 8601), `ipv4()`. - **Refines numericos:** `finite()` (rechaza `Infinity`/`-Infinity`/`NaN`), `positive()` (> 0), `nonnegative()` (>= 0), `multipleOf(n)`. - **Coercion:** `coerceNumber()`, `coerceBoolean()`, `coerceString()` — hermanos generales de `coerceDate()` para entradas sueltas (inputs HTML, query strings). - **Standard Schema sincrono:** `schema['~standard'].validate(x)` resuelve de forma SINCRONA cuando el schema no tiene pasos async (devuelve el `Result` directo en vez de `Promise`). SS v1 permite `Result | Promise`; esto elimina el branch async + el workaround `untrack` en los consumidores (`form-core`, `storage`) para schemas sincronos. - **Transforms de string:** `trim()`, `toLowerCase()`, `toUpperCase()` para normalizar formularios. - **Modificador `nullish()`:** acepta `undefined` y `null` (= `optional` + `nullable`). - **Combinadores `tuple()` y `record()`:** tupla heterogenea de longitud fija + diccionario de claves arbitrarias. Amplian el vocabulario `SchemaKind` (`'tuple'`/`'record'`); `Form.AutoFields` los degrada con un aviso "not supported yet". - **Utilidades de object:** `pick` / `omit` / `partial` / `extend` / `merge` — derivan un `object()` reusando los campos del original, con tipos precisos via `Pick`/`Omit`/`Partial`. - **API depurada:** `dateValue()` / `timeValue()` ya no aceptan `opts` (eran ignorados). Para validar granularidad/calendario, compon un `refine` sobre el valor. - 8 nuevos issue codes (`uuid`, `slug`, `datetime`, `ipv4`, `finite`, `positive`, `nonnegative`, `multiple_of`) con traduccion es/en. ## Importaciones Sium expone una fachada publica y barrels tematicos. ```ts import { createEngineSium } from '$sium'; const sium = createEngineSium(); ``` Si quieres traducciones activas, inyecta el engine de traducciones: ```ts import { createEngineSium, siumLangs } from '$sium'; import { createEngineLangs } from '$langs'; const langs = createEngineLangs({ sium: siumLangs }, 'es'); const sium = createEngineSium({ langs }); ``` ```ts import { object, pipe, string, email, min } from '$sium/core'; import { colorValue, dateValue, timeValue } from '$sium/types'; import { siumLangs, ISSUE_CODES, resolve } from '$sium/langs'; ``` `createSchema()` es interno. La API publica debe entrar por `createEngineSium()` o por los barrels `core`, `types` y `langs`. El directorio `_examples/` contiene esquemas de referencia (formularios de login, signup, settings, etc.) usados por `/test/sium` y citados en este README. **No es API publica** — el guion bajo inicial es la convencion que señala material interno/dev. No esta re-exportado por `sium/index.ts` y ningun consumidor lo importa. Si encuentras un caso real para uno de esos esquemas, copialo al consumidor; promoverlo a `sium` publico significaria soporte indefinido para una decision de UX que no pertenece al runtime. ## Idea Central Un schema Sium es un codec bidireccional con validacion, introspeccion y compatibilidad Standard Schema. ```ts interface Schema { readonly '~standard': StandardSchemaV1.Props; readonly '~sium': SiumIntrospection; decode(input: I, ctx?: Partial): Promise; decodeSync(input: I, ctx?: Partial): O; encode(value: O): I; validate(input: I, ctx?: Partial): Promise>; validateSync(input: I, ctx?: Partial): Result; } ``` `decode` valida y transforma. Si falla, lanza `SiumValidationError`. `validate` envuelve `decode` y devuelve `{ ok: true, value }` o `{ ok: false, issues }`. `createEngineSium({ logger }).validate(schema, input)` hace lo mismo y, si falla, emite un diagnóstico `SIUM_DIAGNOSTIC_EVENTS.VALIDATION_FAILED` a nivel `debug` (categoría `SIUM_MODULE`), con `issueCount` e `issues`. `encode` invierte la salida hacia la forma de entrada. En transforms de una sola direccion actua como identidad para esa parte; usa `codec()` cuando necesites reversibilidad real. `~standard` permite usar schemas Sium en cualquier consumidor de Standard Schema v1. Los schemas sincronos resuelven de forma sincrona (`validate` devuelve el `Result` directo); solo los que tienen pasos async devuelven `Promise`. `~sium` contiene metadata para UI, debug, docs y herramientas. ## Por Que Existe 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 Active. - UI metadata: `meta.widget`, `meta.channel`, labels y opciones viajan dentro del schema. - Errores traducibles: los issues llevan `code`, `message` idlangref, `params` y `path`. - Interop: cada schema tambien habla Standard Schema v1. ## Arquitectura ```txt src/arts/sium/ index.ts fachada publica engine-sium.ts createEngineSium() engine-resolver.ts, engine-validation.ts resolucion de mensajes idlangref + runtime de validate/validateSync consts.ts, errors.ts, diagnostics.ts SIUM_MODULE + errores tipados (CodeError) + catalogo de logs core/ kernel puro TypeScript (schemas, combinators, pipe) types/ tipos de dominio sobre $libs/color y $libs/days langs/ catalogo de mensajes traducibles + fallback de interpolacion _examples/ schemas de ejemplo (dev-only, fuera del barril publico) test/ suite Vitest ``` Reglas de dependencia: - `core/` no importa Svelte, UI, `days`, `color` ni `langs`. - `types/` puede importar `core/`, `$libs/color` y `$libs/days`. - `langs/` define el catalogo `siumLangs` y un fallback minimo de interpolacion. - `engine-sium.ts` compone la fachada publica. ## Guia Rapida ### Usar La Fachada ```ts import { createEngineSium } from '$sium'; const s = createEngineSium(); export const User = s.object({ name: s.pipe(s.string(), s.min(2), s.max(40)), email: s.pipe(s.string(), s.email()), age: s.optional(s.pipe(s.number(), s.integer(), s.min(0))) }); const result = await User.validate({ name: 'Ada', email: 'ada@example.com', age: 37 }); ``` Con integracion: ```ts const s = createEngineSium({ langs, logger }); s.resolveIssue({ path: ['email'], code: 'type', message: '#?sium.errors.type|Expected {{expected}} but received {{actual}}', params: { expected: 'string', actual: 'number' } }); ``` Si `langs` existe, `resolveIssue()` delega en `EngineLangs`. Si no existe, usa el fallback local de `$sium/langs.resolve`. En desarrollo tambien puedes validar a traves del engine para obtener logs `debug` cuando hay issues: ```ts const result = await s.validate(User, input); const syncResult = s.validateSync(User, input); ``` Estos wrappers no cambian el resultado de `schema.validate()`. Solo emiten un log `debug` en la categoria exportada `LOGGER_CATEGORY` cuando la validacion falla y has inyectado `logger`. ### Usar Imports Granulares ```ts import { object, optional, pipe, string, number, integer, min, email } from '$sium/core'; export const User = object({ name: pipe(string(), min(2)), email: pipe(string(), email()), age: optional(pipe(number(), integer(), min(0))) }); ``` ### Validar ```ts const result = await User.validate(input); if (result.ok) { result.value; } else { result.issues; } ``` ### Decodificar Con Excepcion ```ts import { SiumValidationError } from '$sium/core'; try { const value = await User.decode(input); } catch (error) { if (error instanceof SiumValidationError) { console.log(error.issues); } } ``` ### Codificar ```ts const QueryNumber = s.pipe( s.string(), s.codec( (value) => Number(value), (value) => String(value) ) ); await QueryNumber.decode('42'); // 42 QueryNumber.encode(42); // '42' ``` ## API De Core ### Primitivos ```ts string(); number(); boolean(); literal('admin'); enumOf(['admin', 'user', 'guest'] as const); ``` ### Modificadores ```ts optional(string()); nullable(string()); nullish(string()); defaulted(number(), () => 18); ``` `optional` permite `undefined`. `nullable` permite `null`. `nullish` permite `undefined` y `null` (combina `optional` + `nullable`). `defaulted` aplica el valor por defecto en decode cuando el input es `undefined`. Si el schema interno es async, los wrappers conservan el short-circuit sincronico del sentinel: `optional(asyncSchema).decodeSync(undefined)`, `nullable(asyncSchema).decodeSync(null)` y `defaulted(asyncSchema).decodeSync(undefined)` no entran al schema interno. Cualquier valor que si necesite validar el inner async lanza `SiumAsyncSchemaError` en APIs sync. ### Combinadores ```ts object({ name: string() }); array(string()); tuple(string(), number(), boolean()); record(number()); union(string(), number()); discriminated('kind', [ object({ kind: literal('text'), value: string() }), object({ kind: literal('count'), value: number() }) ]); ``` `object()` usa `unknownKeys: 'strip'` por defecto. ```ts object({ name: string() }, { unknownKeys: 'strict' }); object({ name: string() }, { unknownKeys: 'passthrough' }); ``` `tuple(...)` valida posicionalmente y exige la longitud exacta (issue `tuple_length`). `record(schema)` valida un objeto plano de claves arbitrarias cuyos valores comparten `schema`. ### Utilidades de object Derivan un nuevo `object()` a partir de otro, reusando sus campos: ```ts pick(User, ['id', 'name']); // solo esos campos omit(User, ['password']); // todos menos esos partial(User); // todos los campos opcionales extend(User, { role: string() }); // anade / sobrescribe campos merge(Base, Overrides); // combina dos object (gana el segundo) ``` Los tipos resultantes se derivan con `Pick` / `Omit` / `Partial` sobre el schema fuente, asi que siguen siendo precisos. Pasar un schema que no sea `object()` lanza `SiumObjectUtilError`. ### Pipe Y Steps ```ts pipe(string(), min(3), max(40), meta({ label: 'Name', widget: 'text-field' })); ``` Steps disponibles: - `refine(check, fail, opts?)` - `transform(fn, opts?)` - `codec(decode, encode, opts?)` - `meta(annotations)` Si una funcion normal devuelve `Promise`, declara el step como async. ```ts const UniqueEmail = pipe( string(), refine( (value) => api.emailIsFree(value), { code: 'custom', message: '#?sium.errors.custom|Email is already in use' }, { async: true } ) ); ``` ### Refines Incluidos ```ts min(2); max(40); length(8); regex(/^[a-z0-9-]+$/); email(); url(); integer(); // formato (string) uuid(); slug(); datetime(); // ISO 8601 ipv4(); // numericos finite(); // rechaza Infinity / -Infinity / NaN positive(); // > 0 nonnegative(); // >= 0 multipleOf(0.5); ``` `min` y `max` funcionan con numeros, strings y arrays. Emiten codigos distintos para valor numerico y longitud. `finite()` cubre el hueco de `number()`, que acepta `Infinity` por diseno. `positive()` no implica `finite()`: compon ambos para rechazar tambien `Infinity`. ### Coercion Para entradas sueltas (inputs HTML, query strings, FormData) que llegan como string y necesitan convertirse a un primitivo: ```ts coerceNumber(); // string | number -> number (rechaza '' y NaN; Infinity pasa, compon con finite()) coerceBoolean(); // string | number | boolean -> boolean ('true/1/yes/on' vs 'false/0/no/off') coerceString(); // string | number | boolean -> string ``` Son los hermanos generales de `coerceDate()`. Cada uno reporta el `kind` de SALIDA (`coerceNumber()` -> `number`), asi que `Form.AutoFields` renderiza el widget correcto. ### Transforms De String Normalizadores que cambian el valor en decode (son `transform`, sin `encode`). Aplica despues de `string()`: ```ts pipe(string(), trim()); // recorta espacios pipe(string(), toLowerCase()); // minusculas pipe(string(), toUpperCase()); // mayusculas pipe(string(), trim(), toLowerCase()); // email normalizado ``` El `kind` sigue siendo `string`, asi que son transparentes para `Form.AutoFields`. ## Tipos De Dominio Los tipos de dominio viven en `$sium/types` y envuelven las librerias canonicas `$libs/color` y `$libs/days`. ### Color ```ts import { colorValue, red, green, blue, hue, saturation, lightness, brightness, alpha } from '$sium/types'; const Color = colorValue(); const Red = red(); ``` `colorValue()` valida un `ColorValue` normalizado con `hex`, `rgb`, `hsl` y `hsv`. Los segmentos RGB son enteros `0..255`. Los segmentos HSL/HSV son numeros en sus rangos naturales. `alpha()` valida `0..1`. ### Fecha ```ts import { dateValue, dateRange, coerceDate, year, month, day } from '$sium/types'; const DateOnly = dateValue(); const Range = dateRange(); const FromNativeDate = coerceDate(); ``` `dateValue()` acepta `DateValue` de `$libs/days`. `coerceDate()` acepta `Date | DateValue` y devuelve `DateValue`. `dateRange()` valida `{ start, end }` y rechaza rangos donde `start > end`. ### Hora ```ts import { timeValue, timeRange, hour, minute, second, dayPeriod } from '$sium/types'; const TimeOnly = timeValue(); const Range = timeRange(); ``` `timeRange()` valida `{ start, end }` y rechaza rangos donde `start > end`. `hour({ cycle: 12 })` usa rango `1..12`. `hour()` usa rango `0..23`. ## Introspeccion Cada schema expone `schema['~sium']`. ```ts const schema = pipe(string(), email(), meta({ label: 'Email', widget: 'email-field' })); schema['~sium']; ``` Forma simplificada: ```ts { kind: 'string', wrappers: [], effects: ['refine'], async: false, meta: { label: 'Email', widget: 'email-field' }, shape: undefined } ``` Herramientas incluidas: ```ts serializeSchema(schema); countLeafFields(schema); walkSchema(schema, (node, path) => {}); ``` `walkSchema()` recorre objects, arrays y wrappers. Las variantes de union/discriminated no se expanden automaticamente para evitar UI ambigua. ## Errores E i18n Un issue tiene esta forma: ```ts type Issue = { path: ReadonlyArray; code: string; message: string; params?: Record; }; ``` `message` usa idlangref: ```txt #?sium.errors.type|Expected {{expected}} but received {{actual}} ``` Los mensajes base viven en `siumLangs`. ```ts import { siumLangs, resolve } from '$sium/langs'; resolve('#?sium.errors.type|Invalid', { expected: 'string', actual: 'number' }); ``` `resolve()` de `$sium/langs` no traduce por locale. Es solo el fallback local de interpolacion cuando no hay `EngineLangs`. Para traduccion real: ```ts const sium = createEngineSium({ langs }); const text = sium.resolveIssue(issue, 'es'); ``` El engine expone `langSchema` como nombre historico para acceder al catalogo estatico si lo necesitas: ```ts sium.langSchema === siumLangs; ``` Codigos estables actuales: ```ts type literal enum min_value max_value min_length max_length length regex email url integer custom range_order unknown_keys union_no_match discriminated_missing_key discriminated_unknown_value ``` ## Contexto De Validacion Los callbacks de `refine`, `transform` y `codec` reciben `ctx`. ```ts type Ctx = { path: ReadonlyArray; rootInput: unknown; rootValue?: unknown; locale?: string; meta?: Record; }; ``` `ctx.path` es absoluto. En `object({ user: object({ name }) })`, un refine de `name` recibe `['user', 'name']`. `ctx.rootInput` es el input original. `ctx.rootValue` es passthrough opcional. Sium lo conserva si el caller o un adapter lo pasan en el contexto, pero el core no lo sintetiza automaticamente. Para validaciones cross-field sobre input de usuario, usa `ctx.rootInput`. Para validaciones cross-field sobre datos ya decodificados, coloca un `refine()` sobre el schema `object()` y usa el objeto decodificado que recibe ese refine. ## Ejemplos ### Login ```ts import { object, pipe, string, email, min, meta } from '$sium/core'; export const Login = object({ email: pipe(string(), email(), meta({ label: 'Email' })), password: pipe(string(), min(8), meta({ label: 'Password' })) }); ``` ### Signup Con Confirmacion ```ts import { boolean, object, optional, pipe, refine, string, meta } from '$sium/core'; export const Signup = pipe( object({ email: pipe(string(), meta({ label: 'Email' })), password: pipe(string(), meta({ label: 'Password' })), confirmPassword: pipe(string(), meta({ label: 'Confirm password' })), acceptTerms: pipe( boolean(), refine((value) => value === true, { code: 'custom', message: '#?sium.errors.custom|You must accept the terms' }), meta({ label: 'Accept terms' }) ), newsletter: pipe(optional(boolean()), meta({ label: 'Newsletter' })) }), refine((value) => value.password === value.confirmPassword, { code: 'custom', message: '#?sium.errors.custom|Passwords do not match' }) ); ``` ### Perfil Con Fecha Y Color ```ts import { object, pipe, string, email, min, max, meta } from '$sium/core'; import { colorValue, dateValue } from '$sium/types'; export const Profile = object({ name: pipe(string(), min(2), max(40), meta({ label: 'Name' })), email: pipe(string(), email(), meta({ label: 'Email' })), birthDate: pipe(dateValue(), meta({ label: 'Birth date', widget: 'date-field' })), favoriteColor: pipe(colorValue(), meta({ label: 'Favorite color', widget: 'color-picker' })) }); ``` ### Reserva Con Rango ```ts import { integer, max, meta, min, number, object, optional, pipe, string } from '$sium/core'; import { dateRange, timeValue } from '$sium/types'; export const Booking = object({ range: pipe(dateRange(), meta({ label: 'Date range' })), checkInTime: pipe(timeValue(), meta({ label: 'Check-in time' })), guests: pipe(number(), integer(), min(1), max(10), meta({ label: 'Guests' })), notes: pipe(optional(string()), meta({ label: 'Notes' })) }); ``` ### Discriminated Union ```ts import { discriminated, literal, number, object, string } from '$sium/core'; export const Event = discriminated('kind', [ object({ kind: literal('text'), message: string() }), object({ kind: literal('score'), value: number() }) ]); ``` ### Recursion Con Lazy ```ts import { array, lazy, object, optional, string } from '$sium/core'; import type { Schema } from '$sium/core'; type TreeNode = { label: string; children?: TreeNode[]; }; export const Tree: Schema = lazy(() => object({ label: string(), children: optional(array(Tree)) }) ); ``` `lazy()` protege errores de construccion del schema: si el thunk intenta usar el propio proxy mientras resuelve, o devuelve ese mismo proxy como schema final, lanza `SiumLazyResolvingError`. Esto no es deteccion de ciclos en datos de usuario. Si pasas un objeto runtime autociclico a un schema recursivo, el decode/encode puede seguir desbordando la pila; valida esos datos antes o anade una capa de normalizacion especifica del dominio. ### Transform Y Codec ```ts import { codec, pipe, string, transform } from '$sium/core'; export const TrimmedLower = pipe( string(), transform((value) => value.trim().toLowerCase()) ); export const NumberFromString = pipe( string(), codec( (value) => Number(value), (value) => String(value) ) ); ``` ### Validacion Async ```ts import { pipe, refine, string } from '$sium/core'; export const UniqueUsername = pipe( string(), refine( (value) => fetch(`/api/users/${value}`).then((res) => res.status === 404), { code: 'custom', message: '#?sium.errors.custom|Username is already taken' }, { async: true } ) ); ``` ## Integracion Svelte Sium no tiene adapter Svelte propio: la integracion es agnostica al validador y vive en la capa de formularios, no en sium. - El core es TypeScript puro y se usa sin Svelte. - Los schemas exponen `~standard` (Standard Schema v1), asi que `createForm` / `Form.Provider` los validan reactivamente igual que a Zod/Valibot/ArkType. Los schemas sincronos resuelven de forma sincrona. - `Form.AutoFields` auto-renderiza los campos leyendo la introspeccion `~sium` (`kind`, `wrappers`, `meta.widget`, …). - Los issues idlangref (`#?sium.errors.*`) los traduce el servicio `langs` compartido, reactivo al locale activo. - langs/logger se inyectan en sium por `createEngineSium({ langs, logger })`. sium es page-scoped: lo declara la app, no el runtime UIX. ## Recetas Rapidas ### UUID v4 ```ts const UUID = pipe( string(), regex(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i) ); ``` ### Slug ```ts const Slug = pipe(string(), min(1), max(80), regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/)); ``` ### Password Fuerte ```ts const StrongPassword = pipe( string(), min(8), refine((value) => /[a-z]/.test(value), { code: 'custom', message: '#?sium.errors.custom|Password needs a lowercase letter' }), refine((value) => /[A-Z]/.test(value), { code: 'custom', message: '#?sium.errors.custom|Password needs an uppercase letter' }), refine((value) => /\d/.test(value), { code: 'custom', message: '#?sium.errors.custom|Password needs a number' }) ); ``` ### HTTPS URL ```ts const HttpsUrl = pipe( string(), url(), refine((value) => new URL(value).protocol === 'https:', { code: 'custom', message: '#?sium.errors.custom|URL must use HTTPS' }) ); ``` ### File Upload ```ts const ImageFile = refine((file) => file.size <= 2_000_000 && file.type.startsWith('image/'), { code: 'custom', message: '#?sium.errors.custom|File must be an image under 2 MB' }); ``` ## Troubleshooting ### `SiumValidationError` Lanzado por `decode` cuando hay errores de validacion. ```ts if (error instanceof SiumValidationError) { error.issues; } ``` ### `SiumAsyncSchemaError` Lanzado por `decodeSync` o `validateSync` cuando la ruta de validacion necesita steps async. Los wrappers de sentinel pueden devolver sincronamente si no consultan el inner async (`optional(undefined)`, `nullable(null)`, `defaulted(undefined)`). Los combinadores siguen la misma regla por ruta: por ejemplo, `object({ a: optional(asyncSchema) }).decodeSync({ a: undefined })` puede completarse; `{ a: 'x' }` lanza porque ya necesita validar el inner async. Solucion: usa `await schema.decode(...)` o `await schema.validate(...)`. ### Un callback devuelve Promise pero `~sium.async` es false JavaScript no permite detectar una funcion normal que devuelve `Promise` sin ejecutarla. Declara el step con `{ async: true }`. ```ts refine((value) => Promise.resolve(Boolean(value)), { code: 'custom' }, { async: true }); ``` ### El path de un error no coincide Los errores emitidos dentro de schemas hijos deben usar `ctx.path`. Sium pasa paths absolutos a hijos de object y array. ## Verificacion Recomendada ```bash npx vitest run src/arts/sium/test/barrel.test.ts src/arts/sium/test/engine-sium.test.ts src/arts/sium/test/combinators.test.ts src/arts/sium/test/date.test.ts src/arts/sium/test/time.test.ts src/arts/sium/test/color.test.ts src/arts/sium/test/pipe.test.ts src/arts/sium/test/primitives.test.ts src/arts/sium/test/refines.test.ts src/arts/sium/test/schema.test.ts src/arts/sium/test/standard-schema.test.ts src/arts/sium/test/modifiers.test.ts src/arts/sium/test/issue.test.ts src/arts/sium/test/lazy.test.ts src/arts/sium/test/introspect.test.ts src/arts/sium/test/langs.test.ts src/arts/sium/test/resolver.test.ts src/arts/sium/test/coerce.test.ts src/arts/sium/test/refines-2.test.ts src/arts/sium/test/transforms.test.ts src/arts/sium/test/tuple.test.ts src/arts/sium/test/record.test.ts src/arts/sium/test/object-utils.test.ts ``` ```bash npx vitest run src/libs/days/test/parse.test.ts src/libs/days/test/queries.test.ts src/libs/color/test/guards.test.ts ``` ## Criterio Para Cambios Futuros Antes de ampliar Sium, comprueba que el cambio cumple estas reglas. - El core sigue siendo TypeScript puro. - La API publica no expone `createSchema()`. - Cada schema mantiene `decode`, `encode`, `validate`, `~standard` y `~sium`. - Los errores son datos estructurados, no strings finales. - Los tipos de dominio reutilizan `$libs/color` y `$libs/days`. - La documentacion se actualiza en este unico README.