npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@drasi/lib

v0.2.0

Published

Embed the Drasi continuous-query engine in Node.js, with dynamic plugin loading and JavaScript-defined sources and reactions

Downloads

411

Readme

@drasi/lib

npm version npm downloads license docs

📖 Documentation: https://drasi-project.github.io/drasi-nodejs/ — installation, concepts, the full API reference, guides, and an end-to-end tutorial.

Embed the Drasi continuous-query engine directly in your Node.js application. @drasi/lib is a native (napi-rs) binding around Drasi's embeddable engine (drasi-lib) and its host SDK (drasi-host-sdk), so you get:

  • In-process continuous queries over a property graph, in Cypher or GQL.
  • Dynamic plugin loading — discover and load Drasi source/reaction/bootstrap plugins (self-contained cdylib .so/.dylib/.dll files) at runtime, exactly like drasi-server does.
  • JavaScript-defined components — define a reaction as a JS callback, or a source you push changes into from your own code. No Rust required.

Status: pre-1.0 but functional and published to npm. The core engine, dynamic plugin loading with OCI plugin fetch (from ghcr.io/drasi-project), JS sources/reactions, streaming, lifecycle management, a secret store, a persistent state store (redb), concrete TypeScript types with typed error codes, and metrics are implemented and tested. Distributed with prebuilt binaries for Windows (x64), Linux (x64/arm64), and Apple-silicon macOS (arm64). The API is still evolving ahead of 1.0 — see the Roadmap below.

Install

Once published, @drasi/lib ships prebuilt binaries, so consumers install with no Rust toolchain:

npm install @drasi/lib

npm resolves the correct native binary from a per-platform optional dependency (@drasi/lib-<platform>) for Windows (x64), Linux (x64/arm64), and Apple-silicon macOS (arm64). Intel macOS (x64) has no prebuilt binary and must be built from source. See docs/releasing.md for how the binaries are built and distributed.

Build from source

Building from source is only needed to develop @drasi/lib or to run on a platform without a prebuilt binary. It builds a native addon whose Rust dependencies (the Drasi engine and plugin host SDK) come from crates.io, so no sibling checkout is required (see Dependency strategy):

npm install
npm run build          # produces index.js, index.d.ts and the .node binary
npm test

Quickstart

import { Drasi } from '@drasi/lib';

const drasi = await Drasi.create('my-app');

// Discover & load cdylib plugins from a directory.
await drasi.loadPlugins('./plugins');
await drasi.start();

// A source plugin produces graph changes...
await drasi.addSource('mock', 'counters', { dataType: { type: 'counter' }, intervalMs: 500 });

// ...a continuous query reacts to them...
await drasi.addQuery('big', 'MATCH (c:Counter) WHERE c.value > 3 RETURN c.value AS value', ['counters']);

// ...and you can read the live result set.
console.log(await drasi.getQueryResults('big'));

Define a reaction in JavaScript

await drasi.addJsReaction('on-change', ['big'], (result) => {
  // { query_id, sequence, results: [{ type: 'ADD'|'UPDATE'|'DELETE', data, before?, after? }] }
  for (const diff of result.results) {
    if (diff.type === 'ADD') console.log('added', diff.data);
  }
});

Push changes from a JavaScript source

await drasi.addJsSource('orders');
await drasi.addQuery('open', 'MATCH (o:Order) WHERE o.status = "open" RETURN o.id AS id', ['orders']);

await drasi.pushChange('orders', {
  op: 'insert',                 // insert | update | delete
  id: 'o1',
  labels: ['Order'],
  properties: { status: 'open', total: 42 },
});

See examples/ for runnable scripts (quickstart.mjs, js-reaction.mjs, js-source.mjs), and examples/electron-explorer for a full Electron + React desktop app that browses/installs plugins and builds and observes a topology on the embedded engine.

API overview

The full, browsable API reference lives on the documentation site. The summary below is a quick reference.

Drasi.create(id, options?)Promise<Drasi>. options.secrets seeds an in-memory secret store ({ secrets: { DB_PASSWORD: '…' } }) that cdylib plugins resolve ConfigValue::Secret/EnvironmentVariable references against; options.stateStore ({ kind: 'redb', path }) enables a persistent plugin state store. Drasi.fromConfig(config) builds and starts an engine from a declarative object (see the API reference, or docs/api-reference.md for the audit-oriented deep dive).

| Area | Methods | | --- | --- | | Plugins | loadPlugins(dir, verify?), watchPlugins(dir), pluginKinds(), sourceConfigSchema(kind), reactionConfigSchema(kind), bootstrapConfigSchema(kind), listPluginTags(repo), pullPlugin(reference, destDir, filename, options?) | | Sources | addSource(kind, id, config, autoStart?, bootstrap?), addJsSource(id, autoStart?), pushChange(sourceId, change), updateSource, startSource, stopSource, removeSource, listSources, getSourceSchema(id), getGraphSchema() | | Queries | addQuery(id, query, sources, language?, joins?), updateQuery, startQuery, stopQuery, removeQuery, getQueryResults(id), listQueries | | Reactions | addReaction(kind, id, queryIds, config), addJsReaction(id, queryIds, cb), addDurableJsReaction(id, queryIds, asyncCb, options?), updateReaction, startReaction, stopReaction, removeReaction, listReactions | | Metrics | getQueryMetrics(id), getReactionMetrics(id), getLifecycleMetrics() | | Streaming | onAllEvents(cb), onQueryEvents(id, cb), onSourceEvents(id, cb), onReactionEvents(id, cb), onSourceLogs(id, cb), onQueryLogs(id, cb), onReactionLogs(id, cb) | | Lifecycle | start(), stop(), close() |

language is "cypher" (default) or "gql" — any other value is rejected with a typed UNKNOWN_QUERY_LANGUAGE error. pushChange emits nodes, or relations when change includes startId/endId. Generated TypeScript types are in index.d.ts. Callbacks are unref'd, so they don't keep the Node process alive on their own.

Ordering tip

Call start() first, then add components. Components added to a running engine auto-start individually. (Adding everything and then calling start() also works.)

Types

The generated index.d.ts is self-contained: every config/result parameter and return, plus the callback payloads, is exposed with a concrete TypeScript type (no bare any). Import them directly from the package.

import type { SourceChangeInput, QueryResultEvent } from '@drasi/lib';
const change: SourceChangeInput = { op: 'insert', id: 'o1', labels: ['Order'] };
const onResult = (event: QueryResultEvent) => console.log(event.results.length);

Error handling

Argument/validation failures throw a stable, machine-readable code so callers can branch on it instead of matching human-readable messages. DrasiErrorCode is exported as a regular (non-const) enum with string values — it is safe under isolatedModules / esbuild / swc / Vite and has a real runtime value, so both the type and value are usable:

import { DrasiErrorCode } from '@drasi/lib';

try {
  await drasi.addSource('unknown', 's', {});
} catch (err) {
  if ((err as { code?: string }).code === DrasiErrorCode.UnknownSourceKind) {
    // handle the unregistered-kind case
  }
}

Because napi-rs can only attach a custom code on a synchronous throw (async promise rejections are forced to code === 'GenericFailure'), the two error classes behave as follows — the human-readable message is the same in both cases:

  • Synchronous throws (err.code is the stable code). Argument validation runs synchronously, before the method returns its Promise. This covers, on their normal paths: UNKNOWN_SOURCE_KIND, UNKNOWN_REACTION_KIND, UNKNOWN_BOOTSTRAP_KIND, BOOTSTRAP_KIND_REQUIRED, MISSING_CONFIG_FIELD, NO_JS_SOURCE, JS_SOURCE_CLOSED, CHANGE_NOT_OBJECT, CHANGE_OP_REQUIRED, CHANGE_ID_REQUIRED, RELATION_REQUIRES_BOTH_ENDS, UNKNOWN_CHANGE_OP, STATE_STORE_PATH_REQUIRED, and UNKNOWN_STATE_STORE_KIND. Note this means validation errors surface as a synchronous throw rather than a rejected promise — transparent to await/try callers, but a bare p = fn(); p.catch(...) (no await) will not catch them.
  • Async fallbacks (message-only; err.code === 'GenericFailure'). A few paths can only fail after the async work has begun: component creation inside fromConfig (plugin kinds resolve after the async plugin load) and the rare race where a JS source closes mid-pushChange. There the stable code is embedded in the message as a trailing [CODE] token (e.g. unknown source kind 'x' [UNKNOWN_SOURCE_KIND]) so a single check still works:
function drasiCode(err: unknown): string | undefined {
  const e = err as { code?: string; message?: string };
  if (e.code && e.code !== 'GenericFailure') return e.code;        // sync throw
  return e.message?.match(/\[([A-Z_]+)\]\s*$/)?.[1];               // async fallback
}

How it works

A .node addon is itself a cdylib, so it can host drasi-lib and dlopen plugin cdylibs through drasi-host-sdk — the same mechanism drasi-server uses. Plugins are fully self-contained, each with their own async runtime, and talk to the host across a stable #[repr(C)] FFI with load-time version negotiation. JS callbacks are bridged with napi ThreadsafeFunctions; async methods return Promises driven by a Tokio runtime.

Dependency strategy

The Drasi engine and host SDK are consumed from crates.io. The workspace versions its crates on independent lines — only drasi-host-sdk and drasi-plugin-sdk track the shared workspace version — so the pinned numbers differ by design (drasi-host-sdk/drasi-plugin-sdk 0.10, drasi-lib 0.8, drasi-core 0.5), yet their published dependency requirements resolve to a single coherent graph. Exact versions are locked in Cargo.lock.

The FFI ABI is negotiated between drasi-host-sdk (host) and drasi-plugin-sdk (plugins), which always share the workspace version, so any plugin built against a matching drasi-plugin-sdk major.minor loads cleanly. The example plugins used by the test suite are fetched from crates.io and built with --features dynamic-plugin by scripts/build-plugins.mjs (the npm pretest step); bump their pinned versions alongside drasi-plugin-sdk when upgrading the SDK.

Roadmap

Implemented: dynamic plugin loading (+ optional SHA-256 verification), OCI plugin fetch from ghcr.io/drasi-project (pullPlugin/listPluginTags) with opt-in cosign signature enforcement, JS sources (nodes + relations + bootstrap replay) and reactions (including durable, checkpointed reactions), event & log streaming, secret/env config resolution for plugins, bootstrap-provider wiring, persistent state store (redb) and persistent query-index backend (RocksDB), identity providers, plugin hot-reload, lifecycle/update APIs, concrete public TypeScript types with typed error codes (DrasiErrorCode), plugin config-schema accessors with tokenized config-validation errors, query-language validation, and metrics accessors. Published to npm with cross-platform prebuilt binaries and build provenance (see docs/releasing.md).

Still to come:

  • Full in-engine JSON-schema config validation (schemas are already exposed via sourceConfigSchema/reactionConfigSchema for client-side validation today).
  • Prebuilt binaries for Intel macOS (x86_64-apple-darwin); Intel-mac users currently build from source.

Measuring success

Adoption is tracked via npm downloads and dependents. See docs/metrics.md for how to read the numbers — the public npm downloads API, npmjs.com Insights/dependents, and third-party dashboards — plus the baseline and where to record targets.

License

Licensed under the Apache License, Version 2.0 — see LICENSE.

Contributing

Contributions are welcome! See CONTRIBUTING.md for the development workflow and the DCO sign-off requirement, and CODE_OF_CONDUCT.md. To report a security issue, see SECURITY.md.