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,427 @@
1
+ import { QueryBuilder } from "./queryBuilder.js";
2
+ import { QueryCache } from "./sqlTranslator.js";
3
+ import type { DatabaseAdapter, FieldDefinition, RelationshipDefinition } from "./types.js";
4
+ /**
5
+ * Convert a snake_case name to camelCase.
6
+ * Lowercases the input first so UPPERCASE DB column names (Firebird/Oracle) map correctly.
7
+ */
8
+ export declare function snakeToCamel(name: string): string;
9
+ /**
10
+ * Convert a camelCase name to snake_case.
11
+ */
12
+ export declare function camelToSnake(name: string): string;
13
+ /**
14
+ * Convert an in-memory field value to its database representation.
15
+ * A "json" field serialises its object/array to a JSON string for the driver
16
+ * (parity with the Python master's JSONField.to_db). A value that can't be
17
+ * serialised (e.g. a circular reference or a BigInt) throws — save() builds
18
+ * the row inside its try/catch, so it fails loud (rolls back, returns false,
19
+ * records the cause). null/undefined and an already-serialised string pass
20
+ * through untouched. Every other field type is returned as-is.
21
+ */
22
+ export declare function toDbFieldValue(def: FieldDefinition | undefined, value: unknown): unknown;
23
+ /**
24
+ * Convert a database value to its in-memory representation for a field.
25
+ * A "json" column comes back from the driver as a JSON string (SQLite TEXT,
26
+ * MySQL JSON, PostgreSQL JSONB via the text protocol, MSSQL NVARCHAR); decode
27
+ * it to the object/array the property expects (parity with the Python master's
28
+ * JSONField parse-on-read). A value already an object/array is left untouched;
29
+ * null stays null; a non-decodable string keeps its raw form.
30
+ */
31
+ export declare function fromDbFieldValue(def: FieldDefinition | undefined, value: unknown): unknown;
32
+ /**
33
+ * BaseModel provides instance methods for ORM models.
34
+ * Models extend this class and define static properties.
35
+ *
36
+ * Usage:
37
+ * class User extends BaseModel {
38
+ * static tableName = "users";
39
+ * static fields = { id: { type: "integer", primaryKey: true, autoIncrement: true }, ... };
40
+ * static softDelete = true;
41
+ * static tableFilter = "active = 1";
42
+ * static hasOne = [{ model: "Profile", foreignKey: "user_id" }];
43
+ * static hasMany = [{ model: "Post", foreignKey: "author_id" }];
44
+ * static _db = "secondary";
45
+ * static fieldMapping = { firstName: "first_name", lastName: "last_name" };
46
+ * static autoMap = true; // auto-generate fieldMapping from camelCase → snake_case
47
+ * }
48
+ */
49
+ export declare class BaseModel {
50
+ static tableName: string;
51
+ static fields: Record<string, FieldDefinition>;
52
+ static softDelete?: boolean;
53
+ static tableFilter?: string;
54
+ static hasOne?: RelationshipDefinition[];
55
+ static hasMany?: RelationshipDefinition[];
56
+ static belongsTo?: RelationshipDefinition[];
57
+ static _db?: string;
58
+ static _queryCache?: QueryCache;
59
+ /**
60
+ * When true, auto-generates fieldMapping entries from camelCase field names
61
+ * to snake_case DB column names. Explicit fieldMapping entries always win.
62
+ */
63
+ static autoMap: boolean;
64
+ /**
65
+ * Maps JS property names to database column names.
66
+ * Example: { firstName: "first_name" } means the JS property `firstName`
67
+ * corresponds to the database column `first_name`.
68
+ * Properties not listed here use the property name as-is.
69
+ */
70
+ static fieldMapping: Record<string, string>;
71
+ /**
72
+ * When true, auto-generates CRUD routes for this model.
73
+ * Models must explicitly opt-in by setting `static autoCrud = true;`.
74
+ */
75
+ static autoCrud: boolean;
76
+ /** Instance data */
77
+ [key: string]: unknown;
78
+ /** Relationship cache for lazy loading */
79
+ private _relCache;
80
+ /**
81
+ * Cause of the most recent failed save(). null when the last save()
82
+ * succeeded. Mirrors db.getError() so a caller that checks
83
+ * `if (!(await model.save()))` can still recover the real cause via
84
+ * `model.getError()` / `model.lastError` — the failure never vanishes
85
+ * silently. Set by save() (validation message or driver error), cleared
86
+ * to null on a successful save.
87
+ */
88
+ lastError: string | null;
89
+ constructor(data?: Record<string, unknown> | string);
90
+ /**
91
+ * Get the database column name for a JS property.
92
+ * Returns the mapped column name, or the property name if no mapping exists.
93
+ */
94
+ static getDbColumn(prop: string): string;
95
+ /**
96
+ * Get all instance data converted to database column names.
97
+ * Uses fieldMapping to translate JS property names to DB column names.
98
+ */
99
+ getDbData(): Record<string, unknown>;
100
+ /**
101
+ * Get the reverse mapping (DB column → JS property).
102
+ * Flips fieldMapping so that { firstName: "first_name" } becomes { first_name: "firstName" }.
103
+ */
104
+ static getReverseMapping(): Record<string, string>;
105
+ /**
106
+ * Process any foreignKey field definitions on this model, auto-wiring:
107
+ * - belongsTo entries on this model (strip _id from key → association name)
108
+ * - hasMany entries on the referenced model via the module-level _fkRegistry
109
+ *
110
+ * Idempotent — safe to call multiple times.
111
+ */
112
+ static _processForeignKeys(): void;
113
+ /**
114
+ * Merge any FK-registry-registered hasMany entries for this model.
115
+ * Called before relationship resolution so the referenced model gets its has-many wired.
116
+ */
117
+ static _applyFkRegistry(): void;
118
+ /**
119
+ * Create a fluent QueryBuilder pre-configured for this model's table and database.
120
+ *
121
+ * Usage:
122
+ * const results = User.query().where("active = ?", [1]).orderBy("name").get();
123
+ *
124
+ * @returns A QueryBuilder instance bound to this model's table and database.
125
+ */
126
+ static query(): QueryBuilder;
127
+ /**
128
+ * Get the database adapter for this model.
129
+ * If no adapter is registered, attempts auto-discovery from TINA4_DATABASE_URL.
130
+ * SQLite URLs are initialised synchronously. Other engines require initDatabase()
131
+ * to be called before first use.
132
+ */
133
+ protected static getDb(): DatabaseAdapter;
134
+ /**
135
+ * Get the primary key field name (JS property name).
136
+ */
137
+ protected static getPkField(): string;
138
+ /**
139
+ * EVERY primary-key field name, in declaration order.
140
+ *
141
+ * A key may span several columns. `getPkField()` returns only the FIRST and
142
+ * is kept for the auto-increment paths, which are single-column by
143
+ * definition. Anything that ADDRESSES a row must use this: keying on one
144
+ * column of a composite key matches every row sharing that value, which is
145
+ * the data-loss shape feature 4 removed from the raw write path below.
146
+ */
147
+ protected static getPkFields(): string[];
148
+ /** A WHERE naming EVERY primary-key column, and its bound params. */
149
+ protected pkWhere(): {
150
+ sql: string;
151
+ params: unknown[];
152
+ };
153
+ /**
154
+ * Get the primary key database column name (applies fieldMapping).
155
+ */
156
+ protected static getPkColumn(): string;
157
+ /**
158
+ * Find a record by primary key.
159
+ * @param id Primary key value.
160
+ * @param include Optional array of relationship names to eager-load.
161
+ */
162
+ static findById<T extends BaseModel>(this: new (data?: Record<string, unknown>) => T, id: unknown, include?: string[]): Promise<T | null>;
163
+ /**
164
+ * Create a new instance from data, save it, and return the saved instance.
165
+ *
166
+ * Canonical #3: if the underlying save() fails (validation errors or a
167
+ * driver error), create() returns `false` — it does NOT hand back a
168
+ * possibly-unsaved instance, so a failed insert can never masquerade as a
169
+ * success. The failure cause is logged and available on the (discarded)
170
+ * instance's getError() via the same path save() uses.
171
+ *
172
+ * Usage:
173
+ * const user = User.create({ name: "Alice", email: "alice@example.com" });
174
+ * if (!(await User.create({ name: null }))) { ... } // save() failed -> false
175
+ */
176
+ static create<T extends BaseModel>(this: new (data?: Record<string, unknown>) => T, data?: Record<string, unknown>): Promise<T | false>;
177
+ /**
178
+ * Find record(s) by primary key, filter object, or all.
179
+ *
180
+ * Outlier C — overloaded on the first argument (parity with
181
+ * Python/PHP/Ruby):
182
+ * - number | string (scalar PK) → single instance (or null), like
183
+ * findById(pk). `include` is accepted as the 2nd argument in this form.
184
+ * - object (filter) → array of instances (AND-ed conditions).
185
+ * - omitted → array of all records.
186
+ *
187
+ * Usage:
188
+ * User.find(1) → User | null (PK lookup)
189
+ * User.find(1, ["posts"]) → User | null (PK lookup + eager)
190
+ * User.find({ name: "Alice" }) → [User, ...]
191
+ * User.find({ age: 18 }, 10) → [User, ...] (limit 10)
192
+ * User.find({}, 100, 0, "name ASC") → [User, ...] (with orderBy)
193
+ * User.find() → all records
194
+ */
195
+ static find<T extends BaseModel>(this: new (data?: Record<string, unknown>) => T, pk: number | string, include?: string[]): Promise<T | null>;
196
+ static find<T extends BaseModel>(this: new (data?: Record<string, unknown>) => T, filter?: Record<string, unknown>, limit?: number, offset?: number, orderBy?: string, include?: string[]): Promise<T[]>;
197
+ /**
198
+ * Load a record into this instance via selectOne.
199
+ * Returns true if found and loaded, false otherwise.
200
+ */
201
+ /**
202
+ * Load a record into this instance.
203
+ *
204
+ * Usage:
205
+ * orm.id = 1; orm.load() — uses PK already set
206
+ * orm.load("id = ?", [1]) — filter with params
207
+ * orm.load("id = 1") — filter string
208
+ *
209
+ * Returns true if found, false otherwise.
210
+ */
211
+ load(filter?: string, params?: unknown[], include?: string[]): Promise<boolean>;
212
+ /**
213
+ * Find all records.
214
+ *
215
+ * BREAKING (3.13.95, parity): the signature is now
216
+ * `all(limit?, offset?, include?, orderBy?)`. It NO LONGER accepts leading
217
+ * `where`/`params`.
218
+ *
219
+ * Node was the sole outlier of the four. The master and the other two never
220
+ * had a filter on `all()`:
221
+ * Python all(limit=100, offset=0, include=None, order_by=None)
222
+ * PHP all(int $limit = 100, int $offset = 0, ?array $include, ?string $orderBy)
223
+ * Ruby all(limit: 100, offset: nil, order_by: nil, include: nil)
224
+ * Node's extra leading parameters shifted every argument, so the same
225
+ * positional call meant different things in different languages -- which is
226
+ * precisely what the parity mandate exists to prevent.
227
+ *
228
+ * MIGRATION: a filtered read moves to `where()`, which already exists and
229
+ * takes the conditions first:
230
+ * before: User.all("age > ?", [28])
231
+ * after: User.where("age > ?", [28])
232
+ * TypeScript callers get a compile error (string is not assignable to number),
233
+ * so the break is loud rather than silent.
234
+ *
235
+ * @param limit Max records (default 100, the shared cross-framework cap).
236
+ * @param offset Records to skip (default 0).
237
+ * @param include Relationship names to eager-load.
238
+ * @param orderBy ORDER BY clause (e.g. "name ASC").
239
+ */
240
+ static all<T extends BaseModel>(this: new (data?: Record<string, unknown>) => T, limit?: number, offset?: number, include?: string[], orderBy?: string): Promise<T[]>;
241
+ /**
242
+ * Query records with a WHERE clause.
243
+ * Matches Python/PHP/Ruby where() API.
244
+ *
245
+ * @param conditions WHERE clause (e.g. "age > ? AND active = ?")
246
+ * @param params Bind parameters
247
+ * @param limit Max records (default 100)
248
+ * @param offset Skip records (default 0)
249
+ * @param include Relationship names to eager-load
250
+ * @param orderBy ORDER BY clause (e.g. "name ASC")
251
+ */
252
+ static where<T extends BaseModel>(this: new (data?: Record<string, unknown>) => T, conditions: string, params?: unknown[], limit?: number, offset?: number, include?: string[], orderBy?: string): Promise<T[]>;
253
+ /**
254
+ * Save this instance (insert or update). Returns this on success (fluent
255
+ * self), false on failure.
256
+ *
257
+ * Fails loud, never silent (the same principle db.execute() follows by
258
+ * raising). On ANY failure path save() returns `false` — keeping the
259
+ * contract callers rely on (`if (!(await model.save())) ...`) — but it also
260
+ * (a) logs the real cause via Log.error with model/table context and
261
+ * (b) records the cause on `this.lastError` so a caller can recover it after
262
+ * the fact via getError() / lastError. It never throws and never changes the
263
+ * `this | false` return shape.
264
+ *
265
+ * Two distinct failure paths, both loud:
266
+ * - Validation (canonical #2): validate() runs FIRST. If it returns errors,
267
+ * save() logs them, records them on lastError, and returns false WITHOUT
268
+ * touching the database — an invalid model never reaches the driver.
269
+ * - Database: a driver error (NOT NULL, duplicate PK, missing table, ...) is
270
+ * rolled back, logged with the underlying cause, recorded on lastError,
271
+ * and returns false — the cause is no longer swallowed silently.
272
+ */
273
+ save(): Promise<this | false>;
274
+ /**
275
+ * Return the cause of the most recent failed save(), or null.
276
+ *
277
+ * Mirrors db.getError(). After save() returns false — whether from
278
+ * validation or a driver error — the real cause is retrievable here (and on
279
+ * this.lastError) so a caller using the `if (!(await model.save()))`
280
+ * contract can still surface it. Cleared to null on a successful save.
281
+ */
282
+ getError(): string | null;
283
+ /**
284
+ * Delete this instance. Uses soft delete if configured.
285
+ */
286
+ delete(): Promise<boolean>;
287
+ /**
288
+ * Convert to plain object (dictionary).
289
+ * @param include Optional array of relationship names to include (supports dot notation for nesting).
290
+ * @param case_ Key casing: 'camel' (default, keys as-is) or 'snake' (convert via fieldMapping).
291
+ */
292
+ toDict(include?: string[], case_?: "camel" | "snake"): Record<string, unknown>;
293
+ /**
294
+ * Convert to an associative object (alias for toDict).
295
+ */
296
+ toAssoc(include?: string[], case_?: "camel" | "snake"): Record<string, unknown>;
297
+ /**
298
+ * Convert to a plain object (alias for toDict).
299
+ */
300
+ toObject(case_?: "camel" | "snake"): Record<string, unknown>;
301
+ /**
302
+ * Convert to an array of values.
303
+ */
304
+ toArray(): unknown[];
305
+ /**
306
+ * Convert to a list (alias for toArray).
307
+ */
308
+ toList(): unknown[];
309
+ /**
310
+ * Convert to JSON string.
311
+ * @param include Optional relationship names to include.
312
+ */
313
+ toJson(include?: string[], case_?: "camel" | "snake"): string;
314
+ /**
315
+ * Validate this instance's values against the model's field definitions.
316
+ * Returns an array of error strings (empty array means valid).
317
+ */
318
+ validate(): string[];
319
+ /**
320
+ * Generate and execute CREATE TABLE DDL from the model's field definitions.
321
+ * Uses the adapter's createTable method if available, otherwise builds SQL directly.
322
+ */
323
+ static createTable(): Promise<boolean>;
324
+ /**
325
+ * Find a record by primary key or throw an error if not found.
326
+ */
327
+ static findOrFail<T extends BaseModel>(this: new (data?: Record<string, unknown>) => T, id: unknown): Promise<T>;
328
+ /**
329
+ * Return true if a record with the given primary key exists.
330
+ */
331
+ static exists(pkValue: unknown): Promise<boolean>;
332
+ /**
333
+ * Run a raw SQL query with results cached by TTL. Cache is per-model-class.
334
+ *
335
+ * @param sql SQL query string.
336
+ * @param params Bind parameters.
337
+ * @param ttl Cache TTL in seconds (default 60).
338
+ * @param limit Max records to return (default 100).
339
+ * @param offset Records to skip (default 0).
340
+ * @param include Relationship names to eager-load on cache miss.
341
+ */
342
+ static cached<T extends BaseModel>(this: new (data?: Record<string, unknown>) => T, sql: string, params?: unknown[], ttl?: number, limit?: number, offset?: number, include?: string[]): Promise<T[]>;
343
+ /**
344
+ * Clear the per-model query cache.
345
+ */
346
+ static clearCache(): void;
347
+ /**
348
+ * Execute a raw SQL SELECT and return results as model instances.
349
+ */
350
+ static select<T extends BaseModel>(this: new (data?: Record<string, unknown>) => T, sql: string, params?: unknown[], limit?: number, offset?: number): Promise<T[]>;
351
+ static selectOne<T extends BaseModel>(this: new (data?: Record<string, unknown>) => T, sql: string, params?: unknown[], include?: string[]): Promise<T | null>;
352
+ /**
353
+ * Permanently delete this instance, bypassing soft delete.
354
+ */
355
+ forceDelete(): Promise<boolean>;
356
+ /**
357
+ * Restore a soft-deleted record.
358
+ */
359
+ restore(): Promise<boolean>;
360
+ /**
361
+ * Find records including soft-deleted ones.
362
+ */
363
+ static withTrashed<T extends BaseModel>(this: new (data?: Record<string, unknown>) => T, conditions?: string, params?: unknown[], limit?: number, offset?: number): Promise<T[]>;
364
+ /**
365
+ * Count records matching conditions (respects soft delete and table filter).
366
+ */
367
+ static count(conditions?: string, params?: unknown[]): Promise<number>;
368
+ /**
369
+ * Register a reusable query scope on the class.
370
+ *
371
+ * Usage:
372
+ * User.scope("active", "active = ?", [1]);
373
+ * const users = (User as any).active(); // calls where("active = ?", [1])
374
+ * const users = (User as any).active(10, 5); // with limit/offset
375
+ */
376
+ static scope(name: string, filterSql: string, params?: unknown[]): void;
377
+ /**
378
+ * Load a has-one related model instance.
379
+ */
380
+ hasOne<T extends BaseModel, R extends BaseModel>(this: T, relatedClass: typeof BaseModel & (new (data?: Record<string, unknown>) => R), foreignKey: string): Promise<R | null>;
381
+ /**
382
+ * Load has-many related model instances.
383
+ */
384
+ hasMany<T extends BaseModel, R extends BaseModel>(this: T, relatedClass: typeof BaseModel & (new (data?: Record<string, unknown>) => R), foreignKey: string, limit?: number, offset?: number): Promise<R[]>;
385
+ /**
386
+ * Load the parent model this instance belongs to.
387
+ */
388
+ belongsTo<T extends BaseModel, R extends BaseModel>(this: T, relatedClass: typeof BaseModel & (new (data?: Record<string, unknown>) => R), foreignKey: string): Promise<R | null>;
389
+ /**
390
+ * Register a model class for lookup by name (used by eager loading).
391
+ */
392
+ static _modelRegistry: Record<string, typeof BaseModel>;
393
+ static registerModel(name: string, modelClass: typeof BaseModel): void;
394
+ /**
395
+ * Process foreignKey fields on every registered model so the cross-model
396
+ * _fkRegistry (and each model's belongsTo/hasMany) is fully wired regardless
397
+ * of which model was used first. Idempotent — _processForeignKeys() and
398
+ * _applyFkRegistry() both guard against duplicates.
399
+ */
400
+ private static _processAllForeignKeys;
401
+ /**
402
+ * Resolve a model class by name from the registry.
403
+ */
404
+ private static _resolveModel;
405
+ /**
406
+ * Eager load relationships for a collection of instances (prevents N+1).
407
+ * @param instances Array of model instances.
408
+ * @param include Array of relationship names (supports dot notation for nesting).
409
+ */
410
+ static _eagerLoad(instances: BaseModel[], include: string[]): Promise<void>;
411
+ /**
412
+ * Public alias for _eagerLoad. Eagerly loads relationships for a list of instances,
413
+ * preventing N+1 queries.
414
+ *
415
+ * Usage:
416
+ * const users = User.all();
417
+ * await User.eagerLoad(users, ["posts", "profile"]);
418
+ *
419
+ * @param instances Array of model instances to load relationships onto.
420
+ * @param includeList Array of relationship names (supports dot notation for nesting).
421
+ */
422
+ static eagerLoad(instances: BaseModel[], includeList: string[]): Promise<void>;
423
+ /**
424
+ * Clear the relationship cache.
425
+ */
426
+ clearRelCache(): void;
427
+ }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Tina4 Cached Database — Transparent query cache decorator for DatabaseAdapter.
3
+ *
4
+ * Wraps any DatabaseAdapter and caches SELECT results from fetch() and fetchOne()
5
+ * (plus their *Async variants). Write operations (insert, update, delete, execute,
6
+ * createTable, addColumn) flush the entire cache when caching is enabled.
7
+ *
8
+ * One store, two layers (mirrors the Python master — tina4_python/database/connection.py):
9
+ *
10
+ * • request-scoped (DEFAULT OFF, opt-in TINA4_AUTO_CACHING=true) — dedupes
11
+ * identical SELECTs to protect the DB from rapid repeat reads. Cleared at the
12
+ * START of every HTTP request (via Database.resetRequestCaches()) AND on any
13
+ * write, with a short safety TTL (TINA4_AUTO_CACHING_TTL, default 5s) for
14
+ * non-request contexts (scripts/workers). Default OFF because a request-scoped
15
+ * cache defaulting ON is a footgun — a read-after-write in one request (e.g.
16
+ * SELECT MAX(id) then INSERT) returns a cached pre-write value. Opt in for
17
+ * read-heavy endpoints.
18
+ * • persistent (opt-in, TINA4_DB_CACHE=true) — cross-request TTL cache that is
19
+ * NOT cleared per request; entries expire by TINA4_DB_CACHE_TTL (default 30s).
20
+ *
21
+ * enabled = persistent || requestScoped
22
+ * mode = persistent ? "persistent" : (requestScoped ? "request" : "off")
23
+ * ttl = persistent ? 30 : 5 (env-overridable)
24
+ *
25
+ * Usage (the framework wires this automatically at the adapter bind path):
26
+ * import { CachedDatabaseAdapter } from "@tina4/orm";
27
+ * import { SQLiteAdapter } from "./adapters/sqlite.js";
28
+ *
29
+ * const raw = new SQLiteAdapter("./data/app.db");
30
+ * const db = new CachedDatabaseAdapter(raw);
31
+ * db.fetch("SELECT * FROM users"); // cached on second call
32
+ * db.cacheStats(); // { enabled, mode, hits, misses, size, ttl }
33
+ */
34
+ import { QueryCache } from "./sqlTranslator.js";
35
+ import type { DatabaseAdapter, DatabaseResult, ColumnInfo, FieldDefinition } from "./types.js";
36
+ /**
37
+ * Options for wrapping an adapter with a query cache. When several pooled
38
+ * connections must share one cache store (so a write on any connection
39
+ * invalidates reads cached by all of them), pass the same `sharedCache`.
40
+ */
41
+ export interface CachedAdapterOptions {
42
+ /** Force-enable the persistent (cross-request) layer. Defaults to TINA4_DB_CACHE. */
43
+ persistent?: boolean;
44
+ /** Force-enable the request-scoped layer. Defaults to TINA4_AUTO_CACHING (default false / opt-in). */
45
+ requestScoped?: boolean;
46
+ /** Override the effective TTL (seconds). Defaults to the mode-appropriate env var. */
47
+ ttl?: number;
48
+ /** Share a single QueryCache store across multiple wrappers (pooled connections). */
49
+ sharedCache?: QueryCache;
50
+ }
51
+ export declare class CachedDatabaseAdapter implements DatabaseAdapter {
52
+ /**
53
+ * Live wrappers, so the request dispatcher can clear the request-scoped cache
54
+ * on every connection at the start of each HTTP request. Mirrors Python's
55
+ * `Database._instances` WeakSet. A WeakSet lets closed connections be GC'd.
56
+ */
57
+ private static instances;
58
+ private adapter;
59
+ private cache;
60
+ /** Persistent (cross-request) layer — TINA4_DB_CACHE. */
61
+ private cachePersistent;
62
+ /** Request-scoped layer — TINA4_AUTO_CACHING (default OFF / opt-in). */
63
+ private cacheRequestScoped;
64
+ private enabled;
65
+ private ttl;
66
+ private hits;
67
+ private misses;
68
+ /**
69
+ * Persistent-mode distributed backend (TINA4_DB_CACHE=true). Built lazily from
70
+ * the SAME unified `createBackend()` factory the response/KV cache uses, so
71
+ * multiple Database instances share one cache with global write-invalidation
72
+ * (parity with Python's connection.py, which routes the persistent DB cache
73
+ * through `_create_backend`). The read path (`fetchAsync`/`fetchOneAsync`/
74
+ * `queryAsync`) is async, so the backend's async get/set work directly — no
75
+ * sync-path restriction. Request-scoped mode keeps the in-process QueryCache
76
+ * above (ephemeral, fastest, never serialized).
77
+ *
78
+ * `null` until the first async read builds it; a `memory` backend (the
79
+ * default) means the persistent layer behaves in-process exactly as before, so
80
+ * default behaviour is unchanged and only an explicit redis/etc. backend
81
+ * distributes.
82
+ */
83
+ private backend;
84
+ private backendPromise;
85
+ private backendName;
86
+ /**
87
+ * WHICH DATABASE this wrapper caches for, folded into every cache key.
88
+ * Empty only for an adapter built outside the URL/config funnels, which then
89
+ * behaves exactly as before rather than colliding with a tagged one.
90
+ *
91
+ * Optional-chained on the ADAPTER, not just the property. `setAdapter(null)`
92
+ * is the documented reset idiom (migrateCli.test.ts uses it to clear ORM
93
+ * state between cases) and it reaches here through wrapWithCache. Before the
94
+ * identity field existed the constructor only STORED the adapter, so a null
95
+ * passed through harmlessly; reading `adapter.cacheIdentity` turned that
96
+ * reset into "Cannot read properties of null".
97
+ */
98
+ private readonly identity;
99
+ constructor(adapter: DatabaseAdapter, options?: CachedAdapterOptions);
100
+ /**
101
+ * Whether the persistent layer should use a distributed/serialised backend.
102
+ * For the default `memory` backend we keep the in-process QueryCache (fast,
103
+ * no serialisation) so behaviour is identical to before; only an explicit
104
+ * non-memory backend (redis/valkey/memcached/mongodb/database/file) routes
105
+ * through the unified async backend for cross-instance sharing.
106
+ */
107
+ private usesPersistentBackend;
108
+ /** Lazily build (and memoise) the persistent backend via createBackend(). */
109
+ private getBackend;
110
+ /** Current cache mode: "persistent" | "request" | "off". */
111
+ cacheMode(): "persistent" | "request" | "off";
112
+ /** Whether either cache layer is active. */
113
+ cacheEnabled(): boolean;
114
+ /**
115
+ * Clear the request-scoped cache at the start of an HTTP request.
116
+ * No-op in persistent mode (cross-request entries survive to their TTL).
117
+ * Cumulative hit/miss counters are preserved. Mirrors Python's
118
+ * `Database.cache_new_request()`.
119
+ */
120
+ cacheNewRequest(): void;
121
+ /**
122
+ * Clear the request-scoped cache on every live wrapper. The request
123
+ * dispatcher calls this at the start of each HTTP request so request-scoped
124
+ * caching never serves rows across requests. Persistent-mode connections are
125
+ * left alone. Mirrors Python's `Database.reset_request_caches()` classmethod.
126
+ */
127
+ static resetRequestCaches(): void;
128
+ cacheStats(): {
129
+ enabled: boolean;
130
+ mode: "persistent" | "request" | "off";
131
+ hits: number;
132
+ misses: number;
133
+ size: number;
134
+ ttl: number;
135
+ backend?: string;
136
+ };
137
+ /** Flush the query cache and reset counters. Mirrors Python `cache_clear()`. */
138
+ cacheClear(): void;
139
+ /** Clear the entire query cache (called on writes). */
140
+ private invalidate;
141
+ /** Async write-invalidation — awaits the distributed backend clear. */
142
+ private invalidateAsync;
143
+ private backendGetRows;
144
+ private backendSetRows;
145
+ private backendGetOne;
146
+ private backendSetOne;
147
+ execute(sql: string, params?: unknown[]): unknown;
148
+ executeMany(sql: string, paramsList: unknown[][]): {
149
+ totalAffected: number;
150
+ lastId?: number | bigint;
151
+ };
152
+ query<T = Record<string, unknown>>(sql: string, params?: unknown[]): T[];
153
+ fetch<T = Record<string, unknown>>(sql: string, params?: unknown[], limit?: number, skip?: number, noCache?: boolean): T[];
154
+ fetchOne<T = Record<string, unknown>>(sql: string, params?: unknown[], noCache?: boolean): T | null;
155
+ insert(table: string, data: Record<string, unknown> | Record<string, unknown>[]): DatabaseResult;
156
+ update(table: string, data: Record<string, unknown>, filter: Record<string, unknown> | string, params?: unknown[]): DatabaseResult;
157
+ delete(table: string, filter: Record<string, unknown> | string | Record<string, unknown>[], params?: unknown[]): DatabaseResult;
158
+ startTransaction(): void;
159
+ commit(): void;
160
+ rollback(): void;
161
+ getTables(): string[];
162
+ getColumns(table: string): ColumnInfo[];
163
+ lastInsertId(): number | bigint | string | null;
164
+ close(): void;
165
+ tableExists(name: string): boolean;
166
+ createTable(name: string, columns: Record<string, FieldDefinition>): void;
167
+ getTableColumns?(name: string): Array<{
168
+ name: string;
169
+ type: string;
170
+ }>;
171
+ addColumn?(table: string, colName: string, def: FieldDefinition): void;
172
+ fetchAsync<T = Record<string, unknown>>(sql: string, params?: unknown[], limit?: number, skip?: number, noCache?: boolean): Promise<T[]>;
173
+ fetchOneAsync<T = Record<string, unknown>>(sql: string, params?: unknown[], noCache?: boolean): Promise<T | null>;
174
+ queryAsync<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]>;
175
+ executeAsync(sql: string, params?: unknown[]): Promise<unknown>;
176
+ insertAsync(table: string, data: Record<string, unknown> | Record<string, unknown>[]): Promise<DatabaseResult>;
177
+ updateAsync(table: string, data: Record<string, unknown>, filter: Record<string, unknown> | string, params?: unknown[]): Promise<DatabaseResult>;
178
+ deleteAsync(table: string, filter: Record<string, unknown> | string | Record<string, unknown>[], params?: unknown[]): Promise<DatabaseResult>;
179
+ startTransactionAsync(): Promise<void>;
180
+ commitAsync(): Promise<void>;
181
+ rollbackAsync(): Promise<void>;
182
+ tableExistsAsync(name: string): Promise<boolean>;
183
+ tablesAsync(): Promise<string[]>;
184
+ columnsAsync(table: string): Promise<ColumnInfo[]>;
185
+ createTableAsync(name: string, columns: Record<string, FieldDefinition>): Promise<void>;
186
+ /**
187
+ * Access the underlying (unwrapped) adapter directly.
188
+ */
189
+ getAdapter(): DatabaseAdapter;
190
+ }