Allow path-based sync Sium combinators

active-uix
dev 5 months ago
parent ac5fdca4e7
commit 848259e225

@ -127,8 +127,11 @@ Actualizacion 2026-05-16:
`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`.
2. Cerrado: combinadores (`object`, `array`, `union`, `discriminated`) usan
la misma semantica por ruta que wrappers. Un schema marcado `async` puede
completar `decodeSync` si el input no necesita tocar trabajo async (por
ejemplo field optional async ausente, array vacio o union sync-first). Si
la ruta seleccionada necesita async, sigue lanzando `SiumAsyncSchemaError`.
3. Cerrado: `lazy()` documenta limites de datos runtime autociclicos y lanza
`SiumLazyResolvingError` cuando el thunk usa el proxy mientras resuelve o
devuelve el mismo proxy como schema final. No se anade `WeakSet` global
@ -137,6 +140,9 @@ Actualizacion 2026-05-16:
4. Validado tras `lazy()`: `npx vitest run src/arts/sium/test` -> 18
archivos, 370 tests OK; `npm run check` -> 0 errores, 0 warnings;
`npm run test` -> 233 archivos, 2266 tests OK.
5. Validado tras paths sync/async de combinadores: `npx vitest run
src/arts/sium/test` -> 18 archivos, 374 tests OK; `npm run check` -> 0
errores, 0 warnings; `npm run test` -> 233 archivos, 2270 tests OK.
- 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.

@ -744,6 +744,9 @@ if (error instanceof SiumValidationError) {
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(...)`.

@ -80,6 +80,7 @@ export function array<I, O>(item: Schema<I, O>): Schema<I[], O[]> {
item
},
decodeImpl: item['~sium'].async ? decodeAsyncArray : decodeSyncArray,
decodeSyncImpl: decodeSyncArray,
encode: (value) => {
if (!Array.isArray(value)) {
throw new SiumEncodeExpectsArrayError();

@ -118,6 +118,7 @@ export function object<T extends Record<string, Schema>>(
unknownKeys
},
decodeImpl: isAsync ? decodeAsyncShape : decodeSyncShape,
decodeSyncImpl: decodeSyncShape,
encode: (value) => {
if (!isPlainObjectRecord(value)) {
throw new SiumEncodeExpectsObjectError('object');

@ -68,6 +68,7 @@ export function union<M extends readonly [Schema, ...Schema[]]>(
members
},
decodeImpl: isAsync ? decodeAsyncUnion : decodeSyncUnion,
decodeSyncImpl: decodeSyncUnion,
encode: (value) => value as InferInput<M[number]>
});
}
@ -160,6 +161,7 @@ export function discriminated<K extends string, M extends readonly [Schema, ...S
members
},
decodeImpl: isAsync ? decodeAsyncDiscriminated : decodeSyncDiscriminated,
decodeSyncImpl: decodeSyncDiscriminated,
encode: (value) => {
if (!isPlainObjectRecord(value)) {
throw new SiumEncodeExpectsObjectError('discriminated');

@ -1,5 +1,6 @@
import { describe, expect, expectTypeOf, it, vi } from 'vitest';
import { array, discriminated, object, union } from '../core/combinators';
import { optional } from '../core/modifiers';
import { pipe, refine } from '../core/pipe';
import { literal, number, string } from '../core/primitives';
import { createSchema } from '../core/schema';
@ -297,6 +298,19 @@ describe('object()', () => {
expect(() => schema.decodeSync({ a: 'x', b: 1 })).toThrow(SiumAsyncSchemaError);
});
it('short-circuits async optional fields in sync object paths', () => {
const asyncField = createSchema({
kind: 'string',
async: true,
decodeImpl: async (input: string) => input,
encode: (value: string) => value
});
const schema = object({ a: optional(asyncField), b: number() });
expect(schema.decodeSync({ a: undefined, b: 1 })).toEqual({ a: undefined, b: 1 });
expect(() => schema.decodeSync({ a: 'x', b: 1 })).toThrow(SiumAsyncSchemaError);
});
});
describe('array()', () => {
@ -429,6 +443,19 @@ describe('array()', () => {
expect(() => schema.decodeSync(['a'])).toThrow(SiumAsyncSchemaError);
});
it('short-circuits sync array paths that do not need async item work', () => {
const asyncItem = createSchema({
kind: 'string',
async: true,
decodeImpl: async (input: string) => input,
encode: (value: string) => value
});
expect(array(asyncItem).decodeSync([])).toEqual([]);
expect(array(optional(asyncItem)).decodeSync([undefined])).toEqual([undefined]);
expect(() => array(asyncItem).decodeSync(['a'])).toThrow(SiumAsyncSchemaError);
});
});
describe('union()', () => {
@ -496,6 +523,18 @@ describe('union()', () => {
expect(() => schema.decodeSync('x')).toThrow(SiumAsyncSchemaError);
});
it('can decode sync union members before an async member is needed', () => {
const asyncMember = createSchema({
kind: 'string',
async: true,
decodeImpl: async (input: string) => input,
encode: (value: string) => value
});
expect(union(number(), asyncMember).decodeSync(1)).toBe(1);
expect(() => union(asyncMember, number()).decodeSync(1 as never)).toThrow(SiumAsyncSchemaError);
});
});
describe('discriminated()', () => {
@ -640,4 +679,24 @@ describe('discriminated()', () => {
expect(() => schema.decodeSync({ kind: 'a', val: 'x' })).toThrow(SiumAsyncSchemaError);
});
it('short-circuits selected discriminated member paths when async work is not needed', () => {
const asyncValue = createSchema({
kind: 'string',
async: true,
decodeImpl: async (input: string) => input,
encode: (value: string) => value
});
const schema = discriminated('kind', [
object({ kind: literal('a'), val: optional(asyncValue) }),
object({ kind: literal('b'), val: number() })
]);
expect(schema.decodeSync({ kind: 'a', val: undefined })).toEqual({
kind: 'a',
val: undefined
});
expect(schema.decodeSync({ kind: 'b', val: 1 })).toEqual({ kind: 'b', val: 1 });
expect(() => schema.decodeSync({ kind: 'a', val: 'x' })).toThrow(SiumAsyncSchemaError);
});
});

Loading…
Cancel
Save

Powered by TurnKey Linux.