import type { Store } from "@rotorsoft/act/types"; /** * Optional features a {@link Store} implementation may or may not * support. Default to `false` — only enable a flag when the adapter * exposes the corresponding surface and you want the TCK to cover it. */ export type StoreCapabilities = { /** * Adapter implements {@link Store.notify}. When `true`, the TCK runs * the cross-instance conformance cases: a listener receives commits * from a *sibling* instance produced by the same `factory`, never its * own commits (the port's self-filtering MUST), and exactly one * notification per commit transaction carrying the full event batch. * Requires the `factory` to produce instances sharing one backing * store — e.g. two PostgresStores on the same schema/table, or two * `withBroker` decorators on one broker. True cross-*process* * LISTEN/NOTIFY plumbing needs two processes and stays in the * adapter's own suite. */ readonly notify?: boolean; /** * Adapter implements {@link Store.restore}. When `true`, the TCK * runs the full restore suite — empty-source / single-stream / * multi-stream happy paths, ISO-string `created`, pre-existing * wipe, subscription clearing, causation remap, and atomic * rollback on mid-iteration throw. */ readonly restore?: boolean; /** * Adapter honours `subscribe`'s optional `correlator` argument and answers * `correlating` (#1532). When `true`, the TCK runs the correlation-lease * suite — exclusive acquisition, renewal by the same holder, re-acquisition * after expiry, per-key independence, and the keyed checkpoint. * * Optional because a store that ignores the argument simply lets every * worker scan, which is the pre-#1532 behaviour and remains correct: the * marks are idempotent, so the duplication is waste rather than error. */ readonly lease_correlation?: boolean; /** * Adapter supports sensitive-data isolation (#566): accepts the * optional `pii` field on commit messages, returns it on load * outputs, and implements {@link Store.forget_pii}. When `true`, * the TCK runs the PII isolation suite — commit-with-pii * round-trip, commit-without-pii passthrough, `forget_pii` happy * path, idempotency, and isolation across streams. */ readonly pii_isolation?: boolean; /** * Adapter supports competing consumers — two workers may call * `claim()` concurrently and the store hands each stream to at most * one of them (PostgreSQL via `FOR UPDATE SKIP LOCKED`; the in-memory * store via single-threaded atomic claim). When `true`, the TCK runs * the concurrency suite. Single-writer embedded stores (e.g. SQLite, * where concurrent write transactions raise `SQLITE_BUSY` rather than * serializing) leave this `false`: their deployment model is a single * drain worker per database file. */ readonly concurrent_claim?: boolean; /** * Adapter implements the {@link QueryStreams.source_matches} reverse- * match filter — "subscriptions whose stored `source` pattern matches * at least one of these names" (`name ~ source`). When `true`, the TCK * runs the `source_matches` suite. Stores that can't express reverse- * regex (e.g. an anchor-aware `LIKE` approximation) leave it `false`; * the close-cycle safety probe then falls back to an unfiltered scan, * so correctness never depends on this flag — only probe cost. */ readonly source_matches?: boolean; /** * Adapter matches a **pattern** reaction `source` (one carrying regex * metacharacters, e.g. the calculator's `^(A|B)$`) as a full RegExp in * claim()'s has-work probe, so a stream whose max event id exceeds the * subscription watermark is claimed when its name matches the pattern. * When `true`, the TCK runs the pattern-source claim suite. Literal * sources stay exact everywhere regardless of this flag — the fast, * index-friendly path. * * Stores that cannot run an arbitrary regex against candidate streams * (e.g. SQLite, whose libsql build has no `REGEXP` and whose portable * `LIKE` grammar cannot express alternation/grouping) leave this * `false` and instead reject a non-portable claim source at * {@link subscribe} time — see {@link rejects_nonportable_claim_source}. */ readonly pattern_claim_source?: boolean; /** * Adapter cannot faithfully match every regex claim `source`, so it * fails loud at registration: {@link subscribe} throws * {@link ValidationError} for a source outside its portable subset * (alternation/grouping like `^(A|B)$`), rather than silently never * claiming the stream. When `true`, the TCK runs the reject-at-subscribe * case. Currently only SQLite sets this — its message points operators * to InMemory/PG for full regex claim sources. */ readonly rejects_nonportable_claim_source?: boolean; /** * Adapter honors the {@link SubscribeInput.correlated_at} **work mark** * (#1485): `subscribe` applies it as `GREATEST(correlated_at, N)` and * `claim` serves a marked stream from the subscription row alone * (`at < correlated_at`) instead of probing the event log. When `true`, * the TCK runs the work-set suite — mark-then-claim, monotonicity * across positive/zero/negative values, `ack` retiring a stream from * the claimable set and a later mark re-adding it, and the operator * surfaces (`reset`, `unblock`, `defer`, `prioritize`) leaving the mark * intact. * * @deprecated Ignored since #1488 — the mark is the only eligibility * rule, so this suite always runs. The field is kept so an adapter that * still passes `work_set: true` keeps compiling. */ readonly work_set?: boolean; }; /** * Options for {@link runStoreTck}. */ export type StoreTckOptions = { /** * Display name for the implementation under test. */ readonly name: string; /** * Returns the {@link Store} instance under test. Called once during * `beforeAll`. The TCK does not assume a fresh store per test — each * test namespaces its streams via {@link uid} so they don't collide. * The TCK calls `store.seed()` once before any test runs and * `store.dispose()` after all tests, with `store.drop()` in between * if any test needs it. */ readonly factory: () => Store | Promise; /** * Constructs the adapter the way a first-time user does: with its own * documented defaults and nothing else — `() => new MyStore()`. * * Supplying it opts into the default-configuration suite, which * allows exactly two outcomes and outlaws the third: * * 1. **Construction throws.** The adapter has no safe default and * says so — an operator sees the error immediately. * 2. **Construction succeeds and the store round-trips**, including a * second commit that advances the version. * 3. ~~Construction succeeds, `commit` reports success, and the data * is not there.~~ This is the shape the suite exists to catch * (#1443: a zero-config SQLite store defaulted to a per-connection * private in-memory database, so `seed`'s DDL landed where later * statements could not see it — every write was accepted and lost). * * The suite never calls `drop()`, so it is safe to point at a default * that resolves to a real shared database; it namespaces its stream * with {@link uid} like every other case. */ readonly default_factory?: () => Store | Promise; /** * Optional capabilities flags — see {@link StoreCapabilities}. */ readonly capabilities?: StoreCapabilities; }; /** * Runs the Store contract test compatibility kit against the * implementation produced by `options.factory`. * * The TCK is the executable definition of the {@link Store} contract. * Every method on the interface in `libs/act/src/types/ports.ts` has * matching cases here: * * - `commit` — single + multi-event commits, optimistic concurrency * - `query` — stream, names, correlation, before, after, created_after, * created_before, limit, with_snaps, stream_exact, backward traversal * - stream filter grammar — the portable regex subset (`^`, `$`, `.`, * `.*`, literal characters) matches identically everywhere; richer * patterns either match with full regex semantics or throw * `ValidationError` — never a silent approximation * - `subscribe` — idempotent re-subscribe, watermark return value * - `claim` / `ack` — lease lifecycle, dual frontiers, leased streams * not double-claimed, exact-source has-work matching, timed-out-lease * retry accounting * - `block` — blocked streams hidden from claim, only same-drainer can block * - `reset` — restart watermarks (including blocked), no-op for missing * - `prioritize` — bulk priority updates by filter * - `truncate` — snapshot vs tombstone seeding, empty inputs, missing * streams; windowed boundaries (`before`/`max_id`) — prefix deleted * behind the closest safe snapshot, tail + subscriptions kept, * no-snapshot no-op, mixed full + windowed targets * - `query_streams` — filters, exact-match, pagination, blocked * - `notify` (capability-gated) — cross-instance delivery, self-filtering * (an instance never receives its own commits), one notification per * commit transaction with the full event batch * * Tests namespace their streams with a per-test {@link uid} so the * suite is parallel-safe against a shared backing store (e.g., a real * Postgres instance running tests for the whole monorepo concurrently). * * @example * ```ts * import { runStoreTck } from "@rotorsoft/act-tck"; * import { InMemoryStore } from "@rotorsoft/act"; * * runStoreTck({ * name: "InMemoryStore", * factory: () => new InMemoryStore(), * }); * ``` */ export declare const runStoreTck: (options: StoreTckOptions) => void; //# sourceMappingURL=store-tck.d.ts.map