tina4-nodejs 3.13.94 → 3.13.96

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 (123) hide show
  1. package/CLAUDE.md +158 -30
  2. package/README.md +1 -1
  3. package/package.json +3 -1
  4. package/packages/cli/dist/bin.js +30911 -28444
  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 +30810 -28261
  8. package/packages/core/public/css/tina4.min.css +1 -1
  9. package/packages/core/src/ai.ts +7 -1
  10. package/packages/core/src/auth.ts +191 -39
  11. package/packages/core/src/background.ts +19 -19
  12. package/packages/core/src/cache.ts +492 -49
  13. package/packages/core/src/devAdmin.ts +79 -32
  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 +6 -7
  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 +294 -106
  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 +1 -1
  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 +34 -16
  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 +886 -421
  32. package/packages/core/src/session.ts +244 -27
  33. package/packages/core/src/sessionHandlers/databaseHandler.ts +338 -48
  34. package/packages/core/src/sessionHandlers/memcachedHandler.ts +181 -0
  35. package/packages/core/src/sessionHandlers/mongoClient.ts +293 -208
  36. package/packages/core/src/sessionHandlers/mongoHandler.ts +88 -8
  37. package/packages/core/src/sessionHandlers/respClient.ts +16 -147
  38. package/packages/core/src/sessionHandlers/sqlClient.ts +290 -0
  39. package/packages/core/src/sessionHandlers/syncBridge.ts +190 -0
  40. package/packages/core/src/sessionHandlers/syncSocket.ts +236 -0
  41. package/packages/core/src/testClient.ts +18 -5
  42. package/packages/core/src/trustedProxy.ts +249 -0
  43. package/packages/core/src/types.ts +29 -5
  44. package/packages/core/src/websocket.ts +66 -0
  45. package/packages/orm/dist/index.js +22717 -20168
  46. package/packages/orm/src/adapters/firebird.ts +183 -56
  47. package/packages/orm/src/adapters/mongodb.ts +25 -4
  48. package/packages/orm/src/adapters/mssql.ts +114 -29
  49. package/packages/orm/src/adapters/mysql.ts +103 -40
  50. package/packages/orm/src/adapters/odbc.ts +44 -21
  51. package/packages/orm/src/adapters/postgres.ts +118 -26
  52. package/packages/orm/src/adapters/sqlDialect.ts +120 -0
  53. package/packages/orm/src/adapters/sqlite.ts +60 -24
  54. package/packages/orm/src/autoCrud.ts +12 -10
  55. package/packages/orm/src/baseModel.ts +135 -40
  56. package/packages/orm/src/cachedDatabase.ts +43 -19
  57. package/packages/orm/src/connectTimeout.ts +265 -0
  58. package/packages/orm/src/database.ts +241 -197
  59. package/packages/orm/src/databaseResult.ts +51 -28
  60. package/packages/orm/src/databaseUrl.ts +484 -0
  61. package/packages/orm/src/docstore.ts +386 -145
  62. package/packages/orm/src/index.ts +13 -6
  63. package/packages/orm/src/migration.ts +44 -11
  64. package/packages/orm/src/model.ts +4 -0
  65. package/packages/orm/src/queryBuilder.ts +47 -6
  66. package/packages/orm/src/sqlTranslator.ts +310 -4
  67. package/packages/orm/src/types.ts +21 -77
  68. package/packages/swagger/dist/index.js +78 -20
  69. package/packages/swagger/src/generator.ts +172 -29
  70. package/types/core/src/ai.d.ts +1 -1
  71. package/types/core/src/auth.d.ts +28 -5
  72. package/types/core/src/background.d.ts +3 -3
  73. package/types/core/src/cache.d.ts +15 -12
  74. package/types/core/src/dispatchPipeline.d.ts +117 -0
  75. package/types/core/src/dotenv.d.ts +38 -16
  76. package/types/core/src/index.d.ts +6 -9
  77. package/types/core/src/logger.d.ts +93 -16
  78. package/types/core/src/messenger.d.ts +47 -6
  79. package/types/core/src/metrics.d.ts +25 -61
  80. package/types/core/src/middleware.d.ts +134 -11
  81. package/types/core/src/queue.d.ts +54 -5
  82. package/types/core/src/queueBackends/kafkaBackend.d.ts +1 -1
  83. package/types/core/src/queueBackends/liteBackend.d.ts +9 -0
  84. package/types/core/src/queueBackends/mongoBackend.d.ts +24 -2
  85. package/types/core/src/queueBackends/rabbitmqBackend.d.ts +3 -3
  86. package/types/core/src/router.d.ts +14 -3
  87. package/types/core/src/server.d.ts +15 -4
  88. package/types/core/src/session.d.ts +87 -2
  89. package/types/core/src/sessionHandlers/databaseHandler.d.ts +60 -5
  90. package/types/core/src/sessionHandlers/memcachedHandler.d.ts +60 -0
  91. package/types/core/src/sessionHandlers/mongoClient.d.ts +16 -5
  92. package/types/core/src/sessionHandlers/mongoHandler.d.ts +51 -3
  93. package/types/core/src/sessionHandlers/respClient.d.ts +2 -2
  94. package/types/core/src/sessionHandlers/sqlClient.d.ts +39 -0
  95. package/types/core/src/sessionHandlers/syncBridge.d.ts +91 -0
  96. package/types/core/src/sessionHandlers/syncSocket.d.ts +49 -0
  97. package/types/core/src/trustedProxy.d.ts +44 -0
  98. package/types/core/src/types.d.ts +28 -5
  99. package/types/core/src/websocket.d.ts +26 -0
  100. package/types/orm/src/adapters/firebird.d.ts +55 -10
  101. package/types/orm/src/adapters/mongodb.d.ts +2 -2
  102. package/types/orm/src/adapters/mssql.d.ts +18 -11
  103. package/types/orm/src/adapters/mysql.d.ts +11 -10
  104. package/types/orm/src/adapters/odbc.d.ts +9 -12
  105. package/types/orm/src/adapters/postgres.d.ts +11 -10
  106. package/types/orm/src/adapters/sqlDialect.d.ts +71 -0
  107. package/types/orm/src/adapters/sqlite.d.ts +15 -3
  108. package/types/orm/src/baseModel.d.ts +45 -9
  109. package/types/orm/src/cachedDatabase.d.ts +18 -5
  110. package/types/orm/src/connectTimeout.d.ts +100 -0
  111. package/types/orm/src/database.d.ts +78 -28
  112. package/types/orm/src/databaseResult.d.ts +29 -15
  113. package/types/orm/src/databaseUrl.d.ts +125 -0
  114. package/types/orm/src/docstore.d.ts +102 -43
  115. package/types/orm/src/index.d.ts +6 -4
  116. package/types/orm/src/migration.d.ts +4 -3
  117. package/types/orm/src/queryBuilder.d.ts +23 -3
  118. package/types/orm/src/sqlTranslator.d.ts +126 -2
  119. package/types/orm/src/types.d.ts +21 -38
  120. package/packages/core/src/scss.ts +0 -623
  121. package/packages/core/src/sessionHandlers/redisHandler.ts +0 -219
  122. package/types/core/src/scss.d.ts +0 -19
  123. package/types/core/src/sessionHandlers/redisHandler.d.ts +0 -60
@@ -1,9 +1,3 @@
1
- /**
2
- * Tina4 Firebird Adapter — uses the `node-firebird` package (optional peer dependency).
3
- *
4
- * Install: npm install node-firebird
5
- * URL format: firebird://user:pass@host:port/path/to/database.fdb
6
- */
7
1
  import type { DatabaseAdapter, DatabaseResult, ColumnInfo, FieldDefinition } from "../types.js";
8
2
  /**
9
3
  * Turn a URL path component into a Firebird database identifier.
@@ -57,6 +51,39 @@ export interface FirebirdConfig {
57
51
  /** Connection charset. Overridden by a `?charset=` URL query; see resolveFirebirdCharset. */
58
52
  charset?: string;
59
53
  }
54
+ /**
55
+ * Quote an identifier the way Firebird actually stores it: UPPERCASE.
56
+ *
57
+ * Firebird folds an UNQUOTED identifier to upper case and treats a QUOTED one as
58
+ * case-sensitive. So after the ordinary `CREATE TABLE probe_t (...)` the table is
59
+ * PROBE_T, and `INSERT INTO "probe_t"` matches nothing:
60
+ *
61
+ * Dynamic SQL Error / Table unknown / probe_t
62
+ *
63
+ * That broke the insert path against every conventionally-created table, columns
64
+ * included. A name the caller has ALREADY quoted is passed through untouched,
65
+ * which is the escape hatch for a genuinely case-sensitive `CREATE TABLE "orders"`.
66
+ */
67
+ export declare function fbQuote(name: string): string;
68
+ /**
69
+ * Firebird's stored column name, folded back only when it was folded.
70
+ *
71
+ * Firebird's identifier folding is ASYMMETRIC. An unquoted `AS x` is stored
72
+ * UPPERCASE, so the driver hands back "X" where every other engine Tina4
73
+ * supports gives "x" — PostgreSQL folds to lower, and MySQL, SQLite and MSSQL
74
+ * preserve what you wrote. Portable code reading row.x broke on Firebird alone.
75
+ *
76
+ * A QUOTED `AS "MyCol"` is stored exactly as written, and that case is
77
+ * deliberate — the caller asked for it — so it is left alone. Folding
78
+ * unconditionally makes a mixed-case key unreachable, the same asymmetric trap
79
+ * that made tableExists miss quoted tables.
80
+ *
81
+ * So: fold back only a name carrying no lowercase letter, the only thing
82
+ * unquoted folding can produce. A quoted ALL-CAPS name is genuinely
83
+ * indistinguishable from a folded one and is lowercased too; that ambiguity is
84
+ * Firebird's, and it is the one spelling this cannot round-trip.
85
+ */
86
+ export declare function firebirdColumnName(raw: string): string;
60
87
  export declare class FirebirdAdapter implements DatabaseAdapter {
61
88
  private config;
62
89
  private db;
@@ -116,22 +143,40 @@ export declare class FirebirdAdapter implements DatabaseAdapter {
116
143
  insert(table: string, data: Record<string, unknown> | Record<string, unknown>[]): DatabaseResult;
117
144
  insertAsync(table: string, data: Record<string, unknown> | Record<string, unknown>[]): Promise<DatabaseResult>;
118
145
  update(table: string, data: Record<string, unknown>, filter: Record<string, unknown>, params?: unknown[]): DatabaseResult;
119
- updateAsync(table: string, data: Record<string, unknown>, filter: Record<string, unknown>): Promise<DatabaseResult>;
146
+ updateAsync(table: string, data: Record<string, unknown>, filter: Record<string, unknown> | string, params?: unknown[]): Promise<DatabaseResult>;
120
147
  delete(table: string, filter: Record<string, unknown>, params?: unknown[]): DatabaseResult;
121
- deleteAsync(table: string, filter: Record<string, unknown>): Promise<DatabaseResult>;
148
+ deleteAsync(table: string, filter: Record<string, unknown> | string, params?: unknown[]): Promise<DatabaseResult>;
122
149
  startTransaction(): void;
123
150
  startTransactionAsync(): Promise<void>;
124
151
  commit(): void;
125
152
  commitAsync(): Promise<void>;
126
153
  rollback(): void;
127
154
  rollbackAsync(): Promise<void>;
128
- tables(): string[];
155
+ getTables(): string[];
129
156
  tablesAsync(): Promise<string[]>;
130
- columns(table: string): ColumnInfo[];
157
+ getColumns(table: string): ColumnInfo[];
131
158
  columnsAsync(table: string): Promise<ColumnInfo[]>;
132
159
  lastInsertId(): number | bigint | null;
133
160
  close(): void;
134
161
  tableExists(name: string): boolean;
162
+ /**
163
+ * Is this table present, under either spelling Firebird could have stored?
164
+ *
165
+ * Firebird's folding rule is ASYMMETRIC:
166
+ * CREATE TABLE foo -> stored as FOO (unquoted folds to UPPER)
167
+ * CREATE TABLE "Foo" -> stored as Foo (quoted keeps its case)
168
+ *
169
+ * So upper-casing is CORRECT for the unquoted case - the common one - and
170
+ * WRONG for a quoted mixed-case table, which is a real thing on Firebird.
171
+ * Dropping the upper-case would not fix that, it would invert which half is
172
+ * broken.
173
+ *
174
+ * tableExistsAsync("Foo") is genuinely AMBIGUOUS: the caller could mean the
175
+ * quoted `Foo` or the unquoted `FOO`. Match EITHER. Do not "simplify" this
176
+ * back to one comparison - that is the bug it replaces, where a quoted
177
+ * mixed-case table read as absent and createTableAsync's idempotency guard
178
+ * (below) never fired.
179
+ */
135
180
  tableExistsAsync(name: string): Promise<boolean>;
136
181
  createTable(name: string, columns: Record<string, FieldDefinition>): void;
137
182
  createTableAsync(name: string, columns: Record<string, FieldDefinition>): Promise<void>;
@@ -55,9 +55,9 @@ export declare class MongodbAdapter implements DatabaseAdapter {
55
55
  commitAsync(): Promise<void>;
56
56
  rollback(): void;
57
57
  rollbackAsync(): Promise<void>;
58
- tables(): string[];
58
+ getTables(): string[];
59
59
  tablesAsync(): Promise<string[]>;
60
- columns(table: string): ColumnInfo[];
60
+ getColumns(table: string): ColumnInfo[];
61
61
  /**
62
62
  * Infer column schema by sampling a document from the collection.
63
63
  * MongoDB is schema-less; this returns field names and inferred JS types.
@@ -1,9 +1,3 @@
1
- /**
2
- * Tina4 MSSQL Adapter — uses the `tedious` package (optional peer dependency).
3
- *
4
- * Install: npm install tedious
5
- * URL format: mssql://user:pass@host:port/database
6
- */
7
1
  import type { DatabaseAdapter, DatabaseResult, ColumnInfo, FieldDefinition } from "../types.js";
8
2
  export interface MssqlConfig {
9
3
  host?: string;
@@ -16,6 +10,13 @@ export interface MssqlConfig {
16
10
  }
17
11
  export declare class MssqlAdapter implements DatabaseAdapter {
18
12
  private config;
13
+ /**
14
+ * Postgres, MySQL and MSSQL all REQUIRE a name for a derived table, so
15
+ * the COUNT probe in Database.countProbe wraps as
16
+ * `FROM (sql) AS _count_query`. SQLite and Firebird leave this unset and
17
+ * get no alias - Firebird rejects `AS` in that position.
18
+ */
19
+ readonly countSubqueryAlias = "_count_query";
19
20
  private connection;
20
21
  private _lastInsertId;
21
22
  private _inTransaction;
@@ -27,7 +28,13 @@ export declare class MssqlAdapter implements DatabaseAdapter {
27
28
  /** Translate SQL for MSSQL dialect. */
28
29
  translateSql(sql: string): string;
29
30
  private execSqlPromise;
30
- /** Convert ? placeholders to @p0, @p1, ... for tedious. */
31
+ /**
32
+ * Convert ? placeholders to @p0, @p1, ... for tedious.
33
+ *
34
+ * `startAt` lets a caller that has already consumed N placeholders (an UPDATE
35
+ * whose SET values are @p0..@p{N-1}) continue the numbering into a raw WHERE
36
+ * fragment instead of restarting at @p0.
37
+ */
31
38
  private convertPlaceholders;
32
39
  execute(sql: string, params?: unknown[]): unknown;
33
40
  executeMany(sql: string, paramsList: unknown[][]): {
@@ -48,18 +55,18 @@ export declare class MssqlAdapter implements DatabaseAdapter {
48
55
  insert(table: string, data: Record<string, unknown> | Record<string, unknown>[]): DatabaseResult;
49
56
  insertAsync(table: string, data: Record<string, unknown> | Record<string, unknown>[]): Promise<DatabaseResult>;
50
57
  update(table: string, data: Record<string, unknown>, filter: Record<string, unknown>, params?: unknown[]): DatabaseResult;
51
- updateAsync(table: string, data: Record<string, unknown>, filter: Record<string, unknown>): Promise<DatabaseResult>;
58
+ updateAsync(table: string, data: Record<string, unknown>, filter: Record<string, unknown> | string, params?: unknown[]): Promise<DatabaseResult>;
52
59
  delete(table: string, filter: Record<string, unknown>, params?: unknown[]): DatabaseResult;
53
- deleteAsync(table: string, filter: Record<string, unknown>): Promise<DatabaseResult>;
60
+ deleteAsync(table: string, filter: Record<string, unknown> | string, params?: unknown[]): Promise<DatabaseResult>;
54
61
  startTransaction(): void;
55
62
  startTransactionAsync(): Promise<void>;
56
63
  commit(): void;
57
64
  commitAsync(): Promise<void>;
58
65
  rollback(): void;
59
66
  rollbackAsync(): Promise<void>;
60
- tables(): string[];
67
+ getTables(): string[];
61
68
  tablesAsync(): Promise<string[]>;
62
- columns(table: string): ColumnInfo[];
69
+ getColumns(table: string): ColumnInfo[];
63
70
  columnsAsync(table: string): Promise<ColumnInfo[]>;
64
71
  lastInsertId(): number | bigint | null;
65
72
  close(): void;
@@ -1,9 +1,3 @@
1
- /**
2
- * Tina4 MySQL Adapter — uses the `mysql2` package (optional peer dependency).
3
- *
4
- * Install: npm install mysql2
5
- * URL format: mysql://user:pass@host:port/database
6
- */
7
1
  import type { DatabaseAdapter, DatabaseResult, ColumnInfo, FieldDefinition } from "../types.js";
8
2
  export interface MysqlConfig {
9
3
  host?: string;
@@ -15,6 +9,13 @@ export interface MysqlConfig {
15
9
  }
16
10
  export declare class MysqlAdapter implements DatabaseAdapter {
17
11
  private config;
12
+ /**
13
+ * Postgres, MySQL and MSSQL all REQUIRE a name for a derived table, so
14
+ * the COUNT probe in Database.countProbe wraps as
15
+ * `FROM (sql) AS _count_query`. SQLite and Firebird leave this unset and
16
+ * get no alias - Firebird rejects `AS` in that position.
17
+ */
18
+ readonly countSubqueryAlias = "_count_query";
18
19
  private connection;
19
20
  private _lastInsertId;
20
21
  private _inTransaction;
@@ -44,18 +45,18 @@ export declare class MysqlAdapter implements DatabaseAdapter {
44
45
  insert(table: string, data: Record<string, unknown> | Record<string, unknown>[]): DatabaseResult;
45
46
  insertAsync(table: string, data: Record<string, unknown> | Record<string, unknown>[]): Promise<DatabaseResult>;
46
47
  update(table: string, data: Record<string, unknown>, filter: Record<string, unknown>, params?: unknown[]): DatabaseResult;
47
- updateAsync(table: string, data: Record<string, unknown>, filter: Record<string, unknown>): Promise<DatabaseResult>;
48
+ updateAsync(table: string, data: Record<string, unknown>, filter: Record<string, unknown> | string, params?: unknown[]): Promise<DatabaseResult>;
48
49
  delete(table: string, filter: Record<string, unknown>, params?: unknown[]): DatabaseResult;
49
- deleteAsync(table: string, filter: Record<string, unknown>): Promise<DatabaseResult>;
50
+ deleteAsync(table: string, filter: Record<string, unknown> | string, params?: unknown[]): Promise<DatabaseResult>;
50
51
  startTransaction(): void;
51
52
  startTransactionAsync(): Promise<void>;
52
53
  commit(): void;
53
54
  commitAsync(): Promise<void>;
54
55
  rollback(): void;
55
56
  rollbackAsync(): Promise<void>;
56
- tables(): string[];
57
+ getTables(): string[];
57
58
  tablesAsync(): Promise<string[]>;
58
- columns(table: string): ColumnInfo[];
59
+ getColumns(table: string): ColumnInfo[];
59
60
  columnsAsync(table: string): Promise<ColumnInfo[]>;
60
61
  lastInsertId(): number | bigint | null;
61
62
  close(): void;
@@ -1,13 +1,3 @@
1
- /**
2
- * Tina4 ODBC Adapter — uses the `odbc` package (optional peer dependency).
3
- *
4
- * Install: npm install odbc
5
- * URL format: odbc:///DSN=MyDSN
6
- * odbc:///DRIVER={driver};SERVER=host;DATABASE=db
7
- *
8
- * The connection string after stripping the "odbc:///" prefix is passed
9
- * directly to odbc.connect(), so any valid ODBC connection string works.
10
- */
11
1
  import type { DatabaseAdapter, DatabaseResult, ColumnInfo, FieldDefinition } from "../types.js";
12
2
  export interface OdbcConfig {
13
3
  /** Full ODBC connection string, e.g. "DSN=MyDSN" or "DRIVER={SQL Server};SERVER=host;DATABASE=db" */
@@ -26,6 +16,13 @@ export declare class OdbcAdapter implements DatabaseAdapter {
26
16
  constructor(config: OdbcConfig | string);
27
17
  /** Extract the raw ODBC connection string from config. */
28
18
  private getConnectionString;
19
+ /**
20
+ * The address for a diagnostic message. ODBC hides it inside an opaque
21
+ * driver keyword string, so this reads the standard keywords and falls back to
22
+ * the data-source name - it is never used to connect, only to say which target
23
+ * hung.
24
+ */
25
+ private describeTarget;
29
26
  /** Connect to the ODBC data source. Must be called before using the adapter. */
30
27
  connect(): Promise<void>;
31
28
  private ensureConnected;
@@ -43,8 +40,8 @@ export declare class OdbcAdapter implements DatabaseAdapter {
43
40
  startTransaction(): void;
44
41
  commit(): void;
45
42
  rollback(): void;
46
- tables(): string[];
47
- columns(table: string): ColumnInfo[];
43
+ getTables(): string[];
44
+ getColumns(table: string): ColumnInfo[];
48
45
  tableExists(name: string): boolean;
49
46
  createTable(name: string, columns: Record<string, FieldDefinition>): void;
50
47
  getTableColumns(name: string): Array<{
@@ -1,9 +1,3 @@
1
- /**
2
- * Tina4 PostgreSQL Adapter — uses the `pg` package (optional peer dependency).
3
- *
4
- * Install: npm install pg @types/pg
5
- * URL format: postgresql://user:pass@host:port/database
6
- */
7
1
  import type { DatabaseAdapter, DatabaseResult, ColumnInfo, FieldDefinition } from "../types.js";
8
2
  export interface PostgresConfig {
9
3
  host?: string;
@@ -15,6 +9,13 @@ export interface PostgresConfig {
15
9
  }
16
10
  export declare class PostgresAdapter implements DatabaseAdapter {
17
11
  private config;
12
+ /**
13
+ * Postgres, MySQL and MSSQL all REQUIRE a name for a derived table, so
14
+ * the COUNT probe in Database.countProbe wraps as
15
+ * `FROM (sql) AS _count_query`. SQLite and Firebird leave this unset and
16
+ * get no alias - Firebird rejects `AS` in that position.
17
+ */
18
+ readonly countSubqueryAlias = "_count_query";
18
19
  private client;
19
20
  private _lastInsertId;
20
21
  private _inTransaction;
@@ -61,18 +62,18 @@ export declare class PostgresAdapter implements DatabaseAdapter {
61
62
  insert(table: string, data: Record<string, unknown> | Record<string, unknown>[]): DatabaseResult;
62
63
  insertAsync(table: string, data: Record<string, unknown> | Record<string, unknown>[]): Promise<DatabaseResult>;
63
64
  update(table: string, data: Record<string, unknown>, filter: Record<string, unknown>, params?: unknown[]): DatabaseResult;
64
- updateAsync(table: string, data: Record<string, unknown>, filter: Record<string, unknown>): Promise<DatabaseResult>;
65
+ updateAsync(table: string, data: Record<string, unknown>, filter: Record<string, unknown> | string, params?: unknown[]): Promise<DatabaseResult>;
65
66
  delete(table: string, filter: Record<string, unknown>, params?: unknown[]): DatabaseResult;
66
- deleteAsync(table: string, filter: Record<string, unknown>): Promise<DatabaseResult>;
67
+ deleteAsync(table: string, filter: Record<string, unknown> | string, params?: unknown[]): Promise<DatabaseResult>;
67
68
  startTransaction(): void;
68
69
  startTransactionAsync(): Promise<void>;
69
70
  commit(): void;
70
71
  commitAsync(): Promise<void>;
71
72
  rollback(): void;
72
73
  rollbackAsync(): Promise<void>;
73
- tables(): string[];
74
+ getTables(): string[];
74
75
  tablesAsync(): Promise<string[]>;
75
- columns(table: string): ColumnInfo[];
76
+ getColumns(table: string): ColumnInfo[];
76
77
  columnsAsync(table: string): Promise<ColumnInfo[]>;
77
78
  lastInsertId(): number | bigint | string | null;
78
79
  close(): void;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * One CRUD SQL builder for every engine, instead of one per adapter.
3
+ *
4
+ * Feature 3's last open item, the 4.3x LOC finding: `insert`/`update`/`delete`
5
+ * built their SQL independently in all seven adapters. Building
6
+ * `INSERT INTO x (a, b) VALUES (?, ?)` is not engine-specific work - Ruby has
7
+ * always done it once - and the seven copies differed in exactly two ways:
8
+ *
9
+ * IDENTIFIER QUOTING "col" | `col` | [col] | Firebird's fbQuote
10
+ * PARAMETER MARKER ? | $1 | @p1
11
+ *
12
+ * Both are captured in a `Dialect` below, so the builders are shared and each
13
+ * adapter declares only what genuinely differs about its engine.
14
+ *
15
+ * These functions build STRINGS and nothing else. Execution and result
16
+ * extraction stay in the adapters on purpose: those really are per-driver
17
+ * (`client.query` vs `lastInsertRowid` vs a Firebird transaction handle), and
18
+ * folding them in here would trade a real duplication for a fake abstraction.
19
+ *
20
+ * MongoDB has no entry: it does not build SQL at all.
21
+ */
22
+ /** How one engine spells identifiers and parameter markers. */
23
+ export interface Dialect {
24
+ /** Quote a table or column name for this engine. */
25
+ quote(name: string): string;
26
+ /**
27
+ * The parameter marker for the 1-based position `index`. Engines with
28
+ * positional markers ($1, @p1) use the index; the rest ignore it.
29
+ */
30
+ marker(index: number): string;
31
+ }
32
+ /** SQLite, and ODBC which follows the SQL standard spelling. */
33
+ export declare const ANSI_DIALECT: Dialect;
34
+ /** PostgreSQL: standard quoting, positional $N markers. */
35
+ export declare const POSTGRES_DIALECT: Dialect;
36
+ /** MySQL: backtick quoting. */
37
+ export declare const MYSQL_DIALECT: Dialect;
38
+ /** MSSQL: bracket quoting, named @pN markers. */
39
+ export declare const MSSQL_DIALECT: Dialect;
40
+ /**
41
+ * Firebird quotes only when it has to: an unquoted identifier is folded to
42
+ * UPPER CASE, so quoting a lower-case name would make it unfindable. The
43
+ * adapter owns that rule and passes its own quoter in.
44
+ */
45
+ export declare function firebirdDialect(fbQuote: (name: string) => string): Dialect;
46
+ /**
47
+ * `INSERT INTO <table> (<cols>) VALUES (<markers>)`.
48
+ *
49
+ * @param suffix Appended verbatim - PostgreSQL passes " RETURNING *" and MSSQL
50
+ * its SCOPE_IDENTITY() probe, the genuinely engine-specific parts.
51
+ * @param startAt Position of the FIRST marker. PostgreSQL numbers its `$N` from
52
+ * 1; MSSQL names its `@pN` from 0 and BINDS by that same name, so
53
+ * shifting it would produce SQL whose parameters do not exist.
54
+ * Engines using `?` ignore this.
55
+ */
56
+ export declare function buildInsert(dialect: Dialect, table: string, keys: string[], suffix?: string, startAt?: number): string;
57
+ /**
58
+ * The `SET a = ?, b = ?` fragment of an UPDATE.
59
+ *
60
+ * @param startAt 1-based position of the FIRST marker. An UPDATE's WHERE
61
+ * clause continues the numbering after the SET values, so a
62
+ * positional engine ($N, @pN) must not restart at 1.
63
+ */
64
+ export declare function buildSetClause(dialect: Dialect, keys: string[], startAt?: number): string;
65
+ /**
66
+ * The `a = ? AND b = ?` fragment for an object filter.
67
+ *
68
+ * @param startAt 1-based position of the first marker, for the same reason as
69
+ * buildSetClause.
70
+ */
71
+ export declare function buildWhereClause(dialect: Dialect, keys: string[], startAt?: number): string;
@@ -2,6 +2,18 @@ import type { DatabaseAdapter, DatabaseResult, ColumnInfo, FieldDefinition } fro
2
2
  export declare class SQLiteAdapter implements DatabaseAdapter {
3
3
  private db;
4
4
  private _lastInsertId;
5
+ /**
6
+ * TINA4_DATABASE_CONNECT_TIMEOUT DOES NOT APPLY HERE, deliberately.
7
+ *
8
+ * There is no connect() to bound: `node:sqlite` opens the file in this
9
+ * SYNCHRONOUS constructor, and a synchronous call cannot be interrupted by a
10
+ * timer on the same thread - the event loop only gets to run the timer after
11
+ * `new DatabaseSync()` has already returned. There is also no host and no port
12
+ * to name in a timeout error. The one case that could still block is a local
13
+ * file on a wedged network mount, which is a kernel-level stall no JS bound
14
+ * can reach. Stated here so the exclusion reads as a decision rather than an
15
+ * adapter somebody forgot.
16
+ */
5
17
  constructor(dbPath: string);
6
18
  execute(sql: string, params?: unknown[]): unknown;
7
19
  executeMany(sql: string, paramsList: unknown[][]): {
@@ -12,14 +24,14 @@ export declare class SQLiteAdapter implements DatabaseAdapter {
12
24
  fetch<T = Record<string, unknown>>(sql: string, params?: unknown[], limit?: number, skip?: number): T[];
13
25
  fetchOne<T = Record<string, unknown>>(sql: string, params?: unknown[]): T | null;
14
26
  insert(table: string, data: Record<string, unknown> | Record<string, unknown>[]): DatabaseResult;
15
- update(table: string, data: Record<string, unknown>, filter: Record<string, unknown>, params?: unknown[]): DatabaseResult;
27
+ update(table: string, data: Record<string, unknown>, filter: Record<string, unknown> | string, params?: unknown[]): DatabaseResult;
16
28
  delete(table: string, filter: Record<string, unknown> | string | Record<string, unknown>[], params?: unknown[]): DatabaseResult;
17
29
  private _inTransaction;
18
30
  startTransaction(): void;
19
31
  commit(): void;
20
32
  rollback(): void;
21
- tables(): string[];
22
- columns(table: string): ColumnInfo[];
33
+ getTables(): string[];
34
+ getColumns(table: string): ColumnInfo[];
23
35
  lastInsertId(): number | bigint | null;
24
36
  close(): void;
25
37
  /**
@@ -135,6 +135,21 @@ export declare class BaseModel {
135
135
  * Get the primary key field name (JS property name).
136
136
  */
137
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
+ };
138
153
  /**
139
154
  * Get the primary key database column name (applies fieldMapping).
140
155
  */
@@ -195,20 +210,41 @@ export declare class BaseModel {
195
210
  */
196
211
  load(filter?: string, params?: unknown[], include?: string[]): Promise<boolean>;
197
212
  /**
198
- * Find all records, optionally with a where clause.
199
- * Alias: all()
200
- * @param where Optional WHERE clause.
201
- * @param params Optional query parameters.
202
- * @param include Optional array of relationship names to eager-load.
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").
203
239
  */
204
- static all<T extends BaseModel>(this: new (data?: Record<string, unknown>) => T, where?: string, params?: unknown[], include?: string[], orderBy?: string): Promise<T[]>;
240
+ static all<T extends BaseModel>(this: new (data?: Record<string, unknown>) => T, limit?: number, offset?: number, include?: string[], orderBy?: string): Promise<T[]>;
205
241
  /**
206
242
  * Query records with a WHERE clause.
207
243
  * Matches Python/PHP/Ruby where() API.
208
244
  *
209
245
  * @param conditions WHERE clause (e.g. "age > ? AND active = ?")
210
246
  * @param params Bind parameters
211
- * @param limit Max records (default 20)
247
+ * @param limit Max records (default 100)
212
248
  * @param offset Skip records (default 0)
213
249
  * @param include Relationship names to eager-load
214
250
  * @param orderBy ORDER BY clause (e.g. "name ASC")
@@ -299,7 +335,7 @@ export declare class BaseModel {
299
335
  * @param sql SQL query string.
300
336
  * @param params Bind parameters.
301
337
  * @param ttl Cache TTL in seconds (default 60).
302
- * @param limit Max records to return (default 20).
338
+ * @param limit Max records to return (default 100).
303
339
  * @param offset Records to skip (default 0).
304
340
  * @param include Relationship names to eager-load on cache miss.
305
341
  */
@@ -311,7 +347,7 @@ export declare class BaseModel {
311
347
  /**
312
348
  * Execute a raw SQL SELECT and return results as model instances.
313
349
  */
314
- static select<T extends BaseModel>(this: new (data?: Record<string, unknown>) => T, sql: string, params?: unknown[]): Promise<T[]>;
350
+ static select<T extends BaseModel>(this: new (data?: Record<string, unknown>) => T, sql: string, params?: unknown[], limit?: number, offset?: number): Promise<T[]>;
315
351
  static selectOne<T extends BaseModel>(this: new (data?: Record<string, unknown>) => T, sql: string, params?: unknown[], include?: string[]): Promise<T | null>;
316
352
  /**
317
353
  * Permanently delete this instance, bypassing soft delete.
@@ -83,6 +83,19 @@ export declare class CachedDatabaseAdapter implements DatabaseAdapter {
83
83
  private backend;
84
84
  private backendPromise;
85
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;
86
99
  constructor(adapter: DatabaseAdapter, options?: CachedAdapterOptions);
87
100
  /**
88
101
  * Whether the persistent layer should use a distributed/serialised backend.
@@ -140,13 +153,13 @@ export declare class CachedDatabaseAdapter implements DatabaseAdapter {
140
153
  fetch<T = Record<string, unknown>>(sql: string, params?: unknown[], limit?: number, skip?: number, noCache?: boolean): T[];
141
154
  fetchOne<T = Record<string, unknown>>(sql: string, params?: unknown[], noCache?: boolean): T | null;
142
155
  insert(table: string, data: Record<string, unknown> | Record<string, unknown>[]): DatabaseResult;
143
- update(table: string, data: Record<string, unknown>, filter: Record<string, unknown>): DatabaseResult;
144
- delete(table: string, filter: Record<string, unknown> | string | 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;
145
158
  startTransaction(): void;
146
159
  commit(): void;
147
160
  rollback(): void;
148
- tables(): string[];
149
- columns(table: string): ColumnInfo[];
161
+ getTables(): string[];
162
+ getColumns(table: string): ColumnInfo[];
150
163
  lastInsertId(): number | bigint | string | null;
151
164
  close(): void;
152
165
  tableExists(name: string): boolean;
@@ -161,7 +174,7 @@ export declare class CachedDatabaseAdapter implements DatabaseAdapter {
161
174
  queryAsync<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]>;
162
175
  executeAsync(sql: string, params?: unknown[]): Promise<unknown>;
163
176
  insertAsync(table: string, data: Record<string, unknown> | Record<string, unknown>[]): Promise<DatabaseResult>;
164
- updateAsync(table: string, data: Record<string, unknown>, filter: Record<string, unknown>, params?: unknown[]): Promise<DatabaseResult>;
177
+ updateAsync(table: string, data: Record<string, unknown>, filter: Record<string, unknown> | string, params?: unknown[]): Promise<DatabaseResult>;
165
178
  deleteAsync(table: string, filter: Record<string, unknown> | string | Record<string, unknown>[], params?: unknown[]): Promise<DatabaseResult>;
166
179
  startTransactionAsync(): Promise<void>;
167
180
  commitAsync(): Promise<void>;
@@ -0,0 +1,100 @@
1
+ /** Seconds. Long enough for a cold cross-region connect, short enough to page. */
2
+ export declare const DEFAULT_DATABASE_CONNECT_TIMEOUT_SECONDS = 10;
3
+ /**
4
+ * Resolve the connect budget in MILLISECONDS, or `null` when the bound is
5
+ * disabled.
6
+ *
7
+ * Milliseconds because every consumer needs them: `setTimeout` and all four
8
+ * driver knobs are in ms. Seconds are the operator-facing unit, so the variable
9
+ * is read as seconds and converted once, here.
10
+ *
11
+ * Call this ONCE per connect and pass the result down - it is the only resolver,
12
+ * so calling it twice would warn twice about one typo.
13
+ */
14
+ export declare function connectTimeoutMillis(): number | null;
15
+ /**
16
+ * Clock slack when deciding whether a failed connect was OUR bound expiring.
17
+ *
18
+ * THE DECISION IS MADE BY ELAPSED TIME, NEVER BY MATCHING THE DRIVER'S TEXT.
19
+ * The four clients word an expiry four different ways - pg `timeout expired`,
20
+ * mysql2 `connect ETIMEDOUT`, tedious `Failed to connect to ... in 2000ms`,
21
+ * Mongo `Server selection timed out after 2000 ms` - and a marker table would
22
+ * drift the moment any of them reworded, then MISS. A missed timeout is the
23
+ * whole defect this file exists to prevent, so nothing here reads the message.
24
+ *
25
+ * WHY 50ms, AND WHY NOT ZERO. Python and PHP use no tolerance at all, PHP having
26
+ * measured its four C clients OVERSHOOTING their deadline by ~3ms at a 3s bound -
27
+ * a client that measures its own elapsed time can never report early. Node's
28
+ * knobs are not those: all four are plain JS `setTimeout` calls (pg `client.js`,
29
+ * mysql2 `base/connection.js`, tedious `connection.js`, and the Mongo driver's
30
+ * selection loop), and libuv's loop time is coarse, so a Node timer CAN fire
31
+ * before `performance.now()` agrees the budget has passed. MEASURED, 60 rounds
32
+ * at a 200ms budget:
33
+ *
34
+ * Linux x64, Node v24.18.0 earliest -0.7125ms (fires EARLY)
35
+ * darwin arm64, Node v24.9.0 earliest +0.0872ms (never early)
36
+ *
37
+ * Zero would therefore be a real miss on Linux. 50ms is a ~70x margin on the
38
+ * measured worst case, and still 0.5% of the 10s default. Ruby's 250ms is for a
39
+ * different problem - libpq's `connect_timeout` is INTEGER SECONDS and reads a
40
+ * 10s bound back as 9.998s - which no Node client has.
41
+ *
42
+ * It does NOT inflate the operator's N: it only widens what COUNTS as the bound
43
+ * expiring, never how long anything waits. It errs deliberately: over-translating
44
+ * a genuine fast failure that lands within 50ms of the bound still shows the
45
+ * operator the driver's real error (`Driver reported:`, plus `cause`), whereas
46
+ * under-translating hands them a bare driver message naming no variable.
47
+ */
48
+ export declare const CONNECT_TIMEOUT_TOLERANCE_MS = 50;
49
+ /**
50
+ * The value for a driver's own connect-timeout option, from the Tina4 budget.
51
+ * `null` in, `null` out - a disabled bound sets no driver option at all.
52
+ *
53
+ * ROUNDED UP, AND NEVER TO 0. Up, so the driver's timer can never expire before
54
+ * our clock has reached the bound - that ordering is the whole basis for the
55
+ * elapsed-time test in `withConnectTimeout`, and rounding down would break it.
56
+ * Never 0, because three of the four knobs read 0 as WAIT FOREVER: pg does
57
+ * `connectionTimeoutMillis || 0` then `if (> 0)`, mysql2 does
58
+ * `if (this.config.connectTimeout)`, and libpq (the same trap Python and Ruby
59
+ * name) treats `connect_timeout=0` as no limit. A sub-millisecond bound must
60
+ * therefore floor at 1ms rather than silently disabling the bound being set.
61
+ */
62
+ export declare function driverConnectTimeoutMillis(budgetMs: number | null): number | null;
63
+ /**
64
+ * Best-effort host/port for the DIAGNOSTIC, from either a config object or a
65
+ * connection URL. Never used to connect - the adapter has already done that with
66
+ * its own parsing, and this must not become a second, divergent parser that
67
+ * decides where anything dials.
68
+ */
69
+ export declare function connectTarget(config: {
70
+ host?: string;
71
+ port?: number;
72
+ } | string, defaultPort: number): {
73
+ host: string;
74
+ port: number | string;
75
+ };
76
+ /**
77
+ * Bound a driver connect, and name the bound when it expires.
78
+ *
79
+ * @param attempt a THUNK that starts the driver's connect. It is a thunk, not a
80
+ * promise, so OUR CLOCK STARTS FIRST - everything that touches
81
+ * the driver must run inside it. That ordering is load-bearing:
82
+ * the driver arms its own timer somewhere in here (mysql2 arms
83
+ * its at the END OF ITS CONSTRUCTOR, not in `connect()`), and
84
+ * only by starting first can we know that the driver's timer
85
+ * cannot expire before `elapsed` has reached the bound.
86
+ * @param budgetMs from `connectTimeoutMillis()`; `null` runs `attempt` untouched
87
+ * @param host named in the error - an operator needs to know WHICH server hung
88
+ * @param port named in the error alongside the host
89
+ * @param abandon called if the driver answers AFTER we gave up, with whatever it
90
+ * produced. Nobody will ever use that connection, so the adapter
91
+ * closes it here rather than leaking a socket for the life of the
92
+ * process - a connect that is retried every 10s would otherwise
93
+ * accumulate one abandoned connection per attempt, forever.
94
+ *
95
+ * TWO WAYS OUT, both wearing the same message. Normally the DRIVER's timer fires
96
+ * first (it was armed first) and we TRANSLATE its failure; where the driver has
97
+ * no knob, or its knob did not cover the phase that hung, our own timer fires as
98
+ * the backstop and there is no driver diagnosis to report.
99
+ */
100
+ export declare function withConnectTimeout<T>(attempt: () => Promise<T>, budgetMs: number | null, host: string, port: number | string, abandon?: (arrived: T) => void): Promise<T>;