diff --git a/continue.md b/continue.md index 1791e6bf7..eec60afb7 100644 --- a/continue.md +++ b/continue.md @@ -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. diff --git a/src/arts/sium/README.md b/src/arts/sium/README.md index 76c2be483..f439e9b21 100644 --- a/src/arts/sium/README.md +++ b/src/arts/sium/README.md @@ -613,6 +613,12 @@ export const Tree: Schema = 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 diff --git a/src/arts/sium/core/lazy.ts b/src/arts/sium/core/lazy.ts index 146efba1b..29e1572d0 100644 --- a/src/arts/sium/core/lazy.ts +++ b/src/arts/sium/core/lazy.ts @@ -20,6 +20,7 @@ function createResolvingAccessError(): SiumLazyResolvingError { export function lazy(getSchema: () => Schema): Schema { let cached: Schema | undefined; let resolving = false; + let proxy: Schema; const unresolvedStandard = { version: 1, @@ -54,14 +55,19 @@ export function lazy(getSchema: () => Schema): Schema { resolving = true; try { - cached = getSchema(); + const resolved = getSchema(); + if (resolved === proxy) { + throw createResolvingAccessError(); + } + + cached = resolved; return cached; } finally { resolving = false; } }; - const schema: Schema = { + proxy = { get '~standard'() { if (cached !== undefined) { return cached['~standard']; @@ -91,5 +97,5 @@ export function lazy(getSchema: () => Schema): Schema { validateSync: (input, ctx) => getResolvedSchema().validateSync(input, ctx) }; - return schema; + return proxy; } diff --git a/src/arts/sium/test/lazy.test.ts b/src/arts/sium/test/lazy.test.ts index e4fcc8743..827ec98fb 100644 --- a/src/arts/sium/test/lazy.test.ts +++ b/src/arts/sium/test/lazy.test.ts @@ -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 = 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 = lazy(() => schema); + + expect(() => schema.decodeSync('x')).toThrow(SiumLazyResolvingError); + }); + it('is re-exported by the core barrel', () => { expect(lazy).toBeTypeOf('function'); }); }); -