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.
Files changed (193) hide show
  1. package/CLAUDE.md +170 -28
  2. package/README.md +2 -2
  3. package/package.json +13 -9
  4. package/packages/cli/dist/bin.js +33126 -30055
  5. package/packages/cli/src/commands/metrics.ts +17 -11
  6. package/packages/cli/src/commands/serve.ts +10 -9
  7. package/packages/core/dist/index.js +33062 -29908
  8. package/packages/core/src/ai.ts +7 -1
  9. package/packages/core/src/auth.ts +191 -39
  10. package/packages/core/src/background.ts +19 -19
  11. package/packages/core/src/cache.ts +492 -49
  12. package/packages/core/src/devAdmin.ts +79 -32
  13. package/packages/core/src/devMailbox.ts +20 -44
  14. package/packages/core/src/dispatchPipeline.ts +285 -0
  15. package/packages/core/src/dotenv.ts +185 -40
  16. package/packages/core/src/index.ts +7 -6
  17. package/packages/core/src/logger.ts +257 -36
  18. package/packages/core/src/mcp.ts +1 -1
  19. package/packages/core/src/messenger.ts +81 -13
  20. package/packages/core/src/metrics.ts +199 -961
  21. package/packages/core/src/middleware.ts +390 -123
  22. package/packages/core/src/queue.ts +188 -32
  23. package/packages/core/src/queueBackends/kafkaBackend.ts +109 -13
  24. package/packages/core/src/queueBackends/liteBackend.ts +13 -0
  25. package/packages/core/src/queueBackends/mongoBackend.ts +101 -9
  26. package/packages/core/src/queueBackends/rabbitmqBackend.ts +22 -4
  27. package/packages/core/src/rateLimiter.ts +10 -5
  28. package/packages/core/src/request.ts +6 -9
  29. package/packages/core/src/response.ts +46 -1
  30. package/packages/core/src/router.ts +29 -4
  31. package/packages/core/src/server.ts +751 -414
  32. package/packages/core/src/session.ts +244 -27
  33. package/packages/core/src/sessionHandlers/childError.ts +72 -0
  34. package/packages/core/src/sessionHandlers/databaseHandler.ts +338 -48
  35. package/packages/core/src/sessionHandlers/memcachedHandler.ts +181 -0
  36. package/packages/core/src/sessionHandlers/mongoClient.ts +293 -202
  37. package/packages/core/src/sessionHandlers/mongoHandler.ts +88 -8
  38. package/packages/core/src/sessionHandlers/respClient.ts +16 -143
  39. package/packages/core/src/sessionHandlers/sqlClient.ts +290 -0
  40. package/packages/core/src/sessionHandlers/syncBridge.ts +190 -0
  41. package/packages/core/src/sessionHandlers/syncSocket.ts +236 -0
  42. package/packages/core/src/testClient.ts +18 -5
  43. package/packages/core/src/trustedProxy.ts +249 -0
  44. package/packages/core/src/types.ts +29 -5
  45. package/packages/core/src/websocket.ts +66 -0
  46. package/packages/frond/dist/index.js +74 -31
  47. package/packages/frond/src/engine.ts +99 -33
  48. package/packages/orm/dist/index.js +26554 -23400
  49. package/packages/orm/src/adapters/firebird.ts +183 -56
  50. package/packages/orm/src/adapters/mongodb.ts +25 -4
  51. package/packages/orm/src/adapters/mssql.ts +114 -29
  52. package/packages/orm/src/adapters/mysql.ts +103 -40
  53. package/packages/orm/src/adapters/odbc.ts +44 -21
  54. package/packages/orm/src/adapters/postgres.ts +118 -26
  55. package/packages/orm/src/adapters/sqlDialect.ts +120 -0
  56. package/packages/orm/src/adapters/sqlite.ts +64 -25
  57. package/packages/orm/src/baseModel.ts +135 -40
  58. package/packages/orm/src/cachedDatabase.ts +43 -19
  59. package/packages/orm/src/connectTimeout.ts +265 -0
  60. package/packages/orm/src/database.ts +338 -198
  61. package/packages/orm/src/databaseResult.ts +65 -13
  62. package/packages/orm/src/databaseUrl.ts +484 -0
  63. package/packages/orm/src/docstore.ts +386 -145
  64. package/packages/orm/src/index.ts +13 -3
  65. package/packages/orm/src/migration.ts +18 -3
  66. package/packages/orm/src/queryBuilder.ts +38 -4
  67. package/packages/orm/src/sqlTranslator.ts +310 -4
  68. package/packages/orm/src/types.ts +15 -4
  69. package/types/cli/src/bin.d.ts +92 -0
  70. package/types/cli/src/commands/build.d.ts +2 -0
  71. package/types/cli/src/commands/generate.d.ts +47 -0
  72. package/types/cli/src/commands/init.d.ts +1 -0
  73. package/types/cli/src/commands/metrics.d.ts +6 -0
  74. package/types/cli/src/commands/migrate.d.ts +1 -0
  75. package/types/cli/src/commands/migrateCreate.d.ts +1 -0
  76. package/types/cli/src/commands/migrateRollback.d.ts +1 -0
  77. package/types/cli/src/commands/migrateStatus.d.ts +1 -0
  78. package/types/cli/src/commands/queue.d.ts +20 -0
  79. package/types/cli/src/commands/routes.d.ts +1 -0
  80. package/types/cli/src/commands/seed.d.ts +1 -0
  81. package/types/cli/src/commands/serve.d.ts +6 -0
  82. package/types/cli/src/commands/test.d.ts +1 -0
  83. package/types/core/src/ai.d.ts +64 -0
  84. package/types/core/src/api.d.ts +262 -0
  85. package/types/core/src/auth.d.ts +177 -0
  86. package/types/core/src/authGate.d.ts +20 -0
  87. package/types/core/src/background.d.ts +34 -0
  88. package/types/core/src/cache.d.ts +163 -0
  89. package/types/core/src/constants.d.ts +38 -0
  90. package/types/core/src/container.d.ts +44 -0
  91. package/types/core/src/context/chunker.d.ts +31 -0
  92. package/types/core/src/context/index.d.ts +93 -0
  93. package/types/core/src/devAdmin.d.ts +179 -0
  94. package/types/core/src/devMailbox.d.ts +54 -0
  95. package/types/core/src/dispatchPipeline.d.ts +117 -0
  96. package/types/core/src/docs.d.ts +141 -0
  97. package/types/core/src/docsAutoDiscovery.d.ts +6 -0
  98. package/types/core/src/dotenv.d.ts +87 -0
  99. package/types/core/src/env.d.ts +28 -0
  100. package/types/core/src/errorOverlay.d.ts +36 -0
  101. package/types/core/src/events.d.ts +75 -0
  102. package/types/core/src/fakeData.d.ts +55 -0
  103. package/types/core/src/feedback.d.ts +90 -0
  104. package/types/core/src/graphql.d.ts +207 -0
  105. package/types/core/src/health.d.ts +22 -0
  106. package/types/core/src/htmlElement.d.ts +75 -0
  107. package/types/core/src/i18n.d.ts +37 -0
  108. package/types/core/src/index.d.ts +92 -0
  109. package/types/core/src/job.d.ts +39 -0
  110. package/types/core/src/logger.d.ts +200 -0
  111. package/types/core/src/mcp.d.ts +248 -0
  112. package/types/core/src/messenger.d.ts +191 -0
  113. package/types/core/src/metrics.d.ts +41 -0
  114. package/types/core/src/middleware.d.ts +330 -0
  115. package/types/core/src/mqtt.d.ts +257 -0
  116. package/types/core/src/mqttMessage.d.ts +67 -0
  117. package/types/core/src/plan.d.ts +96 -0
  118. package/types/core/src/projectIndex.d.ts +56 -0
  119. package/types/core/src/queue.d.ts +268 -0
  120. package/types/core/src/queueBackends/kafkaBackend.d.ts +117 -0
  121. package/types/core/src/queueBackends/liteBackend.d.ts +128 -0
  122. package/types/core/src/queueBackends/mongoBackend.d.ts +119 -0
  123. package/types/core/src/queueBackends/rabbitmqBackend.d.ts +55 -0
  124. package/types/core/src/rateLimiter.d.ts +49 -0
  125. package/types/core/src/request.d.ts +25 -0
  126. package/types/core/src/response.d.ts +28 -0
  127. package/types/core/src/routeDiscovery.d.ts +12 -0
  128. package/types/core/src/router.d.ts +366 -0
  129. package/types/core/src/scss.d.ts +19 -0
  130. package/types/core/src/server.d.ts +146 -0
  131. package/types/core/src/service.d.ts +115 -0
  132. package/types/core/src/session.d.ts +341 -0
  133. package/types/core/src/sessionHandlers/childError.d.ts +34 -0
  134. package/types/core/src/sessionHandlers/databaseHandler.d.ts +97 -0
  135. package/types/core/src/sessionHandlers/memcachedHandler.d.ts +60 -0
  136. package/types/core/src/sessionHandlers/mongoClient.d.ts +35 -0
  137. package/types/core/src/sessionHandlers/mongoHandler.d.ts +109 -0
  138. package/types/core/src/sessionHandlers/respClient.d.ts +22 -0
  139. package/types/core/src/sessionHandlers/sqlClient.d.ts +39 -0
  140. package/types/core/src/sessionHandlers/syncBridge.d.ts +91 -0
  141. package/types/core/src/sessionHandlers/syncSocket.d.ts +49 -0
  142. package/types/core/src/sessionHandlers/valkeyHandler.d.ts +65 -0
  143. package/types/core/src/static.d.ts +2 -0
  144. package/types/core/src/test.d.ts +94 -0
  145. package/types/core/src/testClient.d.ts +36 -0
  146. package/types/core/src/testing.d.ts +58 -0
  147. package/types/core/src/trustedProxy.d.ts +44 -0
  148. package/types/core/src/types.d.ts +242 -0
  149. package/types/core/src/validator.d.ts +52 -0
  150. package/types/core/src/websocket.d.ts +402 -0
  151. package/types/core/src/websocketBackplane.d.ts +166 -0
  152. package/types/core/src/websocketConnection.d.ts +54 -0
  153. package/types/core/src/wsdl.d.ts +101 -0
  154. package/types/frond/src/engine.d.ts +263 -0
  155. package/types/frond/src/index.d.ts +2 -0
  156. package/types/orm/src/adapters/firebird.d.ts +183 -0
  157. package/types/orm/src/adapters/mongodb.d.ts +81 -0
  158. package/types/orm/src/adapters/mssql.d.ts +77 -0
  159. package/types/orm/src/adapters/mysql.d.ts +67 -0
  160. package/types/orm/src/adapters/odbc.d.ts +94 -0
  161. package/types/orm/src/adapters/postgres.d.ts +86 -0
  162. package/types/orm/src/adapters/sqlDialect.d.ts +71 -0
  163. package/types/orm/src/adapters/sqlite.d.ts +68 -0
  164. package/types/orm/src/autoCrud.d.ts +73 -0
  165. package/types/orm/src/baseModel.d.ts +427 -0
  166. package/types/orm/src/cachedDatabase.d.ts +190 -0
  167. package/types/orm/src/connectTimeout.d.ts +100 -0
  168. package/types/orm/src/database.d.ts +655 -0
  169. package/types/orm/src/databaseResult.d.ts +109 -0
  170. package/types/orm/src/databaseUrl.d.ts +125 -0
  171. package/types/orm/src/docstore.d.ts +241 -0
  172. package/types/orm/src/fakeData.d.ts +22 -0
  173. package/types/orm/src/index.d.ts +43 -0
  174. package/types/orm/src/migration.d.ts +275 -0
  175. package/types/orm/src/model.d.ts +7 -0
  176. package/types/orm/src/query.d.ts +14 -0
  177. package/types/orm/src/queryBuilder.d.ts +193 -0
  178. package/types/orm/src/realtime/index.d.ts +7 -0
  179. package/types/orm/src/realtime/models/attachment.d.ts +43 -0
  180. package/types/orm/src/realtime/models/channel.d.ts +32 -0
  181. package/types/orm/src/realtime/models/channelMember.d.ts +32 -0
  182. package/types/orm/src/realtime/models/message.d.ts +36 -0
  183. package/types/orm/src/realtime/models/workspace.d.ts +26 -0
  184. package/types/orm/src/realtime/realtime.d.ts +24 -0
  185. package/types/orm/src/realtime/storage.d.ts +61 -0
  186. package/types/orm/src/seeder.d.ts +118 -0
  187. package/types/orm/src/sqlTranslator.d.ts +258 -0
  188. package/types/orm/src/types.d.ts +148 -0
  189. package/types/orm/src/validation.d.ts +6 -0
  190. package/types/swagger/src/generator.d.ts +46 -0
  191. package/types/swagger/src/index.d.ts +2 -0
  192. package/types/swagger/src/ui.d.ts +11 -0
  193. 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>;