Repository: kysely-org/kysely Branch: master Commit: 91cf3733b2a4 Files: 616 Total size: 2.6 MB Directory structure: gitextract_h4793jw5/ ├── .github/ │ └── workflows/ │ ├── bench.yml │ ├── compare.yml │ ├── preview.yml │ ├── publish.yml │ └── test.yml ├── .gitignore ├── .mocharc.js ├── .node-version ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .prettierrc.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── FUNDING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── deno.check.d.ts ├── deno.check.json ├── deno.lint.json ├── docker-compose.yml ├── docs/ │ └── index.html ├── example/ │ ├── .gitignore │ ├── .prettierrc.json │ ├── README.md │ ├── package.json │ ├── src/ │ │ ├── app.ts │ │ ├── authentication/ │ │ │ ├── auth-token.service.ts │ │ │ ├── auth-token.ts │ │ │ ├── authentication.service.ts │ │ │ ├── refresh-token.repository.ts │ │ │ ├── refresh-token.table.ts │ │ │ └── refresh-token.ts │ │ ├── config.ts │ │ ├── context.ts │ │ ├── database.ts │ │ ├── migrate-to-latest.ts │ │ ├── migrations/ │ │ │ ├── 2021_09_18_06_54_59_create_user.ts │ │ │ ├── 2021_09_18_14_05_20_create_refresh_token.ts │ │ │ └── 2021_09_18_18_22_45_create_sign_in_method.ts │ │ ├── router.ts │ │ ├── user/ │ │ │ ├── sign-in-method/ │ │ │ │ ├── password-sign-in-method.table.ts │ │ │ │ ├── sign-in-method.controller.ts │ │ │ │ ├── sign-in-method.repository.ts │ │ │ │ ├── sign-in-method.service.ts │ │ │ │ ├── sign-in-method.table.ts │ │ │ │ └── sign-in-method.ts │ │ │ ├── signed-in-user.ts │ │ │ ├── user.controller.ts │ │ │ ├── user.repository.ts │ │ │ ├── user.service.ts │ │ │ ├── user.table.ts │ │ │ └── user.ts │ │ └── util/ │ │ ├── ajv.ts │ │ ├── errors.ts │ │ └── object.ts │ ├── test/ │ │ ├── test-config.ts │ │ ├── test-context.ts │ │ └── user/ │ │ └── user.test.ts │ └── tsconfig.json ├── jsr.json ├── outdated-typescript.d.ts ├── package.json ├── pnpm-workspace.yaml ├── scripts/ │ ├── add-deno-type-references.js │ ├── align-versions.mts │ ├── check-esm-imports.js │ ├── check-exports.js │ ├── copy-interface-documentation.js │ ├── exclude-test-files-for-backwards-compat.mts │ ├── generate-site-examples.js │ ├── module-fixup.js │ ├── remove-global-augmentations.mts │ ├── tsconfig.json │ └── util/ │ └── for-each-file.js ├── site/ │ ├── .gitignore │ ├── babel.config.js │ ├── docs/ │ │ ├── dialects.md │ │ ├── examples/ │ │ │ ├── _category_.json │ │ │ ├── cte/ │ │ │ │ ├── 0010-simple-selects.js │ │ │ │ ├── 0010-simple-selects.mdx │ │ │ │ ├── 0020-inserts-updates-and-deletions.js │ │ │ │ ├── 0020-inserts-updates-and-deletions.mdx │ │ │ │ └── _category_.json │ │ │ ├── delete/ │ │ │ │ ├── 0010-single-row.js │ │ │ │ ├── 0010-single-row.mdx │ │ │ │ └── _category_.json │ │ │ ├── insert/ │ │ │ │ ├── 0010-single-row.js │ │ │ │ ├── 0010-single-row.mdx │ │ │ │ ├── 0020-multiple-rows.js │ │ │ │ ├── 0020-multiple-rows.mdx │ │ │ │ ├── 0030-returning-data.js │ │ │ │ ├── 0030-returning-data.mdx │ │ │ │ ├── 0040-complex-values.js │ │ │ │ ├── 0040-complex-values.mdx │ │ │ │ ├── 0050-insert-subquery.js │ │ │ │ ├── 0050-insert-subquery.mdx │ │ │ │ └── _category_.json │ │ │ ├── join/ │ │ │ │ ├── 0010-simple-inner-join.js │ │ │ │ ├── 0010-simple-inner-join.mdx │ │ │ │ ├── 0020-aliased-inner-join.js │ │ │ │ ├── 0020-aliased-inner-join.mdx │ │ │ │ ├── 0030-complex-join.js │ │ │ │ ├── 0030-complex-join.mdx │ │ │ │ ├── 0040-subquery-join.js │ │ │ │ ├── 0040-subquery-join.mdx │ │ │ │ └── _category_.json │ │ │ ├── merge/ │ │ │ │ ├── 0010-source-row-existence.js │ │ │ │ ├── 0010-source-row-existence.mdx │ │ │ │ ├── 0020-temporary-changes-table.js │ │ │ │ ├── 0020-temporary-changes-table.mdx │ │ │ │ └── _category_.json │ │ │ ├── select/ │ │ │ │ ├── 0010-a-single-column.js │ │ │ │ ├── 0010-a-single-column.mdx │ │ │ │ ├── 0020-column-with-a-table.js │ │ │ │ ├── 0020-column-with-a-table.mdx │ │ │ │ ├── 0030-multiple-columns.js │ │ │ │ ├── 0030-multiple-columns.mdx │ │ │ │ ├── 0040-aliases.js │ │ │ │ ├── 0040-aliases.mdx │ │ │ │ ├── 0050-complex-selections.js │ │ │ │ ├── 0050-complex-selections.mdx │ │ │ │ ├── 0051-not-null.js │ │ │ │ ├── 0051-not-null.mdx │ │ │ │ ├── 0060-function-calls.js │ │ │ │ ├── 0060-function-calls.mdx │ │ │ │ ├── 0070-distinct.js │ │ │ │ ├── 0070-distinct.mdx │ │ │ │ ├── 0080-distinct-on.js │ │ │ │ ├── 0080-distinct-on.mdx │ │ │ │ ├── 0090-all-columns.js │ │ │ │ ├── 0090-all-columns.mdx │ │ │ │ ├── 0100-all-columns-of-a-table.js │ │ │ │ ├── 0100-all-columns-of-a-table.mdx │ │ │ │ ├── 0110-nested-array.js │ │ │ │ ├── 0110-nested-array.mdx │ │ │ │ ├── 0120-nested-object.js │ │ │ │ ├── 0120-nested-object.mdx │ │ │ │ ├── 0130-generic-find-query.js │ │ │ │ ├── 0130-generic-find-query.mdx │ │ │ │ └── _category_.json │ │ │ ├── transactions/ │ │ │ │ ├── 0010-simple-transaction.js │ │ │ │ ├── 0010-simple-transaction.mdx │ │ │ │ ├── 0011-controlled-transaction.js │ │ │ │ ├── 0011-controlled-transaction.mdx │ │ │ │ ├── 0012-controlled-transaction-w-savepoints.js │ │ │ │ ├── 0012-controlled-transaction-w-savepoints.mdx │ │ │ │ └── _category_.json │ │ │ ├── update/ │ │ │ │ ├── 0010-single-row.js │ │ │ │ ├── 0010-single-row.mdx │ │ │ │ ├── 0020-complex-values.js │ │ │ │ ├── 0020-complex-values.mdx │ │ │ │ ├── 0030-my-sql-joins.js │ │ │ │ ├── 0030-my-sql-joins.mdx │ │ │ │ └── _category_.json │ │ │ └── where/ │ │ │ ├── 0010-simple-where-clause.js │ │ │ ├── 0010-simple-where-clause.mdx │ │ │ ├── 0020-where-in.js │ │ │ ├── 0020-where-in.mdx │ │ │ ├── 0030-object-filter.js │ │ │ ├── 0030-object-filter.mdx │ │ │ ├── 0040-or-where.js │ │ │ ├── 0040-or-where.mdx │ │ │ ├── 0050-conditional-where-calls.js │ │ │ ├── 0050-conditional-where-calls.mdx │ │ │ ├── 0060-complex-where-clause.js │ │ │ ├── 0060-complex-where-clause.mdx │ │ │ └── _category_.json │ │ ├── execution.mdx │ │ ├── generating-types.md │ │ ├── getting-started/ │ │ │ ├── Dialects.tsx │ │ │ ├── IUseADifferentDialect.tsx │ │ │ ├── IUseADifferentPackageManager.tsx │ │ │ ├── Installation.tsx │ │ │ ├── Instantiation.tsx │ │ │ ├── Querying.tsx │ │ │ ├── Summary.tsx │ │ │ ├── _prerequisites.mdx │ │ │ ├── _types.mdx │ │ │ └── shared.tsx │ │ ├── getting-started.mdx │ │ ├── integrations/ │ │ │ ├── _category_.json │ │ │ ├── llms.mdx │ │ │ └── supabase.mdx │ │ ├── intro.mdx │ │ ├── migrations.mdx │ │ ├── playground.mdx │ │ ├── plugins.md │ │ ├── recipes/ │ │ │ ├── 0001-relations.md │ │ │ ├── 0001-reusable-helpers.md │ │ │ ├── 0002-data-types.md │ │ │ ├── 0003-raw-sql.md │ │ │ ├── 0004-splitting-query-building-and-execution.md │ │ │ ├── 0005-conditional-selects.md │ │ │ ├── 0006-expressions.md │ │ │ ├── 0007-schemas.md │ │ │ ├── 0008-deduplicate-joins.md │ │ │ ├── 0009-excessively-deep-types.md │ │ │ ├── 0010-extending-kysely.md │ │ │ ├── 0011-introspecting-relation-metadata.md │ │ │ ├── 0012-logging.md │ │ │ └── _category_.json │ │ └── runtimes/ │ │ ├── _category_.json │ │ ├── browser.md │ │ └── deno.mdx │ ├── docusaurus.config.ts │ ├── package.json │ ├── sidebars.js │ ├── src/ │ │ ├── components/ │ │ │ ├── DemoVideo.module.css │ │ │ ├── DemoVideo.tsx │ │ │ ├── Playground.module.css │ │ │ ├── Playground.tsx │ │ │ ├── SectionFeatures/ │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ │ ├── SectionQuotes/ │ │ │ │ ├── Quote.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── quotes.ts │ │ │ │ ├── reddit.module.css │ │ │ │ └── styles.module.css │ │ │ └── playground-example-types.ts │ │ ├── css/ │ │ │ └── custom.css │ │ └── pages/ │ │ ├── index.module.css │ │ └── index.tsx │ ├── static/ │ │ ├── .nojekyll │ │ └── demo_optimized.webm │ └── tsconfig.json ├── src/ │ ├── dialect/ │ │ ├── database-introspector.ts │ │ ├── dialect-adapter-base.ts │ │ ├── dialect-adapter.ts │ │ ├── dialect.ts │ │ ├── mssql/ │ │ │ ├── mssql-adapter.ts │ │ │ ├── mssql-dialect-config.ts │ │ │ ├── mssql-dialect.ts │ │ │ ├── mssql-driver.ts │ │ │ ├── mssql-introspector.ts │ │ │ └── mssql-query-compiler.ts │ │ ├── mysql/ │ │ │ ├── mysql-adapter.ts │ │ │ ├── mysql-dialect-config.ts │ │ │ ├── mysql-dialect.ts │ │ │ ├── mysql-driver.ts │ │ │ ├── mysql-introspector.ts │ │ │ └── mysql-query-compiler.ts │ │ ├── postgres/ │ │ │ ├── postgres-adapter.ts │ │ │ ├── postgres-dialect-config.ts │ │ │ ├── postgres-dialect.ts │ │ │ ├── postgres-driver.ts │ │ │ ├── postgres-introspector.ts │ │ │ └── postgres-query-compiler.ts │ │ └── sqlite/ │ │ ├── sqlite-adapter.ts │ │ ├── sqlite-dialect-config.ts │ │ ├── sqlite-dialect.ts │ │ ├── sqlite-driver.ts │ │ ├── sqlite-introspector.ts │ │ └── sqlite-query-compiler.ts │ ├── driver/ │ │ ├── connection-provider.ts │ │ ├── database-connection.ts │ │ ├── default-connection-provider.ts │ │ ├── driver.ts │ │ ├── dummy-driver.ts │ │ ├── runtime-driver.ts │ │ └── single-connection-provider.ts │ ├── dynamic/ │ │ ├── dynamic-reference-builder.ts │ │ ├── dynamic-table-builder.ts │ │ └── dynamic.ts │ ├── expression/ │ │ ├── expression-builder.ts │ │ ├── expression-wrapper.ts │ │ └── expression.ts │ ├── helpers/ │ │ ├── mssql.ts │ │ ├── mysql.ts │ │ ├── postgres.ts │ │ └── sqlite.ts │ ├── index.ts │ ├── kysely.ts │ ├── migration/ │ │ ├── file-migration-provider.ts │ │ └── migrator.ts │ ├── operation-node/ │ │ ├── add-column-node.ts │ │ ├── add-constraint-node.ts │ │ ├── add-index-node.ts │ │ ├── aggregate-function-node.ts │ │ ├── alias-node.ts │ │ ├── alter-column-node.ts │ │ ├── alter-table-node.ts │ │ ├── and-node.ts │ │ ├── binary-operation-node.ts │ │ ├── case-node.ts │ │ ├── cast-node.ts │ │ ├── check-constraint-node.ts │ │ ├── collate-node.ts │ │ ├── column-definition-node.ts │ │ ├── column-node.ts │ │ ├── column-update-node.ts │ │ ├── common-table-expression-name-node.ts │ │ ├── common-table-expression-node.ts │ │ ├── constraint-node.ts │ │ ├── create-index-node.ts │ │ ├── create-schema-node.ts │ │ ├── create-table-node.ts │ │ ├── create-type-node.ts │ │ ├── create-view-node.ts │ │ ├── data-type-node.ts │ │ ├── default-insert-value-node.ts │ │ ├── default-value-node.ts │ │ ├── delete-query-node.ts │ │ ├── drop-column-node.ts │ │ ├── drop-constraint-node.ts │ │ ├── drop-index-node.ts │ │ ├── drop-schema-node.ts │ │ ├── drop-table-node.ts │ │ ├── drop-type-node.ts │ │ ├── drop-view-node.ts │ │ ├── explain-node.ts │ │ ├── fetch-node.ts │ │ ├── foreign-key-constraint-node.ts │ │ ├── from-node.ts │ │ ├── function-node.ts │ │ ├── generated-node.ts │ │ ├── group-by-item-node.ts │ │ ├── group-by-node.ts │ │ ├── having-node.ts │ │ ├── identifier-node.ts │ │ ├── insert-query-node.ts │ │ ├── join-node.ts │ │ ├── json-operator-chain-node.ts │ │ ├── json-path-leg-node.ts │ │ ├── json-path-node.ts │ │ ├── json-reference-node.ts │ │ ├── limit-node.ts │ │ ├── list-node.ts │ │ ├── matched-node.ts │ │ ├── merge-query-node.ts │ │ ├── modify-column-node.ts │ │ ├── offset-node.ts │ │ ├── on-conflict-node.ts │ │ ├── on-duplicate-key-node.ts │ │ ├── on-node.ts │ │ ├── operation-node-source.ts │ │ ├── operation-node-transformer.ts │ │ ├── operation-node-visitor.ts │ │ ├── operation-node.ts │ │ ├── operator-node.ts │ │ ├── or-action-node.ts │ │ ├── or-node.ts │ │ ├── order-by-item-node.ts │ │ ├── order-by-node.ts │ │ ├── output-node.ts │ │ ├── over-node.ts │ │ ├── parens-node.ts │ │ ├── partition-by-item-node.ts │ │ ├── partition-by-node.ts │ │ ├── primary-key-constraint-node.ts │ │ ├── primitive-value-list-node.ts │ │ ├── query-node.ts │ │ ├── raw-node.ts │ │ ├── reference-node.ts │ │ ├── references-node.ts │ │ ├── refresh-materialized-view-node.ts │ │ ├── rename-column-node.ts │ │ ├── rename-constraint-node.ts │ │ ├── returning-node.ts │ │ ├── schemable-identifier-node.ts │ │ ├── select-all-node.ts │ │ ├── select-modifier-node.ts │ │ ├── select-query-node.ts │ │ ├── selection-node.ts │ │ ├── set-operation-node.ts │ │ ├── simple-reference-expression-node.ts │ │ ├── table-node.ts │ │ ├── top-node.ts │ │ ├── tuple-node.ts │ │ ├── unary-operation-node.ts │ │ ├── unique-constraint-node.ts │ │ ├── update-query-node.ts │ │ ├── using-node.ts │ │ ├── value-list-node.ts │ │ ├── value-node.ts │ │ ├── values-node.ts │ │ ├── when-node.ts │ │ ├── where-node.ts │ │ └── with-node.ts │ ├── parser/ │ │ ├── binary-operation-parser.ts │ │ ├── coalesce-parser.ts │ │ ├── collate-parser.ts │ │ ├── data-type-parser.ts │ │ ├── default-value-parser.ts │ │ ├── delete-from-parser.ts │ │ ├── expression-parser.ts │ │ ├── fetch-parser.ts │ │ ├── group-by-parser.ts │ │ ├── identifier-parser.ts │ │ ├── insert-values-parser.ts │ │ ├── join-parser.ts │ │ ├── merge-into-parser.ts │ │ ├── merge-parser.ts │ │ ├── on-commit-action-parse.ts │ │ ├── on-modify-action-parser.ts │ │ ├── order-by-parser.ts │ │ ├── parse-utils.ts │ │ ├── partition-by-parser.ts │ │ ├── reference-parser.ts │ │ ├── returning-parser.ts │ │ ├── savepoint-parser.ts │ │ ├── select-from-parser.ts │ │ ├── select-parser.ts │ │ ├── set-operation-parser.ts │ │ ├── table-parser.ts │ │ ├── top-parser.ts │ │ ├── tuple-parser.ts │ │ ├── unary-operation-parser.ts │ │ ├── update-parser.ts │ │ ├── update-set-parser.ts │ │ ├── value-parser.ts │ │ └── with-parser.ts │ ├── plugin/ │ │ ├── camel-case/ │ │ │ ├── camel-case-plugin.ts │ │ │ ├── camel-case-transformer.ts │ │ │ └── camel-case.ts │ │ ├── deduplicate-joins/ │ │ │ ├── deduplicate-joins-plugin.ts │ │ │ └── deduplicate-joins-transformer.ts │ │ ├── handle-empty-in-lists/ │ │ │ ├── handle-empty-in-lists-plugin.ts │ │ │ ├── handle-empty-in-lists-transformer.ts │ │ │ └── handle-empty-in-lists.ts │ │ ├── immediate-value/ │ │ │ ├── immediate-value-plugin.ts │ │ │ └── immediate-value-transformer.ts │ │ ├── kysely-plugin.ts │ │ ├── noop-plugin.ts │ │ ├── parse-json-results/ │ │ │ └── parse-json-results-plugin.ts │ │ └── with-schema/ │ │ ├── with-schema-plugin.ts │ │ └── with-schema-transformer.ts │ ├── query-builder/ │ │ ├── aggregate-function-builder.ts │ │ ├── case-builder.ts │ │ ├── cte-builder.ts │ │ ├── delete-query-builder.ts │ │ ├── delete-result.ts │ │ ├── function-module.ts │ │ ├── having-interface.ts │ │ ├── insert-query-builder.ts │ │ ├── insert-result.ts │ │ ├── join-builder.ts │ │ ├── json-path-builder.ts │ │ ├── merge-query-builder.ts │ │ ├── merge-result.ts │ │ ├── no-result-error.ts │ │ ├── on-conflict-builder.ts │ │ ├── order-by-interface.ts │ │ ├── order-by-item-builder.ts │ │ ├── output-interface.ts │ │ ├── over-builder.ts │ │ ├── returning-interface.ts │ │ ├── select-query-builder-expression.ts │ │ ├── select-query-builder.ts │ │ ├── update-query-builder.ts │ │ ├── update-result.ts │ │ └── where-interface.ts │ ├── query-compiler/ │ │ ├── compiled-query.ts │ │ ├── default-query-compiler.ts │ │ └── query-compiler.ts │ ├── query-creator.ts │ ├── query-executor/ │ │ ├── default-query-executor.ts │ │ ├── noop-query-executor.ts │ │ ├── query-executor-base.ts │ │ ├── query-executor-provider.ts │ │ └── query-executor.ts │ ├── raw-builder/ │ │ ├── raw-builder.ts │ │ └── sql.ts │ ├── schema/ │ │ ├── alter-column-builder.ts │ │ ├── alter-table-add-foreign-key-constraint-builder.ts │ │ ├── alter-table-add-index-builder.ts │ │ ├── alter-table-builder.ts │ │ ├── alter-table-drop-constraint-builder.ts │ │ ├── alter-table-executor.ts │ │ ├── check-constraint-builder.ts │ │ ├── column-definition-builder.ts │ │ ├── create-index-builder.ts │ │ ├── create-schema-builder.ts │ │ ├── create-table-builder.ts │ │ ├── create-type-builder.ts │ │ ├── create-view-builder.ts │ │ ├── drop-index-builder.ts │ │ ├── drop-schema-builder.ts │ │ ├── drop-table-builder.ts │ │ ├── drop-type-builder.ts │ │ ├── drop-view-builder.ts │ │ ├── foreign-key-constraint-builder.ts │ │ ├── primary-key-constraint-builder.ts │ │ ├── refresh-materialized-view-builder.ts │ │ ├── schema.ts │ │ └── unique-constraint-builder.ts │ └── util/ │ ├── assert.ts │ ├── column-type.ts │ ├── compilable.ts │ ├── deferred.ts │ ├── explainable.ts │ ├── infer-result.ts │ ├── json-object-args.ts │ ├── log-once.ts │ ├── log.ts │ ├── object-utils.ts │ ├── performance-now.ts │ ├── provide-controlled-connection.ts │ ├── query-id.ts │ ├── random-string.ts │ ├── require-all-props.ts │ ├── stack-trace-utils.ts │ ├── streamable.ts │ ├── type-error.ts │ └── type-utils.ts ├── test/ │ ├── browser/ │ │ ├── index.html │ │ ├── main.ts │ │ └── test.js │ ├── bun/ │ │ ├── bun.test.ts │ │ ├── package.json │ │ └── tsconfig.json │ ├── cloudflare-workers/ │ │ ├── .dev.vars │ │ ├── api.ts │ │ ├── package.json │ │ └── test.ts │ ├── composite-ts/ │ │ ├── index.mts │ │ ├── package.json │ │ └── tsconfig.json │ ├── deno/ │ │ ├── cdn.test.ts │ │ ├── deno.json │ │ └── local.test.ts │ ├── node/ │ │ ├── src/ │ │ │ ├── aggregate-function.test.ts │ │ │ ├── array.test.ts │ │ │ ├── async-dispose.test.ts │ │ │ ├── camel-case.test.ts │ │ │ ├── case.test.ts │ │ │ ├── clear.test.ts │ │ │ ├── coalesce.test.ts │ │ │ ├── controlled-transaction.test.ts │ │ │ ├── deduplicate-joins.test.ts │ │ │ ├── delete.test.ts │ │ │ ├── disconnects.test.ts │ │ │ ├── error-stack.test.ts │ │ │ ├── execute.test.ts │ │ │ ├── explain.test.ts │ │ │ ├── expression.test.ts │ │ │ ├── group-by.test.ts │ │ │ ├── handle-empty-in-lists-plugin.test.ts │ │ │ ├── having.test.ts │ │ │ ├── immediate-value-plugin.test.ts │ │ │ ├── insert.test.ts │ │ │ ├── introspect.test.ts │ │ │ ├── join.test.ts │ │ │ ├── json-traversal.test.ts │ │ │ ├── json.test.ts │ │ │ ├── log-once.test.ts │ │ │ ├── logging.test.ts │ │ │ ├── merge.test.ts │ │ │ ├── migration.test.ts │ │ │ ├── object-util.test.ts │ │ │ ├── order-by.test.ts │ │ │ ├── parse-json-results-plugin.test.ts │ │ │ ├── performance.test.ts │ │ │ ├── query-id.test.ts │ │ │ ├── raw-query.test.ts │ │ │ ├── raw-sql.test.ts │ │ │ ├── replace.test.ts │ │ │ ├── sanitize-identifiers.test.ts │ │ │ ├── schema.test.ts │ │ │ ├── select.test.ts │ │ │ ├── set-operation.test.ts │ │ │ ├── sql-injection.test.ts │ │ │ ├── stream.test.ts │ │ │ ├── test-migrations/ │ │ │ │ ├── migration1.ts │ │ │ │ └── migration2.ts │ │ │ ├── test-setup.ts │ │ │ ├── transaction.test.ts │ │ │ ├── update.test.ts │ │ │ ├── where.test.ts │ │ │ ├── with-schema.test.ts │ │ │ └── with.test.ts │ │ └── tsconfig.json │ ├── outdated-ts/ │ │ ├── outdated-ts.test.ts │ │ ├── package.json │ │ └── tsconfig.json │ ├── scripts/ │ │ └── mysql-init.sql │ ├── ts-benchmarks/ │ │ ├── index.ts │ │ ├── order-by.bench.ts │ │ ├── package.json │ │ ├── select-from.bench.ts │ │ └── tsconfig.json │ └── typings/ │ ├── index.d.ts │ ├── package.json │ ├── shared.d.ts │ └── test-d/ │ ├── aggregate-function.test-d.ts │ ├── alter-table.test-d.ts │ ├── assert-type.test-d.ts │ ├── case.test-d.ts │ ├── clear.test-d.ts │ ├── coalesce.test-d.ts │ ├── create-table.test-d.ts │ ├── delete-query-builder.test-d.ts │ ├── expression.test-d.ts │ ├── generic-pre-5.4.test-d.ts │ ├── generic.test-d.ts │ ├── huge-db.test-d.ts │ ├── if.test-d.ts │ ├── index.test-d.ts │ ├── infer-result.test-d.ts │ ├── insert.test-d.ts │ ├── join.test-d.ts │ ├── json-traversal.test-d.ts │ ├── kysely-any.test-d.ts │ ├── merge.test-d.ts │ ├── postgres-json.test-d.ts │ ├── select-from.test-d.ts │ ├── select-no-from.test-d.ts │ ├── select.test-d.ts │ ├── set-operation.test-d.ts │ ├── update.test-d.ts │ ├── where.test-d.ts │ └── with.test-d.ts ├── tsconfig-base.json ├── tsconfig-cjs.json └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/bench.yml ================================================ name: bench permissions: contents: read on: push: branches: - master paths-ignore: - '.github/workflows/preview.yml' - 'assets/**' - 'docs/**' - 'example/**' - 'site/**' - '.gitignore' - '.node-version' - '.npmrc' - '.nvmrc' - '.prettierignore' - '.prettierrc.json' - '*.md' - 'deno*' - 'jsr*' - 'LICENSE' - 'outdated-typescript.d.ts' pull_request: paths-ignore: - '.github/workflows/preview.yml' - 'assets/**' - 'docs/**' - 'example/**' - 'site/**' - '.gitignore' - '.node-version' - '.npmrc' - '.nvmrc' - '.prettierignore' - '.prettierrc.json' - '*.md' - 'deno*' - 'jsr*' - 'LICENSE' - 'outdated-typescript.d.ts' workflow_dispatch: jobs: typescript: name: TypeScript Benchmarks permissions: pull-requests: write runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 with: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install pnpm uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - name: Use Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: cache: 'pnpm' node-version: lts/* - name: Install dependencies run: pnpm install - name: Run benchmarks id: bench run: | # Run the benchmark command and capture its exit code pnpm bench:ts > /tmp/benchmark_output.txt 2>&1 exit_code=$? # Process the output summary_output=$(cat /tmp/benchmark_output.txt | grep -v "> " | sed '/./,$!d') # Set a boolean output indicating if it failed with exit code 1 if [ $exit_code -eq 1 ]; then echo "benchmark_failed=true" >> $GITHUB_OUTPUT else echo "benchmark_failed=false" >> $GITHUB_OUTPUT fi echo "full_summary<> $GITHUB_OUTPUT echo "$summary_output" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - name: Analyze Results id: analyze run: | full_summary="${{ steps.bench.outputs.full_summary }}" # This powerful awk script performs two actions: # 1. It finds blocks missing a Delta line and calculates it. # 2. It filters for all blocks where the final delta is not zero. changed_blocks=$(echo "$full_summary" | awk ' BEGIN { RS="" } # Process the input in blocks separated by blank lines { block_text = $0 # Store the original block text is_changed = 0 # If a block is missing a Delta line, calculate it if ($0 ~ /Baseline:/ && $0 !~ /Delta:/) { # Extract numeric values for Result and Baseline match($0, /Result: ([0-9.]+)/, r) match($0, /Baseline: ([0-9.]+)/, b) result_val = r[1] baseline_val = b[1] # Calculate the delta, avoiding division by zero if (baseline_val > 0) { delta = ((result_val / baseline_val) - 1) * 100 # Append the newly calculated, formatted Delta line to the block block_text = sprintf("%s\n📊 Delta: %+.2f%%", block_text, delta) } } # Now, check the (potentially modified) block for a non-zero delta. # This works for both pre-existing deltas and the ones we just calculated. if (block_text ~ /Delta:/ && block_text !~ /Delta: (\+)?0\.00%/) { # Filter out any summary lines that are not part of a benchmark block if (block_text ~ /🏌️/) { print block_text "\n" # Print the complete, changed block } } }') if [ -n "$changed_blocks" ]; then echo "has_changes=true" >> $GITHUB_OUTPUT echo "changed_blocks<> $GITHUB_OUTPUT echo "$changed_blocks" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT else echo "has_changes=false" >> $GITHUB_OUTPUT fi - name: PR Comment on Changes if: steps.analyze.outputs.has_changes == 'true' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3.0.1 with: comment-tag: typescript-benchmarks message: |
⏱️ Benchmark changes detected following ${{ github.event.pull_request.head.sha }} The following benchmarks have changed from the baseline: ``` ${{ steps.analyze.outputs.changed_blocks }} ```
- name: PR Comment on No Changes if: steps.analyze.outputs.has_changes == 'false' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3.0.1 with: comment-tag: typescript-benchmarks message: ✅ ⏱️ **No benchmark changes detected** following ${{ github.event.pull_request.head.sha }}. - name: Fail job if benchmark command failed if: steps.bench.outputs.benchmark_failed == 'true' run: | echo "Benchmark command failed with exit code 1" exit 1 ================================================ FILE: .github/workflows/compare.yml ================================================ name: compare llms-full.txt permissions: contents: read on: pull_request: paths: - '.github/workflows/compare.yml' - 'site/**' workflow_dispatch: jobs: build-current: name: Build current branch runs-on: ubuntu-latest outputs: artifact-name: current-llms-full-txt steps: - name: Harden Runner uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 with: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install pnpm uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - name: Use Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: cache: 'pnpm' node-version: lts/* - name: Install dependencies run: pnpm install - name: Build working-directory: site run: pnpm build - name: Upload current build artifact uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: current-llms-full-txt path: site/build/llms-full.txt build-target: name: Build target branch runs-on: ubuntu-latest outputs: artifact-name: target-llms-full-txt steps: - name: Harden Runner uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 with: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install pnpm uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - name: Use Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: cache: 'pnpm' node-version: lts/* - name: Install dependencies run: pnpm install - name: Checkout target branch and build run: | TARGET_BRANCH=${{ github.event.pull_request.base.ref || 'master' }} git fetch origin $TARGET_BRANCH:$TARGET_BRANCH git checkout $TARGET_BRANCH pnpm install cd site && pnpm build - name: Upload target build artifact uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: target-llms-full-txt path: site/build/llms-full.txt diff: name: Diff current branch vs. target branch runs-on: ubuntu-latest needs: [build-current, build-target] steps: - name: Download current build uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: current-llms-full-txt path: current/ - name: Download target build uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: target-llms-full-txt path: target/ - name: Diff current vs. target uses: LouisBrunner/diff-action@9ea7b75986aa27143ad4928974c98a5a1bd92170 # v2.2.0 with: mode: addition new: current/llms-full.txt old: target/llms-full.txt output: diff-llms-full.txt tolerance: mixed ================================================ FILE: .github/workflows/preview.yml ================================================ name: preview permissions: contents: read on: push: branches: - master pull_request: paths: - '**/*' - '!.github/workflows/*.yml' - '.github/workflows/preview.yml' - '!assets/**' - '!docs/**' - '!example/**' - '!site/**' - '!test/**' - '!.node-version' - '!.nvmrc' - '!.prettierignore' - '!.prettierrc.json' - '!*.md' - '!deno.check*' - '!deno.lint.json' - '!docker-compose.yml' - '!jsr.json' - '!LICENSE' jobs: release: name: Release preview build runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 with: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install pnpm uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - name: Use Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: cache: 'pnpm' node-version: lts/* - name: Install dependencies run: pnpm install - name: Build run: pnpm build - name: Release preview version run: pnpx pkg-pr-new@0.0.61 publish --template './example' ================================================ FILE: .github/workflows/publish.yml ================================================ # https://docs.npmjs.com/trusted-publishers#github-actions-configuration name: publish on: push: tags: - 'v*' permissions: contents: read id-token: write # Required for OIDC jobs: npm: if: github.repository == 'kysely-org/kysely' environment: release runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 with: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 # Need full history to check branch ancestry - name: verify tag format and branch run: | # Validate ref_name format before assignment if [[ ! "${{ github.ref_name }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "Error: tag does not follow semver!" exit 1 fi TAG="${{ github.ref_name }}" # Check if tag points to a commit that exists on master branch TAG_COMMIT=$(git rev-list -n 1 "$TAG") if ! git merge-base --is-ancestor "$TAG_COMMIT" origin/master; then echo "Error: tag is not based on master branch" exit 1 fi - name: Install pnpm uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - name: Use Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: cache: pnpm node-version: lts/* registry-url: https://registry.npmjs.org/ - name: verify package version run: | TAG="${{ github.ref_name }}" VERSION="${TAG#v}" # Get version from package.json safely (parse as JSON, don't execute) PACKAGE_VERSION=$(node -p "JSON.parse(require('node:fs').readFileSync('./package.json', 'utf8')).version") # Validate version format: must be MAJOR.MINOR.PATCH if [[ ! "$PACKAGE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "Error: package.json version does not follow semver!" exit 1 fi # Verify versions match if [[ "$VERSION" != "$PACKAGE_VERSION" ]]; then echo "Error: tag and package.json version don't match!" exit 1 fi - name: Install dependencies run: npm i -g npm@^11.5.2 && pnpm i --frozen-lockfile --prefer-offline - name: Publish run: pnpm publish --no-git-checks # the workflow runs in a detached head state, so git checks fail jsr: needs: npm environment: release runs-on: ubuntu-latest steps: - name: Harden the runner (Audit all outbound calls) uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 with: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install pnpm uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - name: Use Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: cache: 'pnpm' node-version: lts/* - name: Install dependencies run: pnpm i - name: Prepare for publish run: pnpm script:remove-global-augmentations - name: publish run: pnpm jsr publish --allow-dirty ================================================ FILE: .github/workflows/test.yml ================================================ name: test permissions: contents: read on: push: branches: - master paths: - '**/*' - '!.github/workflows/*.yml' - '.github/workflows/test.yml' - '!assets/**' - '!docs/**' - '!example/**' - '!site/**' - '!.node-version' - '!.nvmrc' - '!.prettierignore' - '!.prettierrc.json' - '!*.md' - '!LICENSE' pull_request: paths: - '**/*' - '!.github/workflows/*.yml' - '.github/workflows/test.yml' - '!assets/**' - '!docs/**' - '!example/**' - '!site/**' - '!.node-version' - '!.nvmrc' - '!.prettierignore' - '!.prettierrc.json' - '!*.md' - '!LICENSE' workflow_dispatch: jobs: node: strategy: fail-fast: false matrix: # https://endoflife.date/nodejs node-version: [20.x, 22.x, 24.x, 25.x] with-transformer: [false, true] runs-on: ubuntu-latest name: Node.js (${{ matrix.node-version }})${{ matrix.with-transformer && ' /w transformer' || '' }} steps: - name: Harden Runner uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 with: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install pnpm uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: cache: 'pnpm' node-version: ${{ matrix.node-version }} - name: Install dependencies run: pnpm install - name: Run docker compose run: docker compose up -d - name: Run node tests run: ${{ matrix.with-transformer && 'TEST_TRANSFORMER=1 ' || '' }}pnpm test - name: Run esbuild test if: ${{ !matrix.with-transformer }} run: pnpm test:esbuild deno: name: Deno runs-on: ubuntu-latest strategy: fail-fast: false matrix: # https://endoflife.date/deno deno-version: [2.5.x] steps: - name: Harden Runner uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 with: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install pnpm uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - name: Use Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: cache: 'pnpm' node-version: lts/* - name: Use Deno ${{ matrix.deno-version }} uses: denoland/setup-deno@e95548e56dfa95d4e1a28d6f422fafe75c4c26fb # v2.0.3 with: deno-version: ${{ matrix.deno-version }} - name: Install dependencies run: pnpm install - name: Run docker compose run: docker compose up -d - name: Build run: pnpm build - name: Run deno tests run: pnpm test:deno - name: Lint for Deno/JSR run: pnpm lint:deno bun: name: Bun runs-on: ubuntu-latest strategy: fail-fast: false matrix: bun-version: [1.1.44, 1.2.23, 1.3] steps: - name: Harden Runner uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 with: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install pnpm uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - name: Use Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: cache: 'pnpm' node-version: lts/* - name: Install dependencies run: pnpm install - name: Use Bun ${{ matrix.bun-version }} uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 with: bun-version: ${{ matrix.bun-version }} - name: Run docker compose run: docker compose up -d - name: Run bun tests run: pnpm test:bun browser: name: Browser runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 with: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install pnpm uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - name: Use Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: cache: 'pnpm' node-version: lts/* - name: Install dependencies run: pnpm install - name: Install playwright run: pnpm playwright install chromium - name: Run browser tests run: pnpm test:browser cloudflare-workers: name: Cloudflare Workers runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 with: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install pnpm uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - name: Use Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: cache: 'pnpm' node-version: lts/* - name: Install dependencies run: pnpm install - name: Run docker compose run: docker compose up -d - name: Run cloudflare workers test run: pnpm test:cloudflare-workers older-typescript-version: name: Older TypeScript version runs-on: ubuntu-latest strategy: fail-fast: false matrix: typescript-version: [ ~4.6.0, # 28.2.2022 https://devblogs.microsoft.com/typescript/announcing-typescript-4-6/ ~4.7.0, # 24.5.2022 https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/ ~4.8.0, # 25.8.2022 https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/ ~4.9.0, # 15.11.2022 https://devblogs.microsoft.com/typescript/announcing-typescript-4-9/ ~5.0.0, # 16.3.2023 https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/ ~5.2.0, # 24.8.2023 https://devblogs.microsoft.com/typescript/announcing-typescript-5-1/ https://devblogs.microsoft.com/typescript/announcing-typescript-5-2/ ~5.3.0, # 20.11.2023 https://devblogs.microsoft.com/typescript/announcing-typescript-5-3/ ~5.4.0, # 6.3.2024 https://devblogs.microsoft.com/typescript/announcing-typescript-5-4/ ~5.8.0, # 28.2.2025 https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/ https://devblogs.microsoft.com/typescript/announcing-typescript-5-6/ https://devblogs.microsoft.com/typescript/announcing-typescript-5-7/ https://devblogs.microsoft.com/typescript/announcing-typescript-5-8/ # ~5.9.0, # 1.8.2025 https://devblogs.microsoft.com/typescript/announcing-typescript-5-9/ ] steps: - name: Harden Runner uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 with: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install pnpm uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - name: Use Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: cache: 'pnpm' node-version: lts/* - name: Install dependencies run: pnpm install - name: Run build with newer TypeScript run: pnpm build - name: Set TypeScript and TSD versions run: | TS_VERSION="${{ matrix.typescript-version }}" echo "TS_VERSION=$TS_VERSION" >> $GITHUB_ENV TSD_VERSION=$(echo '{"~4.6.0":"0.20.0", "~4.7.0":"0.22.0", "~4.8.0":"0.24.1", "~4.9.0":"0.27.0", "~5.0.0":"0.28.1", "~5.2.0":"0.29.0", "~5.3.0":"0.30.7", "~5.4.0":"0.31.2", "~5.8.0":"0.32.0", "~5.9.0":"0.33.0"}' | jq -r --arg key "$TS_VERSION" '.[$key]') echo "TSD_VERSION=$TSD_VERSION" >> $GITHUB_ENV - name: Install Typescript (${{ env.TS_VERSION }}) and TSD (${{ env.TSD_VERSION }}) run: pnpm i -D typescript@${{ env.TS_VERSION }} tsd@${{ env.TSD_VERSION }} - name: Exclude non-backward compatible tests run: pnpm script:exclude-test-files-for-backwards-compat - name: Run tests with older TypeScript version run: pnpm test:typings${{ env.TS_VERSION != '~4.6.0' && env.TS_VERSION != '~4.7.0' && ' && pnpm test:node:build' || '' }} jsdocs: name: JSDocs runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 with: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install pnpm uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - name: Use Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: cache: 'pnpm' node-version: lts/* - name: Use Deno uses: denoland/setup-deno@e95548e56dfa95d4e1a28d6f422fafe75c4c26fb # v2.0.3 with: deno-version: 2.x - name: Install dependencies run: pnpm install - name: Build run: pnpm build - name: Type-check JSDocs code blocks run: pnpm test:jsdocs typescript-native: name: TypeScript Native runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 with: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install pnpm uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - name: Use Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: cache: 'pnpm' node-version: lts/* - name: Install dependencies run: pnpm i - name: Build run: pnpm build - name: Run TypeScript Native tests run: pnpx @typescript/native-preview --project ./test/node/tsconfig.json typescript-composite: name: TypeScript Composite runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 with: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install pnpm uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - name: Use Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: cache: 'pnpm' node-version: lts/* - name: Install dependencies run: pnpm i - name: Test working-directory: test/composite-ts run: npm i && npm run check:types jsr: name: JSR runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0 with: egress-policy: audit - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install pnpm uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - name: Use Node.js uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: cache: 'pnpm' node-version: lts/* - name: Install dependencies run: pnpm i - name: Prepare for publish run: pnpm script:remove-global-augmentations - name: Dry-run jsr publish run: pnpm jsr publish --allow-dirty --dry-run ================================================ FILE: .gitignore ================================================ dist /helpers /test/browser/bundle.js node_modules .vscode .idea tsconfig.tsbuildinfo ================================================ FILE: .mocharc.js ================================================ const { isCI } = require('std-env') module.exports = { forbidOnly: isCI, } ================================================ FILE: .node-version ================================================ 22 ================================================ FILE: .npmrc ================================================ # so we don't have to `-w` anytime we bump root workspace dependencies. ignore-workspace-root-check=true ================================================ FILE: .nvmrc ================================================ lts/* ================================================ FILE: .prettierignore ================================================ *.md ================================================ FILE: .prettierrc.json ================================================ { "semi": false, "singleQuote": true, "trailingComma": "all" } ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # ~~Contributor Covenant Code of Conduct~~ Kysely Code of Conduct ## Our Pledge ~~We as members, contributors, and leaders pledge to make participation in our~~ ~~community a harassment-free experience for everyone, regardless of age, body~~ ~~size, visible or invisible disability, ethnicity, sex characteristics, gender~~ ~~identity and expression, level of experience, education, socio-economic status,~~ ~~nationality, personal appearance, race, religion, or sexual identity~~ ~~and orientation.~~ ~~We pledge to act and interact in ways that contribute to an open, welcoming,~~ ~~diverse, inclusive, and healthy community.~~ Don't be an asshole. You are not entitled to other people's time, energy, or attention. That's it. That's the pledge. ## Our Standards ~~Examples of behavior that contributes to a positive environment for our~~ ~~community include:~~ ~~* Demonstrating empathy and kindness toward other people~~ ~~* Being respectful of differing opinions, viewpoints, and experiences~~ ~~* Giving and gracefully accepting constructive feedback~~ ~~* Accepting responsibility and apologizing to those affected by our mistakes,~~ ~~and learning from the experience~~ ~~* Focusing on what is best not just for us as individuals, but for the~~ ~~overall community~~ ~~Examples of unacceptable behavior include:~~ ~~* The use of sexualized language or imagery, and sexual attention or~~ ~~advances of any kind~~ ~~* Trolling, insulting or derogatory comments, and personal or political attacks~~ ~~* Public or private harassment~~ ~~* Publishing others' private information, such as a physical or email~~ ~~address, without their explicit permission~~ ~~* Other conduct which could reasonably be considered inappropriate in a~~ ~~professional setting~~ Don't be an asshole. You are not entitled to other people's time, energy, or attention. Examples of being an asshole or entitled include: * Probably not the best idea to come in complaining and not having minimal respect to develop some basic sentences. > what is the migration_lock ?? why we have this in the database - https://github.com/kysely-org/kysely/issues/973 * Probably not the best idea to attack the maintainer after he answered succinctly (out of character) for whatever reason (context switch? busy?). > You got any more of them there examples buster? - https://github.com/kysely-org/kysely/issues/677#issuecomment-2019876074 * Probably not the best idea to come in all yelling and demanding stuff. > NO documentation about error handling!!!! > I am really confused that the docs doesnt have anything about error handling > this is a mandataory things to add!! - https://github.com/kysely-org/kysely/issues/1217 * Probably not the best idea to tone police the maintainer after he spent time explaining library philosophy, and to publicly refuse to contribute something you demanded. > I'm sorry fella but with your tone I really don't feel like it. Good luck - https://github.com/kysely-org/kysely/issues/709#issuecomment-1727617260 * Probably not the best idea to question the maintainer's decisions after properly answering your question, and to be entitled for JavaScript skill issue help after the issue is closed. > I don't understand your answer and closing the issue. - https://github.com/kysely-org/kysely/issues/527#issuecomment-1577383794 * Probably not the best idea to jump into closed issues and be entitled to other people's efforts. > why on earth there is no mention of this in the documentation? - https://github.com/kysely-org/kysely/issues/649#issuecomment-2430854835 * Probably not the best idea to not search existing issues, and then ask the maintainer to spend time doing it. > could you please provide numbers of duplicated issues to explore the topic? - https://github.com/kysely-org/kysely/issues/762#issuecomment-1801951960 * Probably not the best idea to sarcastically suggest the maintainer is not helpful while using his hard work for free. > Most helpful man in the world - https://github.com/kysely-org/kysely/issues/762#issuecomment-2404599281 * Probably not the best idea to side with the asshole/entitled by upvoting them or downvoting us. ![image](https://github.com/user-attachments/assets/ca6efd57-d8db-428a-a274-7b45b380887f) ![image](https://github.com/user-attachments/assets/62356fa3-7cea-4b82-9967-d56c76f0beee) ## Enforcement Responsibilities ~~Community leaders are responsible for clarifying and enforcing our standards of~~ ~~acceptable behavior and will take appropriate and fair corrective action in~~ ~~response to any behavior that they deem inappropriate, threatening, offensive,~~ ~~or harmful.~~ ~~Community leaders have the right and responsibility to remove, edit, or reject~~ ~~comments, commits, code, wiki edits, issues, and other contributions that are~~ ~~not aligned to this Code of Conduct, and will communicate reasons for moderation~~ ~~decisions when appropriate.~~ We are here in our free time. We are not getting paid to serve and please you. We will not tolerate assholes or entitled behavior. We are capable of banning you from the GitHub organization, Discord server, or any other community space we manage. Don't make us do it. ## Scope ~~This Code of Conduct applies within all community spaces, and also applies when~~ ~~an individual is officially representing the community in public spaces.~~ ~~Examples of representing our community include using an official e-mail address,~~ ~~posting via an official social media account, or acting as an appointed~~ ~~representative at an online or offline event.~~ Relevant to all repositories in the Kysely GitHub organization, the Kysely Discord server, and any other community space managed by Kysely. ## Enforcement ~~Instances of abusive, harassing, or otherwise unacceptable behavior may be~~ ~~reported to the community leaders responsible for enforcement at~~ ~~Kysely's GitHub repositories and Discord server.~~ ~~All complaints will be reviewed and investigated promptly and fairly.~~ ~~All community leaders are obligated to respect the privacy and security of the~~ ~~reporter of any incident.~~ Report to us anyone who is being an asshole or entitled. We will take appropriate action if necessary. ## Enforcement Guidelines ~~Community leaders will follow these Community Impact Guidelines in determining~~ ~~the consequences for any action they deem in violation of this Code of Conduct:~~ We might do these things: ~~### 1. Correction~~ **Community Impact**: ~~Use of inappropriate language or other behavior deemed~~ ~~unprofessional or unwelcome in the community.~~ Someone being an asshole or entitled. **Consequence**: ~~A private, written warning from community leaders, providing~~ ~~clarity around the nature of the violation and an explanation of why the~~ ~~behavior was inappropriate. A public apology may be requested.~~ We might return the favor (we're not perfect), ignore you, edit/close/lock/delete your post/comment, or ban you. There is no strike system. ~~### 2. Warning~~ ~~**Community Impact**: A violation through a single incident or series~~ ~~of actions.~~ ~~**Consequence**: A warning with consequences for continued behavior. No~~ ~~interaction with the people involved, including unsolicited interaction with~~ ~~those enforcing the Code of Conduct, for a specified period of time. This~~ ~~includes avoiding interactions in community spaces as well as external channels~~ ~~like social media. Violating these terms may lead to a temporary or~~ ~~permanent ban.~~ ~~### 3. Temporary Ban~~ ~~**Community Impact**: A serious violation of community standards, including~~ ~~sustained inappropriate behavior.~~ ~~**Consequence**: A temporary ban from any sort of interaction or public~~ ~~communication with the community for a specified period of time. No public or~~ ~~private interaction with the people involved, including unsolicited interaction~~ ~~with those enforcing the Code of Conduct, is allowed during this period.~~ ~~Violating these terms may lead to a permanent ban.~~ ~~### 4. Permanent Ban~~ ~~**Community Impact**: Demonstrating a pattern of violation of community~~ ~~standards, including sustained inappropriate behavior, harassment of an~~ ~~individual, or aggression toward or disparagement of classes of individuals.~~ ~~**Consequence**: A permanent ban from any sort of public interaction within~~ ~~the community.~~ ## ~~Attribution~~ ~~This Code of Conduct is adapted from the [Contributor Covenant][homepage],~~ ~~version 2.0, available at~~ ~~https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.~~ ~~Community Impact Guidelines were inspired by [Mozilla's code of conduct~~ ~~enforcement ladder](https://github.com/mozilla/diversity).~~ ~~[homepage]: https://www.contributor-covenant.org~~ ~~For answers to common questions about this code of conduct, see the FAQ at~~ ~~https://www.contributor-covenant.org/faq. Translations are available at~~ ~~https://www.contributor-covenant.org/translations.~~ ================================================ FILE: CONTRIBUTING.md ================================================ # Contribution Guidelines Wanna contribute to Kysely or other libraries within the [kysely-org](https://github.com/kysely-org) organization? You're awesome! 🤗 ## 😕 How Can I Contribute? ## 📢 By Spreading the Word If you like what we're building here, please: 1. Tell your friends, co-workers, family, neighbors, followers, agent markdowns, etc. 1. Speak about it in meetups and conferences. (if you need help with the presentation, please ask on Discord!). There is no company behind this organization and project. We do this in our free time and cannot advertise it all by ourselves. Companies have dedicated devrel teams doing all that work, other projects might have community managers or full-time developers with the capacity to also handle social media and meetups with higher frequency. More people using Kysely eventually means: 1. less bugs left unreported and unsolved. 1. high chance people share workarounds. 1. more data points for innovation. 1. more people who might contribute and push this thing even further. 1. it becomes a standard part of the Node.js/Bun/Deno developer's toolbox so you'll happily stumble upon it in future codebases you'll join, or it'll be easier to get buy-in from co-workers/leadership. ## 🤝 By Helping People in Issues or on Discord We need all the help we can get with supporting the community, answering questions, triaging! If you've been using the library for a while, or know a thing or two about SQL, and specific databases, don't be shy about it! ## 🐛 By Submitting Issues (Bugs, Enhancement Ideas, Questions) or Opening threads on Discord Please use search and make sure an existing **open** issue or thread doesn't exist before submitting. If an existing issue is only somewhat relevant, please submit a new issue and reference the old one instead of commenting on the existing one. Let us label your new issue as `duplicate` if it is the same thing - it's fine! If it's not the same thing and you commented on another issue, it makes it harder to track on our end. If possible/relevant, please provide a [playground link](https://kyse.link), Stackblitz OR a public git repository that reproduces the issue. Please provide the exact error/warning texts you're getting. "this doesn't work" or "this throws an error" are not helping us help you. Don't be an asshole. Don't demand support/service from us. ## ⌨️ With Code! ### 📘 Documentation Code Contributions Pull requests are always welcome! The [kysely.dev](https://kysely.dev) application is located @ [/site](https://github.com/kysely-org/kysely/tree/master/site). It is a pretty standard [docusaurus](https://docusaurus.io/) application. The code examples are extracted, using a custom script (see `"script:generate-site-examples"` @ [package.json](https://github.com/kysely-org/kysely/blob/master/package.json)), and custom annotations, from [JSDocs](https://jsdoc.app/) comments in the source code @ [/src](https://github.com/kysely-org/kysely/tree/master/src). If you need to change an existing code example, please do so in the source code AND then run the script. _TODO: explain how to add a new code example._ If it is a big change (lots of lines of code OR files involved), please get a conversation going on an issue or on Discord before starting work on it. ### 🧙 Implementation Code Contributions (Bugfixes, Enhancements [Existing or New Features]) Pull requests (PRs) are welcome, BUT since: 1. Our time and capacity as maintainers are limited. 1. Your time is limited. 1. Your motivation and morale is important to us. 1. Onboarding in a new highly opinionated open-source project can be challenging. We need a process in place: 1. To make sure only things that have a good chance of being accepted are worked on. We hate saying "no". 1. To make sure that things that are being worked on don't result in too much back-and-forth between authors and maintainers. We hate seeing these things drag on for a long time, and we know how frustrating it is on your end. 1. To make sure there's no pressure on you when you take on a task. 1. To make sure there's a clear understanding of who is working on what to avoid redundancies and conflicts. #### The Process Here is the gist of it: 1. If an issue on the subject **doesn't exist** yet, **submit** one. If you want to work on it, **ask** to be **assigned** to it in the issue **description**, in the **comments**, OR on **Discord**. 1. If an issue on the subject **exists** and is labeled with `bug` OR (`enhancement` AND `greenlit`\* OR even `good first issue`\**): 1. If there is a pull request (**PR**) **linked** to it: 1. If the **PR** is **stale** (a few months without new commits or comments from the PR author OR Kysely maintainers), **ask** to be **assigned** to it and continue work on the PR. 1. If the **PR** is **not stale**, you can still offer some review comments, or **ask** to **pair** up with the PR author. 1. If there is **no PR linked** to it and a person is **already assigned** to it: 1. If the assignment is stale (a few weeks without a PR opened after the person was assigned to the issue), ask to be assigned to the issue in the comments OR on Discord. 1. If the assignment is not stale, it's OK to ask the assignee if they'll be willing to pair up. 1. If there is no PR linked to it and no assignee, ask to be assigned to it in the comments OR on Discord. _\* an issue that was reviewed by the maintainers and would be nice to get a pull request for from the community._ _\** an issue that was reviewed by the maintainers and would be a nice opportunity to onboard an open-source newbie into the codebase._ Once you're assigned to an issue, you can start working on your code changes. It's best to ask questions in the issue or on Discord as early as possible, and even share your progress with the maintainers and community via a draft (WIP) pull request. We will close surprise pull requests that are not related to an existing issue or the pull request submitter is not assigned to the issue. We will close pull requests where the changes are too far from acceptable. Such cases are a waste of time for us to even begin to comment on. You can ask for quick informal feedback on Discord in such cases and submit another pull request with better changes later. #### Style/Design Philosophy * Kysely should have zero dependencies. * Kysely should work in all JavaScript environments (node.js, deno & modern browsers), even though the main focus is node.js. * Everything is immutable. * The API should be as close to writing raw SQL as possible while still providing type safety. * Everything outside of dialect implementations is dialect-agnostic. * Everything is tested. * Functionality - No mocks. Everything is tested against real database instances. No partial testing. If a sql "thing" is supported by some databases, test it against all of them. * Types - We're a type-safe package, you get the idea. * Everything consumer-facing should be documented. * Everything is type-safe. Things that cannot be implemented in a way that is safe for all cases, are best left for consumers to implement. * Most features should have escape hatches. * Less is more. #### Getting Started 1. fork kysely. 1. clone your fork. 1. install Node.js (preferably the latest even-numbered version). we recommend using [`fnm`](https://github.com/Schniz/fnm) for managing Node.js versions. 1. install `corepack` by running `npm i -g corepack` in your terminal. 1. run `corepack enable` in your terminal. 1. `cd` into the repository. 1. run `corepack install` in your terminal to install the exact package manager and version used in this project. 1. run `pnpm i` in your terminal to install dependencies. if `pnpm` tells you it ignored a `postinstall` script from some package - tells us immediately via issue or Discord - this is a security concern! 1. create a branch (we don't care about naming). 1. create a draft pull request. link the relevant issue by referring to it in the PR's description. E.g. `closes #123` will link the PR to issue/pull request #123. 1. implement your changes. #### Testing 1. write functionality tests @ [/test/node](https://github.com/kysely-org/kysely/tree/master/test/node). 1. write typings tests @ [/test/typings](https://github.com/kysely-org/kysely/tree/master/test/typings) 1. install Docker. 1. run `docker compose up -d` in your terminal to spin up database instances. 1. run `pnpm test` in your terminal. ================================================ FILE: FUNDING.md ================================================ # Funding Kysely is an MIT-licensed open-source project and is completely free to use - and will remain so. Kysely is a labor of love. We do this in our free time. We do not accept donations. - Getting paid for open-source won't make us put more time and effort into open-source. We have family, friends, and other hobbies and interests. - We work in the tech industry, full-time, as software developers. We are comfortable as-is. - We don't believe in freelance open-source as a stable source of income for the long term. We have mouths to feed, and careers to maintain. - Sponsored projects tend to be affected by big sponsors. We want to keep Kysely away from companies' and VCs' opinions and business needs. - Kysely is escapism for us. We don't want it to feel like another job. Instead of donating to us, we encourage you to support meaningful causes or other open-source projects in need of funding. Thank you for supporting Kysely through your usage, contributions, and feedback. ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2022 Sami Koskimäki Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://stand-with-ukraine.pp.ua) [![NPM Version](https://img.shields.io/npm/v/kysely?style=flat&label=latest)](https://github.com/kysely-org/kysely/releases/latest) [![Tests](https://github.com/kysely-org/kysely/actions/workflows/test.yml/badge.svg)](https://github.com/kysely-org/kysely) [![License](https://img.shields.io/github/license/kysely-org/kysely?style=flat)](https://github.com/kysely-org/kysely/blob/master/LICENSE) [![Issues](https://img.shields.io/github/issues-closed/kysely-org/kysely?logo=github)](https://github.com/kysely-org/kysely/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc) [![Pull Requests](https://img.shields.io/github/issues-pr-closed/kysely-org/kysely?label=PRs&logo=github&style=flat)](https://github.com/kysely-org/kysely/pulls?q=is%3Apr+is%3Aopen+sort%3Aupdated-desc) ![GitHub contributors](https://img.shields.io/github/contributors/kysely-org/kysely) [![NPM Downloads](https://img.shields.io/npm/dw/kysely?logo=npm)](https://www.npmjs.com/package/kysely) [![JSR Downloads](https://jsr.io/badges/@kysely/kysely/weekly-downloads)](https://jsr.io/@kysely/kysely) [![JSR Score](https://jsr.io/badges/@kysely/kysely/score)](https://jsr.io/@kysely/kysely) ###### Join the discussion ⠀⠀⠀⠀⠀⠀⠀ [![Discord](https://img.shields.io/badge/Discord-%235865F2.svg?style=flat&logo=discord&logoColor=white)](https://discord.gg/xyBJ3GwvAm) [![Bluesky](https://img.shields.io/badge/Bluesky-0285FF?style=flat&logo=Bluesky&logoColor=white)](https://bsky.app/profile/kysely.dev) ###### Get started [![Postgres](https://img.shields.io/badge/postgres-%23316192.svg?style=flat&logo=postgresql&logoColor=white)](https://kysely.dev/docs/getting-started?dialect=postgresql) [![MySQL](https://img.shields.io/badge/mysql-4479A1.svg?style=flat&logo=mysql&logoColor=white)](https://kysely.dev/docs/getting-started?dialect=mysql) [![MicrosoftSQLServer](https://img.shields.io/badge/Microsoft%20SQL%20Server-CC2927?style=flat&logo=microsoft%20sql%20server&logoColor=white)](https://kysely.dev/docs/getting-started?dialect=mssql) [![SQLite](https://img.shields.io/badge/sqlite-%2307405e.svg?style=flat&logo=sqlite&logoColor=white)](https://kysely.dev/docs/getting-started?dialect=sqlite) & more! # [Kysely](https://kysely.dev) Kysely (pronounce “Key-Seh-Lee”) is a type-safe and autocompletion-friendly [TypeScript](https://www.typescriptlang.org/) [SQL](https://en.wikipedia.org/wiki/SQL) query builder. Inspired by [Knex.js](http://knexjs.org/). Mainly developed for [Node.js](https://nodejs.org/en/) but also runs on all other [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript) environments like [Deno](https://deno.com/), [Bun](https://bun.sh/), [Cloudflare Workers](https://workers.cloudflare.com/) and web browsers. ![](https://github.com/kysely-org/kysely/blob/master/assets/demo.gif) Kysely makes sure you only refer to tables and columns that are visible to the part of the query you're writing. The result type only has the selected columns with correct types and aliases. As an added bonus you get autocompletion for all that stuff. As shown in the gif above, through the pure magic of modern TypeScript, Kysely is even able to parse the alias given to `pet.name` and add the `pet_name` column to the result row type. Kysely is able to infer column names, aliases and types from selected subqueries, joined subqueries, `with` statements and pretty much anything you can think of. Of course there are cases where things cannot be typed at compile time, and Kysely offers escape hatches for these situations. See the [sql template tag](https://kysely-org.github.io/kysely-apidoc/interfaces/Sql.html) and the [DynamicModule](https://kysely-org.github.io/kysely-apidoc/classes/DynamicModule.html#ref) for more info. All API documentation is written in the typing files and you can simply hover over the module, class or method you're using to see it in your IDE. The same documentation is also hosted [here](https://kysely-org.github.io/kysely-apidoc/). If you start using Kysely and can't find something you'd want to use, please open an issue or join our [Discord server](https://discord.gg/xyBJ3GwvAm). # Getting started Please visit our documentation site [kysely.dev](https://kysely.dev) to get started. We also have a comprehensive API documentation hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation in your IDE by hovering over a class/method/property/whatever. # Core team ## Project leads Responsible for project direction, API design, maintenance, code reviews, community support, documentation, and working on some of the most impactful/challenging things.

Sami Koskimäki

(the author)

Igal Klebanov

(the dynamo)
## Honorable mentions People who had special impact on the project and its growth.

Fernando Hurtado

(1st docs)

Wirekang

(playground)

Tim Griesser

(Knex)

Robin Blomberg

(codegen)

Shoubhit Dash

(prisma idea)

Valtýr Örn Kjartansson

(prisma impl)

Dax Raad

(early adopter)

Theo Browne

(early promoter)

Lee Robinson

(early promoter)

Ethan Resnick

(timely feedback)

Harminder Virk

(dope writeup)

Johan Eliasson

(promoter/educator)
## All contributors


Want to contribute? Check out our contribution guidelines.

Powered by Vercel

================================================ FILE: SECURITY.md ================================================ # Security Policy We take security seriously. We are responsible maintainers. Kysely is widely used in production and there's a lot at stake. We're not perfect. ## Supply Chain Attacks We're on the frontline. We listen, we adapt. We try to use up-to-date best practices and standards from the maintainer community. Being hacked and helping distribute malicious code to our community will be soul crushing to us. We're not perfect. GitHub is not perfect. [NPM](https://npmjs.com) is not perfect. ### Recommendations These are not perfect. 1. Our runtimes are not perfect. Keep yours up-to-date - [End Of Life (EOL) versions don't receive security updates](https://nodejs.org/en/blog/announcements/node-18-eol-support) and there are fewer eyes watching. 1. [`pnpm`](https://pnpm.io) is not perfect, but it's the [closest](https://pnpm.io/supply-chain-security) we have - use it as your package manager. 1. It offers out the box [protection from malicious `postinstall` scripts](https://pnpm.io/settings#onlybuiltdependencies) - many attackers use `postinstall` scripts to run/setup their malicious code while you install the package. 1. It allows to [ignore new package versions](https://pnpm.io/settings#minimumreleaseage) with a configurable time period - response to supply chain attacks (regaining NPM/GitHub access, pulling the malicious package versions off NPM and publishing new safe versions) usually takes up to 24 hours. 1. [Provenance](https://docs.npmjs.com/viewing-package-provenance) is not perfect, but it's the closest we have - audit publish flows, source commits, builds, etc. 1. Our ecosystem is not perfect. Simplify/flatten your dependency graph. You probably don't need some of those libraries. Your runtime might have a native solution for some of these things. You can copy that single function over - attackers prey on (undermaintained) transitive dependencies as the blast radius is bigger, and response takes longer. 1. Auth is not perfect. Use secret/password managers. Encrypt. [2FA](https://en.wikipedia.org/wiki/Multi-factor_authentication) everything. Don't access production directly from laptops - many attacks involve exfiltration, and they're [getting more creative](https://www.anthropic.com/news/detecting-countering-misuse-aug-2025). ## Reporting Security Issues To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/kysely-org/kysely/security/advisories/new) tab. Don't abuse the system. Don't waste our time with troll/spam/AI slop false reports. Don't be an asshole. We're not perfect. ================================================ FILE: deno.check.d.ts ================================================ import type { ColumnType, Generated, GeneratedAlways, Insertable, Kysely, Selectable, SqlBool, Updateable, } from './dist/esm' export interface Database { audit: AuditTable person: PersonTable person_backup: PersonTable pet: PetTable toy: ToyTable wine: WineTable wine_stock_change: WineStockChangeTable } interface AuditTable { id: Generated action: string } interface PersonTable { id: Generated address: { city: string } | null age: number | null birthdate: ColumnType created_at: GeneratedAlways deleted_at: ColumnType experience: { role: string }[] | null first_name: string gender: 'male' | 'female' | 'other' | null has_pets: Generated<'Y' | 'N'> last_name: string | null middle_name: string | null nicknames: string[] | null nullable_column: string | null profile: { addresses: { city: string }[] website: { url: string } } | null updated_at: ColumnType marital_status: 'single' | 'married' | 'divorced' | 'widowed' | null } interface PetTable { id: Generated created_at: GeneratedAlways is_favorite: Generated name: string owner_id: number species: Species } interface ToyTable { id: Generated name: string pet_id: number price: number } interface WineTable { id: Generated name: string stock: number } interface WineStockChangeTable { id: Generated stock_delta: number wine_name: string } export type Person = Selectable export type NewPerson = Insertable export type PersonUpdate = Updateable export type Pet = Selectable export type NewPet = Insertable export type PetUpdate = Updateable export type Species = 'dog' | 'cat' | 'hamster' declare global { // @ts-ignore export class Buffer { static isBuffer(obj: unknown): obj is { length: number } static compare(a: Buffer, b: Buffer): number } export const db: Kysely export function functionThatExpectsPersonWithNonNullValue( person: Person & { nullable_column: string }, ): void } ================================================ FILE: deno.check.json ================================================ { "$schema": "https://deno.land/x/deno/cli/schemas/config-file.v1.json", "compilerOptions": { "types": ["./deno.check.d.ts"] }, "imports": { "better-sqlite3": "npm:better-sqlite3", "kysely": "./dist/esm", "kysely/helpers/mssql": "./dist/esm/helpers/mssql.js", "kysely/helpers/mysql": "./dist/esm/helpers/mysql.js", "kysely/helpers/postgres": "./dist/esm/helpers/postgres.js", "kysely/helpers/sqlite": "./dist/esm/helpers/sqlite.js", "lodash/snakeCase": "npm:lodash/snakeCase", "mysql2": "npm:mysql2", "pg": "npm:pg", "pg-cursor": "npm:pg-cursor", "tarn": "npm:tarn", "tedious": "npm:tedious", "type-editor": "./deno.check.d.ts" } } ================================================ FILE: deno.lint.json ================================================ { "$schema": "https://deno.land/x/deno/cli/schemas/config-file.v1.json", "lint": { "rules": { "tags": ["jsr"] } } } ================================================ FILE: docker-compose.yml ================================================ services: # credit to Knex.js team for the following mssql setup here: mssql: image: mcr.microsoft.com/mssql/server:2022-latest ports: - '21433:1433' environment: ACCEPT_EULA: Y MSSQL_PID: Express SA_PASSWORD: KyselyTest0 healthcheck: test: /opt/mssql-tools/bin/sqlcmd -S mssql -U sa -P 'KyselyTest0' -Q 'select 1' waitmssql: image: mcr.microsoft.com/mssql/server:2017-latest links: - mssql depends_on: - mssql environment: MSSQL_PID: Express entrypoint: - bash - -c # https://docs.microsoft.com/en-us/sql/relational-databases/logs/control-transaction-durability?view=sql-server-ver15#bkmk_DbControl - 'until /opt/mssql-tools/bin/sqlcmd -S mssql -U sa -P KyselyTest0 -d master -Q "CREATE DATABASE kysely_test; ALTER DATABASE kysely_test SET ALLOW_SNAPSHOT_ISOLATION ON; ALTER DATABASE kysely_test SET DELAYED_DURABILITY = FORCED"; do sleep 5; done' mysql: image: 'mysql/mysql-server' environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: kysely_test ports: - '3308:3306' volumes: - ./test/scripts/mysql-init.sql:/data/application/init.sql command: --init-file /data/application/init.sql postgres: image: 'postgres' environment: POSTGRES_DB: kysely_test POSTGRES_USER: kysely POSTGRES_HOST_AUTH_METHOD: trust ports: - '5434:5432' ================================================ FILE: docs/index.html ================================================ ================================================ FILE: example/.gitignore ================================================ lib node_modules .vscode ================================================ FILE: example/.prettierrc.json ================================================ { "semi": false, "singleQuote": true } ================================================ FILE: example/README.md ================================================ # An example server that uses Kysely This is a simple but realistic Koa based server that shows one way to use Kysely. Since this example attempts to mimic a real world project, most of the code isn't relevant to learning how to use Kysely. The relevant parts are the repositories and how they are used. This examples is by no means the best or the right way to use Kysely, but simply one possible way. The server has three main levels of abstraction: 1. **Repository**: Repositories contain all Kysely code and provide higher level methods for dealing with the database. 2. **Service**: All business logic is implemented in the service layer. Services use repositories to interact with the database. While repositories deal with database rows and types like `UserRow` the service layer doesn't leak out those types. For example user service methods return and take `User` objects instead of `UserRow` objects. 3. **Controller**: Controllers define the HTTP API. Controllers validate and convert the inputs and outputs from/to the network and call services to carry out the actual business logic. ## Running the example All you need to do start poking around with the code is to clone kysely, go to the example folder and run: ``` npm install npm test ``` You need to have postgres running in the default port `5432` and the default postgres user `postgres` should exist with no password. You can modify the [test configuration](https://github.com/kysely-org/kysely/blob/master/example/test/test-config.ts) if you want to use different settings. ================================================ FILE: example/package.json ================================================ { "name": "kysely_koa_example", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "build": "rm -rf dist && tsc", "test": "mocha --timeout 5000 -r ts-node/register test/**/*.test.ts", "migrate:latest": "node dist/migrate-to-latest.js" }, "author": "Sami Koskimäki ", "license": "MIT", "dependencies": { "ajv": "^8.11.2", "dotenv": "^16.0.0", "jsonwebtoken": "^9.0.0", "koa": "^2.14.1", "koa-bodyparser": "^4.3.0", "koa-compress": "^5.1.0", "koa-json": "^2.0.2", "koa-router": "^12.0.0", "kysely": "0.23.3", "pg": "^8.8.0" }, "devDependencies": { "@types/chai": "^4.3.4", "@types/jsonwebtoken": "^9.0.0", "@types/koa": "^2.13.5", "@types/koa-bodyparser": "^4.3.10", "@types/koa-compress": "^4.0.3", "@types/koa-json": "^2.0.20", "@types/koa-router": "^7.4.4", "@types/mocha": "^10.0.1", "@types/node": "^16.11.9", "axios": "^1.2.2", "chai": "^4.3.7", "mocha": "^10.2.0", "ts-node": "^10.9.1", "typescript": "^4.9.4" } } ================================================ FILE: example/src/app.ts ================================================ import * as Koa from 'koa' import * as json from 'koa-json' import * as compress from 'koa-compress' import * as bodyParser from 'koa-bodyparser' import { Server } from 'http' import { Kysely, PostgresDialect } from 'kysely' import { Config } from './config' import { Context, ContextExtension } from './context' import { Database } from './database' import { Router } from './router' import { userController } from './user/user.controller' import { ControllerError } from './util/errors' import { isObject } from './util/object' import { Pool } from 'pg' export class App { #config: Config #koa: Koa #router: Router #db: Kysely #server?: Server constructor(config: Config) { this.#config = config this.#koa = new Koa() this.#router = new Router() this.#db = new Kysely({ dialect: new PostgresDialect({ pool: async () => new Pool(this.#config.database), }), }) this.#koa.use(compress()) this.#koa.use(bodyParser()) this.#koa.use(json()) this.#koa.use(this.errorHandler) this.#koa.use(this.decorateContext) userController(this.#router) this.#koa.use(this.#router.routes()) this.#koa.use(this.#router.allowedMethods()) } get db(): Kysely { return this.#db } async start(): Promise { return new Promise((resolve) => { this.#server = this.#koa.listen(this.#config.port, resolve) }) } async stop(): Promise { await new Promise((resolve, reject) => { this.#server?.close((err) => { if (err) { reject(err) } else { resolve() } }) }) await this.#db?.destroy() } private readonly errorHandler = async ( ctx: Context, next: Koa.Next, ): Promise => { try { await next() } catch (error) { if (error instanceof ControllerError) { respondError(ctx, error) } else { respondError(ctx, createUnknownError(error)) } } } private readonly decorateContext = async ( ctx: Context, next: Koa.Next, ): Promise => { ctx.db = this.#db! await next() } } function respondError(ctx: Context, error: ControllerError): void { ctx.status = error.status ctx.body = error.toJSON() } function createUnknownError(error: unknown): ControllerError { return new ControllerError( 500, 'UnknownError', (isObject(error) ? error.message : undefined) ?? 'unknown error', ) } ================================================ FILE: example/src/authentication/auth-token.service.ts ================================================ import * as jwt from 'jsonwebtoken' import * as refreshTokenRepository from './refresh-token.repository' import { Kysely } from 'kysely' import { config } from '../config' import { Database } from '../database' import { AuthToken } from './auth-token' import { RefreshToken } from './refresh-token' export class AuthTokenError extends Error {} export class InvalidAuthTokenError extends AuthTokenError {} export class AuthTokenExpiredError extends AuthTokenError {} export class RefreshTokenUserIdMismatchError extends Error {} export interface AuthTokenPayload { userId: string refreshTokenId: string } interface RefreshTokenPayload { userId: string refreshTokenId: string isRefreshToken: true } export async function createRefreshToken( db: Kysely, userId: string, ): Promise { const { refresh_token_id } = await refreshTokenRepository.insertRefreshToken( db, userId, ) return signRefreshToken({ userId, refreshTokenId: refresh_token_id, isRefreshToken: true, }) } function signRefreshToken(tokenPayload: RefreshTokenPayload): RefreshToken { // Refresh tokens never expire. return { refreshToken: jwt.sign(tokenPayload, config.authTokenSecret) } } export async function createAuthToken( db: Kysely, refreshToken: RefreshToken, ): Promise { const { userId, refreshTokenId } = verifyRefreshToken(refreshToken) await refreshTokenRepository.updateRefreshToken(db, refreshTokenId, { last_refreshed_at: new Date(), }) return signAuthToken({ userId, refreshTokenId }) } function verifyRefreshToken(token: RefreshToken): RefreshTokenPayload { const payload = verifyToken(token.refreshToken) if ( !payload || typeof payload !== 'object' || typeof payload.userId !== 'string' || typeof payload.refreshTokenId !== 'string' || payload.isRefreshToken !== true ) { throw new InvalidAuthTokenError() } return { userId: payload.userId, refreshTokenId: payload.refreshTokenId, isRefreshToken: true, } } function signAuthToken(tokenPayload: AuthTokenPayload): AuthToken { return { authToken: jwt.sign(tokenPayload, config.authTokenSecret, { expiresIn: config.authTokenExpiryDuration, }), } } export function verifyAuthToken(token: AuthToken): AuthTokenPayload { const payload = verifyToken(token.authToken) if ( !payload || typeof payload !== 'object' || typeof payload.userId !== 'string' || typeof payload.refreshTokenId !== 'string' ) { throw new InvalidAuthTokenError() } return { userId: payload.userId, refreshTokenId: payload.refreshTokenId, } } function verifyToken(token: string): string | jwt.JwtPayload { try { return jwt.verify(token, config.authTokenSecret) } catch (error) { if (error instanceof jwt.TokenExpiredError) { throw new AuthTokenExpiredError() } throw new InvalidAuthTokenError() } } export async function deleteRefreshToken( db: Kysely, userId: string, refreshToken: RefreshToken, ): Promise { const payload = verifyRefreshToken(refreshToken) if (payload.userId !== userId) { throw new RefreshTokenUserIdMismatchError() } await db .deleteFrom('refresh_token') .where('refresh_token_id', '=', payload.refreshTokenId) .execute() } ================================================ FILE: example/src/authentication/auth-token.ts ================================================ export interface AuthToken { authToken: string } ================================================ FILE: example/src/authentication/authentication.service.ts ================================================ import * as refreshTokenRepository from './refresh-token.repository' import * as authTokenService from './auth-token.service' import { AuthTokenExpiredError, AuthTokenPayload } from './auth-token.service' import { Next } from 'koa' import { Context } from '../context' import { ControllerError } from '../util/errors' export async function authenticateUser( ctx: Context, next: Next, ): Promise { const { userId } = ctx.params if (!userId) { throw new ControllerError( 400, 'NoUserIdParameter', 'no user id parameter found in the route', ) } const authorization = ctx.headers['authorization'] if (!authorization || !authorization.startsWith('Bearer ')) { throw new ControllerError( 400, 'InvalidAuthorizationHeader', 'missing or invalid Authorization header', ) } const authToken = authorization.substring('Bearer '.length) let authTokenPayload: AuthTokenPayload | undefined try { authTokenPayload = authTokenService.verifyAuthToken({ authToken }) } catch (error) { if (error instanceof AuthTokenExpiredError) { throw new ControllerError( 401, 'ExpiredAuthToken', 'the auth token has expired', ) } throw new ControllerError(401, 'InvalidAuthToken', 'invalid auth token') } if (userId !== authTokenPayload.userId) { throw new ControllerError(403, 'UserMismatch', "wrong user's auth token") } const refreshToken = await refreshTokenRepository.findRefreshToken( ctx.db, authTokenPayload.userId, authTokenPayload.refreshTokenId, ) if (!refreshToken) { throw new ControllerError( 404, 'UserOrRefreshTokenNotFound', 'either the user or the refresh token has been deleted', ) } return next() } ================================================ FILE: example/src/authentication/refresh-token.repository.ts ================================================ import { Kysely } from 'kysely' import { Database } from '../database' import { RefreshTokenRow, UpdateableRefreshTokenRow, } from './refresh-token.table' export async function insertRefreshToken( db: Kysely, userId: string, ): Promise { const [refreshToken] = await db .insertInto('refresh_token') .values({ user_id: userId, last_refreshed_at: new Date(), }) .returningAll() .execute() return refreshToken } export async function findRefreshToken( db: Kysely, userId: string, refreshTokenId: string, ): Promise { const token = await db .selectFrom('refresh_token as rt') .selectAll('rt') .innerJoin('user as u', 'rt.user_id', 'u.user_id') .where('u.user_id', '=', userId) .where('rt.refresh_token_id', '=', refreshTokenId) .executeTakeFirst() return token } export async function updateRefreshToken( db: Kysely, refreshTokenId: string, patch: Pick, ): Promise { await db .updateTable('refresh_token') .set(patch) .where('refresh_token_id', '=', refreshTokenId) .execute() } ================================================ FILE: example/src/authentication/refresh-token.table.ts ================================================ import { Generated, Insertable, Selectable, Updateable } from 'kysely' export interface RefreshTokenTable { refresh_token_id: Generated user_id: string last_refreshed_at: Date created_at: Generated } export type RefreshTokenRow = Selectable export type InsertableRefreshTokenRow = Insertable export type UpdateableRefreshTokenRow = Updateable ================================================ FILE: example/src/authentication/refresh-token.ts ================================================ import { ajv } from '../util/ajv' export interface RefreshToken { refreshToken: string } export const validateRefreshToken = ajv.compile({ type: 'object', required: ['refreshToken'], properties: { refreshToken: { type: 'string', }, }, }) ================================================ FILE: example/src/config.ts ================================================ import * as dotenv from 'dotenv' import { ConnectionConfig } from 'pg' dotenv.config() export interface Config { readonly port: number readonly authTokenSecret: string readonly authTokenExpiryDuration: string readonly database: ConnectionConfig } export const config: Config = Object.freeze({ port: parseInt(getEnvVariable('PORT'), 10), authTokenSecret: getEnvVariable('AUTH_TOKEN_SECRET'), authTokenExpiryDuration: getEnvVariable('AUTH_TOKEN_EXIRY_DURATION'), database: Object.freeze({ database: getEnvVariable('DATABASE'), host: getEnvVariable('DATABASE_HOST'), user: getEnvVariable('DATABASE_USER'), }), }) function getEnvVariable(name: string): string { if (!process.env[name]) { throw new Error(`environment variable ${name} not found`) } return process.env[name]! } ================================================ FILE: example/src/context.ts ================================================ import * as Koa from 'koa' import * as KoaRouter from 'koa-router' import { Kysely } from 'kysely' import { Database } from './database' export interface ContextExtension { db: Kysely } export type Context = Koa.ParameterizedContext< any, ContextExtension & KoaRouter.IRouterParamContext, any > ================================================ FILE: example/src/database.ts ================================================ import { RefreshTokenTable } from './authentication/refresh-token.table' import { PasswordSignInMethodTable } from './user/sign-in-method/password-sign-in-method.table' import { SignInMethodTable } from './user/sign-in-method/sign-in-method.table' import { UserTable } from './user/user.table' export interface Database { user: UserTable refresh_token: RefreshTokenTable sign_in_method: SignInMethodTable password_sign_in_method: PasswordSignInMethodTable } ================================================ FILE: example/src/migrate-to-latest.ts ================================================ import * as path from 'path' import { promises as fs } from 'fs' import { Database } from './database' import { config } from './config' import { Kysely, Migrator, PostgresDialect, FileMigrationProvider, } from 'kysely' import { Pool } from 'pg' async function migrateToLatest() { const db = new Kysely({ dialect: new PostgresDialect({ pool: new Pool(config.database), }), }) const migrator = new Migrator({ db, provider: new FileMigrationProvider({ fs, path, migrationFolder: path.join(__dirname, 'migrations'), }), }) const { error, results } = await migrator.migrateToLatest() results?.forEach((it) => { if (it.status === 'Success') { console.log(`migration "${it.migrationName}" was executed successfully`) } else if (it.status === 'Error') { console.error(`failed to execute migration "${it.migrationName}"`) } }) if (error) { console.error('failed to migrate') console.error(error) process.exit(1) } await db.destroy() } migrateToLatest() ================================================ FILE: example/src/migrations/2021_09_18_06_54_59_create_user.ts ================================================ import { Kysely, sql } from 'kysely' export async function up(db: Kysely): Promise { await db.schema .createTable('user') .addColumn('user_id', 'uuid', (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`), ) .addColumn('first_name', 'text') .addColumn('last_name', 'text') .addColumn('email', 'text', (col) => col.unique()) .addColumn('created_at', 'timestamp', (col) => col.defaultTo(sql`NOW()`)) .execute() } export async function down(db: Kysely): Promise { await db.schema.dropTable('user').execute() } ================================================ FILE: example/src/migrations/2021_09_18_14_05_20_create_refresh_token.ts ================================================ import { Kysely, sql } from 'kysely' export async function up(db: Kysely): Promise { await db.schema .createTable('refresh_token') .addColumn('refresh_token_id', 'uuid', (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`), ) .addColumn('user_id', 'uuid', (col) => col.references('user.user_id').notNull().onDelete('cascade'), ) .addColumn('last_refreshed_at', 'timestamp', (col) => col.notNull()) .addColumn('created_at', 'timestamp', (col) => col.notNull().defaultTo(sql`NOW()`), ) .execute() await db.schema .createIndex('refresh_token_user_id_index') .on('refresh_token') .column('user_id') .execute() } export async function down(db: Kysely): Promise { await db.schema.dropTable('refresh_token').execute() } ================================================ FILE: example/src/migrations/2021_09_18_18_22_45_create_sign_in_method.ts ================================================ import { Kysely } from 'kysely' export async function up(db: Kysely): Promise { await db.schema .createTable('sign_in_method') .addColumn('user_id', 'uuid', (col) => col.references('user.user_id').notNull().onDelete('cascade'), ) .addColumn('type', 'text', (col) => col.notNull()) .addPrimaryKeyConstraint('sign_in_method_primary_key', ['user_id', 'type']) .execute() await db.schema .createTable('password_sign_in_method') .addColumn('user_id', 'uuid', (col) => col.references('user.user_id').notNull().primaryKey().onDelete('cascade'), ) .addColumn('password_hash', 'text', (col) => col.notNull()) .execute() await db.schema .createIndex('sign_in_method_user_id_index') .on('sign_in_method') .column('user_id') .execute() await db.schema .createIndex('password_sign_in_method_user_id_index') .on('password_sign_in_method') .column('user_id') .execute() } export async function down(db: Kysely): Promise { await db.schema.dropTable('sign_in_method').execute() await db.schema.dropTable('password_sign_in_method').execute() } ================================================ FILE: example/src/router.ts ================================================ import * as KoaRouter from 'koa-router' import { ContextExtension } from './context' export class Router extends KoaRouter {} ================================================ FILE: example/src/user/sign-in-method/password-sign-in-method.table.ts ================================================ import { Insertable, Selectable, Updateable } from 'kysely' export interface PasswordSignInMethodTable { user_id: string password_hash: string } export type PasswordSignInMethodRow = Selectable export type InsertablePasswordSignInMethodRow = Insertable export type UpdateablePasswordSignInMethodRow = Updateable ================================================ FILE: example/src/user/sign-in-method/sign-in-method.controller.ts ================================================ import * as signInMethodService from './sign-in-method.service' import * as authenticationService from '../../authentication/authentication.service' import * as authTokenService from '../../authentication/auth-token.service' import { RefreshTokenUserIdMismatchError } from '../../authentication/auth-token.service' import { PasswordTooLongError, PasswordTooWeakError, SignInMethodNotFoundError, UserAlreadyHasSignInMethodError, WrongPasswordError, } from './sign-in-method.service' import { validateRefreshToken } from '../../authentication/refresh-token' import { Router } from '../../router' import { ControllerError, UserNotFoundError } from '../../util/errors' import { validatePasswordSignInMethod } from './sign-in-method' export function signInMethodController(router: Router): void { router.post( '/api/v1/user/:userId/sign-in-methods', authenticationService.authenticateUser, async (ctx) => { const { body } = ctx.request if (!validatePasswordSignInMethod(body)) { throw new ControllerError( 400, 'InvalidSignInMethod', 'invalid sign in method', ) } try { await ctx.db.transaction().execute(async (trx) => { await signInMethodService.addPasswordSignInMethod( trx, ctx.params.userId, body, ) }) ctx.status = 201 ctx.body = { success: true } } catch (error) { if (error instanceof UserNotFoundError) { throw new ControllerError(404, 'UserNotFound', 'user not found') } else if (error instanceof PasswordTooWeakError) { throw new ControllerError( 400, 'PasswordTooWeak', 'password is too weak', ) } else if (error instanceof PasswordTooLongError) { throw new ControllerError( 400, 'PasswordTooLong', 'password is too long', ) } else if (error instanceof UserAlreadyHasSignInMethodError) { throw new ControllerError( 409, 'UserAlreadyHasSignInMethod', 'the user already has a sign in method', ) } throw error } }, ) router.post('/api/v1/user/sign-in', async (ctx) => { const { body } = ctx.request if (!validatePasswordSignInMethod(body)) { throw new ControllerError( 400, 'InvalidSignInMethod', 'invalid sign in method', ) } try { const signedInUser = await ctx.db.transaction().execute(async (trx) => { return signInMethodService.singInUsingPassword(trx, body) }) ctx.status = 200 ctx.body = { user: signedInUser.user, authToken: signedInUser.authToken.authToken, refreshToken: signedInUser.refreshToken.refreshToken, } } catch (error) { if ( error instanceof UserNotFoundError || error instanceof WrongPasswordError || error instanceof SignInMethodNotFoundError ) { // Don't leak too much information about why the sign in failed. throw new ControllerError( 401, 'InvalidCredentials', 'wrong email or password', ) } throw error } }) router.post( '/api/v1/user/:userId/sign-out', authenticationService.authenticateUser, async (ctx) => { const { body } = ctx.request if (!validateRefreshToken(body)) { throw new ControllerError( 400, 'InvalidRefreshToken', 'the body must contain a valid refresh token', ) } try { await authTokenService.deleteRefreshToken( ctx.db, ctx.params.userId, body, ) ctx.status = 200 ctx.body = { success: true } } catch (error) { if (error instanceof RefreshTokenUserIdMismatchError) { throw new ControllerError( 403, 'RefreshTokenUserIdMismatch', "cannot delete another user's refresh token", ) } throw error } }, ) } ================================================ FILE: example/src/user/sign-in-method/sign-in-method.repository.ts ================================================ import { Kysely } from 'kysely' import { Database } from '../../database' import { InsertablePasswordSignInMethodRow, PasswordSignInMethodRow, } from './password-sign-in-method.table' export async function findPasswordSignInMethod( db: Kysely, userId: string, ): Promise { const method = await db .selectFrom('sign_in_method as sim') .innerJoin('password_sign_in_method as psim', 'psim.user_id', 'sim.user_id') .selectAll('psim') .where('sim.type', '=', 'password') .where('sim.user_id', '=', userId) .executeTakeFirst() return method } export async function insertPasswordSignInMethod( db: Kysely, method: InsertablePasswordSignInMethodRow, ): Promise { await db .with('sim', (db) => db .insertInto('sign_in_method') .values({ user_id: method.user_id, type: 'password' }), ) .insertInto('password_sign_in_method') .values(method) .execute() return method } ================================================ FILE: example/src/user/sign-in-method/sign-in-method.service.ts ================================================ import * as crypto from 'crypto' import * as signInMethodRepository from './sign-in-method.repository' import * as userService from '../user.service' import * as authTokenService from '../../authentication/auth-token.service' import { Transaction } from 'kysely' import { Database } from '../../database' import { UserNotFoundError } from '../../util/errors' import { SignedInUser } from '../signed-in-user' import { PasswordSignInMethod } from './sign-in-method' export const MIN_PASSWORD_LENGTH = 8 export const MAX_PASSWORD_LENGTH = 255 export class UserAlreadyHasSignInMethodError extends Error {} export class SignInMethodNotFoundError extends Error {} export class WrongPasswordError extends Error {} export class PasswordTooWeakError extends Error {} export class PasswordTooLongError extends Error {} export async function addPasswordSignInMethod( trx: Transaction, userId: string, method: PasswordSignInMethod, ): Promise { const user = await userService.lockUserById(trx, userId) if (!user) { throw new UserNotFoundError() } if (user.email) { throw new UserAlreadyHasSignInMethodError() } await signInMethodRepository.insertPasswordSignInMethod(trx, { user_id: userId, password_hash: await encryptPassword(method.password), }) await userService.setUserEmail(trx, userId, method.email) } async function encryptPassword(password: string): Promise { if (password.length < MIN_PASSWORD_LENGTH) { throw new PasswordTooWeakError() } if (password.length > MAX_PASSWORD_LENGTH) { throw new PasswordTooLongError() } return encryptSecret(password) } async function encryptSecret(secret: string): Promise { const salt = crypto.randomBytes(16).toString('hex') return `${salt}:${await scrypt(secret, salt)}` } async function verifySecret(secret: string, hash: string): Promise { const [salt, secretHash] = hash.split(':') return (await scrypt(secret, salt)) === secretHash } async function scrypt(secret: string, salt: string): Promise { return new Promise((resolve, reject) => { crypto.scrypt( secret, salt, 64, { N: 16384, r: 8, p: 1 }, (err, secretHash) => { if (err) { return reject(err) } resolve(secretHash.toString('hex')) }, ) }) } export async function singInUsingPassword( trx: Transaction, method: PasswordSignInMethod, ): Promise { const user = await userService.lockUserByEmail(trx, method.email) if (!user) { throw new UserNotFoundError() } const signInMethod = await signInMethodRepository.findPasswordSignInMethod( trx, user.id, ) if (!signInMethod) { throw new SignInMethodNotFoundError() } if (!(await verifyPassword(method.password, signInMethod.password_hash))) { throw new WrongPasswordError() } const refreshToken = await authTokenService.createRefreshToken(trx, user.id) const authToken = await authTokenService.createAuthToken(trx, refreshToken) return { user, authToken, refreshToken, } } async function verifyPassword( password: string, hash: string, ): Promise { return verifySecret(password, hash) } ================================================ FILE: example/src/user/sign-in-method/sign-in-method.table.ts ================================================ import { Insertable, Selectable, Updateable } from 'kysely' export interface SignInMethodTable { user_id: string type: 'password' } export type SignInMethodRow = Selectable export type InsertableSignInMethodRow = Insertable export type UpdateableSignInMethodRow = Updateable ================================================ FILE: example/src/user/sign-in-method/sign-in-method.ts ================================================ import { ajv } from '../../util/ajv' export type SignInMethod = PasswordSignInMethod export interface PasswordSignInMethod { email: string password: string } export const validatePasswordSignInMethod = ajv.compile({ type: 'object', required: ['email', 'password'], properties: { email: { type: 'string', }, password: { type: 'string', }, }, }) ================================================ FILE: example/src/user/signed-in-user.ts ================================================ import { AuthToken } from '../authentication/auth-token' import { RefreshToken } from '../authentication/refresh-token' import { User } from './user' export interface SignedInUser { refreshToken: RefreshToken authToken: AuthToken user: User } ================================================ FILE: example/src/user/user.controller.ts ================================================ import * as userService from './user.service' import * as authenticationService from '../authentication/authentication.service' import { Router } from '../router' import { signInMethodController } from './sign-in-method/sign-in-method.controller' import { validateCreateAnonymousUserRequest } from './user' import { ControllerError } from '../util/errors' export function userController(router: Router): void { router.post('/api/v1/user', async (ctx) => { const { body } = ctx.request if (!validateCreateAnonymousUserRequest(body)) { throw new ControllerError(400, 'InvalidUser', 'invalid user') } const result = await ctx.db.transaction().execute(async (trx) => { return userService.createAnonymousUser(trx, body) }) ctx.status = 201 ctx.body = { user: result.user, authToken: result.authToken.authToken, refreshToken: result.refreshToken.refreshToken, } }) router.get( '/api/v1/user/:userId', authenticationService.authenticateUser, async (ctx) => { const { userId } = ctx.params const user = await userService.findUserById(ctx.db, userId) if (!user) { throw new ControllerError( 404, 'UserNotFound', `user with id ${userId} was not found`, ) } ctx.body = { user } }, ) signInMethodController(router) } ================================================ FILE: example/src/user/user.repository.ts ================================================ import { Kysely, Transaction } from 'kysely' import { Database } from '../database' import { InsertableUserRow, UserRow } from './user.table' export async function insertUser( db: Kysely, user: InsertableUserRow, ): Promise { const insertedUser = await db .insertInto('user') .values(user) .returningAll() .executeTakeFirstOrThrow() return insertedUser } export async function findUserById( db: Kysely, id: string, ): Promise { const user = await db .selectFrom('user') .where('user_id', '=', id) .selectAll('user') .executeTakeFirst() return user } export async function lockUserById( trx: Transaction, id: string, ): Promise { return lockUser(trx, 'user_id', id) } export async function lockUserByEmail( trx: Transaction, email: string, ): Promise { return lockUser(trx, 'email', email) } async function lockUser( trx: Transaction, column: 'user_id' | 'email', value: string, ): Promise { const user = await trx .selectFrom('user') .where(column, '=', value) .selectAll('user') .forUpdate() .executeTakeFirst() return user } export async function setUserEmail( db: Kysely, id: string, email: string, ): Promise { await db .updateTable('user') .where('user_id', '=', id) .set({ email }) .execute() } ================================================ FILE: example/src/user/user.service.ts ================================================ import * as userRepository from './user.repository' import * as authTokenService from '../authentication/auth-token.service' import { Kysely, Transaction } from 'kysely' import { Database } from '../database' import { SignedInUser } from './signed-in-user' import { CreateAnonymousUserRequest, User } from './user' import { UserRow } from './user.table' export async function createAnonymousUser( db: Kysely, request: CreateAnonymousUserRequest, ): Promise { const user = await userRepository.insertUser(db, { first_name: request.firstName, last_name: request.lastName, }) const refreshToken = await authTokenService.createRefreshToken( db, user.user_id, ) const authToken = await authTokenService.createAuthToken(db, refreshToken) return { refreshToken, authToken, user: userRowToUser(user), } } export async function findUserById( db: Kysely, userId: string, ): Promise { const userRow = await userRepository.findUserById(db, userId) if (userRow) { return userRowToUser(userRow) } } export async function lockUserById( trx: Transaction, id: string, ): Promise { const userRow = await userRepository.lockUserById(trx, id) if (userRow) { return userRowToUser(userRow) } } export async function lockUserByEmail( trx: Transaction, email: string, ): Promise { const userRow = await userRepository.lockUserByEmail(trx, email) if (userRow) { return userRowToUser(userRow) } } export async function setUserEmail( db: Kysely, userId: string, email: string, ): Promise { await userRepository.setUserEmail(db, userId, email) } export function userRowToUser(user: UserRow): User { return { id: user.user_id, firstName: user.first_name, lastName: user.last_name, email: user.email, } } ================================================ FILE: example/src/user/user.table.ts ================================================ import { Generated, Insertable, Selectable, Updateable } from 'kysely' export interface UserTable { user_id: Generated first_name: string | null last_name: string | null email: string | null created_at: Generated } export type UserRow = Selectable export type InsertableUserRow = Insertable export type UpdateableUserRow = Updateable ================================================ FILE: example/src/user/user.ts ================================================ import { ajv } from '../util/ajv' export interface User { id: string firstName: string | null lastName: string | null email: string | null } export interface CreateAnonymousUserRequest { firstName?: string lastName?: string } export const validateCreateAnonymousUserRequest = ajv.compile({ type: 'object', properties: { firstName: { type: 'string', }, lastName: { type: 'string', }, }, }) ================================================ FILE: example/src/util/ajv.ts ================================================ import Ajv from 'ajv' export const ajv = new Ajv() ================================================ FILE: example/src/util/errors.ts ================================================ export type AuthenticationErrors = | 'NoUserIdParameter' | 'InvalidAuthorizationHeader' | 'InvalidAuthToken' | 'ExpiredAuthToken' | 'UserMismatch' | 'UserOrRefreshTokenNotFound' export type UserApiErrors = 'InvalidUser' | 'UserNotFound' export type SignInMethodApiErrors = | 'InvalidSignInMethod' | 'UserAlreadyHasSignInMethod' | 'PasswordTooWeak' | 'PasswordTooLong' | 'InvalidCredentials' | 'InvalidRefreshToken' | 'RefreshTokenUserIdMismatch' export type ErrorCode = | 'UnknownError' | AuthenticationErrors | UserApiErrors | SignInMethodApiErrors export type ErrorStatus = 400 | 401 | 403 | 404 | 409 | 500 export class ControllerError extends Error { readonly status: ErrorStatus readonly code: ErrorCode readonly data?: any constructor( status: ErrorStatus, code: ErrorCode, message: string, data?: any, ) { super(message) this.status = status this.code = code this.data = data } toJSON() { return { error: { code: this.code, message: this.message }, } } } export class UserNotFoundError extends Error {} ================================================ FILE: example/src/util/object.ts ================================================ export function isObject(value: unknown): value is Record { return typeof value === 'object' && value !== null } ================================================ FILE: example/test/test-config.ts ================================================ import { ConnectionConfig } from 'pg' import { Config } from '../src/config' export interface TestConfig extends Config { readonly adminDatabase: ConnectionConfig } export const testConfig: TestConfig = { port: 3001, authTokenSecret: 'a498a5cf13a8194a2477f9284df34af3954fad3dc8459e343a', authTokenExpiryDuration: '2h', database: { host: 'localhost', database: 'kysely_koa_example_test', user: 'postgres', }, adminDatabase: { host: 'localhost', database: 'postgres', user: 'postgres', }, } ================================================ FILE: example/test/test-context.ts ================================================ import * as path from 'path' import axios from 'axios' import { promises as fs } from 'fs' import { FileMigrationProvider, Kysely, Migrator, PostgresDialect, sql, } from 'kysely' import { testConfig } from './test-config' import { App } from '../src/app' import { Database } from '../src/database' import { User } from '../src/user/user' import { Pool } from 'pg' export class TestContext { #app?: App request = axios.create({ baseURL: `http://localhost:${testConfig.port}`, validateStatus: () => true, }) get db(): Kysely { return this.#app!.db } before = async (): Promise => { const adminDb = new Kysely({ dialect: new PostgresDialect({ pool: new Pool(testConfig.adminDatabase), }), }) // Create our test database const { database } = testConfig.database await sql`drop database if exists ${sql.id(database!)}`.execute(adminDb) await sql`create database ${sql.id(database!)}`.execute(adminDb) await adminDb.destroy() // Now connect to the test database and run the migrations const db = new Kysely({ dialect: new PostgresDialect({ pool: new Pool(testConfig.database), }), }) const migrator = new Migrator({ db, provider: new FileMigrationProvider({ fs, path, migrationFolder: path.join(__dirname, '../src/migrations'), }), }) await migrator.migrateToLatest() await db.destroy() } after = async (): Promise => { // Nothing to do here at the moment } beforeEach = async (): Promise => { this.#app = new App(testConfig) // Clear the database await this.db.deleteFrom('user').execute() await this.#app.start() } afterEach = async (): Promise => { await this.#app?.stop() this.#app = undefined } createUser = async (): Promise<{ user: User authToken: string refreshToken: string }> => { const res = await this.request.post(`/api/v1/user`, { firstName: 'Test', lastName: 'Testerson', }) return res.data } } ================================================ FILE: example/test/user/user.test.ts ================================================ import { expect } from 'chai' import { TestContext } from '../test-context' import { User } from '../../src/user/user' import { AxiosResponse } from 'axios' const EMAIL = 'foo@bar.fake' const PASSWORD = '12345678' describe('user tests', () => { const ctx = new TestContext() before(ctx.before) beforeEach(ctx.beforeEach) after(ctx.after) afterEach(ctx.afterEach) it('should create an anonoymous user', async () => { const res = await ctx.request.post(`/api/v1/user`, { firstName: 'Anon', }) expect(res.status).to.equal(201) expect(res.data.user.firstName).to.equal('Anon') expect(res.data.user.lastName).to.equal(null) expect(res.data.user.email).to.equal(null) // The returned auth token should be usable. const getRes = await ctx.request.get<{ user: User }>( `/api/v1/user/${res.data.user.id}`, createAuthHeaders(res.data.authToken), ) expect(getRes.status).to.equal(200) expect(getRes.data.user).to.eql(res.data.user) }) it('should get user by id', async () => { const { user, authToken } = await ctx.createUser() const res = await ctx.request.get<{ user: User }>( `/api/v1/user/${user.id}`, createAuthHeaders(authToken), ) expect(res.status).to.equal(200) expect(res.data).to.eql({ user }) }) it('should add a password sign in method for a user', async () => { const { user, authToken } = await ctx.createUser() const res = await createPasswordSignInMethod(user.id, authToken) expect(res.status).to.equal(201) expect(res.data.success).to.equal(true) }) it('should sign in a user', async () => { const { user, authToken } = await ctx.createUser() await createPasswordSignInMethod(user.id, authToken) const res = await ctx.request.post(`/api/v1/user/sign-in`, { email: EMAIL, password: PASSWORD, }) expect(res.status).to.equal(200) // The returned auth token should be usable. const getRes = await ctx.request.get<{ user: User }>( `/api/v1/user/${res.data.user.id}`, createAuthHeaders(authToken), ) expect(getRes.status).to.equal(200) expect(getRes.data.user).to.eql(res.data.user) }) it('should fail to sign in user with the wrong password', async () => { const { user, authToken } = await ctx.createUser() await createPasswordSignInMethod(user.id, authToken) const res = await ctx.request.post(`/api/v1/user/sign-in`, { email: EMAIL, password: 'wrong password', }) expect(res.status).to.equal(401) expect(res.data).to.eql({ error: { code: 'InvalidCredentials', message: 'wrong email or password', }, }) // Only the one refresh token created for the anonymous user should exists. expect( await ctx.db .selectFrom('refresh_token') .select('refresh_token.user_id') .execute(), ).to.have.length(1) }) it('should sign out a user', async () => { const { user, authToken, refreshToken } = await ctx.createUser() const res = await ctx.request.post( `/api/v1/user/${user.id}/sign-out`, { refreshToken }, createAuthHeaders(authToken), ) expect(res.status).to.equal(200) // The auth token should no longer be usable. const getRes = await ctx.request.get( `/api/v1/user/${user.id}`, createAuthHeaders(authToken), ) expect(getRes.status).to.equal(404) expect(getRes.data.error.code).to.equal('UserOrRefreshTokenNotFound') }) function createAuthHeaders(authToken: string) { return { headers: { Authorization: `Bearer ${authToken}`, }, } } async function createPasswordSignInMethod( userId: string, authToken: string, ): Promise> { return await ctx.request.post<{ success: true }>( `/api/v1/user/${userId}/sign-in-methods`, { email: EMAIL, password: PASSWORD }, createAuthHeaders(authToken), ) } }) ================================================ FILE: example/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "target": "ESNext", "declaration": true, "outDir": "./dist", "strict": true, "noImplicitAny": true }, "include": ["src/**/*"] } ================================================ FILE: jsr.json ================================================ { "$schema": "https://jsr.io/schema/config-file.v1.json", "name": "@kysely/kysely", "version": "0.28.14", "license": "MIT", "exports": { ".": "./src/index.ts", "./helpers/mysql": "./src/helpers/mysql.ts", "./helpers/mssql": "./src/helpers/mssql.ts", "./helpers/postgres": "./src/helpers/postgres.ts", "./helpers/sqlite": "./src/helpers/sqlite.ts" }, "publish": { "include": [ "LICENSE", "README.md", "src/**/*.ts" ] } } ================================================ FILE: outdated-typescript.d.ts ================================================ import type { KyselyTypeError } from './dist/cjs/util/type-error' declare const Kysely: KyselyTypeError<'The installed TypeScript version is outdated and cannot guarantee type-safety with Kysely. Please upgrade to version 4.6 or newer.'> declare const RawBuilder: KyselyTypeError<'The installed TypeScript version is outdated and cannot guarantee type-safety with Kysely. Please upgrade to version 4.6 or newer.'> declare const sql: KyselyTypeError<'The installed TypeScript version is outdated and cannot guarantee type-safety with Kysely. Please upgrade to version 4.6 or newer.'> ================================================ FILE: package.json ================================================ { "name": "kysely", "version": "0.28.14", "description": "Type safe SQL query builder", "repository": { "type": "git", "url": "git://github.com/kysely-org/kysely.git" }, "homepage": "https://kysely.dev", "keywords": [ "query", "builder", "query builder", "sql", "typescript", "postgres", "postgresql", "mysql", "sqlite", "mssql", "ms sql server", "sql server", "type-safe", "type-safety", "plugins" ], "engines": { "node": ">=20.0.0" }, "main": "dist/cjs/index.js", "module": "dist/esm/index.js", "typesVersions": { "<4.6": { "*": [ "outdated-typescript.d.ts" ] } }, "files": [ "dist", "helpers", "outdated-typescript.d.ts" ], "exports": { ".": { "import": "./dist/esm/index.js", "require": "./dist/cjs/index.js", "default": "./dist/cjs/index.js" }, "./helpers/postgres": { "import": "./dist/esm/helpers/postgres.js", "require": "./dist/cjs/helpers/postgres.js", "default": "./dist/cjs/helpers/postgres.js" }, "./helpers/mysql": { "import": "./dist/esm/helpers/mysql.js", "require": "./dist/cjs/helpers/mysql.js", "default": "./dist/cjs/helpers/mysql.js" }, "./helpers/mssql": { "import": "./dist/esm/helpers/mssql.js", "require": "./dist/cjs/helpers/mssql.js", "default": "./dist/cjs/helpers/mssql.js" }, "./helpers/sqlite": { "import": "./dist/esm/helpers/sqlite.js", "require": "./dist/cjs/helpers/sqlite.js", "default": "./dist/cjs/helpers/sqlite.js" } }, "sideEffects": false, "scripts": { "clean": "rm -rf dist & rm -rf test/node/dist & rm -rf test/browser/bundle.js & rm -rf helpers", "bench:ts": "pnpm build && cd ./test/ts-benchmarks && node --experimental-strip-types ./index.ts", "test": "pnpm build && pnpm test:node:build && pnpm test:node:run && pnpm test:typings && pnpm test:esmimports && pnpm test:exports", "test:node:build": "tsc -p test/node", "test:node": "pnpm build && pnpm test:node:build && pnpm test:node:run", "test:node:run": "mocha --timeout 15000 test/node/dist/**/*.test.js", "test:browser:build": "rm -rf test/browser/bundle.js && esbuild test/browser/main.ts --bundle --outfile=test/browser/bundle.js", "test:browser": "pnpm build && pnpm test:browser:build && node test/browser/test.js", "test:bun": "pnpm build && bun link && cd test/bun && bun install && bun run test", "test:cloudflare-workers": "pnpm build && pnpm -r test --filter kysely-cloudflare-workers-test", "test:deno": "deno run --allow-env --allow-read --allow-net --no-lock test/deno/local.test.ts && deno run --allow-env --allow-read --allow-net --no-lock test/deno/cdn.test.ts", "test:typings": "tsd test/typings", "test:esmimports": "node scripts/check-esm-imports.js", "test:esbuild": "esbuild --bundle --platform=node --external:pg-native dist/esm/index.js --outfile=/dev/null", "test:exports": "attw --pack . && node scripts/check-exports.js", "test:jsdocs": "deno check --doc-only --no-lock --unstable-sloppy-imports --config=\"deno.check.json\" ./src", "test:outdatedts": "pnpm build && cd test/outdated-ts && pnpm i && pnpm test", "lint:deno": "deno lint --config=\"deno.lint.json\" ./src", "prettier": "prettier --write 'src/**/*.ts' 'test/**/*.ts'", "build": "pnpm clean && (pnpm build:esm & pnpm build:cjs) && pnpm script:module-fixup && pnpm script:copy-interface-doc", "build:esm": "tsc -p tsconfig.json && pnpm script:add-deno-type-references", "build:cjs": "tsc -p tsconfig-cjs.json", "script:module-fixup": "node scripts/module-fixup.js", "script:copy-interface-doc": "node scripts/copy-interface-documentation.js", "script:add-deno-type-references": "node scripts/add-deno-type-references.js", "script:align-versions": "node --experimental-strip-types scripts/align-versions.mts", "script:generate-site-examples": "node scripts/generate-site-examples.js", "script:exclude-test-files-for-backwards-compat": "node --experimental-strip-types scripts/exclude-test-files-for-backwards-compat.mts", "script:remove-global-augmentations": "node --experimental-strip-types scripts/remove-global-augmentations.mts", "prepublishOnly": "pnpm build && pnpm test:exports", "version": "pnpm script:align-versions && git add ." }, "author": "Sami Koskimäki ", "license": "MIT", "contributors": [ "Sami Koskimäki ", "Igal Klebanov " ], "devDependencies": { "@arethetypeswrong/cli": "^0.18.2", "@ark/attest": "^0.56.0", "@types/better-sqlite3": "^7.6.13", "@types/chai": "^5.2.3", "@types/chai-as-promised": "^8.0.2", "@types/mocha": "^10.0.10", "@types/node": "^25.5.0", "@types/pg": "^8.18.0", "@types/pg-cursor": "^2.7.2", "@types/semver": "^7.7.1", "@types/sinon": "^21.0.0", "better-sqlite3": "^12.8.0", "chai": "^6.2.2", "chai-as-promised": "^8.0.2", "esbuild": "^0.27.4", "jsr": "^0.14.3", "lodash": "^4.17.23", "mocha": "^11.7.5", "mysql2": "^3.20.0", "pathe": "^2.0.3", "pg": "^8.20.0", "pg-cursor": "^2.19.0", "pkg-types": "^2.3.0", "playwright": "^1.58.2", "prettier": "^3.8.1", "semver": "^7.7.4", "sinon": "^21.0.3", "std-env": "^4.0.0", "tarn": "^3.0.2", "tedious": "^19.2.1", "tsd": "^0.33.0", "typescript": "~5.9.3" }, "packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264" } ================================================ FILE: pnpm-workspace.yaml ================================================ packages: - './site' - './test/cloudflare-workers' # Allow (true) or disallow (false) script execution. # https://pnpm.io/settings#allowbuilds allowBuilds: better-sqlite3@12.8.0: true core-js: false # site dependency, useless scripts core-js-pure: false # site dependency, useless scripts esbuild@0.27.4: true sharp: false # wrangler dependency, useless scripts workerd@1.20260317.1: true # wrangler@4.75.0 dependency # All transitive dependencies must be resolved from a trusted source. # Packages from trusted sources are considered safer, as they are typically subject # to more reliable verification and scanning for malware and vulnerabilities. # https://pnpm.io/settings#blockexoticsubdeps blockExoticSubdeps: true # The minimum number of minutes that must pass after a version is published before # pnpm will install it. # https://pnpm.io/settings#minimumreleaseage minimumReleaseAge: 1440 # 1 day old in minutes. # Fail if a package's trust level has decreased compared to previous releases. # https://pnpm.io/settings#trustpolicy trustPolicy: no-downgrade # A list of package selectors that should be excluded from the trust policy check. # https://pnpm.io/settings#trustpolicyexclude trustPolicyExclude: - semver@5.7.2 || 6.3.1 # tsd@0.20.0 || @docusaurus/core@3.9.2 - https://socket.dev/npm/package/semver/overview/5.7.2 || https://socket.dev/npm/package/semver/overview/6.3.1 ================================================ FILE: scripts/add-deno-type-references.js ================================================ /** * This scripts adds a `/// ` directive * at the beginning of each ESM JavaScript file so that they work with * deno. */ const fs = require('fs') const path = require('path') const forEachFile = require('./util/for-each-file') const ESM_DIST_PATH = path.join(__dirname, '..', 'dist', 'esm') forEachFile(ESM_DIST_PATH, (filePath) => { if (filePath.endsWith('.js')) { const dTsFile = path.basename(filePath).replace(/\.js$/, '.d.ts') const content = fs.readFileSync(filePath, { encoding: 'utf-8' }) fs.writeFileSync( filePath, `/// \n${content}`, ) } }) ================================================ FILE: scripts/align-versions.mts ================================================ /** * This script aligns docs site package.json AND jsr versions with * Kysely's version so we use only the latest published version in the docs and JSR publish. */ import { writeFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' import { dirname, join } from 'pathe' import jsrJson from '../jsr.json' with { type: 'json' } import pkgJson from '../package.json' with { type: 'json' } import sitePkgJson from '../site/package.json' with { type: 'json' } const __dirname = dirname(fileURLToPath(import.meta.url)) const { version } = pkgJson writeFileSync( join(__dirname, '../site/package.json'), JSON.stringify({ ...sitePkgJson, version }, null, 2) + '\n', ) writeFileSync( join(__dirname, '../jsr.json'), JSON.stringify({ ...jsrJson, version }, null, 2) + '\n', ) ================================================ FILE: scripts/check-esm-imports.js ================================================ /** * This script goes through all source files and makes sure * imports end with '.js'. If they don't, the ESM version will * not work. ESM imports must have the full file name. */ const fs = require('fs') const path = require('path') const forEachFile = require('./util/for-each-file') let errorsFound = false function checkDir(dir) { forEachFile(dir, (filePath) => { let errorsFoundInFile = false if (filePath.endsWith('.ts')) { for (const row of readLines(filePath)) { if (isLocalImport(row) && !isDotJsImport(row)) { if (!errorsFoundInFile) { if (errorsFound) { console.log(' ') } console.log(`invalid imports in file ${filePath}`) errorsFoundInFile = true } console.log(row) errorsFound = true } } } }) } function readLines(filePath) { const data = fs.readFileSync(filePath).toString('utf-8') return data.split('\n') } function isLocalImport(row) { return /from ['"]\./.test(row) } function isDotJsImport(row) { return /.js['"][\n\t\s]*$/.test(row) } checkDir(path.join(__dirname, '..', 'src')) if (errorsFound) { console.log(' ') console.log('check-esm-imports.js failed!') process.exit(1) } ================================================ FILE: scripts/check-exports.js ================================================ /** * This script ensures all files in a path are exported in the index.ts file. * For now it only checks the operation-node folder, as we've had issues with * missing exports there. */ const fs = require('node:fs') const path = require('node:path') const forEachFile = require('./util/for-each-file') let errorsFound = false function checkExports(dir) { const indexFileContents = fs.readFileSync( path.join(__dirname, '..', 'src/index.ts'), 'utf-8', ) forEachFile(dir, (filePath) => { if (filePath.endsWith('.ts')) { const expectedExportPath = filePath.replace( /^.+\/src\/(.+)\.ts$/, "'./$1.js'", ) if (!indexFileContents.includes(expectedExportPath)) { console.log(`Missing export: ${expectedExportPath}`) errorsFound = true } } }) } checkExports(path.join(__dirname, '..', 'src/operation-node')) if (errorsFound) { console.log(' ') console.log('check-exports.js failed!') process.exit(1) } ================================================ FILE: scripts/copy-interface-documentation.js ================================================ /** * This script goes through all generated type definition files and copies * method/property documentation from interfaces to the implementing methods * IF the implementation doesn't have its own documentation. * * This is done for convenience: users can cmd-click method names and immediately * see the documentation. If we don't do this, users need to cmd-click the method * and then manually find the correct interface that contains the documentation. * * Hovering over a method/property works even without this script, but not all * people are happy reading docs from the small hovering window. */ const fs = require('fs') const path = require('path') const forEachFile = require('./util/for-each-file') const DIST_PATH = path.join(__dirname, '..', 'dist') const PROPERTY_REGEX = /^\s+(?:get )?(?:readonly )?(?:abstract )?(\w+)[\(:<]/ const OBJECT_REGEXES = [ /^(?:export )?declare (?:abstract )?class (\w+)/, /^(?:export )?interface (\w+)/, ] const GENERIC_ARGUMENTS_REGEX = /<[\w"'`,{}=|\[\] ]+>/g const JSDOC_START_REGEX = /^\s+\/\*\*/ const JSDOC_END_REGEX = /^\s+\*\// function main() { for (const distSubDir of ['cjs', 'esm']) { const subDirPath = path.join(DIST_PATH, distSubDir) const files = [] if (!fs.existsSync(subDirPath)) { continue } forEachFile(subDirPath, (filePath) => { if (filePath.endsWith('.d.ts')) { const file = { path: filePath, lines: readLines(filePath), } file.objects = parseObjects(file) if (file.objects.length > 0) { files.push(file) } } }) copyDocumentation(files) } } function readLines(filePath) { const data = fs.readFileSync(filePath).toString('utf-8') return data.split('\n') } /** * Parses all object (class, interface) declarations from the given * type declaration file. */ function parseObjects(file) { const objects = [] let lineIdx = 0 while (lineIdx < file.lines.length) { for (const regex of OBJECT_REGEXES) { const objectMatch = regex.exec(file.lines[lineIdx]) if (objectMatch) { const object = { name: objectMatch[1], lineIdx, implements: parseImplements(file.lines[lineIdx]), properties: [], } while (!file.lines[++lineIdx].startsWith('}')) { const propertyMatch = PROPERTY_REGEX.exec(file.lines[lineIdx]) if (propertyMatch) { const property = { name: propertyMatch[1], lineIdx: lineIdx, doc: parseDocumentation(file.lines, lineIdx), } Object.defineProperty(property, 'object', { value: object }) object.properties.push(property) } } Object.defineProperty(object, 'file', { value: file }) objects.push(object) continue } } ++lineIdx } return objects } /** * Given an object declaration line like * * export class A extends B implements C, D { * * or * * interface A extends B { * * extracts the names of the extended and implemented objects. * The first example would return ['B', 'C', 'D'] and the second * would return ['B']. */ function parseImplements(line) { if (!line.endsWith('{')) { console.warn( `skipping object declaration "${line}". Expected it to end with "{"'`, ) return [] } // Remove { from the end. line = line.substring(0, line.length - 1) // Strip generics. We need to do this in a loop to strip nested generics. while (line.includes('<')) { let strippedLine = line.replace(GENERIC_ARGUMENTS_REGEX, '') if (strippedLine === line) { console.warn(`unable to strip generics from "${line}"`) return [] } line = strippedLine } if (line.includes('extends')) { line = line.split('extends')[1].replace('implements', ',') } else if (line.includes('implements')) { line = line.split('implements')[1] } else { return [] } return line.split(',').map((it) => it.trim()) } /** * Given the line index of a property (method, getter) declaration * extracts the jsdoc comment above it if one exists. */ function parseDocumentation(lines, propertyLineIdx) { const doc = [] let lineIdx = propertyLineIdx - 1 if (JSDOC_END_REGEX.test(lines[lineIdx])) { doc.push(lines[lineIdx]) --lineIdx while (!JSDOC_START_REGEX.test(lines[lineIdx])) { doc.push(lines[lineIdx]) --lineIdx } doc.push(lines[lineIdx]) return doc.reverse() } return undefined } function copyDocumentation(files) { for (const file of files) { for (const object of file.objects) { const undocumentedProperties = new Set() // Only keep one undocumented property by same name. object.properties = object.properties.filter((it) => { if (it.doc) { return true } if (undocumentedProperties.has(it.name)) { return false } undocumentedProperties.add(it.name) return true }) } } for (const file of files) { for (const object of file.objects.slice().reverse()) { for (const property of object.properties.slice().reverse()) { if (!property.doc) { const docProperty = findDocProperty(files, object, property.name) if (docProperty) { file.lines.splice(property.lineIdx, 0, ...docProperty.doc) } } } } fs.writeFileSync(file.path, file.lines.join('\n')) } } function findDocProperty(files, object, propertyName) { for (const interfaceName of object.implements) { const interfaceObject = findObject(files, interfaceName) if (!interfaceObject) { continue } const interfaceProperty = interfaceObject.properties.find( (it) => it.name === propertyName, ) if (interfaceProperty?.doc) { return interfaceProperty } // Search all parents even if this object didn't have the // proprty. It may be defined and documented in an ancestor. const doc = findDocProperty(files, interfaceObject, propertyName) if (doc) { return doc } } return undefined } function findObject(files, objectName) { for (const file of files) { for (const object of file.objects) { if (object.name === objectName) { return object } } } return undefined } main() ================================================ FILE: scripts/exclude-test-files-for-backwards-compat.mts ================================================ import { unlink } from 'node:fs/promises' import { dirname, resolve } from 'pathe' import { readPackageJSON, readTSConfig, writeTSConfig } from 'pkg-types' import { lt } from 'semver' const { devDependencies } = await readPackageJSON() const typescriptVersion = devDependencies!.typescript.replace(/^[~^]/, '') console.log('typescriptVersion', typescriptVersion) if (lt(typescriptVersion, '5.2.0')) { const tsconfigPath = resolve( dirname(new URL(import.meta.url).pathname), '../test/node/tsconfig.json', ) const tsconfig = await readTSConfig(tsconfigPath) const updatedTSConfig = { ...tsconfig, // `using` keyword support was only added in 5.2.0 exclude: (tsconfig.exclude || []).concat('src/async-dispose.test.ts'), } await writeTSConfig(tsconfigPath, updatedTSConfig) console.log( `Updated ${tsconfigPath} to exclude async-dispose.test.ts`, JSON.stringify(updatedTSConfig, null, 2), ) } if (lt(typescriptVersion, '5.4.0')) { // inference from generics was only fixed in 5.4.0, before that you had to explicitly pass type arguments, and the inferred results were wider. const typingsTestFilePath = resolve( dirname(new URL(import.meta.url).pathname), '../test/typings/test-d/generic.test-d.ts', ) await unlink(typingsTestFilePath) console.log(`Deleted ${typingsTestFilePath}`) } ================================================ FILE: scripts/generate-site-examples.js ================================================ /** * This script goes through all generated type definitions and creates * playground examples in the site for all code examples that are * annotated using the ` * comment. */ const fs = require('fs') const path = require('path') const forEachFile = require('./util/for-each-file') const _ = require('lodash') const ESM_PATH = path.join(__dirname, '..', 'dist', 'esm') const SITE_EXAMPLE_PATH = path.join(__dirname, '..', 'site', 'docs', 'examples') const SITE_EXAMPLE_START_REGEX = // const CODE_BLOCK_START_REGEX = /\*\s*```/ const CODE_BLOCK_END_REGEX = /\*\s*```/ const COMMENT_LINE_REGEX = /\*\s*(.*)/ const CODE_LINE_REGEX = /\*(.*)/ const moreExamplesByCategory = { select: { 'select method': 'https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#select', 'selectAll method': 'https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#selectAll', 'selectFrom method': 'https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#selectFrom', }, where: { 'where method': 'https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#where', 'whereRef method': 'https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#whereRef', }, join: { 'innerJoin method': 'https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#innerJoin', 'leftJoin method': 'https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#leftJoin', 'rightJoin method': 'https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#rightJoin', 'fullJoin method': 'https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#fullJoin', }, insert: { 'values method': 'https://kysely-org.github.io/kysely-apidoc/classes/InsertQueryBuilder.html#values', 'onConflict method': 'https://kysely-org.github.io/kysely-apidoc/classes/InsertQueryBuilder.html#onConflict', 'returning method': 'https://kysely-org.github.io/kysely-apidoc/classes/InsertQueryBuilder.html#returning', 'insertInto method': 'https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#insertInto', }, update: { 'set method': 'https://kysely-org.github.io/kysely-apidoc/classes/UpdateQueryBuilder.html#set', 'returning method': 'https://kysely-org.github.io/kysely-apidoc/classes/UpdateQueryBuilder.html#returning', 'updateTable method': 'https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#updateTable', }, delete: { 'deleteFrom method': 'https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#deleteFrom', 'returning method': 'https://kysely-org.github.io/kysely-apidoc/classes/DeleteQueryBuilder.html#returning', }, merge: { 'mergeInto method': 'https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#mergeInto', 'using method': 'https://kysely-org.github.io/kysely-apidoc/classes/MergeQueryBuilder.html#using', 'whenMatched method': 'https://kysely-org.github.io/kysely-apidoc/classes/WheneableMergeQueryBuilder.html#whenMatched', 'thenUpdateSet method': 'https://kysely-org.github.io/kysely-apidoc/classes/MatchedThenableMergeQueryBuilder.html#thenUpdateSet', 'thenDelete method': 'https://kysely-org.github.io/kysely-apidoc/classes/MatchedThenableMergeQueryBuilder.html#thenDelete', 'thenDoNothing method': 'https://kysely-org.github.io/kysely-apidoc/classes/MatchedThenableMergeQueryBuilder.html#thenDoNothing', 'whenNotMatched method': 'https://kysely-org.github.io/kysely-apidoc/classes/WheneableMergeQueryBuilder.html#whenNotMatched', 'thenInsertValues method': 'https://kysely-org.github.io/kysely-apidoc/classes/NotMatchedThenableMergeQueryBuilder.html#thenInsertValues', }, transactions: { 'transaction method': 'https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#transaction', }, } function main() { deleteAllExamples() forEachFile(ESM_PATH, (filePath) => { if (!filePath.endsWith('.d.ts')) { return } const lines = readLines(filePath) const state = { filePath, /** @type {string | null} */ line: null, lineIndex: 0, annotation: null, inExample: false, inCodeBlock: false, commentLines: [], codeLines: [], } for (let l = 0; l < lines.length; ++l) { state.line = lines[l] state.lineIndex = l + 1 if (state.inExample) { if (state.inCodeBlock) { if (isCodeBlockEnd(state)) { writeSiteExample(state) exitExample(state) } else { addCodeLine(state) } } else if (isCodeBlockStart(state)) { enterCodeBlock(state) } else { addCommentLine(state) } } else if (isExampleStart(state)) { enterExample(state) } } }) overwritePlaygroundTypes() } function deleteAllExamples() { for (const category of fs.readdirSync(SITE_EXAMPLE_PATH)) { const folderPath = path.join(SITE_EXAMPLE_PATH, category) if (!fs.statSync(folderPath).isFile()) { for (const file of fs.readdirSync(folderPath)) { const filePath = path.join(folderPath, file) if (file.endsWith('.js') || file.endsWith('.mdx')) { fs.unlinkSync(filePath) } } } } } function readLines(filePath) { const data = fs.readFileSync(filePath).toString('utf-8') return data.split('\n') } function isCodeBlockEnd(state) { return CODE_BLOCK_END_REGEX.test(state.line) } function writeSiteExample(state) { const [, category, name, priority] = state.annotation const code = trimEmptyLines(state.codeLines).join('\n') const comment = trimEmptyLines(state.commentLines).join('\n') const codeVariable = _.camelCase(name) const fileName = `${priority.padStart(4, '0')}-${_.kebabCase(name)}` const folderPath = path.join(SITE_EXAMPLE_PATH, category) const filePath = path.join(folderPath, fileName) const codeFile = `export const ${codeVariable} = \`${deindent(code) .replaceAll('`', '\\`') .replaceAll('${', '\\${')}\`` const parts = [ deindent(` --- title: '${name}' --- # ${name} `), ] if (comment?.trim()) { parts.push(comment, '') } parts.push( deindent(` import { Playground } from '../../../src/components/Playground' import { ${codeVariable} } from './${fileName}'
`), ) const moreExamples = buildMoreExamplesMarkdown(category) if (moreExamples?.trim()) { parts.push(moreExamples) } const exampleFile = parts.join('\n') if (!fs.existsSync(folderPath)) { fs.mkdirSync(folderPath, { recursive: true }) fs.writeFileSync( path.join(folderPath, '_category_.json'), `{ "label": "${_.startCase(category)}", "position": 0, // TODO: set the position of the category. "link": { "type": "generated-index", "description": "Short and simple examples of using the ${category} functionality." // TODO: review this. } }`, ) } fs.writeFileSync(filePath + '.js', codeFile, {}) fs.writeFileSync(filePath + '.mdx', exampleFile) } function buildMoreExamplesMarkdown(category) { const links = moreExamplesByCategory[category] if (!links) { return undefined } const lines = [ ':::info[More examples]', 'The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/),', 'but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always', 'just one hover away!', '', 'For example, check out these sections:', ] for (const linkName of Object.keys(links)) { lines.push(` - [${linkName}](${links[linkName]})`) } lines.push(':::\n') return lines.join('\n') } function exitExample(state) { state.annotation = null state.inExample = false state.inCodeBlock = false state.commentLines = [] state.codeLines = [] } function addCodeLine(state) { const code = CODE_LINE_REGEX.exec(state.line) if (!code) { console.error( `found invalid code block in a site example in ${state.filePath}:${state.lineIndex}`, ) process.exit(1) } state.codeLines.push(code[1]) } function isCodeBlockStart(state) { return CODE_BLOCK_START_REGEX.test(state.line) } function enterCodeBlock(state) { state.inCodeBlock = true } function addCommentLine(state) { const comment = COMMENT_LINE_REGEX.exec(state.line) if (!comment) { console.error( `found invalid comment in a site example in ${state.filePath}:${state.lineIndex}`, ) process.exit(1) } state.commentLines.push(comment[1]) } function isExampleStart(state) { return SITE_EXAMPLE_START_REGEX.test(state.line) } function enterExample(state) { state.annotation = SITE_EXAMPLE_ANNOTATION_REGEX.exec(state.line) if (!state.annotation) { console.error( `found invalid site example annotation in ${state.filePath}:${state.lineIndex}`, ) process.exit(1) } state.inExample = true } function deindent(str) { let lines = str.split('\n') // Remove empty lines from the beginning. while (lines[0].trim().length === 0) { lines = lines.slice(1) } let ws = Number.MAX_SAFE_INTEGER for (const line of lines) { if (line.trim().length > 0) { const [, wsExec] = /^(\s*)/.exec(line) || [] if (wsExec != null && wsExec.length < ws) { ws = wsExec.length } } } return lines.map((line) => line.substring(ws)).join('\n') } function trimEmptyLines(lines) { while (lines.length && lines[0].trim().length === 0) { lines = lines.slice(1) } while (lines.length && lines[lines.length - 1].trim().length === 0) { lines = lines.slice(0, lines.length - 1) } return lines } function overwritePlaygroundTypes() { const jsdocsTestTypes = fs.readFileSync( path.join(__dirname, '../deno.check.d.ts'), { encoding: 'utf-8' }, ) const playgroundTypes = `// THIS FILE IS GENERATED BY \`${path.relative('.', __filename)}\` // DO NOT EDIT THIS FILE DIRECTLY export const GENERATED_PLAYGROUND_EXAMPLE_TYPES = \`${jsdocsTestTypes .replace(/['"`].*dist\/.*['"`]/g, "'kysely'") .replaceAll('`', '\\`')}\`;` fs.writeFileSync( path.join(__dirname, '../site/src/components/playground-example-types.ts'), playgroundTypes, { encoding: 'utf-8' }, ) } main() ================================================ FILE: scripts/module-fixup.js ================================================ /** * Adds a package.json file to both commonjs and ESM distribution * folders with a correct module type. This is needed in order to * be able to export both commonjs and ESM versions. * * This script also creates a dummy files for all `exports` in the * package.json to make CJS happy. */ const fs = require('fs') const path = require('path') const package = require('../package.json') const ROOT_PATH = path.join(__dirname, '..') const DIST_PATH = path.join(ROOT_PATH, 'dist') for (const [folder, type] of [ ['cjs', 'commonjs'], ['esm', 'module'], ]) { fs.writeFileSync( path.join(DIST_PATH, folder, 'package.json'), JSON.stringify({ type, sideEffects: false }), ) } for (const ex of Object.keys(package.exports)) { if (ex === '.') { continue } const [, ...folders] = ex.split('/') const fileName = folders.pop() const [, ...targetFolders] = package.exports[ex].require.split('/') const targetFileName = targetFolders.pop() const target = path.posix.relative( path.posix.join(ROOT_PATH, ...folders), path.posix.join(ROOT_PATH, ...targetFolders, targetFileName), ) fs.mkdirSync(path.join(ROOT_PATH, ...folders), { recursive: true, }) fs.writeFileSync( path.join(ROOT_PATH, ...folders, fileName + '.js'), `module.exports = require('${target}')`, ) fs.writeFileSync( path.join(ROOT_PATH, ...folders, fileName + '.d.ts'), `export * from '${target}'`, ) } ================================================ FILE: scripts/remove-global-augmentations.mts ================================================ /** * This script removes global augmentations so we could publish to JSR. * https://github.com/denoland/deno/issues/23427 */ import { readFileSync, writeFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' import { dirname, join } from 'pathe' import ts from 'typescript' const __dirname = dirname(fileURLToPath(import.meta.url)) const kyselyTSPath = join(__dirname, '../src/kysely.ts') const sourceCode = readFileSync(kyselyTSPath, 'utf8') const sourceFile = ts.createSourceFile( kyselyTSPath, sourceCode, ts.ScriptTarget.Latest, true, ) const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }) const nodesToRemove = new Set() function visit(node: ts.Node) { if (ts.isModuleDeclaration(node) && node.name.text === 'global') { return nodesToRemove.add(node) } ts.forEachChild(node, visit) } visit(sourceFile) const newStatements = sourceFile.statements.filter( (stmt) => !nodesToRemove.has(stmt), ) const newSourceFile = ts.factory.updateSourceFile(sourceFile, newStatements) const result = printer.printFile(newSourceFile) writeFileSync(kyselyTSPath, result, 'utf8') ================================================ FILE: scripts/tsconfig.json ================================================ { "extends": "../tsconfig-base.json", "compilerOptions": { "allowSyntheticDefaultImports": true, "module": "ESNext", "resolveJsonModule": true }, "include": ["./"] } ================================================ FILE: scripts/util/for-each-file.js ================================================ const fs = require('fs') const path = require('path') function forEachFile(dir, callback) { const files = fs.readdirSync(dir).filter((it) => it !== '.' && it !== '..') for (const file of files) { const filePath = path.join(dir, file) if (isDir(filePath)) { forEachFile(filePath, callback) } else { callback(filePath) } } } function isDir(file) { return fs.lstatSync(file).isDirectory() } module.exports = forEachFile ================================================ FILE: site/.gitignore ================================================ # Dependencies /node_modules # Production /build # Generated files .docusaurus .cache-loader # Misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* .vercel ================================================ FILE: site/babel.config.js ================================================ module.exports = { presets: [require.resolve('@docusaurus/core/lib/babel/preset')], } ================================================ FILE: site/docs/dialects.md ================================================ # Dialects A dialect is the glue between Kysely and the underlying database engine. Check the [API docs](https://kysely-org.github.io/kysely-apidoc/interfaces/Dialect.html) to learn how to build your own. ## Core dialects | Dialect | Link | | --- | --- | | PostgreSQL | https://kysely-org.github.io/kysely-apidoc/classes/PostgresDialect.html | | MySQL | https://kysely-org.github.io/kysely-apidoc/classes/MysqlDialect.html | | Microsoft SQL Server (MSSQL) | https://kysely-org.github.io/kysely-apidoc/classes/MssqlDialect.html | | SQLite | https://kysely-org.github.io/kysely-apidoc/classes/SqliteDialect.html | ## Organization dialects | Dialect | Link | | --- | --- | | Postgres.js | https://github.com/kysely-org/kysely-postgres-js | | SingleStore Data API | https://github.com/kysely-org/kysely-singlestore | ## Community dialects | Dialect | Link | |-------------------------------|-----------------------------------------------------------------------------| | PlanetScale Serverless Driver | https://github.com/depot/kysely-planetscale | | Cloudflare D1 | https://github.com/aidenwallis/kysely-d1 | | Cloudflare Durable Objects | https://github.com/benallfree/kysely-do | | AWS RDS Data API | https://github.com/serverless-stack/kysely-data-api | | SurrealDB | https://github.com/igalklebanov/kysely-surrealdb | | Neon | https://github.com/seveibar/kysely-neon | | Xata | https://github.com/xataio/client-ts/tree/main/packages/plugin-client-kysely | | AWS S3 Select | https://github.com/igalklebanov/kysely-s3-select | | libSQL/sqld | https://github.com/libsql/kysely-libsql | | Fetch driver | https://github.com/andersgee/kysely-fetch-driver | | SQLite WASM | https://github.com/DallasHoff/sqlocal | | Deno SQLite | https://gitlab.com/soapbox-pub/kysely-deno-sqlite | | Node SQLite | https://github.com/wolfie/kysely-node-native-sqlite | | TiDB Cloud Serverless Driver | https://github.com/tidbcloud/kysely | | Capacitor SQLite Kysely | https://github.com/DawidWetzler/capacitor-sqlite-kysely | | BigQuery | https://github.com/maktouch/kysely-bigquery | | Clickhouse | https://github.com/founderpathcom/kysely-clickhouse | | PGLite | https://github.com/czeidler/kysely-pglite-dialect | | Oracle | https://github.com/griffiths-waite/kysely-oracledb | | Firebird | https://github.com/benkoppe/kysely-firebird | | MariaDB | https://github.com/awaludinar/kysely-mariadb | ================================================ FILE: site/docs/examples/_category_.json ================================================ { "label": "Examples", "position": 5, "link": { "type": "generated-index", "description": "Short and simple examples of how to use Kysely to achieve common tasks." } } ================================================ FILE: site/docs/examples/cte/0010-simple-selects.js ================================================ export const simpleSelects = `const result = await db // Create a CTE called \`jennifers\` that selects all // persons named 'Jennifer'. .with('jennifers', (db) => db .selectFrom('person') .where('first_name', '=', 'Jennifer') .select(['id', 'age']) ) // Select all rows from the \`jennifers\` CTE and // further filter it. .with('adult_jennifers', (db) => db .selectFrom('jennifers') .where('age', '>', 18) .select(['id', 'age']) ) // Finally select all adult jennifers that are // also younger than 60. .selectFrom('adult_jennifers') .where('age', '<', 60) .selectAll() .execute()` ================================================ FILE: site/docs/examples/cte/0010-simple-selects.mdx ================================================ --- title: 'Simple selects' --- # Simple selects Common table expressions (CTE) are a great way to modularize complex queries. Essentially they allow you to run multiple separate queries within a single roundtrip to the DB. Since CTEs are a part of the main query, query optimizers inside DB engines are able to optimize the overall query. For example, postgres is able to inline the CTEs inside the using queries if it decides it's faster. import { Playground } from '../../../src/components/Playground' import { simpleSelects } from './0010-simple-selects'
================================================ FILE: site/docs/examples/cte/0020-inserts-updates-and-deletions.js ================================================ export const insertsUpdatesAndDeletions = `const result = await db .with('new_person', (db) => db .insertInto('person') .values({ first_name: 'Jennifer', age: 35, }) .returning('id') ) .with('new_pet', (db) => db .insertInto('pet') .values({ name: 'Doggo', species: 'dog', is_favorite: true, // Use the id of the person we just inserted. owner_id: db .selectFrom('new_person') .select('id') }) .returning('id') ) .selectFrom(['new_person', 'new_pet']) .select([ 'new_person.id as person_id', 'new_pet.id as pet_id' ]) .execute()` ================================================ FILE: site/docs/examples/cte/0020-inserts-updates-and-deletions.mdx ================================================ --- title: 'Inserts, updates and deletions' --- # Inserts, updates and deletions Some databases like postgres also allow you to run other queries than selects in CTEs. On these databases CTEs are extremely powerful: import { Playground } from '../../../src/components/Playground' import { insertsUpdatesAndDeletions } from './0020-inserts-updates-and-deletions'
================================================ FILE: site/docs/examples/cte/_category_.json ================================================ { "label": "CTE", "position": 9, "link": { "type": "generated-index", "description": "Short and simple examples of how to use Common Table Expressions (CTE) in queries." } } ================================================ FILE: site/docs/examples/delete/0010-single-row.js ================================================ export const singleRow = `const result = await db .deleteFrom('person') .where('person.id', '=', 1) .executeTakeFirst() console.log(result.numDeletedRows)` ================================================ FILE: site/docs/examples/delete/0010-single-row.mdx ================================================ --- title: 'Single row' --- # Single row Delete a single row: import { Playground } from '../../../src/components/Playground' import { singleRow } from './0010-single-row'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [deleteFrom method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#deleteFrom) - [returning method](https://kysely-org.github.io/kysely-apidoc/classes/DeleteQueryBuilder.html#returning) ::: ================================================ FILE: site/docs/examples/delete/_category_.json ================================================ { "label": "DELETE", "position": 6, "link": { "type": "generated-index", "description": "Short and simple examples of how to write DELETE queries." } } ================================================ FILE: site/docs/examples/insert/0010-single-row.js ================================================ export const singleRow = `const result = await db .insertInto('person') .values({ first_name: 'Jennifer', last_name: 'Aniston', age: 40 }) .executeTakeFirst() // \`insertId\` is only available on dialects that // automatically return the id of the inserted row // such as MySQL and SQLite. On PostgreSQL, for example, // you need to add a \`returning\` clause to the query to // get anything out. See the "returning data" example. console.log(result.insertId)` ================================================ FILE: site/docs/examples/insert/0010-single-row.mdx ================================================ --- title: 'Single row' --- # Single row Insert a single row: import { Playground } from '../../../src/components/Playground' import { singleRow } from './0010-single-row'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [values method](https://kysely-org.github.io/kysely-apidoc/classes/InsertQueryBuilder.html#values) - [onConflict method](https://kysely-org.github.io/kysely-apidoc/classes/InsertQueryBuilder.html#onConflict) - [returning method](https://kysely-org.github.io/kysely-apidoc/classes/InsertQueryBuilder.html#returning) - [insertInto method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#insertInto) ::: ================================================ FILE: site/docs/examples/insert/0020-multiple-rows.js ================================================ export const multipleRows = `await db .insertInto('person') .values([{ first_name: 'Jennifer', last_name: 'Aniston', age: 40, }, { first_name: 'Arnold', last_name: 'Schwarzenegger', age: 70, }]) .execute()` ================================================ FILE: site/docs/examples/insert/0020-multiple-rows.mdx ================================================ --- title: 'Multiple rows' --- # Multiple rows On dialects that support it (for example PostgreSQL) you can insert multiple rows by providing an array. Note that the return value is once again very dialect-specific. Some databases may only return the id of the *last* inserted row and some return nothing at all unless you call `returning`. import { Playground } from '../../../src/components/Playground' import { multipleRows } from './0020-multiple-rows'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [values method](https://kysely-org.github.io/kysely-apidoc/classes/InsertQueryBuilder.html#values) - [onConflict method](https://kysely-org.github.io/kysely-apidoc/classes/InsertQueryBuilder.html#onConflict) - [returning method](https://kysely-org.github.io/kysely-apidoc/classes/InsertQueryBuilder.html#returning) - [insertInto method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#insertInto) ::: ================================================ FILE: site/docs/examples/insert/0030-returning-data.js ================================================ export const returningData = `const result = await db .insertInto('person') .values({ first_name: 'Jennifer', last_name: 'Aniston', age: 40, }) .returning(['id', 'first_name as name']) .executeTakeFirstOrThrow()` ================================================ FILE: site/docs/examples/insert/0030-returning-data.mdx ================================================ --- title: 'Returning data' --- # Returning data On supported dialects like PostgreSQL you need to chain `returning` to the query to get the inserted row's columns (or any other expression) as the return value. `returning` works just like `select`. Refer to `select` method's examples and documentation for more info. import { Playground } from '../../../src/components/Playground' import { returningData } from './0030-returning-data'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [values method](https://kysely-org.github.io/kysely-apidoc/classes/InsertQueryBuilder.html#values) - [onConflict method](https://kysely-org.github.io/kysely-apidoc/classes/InsertQueryBuilder.html#onConflict) - [returning method](https://kysely-org.github.io/kysely-apidoc/classes/InsertQueryBuilder.html#returning) - [insertInto method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#insertInto) ::: ================================================ FILE: site/docs/examples/insert/0040-complex-values.js ================================================ export const complexValues = `import { sql } from 'kysely' const ani = "Ani" const ston = "ston" const result = await db .insertInto('person') .values(({ ref, selectFrom, fn }) => ({ first_name: 'Jennifer', last_name: sql\`concat(\${ani}, \${ston})\`, middle_name: ref('first_name'), age: selectFrom('person') .select(fn.avg('age').as('avg_age')), })) .executeTakeFirst()` ================================================ FILE: site/docs/examples/insert/0040-complex-values.mdx ================================================ --- title: 'Complex values' --- # Complex values In addition to primitives, the values can also be arbitrary expressions. You can build the expressions by using a callback and calling the methods on the expression builder passed to it: import { Playground } from '../../../src/components/Playground' import { complexValues } from './0040-complex-values'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [values method](https://kysely-org.github.io/kysely-apidoc/classes/InsertQueryBuilder.html#values) - [onConflict method](https://kysely-org.github.io/kysely-apidoc/classes/InsertQueryBuilder.html#onConflict) - [returning method](https://kysely-org.github.io/kysely-apidoc/classes/InsertQueryBuilder.html#returning) - [insertInto method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#insertInto) ::: ================================================ FILE: site/docs/examples/insert/0050-insert-subquery.js ================================================ export const insertSubquery = `const result = await db.insertInto('person') .columns(['first_name', 'last_name', 'age']) .expression((eb) => eb .selectFrom('pet') .select((eb) => [ 'pet.name', eb.val('Petson').as('last_name'), eb.lit(7).as('age'), ]) ) .execute()` ================================================ FILE: site/docs/examples/insert/0050-insert-subquery.mdx ================================================ --- title: 'Insert subquery' --- # Insert subquery You can create an `INSERT INTO SELECT FROM` query using the `expression` method. This API doesn't follow our WYSIWYG principles and might be a bit difficult to remember. The reasons for this design stem from implementation difficulties. import { Playground } from '../../../src/components/Playground' import { insertSubquery } from './0050-insert-subquery'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [values method](https://kysely-org.github.io/kysely-apidoc/classes/InsertQueryBuilder.html#values) - [onConflict method](https://kysely-org.github.io/kysely-apidoc/classes/InsertQueryBuilder.html#onConflict) - [returning method](https://kysely-org.github.io/kysely-apidoc/classes/InsertQueryBuilder.html#returning) - [insertInto method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#insertInto) ::: ================================================ FILE: site/docs/examples/insert/_category_.json ================================================ { "label": "INSERT", "position": 4, "link": { "type": "generated-index", "description": "Short and simple examples of how to write INSERT queries." } } ================================================ FILE: site/docs/examples/join/0010-simple-inner-join.js ================================================ export const simpleInnerJoin = `const result = await db .selectFrom('person') .innerJoin('pet', 'pet.owner_id', 'person.id') // \`select\` needs to come after the call to \`innerJoin\` so // that you can select from the joined table. .select(['person.id', 'pet.name as pet_name']) .execute()` ================================================ FILE: site/docs/examples/join/0010-simple-inner-join.mdx ================================================ --- title: 'Simple inner join' --- # Simple inner join Simple `inner join`s can be done by providing a table name and two columns to join: import { Playground } from '../../../src/components/Playground' import { simpleInnerJoin } from './0010-simple-inner-join'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [innerJoin method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#innerJoin) - [leftJoin method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#leftJoin) - [rightJoin method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#rightJoin) - [fullJoin method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#fullJoin) ::: ================================================ FILE: site/docs/examples/join/0020-aliased-inner-join.js ================================================ export const aliasedInnerJoin = `await db.selectFrom('person') .innerJoin('pet as p', 'p.owner_id', 'person.id') .where('p.name', '=', 'Doggo') .selectAll() .execute()` ================================================ FILE: site/docs/examples/join/0020-aliased-inner-join.mdx ================================================ --- title: 'Aliased inner join' --- # Aliased inner join You can give an alias for the joined table like this: import { Playground } from '../../../src/components/Playground' import { aliasedInnerJoin } from './0020-aliased-inner-join'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [innerJoin method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#innerJoin) - [leftJoin method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#leftJoin) - [rightJoin method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#rightJoin) - [fullJoin method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#fullJoin) ::: ================================================ FILE: site/docs/examples/join/0030-complex-join.js ================================================ export const complexJoin = `await db.selectFrom('person') .innerJoin( 'pet', (join) => join .onRef('pet.owner_id', '=', 'person.id') .on('pet.name', '=', 'Doggo') .on((eb) => eb.or([ eb('person.age', '>', 18), eb('person.age', '<', 100) ])) ) .selectAll() .execute()` ================================================ FILE: site/docs/examples/join/0030-complex-join.mdx ================================================ --- title: 'Complex join' --- # Complex join You can provide a function as the second argument to get a join builder for creating more complex joins. The join builder has a bunch of `on*` methods for building the `on` clause of the join. There's basically an equivalent for every `where` method (`on`, `onRef` etc.). You can do all the same things with the `on` method that you can with the corresponding `where` method (like [OR expressions for example](https://kysely.dev/docs/examples/WHERE/or-where)). See the `where` method documentation for more examples. import { Playground } from '../../../src/components/Playground' import { complexJoin } from './0030-complex-join'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [innerJoin method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#innerJoin) - [leftJoin method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#leftJoin) - [rightJoin method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#rightJoin) - [fullJoin method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#fullJoin) ::: ================================================ FILE: site/docs/examples/join/0040-subquery-join.js ================================================ export const subqueryJoin = `const result = await db.selectFrom('person') .innerJoin( (eb) => eb .selectFrom('pet') .select(['owner_id as owner', 'name']) .where('name', '=', 'Doggo') .as('doggos'), (join) => join .onRef('doggos.owner', '=', 'person.id'), ) .selectAll('doggos') .execute()` ================================================ FILE: site/docs/examples/join/0040-subquery-join.mdx ================================================ --- title: 'Subquery join' --- # Subquery join You can join a subquery by providing two callbacks: import { Playground } from '../../../src/components/Playground' import { subqueryJoin } from './0040-subquery-join'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [innerJoin method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#innerJoin) - [leftJoin method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#leftJoin) - [rightJoin method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#rightJoin) - [fullJoin method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#fullJoin) ::: ================================================ FILE: site/docs/examples/join/_category_.json ================================================ { "label": "JOIN", "position": 3, "link": { "type": "generated-index", "description": "Examples of queries that use JOINs." } } ================================================ FILE: site/docs/examples/merge/0010-source-row-existence.js ================================================ export const sourceRowExistence = `const result = await db .mergeInto('person as target') .using('pet as source', 'source.owner_id', 'target.id') .whenMatchedAnd('target.has_pets', '!=', 'Y') .thenUpdateSet({ has_pets: 'Y' }) .whenNotMatchedBySourceAnd('target.has_pets', '=', 'Y') .thenUpdateSet({ has_pets: 'N' }) .executeTakeFirstOrThrow() console.log(result.numChangedRows)` ================================================ FILE: site/docs/examples/merge/0010-source-row-existence.mdx ================================================ --- title: 'Source row existence' --- # Source row existence Update a target column based on the existence of a source row: import { Playground } from '../../../src/components/Playground' import { sourceRowExistence } from './0010-source-row-existence'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [mergeInto method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#mergeInto) - [using method](https://kysely-org.github.io/kysely-apidoc/classes/MergeQueryBuilder.html#using) - [whenMatched method](https://kysely-org.github.io/kysely-apidoc/classes/WheneableMergeQueryBuilder.html#whenMatched) - [thenUpdateSet method](https://kysely-org.github.io/kysely-apidoc/classes/MatchedThenableMergeQueryBuilder.html#thenUpdateSet) - [thenDelete method](https://kysely-org.github.io/kysely-apidoc/classes/MatchedThenableMergeQueryBuilder.html#thenDelete) - [thenDoNothing method](https://kysely-org.github.io/kysely-apidoc/classes/MatchedThenableMergeQueryBuilder.html#thenDoNothing) - [whenNotMatched method](https://kysely-org.github.io/kysely-apidoc/classes/WheneableMergeQueryBuilder.html#whenNotMatched) - [thenInsertValues method](https://kysely-org.github.io/kysely-apidoc/classes/NotMatchedThenableMergeQueryBuilder.html#thenInsertValues) ::: ================================================ FILE: site/docs/examples/merge/0020-temporary-changes-table.js ================================================ export const temporaryChangesTable = `const result = await db .mergeInto('wine as target') .using( 'wine_stock_change as source', 'source.wine_name', 'target.name', ) .whenNotMatchedAnd('source.stock_delta', '>', 0) .thenInsertValues(({ ref }) => ({ name: ref('source.wine_name'), stock: ref('source.stock_delta'), })) .whenMatchedAnd( (eb) => eb('target.stock', '+', eb.ref('source.stock_delta')), '>', 0, ) .thenUpdateSet('stock', (eb) => eb('target.stock', '+', eb.ref('source.stock_delta')), ) .whenMatched() .thenDelete() .executeTakeFirstOrThrow()` ================================================ FILE: site/docs/examples/merge/0020-temporary-changes-table.mdx ================================================ --- title: 'Temporary changes table' --- # Temporary changes table Merge new entries from a temporary changes table: import { Playground } from '../../../src/components/Playground' import { temporaryChangesTable } from './0020-temporary-changes-table'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [mergeInto method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#mergeInto) - [using method](https://kysely-org.github.io/kysely-apidoc/classes/MergeQueryBuilder.html#using) - [whenMatched method](https://kysely-org.github.io/kysely-apidoc/classes/WheneableMergeQueryBuilder.html#whenMatched) - [thenUpdateSet method](https://kysely-org.github.io/kysely-apidoc/classes/MatchedThenableMergeQueryBuilder.html#thenUpdateSet) - [thenDelete method](https://kysely-org.github.io/kysely-apidoc/classes/MatchedThenableMergeQueryBuilder.html#thenDelete) - [thenDoNothing method](https://kysely-org.github.io/kysely-apidoc/classes/MatchedThenableMergeQueryBuilder.html#thenDoNothing) - [whenNotMatched method](https://kysely-org.github.io/kysely-apidoc/classes/WheneableMergeQueryBuilder.html#whenNotMatched) - [thenInsertValues method](https://kysely-org.github.io/kysely-apidoc/classes/NotMatchedThenableMergeQueryBuilder.html#thenInsertValues) ::: ================================================ FILE: site/docs/examples/merge/_category_.json ================================================ { "label": "MERGE", "position": 7, "link": { "type": "generated-index", "description": "Short and simple examples of how to write MERGE queries." } } ================================================ FILE: site/docs/examples/select/0010-a-single-column.js ================================================ export const aSingleColumn = `const persons = await db .selectFrom('person') .select('id') .where('first_name', '=', 'Arnold') .execute()` ================================================ FILE: site/docs/examples/select/0010-a-single-column.mdx ================================================ --- title: 'A single column' --- # A single column Select a single column: import { Playground } from '../../../src/components/Playground' import { aSingleColumn } from './0010-a-single-column'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [select method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#select) - [selectAll method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#selectAll) - [selectFrom method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#selectFrom) ::: ================================================ FILE: site/docs/examples/select/0020-column-with-a-table.js ================================================ export const columnWithATable = `const persons = await db .selectFrom(['person', 'pet']) .select('person.id') .execute()` ================================================ FILE: site/docs/examples/select/0020-column-with-a-table.mdx ================================================ --- title: 'Column with a table' --- # Column with a table Select a single column and specify a table: import { Playground } from '../../../src/components/Playground' import { columnWithATable } from './0020-column-with-a-table'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [select method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#select) - [selectAll method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#selectAll) - [selectFrom method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#selectFrom) ::: ================================================ FILE: site/docs/examples/select/0030-multiple-columns.js ================================================ export const multipleColumns = `const persons = await db .selectFrom('person') .select(['person.id', 'first_name']) .execute()` ================================================ FILE: site/docs/examples/select/0030-multiple-columns.mdx ================================================ --- title: 'Multiple columns' --- # Multiple columns Select multiple columns: import { Playground } from '../../../src/components/Playground' import { multipleColumns } from './0030-multiple-columns'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [select method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#select) - [selectAll method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#selectAll) - [selectFrom method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#selectFrom) ::: ================================================ FILE: site/docs/examples/select/0040-aliases.js ================================================ export const aliases = `const persons = await db .selectFrom('person as p') .select([ 'first_name as fn', 'p.last_name as ln' ]) .execute()` ================================================ FILE: site/docs/examples/select/0040-aliases.mdx ================================================ --- title: 'Aliases' --- # Aliases You can give an alias for selections and tables by appending `as the_alias` to the name: import { Playground } from '../../../src/components/Playground' import { aliases } from './0040-aliases'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [select method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#select) - [selectAll method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#selectAll) - [selectFrom method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#selectFrom) ::: ================================================ FILE: site/docs/examples/select/0050-complex-selections.js ================================================ export const complexSelections = `import { sql } from 'kysely' const persons = await db.selectFrom('person') .select(({ eb, selectFrom, or, val, lit }) => [ // Select a correlated subquery selectFrom('pet') .whereRef('person.id', '=', 'pet.owner_id') .select('pet.name') .orderBy('pet.name') .limit(1) .as('first_pet_name'), // Build and select an expression using // the expression builder or([ eb('first_name', '=', 'Jennifer'), eb('first_name', '=', 'Arnold') ]).as('is_jennifer_or_arnold'), // Select a raw sql expression sql\`concat(first_name, ' ', last_name)\`.as('full_name'), // Select a static string value val('Some value').as('string_value'), // Select a literal value lit(42).as('literal_value'), ]) .execute()` ================================================ FILE: site/docs/examples/select/0050-complex-selections.mdx ================================================ --- title: 'Complex selections' --- # Complex selections You can select arbitrary expression including subqueries and raw sql snippets. When you do that, you need to give a name for the selections using the `as` method: import { Playground } from '../../../src/components/Playground' import { complexSelections } from './0050-complex-selections'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [select method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#select) - [selectAll method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#selectAll) - [selectFrom method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#selectFrom) ::: ================================================ FILE: site/docs/examples/select/0051-not-null.js ================================================ export const notNull = `import { NotNull } from 'kysely' import { jsonObjectFrom } from 'kysely/helpers/postgres' const persons = db .selectFrom('person') .select((eb) => [ 'last_name', // Let's assume we know the person has at least one // pet. We can use the \`.$notNull()\` method to make // the expression not null. You could just as well // add \`pet\` to the \`$narrowType\` call below. jsonObjectFrom( eb.selectFrom('pet') .selectAll() .limit(1) .whereRef('person.id', '=', 'pet.owner_id') ).$notNull().as('pet') ]) .where('last_name', 'is not', null) // $narrowType can be used to narrow the output type. // The special \`NotNull\` type can be used to make a // selection not null. You could add \`pet: NotNull\` // here and omit the \`$notNull()\` call on it. // Use whichever way you prefer. .$narrowType<{ last_name: NotNull }>() .execute()` ================================================ FILE: site/docs/examples/select/0051-not-null.mdx ================================================ --- title: 'Not null' --- # Not null Sometimes you can be sure something's not null, but Kysely isn't able to infer it. For example calling `where('last_name', 'is not', null)` doesn't make `last_name` not null in the result type, but unless you have other where statements you can be sure it's never null. Kysely has a couple of helpers for dealing with these cases: `$notNull()` and `$narrowType`. Both are used in the following example: import { Playground } from '../../../src/components/Playground' import { notNull } from './0051-not-null'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [select method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#select) - [selectAll method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#selectAll) - [selectFrom method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#selectFrom) ::: ================================================ FILE: site/docs/examples/select/0060-function-calls.js ================================================ export const functionCalls = `import { sql } from 'kysely' const result = await db.selectFrom('person') .innerJoin('pet', 'pet.owner_id', 'person.id') .select(({ fn, val, ref }) => [ 'person.id', // The \`fn\` module contains the most common // functions. fn.count('pet.id').as('pet_count'), // You can call any function by calling \`fn\` // directly. The arguments are treated as column // references by default. If you want to pass in // values, use the \`val\` function. fn('concat', [ val('Ms. '), 'first_name', val(' '), 'last_name' ]).as('full_name_with_title'), // You can call any aggregate function using the // \`fn.agg\` function. fn.agg('array_agg', ['pet.name']).as('pet_names'), // And once again, you can use the \`sql\` // template tag. The template tag substitutions // are treated as values by default. If you want // to reference columns, you can use the \`ref\` // function. sql\`concat( \${ref('first_name')}, ' ', \${ref('last_name')} )\`.as('full_name') ]) .groupBy('person.id') .having((eb) => eb.fn.count('pet.id'), '>', 10) .execute()` ================================================ FILE: site/docs/examples/select/0060-function-calls.mdx ================================================ --- title: 'Function calls' --- # Function calls This example shows how to create function calls. These examples also work in any other place (`where` calls, updates, inserts etc.). The only difference is that you leave out the alias (the `as` call) if you use these in any other place than `select`. import { Playground } from '../../../src/components/Playground' import { functionCalls } from './0060-function-calls'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [select method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#select) - [selectAll method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#selectAll) - [selectFrom method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#selectFrom) ::: ================================================ FILE: site/docs/examples/select/0070-distinct.js ================================================ export const distinct = `const persons = await db.selectFrom('person') .select('first_name') .distinct() .execute()` ================================================ FILE: site/docs/examples/select/0070-distinct.mdx ================================================ --- title: 'Distinct' --- # Distinct import { Playground } from '../../../src/components/Playground' import { distinct } from './0070-distinct'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [select method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#select) - [selectAll method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#selectAll) - [selectFrom method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#selectFrom) ::: ================================================ FILE: site/docs/examples/select/0080-distinct-on.js ================================================ export const distinctOn = `const persons = await db.selectFrom('person') .innerJoin('pet', 'pet.owner_id', 'person.id') .where('pet.name', '=', 'Doggo') .distinctOn('person.id') .selectAll('person') .execute()` ================================================ FILE: site/docs/examples/select/0080-distinct-on.mdx ================================================ --- title: 'Distinct on' --- # Distinct on import { Playground } from '../../../src/components/Playground' import { distinctOn } from './0080-distinct-on'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [select method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#select) - [selectAll method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#selectAll) - [selectFrom method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#selectFrom) ::: ================================================ FILE: site/docs/examples/select/0090-all-columns.js ================================================ export const allColumns = `const persons = await db .selectFrom('person') .selectAll() .execute()` ================================================ FILE: site/docs/examples/select/0090-all-columns.mdx ================================================ --- title: 'All columns' --- # All columns The `selectAll` method generates `SELECT *`: import { Playground } from '../../../src/components/Playground' import { allColumns } from './0090-all-columns'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [select method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#select) - [selectAll method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#selectAll) - [selectFrom method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#selectFrom) ::: ================================================ FILE: site/docs/examples/select/0100-all-columns-of-a-table.js ================================================ export const allColumnsOfATable = `const persons = await db .selectFrom('person') .selectAll('person') .execute()` ================================================ FILE: site/docs/examples/select/0100-all-columns-of-a-table.mdx ================================================ --- title: 'All columns of a table' --- # All columns of a table Select all columns of a table: import { Playground } from '../../../src/components/Playground' import { allColumnsOfATable } from './0100-all-columns-of-a-table'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [select method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#select) - [selectAll method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#selectAll) - [selectFrom method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#selectFrom) ::: ================================================ FILE: site/docs/examples/select/0110-nested-array.js ================================================ export const nestedArray = `import { jsonArrayFrom } from 'kysely/helpers/postgres' const result = await db .selectFrom('person') .select((eb) => [ 'id', jsonArrayFrom( eb.selectFrom('pet') .select(['pet.id as pet_id', 'pet.name']) .whereRef('pet.owner_id', '=', 'person.id') .orderBy('pet.name') ).as('pets') ]) .execute()` ================================================ FILE: site/docs/examples/select/0110-nested-array.mdx ================================================ --- title: 'Nested array' --- # Nested array While kysely is not an ORM and it doesn't have the concept of relations, we do provide helpers for fetching nested objects and arrays in a single query. In this example we use the `jsonArrayFrom` helper to fetch person's pets along with the person's id. Please keep in mind that the helpers under the `kysely/helpers` folder, including `jsonArrayFrom`, are not guaranteed to work with third party dialects. In order for them to work, the dialect must automatically parse the `json` data type into JavaScript JSON values like objects and arrays. Some dialects might simply return the data as a JSON string. In these cases you can use the built in `ParseJSONResultsPlugin` to parse the results. import { Playground } from '../../../src/components/Playground' import { nestedArray } from './0110-nested-array'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [select method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#select) - [selectAll method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#selectAll) - [selectFrom method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#selectFrom) ::: ================================================ FILE: site/docs/examples/select/0120-nested-object.js ================================================ export const nestedObject = `import { jsonObjectFrom } from 'kysely/helpers/postgres' const result = await db .selectFrom('person') .select((eb) => [ 'id', jsonObjectFrom( eb.selectFrom('pet') .select(['pet.id as pet_id', 'pet.name']) .whereRef('pet.owner_id', '=', 'person.id') .where('pet.is_favorite', '=', true) ).as('favorite_pet') ]) .execute()` ================================================ FILE: site/docs/examples/select/0120-nested-object.mdx ================================================ --- title: 'Nested object' --- # Nested object While kysely is not an ORM and it doesn't have the concept of relations, we do provide helpers for fetching nested objects and arrays in a single query. In this example we use the `jsonObjectFrom` helper to fetch person's favorite pet along with the person's id. Please keep in mind that the helpers under the `kysely/helpers` folder, including `jsonObjectFrom`, are not guaranteed to work with third-party dialects. In order for them to work, the dialect must automatically parse the `json` data type into JavaScript JSON values like objects and arrays. Some dialects might simply return the data as a JSON string. In these cases you can use the built in `ParseJSONResultsPlugin` to parse the results. import { Playground } from '../../../src/components/Playground' import { nestedObject } from './0120-nested-object'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [select method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#select) - [selectAll method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#selectAll) - [selectFrom method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#selectFrom) ::: ================================================ FILE: site/docs/examples/select/0130-generic-find-query.js ================================================ export const genericFindQuery = `import { SelectType } from 'kysely' import { Database } from 'type-editor' async function getRowByColumn< T extends keyof Database, C extends keyof Database[T] & string, V extends SelectType, >(t: T, c: C, v: V) { // We need to use the dynamic module since the table name // is not known at compile time. const { table, ref } = db.dynamic return await db .selectFrom(table(t).as('t')) .selectAll() .where(ref(c), '=', v) .orderBy('t.id') .executeTakeFirstOrThrow() } const person = await getRowByColumn('person', 'first_name', 'Arnold')` ================================================ FILE: site/docs/examples/select/0130-generic-find-query.mdx ================================================ --- title: 'Generic find query' --- # Generic find query A generic type-safe helper function for finding a row by a column value: import { Playground } from '../../../src/components/Playground' import { genericFindQuery } from './0130-generic-find-query'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [select method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#select) - [selectAll method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#selectAll) - [selectFrom method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#selectFrom) ::: ================================================ FILE: site/docs/examples/select/_category_.json ================================================ { "label": "SELECT", "position": 1, "link": { "type": "generated-index", "description": "Short and simple examples of using the SELECT methods." } } ================================================ FILE: site/docs/examples/transactions/0010-simple-transaction.js ================================================ export const simpleTransaction = `const catto = await db.transaction().execute(async (trx) => { const jennifer = await trx.insertInto('person') .values({ first_name: 'Jennifer', last_name: 'Aniston', age: 40, }) .returning('id') .executeTakeFirstOrThrow() return await trx.insertInto('pet') .values({ owner_id: jennifer.id, name: 'Catto', species: 'cat', is_favorite: false, }) .returningAll() .executeTakeFirst() })` ================================================ FILE: site/docs/examples/transactions/0010-simple-transaction.mdx ================================================ --- title: 'Simple transaction' --- # Simple transaction This example inserts two rows in a transaction. If an exception is thrown inside the callback passed to the `execute` method, 1. the exception is caught, 2. the transaction is rolled back, and 3. the exception is thrown again. Otherwise the transaction is committed. import { Playground } from '../../../src/components/Playground' import { simpleTransaction } from './0010-simple-transaction'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [transaction method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#transaction) ::: ================================================ FILE: site/docs/examples/transactions/0011-controlled-transaction.js ================================================ export const controlledTransaction = `const trx = await db.startTransaction().execute() try { const jennifer = await trx.insertInto('person') .values({ first_name: 'Jennifer', last_name: 'Aniston', age: 40, }) .returning('id') .executeTakeFirstOrThrow() const catto = await trx.insertInto('pet') .values({ owner_id: jennifer.id, name: 'Catto', species: 'cat', is_favorite: false, }) .returningAll() .executeTakeFirstOrThrow() await trx.commit().execute() // ... } catch (error) { await trx.rollback().execute() }` ================================================ FILE: site/docs/examples/transactions/0011-controlled-transaction.mdx ================================================ --- title: 'Controlled transaction' --- # Controlled transaction A controlled transaction allows you to commit and rollback manually, execute savepoint commands, and queries in general. In this example we start a transaction, use it to insert two rows and then commit the transaction. If an error is thrown, we catch it and rollback the transaction. import { Playground } from '../../../src/components/Playground' import { controlledTransaction } from './0011-controlled-transaction'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [transaction method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#transaction) ::: ================================================ FILE: site/docs/examples/transactions/0012-controlled-transaction-w-savepoints.js ================================================ export const controlledTransactionWSavepoints = `const trx = await db.startTransaction().execute() try { const jennifer = await trx .insertInto('person') .values({ first_name: 'Jennifer', last_name: 'Aniston', age: 40, }) .returning('id') .executeTakeFirstOrThrow() const trxAfterJennifer = await trx.savepoint('after_jennifer').execute() try { const catto = await trxAfterJennifer .insertInto('pet') .values({ owner_id: jennifer.id, name: 'Catto', species: 'cat', }) .returning('id') .executeTakeFirstOrThrow() await trxAfterJennifer .insertInto('toy') .values({ name: 'Bone', price: 1.99, pet_id: catto.id }) .execute() } catch (error) { await trxAfterJennifer.rollbackToSavepoint('after_jennifer').execute() } await trxAfterJennifer.releaseSavepoint('after_jennifer').execute() await trx.insertInto('audit').values({ action: 'added Jennifer' }).execute() await trx.commit().execute() } catch (error) { await trx.rollback().execute() }` ================================================ FILE: site/docs/examples/transactions/0012-controlled-transaction-w-savepoints.mdx ================================================ --- title: 'Controlled transaction /w savepoints' --- # Controlled transaction /w savepoints A controlled transaction allows you to commit and rollback manually, execute savepoint commands, and queries in general. In this example we start a transaction, insert a person, create a savepoint, try inserting a toy and a pet, and if an error is thrown, we rollback to the savepoint. Eventually we release the savepoint, insert an audit record and commit the transaction. If an error is thrown, we catch it and rollback the transaction. import { Playground } from '../../../src/components/Playground' import { controlledTransactionWSavepoints } from './0012-controlled-transaction-w-savepoints'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [transaction method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#transaction) ::: ================================================ FILE: site/docs/examples/transactions/_category_.json ================================================ { "label": "Transactions", "position": 8, "link": { "type": "generated-index", "description": "Short and simple examples of how to use transactions." } } ================================================ FILE: site/docs/examples/update/0010-single-row.js ================================================ export const singleRow = `const result = await db .updateTable('person') .set({ first_name: 'Jennifer', last_name: 'Aniston' }) .where('id', '=', 1) .executeTakeFirst()` ================================================ FILE: site/docs/examples/update/0010-single-row.mdx ================================================ --- title: 'Single row' --- # Single row Update a row in `person` table: import { Playground } from '../../../src/components/Playground' import { singleRow } from './0010-single-row'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [set method](https://kysely-org.github.io/kysely-apidoc/classes/UpdateQueryBuilder.html#set) - [returning method](https://kysely-org.github.io/kysely-apidoc/classes/UpdateQueryBuilder.html#returning) - [updateTable method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#updateTable) ::: ================================================ FILE: site/docs/examples/update/0020-complex-values.js ================================================ export const complexValues = `const result = await db .updateTable('person') .set((eb) => ({ age: eb('age', '+', 1), first_name: eb.selectFrom('pet').select('name').limit(1), last_name: 'updated', })) .where('id', '=', 1) .executeTakeFirst()` ================================================ FILE: site/docs/examples/update/0020-complex-values.mdx ================================================ --- title: 'Complex values' --- # Complex values As always, you can provide a callback to the `set` method to get access to an expression builder: import { Playground } from '../../../src/components/Playground' import { complexValues } from './0020-complex-values'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [set method](https://kysely-org.github.io/kysely-apidoc/classes/UpdateQueryBuilder.html#set) - [returning method](https://kysely-org.github.io/kysely-apidoc/classes/UpdateQueryBuilder.html#returning) - [updateTable method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#updateTable) ::: ================================================ FILE: site/docs/examples/update/0030-my-sql-joins.js ================================================ export const mySqlJoins = `const result = await db .updateTable(['person', 'pet']) .set('person.first_name', 'Updated person') .set('pet.name', 'Updated doggo') .whereRef('person.id', '=', 'pet.owner_id') .where('person.id', '=', 1) .executeTakeFirst()` ================================================ FILE: site/docs/examples/update/0030-my-sql-joins.mdx ================================================ --- title: 'MySQL joins' --- # MySQL joins MySQL allows you to join tables directly to the "main" table and update rows of all joined tables. This is possible by passing all tables to the `updateTable` method as a list and adding the `ON` conditions as `WHERE` statements. You can then use the `set(column, value)` variant to update columns using table qualified names. The `UpdateQueryBuilder` also has `innerJoin` etc. join methods, but those can only be used as part of a PostgreSQL `update set from join` query. Due to type complexity issues, we unfortunately can't make the same methods work in both cases. import { Playground } from '../../../src/components/Playground' import { mySqlJoins } from './0030-my-sql-joins'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [set method](https://kysely-org.github.io/kysely-apidoc/classes/UpdateQueryBuilder.html#set) - [returning method](https://kysely-org.github.io/kysely-apidoc/classes/UpdateQueryBuilder.html#returning) - [updateTable method](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#updateTable) ::: ================================================ FILE: site/docs/examples/update/_category_.json ================================================ { "label": "UPDATE", "position": 5, "link": { "type": "generated-index", "description": "Short and simple examples of how to write UPDATE queries." } } ================================================ FILE: site/docs/examples/where/0010-simple-where-clause.js ================================================ export const simpleWhereClause = `const person = await db .selectFrom('person') .selectAll() .where('first_name', '=', 'Jennifer') .where('age', '>', 40) .executeTakeFirst()` ================================================ FILE: site/docs/examples/where/0010-simple-where-clause.mdx ================================================ --- title: 'Simple where clause' --- # Simple where clause `where` method calls are combined with `AND`: import { Playground } from '../../../src/components/Playground' import { simpleWhereClause } from './0010-simple-where-clause'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [where method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#where) - [whereRef method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#whereRef) ::: ================================================ FILE: site/docs/examples/where/0020-where-in.js ================================================ export const whereIn = `const persons = await db .selectFrom('person') .selectAll() .where('id', 'in', [1, 2, 3]) .execute()` ================================================ FILE: site/docs/examples/where/0020-where-in.mdx ================================================ --- title: 'Where in' --- # Where in Find multiple items using a list of identifiers: import { Playground } from '../../../src/components/Playground' import { whereIn } from './0020-where-in'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [where method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#where) - [whereRef method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#whereRef) ::: ================================================ FILE: site/docs/examples/where/0030-object-filter.js ================================================ export const objectFilter = `const persons = await db .selectFrom('person') .selectAll() .where((eb) => eb.and({ first_name: 'Jennifer', last_name: eb.ref('first_name') })) .execute()` ================================================ FILE: site/docs/examples/where/0030-object-filter.mdx ================================================ --- title: 'Object filter' --- # Object filter You can use the `and` function to create a simple equality filter using an object import { Playground } from '../../../src/components/Playground' import { objectFilter } from './0030-object-filter'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [where method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#where) - [whereRef method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#whereRef) ::: ================================================ FILE: site/docs/examples/where/0040-or-where.js ================================================ export const orWhere = `const persons = await db .selectFrom('person') .selectAll() // 1. Using the \`or\` method on the expression builder: .where((eb) => eb.or([ eb('first_name', '=', 'Jennifer'), eb('first_name', '=', 'Sylvester') ])) // 2. Chaining expressions using the \`or\` method on the // created expressions: .where((eb) => eb('last_name', '=', 'Aniston').or('last_name', '=', 'Stallone') ) .execute()` ================================================ FILE: site/docs/examples/where/0040-or-where.mdx ================================================ --- title: 'OR where' --- # OR where To combine conditions using `OR`, you can use the expression builder. There are two ways to create `OR` expressions. Both are shown in this example: import { Playground } from '../../../src/components/Playground' import { orWhere } from './0040-or-where'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [where method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#where) - [whereRef method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#whereRef) ::: ================================================ FILE: site/docs/examples/where/0050-conditional-where-calls.js ================================================ export const conditionalWhereCalls = `import { Expression, SqlBool } from 'kysely' const firstName: string | undefined = 'Jennifer' const lastName: string | undefined = 'Aniston' const under18 = true const over60 = true let query = db .selectFrom('person') .selectAll() if (firstName) { // The query builder is immutable. Remember to reassign // the result back to the query variable. query = query.where('first_name', '=', firstName) } if (lastName) { query = query.where('last_name', '=', lastName) } if (under18 || over60) { // Conditional OR expressions can be added like this. query = query.where((eb) => { const ors: Expression[] = [] if (under18) { ors.push(eb('age', '<', 18)) } if (over60) { ors.push(eb('age', '>', 60)) } return eb.or(ors) }) } const persons = await query.execute()` ================================================ FILE: site/docs/examples/where/0050-conditional-where-calls.mdx ================================================ --- title: 'Conditional where calls' --- # Conditional where calls You can add expressions conditionally like this: import { Playground } from '../../../src/components/Playground' import { conditionalWhereCalls } from './0050-conditional-where-calls'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [where method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#where) - [whereRef method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#whereRef) ::: ================================================ FILE: site/docs/examples/where/0060-complex-where-clause.js ================================================ export const complexWhereClause = `const firstName = 'Jennifer' const maxAge = 60 const persons = await db .selectFrom('person') .selectAll('person') .where(({ eb, or, and, not, exists, selectFrom }) => and([ or([ eb('first_name', '=', firstName), eb('age', '<', maxAge) ]), not(exists( selectFrom('pet') .select('pet.id') .whereRef('pet.owner_id', '=', 'person.id') )) ])) .execute()` ================================================ FILE: site/docs/examples/where/0060-complex-where-clause.mdx ================================================ --- title: 'Complex where clause' --- # Complex where clause For complex `where` expressions you can pass in a single callback and use the `ExpressionBuilder` to build your expression: import { Playground } from '../../../src/components/Playground' import { complexWhereClause } from './0060-complex-where-clause'
:::info[More examples] The API documentation is packed with examples. The API docs are hosted [here](https://kysely-org.github.io/kysely-apidoc/), but you can access the same documentation by hovering over functions/methods/classes in your IDE. The examples are always just one hover away! For example, check out these sections: - [where method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#where) - [whereRef method](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#whereRef) ::: ================================================ FILE: site/docs/examples/where/_category_.json ================================================ { "label": "WHERE", "position": 2, "link": { "type": "generated-index", "description": "Short and simple examples of how to use the where method to add a WHERE statement. While most of the examples show a SELECT query, the where method works exactly the same in UPDATE and DELETE queries too." } } ================================================ FILE: site/docs/execution.mdx ================================================ --- sidebar_position: 3 title: 'Execution flow' --- The following page gives a **simplified** overview of Kysely's execution flow, from query building to querying the database. It is a nice introduction for anyone looking to understand how Kysely works under the hood. Knowing what your dependencies do and don't do is always a good idea! This breakdown explains the journey from a type-safe method call in your application to receiving results from the database, as depicted in the diagram. 1. ### Immutable query building The process starts in your `App`. You interact with the `QueryBuilder` by calling its methods (`selectFrom`, `where`, etc.). Each call returns a _new_ `QueryBuilder` instance containing an updated, immutable `QueryAST` (Abstract Syntax Tree), which is the internal representation of your SQL query. ```mermaid sequenceDiagram actor Y as App participant QB as QueryBuilder activate Y loop more to build Y->>+QB: method(type-safe inputs) QB->>QB: QueryAST QB-->>Y: new QueryBuilder end deactivate QB deactivate Y ``` 1. ### Initiating execution When you chain the final `.execute()` call, the `QueryBuilder` begins a multi-step execution process, commanding the `QueryExecutor` to perform distinct tasks. ```mermaid sequenceDiagram autonumber 4 actor Y as App participant QB as QueryBuilder activate Y Y->>+QB: execute() deactivate QB deactivate Y ``` 1. ### Query Transformation First, the `QueryBuilder` instructs the `QueryExecutor` to process the `QueryAST`. The `QueryExecutor` iterates through all registered plugins, calling `transformQuery` on each. This allows plugins to modify the query structure before compilation. The final, transformed `QueryAST` is returned to the `QueryBuilder`. ```mermaid sequenceDiagram autonumber 5 participant QB as QueryBuilder participant QE as QueryExecutor participant P as Plugin activate QB QB->>+QE: transformQuery(QueryAST) loop all plugins QE->>+P: transformQuery(QueryAST) P-->>-QE: Transformed QueryAST end QE-->>-QB: Transformed QueryAST deactivate QB ``` 1. ### Query Compilation Next, the `QueryBuilder` tells the `QueryExecutor` to compile the transformed AST. The `QueryExecutor` delegates this to the `Dialect`-specific `QueryCompiler`. The compiler traverses the AST and produces a `CompiledQuery` object (containing the final SQL string and parameters). This `CompiledQuery` is then returned to the `QueryBuilder`. ```mermaid sequenceDiagram autonumber 9 participant QB as QueryBuilder participant QE as QueryExecutor box Dialect participant QC as QueryCompiler end activate QB QB->>+QE: compileQuery(QueryAST) QE->>+QC: compileQuery(QueryAST) QC-->>-QE: CompiledQuery QE-->>-QB: CompiledQuery deactivate QB ``` 1. ### Execution & Connection Handling The `QueryBuilder` now asks the `QueryExecutor` to execute the `CompiledQuery`. - The `QueryExecutor` requests a connection from Kysely's `Driver`. - The `Driver`'s job is to abstract away vendor-specific details. It communicates with the actual third-party `DatabaseDriver` — for example, the `pg` or `mysql2` npm package — to get a connection from its pool. - A `DatabaseConnection` object, which wraps the native connection, is returned to the `QueryExecutor`. ```mermaid sequenceDiagram autonumber 13 participant QB as QueryBuilder participant QE as QueryExecutor box Dialect participant D as Driver end actor DD as DatabaseDriver activate QB QB->>+QE: executeQuery(CompiledQuery) QE->>+D: acquireConnection() opt differs per driver D->>+DD: DD-->>-D: connection end D-->>-QE: new DatabaseConnection deactivate QE deactivate QB ``` 1. ### Database Query The `QueryExecutor` passes the `CompiledQuery` to the `DatabaseConnection` object, which executes it. The `DatabaseConnection` uses the underlying `DatabaseDriver` to send the SQL and parameters to the database for execution. The `DatabaseDriver` sends the raw results back. The `DatabaseConnection` standardizes these into a `QueryResult` object and returns it to the `QueryExecutor`. Immediately after, the connection is released back to the pool. ```mermaid sequenceDiagram autonumber 18 participant QE as QueryExecutor box Dialect participant DC as DatabaseConnection end actor DD as DatabaseDriver activate QE QE->>+DC: executeQuery(CompiledQuery) DC->>+DD: SQL + Params DD-->>-DC: Raw Results DC-->>-QE: QueryResult deactivate QE ``` 1. ### Result Transformation The `QueryResult` is then passed through the plugin system again. The `QueryExecutor` calls the `transformResult` method on each plugin, allowing for final modifications to the results before they are returned to the `App`. ```mermaid sequenceDiagram autonumber 22 participant QE as QueryExecutor participant P as Plugin activate QE loop all plugins QE->>+P: transformResults(QueryResult) P-->>-QE: Transformed QueryResult end deactivate QE ``` 1. ### Returning to the App The final, transformed `QueryResult` is passed up from the `QueryExecutor` to the `QueryBuilder`. The `QueryBuilder` then resolves the promise from the initial `.execute()` call, delivering the final, typed results to your `App`. ```mermaid sequenceDiagram actor Y as App participant QB as QueryBuilder participant QE as QueryExecutor participant P as Plugin box Dialect participant QC as QueryCompiler participant D as Driver participant DC as DatabaseConnection end actor DD as DatabaseDriver activate Y loop more to build Y->>+QB: method(type-safe inputs) QB->>QB: QueryAST QB-->>Y: new QueryBuilder end Y->>QB: execute() QB->>+QE: transformQuery(QueryAST) loop all plugins QE->>+P: transformQuery(QueryAST) P-->>-QE: Transformed QueryAST end QE-->>-QB: Transformed QueryAST QB->>+QE: compileQuery(QueryAST) QE->>+QC: compileQuery(QueryAST) QC-->>-QE: CompiledQuery QE-->>-QB: CompiledQuery QB->>+QE: executeQuery(CompiledQuery) QE->>+D: acquireConnection() opt differs per driver D->>+DD: DD-->>-D: connection end D-->>-QE: new DatabaseConnection QE->>+DC: executeQuery(CompiledQuery) DC->>+DD: SQL + Params DD-->>-DC: Raw Results DC-->>-QE: QueryResult loop all plugins QE->>+P: transformResults(QueryResult) P-->>-QE: Transformed QueryResult end QE-->>-QB: Query Results QB-->>-Y: Query Results deactivate Y ``` ================================================ FILE: site/docs/generating-types.md ================================================ # Generating types To work with Kysely, you're required to provide a database schema type definition to the Kysely constructor. In many cases, defining your database schema definitions manually is good enough. However, when building production applications, it's best to stay aligned with the database schema, by automatically generating the database schema type definitions. There are several ways to do this using third-party libraries: - [kysely-codegen](https://github.com/RobinBlomberg/kysely-codegen) - This library generates Kysely database schema type definitions by connecting to and introspecting your database. This library works with all built-in dialects. - [prisma-kysely](https://github.com/valtyr/prisma-kysely) - This library generates Kysely database schema type definitions from your existing Prisma schemas. - [kanel-kysely](https://github.com/kristiandupont/kanel/tree/main/packages/kanel-kysely) - This library generates Kysely database schema type definitions by connecting to and introspecting your database. This library extends Kanel which is a mature PostgreSQL-only type generator. - [kysely-schema-generator](https://github.com/deanc/kysely-schema-generator) - This library generates Kysely database schema type definitions by connecting to and introspecting your database. Current MySQL only. ================================================ FILE: site/docs/getting-started/Dialects.tsx ================================================ import Admonition from '@theme/Admonition' import CodeBlock from '@theme/CodeBlock' import Heading from '@theme/Heading' import Link from '@docusaurus/Link' import TabItem from '@theme/TabItem' import Tabs from '@theme/Tabs' import { IUseADifferentPackageManager } from './IUseADifferentPackageManager' import { getDriverNPMPackageNames, getBashCommand, getDenoCommand, isDialectSupported, POOL_NPM_PACKAGE_NAMES, PRETTY_DIALECT_NAMES, PRETTY_PACKAGE_MANAGER_NAMES, type Dialect, type PackageManager, PACKAGE_MANAGERS, type PropsWithPackageManager, useSearchState, DEFAULT_PACKAGE_MANAGER, } from './shared' export type DialectsProps = PropsWithPackageManager interface BuiltInDialect { value: Dialect driverDocsURL: string poolDocsURL?: string } const builtInDialects: BuiltInDialect[] = [ { value: 'postgresql', driverDocsURL: 'https://node-postgres.com/', }, { value: 'mysql', driverDocsURL: 'https://github.com/sidorares/node-mysql2/tree/master/documentation', }, { value: 'mssql', driverDocsURL: 'https://tediousjs.github.io/tedious/index.html', poolDocsURL: 'https://github.com/vincit/tarn.js', }, { value: 'sqlite', driverDocsURL: 'https://github.com/WiseLibs/better-sqlite3/blob/master/docs/api.md', }, ] export function Dialects(props: DialectsProps) { const packageManager = useSearchState({ defaultValue: DEFAULT_PACKAGE_MANAGER, searchParam: props.packageManagerSearchParam, validator: (value) => PACKAGE_MANAGERS.includes(value as never), value: props.packageManager, }) return ( <>

For Kysely's query compilation and execution to work, it needs to understand your database's SQL specification and how to communicate with it. This requires a Dialect implementation.

There are 4 built-in dialects for PostgreSQL, MySQL, Microsoft SQL Server (MSSQL), and SQLite. Additionally, the community has implemented several dialects to choose from. Find out more at{' '} "Dialects".

Driver installation

A Dialect implementation usually requires a database driver library as a peer dependency. Let's install it using the same package manager command from before:

{/* @ts-ignore For some odd reason, Tabs doesn't accept children in this file. */} {builtInDialects.map(({ driverDocsURL, poolDocsURL, value }) => { const driverNPMPackage = getDriverNPMPackageNames()[value] const poolNPMPackage = POOL_NPM_PACKAGE_NAMES[value] const prettyDialectName = PRETTY_DIALECT_NAMES[value] const installationCommand = packageManager === 'deno' ? getDenoCommand({ [driverNPMPackage]: `npm:${driverNPMPackage}`, [`${driverNPMPackage}-pool`]: driverNPMPackage === 'pg' ? 'npm:pg-pool' : undefined, }) : getBashCommand(packageManager, driverNPMPackage, [ poolNPMPackage, ]) return ( // @ts-ignore For some odd reason, TabItem doesn't accept children in this file. {!isDialectSupported(value, packageManager) ? ( ) : ( <>

Kysely's built-in {prettyDialectName} dialect uses the " {driverNPMPackage}" driver library under the hood. Please refer to its{' '} official documentation for configuration options.

{poolNPMPackage ? (

Additionally, Kysely's {prettyDialectName} dialect uses the "{poolNPMPackage}" resource pool package for connection pooling. Please refer to its{' '} official documentation for configuration options.

) : null}

{installationCommand.intro}

{installationCommand.content} )}
) })}
Kysely can also work in compile-only mode that doesn't require a database driver. Find out more at{' '} "Splitting query building and execution" . ) } interface UnsupportedDriverProps { dialect: string driverNPMPackage: string packageManager: PackageManager } function UnsupportedDriver(props: UnsupportedDriverProps) { const { dialect, packageManager } = props const packageManagerName = PRETTY_PACKAGE_MANAGER_NAMES[packageManager || 'npm'] return ( Kysely's built-in {dialect} dialect does not work in {packageManagerName}{' '} because the driver library it uses, "{props.driverNPMPackage}", doesn't. You have to use a community {dialect} dialect that works in{' '} {packageManagerName}, or implement your own. ) } ================================================ FILE: site/docs/getting-started/IUseADifferentDialect.tsx ================================================ import Link from '@docusaurus/Link' import { DEFAULT_DIALECT, PRETTY_DIALECT_NAMES, type PropsWithDialect, } from './shared' export function IUseADifferentDialect( props: Pick, ) { const { dialect, dialectSelectionID } = props if (!dialectSelectionID) { return null } const dialectName = PRETTY_DIALECT_NAMES[dialect || DEFAULT_DIALECT] return (

I use a different dialect (not {dialectName})

) } ================================================ FILE: site/docs/getting-started/IUseADifferentPackageManager.tsx ================================================ import Link from '@docusaurus/Link' import { DEFAULT_PACKAGE_MANAGER, PRETTY_PACKAGE_MANAGER_NAMES, type PropsWithPackageManager, } from './shared' export function IUseADifferentPackageManager( props: Pick< PropsWithPackageManager, 'packageManager' | 'packageManagerSelectionID' >, ) { const { packageManager, packageManagerSelectionID } = props if (!packageManagerSelectionID) { return null } const packageManagerName = PRETTY_PACKAGE_MANAGER_NAMES[packageManager || DEFAULT_PACKAGE_MANAGER] return (

I use a different package manager (not {packageManagerName})

) } ================================================ FILE: site/docs/getting-started/Installation.tsx ================================================ import type { ReactNode } from 'react' import CodeBlock from '@theme/CodeBlock' import Link from '@docusaurus/Link' import Tabs from '@theme/Tabs' import TabItem from '@theme/TabItem' import { getBashCommand, getDenoCommand, PRETTY_PACKAGE_MANAGER_NAMES, type Command, type PackageManager, } from './shared' interface PackageManagerDetails { value: PackageManager description: ReactNode command: Command } const JavaScriptLink = () => ( JavaScript ) const NodeJSLink = () => Node.js const packageManagers: PackageManagerDetails[] = [ { value: 'npm', description: ( <> {PRETTY_PACKAGE_MANAGER_NAMES.npm}{' '} is the default package manager for , and to where Kysely is published.
Your project is using {PRETTY_PACKAGE_MANAGER_NAMES.npm} if it has a{' '} package-lock.json file in its root folder. ), command: getBashCommand('npm', 'kysely'), }, { value: 'pnpm', description: ( <> {PRETTY_PACKAGE_MANAGER_NAMES.pnpm} is a fast, disk space efficient package manager for .
Your project is using {PRETTY_PACKAGE_MANAGER_NAMES.pnpm} if it has a{' '} pnpm-lock.yaml file in its root folder. ), command: getBashCommand('pnpm', 'kysely'), }, { value: 'yarn', description: ( <> {PRETTY_PACKAGE_MANAGER_NAMES.yarn} {' '} is a fast, reliable and secure dependency manager for .
Your project is using {PRETTY_PACKAGE_MANAGER_NAMES.yarn} if it has a{' '} yarn.lock file in its root folder. ), command: getBashCommand('yarn', 'kysely'), }, { value: 'deno', description: ( <> {PRETTY_PACKAGE_MANAGER_NAMES.deno} {' '} is a secure runtime for and{' '} TypeScript. ), command: getDenoCommand(), }, { value: 'bun', description: ( <> {PRETTY_PACKAGE_MANAGER_NAMES.bun} is a new runtime built for speed, with a native bundler, transpiler, test runner, and {PRETTY_PACKAGE_MANAGER_NAMES.npm} -compatible package manager baked-in. ), command: getBashCommand('bun', 'kysely'), }, ] export function Installation() { return ( <>

Kysely can be installed using any of the following package managers:

{/* @ts-ignore For some odd reason, Tabs doesn't accept children in this file. */} {packageManagers.map(({ command, value, ...packageManager }) => ( // @ts-ignore For some odd reason, TabItem doesn't accept children in this file.

{packageManager.description}

{command.intro}

{command.content}
))}
) } ================================================ FILE: site/docs/getting-started/Instantiation.tsx ================================================ import Admonition from '@theme/Admonition' import CodeBlock from '@theme/CodeBlock' import { IUseADifferentDialect } from './IUseADifferentDialect' import { IUseADifferentPackageManager } from './IUseADifferentPackageManager' import { getDriverNPMPackageNames, isDialectSupported, POOL_NPM_PACKAGE_NAMES, PRETTY_PACKAGE_MANAGER_NAMES, type Dialect, type PackageManager, type PropsWithDialect, DIALECT_CLASS_NAMES, PRETTY_DIALECT_NAMES, type PropsWithPackageManager, useSearchState, DIALECTS, DEFAULT_PACKAGE_MANAGER, DEFAULT_DIALECT, PACKAGE_MANAGERS, } from './shared' export type InstantiationProps = PropsWithDialect export function Instantiation(props: InstantiationProps) { const { dialectSelectionID, packageManagerSelectionID } = props const dialect = useSearchState({ defaultValue: DEFAULT_DIALECT, searchParam: props.dialectSearchParam, validator: (value) => DIALECTS.includes(value as never), value: props.dialect, }) const packageManager = useSearchState({ defaultValue: DEFAULT_PACKAGE_MANAGER, searchParam: props.packageManagerSearchParam, validator: (value) => PACKAGE_MANAGERS.includes(value as never), value: props.packageManager, }) const dialectSpecificCodeSnippet = !isDialectSupported( dialect, packageManager, ) ? getNotSupportedCode(dialect, packageManager) : getDialectSpecificCodeSnippet(dialect, packageManager) const dialectClassName = DIALECT_CLASS_NAMES[dialect] return ( <>

Let's create a Kysely instance {isDialectSupported(dialect, packageManager) ? ( <> using the built-in {dialectClassName} dialect ) : ( assuming a compatible community dialect exists )} :

{`import { Database } from './types.ts' // this is the Database interface we defined earlier ${dialectSpecificCodeSnippet} // Database interface is passed to Kysely's constructor, and from now on, Kysely // knows your database structure. // Dialect is passed to Kysely's constructor, and from now on, Kysely knows how // to communicate with your database. export const db = new Kysely({ dialect, })`} {dialectSelectionID || packageManagerSelectionID ? (
) : null} In most cases, you should only create a single Kysely instance per database. Most dialects use a connection pool internally, or no connections at all, so there's no need to create a new instance for each request. Use a secrets manager, environment variables (DO NOT commit `.env` files to your repository), or a similar solution, to avoid hardcoding database credentials in your code. When needed, you can dispose of the Kysely instance, release resources and close all connections by invoking the db.destroy(){' '} function. ) } function getNotSupportedCode( dialect: Dialect, packageManager: PackageManager, ): string { return `/* Kysely doesn't support ${PRETTY_DIALECT_NAMES[dialect]} + ${ PRETTY_PACKAGE_MANAGER_NAMES[packageManager || 'npm'] } out of the box. Import a community dialect that does here. */ import { Kysely } from 'kysely' const dialect = /* instantiate the dialect here */` } function getDialectSpecificCodeSnippet( dialect: Dialect, packageManager: PackageManager, ): string { const driverNPMPackageName = getDriverNPMPackageNames(packageManager)[dialect] const dialectClassName = DIALECT_CLASS_NAMES[dialect] const poolClassName = 'Pool' const poolClassImport = packageManager === 'deno' ? poolClassName : `{ ${poolClassName} }` if (dialect === 'postgresql') { return `import ${poolClassImport} from '${driverNPMPackageName}' import { Kysely, ${dialectClassName} } from 'kysely' const dialect = new ${dialectClassName}({ pool: new ${poolClassName}({ database: 'test', host: 'localhost', user: 'admin', port: 5434, max: 10, }) })` } if (dialect === 'mysql') { const poolFactoryName = 'createPool' return `import { ${poolFactoryName} } from '${driverNPMPackageName}' // do not use 'mysql2/promises'! import { Kysely, ${dialectClassName} } from 'kysely' const dialect = new ${dialectClassName}({ pool: ${poolFactoryName}({ database: 'test', host: 'localhost', user: 'admin', password: '123', port: 3308, connectionLimit: 10, }) })` } if (dialect === 'mssql') { const poolPackageName = POOL_NPM_PACKAGE_NAMES.mssql return `import * as ${driverNPMPackageName} from '${driverNPMPackageName}' import * as ${poolPackageName} from '${poolPackageName}' import { Kysely, ${dialectClassName} } from 'kysely' const dialect = new ${dialectClassName}({ ${poolPackageName}: { ...${poolPackageName}, options: { min: 0, max: 10, }, }, ${driverNPMPackageName}: { ...${driverNPMPackageName}, connectionFactory: () => new ${driverNPMPackageName}.Connection({ authentication: { options: { password: 'password', userName: 'username', }, type: 'default', }, options: { database: 'some_db', port: 1433, trustServerCertificate: true, }, server: 'localhost', }), }, })` } if (dialect === 'sqlite') { const driverImportName = 'SQLite' return `import ${driverImportName} from '${driverNPMPackageName}' import { Kysely, ${dialectClassName} } from 'kysely' const dialect = new ${dialectClassName}({ database: new ${driverImportName}(':memory:'), })` } throw new Error(`Unsupported dialect: ${dialect}`) } ================================================ FILE: site/docs/getting-started/Querying.tsx ================================================ import Admonition from '@theme/Admonition' import CodeBlock from '@theme/CodeBlock' import Link from '@docusaurus/Link' import { IUseADifferentDialect } from './IUseADifferentDialect' import { DEFAULT_DIALECT, DIALECTS, useSearchState, type Dialect, type PropsWithDialect, } from './shared' const postgresqlCodeSnippet = `export async function createPerson(person: NewPerson) { return await db.insertInto('person') .values(person) .returningAll() .executeTakeFirstOrThrow() } export async function deletePerson(id: number) { return await db.deleteFrom('person').where('id', '=', id) .returningAll() .executeTakeFirst() }` const dialectSpecificCodeSnippets: Record = { postgresql: postgresqlCodeSnippet, mysql: `export async function createPerson(person: NewPerson) { const { insertId } = await db.insertInto('person') .values(person) .executeTakeFirstOrThrow() return await findPersonById(Number(insertId!)) } export async function deletePerson(id: number) { const person = await findPersonById(id) if (person) { await db.deleteFrom('person').where('id', '=', id).execute() } return person }`, mssql: `// As of v0.27.0, Kysely doesn't support the \`OUTPUT\` clause. This will change // in the future. For now, the following implementations achieve the same results // as other dialects' examples, but with extra steps. export async function createPerson(person: NewPerson) { const compiledQuery = db.insertInto('person').values(person).compile() const { rows: [{ id }], } = await db.executeQuery>({ ...compiledQuery, sql: \`\${compiledQuery.sql}; select scope_identity() as id\` }) return await findPersonById(id) } export async function deletePerson(id: number) { const person = await findPersonById(id) if (person) { await db.deleteFrom('person').where('id', '=', id).execute() } return person }`, sqlite: postgresqlCodeSnippet, // TODO: Update to use output clause once #687 is completed } export function Querying(props: PropsWithDialect) { const dialect = useSearchState({ defaultValue: DEFAULT_DIALECT, searchParam: props.dialectSearchParam, validator: (value) => DIALECTS.includes(value as never), value: props.dialect, }) const dialectSpecificCodeSnippet = dialectSpecificCodeSnippets[dialect] return ( <>

Let's implement the person repository:

{`import { db } from './database' import { PersonUpdate, Person, NewPerson } from './types' export async function findPersonById(id: number) { return await db.selectFrom('person') .where('id', '=', id) .selectAll() .executeTakeFirst() } export async function findPeople(criteria: Partial) { let query = db.selectFrom('person') if (criteria.id) { query = query.where('id', '=', criteria.id) // Kysely is immutable, you must re-assign! } if (criteria.first_name) { query = query.where('first_name', '=', criteria.first_name) } if (criteria.last_name !== undefined) { query = query.where( 'last_name', criteria.last_name === null ? 'is' : '=', criteria.last_name ) } if (criteria.gender) { query = query.where('gender', '=', criteria.gender) } if (criteria.created_at) { query = query.where('created_at', '=', criteria.created_at) } return await query.selectAll().execute() } export async function updatePerson(id: number, updateWith: PersonUpdate) { await db.updateTable('person').set(updateWith).where('id', '=', id).execute() } ${dialectSpecificCodeSnippet}`} This is a simplified example with basic CRUD operations. Kysely supports many more SQL features including: joins, subqueries, complex boolean logic, set operations, CTEs, functions (aggregate and window functions included), raw SQL, transactions, DDL queries, etc.
Find out more at Examples.
) } ================================================ FILE: site/docs/getting-started/Summary.tsx ================================================ import Admonition from '@theme/Admonition' import CodeBlock from '@theme/CodeBlock' import Link from '@docusaurus/Link' import { IUseADifferentDialect } from './IUseADifferentDialect' import { DEFAULT_DIALECT, DIALECTS, useSearchState, type Dialect, type PropsWithDialect, } from './shared' const dialectSpecificCodeSnippets: Record = { postgresql: ` await db.schema.createTable('person') .addColumn('id', 'serial', (cb) => cb.primaryKey()) .addColumn('first_name', 'varchar', (cb) => cb.notNull()) .addColumn('last_name', 'varchar') .addColumn('gender', 'varchar(50)', (cb) => cb.notNull()) .addColumn('created_at', 'timestamp', (cb) => cb.notNull().defaultTo(sql\`now()\`) ) .execute()`, mysql: ` await db.schema.createTable('person') .addColumn('id', 'integer', (cb) => cb.primaryKey().autoIncrement()) .addColumn('first_name', 'varchar(255)', (cb) => cb.notNull()) .addColumn('last_name', 'varchar(255)') .addColumn('gender', 'varchar(50)', (cb) => cb.notNull()) .addColumn('created_at', 'timestamp', (cb) => cb.notNull().defaultTo(sql\`now()\`) ) .execute()`, // TODO: Update line 42's IDENTITY once identity(1,1) is added to core. mssql: ` await db.schema.createTable('person') .addColumn('id', 'integer', (cb) => cb.primaryKey().modifyEnd(sql\`identity\`)) .addColumn('first_name', 'varchar(255)', (cb) => cb.notNull()) .addColumn('last_name', 'varchar(255)') .addColumn('gender', 'varchar(50)', (cb) => cb.notNull()) .addColumn('created_at', 'datetime', (cb) => cb.notNull().defaultTo(sql\`GETDATE()\`) ) .execute()`, sqlite: ` await db.schema.createTable('person') .addColumn('id', 'integer', (cb) => cb.primaryKey().autoIncrement().notNull()) .addColumn('first_name', 'varchar(255)', (cb) => cb.notNull()) .addColumn('last_name', 'varchar(255)') .addColumn('gender', 'varchar(50)', (cb) => cb.notNull()) .addColumn('created_at', 'timestamp', (cb) => cb.notNull().defaultTo(sql\`current_timestamp\`) ) .execute()`, } const dialectSpecificTruncateSnippets: Record = { postgresql: `await sql\`truncate table \${sql.table('person')}\`.execute(db)`, mysql: `await sql\`truncate table \${sql.table('person')}\`.execute(db)`, mssql: `await sql\`truncate table \${sql.table('person')}\`.execute(db)`, sqlite: `await sql\`delete from \${sql.table('person')}\`.execute(db)`, } export function Summary(props: PropsWithDialect) { const dialect = useSearchState({ defaultValue: DEFAULT_DIALECT, searchParam: props.dialectSearchParam, validator: (value) => DIALECTS.includes(value as never), value: props.dialect, }) const dialectSpecificCodeSnippet = dialectSpecificCodeSnippets[dialect] const dialectSpecificTruncateSnippet = dialectSpecificTruncateSnippets[dialect] return ( <>

We've seen how to install and instantiate Kysely, its dialects and underlying drivers. We've also seen how to use Kysely to query a database.

Let's put it all to the test:

{`import { sql } from 'kysely' import { db } from './database' import * as PersonRepository from './PersonRepository' describe('PersonRepository', () => { before(async () => { ${dialectSpecificCodeSnippet} }) afterEach(async () => { ${dialectSpecificTruncateSnippet} }) after(async () => { await db.schema.dropTable('person').execute() }) it('should find a person with a given id', async () => { await PersonRepository.findPersonById(123) }) it('should find all people named Arnold', async () => { await PersonRepository.findPeople({ first_name: 'Arnold' }) }) it('should update gender of a person with a given id', async () => { await PersonRepository.updatePerson(123, { gender: 'woman' }) }) it('should create a person', async () => { await PersonRepository.createPerson({ first_name: 'Jennifer', last_name: 'Aniston', gender: 'woman', }) }) it('should delete a person with a given id', async () => { await PersonRepository.deletePerson(123) }) })`} As you can see, Kysely supports DDL queries. It also supports classic "up/down" migrations. Find out more at{' '} Migrations. ) } ================================================ FILE: site/docs/getting-started/_prerequisites.mdx ================================================ - [TypeScript](https://www.typescriptlang.org/) - Minimum supported version [4.6](https://devblogs.microsoft.com/typescript/announcing-typescript-4-6/#indexed-access-inference-improvements). - For even more type-safety and accurate inference, use version [5.4](https://devblogs.microsoft.com/typescript/announcing-typescript-5-4/#notable-behavioral-changes) or later. - For improved compilation performance, use version [5.9](https://devblogs.microsoft.com/typescript/announcing-typescript-5-9/#cache-instantiations-on-mappers) or later. - You must enable `strict` mode in your `tsconfig.json` file's `compilerOptions`: ```ts title="tsconfig.json" { // ... "compilerOptions": { // ... "strict": true // ... } // ... } ``` ================================================ FILE: site/docs/getting-started/_types.mdx ================================================ For Kysely's type-safety and autocompletion to work, it needs to know your database structure. This requires a TypeScript Database interface, that contains table names as keys and table schema interfaces as values. **Let's define our first database interface:** ```ts title="src/types.ts" import { ColumnType, Generated, Insertable, JSONColumnType, Selectable, Updateable, } from 'kysely' export interface Database { person: PersonTable pet: PetTable } // This interface describes the `person` table to Kysely. Table // interfaces should only be used in the `Database` type above // and never as a result type of a query!. See the `Person`, // `NewPerson` and `PersonUpdate` types below. export interface PersonTable { // Columns that are generated by the database should be marked // using the `Generated` type. This way they are automatically // made optional in inserts and updates. id: Generated first_name: string gender: 'man' | 'woman' | 'other' // If the column is nullable in the database, make its type nullable. // Don't use optional properties. Optionality is always determined // automatically by Kysely. last_name: string | null // You can specify a different type for each operation (select, insert and // update) using the `ColumnType` // wrapper. Here we define a column `created_at` that is selected as // a `Date`, can optionally be provided as a `string` in inserts and // can never be updated: created_at: ColumnType // You can specify JSON columns using the `JSONColumnType` wrapper. // It is a shorthand for `ColumnType`, where T // is the type of the JSON object/array retrieved from the database, // and the insert and update types are always `string` since you're // always stringifying insert/update values. metadata: JSONColumnType<{ login_at: string ip: string | null agent: string | null plan: 'free' | 'premium' }> } // You should not use the table schema interfaces directly. Instead, you should // use the `Selectable`, `Insertable` and `Updateable` wrappers. These wrappers // make sure that the correct types are used in each operation. // // Most of the time you should trust the type inference and not use explicit // types at all. These types can be useful when typing function arguments. export type Person = Selectable export type NewPerson = Insertable export type PersonUpdate = Updateable export interface PetTable { id: Generated name: string owner_id: number species: 'dog' | 'cat' } export type Pet = Selectable export type NewPet = Insertable export type PetUpdate = Updateable ``` :::tip[Codegen] For production apps, it is recommended to automatically generate your Database interface by introspecting your production database or Prisma schemas. Generated types might differ in naming convention, internal order, etc. Find out more at ["Generating types"](https://kysely.dev/docs/generating-types). ::: :::info[Runtime types] Kysely only deals with types in the TypeScript level. The runtime JavaScript types are decided by the underlying third-party driver such as `pg` or `mysql2` and it's up to you to select the correct TypeScript types in the database interface. Kysely never touches the runtime output types in any way. Find out more at ["Data types"](https://kysely.dev/docs/recipes/data-types). ::: ================================================ FILE: site/docs/getting-started/shared.tsx ================================================ import { useLocation } from '@docusaurus/router' import { useEffect, useState, type ReactNode } from 'react' import packageJson from '../../package.json' export const DIALECTS = ['postgresql', 'mysql', 'sqlite', 'mssql'] as const export type Dialect = (typeof DIALECTS)[number] export const DEFAULT_DIALECT = 'postgresql' satisfies Dialect export type PropsWithDialect

= P & { dialect?: Dialect dialectSearchParam?: string dialectSelectionID?: string } export type PropsWithPackageManager

= P & { packageManager?: PackageManager packageManagerSearchParam?: string packageManagerSelectionID?: string } export const PACKAGE_MANAGERS = ['npm', 'pnpm', 'yarn', 'deno', 'bun'] as const export type PackageManager = (typeof PACKAGE_MANAGERS)[number] export const DEFAULT_PACKAGE_MANAGER = 'npm' satisfies PackageManager const PACKAGE_MANAGER_UNSUPPORTED_DIALECTS: Record = { bun: ['sqlite'], deno: ['sqlite', 'mssql'], npm: [], pnpm: [], yarn: [], } export function isDialectSupported( dialect: Dialect, packageManager: PackageManager, ): boolean { return !PACKAGE_MANAGER_UNSUPPORTED_DIALECTS[packageManager].includes(dialect) } export const DIALECT_CLASS_NAMES = { postgresql: 'PostgresDialect', mysql: 'MysqlDialect', mssql: 'MssqlDialect', sqlite: 'SqliteDialect', } as const satisfies Record export const getDriverNPMPackageNames = ( packageManager: PackageManager = 'npm', ) => ({ postgresql: packageManager === 'deno' ? 'pg-pool' : 'pg', mysql: 'mysql2', mssql: 'tedious', sqlite: 'better-sqlite3', }) as const satisfies Record export const POOL_NPM_PACKAGE_NAMES = { mssql: 'tarn', } as const satisfies Partial> export const PRETTY_DIALECT_NAMES = { postgresql: 'PostgreSQL', mysql: 'MySQL', mssql: 'Microsoft SQL Server (MSSQL)', sqlite: 'SQLite', } as const satisfies Record export const PRETTY_PACKAGE_MANAGER_NAMES = { npm: 'npm', pnpm: 'pnpm', yarn: 'Yarn', deno: 'Deno', bun: 'Bun', } as const satisfies Record const PACKAGE_MANAGER_INSTALL_COMMANDS = { npm: 'npm install', pnpm: 'pnpm install', yarn: 'yarn add', bun: 'bun install', } as const satisfies Omit, 'deno'> export interface Command { content: ReactNode intro: ReactNode language: string title: string } export function getBashCommand( packageManager: PackageManager, installedPackage: string, additionalPackages?: string[], ): Command { if (packageManager === 'deno') { throw new Error('Deno has no bash command') } return { content: `${ PACKAGE_MANAGER_INSTALL_COMMANDS[packageManager] } ${installedPackage}${ additionalPackages?.length ? ` ${additionalPackages.join(' ')}` : '' }`, intro: 'Run the following command in your terminal:', language: 'bash', title: 'terminal', } } export function getDenoCommand( additionalImports?: Record, ): Command { return { content: JSON.stringify( { imports: { kysely: `npm:kysely@^${packageJson.version}`, ...additionalImports, }, }, null, 2, ), intro: ( <> Your root deno.json 's "imports" field should include the following dependencies: ), language: 'json', title: 'deno.json', } } export interface UseSearchStateProps { defaultValue?: Value searchParam?: string validator?: (searchValue: string | null) => boolean value?: Value } export function useSearchState( props: UseSearchStateProps, ): Value { const { defaultValue, searchParam, validator, value } = props const [state, setState] = useState(value || defaultValue) const { search } = useLocation() useEffect( function syncStateWithSearch() { // value overrides search value. // no search param? ignore URL. if (value || !searchParam) { return } const searchValue = new URLSearchParams(search).get(searchParam) if ( searchValue == null || searchValue === state || (validator && !validator(searchValue)) ) { return } setState(searchValue as Value) }, [search], ) useEffect(() => { if (value && value !== state) { setState(value) } }, [value, state]) return state } ================================================ FILE: site/docs/getting-started.mdx ================================================ --- sidebar_position: 2 title: 'Getting started' --- import BrowserOnly from '@docusaurus/BrowserOnly' import Prerequisites from './getting-started/_prerequisites.mdx' import { Installation } from './getting-started/Installation' import Types from './getting-started/_types.mdx' import { Dialects } from './getting-started/Dialects' import { Instantiation } from './getting-started/Instantiation' import { Querying } from './getting-started/Querying' import { Summary } from './getting-started/Summary' # Getting started ## Prerequisites ## Installation ## Types ## Dialects ## Instantiation ## Querying ## Summary

================================================ FILE: site/docs/integrations/_category_.json ================================================ { "label": "Integrations", "position": 10, "link": { "type": "generated-index", "description": "Kysely integrations with other libraries, ORMs, and AI assistants." } } ================================================ FILE: site/docs/integrations/llms.mdx ================================================ # LLMs Kysely provides LLM-friendly documentation to help AI tools like **Cursor**, **Windsurf**, **GitHub Copilot**, **ChatGPT**, **Claude**, and **Claude Code** understand and work with it. `llms.txt` documentation is automatically generated and kept up-to-date with each push on GitHub and is based on the [llms.txt standard](https://llmstxt.org/). ## Documentation Structure The [llms.txt](https://kysely.dev/llms.txt) file acts as a summary and index to all the docs pages. The [llms-full.txt](https://kysely.dev/llms-full.txt) file includes all of the Kysely docs in a single file. ## Usage with AI Tools ### Cursor In Cursor, you can reference the documentation using the `@Docs` feature: 1. Type `@Docs` in your prompt 2. Reference the Kysely documentation URL: `https://kysely.dev/llms-full.txt` 3. Ask questions about Kysely queries, types, or database operations ### Windsurf For Windsurf users: 1. Reference the documentation using `@https://kysely.dev/llms-full.txt` 2. Or add it to your `.windsurfrules` file for persistent access ### ChatGPT & Claude When using ChatGPT or Claude: 1. Mention that you're using Kysely 2. Reference the documentation URL: `https://kysely.dev/llms-full.txt` 3. The AI will fetch and use the documentation to provide accurate answers ### GitHub Copilot While Copilot doesn't directly support external documentation, you can: 1. Include relevant documentation snippets in your comments 2. Reference query builder methods and types accurately for better suggestions ### Claude Code For Claude Code users: 1. Reference the documentation by mentioning the URL: `https://kysely.dev/llms-full.txt` 2. Ask Claude Code to analyze the documentation for specific Kysely patterns 3. Use commands like: `claude -p "Using the Kysely docs at https://kysely.dev/llms-full.txt, help me build a type-safe query"` ## Example Prompts Here are some example prompts you can use with AI tools: - "Using Kysely, how do I build a type-safe SELECT query with JOINs?" - "Show me how to define a database schema interface for Kysely" - "How can I use transactions with Kysely?" - "Create a complex query with subqueries and aggregations" - "How to handle database migrations with Kysely?" ================================================ FILE: site/docs/integrations/supabase.mdx ================================================ # Supabase Supabase is an open-source Firebase alternative that provides a suite of tools for building applications. At the core, it is a managed PostgreSQL database vendor. They provide a CLI library called `supabase` that's at the heart of their ecosystem. It manages your database, migrates it and can generate TypeScript types from it. They also provide a JavaScript client library called `@supabase/supabase-js` that wraps a PostgREST API, and is pretty limited - doesn't even allow raw SQL. This is where Kysely comes in. We provide a bridge library called `kysely-supabase` that allows you to translate `supabase`'s generated TypeScript types into types compatible with Kysely. ## Prerequisites 1. `supabase` CLI installed and a Supabase project set up. 1. `kysely` installed. 1. A PostgreSQL driver installed - e.g. `pg` or `postgres`. The latter requires `kysely-postgres-js` to be installed as well. ## Installation ```bash npm i -D kysely-supabase ``` ## Usage ### Generate TypeScript types using `supabase` CLI ```bash npx supabase gen types typescript --local > path/to/supabase/generated/types/file ``` ### Translate Supabase types to Kysely types ```ts title="src/types.ts" import type { Database as SupabaseDatabase } from 'path/to/supabase/generated/types/file' import type { KyselifyDatabase } from 'kysely-supabase' export type Database = KyselifyDatabase ``` ### Pass translated types to Kysely constructor ```ts title="src/db.ts" import { Kysely, PostgresDialect } from 'kysely' import { Pool } from 'pg' import type { Database } from './types' export const db = new Kysely({ // ^^^^^^^^ dialect: new PostgresDialect({ pool: new Pool({ connectionString: process.env.DATABASE_URL, }), }), }) ``` ================================================ FILE: site/docs/intro.mdx ================================================ --- sidebar_position: 1 --- import { DemoVideo } from '@site/src/components/DemoVideo' # Introduction Kysely (pronounced “Key-Seh-Lee”) is a type-safe and autocompletion-friendly TypeScript SQL query builder. Inspired by Knex. Mainly developed for [node.js](https://nodejs.org/en/) but also runs on all other JavaScript environments like [deno](https://deno.land/) and [bun](https://bun.sh/). Kysely makes sure you only refer to tables and columns that are visible to the part of the query you're writing. The result type only has the selected columns with correct types and aliases. As an added bonus you get autocompletion for all that stuff. As shown in the gif above, through the pure magic of modern TypeScript, Kysely is even able to parse the alias given to `pet.name` and add the `pet_name` column to the result row type. Kysely is able to infer column names, aliases and types from selected subqueries, joined subqueries, `with` statements and pretty much anything you can think of. Of course there are cases where things cannot be typed at compile time, and Kysely offers escape hatches for these situations. See the [sql template tag](https://kysely-org.github.io/kysely-apidoc/interfaces/Sql.html) and the [DynamicModule](https://kysely-org.github.io/kysely-apidoc/classes/DynamicModule.html#ref) for more info. All API documentation is written in the typing files and you can simply hover over the module, class or method you're using to see it in your IDE. The same documentation is also hosted [here](https://kysely-org.github.io/kysely-apidoc/). If you start using Kysely and can't find something you'd want to use, please open an issue or join our [Discord server](https://discord.gg/xyBJ3GwvAm). ## Looking for help? If you start using Kysely and can't find something you'd want to use, please [open an issue](https://github.com/kysely-org/kysely/issues) or [join our Discord server](https://discord.gg/xyBJ3GwvAm). ================================================ FILE: site/docs/migrations.mdx ================================================ --- sidebar_position: 4 --- # Migrations ## Migration files Migration files should look like this: ```ts import { Kysely } from 'kysely' export async function up(db: Kysely): Promise { // Migration code } export async function down(db: Kysely): Promise { // Migration code } ``` The `up` function is called when you update your database schema to the next version and `down` when you go back to previous version. The only argument for the functions is an instance of `Kysely`. It's important to use `Kysely` and not `Kysely`. Migrations should never depend on the current code of your app because they need to work even when the app changes. Migrations need to be "frozen in time". Migrations can use the `Kysely.schema` module to modify the schema. Migrations can also run normal queries to read/modify data. ## Execution order Migrations will be run in the alpha-numeric order of your migration names. An excellent way to name your migrations is to prefix them with an ISO 8601 date string. By default, Kysely will also ensure this order matches the execution order of any previously executed migrations in your database. If the orders do not match (for example, a new migration was added alphabetically before a previously executed one), an error will be returned. This adds safety by always executing your migrations in the correct, alphanumeric order. There is also an `allowUnorderedMigrations` option. This option will allow new migrations to be run even if they are added alphabetically before ones that have already executed. Allowing unordered migrations works well in large teams where multiple team members may add migrations at the same time in parallel commits without knowing about the other migrations. Pending (unexecuted) migrations will be run in alpha-numeric order when migrating up. When migrating down, migrations will be undone in the opposite order in which they were executed (reverse sorted by execution timestamp). To allow unordered migrations, pass the `allowUnorderedMigrations` option to Migrator: ```ts const migrator = new Migrator({ db, provider: new FileMigrationProvider(...), allowUnorderedMigrations: true }) ``` ## Single file vs multiple file migrations You don't need to store your migrations as separate files if you don't want to. You can easily implement your own MigrationProvider and give it to the Migrator class when you instantiate one. ## PostgreSQL migration example ```ts import { Kysely, sql } from 'kysely' export async function up(db: Kysely): Promise { await db.schema .createTable('person') .addColumn('id', 'serial', (col) => col.primaryKey()) .addColumn('first_name', 'varchar', (col) => col.notNull()) .addColumn('last_name', 'varchar') .addColumn('gender', 'varchar(50)', (col) => col.notNull()) .addColumn('created_at', 'timestamp', (col) => col.defaultTo(sql`now()`).notNull(), ) .execute() await db.schema .createTable('pet') .addColumn('id', 'serial', (col) => col.primaryKey()) .addColumn('name', 'varchar', (col) => col.notNull().unique()) .addColumn('owner_id', 'integer', (col) => col.references('person.id').onDelete('cascade').notNull(), ) .addColumn('species', 'varchar', (col) => col.notNull()) .execute() await db.schema .createIndex('pet_owner_id_index') .on('pet') .column('owner_id') .execute() } export async function down(db: Kysely): Promise { await db.schema.dropTable('pet').execute() await db.schema.dropTable('person').execute() } ``` ## SQLite migration example ```ts import { Kysely, sql } from 'kysely' export async function up(db: Kysely): Promise { await db.schema .createTable('person') .addColumn('id', 'integer', (col) => col.primaryKey()) .addColumn('first_name', 'text', (col) => col.notNull()) .addColumn('last_name', 'text') .addColumn('gender', 'text', (col) => col.notNull()) .addColumn('created_at', 'text', (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull(), ) .execute() await db.schema .createTable('pet') .addColumn('id', 'integer', (col) => col.primaryKey()) .addColumn('name', 'text', (col) => col.notNull().unique()) .addColumn('owner_id', 'integer', (col) => col.references('person.id').onDelete('cascade').notNull(), ) .addColumn('species', 'text', (col) => col.notNull()) .execute() await db.schema .createIndex('pet_owner_id_index') .on('pet') .column('owner_id') .execute() } export async function down(db: Kysely): Promise { await db.schema.dropTable('pet').execute() await db.schema.dropTable('person').execute() } ``` ## CLI (optional) Kysely offers a CLI you can use for migrations (and more). It can help you create and run migrations. It is not part of the core, and your mileage may vary. For more information, visit https://github.com/kysely-org/kysely-ctl. ## Running migrations You can then use: ```ts const migrator = new Migrator(migratorConfig) await migrator.migrateToLatest() ``` to run all migrations that have not yet been run. See the Migrator class's documentation for more info. You will probably want to add a simple migration script to your projects like this: ```ts import * as path from 'path' import { Pool } from 'pg' import { promises as fs } from 'fs' import { Kysely, Migrator, PostgresDialect, FileMigrationProvider, } from 'kysely' import { Database } from './types' async function migrateToLatest() { const db = new Kysely({ dialect: new PostgresDialect({ pool: new Pool({ host: 'localhost', database: 'kysely_test', }), }), }) const migrator = new Migrator({ db, provider: new FileMigrationProvider({ fs, path, // This needs to be an absolute path. migrationFolder: path.join(__dirname, 'some/path/to/migrations'), }), }) const { error, results } = await migrator.migrateToLatest() results?.forEach((it) => { if (it.status === 'Success') { console.log(`migration "${it.migrationName}" was executed successfully`) } else if (it.status === 'Error') { console.error(`failed to execute migration "${it.migrationName}"`) } }) if (error) { console.error('failed to migrate') console.error(error) process.exit(1) } await db.destroy() } migrateToLatest() ``` The migration methods use a lock on the database level and parallel calls are executed serially. This means that you can safely call migrateToLatest and other migration methods from multiple server instances simultaneously and the migrations are guaranteed to only be executed once. The locks are also automatically released if the migration process crashes or the connection to the database fails. ## Reference documentation [Migrator](https://kysely-org.github.io/kysely-apidoc/classes/Migrator.html) ================================================ FILE: site/docs/playground.mdx ================================================ --- sidebar_position: 3 --- # Playground [@wirekang](https://github.com/wirekang) has created a [playground for Kysely](https://kyse.link). You can use it to quickly test stuff out and for creating code examples for your issues, PRs and Discord messages. import { Playground } from '../src/components/Playground' Kysely IS NOT an ORM. Kysely DOES NOT have the concept of relations. Kysely IS a query builder. Kysely DOES build the SQL you tell it to, nothing more, nothing less. Phew, glad we got that out the way.. Having said all that, there are ways to nest related rows in your queries. You just have to do it using the tools SQL and the underlying dialect (e.g. PostgreSQL, MySQL, or SQLite) provide. In this recipe we show one way to do that when using the built-in PostgreSQL, MySQL, and SQLite dialects. This recipe is supported on MySQL versions starting from 8.0.14. This is due to the way subqueries use outer references in this recipe (cf. [MySQL 8.0.14 changelog](https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-14.html#mysqld-8-0-14-optimizer) | [MariaDB is not supported yet](https://jira.mariadb.org/browse/MDEV-19078)). ## The `json` data type and functions PostgreSQL and MySQL have rich JSON support through their `json` data types and functions. `pg` and `mysql2`, the node drivers, automatically parse returned `json` columns as json objects. With the combination of these two things, we can write some super efficient queries with nested relations. :::info[Parsing JSON] The built in `SqliteDialect` and some third-party dialects don't parse the returned JSON columns to objects automatically. Not even if they use `PostgreSQL` or `MySQL` under the hood! Parsing is handled (or not handled) by the database driver that Kysely has no control over. If your JSON columns get returned as strings, you can use the `ParseJSONResultsPlugin`: ```ts const db = new Kysely({ ... plugins: [new ParseJSONResultsPlugin()] }) ``` ::: Let's start with some raw postgres SQL, and then see how we can write the query using Kysely in a nice type-safe way. In the following query, we fetch a list of people (from "person" table) and for each person, we nest the person's pets, and mother, into the returned objects: ```sql SELECT person.*, -- Select person's pets as a json array ( SELECT COALESCE(JSON_AGG(pets), '[]') FROM ( SELECT pet.id, pet.name FROM pet WHERE pet.owner_id = person.id ORDER BY pet.name ) pets ) pets, -- Select person's mother as a json object ( SELECT TO_JSON(mother) FROM ( SELECT mother.id, mother.first_name FROM person as mother WHERE mother.id = person.mother_id ) mother ) mother FROM person ``` Simple right 😅. Yeah, not so much. But it does provide full control over the queries and a really good performance as long as you have indices (or indexes, we don't judge) for "pet.owner_id" and "person.mother_id". Fortunately we can improve and simplify this a lot using Kysely. First let's define a couple of helpers: ```ts function jsonArrayFrom(expr: Expression) { return sql[]>`(select coalesce(json_agg(agg), '[]') from ${expr} as agg)` } function jsonObjectFrom(expr: Expression) { return sql>`(select to_json(obj) from ${expr} as obj)` } ``` These helpers are included in Kysely and you can import them from the `helpers` module like this: ```ts import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres' ``` MySQL and SQLite versions of the helpers are slightly different, but you can use them the same way. You can import them like this: ```ts import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/mysql' ``` ```ts import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/sqlite' ``` With these helpers, our example query already becomes a little more bearable to look at: ```ts const persons = await db .selectFrom('person') .selectAll('person') .select((eb) => [ // pets jsonArrayFrom( eb.selectFrom('pet') .select(['pet.id', 'pet.name']) .whereRef('pet.owner_id', '=', 'person.id') .orderBy('pet.name') ).as('pets'), // mother jsonObjectFrom( eb.selectFrom('person as mother') .select(['mother.id', 'mother.first_name']) .whereRef('mother.id', '=', 'person.mother_id') ).as('mother') ]) .execute() console.log(persons[0].pets[0].name) console.log(persons[0].mother?.first_name) ``` That's better right? If you need to do this over and over in your codebase, you can create some helpers like these: ```ts function pets(ownerId: Expression) { return jsonArrayFrom( db.selectFrom('pet') .select(['pet.id', 'pet.name']) .where('pet.owner_id', '=', ownerId) .orderBy('pet.name') ) } function mother(motherId: Expression) { return jsonObjectFrom( db.selectFrom('person as mother') .select(['mother.id', 'mother.first_name']) .where('mother.id', '=', motherId) ) } ``` And now you get this: ```ts const persons = await db .selectFrom('person') .selectAll('person') .select(({ ref }) => [ pets(ref('person.id')).as('pets'), mother(ref('person.mother_id')).as('mother') ]) .execute() console.log(persons[0].pets[0].name) console.log(persons[0].mother?.first_name) ``` In some cases Kysely marks your selections as nullable if it's not able to know the related object always exists. If you have that information, you can mark the relation non-null using the `$notNull()` helper like this: ```ts const persons = await db .selectFrom('person') .selectAll('person') .select(({ ref }) => [ pets(ref('person.id')).as('pets'), mother(ref('person.mother_id')).$notNull().as('mother') ]) .execute() console.log(persons[0].pets[0].name) console.log(persons[0].mother.first_name) ``` If you need to select relations conditionally, `$if` is your friend: ```ts const persons = await db .selectFrom('person') .selectAll('person') .$if(includePets, (qb) => qb.select( (eb) => pets(eb.ref('person.id')).as('pets') )) .$if(includeMom, (qb) => qb.select( (eb) => mother(eb.ref('person.mother_id')).as('mother') )) .execute() ``` ================================================ FILE: site/docs/recipes/0001-reusable-helpers.md ================================================ # Reusable helpers :::info [Here's](https://kyse.link/qm67s) a playground link containing all the code in this recipe. ::: Let's say you want to write the following query: ```sql SELECT id, first_name FROM person WHERE upper(last_name) = $1 ``` Kysely doesn't have a built-in `upper` function but there are at least three ways you could write this: ```ts const lastName = 'STALLONE' const persons = await db .selectFrom('person') .select(['id', 'first_name']) // 1. `sql` template tag. This is the least type-safe option. // You're providing the column name without any type-checking, // and plugins won't affect it. .where( sql`upper(last_name)`, '=', lastName ) // 2. `sql` template tag with `ref`. Anything passed to `ref` // gets type-checked against the accumulated query context. .where(({ eb, ref }) => eb( sql`upper(${ref('last_name')})`, '=', lastName )) // 3. The `fn` function helps you avoid missing parentheses/commas // errors and uses refs as 1st class arguments. .where(({ eb, fn }) => eb( fn('upper', ['last_name']), '=', lastName )) .execute() ``` but each option could be more readable or type-safe. Fortunately Kysely allows you to easily create composable, reusable and type-safe helper functions: ```ts import { Expression, sql } from 'kysely' function upper(expr: Expression) { return sql`upper(${expr})` } function lower(expr: Expression) { return sql`lower(${expr})` } function concat(...exprs: Expression[]) { return sql.join(exprs, sql`||`) } ``` Using the `upper` helper, our query would look like this: ```ts const lastName = 'STALLONE' const persons = await db .selectFrom('person') .select(['id', 'first_name']) .where(({ eb, ref }) => eb( upper(ref('last_name')), '=', lastName )) .execute() ``` The recipe for helper functions is simple: take inputs as `Expression` instances where `T` is the type of the expression. For example `upper` takes in any `string` expression since it transforms strings to upper case. If you implemented the `round` function, it'd take in `Expression` since you can only round numbers. The helper functions should then use the inputs to create an output that's also an `Expression`. Everything you can create using the expression builder is an instance of `Expression`. So is the output of the `sql` template tag and all methods under the `sql` object. Same goes for `SelectQueryBuilder` and pretty much everything else in Kysely. Everything's an expression. See [this recipe](https://kysely.dev/docs/recipes/expressions) to learn more about expressions. So we've learned that everything's an expression and that expressions are composable. Let's put this idea to use: ```ts const persons = await db .selectFrom('person') .select(['id', 'first_name']) .where(({ eb, ref, val }) => eb( concat( lower(ref('first_name')), val(' '), upper(ref('last_name')) ), '=', 'sylvester STALLONE' )) .execute() ``` So far we've only used our helper functions in the first argument of `where` but you can use them anywhere: ```ts const persons = await db .selectFrom('person') .innerJoin('pet', (join) => join.on(eb => eb( 'person.first_name', '=', lower(eb.ref('pet.name')) ))) .select(({ ref, val }) => [ 'first_name', // If you use a helper in `select`, you need to always provide an explicit // name for it using the `as` method. concat(ref('person.first_name'), val(' '), ref('pet.name')).as('name_with_pet') ]) .orderBy(({ ref }) => lower(ref('first_name'))) .execute() ``` ## Reusable helpers using `ExpressionBuilder` Here's an example of a helper function that uses the expression builder instead of raw SQL: ```ts import { Expression, expressionBuilder } from 'kysely' function idsOfPersonsThatHaveDogNamed(name: Expression) { const eb = expressionBuilder() // A subquery that returns the identifiers of all persons // that have a dog named `name`. return eb .selectFrom('pet') .select('pet.owner_id') .where('pet.species', '=', 'dog') .where('pet.name', '=', name) } ``` And here's how you could use it: ```ts const dogName = 'Doggo' const persons = await db .selectFrom('person') .selectAll('person') .where((eb) => eb( 'person.id', 'in', idsOfPersonsThatHaveDogNamed(eb.val(dogName)) )) .execute() ``` Note that `idsOfPersonsThatHaveDogNamed` doesn't execute a separate query but instead returns a subquery expression that's compiled as a part of the parent query: ```sql select person.* from person where person.id in ( select pet.owner_id from pet where pet.species = 'dog' and pet.name = ? ) ``` In all our examples we've used the following syntax: ```ts .where(eb => eb(left, operator, right)) ``` When the expression builder `eb` is used as a function, it creates a binary expression. All binary expressions with a comparison operator are represented as a `Expression`. You don't always need to return `eb(left, operator, right)` from the callback though. Since `Expressions` are composable and reusable, you can return any `Expression`. This means you can create helpers like this: ```ts function isOlderThan(age: Expression) { return sql`age > ${age}` } ``` ```ts const persons = await db .selectFrom('person') .select(['id', 'first_name']) .where(({ val }) => isOlderThan(val(60))) .execute() ``` ## Dealing with nullable expressions If you want your helpers to work with nullable expressions (nullable columns etc.), you can do something like this: ```ts import { Expression } from 'kysely' // This function accepts both nullable and non-nullable string expressions. function toInt(expr: Expression) { // This returns `Expression` if `expr` is nullable // and `Expression` otherwise. return sql`(${expr})::integer` } ``` ## Passing select queries as expressions Let's say we have the following query: ```ts const expr: Expression<{ name: string }> = db .selectFrom('pet') .select('pet.name') ``` The expression type of our query is `Expression<{ name: string }>` but SQL allows you to use a query like that as an `Expression`. In other words, SQL allows you to use single-column record types like scalars. Most of the time Kysely is able to automatically handle this case but with helper functions you need to use `$asScalar()` to convert the type. Here's an example: ```ts const persons = await db .selectFrom('person') .select((eb) => [ 'id', 'first_name', upper( eb.selectFrom('pet') .select('name') .whereRef('person.id', '=', 'pet.owner_id') .limit(1) .$asScalar() // <-- This is needed .$notNull() ).as('pet_name') ]) ``` The subquery is an `Expression<{ name: string }>` but our `upper` function only accepts `Expression`. That's why we need to call `$asScalar()`. `$asScalar()` has no effect on the generated SQL. It's simply a type-level helper. We also used `$notNull()` in the example because our simple `upper` function doesn't support nullable expressions. ================================================ FILE: site/docs/recipes/0002-data-types.md ================================================ # Data types When talking about data types in Kysely we need to make a distinction between the two kinds of types: 1. Typescript types 2. Runtime JavaScript types ## Typescript types In Kysely, you only define TypeScript types for your tables and columns. Since TypeScript is entirely a compile-time concept, TypeScript types __can't__ affect runtime JavaScript types. If you define your column to be a `string` in TypeScript but the database returns a `number`, the runtime type doesn't magically change to `string`. You'll see a `string` in the TypeScript code, but observe a number when you run the program. :::info It's up to **you** to select correct TypeScript types for your columns based on what the driver returns. ::: ## Runtime JavaScript types The database driver, such as `pg` or `mysql2`, decides the runtime JavaScript types the queries return. Kysely never touches the runtime types the driver returns. In fact, Kysely doesn't touch the data returned by the driver in any way. It simply executes the query and returns whatever the driver returns. An exception to this rule is when you use a plugin like `CamelCasePlugin`, in which case Kysely does change the column names. You need to read the underlying driver's documentation or otherwise figure out what the driver returns and then align the TypeScript types to match them. ### Configuring runtime JavaScript types Most drivers provide a way to change the returned types. For example `pg` returns `bigint` and `numeric` types as strings by default, but often you want to configure it to return numbers instead. #### Postgres When using the `pg` driver, you can use the [pg-types](https://github.com/brianc/node-pg-types) package to configure the types. For example here's how you'd configure the `bigint` to be returned as a number: ```ts import { Kysely, PostgresDialect } from 'kysely' import * as pg from 'pg' const int8TypeId = 20 // Map int8 to number. pg.types.setTypeParser(int8TypeId, (val) => { return parseInt(val, 10) }) export const db = new Kysely({ dialect: new PostgresDialect({ pool: new pg.Pool(config), }), }) ``` See the documentation [here](https://github.com/brianc/node-pg-types) on how to figure out the correct type id. #### MySQL When using the `mysql2` driver, you an use the [typeCast](https://github.com/mysqljs/mysql?tab=readme-ov-file#custom-type-casting) pool property. For example here's how you'd map `tinyint(1)` to a boolean: ```ts import { Kysely, MysqlDialect } from 'kysely' import { createPool } from 'mysql2' export const db = new Kysely({ dialect: new MysqlDialect({ pool: createPool({ ...config, // Map tinyint(1) to boolean typeCast(field, next) { if (field.type === 'TINY' && field.length === 1) { return field.string() === '1' } else { return next() } }, }), }), }) ``` ## Type generators There are third-party type generators such as [kysely-codegen](https://github.com/RobinBlomberg/kysely-codegen) and [kanel-kysely](https://kristiandupont.github.io/kanel/kanel-kysely.html) that automatically generate TypeScript types based on the database schema. Find out more at ["Generating types"](https://kysely.dev/docs/generating-types). If these tools generate a type that doesn't match the runtime type you observe, please refer to their documentation or open an issue in their github. Kysely has no control over these libraries. ================================================ FILE: site/docs/recipes/0003-raw-sql.md ================================================ # Raw SQL You can execute raw SQL strings and pass raw SQL snippets to pretty much any method or function using the [sql template tag](https://kysely-org.github.io/kysely-apidoc/interfaces/Sql.html). ================================================ FILE: site/docs/recipes/0004-splitting-query-building-and-execution.md ================================================ # Splitting query building and execution Kysely is primarily a type-safe sql query builder. It also does query execution, migrations, etc. in order to align with Knex's "batteries included" approach. ## "Cold" Kysely instances In order to use Kysely purely as a query builder without database driver dependencies, you can instantiate it with the built-in `DummyDriver` class: ```ts import { Generated, DummyDriver, Kysely, PostgresAdapter, PostgresIntrospector, PostgresQueryCompiler, } from 'kysely' interface Person { id: Generated first_name: string last_name: string | null } interface Database { person: Person } const db = new Kysely({ dialect: { createAdapter: () => new PostgresAdapter(), createDriver: () => new DummyDriver(), createIntrospector: (db) => new PostgresIntrospector(db), createQueryCompiler: () => new PostgresQueryCompiler(), }, }) ``` This Kysely instance will compile to PostgreSQL sql dialect. You can brew "dummy" dialects to compile to all kinds of sql dialects (e.g. MySQL). Trying to execute queries using "cold" kysely instances will return empty results without communicating with a database. > "Cold" Kysely instances are not required for the following sections. You can use "hot" kysely instances, with real drivers, if you want to. ## Compile a query To compile a query, simply call `.compile()` at the end of the query building chain: ```ts const compiledQuery = db .selectFrom('person') .select('first_name') .where('id', '=', id) .compile() console.log(compiledQuery) // { sql: 'select "first_name" from "person" where "id" = $1', parameters: [1], query: { ... } } ``` The result of `.compile()` is a `CompiledQuery` object. It contains the query string (in `sql` field), parameters and the original Kysely-specific syntax tree used for compilation. This output alone can be used with any database driver that understands the sql dialect used (PostgreSQL in this example). Raw queries can be compiled as well: ```ts import { Selectable, sql } from 'kysely' const compiledQuery = sql>`select * from person where id = ${id}`.compile(db) console.log(compiledQuery) // { sql: 'select * from person where id = $1', parameters: [1], query: { ... } } ``` ## Infer result type Kysely supports inferring a (compiled) query's result type even when detached from query building chains. This allows splitting query building, compilation and execution code without losing type-safety. ```ts import { InferResult } from 'kysely' const query = db .selectFrom('person') .select('first_name') .where('id', '=', id) type QueryReturnType = InferResult // { first_name: string }[] const compiledQuery = query.compile() type CompiledQueryReturnType = InferResult // { first_name: string }[] ``` ## Execute compiled queries The `CompiledQuery` object returned by `.compile()` can be executed via "hot" Kysely instances (real drivers in use): ```ts const compiledQuery = db .selectFrom('person') .select('first_name') .where('id', '=', id) .compile() const results = await db.executeQuery(compiledQuery) ``` The `QueryResult` object returned by `.executeQuery()` contains the query results' rows, insertId and number of affected rows (if applicable). ================================================ FILE: site/docs/recipes/0005-conditional-selects.md ================================================ # Conditional selects Sometimes you may want to select some fields based on a runtime condition. Something like this: ```ts async function getPerson(id: number, withLastName: boolean) {} ``` If `withLastName` is true the person object is returned with a `last_name` property, otherwise without it. Your first thought can be to simply do this: ```ts async function getPerson(id: number, withLastName: boolean) { let query = db.selectFrom('person').select('first_name').where('id', '=', id) if (withLastName) { // ❌ The type of `query` doesn't change here query = query.select(['last_name', sql.val('person_with_last_name' as const).as('kind')]) } // ❌ Wrong return type { first_name: string, kind: 'person' } return await query.select(sql.val('person' as const).as('kind')).executeTakeFirstOrThrow() } ``` While that _would_ compile, the result type would be `{ first_name: string, kind: 'person' }` without the `last_name` column and `kind` being "person_with_last_name", which is wrong. What happens is that the type of `query` when created is something, let's say `A`. The type of the query with `last_name` selection is `B` which extends `A` but also contains information about the new selection. When you assign an object of type `B` to `query` inside the `if` statement, the type gets downcast to `A`. :::info You _can_ write code like this to add conditional `where`, `groupBy`, `orderBy` etc. statements that don't change the type of the query builder, but it doesn't work with `select`, `returning`, `innerJoin` etc. that _do_ change the type of the query builder. ::: In this simple case you could implement the method like this: ```ts async function getPerson(id: number, withLastName: boolean) { const query = db .selectFrom("person") .select("first_name") .where("id", "=", id); if (withLastName) { // ✅ The return type is { first_name: string, last_name: string, kind: 'person_with_last_name' } return await query .select([ "last_name", sql.val("person_with_last_name").as("kind"), ]) .executeTakeFirstOrThrow(); } // ✅ The return type is { first_name: string, kind: 'person' } return await query .select(sql.val("person").as("kind")) .executeTakeFirstOrThrow(); } ``` This works fine when you have one single condition. As soon as you have two or more conditions the amount of code explodes if you want to keep things type-safe. You need to create a separate branch for every possible combination of selections or otherwise the types won't be correct. This is where the [$if](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#_if) method can help you: ```ts async function getPerson(id: number, withLastName: boolean) { // ✅ The return type is { first_name: string, last_name?: string } return await db .selectFrom("person") .select("first_name") .$if(withLastName, (qb) => qb.select("last_name")) .where("id", "=", id) .executeTakeFirstOrThrow(); } ``` Any selections added inside the `$if` callback will be added as optional fields to the output type since we can't know if the selections were actually made before running the code. A downside of `$if` is that, unlike the imperative example, it cannot result in discriminated union return types - `kind` would be a union of `'person' | 'person_with_last_name'`. ================================================ FILE: site/docs/recipes/0006-expressions.md ================================================ # Expressions An [`Expression`](https://kysely-org.github.io/kysely-apidoc/interfaces/Expression.html) is the basic type-safe query building block in Kysely. Pretty much all methods accept expressions as inputs. Most internal classes like [SelectQueryBuilder](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html) and [RawBuilder](https://kysely-org.github.io/kysely-apidoc/interfaces/RawBuilder.html) (the return value of the [sql tag](https://kysely-org.github.io/kysely-apidoc/functions/sql-1.html)) are expressions themselves. `Expression` represents an arbitrary SQL expression, like a binary expression (e.g. `a + b`), or a function call (e.g. `concat(arg1, ' ', arg2, ...)`). It can be any combination of those, no matter how complex. `T` is the output type of the expression. ## Expression builder Expressions are usually built using an instance of [`ExpressionBuilder`](https://kysely-org.github.io/kysely-apidoc/interfaces/ExpressionBuilder.html). `DB` is the same database type you give to `Kysely` when you create an instance. `TB` is the union of all table names that are visible in the context. For example `ExpressionBuilder` means you can reference `person` and `pet` columns in the created expressions. You can get an instance of the expression builder using a callback: ```ts const person = await db .selectFrom('person') // `eb` is an instance of ExpressionBuilder .select((eb) => [ // Call the `upper` function on `first_name`. There's a bunch of // shortcuts to functions under the `fn` object such as // `eb.fn.coalesce()` that provide a cleaner syntax. eb.fn('upper', ['first_name']).as('upper_first_name'), // Select a subquery eb.selectFrom('pet') .select('name') .whereRef('pet.owner_id', '=', 'person.id') .limit(1) .as('pet_name'), // Select a boolean expression eb('first_name', '=', 'Jennifer').as('is_jennifer'), // Select a static string value eb.val('Some value').as('string_value'), // Select a literal value eb.lit(42).as('literal_value'), ]) // You can also destructure the expression builder like this .where(({ and, or, eb, not, exists, selectFrom }) => or([ and([ eb('first_name', '=', firstName), eb('last_name', '=', lastName) ]), not(exists( selectFrom('pet') .select('pet.id') .whereRef('pet.owner_id', '=', 'person.id') .where('pet.species', 'in', ['dog', 'cat']) )) ])) .executeTakeFirstOrThrow() console.log(person.upper_first_name) console.log(person.pet_name) console.log(person.is_jennifer) ``` The generated SQL: ```sql select upper("first_name") as "upper_first_name", ( select "name" from "pet" where "pet"."owner_id" = "person"."id" limit 1 ) as "pet_name", "first_name" = $1 as "is_jennifer", $2 as "string_value", 42 as "literal_value" from "person" where ( ( "first_name" = $3 and "last_name" = $4 ) or not exists ( select "pet.id" from "pet" where "pet"."owner_id" = "person"."id" and "pet"."species" in ($5, $6) ) ) ``` In the above query we used the expression builder in `select` and `where` methods. You can use it the same way in other methods like `having`, `on`, `orderBy`, `groupBy` etc. All expressions are composable. You can pass expressions as arguments of any expression. All query builder methods in Kysely accept expressions and expression builder callbacks. All expression builder methods offer auto-completions and type-safety just like methods on the query builders. You might be wondering, "why do I need to use a callback to get the expression builder?". "Why not just create an instance using a global function?". The reason is that when you use a callback, Kysely is able to infer the context correctly. The expression builder's methods only auto-complete and accept column and table names that are available in the context. In other words, using a callback provides more type-safety! There's also a global function `expressionBuilder` you can use to create expression builders: ```ts import { expressionBuilder } from 'kysely' // `eb1` has type `ExpressionBuilder` which means there are no tables in the // context. This variant should be used most of the time in helper functions since you // shouldn't make assumptions about the calling context. const eb1 = expressionBuilder() // `eb2` has type `ExpressionBuilder`. You can reference `person` columns // directly in all expression builder methods. const eb2 = expressionBuilder() // In this one you'd have access to tables `person` and `pet` and all their columns. const eb3 = expressionBuilder() let qb = query .selectFrom('person') .innerJoin('movie as m', 'm.director_id', 'person.id') // You can also provide a query builder instance and the context is inferred automatically. // Type of `eb` is `ExpressionBuilder` const eb = expressionBuilder(qb) qb = qb.where(eb.not(eb.exists( eb.selectFrom('pet') .select('pet.id') .whereRef('pet.name', '=', 'm.name') ))) ``` ## Creating reusable helpers The expression builder can be used to create reusable helper functions. Let's say we have a complex `where` expression we want to reuse in multiple queries: ```ts function hasDogNamed(name: string): Expression { const eb = expressionBuilder() return eb.exists( eb.selectFrom('pet') .select('pet.id') .whereRef('pet.owner_id', '=', 'person.id') .where('pet.species', '=', 'dog') .where('pet.name', '=', name) ) } ``` This helper can now be used in any query, and would work just fine if "person" table is in context: ```ts const doggoPersons = await db .selectFrom('person') .selectAll('person') .where(hasDogNamed('Doggo')) .execute() ``` However, the above helper is not very type-safe. The following code would compile, but fail at runtime: ```ts const bigFatFailure = await db .selectFrom('movie') // <-- "person" table is not in context! .selectAll('movie') .where(hasDogNamed('Doggo')) // <-- but we're referring to "person.id" in our helper .execute() ``` It's better to not make assumptions about the calling context and pass in all dependencies as arguments. In the following example we pass in the person's id as an expression. We also changed the type of `name` from `string` to `Expression`, which allows us to pass in arbitrary expressions instead of just values. ```ts function hasDogNamed(name: Expression, ownerId: Expression) { // Create an expression builder without any tables in the context. // This way we make no assumptions about the calling context. const eb = expressionBuilder() return eb.exists( eb.selectFrom('pet') .select('pet.id') .where('pet.owner_id', '=', ownerId) .where('pet.species', '=', 'dog') .where('pet.name', '=', name) ) } ``` Here's how you'd use our brand new helper: ```ts const doggoPersons = await db .selectFrom('person') .selectAll('person') .where((eb) => hasDogNamed(eb.val('Doggo'), eb.ref('person.id'))) .execute() ``` Learn more about reusable helper functions [here](https://kysely.dev/docs/recipes/reusable-helpers). ## Conditional expressions In the following, we'll only cover `where` expressions. The same logic applies to `having`, `on`, `orderBy`, `groupBy` etc. > This section should not be confused with conditional selections in `select` clauses, which is a whole 'nother topic we discuss in [this recipe](https://kysely.dev/docs/recipes/conditional-selects). Having a set of optional filters you want to combine using `and`, is the most basic and common use case of conditional `where` expressions. Since the `where`, `having` and other filter functions are additive, most of the time this is enough: ```ts let query = db .selectFrom('person') .selectAll('person') if (firstName) { // The query builder is immutable. Remember to replace the builder // with the new one. query = query.where('first_name', '=', firstName) } if (lastName) { query = query.where('last_name', '=', lastName) } const persons = await query.execute() ``` The same query can be built using the expression builder like this: ```ts const persons = await db .selectFrom('person') .selectAll('person') .where((eb) => { const filters: Expression[] = [] if (firstName) { filters.push(eb('first_name', '=', firstName)) } if (lastName) { filters.push(eb('last_name', '=', lastName)) } return eb.and(filters) }) .execute() ``` Using the latter design, you can build conditional expressions of any complexity. ================================================ FILE: site/docs/recipes/0007-schemas.md ================================================ # Working with schemas First of all, when we talk about schemas in this document, we mean custom schemas like [postgres schemas](https://www.postgresql.org/docs/14/ddl-schemas.html). There are two common ways to use schemas: 1. To group a logical set of tables under the same "namespace". For example all tables directly related to users could live under a `user` schema. 2. To have a separate namespaced copy of a set of tables for each tenant in a multitenant application. Kysely offers tools for both of these cases. ## 1 When you have an enumarable set of schemas, you can add them to your database interface like this: ```ts interface Database { 'user.user': UserTable 'user.user_permission': UserPermissionTable 'user.permission': PermissionTable pet: PetTable } ``` then you can refer to the tables just like you would a normal table: ```ts db.selectFrom('user.user') .where('username', '=', '') // You can also include the full table name .where('user.user.created_at', '>', createdAt) .innerJoin('user.user_permission as up', 'up.user_id', 'user.user.id') .innerJoin('user.permission as p', 'p.id', 'up.permission_id') .selectAll() ``` ## 2 In the multitenant case you have a schema per tenant and you can't add each of them to the database interface, nor would it make sense to do so. In this case you can use the [withSchema](https://kysely-org.github.io/kysely-apidoc/classes/Kysely.html#withSchema) method. The `withSchema` method sets the default schema of all table references that don't explicitly specify a schema: ```ts db.withSchema(tenant) .selectFrom('user') .innerJoin('user_permission as up', 'up.user_id', 'user.id') .innerJoin('public.permission as p', 'p.id', 'up.permission_id') .selectAll() ``` This is the generated SQL assuming `tenant` equals `'acme'`: ```sql select * from "acme"."user" inner join "acme"."user_permission" as "up" on "up"."user_id" = "acme"."user"."id" inner join "public"."permission" as "p" on "p"."id" = "up"."permission_id" ``` In this example we also referred to a shared table `permission` in the `public` schema. Please note that you need to add a `'public.permission': PermissionTable` item in your database schema to be able to refer to the `public.permission` table: ```ts interface Database { // Add your tenant tables without any schema: user: UserTable user_permission: UserPermissionTable // Add schemas and tables you need to explicitly reference like this: 'public.permission': PermissionTable // You can also have other shared tables with or without schemas here. // But keep in mind that if you want to refer to them from a `withSchema` // query, you need the table name with the schema name. pet: PetTable } ``` See the [first case](#1) for more info. ================================================ FILE: site/docs/recipes/0008-deduplicate-joins.md ================================================ # Deduplicate joins When building dynamic queries, you sometimes end up in situations where the same join could be added twice. Consider this query: ```ts async function getPerson( id: number, withPetName: boolean, withPetSpecies: boolean ) { return await db .selectFrom('person') .selectAll('person') .$if(withPetName, (qb) => qb .innerJoin('pet', 'pet.owner_id', 'person.id') .select('pet.name as pet_name') ) .$if(withPetSpecies, (qb) => qb .innerJoin('pet', 'pet.owner_id', 'person.id') .select('pet.species as pet_species') ) .where('person.id', '=', id) .executeTakeFirst() } ``` We have two optional selections `pet_name` and `pet_species`. Both of them require the `pet` table to be joined, but we don't want to add an unnecessary join if both `withPetName` and `withPetSpecies` are `false`. But if both `withPetName` and `withPetSpecies` are `true`, we end up with two identical joins which will cause an error in the database. To prevent the error from happening, you can install the [DeduplicateJoinsPlugin](https://kysely-org.github.io/kysely-apidoc/classes/DeduplicateJoinsPlugin.html). You can either install it globally by providing it in the configuration: ```ts const db = new Kysely({ dialect, plugins: [new DeduplicateJoinsPlugin()], }) ``` or you can use it when needed: ```ts async function getPerson( id: number, withPetName: boolean, withPetSpecies: boolean ) { return await db .withPlugin(new DeduplicateJoinsPlugin()) .selectFrom('person') .selectAll('person') .$if(withPetName, (qb) => qb .innerJoin('pet', 'pet.owner_id', 'person.id') .select('pet.name as pet_name') ) .$if(withPetSpecies, (qb) => qb .innerJoin('pet', 'pet.owner_id', 'person.id') .select('pet.species as pet_species') ) .where('person.id', '=', id) .executeTakeFirst() } ``` You may wonder why this is a plugin and not the default behavior? The reason is that it's surprisingly difficult to detect if two joins are identical. It's trivial for simple joins like the ones in the example, but becomes quite complex with arbitrary joins with nested subqueries etc. There may be corner cases where the `DeduplicateJoinsPlugin` fails and we don't want it to affect people that don't need this deduplication (most people). See [this recipe](/docs/recipes/conditional-selects) if you are wondering why we are using the `$if` method. ================================================ FILE: site/docs/recipes/0009-excessively-deep-types.md ================================================ # Dealing with the `Type instantiation is excessively deep and possibly infinite` error Kysely uses complex type magic to achieve its type safety. This complexity is sometimes too much for TypeScript and you get errors like this: ``` error TS2589: Type instantiation is excessively deep and possibly infinite. ``` In these case you can often use the [$assertType](https://kysely-org.github.io/kysely-apidoc/interfaces/SelectQueryBuilder.html#_assertType) method to help TypeScript a little bit. When you use this method to assert the output type of a query, Kysely can drop the complex output type that consists of multiple nested helper types and replace it with the simple asserted type. Using this method doesn't reduce type safety at all. You have to pass in a type that is structurally equal to the current type. For example having more than 12 `with` statements in a query can lead to the `TS2589` error: ```ts const res = await db .with('w1', (qb) => qb.selectFrom('person').select('first_name as fn1')) .with('w2', (qb) => qb.selectFrom('person').select('first_name as fn2')) .with('w3', (qb) => qb.selectFrom('person').select('first_name as fn3')) .with('w4', (qb) => qb.selectFrom('person').select('first_name as fn4')) .with('w5', (qb) => qb.selectFrom('person').select('first_name as fn5')) .with('w6', (qb) => qb.selectFrom('person').select('first_name as fn6')) .with('w7', (qb) => qb.selectFrom('person').select('first_name as fn7')) .with('w8', (qb) => qb.selectFrom('person').select('first_name as fn8')) .with('w9', (qb) => qb.selectFrom('person').select('first_name as fn9')) .with('w10', (qb) => qb.selectFrom('person').select('first_name as fn10')) .with('w11', (qb) => qb.selectFrom('person').select('first_name as fn11')) .with('w12', (qb) => qb.selectFrom('person').select('first_name as fn12')) .with('w13', (qb) => qb.selectFrom('person').select('first_name as fn13')) .selectFrom(['w1', 'w2', 'w3', 'w4', 'w5', 'w6', 'w7', 'w8', 'w9', 'w10', 'w11', 'w12', 'w13']) .selectAll() .executeTakeFirstOrThrow() ``` But if you simplify one or more of the `with` statements using `$assertType`, you get rid of the error: ```ts const res = await db .with('w1', (qb) => qb.selectFrom('person').select('first_name as fn1')) .with('w2', (qb) => qb.selectFrom('person').select('first_name as fn2')) .with('w3', (qb) => qb.selectFrom('person').select('first_name as fn3')) .with('w4', (qb) => qb.selectFrom('person').select('first_name as fn4')) .with('w5', (qb) => qb.selectFrom('person').select('first_name as fn5')) .with('w6', (qb) => qb.selectFrom('person').select('first_name as fn6')) .with('w7', (qb) => qb.selectFrom('person').select('first_name as fn7')) .with('w8', (qb) => qb.selectFrom('person').select('first_name as fn8')) .with('w9', (qb) => qb.selectFrom('person').select('first_name as fn9')) .with('w10', (qb) => qb.selectFrom('person').select('first_name as fn10')) .with('w11', (qb) => qb.selectFrom('person').select('first_name as fn11')) .with('w12', (qb) => qb .selectFrom('person') .select('first_name as fn12') .$assertType<{ fn12: string }>() ) .with('w13', (qb) => qb .selectFrom('person') .select('first_name as fn13') .$assertType<{ fn13: string }>() ) .selectFrom(['w1', 'w2', 'w3', 'w4', 'w5', 'w6', 'w7', 'w8', 'w9', 'w10', 'w11', 'w12', 'w13']) .selectAll() .executeTakeFirstOrThrow() ``` The type you provide for `$assertType` must be structurally equal to the return type of the subquery. Therefore no type safety is lost. I know what you're thinking: "can't this be done automatically?" No, unfortunately it can't. There's no way to do this using current TypeScript features. Typescript drags along all the parts the type is built with. Even though it could simplify the type into a simple object, it doesn't. We need to explictly tell it to do that. "But there's this `Simplify` helper I've seen and it does exactly what you need". You mean this one: ```ts export type Simplify = { [K in keyof T]: T[K] } & {} ``` While that does simplify the type when you hover over it in your IDE, it doesn't actually drop the complex type underneath. You can try this yourself with the example above. ================================================ FILE: site/docs/recipes/0010-extending-kysely.md ================================================ # Extending kysely In many cases, Kysely doesn't provide a built-in type-safe method for a feature. It's often because adding that feature in a generic way that would work in all use cases is difficult or impossible. In many cases it's better to create little helper functions in your project that suit your use case. Kysely makes this simple. The Kysely API is designed around two interfaces [`Expression`](https://kysely-org.github.io/kysely-apidoc/interfaces/Expression.html) and [`AliasedExpression`](https://kysely-org.github.io/kysely-apidoc/interfaces/AliasedExpression.html). Almost every method accepts values that implement these interfaces and most Kysely internals achieve their "type magic" by implementing them. Most of the time you can create your helpers using the [sql template tag](https://kysely-org.github.io/kysely-apidoc/interfaces/Sql.html) and the `RawBuilder` and `AliasedRawBuilder` class instances it returns, but it's good to first understand how the underlying interfaces they implement, `Expression` and `AliasedExpression`, work. ## Expression [`Expression`](https://kysely-org.github.io/kysely-apidoc/interfaces/Expression.html) is a simple interface that has a type `T` and a single method `toOperationNode()`. `T` tells Kysely's type system the type of the expression. `toOperationNode()` returns instructions on what SQL should be produced once the expression is compiled. Here's an example of a custom expression for `JSON` or `JSONB` values on PostgreSQL: ```ts import { Expression, Kysely, OperationNode, sql } from 'kysely' class JsonValue implements Expression { #value: T constructor(value: T) { this.#value = value } // This is a mandatory getter. You must add it and always return `undefined`. // The return type must always be `T | undefined`. get expressionType(): T | undefined { return undefined } toOperationNode(): OperationNode { const json = JSON.stringify(this.#value) // Most of the time you can use the `sql` template tag to build the returned node. // The `sql` template tag takes care of passing the `json` string as a parameter, alongside the sql string, to the DB. return sql`CAST(${json} AS JSONB)`.toOperationNode() } } ``` Now you can use your new `JsonValue` expression pretty much anywhere _as a value_ in a type-safe way: ```ts interface DB { person: { address: { postalCode: string street: string } } } async function test(db: Kysely) { await db .insertInto('person') .values({ address: new JsonValue({ postalCode: '123456', street: 'Kysely avenue 42', }), }) .execute() await db .selectFrom('person') .selectAll() .where( 'address', '@>', new JsonValue({ postalCode: '123456', street: 'Kysely avenue 42' }) ) .execute() } ``` Most of the time you don't need to create your own classes that implement the `Expression` interface. You can simply wrap the [sql template tag](https://kysely-org.github.io/kysely-apidoc/interfaces/Sql.html) and the `RawBuilder` class instance it returns in a function. `RawBuilder`, like most things in Kysely, implements the `Expression` interface. Our previous example would get simplified into this: ```ts import { Kysely, RawBuilder, sql } from 'kysely' function json(value: T): RawBuilder { return sql`CAST(${JSON.stringify(value)} AS JSONB)` } ``` And you'd use it like this: ```ts interface DB { person: { address: { postalCode: string street: string } } } async function test(db: Kysely) { await db .insertInto('person') .values({ address: json({ postalCode: '123456', street: 'Kysely avenue 42', }), }) .execute() await db .selectFrom('person') .selectAll() .where( 'address', '@>', json({ postalCode: '123456', street: 'Kysely avenue 42' }) ) .execute() } ``` ## AliasedExpression While `Expression` holds the type and compilation instructions of an SQL expression, [`AliasedExpression`](https://kysely-org.github.io/kysely-apidoc/interfaces/AliasedExpression.html) also holds an alias (a name) for that expression. `AliasedExpression` can be used in places where you need a name for the expression, like in a `SELECT` statement or a `FROM` statement. `AliasedExpression` is how kysely is able to infer the name and type of result columns. Let's expand the `JsonValue` example from the [previous section](#expression). We'll add an `as` method for the `JsonValue` class that can be used to turn an `Expression` into an `AliasedExpression`: ```ts import { Expression, AliasedExpression, Kysely, OperationNode, sql, AliasNode, IdentifierNode, } from 'kysely' class JsonValue implements Expression { // ... Methods from the previous example ... as(alias: A): AliasedJsonValue { return new AliasedJsonValue(this, alias) } } class AliasedJsonValue implements AliasedExpression { #expression: Expression #alias: A constructor(expression: Expression, alias: A) { this.#expression = expression this.#alias = alias } get expression(): Expression { return this.#expression } get alias(): A { return this.#alias } toOperationNode(): AliasNode { return AliasNode.create( this.#expression.toOperationNode(), IdentifierNode.create(this.#alias) ) } } ``` And now you can use `JsonValue` in `select` statements too with full type safety: ```ts interface DB { person: { address: { postalCode: string street: string } } } async function test(db: Kysely) { const result = await db .selectFrom('person') .select([new JsonValue({ someValue: 42 }).as('some_object'), 'address']) .where( 'address', '@>', new JsonValue({ postalCode: '123456', street: 'Kysely avenue 42' }) ) .executeTakeFirstOrThrow() console.log(result.some_object.someValue) console.log(result.address.postalCode) } ``` Again, in most cases you don't need to implement your own `AliasedExpression`. `RawBuilder` has a similar `as` method and we can use the three line long `json` function from our previous example: ```ts function json(value: T): RawBuilder { return sql`CAST(${JSON.stringify(value)} AS JSONB)` } ``` ```ts interface DB { person: { address: { postalCode: string street: string } } } async function test(db: Kysely) { const result = await db .selectFrom('person') .select([json({ someValue: 42 }).as('some_object'), 'address']) .where( 'address', '@>', json({ postalCode: '123456', street: 'Kysely avenue 42' }) ) .executeTakeFirstOrThrow() console.log(result.address.postalCode) console.log(result.some_object.someValue) } ``` ## A more complex example Consider this query: ```sql insert into t (t1, t2) select v.v1, j.j2 from (values ($1, $2, $3), ($4, $5, $6)) as v(id, v1, v2) inner join j on v.id = j.vid ``` Kysely doesn't have built-in support for the `values` keyword in this context, but you can create a type-safe helper function like this: ```ts function values, A extends string>( records: R[], alias: A ): AliasedRawBuilder { // Assume there's at least one record and all records // have the same keys. const keys = Object.keys(records[0]) // Transform the records into a list of lists such as // ($1, $2, $3), ($4, $5, $6) const values = sql.join( records.map((r) => sql`(${sql.join(keys.map((k) => r[k]))})`) ) // Create the alias `v(id, v1, v2)` that specifies the table alias // AND a name for each column. const wrappedAlias = sql.ref(alias) const wrappedColumns = sql.join(keys.map(sql.ref)) const aliasSql = sql`${wrappedAlias}(${wrappedColumns})` // Finally create a single `AliasedRawBuilder` instance of the // whole thing. Note that we need to explicitly specify // the alias type using `.as` because we are using a // raw sql snippet as the alias. return sql`(values ${values})`.as(aliasSql) } ``` A lot is going on in this function, but it's all documented in the [sql template tag's documentation.](https://kysely-org.github.io/kysely-apidoc/interfaces/Sql.html) Most of the time a helper like this would return either an instance of `RawBuilder` or `AliasedRawBuilder` and you'd create an instance using the `sql` template tag. You'd return a `RawBuilder` instance when only the data type of a column/table is needed and an `AliasedRawBuilder` when also the name of the column/table is needed. Our example function creates kind of a temporary table, so we need to tell Kysely both the type of the table AND the name of the table. This is how you could now create our query using the `values` helper: ```ts // This could come as an input from somewhere. const records = [ { id: 1, v1: 'foo', v2: 'bar', }, { id: 2, v1: 'baz', v2: 'spam', }, ] db.insertInto('t') .columns(['t1', 't2']) .expression( // The `values` function automatically parses the column types // from the records and you can refer to them through the table // alias `v`. This works because Kysely is able to parse the // AliasedRawBuilder type. db .selectFrom(values(records, 'v')) .innerJoin('j', 'v.id', 'j.vid') .select(['v.v1', 'j.j2']) ) ``` ## Extending using inheritance You usually don't want to do this because of the complexity of the types and TypeScript's limitations when it comes to inheritence and return types. You'll quickly run into problems. Even though Kysely uses classes, it is not designed from the OOP point of view. Classes are used because they are supported natively by TypeScript. They provide private variables and a nice discoverable API. ## Extending using module augmentation > DISCLAIMER: We do not support this method. Use at your own risk. You can override and extend Kysely's builder classes via [Typescript module augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation). The following example adds an `addIdColumn` method to `CreateTableBuilder`, which helps in adding a PostgreSQL UUID primary key column: ```ts declare module 'kysely/dist/cjs/schema/create-table-builder' { interface CreateTableBuilder { addIdColumn( col?: CN ): CreateTableBuilder } } CreateTableBuilder.prototype.addIdColumn = function ( this: CreateTableBuilder, col?: string ) { return this.addColumn(col || 'id', 'uuid', (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`) ) } ``` Now you can use `addIdColumn` seamlessly to create several tables with a uniform primary key definition: ```ts db.schema.createTable('person').addIdColumn().addColumn('name', 'varchar') db.schema.createTable('pet').addColumn('species', 'varchar').addIdColumn() ``` ================================================ FILE: site/docs/recipes/0011-introspecting-relation-metadata.md ================================================ # Introspecting relation metadata Extracting metadata about tables and views from your database schema in runtime is possible using the methods in the `instrospection` property of a `Kysely` instance. The example below uses a PostgreSQL connection to print information about all tables and views found in the database schema: ```ts import { Kysely, PostgresDialect } from 'kysely' import pg from 'pg' const { Pool } = pg async function logDatabaseSchema() { const db = new Kysely({ dialect: new PostgresDialect({ pool: new Pool({ connectionString: process.env.DATABASE_URL, }), }), }) const tables = await db.introspection.getTables() // ^? TableMetadata[] console.log({ tables }) } logDatabaseSchema() ``` For more information check the docs for details on the interfaces [DatabaseIntrospector](https://kysely-org.github.io/kysely-apidoc/interfaces/DatabaseIntrospector.html) and [TableMetadata](https://kysely-org.github.io/kysely-apidoc/interfaces/TableMetadata.html). ================================================ FILE: site/docs/recipes/0012-logging.md ================================================ # Logging It is possible to set up logs for all queries using the `log` property when instantiating `Kysely`. There are 2 ways to configure logging: ## 1. Provide an array with log level/s You can provide an array of log levels to the `log` property when instantiating `Kysely`. When `'query'` is included in the array, `Kysely` will log all executed queries, not including parameter values. When `'error'` is included in the array, `Kysely` will log all errors. ```ts const db = new Kysely({ ... log: ['query', 'error'] ... }); ``` ## 2. Provide a custom logging function You can provide a custom logging function to the `log` property when instantiating `Kysely`. The custom logging function receives a log event as an argument. The `LogEvent` interface is defined as follows: ```ts interface LogEvent { level: 'query' | 'error'; query: CompiledQuery; // this object contains the raw SQL string, parameters, and Kysely's SQL syntax tree that helped output the raw SQL string. queryDurationMillis: number; // the time in milliseconds it took for the query to execute and get a response from the database. error: unknown; // only present if `level` is `'error'`. } ``` Example: ```ts const db = new Kysely({ dialect: new PostgresDialect(postgresConfig), log(event) { if (event.level === "error") { console.error("Query failed : ", { durationMs: event.queryDurationMillis, error: event.error, sql: event.query.sql, params: event.query.parameters.map(maskPII), }); } else { // `'query'` console.log("Query executed : ", { durationMs: event.queryDurationMillis, sql: event.query.sql, params: event.query.parameters.map(maskPII), }); } } }) ``` For more information check the docs for details on the interfaces [KyselyConfig](https://kysely-org.github.io/kysely-apidoc/interfaces/KyselyConfig.html). ================================================ FILE: site/docs/recipes/_category_.json ================================================ { "label": "Recipes", "position": 6, "link": { "type": "generated-index", "description": "A list of guides or recipes explaning how to use various features of the library. These are more advanced topics, make sure you familiriaze yourself with the examples first." } } ================================================ FILE: site/docs/runtimes/_category_.json ================================================ { "label": "Other runtimes", "position": 10, "link": { "type": "generated-index", "description": "Kysely works on the browser, Node.js and Deno. Here are some examples of how to use it." } } ================================================ FILE: site/docs/runtimes/browser.md ================================================ # Browser Kysely also runs in the browser. Here's a minimal example: ```ts import { Kysely, Generated, DummyDriver, SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler, } from 'kysely' interface Person { id: Generated first_name: string last_name: string | null } interface Database { person: Person } const db = new Kysely({ dialect: { createAdapter() { return new SqliteAdapter() }, createDriver() { return new DummyDriver() }, createIntrospector(db: Kysely) { return new SqliteIntrospector(db) }, createQueryCompiler() { return new SqliteQueryCompiler() }, }, }) window.addEventListener('load', () => { const sql = db.selectFrom('person').select('id').compile() const result = document.createElement('span') result.id = 'result' result.innerHTML = sql.sql document.body.appendChild(result) }) ``` ================================================ FILE: site/docs/runtimes/deno.mdx ================================================ --- sidebar_position: 6 title: 'Running on Deno' --- # Running on Deno Kysely doesn't include drivers for Deno, but you can still use Kysely as a query builder or implement your own driver: ```ts // We use jsdeliver to get Kysely from npm. import { DummyDriver, Generated, Kysely, PostgresAdapter, PostgresIntrospector, PostgresQueryCompiler, } from 'https://cdn.jsdelivr.net/npm/kysely/dist/esm/index.js' interface Person { id: Generated first_name: string last_name: string | null } interface Database { person: Person } const db = new Kysely({ dialect: { createAdapter() { return new PostgresAdapter() }, createDriver() { // You need a driver to be able to execute queries. In this example // we use the dummy driver that never does anything. return new DummyDriver() }, createIntrospector(db: Kysely) { return new PostgresIntrospector(db) }, createQueryCompiler() { return new PostgresQueryCompiler() }, }, }) const query = db.selectFrom('person').select('id') const sql = query.compile() console.log(sql.sql) ``` ================================================ FILE: site/docusaurus.config.ts ================================================ import type { Options as PresetClassicOptions, ThemeConfig as PresetClassicThemeConfig, } from '@docusaurus/preset-classic' import type { Config } from '@docusaurus/types' import type { MermaidConfig } from 'mermaid' import { themes } from 'prism-react-renderer' import type { PluginOptions as LLMsTXTPluginOptions } from '@signalwire/docusaurus-plugin-llms-txt' import type { PluginOptions as VercelAnalyticsPluginOptions } from '@docusaurus/plugin-vercel-analytics' export default { baseUrl: '/', favicon: 'img/favicon.ico', i18n: { defaultLocale: 'en', locales: ['en'], }, markdown: { mdx1Compat: { admonitions: false, comments: false, headingIds: false, }, mermaid: true, }, onBrokenAnchors: 'throw', onBrokenLinks: 'throw', onBrokenMarkdownLinks: 'throw', onDuplicateRoutes: 'throw', organizationName: 'kysely-org', plugins: [ [ '@signalwire/docusaurus-plugin-llms-txt', { content: { // https://www.npmjs.com/package/@signalwire/docusaurus-plugin-llms-txt#content-selectors contentSelectors: [ '.theme-doc-markdown', // Docusaurus main content area 'main .container .col', // Bootstrap-style layout 'main .theme-doc-wrapper', // Docusaurus wrapper 'article', // Semantic article element 'main .container', // Broader container 'main', // Fallback to main element '.code-example', ], enableLlmsFullTxt: true, includeGeneratedIndex: false, includePages: true, includeVersionedDocs: false, relativePaths: false, }, depth: 3, onRouteError: 'throw', siteDescription: 'The most powerful type-safe SQL query builder for TypeScript', siteTitle: 'Kysely', } satisfies LLMsTXTPluginOptions, ], [ 'vercel-analytics', { debug: true, mode: 'auto' } satisfies Omit< VercelAnalyticsPluginOptions, 'id' >, ], ], presets: [ [ 'classic', { blog: false, docs: { editUrl: 'https://github.com/kysely-org/kysely/tree/master/site', sidebarPath: require.resolve('./sidebars.js'), }, theme: { customCss: [ require.resolve('./src/css/custom.css'), require.resolve('@radix-ui/colors/sky.css'), require.resolve('@radix-ui/colors/gray.css'), require.resolve('@radix-ui/colors/blue.css'), require.resolve('@radix-ui/colors/green.css'), require.resolve('@radix-ui/colors/yellow.css'), ], }, } satisfies PresetClassicOptions, ], ], projectName: 'kysely', tagline: 'The most powerful type-safe SQL query builder for TypeScript', themeConfig: { algolia: { // Public API key, safe to expose. See https://docusaurus.io/docs/search#using-algolia-docsearch apiKey: 'ebee59ab1b71803be5983f6dbfeea352', appId: 'MDKJWTIJFR', contextualSearch: true, indexName: 'kysely', }, colorMode: { defaultMode: 'dark', disableSwitch: false, respectPrefersColorScheme: true, }, docs: { sidebar: { autoCollapseCategories: true, hideable: true, }, }, footer: { links: [ { items: [ { label: 'Introduction', to: '/docs/intro' }, { label: 'Getting started', to: '/docs/getting-started' }, { label: 'Playground', to: '/docs/playground' }, { label: 'Migrations', to: '/docs/migrations' }, // { label: 'Examples', to: '/docs/category/examples' }, { label: 'Recipes', to: '/docs/category/recipes' }, { label: 'Other runtimes', to: '/docs/category/other-runtimes' }, { label: 'Dialects', to: '/docs/dialects' }, { label: 'Generating types', to: '/docs/generating-types' }, { label: 'Plugin system', to: '/docs/plugins' }, ], title: 'Docs', }, { items: [ { label: 'SELECT', to: '/docs/category/select' }, { label: 'WHERE', to: '/docs/category/where' }, { label: 'JOIN', to: '/docs/category/join' }, { label: 'INSERT', to: '/docs/category/insert' }, { label: 'UPDATE', to: '/docs/category/update' }, { label: 'DELETE', to: '/docs/category/delete' }, { label: 'Transactions', to: '/docs/category/transactions' }, { label: 'CTE', to: '/docs/category/cte' }, ], title: 'Examples', }, { items: [ { label: 'Discord', href: 'https://discord.gg/xyBJ3GwvAm', }, { label: 'Bluesky', href: 'https://bsky.app/profile/kysely.dev', }, ], title: 'Community', }, { items: [ { label: 'GitHub', href: 'https://github.com/kysely-org/kysely', }, { label: 'API docs', href: 'https://kysely-org.github.io/kysely-apidoc/', }, ], title: 'Other', }, { items: [ { html: `Powered by Vercel`, }, ], title: 'Sponsors', }, ], style: 'dark', }, headTags: [ { attributes: { href: 'https://fonts.googleapis.com', rel: 'preconnect', }, tagName: 'link', }, { attributes: { crossOrigin: 'anonymous', href: 'https://fonts.gstatic.com', rel: 'preconnect', }, tagName: 'link', }, { attributes: { as: 'style', onLoad: "this.onload=null;this.rel='stylesheet'", href: 'https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap', rel: 'preload', }, tagName: 'link', }, { attributes: { as: 'image', fetchpriority: 'high', href: '/demo-poster.webp', rel: 'preload', }, tagName: 'link', }, ], mermaid: { options: { sequence: { mirrorActors: false, showSequenceNumbers: true, }, } satisfies MermaidConfig, }, metadata: [ { content: 'Kysely is the most powerful type-safe SQL query builder for TypeScript. Get unparalleled autocompletion and compile-time type safety for complex queries, joins, and subqueries. Used in production by Deno, Maersk, and Cal.com. Modern TypeScript, zero runtime overhead.', name: 'description', }, ], navbar: { items: [ { docId: 'intro', label: 'Docs', position: 'left', type: 'doc', }, { href: 'https://github.com/kysely-org/kysely', label: 'GitHub', position: 'right', }, { href: 'https://kysely-org.github.io/kysely-apidoc', label: 'API docs', position: 'right', }, ], logo: { alt: 'Kysely Logo', height: 32, src: 'img/logo.svg', width: 32, }, style: 'dark', title: 'Kysely', }, prism: { darkTheme: themes.dracula, theme: themes.github, }, } satisfies PresetClassicThemeConfig, themes: ['@docusaurus/theme-mermaid'], title: 'Kysely', url: 'https://kysely.dev', } satisfies Config ================================================ FILE: site/package.json ================================================ { "name": "kysely-site", "version": "0.28.14", "private": true, "scripts": { "docusaurus": "docusaurus", "start": "docusaurus start", "build": "docusaurus build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", "clear": "docusaurus clear", "serve": "docusaurus serve", "write-translations": "docusaurus write-translations", "write-heading-ids": "docusaurus write-heading-ids", "typecheck": "tsc" }, "dependencies": { "@docusaurus/core": "^3.9.2", "@docusaurus/plugin-vercel-analytics": "^3.9.2", "@docusaurus/preset-classic": "^3.9.2", "@docusaurus/theme-common": "^3.9.2", "@docusaurus/theme-mermaid": "^3.9.2", "@mdx-js/react": "^3.1.1", "@radix-ui/colors": "^3.0.0", "clsx": "^2.1.1", "prism-react-renderer": "^2.3.1", "react": "^19.2.4", "react-dom": "^19.2.4" }, "devDependencies": { "@docusaurus/module-type-aliases": "^3.9.2", "@docusaurus/tsconfig": "^3.9.2", "@docusaurus/types": "^3.9.2", "@signalwire/docusaurus-plugin-llms-txt": "^1.2.2", "@types/react": "^19.2.14", "mermaid": "^11.13.0", "typescript": "^5.9.3" }, "browserslist": { "production": [ ">0.5%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "engines": { "node": ">=20.0" }, "packageManager": "pnpm@10.16.1+sha512.0e155aa2629db8672b49e8475da6226aa4bdea85fdcdfdc15350874946d4f3c91faaf64cbdc4a5d1ab8002f473d5c3fcedcd197989cf0390f9badd3c04678706" } ================================================ FILE: site/sidebars.js ================================================ /** * Creating a sidebar enables you to: - create an ordered group of docs - render a sidebar for each doc of that group - provide next/previous navigation The sidebars can be generated from the filesystem, or explicitly defined here. Create as many sidebars as you want. */ // @ts-check /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ const sidebars = { // By default, Docusaurus generates a sidebar from the docs folder structure tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }], // But you can create a sidebar manually /* tutorialSidebar: [ 'intro', 'hello', { type: 'category', label: 'Tutorial', items: ['tutorial-basics/create-a-document'], }, ], */ } module.exports = sidebars ================================================ FILE: site/src/components/DemoVideo.module.css ================================================ .videoContainer { background: transparent; max-width: 832px; position: relative; width: 100%; } .videoContainer video { background: transparent; border: none; border-radius: 24px; display: block; height: auto; margin: 0; max-height: 610px; outline: none; padding: 0; width: 100%; } ================================================ FILE: site/src/components/DemoVideo.tsx ================================================ import { useEffect, useRef } from 'react' import styles from './DemoVideo.module.css' export function DemoVideo() { const videoRef = useRef(null) useEffect(() => { const { current: video } = videoRef if (!video) { return } video.load() const handleCanPlay = () => { video.play().catch(() => {}) video.removeEventListener('canplay', handleCanPlay) } video.addEventListener('canplay', handleCanPlay) return () => { video.removeEventListener('canplay', handleCanPlay) } }, []) return (
) } ================================================ FILE: site/src/components/Playground.module.css ================================================ .playground { border: 1px solid var(--gray-3); border-radius: 7px; min-height: 600px; width: 100%; } .visuallyHidden { border: 0; clip: rect(0, 0, 0, 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } ================================================ FILE: site/src/components/Playground.tsx ================================================ import { useColorMode } from '@docusaurus/theme-common' import { useEffect, useRef, useState } from 'react' import styles from './Playground.module.css' import { GENERATED_PLAYGROUND_EXAMPLE_TYPES } from './playground-example-types' import clsx from 'clsx' import CodeBlock from '@theme/CodeBlock' export function Playground(props: PlaygroundProps) { const src = useSrc(props) const [loadFailed, setLoadFailed] = useState(false) const iframeRef = useRef(null) useEffect(() => { const { current: iframe } = iframeRef if (!iframe) { return } let failTimer: NodeJS.Timeout const handleLoad = () => { clearTimeout(failTimer) } iframe.addEventListener('load', handleLoad) failTimer = setTimeout(() => { setLoadFailed(true) }, 2_000) return () => { iframe.removeEventListener('load', handleLoad) clearTimeout(failTimer) } }, [src]) return ( <> {!loadFailed && (