{"id":17189783,"url":"https://github.com/synvox/sql","last_synced_at":"2025-04-13T19:24:52.416Z","repository":{"id":35071442,"uuid":"203281882","full_name":"Synvox/sql","owner":"Synvox","description":"sql is another sql template string library. Bring your own database client.","archived":false,"fork":false,"pushed_at":"2025-01-22T02:33:31.000Z","size":2481,"stargazers_count":1,"open_issues_count":6,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-22T19:01:47.385Z","etag":null,"topics":["nodejs","postgresql","query-builder","sql"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@synvox/sql","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/Synvox.png","metadata":{"files":{"readme":"README.md","changelog":null,"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":"2019-08-20T02:08:28.000Z","updated_at":"2025-01-06T03:20:47.000Z","dependencies_parsed_at":"2024-05-06T21:46:19.148Z","dependency_job_id":"a87f8314-74f0-4d08-8ac8-d65cc82857b6","html_url":"https://github.com/Synvox/sql","commit_stats":{"total_commits":32,"total_committers":2,"mean_commits":16.0,"dds":0.03125,"last_synced_commit":"96fca7844ef465344a4c5dec9193340f242546b6"},"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Synvox%2Fsql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Synvox%2Fsql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Synvox%2Fsql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Synvox%2Fsql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Synvox","download_url":"https://codeload.github.com/Synvox/sql/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248766835,"owners_count":21158330,"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":["nodejs","postgresql","query-builder","sql"],"created_at":"2024-10-15T01:12:31.278Z","updated_at":"2025-04-13T19:24:52.381Z","avatar_url":"https://github.com/Synvox.png","language":"TypeScript","readme":"# `@synvox/sql`\n\n**`sql` is another SQL template string library**\n\n```bash\nnpm i @synvox/sql\n```\n\n---\n\n## Setting up for your client\n\n`@synvox/sql` does not depend on a specific SQL client. You need to implement the `Pool`, `PoolClient`, and `Client` interfaces (exported from `@synvox/sql`) to use the library with the database drivers of your choice. Below is an example using `pg`:\n\n```ts\nimport { connect, Pool, PoolClient, Client } from \"@synvox/sql\";\nimport * as pg from \"pg\";\n\n// Example Pool implementation\nclass MyPool extends Pool {\n  client: pg.Pool;\n\n  constructor() {\n    super();\n    this.client = new pg.Pool();\n  }\n\n  query(query: string, values: any[]) {\n    return this.client.query(query, values);\n  }\n\n  async isolate() {\n    const pgClient = await this.client.connect();\n    return new MyPoolClient(pgClient);\n  }\n}\n\n// Example PoolClient implementation\nclass MyPoolClient extends PoolClient {\n  client: pg.PoolClient;\n\n  constructor(client: pg.PoolClient) {\n    super();\n    this.client = client;\n  }\n\n  query(query: string, values: any[]) {\n    return this.client.query(query, values);\n  }\n\n  async release() {\n    this.client.release();\n  }\n}\n\n// Example Client implementation (no pooling)\nclass MyClient extends Client {\n  client: pg.Client;\n\n  constructor() {\n    super();\n    this.client = new pg.Client();\n  }\n\n  async query(query: string, values: any[]) {\n    return this.client.query(query, values);\n  }\n}\n\n// Creating a connection using pooling:\nconst sql = connect(new MyPool());\n\n// or if you don't need pooling:\nconst sql2 = connect(new MyClient());\n```\n\n---\n\n## Executing Queries\n\n### `exec()`\n\nUse **`exec`** to run queries that do not need to return rows or for multi-statement execution:\n\n```ts\nawait sql`\n  create table example (\n    id serial primary key\n  )\n`.exec();\n```\n\n### `all()`\n\nUse **`all`** to get the rows returned from a query:\n\n```ts\nlet users = await sql`\n  select * from users\n`.all\u003cUser\u003e();\n```\n\n### `first()`\n\nUse **`first`** to get the first row returned from a query:\n\n```ts\nlet user = await sql`\n  select * from users\n`.first\u003cUser\u003e();\n```\n\n### `exists()`\n\nUse **`exists`** to quickly check if any rows match a condition:\n\n```ts\nlet runJobs = await sql`\n  select *\n  from jobs\n  where run_at \u003c now()\n  limit 1\n`.exists();\n```\n\n### `paginate()`\n\nUse **`paginate`** to limit and offset results:\n\n```ts\nlet users = await sql`\n  select * from users\n`.paginate\u003cUser\u003e({ page: 0, per: 100 });\n```\n\n_This wraps your query like:_\n\n```sql\nselect paginated.* from ( ... ) paginated limit ? offset ?\n```\n\n---\n\n## Composing Queries\n\n### Subqueries\n\nCompose queries together with subqueries:\n\n```ts\nlet subQuery = sql`select id from other_table`;\n\nawait sql`\n  select *\n  from table_name\n  where other_id in (${subQuery})\n`.all();\n```\n\n### Query Builders\n\n```ts\nawait sql`\n  insert into users ${sql.values({ name: \"Ryan\", active: false })}\n`.exec();\n// insert into users(name, active) values ($0, $1)\n// $0 = \"Ryan\"\n// $1 = false\n```\n\n```ts\nawait sql`\n  insert into users ${sql.values([\n    { name: \"Ryan\", active: false },\n    { name: \"Nolan\", active: false },\n  ])}\n`.exec();\n// insert into users(name, active) values ($0, $1), ($2, $3)\n// $0 = \"Ryan\"\n// $1 = false\n// $2 = \"Nolan\"\n// $3 = false\n```\n\n```ts\nawait sql`\n  update users\n  ${sql.set({ name: \"Ryan\", active: true })}\n  where id = ${1}\n`.exec();\n// update users set name = $0, active = $1 where id = $2\n// $0 = \"Ryan\"\n// $1 = true\n// $2 = 1\n```\n\n```ts\nawait sql`\n  select *\n  from users\n  where ${sql.and({ id: 1, active: true })}\n`.all();\n// select * from users where (id = $0 and active = $1)\n// $0 = 1\n// $1 = true\n```\n\n```ts\nawait sql`\n  select *\n  from users\n  where ${sql.or({ id: 1, active: true })}\n`.all();\n// select * from users where (id = $0 or active = $1)\n// $0 = 1\n// $1 = true\n```\n\n---\n\n### Arrays\n\nConvert arrays into a comma-separated list:\n\n```ts\nawait sql`\n  select *\n  from users\n  where id in (${sql.array([1, 2, 3])})\n`.exec();\n// select * from users where id in ($0, $1, $2)\n// $0 = 1\n// $1 = 2\n// $2 = 3\n```\n\n---\n\n### References\n\nUse **`sql.ref`** to reference a column or table:\n\n```ts\nawait sql`\n  select ${sql.ref(\"users.id\")}\n  from users\n`.all();\n```\n\n---\n\n### Join\n\nThe signature for `join` is:\n\n```ts\njoin(delimiter: SqlFragment, [first, ...rest]: SqlFragment[]): SqlFragment\n```\n\nSo, the **delimiter** is the first parameter, and the **array of fragments** is the second parameter:\n\n```ts\nawait sql`\n  select ${sql.join(sql`, `, [sql`users.id`, sql`users.name`])}\n  from users\n`.all();\n// select users.id, users.name from users\n```\n\n---\n\n### Literals\n\nUse **`sql.literal`** for direct literal insertion:\n\n```ts\nawait sql`\n  insert into points (location)\n  values (${sql.literal([100, 100])})\n`.exec();\n```\n\n---\n\n## Transactions\n\n`@synvox/sql` supports two transaction helpers:\n\n1. **`sql.transaction`** – Retries on deadlock. Use this for queries that affect only the database.\n2. **`sql.impureTransaction`** – Does **not** retry on deadlock by default. Use this if the transaction has side effects outside of the database.\n\n```ts\nawait sql.transaction(async (trxSql) =\u003e {\n  // Use trxSql like normal. Commit and rollback are handled for you.\n  // If an error is thrown, the transaction will be rolled back.\n});\n```\n\n---\n\n## Dedicated Connection\n\nUse **`sql.connection`** if you need a dedicated connection (via your `PoolClient`) for a set of queries:\n\n```ts\nawait sql.connection(async (sql) =\u003e {\n  // This block uses a dedicated connection.\n  await sql`select pg_sleep(1)`.exec();\n});\n// Connection is automatically released afterward.\n```\n\n---\n\n## Previewing Queries\n\nYou can preview a query string by calling **`preview`**:\n\n```ts\nlet migration = sql`\n  insert into users ${sql.values({ firstName: \"Ryan\" })}\n`.preview();\n\n// =\u003e insert into users (first_name) values ('Ryan')\n```\n\n---\n\n## Migrations\n\nIf you’ve installed the optional migration helpers (or have your own system), you can run migrations with something like:\n\n```ts\nimport { migrate } from \"@synvox/sql/migrate\"; // Hypothetical import\n\n// Where directoryName is the path to your migration files:\nawait migrate(sql, directoryName);\n```\n\nA migration file might look like:\n\n```ts\n// migrations/001-create-users.ts\n\nexport async function up(sql) {\n  await sql`\n    create table users (\n      id serial primary key,\n      first_name text not null,\n      last_name text not null\n    )\n  `.exec();\n}\n```\n\n\u003e **Note**: Migrations in `@synvox/sql` do not currently support “down” migrations.\n\n---\n\n## Seeds\n\nSimilarly, if you have the optional seed helpers:\n\n```ts\nimport { seed } from \"@synvox/sql/seed\"; // Hypothetical import\n\nawait seed(sql, directoryName);\n```\n\nA seed file might look like:\n\n```ts\n// seeds/001-insert-default-users.ts\n\nexport async function seed(sql) {\n  await sql`\n    insert into users ${sql.values({ first_name: \"Ryan\", last_name: \"Allred\" })}\n    on conflict do nothing\n  `.exec();\n}\n```\n\n---\n\n## Types\n\nIf you’re using the optional type generator:\n\n```ts\nimport { types } from \"@synvox/sql/types\"; // Hypothetical import\n\nawait types(sql, fileNameToWriteTypes, [\"schema_name\"]);\n```\n\nThis will generate a file containing interfaces for each table in the schema. If you omit `[\"schema_name\"]`, it will generate types for all schemas.\n\n---\n\n## Case Transforms\n\nBy default:\n\n- Rows from the database are **`camelCased`**.\n- Identifiers passed through the helper functions (like `sql.values` or `sql.set`) are **`snake_cased`** in the database.\n\nTo change this behavior, call `connect` with a `caseMethod` option:\n\n```ts\nconst sql = connect(new MyPool(), {\n  caseMethod: \"none\", // or 'snake' | 'camel'\n});\n```\n\n---\n\n## Debugging\n\nThis project uses the `debug` library. To see debug logs, run your app with:\n\n```bash\nDEBUG=sql:* node yourApp.js\n```\n\nYou’ll see logs for queries, transaction attempts, and errors.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsynvox%2Fsql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsynvox%2Fsql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsynvox%2Fsql/lists"}