diff --git a/NEXT_STEPS.md b/NEXT_STEPS.md index c92f3ee..dee33ae 100644 --- a/NEXT_STEPS.md +++ b/NEXT_STEPS.md @@ -31,6 +31,7 @@ Estado al cierre: - `arts/conn/connection.ts` delega la programacion de reconnect y exhaustion en `connection-reconnect-runtime.ts`. - `libs/perm/evaluator.ts` delega helpers puros de resultado, dependencias y comparacion en `evaluator-helpers.ts`. - `arts/http/engine-http.ts` delega la validacion preflight de `bodySchema` en `request-validation.ts`. + - `arts/sium/core/pipe.ts` queda centrado en composicion; factories `refine/transform/codec/meta` viven en `steps.ts`. - `arts/conn/connection.ts` delega decode/routing de frames entrantes en `connection-message-router.ts`. - `arts/conn/connection.ts` delega el intento open/auth/flush/join en `connection-connect.ts`. - `arts/conn/connection.ts` delega current transport, attach/detach y close en `connection-transport-runtime.ts`. diff --git a/src/arts/sium/core/pipe.ts b/src/arts/sium/core/pipe.ts index 4f19060..7bcab4d 100644 --- a/src/arts/sium/core/pipe.ts +++ b/src/arts/sium/core/pipe.ts @@ -1,11 +1,7 @@ -import { issue } from './issue'; import { createSchema } from './schema'; import { - SiumAsyncSchemaError, - SiumValidationError, type Ctx, type MetaAnnotations, - type RefineFailure, type Schema, type SchemaEffect, type Step @@ -13,35 +9,6 @@ import { type UnknownSchema = Schema; type UnknownStep = Step; -type StepOptions = { - /** - * Mark callbacks as async when they are regular functions that return a - * Promise. JavaScript cannot detect that shape before executing the callback. - */ - async?: boolean; -}; - -import { isPromiseLike } from './internals'; - -function isAsyncFunction(fn: Function): boolean { - return fn.constructor.name === 'AsyncFunction'; -} - -function createAsyncStepError() { - return new SiumAsyncSchemaError( - undefined, - 'Step contains async logic — use decode() instead of decodeSync().' - ); -} - -function createRefineIssue(path: ReadonlyArray, fail: RefineFailure) { - return issue({ - path, - code: fail.code ?? 'custom', - message: fail.message ?? '#?sium.errors.custom|Invalid value', - ...(fail.params === undefined ? {} : { params: fail.params }) - }); -} function mergeMeta( baseMeta: Readonly, @@ -191,146 +158,4 @@ export function pipe( }); } -/** - * Creates a refinement step that validates the current value without changing it. - * - * Failed checks emit a single `Issue` built from `fail`, defaulting to - * `code='custom'` and `message='#?sium.errors.custom|Invalid value'`. - */ -export function refine( - check: (value: T, ctx: Ctx) => boolean | Promise, - fail: RefineFailure, - opts?: StepOptions -): Step { - const async = opts?.async ?? isAsyncFunction(check); - - return { - kind: 'refine', - async, - meta: {}, - decode: async (input, ctx) => { - try { - const result = await check(input, ctx); - if (result) { - return input; - } - - throw new SiumValidationError([createRefineIssue(ctx.path, fail)]); - } catch (error) { - if (error instanceof SiumValidationError) { - throw error; - } - - throw error; - } - }, - decodeSync: (input, ctx) => { - if (async) { - throw createAsyncStepError(); - } - - try { - const result = check(input, ctx); - if (isPromiseLike(result)) { - throw createAsyncStepError(); - } - - if (result) { - return input; - } - - throw new SiumValidationError([createRefineIssue(ctx.path, fail)]); - } catch (error) { - if (error instanceof SiumValidationError) { - throw error; - } - - throw error; - } - } - }; -} - -/** - * Creates a one-way transformation step. - * - * `transform` affects decode only. It intentionally does not expose `encode`, - * so `pipe(...).encode(...)` treats it as identity in reverse composition. - */ -export function transform( - fn: (value: I, ctx: Ctx) => O | Promise, - opts?: StepOptions -): Step { - const async = opts?.async ?? isAsyncFunction(fn); - - return { - kind: 'transform', - async, - meta: {}, - decode: async (input, ctx) => await fn(input, ctx), - decodeSync: (input, ctx) => { - if (async) { - throw createAsyncStepError(); - } - - const result = fn(input, ctx); - if (isPromiseLike(result)) { - throw createAsyncStepError(); - } - - return result; - } - }; -} - -/** - * Creates a bidirectional step with explicit decode and encode sides. - * - * Unlike `transform`, `codec` participates in reverse composition during - * `pipe(...).encode(...)` by applying the provided `encode` function. - */ -export function codec( - decode: (value: I, ctx: Ctx) => O | Promise, - encode: (value: O) => I, - opts?: StepOptions -): Step { - const async = opts?.async ?? isAsyncFunction(decode); - - return { - kind: 'codec', - async, - meta: {}, - decode: async (input, ctx) => await decode(input, ctx), - decodeSync: (input, ctx) => { - if (async) { - throw createAsyncStepError(); - } - - const result = decode(input, ctx); - if (isPromiseLike(result)) { - throw createAsyncStepError(); - } - - return result; - }, - encode - }; -} - -/** - * Creates a pure annotation step that leaves the value untouched. - * - * `meta` never changes runtime data and never participates in encode; it only - * contributes shallow-copied annotations to the final schema introspection. - */ -export function meta(annotations: MetaAnnotations): Step { - const copiedMeta = { ...annotations }; - - return { - kind: 'meta', - async: false, - meta: copiedMeta, - decode: async (input) => input, - decodeSync: (input) => input - }; -} +export { refine, transform, codec, meta } from './steps'; diff --git a/src/arts/sium/core/steps.ts b/src/arts/sium/core/steps.ts new file mode 100644 index 0000000..a43924d --- /dev/null +++ b/src/arts/sium/core/steps.ts @@ -0,0 +1,182 @@ +import { issue } from './issue'; +import { + SiumAsyncSchemaError, + SiumValidationError, + type Ctx, + type MetaAnnotations, + type RefineFailure, + type Step +} from './types'; +import { isPromiseLike } from './internals'; + +type StepOptions = { + /** + * Mark callbacks as async when they are regular functions that return a + * Promise. JavaScript cannot detect that shape before executing the callback. + */ + async?: boolean; +}; + +function isAsyncFunction(fn: Function): boolean { + return fn.constructor.name === 'AsyncFunction'; +} + +function createAsyncStepError() { + return new SiumAsyncSchemaError( + undefined, + 'Step contains async logic — use decode() instead of decodeSync().' + ); +} + +function createRefineIssue(path: ReadonlyArray, fail: RefineFailure) { + return issue({ + path, + code: fail.code ?? 'custom', + message: fail.message ?? '#?sium.errors.custom|Invalid value', + ...(fail.params === undefined ? {} : { params: fail.params }) + }); +} + +/** + * Creates a refinement step that validates the current value without changing it. + * + * Failed checks emit a single `Issue` built from `fail`, defaulting to + * `code='custom'` and `message='#?sium.errors.custom|Invalid value'`. + */ +export function refine( + check: (value: T, ctx: Ctx) => boolean | Promise, + fail: RefineFailure, + opts?: StepOptions +): Step { + const async = opts?.async ?? isAsyncFunction(check); + + return { + kind: 'refine', + async, + meta: {}, + decode: async (input, ctx) => { + try { + const result = await check(input, ctx); + if (result) { + return input; + } + + throw new SiumValidationError([createRefineIssue(ctx.path, fail)]); + } catch (error) { + if (error instanceof SiumValidationError) { + throw error; + } + + throw error; + } + }, + decodeSync: (input, ctx) => { + if (async) { + throw createAsyncStepError(); + } + + try { + const result = check(input, ctx); + if (isPromiseLike(result)) { + throw createAsyncStepError(); + } + + if (result) { + return input; + } + + throw new SiumValidationError([createRefineIssue(ctx.path, fail)]); + } catch (error) { + if (error instanceof SiumValidationError) { + throw error; + } + + throw error; + } + } + }; +} + +/** + * Creates a one-way transformation step. + * + * `transform` affects decode only. It intentionally does not expose `encode`, + * so `pipe(...).encode(...)` treats it as identity in reverse composition. + */ +export function transform( + fn: (value: I, ctx: Ctx) => O | Promise, + opts?: StepOptions +): Step { + const async = opts?.async ?? isAsyncFunction(fn); + + return { + kind: 'transform', + async, + meta: {}, + decode: async (input, ctx) => await fn(input, ctx), + decodeSync: (input, ctx) => { + if (async) { + throw createAsyncStepError(); + } + + const result = fn(input, ctx); + if (isPromiseLike(result)) { + throw createAsyncStepError(); + } + + return result; + } + }; +} + +/** + * Creates a bidirectional step with explicit decode and encode sides. + * + * Unlike `transform`, `codec` participates in reverse composition during + * `pipe(...).encode(...)` by applying the provided `encode` function. + */ +export function codec( + decode: (value: I, ctx: Ctx) => O | Promise, + encode: (value: O) => I, + opts?: StepOptions +): Step { + const async = opts?.async ?? isAsyncFunction(decode); + + return { + kind: 'codec', + async, + meta: {}, + decode: async (input, ctx) => await decode(input, ctx), + decodeSync: (input, ctx) => { + if (async) { + throw createAsyncStepError(); + } + + const result = decode(input, ctx); + if (isPromiseLike(result)) { + throw createAsyncStepError(); + } + + return result; + }, + encode + }; +} + +/** + * Creates a pure annotation step that leaves the value untouched. + * + * `meta` never changes runtime data and never participates in encode; it only + * contributes shallow-copied annotations to the final schema introspection. + */ +export function meta(annotations: MetaAnnotations): Step { + const copiedMeta = { ...annotations }; + + return { + kind: 'meta', + async: false, + meta: copiedMeta, + decode: async (input) => input, + decodeSync: (input) => input + }; +}