5
stars
357
commits
TypeScript
primary language
Sep 9, 2026
updated
A modular, multi-language query engine for the JavaScript data already in your app's memory.
📖 Documentation · 🕹️ Live demo · 🎓 Thesis
DortDB queries arrays, DOM trees, and graphs in place, without moving them into a separate database process. SQL, Cypher, and XQuery come with it, you can mix them in a single query, and you can add a language of your own. Every language compiles to one shared algebra, so the optimizer plans and the executor runs a cross-model query as a single plan.
import { DortDB } from '@dortdb/core';
import { defaultRules } from '@dortdb/core/optimizer';
import { SQL } from '@dortdb/lang-sql';
const db = new DortDB({ mainLang: SQL(), optimizer: { rules: defaultRules } });
db.registerSource(['users'], [
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 },
]);
db.query('SELECT name FROM users WHERE age > 27');
// -> { schema: ['name'], data: [{ name: 'Alice' }] }
DortDB lets you:
DortDB does not:
See Alternatives for libraries that may suit your use case better.
Install the core plus whichever language packages you need:
npm i @dortdb/core @dortdb/lang-sql
# add more languages when you need them
npm i @dortdb/lang-cypher graphology @dortdb/lang-xquery
Only the Cypher package needs Graphology, as a peer dependency.
The feature that sets DortDB apart is embedding one language inside another. A
LANG block switches languages, and the inner query can read values from the
surrounding scope. Everything lowers to the same algebra, so the engine
optimizes and runs the whole query as one plan instead of as opaque nested
calls.
const db = new DortDB({
mainLang: SQL(),
additionalLangs: [XQuery()],
optimizer: { rules: defaultRules },
});
db.registerSource(['users'], [/* ... */]);
db.registerSource(['invoices'], new DOMParser().parseFromString('...', 'text/xml'));
// SQL filters users by a count computed with XQuery over an XML document
db.query(`
SELECT name, age
FROM users
WHERE age > 30 AND (
LANG xquery
fn:count($invoices/customer[. = $users:name])
) > 5
`);
LANG <name> and ends at its enclosing scope (such as a closing parenthesis) or an explicit LANG EXIT.See Cross-language Queries for the full syntax and scope rules.
A data adapter separates a language from the concrete shape of your data, so you
can point a language at sources of another shape. This one teaches SQL to read
Map-backed rows instead of plain objects.
const db = new DortDB({
mainLang: SQL({
adapter: {
createColumnAccessor: (prop) => (row: Map<string, unknown>) => row.get(prop),
createRow: (keys, values) => new Map(keys.map((k, i) => [k, values[i]])),
},
}),
optimizer: { rules: defaultRules },
});
db.registerSource(['users'], [
new Map([['name', 'Alice'], ['age', 30]]),
new Map([['name', 'Bob'], ['age', 25]]),
]);
db.query('SELECT name, age FROM users WHERE age > 27');
// -> { schema: ['name', 'age'], data: [{ name: 'Alice', age: 30 }] }
An adapter needs both members. createColumnAccessor reads a column out of a
row, and createRow builds a row of the same shape when the engine constructs
one itself. See Data Adapters.
Register secondary indices for faster lookups and joins. The optimizer uses a matching index automatically.
import { DortDB, MapIndex } from '@dortdb/core';
db.registerSource(['users'], [/* ... */]);
// index a column, or any subquery-free expression
db.createIndex(['users'], ['age'], MapIndex);
db.createIndex(['users'], ['name[0] + age'], MapIndex);
An index class can also speed up joins over streams that have no index of their
own. List those classes in hashJoinIndices to make them available to the
executor.
new DortDB({
mainLang: SQL(),
optimizer: { rules: defaultRules },
executor: { hashJoinIndices: [MapIndex] },
});
Package your own functions, operators, aggregates, and casts as an extension.
The @dortdb/datetime extension in this repo
adds date and time functions.
import { datetime } from '@dortdb/datetime';
const db = new DortDB({
mainLang: SQL(),
optimizer: { rules: defaultRules },
extensions: [datetime],
});
db.query(`SELECT date.sub(now(), interval('3 years')) AS cutoff`);
See Extending DortDB.
| Package | Description |
|---|---|
@dortdb/core | The language-neutral engine, optimizer, index abstractions, and extension points. |
@dortdb/lang-sql | SQL over arrays of objects. |
@dortdb/lang-cypher | Cypher-based queries over property graphs. |
@dortdb/lang-xquery | XQuery over XML, DOM, and tree-shaped data. |
@dortdb/datetime | Example extension bundling date/time functions. |
The @dortdb/lang-cypher package is a set of implementation extensions to
Cypher, based on the openCypher grammar, which is
under the Apache License 2.0. The openCypher Implementers Group has not approved
it. Cypher® is a registered trademark of Neo4j, Inc.
The full documentation, the guides, and a generated API reference are at filipjezek.github.io/dortdb.
The thesis covers the design and the formal background in depth.
If DortDB is not the right tool for your use case, try one of these libraries.
document.evaluate: The XPath engine built into browsers. It evaluates XPath only, not full XQuery.Released under the ISC License, except for the openCypher-derived
grammar in @dortdb/lang-cypher, which is under the Apache License 2.0 (see that
package's NOTICE).
339 commits
18 commits
TypeScript
77.2%
Jupyter Notebook
15.9%
PEG.js
3.3%
Python
1.4%
5
stars
357
commits
TypeScript
primary language
Sep 9, 2026
updated
A modular, multi-language query engine for the JavaScript data already in your app's memory.
📖 Documentation · 🕹️ Live demo · 🎓 Thesis
DortDB queries arrays, DOM trees, and graphs in place, without moving them into a separate database process. SQL, Cypher, and XQuery come with it, you can mix them in a single query, and you can add a language of your own. Every language compiles to one shared algebra, so the optimizer plans and the executor runs a cross-model query as a single plan.
import { DortDB } from '@dortdb/core';
import { defaultRules } from '@dortdb/core/optimizer';
import { SQL } from '@dortdb/lang-sql';
const db = new DortDB({ mainLang: SQL(), optimizer: { rules: defaultRules } });
db.registerSource(['users'], [
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 },
]);
db.query('SELECT name FROM users WHERE age > 27');
// -> { schema: ['name'], data: [{ name: 'Alice' }] }
DortDB lets you:
DortDB does not:
See Alternatives for libraries that may suit your use case better.
Install the core plus whichever language packages you need:
npm i @dortdb/core @dortdb/lang-sql
# add more languages when you need them
npm i @dortdb/lang-cypher graphology @dortdb/lang-xquery
Only the Cypher package needs Graphology, as a peer dependency.
The feature that sets DortDB apart is embedding one language inside another. A
LANG block switches languages, and the inner query can read values from the
surrounding scope. Everything lowers to the same algebra, so the engine
optimizes and runs the whole query as one plan instead of as opaque nested
calls.
const db = new DortDB({
mainLang: SQL(),
additionalLangs: [XQuery()],
optimizer: { rules: defaultRules },
});
db.registerSource(['users'], [/* ... */]);
db.registerSource(['invoices'], new DOMParser().parseFromString('...', 'text/xml'));
// SQL filters users by a count computed with XQuery over an XML document
db.query(`
SELECT name, age
FROM users
WHERE age > 30 AND (
LANG xquery
fn:count($invoices/customer[. = $users:name])
) > 5
`);
LANG <name> and ends at its enclosing scope (such as a closing parenthesis) or an explicit LANG EXIT.See Cross-language Queries for the full syntax and scope rules.
A data adapter separates a language from the concrete shape of your data, so you
can point a language at sources of another shape. This one teaches SQL to read
Map-backed rows instead of plain objects.
const db = new DortDB({
mainLang: SQL({
adapter: {
createColumnAccessor: (prop) => (row: Map<string, unknown>) => row.get(prop),
createRow: (keys, values) => new Map(keys.map((k, i) => [k, values[i]])),
},
}),
optimizer: { rules: defaultRules },
});
db.registerSource(['users'], [
new Map([['name', 'Alice'], ['age', 30]]),
new Map([['name', 'Bob'], ['age', 25]]),
]);
db.query('SELECT name, age FROM users WHERE age > 27');
// -> { schema: ['name', 'age'], data: [{ name: 'Alice', age: 30 }] }
An adapter needs both members. createColumnAccessor reads a column out of a
row, and createRow builds a row of the same shape when the engine constructs
one itself. See Data Adapters.
Register secondary indices for faster lookups and joins. The optimizer uses a matching index automatically.
import { DortDB, MapIndex } from '@dortdb/core';
db.registerSource(['users'], [/* ... */]);
// index a column, or any subquery-free expression
db.createIndex(['users'], ['age'], MapIndex);
db.createIndex(['users'], ['name[0] + age'], MapIndex);
An index class can also speed up joins over streams that have no index of their
own. List those classes in hashJoinIndices to make them available to the
executor.
new DortDB({
mainLang: SQL(),
optimizer: { rules: defaultRules },
executor: { hashJoinIndices: [MapIndex] },
});
Package your own functions, operators, aggregates, and casts as an extension.
The @dortdb/datetime extension in this repo
adds date and time functions.
import { datetime } from '@dortdb/datetime';
const db = new DortDB({
mainLang: SQL(),
optimizer: { rules: defaultRules },
extensions: [datetime],
});
db.query(`SELECT date.sub(now(), interval('3 years')) AS cutoff`);
See Extending DortDB.
| Package | Description |
|---|---|
@dortdb/core | The language-neutral engine, optimizer, index abstractions, and extension points. |
@dortdb/lang-sql | SQL over arrays of objects. |
@dortdb/lang-cypher | Cypher-based queries over property graphs. |
@dortdb/lang-xquery | XQuery over XML, DOM, and tree-shaped data. |
@dortdb/datetime | Example extension bundling date/time functions. |
The @dortdb/lang-cypher package is a set of implementation extensions to
Cypher, based on the openCypher grammar, which is
under the Apache License 2.0. The openCypher Implementers Group has not approved
it. Cypher® is a registered trademark of Neo4j, Inc.
The full documentation, the guides, and a generated API reference are at filipjezek.github.io/dortdb.
The thesis covers the design and the formal background in depth.
If DortDB is not the right tool for your use case, try one of these libraries.
document.evaluate: The XPath engine built into browsers. It evaluates XPath only, not full XQuery.Released under the ISC License, except for the openCypher-derived
grammar in @dortdb/lang-cypher, which is under the Apache License 2.0 (see that
package's NOTICE).
339 commits
18 commits
TypeScript
77.2%
Jupyter Notebook
15.9%
PEG.js
3.3%
Python
1.4%