Save the overhead working with SQL and structured data - from the time and effort spent figuring out relational queries to the labour managing schemas! Try a modern, simplistic take on SQL and databases in general!
Linked QL is a simplistic database abstraction for modern apps that ridiculously streamlines the amount of SQL you write and the schema management work you do.
What does it do at a high level?
π₯ Takes the ORM and friends out of the way and let's you write actual SQL, but this time, beautiful and streamlined SQL that you will actually enjoy. (Linked QL extends standard SQL with new syntax sugars that let you write relational queries in less than half the code.)
β‘οΈ Takes the process out of schema management and lets you just ALTER away your DB, but with automatic schema versioning happening behind the scenes.
π₯ Brings the "schema-as-code" philosophy to its true practice wherein you are able to manage your entire DB structure out of a single schema.json (or schema.yml) file.
Linked QL comes as a small library and is usable over your DB of choice - from the server-side Postgres, mariadb and MySQL, to the client-side IndexedDB, and the in-memory plain JSON object!
Obtain the Linked QL client for your target database:
For SQL databases, install the regular SQL client you use for your DB. (Typically, pg for PostgreSQL, mariadb for mariadb, mysql/mysql2 for MySQL databases.)
Using PostgreSQL as an example, install the pg client:
npm install pg
Use Linked QL as a wrapper over that:
// Import pg and LinkedQlimport pg from'pg';
importLinkedQlfrom'@linked-db/linked-ql/sql';
// Connect pgconst pgClient = new pg.Client({
host: 'localhost',
port: 5432,
});
await pgClient.connect();
// Use LinkedQl as a wrapper over thatconst client = newLinkedQl(pgClient, { dialect: 'postgres' });
See also: sample setup for mariadb
Note that your mariadb database must be v10.5.2 or higher. (MySQL v8 comparably.) In addition, Linked QL needs to be able to run multiple statements in one query. The multipleStatements connector parameter below is thus required. We also need to have the bitOneIsBoolean parameter in place.
// Import mariadb and LinkedQlimport mariadb from'mariadb';
importLinkedQlfrom'@linked-db/linked-ql/sql';
// Connect pgconst myConnection = await mariadb.createConnection({
host: '127.0.0.1',
user: 'root',
port: 3306,
// -------multipleStatements: true, // RequiredbitOneIsBoolean: true, // The default, but requiredtrace: true, // Recommended
});
// Use LinkedQl as a wrapper over thatconst client = newLinkedQl(myConnection, { dialect: 'mysql' });
For the client-side IndexedDB, import and instantiate the IDB client. (Coming soon)
// Import IDB as LinkedQlimportLinkedQlfrom'@linked-db/linked-ql/idb';
// Create an instance.const client = newLinkedQl;
To work with Linked QL's in-memory object database, import and instantiate the ODB client. (Coming soon)
// Import ODB as LinkedQlimportLinkedQlfrom'@linked-db/linked-ql/odb';
// Create an instance.constLinkedQlClient = newLinkedQl;
All client instances above implement the same interface:
client.query('SELECT fname, lname FROM users WHERE role = $1', { values: ['admin'] }).then(result => {
console.log(result);
});
const result = await client.query('SELECT fname, lname FROM users WHERE role = $1', { values: ['admin'] });
console.log(result);
This API and more are covered right in the API area.
By design, you are able to choose between running raw SQL using client.query() and running equivalent statements using APIs like client.createDatabase(), client.alterDatabase(), client.dropDatabase(), database.createTable(), database.alterTable(), database.dropTable(), table.select(), table.insert(), table.upsert(), table.update(), table.delete(), etc. (All as covered in the API area.)
β¨ Now, that's like: whatever your query style or usecase, there's a thing in Linked QL for you!
Introducing Magic Paths
π₯ Express relationships graphically! You shouldn't always have to write JOINS!
Meet Linked QL's magic path operators, a syntax extension to SQL, that lets you connect to columns on other tables without writing a single JOIN clause. Linked QL uses heuristics on your DB structure to figure out the details and the relevant JOINS behind the scenes.
Where you normally would write...
-- Regular SQLSELECT title, users.fname AS author_name FROM posts
LEFTJOIN users ON users.id = posts.author
Linked QL lets you draw a path to express the relationship:
-- Linked QLSELECT title, author ~> fname AS author_name FROM posts
And here's a scenario showing a typical schema and an example query each:
-- The users tableCREATETABLE users (
id intprimary key generated always asidentity,
title varchar,
name varchar,
role intreferences roles (id),
created_time timestamp
);
-- The books tableCREATETABLE books (
id intprimary key generated always asidentity,
title varchar,
content varchar,
author intreferences users (id),
created_time timestamp
);
-- Regular SQLSELECT book.id, book.title, content, book.created_time, user.id AS author_id, user.title AS author_title, user.name AS author_name
FROM books AS book LEFTJOIN users ASuserON user.id = book.author
-- Linked QLSELECT id, title, content, created_time, author ~> id, author ~> title, author ~> name
FROM books
β¨ Now, that translates to about 50% code, plus whole namespacing exercise, having been eliminated! Yet, no questions asked about your schema, and none of the usual upfront relationship mapping!
Taking things further, you are able to chain these operators to any level for your multi-level relationships:
-- Linked QLSELECT*FROM books
WHERE author ~> role ~> codename ='admin'
and for the different forms of relationships out there (one-to-many, many-to-one, many-to-many), path operators can go in any direction:
-- Linked QLSELECT*FROM users
WHERE author <~ books ~> title ='Beauty and the Beast'
Plus, with Linked QL being a superset of SQL, you can combine the new magic together with the old LEFT JOIN/RIGHT JOIN/etc clauses with zero implications:
-- Linked QLSELECT users.*FROM users, some_other_table.id
LEFTJOIN some_other_table USING some_other_condition
WHERE author <~ books ~> title ='Beauty and the Beast'
giving you just the right tool for the job in every scenario: the regular JOINS for whatever calls for them; magic paths for when the very JOINS are an overkill!
β¨ We think this will make a lot of your tooling and manual work around SQL obsolete and your codebase saner! You essentially get back SQL - and with it, a dose of magic!
Introducing Auto-Versioning
β‘οΈ Create, alter, and drop schemas without needing to worry about versioning.
You may be doing too much!
Databases have historically lacked the concept of versioning, and that has seen all of the engineering work pushed down to the client application. If you've ever had to adopt a special process for defining and managing your schemas, wherein changes are handled through specially-named, chronologically-ordered files within your application...
then you've faced the problem that this defeciency in databases creates!
Meet Linked QL's Automatic Schema Savepoint and Rollback feature - a little addition to your database that does the heavy-lifting of schema versiong at the database level!
Here, you alter your schema and get back a reference to a "savepoint" automatically created for you:
// Alter schemaconst savepoint = await client.query('CREATE TABLE public.users (id int, name varchar)', {
description: 'Create users table',
});
// As an axample of what you see:console.log(savepoint.description); // Create users tableconsole.log(savepoint.versionTag); // 1console.log(savepoint.savepointDate); // 2024-07-17T22:40:56.786Z// Or to see everything:console.table(savepoint.toJSON());
You're also able to access the same savepoint on-demand using the database.savepoint() API:
β¨ Now, that's a go-ahead to alter your DB carefree! But this time, in a safety net!
Taking that further, you also get a way to roll forward from a rollback state! (Much like hitting "Redo" to reverse a certain "Undo").
This time, on calling database.savepoint(), you indicate that you want a "forward" movement from your current point in time:
// "Undo" the last rollback (Gets the users table re-created)let savepoint = await client.database('public').savepoint({ direction: 'forward' });
await savepoint.rollback();
You essentially get time travel in any direction - and as seamlessly as you move on a movie track!
β¨ Meanwhile, your schema histories now live as data (instead of as files), making them queryable, analyzable, and even visualizable, just as regular data! Plus, the DB now essentially becomes the absolute source of truth for both itself and its client applications!
Re-Introducing Schema-as-Code with schema.json
π₯ Have your entire DB structure live in a single schema.json (or schema.yml) file that you edit in-place!
With schema versioning now happening at the database level, the whole concept of database migrations at the application level should also change: no need to keep a growing list of migration files just to maintain past states! We found that you could essentially streamline you whole "database" footprint to fit in a single schema.json (or schema.yml) file!
If you had that somewhere in your application, say at ./database/schema.json, Linked QL could help keep it in sync both ways with your database:
you add or remove a database object or table object or column object... and it is automatically reflected in your DB structure at the click of a command: linkedql commit
your colleague makes new changes from their codebase... and it is automatically reflected in your local copy at your next git pull, or at the click of a command: linkedql refresh
β‘οΈ You also get to see a version number on each database object in your schema essentially incrementing on each migrate operation (whether by you or by colleague), and decrementing on each rollback operation (whether by you or by colleague).
To setup:
Make a directory within your application for database concerns. Linked QL will want to look in ./database, but you will be able to point to your preferred location when running Linked QL commands.
Have a driver.js file in that directory that has a default export function that returns a Linked QL instance. This will be imported and used by Linked QL to interact with your database. This could look something like:
Have your DB structure defined in a schema.json (or schema.yml) file in that directory. (See schema.json above for a guide.)
Now, you can always extend your DB structure with new objects, drop existsing ones, or edit them in-place. Only, for an existing database, table, column, constraint, or index, names may be changed, but not in-place! A "rename" operation is done with the addition of a temporary $name attribute:
{
"name": "old_name",
"$name": "new_name"
}
The old name being in place is needed to find the target during migration. The temporary $name attribute automatically disappears after new name has been picked up by Linked QL at next linkedql commit.
To run:
Use linkedql commit to walk through your staged local changes and interactively perform a migration against your database.
Use linkedql rollback to walk through the latest savepoint at each database and interactively perform a rollback.
Use linkedql state to just view the state of each database.
A query client that extends standard SQL with new syntax sugars and enables auto-versioning capabilities on any database
We found that @linked-db/linked-ql demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Β It has 0 open source maintainers collaborating on the project.
Package last updated on 19 Sep 2024
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Ransomware payment rates hit an all-time low in 2024 as law enforcement crackdowns, stronger defenses, and shifting policies make attacks riskier and less profitable.