diff --git a/types/koa/index.d.ts b/types/koa/index.d.ts index 6dee5d822092a5..2da645b512f9f5 100644 --- a/types/koa/index.d.ts +++ b/types/koa/index.d.ts @@ -465,7 +465,7 @@ declare class Application< * @param {number} [options.subdomainOffset] Subdomain offset * @param {string} [options.proxyIpHeader] Proxy IP header, defaults to X-Forwarded-For * @param {number} [options.maxIpsCount] Max IPs read from proxy IP header, default to 0 (means infinity) - * @param {boolean} [options.asyncLocalStorage] Enable AsyncLocalStorage + * @param {boolean|AsyncLocalStorage} [options.asyncLocalStorage] Pass `true` or an instance of `AsyncLocalStorage` to enable async local storage */ constructor(options?: { env?: string | undefined; @@ -474,7 +474,7 @@ declare class Application< subdomainOffset?: number | undefined; proxyIpHeader?: string | undefined; maxIpsCount?: number | undefined; - asyncLocalStorage?: boolean | undefined; + asyncLocalStorage?: boolean | AsyncLocalStorage | undefined; }); /** diff --git a/types/koa/test/constructor.ts b/types/koa/test/constructor.ts index eb3c7debc44b04..87fd4ce56fec9c 100644 --- a/types/koa/test/constructor.ts +++ b/types/koa/test/constructor.ts @@ -1,19 +1,41 @@ import Koa = require("koa"); +import assert from "node:assert/strict"; +import { AsyncLocalStorage } from "node:async_hooks"; -const app = new Koa({ - env: "abc", - keys: ["im a newer secret", "i like turtle"], - proxy: true, - subdomainOffset: 2, - proxyIpHeader: "XYZ-Forwarded-For", - maxIpsCount: 2, - asyncLocalStorage: true, -}); +{ + const app = new Koa({ + env: "abc", + keys: ["im a newer secret", "i like turtle"], + proxy: true, + subdomainOffset: 2, + proxyIpHeader: "XYZ-Forwarded-For", + maxIpsCount: 2, + asyncLocalStorage: true, + }); -app.use(ctx => { - ctx.body = "Hello World"; -}); + app.use(ctx => { + ctx.body = "Hello World"; + }); -app.listen(3000); + app.listen(3000); +} -const server = app.listen(); +{ + const asyncLocalStorage = new AsyncLocalStorage(); + const app = new Koa({ asyncLocalStorage }); + + assert(app.currentContext === undefined); + + app.use(async (ctx, next) => { + callSomeFunction(); + ctx.body = "ok"; + await next(); + }); + + function callSomeFunction() { + const ctx = asyncLocalStorage.getStore(); + assert(ctx === app.currentContext); + } + + app.listen(); +} diff --git a/types/sql.js/index.d.ts b/types/sql.js/index.d.ts index ad4e9c7e996c51..bab3e65b8b15f7 100644 --- a/types/sql.js/index.d.ts +++ b/types/sql.js/index.d.ts @@ -7,6 +7,14 @@ type ParamsCallback = (obj: ParamsObject) => void; type SqlJsConfig = Partial; type BindParams = SqlValue[] | ParamsObject | null; +type UpdateHookOperation = "insert" | "update" | "delete"; +type UpdateHookCallback = ( + operation: UpdateHookOperation, + database: string, + table: string, + rowId: number, +) => void; + interface QueryExecResult { columns: string[]; values: SqlValue[][]; @@ -149,6 +157,36 @@ declare class Database { * `;`). This limitation does not apply to params as an object. */ run(sql: string, params?: BindParams): Database; + + /** Registers an update hook with SQLite. + * + * Every time a row is changed by whatever means, the callback is called + * once with the change (`'insert'`, `'update'` or `'delete'`), the database + * name and table name where the change happened and the + * [rowid](https://www.sqlite.org/rowidtable.html) + * of the row that has been changed. + * + * The rowid is cast to a plain number. If it exceeds + * `Number.MAX_SAFE_INTEGER` (2^53 - 1), an error will be thrown. + * + * **Important notes:** + * - The callback **MUST NOT** modify the database in any way + * - Only a single callback can be registered at a time + * - Unregister the callback by passing `null` + * - Not called for some updates like `ON REPLACE CONFLICT` and `TRUNCATE` + * (a `DELETE FROM` without a `WHERE` clause) + * + * See SQLite documentation on + * [sqlite3_update_hook](https://www.sqlite.org/c3ref/update_hook.html) + * for more details + * + * @param callback + * - Callback to be executed when a row changes. Takes the type of change, + * the name of the database, the name of the table, and the row id of the + * changed row. + * - Set to `null` to unregister. + */ + updateHook(callback: UpdateHookCallback | null): Database; } declare class Statement { @@ -290,6 +328,7 @@ declare namespace initSqlJs { // types SqlValue, StatementIteratorResult, + UpdateHookCallback, }; // classes diff --git a/types/sql.js/sql.js-tests.ts b/types/sql.js/sql.js-tests.ts index 3e93e2a2e453b1..e7fcadf5889b3b 100644 --- a/types/sql.js/sql.js-tests.ts +++ b/types/sql.js/sql.js-tests.ts @@ -28,6 +28,11 @@ initSqlJs().then(SqlJs => { + "CREATE TABLE test_table (id INTEGER PRIMARY KEY, content TEXT);"; db.run(createTableStatement); + // Register handler for database changes + db.updateHook((operation, database, table, rowId) => { + console.log("SQLite database is updated", { operation, database, table, rowId }); + }); + // Insert 2 records for testing. const insertRecordStatement = "INSERT INTO test_table (id, content) VALUES (@id, @content);"; db.run(insertRecordStatement, {