Store¶
Event stores, URL parsing, and migration. For the conceptual
model see concepts/graph (graph as
projection of the event log) and
concepts/replay.
Stores¶
Bases: Protocol
Append-only per-run event log. CONTRACT v0.5 #2.
The persistence seam: an instance is scoped to one run_id
even when runs share a backing file. Deliberately minimal —
append, iterate, count, lookup, truncate-after, close; no
queries, no indexes beyond what the backend ships. Backends
(in-memory, SQLite, Postgres) implement this protocol and pass
EventStoreConformance. The log is the source of truth; the
graph projection rebuilds from here and is never stored here.
Volatile, list-backed event store. CONTRACT v0.5 #2.
The reference implementation of the EventStore protocol:
append-only, id-addressed, iteration in append order. Everything
lives in process memory and nothing survives the interpreter —
exactly right for tests and ephemeral runs, and exactly wrong for
anything that must be resumed, forked, or audited later; use
SQLiteEventStore or PostgresEventStore for those.
Per-run view onto a SQLite-backed event log.
Direct construction expects an explicit path and run_id.
For most cases prefer Runtime(graph, persist_to=...), which
opens the store, mints a run_id if needed, and wires it onto
the runtime — the v1.0.1 user-test surfaced that constructing
SQLiteEventStore by hand is a low-frequency operator path,
not the happy path.
upsert_run(*, parent_run_id=None, forked_at_event_id=None, label=None, created_at, goal=None, frame_id=None)
¶
Insert or update this run's row. None never clears a
stored value (v1.3 fix): Runtime.load upserts with only
created_at, and before the COALESCEs below that erased a
fork's parent_run_id / forked_at_event_id / label
on every reload — silently destroying the lineage records
promote() verifies against.
put_snapshot(state_hash, blob, *, created_at)
¶
Store a snapshot blob keyed by its state hash (idempotent).
get_snapshot(state_hash)
¶
The snapshot blob for state_hash, or None.
archive_prefix(before_seq, *, archived_at)
¶
Move this run's rows with seq < before_seq to the archive
tier, in one transaction. Idempotent (re-running moves nothing).
Returns the number of rows moved.
archive_run(*, archived_at)
¶
Move ALL of this run's rows to the archive tier (retire).
iter_archived()
¶
This run's archived events, in original seq order.
has_archived()
¶
True when any of this run's events sit in the archive tier.
fork_run(path, *, parent_run_id, new_run_id, at_event_id, label, created_at)
classmethod
¶
Copy events from parent_run_id up to and including at_event_id into new_run_id (CONTRACT v0.5 #11: copy rows, no row-sharing).
Returns the number of events copied.
Graph stores¶
The materialized graph projection (objects, relations, patches) lives
behind a GraphStore, distinct from the durable EventStore above. See
the Using the FalkorDB graph store guide.
Bases: ABC
Backend for the materialized graph projection.
Implementations store objects, relations, and patches keyed by id.
put_* is an upsert (insert or overwrite the entity with the same
id). get_* returns None when the id is unknown. remove_*
is a no-op when the id is unknown (the projector already guards
against double-removal, but tolerating it keeps backends simple).
put_object(obj)
abstractmethod
¶
Insert or overwrite obj keyed by obj.id.
get_object(object_id)
abstractmethod
¶
Return the object with object_id or None.
remove_object(object_id)
abstractmethod
¶
Drop the object with object_id if present.
all_objects()
abstractmethod
¶
Return every object. Order is unspecified.
put_relation(rel)
abstractmethod
¶
Insert or overwrite rel keyed by rel.id.
get_relation(relation_id)
abstractmethod
¶
Return the relation with relation_id or None.
remove_relation(relation_id)
abstractmethod
¶
Drop the relation with relation_id if present.
all_relations()
abstractmethod
¶
Return every relation. Order is unspecified.
put_patch(patch)
abstractmethod
¶
Insert or overwrite patch keyed by patch.id.
get_patch(patch_id)
abstractmethod
¶
Return the patch with patch_id or None.
all_patches()
abstractmethod
¶
Return every patch. Order is unspecified.
find_objects(type=None)
¶
Return objects, optionally filtered to those whose type equals
type. None means "every object". Type-equality only — the
where predicate is applied by the caller, not here.
find_objects_in_types(types)
¶
Return objects whose type is any of types (OR), preserving
:meth:all_objects enumeration order. An empty types returns
[]. Like :meth:find_objects this is type-equality only — the
where predicate stays with the caller. The default scans once;
a backend MAY push a type IN [...] filter into the database, but
MUST preserve the single-pass order so it matches this base class.
find_relations(source=None, target=None, type=None)
¶
Return relations matching every provided filter (AND). A None
filter is unconstrained; passing none returns every relation.
neighborhood(object_id, depth=1)
¶
Undirected breadth-first walk from object_id out to depth
edges. Returns (objects, relations) where objects are the
reachable materialized objects (the start included; endpoints that
are not objects are skipped) and relations are every relation
traversed. Returns ([], []) if object_id is not an object.
match_chain(node_types, rels)
¶
Enumerate every structural match of a linear node→rel→node chain.
node_types is one entry per node position (None = any type);
rels is one (rel_type, direction) per hop, where direction
is "right" ((a)-[]->(b)) or "left" ((a)<-[]-(b)) and
rel_type None means any. len(rels) must be
len(node_types) - 1.
Only the structural filters — node types, relation types, and
directions — are applied here; node {prop: value} equality and
WHERE are layered on by the caller. Semantics are homomorphic:
a single object or relation may fill more than one position (matching
the pattern matcher and FalkorDB, neither of which enforces
node/relationship uniqueness). The default walks the chain
depth-first via :meth:find_objects / :meth:find_relations; a
backend MAY override to resolve the whole chain in one query, but MUST
return exactly what this default would (order aside).
clear()
¶
Drop all objects, relations, and patches. Default: per-kind removal.
remove_patch(patch_id)
¶
Drop the patch with patch_id if present.
Not part of the projector's hot path (patches are never deleted by
events — only superseded), but :meth:clear needs it. Subclasses
that store patches must override.
close()
¶
Release any backend resources. Default: no-op.
Bases: GraphStore
Volatile, dict-backed GraphStore. The default backend.
Stores the live :class:~activegraph.core.graph.Object /
:class:~activegraph.core.graph.Relation / :class:~activegraph.core.patch.Patch
instances by id and returns them directly (no copy), so the projector's
in-place mutations (obj.data.update(...), obj.version += 1)
behave exactly as they did before the GraphStore seam existed.
Bases: GraphStore
A :class:GraphStore backed by a FalkorDB graph.
Parameters¶
path:
Filesystem path for the embedded falkordblite database. Used
only in embedded mode (no connection info given). When None, an
ephemeral embedded instance is created.
graph_name:
Name of the FalkorDB graph to use within the database/server.
Defaults to "activegraph". Ignored when graph is supplied.
graph:
An existing FalkorDB graph handle (anything exposing query /
ro_query). When provided, all other connection parameters are
ignored and this store does not own the connection's lifecycle.
url:
FalkorDB server URL (e.g. "falkor://host:6379"). Triggers
server mode via the falkordb client.
host, port, username, password:
FalkorDB server connection settings. Supplying host triggers
server mode. port defaults to 6379.
Connection resolution order: explicit graph → explicit url /
host → FALKORDB_URL / FALKORDB_HOST env vars → embedded
falkordblite.
Install the server client with pip install 'activegraph[falkordb]'
or the embedded engine with
pip install 'activegraph[falkordb-embedded]'.
URL parsing + helpers¶
Open a store for run_id at url. Returns an EventStore.
This is the single entry point the runtime and CLI use to open a store from a URL. Drivers are imported lazily so the Postgres dependency stays optional.
Parse a store URL, or raise InvalidStoreURL with a helpful message.
Accepts the sqlite:///path and postgres://... families and
returns a :class:StoreURL with the normalised scheme, the raw
URL, and the filesystem path for SQLite forms. The single entry
point for URL validation — open_store and the CLI both route
through here, so a malformed URL fails identically everywhere.
Migration¶
Copy every run (or a subset) from source_url into dest_url.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_url
|
str
|
e.g. |
required |
dest_url
|
str
|
e.g. |
required |
only_run_ids
|
Optional[list[str]]
|
if given, migrate only these runs. |
None
|
on_progress
|
Optional[Callable[[MigrationRunReport], None]]
|
called after each run finishes (success or failure)
with the |
None
|
skip_corrupted
|
bool
|
if True, rows whose payload fails JSON decode
are skipped (not migrated, not failing the run). The
skipped event ids appear in the per-run report's
|
False
|
Returns:
| Type | Description |
|---|---|
MigrationReport
|
A |
MigrationReport
|
successful iff every run's status is |
Aggregate result of a store-to-store migration.
One :class:MigrationRunReport per run found at the source;
ok is True when nothing failed (skips are fine), and
failures filters the runs that need attention. Value object
returned by migrate and rendered by the CLI.
Outcome of migrating one run between stores.
status is ok (copied), skipped (already present at the
destination), or failed (with error set);
events_migrated counts rows written this invocation, so a
rerun after a partial failure reports only the delta.
skipped_events lists ids dropped under --skip-corrupted.