tina4-nodejs 3.13.92 → 3.13.95
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +170 -28
- package/README.md +2 -2
- package/package.json +13 -9
- package/packages/cli/dist/bin.js +33126 -30055
- package/packages/cli/src/commands/metrics.ts +17 -11
- package/packages/cli/src/commands/serve.ts +10 -9
- package/packages/core/dist/index.js +33062 -29908
- package/packages/core/src/ai.ts +7 -1
- package/packages/core/src/auth.ts +191 -39
- package/packages/core/src/background.ts +19 -19
- package/packages/core/src/cache.ts +492 -49
- package/packages/core/src/devAdmin.ts +79 -32
- package/packages/core/src/devMailbox.ts +20 -44
- package/packages/core/src/dispatchPipeline.ts +285 -0
- package/packages/core/src/dotenv.ts +185 -40
- package/packages/core/src/index.ts +7 -6
- package/packages/core/src/logger.ts +257 -36
- package/packages/core/src/mcp.ts +1 -1
- package/packages/core/src/messenger.ts +81 -13
- package/packages/core/src/metrics.ts +199 -961
- package/packages/core/src/middleware.ts +390 -123
- package/packages/core/src/queue.ts +188 -32
- package/packages/core/src/queueBackends/kafkaBackend.ts +109 -13
- package/packages/core/src/queueBackends/liteBackend.ts +13 -0
- package/packages/core/src/queueBackends/mongoBackend.ts +101 -9
- package/packages/core/src/queueBackends/rabbitmqBackend.ts +22 -4
- package/packages/core/src/rateLimiter.ts +10 -5
- package/packages/core/src/request.ts +6 -9
- package/packages/core/src/response.ts +46 -1
- package/packages/core/src/router.ts +29 -4
- package/packages/core/src/server.ts +751 -414
- package/packages/core/src/session.ts +244 -27
- package/packages/core/src/sessionHandlers/childError.ts +72 -0
- package/packages/core/src/sessionHandlers/databaseHandler.ts +338 -48
- package/packages/core/src/sessionHandlers/memcachedHandler.ts +181 -0
- package/packages/core/src/sessionHandlers/mongoClient.ts +293 -202
- package/packages/core/src/sessionHandlers/mongoHandler.ts +88 -8
- package/packages/core/src/sessionHandlers/respClient.ts +16 -143
- package/packages/core/src/sessionHandlers/sqlClient.ts +290 -0
- package/packages/core/src/sessionHandlers/syncBridge.ts +190 -0
- package/packages/core/src/sessionHandlers/syncSocket.ts +236 -0
- package/packages/core/src/testClient.ts +18 -5
- package/packages/core/src/trustedProxy.ts +249 -0
- package/packages/core/src/types.ts +29 -5
- package/packages/core/src/websocket.ts +66 -0
- package/packages/frond/dist/index.js +74 -31
- package/packages/frond/src/engine.ts +99 -33
- package/packages/orm/dist/index.js +26554 -23400
- package/packages/orm/src/adapters/firebird.ts +183 -56
- package/packages/orm/src/adapters/mongodb.ts +25 -4
- package/packages/orm/src/adapters/mssql.ts +114 -29
- package/packages/orm/src/adapters/mysql.ts +103 -40
- package/packages/orm/src/adapters/odbc.ts +44 -21
- package/packages/orm/src/adapters/postgres.ts +118 -26
- package/packages/orm/src/adapters/sqlDialect.ts +120 -0
- package/packages/orm/src/adapters/sqlite.ts +64 -25
- package/packages/orm/src/baseModel.ts +135 -40
- package/packages/orm/src/cachedDatabase.ts +43 -19
- package/packages/orm/src/connectTimeout.ts +265 -0
- package/packages/orm/src/database.ts +338 -198
- package/packages/orm/src/databaseResult.ts +65 -13
- package/packages/orm/src/databaseUrl.ts +484 -0
- package/packages/orm/src/docstore.ts +386 -145
- package/packages/orm/src/index.ts +13 -3
- package/packages/orm/src/migration.ts +18 -3
- package/packages/orm/src/queryBuilder.ts +38 -4
- package/packages/orm/src/sqlTranslator.ts +310 -4
- package/packages/orm/src/types.ts +15 -4
- package/types/cli/src/bin.d.ts +92 -0
- package/types/cli/src/commands/build.d.ts +2 -0
- package/types/cli/src/commands/generate.d.ts +47 -0
- package/types/cli/src/commands/init.d.ts +1 -0
- package/types/cli/src/commands/metrics.d.ts +6 -0
- package/types/cli/src/commands/migrate.d.ts +1 -0
- package/types/cli/src/commands/migrateCreate.d.ts +1 -0
- package/types/cli/src/commands/migrateRollback.d.ts +1 -0
- package/types/cli/src/commands/migrateStatus.d.ts +1 -0
- package/types/cli/src/commands/queue.d.ts +20 -0
- package/types/cli/src/commands/routes.d.ts +1 -0
- package/types/cli/src/commands/seed.d.ts +1 -0
- package/types/cli/src/commands/serve.d.ts +6 -0
- package/types/cli/src/commands/test.d.ts +1 -0
- package/types/core/src/ai.d.ts +64 -0
- package/types/core/src/api.d.ts +262 -0
- package/types/core/src/auth.d.ts +177 -0
- package/types/core/src/authGate.d.ts +20 -0
- package/types/core/src/background.d.ts +34 -0
- package/types/core/src/cache.d.ts +163 -0
- package/types/core/src/constants.d.ts +38 -0
- package/types/core/src/container.d.ts +44 -0
- package/types/core/src/context/chunker.d.ts +31 -0
- package/types/core/src/context/index.d.ts +93 -0
- package/types/core/src/devAdmin.d.ts +179 -0
- package/types/core/src/devMailbox.d.ts +54 -0
- package/types/core/src/dispatchPipeline.d.ts +117 -0
- package/types/core/src/docs.d.ts +141 -0
- package/types/core/src/docsAutoDiscovery.d.ts +6 -0
- package/types/core/src/dotenv.d.ts +87 -0
- package/types/core/src/env.d.ts +28 -0
- package/types/core/src/errorOverlay.d.ts +36 -0
- package/types/core/src/events.d.ts +75 -0
- package/types/core/src/fakeData.d.ts +55 -0
- package/types/core/src/feedback.d.ts +90 -0
- package/types/core/src/graphql.d.ts +207 -0
- package/types/core/src/health.d.ts +22 -0
- package/types/core/src/htmlElement.d.ts +75 -0
- package/types/core/src/i18n.d.ts +37 -0
- package/types/core/src/index.d.ts +92 -0
- package/types/core/src/job.d.ts +39 -0
- package/types/core/src/logger.d.ts +200 -0
- package/types/core/src/mcp.d.ts +248 -0
- package/types/core/src/messenger.d.ts +191 -0
- package/types/core/src/metrics.d.ts +41 -0
- package/types/core/src/middleware.d.ts +330 -0
- package/types/core/src/mqtt.d.ts +257 -0
- package/types/core/src/mqttMessage.d.ts +67 -0
- package/types/core/src/plan.d.ts +96 -0
- package/types/core/src/projectIndex.d.ts +56 -0
- package/types/core/src/queue.d.ts +268 -0
- package/types/core/src/queueBackends/kafkaBackend.d.ts +117 -0
- package/types/core/src/queueBackends/liteBackend.d.ts +128 -0
- package/types/core/src/queueBackends/mongoBackend.d.ts +119 -0
- package/types/core/src/queueBackends/rabbitmqBackend.d.ts +55 -0
- package/types/core/src/rateLimiter.d.ts +49 -0
- package/types/core/src/request.d.ts +25 -0
- package/types/core/src/response.d.ts +28 -0
- package/types/core/src/routeDiscovery.d.ts +12 -0
- package/types/core/src/router.d.ts +366 -0
- package/types/core/src/scss.d.ts +19 -0
- package/types/core/src/server.d.ts +146 -0
- package/types/core/src/service.d.ts +115 -0
- package/types/core/src/session.d.ts +341 -0
- package/types/core/src/sessionHandlers/childError.d.ts +34 -0
- package/types/core/src/sessionHandlers/databaseHandler.d.ts +97 -0
- package/types/core/src/sessionHandlers/memcachedHandler.d.ts +60 -0
- package/types/core/src/sessionHandlers/mongoClient.d.ts +35 -0
- package/types/core/src/sessionHandlers/mongoHandler.d.ts +109 -0
- package/types/core/src/sessionHandlers/respClient.d.ts +22 -0
- package/types/core/src/sessionHandlers/sqlClient.d.ts +39 -0
- package/types/core/src/sessionHandlers/syncBridge.d.ts +91 -0
- package/types/core/src/sessionHandlers/syncSocket.d.ts +49 -0
- package/types/core/src/sessionHandlers/valkeyHandler.d.ts +65 -0
- package/types/core/src/static.d.ts +2 -0
- package/types/core/src/test.d.ts +94 -0
- package/types/core/src/testClient.d.ts +36 -0
- package/types/core/src/testing.d.ts +58 -0
- package/types/core/src/trustedProxy.d.ts +44 -0
- package/types/core/src/types.d.ts +242 -0
- package/types/core/src/validator.d.ts +52 -0
- package/types/core/src/websocket.d.ts +402 -0
- package/types/core/src/websocketBackplane.d.ts +166 -0
- package/types/core/src/websocketConnection.d.ts +54 -0
- package/types/core/src/wsdl.d.ts +101 -0
- package/types/frond/src/engine.d.ts +263 -0
- package/types/frond/src/index.d.ts +2 -0
- package/types/orm/src/adapters/firebird.d.ts +183 -0
- package/types/orm/src/adapters/mongodb.d.ts +81 -0
- package/types/orm/src/adapters/mssql.d.ts +77 -0
- package/types/orm/src/adapters/mysql.d.ts +67 -0
- package/types/orm/src/adapters/odbc.d.ts +94 -0
- package/types/orm/src/adapters/postgres.d.ts +86 -0
- package/types/orm/src/adapters/sqlDialect.d.ts +71 -0
- package/types/orm/src/adapters/sqlite.d.ts +68 -0
- package/types/orm/src/autoCrud.d.ts +73 -0
- package/types/orm/src/baseModel.d.ts +427 -0
- package/types/orm/src/cachedDatabase.d.ts +190 -0
- package/types/orm/src/connectTimeout.d.ts +100 -0
- package/types/orm/src/database.d.ts +655 -0
- package/types/orm/src/databaseResult.d.ts +109 -0
- package/types/orm/src/databaseUrl.d.ts +125 -0
- package/types/orm/src/docstore.d.ts +241 -0
- package/types/orm/src/fakeData.d.ts +22 -0
- package/types/orm/src/index.d.ts +43 -0
- package/types/orm/src/migration.d.ts +275 -0
- package/types/orm/src/model.d.ts +7 -0
- package/types/orm/src/query.d.ts +14 -0
- package/types/orm/src/queryBuilder.d.ts +193 -0
- package/types/orm/src/realtime/index.d.ts +7 -0
- package/types/orm/src/realtime/models/attachment.d.ts +43 -0
- package/types/orm/src/realtime/models/channel.d.ts +32 -0
- package/types/orm/src/realtime/models/channelMember.d.ts +32 -0
- package/types/orm/src/realtime/models/message.d.ts +36 -0
- package/types/orm/src/realtime/models/workspace.d.ts +26 -0
- package/types/orm/src/realtime/realtime.d.ts +24 -0
- package/types/orm/src/realtime/storage.d.ts +61 -0
- package/types/orm/src/seeder.d.ts +118 -0
- package/types/orm/src/sqlTranslator.d.ts +258 -0
- package/types/orm/src/types.d.ts +148 -0
- package/types/orm/src/validation.d.ts +6 -0
- package/types/swagger/src/generator.d.ts +46 -0
- package/types/swagger/src/index.d.ts +2 -0
- package/types/swagger/src/ui.d.ts +11 -0
- package/packages/core/src/sessionHandlers/redisHandler.ts +0 -206
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
import type { DatabaseAdapter, DatabaseResult as DatabaseWriteResult, ColumnInfo, FieldDefinition } from "./types.js";
|
|
2
|
+
import { DatabaseResult } from "./databaseResult.js";
|
|
3
|
+
import { DatabaseUrl } from "./databaseUrl.js";
|
|
4
|
+
import { type CachedAdapterOptions } from "./cachedDatabase.js";
|
|
5
|
+
/**
|
|
6
|
+
* v3.13.12 — strip trailing `;` and whitespace from user-supplied SQL
|
|
7
|
+
* before the framework wraps it with COUNT(*) subqueries or appends
|
|
8
|
+
* LIMIT/OFFSET clauses. Without this, `"SELECT * FROM t;"` becomes
|
|
9
|
+
* `"SELECT * FROM t; LIMIT 100 OFFSET 0"` — a syntax error on every
|
|
10
|
+
* engine. Internal semicolons (in string literals, between meaningful
|
|
11
|
+
* statements) are left alone; drivers reject those if the engine
|
|
12
|
+
* doesn't support multi-statement.
|
|
13
|
+
*
|
|
14
|
+
* Exported so adapters and external tooling can compose it.
|
|
15
|
+
*/
|
|
16
|
+
export declare function stripTrailingSemicolons(sql: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Adapter bridge helpers (v3.14.0, Option A).
|
|
19
|
+
*
|
|
20
|
+
* The public Database/BaseModel/QueryBuilder API is async so it works on the
|
|
21
|
+
* async adapters (PostgreSQL/MySQL/MSSQL/Firebird/Mongo). SQLite implements
|
|
22
|
+
* only the synchronous methods (`node:sqlite` is sync); the async adapters
|
|
23
|
+
* implement only the `*Async` variants and make the sync methods throw.
|
|
24
|
+
*
|
|
25
|
+
* Each helper prefers the adapter's `*Async` method when present and awaits it,
|
|
26
|
+
* otherwise falls back to the sync method. For SQLite the fallback resolves
|
|
27
|
+
* instantly; for async adapters the awaited promise does the real work. This is
|
|
28
|
+
* the single chokepoint every public read/write flows through.
|
|
29
|
+
*/
|
|
30
|
+
export declare function adapterFetch<T = Record<string, unknown>>(adapter: DatabaseAdapter, sql: string, params?: unknown[], limit?: number, skip?: number, noCache?: boolean): Promise<T[]>;
|
|
31
|
+
export declare function adapterQuery<T = Record<string, unknown>>(adapter: DatabaseAdapter, sql: string, params?: unknown[]): Promise<T[]>;
|
|
32
|
+
export declare function adapterFetchOne<T = Record<string, unknown>>(adapter: DatabaseAdapter, sql: string, params?: unknown[]): Promise<T | null>;
|
|
33
|
+
export declare function adapterExecute(adapter: DatabaseAdapter, sql: string, params?: unknown[]): Promise<unknown>;
|
|
34
|
+
export declare function adapterStartTransaction(adapter: DatabaseAdapter): Promise<void>;
|
|
35
|
+
export declare function adapterCommit(adapter: DatabaseAdapter): Promise<void>;
|
|
36
|
+
export declare function adapterRollback(adapter: DatabaseAdapter): Promise<void>;
|
|
37
|
+
export declare function adapterTableExists(adapter: DatabaseAdapter, name: string): Promise<boolean>;
|
|
38
|
+
export declare function adapterTables(adapter: DatabaseAdapter): Promise<string[]>;
|
|
39
|
+
export declare function adapterColumns(adapter: DatabaseAdapter, table: string): Promise<ColumnInfo[]>;
|
|
40
|
+
export declare function adapterCreateTable(adapter: DatabaseAdapter, name: string, columns: Record<string, FieldDefinition>): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Extract the engine-assigned auto-increment id from an `execute()` result.
|
|
43
|
+
*
|
|
44
|
+
* SQLite returns `{ lastInsertRowid }`. PostgreSQL (pg) returns a result whose
|
|
45
|
+
* `rows[0].id` holds the value when the statement had a `RETURNING` clause
|
|
46
|
+
* (insertAsync adds one). MySQL/MSSQL adapters set the adapter's lastId,
|
|
47
|
+
* so callers fall back to `adapter.lastInsertId()` when the result has neither.
|
|
48
|
+
*/
|
|
49
|
+
export declare function extractLastInsertId(result: unknown): number | bigint | null;
|
|
50
|
+
/**
|
|
51
|
+
* The default row cap on every read path that advertises a `limit`.
|
|
52
|
+
*
|
|
53
|
+
* One number for the whole family (Python, PHP, Ruby and Node all default to
|
|
54
|
+
* this). Pagination is a default principle: an un-paginated read of a table
|
|
55
|
+
* that grew to a million rows is a production incident waiting to happen. A
|
|
56
|
+
* caller who wants more passes a bigger limit.
|
|
57
|
+
*/
|
|
58
|
+
export declare const DEFAULT_ROW_CAP = 100;
|
|
59
|
+
/**
|
|
60
|
+
* Wrap a raw adapter with the query cache so BOTH `db.fetch()` (via the
|
|
61
|
+
* Database wrapper) AND ORM reads (via `getAdapter()` / `getNamedAdapter()`)
|
|
62
|
+
* are cached through the same store and counters.
|
|
63
|
+
*
|
|
64
|
+
* Idempotent: an already-wrapped adapter is returned as-is, so re-binding the
|
|
65
|
+
* same adapter (or binding the adapter a Database wrapper already holds) never
|
|
66
|
+
* double-wraps. `options.sharedCache` backs all pooled connections with one
|
|
67
|
+
* store so a write on any connection invalidates reads cached by all of them.
|
|
68
|
+
*
|
|
69
|
+
* Caching is OFF by default — both layers are opt-in. Turn the request-scoped
|
|
70
|
+
* layer on with TINA4_AUTO_CACHING=true (for read-heavy endpoints) and/or the
|
|
71
|
+
* persistent cross-request layer with TINA4_DB_CACHE=true. With both unset the
|
|
72
|
+
* wrapper passes everything straight through (no cached read-after-write footgun).
|
|
73
|
+
*/
|
|
74
|
+
export declare function wrapWithCache(adapter: DatabaseAdapter, options?: CachedAdapterOptions): DatabaseAdapter;
|
|
75
|
+
/**
|
|
76
|
+
* Resolve the underlying wrapped adapter for a given raw adapter — used so the
|
|
77
|
+
* Database wrapper and `getAdapter()` end up holding the SAME
|
|
78
|
+
* CachedDatabaseAdapter instance (one cache, one set of counters).
|
|
79
|
+
*/
|
|
80
|
+
export declare function setAdapter(adapter: DatabaseAdapter): DatabaseAdapter;
|
|
81
|
+
/**
|
|
82
|
+
* Clear the request-scoped query cache on every live connection at the start of
|
|
83
|
+
* each HTTP request, so request-scoped caching never serves rows across
|
|
84
|
+
* requests. Persistent-mode connections (TINA4_DB_CACHE=true) are untouched.
|
|
85
|
+
*
|
|
86
|
+
* The request dispatcher calls this. Mirrors Python's
|
|
87
|
+
* `Database.reset_request_caches()`.
|
|
88
|
+
*/
|
|
89
|
+
export declare function resetRequestCaches(): void;
|
|
90
|
+
/**
|
|
91
|
+
* Public, user-facing API to bind a database connection.
|
|
92
|
+
*
|
|
93
|
+
* - No `name` → registers `adapter` as the global default connection
|
|
94
|
+
* (what `getAdapter()` returns and what every model resolves to unless it
|
|
95
|
+
* declares `static _db`). This is the manual equivalent of the auto-binding
|
|
96
|
+
* that `initDatabase()` performs from `.env`/`TINA4_DATABASE_URL`.
|
|
97
|
+
* - With `name` → registers `adapter` in the named registry. A model with
|
|
98
|
+
* `static _db = name` resolves to it via `getNamedAdapter(name)`.
|
|
99
|
+
*
|
|
100
|
+
* Mirrors the Python master `bind_database(db, name=None)`.
|
|
101
|
+
*
|
|
102
|
+
* import { bindDatabase, createAdapterFromUrl } from "@tina4/orm";
|
|
103
|
+
*
|
|
104
|
+
* // Default connection
|
|
105
|
+
* bindDatabase(adapter);
|
|
106
|
+
*
|
|
107
|
+
* // Named secondary connection built from a URL (kept synchronous —
|
|
108
|
+
* // build the adapter first, then bind it)
|
|
109
|
+
* bindDatabase(await createAdapterFromUrl(url, user, pass), "analytics");
|
|
110
|
+
*
|
|
111
|
+
* `bindDatabase` itself is synchronous: it takes an already-constructed
|
|
112
|
+
* adapter. Use `createAdapterFromUrl()` to build a named secondary adapter
|
|
113
|
+
* from a URL without making it the default.
|
|
114
|
+
*/
|
|
115
|
+
export declare function bindDatabase(adapter: DatabaseAdapter, name?: string): void;
|
|
116
|
+
export declare function getAdapter(): DatabaseAdapter;
|
|
117
|
+
/**
|
|
118
|
+
* Register a named adapter for multi-database support.
|
|
119
|
+
* Models reference it via `static _db = 'name'`.
|
|
120
|
+
*/
|
|
121
|
+
export declare function setNamedAdapter(name: string, adapter: DatabaseAdapter): void;
|
|
122
|
+
/**
|
|
123
|
+
* Get a named adapter previously registered via `bindDatabase(adapter, name)`
|
|
124
|
+
* (or the lower-level `setNamedAdapter(name, adapter)`).
|
|
125
|
+
*
|
|
126
|
+
* Throws a clear error if the name isn't registered — a model that declares
|
|
127
|
+
* `static _db = "name"` resolves through here, so a missing name means the
|
|
128
|
+
* connection was never bound. The message tells the developer exactly how to
|
|
129
|
+
* fix it rather than silently falling back to the default connection (which
|
|
130
|
+
* would hide the mistake and write to the wrong database).
|
|
131
|
+
*/
|
|
132
|
+
export declare function getNamedAdapter(name: string): DatabaseAdapter;
|
|
133
|
+
export declare function closeDatabase(): void;
|
|
134
|
+
export interface DatabaseConfig {
|
|
135
|
+
type?: "sqlite" | "postgres" | "mysql" | "mssql" | "sqlserver" | "firebird" | "mongodb" | "odbc";
|
|
136
|
+
path?: string;
|
|
137
|
+
url?: string;
|
|
138
|
+
host?: string;
|
|
139
|
+
port?: number;
|
|
140
|
+
user?: string;
|
|
141
|
+
username?: string;
|
|
142
|
+
password?: string;
|
|
143
|
+
database?: string;
|
|
144
|
+
/** ODBC-specific: full connection string, e.g. "DSN=MyDSN" or "DRIVER={SQL Server};SERVER=host;DATABASE=db" */
|
|
145
|
+
connectionString?: string;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Parsed result from a TINA4_DATABASE_URL connection string.
|
|
149
|
+
*/
|
|
150
|
+
/**
|
|
151
|
+
* Parse a connection URL into a `DatabaseUrl` value.
|
|
152
|
+
*
|
|
153
|
+
* Breaking (feature 5): this returned a `ParsedDatabaseUrl` struct whose fields
|
|
154
|
+
* were `type`, `user` and `path`. It now returns a `DatabaseUrl`, whose fields
|
|
155
|
+
* are `engine`, `username` and `database` - the same names PHP, Python and Ruby
|
|
156
|
+
* use, and the same names as the TINA4_DATABASE_USERNAME env var they come from.
|
|
157
|
+
* `ParsedDatabaseUrl` is gone rather than kept as an alias.
|
|
158
|
+
*
|
|
159
|
+
* The 43-CC body that used to live here - the worst function measured anywhere
|
|
160
|
+
* in the audit - is now one small parser per engine inside the value type.
|
|
161
|
+
*/
|
|
162
|
+
export declare function parseDatabaseUrl(url: string, username?: string, password?: string): DatabaseUrl;
|
|
163
|
+
/**
|
|
164
|
+
* A wrapper class around a DatabaseAdapter that provides a clean, high-level API.
|
|
165
|
+
*
|
|
166
|
+
* Mirrors the Database class in Python/Ruby Tina4 implementations.
|
|
167
|
+
*
|
|
168
|
+
* Usage:
|
|
169
|
+
* const db = await Database.create("sqlite:///path/to/db.sqlite");
|
|
170
|
+
* const rows = db.fetch("SELECT * FROM users WHERE active = ?", [true], 10, 0);
|
|
171
|
+
* const user = db.fetchOne("SELECT * FROM users WHERE id = ?", [1]);
|
|
172
|
+
* db.insert("users", { name: "Alice", email: "alice@example.com" });
|
|
173
|
+
* db.update("users", { name: "Bob" }, { id: 1 });
|
|
174
|
+
* db.delete("users", { id: 1 });
|
|
175
|
+
* db.close();
|
|
176
|
+
*
|
|
177
|
+
* Connection pooling:
|
|
178
|
+
* const db = await Database.create("sqlite:///data/app.db", undefined, undefined, 4);
|
|
179
|
+
* // 4 connections, round-robin rotation
|
|
180
|
+
*/
|
|
181
|
+
export declare class Database {
|
|
182
|
+
private adapter;
|
|
183
|
+
/** Connection pool — array of adapters with lazy creation */
|
|
184
|
+
private pool;
|
|
185
|
+
/** Pool size (0 = single connection) */
|
|
186
|
+
private _poolSize;
|
|
187
|
+
/** Round-robin index */
|
|
188
|
+
private poolIndex;
|
|
189
|
+
/** Factory for creating new adapters (used by pool) */
|
|
190
|
+
private adapterFactory;
|
|
191
|
+
/** table -> primary-key column name (or null), introspected once */
|
|
192
|
+
private _pkCache;
|
|
193
|
+
/**
|
|
194
|
+
* Whether a standalone write auto-commits. ON by default — a write made
|
|
195
|
+
* outside an explicit transaction commits on its own connection before
|
|
196
|
+
* returning (so it's durable and visible across pooled connections). Inside
|
|
197
|
+
* startTransaction()/commit()/rollback() the per-statement commit is
|
|
198
|
+
* suppressed, so explicit transactions stay atomic. Set TINA4_AUTOCOMMIT=false
|
|
199
|
+
* for strict manual-commit mode.
|
|
200
|
+
*/
|
|
201
|
+
private autoCommit;
|
|
202
|
+
private lastError;
|
|
203
|
+
/** Database engine type (sqlite, postgres, mysql, mssql, firebird) */
|
|
204
|
+
private dbType;
|
|
205
|
+
/**
|
|
206
|
+
* Async-local storage for the adapter pinned to the current transaction.
|
|
207
|
+
*
|
|
208
|
+
* With pooling enabled, ordinary calls round-robin through the pool. Inside
|
|
209
|
+
* a transaction, however, all calls must land on the SAME adapter — otherwise
|
|
210
|
+
* startTransaction(), execute() and commit() each rotate to a different
|
|
211
|
+
* connection and the transaction is meaningless (executes autocommit on
|
|
212
|
+
* whatever adapter they hit; the final commit lands on yet another adapter
|
|
213
|
+
* that has nothing to commit; rollback() is silently no-op'd).
|
|
214
|
+
*
|
|
215
|
+
* AsyncLocalStorage is the Node analog of Python's threading.local. It pins
|
|
216
|
+
* the adapter to the current async task tree so concurrent transactions on
|
|
217
|
+
* the same Database don't clobber each other. startTransaction() sets the
|
|
218
|
+
* pin via .enterWith(); commit()/rollback() clear it.
|
|
219
|
+
*/
|
|
220
|
+
private txStore;
|
|
221
|
+
/**
|
|
222
|
+
* Create a Database wrapping an existing adapter.
|
|
223
|
+
* For creating a Database from a URL, use the async static factories:
|
|
224
|
+
* Database.create(url) or Database.fromEnv()
|
|
225
|
+
*/
|
|
226
|
+
constructor(adapter: DatabaseAdapter);
|
|
227
|
+
/**
|
|
228
|
+
* Set the engine type ("sqlite" | "postgres" | "mysql" | "mssql" |
|
|
229
|
+
* "firebird" | "mongodb"). The static `Database.create` factory assigns the
|
|
230
|
+
* private `dbType` directly; `initDatabase()` (a free function, no private
|
|
231
|
+
* access) routes through this setter so a URL connection is correctly typed.
|
|
232
|
+
* Without it a `postgres://` connection kept the `"sqlite"` default and
|
|
233
|
+
* `getNextId()` took the SQLite branch — hitting the non-existent
|
|
234
|
+
* `tina4_sequences` table on PostgreSQL instead of native sequences (#255).
|
|
235
|
+
*/
|
|
236
|
+
setDbType(type: string): void;
|
|
237
|
+
/**
|
|
238
|
+
* Async factory: creates a Database from a connection URL.
|
|
239
|
+
* Works with all adapter types (sqlite, postgres, mysql, mssql, firebird).
|
|
240
|
+
*
|
|
241
|
+
* @param url - Connection URL
|
|
242
|
+
* @param username - Optional username
|
|
243
|
+
* @param password - Optional password
|
|
244
|
+
* @param pool - Number of pooled connections (0 = single, N>0 = round-robin)
|
|
245
|
+
*/
|
|
246
|
+
static create(url: string, username?: string, password?: string, pool?: number): Promise<Database>;
|
|
247
|
+
/**
|
|
248
|
+
* Create a Database from an environment variable.
|
|
249
|
+
* @param envKey - Name of the env var holding the connection URL. Defaults to "TINA4_DATABASE_URL".
|
|
250
|
+
* @param pool - Number of pooled connections (0 = single, N>0 = round-robin)
|
|
251
|
+
*/
|
|
252
|
+
static fromEnv(envKey?: string, pool?: number): Promise<Database>;
|
|
253
|
+
/**
|
|
254
|
+
* Get the next adapter — from pool (round-robin) or single connection.
|
|
255
|
+
*
|
|
256
|
+
* If a transaction is active (an adapter is pinned in async-local storage),
|
|
257
|
+
* that adapter is returned for every call so the whole transaction is
|
|
258
|
+
* atomic on one connection. Otherwise pooled mode round-robins.
|
|
259
|
+
*/
|
|
260
|
+
private getNextAdapter;
|
|
261
|
+
/** Get the underlying adapter (for advanced / escape-hatch usage). */
|
|
262
|
+
getAdapter(): DatabaseAdapter;
|
|
263
|
+
/** Get the pool size (0 = single connection mode). */
|
|
264
|
+
poolSize(): number;
|
|
265
|
+
/** Alias for poolSize() — returns total pool size (0 = single connection mode). */
|
|
266
|
+
size(): number;
|
|
267
|
+
/** Get the number of active (created) connections in the pool. */
|
|
268
|
+
activeCount(): number;
|
|
269
|
+
/**
|
|
270
|
+
* Borrow a connection from the pool (or the single adapter).
|
|
271
|
+
* The caller is responsible for returning it via checkin().
|
|
272
|
+
*/
|
|
273
|
+
checkout(): DatabaseAdapter;
|
|
274
|
+
/**
|
|
275
|
+
* Return a borrowed connection to the pool.
|
|
276
|
+
* For round-robin pools this is a no-op (connections stay in the pool array),
|
|
277
|
+
* but the method exists for API parity and future pooling strategies.
|
|
278
|
+
*/
|
|
279
|
+
checkin(_adapter: DatabaseAdapter): void;
|
|
280
|
+
/**
|
|
281
|
+
* Close all pooled connections and clear the pool.
|
|
282
|
+
* Equivalent to close() but named for explicit pool teardown.
|
|
283
|
+
*/
|
|
284
|
+
closeAll(): void;
|
|
285
|
+
/** Query rows with optional pagination. Returns a DatabaseResult wrapper.
|
|
286
|
+
*
|
|
287
|
+
* Async since v3.14.0 (Option A): the public API awaits the adapter's
|
|
288
|
+
* `*Async` method when present (PostgreSQL/MySQL/MSSQL/Firebird/Mongo) and
|
|
289
|
+
* falls back to the synchronous method for SQLite (`node:sqlite` is sync, so
|
|
290
|
+
* the fallback resolves instantly). This is the breaking change that makes
|
|
291
|
+
* the wrapper work uniformly across every engine.
|
|
292
|
+
*/
|
|
293
|
+
/**
|
|
294
|
+
* Fetch rows with pagination, capped at DEFAULT_ROW_CAP (100) when the
|
|
295
|
+
* caller does not pass a limit.
|
|
296
|
+
*
|
|
297
|
+
* The cap is the one row-cap number the whole family shares (Python, PHP and
|
|
298
|
+
* Ruby all default `fetch` to 100). Node was the outlier: `limit` was
|
|
299
|
+
* optional with NO default, so a bare `db.fetch("select * from big_table")`
|
|
300
|
+
* returned every row.
|
|
301
|
+
*
|
|
302
|
+
* `fetchAll` deliberately does NOT inherit the cap — see below.
|
|
303
|
+
*/
|
|
304
|
+
fetch(sql: string, params?: unknown[], limit?: number, offset?: number, opts?: {
|
|
305
|
+
noCache?: boolean;
|
|
306
|
+
}): Promise<DatabaseResult>;
|
|
307
|
+
/**
|
|
308
|
+
* The shared read body. `limit` is passed through VERBATIM: `undefined`
|
|
309
|
+
* means "no LIMIT clause at all", which is how `fetchAll` stays uncapped.
|
|
310
|
+
*
|
|
311
|
+
* This exists because Node's adapters treat `limit: 0` as `LIMIT 0` (zero
|
|
312
|
+
* rows), not as the "no truncation" sentinel Python and PHP use — so the cap
|
|
313
|
+
* cannot live on the parameter default, or `fetchAll()` would silently
|
|
314
|
+
* inherit it and stop returning every row.
|
|
315
|
+
*/
|
|
316
|
+
private _fetchWithLimit;
|
|
317
|
+
/**
|
|
318
|
+
* The true row count for `sql`, ignoring the pagination we appended.
|
|
319
|
+
*
|
|
320
|
+
* `count` is the TRUE TOTAL for the filter, not the number of rows this page
|
|
321
|
+
* returned. Node and Ruby used to populate it with `records.length` while
|
|
322
|
+
* Python and PHP populated it from a probe, so `db.fetch(sql).count` answered
|
|
323
|
+
* 20 here and 250 there for one query against one table, and every paginated
|
|
324
|
+
* response built on it under-reported. MEASURED 2026-08-05 on a 250-row table
|
|
325
|
+
* read with limit=20: Node reported total 20 over 2 pages against Python's
|
|
326
|
+
* 250 over 13.
|
|
327
|
+
*
|
|
328
|
+
* Only probed when a limit was actually applied. With no limit the rows
|
|
329
|
+
* returned ARE the whole answer for this SQL, so `records.length` is already
|
|
330
|
+
* the true total and a second round-trip would buy nothing — which is also
|
|
331
|
+
* what keeps `fetchAll()` at one query.
|
|
332
|
+
*
|
|
333
|
+
* BEST EFFORT, and it can never mask a real failure: it runs AFTER the main
|
|
334
|
+
* query (which has already thrown on bad SQL) and returns undefined on any
|
|
335
|
+
* error. `undefined` — not 0 — is the miss value, so DatabaseResult falls
|
|
336
|
+
* back to records.length, a true lower bound. Reporting 0 next to 100 real
|
|
337
|
+
* records would be the same "states a wrong number authoritatively" defect
|
|
338
|
+
* this change exists to remove.
|
|
339
|
+
*
|
|
340
|
+
* The closing paren goes on its OWN LINE: appended inline, a trailing
|
|
341
|
+
* `-- comment` in the caller's SQL comments it out and the probe dies with
|
|
342
|
+
* "incomplete input". Postgres, MySQL and MSSQL additionally require a name
|
|
343
|
+
* for the derived table; SQLite and Firebird do not, and Firebird rejects
|
|
344
|
+
* `AS` there — so the alias comes from the adapter, not an assumption.
|
|
345
|
+
*/
|
|
346
|
+
private countProbe;
|
|
347
|
+
/**
|
|
348
|
+
* Fetch a single row or null.
|
|
349
|
+
*
|
|
350
|
+
* Pass `{ noCache: true }` as the trailing options object to bypass the
|
|
351
|
+
* query cache for this one call — no lookup, no store, run directly
|
|
352
|
+
* (mirrors the Python master's `no_cache`). Default preserves caching.
|
|
353
|
+
*/
|
|
354
|
+
fetchOne<T = Record<string, unknown>>(sql: string, params?: unknown[], opts?: {
|
|
355
|
+
noCache?: boolean;
|
|
356
|
+
}): Promise<T | null>;
|
|
357
|
+
/**
|
|
358
|
+
* Fetch rows and return the records array directly.
|
|
359
|
+
*
|
|
360
|
+
* Symmetric with `fetchOne`. For the common case where you just want
|
|
361
|
+
* the rows and don't need the `DatabaseResult` metadata, this is one
|
|
362
|
+
* less attribute access than `fetch(...).records`.
|
|
363
|
+
*
|
|
364
|
+
* const rows = db.fetchAll("SELECT * FROM users WHERE active = ?", [true]);
|
|
365
|
+
* for (const row of rows) console.log(row.name);
|
|
366
|
+
*
|
|
367
|
+
* Returns `[]` (not `null`) when no rows match. Cross-framework parity
|
|
368
|
+
* with Python `db.fetch_all()`, PHP `$db->fetchAll()`, and Ruby `db.fetch_all`.
|
|
369
|
+
*
|
|
370
|
+
* Pass `{ noCache: true }` as the trailing options object to bypass the
|
|
371
|
+
* query cache for this one call — no lookup, no store, run directly
|
|
372
|
+
* (mirrors the Python master's `no_cache`). The options object is a
|
|
373
|
+
* SEPARATE trailing argument, never the params array.
|
|
374
|
+
*/
|
|
375
|
+
fetchAll<T = Record<string, unknown>>(sql: string, params?: unknown[], limit?: number, offset?: number, opts?: {
|
|
376
|
+
noCache?: boolean;
|
|
377
|
+
}): Promise<T[]>;
|
|
378
|
+
/**
|
|
379
|
+
* Execute a write statement.
|
|
380
|
+
*
|
|
381
|
+
* On SUCCESS returns `true` for simple writes, or the result set when the
|
|
382
|
+
* SQL contains RETURNING / CALL / EXEC / SELECT.
|
|
383
|
+
*
|
|
384
|
+
* On a SQL error (bad SQL, constraint violation, dead/aborted connection,
|
|
385
|
+
* missing driver) it FAILS LOUD: it records the cause on `lastError`
|
|
386
|
+
* (readable via `getError()`) and then RE-THROWS — it never swallows the
|
|
387
|
+
* error and returns `false`. This mirrors `fetch()`/`fetchOne()`, which
|
|
388
|
+
* already raise. Callers that need a boolean (e.g. ORM `save()`,
|
|
389
|
+
* `createTable()`, the migration runner, dev-admin/MCP DB tools) must
|
|
390
|
+
* `try/catch` and convert, rather than testing the return value.
|
|
391
|
+
*/
|
|
392
|
+
execute(sql: string, params?: unknown[]): Promise<boolean | unknown>;
|
|
393
|
+
/** Insert one row (object) or a batch of rows (array of objects) into a table. */
|
|
394
|
+
insert(table: string, data: Record<string, unknown> | Record<string, unknown>[]): Promise<DatabaseWriteResult>;
|
|
395
|
+
/**
|
|
396
|
+
* The table's primary-key column, introspected once and cached.
|
|
397
|
+
*
|
|
398
|
+
* Uses the cross-engine getColumns() contract (v3.13.14, #48), which reports
|
|
399
|
+
* primaryKey per column on every adapter. Resolves to null when the table has
|
|
400
|
+
* no primary key or cannot be introspected.
|
|
401
|
+
*/
|
|
402
|
+
primaryKey(table: string): Promise<string[]>;
|
|
403
|
+
/**
|
|
404
|
+
* A failed write must be loud.
|
|
405
|
+
*
|
|
406
|
+
* The adapters catch a SQL error and return { success: false, affectedRows: 0 },
|
|
407
|
+
* so a filterless update produced invalid SQL ("... WHERE ") and reported
|
|
408
|
+
* nothing rather than raising. A caller who does not inspect the result
|
|
409
|
+
* believes the write landed (audit feature 4, P1).
|
|
410
|
+
*/
|
|
411
|
+
private static assertWrote;
|
|
412
|
+
/**
|
|
413
|
+
* Update rows. A write with no filter is an error, not a full-table write.
|
|
414
|
+
*
|
|
415
|
+
* With no explicit filter the primary key is taken out of `data` and used as
|
|
416
|
+
* the WHERE clause. With neither a filter nor a primary key in `data` this
|
|
417
|
+
* throws rather than silently changing nothing (audit feature 4, P1).
|
|
418
|
+
*/
|
|
419
|
+
update(table: string, data: Record<string, unknown>, filter?: Record<string, unknown> | string, params?: unknown[]): Promise<DatabaseWriteResult>;
|
|
420
|
+
/** Delete rows. A filterless delete throws; use truncate() to empty a table. */
|
|
421
|
+
delete(table: string, filter?: Record<string, unknown> | string | Record<string, unknown>[], params?: unknown[]): Promise<DatabaseWriteResult>;
|
|
422
|
+
/** Remove every row. The explicit spelling of a whole-table delete. */
|
|
423
|
+
truncate(table: string): Promise<DatabaseWriteResult>;
|
|
424
|
+
/** Close all database connections (pool or single). */
|
|
425
|
+
close(): void;
|
|
426
|
+
/**
|
|
427
|
+
* True while an explicit transaction is active on the current async context.
|
|
428
|
+
* startTransaction() pins an adapter into txStore; commit()/rollback() clear
|
|
429
|
+
* it. Standalone writes only auto-commit when this is false, so per-statement
|
|
430
|
+
* commits never break the atomicity of an explicit transaction.
|
|
431
|
+
*/
|
|
432
|
+
private inExplicitTransaction;
|
|
433
|
+
/**
|
|
434
|
+
* Start a transaction. Pins the adapter to the current async context for
|
|
435
|
+
* the whole transaction so executes and the final commit/rollback all run
|
|
436
|
+
* on the same connection (critical when pool > 0).
|
|
437
|
+
*
|
|
438
|
+
* Nested-begin guard (DB-contract C): a second startTransaction() on a
|
|
439
|
+
* context that already has a pinned adapter is a double-begin — the inner
|
|
440
|
+
* BEGIN silently commits or no-ops on most engines, leaving the connection
|
|
441
|
+
* mid-transaction with the caller none the wiser. We keep a depth counter and
|
|
442
|
+
* log a clear warning instead of silently re-beginning; the pin stays on the
|
|
443
|
+
* original adapter so the eventual commit/rollback still land on the right
|
|
444
|
+
* connection, and the matching inner commit just unwinds the depth.
|
|
445
|
+
*/
|
|
446
|
+
startTransaction(): Promise<void>;
|
|
447
|
+
/**
|
|
448
|
+
* Commit the current transaction.
|
|
449
|
+
*
|
|
450
|
+
* FAIL LOUD (DB-contract C): if the underlying commit raises, capture
|
|
451
|
+
* lastError and RE-THROW — never swallow. On failure the transaction pin is
|
|
452
|
+
* RETAINED so the caller's follow-up rollback() lands on the SAME connection
|
|
453
|
+
* (clearing it would leak a dirty connection back into the pool and route the
|
|
454
|
+
* rollback to a different one). The pin is cleared ONLY on a successful
|
|
455
|
+
* commit. An inner commit of an ignored nested begin (depth > 1) just unwinds
|
|
456
|
+
* the depth — the outer commit is the real one.
|
|
457
|
+
*/
|
|
458
|
+
commit(): Promise<void>;
|
|
459
|
+
/**
|
|
460
|
+
* Rollback the current transaction — the terminal cleanup of a transaction,
|
|
461
|
+
* so it ALWAYS clears the pin (and the depth counter), even after a failed
|
|
462
|
+
* commit (it routes to the retained pinned connection and cleans it up). If
|
|
463
|
+
* the underlying rollback itself raises, lastError is captured and the error
|
|
464
|
+
* re-thrown, but the pin is still released so a poisoned connection doesn't
|
|
465
|
+
* stay pinned to this context forever.
|
|
466
|
+
*/
|
|
467
|
+
rollback(): Promise<void>;
|
|
468
|
+
/** Check if a table exists. */
|
|
469
|
+
tableExists(name: string): Promise<boolean>;
|
|
470
|
+
/** List all tables in the database. */
|
|
471
|
+
getTables(): Promise<string[]>;
|
|
472
|
+
/**
|
|
473
|
+
* Get column metadata for a table.
|
|
474
|
+
* Uses the adapter's columns() method which handles engine-specific introspection
|
|
475
|
+
* (PRAGMA table_info for SQLite, information_schema.columns for others).
|
|
476
|
+
*
|
|
477
|
+
* @param tableName - Name of the table to inspect.
|
|
478
|
+
* @returns Array of column info objects: { name, type, nullable, default, primaryKey }.
|
|
479
|
+
*/
|
|
480
|
+
getColumns(tableName: string): Promise<{
|
|
481
|
+
name: string;
|
|
482
|
+
type: string;
|
|
483
|
+
nullable?: boolean;
|
|
484
|
+
default?: unknown;
|
|
485
|
+
primaryKey?: boolean;
|
|
486
|
+
}[]>;
|
|
487
|
+
/**
|
|
488
|
+
* Execute a SQL statement with multiple parameter sets (batch insert/update).
|
|
489
|
+
* Wraps all executions in a single transaction for atomicity and performance.
|
|
490
|
+
*
|
|
491
|
+
* @param sql - The SQL statement with parameter placeholders.
|
|
492
|
+
* @param paramSets - Array of parameter arrays, one per execution.
|
|
493
|
+
* @returns Array of results from each execution.
|
|
494
|
+
*/
|
|
495
|
+
executeMany(sql: string, paramSets?: unknown[][]): Promise<unknown[]>;
|
|
496
|
+
/** Return the last execute() error message, or null. */
|
|
497
|
+
getError(): string | null;
|
|
498
|
+
/**
|
|
499
|
+
* Return query cache statistics from the REAL cache backing this connection.
|
|
500
|
+
*
|
|
501
|
+
* The bound adapter is a CachedDatabaseAdapter (caching is OFF by default —
|
|
502
|
+
* both layers opt-in: request-scoped via TINA4_AUTO_CACHING=true, persistent
|
|
503
|
+
* via TINA4_DB_CACHE=true), so we read the live counters + size + mode from it.
|
|
504
|
+
* Mirrors Python's `Database.cache_stats()`: `{ enabled, mode, hits, misses, size, ttl }`.
|
|
505
|
+
*/
|
|
506
|
+
cacheStats(): {
|
|
507
|
+
enabled: boolean;
|
|
508
|
+
mode: "persistent" | "request" | "off";
|
|
509
|
+
hits: number;
|
|
510
|
+
misses: number;
|
|
511
|
+
size: number;
|
|
512
|
+
ttl: number;
|
|
513
|
+
backend?: string;
|
|
514
|
+
};
|
|
515
|
+
/** Flush the query cache and reset counters (mirrors Python `cache_clear()`). */
|
|
516
|
+
cacheClear(): void;
|
|
517
|
+
/**
|
|
518
|
+
* Clear the request-scoped cache at the START of an HTTP request on this
|
|
519
|
+
* connection (no-op in persistent mode). Mirrors Python's
|
|
520
|
+
* `Database.cache_new_request()`.
|
|
521
|
+
*/
|
|
522
|
+
cacheNewRequest(): void;
|
|
523
|
+
/** Get the last auto-increment id. */
|
|
524
|
+
getLastId(): string | number;
|
|
525
|
+
/**
|
|
526
|
+
* Create the tina4_sequences table if it doesn't exist.
|
|
527
|
+
* Used by sequenceNext() for race-safe ID generation on
|
|
528
|
+
* SQLite, MySQL, MSSQL, and as a PostgreSQL fallback.
|
|
529
|
+
*/
|
|
530
|
+
private ensureSequenceTable;
|
|
531
|
+
/**
|
|
532
|
+
* Best-effort MAX(pk) seed for a new sequence row. 0 if the table is
|
|
533
|
+
* missing/empty. Mirrors Python's `_sequence_seed_value`.
|
|
534
|
+
*/
|
|
535
|
+
private sequenceSeedValue;
|
|
536
|
+
/**
|
|
537
|
+
* Atomically increment and return the next value from the sequence table.
|
|
538
|
+
*
|
|
539
|
+
* DB-contract B (no duplicate primary keys under concurrency): the old path
|
|
540
|
+
* was read-increment-read across several `await` points, so two concurrent
|
|
541
|
+
* async callers could read the same `current_value` and return the same id.
|
|
542
|
+
* This now uses a single atomic increment-and-return per engine, pinned to
|
|
543
|
+
* ONE adapter so the two statements (where two are needed) land on the same
|
|
544
|
+
* connection:
|
|
545
|
+
*
|
|
546
|
+
* * SQLite: the SQLiteAdapter does ensure-table + seed + the atomic
|
|
547
|
+
* `UPDATE ... RETURNING current_value` (>= 3.35; else `+1` then `SELECT`)
|
|
548
|
+
* as ONE synchronous burst — no `await` between read and write, so no
|
|
549
|
+
* other async task can interleave (Node analog of Python's _write_lock).
|
|
550
|
+
* * MySQL: `UPDATE ... SET current_value = LAST_INSERT_ID(current_value + 1)`
|
|
551
|
+
* then `SELECT LAST_INSERT_ID()` on the SAME pinned connection
|
|
552
|
+
* (LAST_INSERT_ID is per-connection → race-safe).
|
|
553
|
+
* * MSSQL: `UPDATE ... SET current_value = current_value + 1 OUTPUT
|
|
554
|
+
* inserted.current_value ...` — one atomic statement.
|
|
555
|
+
*
|
|
556
|
+
* Seeding is always a race-safe insert-if-absent (INSERT OR IGNORE /
|
|
557
|
+
* INSERT IGNORE / INSERT ... WHERE NOT EXISTS) seeded from MAX(pk), run
|
|
558
|
+
* BEFORE the increment — never a read-then-insert gap. On error we RAISE
|
|
559
|
+
* (never silently fall back to 1).
|
|
560
|
+
*/
|
|
561
|
+
private sequenceNext;
|
|
562
|
+
/**
|
|
563
|
+
* MySQL atomic sequence step. LAST_INSERT_ID(expr) stashes `expr` in this
|
|
564
|
+
* CONNECTION's session var and returns it, so the read-back is per-connection
|
|
565
|
+
* and race-safe. Runs on the pinned adapter.
|
|
566
|
+
*/
|
|
567
|
+
private sequenceNextMysql;
|
|
568
|
+
/**
|
|
569
|
+
* MSSQL atomic sequence step. A single `UPDATE ... OUTPUT
|
|
570
|
+
* inserted.current_value` increments and returns the new value in one
|
|
571
|
+
* statement. Runs on the pinned adapter.
|
|
572
|
+
*/
|
|
573
|
+
private sequenceNextMssql;
|
|
574
|
+
/**
|
|
575
|
+
* Defensive generic atomic-ish path for any engine not otherwise special-cased
|
|
576
|
+
* (and the SQLite fallback if the adapter lacks the synchronous helper). Seeds
|
|
577
|
+
* if absent, then increments and reads on the pinned connection.
|
|
578
|
+
*/
|
|
579
|
+
private sequenceNextGeneric;
|
|
580
|
+
/**
|
|
581
|
+
* Pre-generate the next available primary key ID using engine-aware strategies.
|
|
582
|
+
*
|
|
583
|
+
* - Firebird: auto-creates a generator if missing, then increments via GEN_ID (atomic).
|
|
584
|
+
* - PostgreSQL: tries nextval() first; if sequence missing, auto-creates it
|
|
585
|
+
* seeded from MAX; falls through to sequence table on failure.
|
|
586
|
+
* - SQLite/MySQL/MSSQL: uses tina4_sequences table with atomic UPDATE + SELECT
|
|
587
|
+
* (race-safe, replaces old MAX+1).
|
|
588
|
+
* - Returns 1 if the table is empty or does not exist.
|
|
589
|
+
*/
|
|
590
|
+
getNextId(table: string, pkColumn?: string, generatorName?: string): Promise<number>;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Build a connected `DatabaseAdapter` from a connection URL.
|
|
594
|
+
*
|
|
595
|
+
* Used internally by `initDatabase()` and `Database.create()`, and exported so
|
|
596
|
+
* users can construct a NAMED secondary adapter without making it the default:
|
|
597
|
+
*
|
|
598
|
+
* bindDatabase(await createAdapterFromUrl(url, user, pass), "analytics");
|
|
599
|
+
*
|
|
600
|
+
* Unlike `initDatabase()`, this does NOT call `setAdapter()` — it returns a
|
|
601
|
+
* standalone adapter that the caller decides what to do with. For async engines
|
|
602
|
+
* (Postgres/MySQL/MSSQL/Firebird/Mongo) the returned adapter is already
|
|
603
|
+
* connected; SQLite connects lazily.
|
|
604
|
+
*/
|
|
605
|
+
export declare function createAdapterFromUrl(url: string, username?: string, password?: string): Promise<DatabaseAdapter>;
|
|
606
|
+
/**
|
|
607
|
+
* Initialize the database from a config object or TINA4_DATABASE_URL env var.
|
|
608
|
+
* Now returns a Database wrapper instance.
|
|
609
|
+
*
|
|
610
|
+
* Priority:
|
|
611
|
+
* 1. config.url (explicit URL)
|
|
612
|
+
* 2. process.env.TINA4_DATABASE_URL
|
|
613
|
+
* 3. config.type + config.path (legacy)
|
|
614
|
+
*/
|
|
615
|
+
/**
|
|
616
|
+
* Resolve the connection-pool size from `TINA4_DB_POOL`.
|
|
617
|
+
*
|
|
618
|
+
* Default: 0 (single-connection mode). Any positive integer enables
|
|
619
|
+
* round-robin pooling with that many connections — Database.create() honours
|
|
620
|
+
* this transparently. The env var is the simple deploy-time override; tests
|
|
621
|
+
* and library users can still pass `pool` directly to Database.create().
|
|
622
|
+
*/
|
|
623
|
+
export declare function resolveDbPool(): number;
|
|
624
|
+
/**
|
|
625
|
+
* Open a database connection — convention name matching SQLAlchemy
|
|
626
|
+
* `engine.connect()` and the cross-framework Database.get_connection()
|
|
627
|
+
* surface shipped in 3.13.x.
|
|
628
|
+
*
|
|
629
|
+
* Equivalent to `initDatabase({ url })` but with an opinionated, simpler
|
|
630
|
+
* signature: pass a URL string directly, or omit for env-based defaults
|
|
631
|
+
* (falls back to in-memory SQLite when nothing resolves).
|
|
632
|
+
*
|
|
633
|
+
* const db = await Database.getConnection(); // from env
|
|
634
|
+
* const db = await Database.getConnection("sqlite://./app.db"); // explicit URL
|
|
635
|
+
* const db = await Database.getConnection("postgres://localhost/x", { username: "u", password: "p" });
|
|
636
|
+
*
|
|
637
|
+
* Cross-framework parity with Python `Database.get_connection()`, PHP
|
|
638
|
+
* `\Tina4\Database::getConnection()`, and Ruby `Tina4::Database.get_connection`.
|
|
639
|
+
*/
|
|
640
|
+
export declare namespace Database {
|
|
641
|
+
function getConnection(url?: string, opts?: {
|
|
642
|
+
username?: string;
|
|
643
|
+
password?: string;
|
|
644
|
+
}): Promise<Database>;
|
|
645
|
+
/**
|
|
646
|
+
* Clear the request-scoped query cache on every live connection.
|
|
647
|
+
*
|
|
648
|
+
* Static convenience mirroring Python's `Database.reset_request_caches()`
|
|
649
|
+
* classmethod. The request dispatcher calls this at the start of each HTTP
|
|
650
|
+
* request so request-scoped caching never serves rows across requests.
|
|
651
|
+
* Persistent-mode connections (TINA4_DB_CACHE=true) are left alone.
|
|
652
|
+
*/
|
|
653
|
+
function resetRequestCaches(): void;
|
|
654
|
+
}
|
|
655
|
+
export declare function initDatabase(config?: DatabaseConfig): Promise<Database>;
|