feat(8.0): temporal range verbs — diff, history, since(gen|Date), asOf{exclusive}, transactionLog window
asOf() answers "state AT a point"; these answer "what happened BETWEEN two
points" and "one entity's whole history", all on the existing generation records.
- Extract resolveAsOfGeneration() as the shared gen|Date→{generation,timestamp}
chokepoint (reachability + Date semantics identical everywhere). asOf()'s
inclusive path is byte-identical — db-mvcc still 25/25.
- asOf(target, { exclusive }) — strict-before (resolved−1, clamped at gen 0,
never a RangeError).
- db.since(Db | generation | Date) — overload; number/Date resolve via the shared
resolver (new DbHost.resolveGeneration); EXCLUSIVE lower bound;
since(db) === since(db.generation). Same-store guard via Db.belongsToStore.
- brain.diff(a, b) → { added, removed, modified } split by nouns/verbs. EARNS its
name: candidate set is ONLY changedBetween(gLow,gHigh), each classified by
existence at both endpoints + a key-order-insensitive value compare
(new src/db/stableEqual.ts) — a touched-but-reverted / born-and-died id is in
no bucket.
- brain.history(id, { from, to }) → every distinct version oldest→newest, each
value === asOf(version.generation).get(id); null = removal; kind auto-detected
(throws on UUID-space collision). New store helper generationsTouching().
- brain.transactionLog({ from, to, limit }) — INCLUSIVE generation/Date window
(contrast since's exclusive lower bound); limit applied last.
- Compaction policy locked: diff/since THROW GenerationCompactedError below the
horizon; history TRUNCATES to it.
Types DiffResult/HistoryVersion/EntityHistory exported from the package root.
Tests: tests/integration/db-temporal.test.ts (8 proofs incl. diff-earns-its-name,
history↔asOf cross-check, the composition proof, granularity, compaction contrast)
+ tests/unit/db/stableEqual.test.ts (6). docs/guides/snapshots-and-time-travel.md
+ RELEASES updated. 1477 unit + db-temporal 8 + db-mvcc 25 green.
This commit is contained in:
parent
373a48122d
commit
2c84f86815
10 changed files with 1069 additions and 37 deletions
|
|
@ -25,7 +25,7 @@
|
|||
* `docs/ADR-001-generational-mvcc.md` for the full justification.
|
||||
*/
|
||||
|
||||
import type { AddParams, UpdateParams, RelateParams } from '../types/brainy.types.js'
|
||||
import type { AddParams, UpdateParams, RelateParams, Entity, Relation } from '../types/brainy.types.js'
|
||||
|
||||
// ============================================================================
|
||||
// Transaction operations (brain.transact input)
|
||||
|
|
@ -193,6 +193,58 @@ export interface ChangedIds {
|
|||
toGeneration: number
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The result of {@link Brainy.diff}: ids that came into existence,
|
||||
* went away, or changed value between two states, each split by kind. Unlike the
|
||||
* raw touched-id set of {@link ChangedIds}, `diff` EARNS its name — it resolves
|
||||
* each touched id at BOTH endpoints and classifies by existence (added/removed)
|
||||
* and stable value comparison (modified). An id touched within the interval but
|
||||
* whose endpoint states are identical (touched-then-reverted) appears in NONE of
|
||||
* the three buckets. Orientation is `a → b`: `added` exists at `b` not `a`.
|
||||
*/
|
||||
export interface DiffResult {
|
||||
/** Ids absent at `a`, present at `b`. */
|
||||
added: { nouns: string[]; verbs: string[] }
|
||||
/** Ids present at `a`, absent at `b`. */
|
||||
removed: { nouns: string[]; verbs: string[] }
|
||||
/** Ids present at both endpoints with a changed stored value. */
|
||||
modified: { nouns: string[]; verbs: string[] }
|
||||
/** The resolved generation of endpoint `a`. */
|
||||
fromGeneration: number
|
||||
/** The resolved generation of endpoint `b`. */
|
||||
toGeneration: number
|
||||
}
|
||||
|
||||
/**
|
||||
* @description One version of a single entity or relationship in its
|
||||
* {@link EntityHistory} — the materialized state at a committed generation that
|
||||
* changed it. `value` is `null` when the id did not exist at that version
|
||||
* (created-after / removed). Granularity is `transact()` commits.
|
||||
*/
|
||||
export interface HistoryVersion<T = any> {
|
||||
/** The committed generation that produced this version. */
|
||||
generation: number
|
||||
/** Commit timestamp of that generation (ms since epoch). */
|
||||
timestamp: number
|
||||
/** Materialized state at this version, or `null` if the id did not exist then. */
|
||||
value: Entity<T> | Relation<T> | null
|
||||
}
|
||||
|
||||
/**
|
||||
* @description The result of {@link Brainy.history}: every distinct version of
|
||||
* ONE id over a generation range, oldest → newest. Consecutive identical states
|
||||
* are collapsed (one entry per distinct state). `kind` is auto-detected from the
|
||||
* id's presence in the noun vs verb spaces.
|
||||
*/
|
||||
export interface EntityHistory<T = any> {
|
||||
/** The id whose history this is. */
|
||||
id: string
|
||||
/** Whether `id` is an entity (`'noun'`) or relationship (`'verb'`). */
|
||||
kind: 'noun' | 'verb'
|
||||
/** Distinct versions, oldest first. */
|
||||
versions: HistoryVersion<T>[]
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Persisted record shapes
|
||||
// ============================================================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue