Status: Beta - API is functional and tested, but may receive breaking changes. Published as datahike@next on npm.
The Datahike JavaScript API provides a Promise-based interface for Node.js and browser environments. All async operations return Promises, and data is automatically converted between JavaScript and ClojureScript.
src/datahike/js/
├── api.cljs # Main JS API implementation
├── api_macros.clj # Macro for generating API functions
├── naming.cljc # Shared naming conventions (ClojureScript → JavaScript)
└── typescript.clj # TypeScript definition generator
npm-package/
├── test.js # Comprehensive test suite
├── package.template.json # Version-controlled template
├── README.md # npm package documentation
├── PUBLISHING.md # Publishing guide
└── index.d.ts # Generated TypeScript definitions
bb/src/tools/
└── npm.clj # Build automation (version, types, compile, test)
The JS API uses a modern automated build pipeline:
# Full build: version + types + compile + test
bb npm-build
# Individual steps:
bb npm-version # Generate package.json from template with version from config.edn
bb codegen-ts # Generate TypeScript definitions
bb npm-test # Run npm package testsVersions are automatically calculated as major.minor.commit-count from config.edn.
Output is generated in npm-package/datahike.js.api.js with advanced compilation.
npm install datahike@nextconst d = require('datahike');
const crypto = require('crypto');
async function example() {
// Configuration - must use UUID for :id
const config = {
store: {
backend: ':memory',
id: crypto.randomUUID()
}
};
// Create database
await d.createDatabase(config);
// Connect
const conn = await d.connect(config);
// Define schema
// Keys: WITHOUT colon (plain strings)
// Values: WITH colon prefix (keywords)
const schema = [
{
'db/ident': ':name',
'db/valueType': ':db.type/string',
'db/cardinality': ':db.cardinality/one'
},
{
'db/ident': ':age',
'db/valueType': ':db.type/long',
'db/cardinality': ':db.cardinality/one'
}
];
await d.transact(conn, schema);
// Insert data (data keys without colons)
const data = [
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 }
];
await d.transact(conn, data);
// Get database value (synchronous)
const db = await d.db(conn);
// Get datoms
const datoms = await d.datoms(db, ':eavt');
console.log('Datoms:', datoms.length);
// Pull API (pattern attributes with colons)
const entityId = 1; // Find ID through query or datoms
const pulled = await d.pull(db, [':name', ':age'], entityId);
console.log('Entity:', pulled);
// Clean up
d.release(conn);
await d.deleteDatabase(config);
}Datahike uses universal EDN conversion rules that are consistent across Python, JavaScript, and Java bindings:
Keys are always keywordized. Values starting with
:become keywords, everything else remains literal.
- Object keys: Always converted to keywords (
:prefix added automatically) - String values starting with
:: Converted to keywords (e.g.,":memory"→:memory) - String values starting with
\\:: Literal colon string (e.g.,"\\:literal"→":literal") - Other string values: Remain as strings
- Arrays: Converted to CLJ vectors
- Numbers, booleans, null: Pass through unchanged
- UUID strings: Auto-detected and converted to UUID objects (convenience feature)
- Keywords: Converted to strings with
:prefix - CLJ maps: Converted to JS objects (keyword keys become strings)
- Vectors/Lists: Converted to Arrays
- Sets: Converted to Arrays
- Special: DB values, datoms, and connections pass through unchanged
For complete conversion rules and edge cases, see EDN Conversion Documentation.
The universal EDN conversion rules make keyword syntax simple and predictable:
Simple rule: Keys never need :, values that should be keywords need :
// ✅ Schema definition
const schema = [{
'db/ident': ':name', // Key auto-keywordized, value is keyword
'db/valueType': ':db.type/string', // Both become keywords
'db/cardinality': ':db.cardinality/one'
}];
// ✅ Data insertion
const data = [
{ name: 'Alice', age: 30 } // Keys auto-keywordized, values are literals
];
// ✅ Configuration
const config = {
store: {
backend: ':memory', // ":memory" becomes :memory keyword
id: 'test' // "test" stays as string
},
'schema-flexibility': ':read', // ":read" becomes :read keyword
'keep-history?': true // boolean passes through
};
// ✅ Pull patterns (array of keyword strings)
const pattern = [':name', ':age']; // Strings with : become keywords
// ✅ Literal colon strings (rare)
const data = [{
description: '\\:starts-with-colon' // Escaped → ":starts-with-colon" (string)
}];const crypto = require('crypto');
// In-memory backend (requires UUID)
const memConfig = {
store: {
backend: ':memory',
id: crypto.randomUUID()
}
};
// File backend (Node.js only)
const fileConfig = {
store: {
backend: ':file',
path: '/path/to/db'
}
};All database operations return Promises. Use await or .then():
// Using await (recommended)
const conn = await d.connect(config);
const result = await d.transact(conn, data);
console.log(result['tx-data']); // Note: 'tx-data' not 'tx_data'
console.log(result['db-before']);
console.log(result['db-after']);
// Using promises
d.connect(config).then(conn => {
return d.transact(conn, data);
}).then(result => {
console.log(result['tx-data']);
});All functions use camelCase naming. The JavaScript API automatically:
- Converts
kebab-case→camelCase - Removes
!and?suffixes - Renames
with→withDb(reserved keyword)
Main API Functions:
- Database Lifecycle:
createDatabase,deleteDatabase,databaseExists - Connection:
connect,release - Database Values:
db,asOf,since,history,withDb,dbWith - Transactions:
transact(async, returns Promise),loadEntities - Queries:
q,pull,pullMany,datoms,seekDatoms,entity,entityDb - Schema:
schema,reverseSchema - Utilities:
tempid,isFiltered,filter,indexRange - Maintenance:
gcStorage - Info:
datahikeVersion
Note: transact! from Clojure becomes transact in JavaScript (the ! is removed).
Full TypeScript definitions are automatically generated and included:
import * as d from 'datahike';
interface Config {
store: {
backend: string;
id?: string;
path?: string;
};
'keep-history'?: boolean;
'schema-flexibility'?: string;
}
const config: Config = {
store: { backend: ':memory', id: 'example' }
};
async function example() {
await d.createDatabase(config);
const conn = await d.connect(config);
const db = d.db(conn);
// TypeScript will check types automatically
}The comprehensive test suite in npm-package/test.js covers all functionality:
- Basic database operations
- Schema and transactions
- Datoms API
- Pull API
- Entity API
- Temporal databases (history, as-of, since)
- File backend persistence
- Query API
Run tests with:
bb npm-testTests are automatically run in CI/CD as part of bb check.
Naming is centralized in src/datahike/js/naming.cljc for consistency:
;; Functions to skip (incompatible with JS or aliases)
(def js-skip-list #{'transact}) ; sync version, use transact! instead
;; Conversion rules:
;; database-exists? → databaseExists
;; create-database → createDatabase
;; transact! → transact (! removed)
;; with → withDb (reserved keyword)- Add to API specification: Function must be in
datahike.api.specification - Update skip list (if needed): Add to
js-skip-listinnaming.cljcto exclude - Rebuild package:
This will:
bb npm-build
- Generate TypeScript definitions (using naming.cljc)
- Compile API functions (using api_macros.clj)
- Run tests
Versions are calculated automatically from config.edn:
- Format:
major.minor.commit-count - Example:
0.6.1637(major: 0, minor: 6, commits: 1637) package.jsonis generated frompackage.template.jsonduring build
See npm-package/PUBLISHING.md for detailed publishing instructions.
Quick workflow:
# 1. Build and test
bb npm-build
# 2. Verify package
cd npm-package && npm pack --dry-run
# 3. Publish as next
npm publish --tag nextThe Datalog query API requires EDN string format:
// ✅ Works: EDN string format
const results = await d.q(
'[:find ?name ?age :where [?e :name ?name] [?e :age ?age]]',
db
);
// ❌ Doesn't work: JavaScript object syntax
// const results = await d.q({ find: '?e', where: [...] }, db);
// ✅ Alternative: Use Datoms API for simple queries
const datoms = await d.datoms(db, ':eavt');The entity function returns ClojureScript objects, not plain JavaScript objects. Use the Pull API for plain data:
// ✅ Recommended: Pull API returns plain objects
const data = await d.pull(db, [':name', ':age'], entityId);
console.log(data.name); // Works
// ⚠️ Entity API returns ClojureScript objects
const entity = await d.entity(db, entityId);
// Accessing attributes requires understanding ClojureScript objectsShadow-cljs compilation may produce warnings from dependencies:
- BigInt warnings from
persistent-sorted-set(harmless, ES2020 feature) - Infer warnings (cosmetic, doesn't affect functionality)
- Redef warning for
filter(expected, intentional override)
These can be safely ignored.
Functions are automatically generated from datahike.api.specification by the emit-js-api macro. To add a function:
- Add it to
api-specificationinsrc/datahike/api/specification.cljc - Add to
js-skip-listinapi_macros.cljif it should be excluded - Rebuild with
npx shadow-cljs compile npm-release
If you need to handle special types, update clj->js-recursive and js->clj-recursive in src/datahike/js/api.cljs.
Published as datahike@next on npm. Install with:
npm install datahike@next- Add transaction builder helpers for common patterns
- Fluent/chainable API for queries
- Convenience wrappers for common query patterns
- Improved error messages for JavaScript users
- Helper module with EDN type constructors (similar to Python's
edn.keyword(),kw.DB_IDconstants)