{"id":13452108,"url":"https://github.com/gajus/slonik","last_synced_at":"2026-06-05T20:00:59.976Z","repository":{"id":37587549,"uuid":"86057635","full_name":"gajus/slonik","owner":"gajus","description":"A Node.js PostgreSQL client with runtime and build time type safety, and composable SQL.","archived":false,"fork":false,"pushed_at":"2026-05-29T22:09:00.000Z","size":8974,"stargazers_count":4901,"open_issues_count":1,"forks_count":157,"subscribers_count":27,"default_branch":"main","last_synced_at":"2026-05-30T00:20:20.782Z","etag":null,"topics":["javascript","nodejs","postgres","postgresql","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gajus.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"gajus"}},"created_at":"2017-03-24T10:45:50.000Z","updated_at":"2026-05-29T22:08:31.000Z","dependencies_parsed_at":"2024-03-12T04:32:42.696Z","dependency_job_id":"ea37c743-d4fd-4503-b7e2-5e89457bc4f1","html_url":"https://github.com/gajus/slonik","commit_stats":{"total_commits":1390,"total_committers":59,"mean_commits":"23.559322033898304","dds":"0.10431654676258995","last_synced_commit":"d034e2fcdb827b3cf0efdc53c04d8bbc6d4fbced"},"previous_names":["gajus/mightyql"],"tags_count":1589,"template":false,"template_full_name":null,"purl":"pkg:github/gajus/slonik","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gajus%2Fslonik","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gajus%2Fslonik/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gajus%2Fslonik/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gajus%2Fslonik/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gajus","download_url":"https://codeload.github.com/gajus/slonik/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gajus%2Fslonik/sbom","scorecard":{"id":417333,"data":{"date":"2025-08-11","repo":{"name":"github.com/gajus/slonik","commit":"12dd253db08d8d6b94f184f6840f72111829ca15"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.2,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":0,"reason":"Found 0/22 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":10,"reason":"30 commit(s) and 5 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/feature.yaml:1","Warn: no topLevel permission defined: .github/workflows/main.yaml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":9,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Warn: project license file does not contain an FSF or OSI license."],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/feature.yaml:8: update your workflow using https://app.stepsecurity.io/secureworkflow/gajus/slonik/feature.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/feature.yaml:12: update your workflow using https://app.stepsecurity.io/secureworkflow/gajus/slonik/feature.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/feature.yaml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/gajus/slonik/feature.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/feature.yaml:29: update your workflow using https://app.stepsecurity.io/secureworkflow/gajus/slonik/feature.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/feature.yaml:33: update your workflow using https://app.stepsecurity.io/secureworkflow/gajus/slonik/feature.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/feature.yaml:36: update your workflow using https://app.stepsecurity.io/secureworkflow/gajus/slonik/feature.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/feature.yaml:80: update your workflow using https://app.stepsecurity.io/secureworkflow/gajus/slonik/feature.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/feature.yaml:84: update your workflow using https://app.stepsecurity.io/secureworkflow/gajus/slonik/feature.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/feature.yaml:87: update your workflow using https://app.stepsecurity.io/secureworkflow/gajus/slonik/feature.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/main.yaml:8: update your workflow using https://app.stepsecurity.io/secureworkflow/gajus/slonik/main.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/main.yaml:12: update your workflow using https://app.stepsecurity.io/secureworkflow/gajus/slonik/main.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/main.yaml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/gajus/slonik/main.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/main.yaml:33: update your workflow using https://app.stepsecurity.io/secureworkflow/gajus/slonik/main.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/main.yaml:37: update your workflow using https://app.stepsecurity.io/secureworkflow/gajus/slonik/main.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/main.yaml:40: update your workflow using https://app.stepsecurity.io/secureworkflow/gajus/slonik/main.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/main.yaml:50: update your workflow using https://app.stepsecurity.io/secureworkflow/gajus/slonik/main.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/main.yaml:88: update your workflow using https://app.stepsecurity.io/secureworkflow/gajus/slonik/main.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/main.yaml:92: update your workflow using https://app.stepsecurity.io/secureworkflow/gajus/slonik/main.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/main.yaml:95: update your workflow using https://app.stepsecurity.io/secureworkflow/gajus/slonik/main.yaml/main?enable=pin","Warn: containerImage not pinned by hash: packages/test-ssls/Dockerfile:1: pin your Docker image by updating postgres:16 to postgres:16@sha256:ddfe3e8713e3ee5b8f286082cb12512488dfbf3f5a1ecb0b74a42e6055af0a5f","Info:   0 out of  12 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   7 third-party GitHubAction dependencies pinned","Info:   0 out of   1 containerImage dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 11 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":4,"reason":"6 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-xffm-g5w8-qvg7","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-19T00:15:21.199Z","repository_id":37587549,"created_at":"2025-08-19T00:15:21.199Z","updated_at":"2025-08-19T00:15:21.199Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33711061,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["javascript","nodejs","postgres","postgresql","typescript"],"created_at":"2024-07-31T07:01:13.223Z","updated_at":"2026-06-05T20:00:59.959Z","avatar_url":"https://github.com/gajus.png","language":"TypeScript","funding_links":["https://github.com/sponsors/gajus"],"categories":["Packages","TypeScript","Repository","包","JavaScript","目录","Clients","**1. Libraries**","typescript","Compiled list","\u003ca name=\"TypeScript\"\u003e\u003c/a\u003eTypeScript"],"sub_categories":["Database","数据库","NPM","plv8:"],"readme":"# Slonik\n\n[![NPM version](http://img.shields.io/npm/v/slonik.svg?style=flat-square)](https://www.npmjs.org/package/slonik)\n[![Canonical Code Style](https://img.shields.io/badge/code%20style-canonical-blue.svg?style=flat-square)](https://github.com/gajus/canonical)\n[![Twitter Follow](https://img.shields.io/twitter/follow/kuizinas.svg?style=social\u0026label=Follow)](https://twitter.com/kuizinas)\n\nA [battle-tested](#battle-tested) Node.js PostgreSQL client with strict types, detailed logging and assertions.\n\n![Tailing Slonik logs](./.README/slonik-log-tailing.gif)\n\n## Principles\n\n- Promotes writing raw SQL.\n- Discourages ad-hoc dynamic generation of SQL.\n\nRead: [Stop using Knex.js](https://medium.com/@gajus/bf410349856c)\n\n## Features\n\n- [Runtime validation](#runtime-validation).\n- [Assertions and type safety](#repeating-code-patterns-and-type-safety).\n- [Safe connection handling](#protecting-against-unsafe-connection-handling).\n- [Safe transaction handling](#protecting-against-unsafe-transaction-handling).\n- [Safe value interpolation](#protecting-against-unsafe-value-interpolation).\n- [Transaction nesting](#transaction-nesting).\n- [Transaction events](#transaction-events).\n- [Transaction retrying](#transaction-retrying).\n- [Query retrying](#query-retrying).\n- Detailed [logging](#debugging).\n- [Asynchronous stack trace resolution](#capture-stack-trace).\n- [Middlewares](#interceptors).\n- [Mapped errors](#error-handling).\n- [ESLint plugin](https://github.com/gajus/eslint-plugin-sql).\n\n## Contents\n\n- [Slonik](#slonik)\n  - [Sponsors](#sponsors)\n  - [Principles](#principles)\n  - [Features](#features)\n  - [Contents](#contents)\n  - [About Slonik](#about-slonik)\n    - [Battle-Tested](#battle-tested)\n    - [Origin of the name](#origin-of-the-name)\n    - [Repeating code patterns and type safety](#repeating-code-patterns-and-type-safety)\n    - [Protecting against unsafe connection handling](#protecting-against-unsafe-connection-handling)\n    - [Protecting against unsafe transaction handling](#protecting-against-unsafe-transaction-handling)\n    - [Protecting against unsafe value interpolation](#protecting-against-unsafe-value-interpolation)\n  - [Documentation](#documentation)\n  - [Usage](#usage)\n    - [Connection URI](#connection-uri)\n    - [Create connection](#create-connection)\n    - [End connection pool](#end-connection-pool)\n    - [Describing the current state of the connection pool](#describing-the-current-state-of-the-connection-pool)\n    - [API](#api)\n    - [Default configuration](#default-configuration)\n    - [Checking out a client from the connection pool](#checking-out-a-client-from-the-connection-pool)\n    - [Events](#events)\n  - [How are they different?](#how-are-they-different)\n    - [`pg` vs `slonik`](#pg-vs-slonik)\n    - [`pg-promise` vs `slonik`](#pg-promise-vs-slonik)\n    - [`postgres` vs `slonik`](#postgres-vs-slonik)\n  - [Type parsers](#type-parsers)\n    - [Built-in type parsers](#built-in-type-parsers)\n  - [Interceptors](#interceptors)\n    - [Interceptor methods](#interceptor-methods)\n    - [Community interceptors](#community-interceptors)\n  - [Recipes](#recipes)\n    - [Inserting large number of rows](#inserting-large-number-of-rows)\n    - [Routing queries to different connections](#routing-queries-to-different-connections)\n    - [Building Utility Statements](#building-utility-statements)\n    - [Inserting vector data](#inserting-vector-data)\n  - [Runtime validation](#runtime-validation)\n    - [Motivation](#motivation)\n    - [Result parser interceptor](#result-parser-interceptor)\n    - [Example use of `sql.type`](#example-use-of-sqltype)\n    - [Performance penalty](#performance-penalty)\n    - [Unknown keys](#unknown-keys)\n    - [Handling schema validation errors](#handling-schema-validation-errors)\n    - [Inferring types](#inferring-types)\n    - [Transforming results](#transforming-results)\n  - [`sql` tag](#sql-tag)\n    - [Type aliases](#type-aliases)\n    - [Typing `sql` tag](#typing-sql-tag)\n  - [Value placeholders](#value-placeholders)\n    - [Tagged template literals](#tagged-template-literals)\n    - [Manually constructing the query](#manually-constructing-the-query)\n    - [Nesting `sql`](#nesting-sql)\n  - [Query building](#query-building)\n    - [`sql.and`](#sqland)\n    - [`sql.array`](#sqlarray)\n    - [`sql.binary`](#sqlbinary)\n    - [`sql.date`](#sqldate)\n    - [`sql.fragment`](#sqlfragment)\n    - [`sql.identifier`](#sqlidentifier)\n    - [`sql.interval`](#sqlinterval)\n    - [`sql.join`](#sqljoin)\n    - [`sql.json`](#sqljson)\n    - [`sql.jsonb`](#sqljsonb)\n    - [`sql.list`](#sqllist)\n    - [`sql.or`](#sqlor)\n    - [`sql.literalValue`](#sqlliteralvalue)\n    - [`sql.timestamp`](#sqltimestamp)\n    - [`sql.unnest`](#sqlunnest)\n    - [`sql.unsafe`](#sqlunsafe)\n    - [`sql.uuid`](#sqluuid)\n    - [`sql.prepared`](#sqlprepared)\n  - [Tips](#tips)\n    - [Prefer `sql.and`, `sql.or`, and `sql.list` over `sql.join`](#prefer-sqland-sqlor-and-sqllist-over-sqljoin)\n    - [Hoist Zod schemas with `babel-plugin-zod-hoist`](#hoist-zod-schemas-with-babel-plugin-zod-hoist)\n    - [Validate SQL queries with `eslint-plugin-slonik`](#validate-sql-queries-with-eslint-plugin-slonik)\n  - [Query methods](#query-methods)\n    - [`any`](#any)\n    - [`anyFirst`](#anyfirst)\n    - [`exists`](#exists)\n    - [`many`](#many)\n    - [`manyFirst`](#manyfirst)\n    - [`maybeOne`](#maybeone)\n    - [`maybeOneFirst`](#maybeonefirst)\n    - [`one`](#one)\n    - [`oneFirst`](#onefirst)\n    - [`query`](#query)\n    - [`stream`](#stream)\n    - [`transaction`](#transaction)\n  - [Utilities](#utilities)\n    - [`parseDsn`](#parsedsn)\n    - [`stringifyDsn`](#stringifydsn)\n  - [Error handling](#error-handling)\n    - [Original `node-postgres` error](#original-node-postgres-error)\n    - [Handling `BackendTerminatedError`](#handling-backendterminatederror)\n    - [Handling `CheckIntegrityConstraintViolationError`](#handling-checkintegrityconstraintviolationerror)\n    - [Handling `ConnectionError`](#handling-connectionerror)\n    - [Handling `DataIntegrityError`](#handling-dataintegrityerror)\n    - [Handling `ForeignKeyIntegrityConstraintViolationError`](#handling-foreignkeyintegrityconstraintviolationerror)\n    - [Handling `IntegrityConstraintViolationError`](#handling-integrityconstraintviolationerror)\n    - [Handling `NotFoundError`](#handling-notfounderror)\n    - [Handling `NotNullIntegrityConstraintViolationError`](#handling-notnullintegrityconstraintviolationerror)\n    - [Handling `StatementCancelledError`](#handling-statementcancellederror)\n    - [Handling `StatementTimeoutError`](#handling-statementtimeouterror)\n    - [Handling `UniqueIntegrityConstraintViolationError`](#handling-uniqueintegrityconstraintviolationerror)\n    - [Handling `TupleMovedToAnotherPartitionError`](#handling-tuplemovedtoanotherpartitionerror)\n  - [Migrations](#migrations)\n  - [Types](#types)\n  - [Debugging](#debugging)\n    - [Logging](#logging)\n    - [Capture stack trace](#capture-stack-trace)\n  - [Syntax Highlighting](#syntax-highlighting)\n    - [Atom Syntax Highlighting Plugin](#atom-syntax-highlighting-plugin)\n    - [VS Code Syntax Highlighting Extension](#vs-code-syntax-highlighting-extension)\n  - [Development](#development)\n\n## About Slonik\n\n### Battle-Tested\n\nSlonik began as a collection of utilities designed for working with [`node-postgres`](https://github.com/brianc/node-postgres). It continues to use `node-postgres` driver as it provides a robust foundation for interacting with PostgreSQL. However, what once was a collection of utilities has since grown into a framework that abstracts repeating code patterns, protects against unsafe connection handling and value interpolation, and provides a rich debugging experience.\n\nSlonik has been [battle-tested](https://medium.com/@gajus/lessons-learned-scaling-postgresql-database-to-1-2bn-records-month-edc5449b3067) with large data volumes and queries ranging from simple CRUD operations to data-warehousing needs.\n\n### Origin of the name\n\n![Slonik](./.README/postgresql-elephant.png)\n\n**\"Słonik\"** is a Polish diminutive of **\"słoń,\"** meaning “little elephant” or “baby elephant.” The word **\"słoń\"** itself comes from Proto-Slavic \\*_slonъ_, which was likely borrowed from a Germanic language and may ultimately trace back to Latin.\n\n### Repeating code patterns and type safety\n\nAmong the primary reasons for developing Slonik, was the motivation to reduce the repeating code patterns and add a level of type safety. This is primarily achieved through the methods such as `one`, `many`, etc. But what is the issue? It is best illustrated with an example.\n\nSuppose the requirement is to write a method that retrieves a resource ID given values defining (what we assume to be) a unique constraint. If we did not have the aforementioned helper methods available, then it would need to be written as:\n\n```ts\nimport { sql, type DatabaseConnection } from \"slonik\";\n\ntype DatabaseRecordIdType = number;\n\nconst getFooIdByBar = async (\n  connection: DatabaseConnection,\n  bar: string,\n): Promise\u003cDatabaseRecordIdType\u003e =\u003e {\n  const fooResult = await connection.query(sql.typeAlias(\"id\")`\n    SELECT id\n    FROM foo\n    WHERE bar = ${bar}\n  `);\n\n  if (fooResult.rowCount === 0) {\n    throw new Error(\"Resource not found.\");\n  }\n\n  if (fooResult.rowCount \u003e 1) {\n    throw new Error(\"Data integrity constraint violation.\");\n  }\n\n  return fooResult[0].id;\n};\n```\n\n`oneFirst` method abstracts all of the above logic into:\n\n```ts\nconst getFooIdByBar = (\n  connection: DatabaseConnection,\n  bar: string,\n): Promise\u003cDatabaseRecordIdType\u003e =\u003e {\n  return connection.oneFirst(sql.typeAlias(\"id\")`\n    SELECT id\n    FROM foo\n    WHERE bar = ${bar}\n  `);\n};\n```\n\n`oneFirst` throws:\n\n- `NotFoundError` if query returns no rows\n- `DataIntegrityError` if query returns multiple rows\n- `DataIntegrityError` if query returns multiple columns\n\nIn the absence of helper methods, the overhead of repeating code becomes particularly visible when writing routines where multiple queries depend on the proceeding query results. Using methods with inbuilt assertions ensures that in case of an error, the error points to the source of the problem. In contrast, unless assertions for all possible outcomes are typed out as in the previous example, the unexpected result of the query will be fed to the next operation. If you are lucky, the next operation will simply break; if you are unlucky, you are risking data corruption and hard-to-locate bugs.\n\nFurthermore, using methods that guarantee the shape of the results allows us to leverage static type checking and catch some of the errors even before executing the code, e.g.\n\n```ts\nconst fooId = await connection.many(sql.typeAlias(\"id\")`\n  SELECT id\n  FROM foo\n  WHERE bar = ${bar}\n`);\n\nawait connection.query(sql.typeAlias(\"void\")`\n  DELETE FROM baz\n  WHERE foo_id = ${fooId}\n`);\n```\n\nStatic type check of the above example will produce a warning as the `fooId` is guaranteed to be an array and binding of the last query is expecting a primitive value.\n\n### Protecting against unsafe connection handling\n\nSlonik only allows to check out a connection for the duration of the promise routine supplied to the `pool#connect()` method.\n\nThe primary reason for implementing _only_ this connection pooling method is because the alternative is inherently unsafe, e.g.\n\n```ts\n// This is not valid Slonik API\n\nconst main = async () =\u003e {\n  const connection = await pool.connect();\n\n  await connection.query(sql.typeAlias(\"foo\")`SELECT foo()`);\n\n  await connection.release();\n};\n```\n\nIn this example, if `SELECT foo()` produces an error, then connection is never released, i.e. the connection hangs indefinitely.\n\nA fix to the above is to ensure that `connection#release()` is always called, i.e.\n\n```ts\n// This is not valid Slonik API\n\nconst main = async () =\u003e {\n  const connection = await pool.connect();\n\n  let lastExecutionResult;\n\n  try {\n    lastExecutionResult = await connection.query(sql.typeAlias(\"foo\")`SELECT foo()`);\n  } finally {\n    await connection.release();\n  }\n\n  return lastExecutionResult;\n};\n```\n\nSlonik abstracts the latter pattern into `pool#connect()` method.\n\n```ts\nconst main = () =\u003e {\n  return pool.connect((connection) =\u003e {\n    return connection.query(sql.typeAlias(\"foo\")`SELECT foo()`);\n  });\n};\n```\n\nUsing this pattern, we guarantee that connection is always released as soon as the `connect()` routine resolves or is rejected.\n\n#### Resetting connection state\n\nAfter the connection is released, Slonik resets the connection state. This is to prevent connection state from leaking between queries.\n\nThe default behaviour is to execute `DISCARD ALL` command. This behaviour can be adjusted by configuring `resetConnection` routine, e.g.\n\n```ts\nimport { createPool, sql } from \"slonik\";\nimport { createPgDriverFactory } from \"@slonik/pg-driver\";\n\nconst pool = createPool(\"postgres://\", {\n  driverFactory: createPgDriverFactory(),\n  resetConnection: async (connection) =\u003e {\n    await connection.query(\"DISCARD ALL\");\n  },\n});\n```\n\n\u003e [!NOTE]\n\u003e Resetting a connection is a heavy operation. Depending on the application requirements, it may make sense to disable connection reset, e.g.\n\u003e\n\u003e ```ts\n\u003e import { createPool } from \"slonik\";\n\u003e import { createPgDriverFactory } from \"@slonik/pg-driver\";\n\u003e\n\u003e const pool = createPool(\"postgres://\", {\n\u003e   driverFactory: createPgDriverFactory(),\n\u003e   resetConnection: async () =\u003e {},\n\u003e });\n\u003e ```\n\n### Protecting against unsafe transaction handling\n\nJust like in the [unsafe connection handling](#protecting-against-unsafe-connection-handling) example, Slonik only allows to create a transaction for the duration of the promise routine supplied to the `connection#transaction()` method.\n\n```ts\nconnection.transaction(async (transactionConnection) =\u003e {\n  await transactionConnection.query(sql.typeAlias(\"void\")`INSERT INTO foo (bar) VALUES ('baz')`);\n  await transactionConnection.query(sql.typeAlias(\"void\")`INSERT INTO qux (quux) VALUES ('quuz')`);\n});\n```\n\nThis pattern ensures that the transaction is either committed or aborted the moment the promise is either resolved or rejected.\n\n\u003e [!NOTE]\n\u003e If you receive an error `UnexpectedForeignConnectionError`, then you are trying to execute a query using a connection that is not associated with the transaction. This error is thrown to prevent accidental unsafe transaction handling, e.g.\n\u003e\n\u003e ```ts\n\u003e pool.transaction(async (transactionConnection) =\u003e {\n\u003e   await pool.query(sql.typeAlias(\"void\")`INSERT INTO foo (bar) VALUES ('baz')`);\n\u003e });\n\u003e ```\n\u003e\n\u003e In this example, the query is executed using the `connection` that is not associated with the transaction. This is unsafe because the query is not part of the transaction and will not be rolled back if the transaction is aborted.\n\u003e This behaviour can be disabled by setting `dangerouslyAllowForeignConnections` to `true` in the `ClientConfiguration`.\n\n### Protecting against unsafe value interpolation\n\n[SQL injections](https://en.wikipedia.org/wiki/SQL_injection) are one of the most well known attack vectors. Some of the [biggest data leaks](https://en.wikipedia.org/wiki/SQL_injection#Examples) were the consequence of improper user-input handling. In general, SQL injections are easily preventable by using parameterization and by restricting database permissions, e.g.\n\n```ts\n// This is not valid Slonik API\n\nconnection.query(\"SELECT $1\", [userInput]);\n```\n\nIn this example, the query text (`SELECT $1`) and parameters (`userInput`) are passed separately to the PostgreSQL server where the parameters are safely substituted into the query. This is a safe way to execute a query using user-input.\n\nThe vulnerabilities appear when developers cut corners or when they do not know about parameterization, i.e. there is a risk that someone will instead write:\n\n```ts\n// This is not valid Slonik API\n\nconnection.query(\"SELECT '\" + userInput + \"'\");\n```\n\nAs evident by the history of the data leaks, this happens more often than anyone would like to admit. This security vulnerability is especially a significant risk in Node.js community, where a predominant number of developers are coming from frontend and have not had training working with RDBMSes. Therefore, one of the key selling points of Slonik is that it adds multiple layers of protection to prevent unsafe handling of user input.\n\nTo begin with, Slonik does not allow running plain-text queries.\n\n```ts\n// This is not valid Slonik API\n\nconnection.query(\"SELECT 1\");\n```\n\nThe above invocation would produce an error:\n\n\u003e TypeError: Query must be constructed using `sql` tagged template literal.\n\nThis means that the only way to run a query is by constructing it using [`sql` tagged template literal](https://github.com/gajus/slonik#value-placeholders-tagged-template-literals), e.g.\n\n```ts\nconnection.query(sql.unsafe`SELECT 1`);\n```\n\nTo add a parameter to the query, user must use [template literal placeholders](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Description), e.g.\n\n```ts\nconnection.query(sql.unsafe`SELECT ${userInput}`);\n```\n\nSlonik takes over from here and constructs a query with value bindings, and sends the resulting query text and parameters to PostgreSQL. There is no other way of passing parameters to the query – this adds a strong layer of protection against accidental unsafe user input handling due to limited knowledge of the SQL client API.\n\nAs Slonik restricts user's ability to generate and execute dynamic SQL, it provides helper functions used to generate fragments of the query and the corresponding value bindings, e.g. [`sql.identifier`](#sqlidentifier), [`sql.join`](#sqljoin) and [`sql.unnest`](#sqlunnest). These methods generate tokens that the query executor interprets to construct a safe query, e.g.\n\n```ts\nconnection.query(sql.unsafe`\n  SELECT ${sql.identifier([\"foo\", \"a\"])}\n  FROM (\n    VALUES\n    (\n      ${sql.join(\n        [\n          sql.join([\"a1\", \"b1\", \"c1\"], sql.fragment`, `),\n          sql.join([\"a2\", \"b2\", \"c2\"], sql.fragment`, `),\n        ],\n        sql.fragment`), (`,\n      )}\n    )\n  ) foo(a, b, c)\n  WHERE foo.b IN (${sql.join([\"c1\", \"a2\"], sql.fragment`, `)})\n`);\n```\n\nThis (contrived) example generates a query equivalent to:\n\n```sql\nSELECT \"foo\".\"a\"\nFROM (\n  VALUES\n    ($1, $2, $3),\n    ($4, $5, $6)\n) foo(a, b, c)\nWHERE foo.b IN ($7, $8)\n```\n\nThis query is executed with the parameters provided by the user.\n\nTo sum up, Slonik is designed to prevent accidental creation of queries vulnerable to SQL injections.\n\n## Documentation\n\n## Usage\n\n### Connection URI\n\nSlonik client is configured using a custom connection URI (DSN).\n\n```tson\npostgresql://[user[:password]@][host[:port]][/database name][?name=value[\u0026...]]\n```\n\nSupported parameters:\n\n| Name               | Meaning                                                                                                                                        | Default   |\n| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | --------- |\n| `application_name` | [`application_name`](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-APPLICATION-NAME)                                |           |\n| `options`          | [`options`](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-OPTIONS)                                                  |           |\n| `sslcert`          | The location of the [certificate](https://www.postgresql.org/docs/current/libpq-ssl.html#LIBPQ-SSL-CLIENTCERT).                                | -         |\n| `sslkey`           | The location of the [certificate](https://www.postgresql.org/docs/current/libpq-ssl.html#LIBPQ-SSL-CLIENTCERT).                                | -         |\n| `sslmode`          | [`sslmode`](https://www.postgresql.org/docs/current/libpq-ssl.html#LIBPQ-SSL-PROTECTION) (supported values: `disable`, `no-verify`, `require`) | `disable` |\n| `sslrootcert`      | The location of the [root certificate](\u003c(https://www.postgresql.org/docs/current/libpq-ssl.html#LIBQ-SSL-CERTIFICATES)\u003e) file.                 |           |\n\nNote that unless listed above, other [libpq parameters](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS) are not supported.\n\nExamples of valid DSNs:\n\n```text\npostgresql://\npostgresql://localhost\npostgresql://localhost:5432\npostgresql://localhost/foo\npostgresql://foo@localhost\npostgresql://foo:bar@localhost\npostgresql://foo@localhost/bar?application_name=baz\n```\n\nUnix-domain socket connection is chosen if the host part is either empty or looks like an absolute path name.\n\n```text\npostgresql:///dbname?host=/var/lib/postgresql\npostgresql://%2Fvar%2Flib%2Fpostgresql/dbname\n```\n\nOther configurations are available through the [`clientConfiguration` parameter](https://github.com/gajus/slonik#api).\n\n#### Dynamic passwords\n\nThe `password` configuration option supports async callbacks, enabling IAM-based database authentication (AWS RDS IAM, GCP Cloud SQL IAM, Azure AD tokens) where credentials expire and must be refreshed per connection.\n\n```ts\nimport { createPool } from \"slonik\";\nimport { createPgDriverFactory } from \"@slonik/pg-driver\";\nimport { generateRdsAuthToken } from \"./iam.js\";\n\nconst pool = await createPool(\n  \"postgres://iam_user@mydb.us-east-1.rds.amazonaws.com:5432/mydb?sslmode=require\",\n  {\n    driverFactory: createPgDriverFactory(),\n    password: async () =\u003e generateRdsAuthToken(),\n  },\n);\n```\n\nWhen provided, the `password` option overrides any password in the connection URI. It accepts a static string or a callback returning a string (sync or async). The callback is invoked by the underlying `pg` driver each time a new connection is established.\n\n### Create connection\n\nUse `createPool` to create a connection pool, e.g.\n\n```ts\nimport { createPool } from \"slonik\";\nimport { createPgDriverFactory } from \"@slonik/pg-driver\";\n\nconst pool = await createPool(\"postgres://\", {\n  driverFactory: createPgDriverFactory(),\n});\n```\n\n\u003e **Note:** If you are new to Slonik, then you should read [Integrating Slonik with Express.js](https://dev.to/gajus/integrating-slonik-with-expressjs-33kn).\n\nInstance of Slonik connection pool can be then used to create a new connection, e.g.\n\n```ts\npool.connect(async (connection) =\u003e {\n  await connection.query(sql.typeAlias(\"id\")`SELECT 1 AS id`);\n});\n```\n\nThe connection will be kept alive until the promise resolves (the result of the method supplied to `connect()`).\n\nRefer to [query method](#query-methods) documentation to learn about the connection methods.\n\nIf you do not require having a persistent connection to the same backend, then you can directly use `pool` to run queries, e.g.\n\n```ts\npool.query(sql.typeAlias(\"id\")`SELECT 1 AS id`);\n```\n\nBeware that in the latter example, the connection picked to execute the query is a random connection from the connection pool, i.e. using the latter method (without explicit `connect()`) does not guarantee that multiple queries will refer to the same backend.\n\n### End connection pool\n\nUse `pool.end()` to end idle connections and prevent creation of new connections.\n\nThe result of `pool.end()` is a promise that is resolved when all connections are ended.\n\n```ts\nimport { createPool, sql } from \"slonik\";\nimport { createPgDriverFactory } from \"@slonik/pg-driver\";\n\nconst pool = await createPool(\"postgres://\", {\n  driverFactory: createPgDriverFactory(),\n});\n\nconst main = async () =\u003e {\n  await pool.query(sql.typeAlias(\"id\")`\n    SELECT 1 AS id\n  `);\n\n  await pool.end();\n};\n\nmain();\n```\n\nNote: `pool.end()` does not terminate active connections/ transactions.\n\n### Describing the current state of the connection pool\n\nUse `pool.state()` to find out if pool is alive and how many connections are active and idle, and how many clients are waiting for a connection.\n\n```ts\nimport { createPool, sql } from \"slonik\";\nimport { createPgDriverFactory } from \"@slonik/pg-driver\";\n\nconst pool = await createPool(\"postgres://\", {\n  driverFactory: createPgDriverFactory(),\n});\n\nconst main = async () =\u003e {\n  pool.state();\n\n  // {\n  //   acquiredConnections: 0,\n  //   idleConnections: 0,\n  //   pendingDestroyConnections: 0,\n  //   pendingReleaseConnections: 0,\n  //   state: 'ACTIVE',\n  //   waitingClients: 0,\n  // }\n\n  await pool.connect(() =\u003e {\n    pool.state();\n\n    // {\n    //   acquiredConnections: 1,\n    //   idleConnections: 0,\n    //   pendingDestroyConnections: 0,\n    //   pendingReleaseConnections: 0,\n    //   state: 'ACTIVE',\n    //   waitingClients: 0,\n    // }\n  });\n\n  pool.state();\n\n  // {\n  //   acquiredConnections: 0,\n  //   idleConnections: 1,\n  //   pendingDestroyConnections: 0,\n  //   pendingReleaseConnections: 0,\n  //   state: 'ACTIVE',\n  //   waitingClients: 0,\n  // }\n\n  await pool.end();\n\n  pool.state();\n\n  // {\n  //   acquiredConnections: 0,\n  //   idleConnections: 0,\n  //   pendingDestroyConnections: 0,\n  //   pendingReleaseConnections: 0,\n  //   state: 'ENDED',\n  //   waitingClients: 0,\n  // }\n};\n\nmain();\n```\n\nNote: `pool.end()` does not terminate active connections/ transactions.\n\n### API\n\n```ts\n/**\n * @param connectionUri PostgreSQL [Connection URI](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING).\n */\ncreatePool(\n  connectionUri: string,\n  clientConfiguration: ClientConfiguration\n): DatabasePool;\n\n/**\n * @property captureStackTrace Dictates whether to capture stack trace before executing query. Middlewares access stack trace through query execution context. (Default: false)\n * @property connectionRetryLimit Number of times to retry establishing a new connection. (Default: 3)\n * @property connectionTimeout Timeout (in milliseconds) after which an error is raised if connection cannot be established. (Default: 5000)\n * @property dangerouslyAllowForeignConnections Allow using connections that are not associated with the transaction. (Default: false)\n * @property driverFactory Overrides the default DriverFactory. (Default: \"pg\" driver factory)\n * @property gracefulTerminationTimeout Timeout (in milliseconds) that kicks in after a connection with an active query is requested to end. This is the amount of time that is allowed for query to complete before terminating it. (Default: 5000)\n * @property idleInTransactionSessionTimeout Timeout (in milliseconds) after which idle clients are closed. Use 'DISABLE_TIMEOUT' constant to disable the timeout. (Default: 60000)\n * @property idleTimeout Timeout (in milliseconds) after which idle clients are closed. Use 'DISABLE_TIMEOUT' constant to disable the timeout. (Default: 5000)\n * @property interceptors An array of [Slonik interceptors](https://github.com/gajus/slonik#interceptors).\n * @property maximumConnectionAge The maximum age of a connection allowed in the pool. After this age, the connection will be destroyed. (Default: 30 minutes)\n * @property maximumPoolSize (Deprecated: use maxPoolSize) Do not allow more than this many connections. (Default: 10)\n * @property maxPoolSize Do not allow more than this many connections. (Default: 10)\n * @property minimumPoolSize (Deprecated: use minPoolSize) Ensure that at least this many connections are available in the pool. (Default: 0)\n * @property minPoolSize Ensure that at least this many connections are available in the pool. (Default: 0)\n * @property queryRetryLimit Number of times a query failing with Transaction Rollback class error, that doesn't belong to a transaction, is retried. (Default: 5)\n * @property ssl [tls.connect options](https://nodejs.org/api/tls.html#tlsconnectoptions-callback)\n * @property statementTimeout Timeout (in milliseconds) after which database is instructed to abort the query. Use 'DISABLE_TIMEOUT' constant to disable the timeout. (Default: 60000)\n * @property tracing Controls whether Slonik creates OpenTelemetry spans. (Default: false)\n * @property transactionRetryLimit Number of times a transaction failing with Transaction Rollback class error is retried. (Default: 5)\n * @property typeParsers An array of [Slonik type parsers](https://github.com/gajus/slonik#type-parsers).\n */\ntype ClientConfiguration = {\n  captureStackTrace?: boolean,\n  connectionRetryLimit?: number,\n  connectionTimeout?: number | 'DISABLE_TIMEOUT',\n  driverFactory?: DriverFactory,\n  gracefulTerminationTimeout?: number,\n  idleInTransactionSessionTimeout?: number | 'DISABLE_TIMEOUT',\n  idleTimeout?: number | 'DISABLE_TIMEOUT',\n  interceptors?: Interceptor[],\n  maximumConnectionAge?: number,\n  maximumPoolSize?: number, // deprecated, use maxPoolSize\n  maxPoolSize?: number,\n  minimumPoolSize?: number, // deprecated, use minPoolSize\n  minPoolSize?: number,\n  queryRetryLimit?: number,\n  ssl?: Parameters\u003ctls.connect\u003e[0],\n  statementTimeout?: number | 'DISABLE_TIMEOUT',\n  tracing?: boolean,\n  transactionRetryLimit?: number,\n  typeParsers?: TypeParser[],\n};\n```\n\nExample:\n\n```ts\nimport { createPool } from \"slonik\";\nimport { createPgDriverFactory } from \"@slonik/pg-driver\";\n\nconst pool = await createPool(\"postgres://\", {\n  driverFactory: createPgDriverFactory(),\n});\n\nawait pool.query(sql.typeAlias(\"id\")`SELECT 1 AS id`);\n```\n\n### Default configuration\n\n#### Default interceptors\n\nNone.\n\nCheck out [`slonik-interceptor-preset`](https://github.com/gajus/slonik-interceptor-preset) for an opinionated collection of interceptors.\n\n#### Default type parsers\n\nThese type parsers are enabled by default:\n\n| Type name     | Implementation                                            |\n| ------------- | --------------------------------------------------------- |\n| `date`        | Produces a literal date as a string (format: YYYY-MM-DD). |\n| `int8`        | Produces an integer.                                      |\n| `interval`    | Produces interval in seconds (integer).                   |\n| `numeric`     | Produces a float.                                         |\n| `timestamp`   | Produces a unix timestamp (in milliseconds).              |\n| `timestamptz` | Produces a unix timestamp (in milliseconds).              |\n\nTo disable the default type parsers, pass an empty array, e.g.\n\n```ts\ncreatePool(\"postgres://\", {\n  typeParsers: [],\n});\n```\n\nYou can create default type parser collection using `createTypeParserPreset`, e.g.\n\n```ts\nimport { createTypeParserPreset } from \"slonik\";\nimport { createPgDriverFactory } from \"@slonik/pg-driver\";\n\ncreatePool(\"postgres://\", {\n  driverFactory: createPgDriverFactory(),\n  typeParsers: [...createTypeParserPreset()],\n});\n```\n\n#### Default timeouts\n\nThere are 4 types of configurable timeouts:\n\n| Configuration                     | Description                                                                                                                             | Default |\n| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------- |\n| `connectionTimeout`               | Timeout (in milliseconds) after which an error is raised if connection cannot be established.                                           | 5000    |\n| `idleInTransactionSessionTimeout` | Timeout (in milliseconds) after which idle clients are closed. Use 'DISABLE_TIMEOUT' constant to disable the timeout.                   | 60000   |\n| `idleTimeout`                     | Timeout (in milliseconds) after which idle clients are closed. Use 'DISABLE_TIMEOUT' constant to disable the timeout.                   | 5000    |\n| `statementTimeout`                | Timeout (in milliseconds) after which database is instructed to abort the query. Use 'DISABLE_TIMEOUT' constant to disable the timeout. | 60000   |\n\nSlonik sets aggressive timeouts by default. These timeouts are designed to provide safe interface to the database. These timeouts might not work for all programs. If your program has long running statements, consider adjusting timeouts just for those statements instead of changing the defaults.\n\n#### Known limitations of using pg-native with Slonik\n\n`pg-native` is not officially supported by Slonik.\n\n### Checking out a client from the connection pool\n\nSlonik only allows to check out a connection for the duration of the promise routine supplied to the `pool#connect()` method.\n\n```ts\nimport { createPool } from \"slonik\";\nimport { createPgDriverFactory } from \"@slonik/pg-driver\";\n\nconst pool = await createPool(\"postgres://localhost\", {\n  driverFactory: createPgDriverFactory(),\n});\n\nconst result = await pool.connect(async (connection) =\u003e {\n  await connection.query(sql.typeAlias(\"id\")`SELECT 1 AS id`);\n  await connection.query(sql.typeAlias(\"id\")`SELECT 2 AS id`);\n\n  return \"foo\";\n});\n\nresult;\n// 'foo'\n```\n\nConnection is released back to the pool after the promise produced by the function supplied to `connect()` method is either resolved or rejected.\n\nRead: [Protecting against unsafe connection handling](#protecting-against-unsafe-connection-handling).\n\n### Events\n\nThe `DatabasePool` extends `DatabasePoolEventEmitter` and exposes the following events:\n\n- `error`: `(error: SlonikError) =\u003e void` – emitted for all errors that happen within the pool.\n\n```ts\nimport { createPool, type DatabasePoolEventEmitter } from \"slonik\";\nimport { createPgDriverFactory } from \"@slonik/pg-driver\";\n\nconst pool = await createPool(\"postgres://localhost\", {\n  driverFactory: createPgDriverFactory(),\n});\n\npool.on(\"error\", (error) =\u003e {\n  console.error(error);\n});\n```\n\n## How are they different?\n\n### \u003ccode\u003epg\u003c/code\u003e vs \u003ccode\u003eslonik\u003c/code\u003e\n\n[`pg`](https://github.com/brianc/node-postgres) is built intentionally to provide unopinionated, minimal abstraction and encourages use of other modules to implement convenience methods.\n\nSlonik is built on top of `pg` and it provides convenience methods for [building queries](#value-placeholders) and [querying data](#query-methods).\n\nWork on `pg` began on [Tue Sep 28 22:09:21 2010](https://github.com/brianc/node-postgres/commit/cf637b08b79ef93d9a8b9dd2d25858aa7e9f9bdc). It is authored by [Brian Carlson](https://github.com/brianc).\n\n### \u003ccode\u003epg-promise\u003c/code\u003e vs \u003ccode\u003eslonik\u003c/code\u003e\n\nAs the name suggests, [`pg-promise`](https://github.com/vitaly-t/pg-promise) was originally built to enable use of `pg` module with promises (at the time, `pg` only supported Continuation Passing Style (CPS), i.e. callbacks). Since then `pg-promise` added features for connection/ transaction handling, a powerful query-formatting engine and a declarative approach to handling query results.\n\nThe primary difference between Slonik and `pg-promise`:\n\n- Slonik does not allow to execute raw text queries. Slonik queries can only be constructed using [`sql` tagged template literals](#value-placeholders-tagged-template-literals). This design [protects against unsafe value interpolation](#protecting-against-unsafe-value-interpolation).\n- Slonik implements [interceptor API](#interceptors) (middleware). Middlewares allow to modify connection handling, override queries and modify the query results. Example Slonik interceptors include [field name transformation](https://github.com/gajus/slonik-interceptor-field-name-transformation), [query normalization](https://github.com/gajus/slonik-interceptor-query-normalisation) and [query benchmarking](https://github.com/gajus/slonik-interceptor-query-benchmarking).\n\nNote: Author of `pg-promise` has [objected to the above claims](https://github.com/gajus/slonik/issues/122). I have removed a difference that was clearly wrong. I maintain that the above two differences remain valid differences: even though `pg-promise` might have substitute functionality for variable interpolation and interceptors, it implements them in a way that does not provide the same benefits that Slonik provides, namely: guaranteed security and support for extending library functionality using multiple plugins.\n\nOther differences are primarily in how the equivalent features are implemented, e.g.\n\n| `pg-promise`                                                                             | Slonik                                                                                                                                                                           |\n| ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| [Custom type formatting](https://github.com/vitaly-t/pg-promise#custom-type-formatting). | Not available in Slonik. The current proposal is to create an interceptor that would have access to the [query fragment constructor](https://github.com/gajus/slonik/issues/21). |\n| [formatting filters](https://github.com/vitaly-t/pg-promise#nested-named-parameters)     | Slonik tagged template [value expressions](https://github.com/gajus/slonik#value-placeholders) to construct query fragments and bind parameter values.                           |\n| [Query files](https://github.com/vitaly-t/pg-promise#query-files).                       | Use [`slonik-sql-tag-raw`](https://github.com/gajus/slonik-sql-tag-raw).                                                                                                         |\n| [Tasks](https://github.com/vitaly-t/pg-promise#tasks).                                   | Use [`pool.connect`](https://github.com/gajus/slonik#create-connection).                                                                                                         |\n| Configurable transactions.                                                               | Not available in Slonik. Track [this issue](https://github.com/gajus/slonik/issues/30).                                                                                          |\n| Events.                                                                                  | Use [interceptors](https://github.com/gajus/slonik#interceptors).                                                                                                                |\n\nWhen weighting which abstraction to use, it would be unfair not to consider that `pg-promise` is a mature project with dozens of contributors. Meanwhile, Slonik is a young project (started in March 2017) that until recently was developed without active community input. However, if you do support the unique features that Slonik adds, the opinionated API design, and are not afraid of adopting a technology in its young days, then I warmly invite you to adopt Slonik and become a contributor to what I intend to make the standard PostgreSQL client in the Node.js community.\n\nWork on `pg-promise` began [Wed Mar 4 02:00:34 2015](https://github.com/vitaly-t/pg-promise/commit/78fb80f638e7f28b301f75576701536d6b638f31). It is authored by [Vitaly Tomilov](https://github.com/vitaly-t).\n\n### \u003ccode\u003epostgres\u003c/code\u003e vs \u003ccode\u003eslonik\u003c/code\u003e\n\n[`postgres`](https://github.com/porsager/postgres) recently gained in popularity due to its performance benefits when compared to `pg`. In terms of API, it has a pretty bare-bones API that heavily relies on using ES6 tagged templates and abstracts away many concepts of connection pool handling. While `postgres` API might be preferred by some, projects that already use `pg` may have difficulty migrating.\n\n## Type parsers\n\nType parsers describe how to parse PostgreSQL types.\n\n```ts\ntype TypeParser = {\n  name: string;\n  parse: (value: string) =\u003e *;\n};\n```\n\nExample:\n\n```ts\n{\n  name: 'int8',\n  parse: (value) =\u003e {\n    return parseInt(value, 10);\n  }\n}\n```\n\nNote: Unlike [`pg-types`](https://github.com/brianc/node-pg-types) that uses OIDs to identify types, Slonik identifies types using their names.\n\nUse this query to find type names:\n\n```sql\nSELECT typname\nFROM pg_type\nORDER BY typname ASC\n```\n\nType parsers are configured using [`typeParsers` client configuration](#api).\n\nRead: [Default type parsers](#default-type-parsers).\n\n### Built-in type parsers\n\n| Type name     | Implementation                                            | Factory function name                   |\n| ------------- | --------------------------------------------------------- | --------------------------------------- |\n| `date`        | Produces a literal date as a string (format: YYYY-MM-DD). | `createDateTypeParser`                  |\n| `int8`        | Produces an integer.                                      | `createBigintTypeParser`                |\n| `interval`    | Produces interval in seconds (integer).                   | `createIntervalTypeParser`              |\n| `numeric`     | Produces a float.                                         | `createNumericTypeParser`               |\n| `timestamp`   | Produces a unix timestamp (in milliseconds).              | `createTimestampTypeParser`             |\n| `timestamptz` | Produces a unix timestamp (in milliseconds).              | `createTimestampWithTimeZoneTypeParser` |\n\nBuilt-in type parsers can be created using the exported factory functions, e.g.\n\n```ts\nimport { createTimestampTypeParser } from \"slonik\";\n\ncreateTimestampTypeParser();\n\n// {\n//   name: 'timestamp',\n//   parse: (value) =\u003e {\n//     return value === null ? value : Date.parse(value + ' UTC');\n//   }\n// }\n```\n\n## Interceptors\n\nFunctionality can be added to Slonik client by adding interceptors (middleware).\n\nInterceptors are configured using [client configuration](#api), e.g.\n\n```ts\nimport { createPool } from \"slonik\";\nimport { createPgDriverFactory } from \"@slonik/pg-driver\";\n\nconst interceptors = [];\n\nconst connection = await createPool(\"postgres://\", {\n  driverFactory: createPgDriverFactory(),\n  interceptors,\n});\n```\n\nInterceptors are executed in the order they are added.\n\nRead: [Default interceptors](#default-interceptors).\n\n### Interceptor methods\n\nInterceptor is an object that implements methods that can change the behaviour of the database client at different stages of the connection life-cycle\n\n```ts\ntype Interceptor = {\n  afterPoolConnection?: (\n    connectionContext: ConnectionContext,\n    connection: DatabasePoolConnection,\n  ) =\u003e MaybePromise\u003cnull\u003e;\n  afterQueryExecution?: (\n    queryContext: QueryContext,\n    query: Query,\n    result: QueryResult\u003cQueryResultRow\u003e,\n  ) =\u003e MaybePromise\u003cQueryResult\u003cQueryResultRow\u003e\u003e;\n  beforePoolConnection?: (connectionContext: PoolContext) =\u003e MaybePromise\u003c?DatabasePool\u003e;\n  beforePoolConnectionRelease?: (\n    connectionContext: ConnectionContext,\n    connection: DatabasePoolConnection,\n  ) =\u003e MaybePromise\u003cnull\u003e;\n  beforeQueryExecution?: (\n    queryContext: QueryContext,\n    query: Query,\n  ) =\u003e MaybePromise\u003cQueryResult\u003cQueryResultRow\u003e\u003e | MaybePromise\u003cnull\u003e;\n  beforeQueryResult?: (\n    queryContext: QueryContext,\n    query: Query,\n    result: QueryResult\u003cQueryResultRow\u003e,\n  ) =\u003e MaybePromise\u003cnull\u003e;\n  beforeTransformQuery?: (queryContext: QueryContext, query: Query) =\u003e MaybePromise\u003cnull\u003e;\n  dataIntegrityError?: (\n    queryContext: QueryContext,\n    query: Query,\n    error: DataIntegrityError,\n    result: QueryResult\u003cQueryResultRow\u003e,\n  ) =\u003e MaybePromise\u003cnull\u003e;\n  queryExecutionError?: (\n    queryContext: QueryContext,\n    query: Query,\n    error: SlonikError,\n  ) =\u003e MaybePromise\u003cnull\u003e;\n  transformQuery?: (queryContext: QueryContext, query: Query) =\u003e Query;\n  transformRow?: (\n    queryContext: QueryContext,\n    query: Query,\n    row: QueryResultRow,\n    fields: Field[],\n  ) =\u003e MaybePromise\u003cQueryResultRow\u003e;\n};\n```\n\nFor a given connection attempt, Slonik reuses the same context object across\n`beforePoolConnection`, `afterPoolConnection`, and `beforePoolConnectionRelease`.\n\n#### \u003ccode\u003eafterPoolConnection\u003c/code\u003e\n\nExecuted after a connection is acquired from the connection pool (or a new connection is created), e.g.\n\n```ts\nconst pool = await createPool(\"postgres://\");\n\n// Interceptor is executed here. ↓\npool.connect();\n```\n\n#### \u003ccode\u003eafterQueryExecution\u003c/code\u003e\n\nExecuted after query has been executed and before rows were transformed using `transformRow`.\n\nNote: When query is executed using `stream`, then `afterQuery` is called with empty result set.\n\n#### \u003ccode\u003ebeforeQueryExecution\u003c/code\u003e\n\nThis function can optionally return a direct result of the query which will cause the actual query never to be executed.\n\n#### \u003ccode\u003ebeforeQueryResult\u003c/code\u003e\n\nExecuted just before the result is returned to the client.\n\nUse this method to capture the result that will be returned to the client.\n\n#### \u003ccode\u003ebeforeTransformQuery\u003c/code\u003e\n\nExecuted before `transformQuery`. Use this interceptor to capture the original query (e.g. for logging purposes).\n\n#### \u003ccode\u003ebeforePoolConnection\u003c/code\u003e\n\nExecuted before connection is created.\n\nThis function can optionally return a pool to another database, causing a connection to be made to the new pool.\n\n#### \u003ccode\u003ebeforePoolConnectionRelease\u003c/code\u003e\n\nExecuted before connection is released back to the connection pool, e.g.\n\n```ts\nconst pool = await createPool(\"postgres://\");\n\npool.connect(async () =\u003e {\n  await 1;\n\n  // Interceptor is executed here. ↓\n});\n```\n\n#### \u003ccode\u003equeryExecutionError\u003c/code\u003e\n\nExecuted if query execution produces an error.\n\nUse `queryExecutionError` to log and/ or re-throw another error.\n\n#### \u003ccode\u003edataIntegrityError\u003c/code\u003e\n\nExecuted when a data integrity validation fails (e.g., when a query returns an unexpected number of rows or columns).\n\nUse `dataIntegrityError` to log data integrity violations, perform custom validation logic, or handle data consistency issues. This middleware is called before the `DataIntegrityError` is thrown, allowing you to inspect the query result and error details.\n\n#### \u003ccode\u003etransformQuery\u003c/code\u003e\n\nExecuted before `beforeQueryExecution`.\n\nTransforms query.\n\n#### \u003ccode\u003etransformRow\u003c/code\u003e\n\nExecuted for each row.\n\nTransforms row.\n\nUse `transformRow` to modify the query result.\n\n### Community interceptors\n\n| Name                                                                                                                    | Description                                 |\n| ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |\n| [`slonik-interceptor-field-name-transformation`](https://github.com/gajus/slonik-interceptor-field-name-transformation) | Transforms Slonik query result field names. |\n| [`slonik-interceptor-query-benchmarking`](https://github.com/gajus/slonik-interceptor-query-benchmarking)               | Benchmarks Slonik queries.                  |\n| [`slonik-interceptor-query-cache`](https://github.com/gajus/slonik-interceptor-query-cache)                             | Caches Slonik queries.                      |\n| [`slonik-interceptor-query-logging`](https://github.com/gajus/slonik-interceptor-query-logging)                         | Logs Slonik queries.                        |\n| [`slonik-interceptor-query-normalisation`](https://github.com/gajus/slonik-interceptor-query-normalisation)             | Normalises Slonik queries.                  |\n\nCheck out [`slonik-interceptor-preset`](https://github.com/gajus/slonik-interceptor-preset) for an opinionated collection of interceptors.\n\n## Recipes\n\n### Inserting large number of rows\n\nUse `jsonb_to_recordset` with [`sql.jsonb`](#sqljsonb) to bulk insert rows. This approach handles nested data (e.g. arrays, JSON) natively without manual data manipulation, and has the same performance as `unnest`.\n\n```ts\nawait connection.query(sql.unsafe`\n  INSERT INTO foo (bar, baz, qux)\n  SELECT *\n  FROM jsonb_to_recordset(${sql.jsonb([\n    { bar: 1, baz: 2, qux: 3 },\n    { bar: 4, baz: 5, qux: 6 },\n  ])}) AS t(bar int4, baz int4, qux int4)\n`);\n```\n\n\u003e [!NOTE]\n\u003e Previously this section recommended [`sql.unnest`](#sqlunnest). While `unnest` still works, `jsonb_to_recordset` is preferred because it accepts objects directly (no column-order coupling), handles array/JSON columns without manual serialization, and produces more readable queries. See [Bulk Inserting Nested Data into the Database](https://contra.com/p/P7kB2RPO-bulk-inserting-nested-data-into-the-database-part-ii) for a detailed comparison.\n\n### Routing queries to different connections\n\nA typical load balancing requirement is to route all \"logical\" read-only queries to a read-only instance. This requirement can be implemented in 2 ways:\n\n1. Create two instances of Slonik (read-write and read-only) and pass them around the application as needed.\n1. Use `beforePoolConnection` middleware to assign query to a connection pool based on the query itself.\n\nFirst option is preferable as it is the most explicit. However, it also has the most overhead to implement.\n\nOn the other hand, `beforePoolConnection` makes it easy to route based on conventions, but carries a greater risk of accidentally routing queries with side-effects to a read-only instance.\n\nThe first option is self-explanatory to implement, but this recipe demonstrates my convention for using `beforePoolConnection` to route queries.\n\nNote: How you determine which queries are safe to route to a read-only instance is outside of scope for this documentation.\n\nNote: `beforePoolConnection` only works for connections initiated by a query, i.e. `pool#query` and not `pool#connect()`.\n\nNote: `pool#transaction` triggers `beforePoolConnection` but has no `query`.\n\nNote: This particular implementation does not handle [`SELECT INTO`](https://www.postgresql.org/docs/current/sql-selectinto.html).\n\n```ts\nconst readOnlyPool = await createPool(\"postgres://read-only\");\n\nconst pool = await createPool(\"postgres://main\", {\n  interceptors: [\n    {\n      beforePoolConnection: (connectionContext) =\u003e {\n        if (!connectionContext.query?.sql.trim().startsWith(\"SELECT \")) {\n          // Returning null falls back to using the DatabasePool from which the query originates.\n          return null;\n        }\n\n        // This is a convention for the edge-cases where a SELECT query includes a volatile function.\n        // Adding a @volatile comment anywhere into the query bypasses the read-only route, e.g.\n        // sql.unsafe`\n        //   /* @volatile */\n        //   SELECT write_log()\n        // `\n        if (connectionContext.query?.sql.includes(\"@volatile\")) {\n          return null;\n        }\n\n        // Returning an instance of DatabasePool will attempt to run the query using the other connection pool.\n        // Note that all other interceptors of the pool that the query originated from are short-circuited.\n        return readOnlyPool;\n      },\n    },\n  ],\n});\n\n// This query will use `postgres://read-only` connection.\npool.query(sql.typeAlias(\"id\")`SELECT 1 AS id`);\n\n// This query will use `postgres://main` connection.\npool.query(sql.typeAlias(\"id\")`UPDATE 1 AS id`);\n```\n\n### Building Utility Statements\n\nParameter symbols only work in optimizable SQL commands (SELECT, INSERT, UPDATE, DELETE, and certain commands containing one of these). In other statement types (generically called utility statements, e.g. ALTER, CREATE, DROP and SET), you must insert values textually even if they are just data values.\n\nIn the context of Slonik, if you are building utility statements you must use query building methods that interpolate values directly into queries:\n\n- [`sql.identifier`](#sql-identifier) – for identifiers.\n- [`sql.literalValue`](#sql-literalvalue) – for values.\n\nExample:\n\n```ts\nawait connection.query(sql.typeAlias(\"void\")`\n  CREATE USER ${sql.identifier([\"foo\"])}\n  WITH PASSWORD ${sql.literalValue(\"bar\")}\n`);\n```\n\n### Inserting vector data\n\nIf you are using [`pgvector`](https://github.com/pgvector/pgvector) and need to insert vector data, you can use the following helper function:\n\n```ts\nconst vector = (embeddings: number[]) =\u003e {\n  return sql.fragment`${sql.array(Array.from(embeddings), sql.fragment`real[]`)}::vector`;\n};\n```\n\nNow you can use the `vector` helper function to insert vector data:\n\n```ts\nawait connection.query(sql.typeAlias(\"void\")`\n  INSERT INTO embeddings (id, vector)\n  VALUES (1, ${vector(embedding.data)})\n`);\n```\n\nYou can also use the [`pgvector` NPM package](https://github.com/pgvector/pgvector-node/?tab=readme-ov-file#slonik) to achieve the same result.\n\n## Runtime validation\n\nSlonik integrates [zod](https://github.com/colinhacks/zod) to provide runtime query result validation and static type inference.\n\nValidating queries requires to:\n\n1. Add a [result parser interceptor](#result-parser-interceptor) during slonik initiialization\n1. For every query define a Zod [object](https://github.com/colinhacks/zod#objects) and pass it to `sql.type` tagged template ([see below](#example-use-of-sqltype))\n\n### Motivation\n\nBuild-time type safety guarantees that your application will work as expected at the time of the build (assuming that the types are correct in the first place).\n\nThe problem is that once you deploy the application, the database schema might change independently of the codebase. This drift may result in your application behaving in unpredictable and potentially dangerous ways, e.g., imagine if table `product` changed `price` from `numeric` to `text`. Without runtime validation, this would cause a cascade of problems and potential database corruption. Even worse, without runtime checks, this could go unnoticed for a long time.\n\nIn contrast, by using runtime checks, you can ensure that the contract between your codebase and the database is always respected. If there is a breaking change, the application fails with a loud error that is easy to debug.\n\nBy using `zod`, we get the best of both worlds: type safety and runtime checks.\n\n### Result parser interceptor\n\nSlonik works without the interceptor, but it doesn't validate the query results. To validate results, you must implement an interceptor that parses the results.\n\nFor context, when Zod parsing was first introduced to Slonik, it was enabled for all queries by default. However, I eventually realized that the baked-in implementation is not going to suit everyone's needs. For this reason, I decided to take out the built-in interceptor in favor of providing examples for common use cases. What follows is based on the original default implementation.\n\n```ts\nimport { type Interceptor, type QueryResultRow, SchemaValidationError } from \"slonik\";\n\nconst createResultParserInterceptor = (): Interceptor =\u003e {\n  return {\n    name: \"slonik-interceptor-zod-validation\",\n    // If you are not going to transform results using Zod, then you should use `afterQueryExecution` instead.\n    // Future versions of Zod will provide a more efficient parser when parsing without transformations.\n    // You can even combine the two – use `afterQueryExecution` to validate results, and (conditionally)\n    // transform results as needed in `transformRowAsync`.\n    transformRowAsync: async (executionContext, actualQuery, row) =\u003e {\n      const { log, resultParser } = executionContext;\n\n      if (!resultParser) {\n        return row;\n      }\n\n      // It is recommended (but not required) to parse async to avoid blocking the event loop during validation\n      const validationResult = await resultParser[\"~standard\"].validate(row);\n\n      if (validationResult.issues) {\n        throw new SchemaValidationError(actualQuery, row, validationResult.issues);\n      }\n\n      return validationResult.value as QueryResultRow;\n    },\n  };\n};\n```\n\nTo use it, simply add it as a middleware:\n\n```ts\nimport { createPool } from \"slonik\";\nimport { createPgDriverFactory } from \"@slonik/pg-driver\";\n\ncreatePool(\"postgresql://\", {\n  driverFactory: createPgDriverFactory(),\n  interceptors: [createResultParserInterceptor()],\n});\n```\n\n### Example use of \u003ccode\u003esql.type\u003c/code\u003e\n\nLet's assume that you have a PostgreSQL table `person`:\n\n```sql\nCREATE TABLE \"public\".\"person\" (\n  \"id\" integer GENERATED ALWAYS AS IDENTITY,\n  \"name\" text NOT NULL,\n  PRIMARY KEY (\"id\")\n);\n```\n\nand you want to retrieve all persons in the database, along with their `id` and `name`:\n\n```ts\nconnection.any(sql.unsafe`\n  SELECT id, name\n  FROM person\n`);\n```\n\nWith your knowledge of the database schema, define a zod object:\n\n```ts\nconst personObject = z.object({\n  id: z.number(),\n  name: z.string(),\n});\n```\n\nUpdate your query to use `sql.type` tag and pass `personObject`:\n\n```ts\nconst personQuery = sql.type(personObject)`\n  SELECT id, name\n  FROM person\n`;\n```\n\nFinally, query the database using typed `sql` tagged template:\n\n```ts\nconst persons = await connection.any(personQuery);\n```\n\nWith this information, Slonik guarantees that every member of `persons` is an object that has properties `id` and `name`, which are a non-null `number` and a non-null `string` respectively.\n\n### Performance penalty\n\nIn the context of the network overhead, validation accounts for a tiny amount of the total execution time.\n\nJust to give an idea, in our sample of data, it takes sub 0.1ms to validate 1 row, ~3ms to validate 1,000 and ~25ms to validate 100,000 rows.\n\n### Unknown keys\n\nBy default Zod object schemas strip unknown keys. If you want to disallow unknown keys, you can add [`.strict()`](https://zod.dev/?id=strict) to your schema:\n\n```ts\nz.object({ foo: z.string() }).strict();\n```\n\nUsing the [recommended parser pattern](#result-parser-interceptor), this will produce a `SchemaValidationError`, when a query result includes unknown keys.\n\nConversely you can allow unknown keys to be passed through by using [`.passthrough()`](https://zod.dev/?id=passthrough):\n\n```ts\nz.object({ foo: z.string() }).passthrough();\n```\n\n_Note_: Using `.passthrough()` is not recommended as it reduces type safety and may lead to unexpected behaviour.\n\n### Handling schema validation errors\n\nIf query produces a row that does not satisfy zod object, then `SchemaValidationError` error is thrown.\n\n`SchemaValidationError` includes properties that describe the query and validation errors:\n\n- `sql` – SQL of the query that produced unexpected row.\n- `row` – row data that did not satisfy the schema.\n- `issues` – array of unmet expectations.\n\nWhenever this error occurs, the same information is also included in the [logs](#logging).\n\nIn most cases, you shouldn't attempt to handle these errors at individual query level – allow to propagate to the top of the application and fix the issue when you become aware of it.\n\nHowever, in cases such as dealing with unstructured data, it might be useful to handle these errors at a query level, e.g.\n\n```ts\nimport { SchemaValidationError } from \"slonik\";\ntry {\n} catch (error) {\n  if (error instanceof SchemaValidationError) {\n    // Handle scheme validation error\n  }\n}\n```\n\n### Inferring types\n\nYou can infer the TypeScript type of the query result. There are couple of ways of doing it:\n\n```ts\n// Infer using StandardSchemaV1.\u003ctypeof yourSchema\u003e\n// https://github.com/colinhacks/zod#type-inference\ntype Person = StandardSchemaV1.InferOutput\u003ctypeof personObject\u003e;\n// from sql tagged template `parser` property\ntype Person = StandardSchemaV1.InferOutput\u003cpersonQuery.parser\u003e;\n```\n\n### Transforming results\n\nUsing zod [transform](https://github.com/colinhacks/zod#transform) you can refine the result shape and its type, e.g.\n\n```ts\nconst coordinatesType = z.string().transform((subject) =\u003e {\n  const [x, y] = subject.split(\",\");\n\n  return {\n    x: Number(x),\n    y: Number(y),\n  };\n});\n\nconst zodObject = z.object({\n  foo: coordinatesType,\n});\n\nconst query = sql.type(zodObject)`SELECT '1,2' as foo`;\n\nconst result = await pool.one(query);\n\nexpectTypeOf(result).toMatchTypeOf\u003c{ foo: { x: number; y: number } }\u003e();\n\nt.deepEqual(result, {\n  foo: {\n    x: 1,\n    y: 2,\n  },\n});\n```\n\n## \u003ccode\u003esql\u003c/code\u003e tag\n\n`sql` tag serves two purposes:\n\n- It is used to construct queries with bound parameter values (see [Value placeholders](#value-placeholders)).\n- It used to generate dynamic query fragments (see [Query building](#query-building)).\n\n`sql` tag can be imported from Slonik package:\n\n```ts\nimport { sql } from \"slonik\";\n```\n\nSometimes it may be desirable to construct a custom instance of `sql` tag. In those cases, you can use the `createSqlTag` factory, e.g.\n\n```ts\nimport { createSqlTag } from \"slonik\";\n\nconst sql = createSqlTag();\n```\n\n### Type aliases\n\nYou can create a `sql` tag with a predefined set of Zod type aliases that can be later referenced when creating a query with [runtime validation](#runtime-validation).\n\nSlonik documentation assumes that these type aliases are defined:\n\n```ts\nconst sql = createSqlTag({\n  typeAliases: {\n    // `foo` is a documentation specific example\n    foo: z.object({\n      foo: z.string(),\n    }),\n    id: z.object({\n      id: z.number(),\n    }),\n    void: z.object({}).strict(),\n  },\n});\n```\n\nThese are documentation specific examples that you are not expected to blindly copy. However, `id` and `void` are recommended aliases as they reflect common patterns, e.g.\n\n```ts\nconst personId = await pool.oneFirst(\n  sql.typeAlias(\"id\")`\n    SELECT id\n    FROM person\n  `,\n);\n\nawait pool.query(sql.typeAlias(\"void\")`\n  INSERT INTO person_view (person_id)\n  VALUES (${personId})\n`);\n```\n\n### Typing \u003ccode\u003esql\u003c/code\u003e tag\n\nSee [runtime validation](#runtime-validation).\n\n## Value placeholders\n\n### Tagged template literals\n\nSlonik query methods can only be executed using `sql` [tagged template literal](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals), e.g.\n\n```ts\nimport { sql } from \"slonik\";\n\nconnection.query(sql.typeAlias(\"id\")`\n  SELECT 1 AS id\n  FROM foo\n  WHERE bar = ${\"baz\"}\n`);\n```\n\nThe above is equivalent to evaluating:\n\n```sql\nSELECT 1 AS id\nFROM foo\nWHERE bar = $1\n\n```\n\nquery with 'baz' value binding.\n\n### Manually constructing the query\n\nManually constructing queries is not allowed.\n\nThere is an internal mechanism that checks to see if query was created using `sql` tagged template literal, i.e.\n\n```ts\nconst query = {\n  sql: \"SELECT 1 AS id FROM foo WHERE bar = $1\",\n  type: \"SQL\",\n  values: [\"baz\"],\n};\n\nconnection.query(query);\n```\n\nWill result in an error:\n\n\u003e Query must be constructed using `sql` tagged template literal.\n\nThis is a security measure designed to prevent unsafe query execution.\n\nFurthermore, a query object constructed using `sql` tagged template literal is [frozen](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) to prevent further manipulation.\n\n### Nesting \u003ccode\u003esql\u003c/code\u003e\n\n`sql` tagged template literals can be nested, e.g.\n\n```ts\nconst query0 = sql.unsafe`SELECT ${\"foo\"} FROM bar`;\nconst query1 = sql.unsafe`SELECT ${\"baz\"} FROM (${query0})`;\n```\n\nProduces:\n\n```ts\n{\n  sql: 'SELECT $1 FROM (SELECT $2 FROM bar)',\n  values: [\n    'baz',\n    'foo'\n  ]\n}\n```\n\n## Query building\n\nQueries are built using methods of the `sql` tagged template literal.\n\nIf this is your first time using Slonik, read [Dynamically generating SQL queries using Node.js](https://dev.to/gajus/dynamically-generating-sql-queries-using-node-js-2c1g).\n\n### \u003ccode\u003esql.and\u003c/code\u003e\n\n```ts\n(members: (ValueExpression | false | null | undefined)[]) =\u003e FragmentSqlToken | ListSqlToken;\n```\n\nConcatenates SQL expressions using `AND`. Members that are `false`, `null`, or `undefined` are silently filtered out, making it easy to build conditional WHERE clauses:\n\n```ts\nawait connection.query(sql.unsafe`\n  SELECT *\n  FROM foo\n  WHERE ${sql.and([\n    sql.fragment`bar = ${1}`,\n    name \u0026\u0026 sql.fragment`name = ${name}`,\n    age \u0026\u0026 sql.fragment`age \u003e ${age}`,\n  ])}\n`);\n```\n\nIf `name` is `undefined` and `age` is `30`, this produces:\n\n```ts\n{\n  sql: 'SELECT * FROM foo WHERE bar = $1 AND age \u003e $2',\n  values: [1, 30]\n}\n```\n\nIf all members are filtered out (or the array is empty), `sql.and` produces `TRUE`:\n\n```ts\nsql.fragment`WHERE ${sql.and([false, null, undefined])}`;\n// WHERE TRUE\n```\n\n### \u003ccode\u003esql.array\u003c/code\u003e\n\n```ts\n(\n  values: readonly PrimitiveValueExpression[],\n  memberType: SqlFragment | TypeNameIdentifier,\n) =\u003e ArraySqlToken,\n```\n\nCreates an array value binding, e.g.\n\n```ts\nawait connection.query(sql.typeAlias(\"id\")`\n  SELECT (${sql.array([1, 2, 3], \"int4\")}) AS id\n`);\n```\n\nProduces:\n\n```ts\n{\n  sql: 'SELECT $1::\"int4\"[]',\n  values: [\n    [\n      1,\n      2,\n      3\n    ]\n  ]\n}\n```\n\n#### \u003ccode\u003esql.array\u003c/code\u003e \u003ccode\u003ememberType\u003c/code\u003e\n\nIf `memberType` is a string (`TypeNameIdentifier`), then it is treated as a type name identifier and will be quoted using double quotes, i.e. `sql.array([1, 2, 3], 'int4')` is equivalent to `$1::\"int4\"[]`. The implication is that keywords that are often used interchangeably with type names are not going to work, e.g. [`int4`](https://github.com/postgres/postgres/blob/69edf4f8802247209e77f69e089799b3d83c13a4/src/include/catalog/pg_type.dat#L74-L78) is a type name identifier and will work. However, [`int`](https://github.com/postgres/postgres/blob/69edf4f8802247209e77f69e089799b3d83c13a4/src/include/parser/kwlist.h#L213) is a keyword and will not work. You can either use type name identifiers or you can construct custom member using `sql.fragment` tag, e.g.\n\n```ts\nawait connection.query(sql.typeAlias(\"id\")`\n  SELECT (${sql.array([1, 2, 3], sql.fragment`int[]`)}) AS id\n`);\n```\n\nProduces:\n\n```ts\n{\n  sql: 'SELECT $1::int[]',\n  values: [\n    [\n      1,\n      2,\n      3\n    ]\n  ]\n}\n```\n\n#### \u003ccode\u003esql.array\u003c/code\u003e vs \u003ccode\u003esql.join\u003c/code\u003e\n\nUnlike `sql.join`, `sql.array` generates a stable query of a predictable length, i.e. regardless of the number of values in the array, the generated query remains the same:\n\n- Having a stable query enables [`pg_stat_statements`](https://www.postgresql.org/docs/current/pgstatstatements.html) to aggregate all query execution statistics.\n- Keeping the query length short reduces query parsing time.\n\nExample:\n\n```ts\nsql.typeAlias(\"id\")`\n  SELECT id\n  FROM foo\n  WHERE id IN (${sql.join([1, 2, 3], sql.fragment`, `)})\n`;\nsql.typeAlias(\"id\")`\n  SELECT id\n  FROM foo\n  WHERE id NOT IN (${sql.join([1, 2, 3], sql.fragment`, `)})\n`;\n```\n\nIs equivalent to:\n\n```ts\nsql.typeAlias(\"id\")`\n  SELECT id\n  FROM foo\n  WHERE id = ANY(${sql.array([1, 2, 3], \"int4\")})\n`;\nsql.typeAlias(\"id\")`\n  SELECT id\n  FROM foo\n  WHERE id != ALL(${sql.array([1, 2, 3], \"int4\")})\n`;\n```\n\nFurthermore, unlike `sql.join`, `sql.array` can be used with an empty array of values. In short, `sql.array` should be preferred over `sql.join` when possible.\n\n### \u003ccode\u003esql.binary\u003c/code\u003e\n\n```ts\n(data: Buffer) =\u003e BinarySqlToken;\n```\n\nBinds binary ([`bytea`](https://www.postgresql.org/docs/current/datatype-binary.html)) data, e.g.\n\n```ts\nawait connection.query(sql.unsafe`\n  SELECT ${sql.binary(Buffer.from(\"foo\"))}\n`);\n```\n\nProduces:\n\n```ts\n{\n  sql: 'SELECT $1',\n  values: [\n    Buffer.from('foo')\n  ]\n}\n```\n\n### \u003ccode\u003esql.date\u003c/code\u003e\n\n```ts\n(date: Date | TemporalPlainDate) =\u003e DateSqlToken;\n```\n\nInserts a date, e.g.\n\n```ts\nawait connection.query(sql.unsafe`\n  SELECT ${sql.date(new Date(\"2022-08-19T03:27:24.951Z\"))}\n`);\n```\n\nProduces:\n\n```ts\n{\n  sql: 'SELECT $1::date',\n  values: [\n    '2022-08-19'\n  ]\n}\n```\n\n[`Temporal.PlainDate`](https://tc39.es/proposal-temporal/docs/plaindate.html) is also accepted:\n\n```ts\nawait connection.query(sql.unsafe`\n  SELECT ${sql.date(Temporal.PlainDate.from(\"2022-08-19\"))}\n`);\n```\n\n### \u003ccode\u003esql.fragment\u003c/code\u003e\n\n```ts\n(template: TemplateStringsArray, ...values: ValueExpression[]) =\u003e SqlFragment;\n```\n\nA SQL fragment, e.g.\n\n```ts\nsql.fragment`FOO`;\n```\n\nProduces:\n\n```ts\n{\n  sql: 'FOO',\n  values: []\n}\n```\n\nSQL fragments can be used to build more complex queries, e.g.\n\n```ts\nconst whereFragment = sql.fragment`\n  WHERE bar = 'baz';\n`;\n\nsql.typeAlias(\"id\")`\n  SELECT id\n  FROM foo\n  ${whereFragment}\n`;\n```\n\n#### Fragments vs Queries\n\nThere are two primary differences:\n\n- Fragments are untyped and they cannot be used as inputs to query methods (use `sql.type` instead).\n- Queries are expected to be valid SQL if executed (e.g. `SELECT * FROM foo`); fragments are expected to be valid _fragments_ of SQL (e.g. `WHERE bar = 1`).\n\n\u003e [!WARNING]\n\u003e Due to the way that Slonik internally represents SQL fragments, your query must not contain `$slonik_` literals.\n\n### \u003ccode\u003esql.identifier\u003c/code\u003e\n\n```ts\n(names: string[]) =\u003e IdentifierSqlToken;\n```\n\n[Delimited identifiers](https://www.postgresql.org/docs/current/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS) are created by enclosing an arbitrary sequence of characters in double-quotes (\"). To create a delimited identifier, create an `sql` tag function placeholder value using `sql.identifier`, e.g.\n\n```ts\nsql.typeAlias(\"id\")`\n  SELECT 1 AS id\n  FROM ${sql.identifier([\"bar\", \"baz\"])}\n`;\n```\n\nProduces:\n\n```ts\n{\n  sql: 'SELECT 1 FROM \"bar\".\"baz\"',\n  values: []\n}\n```\n\n### \u003ccode\u003esql.interval\u003c/code\u003e\n\n```ts\n(\n  interval:\n    | {\n        years?: number;\n        months?: number;\n        weeks?: number;\n        days?: number;\n        hours?: number;\n        minutes?: number;\n        seconds?: number;\n      }\n    | TemporalDuration,\n) =\u003e IntervalSqlToken;\n```\n\nInserts an [interval](https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-INTERVAL-INPUT), e.g.\n\n```ts\nsql.typeAlias(\"id\")`\n  SELECT 1 AS id\n  FROM ${sql.interval({ days: 3 })}\n`;\n```\n\nProduces:\n\n```ts\n{\n  sql: 'SELECT make_interval(\"days\" =\u003e $1)',\n  values: [\n    3\n  ]\n}\n```\n\n[`Temporal.Duration`](https://tc39.es/proposal-temporal/docs/duration.html) is also accepted (zero-valued fields are omitted):\n\n```ts\nsql.typeAlias(\"id\")`\n  SELECT 1 AS id\n  FROM ${sql.interval(Temporal.Duration.from({ days: 3 }))}\n`;\n```\n\nYou can use `sql.interval` exactly how you would use PostgreSQL [`make_interval` function](https://www.postgresql.org/docs/current/functions-datetime.html). However, notice that Slonik does not use abbreviations, i.e. \"secs\" is seconds and \"mins\" is minutes.\n\n| `make_interval`                            | `sql.interval`                      | Interval output  |\n| ------------------------------------------ | ----------------------------------- | ---------------- |\n| `make_interval(\"days\" =\u003e 1, \"hours\" =\u003e 2)` | `sql.interval({days: 1, hours: 2})` | `1 day 02:00:00` |\n| `make_interval(\"mins\" =\u003e 1)`               | `sql.interval({minutes: 1})`        | `00:01:00`       |\n| `make_interval(\"secs\" =\u003e 120)`             | `sql.interval({seconds: 120})`      | `00:02:00`       |\n| `make_interval(\"secs\" =\u003e 0.001)`           | `sql.interval({seconds: 0.001})`    | `00:00:00.001`   |\n\n#### Dynamic intervals without \u003ccode\u003esql.interval\u003c/code\u003e\n\nIf you need a dynamic interval (e.g. X days), you can achieve this using multiplication, e.g.\n\n```ts\nsql.unsafe`\n  SELECT ${2} * interval '1 day'\n`;\n```\n\nThe above is equivalent to `interval '2 days'`.\n\nYou could also use `make_interval()` directly, e.g.\n\n```ts\nsql.unsafe`\n  SELECT make_interval(\"days\" =\u003e ${2})\n`;\n```\n\n`sql.interval` was added mostly as a type-safe alternative.\n\n### \u003ccode\u003esql.join\u003c/code\u003e\n\n\u003e [!CAUTION]\n\u003e In most cases, prefer [`sql.array`](#sqlarray) for value lists, [`sql.and`](#sqland) for `AND`-separated conditions, or [`sql.or`](#sqlor) for `OR`-separated conditions. `sql.join` is useful in a narrow set of scenarios where a custom glue is needed, such as `ORDER BY` clauses, `UNION` construction, or comma-separated fragment lists.\n\n```ts\n(members: SqlSqlToken[], glue: SqlSqlToken) =\u003e ListSqlToken;\n```\n\nConcatenates SQL expressions using `glue` separator. Use `sql.join` when you need a custom glue that isn't covered by [`sql.and`](#sqland), [`sql.or`](#sqlor), or [`sql.array`](#sqlarray).\n\nDynamic ORDER BY:\n\n```ts\nconst orderClauses = [sql.fragment`created_at DESC`, sql.fragment`id ASC`];\n\nsql.unsafe`\n  SELECT * FROM foo\n  ORDER BY ${sql.join(orderClauses, sql.fragment`, `)}\n`;\n\n// SELECT * FROM foo ORDER BY created_at DESC, id ASC\n```\n\nUNION:\n\n```ts\nsql.unsafe`\n  ${sql.join(\n    [sql.fragment`SELECT * FROM foo`, sql.fragment`SELECT * FROM bar`],\n    sql.fragment` UNION ALL `,\n  )}\n`;\n\n// SELECT * FROM foo UNION ALL SELECT * FROM bar\n```\n\nDynamic SELECT columns:\n\n```ts\nconst columns = [sql.fragment`foo.name`, sql.fragment`bar.created_at`];\n\nsql.unsafe`\n  SELECT ${sql.join(columns, sql.fragment`, `)}\n  FROM foo\n  JOIN bar ON bar.foo_id = foo.id\n`;\n\n// SELECT foo.name, bar.created_at FROM foo JOIN bar ON bar.foo_id = foo.id\n```\n\n### \u003ccode\u003esql.json\u003c/code\u003e\n\n```ts\n(value: SerializableValue) =\u003e JsonSqlToken;\n```\n\nSerializes value and binds it as a JSON string literal, e.g.\n\n```ts\nawait connection.query(sql.unsafe`\n  SELECT (${sql.json([1, 2, 3])})\n`);\n```\n\nProduces:\n\n```ts\n{\n  sql: 'SELECT $1::json',\n  values: [\n    '[1,2,3]'\n  ]\n}\n```\n\n#### Payload handling\n\n`sql.json` and `sql.jsonb` accept `Record\u003cstring, unknown\u003e` and `unknown[]` payloads directly (e.g. values from `JSON.parse` or generic event payloads), so no cast is required.\n\nBefore serializing, the payload is walked for characters that PostgreSQL rejects in JSON values — null bytes (`U+0000`) and unpaired UTF-16 surrogates. Either causes an `InvalidInputError` with a JSON path (e.g. `$.foo.bar[1]`) pointing at the offending value. This surfaces the bug at the call site rather than letting PostgreSQL fail the query with a generic error.\n\nIf your input is untrusted or may contain JS values that don't survive `JSON.stringify` cleanly (e.g. `Error`, `Map`, `Set`, `BigInt`, `Date` with custom shape requirements, circular references), pre-sanitize before passing it in:\n\n```ts\nconst safeJson = (value: unknown) =\u003e sql.json(sanitizeJsonPayload(value));\n\nawait connection.query(sql.unsafe`\n  INSERT INTO event (payload) VALUES (${safeJson(eventPayload)})\n`);\n```\n\nSlonik does not bundle a sanitizer because the right policy (how to serialize `Error`, whether to strip or throw on null bytes, how to represent `BigInt`, etc.) is application-specific.\n\n### \u003ccode\u003esql.jsonb\u003c/code\u003e\n\n\u003e [!NOTE]\n\u003e See [Payload handling](#payload-handling) for notes on accepted payload types, payload validation, and the recommended sanitizer pattern. They apply equally to `sql.jsonb`.\n\n```ts\n(value: SerializableValue) =\u003e JsonBinarySqlToken;\n```\n\nSerializes value and binds it as a JSON binary, e.g.\n\n```ts\nawait connection.query(sql.unsafe`\n  SELECT (${sql.jsonb([1, 2, 3])})\n`);\n```\n\nProduces:\n\n```ts\n{\n  sql: 'SELECT $1::jsonb',\n  values: [\n    '[1,2,3]'\n  ]\n}\n```\n\n### \u003ccode\u003esql.list\u003c/code\u003e\n\n```ts\n(members: ValueExpression[]) =\u003e ListSqlToken;\n```\n\nConcatenates SQL expressions using `, `. Use for SELECT columns, ORDER BY clauses, tuple construction, and other comma-separated lists:\n\n```ts\nconst columns = [sql.fragment`name`, sql.fragment`created_at`];\n\nawait connection.query(sql.unsafe`\n  SELECT ${sql.list(columns)}\n  FROM foo\n`);\n```\n\nProduces:\n\n```ts\n{\n  sql: 'SELECT name, created_at FROM foo',\n  values: []\n}\n```\n\nThis is equivalent to `sql.join(members, sql.fragment`, `)`.\n\n### \u003ccode\u003esql.or\u003c/code\u003e\n\n```ts\n(members: (ValueExpression | false | null | undefined)[]) =\u003e FragmentSqlToken | ListSqlToken;\n```\n\nConcatenates SQL expressions using `OR`. Like [`sql.and`](#sqland), members that are `false`, `null`, or `undefined` are silently filtered out:\n\n```ts\nawait connection.query(sql.unsafe`\n  SELECT *\n  FROM foo\n  WHERE ${sql.or([name \u0026\u0026 sql.fragment`name = ${name}`, email \u0026\u0026 sql.fragment`email = ${email}`])}\n`);\n```\n\nIf all members are filtered out (or the array is empty), `sql.or` produces `FALSE`:\n\n```ts\nsql.fragment`WHERE ${sql.or([false, null, undefined])}`;\n// WHERE FALSE\n```\n\n### \u003ccode\u003esql.literalValue\u003c/code\u003e\n\n\u003e [!WARNING]\n\u003e Do not use. This method interpolates values as literals and it must be used only for [building utility statements](#building-utility-statements). You are most likely looking for [value placeholders](#value-placeholders).\n\n```ts\n(value: string) =\u003e SqlSqlToken;\n```\n\nEscapes and interpolates a literal value into a query.\n\n```ts\nawait connection.query(sql.unsafe`\n  CREATE USER \"foo\" WITH PASSWORD ${sql.literalValue(\"bar\")}\n`);\n```\n\nProduces:\n\n```ts\n{\n  sql: \"CREATE USER \\\"foo\\\" WITH PASSWORD 'bar'\";\n}\n```\n\n### \u003ccode\u003esql.timestamp\u003c/code\u003e\n\n```ts\n(date: Date | TemporalInstant) =\u003e TimestampSqlToken;\n```\n\nInserts a timestamp, e.g.\n\n```ts\nawait connection.query(sql.unsafe`\n  SELECT ${sql.timestamp(new Date(\"2022-08-19T03:27:24.951Z\"))}\n`);\n```\n\nProduces:\n\n```ts\n{\n  sql: 'SELECT to_timestamp($1)',\n  values: [\n    '1660879644.951'\n  ]\n}\n```\n\n[`Temporal.Instant`](https://tc39.es/proposal-temporal/docs/instant.html) and [`Temporal.ZonedDateTime`](https://tc39.es/proposal-temporal/docs/zoneddatetime.html) are also accepted (any object with an `epochMilliseconds` property):\n\n```ts\nawait connection.query(sql.unsafe`\n  SELECT ${sql.timestamp(Temporal.Now.instant())}\n`);\n```\n\n### \u003ccode\u003esql.unnest\u003c/code\u003e\n\n\u003e [!NOTE]\n\u003e For bulk inserts, prefer using `jsonb_to_recordset` with [`sql.jsonb`](#sqljsonb) instead. It offers the same performance with better ergonomics for complex data. See [Inserting large number of rows](#inserting-large-number-of-rows).\n\n```ts\n(\n  tuples: ReadonlyArray\u003creadonly any[]\u003e,\n  columnTypes:  Array\u003c[...string[], TypeNameIdentifier]\u003e | Array\u003cSqlSqlToken | TypeNameIdentifier\u003e\n): UnnestSqlToken;\n```\n\nCreates an `unnest` expressions, e.g.\n\n```ts\nawait connection.query(sql.unsafe`\n  SELECT bar, baz\n  FROM ${sql.unnest(\n    [\n      [1, \"foo\"],\n      [2, \"bar\"],\n    ],\n    [\"int4\", \"text\"],\n  )} AS foo(bar, baz)\n`);\n```\n\nProduces:\n\n```ts\n{\n  sql: 'SELECT bar, baz FROM unnest($1::\"int4\"[], $2::\"text\"[]) AS foo(bar, baz)',\n  values: [\n    [\n      1,\n      2\n    ],\n    [\n      'foo',\n      'bar'\n    ]\n  ]\n}\n```\n\nIf `columnType` array member type is `string`, it will treat it as a type name identifier (and quote with double quotes; illustrated in the example above).\n\nIf `columnType` array member type is `SqlToken`, it will inline type name without quotes, e.g.\n\n```ts\nawait connection.query(sql.unsafe`\n  SELECT bar, baz\n  FROM ${sql.unnest(\n    [\n      [1, \"foo\"],\n      [2, \"bar\"],\n    ],\n    [sql.fragment`integer`, sql.fragment`text`],\n  )} AS foo(bar, baz)\n`);\n```\n\nProduces:\n\n```ts\n{\n  sql: 'SELECT bar, baz FROM unnest($1::integer[], $2::text[]) AS foo(bar, baz)',\n  values: [\n    [\n      1,\n      2\n    ],\n    [\n      'foo',\n      'bar'\n    ]\n  ]\n}\n```\n\nIf `columnType` array member type is `[...string[], TypeNameIdentifier]`, it will act as [`sql.identifier`](#sqlidentifier), e.g.\n\n```ts\nawait connection.query(sql.unsafe`\n  SELECT bar, baz\n  FROM ${sql.unnest(\n    [\n      [1, 3],\n      [2, 4],\n    ],\n    [\n      [\"foo\", \"int4\"],\n      [\"foo\", \"int4\"],\n    ],\n  )} AS foo(bar, baz)\n`);\n```\n\nProduces:\n\n```ts\n{\n  sql: 'SELECT bar, baz FROM unnest($1::\"foo\".\"int4\"[], $2::\"foo\".\"int4\"[]) AS foo(bar, baz)',\n  values: [\n    [\n      1,\n      2\n    ],\n    [\n      3,\n      4\n    ]\n  ]\n}\n```\n\n### \u003ccode\u003esql.unsafe\u003c/code\u003e\n\n```ts\n(template: TemplateStringsArray, ...values: ValueExpression[]) =\u003e QuerySqlToken;\n```\n\nCreates a query with Zod `any` type. The result of such a query has TypeScript type `any`.\n\n```ts\nconst result = await connection.one(sql.unsafe`\n  SELECT foo\n  FROM bar\n`);\n\n// `result` type is `any`\n```\n\n`sql.unsafe` is effectively a shortcut to `sql.type(z.any())`.\n\n`sql.unsafe` is as a convenience method for development. Your production code must not use `sql.unsafe`. Instead,\n\n- Use `sql.type` to type the query result\n- Use `sql.typeAlias` to alias an existing type\n- Use `sql.fragment` if you are writing a fragment of a query\n\n### \u003ccode\u003esql.uuid\u003c/code\u003e\n\n```ts\n(uuid: string) =\u003e TimestampSqlToken;\n```\n\nInserts a UUID, e.g.\n\n```ts\nawait connection.query(sql.unsafe`\n  SELECT ${sql.uuid(\"00000000-0000-0000-0000-000000000000\")}\n`);\n```\n\nProduces:\n\n```ts\n{\n  sql: 'SELECT $1::uuid',\n  values: [\n    '00000000-0000-0000-0000-000000000000'\n  ]\n}\n```\n\n### \u003ccode\u003esql.prepared\u003c/code\u003e\n\n```ts\n\u003cSchema extends StandardSchemaV1 | ZodTypeAny\u003e(name: string, schema: Schema) =\u003e\n  (template: TemplateStringsArray, ...values: ValueExpression[]) =\u003e\n    QuerySqlToken;\n```\n\nCreates a [named prepared statement](https://www.postgresql.org/docs/current/sql-prepare.html). Named prepared statements allow PostgreSQL to parse and plan a query once, then execute it multiple times with different parameter values. This can significantly improve performance for frequently executed queries.\n\n```ts\nimport { z } from \"zod\";\n\nconst PersonSchema = z.object({\n  id: z.number(),\n  name: z.string(),\n});\n\n// Create a named prepared statement\nconst getPersonQuery = sql.prepared(\"get_person\", PersonSchema)`\n  SELECT id, name\n  FROM person\n  WHERE id = ${1}\n`;\n\n// Execute within a connection session\nawait pool.connect(async (connection) =\u003e {\n  // First execution parses and plans the statement\n  const person1 = await connection.one(\n    sql.prepared(\"get_person\", PersonSchema)`\n      SELECT id, name\n      FROM person\n      WHERE id = ${1}\n    `,\n  );\n\n  // Subsequent executions reuse the prepared statement\n  const person2 = await connection.one(\n    sql.prepared(\"get_person\", PersonSchema)`\n      SELECT id, name\n      FROM person\n      WHERE id = ${2}\n    `,\n  );\n});\n```\n\n\u003e [!IMPORTANT]\n\u003e Named prepared statements are connection-specific in PostgreSQL. They are deallocated when:\n\u003e\n\u003e - The connection is closed\n\u003e - `DISCARD ALL` is executed (which is the default `resetConnection` behavior)\n\u003e - The statement is explicitly deallocated with `DEALLOCATE`\n\u003e\n\u003e For this reason, named prepared statements should be used within a single connection session (using `pool.connect()`) or within a transaction. If you use named prepared statements without `pool.connect()`, each query may be executed on a different connection from the pool, and the prepared statement may not exist.\n\n#### When to use named prepared statements\n\nNamed prepared statements are beneficial when:\n\n- You execute the same query many times within a single connection session\n- The query is complex and has a significant parsing/planning cost\n- You want to reduce the overhead of query preparation\n\nFor most use cases, the default extended query protocol (which uses unnamed prepared statements) is sufficient and you don't need to use `sql.prepared`.\n\n## Tips\n\n### Prefer \u003ccode\u003esql.and\u003c/code\u003e, \u003ccode\u003esql.or\u003c/code\u003e, and \u003ccode\u003esql.list\u003c/code\u003e over \u003ccode\u003esql.join\u003c/code\u003e\n\nWhen building dynamic SQL, prefer the semantic helpers over `sql.join` with an explicit glue fragment:\n\n```ts\n// Hard to scan — the intent is buried in the glue argument\nsql.join([sql.fragment`bar = ${1}`, sql.fragment`baz = ${2}`], sql.fragment` AND `);\n\n// Clear at a glance\nsql.and([sql.fragment`bar = ${1}`, sql.fragment`baz = ${2}`]);\n```\n\nWith `sql.join`, you only learn what the expression does when you reach the glue argument at the end. With `sql.and`, `sql.or`, and `sql.list`, the intent is immediately obvious from the method name. This matters most in code review and when scanning unfamiliar code.\n\n`sql.and` and `sql.or` also filter out `false`, `null`, and `undefined` members, which makes conditional WHERE clauses concise:\n\n```ts\nsql.and([\n  sql.fragment`status = ${status}`,\n  name \u0026\u0026 sql.fragment`name = ${name}`,\n  minAge \u0026\u0026 sql.fragment`age \u003e= ${minAge}`,\n]);\n```\n\nReserve `sql.join` for cases where you need a custom glue that isn't `AND`, `OR`, or `, ` — such as `UNION ALL` or `INTERSECT`.\n\n### Hoist Zod schemas with \u003ccode\u003ebabel-plugin-zod-hoist\u003c/code\u003e\n\nEvery Slonik query requires a result schema (via `sql.type` or `sql.typeAlias`). When schemas are defined inline, they are re-initialized on every function call:\n\n```ts\nconst getUser = (id: number) =\u003e {\n  // A new Zod object is allocated every time getUser is called\n  return pool.one(\n    sql.type(\n      z.object({\n        id: z.number(),\n        name: z.string(),\n      }),\n    )`SELECT id, name FROM users WHERE id = ${id}`,\n  );\n};\n```\n\n[`babel-plugin-zod-hoist`](https://github.com/gajus/babel-plugin-zod-hoist) automatically lifts Zod schemas to module scope at build time, so the schema is allocated once and reused:\n\n```ts\n// After babel-plugin-zod-hoist transforms the code:\nconst _schema = z.object({\n  id: z.number(),\n  name: z.string(),\n});\n\nconst getUser = (id: number) =\u003e {\n  return pool.one(sql.type(_schema)`SELECT id, name FROM users WHERE id = ${id}`);\n};\n```\n\nThis eliminates repeated allocations without requiring you to manually extract schemas.\n\n### Validate SQL queries with \u003ccode\u003eeslint-plugin-slonik\u003c/code\u003e\n\n[`eslint-plugin-slonik`](https://github.com/gajus/eslint-plugin-slonik) validates SQL queries written with Slonik against your database schema at lint time. It catches references to non-existent tables and columns, type mismatches between query results and Zod schemas, and other common mistakes before they reach runtime.\n\n## Query methods\n\n### \u003ccode\u003eany\u003c/code\u003e\n\nReturns result rows.\n\nExample:\n\n```ts\nconst rows = await connection.any(sql.typeAlias(\"foo\")`SELECT foo`);\n```\n\n`#any` is similar to `#query` except that it returns rows without fields information.\n\n### \u003ccode\u003eanyFirst\u003c/code\u003e\n\nReturns value of the first column of every row in the result set.\n\n- Throws `DataIntegrityError` if query returns multiple columns.\n\nExample:\n\n```ts\nconst fooValues = await connection.anyFirst(sql.typeAlias(\"foo\")`SELECT foo`);\n```\n\n### \u003ccode\u003eexists\u003c/code\u003e\n\nReturns a boolean value indicating whether query produces results.\n\nThe query that is passed to this function is wrapped in `SELECT exists()` prior to it getting executed, i.e.\n\n```ts\npool.exists(sql.typeAlias(\"void\")`\n  SELECT\n  LIMIT 1\n`);\n```\n\nis equivalent to:\n\n```ts\npool.oneFirst(sql.unsafe`\n  SELECT exists(\n    SELECT\n    LIMIT 1\n  )\n`);\n```\n\n### \u003ccode\u003emany\u003c/code\u003e\n\nReturns result rows.\n\n- Throws `NotFoundError` if query returns no rows.\n\nExample:\n\n```ts\nconst rows = await connection.many(sql.typeAlias(\"foo\")`SELECT foo`);\n```\n\n### \u003ccode\u003emanyFirst\u003c/code\u003e\n\nReturns value of the first column of every row in the result set.\n\n- Throws `NotFoundError` if query returns no rows.\n- Throws `DataIntegrityError` if query returns multiple columns.\n\nExample:\n\n```ts\nconst fooValues = await connection.manyFirst(sql.typeAlias(\"foo\")`SELECT foo`);\n```\n\n### \u003ccode\u003emaybeOne\u003c/code\u003e\n\nSelects the first row from the result.\n\n- Returns `null` if row is not found.\n- Throws `DataIntegrityError` if query returns multiple rows.\n\nExample:\n\n```ts\nconst row = await connection.maybeOne(sql.typeAlias(\"foo\")`SELECT foo`);\n\n// row.foo is the result of the `foo` column value of the first row.\n```\n\n### \u003ccode\u003emaybeOneFirst\u003c/code\u003e\n\nReturns value of the first column from the first row.\n\n- Returns `null` if row is not found.\n- Throws `DataIntegrityError` if query returns multiple rows.\n- Throws `DataIntegrityError` if query returns multiple columns.\n\nExample:\n\n```ts\nconst foo = await connection.maybeOneFirst(sql.typeAlias(\"foo\")`SELECT foo`);\n\n// foo is the result of the `foo` column value of the first row.\n```\n\n### \u003ccode\u003eone\u003c/code\u003e\n\nSelects the first row from the result.\n\n- Throws `NotFoundError` if query returns no rows.\n- Throws `DataIntegrityError` if query returns multiple rows.\n\nExample:\n\n```ts\nconst row = await connection.one(sql.typeAlias(\"foo\")`SELECT foo`);\n\n// row.foo is the result of the `foo` column value of the first row.\n```\n\n\u003e Note:\n\u003e\n\u003e I've been asked \"What makes this different from [knex.js](http://knexjs.org/) `knex('foo').limit(1)`?\".\n\u003e `knex('foo').limit(1)` simply generates \"SELECT \\* FROM foo LIMIT 1\" query.\n\u003e `knex` is a query builder; it does not assert the value of the result.\n\u003e Slonik `#one` adds assertions about the result of the query.\n\n### \u003ccode\u003eoneFirst\u003c/code\u003e\n\nReturns value of the first column from the first row.\n\n- Throws `NotFoundError` if query returns no rows.\n- Throws `DataIntegrityError` if query returns multiple rows.\n- Throws `DataIntegrityError` if query returns multiple columns.\n\nExample:\n\n```ts\nconst foo = await connection.oneFirst(sql.typeAlias(\"foo\")`SELECT foo`);\n\n// foo is the result of the `foo` column value of the first row.\n```\n\n### \u003ccode\u003equery\u003c/code\u003e\n\nAPI and the result shape are equivalent to [`pg#query`](https://github.com/brianc/node-postgres).\n\nExample:\n\n```ts\nawait connection.query(sql.typeAlias(\"foo\")`SELECT foo`);\n\n// {\n//   command: 'SELECT',\n//   fields: [],\n//   notices: [],\n//   rowCount: 1,\n//   rows: [\n//     {\n//       foo: 'bar'\n//     }\n//   ]\n// }\n```\n\n### \u003ccode\u003erecord\u003c/code\u003e\n\nReturns the result rows as a `key` → `value` record (a plain object).\n\nThe query must return exactly two columns, named `\"key\"` and `\"value\"`. The record key type must be a `string` or a `number`, which is enforced by requiring that the query is typed using `sql.type` (or `sql.typeAlias`).\n\n- Throws `DataIntegrityError` if query returns rows with columns other than `key` and `value`.\n- Throws `DataIntegrityError` if query returns duplicate keys.\n\nExample:\n\n```ts\nconst memberCounts = await connection.record(sql.type(\n  z.object({\n    key: z.number(),\n    value: z.number(),\n  }),\n)`\n  SELECT\n    team_id AS \"key\",\n    count(*)::int AS \"value\"\n  FROM team_member\n  GROUP BY team_id\n`);\n\n// memberCounts is Record\u003cnumber, number\u003e, e.g. { 1: 2, 2: 5 }\n```\n\nReturns an empty object if query returns no rows.\n\n\u003e Note:\n\u003e\n\u003e `record` is not just a convenience method – it asserts the integrity of the result.\n\u003e Building the record manually (e.g. using `Object.fromEntries`) silently drops rows\n\u003e that share the same key, e.g. as a result of an incorrect `GROUP BY` clause.\n\u003e `record` makes that a `DataIntegrityError`.\n\n### \u003ccode\u003estream\u003c/code\u003e\n\nStreams query results.\n\nExample:\n\n```ts\nawait connection.stream(sql.typeAlias(\"foo\")`SELECT foo`, (stream) =\u003e {\n  stream.on(\"data\", (row) =\u003e {\n    row;\n    // {\n    //   data: {\n    //     foo: 'bar'\n    //   },\n    //   fields: [\n    //     {\n    //       name: 'foo',\n    //       dataTypeId: 23,\n    //     }\n    //   ]\n    // }\n  });\n});\n```\n\nYou can also use the [AsyncIterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncIterator) interface:\n\n```ts\nawait connection.stream(sql.typeAlias(\"foo\")`SELECT foo`, async (stream) =\u003e {\n  for await (const row of stream) {\n    row;\n    // {\n    //   data: {\n    //     foo: 'bar'\n    //   },\n    //   fields: [\n    //     {\n    //       name: 'foo',\n    //       dataTypeId: 23,\n    //     }\n    //   ]\n    // }\n  }\n});\n```\n\n### \u003ccode\u003etransaction\u003c/code\u003e\n\n`transaction` method is used wrap execution of queries in `START TRANSACTION` and `COMMIT` or `ROLLBACK`. `COMMIT` is called if the transaction handler returns a promise that resolves; `ROLLBACK` is called otherwise.\n\n`transaction` method can be used together with `createPool` method. When used to create a transaction from an instance of a pool, a new connection is allocated for the duration of the transaction.\n\n```ts\nconst result = await connection.transaction(async (transactionConnection) =\u003e {\n  await transactionConnection.query(sql.unsafe`INSERT INTO foo (bar) VALUES ('baz')`);\n  await transactionConnection.query(sql.unsafe`INSERT INTO qux (quux) VALUES ('corge')`);\n\n  return \"FOO\";\n});\n\nresult === \"FOO\";\n```\n\n#### Transaction nesting\n\nSlonik uses [`SAVEPOINT`](https://www.postgresql.org/docs/current/sql-savepoint.html) to automatically nest transactions, e.g.\n\n```ts\nawait connection.transaction(async (t1) =\u003e {\n  await t1.query(sql.unsafe`INSERT INTO foo (bar) VALUES ('baz')`);\n\n  return t1.transaction((t2) =\u003e {\n    return t2.query(sql.unsafe`INSERT INTO qux (quux) VALUES ('corge')`);\n  });\n});\n```\n\nis equivalent to:\n\n```sql\nSTART TRANSACTION;\nINSERT INTO foo (bar) VALUES ('baz');\nSAVEPOINT slonik_savepoint_1;\nINSERT INTO qux (quux) VALUES ('corge');\nCOMMIT;\n```\n\nSlonik automatically rollsback to the last savepoint if a query belonging to a transaction results in an error, e.g.\n\n```ts\nawait connection.transaction(async (t1) =\u003e {\n  await t1.query(sql.unsafe`INSERT INTO foo (bar) VALUES ('baz')`);\n\n  try {\n    await t1.transaction(async (t2) =\u003e {\n      await t2.query(sql.unsafe`INSERT INTO qux (quux) VALUES ('corge')`);\n\n      return Promise.reject(new Error(\"foo\"));\n    });\n  } catch (error) {}\n});\n```\n\nis equivalent to:\n\n```sql\nSTART TRANSACTION;\nINSERT INTO foo (bar) VALUES ('baz');\nSAVEPOINT slonik_savepoint_1;\nINSERT INTO qux (quux) VALUES ('corge');\nROLLBACK TO SAVEPOINT slonik_savepoint_1;\nCOMMIT;\n```\n\nIf error is unhandled, then the entire transaction is rolledback, e.g.\n\n```ts\nawait connection.transaction(async (t1) =\u003e {\n  await t1.query(sql.typeAlias(\"void\")`INSERT INTO foo (bar) VALUES ('baz')`);\n\n  await t1.transaction(async (t2) =\u003e {\n    await t2.query(sql.typeAlias(\"void\")`INSERT INTO qux (quux) VALUES ('corge')`);\n\n    await t1.transaction(async (t3) =\u003e {\n      await t3.query(sql.typeAlias(\"void\")`INSERT INTO uier (grault) VALUES ('garply')`);\n\n      return Promise.reject(new Error(\"foo\"));\n    });\n  });\n});\n```\n\nis equivalent to:\n\n```sql\nSTART TRANSACTION;\nINSERT INTO foo (bar) VALUES ('baz');\nSAVEPOINT slonik_savepoint_1;\nINSERT INTO qux (quux) VALUES ('corge');\nSAVEPOINT slonik_savepoint_2;\nINSERT INTO uier (grault) VALUES ('garply');\nROLLBACK TO SAVEPOINT slonik_savepoint_2;\nROLLBACK TO SAVEPOINT slonik_savepoint_1;\nROLLBACK;\n```\n\n#### Transaction retrying\n\nTransactions that are failing with [Transaction Rollback](https://www.postgresql.org/docs/current/errcodes-appendix.html) class errors are automatically retried.\n\nA failing transaction will be rolled back and the callback function passed to the transaction method call will be executed again. Nested transactions are also retried until the retry limit is reached. If the nested transaction keeps failing with a [Transaction Rollback](https://www.postgresql.org/docs/current/errcodes-appendix.html) error, then the parent transaction will be retried until the retry limit is reached.\n\nHow many times a transaction is retried is controlled using `transactionRetryLimit` configuration (default: 5) and the `transactionRetryLimit` parameter of the `transaction` method (default: undefined). If a `transactionRetryLimit` is given to the method call then it is used otherwise the `transactionRetryLimit` configuration is used.\n\n#### Transaction events\n\nTransaction connections provide event emitter functionality to monitor transaction lifecycle events. This allows you to listen for transaction commits, rollbacks, and savepoint operations.\n\n```ts\nawait connection.transaction(async (transactionConnection) =\u003e {\n  // Listen for commit events\n  transactionConnection.on(\"commit\", ({ transactionId, transactionDepth }) =\u003e {\n    console.log(`Transaction ${transactionId} committed at depth ${transactionDepth}`);\n  });\n\n  // Listen for rollback events\n  transactionConnection.on(\"rollback\", ({ transactionId, transactionDepth, error }) =\u003e {\n    console.log(`Transaction ${transactionId} rolled back:`, error.message);\n  });\n\n  // Access transaction metadata\n  console.log(\"Transaction ID:\", transactionConnection.transactionId);\n  console.log(\"Transaction Depth:\", transactionConnection.transactionDepth);\n\n  await transactionConnection.query(sql.unsafe`INSERT INTO foo (bar) VALUES ('baz')`);\n\n  // Commit event will be emitted automatically when transaction completes\n});\n```\n\n**Available Events:**\n\n- **`commit`** - Emitted when a top-level transaction (depth = 0) commits successfully\n  - Parameters: `(event: {transactionId: string, transactionDepth: number})`\n- **`rollback`** - Emitted when any transaction rolls back due to an error\n  - Parameters: `(event: {transactionId: string, transactionDepth: number, error: Error})`\n- **`savepoint`** - Emitted when a nested transaction creates a savepoint (depth \u003e 0)\n  - Parameters: `(event: {transactionId: string, transactionDepth: number})`\n- **`rollbackToSavepoint`** - Emitted when a nested transaction rolls back to its savepoint\n  - Parameters: `(event: {transactionId: string, transactionDepth: number, error: Error})`\n\n**Nested Transaction Events:**\n\n```ts\nawait connection.transaction(async (outerTransaction) =\u003e {\n  outerTransaction.on(\"savepoint\", ({ transactionId, transactionDepth }) =\u003e {\n    console.log(`Savepoint created at depth ${transactionDepth}`);\n  });\n\n  outerTransaction.on(\"commit\", ({ transactionId, transactionDepth }) =\u003e {\n    console.log(`Transaction committed at depth ${transactionDepth}`);\n  });\n\n  await outerTransaction.transaction(async (innerTransaction) =\u003e {\n    // This will emit a 'savepoint' event\n    // innerTransaction shares the same event emitter as outerTransaction\n    await innerTransaction.query(sql.unsafe`INSERT INTO nested (value) VALUES ('test')`);\n  });\n\n  // Only the outer transaction will emit a 'commit' event\n});\n```\n\n**Transaction Metadata:**\n\nTransaction connections provide access to transaction metadata:\n\n- **`transactionId`** - Unique identifier for the transaction (consistent across nested levels)\n- **`transactionDepth`** - Current nesting level (0 for top-level, 1+ for nested transactions)\n\nAll standard EventEmitter methods are available: `on()`, `off()`, `once()`, `removeListener()`, `removeAllListeners()`, etc.\n\n#### Query retrying\n\nA single query (not part of a transaction) failing with a [Transaction Rollback](https://www.postgresql.org/docs/current/errcodes-appendix.html) class error is automatically retried.\n\nHow many times it is retried is controlled by using the `queryRetryLimit` configuration (default: 5).\n\n## Utilities\n\n### \u003ccode\u003eparseDsn\u003c/code\u003e\n\n```ts\n(dsn: string) =\u003e ConnectionOptions;\n```\n\nParses DSN to `ConnectionOptions` type.\n\nExample:\n\n```ts\nimport { parseDsn } from \"slonik\";\n\nparseDsn(\"postgresql://foo@localhost/bar?application_name=baz\");\n```\n\nSee [supported parameters](#connection-uri).\n\n### \u003ccode\u003estringifyDsn\u003c/code\u003e\n\n```ts\n(connectionOptions: ConnectionOptions) =\u003e string;\n```\n\nStringifies `ConnectionOptions` to a DSN.\n\nExample:\n\n```ts\nimport { stringifyDsn } from \"slonik\";\n\nstringifyDsn({\n  host: \"localhost\",\n  username: \"foo\",\n  databaseName: \"bar\",\n  applicationName: \"baz\",\n});\n```\n\n## Error handling\n\nAll Slonik errors extend from `SlonikError`, i.e. You can catch Slonik specific errors using the following logic.\n\n```ts\nimport { SlonikError } from \"slonik\";\n\ntry {\n  await query();\n} catch (error) {\n  if (error instanceof SlonikError) {\n    // This error is thrown by Slonik.\n  }\n}\n```\n\n### Original \u003ccode\u003enode-postgres\u003c/code\u003e error\n\nWhen error originates from `node-postgres`, the original error is available under [`cause` property](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause).\n\nThis property is exposed for debugging purposes only. Do not use it for conditional checks – it can change.\n\nIf you require to extract meta-data about a specific type of error (e.g. constraint violation name), raise a GitHub issue describing your use case.\n\n### Handling \u003ccode\u003eBackendTerminatedError\u003c/code\u003e\n\n`BackendTerminatedError` is thrown when the backend is terminated by the user, i.e. [`pg_terminate_backend`](https://www.postgresql.org/docs/current/functions-admin.html#FUNCTIONS-ADMIN-SIGNAL).\n\n`BackendTerminatedError` must be handled at the connection level, i.e.\n\n```ts\nawait pool.connect(async (connection0) =\u003e {\n  try {\n    await pool.connect(async (connection1) =\u003e {\n      const backendProcessId = await connection1.oneFirst(\n        sql.typeAlias(\"id\")`SELECT pg_backend_pid() AS id`,\n      );\n\n      setTimeout(() =\u003e {\n        connection0.query(sql.typeAlias(\"void\")`SELECT pg_cancel_backend(${backendProcessId})`);\n      }, 2000);\n\n      try {\n        await connection1.query(sql.typeAlias(\"void\")`SELECT pg_sleep(30)`);\n      } catch (error) {\n        // This code will not be executed.\n      }\n    });\n  } catch (error) {\n    if (error instanceof BackendTerminatedError) {\n      // Handle backend termination.\n    } else {\n      throw error;\n    }\n  }\n});\n```\n\n### Handling \u003ccode\u003eCheckIntegrityConstraintViolationError\u003c/code\u003e\n\n`CheckIntegrityConstraintViolationError` is thrown when PostgreSQL responds with [`check_violation`](https://www.postgresql.org/docs/9.4/static/errcodes-appendix.html) (`23514`) error.\n\nIt inherits the `columns`, `constraint`, `table` and `detail` properties described under [`IntegrityConstraintViolationError`](#handling-integrityconstraintviolationerror).\n\n### Handling \u003ccode\u003eConnectionError\u003c/code\u003e\n\n`ConnectionError` is thrown when connection cannot be established to the PostgreSQL server.\n\n### Handling \u003ccode\u003eDataIntegrityError\u003c/code\u003e\n\nT","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgajus%2Fslonik","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgajus%2Fslonik","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgajus%2Fslonik/lists"}