Refactor Sium domain schema helpers

active-uix
dev 5 months ago
parent 2255d9f706
commit 1dd9d62eae

@ -1,6 +1,86 @@
# Continue
Fecha de corte: 2026-05-15. Rama: `active-uix`.
Fecha de corte: 2026-05-16. Rama: `active-uix`.
Actualizacion 2026-05-16:
- Soma queda en estado funcionalmente cubierto a nivel provider/runtime:
`npm run check` -> 0 errores / 0 warnings, `npm run test` -> 233 archivos,
2242 tests OK, y la guardia de `*-provider.svelte.ts` sin test directo
devuelve `NO_MISSING_PROVIDER_TESTS`.
- Lo que falta para cerrar Soma de verdad no es mas cobertura de providers, sino
las capas exteriores:
1. Auditar wrappers Svelte publicos (`*.svelte`) frente a sus providers:
props bindables, snippets, forwarding de attrs, eventos y defaults.
2. Auditar barrels publicos (`index.ts`, `exports.ts`) para que no expongan
clases runtime internas, reexports muertos ni fachadas sobre `$libs`.
3. Validar rutas `/uix/components/*` con navegador: sin `pageerror`, sin
errores de consola, sin warnings de traduccion inesperados y con flujos
basicos interactivos.
4. Sincronizar inventario/documentacion Soma con el estado real: componente
activo, visual-native, wrapper publico, provider runtime y tests.
5. Mantener la doctrina DOM actual: escrituras gestionadas, listeners
globales, focus/scroll imperativos y portal/query global pasan por
`ActiveDom`; lecturas locales (`getBoundingClientRect`, `contains`,
`closest`, `scrollTop`, etc.) siguen siendo responsabilidad del componente.
6. Dejar optimizaciones de rendimiento para despues de cerrar API publica y
wrappers: virtualizacion compartida, datetime compartido y micro-churn del
runtime solo si aparecen datos reales o tests que lo justifiquen.
- Auditoria `src/arts/sium/kimi-audit.md` leida y evaluada. Mi veredicto:
buena auditoria, bastante alineada con el rol real de Sium como codec +
validador + introspeccion para Active, pero con dos matices:
- La prioridad critica correcta es integridad de datos, no ampliar Sium hasta
parecerse a Zod.
- Algunos fixes descritos como "1 linea" no lo son si queremos mantener
contratos `decodeSync`, `~sium` y `Form.AutoFields` coherentes.
- Sium P0 recomendado, sin cambios breaking:
1. Corregir `isPlainObjectRecord(...)` para aceptar solo objetos planos. No
usar `value.constructor === Object` como unica regla: preferir
`Object.getPrototypeOf(value) === Object.prototype || null` para no romper
objetos null-prototype y reducir falsos positivos.
2. Mejorar `email()` con una regex conservadora de producto, no RFC completa:
dominio sin puntos dobles y TLD practico de 2+ caracteres.
3. Cachear `regex(pattern)` sin mutar `lastIndex`. Si el pattern trae `g` o
`y`, clonar sin flags mutables o resetear antes de probar.
4. Alinear README con `ctx.rootValue`: hoy solo es seguro en refines de objeto
ya decodificado. Las validaciones cross-field deben vivir en el object
refine, no en field refines.
5. Revisar `optional/nullable/defaulted` con schema interno async. El problema
es real, pero la solucion no es mover un `if`: `createSchema.decodeSync`
lanza por `spec.async === true` antes de entrar en `decodeImpl`. Hace falta
una decision de runtime (`syncShortCircuit`, decodeSync custom por wrapper,
o semantica documentada de "schema async siempre async").
- Sium P1 coordinado con consumidores:
1. No cambiar `lazy()` a `kind: 'lazy'` hasta que
`src/uix/soma/components/form/components/form-auto-fields.svelte` sepa
renderizar/posponer ese kind. Hoy `SchemaKind` no incluye `lazy`.
2. Anadir refinements pequenos y utiles: `finite`, `safe`, `positive`,
`nonempty`, `uuid` solo si hay uso inmediato en Active/Form.
3. Anadir utilidades de objeto (`partial`, `pick`, `omit`, `extend`) y
estructuras (`tuple`, `record`) cuando AutoFields y `~sium.shape` tengan
contrato de introspeccion claro.
4. Considerar `preprocess` y `coerce` como features de formularios, no como
ampliacion generalista.
- Sium P2 interno:
1. Cerrado: `types/_helpers.ts` incorpora `createDomainValueSchema(...)` y
`createDomainRangeSchema(...)`. `colorValue`, `dateValue`, `timeValue`,
`dateRange` y `timeRange` comparten ahora el patron identity codec +
guard + widget + range order sin mover esa logica a `core`.
2. Unificar paths sync/async de combinadores solo con tests amplios; es zona
sensible por `SiumAsyncSchemaError`.
3. Documentar limites de `lazy()` con inputs ciclicos o anadir proteccion
`WeakSet` si empieza a recibir datos no confiables.
- Validacion Sium domain factory:
- `npx vitest run src/arts/sium/test/color.test.ts src/arts/sium/test/date.test.ts src/arts/sium/test/time.test.ts src/arts/sium/test/standard-schema.test.ts` -> 4 archivos, 122 tests OK.
- `npm run check` -> 0 errores, 0 warnings.
- Criterio de cierre para Sium antes de tocar consumidores UI:
- Tests focales de `src/arts/sium` verdes.
- `npm run check` sin warnings.
- README y tipos `SchemaKind`/`SiumShape` sincronizados.
- `Form.AutoFields` actualizado antes de cualquier kind nuevo visible en
`~sium`.
- Ninguna feature nueva entra si solo duplica Zod/Valibot sin consumidor
propio de Active.
Actualizacion 2026-05-15:

@ -1,4 +1,64 @@
import { SiumValidationError } from '../errors';
import { createSchema } from '../core/schema';
import { issue } from '../core/issue';
import type { MetaAnnotations, Schema, SchemaKind } from '../core/types';
type DomainSchemaOptions<T> = {
kind: SchemaKind;
expected: string;
guard: (value: unknown) => value is T;
widget: string;
meta?: MetaAnnotations;
};
type DomainRangeSchemaOptions<T> = DomainSchemaOptions<T> & {
isOrdered: (value: T) => boolean;
};
/**
* Creates an identity codec for domain values validated by a type guard.
*
* Kept under `types/` because this is not a generic sium primitive: it is a
* small factory for Active domain values such as color, date, and time.
*/
export function createDomainValueSchema<T>(opts: DomainSchemaOptions<T>): Schema<T, T> {
return createSchema<T, T>({
kind: opts.kind,
decodeImpl: (input) => {
if (!opts.guard(input)) {
throw createDomainTypeError(opts.expected, input);
}
return input;
},
encode: (value) => value,
meta: { ...opts.meta, widget: opts.widget }
});
}
/**
* Creates an identity codec for ordered domain ranges.
*
* The range guard owns the structural shape (`start`/`end` optionality and
* item type). This helper only centralizes the shared type + order errors.
*/
export function createDomainRangeSchema<T>(opts: DomainRangeSchemaOptions<T>): Schema<T, T> {
return createSchema<T, T>({
kind: opts.kind,
decodeImpl: (input) => {
if (!opts.guard(input)) {
throw createDomainTypeError(opts.expected, input);
}
if (!opts.isOrdered(input)) {
throw new SiumValidationError([createRangeOrderIssue()]);
}
return input;
},
encode: (value) => value,
meta: { ...opts.meta, widget: opts.widget }
});
}
/**
* Range issue raised when `start` is strictly after `end`. Shared by date and
@ -11,3 +71,13 @@ export function createRangeOrderIssue() {
params: {}
});
}
function createDomainTypeError(expected: string, input: unknown): SiumValidationError {
return new SiumValidationError([
issue({
code: 'type',
message: '#?sium.errors.type|Expected {{expected}} but received {{actual}}',
params: { expected, actual: String(input) }
})
]);
}

@ -3,30 +3,17 @@ import { isColorValue } from '$libs/color';
import { number } from '../core';
import { meta, pipe } from '../core';
import { integer, max, min } from '../core';
import { createSchema } from '../core/schema';
import { SiumValidationError } from '../core';
import { issue } from '../core/issue';
import { createDomainValueSchema } from './_helpers';
/**
* Validates a composite color value with hex, rgb, hsl, and hsv representations.
*/
export function colorValue() {
return createSchema<ColorValue, ColorValue>({
return createDomainValueSchema<ColorValue>({
kind: 'color',
decodeImpl: (input) => {
if (!isColorValue(input)) {
throw new SiumValidationError([
issue({
code: 'type',
message: '#?sium.errors.type|Expected {{expected}} but received {{actual}}',
params: { expected: 'ColorValue', actual: String(input) }
})
]);
}
return input;
},
encode: (value) => value,
meta: { widget: 'color-picker' }
expected: 'ColorValue',
guard: isColorValue,
widget: 'color-picker'
});
}

@ -6,8 +6,7 @@ import { integer, max, min } from '../core';
import { createSchema } from '../core/schema';
import { SiumValidationError } from '../core';
import { issue } from '../core/issue';
import { createRangeOrderIssue } from './_helpers';
import { createDomainRangeSchema, createDomainValueSchema } from './_helpers';
function isDateValue(value: unknown): value is DateValue {
if (value == null || typeof value !== 'object') return false;
@ -40,22 +39,11 @@ export function dateValue(opts?: {
calendar?: 'gregorian' | 'buddhist' | string;
granularity?: 'day' | 'hour' | 'minute' | 'second';
}) {
return createSchema<DateValue, DateValue>({
return createDomainValueSchema<DateValue>({
kind: 'date',
decodeImpl: (input) => {
if (!isDateValue(input)) {
throw new SiumValidationError([
issue({
code: 'type',
message: '#?sium.errors.type|Expected {{expected}} but received {{actual}}',
params: { expected: 'DateValue', actual: String(input) }
})
]);
}
return input;
},
encode: (value) => value,
meta: { widget: 'date-field' }
expected: 'DateValue',
guard: isDateValue,
widget: 'date-field'
});
}
@ -102,25 +90,12 @@ export function coerceDate() {
* Validates a date range object with optional start and end DateValues.
*/
export function dateRange() {
return createSchema<DateRange, DateRange>({
return createDomainRangeSchema<DateRange>({
kind: 'date-range',
decodeImpl: (input) => {
if (!isDateRange(input)) {
throw new SiumValidationError([
issue({
code: 'type',
message: '#?sium.errors.type|Expected {{expected}} but received {{actual}}',
params: { expected: 'DateRange', actual: String(input) }
})
]);
}
if (!isOrderedDateRange(input)) {
throw new SiumValidationError([createRangeOrderIssue()]);
}
return input;
},
encode: (value) => value,
meta: { widget: 'date-range-field' }
expected: 'DateRange',
guard: isDateRange,
isOrdered: isOrderedDateRange,
widget: 'date-range-field'
});
}

@ -2,10 +2,7 @@ import type { TimeValue, TimeRange } from '$libs/days';
import { enumOf, number } from '../core/primitives';
import { meta, pipe } from '../core/pipe';
import { integer, max, min } from '../core/refines';
import { createSchema } from '../core/schema';
import { SiumValidationError } from '../errors';
import { issue } from '../core/issue';
import { createRangeOrderIssue } from './_helpers';
import { createDomainRangeSchema, createDomainValueSchema } from './_helpers';
function isTimeValue(value: unknown): value is TimeValue {
if (value == null || typeof value !== 'object') return false;
@ -43,22 +40,11 @@ export function timeValue(opts?: {
granularity?: 'hour' | 'minute' | 'second';
hourCycle?: 12 | 24;
}) {
return createSchema<TimeValue, TimeValue>({
return createDomainValueSchema<TimeValue>({
kind: 'time',
decodeImpl: (input) => {
if (!isTimeValue(input)) {
throw new SiumValidationError([
issue({
code: 'type',
message: '#?sium.errors.type|Expected {{expected}} but received {{actual}}',
params: { expected: 'TimeValue', actual: String(input) }
})
]);
}
return input;
},
encode: (value) => value,
meta: { widget: 'time-field' }
expected: 'TimeValue',
guard: isTimeValue,
widget: 'time-field'
});
}
@ -66,25 +52,12 @@ export function timeValue(opts?: {
* Validates a time range object with optional start and end TimeValues.
*/
export function timeRange() {
return createSchema<TimeRange, TimeRange>({
return createDomainRangeSchema<TimeRange>({
kind: 'time-range',
decodeImpl: (input) => {
if (!isTimeRange(input)) {
throw new SiumValidationError([
issue({
code: 'type',
message: '#?sium.errors.type|Expected {{expected}} but received {{actual}}',
params: { expected: 'TimeRange', actual: String(input) }
})
]);
}
if (!isOrderedTimeRange(input)) {
throw new SiumValidationError([createRangeOrderIssue()]);
}
return input;
},
encode: (value) => value,
meta: { widget: 'time-range-field' }
expected: 'TimeRange',
guard: isTimeRange,
isOrdered: isOrderedTimeRange,
widget: 'time-range-field'
});
}

Loading…
Cancel
Save

Powered by TurnKey Linux.