Concepts
4 minute read
@drasi/lib embeds the Drasi continuous-query engine inside
your Node.js process. To use it well, it helps to understand three building blocks
— sources, continuous queries, and reactions — and the change-driven
model that ties them together.
The change-driven model
Traditional applications pull: you run a query, get an answer, and it’s stale the moment the data changes. To stay current you poll, diff, and re-run.
Drasi flips this around. You declare a continuous query once, and the engine keeps its result set continuously up to date as the underlying data changes. Rather than re-running the query, Drasi tells you exactly what changed — which rows were added, updated, or removed.
flowchart LR S[Sources] -->|changes| Q[Continuous Queries] Q -->|result diffs| R[Reactions]
Everything runs in-process: the engine, the queries, and your JavaScript sources and reactions all live in the same Node.js process. There is no external server, message broker, or database required for the engine itself.
Sources
A source feeds changes into the engine as a stream of graph elements — nodes (with labels and properties) and relations (edges between nodes). Drasi models all incoming data as a property graph so a single query can span multiple sources.
@drasi/lib gives you two kinds of source:
- JavaScript sources — created with
addJsSourceand driven by your own code viapushChange. Use these to feed the engine from an event stream, a webhook, database CDC, or any data your application already has. No Rust required. See JavaScript sources. - Native plugin sources — self-contained cdylib plugins (the same ones
drasi-serveruses) that connect to external systems such as PostgreSQL. Load them from a directory withloadPluginsor pull them from an OCI registry. See Working with plugins.
A source may also have a bootstrap provider that gives newly subscribed queries an initial snapshot of existing data before live changes start flowing.
Continuous queries
A continuous query is a declarative query — written in Cypher (the default)
or GQL — over one or more sources. Once added with
addQuery, the engine maintains
its result set incrementally: as sources emit changes, Drasi computes the minimal
set of result rows that were added, updated, or removed and makes them available to
reactions.
await drasi.addQuery(
'hot-sensors',
'MATCH (s:SensorReading) WHERE s.temperature > 25 RETURN s.sensor_id AS sensor, s.temperature AS temp',
['sensors'],
);
You can read a query’s current result set at any time with
getQueryResults.
Synthetic joins
Sometimes you need to relate elements across sources — or within one source — that have no explicit relationship (no foreign key, no edge). Synthetic joins let the query define those relationships from matching property values, entirely inside the engine:
await drasi.addQuery(
'watchlist',
'MATCH (w:Watchlist)-[:ON_WATCHLIST]->(s:Stock)-[:HAS_PRICE]->(p:Price) RETURN ...',
['watchlist', 'stocks', 'prices'],
'cypher',
[
{ id: 'ON_WATCHLIST', keys: [{ label: 'Watchlist', property: 'symbol' }, { label: 'Stock', property: 'symbol' }] },
{ id: 'HAS_PRICE', keys: [{ label: 'Stock', property: 'symbol' }, { label: 'Price', property: 'symbol' }] },
],
);
Reactions
A reaction consumes a query’s result changes and does something with them.
@drasi/lib offers:
- JavaScript reactions — a plain callback registered with
addJsReaction. It receives aQueryResultEventfor each non-empty batch of changes. This is the simplest way to wire query results into your application. See JavaScript reactions. - Durable JavaScript reactions — an async callback registered with
addDurableJsReaction. The engine checkpoints progress to a persistent state store so a restart resumes after the last checkpoint. Use these when missing a change on a crash is not acceptable. - Native plugin reactions — cdylib reaction plugins added with
addReaction.
Result diffs
Both JavaScript reaction styles receive a QueryResultEvent:
{ query_id: string, sequence: number, timestamp: string,
results: ResultDiff[], metadata: Record<string, unknown> }
Each ResultDiff in results is a tagged union on type:
type | Payload | Meaning |
|---|---|---|
ADD | { data } | A row entered the result set. |
UPDATE | { before, after } | A row’s values changed. |
DELETE | { data } | A row left the result set. |
aggregation | { before?, after } | An aggregated value (e.g. GROUP BY) changed. |
noop | — | No effective change. |
await drasi.addJsReaction('handle', ['hot-sensors'], (event) => {
for (const diff of event.results) {
if (diff.type === 'ADD') console.log('HOT:', diff.data);
else if (diff.type === 'UPDATE') console.log('CHANGED:', diff.after);
else if (diff.type === 'DELETE') console.log('COOLED:', diff.data);
}
});
Lifecycle & ordering
An engine is created with Drasi.create,
started with start, and released with close.
A useful rule of thumb: call start() first, then add components. Components
added to a running engine auto-start individually. Adding everything first and then
calling start() also works — both orderings are supported.
For a fully declarative setup, Drasi.fromConfig
builds and starts an engine from a single config object describing plugins,
sources, queries, and reactions.
Where to go next
- API reference — the complete
Drasiclass surface. - Getting Started — install and run your first query.
- Trading demo — these concepts applied end to end.
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.