# 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.
feat(sium): audit cleanup + 2.0 (coercion, formats, combinators, object utils)
Audit: drop dead SIUM_ERRORS catalogue, no-op try/catch in refine steps, unused isPromiseLike/pathKeys re-export, ignored dateValue/timeValue opts; add a sync fast-path to ~standard.validate so sync schemas no longer force the async branch (+untrack workaround) in form/storage consumers; doc fixes ($lib alias, JSDoc, README).
2.0 (all on the s.* facade): coercion (coerceNumber/Boolean/String); string formats (uuid/slug/datetime/ipv4); numeric (finite/positive/nonnegative/multipleOf); transforms (trim/toLowerCase/toUpperCase) + nullish; combinators tuple/record (+ SchemaKind/SiumShape vocabulary + introspection); object utils pick/omit/partial/extend/merge (+ SiumObjectUtilError). 8 new issue codes with es/en.
398 -> 450 tests, 0 type errors, prettier clean. Updated arts/ diagnostics contract + sium README.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
## 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<Result>` ; 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< I = unknown , O = I > {
readonly '~standard': StandardSchemaV1.Props< I , O > ;
readonly '~sium': SiumIntrospection;
decode(input: I, ctx?: Partial< Ctx > ): Promise< O > ;
decodeSync(input: I, ctx?: Partial< Ctx > ): O;
encode(value: O): I;
validate(input: I, ctx?: Partial< Ctx > ): Promise< Result < O > >;
validateSync(input: I, ctx?: Partial< Ctx > ): Result< O > ;
}
```
`decode` valida y transforma. Si falla, lanza `SiumValidationError` .
`validate` envuelve `decode` y devuelve `{ ok: true, value }` o `{ ok: false, issues }` .
feat(sium): audit cleanup + 2.0 (coercion, formats, combinators, object utils)
Audit: drop dead SIUM_ERRORS catalogue, no-op try/catch in refine steps, unused isPromiseLike/pathKeys re-export, ignored dateValue/timeValue opts; add a sync fast-path to ~standard.validate so sync schemas no longer force the async branch (+untrack workaround) in form/storage consumers; doc fixes ($lib alias, JSDoc, README).
2.0 (all on the s.* facade): coercion (coerceNumber/Boolean/String); string formats (uuid/slug/datetime/ipv4); numeric (finite/positive/nonnegative/multipleOf); transforms (trim/toLowerCase/toUpperCase) + nullish; combinators tuple/record (+ SchemaKind/SiumShape vocabulary + introspection); object utils pick/omit/partial/extend/merge (+ SiumObjectUtilError). 8 new issue codes with es/en.
398 -> 450 tests, 0 type errors, prettier clean. Updated arts/ diagnostics contract + sium README.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
`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.
feat(sium): audit cleanup + 2.0 (coercion, formats, combinators, object utils)
Audit: drop dead SIUM_ERRORS catalogue, no-op try/catch in refine steps, unused isPromiseLike/pathKeys re-export, ignored dateValue/timeValue opts; add a sync fast-path to ~standard.validate so sync schemas no longer force the async branch (+untrack workaround) in form/storage consumers; doc fixes ($lib alias, JSDoc, README).
2.0 (all on the s.* facade): coercion (coerceNumber/Boolean/String); string formats (uuid/slug/datetime/ipv4); numeric (finite/positive/nonnegative/multipleOf); transforms (trim/toLowerCase/toUpperCase) + nullish; combinators tuple/record (+ SchemaKind/SiumShape vocabulary + introspection); object utils pick/omit/partial/extend/merge (+ SiumObjectUtilError). 8 new issue codes with es/en.
398 -> 450 tests, 0 type errors, prettier clean. Updated arts/ diagnostics contract + sium README.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
`~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
feat(sium): audit cleanup + 2.0 (coercion, formats, combinators, object utils)
Audit: drop dead SIUM_ERRORS catalogue, no-op try/catch in refine steps, unused isPromiseLike/pathKeys re-export, ignored dateValue/timeValue opts; add a sync fast-path to ~standard.validate so sync schemas no longer force the async branch (+untrack workaround) in form/storage consumers; doc fixes ($lib alias, JSDoc, README).
2.0 (all on the s.* facade): coercion (coerceNumber/Boolean/String); string formats (uuid/slug/datetime/ipv4); numeric (finite/positive/nonnegative/multipleOf); transforms (trim/toLowerCase/toUpperCase) + nullish; combinators tuple/record (+ SchemaKind/SiumShape vocabulary + introspection); object utils pick/omit/partial/extend/merge (+ SiumObjectUtilError). 8 new issue codes with es/en.
398 -> 450 tests, 0 type errors, prettier clean. Updated arts/ diagnostics contract + sium README.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
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());
feat(sium): audit cleanup + 2.0 (coercion, formats, combinators, object utils)
Audit: drop dead SIUM_ERRORS catalogue, no-op try/catch in refine steps, unused isPromiseLike/pathKeys re-export, ignored dateValue/timeValue opts; add a sync fast-path to ~standard.validate so sync schemas no longer force the async branch (+untrack workaround) in form/storage consumers; doc fixes ($lib alias, JSDoc, README).
2.0 (all on the s.* facade): coercion (coerceNumber/Boolean/String); string formats (uuid/slug/datetime/ipv4); numeric (finite/positive/nonnegative/multipleOf); transforms (trim/toLowerCase/toUpperCase) + nullish; combinators tuple/record (+ SchemaKind/SiumShape vocabulary + introspection); object utils pick/omit/partial/extend/merge (+ SiumObjectUtilError). 8 new issue codes with es/en.
398 -> 450 tests, 0 type errors, prettier clean. Updated arts/ diagnostics contract + sium README.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
nullish(string());
defaulted(number(), () => 18);
```
`optional` permite `undefined` .
`nullable` permite `null` .
feat(sium): audit cleanup + 2.0 (coercion, formats, combinators, object utils)
Audit: drop dead SIUM_ERRORS catalogue, no-op try/catch in refine steps, unused isPromiseLike/pathKeys re-export, ignored dateValue/timeValue opts; add a sync fast-path to ~standard.validate so sync schemas no longer force the async branch (+untrack workaround) in form/storage consumers; doc fixes ($lib alias, JSDoc, README).
2.0 (all on the s.* facade): coercion (coerceNumber/Boolean/String); string formats (uuid/slug/datetime/ipv4); numeric (finite/positive/nonnegative/multipleOf); transforms (trim/toLowerCase/toUpperCase) + nullish; combinators tuple/record (+ SchemaKind/SiumShape vocabulary + introspection); object utils pick/omit/partial/extend/merge (+ SiumObjectUtilError). 8 new issue codes with es/en.
398 -> 450 tests, 0 type errors, prettier clean. Updated arts/ diagnostics contract + sium README.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
`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());
feat(sium): audit cleanup + 2.0 (coercion, formats, combinators, object utils)
Audit: drop dead SIUM_ERRORS catalogue, no-op try/catch in refine steps, unused isPromiseLike/pathKeys re-export, ignored dateValue/timeValue opts; add a sync fast-path to ~standard.validate so sync schemas no longer force the async branch (+untrack workaround) in form/storage consumers; doc fixes ($lib alias, JSDoc, README).
2.0 (all on the s.* facade): coercion (coerceNumber/Boolean/String); string formats (uuid/slug/datetime/ipv4); numeric (finite/positive/nonnegative/multipleOf); transforms (trim/toLowerCase/toUpperCase) + nullish; combinators tuple/record (+ SchemaKind/SiumShape vocabulary + introspection); object utils pick/omit/partial/extend/merge (+ SiumObjectUtilError). 8 new issue codes with es/en.
398 -> 450 tests, 0 type errors, prettier clean. Updated arts/ diagnostics contract + sium README.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
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' });
```
feat(sium): audit cleanup + 2.0 (coercion, formats, combinators, object utils)
Audit: drop dead SIUM_ERRORS catalogue, no-op try/catch in refine steps, unused isPromiseLike/pathKeys re-export, ignored dateValue/timeValue opts; add a sync fast-path to ~standard.validate so sync schemas no longer force the async branch (+untrack workaround) in form/storage consumers; doc fixes ($lib alias, JSDoc, README).
2.0 (all on the s.* facade): coercion (coerceNumber/Boolean/String); string formats (uuid/slug/datetime/ipv4); numeric (finite/positive/nonnegative/multipleOf); transforms (trim/toLowerCase/toUpperCase) + nullish; combinators tuple/record (+ SchemaKind/SiumShape vocabulary + introspection); object utils pick/omit/partial/extend/merge (+ SiumObjectUtilError). 8 new issue codes with es/en.
398 -> 450 tests, 0 type errors, prettier clean. Updated arts/ diagnostics contract + sium README.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
`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();
feat(sium): audit cleanup + 2.0 (coercion, formats, combinators, object utils)
Audit: drop dead SIUM_ERRORS catalogue, no-op try/catch in refine steps, unused isPromiseLike/pathKeys re-export, ignored dateValue/timeValue opts; add a sync fast-path to ~standard.validate so sync schemas no longer force the async branch (+untrack workaround) in form/storage consumers; doc fixes ($lib alias, JSDoc, README).
2.0 (all on the s.* facade): coercion (coerceNumber/Boolean/String); string formats (uuid/slug/datetime/ipv4); numeric (finite/positive/nonnegative/multipleOf); transforms (trim/toLowerCase/toUpperCase) + nullish; combinators tuple/record (+ SchemaKind/SiumShape vocabulary + introspection); object utils pick/omit/partial/extend/merge (+ SiumObjectUtilError). 8 new issue codes with es/en.
398 -> 450 tests, 0 type errors, prettier clean. Updated arts/ diagnostics contract + sium README.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
// 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.
feat(sium): audit cleanup + 2.0 (coercion, formats, combinators, object utils)
Audit: drop dead SIUM_ERRORS catalogue, no-op try/catch in refine steps, unused isPromiseLike/pathKeys re-export, ignored dateValue/timeValue opts; add a sync fast-path to ~standard.validate so sync schemas no longer force the async branch (+untrack workaround) in form/storage consumers; doc fixes ($lib alias, JSDoc, README).
2.0 (all on the s.* facade): coercion (coerceNumber/Boolean/String); string formats (uuid/slug/datetime/ipv4); numeric (finite/positive/nonnegative/multipleOf); transforms (trim/toLowerCase/toUpperCase) + nullish; combinators tuple/record (+ SchemaKind/SiumShape vocabulary + introspection); object utils pick/omit/partial/extend/merge (+ SiumObjectUtilError). 8 new issue codes with es/en.
398 -> 450 tests, 0 type errors, prettier clean. Updated arts/ diagnostics contract + sium README.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
`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< string | number > ;
code: string;
message: string;
params?: Record< string , unknown > ;
};
```
`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< string | number > ;
rootInput: unknown;
rootValue?: unknown;
locale?: string;
meta?: Record< string , unknown > ;
};
```
`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< TreeNode > = 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) => 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
feat(sium): audit cleanup + 2.0 (coercion, formats, combinators, object utils)
Audit: drop dead SIUM_ERRORS catalogue, no-op try/catch in refine steps, unused isPromiseLike/pathKeys re-export, ignored dateValue/timeValue opts; add a sync fast-path to ~standard.validate so sync schemas no longer force the async branch (+untrack workaround) in form/storage consumers; doc fixes ($lib alias, JSDoc, README).
2.0 (all on the s.* facade): coercion (coerceNumber/Boolean/String); string formats (uuid/slug/datetime/ipv4); numeric (finite/positive/nonnegative/multipleOf); transforms (trim/toLowerCase/toUpperCase) + nullish; combinators tuple/record (+ SchemaKind/SiumShape vocabulary + introspection); object utils pick/omit/partial/extend/merge (+ SiumObjectUtilError). 8 new issue codes with es/en.
398 -> 450 tests, 0 type errors, prettier clean. Updated arts/ diagnostics contract + sium README.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4 months ago
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.