import type { EnumDef, EnumValue, SchemaBase } from './dsl.js'; import type { DtoMessage } from './dto.js'; import type { DomainEventSchema } from './domain-event.js'; import type { ExceptionSchema } from './exception.js'; import type { MethodSchema } from './method.js'; import type { ConvertMethodSchema } from './convert.js'; import type { DaoMethodSchema } from './dao.js'; import type { ServiceMethodSchema } from './service.js'; import type { ThirdServiceMethodSchema } from './third-service.js'; import type { UtilsMethodSchema } from './utils.js'; /** A method a flow step can invoke: contract references, or a pure descriptor * (MethodSchema) for the period before the contract file exists. * Third-party methods are their own contract (ThirdServiceMethodSchema) — * they can never bind a flow. */ export type FlowMethodRef = MethodSchema | ConvertMethodSchema | DaoMethodSchema | ServiceMethodSchema | ThirdServiceMethodSchema | UtilsMethodSchema; /** One named data slot of a flow — a register holding a whole object (like a * compiler's register file). The built-in args slot carries the flow input * (flow.args) and needs no registration: parameters exist in every flow. * Named slots carry derived objects and are declared by binding a method's * args/results message. The declared type is documentation — passing a slot * to a method with a different contract converts implicitly. Slots are scoped * to their flow: they cross into a sub-flow only through the shared input * (flow.args) and out of it only through the wrapping node's reads/writes * declarations. */ export interface FlowSlot { name: string; /** The contract message this slot was declared with (documentation only — * call sites convert implicitly). */ type?: unknown; description?: string; /** Field access (e.g. slots.args.amt): resolved at access time against the * slot's declared message fields, returning a SlotFieldRef for the * comparison builders. Unknown fields throw with the declared field list. */ [field: string]: unknown; } /** A field access on a slot (slots.args.amt): the slot plus the resolved * field object from the slot's declared message. */ export interface SlotFieldRef { slot: FlowSlot; /** The resolved field (DtoField | Field) — carries its schema for codegen. */ field: unknown; } /** A flow's slot registers: the built-in args slot plus named slots. */ export type FlowSlots = { args: FlowSlot; } & Record; /** Declare a flow's named slots — each bound to the message of a method's * args/results (the slot's type). The built-in args slot (the flow input) * is added automatically: parameters need no registration. To make its * fields accessible (slots.args.amt), declare its input message via the * `args` key — that declares the slot's type, not a new slot. */ export declare function defineSlots>(slots: T): { [K in keyof T]: FlowSlot & { name: K; }; } & { args: FlowSlot; }; /** A method call with slot bindings: the arg slots are passed to the method * (converted implicitly when the contract differs), the result is assigned * to the result slot. A plain method ref (or an invoke with no bindings) is * a call with args = [slots.args] and no result — the common case. */ export interface FlowCall { method: FlowMethodRef; /** Slots passed as the method's args — the input slot or derived slots. * A single-message contract takes the one slot; multiple slots are for * multi-param signatures (not modeled yet). */ args?: FlowSlot[]; /** Slot assigned from the method's results. */ result?: FlowSlot; } export declare function invoke(method: FlowMethodRef, options?: { args?: FlowSlot[]; result?: FlowSlot; }): FlowCall; /** A node method: a plain contract reference or a call with slot bindings. */ export type FlowNodeMethodRef = FlowMethodRef | FlowCall; export declare function isCall(m: FlowNodeMethodRef | GuardCondition): m is FlowCall; /** The underlying method of a (possibly bound) reference. */ export declare function methodOf(m: FlowNodeMethodRef): FlowMethodRef; /** Comparison operators of a structured condition: ordering, (in)equality * against a literal or enum value, and null checks. */ export type CompareOp = 'lt' | 'le' | 'gt' | 'ge' | 'eq' | 'ne' | 'isNull' | 'isNotNull'; /** A field comparison: a slot field (slots.args.amt) against a literal or an * enum value — the machine-readable form of a when text. isNull/isNotNull * take no value; the others require one. */ export interface Comparison { kind: 'comparison'; op: CompareOp; /** Field-level comparisons bind a slot field; slot-level null checks * (isNull/isNotNull on the slot itself) bind the slot directly. */ field: SlotFieldRef | FlowSlot; /** Compared-against value; null checks take none. */ value?: string | number | EnumValue; } /** The machine-readable condition behind a guard check or a branch edge * (optional): a utils predicate call (its boolean result decides), a field * comparison, or a composite (not/and/or over sub-conditions). */ export type GuardCondition = FlowCall | Comparison | ConditionGroup; /** A composite condition: not (exactly one sub-condition), and/or (two or * more). Renderers parenthesize sub-groups, so nesting stays unambiguous. */ export interface ConditionGroup { kind: 'not' | 'and' | 'or'; conds: GuardCondition[]; } export declare function isConditionGroup(c: unknown): c is ConditionGroup; /** Negation — the only way to invert a condition (e.g. !predicate). */ export declare function not(...conds: GuardCondition[]): ConditionGroup; /** Conjunction — every sub-condition must hold. */ export declare function and(...conds: GuardCondition[]): ConditionGroup; /** Disjunction — at least one sub-condition holds. */ export declare function or(...conds: GuardCondition[]): ConditionGroup; /** True when the condition operand is the slot itself (slot-level null check), * not a field access on it. Slot metadata (name) lives on the proxy target and * reads without field interception; a field access resolves to { slot, field }. */ export declare function isFlowSlot(v: unknown): v is FlowSlot; /** True when the slot carries a scalar Field (its declared type has a jsType) * rather than a message — scalar slots support full comparisons * (gt(slots.total, 100)); message slots only slot-level null checks. */ export declare function isScalarSlot(slot: FlowSlot): boolean; export declare function lt(field: unknown, value: string | number): Comparison; export declare function le(field: unknown, value: string | number): Comparison; export declare function gt(field: unknown, value: string | number): Comparison; export declare function ge(field: unknown, value: string | number): Comparison; export declare function eq(field: unknown, value: string | number | EnumValue): Comparison; export declare function ne(field: unknown, value: string | number | EnumValue): Comparison; export declare function isNull(field: unknown): Comparison; export declare function isNotNull(field: unknown): Comparison; /** An enum value referenced by symbol — the compared-against target of eq/ne * (e.g. eq(slots.args.state, enumValue(PayState, 'success'))). */ export declare function enumValue(def: EnumDef, symbol: string): EnumValue; /** Event publication: a flow node may publish one domain event. The outbox * write joins the node's surrounding transaction — the event is durable with * the node's other side effects. The payload slot defaults to the flow input * (slots.args) when omitted. */ export interface FlowPublish { event: DomainEventSchema; /** The slot carrying the event payload. */ payload?: FlowSlot; } export interface FlowNode extends SchemaBase { /** Optional sub-flow. When present, entering this node runs the sub-flow; * after the sub-flow reaches any terminal node, the outer flow continues * via this node's outgoing edges. Sub-flows nest recursively. A plain * sub-flow may not declare exception ends — it is structure only; use a * tryNode for a protected region. */ flow?: FlowSchema; /** Ordered execution sequence (basic block): methods run in order; if one * throws, the remaining methods are skipped. A plain reference is a call * receiving the input slot (slots.args) — the common case. */ methods?: FlowNodeMethodRef[]; /** A domain event published by this node (same transaction as its methods). */ publish?: FlowPublish; /** Slots this node reads to decide its outgoing branches — a semantic * declaration, not enforced per branch. */ reads?: FlowSlot[]; /** Slots this node assigns without a method call (constructions). */ writes?: FlowSlot[]; } /** One gate check: when the condition holds, either return (early return to * the flow's returnEnd) or throw the given exception (routed implicitly to * the flow's exception end carrying the same name). Exactly one of * return/exception must be set. */ export interface GuardCheck { /** Condition description shown on the branch edge. */ when: string; /** The check returns early (to the flow's returnEnd) — no edge needed. */ return?: boolean; /** The check throws this exception — routed implicitly to the flow's * exception end carrying the same name (no edge needed). */ exception?: ExceptionSchema; /** Slots the check reads to decide. */ reads?: FlowSlot[]; /** Machine-readable condition behind `when` (optional): a utils predicate * call or a field comparison. Omitted when the condition stays descriptive. */ check?: GuardCondition; } /** Gate executor: an ordered set of checks. The first check whose condition * holds routes out implicitly (return to the flow's return end, throw to the * matching exception end); when none holds, the guard falls through its * normal outgoing edges (linked with edge like any node). A guard is the * graph form of a run of `if (bad) throw / return` guards. */ export interface GuardNode extends SchemaBase { type: 'guard'; /** Ordered decision sequence: methods run in order; if one throws, the * remaining methods are skipped. */ methods?: FlowNodeMethodRef[]; /** Ordered checks — first hit wins. */ checks: GuardCheck[]; } /** One catch route of a TryNode: body exception type → handler sub-flow. * First match wins, UnexpectedException is the catch-all and must come * last. The handler's exception ends rethrow out of the TryNode. */ export interface TryCatch { /** Exception this catch handles — must match a body exception end. */ exception: ExceptionSchema; /** Handler sub-flow: its return end falls through, its exception ends * rethrow (declared as the TryNode's own throws). */ handler: FlowSchema; description?: string; } /** Protected region: body, catch handlers, and optional finally are all * sub-flows. Entering the TryNode runs the body; the body's exception ends * are matched against the catches, every catch handler and the body's normal * completion pass through finally (when present), then continue via the * TryNode's outgoing edges. Handler exception ends rethrow out of the region. */ export interface TryNode extends SchemaBase { type: 'try'; /** The protected region. Its exception ends are the region's throw sites. */ body: FlowSchema; /** Ordered catch routes; UnexpectedException (catch-all) must be last. */ catches: TryCatch[]; /** Cleanup sub-flow: may not declare exception ends (Java finally rules). */ finally?: FlowSchema; } /** One branch of an ifNode: when the check holds, control moves to `to`. */ export interface IfCase { /** Branch label (rendered on the branch line). */ when: string; /** Machine-readable condition — required (the ifNode's whole purpose). */ check: GuardCondition; /** Jump target: a step (node/guard/tryNode/ifNode). Flow exits — return * and throw — belong to guard checks, so ends are rejected. */ to: FlowNodeOrEnd; } /** Decision node — the jump twin of a guard: ordered cases evaluated * first-hit, the first check that holds routes to its target; when none * holds, control goes to `else`. All exits are internal (cases and else), * so the node has no outgoing edges. A guard decides exits (return/throw), * an ifNode decides where control goes next (steps only). */ export interface IfNode extends SchemaBase { type: 'if'; /** Ordered branches — first hit wins. */ cases: IfCase[]; /** Default branch: a step (never a flow exit). */ else: FlowNodeOrEnd; } /** Decision node: ordered cases (check → target) plus a default branch. */ export declare function ifNode(name: string, options: { cases: IfCase[]; else: FlowNodeOrEnd; description?: string; }): IfNode; /** Any node an edge can start from. */ export type FlowStep = FlowNode | GuardNode | TryNode | IfNode; /** Any node an edge can point at: a step or a flow exit. */ export type FlowNodeOrEnd = FlowStep | FlowEnd; /** A flow exit: normal return or uncaught exception. Never an edge start. */ export interface FlowEnd extends SchemaBase { type: 'return' | 'exception'; /** Uncaught exception this exit throws — set when type='exception'. */ exception?: ExceptionSchema; } /** The flow's exception end carrying the given exception name — the implicit * target of a guard check throwing it. Undefined when the flow has no such * end (guard checks synthesize one at definition time, so this only happens * for hand-assembled flows). */ export declare function findExceptionEnd(schema: FlowSchema, exceptionName: string): FlowEnd | undefined; /** Where a throw of `exceptionName` from step `n` lands: the target of a typed * throws edge (first match), or an untyped exception edge as catch-all. * Undefined when no route exists (validation error). Guards route implicitly * — see findExceptionEnd. * @deprecated no internal use remains; kept for API compatibility. */ export declare function resolveThrowTarget(schema: FlowSchema, n: FlowStep, exceptionName: string): FlowNodeOrEnd | undefined; export interface FlowEdge extends SchemaBase { /** Trigger condition; undefined = default path (success/normal). */ when?: string; start: FlowStep; end: FlowNodeOrEnd; /** Exception path (rendered dashed); without `throws` it is a catch-all. */ exception?: boolean; /** Typed exception propagation: this edge carries the named exception * (rendered dashed with a `throw X` label). Must target an exception end. * Carrying one of a guard's own check exceptions is rejected — guards * route those implicitly. */ throws?: ExceptionSchema; /** Machine-readable condition behind `when` (optional): a utils predicate * call or a field comparison. */ check?: GuardCondition; } export interface FlowSchema extends SchemaBase { /** Entry node. */ start: FlowStep; /** Default exit — every flow has one; edge(node, flow.returnEnd) is a return. */ returnEnd: FlowEnd; /** All nodes, collected from edges (deduplicated by object identity). */ nodes: FlowNodeOrEnd[]; /** Independent edges; a node may be start of many edges, so cycles are expressible. */ edges: FlowEdge[]; /** Input contract — mirrors a service method's args. */ args?: DtoMessage; /** Output contract — mirrors a service method's results. */ results?: DtoMessage; /** Declared slot registers of this flow (named slots + the built-in args * slot). Sub-flows analyze their own slots; sharing happens through the * same input object (args) or the wrapping node's reads/writes. */ slots?: FlowSlots; /** Slots the enclosing flow has already produced before this sub-flow's * entry (input inheritance). Only meaningful for sub-flows (try bodies, * catch handlers, named sub-flows) — a top-level flow has none. */ entrySlots?: FlowSlot[]; } /** Executor node: an ordered sequence of method invocations (and optionally * one event publication). */ export declare function node(name: string, options?: { flow?: FlowSchema; description?: string; methods?: FlowNodeMethodRef[]; publish?: FlowPublish; reads?: FlowSlot[]; writes?: FlowSlot[]; }): FlowNode; /** Guard node: a gate executor — ordered checks, first hit routes out; * all misses fall through the guard's normal outgoing edges. */ export declare function guard(name: string, options: { checks: GuardCheck[]; methods?: FlowNodeMethodRef[]; description?: string; }): GuardNode; /** Try node: a protected region whose body, catch handlers, and optional * finally are sub-flows. */ export declare function tryNode(name: string, options: { body: FlowSchema; catches: TryCatch[]; finally?: FlowSchema; description?: string; }): TryNode; export declare function defineExceptionEnd(exception: ExceptionSchema, description?: string): FlowEnd; export declare function edge(start: FlowStep, end: FlowNodeOrEnd, options?: { when?: string; description?: string; exception?: boolean; throws?: ExceptionSchema; check?: GuardCondition; }): FlowEdge; export declare function defineFlow(name: string, schema: { start: FlowStep; edges: (flow: FlowSchema) => FlowEdge[]; args?: DtoMessage; results?: DtoMessage; slots?: FlowSlots; entrySlots?: FlowSlot[]; description?: string; }): FlowSchema; /** Exception names declared by a flow's exception ends — the flow's escape * set (throws that reach the caller unless caught by an outer tryNode). */ export declare function exceptionEndNames(flow: FlowSchema): Set; export declare function isEnd(n: FlowNodeOrEnd): n is FlowEnd; export declare function isFlowNode(n: FlowNodeOrEnd): n is FlowNode; export declare function isGuard(n: FlowNodeOrEnd): n is GuardNode; export declare function isTryNode(n: FlowNodeOrEnd): n is TryNode; export declare function isIfNode(n: FlowNodeOrEnd): n is IfNode;