Guard Sium lazy self resolution

active-uix
dev 5 months ago
parent c4eafd02f2
commit ac5fdca4e7

@ -129,8 +129,14 @@ Actualizacion 2026-05-16:
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.
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
porque la deteccion de ciclos de datos debe ser una capa de normalizacion
del dominio si entran datos no confiables.
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.
- 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.

@ -613,6 +613,12 @@ export const Tree: Schema<TreeNode> = lazy(() =>
);
```
`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

@ -20,6 +20,7 @@ function createResolvingAccessError(): SiumLazyResolvingError {
export function lazy<I, O>(getSchema: () => Schema<I, O>): Schema<I, O> {
let cached: Schema<I, O> | undefined;
let resolving = false;
let proxy: Schema<I, O>;
const unresolvedStandard = {
version: 1,
@ -54,14 +55,19 @@ export function lazy<I, O>(getSchema: () => Schema<I, O>): Schema<I, O> {
resolving = true;
try {
cached = getSchema();
const resolved = getSchema();
if (resolved === proxy) {
throw createResolvingAccessError();
}
cached = resolved;
return cached;
} finally {
resolving = false;
}
};
const schema: Schema<I, O> = {
proxy = {
get '~standard'() {
if (cached !== undefined) {
return cached['~standard'];
@ -91,5 +97,5 @@ export function lazy<I, O>(getSchema: () => Schema<I, O>): Schema<I, O> {
validateSync: (input, ctx) => getResolvedSchema().validateSync(input, ctx)
};
return schema;
return proxy;
}

@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest';
import { array, lazy, object, optional, string } from '../core';
import { createSchema } from '../core/schema';
import type { Schema } from '../core/types';
import { SiumAsyncSchemaError } from '../errors';
import { SiumAsyncSchemaError, SiumLazyResolvingError } from '../errors';
type TreeNode = {
value: string;
@ -219,8 +219,22 @@ describe('lazy()', () => {
expect(() => schema.decodeSync('x')).toThrow(SiumAsyncSchemaError);
});
it('throws SiumLazyResolvingError when the thunk uses the proxy while resolving', () => {
const schema: Schema<string, string> = lazy(() => {
schema.decodeSync('x');
return string();
});
expect(() => schema.decodeSync('x')).toThrow(SiumLazyResolvingError);
});
it('throws SiumLazyResolvingError when the thunk returns the same lazy proxy', () => {
const schema: Schema<string, string> = lazy(() => schema);
expect(() => schema.decodeSync('x')).toThrow(SiumLazyResolvingError);
});
it('is re-exported by the core barrel', () => {
expect(lazy).toBeTypeOf('function');
});
});

Loading…
Cancel
Save

Powered by TurnKey Linux.