const db = new PGClient(); // or: MySQLClient | FlashQL | EdgeClient | etc const result = await db.query( `SELECT { id, profile: { name, email }, parent: parent_user ~> { name, email } } FROM users;`, { live: true } );
Show result: output shape + live behaviour
// Structured output (via "{ ... }"): result.rows[0].profile.name; // Foreign key traversal (via "~>"): result.rows[0].parent.name; // Live queries: // result.rows updates automatically as underlying data changes // (any reactive system can observe these updates) Observer.observe(result.rows[0].parent, 'email', (c) => { console.log(c.value, c.oldValue); });
LinkedQL brings:
- live queries, streaming, changefeeds, and sync
- expressive shorthands for relationships and JSON
- automatic schema versioning and query-time version safety
Runs across:
- PostgreSQL, MySQL/MariaDB, and embedded/local storage
- server, browser, edge, and worker runtimes
- both local and remote data as one relational graph
→ All in under 80 KiB (min+zip)
→ A single interface that drops into any application
One SQL interface for local, remote, and live queries
See capabilities ↗ for the full picture.
Important
LinkedQL is backed by 1,000+ tests and growing.
Feedback, issues, and PRs help drive the next 1,000.
See Contributing
LinkedQL is distributed as an npm package:
npm install @linked-db/linked-qlIt provides clients for all supported SQL dialects — including FlashQL, the embeddable SQL engine for local and offline use.
Import and use the client for your database.
| Client/Model | Import Path | Guide |
|---|---|---|
PGClient |
@linked-db/linked-ql/postgres |
PostgreSQL ↗ |
MySQLClient |
@linked-db/linked-ql/mysql |
MySQL ↗ |
MariaDBClient |
@linked-db/linked-ql/mariadb |
MariaDB ↗ |
FlashQL |
@linked-db/linked-ql/flashql |
FlashQL ↗ |
EdgeClient |
@linked-db/linked-ql/edge |
Edge / Browser ↗ |
EdgeWorker |
@linked-db/linked-ql/edge-worker |
Edge Worker ↗ |
See:
LinkedQL exposes a minimal and consistent database interface:
await db.query(sql, options);
await db.query(sql, { live: true, ...options });
await db.stream(sql, options);
await db.transaction(fn);
await db.wal.subscribe(selector, handler);
await db.sync.sync(); // (FlashQL)The same surface applies whether db is a direct PostgreSQL client, a local FlashQL engine, or an EdgeClient.
See:
LinkedQL collapses the traditional data stack — database, API layer, and sync engine — into a single SQL primitive that drops directly into your application.
The Capabilities page is your map to what LinkedQL enables.
Below are a few examples that build up that model step by step.
JSON Literals let you define the exact shape your application expects directly in SQL.
const result = await db.query(`
SELECT {
id: u.id,
name: u.name,
profile: {
email: u.email,
age: u.age
}
} AS user
FROM users u;
`);No remapping step or post-processing code. The query itself defines the shape.
This is fully covered in JSON Literals ↗
DeepRefs let you follow relationships directly in SQL using simple arrow notations.
| Notation | Meaning |
|---|---|
~> |
forward traversal (follow a reference) |
<~ |
reverse traversal (find dependents) |
const result = await db.query(`
SELECT
id,
parent_user ~> email AS parent_email
FROM users;
`);This says:
→ "Given a foreign key (parent_user)"
→ "Tie in the referenced row; select email"
const result = await db.query(`
SELECT
id,
(parent_user <~ users) ~> email AS child_email
FROM users;
`);This walks “backwards” through a relationship:
- Ties in rows that reference the current row
- Selects the
emailcolumn from each
const result = await db.query(`
SELECT {
id,
profile: { name, email },
parent: parent_user ~> { id, name, email }
} FROM users;
`);- Lets you model structures deeply
- Swaps complex alias bookkeeping with clear mental models
await db.query(`
INSERT INTO users
(email, parent_user ~> (id, email))
VALUES
('ada@example.com', ROW (50, 'parent@example.com'));
`);This lets you construct relationships directly in an insert:
→ "Given a base row"
→ "Insert a related row that automatically references the base"
No need for an ORM or manual JOIN logic.
If you've defined foreign key relationships in your tables, you can traverse them directly in the query.
Deeper syntax and traversal patterns are fully covered in DeepRefs ↗.
LinkedQL brings live queries to your database: PostgreSQL, FlashQL, MySQL/MariaDB*.
With just a mode switch { live: true }, you get back a live, self-updating result set.
const result = await db.query(`
SELECT p.title, p.category
FROM posts AS p
WHERE p.published = true
ORDER BY p.created_at DESC
`, { live: true });result.rows updates automatically as the database changes:
- new rows appear
- removed rows disappear
- updated rows mutate in place
No need for dedicated GraphQL servers in front of your database.
The query itself is the subscription.
const result = await db.query(`
SELECT
p.title,
p.category,
author ~> { name, email } AS author
FROM posts AS p
WHERE p.published = true
ORDER BY p.created_at DESC
`, { live: true });→ Works with joins, filters, aggregates, and other constructs
→ Supports the full set of LinkedQL syntax shorthands like DeepRefs and JSON Literals
See the full story in Live Queries ↗.
See the Realtime Engine ↗ paper for a deeper dive.
Note
Fully supported across:
- databases: PostgreSQL, FlashQL, etc. (MySQL/MariaDB support coming soon)
- runtimes and deployment models: client / server / worker / edge
Meet FlashQL – a full SQL engine that runs anywhere + inside your application.
It's LinkedQL's embeddable SQL engine.
import { FlashQL } from '@linked-db/linked-ql/flashql';
const db = new FlashQL();
await db.connect();
const result = await db.query(`
CREATE TABLE users (
id INT PRIMARY KEY,
name TEXT
);
INSERT INTO users VALUES (1, 'Ada'), (2, 'Linus');
SELECT * FROM users ORDER BY id;
`);
console.log(result.rows);
await db.disconnect();FlashQL brings the full LinkedQL feature set into an embedded, local-first runtime.
Built for:
- local-first and offline-first architectures
- data federation and sync across local/remote boundaries
See FlashQL ↗ for a detailed overview.
LinkedQL uses a small set of primitives (EdgeClient, FlashQL) to unlock data federation, sync, and offline-first architectures.
For example:
You can spin up a FlashQL instance locally, backed by an upstream database.
const db = new FlashQL({
getUpstreamClient: (url) =>
new EdgeClient({ url, type: 'http' }),
});
await db.connect();It lets you declare remote data as local tables (views):
await db.query(`
CREATE VIEW users AS
SELECT * FROM users
WITH (replication_origin = 'postgres:/api/db');
`);This simple setup gives you data federation across boundaries:
query both local and remote data as one relational graph:
const result = await db.query(`
SELECT *
FROM users u
JOIN orders o ON o.user_id = u.id;
`);With just an extra keyword, you get automatic sync between local and remote states:
await db.query(`
CREATE REALTIME VIEW users AS
SELECT * FROM users
WITH (replication_origin = 'postgres:/api/db');
`);- Upstream changes apply automatically
- Local changes sync back upstream
With a single abstraction:
- remote data is materialized locally as tables
- queries span local and remote sources seamlessly
- changes sync in both directions automatically
- your app continues to work offline and on reconnects
- no separate sync engine, API layer, or replication pipeline
What typically takes a database, API layer, cache, and sync engine is reduced to one relational model — expressed entirely in SQL.
This is fully covered in:
If you want to explore the full LinkedQL model, see:
-
Capabilities overview → an easy map of LinkedQL
-
FlashQL and local-first architecture → embedded engine, federation, and sync
-
Integration patterns → how this fits into real applications
-
Core API → the full interface surface
-
Or go from the start:
LinkedQL is in active development — and contributions are welcome!
Here’s how you can jump in:
- Issues → Spot a bug or have a feature idea? Open an issue.
- Pull requests → PRs are welcome for fixes, docs, or new ideas.
- Discussions → Not sure where your idea fits? Start a discussion.
⤷ clone → install → test
git clone https://github.com/linked-db/linked-ql.git
cd linked-ql
git checkout next
npm install
npm test- Development happens on the
nextbranch — be sure to switch to it as above after cloning. - Consider creating your feature branch from
nextbefore making changes (e.g.git checkout -b feature/my-idea). - Remember to
npm testbefore submitting a PR. - Check the Progress section above to see where help is most needed.
MIT — see LICENSE