import type { BitPackerSchema, PackingContext, PackingStrategy, } from "@suaveplan/bitpacker"; type ConditionalOp = "==" | "!=" | "<" | "<=" | ">" | ">=" | "in" | "has"; interface IfShape { field: string; op: ConditionalOp; value: unknown; } interface ConditionalSchemaShape { _if?: IfShape; _when?: IfShape; _then: BitPackerSchema; _else?: BitPackerSchema; } const NO_BRANCH = Symbol("NO_BRANCH"); const VALID_OPS: readonly ConditionalOp[] = [ "==", "!=", "<", "<=", ">", ">=", "in", "has", ]; function getPredicate(obj: { _if?: unknown; _when?: unknown; }): IfShape | undefined { const raw = obj._if ?? obj._when; if (!raw || typeof raw !== "object") return undefined; const predicate = raw as { field?: unknown; op?: unknown; value?: unknown; }; if (typeof predicate.field !== "string" || predicate.field.length === 0) { return undefined; } if ( typeof predicate.op !== "string" || !(VALID_OPS as readonly string[]).includes(predicate.op) ) { return undefined; } if (!("value" in predicate)) return undefined; return predicate as IfShape; } export function isConditionalSchema( schema: unknown ): schema is ConditionalSchemaShape { if ( typeof schema !== "object" || schema === null || Array.isArray(schema) ) { return false; } const obj = schema as { _if?: unknown; _when?: unknown; _then?: unknown; }; const predicate = getPredicate(obj); if (predicate === undefined) return false; return obj._then !== undefined; } export function evaluatePredicate( ctx: unknown, field: string, op: ConditionalOp, value: unknown ): boolean { const fieldValue = typeof ctx === "object" && ctx !== null ? (ctx as Record)[field] : undefined; switch (op) { case "==": return fieldValue === value; case "!=": return fieldValue !== value; case "<": return (fieldValue as number) < (value as number); case "<=": return (fieldValue as number) <= (value as number); case ">": return (fieldValue as number) > (value as number); case ">=": return (fieldValue as number) >= (value as number); case "in": return Array.isArray(value) ? value.includes(fieldValue) : false; case "has": return Array.isArray(fieldValue) ? (fieldValue as unknown[]).includes(value) : false; } } export class ConditionalPackingStrategy implements PackingStrategy { canHandle(schema: BitPackerSchema): boolean { return isConditionalSchema(schema); } pack( data: unknown, schema: BitPackerSchema, context: PackingContext ): void { const cfg = schema as unknown as ConditionalSchemaShape; const branch = this.resolveBranch(cfg, context, data); if (branch === NO_BRANCH) return; context.packValue(data, branch as BitPackerSchema); } unpack(schema: BitPackerSchema, context: PackingContext): unknown { const cfg = schema as unknown as ConditionalSchemaShape; const branch = this.resolveBranch(cfg, context, context.parentData); if (branch === NO_BRANCH) return undefined; return context.unpackValue(branch as BitPackerSchema); } private resolveBranch( cfg: ConditionalSchemaShape, context: PackingContext, data: unknown ): BitPackerSchema | typeof NO_BRANCH { const predicate = cfg._if ?? cfg._when; if (predicate === undefined) return NO_BRANCH; const scope = context.parentData ?? data; const matched = evaluatePredicate( scope, predicate.field, predicate.op, predicate.value ); if (matched) return cfg._then; if ("_else" in cfg) return cfg._else as BitPackerSchema; return NO_BRANCH; } }