/** * @module autoclose-policy * @category Internal * * Declarative close-policy options consumed by `.autocloses({...})` * (#838 / epic #802). Three optional fields cover the three * operational pressure points every real close policy traces back to: * * - `after` — time / compliance ("autocloses **after** N days") * - `is` — domain lifecycle ("autocloses ... **is** Resolved") * - `reaches` — resource ("autocloses ... **reaches** 10k events") * * Top-level fields combine with **AND** semantics. This captures the * common cooldown-after-terminal pattern that runs through almost * every business app — *"close 90 days after `Resolved`"*, *"close 14 * days after `Delivered`"*, *"close 30 days after a GDPR deletion * request"*. All conditions must hold for the cycle to truncate. * * A separate `or: {...}` block opens an alternative path: when * present, the policy fires if **either** the top-level AND group * matches **or** any field inside `or` matches. Use it for safety * nets — *"close (Resolved AND aged 90 days) OR if event count * reaches 10k"*. The two-axis split mirrors the two ways close * policies appear in practice: primary close logic (AND-shaped) and * defensive backstops (OR-shaped). * * The state builder's `.autocloses(...)` overload distinguishes * function (predicate) from object (policy) and routes the latter * through {@link compile_autoclose_policy}, which validates via * {@link AutoclosePolicySchema} and returns the compiled predicate. * Operators with custom needs (per-stream metadata, multi-branch * AND/OR like "(`Resolved` + 90d) OR (`Cancelled` + 30d)") keep the * function form; the declarative form covers the 90% case. * * Validation runs at the builder call (`act().build()` time), so * misconfiguration — empty bag, sub-1 `reaches`, sub-minute `after`, * empty `is`, empty `or`, nested `or` inside `or`, unknown keys — * throws at build, not on the first cycle tick. * * @internal */ import { z } from "zod"; import type { AutoclosePredicate, Schemas } from "../types/action.js"; /** The `Date` that lies `days` after `date`. @internal */ export declare function days_after(date: Date, days: number): Date; /** The `Date` that lies `days` in the past. @internal */ export declare function days_before_now(days: number): Date; /** * Zod schema for the declarative {@link AutoclosePolicy} bag. * Internal `const` per the config-validation-schema standard (CLAUDE.md * "Config-validation schemas") — the public surface is the inferred * {@link AutoclosePolicy} type and the `.autocloses({...})` overload, * never this schema. `.strict()` rejects unknown keys so typos surface * at build instead of being silently ignored. * * @internal */ declare const AutoclosePolicySchema: z.ZodObject<{ after: z.ZodOptional>; is: z.ZodOptional>]>>; reaches: z.ZodOptional; or: z.ZodOptional>; is: z.ZodOptional>]>>; reaches: z.ZodOptional; }, z.core.$strict>>; keep: z.ZodOptional>; }, z.core.$strict>; /** * Declarative close-policy options consumed by `.autocloses({...})`. * Top-level fields are AND-combined; the optional `or` block opens an * alternative OR-path. Omitted fields contribute nothing — they do * not mean "match everything." * * @property after - Close when `head.created` is at least the resolved * window in the past. Days are the close surface's only unit; * fractional `days` cover sub-day cooldowns (`{ days: 1/24 }` is one * hour) without introducing another denomination. * @property is - Close when `head.name` matches. String for the * single-terminal-event case (the most common); `readonly string[]` * for multi-terminal states (`Order: Shipped | Delivered | * Cancelled`). * @property reaches - Close when the stream's event count is `>= N` * (inclusive — fires the moment the threshold is reached). * @property or - Alternative OR-path. When present, the policy fires * if EITHER the top-level AND group matches OR any field inside * `or` matches. Used for safety-net backstops layered onto a * primary cooldown policy (e.g. *"(Resolved AND 90 days) OR reaches * 10k"*). Nested `or` inside `or` rejects at build time. * @property keep - Rolling-window retention (#1011), **independent** of * the terminate fields: while the stream stays open, prune events * older than `now − keep` behind the closest safe snapshot via a * windowed close. Does not participate in the AND group or the `or` * block — a policy may terminate, prune, or both. Requires * `.snap(...)` earlier in the builder chain (type-gated) and a * window of at least one day — the close cycle is low-cadence * housekeeping, never sub-day realtime. */ export type AutoclosePolicy = z.infer; /** * Compile a declarative {@link AutoclosePolicy} into an * {@link AutoclosePredicate}. The state-builder's * `.autocloses({...})` overload calls this; tests can also build a * state and read `state.autoclose` to grab the compiled predicate. * * Returned predicate fires when either: * * 1. **All** top-level non-`or` fields match (AND), or * 2. **Any** field inside the `or` block matches. * * Top-level with zero non-`or` fields never satisfies path (1) — the * `every` check on an empty list is short-circuited to `false` so the * policy doesn't truncate the entire universe on an `or`-only * declaration. (Validation rejects all-empty bags up front; this guard * is the in-cycle equivalent for the synthesized empty AND-group.) * * Assignable to any `AutoclosePredicate` slot via * function-parameter contravariance — the returned predicate inspects * `head.name` as a plain string, so narrower event unions stay * assignable. * * Throws `ZodError` at call time when the options are invalid (empty * bag, non-positive `reaches`, sub-minute `after`, empty `is`, empty * `or`, nested `or`, unknown keys). * * @internal */ /** * The smallest `after` window (in days) anywhere in a policy — across * the top-level `after` and the `or.after` block — or `undefined` when * the policy has no time component. * * The synthesized autoclose reaction (#1090) uses this to decide how to * wait: a policy with an `after` defers its re-check to `head.created` * plus this many days (the earliest its time gate could open); a policy * without one (`is` / `reaches` only) has no time gate, so the reaction * just waits for the next event to re-trigger rather than parking on a * due-time. Conservative — the min across branches never defers past * the soonest a branch could fire. * * @internal */ export declare function policy_min_after_days(options: AutoclosePolicy): number | undefined; /** * The rolling-window width (in days) of a policy's `keep` field, or * `undefined` when the policy declares no rolling window. The * synthesized autoclose reaction prunes the prefix older than the * window (via a windowed close) and derives its prune due-time as * `tail.created` plus this many days — the earliest the oldest * surviving domain event can age out of the window. * * @internal */ export declare function policy_keep_days(options: AutoclosePolicy): number | undefined; export declare function compile_autoclose_policy(options: AutoclosePolicy): AutoclosePredicate; export {}; //# sourceMappingURL=autoclose-policy.d.ts.map