安装

¥Installation

Knex 可以在 Node.JS 和浏览器中用作 SQL 查询构建器,但受限于 WebSQL 的约束(例如无法删除表或读取模式)。强烈建议不要在浏览器中编写 SQL 查询以在服务器上执行,因为这可能会导致严重的安全漏洞。在 WebSQL 之外构建的浏览器主要用于学习目的 - 例如,你可以弹出打开控制台并使用 knex 对象在此页面上构建查询。

¥Knex can be used as an SQL query builder in both Node.JS and the browser, limited to WebSQL's constraints (like the inability to drop tables or read schemas). Composing SQL queries in the browser for execution on the server is highly discouraged, as this can be the cause of serious security vulnerabilities. The browser builds outside of WebSQL are primarily for learning purposes - for example, you can pop open the console and build queries on this page using the knex object.

Node.js

Knex 的主要目标环境是 Node.js,你需要安装 knex 库,然后安装相应的数据库库:pg 适用于 PostgreSQL、CockroachDB 和 Amazon Redshift,pg-native 适用于带有原生 C++ 的 PostgreSQL libpq 绑定(需要安装 PostgresSQL 才能链接),mysql 适用于 MySQL 或 MariaDB,sqlite3 适用于 SQLite3,或 tedious 适用于 MSSQL。

¥The primary target environment for Knex is Node.js, you will need to install the knex library, and then install the appropriate database library: pg for PostgreSQL, CockroachDB and Amazon Redshift, pg-native for PostgreSQL with native C++ libpq bindings (requires PostgresSQL installed to link against), mysql for MySQL or MariaDB, sqlite3 for SQLite3, or tedious for MSSQL.

$ npm install knex --save

# Then add one of the following (adding a --save) flag:
$ npm install pg
$ npm install pg-native
$ npm install sqlite3
$ npm install better-sqlite3
$ npm install mysql
$ npm install mysql2
$ npm install oracledb
$ npm install tedious

如果你想使用 CockroachDB 或 Redshift 实例,你可以使用 pg 驱动程序。

¥If you want to use CockroachDB or Redshift instance, you can use the pg driver.

如果你想使用 MariaDB 实例,可以使用 mysql 驱动程序。

¥If you want to use a MariaDB instance, you can use the mysql driver.

浏览器

¥Browser

Knex 可以使用 JavaScript 构建工具(例如 browserifywebpack)来构建。事实上,本文档使用了 包括膝关节.1 版本的 webpack 构建。查看此页面上的源代码以查看正在运行的浏览器构建(全局 knex 变量)。

¥Knex can be built using a JavaScript build tool such as browserify or webpack. In fact, this documentation uses a webpack build which includes knex. View source on this page to see the browser build in-action (the global knex variable).

配置选项

¥Configuration Options

knex 模块本身是一个函数,它接受 Knex 的配置对象,接受一些参数。client 参数是必需的,它确定库将使用哪个客户端适配器。

¥The knex module is itself a function which takes a configuration object for Knex, accepting a few parameters. The client parameter is required and determines which client adapter will be used with the library.

const knex = require('knex')({
  client: 'mysql',
  connection: {
    host : '127.0.0.1',
    port : 3306,
    user : 'your_database_user',
    password : 'your_database_password',
    database : 'myapp_test'
  }
});

连接选项直接传递到适当的数据库客户端以创建连接,并且可以是对象、连接字符串或返回对象的函数:

¥The connection options are passed directly to the appropriate database client to create the connection, and may be either an object, a connection string, or a function returning an object:

PostgreSQL

Knex 的 PostgreSQL 客户端允许你使用附加选项 "searchPath" 自动设置每个连接的初始搜索路径,如下所示。

¥Knex's PostgreSQL client allows you to set the initial search path for each connection automatically using an additional option "searchPath" as shown below.

const pg = require('knex')({
  client: 'pg',
  connection: process.env.PG_CONNECTION_STRING,
  searchPath: ['knex', 'public'],
});

使用 PostgreSQL 驱动程序时,实例化 Knex 配置对象的另一种使用模式可能是使用 connection: {} 对象详细信息来指定各种标志,例如启用 SSL、连接字符串和单个连接配置字段,所有这些都在同一对象中。考虑以下示例:

¥When using the PostgreSQL driver, another usage pattern for instantiating the Knex configuration object could be to use a connection: {} object details to specify various flags such as enabling SSL, a connection string, and individual connection configuration fields all in the same object. Consider the following example:

PostgreSQL

如果 connectionString 是最高优先级使用。如果未指定,则将使用各个连接字段(hostport 等)确定连接详细信息,最后将基于 config["DB_SSL"] 的真值启用 SSL 配置,该值也接受自签名证书。

¥If connectionString is highest priority to use. If left unspecified then connection details will be determined using the individual connection fields (host, port, etc), and finally an SSL configuration will be enabled based on a truthy value of config["DB_SSL"] which will also accept self-signed certificates.

const pg = require('knex')({
  client: 'pg',
  connection: {
    connectionString: config.DATABASE_URL,
    host: config["DB_HOST"],
    port: config["DB_PORT"],
    user: config["DB_USER"],
    database: config["DB_NAME"],
    password: config["DB_PASSWORD"],
    ssl: config["DB_SSL"] ? { rejectUnauthorized: false } : false,
  }
});

以下是实例化 Knex 配置对象的 SQLite 使用模式:

¥The following are SQLite usage patterns for instantiating the Knex configuration object:

SQLite3 或更好的 SQLite3

当你使用 SQLite3 或 Better-SQLite3 适配器时,需要文件名,而不是网络连接。例如:

¥When you use the SQLite3 or Better-SQLite3 adapter, there is a filename required, not a network connection. For example:

const knex = require('knex')({
  client: 'sqlite3', // or 'better-sqlite3'
  connection: {
    filename: "./mydb.sqlite"
  }
});

你还可以通过提供 :memory: 作为文件名来运行带有内存数据库的 SQLite3 或 Better-SQLite3。例如:

¥You can also run either SQLite3 or Better-SQLite3 with an in-memory database by providing :memory: as the filename. For example:

const knex = require('knex')({
  client: 'sqlite3', // or 'better-sqlite3'
  connection: {
    filename: ":memory:"
  }
});

SQLite3

当你使用 SQLite3 适配器时,你可以设置用于打开连接的标志。例如:

¥When you use the SQLite3 adapter, you can set flags used to open the connection. For example:

const knex = require('knex')({
  client: 'sqlite3',
  connection: {
    filename: "file:memDb1?mode=memory&cache=shared",
    flags: ['OPEN_URI', 'OPEN_SHAREDCACHE']
  }
});

Better-SQLite3

使用 Better-SQLite3 适配器,你可以使用 options.nativeBinding 指定适配器编译的 C++ 插件的位置。当你的构建系统对文件进行大量转换/重定位时,这会很有用。

¥With the Better-SQLite3 adapter, you can use options.nativeBinding to specify the location of the adapter's compiled C++ addon. This can be useful when your build system does a lot of transformation/relocation of files.

使用示例:

¥Example use:

const knex = require('knex')({
  client: 'better-sqlite3',
  connection: {
    filename: ":memory:",
    options: {
      nativeBinding: "/path/to/better_sqlite3.node",
    },
  },
});

此外,你可以使用 options.readonly 以只读模式打开数据库:

¥Additionally, you can open the database in read-only mode using options.readonly:

const knex = require('knex')({
  client: 'better-sqlite3',
  connection: {
    filename: "/path/to/db.sqlite3",
    options: {
      readonly: true,
    },
  },
});

有关详细信息,请参阅有关数据库连接选项的 Better-SQLite3 文档

¥For more information, see the Better-SQLite3 documentation on database connection options.

MSSQL

当你使用 MSSQL 客户端时,你可以定义 mapBinding 函数来定义你自己的逻辑,用于从 knex 查询参数映射到 tedious 类型。从函数返回未定义将回退到默认映射。

¥When you use the MSSQL client, you can define a mapBinding function to define your own logic for mapping from knex query parameters to tedious types. Returning undefined from the function will fallback to the default mapping.

import { TYPES } from 'tedious';

const knex = require('knex')({
  client: 'mssql',
  connection: {
    options: {
      mapBinding: value => {
        // bind all strings to varchar instead of nvarchar
        if (typeof value === 'string') {
          return {
            type: TYPES.VarChar,
            value
          };
        }

        // allow devs to pass tedious type at query time
        if (value != null && value.type) {
          return {
            type: value.type,
            value: value.value
          };
        }

        // undefined is returned; falling back to default mapping function
      }
    }
  }
});

信息

当你使用 PostgreSQL 适配器连接非标准数据库时,可以在 knex 配置中添加数据库版本。

¥The database version can be added in knex configuration, when you use the PostgreSQL adapter to connect a non-standard database.

const knex = require('knex')({
  client: 'pg',
  version: '7.2',
  connection: {
    host : '127.0.0.1',
    port : 5432,
    user : 'your_database_user',
    password : 'your_database_password',
    database : 'myapp_test'
  }
});
const knex = require('knex')({
  client: 'mysql',
  version: '5.7',
  connection: {
    host : '127.0.0.1',
    port : 3306,
    user : 'your_database_user',
    password : 'your_database_password',
    database : 'myapp_test'
  }
});

信息

当使用像 knex-aurora-data-api-client 这样的自定义 PostgreSQL 客户端时,你可以明确声明它是否支持 jsonb 列类型

¥When using a custom PostgreSQL client like knex-aurora-data-api-client, you can explicitly state if it supports jsonb column types

const knex = require('knex')({
    client: require('knex-aurora-data-api-client').postgres,
    connection: { resourceArn, secretArn, database: `mydb` },
    version: 'data-api',
    jsonbSupport: true
})

可以使用函数来动态确定连接配置。该函数不接收任何参数,并返回配置对象或配置对象的 promise。

¥A function can be used to determine the connection configuration dynamically. This function receives no parameters, and returns either a configuration object or a promise for a configuration object.

const knex = require('knex')({
  client: 'sqlite3',
  connection: () => ({
    filename: process.env.SQLITE_FILENAME
  })
});

默认情况下,通过函数接收的配置对象会被缓存并重用于所有连接。要更改此行为,可以返回 expirationChecker 函数作为配置对象的一部分。在尝试创建新连接之前会咨询 expirationChecker,如果它返回 true,则会检索新的配置对象。例如,要使用具有有限生命周期的身份验证令牌:

¥By default, the configuration object received via a function is cached and reused for all connections. To change this behavior, an expirationChecker function can be returned as part of the configuration object. The expirationChecker is consulted before trying to create new connections, and in case it returns true, a new configuration object is retrieved. For example, to work with an authentication token that has a limited lifespan:

const knex = require('knex')({
  client: 'postgres',
  connection: async () => {
    const { 
      token, 
      tokenExpiration 
    } = await someCallToGetTheToken();

    return {
      host : 'your_host',
      port : 5432,
      user : 'your_database_user',
      password : token,
      database : 'myapp_test',
      expirationChecker: () => {
        return tokenExpiration <= Date.now();
      }
    };
  }
});

你还可以通过 unix 域套接字进行连接,这将忽略主机和端口。

¥You can also connect via a unix domain socket, which will ignore host and port.

const knex = require('knex')({
  client: 'mysql',
  connection: {
    socketPath : '/path/to/socket.sock',
    user : 'your_database_user',
    password : 'your_database_password',
    database : 'myapp_test'
  }
});

userParams 是一个可选参数,允许你传递可通过 knex.userParams 属性访问的任意参数:

¥userParams is an optional parameter that allows you to pass arbitrary parameters which will be accessible via knex.userParams property:

const knex = require('knex')({
  client: 'mysql',
  connection: {
    host : '127.0.0.1',
    port : 3306,
    user : 'your_database_user',
    password : 'your_database_password',
    database : 'myapp_test'
  },
  userParams: {
    userParam1: '451'
  }
});

初始化库通常只应在应用中发生一次,因为它为当前数据库创建连接池,因此你应该在整个库中使用从初始化调用返回的实例。

¥Initializing the library should normally only ever happen once in your application, as it creates a connection pool for the current database, you should use the instance returned from the initialize call throughout your library.

指定你感兴趣的特定 SQL 风格的客户端。

¥Specify the client for the particular flavour of SQL you are interested in.

const pg = require('knex')({client: 'pg'});

knex('table')
  .insert({a: 'b'})
  .returning('*')
  .toString();
// "insert into "table" ("a") values ('b')"

pg('table')
  .insert({a: 'b'})
  .returning('*')
  .toString();
// "insert into "table" ("a") values ('b') returning *"

withUserParams

如果你想获取具有自定义参数的副本(具有相同的连接)(例如,使用不同的参数执行相同的迁移),你可以在 Knex 实例上调用方法 withUserParams

¥You can call method withUserParams on a Knex instance if you want to get a copy (with same connections) with custom parameters (e. g. to execute same migrations with different parameters)

const knex = require('knex')({
  // Params
});

const knexWithParams = knex.withUserParams({ 
  customUserParam: 'table1'
});
const customUserParam = knexWithParams
  .userParams
  .customUserParam;

debug

在初始化对象上传递 debug: true 标志将为所有查询打开 debugging

¥Passing a debug: true flag on your initialization object will turn on debugging for all queries.

asyncStackTraces

在初始化对象上传递 asyncStackTraces: true 标志将为所有查询构建器、原始查询和模式构建器打开堆栈跟踪捕获。当数据库驱动程序返回错误时,会抛出先前捕获的堆栈跟踪,而不是新的堆栈跟踪。这有助于减轻 node.js/V8 中 await 的默认行为,该行为会破坏堆栈。其性能开销较小,因此建议仅用于开发。默认关闭。

¥Passing an asyncStackTraces: true flag on your initialization object will turn on stack trace capture for all query builders, raw queries and schema builders. When a DB driver returns an error, this previously captured stack trace is thrown instead of a new one. This helps to mitigate default behaviour of await in node.js/V8 which blows the stack away. This has small performance overhead, so it is advised to use only for development. Turned off by default.

pool

配置创建的客户端使用 tarn.js 库初始化连接池。该连接池对于 MySQL 和 PG 库有一个默认设置 min: 2, max: 10,对于 sqlite3 有一个默认设置(由于在单个文件上使用多个连接的问题)。要更改池的配置设置,请将 pool 选项作为初始化块中的键之一传递。

¥The client created by the configuration initializes a connection pool, using the tarn.js library. This connection pool has a default setting of a min: 2, max: 10 for the MySQL and PG libraries, and a single connection for sqlite3 (due to issues with utilizing multiple connections on a single file). To change the config settings for the pool, pass a pool option as one of the keys in the initialize block.

请注意,仅出于历史原因,min 的默认值为 2。尽管 tarn 的默认空闲连接超时为 30 秒(仅当活动连接数超过 min 个时才应用),但它可能会导致旧的连接问题。建议设置 min: 0,以便可以终止所有空闲连接。

¥Note that the default value of min is 2 only for historical reasons. It can result in problems with stale connections, despite tarn's default idle connection timeout of 30 seconds, which is only applied when there are more than min active connections. It is recommended to set min: 0 so all idle connections can be terminated.

查看 tarn.js 库以获取更多信息。

¥Checkout the tarn.js library for more information.

const knex = require('knex')({
  client: 'mysql',
  connection: {
    host : '127.0.0.1',
    port : 3306,
    user : 'your_database_user',
    password : 'your_database_password',
    database : 'myapp_test'
  },
  pool: { min: 0, max: 7 }
});

如果你需要显式拆除连接池,你可以使用 knex.destroy([callback])。你可以通过传递回调或作为 promise 链接来使用 knex.destroy,但不能同时使用两者。要手动初始化已销毁的连接池,可以使用 knex.initialize([config]),如果没有传递配置,它将使用第一个使用的 knex 配置。

¥If you ever need to explicitly teardown the connection pool, you may use knex.destroy([callback]). You may use knex.destroy by passing a callback, or by chaining as a promise, just not both. To manually initialize a destroyed connection pool, you may use knex.initialize([config]), if no config is passed, it will use the first knex configuration used.

afterCreate

当池从数据库服务器获取新连接时,将调用 afterCreate 回调(rawDriverConnection,done)。必须调用 did(err, connection) 回调,knex 才能决定连接是否正常或是否应立即从池中丢弃。

¥afterCreate callback (rawDriverConnection, done) is called when the pool aquires a new connection from the database server. done(err, connection) callback must be called for knex to be able to decide if the connection is ok or if it should be discarded right away from the pool.

const knex = require('knex')({
  client: 'pg',
  connection: {/*...*/},
  pool: {
    afterCreate: function (conn, done) {
      // in this example we use pg driver's connection API
      conn.query('SET timezone="UTC";', function (err) {
        if (err) {
          // first query failed, 
          // return error and don't try to make next query
          done(err, conn);
        } else {
          // do the second query...
          conn.query(
            'SELECT set_limit(0.01);', 
            function (err) {
              // if err is not falsy, 
              //  connection is discarded from pool
              // if connection aquire was triggered by a 
              // query the error is passed to query promise
              done(err, conn);
            });
        }
      });
    }
  }
});

acquireConnectionTimeout

acquireConnectionTimeout 默认为 60000ms,用于确定当无法获取连接时 knex 在抛出超时错误之前应等待多长时间。造成这种情况的最常见原因是用完所有事务连接池,然后在池仍满时尝试在事务之外运行查询。抛出的错误将提供有关连接所针对的查询的信息,以简化查找罪魁祸首的工作。

¥acquireConnectionTimeout defaults to 60000ms and is used to determine how long knex should wait before throwing a timeout error when acquiring a connection is not possible. The most common cause for this is using up all the pool for transaction connections and then attempting to run queries outside of transactions while the pool is still full. The error thrown will provide information on the query the connection was for to simplify the job of locating the culprit.

const knex = require('knex')({
  client: 'pg',
  connection: {/*...*/},
  pool: {/*...*/},
  acquireConnectionTimeout: 10000
});

fetchAsString

由 Oracledb 使用。类型数组。有效类型为 'DATE'、'NUMBER' 和 'CLOB'。当查询具有指定类型之一的任何列时,列数据将作为字符串而不是默认表示形式返回。

¥Utilized by Oracledb. An array of types. The valid types are 'DATE', 'NUMBER' and 'CLOB'. When any column having one of the specified types is queried, the column data is returned as a string instead of the default representation.

const knex = require('knex')({
  client: 'oracledb',
  connection: {/*...*/},
  fetchAsString: [ 'number', 'clob' ]
});

migrations

为了方便起见,可以在初始化库时指定任何迁移配置。请阅读 迁移 部分以获取更多信息和配置选项的完整列表。

¥For convenience, any migration configuration may be specified when initializing the library. Read the Migrations section for more information and a full list of configuration options.

const knex = require('knex')({
  client: 'mysql',
  connection: {
    host : '127.0.0.1',
    port : 3306,
    user : 'your_database_user',
    password : 'your_database_password',
    database : 'myapp_test'
  },
  migrations: {
    tableName: 'migrations'
  }
});

postProcessResponse

用于在将返回的行传递给用户之前修改它们的钩子。例如,可以使用此钩子对返回的列进行 Snake_case -> CamelCase 转换。仅当通过 queryContext 配置查询构建器实例时,queryContext 才可用。

¥Hook for modifying returned rows, before passing them forward to user. One can do for example snake_case -> camelCase conversion for returned columns with this hook. The queryContext is only available if configured for a query builder instance via queryContext.

const knex = require('knex')({
  client: 'mysql',
  // overly simplified snake_case -> camelCase converter
  postProcessResponse: (result, queryContext) => {
    // TODO: add special case for raw results 
    // (depends on dialect)
    if (Array.isArray(result)) {
      return result.map(row => convertToCamel(row));
    } else {
      return convertToCamel(result);
    }
  }
});

wrapIdentifier

Knex 支持将标识符名称自动转换为每种方言的带引号的版本。例如,PostgreSQL 的 'Table.columnName as foo' 将转换为 "表"."columnName" 作为 "foo"。

¥Knex supports transforming identifier names automatically to quoted versions for each dialect. For example 'Table.columnName as foo' for PostgreSQL is converted to "Table"."columnName" as "foo".

使用 wrapIdentifier,人们可以覆盖标识符的转换方式。它可用于覆盖默认功能,例如帮助进行 camelCase -> snake_case 转换。

¥With wrapIdentifier one may override the way how identifiers are transformed. It can be used to override default functionality and for example to help doing camelCase -> snake_case conversion.

转换函数 wrapIdentifier(value, dialectImpl, context): string 将标识符的每个部分获取为单个 value,即来自方言实现和 queryContext 的原始转换函数,仅当通过 builder.queryContext 为查询构建器实例配置以及通过 schema.queryContexttable.queryContext 为架构构建器实例配置时,该函数才可用。例如,使用查询构建器,knex('table').withSchema('foo').select('table.field as otherName').where('id', 1) 将针对以下值 'table''foo''table''field''otherName''id' 调用 wrapIdentifier 转换器。

¥Conversion function wrapIdentifier(value, dialectImpl, context): string gets each part of the identifier as a single value, the original conversion function from the dialect implementation and the queryContext, which is only available if configured for a query builder instance via builder.queryContext, and for schema builder instances via schema.queryContext or table.queryContext. For example, with the query builder, knex('table').withSchema('foo').select('table.field as otherName').where('id', 1) will call wrapIdentifier converter for following values 'table', 'foo', 'table', 'field', 'otherName' and 'id'.

const knex = require('knex')({
  client: 'mysql',
  // overly simplified camelCase -> snake_case converter
  wrapIdentifier: (
    value, 
    origImpl, 
    queryContext
  ) => origImpl(convertToSnakeCase(value))
});

log

Knex 包含一些内部日志函数,用于在适用时打印警告、错误、弃用和调试信息。这些日志函数通常记录到控制台,但可以使用日志选项并提供替代函数来覆盖。不同的日志函数可用于单独的 knex 实例。

¥Knex contains some internal log functions for printing warnings, errors, deprecations, and debug information when applicable. These log functions typically log to the console, but can be overwritten using the log option and providing alternative functions. Different log functions can be used for separate knex instances.

const knex = require('knex')({
  log: {
    warn(message) {
    },
    error(message) {
    },
    deprecate(message) {
    },
    debug(message) {
    },
  }
});

compileSqlOnError

Knex 会在出现查询错误时生成错误消息。默认情况下,Knex 将已编译的 SQL (SELECT * FROM users WHERE password = 'myPassword') 添加到错误消息中。通过将 compileSqlOnError 设置为 false,可以将其更改为参数化 SQL (SELECT * FROM users WHERE password = ?)。

¥Knex builds an error message in case of query error. By default Knex adds compiled SQL (SELECT * FROM users WHERE password = 'myPassword') to the error message. This can be changed to parameterized SQL (SELECT * FROM users WHERE password = ?) by setting compileSqlOnError to false.

const knex = require('knex')({
  compileSqlOnError: false
});

TypeScript

虽然 knex 是用 JavaScript 编写的,但可以使用官方支持的 TypeScript 绑定(在 knex npm 包中)。

¥While knex is written in JavaScript, officially supported TypeScript bindings are available (within the knex npm package).

但值得注意的是,TypeScript 支持目前已尽最大努力。Knex 具有非常灵活的 API,并非所有使用模式都可以进行类型检查,在大多数情况下,我们会选择灵活性。特别是,缺乏类型错误目前并不能保证生成的查询是正确的,因此即使你使用 TypeScript,也建议为其编写测试。

¥However it is to be noted that TypeScript support is currently best-effort. Knex has a very flexible API and not all usage patterns can be type-checked and in most such cases we err on the side of flexibility. In particular, lack of type errors doesn't currently guarantee that the generated queries will be correct and therefore writing tests for them is recommended even if you are using TypeScript.

许多 API 都接受 TRecordTResult 类型参数,使用它们我们可以分别指定数据库表中行的类型和查询结果的类型。当使用 VSCode 等支持 TypeScript 的编辑器时,这对于自动补齐很有帮助。

¥Many of the APIs accept TRecord and TResult type parameters, using which we can specify the type of a row in the database table and the type of the result of the query respectively. This is helpful for auto-completion when using TypeScript-aware editors like VSCode.

为了减少样板文件并添加推断类型,你可以在 'knex/types/tables' 模块中扩充 Tables 接口。

¥To reduce boilerplate and add inferred types, you can augment Tables interface in 'knex/types/tables' module.

import { Knex } from 'knex';

declare module 'knex/types/tables' {
  interface User {
    id: number;
    name: string;
    created_at: string;
    updated_at: string;
  }
  
  interface Tables {
    // This is same as specifying `knex<User>('users')`
    users: User;
    // For more advanced types, you can specify separate type
    // for base model, "insert" type and "update" type.
    // But first: notice that if you choose to use this, 
    // the basic typing showed above can be ignored.
    // So, this is like specifying
    //    knex
    //    .insert<{ name: string }>({ name: 'name' })
    //    .into<{ name: string, id: number }>('users')
    users_composite: Knex.CompositeTableType<
      // This interface will be used for return type and 
      // `where`, `having` etc where full type is required 
      User,
      // Specifying "insert" type will also make sure
      // data matches interface in full. Meaning
      // if interface is `{ a: string, b: string }`,
      // `insert({ a: '' })` will complain about missing fields.
      // 
      // For example, this will require only "name" field when inserting
      // and make created_at and updated_at optional.
      // And "id" can't be provided at all.
      // Defaults to "base" type.
      Pick<User, 'name'> & Partial<Pick<User, 'created_at' | 'updated_at'>>,
      // This interface is used for "update()" calls.
      // As opposed to regular specifying interface only once,
      // when specifying separate update interface, user will be
      // required to match it  exactly. So it's recommended to
      // provide partial interfaces for "update". Unless you want to always
      // require some field (e.g., `Partial<User> & { updated_at: string }`
      // will allow updating any field for User but require updated_at to be
      // always provided as well.
      // 
      // For example, this wil allow updating all fields except "id".
      // "id" will still be usable for `where` clauses so
      //      knex('users_composite')
      //      .update({ name: 'name2' })
      //      .where('id', 10)`
      // will still work.
      // Defaults to Partial "insert" type
      Partial<Omit<User, 'id'>>
    >;
  }
}

当 TypeScript 配置为使用现代模块解析设置(node16nodenext 等)时,编译器期望声明的模块名称以 .js 文件类型结尾。你需要按如下方式声明推断类型:

¥When TypeScript is configured to use a modern module resolution setting (node16, nodenext, etc.), the compiler expects that the declared module name ends with a .js file type. You will need to declare your inferred types as follows instead:

// The trailing `.js` is required by the TypeScript compiler in certain configs:
declare module 'knex/types/tables.js' { // <----- Different module path!!!
  interface Tables {
    // ...
  }
}