/** * Atomic sequence primitive — online-coordinated gap-free numbering. * * `vault.sequence('invoice-2026').next()` returns 1, 2, 3, … with no * gaps and no duplicates, even under concurrent callers. Each named * sequence is an independent counter record at `_sequences/`, * incremented with an optimistic compare-and-swap retry loop — the same * proven pattern as the ledger head (`history/ledger/store.ts`). * * **Explicitly online-only.** Gap-free numbering requires single-authority * serialization, which an offline / non-CAS store cannot provide. `next()` * throws {@link SequenceOfflineError} unless the backing store advertises * `capabilities.casAtomic`. This is a deliberate, honest wall — an offline * writer cannot safely allocate a global sequence number. * * Note on "gap-free": the *sequence* is gap-free (each `next()` yields a * unique, +1 value). If a caller discards a value without using it, that * is a gap in *usage*, not in the sequence — assign each `next()` result * to its record in the same operation. */ import type { NoydbStore } from '../../kernel/types.js'; import { type EnclaveKey } from '../../kernel/enclave/index.js'; export { withSequence } from './active.js'; export { NO_SEQUENCE, type SequenceStrategy } from './strategy.js'; export { SequenceNotEnabledError } from '../../kernel/errors.js'; export declare const SEQUENCE_COLLECTION = "_sequences"; /** Options for `SequenceHandle.next`. Deferred-numbering series use `for`; the CAS counter ignores all of these. */ export interface NextOptions { /** Deferred mode: the record id to number. Ignored by the CAS counter. */ readonly for?: string; /** Deferred mode: reject after this many ms if still unsealed (reserved; not yet enforced in this slice). */ readonly timeoutMs?: number; } /** * Partitioning for a CAS sequence. A partitioned sequence is an * independent counter scoped to one tuple of values — e.g. * `sequence('invoice', { partition: [2026, 'EU'] })` numbers EU-2026 invoices * separately from `[2026, 'US']` and from the bare `invoice` series. * * Partition components are URI-encoded (so `/`, null bytes and other * separators in a value can never collide with the structural separators) and * `'/'`-joined, then appended to the series with a null-byte (`\x00`) * separator. The null byte is illegal in a plain series name, which guarantees * a partitioned key is always disjoint from any unpartitioned series. */ export interface SequenceOptions { /** Partition tuple. Each component is URI-encoded and `'/'`-joined. */ readonly partition?: readonly (string | number)[]; /** * Render template for the serial string. When set, `vault.sequence` * returns a {@link FormattedSequenceHandle} whose `next()` resolves to * `{ serial, formatted }`. Tokens: * - `{seq}` — the allocated integer * - `{seq:0N}` — zero-padded to width N (e.g. `{seq:04}` → `0001`) * - `{partition.i}` — the i-th `partition` component (original value) * * Any other token, or a `{partition.i}` index beyond the supplied * `partition`, throws `ValidationError` at `vault.sequence()` construction. * Per-partition reset is inherent: a new partition tuple starts at 1. */ readonly format?: string; } /** * A formatted sequence handle. Identical to {@link SequenceHandle} * except `next()` also returns the rendered `formatted` string. `peek()` / * `seedTo()` operate on the underlying integer counter, unchanged. */ export interface FormattedSequenceHandle { /** Allocate the next value and return it with its rendered serial string. */ next(opts?: NextOptions): Promise<{ serial: number; formatted: string; }>; /** Read the current integer value without allocating. Returns 0 if never used. */ peek(): Promise; /** Set-if-greater on the underlying integer counter. See {@link SequenceHandle.seedTo}. */ seedTo(n: number): Promise; } /** * Validate a sequence `format` against the supplied partition and return a * pure `(serial) => string` renderer. Eager validation: every token is * checked now, so a bad pattern throws `ValidationError` at construction — * never at `next()` time. */ export declare function compileSequenceFormat(format: string, series: string, partition: readonly (string | number)[] | undefined): (serial: number) => string; /** * Resolve the CAS storage key for a (series, partition) pair. * * With no partition the key is `series` verbatim. With a partition the key is * `${series}\x00${parts}` where `parts` is each component passed through * `encodeURIComponent(String(part))` and `'/'`-joined. The null-byte separator * is illegal in a plain series name, so partitioned keys never collide with * unpartitioned ones; URI-encoding keeps any component containing `/` distinct * from a multi-component partition. * * @throws {ValidationError} if any partition component is empty after `String()` * or is a non-finite number (`NaN`, `±Infinity`). */ export declare function resolveSequenceKey(series: string, opts?: SequenceOptions): string; export interface SequenceHandle { /** Atomically allocate and return the next value (1, 2, 3, …). Deferred series resolve at the next pass. */ next(opts?: NextOptions): Promise; /** Read the current value without allocating. Returns 0 if never used. */ peek(): Promise; /** * Set-if-greater: advance the counter to at least `n`. A no-op if the * current value is already `>= n` (so it never rewinds), and `seedTo(0)` is * a no-op. Idempotent and CAS-safe under concurrent `next()` / `seedTo()`. * * Use after a bundle / CSV import to fast-forward the counter past the * highest imported serial, so subsequent `next()` calls cannot re-use a * number that is already on a record. * * Online-only: throws {@link SequenceOfflineError} on a non-CAS store. */ seedTo(n: number): Promise; } /** Per-call context the vault assembles to build a {@link SequenceStore}. */ export interface SequenceStoreOptions { adapter: NoydbStore; vault: string; encrypted: boolean; getDEK: (collectionName: string) => Promise; actor: string; } export declare class SequenceStore { private readonly adapter; private readonly vault; private readonly encrypted; private readonly getDEK; private readonly actor; /** * Memoized DEK promise. The `_sequences` collection DEK is created on * first access; without sharing one promise, a burst of concurrent * `next()` calls would each trigger DEK creation and diverge (one * writer's ciphertext unreadable by another). One shared promise → one * DEK. */ private dekPromise; constructor(opts: SequenceStoreOptions); /** A handle bound to one sequence name. */ handle(name: string): SequenceHandle; private assertOnline; private dek; private read; private encryptState; peek(name: string): Promise; next(name: string): Promise; seedTo(name: string, n: number): Promise; }