{"id":14973940,"url":"https://github.com/lupennat/ludb","last_synced_at":"2026-02-08T20:34:16.217Z","repository":{"id":134648083,"uuid":"591327694","full_name":"Lupennat/ludb","owner":"Lupennat","description":"Nodejs Query Builder","archived":false,"fork":false,"pushed_at":"2024-09-25T11:37:13.000Z","size":1492,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-08T11:34:43.769Z","etag":null,"topics":["database","laravel","layer","lupdo","mariadb","mssql","mysql","pg","pgsql","query","query-builder","sqlite","sqlite3","sqlsrv"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Lupennat.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-01-20T13:44:56.000Z","updated_at":"2023-04-02T04:35:52.000Z","dependencies_parsed_at":"2024-01-02T01:30:26.437Z","dependency_job_id":"f13642c0-55b3-4540-bd03-fe74deaadd82","html_url":"https://github.com/Lupennat/ludb","commit_stats":{"total_commits":33,"total_committers":2,"mean_commits":16.5,"dds":"0.030303030303030276","last_synced_commit":"6ba28aeaa855202c2976019e5652a43d4b69312a"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lupennat%2Fludb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lupennat%2Fludb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lupennat%2Fludb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lupennat%2Fludb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Lupennat","download_url":"https://codeload.github.com/Lupennat/ludb/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238437653,"owners_count":19472456,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","laravel","layer","lupdo","mariadb","mssql","mysql","pg","pgsql","query","query-builder","sqlite","sqlite3","sqlsrv"],"created_at":"2024-09-24T13:49:42.801Z","updated_at":"2026-02-08T20:34:16.181Z","avatar_url":"https://github.com/Lupennat.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n\t\u003ca href=\"https://www.npmjs.com/package/ludb\" target=\"_blank\"\u003e\n        \u003cimg src=\"https://img.shields.io/npm/v/ludb?color=0476bc\u0026label=\" alt=\"NPM version\"\u003e\n    \u003c/a\u003e\n\t\u003ca href=\"https://www.npmjs.com/package/ludb\" target=\"_blank\"\u003e\n        \u003cimg alt=\"NPM Downloads\" src=\"https://img.shields.io/npm/dm/ludb?color=3890aa\u0026label=\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://app.codecov.io/github/Lupennat/ludb\" target=\"_blank\"\u003e\n        \u003cimg src=\"https://codecov.io/github/Lupennat/ludb/branch/main/graph/badge.svg?token=FOECLCWQ7F\"/\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://snyk.io/test/github/lupennat/ludb\" target=\"_blank\"\u003e\n        \u003cimg src=\"https://snyk.io/test/github/lupennat/ludb/badge.svg\"\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\n# Ludb (Beta Release)\n\n\u003e Ludb offers an (almost) identical api to [Laravel Database](https://laravel.com/docs/database).\n\n# Installation\n\n\u003e npm install ludb\n\nThe Ludb type definitions are included in the lucontainer npm package.\n\nYou also need to install lupdo-drivers needed:\n\n\u003e npm install lupdo-mssql lupdo-mysql lupdo-postgres lupdo-sqlite\n\n```ts\nimport { DatabaseManager } from 'ludb';\n```\n\n# Database: Getting Started\n\n-   [Introduction](#introduction)\n    -   [Configuration](#configuration)\n    -   [Query Builder](#query-builder)\n    -   [Schema Builder](#schema-builder)\n-   [Running SQL Queries](#running-sql-queries)\n    -   [Using Multiple Database Connections](#using-multiple-database-connections)\n    -   [Events](#events)\n    -   [Listening For Query Events](#listening-for-query-events)\n    -   [Monitoring Cumulative Query Time](#monitoring-cumulative-query-time)\n-   [Cache](#cache)\n-   [Database Transactions](#database-transactions)\n-   [Differences With Laravel](#differences-with-laravel)\n-   [Under The Hood](#under-the-hood)\n-   [Api](https://ludb.lupennat.com/api)\n\n## Introduction\n\nAlmost every modern web application interacts with a database. Ludb makes interacting with databases extremely simple across a variety of supported databases using raw SQL, a [fluent query builder](BUILDER.md). Currently, Ludb provides first-party support for five databases:\n\n-   MariaDB 10.3+ ([Version Policy](https://mariaconnection.org/about/#maintenance-policy))\n-   MySQL 5.7+ ([Version Policy](https://en.wikipedia.org/wiki/MySQL#Release_history))\n-   PostgreSQL 10.0+ ([Version Policy](https://www.postgresql.org/support/versioning/))\n-   SQLite 3.8.8+\n-   SQL Server 2017+ ([Version Policy](https://docs.microsoft.com/en-us/lifecycle/products/?products=sql-server))\n\n### Configuration\n\nIn the [configuration object](CONFIG.md), you may define all of your database connections, as well as specify which connection should be used by default.\n\n### Query Builder\n\nOnce you have configured your database connection, you may retrieve the [Query Builder](BUILDER.md) using the `DatabaseManager` connection.\n\n```ts\nimport { DatabaseManager } from 'ludb';\n\nconst DB = new DatabaseManager(config);\nconst connection = DB.connection(connectionName);\nconst query = connection.table('users');\n// or\nconst query = connection.query();\n```\n\n### Schema Builder\n\nOnce you have configured your database connection, you may retrieve the [Schema Builder](SCHEMA.md) using the `DatabaseManager` connection.\n\n```ts\nimport { DatabaseManager } from 'ludb';\n\nconst DB = new DatabaseManager(config);\nconst connection = DB.connection(connectionName);\nconst Schema = connection.getSchemaBuilder();\n```\n\n## Running SQL Queries\n\nOnce you have configured your database connection, you may run queries using the `DatabaseManager` connection.\n\n```ts\nimport { DatabaseManager } from 'ludb';\n\nconst DB = new DatabaseManager(config);\nconst connection = DB.connection(connectionName);\n```\n\n#### Running A Select Query\n\nTo run a basic SELECT query, you may use the `select` method on the `connecttion`:\n\n```ts\nusers = await connection.select('select * from users where active = ?', [1]);\n```\n\nThe first argument passed to the `select` method is the SQL query, while the second argument is any parameter bindings that need to be bound to the query. Typically, these are the values of the `where` clause constraints. Parameter binding provides protection against SQL injection.\n\nThe `select` method will always return an `array` of results. Each result within the array will be an object representing a record from the database:\n\n```ts\ninterface User {\n    name: string;\n}\nusers = await connection.select\u003cUser\u003e('select * from users where active = ?', [1]);\nfor (const user of users) {\n    console.log(user.name);\n}\n```\n\n#### Selecting Scalar Values\n\nSometimes your database query may result in a single, scalar value. Instead of being required to retrieve the query's scalar result from a record object, Ludb allows you to retrieve this value directly using the `scalar` method:\n\n```ts\nburgers = await connection.scalar(\"select count(case when food = 'burger' then 1 end) as burgers from menu\");\n```\n\n#### Using Named Bindings\n\nInstead of using `?` to represent your parameter bindings, you may execute a query using named bindings:\n\n```ts\nresults = await connection.select('select * from users where id = :id', { id: 1 });\n```\n\n#### Running An Insert Statement\n\nTo execute an `insert` statement, you may use the `insert` method on the `connecttion`. Like `select`, this method accepts the SQL query as its first argument and bindings as its second argument:\n\n```ts\nawait connection.insert('insert into users (id, name) values (?, ?)', [1, 'Marc']);\n```\n\n#### Running An Update Statement\n\nThe `update` method should be used to update existing records in the database. The number of rows affected by the statement is returned by the method:\n\n```ts\naffected = await connection.update('update users set votes = 100 where name = ?', ['Anita']);\n```\n\n#### Running A Delete Statement\n\nThe `delete` method should be used to delete records from the database. Like `update`, the number of rows affected will be returned by the method:\n\n```ts\ndeleted = await connection.delete('delete from users');\n```\n\n#### Running A General Statement\n\nSome database statements do not return any value. For these types of operations, you may use the `statement` method on the `connecttion`:\n\n```ts\nawait connection.statement('drop table users');\n```\n\n#### Running An Unprepared Statement\n\nSometimes you may want to execute an SQL statement without binding any values. You may use the `connecttion` `unprepared` method to accomplish this:\n\n```ts\nawait connection.unprepared('update users set votes = 100 where name = \"Dries\"');\n```\n\n\u003e **Warning**  \n\u003e Since unprepared statements do not bind parameters, they may be vulnerable to SQL injection. You should never allow user controlled values within an unprepared statement.\n\n#### Implicit Commits\n\nWhen using the `connecttion` `statement` and `unprepared` methods within transactions you must be careful to avoid statements that cause [implicit commits](https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html). These statements will cause the database engine to indirectly commit the entire transaction, leaving Ludb unaware of the database's transaction level. An example of such a statement is creating a database table:\n\n```ts\nawait connection.unprepared('create table a (col varchar(1) null)');\n```\n\nPlease refer to the MySQL manual for [a list of all statements](https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html) that trigger implicit commits.\n\n### Bindings Caveat\n\nLudb and Lupdo can detect the right type of binded value through the Javascript type of a variable, but SqlServer Ludpo driver need to know the exact type of the database column to make an insert or an update, and in some case it can fail (for instance when a binded value is `null`, or when you are working with time or date).\\\nYou can bypass the problem using the `TypedBinding` object of Lupdo; Ludb make it super easy to implement it providing a complete set of TypedBinding through `bindTo` Api, an example:\n\n```ts\nawait connection.insert('insert into users (id, name, nullablestring) values (?, ?)', [1, 'Marc', connection.bindTo.string(null)]);\n```\n\n### Using Multiple Database Connections\n\nIf your application defines multiple connections in your configuration object, you may access each connection via the `connection` method provided by the `connecttion`. The connection name passed to the `connection` method should correspond to one of the connections listed in your configuration:\n\n```ts\nimport { DatabaseManager } from 'ludb';\n\nconst DB = new DatabaseManager(config);\nconst connection = DB.connection('sqlite').select(/* ... */);\n```\n\nYou may access the raw, underlying Lupdo instance of a connection using the `getPdo` method on a connection instance:\n\n```ts\npdo = connection.connection('connectionName').getPdo();\n```\n\n### Events\n\nLudb emit an event for each query executed, the `QueryExecuted` event instance expose 6 properties:\n\n-   connection: the `ConnectionSession` instance who generate the query\n-   sql: the sql executed\n-   bindings: the bindings of the query executed\n-   time: time of execution in milliseconds\n-   sessionTime: total time of session execution in millisecond\n-   inTransaction: the sql executed is in a transaction\n\nWhen a query is executed in a transaction, all the query executed inside a committed transaction will generate two Event, the first one will have the property `inTransaction` true, the second will be emitted only after the commit will have property `inTransaction` false.\n\nLudb emit an event every time a Lupdo Statement is prepared, the `StatementPrepared` event instance expose 2 properties:\n\n-   connection: the `ConnectionSession` instance who generate the query\n-   statement: the Lupdo Statement\n\nLupdo emit 4 event when a transaction is used, every transaction event expose only the connection property.\n\n-   TransactionBeginning\n-   TransactionCommitted\n-   TransactionCommitting\n-   TransactionRolledBack\n\n### Listening For Query Events\n\nIf you would like to specify a closure that is invoked for each SQL query executed by your application, you may use the `connecttion` `listen` method. This method can be useful for logging queries or debugging.\n\n```ts\nimport { DatabaseManager } from 'ludb';\n\nconst DB = new DatabaseManager(config);\nconst connection = DB.connection('sqlite').listen(query =\u003e {\n    // query.sql;\n    // query.bindings;\n    // query.time;\n});\n```\n\nYou can also detach a listener using `connecttion` `unlisten` method:\n\n```ts\nimport { DatabaseManager } from 'ludb';\n\nconst DB = new DatabaseManager(config);\n\nconst TempListener = query =\u003e {\n    // query.sql;\n    // query.bindings;\n    // query.time;\n    DB.connection('sqlite').unlisten(TempListener);\n};\n\nconst connection = DB.connection('sqlite').listen(TempListener);\n```\n\nBy Default `DatabaseManager` will use an `EventEmitter` instance to manage events. You can provide a custom instance of EventEmitter through constructor.\n\n```ts\nimport { DatabaseManager } from 'ludb';\nimport EventEmitter from 'node:events';\n\nconst emitter = new EventEmitter();\n\nconst DB = new DatabaseManager(config, emitter);\nconst connection = DB.connection('sqlite').listen(query =\u003e {\n    // query.sql;\n    // query.bindings;\n    // query.time;\n});\n```\n\n### Monitoring Cumulative Query Time\n\nA common performance bottleneck of modern web applications is the amount of time they spend querying databases.\nThe `connecttion` `listen` method can be helpful to make any kind of monitoring. An example of monitoring single query time execution:\n\n```ts\nDB.connection('sqlite').listen(query =\u003e {\n    if (query.time \u003e 500 \u0026\u0026 !query.inTransaction) {\n        console.log('warning');\n    }\n});\n```\n\nAn example of monitoring a session query time execution (all transaction queries are executed in a single session):\n\n```ts\nDB.connection('sqlite').listen(query =\u003e {\n    if (query.sessionTime \u003e 500 \u0026\u0026 !query.inTransaction) {\n        console.log('warning');\n    }\n});\n```\n\nSometimes you want to know when your application spends too much time querying the database during a single request. An example with Expressjs\n\n```ts\nimport express, { Express, Request, Response, NextFunction } from 'express';\nimport { DatabaseManager, QueryExecuted } from 'ludb';\n\nconst DB = new DatabaseManager(config);\nconst app: Express = express();\n\nconst beforeMiddleware = (req: Request, res: Response, next: NextFunction) =\u003e {\n    let totalTime = 0;\n    let hasRun = false;\n    const queryExecuted = [];\n    req.referenceQueryId = 'uniqueid-for-req';\n    req.queryLogListener = (event: QueryExecuted) =\u003e {\n        if (event.referenceId ===  req.referenceQueryId \u0026\u0026 !hasRun \u0026\u0026 !event.inTransaction) {\n            totalTime += event.time;\n            queryExecuted.push(event);\n\n            if (totalTime \u003e 500) {\n                hasRun = true;\n                console.log('warning', queryExecuted);\n            }\n        }\n    };\n    DB.connection('connectionName').listen(req.queryLogListener);\n    next();\n};\n\nconst responseHandler = (req: Request, res: Response, next: NextFunction) =\u003e {\n    // do stuff with database using reference\n    // DB.connection('connectionName').reference(req.referenceQueryId).select(...)\n    res.status(200).send({ response: 'ok' });\n    next();\n};\n\nconst afterMiddleware = (req: Request, res: Response, next: NextFunction) =\u003e {\n    DB.connection('connectionName').unlisten(req.queryLogListener);\n    next();\n};\n\napp.get('/', beforeMiddleware, responseHandler, afterMiddleware);\n```\n\n\n## Cache\n\nLudb support caching queries for select operations `selectOne`, `scalar`, `selectFromWriteConnection` and `select`, [here](CACHE.md) you can find more information about caching \n\n## Database Transactions\n\nYou may use the `transaction` method provided by the `connecttion` to run a set of operations within a database transaction. If an exception is thrown within the transaction closure, the transaction will automatically be rolled back and the exception is re-thrown. If the closure executes successfully, the transaction will automatically be committed. You don't need to worry about manually rolling back or committing while using the `transaction` method:\n\n```ts\nawait connection.transaction(async session =\u003e {\n    await session.update('update users set votes = 1');\n\n    await session.delete('delete from posts');\n});\n```\n\n\u003e **Warning**  \n\u003e Since Transaction will generate a new session you should always use the ConnectioSession provided as first parameter of callback. Query executed on default connection will do not be exectued within the transaction.\n\n#### Handling Deadlocks\n\nThe `transaction` method accepts an optional second argument which defines the number of times a transaction should be retried when a deadlock occurs. Once these attempts have been exhausted, an exception will be thrown:\n\n```ts\nawait connection.transaction(async session =\u003e {\n    await session.update('update users set votes = 1');\n\n    await session.delete('delete from posts');\n}, 5);\n```\n\n#### Manually Using Transactions\n\nIf you would like to begin a transaction manually and have complete control over rollbacks and commits, you may use the `beginTransaction` method provided by the `connecttion`:\n\n```ts\nsession = await connection.beginTransaction();\n```\n\nYou can rollback the transaction via the `rollBack` method:\n\n```ts\nsession.rollBack();\n```\n\nLastly, you can commit a transaction via the `commit` method:\n\n```ts\nsession.commit();\n```\n\n\u003e **Warning**  \n\u003e Since Transaction will generate a new session you should always use the ConnectioSession returned by `beginTransacion`. Query executed on default connection will do not be executed within the transaction.\n\n## Differences With Laravel\n\n-   The `DatabaseManager` instance do not proxy methods to default connection, you always need to call `connection(name)` method to access method of `Connection`.\n-   The `DatabaseManager` do not expose functionality to extend registered drivers.\n-   Methods `whenQueryingForLongerThan` and `allowQueryDurationHandlersToRunAgain` do not exist, [Monitoring Cumulative Query Time](#monitoring-cumulative-query-time) offer a valid alternative.\n-   Methods `getQueryLog` and `getRawQueryLog` do not exist, logging query is used only internally for `pretend` method.\n-   Methods `beginTransaction` and `useWriteConnectionWhenReading` return a `ConnectionSession` you must use the session instead the original connection for the queries you want to execute them within the transaction or against the write pdo.\n-   Callbacks for methods `transaction` and `pretend` are called with a `ConnectionSession` you must use the session instead the original connection inside the callback if you want to execute the queries within the transaction or to pretend the execution.\n-   Query Builder return `Array` instead of `Collection`\n-   Connection Method `selectResultSets` is not supported.\n\n## Under The Hood\n\nLudb use [Lupdo](https://www.npmjs.com/package/lupdo) an abstraction layer used for accessing databases.\\\nWhen the nodejs application start and a connection is required from `DatabaseManager` only the first time Ludb generate the pdo connection and it store internally the pdo required for the specific connection.\\\nEveryTime a method that require a builder is invoked within the connection by the user, a new `ConnectionSession` will be initialized and provided to the builder.\\\nThe `ConnectionSession` expose almost all the api exposed by the original `Connection` and is completly \"hidden\" for the user the switch between sessions and connection.\\\nLudb will require a connection from the pool only when a method of `ConnectionSession` require to comunicate with the database, everytime the request is completed the connection will be released to the pool, and the `ConnectionSession` is burned.\\\nFor this reason when methods `transaction`, `beginTransaction`, `pretend` and `useWriteConnectionWhenReading` are called Ludb return the `ConnectionSession` to the user and the user must use the session provided to execute next queries.\n\nLudb will generate 1 or 2 pdo for [Query Builder](#query-bulder) (it depends on write/read configuration) and 1 pdo for [Schema Builder](#schema-builder).\\\nThe Schema Builder Pdo force the Lupdo pool to have only 1 connection, this is necessary to ensure the proper functioning of the exposed Api (temporary tables, for instance, are only visible for the connection that generated them).\n\n### Examples\n\nAn example of `transaction` method:\n\n```ts\nimport { DatabaseManager } from 'ludb';\n\nconst DB = new DatabaseManager(config);\nconst connection = DB.connection(connectionName);\n\nawait connection.transaction(async session =\u003e {\n    await session.update('update users set votes = 1');\n\n    await session.delete('delete from posts');\n});\n\nconst users = await connection.table('users').get();\n```\n\nAn example of `beginTransaction` method:\n\n```ts\nimport { DatabaseManager } from 'ludb';\n\nconst DB = new DatabaseManager(config);\nconst connection = DB.connection(connectionName);\nconst session = await connection.beginTransaction();\ntry {\n    await session.update('update users set votes = 1');\n    await session.delete('delete from posts');\n    await session.commit();\n} catch (error) {\n    await session.rollBack();\n}\n\nconst users = connection.table('users').get();\n```\n\nAn example of `pretend` method:\n\n```ts\nimport { DatabaseManager } from 'ludb';\n\nconst DB = new DatabaseManager(config);\nconst connection = DB.connection(connectionName);\nconst queries = await connection.pretend(async session =\u003e {\n    await session.table('users').get();\n    await session.table('posts').get();\n});\nconsole.log(queries);\nconst users = connection.table('users').get();\n```\n\nAn example of `useWriteConnectionWhenReading` method:\n\n```ts\nimport { DatabaseManager } from 'ludb';\n\nconst DB = new DatabaseManager(config);\nconst connection = DB.connection(connectionName);\nawait connection.table('users').where('id', 10).update({ name: 'Claudio' });\nconst session = connection.useWriteConnectionWhenReading();\nconst userFromWrite = session.table('users').find(10);\nconst userFromRead = connection.table('users').find(10);\n```\n\nAn example of `temporary` with Schema:\n\n```ts\nimport { DatabaseManager } from 'ludb';\nimport * as crypto from 'crypto';\n\nconst DB = new DatabaseManager(config);\nconst connection = DB.connection(connectionName);\nconst Schema = connection.getSchemaBuilder();\n\nawait Schema.table('orders', table =\u003e {\n    table.string('hash_id').index();\n});\n\nawait Schema.create('temp_mappings', table =\u003e {\n    table.temporary();\n    table.integer('id').primary();\n    table.string('hash_id');\n});\n\nconst connection = Schema.getConnection();\n\n// insert mappings in 10K chunks\nawait connection.table('orders').chunkById(1000, async orders =\u003e {\n    const values = orders\n        .map(order =\u003e {\n            const hash = crypto.createHash('sha1').update(order.id).digest('hex');\n            /* Prepare the value string for the SQL statement */\n            return `(${order.id}, '${hash}')`;\n        })\n        .join(',');\n\n    await connection.insert(connection.raw(`INSERT INTO temp_mappings(id, reference) VALUES ${values}`));\n});\n\nawait connection\n    .table('orders')\n    .join('temp_mappings', 'temp_mappgings.id', 'orders.id')\n    .update({ hash_id: connection.raw('temp_mappings.hash_id') });\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flupennat%2Fludb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flupennat%2Fludb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flupennat%2Fludb/lists"}