Tuple Database
Tuple Database is a local-first, embedded database designed for modern JavaScript and TypeScript applications, particularly those embracing the local-first software paradigm. It functions as a reactive, indexable graph database, often described as an 'embedded FoundationDb.' The library, currently at version 2.2.4, provides transactional read/write operations with a schemaless approach, where schemas are enforced by the application's type definitions rather than the database itself. It supports both synchronous and asynchronous storage backends, including SQLite or LevelDb, making it versatile for various environments. Key differentiators include its focus on freeing developers from the complexities of multi-tenant systems by empowering users with local data ownership, offering all reactive queries, and enabling direct manipulation of indexes for graph and relational queries. While the release cadence isn't explicitly stated, active development is indicated by its `2.x.x` versioning and comprehensive feature set, positioning it as a robust solution for frontend state management and embedded data persistence.
Common errors
-
Argument of type 'string[]' is not assignable to parameter of type 'Tuple'.
cause Attempting to use a simple array of strings instead of a properly structured tuple key (e.g., `['type', {id: '...'}]`) in `get`, `set`, `remove`, or `scan` operations.fixEnsure your tuple keys strictly conform to the `Schema` type definitions, using object literals for named parts of the tuple key (e.g., `['user', {id: userId}]`). -
TypeError: require is not a function
cause Attempting to import `tuple-database` using CommonJS `require()` syntax in a project configured for ESM (e.g., in a modern Node.js environment with `"type": "module"` in `package.json` or within a browser module).fixUse ES module `import` statements: `import { Symbol } from 'tuple-database';`. Configure your project's build system (Webpack, Rollup, Vite) or Node.js environment to handle ESM correctly. -
Property 'key' does not exist on type '{ key: any; value: any; }'.cause This error typically occurs when trying to access properties of a `scan` or `get` result without proper TypeScript type narrowing, especially when the result could be `undefined` or `null`.fixAlways check if the result of a database operation is `null` or `undefined` before accessing its properties. Use optional chaining (`?.`) or type guards to ensure type safety, e.g., `if (result) { console.log(result.key); }`.
Warnings
- gotcha Tuple Database is schemaless in its storage layer; schemas are enforced solely by application-level TypeScript types. Inconsistencies can easily arise if the TypeScript schema (`Schema` type) does not accurately reflect how tuples are actually written or read, leading to runtime data retrieval issues or type mismatches.
- breaking Major version updates (e.g., from v1 to v2) may introduce breaking changes in API surface or internal tuple key structures. While not frequent, always consult the release notes for migration guides.
- gotcha The `useTupleDatabase` React hook requires the `TupleDatabaseClient` instance to be passed as a prop, as it does not rely on React Context for database access. This differs from many other state management libraries.
Install
-
npm install tuple-database -
yarn add tuple-database -
pnpm add tuple-database
Imports
- TupleDatabaseClient
const TupleDatabaseClient = require('tuple-database').TupleDatabaseClientimport { TupleDatabaseClient } from 'tuple-database' - transactionalReadWrite
import transactionalReadWrite from 'tuple-database'
import { transactionalReadWrite } from 'tuple-database' - useTupleDatabase
import { useTupleDatabase } from 'tuple-database'import { useTupleDatabase } from 'tuple-database/useTupleDatabase'
Quickstart
import { TupleDatabaseClient, TupleDatabase, InMemoryTupleStorage, transactionalReadWrite } from "tuple-database";
// 1. Define your schema using TypeScript types.
type User = {
id: string;
first_name: string;
last_name: string;
age: number;
};
type UserIndex = {
key: ["user", { id: string }],
value: User;
};
type AgeIndex = {
key: ["userByAge", { age: number }, { id: string }],
value: null;
};
type Schema = UserIndex | AgeIndex;
// 2. Construct your database with an in-memory storage.
const db = new TupleDatabaseClient<Schema>(new TupleDatabase(new InMemoryTupleStorage()));
// 3. Define a transactional write query.
const insertUser = transactionalReadWrite<Schema>()((tx, user: User) => {
const { id, first_name, last_name, age } = user;
tx.set(["user", { id }], user);
tx.set(["userByAge", { age }, { id }], null);
});
// 4. Insert some data.
insertUser(db, { id: "1", first_name: "Chet", last_name: "Corcos", age: 31 });
insertUser(db, { id: "2", first_name: "Tanishq", last_name: "Kancharla", age: 22 });
console.log("Users inserted.");
// 5. Define a read query to get users by age.
function getUsersByAge(dbClient: TupleDatabaseClient<Schema>, minAge: number) {
return dbClient.scan({ prefix: ["userByAge", { age: minAge }], gte: ["userByAge", { age: minAge }] });
}
// 6. Execute a read query and log results.
const usersOver30 = getUsersByAge(db, 30);
console.log("Users with age >= 30:", usersOver30.map(entry => entry.key[2].id));
// For React integration:
// import { useTupleDatabase } from 'tuple-database/useTupleDatabase';
// function App({db}: {db: TupleDatabaseClient<Schema>}) {
// const oldestUser = useTupleDatabase(db, () => {
// const [oldestEntry] = db.scan({prefix: ["userByAge"], reverse: true, limit: 1});
// return oldestEntry ? db.get(["user", {id: oldestEntry.key[2].id}]) : null;
// }, []);
// return <div>The oldest user is age: {oldestUser?.age}</div>;
// }