tina4-nodejs 3.13.98 → 3.13.100

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 (97) hide show
  1. package/CLAUDE.md +24 -25
  2. package/package.json +1 -2
  3. package/packages/cli/dist/bin.js +20698 -18983
  4. package/packages/cli/src/bin.ts +28 -71
  5. package/packages/cli/src/commands/migrate.ts +36 -75
  6. package/packages/cli/src/commands/migrateRollback.ts +10 -1
  7. package/packages/cli/src/commands/test.ts +92 -21
  8. package/packages/core/dist/index.js +20561 -18828
  9. package/packages/core/public/js/tina4-dev-admin.min.js +23 -19
  10. package/packages/core/src/ai.ts +38 -13
  11. package/packages/core/src/api.ts +13 -5
  12. package/packages/core/src/background.ts +9 -3
  13. package/packages/core/src/devAdmin.ts +135 -20
  14. package/packages/core/src/dispatchPipeline.ts +185 -1
  15. package/packages/core/src/docs.ts +33 -5
  16. package/packages/core/src/env.ts +1 -1
  17. package/packages/core/src/errorOverlay.ts +39 -48
  18. package/packages/core/src/fakeData.ts +15 -0
  19. package/packages/core/src/index.ts +17 -6
  20. package/packages/core/src/logger.ts +892 -572
  21. package/packages/core/src/mcp.ts +9 -1
  22. package/packages/core/src/messenger.ts +31 -4
  23. package/packages/core/src/middleware.ts +169 -43
  24. package/packages/core/src/portTakeover.ts +232 -0
  25. package/packages/core/src/request.ts +57 -8
  26. package/packages/core/src/response.ts +67 -0
  27. package/packages/core/src/router.ts +35 -7
  28. package/packages/core/src/server.ts +450 -190
  29. package/packages/core/src/static.ts +81 -12
  30. package/packages/core/src/testClient.ts +126 -137
  31. package/packages/core/src/testing.ts +16 -12
  32. package/packages/core/src/types.ts +21 -9
  33. package/packages/core/src/version.ts +66 -0
  34. package/packages/core/src/websocket.ts +2 -2
  35. package/packages/core/src/websocketBackplane.ts +2 -2
  36. package/packages/frond/dist/index.js +149 -49
  37. package/packages/frond/src/engine.ts +234 -52
  38. package/packages/orm/dist/index.js +10941 -9231
  39. package/packages/orm/src/adapters/firebird.ts +200 -27
  40. package/packages/orm/src/adapters/mongodb.ts +160 -10
  41. package/packages/orm/src/adapters/mssql.ts +38 -11
  42. package/packages/orm/src/adapters/mysql.ts +24 -1
  43. package/packages/orm/src/adapters/odbc.ts +127 -29
  44. package/packages/orm/src/adapters/postgres.ts +18 -0
  45. package/packages/orm/src/adapters/sqlite.ts +93 -14
  46. package/packages/orm/src/autoCrud.ts +72 -8
  47. package/packages/orm/src/baseModel.ts +323 -71
  48. package/packages/orm/src/cachedDatabase.ts +48 -1
  49. package/packages/orm/src/database.ts +162 -59
  50. package/packages/orm/src/fakeData.ts +6 -2
  51. package/packages/orm/src/index.ts +4 -1
  52. package/packages/orm/src/migration.ts +95 -52
  53. package/packages/orm/src/query.ts +16 -4
  54. package/packages/orm/src/seeder.ts +43 -25
  55. package/packages/orm/src/sqlTranslator.ts +104 -19
  56. package/packages/orm/src/types.ts +97 -21
  57. package/packages/orm/src/validation.ts +5 -1
  58. package/packages/swagger/dist/index.js +3 -2
  59. package/packages/swagger/src/generator.ts +19 -4
  60. package/packages/swagger/src/ui.ts +6 -4
  61. package/types/cli/src/bin.d.ts +0 -22
  62. package/types/core/src/ai.d.ts +29 -0
  63. package/types/core/src/api.d.ts +11 -4
  64. package/types/core/src/background.d.ts +5 -2
  65. package/types/core/src/devAdmin.d.ts +35 -0
  66. package/types/core/src/dispatchPipeline.d.ts +41 -1
  67. package/types/core/src/errorOverlay.d.ts +13 -13
  68. package/types/core/src/index.d.ts +9 -6
  69. package/types/core/src/logger.d.ts +111 -185
  70. package/types/core/src/middleware.d.ts +40 -5
  71. package/types/core/src/portTakeover.d.ts +50 -0
  72. package/types/core/src/request.d.ts +15 -0
  73. package/types/core/src/response.d.ts +29 -0
  74. package/types/core/src/server.d.ts +92 -0
  75. package/types/core/src/testClient.d.ts +29 -3
  76. package/types/core/src/testing.d.ts +16 -12
  77. package/types/core/src/types.d.ts +21 -9
  78. package/types/core/src/version.d.ts +11 -0
  79. package/types/core/src/websocketBackplane.d.ts +1 -1
  80. package/types/frond/src/engine.d.ts +60 -8
  81. package/types/orm/src/adapters/firebird.d.ts +61 -2
  82. package/types/orm/src/adapters/mongodb.d.ts +20 -0
  83. package/types/orm/src/adapters/mssql.d.ts +11 -0
  84. package/types/orm/src/adapters/mysql.d.ts +11 -0
  85. package/types/orm/src/adapters/odbc.d.ts +35 -4
  86. package/types/orm/src/adapters/postgres.d.ts +11 -0
  87. package/types/orm/src/adapters/sqlite.d.ts +23 -4
  88. package/types/orm/src/baseModel.d.ts +45 -25
  89. package/types/orm/src/cachedDatabase.d.ts +27 -1
  90. package/types/orm/src/database.d.ts +56 -6
  91. package/types/orm/src/index.d.ts +3 -2
  92. package/types/orm/src/migration.d.ts +23 -5
  93. package/types/orm/src/query.d.ts +3 -0
  94. package/types/orm/src/seeder.d.ts +15 -2
  95. package/types/orm/src/sqlTranslator.d.ts +17 -4
  96. package/types/orm/src/types.d.ts +75 -16
  97. package/packages/core/src/errorOverlay.test.ts +0 -122
@@ -1,5 +1,4 @@
1
1
  import { QueryBuilder } from "./queryBuilder.js";
2
- import { QueryCache } from "./sqlTranslator.js";
3
2
  import type { DatabaseAdapter, FieldDefinition, RelationshipDefinition } from "./types.js";
4
3
  /**
5
4
  * Convert a snake_case name to camelCase.
@@ -29,23 +28,6 @@ export declare function toDbFieldValue(def: FieldDefinition | undefined, value:
29
28
  * null stays null; a non-decodable string keeps its raw form.
30
29
  */
31
30
  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
31
  export declare class BaseModel {
50
32
  static tableName: string;
51
33
  static fields: Record<string, FieldDefinition>;
@@ -55,7 +37,6 @@ export declare class BaseModel {
55
37
  static hasMany?: RelationshipDefinition[];
56
38
  static belongsTo?: RelationshipDefinition[];
57
39
  static _db?: string;
58
- static _queryCache?: QueryCache;
59
40
  /**
60
41
  * When true, auto-generates fieldMapping entries from camelCase field names
61
42
  * to snake_case DB column names. Explicit fieldMapping entries always win.
@@ -315,7 +296,7 @@ export declare class BaseModel {
315
296
  * Validate this instance's values against the model's field definitions.
316
297
  * Returns an array of error strings (empty array means valid).
317
298
  */
318
- validate(): string[];
299
+ validate(isUpdate?: boolean): string[];
319
300
  /**
320
301
  * Generate and execute CREATE TABLE DDL from the model's field definitions.
321
302
  * Uses the adapter's createTable method if available, otherwise builds SQL directly.
@@ -330,18 +311,36 @@ export declare class BaseModel {
330
311
  */
331
312
  static exists(pkValue: unknown): Promise<boolean>;
332
313
  /**
333
- * Run a raw SQL query with results cached by TTL. Cache is per-model-class.
314
+ * Every table a cached query touches: this model's table plus every FROM/JOIN
315
+ * table in `sql`. A write to any of these busts the entry (CACHE-DEC-01).
316
+ */
317
+ static _cacheTags(sql: string): string[];
318
+ /**
319
+ * Run a raw SQL query with results cached by TTL.
320
+ *
321
+ * Invalidation (CACHE-DEC-01): the entry is tagged by every table the query
322
+ * touches (this model's table plus any FROM/JOIN tables) in ONE process-wide
323
+ * shared cache, so a write through the ORM (save/delete/forceDelete/restore)
324
+ * to ANY of those tables busts it -- including a cross-table JOIN cached on a
325
+ * different model. `ttl <= 0` means NO-CACHE: the query runs and the rows are
326
+ * returned but nothing is stored, so every read hits the database.
334
327
  *
335
328
  * @param sql SQL query string.
336
329
  * @param params Bind parameters.
337
- * @param ttl Cache TTL in seconds (default 60).
330
+ * @param ttl Cache TTL in seconds (default 60; <= 0 = no-cache).
338
331
  * @param limit Max records to return (default 100).
339
332
  * @param offset Records to skip (default 0).
340
333
  * @param include Relationship names to eager-load on cache miss.
341
334
  */
342
335
  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
336
  /**
344
- * Clear the per-model query cache.
337
+ * Invalidate every cached query that touches this model's table.
338
+ *
339
+ * Tag-scoped, NOT a wholesale flush: a cached JOIN on another model that reads
340
+ * this table is busted too (it carries this table's tag), while a query that
341
+ * never touches this table is left intact. Called after every ORM write
342
+ * (save/delete/forceDelete/restore) so a read-after-write never serves a
343
+ * stale/deleted row (CACHE-DEC-01).
345
344
  */
346
345
  static clearCache(): void;
347
346
  /**
@@ -380,6 +379,11 @@ export declare class BaseModel {
380
379
  hasOne<T extends BaseModel, R extends BaseModel>(this: T, relatedClass: typeof BaseModel & (new (data?: Record<string, unknown>) => R), foreignKey: string): Promise<R | null>;
381
380
  /**
382
381
  * Load has-many related model instances.
382
+ *
383
+ * With no explicit `limit` this returns the WHOLE set (paged internally, like
384
+ * the lazy accessor), never a silent row cap -- so an imperatively-loaded
385
+ * has_many yields the SAME row count as the lazy path. An explicit `limit`
386
+ * still pages.
383
387
  */
384
388
  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
389
  /**
@@ -394,10 +398,26 @@ export declare class BaseModel {
394
398
  /**
395
399
  * Process foreignKey fields on every registered model so the cross-model
396
400
  * _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.
401
+ * of which model was used first, then attach the lazy relationship accessors.
402
+ * Idempotent every step guards against duplicates.
399
403
  */
400
404
  private static _processAllForeignKeys;
405
+ /**
406
+ * REL-NODE-AUTOWIRE-DEAD: attach a lazy-loading accessor for each declared
407
+ * relationship (belongsTo/hasOne/hasMany) on this model's prototype, so
408
+ * `post.author` / `author.posts` resolve on attribute access. The accessor is
409
+ * async (Node lazy load) and caches into `_relCache` — the SAME cache eager
410
+ * loading fills, so `toDict` stays consistent once a relation has been loaded.
411
+ * Reuses the imperative belongsTo()/hasOne() path and the cross-model registry;
412
+ * a soft-deleted child is excluded and the has-many read is uncapped.
413
+ */
414
+ static _wireRelationshipAccessors(): void;
415
+ /**
416
+ * Lazy has-many read for a relationship accessor: excludes soft-deleted
417
+ * children and returns the WHOLE set (adapterQuery is uncapped, so the tail is
418
+ * never lost). Ordered by the child PK for a stable read.
419
+ */
420
+ private static _loadHasManyLazy;
401
421
  /**
402
422
  * Resolve a model class by name from the registry.
403
423
  */
@@ -144,8 +144,21 @@ export declare class CachedDatabaseAdapter implements DatabaseAdapter {
144
144
  private backendSetRows;
145
145
  private backendGetOne;
146
146
  private backendSetOne;
147
+ /** ADR-0044 required capability — delegates to the wrapped adapter. */
148
+ connect(): void | Promise<void>;
149
+ /** ADR-0044 required capability — delegates to the wrapped adapter. */
150
+ getDatabaseType(): string;
151
+ /**
152
+ * ADR-0044 required capability — a native boolean, readable and writable.
153
+ * A getter/setter pair (not a plain field) so it genuinely delegates to the
154
+ * wrapped adapter rather than drifting out of sync with its real setting.
155
+ */
156
+ get autocommit(): boolean;
157
+ set autocommit(value: boolean);
158
+ get supportsAtomicBatch(): boolean;
159
+ set supportsAtomicBatch(value: boolean);
147
160
  execute(sql: string, params?: unknown[]): unknown;
148
- executeMany(sql: string, paramsList: unknown[][]): {
161
+ executeMany(sql: string, paramsList: unknown[][]): import("./types.js").DatabaseResult | {
149
162
  totalAffected: number;
150
163
  lastId?: number | bigint;
151
164
  };
@@ -173,6 +186,19 @@ export declare class CachedDatabaseAdapter implements DatabaseAdapter {
173
186
  fetchOneAsync<T = Record<string, unknown>>(sql: string, params?: unknown[], noCache?: boolean): Promise<T | null>;
174
187
  queryAsync<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<T[]>;
175
188
  executeAsync(sql: string, params?: unknown[]): Promise<unknown>;
189
+ /**
190
+ * ADR-0044: the async passthrough executeMany() itself was missing (unlike
191
+ * its executeAsync/insertAsync siblings above), so adapterExecuteMany()'s
192
+ * `(adapter as any).executeManyAsync` check found nothing on THIS wrapper
193
+ * and fell through to the synchronous executeMany() below — which forwards
194
+ * to the wrapped adapter's OWN sync executeMany(), the throwing "Use
195
+ * executeManyAsync()" stub on every async-native adapter (Postgres/MySQL/
196
+ * MSSQL/Firebird/Mongo). Real bug, caught by executeManyFacadeTxn.test.ts.
197
+ */
198
+ executeManyAsync(sql: string, paramsList: unknown[][]): Promise<import("./types.js").DatabaseResult | {
199
+ totalAffected: number;
200
+ lastId?: number | bigint;
201
+ }>;
176
202
  insertAsync(table: string, data: Record<string, unknown> | Record<string, unknown>[]): Promise<DatabaseResult>;
177
203
  updateAsync(table: string, data: Record<string, unknown>, filter: Record<string, unknown> | string, params?: unknown[]): Promise<DatabaseResult>;
178
204
  deleteAsync(table: string, filter: Record<string, unknown> | string | Record<string, unknown>[], params?: unknown[]): Promise<DatabaseResult>;
@@ -31,6 +31,32 @@ export declare function adapterFetch<T = Record<string, unknown>>(adapter: Datab
31
31
  export declare function adapterQuery<T = Record<string, unknown>>(adapter: DatabaseAdapter, sql: string, params?: unknown[]): Promise<T[]>;
32
32
  export declare function adapterFetchOne<T = Record<string, unknown>>(adapter: DatabaseAdapter, sql: string, params?: unknown[]): Promise<T | null>;
33
33
  export declare function adapterExecute(adapter: DatabaseAdapter, sql: string, params?: unknown[]): Promise<unknown>;
34
+ /**
35
+ * ADR-0044: the adapter-level batch primitive, called exactly once by
36
+ * Database#executeMany (never looped) — one aggregate DatabaseResult for the
37
+ * whole batch. Normalises whichever native shape an adapter returns: SQLite's
38
+ * `{success, affectedRows, lastId}` (the shared write shape already used by
39
+ * insert/update/delete) or an async-native adapter's `{totalAffected, lastId}`
40
+ * (pre-ADR-0044 shape, not yet unified per-adapter — normalised HERE at the
41
+ * one chokepoint every public write flows through, so the facade's contract
42
+ * is uniform without touching each of the five adapter files' internals).
43
+ */
44
+ export declare function adapterExecuteMany(adapter: DatabaseAdapter, sql: string, paramsList: unknown[][]): Promise<import("./types.js").DatabaseResult>;
45
+ /**
46
+ * Insert one row (or a batch) through the adapter's OWN native insert path
47
+ * (each adapter's `buildInsert()`/`Dialect`, feature 3's SQL builder
48
+ * consolidation) instead of hand-built SQL. This is the ONLY correct way to
49
+ * insert into a caller-named table/columns: Firebird's Dialect quotes only
50
+ * when it has to (an unquoted identifier folds to UPPERCASE, so quoting a
51
+ * lower-case name makes it unfindable — SQL error -204 "Table unknown"),
52
+ * while PostgreSQL/MSSQL/SQLite quote unconditionally. A caller that hand-
53
+ * quotes with one fixed style (e.g. always `"col"`) works on three engines
54
+ * and silently breaks on the fourth. seedTable()/seedOrm() route through
55
+ * this so their engine portability matches insert()/insertAsync()'s, which
56
+ * the write-path + provider contract suites already prove on all four real
57
+ * engines (features 9/10/11/12).
58
+ */
59
+ export declare function adapterInsert(adapter: DatabaseAdapter, table: string, data: Record<string, unknown>): Promise<unknown>;
34
60
  export declare function adapterStartTransaction(adapter: DatabaseAdapter): Promise<void>;
35
61
  export declare function adapterCommit(adapter: DatabaseAdapter): Promise<void>;
36
62
  export declare function adapterRollback(adapter: DatabaseAdapter): Promise<void>;
@@ -394,7 +420,19 @@ export declare class Database {
394
420
  * `try/catch` and convert, rather than testing the return value.
395
421
  */
396
422
  execute(sql: string, params?: unknown[]): Promise<boolean | unknown>;
397
- /** Insert one row (object) or a batch of rows (array of objects) into a table. */
423
+ /**
424
+ * Insert one row (object) or a batch of rows (array of objects) into a table.
425
+ *
426
+ * FAIL LOUD, matching update()/delete()/truncate(): a real driver failure
427
+ * (e.g. a NOT NULL / UNIQUE constraint violation) throws rather than
428
+ * resolving to `{ success: false, affectedRows: 0 }`. The async adapters
429
+ * (Postgres/MySQL/MSSQL/Firebird) already throw directly from insertAsync();
430
+ * SQLiteAdapter.insert() is the one adapter that CATCHES the driver error
431
+ * and returns a `{ success: false, error }` result instead (its own
432
+ * documented contract for the synchronous path) — assertWrote is what
433
+ * converts that into the same thrown DatabaseException every other engine
434
+ * already produces, exactly as it already does for update/delete/truncate.
435
+ */
398
436
  insert(table: string, data: Record<string, unknown> | Record<string, unknown>[]): Promise<DatabaseWriteResult>;
399
437
  /**
400
438
  * The table's primary-key column, introspected once and cached.
@@ -487,16 +525,28 @@ export declare class Database {
487
525
  nullable?: boolean;
488
526
  default?: unknown;
489
527
  primaryKey?: boolean;
528
+ primaryKeyPosition?: number | null;
490
529
  }[]>;
491
530
  /**
492
- * Execute a SQL statement with multiple parameter sets (batch insert/update).
493
- * Wraps all executions in a single transaction for atomicity and performance.
531
+ * Execute a SQL statement with multiple parameter sets as ONE aggregate
532
+ * batch (ADR-0044). Wraps the single delegated call in a transaction for
533
+ * atomicity — never loops #execute itself.
534
+ *
535
+ * BREAKING (ADR-0044, pre-3.14.0): used to return one result PER ROW
536
+ * (`unknown[]`, callers indexed into it) built by the FACADE looping
537
+ * execute()/adapterExecute() per chunk or per row. It now delegates to the
538
+ * adapter's OWN executeMany/executeManyAsync exactly once (DBA-D02: facade
539
+ * delegates once, never a facade row loop) and returns the SAME shared
540
+ * DatabaseResult shape insert()/update()/delete() already return
541
+ * ({success, affectedRows, lastId}) — affectedRows is the total ROW count,
542
+ * never the number of chunks/statements. A caller that indexed into the old
543
+ * per-row array must switch to inspecting the aggregate result.
494
544
  *
495
545
  * @param sql - The SQL statement with parameter placeholders.
496
- * @param paramSets - Array of parameter arrays, one per execution.
497
- * @returns Array of results from each execution.
546
+ * @param paramSets - Array of parameter arrays, one per row.
547
+ * @returns The aggregate DatabaseResult for the whole batch.
498
548
  */
499
- executeMany(sql: string, paramSets?: unknown[][]): Promise<unknown[]>;
549
+ executeMany(sql: string, paramSets?: unknown[][]): Promise<DatabaseWriteResult>;
500
550
  /** Return the last execute() error message, or null. */
501
551
  getError(): string | null;
502
552
  /**
@@ -1,14 +1,15 @@
1
1
  export type { FieldType, FieldDefinition, ModelDefinition, DatabaseAdapter, DatabaseResult as DatabaseWriteResult, ColumnInfo, QueryOptions, RelationshipDefinition, } from "./types.js";
2
+ export { REQUIRED_ADAPTER_CAPABILITIES, NOT_REQUIRED_ON_ADAPTER } from "./types.js";
2
3
  export { DatabaseResult } from "./databaseResult.js";
3
4
  export type { ColumnInfoResult } from "./databaseResult.js";
4
5
  export { Database, initDatabase, getAdapter, setAdapter, bindDatabase, createAdapterFromUrl, closeDatabase, parseDatabaseUrl, setNamedAdapter, getNamedAdapter, resolveDbPool, stripTrailingSemicolons, wrapWithCache, resetRequestCaches } from "./database.js";
5
- export { adapterFetch, adapterQuery, adapterFetchOne, adapterExecute, adapterStartTransaction, adapterCommit, adapterRollback, adapterTableExists, adapterTables, adapterColumns, adapterCreateTable, extractLastInsertId, } from "./database.js";
6
+ export { adapterFetch, adapterQuery, adapterFetchOne, adapterExecute, adapterInsert, adapterStartTransaction, adapterCommit, adapterRollback, adapterTableExists, adapterTables, adapterColumns, adapterCreateTable, extractLastInsertId, } from "./database.js";
6
7
  export type { DatabaseConfig } from "./database.js";
7
8
  export { DatabaseUrl, redactCredentials } from "./databaseUrl.js";
8
9
  export type { DatabaseEngine } from "./databaseUrl.js";
9
10
  export { discoverModels } from "./model.js";
10
11
  export type { DiscoveredModel } from "./model.js";
11
- export { syncModels, ensureMigrationTable, getNextBatch, isMigrationApplied, recordMigration, applyMigration, rollback, getAppliedMigrations, getLastBatchMigrations, removeMigrationRecord, migrate, createMigration, status, Migration, splitStatements, parseSetTerm, normalizeQuotes, sortMigrationFiles, shouldSkipCreateTable, } from "./migration.js";
12
+ export { syncModels, ensureMigrationTable, getNextBatch, isMigrationApplied, recordMigration, applyMigration, rollback, getAppliedMigrations, getLastBatchMigrations, removeMigrationRecord, migrate, createMigration, status, Migration, splitStatements, parseSetTerm, normalizeQuotes, sortMigrationFiles, shouldSkipCreateTable, shouldSkipForFirebird, } from "./migration.js";
12
13
  export type { MigrationResult, MigrationStatus } from "./migration.js";
13
14
  export { AutoCrud, generateCrudRoutes, crudEligibleModels } from "./autoCrud.js";
14
15
  export type { AutoCrudOptions } from "./autoCrud.js";
@@ -1,5 +1,14 @@
1
1
  import type { DatabaseAdapter } from "./types.js";
2
2
  import type { DiscoveredModel } from "./model.js";
3
+ /**
4
+ * If stmt is an ALTER TABLE ... ADD on Firebird and the column already exists,
5
+ * returns a skip reason string. Returns null if the statement should execute normally.
6
+ *
7
+ * Exported (like its shouldSkipCreateTable sibling) so it can be driven
8
+ * directly against a REAL Firebird connection in
9
+ * test/migrationContract.test.ts -- no fake adapter needed.
10
+ */
11
+ export declare function shouldSkipForFirebird(db: DatabaseAdapter, stmt: string): Promise<string | null>;
3
12
  /**
4
13
  * Make CREATE TABLE idempotent on engines lacking IF NOT EXISTS.
5
14
  *
@@ -53,17 +62,26 @@ export declare function removeMigrationRecord(name: string): Promise<void>;
53
62
  /**
54
63
  * Rollback the last batch of migrations using .down.sql files.
55
64
  *
56
- * For each migration in the last batch (in reverse order):
57
- * 1. Looks for a corresponding .down.sql file on disk
58
- * 2. If found, reads and executes the SQL statements
59
- * 3. If not found, logs a warning
60
- * 4. Deletes the tracking record either way
65
+ * FAIL-SAFE (MIG-DEC-02, reuses the Python reference model): for each
66
+ * migration in the last batch (in reverse order), the down artifact must
67
+ * actually run before the tracking record is removed. A MISSING .down.sql
68
+ * (or, on the legacy Map API, no registered down function) or a FAILING down
69
+ * statement now THROWS instead of logging a warning/error and still deleting
70
+ * the record — the old behaviour was the exact MIG-ROLLBACK-DROPS-LEDGER bug:
71
+ * the schema stayed applied but the ledger row vanished, silently untracked.
72
+ * The DELETE runs inside the SAME transaction as the down statements, so a
73
+ * partially-executed down (some statements ran, a later one failed) rolls
74
+ * back too — no half-reversed schema left behind either.
61
75
  *
62
76
  * @param migrationsDir - Directory containing migration files (default: "migrations")
63
77
  * @param delimiter - SQL statement delimiter (default: ";")
64
78
  * @returns Array of the down-migration files that were run, e.g.
65
79
  * "000001_create_users.down.sql". (The legacy down-FUNCTION Map API returns the
66
80
  * bare migration name instead, since no .down.sql file is involved there.)
81
+ * @throws When a migration in the batch has no down artifact, or its down
82
+ * statement(s) fail — the batch stops at that migration; earlier
83
+ * migrations in the SAME call that already rolled back stay rolled back
84
+ * (each is its own transaction).
67
85
  *
68
86
  * NOTE on return form (intentional, cross-framework): migration return values reflect
69
87
  * WHAT each method acted on, so the forms differ by method and that is by design (not
@@ -10,5 +10,8 @@ export declare function buildQuery(tableName: string, options: QueryOptions, ext
10
10
  sql: string;
11
11
  countSql: string;
12
12
  params: unknown[];
13
+ limit: number;
14
+ offset: number;
15
+ page: number;
13
16
  };
14
17
  export declare function parseQueryString(query: Record<string, string>): QueryOptions;
@@ -21,7 +21,18 @@ export interface SeedOptions {
21
21
  overrides?: Record<string, unknown>;
22
22
  /** Delete every existing row in the target before seeding (P2). */
23
23
  clear?: boolean;
24
- /** PRNG seed for reproducible FakeData output (P3). */
24
+ /**
25
+ * PRNG seed for reproducible FakeData output (P3). Honoured by seedOrm and
26
+ * seedModels, which build and seed their own FakeData internally.
27
+ *
28
+ * NOT honoured by seedTable (SEED-TABLE-SEED-INERT, SEED-DEC-01, ratified
29
+ * 2026-08-11 — same principle as the no-op ForeignKeyField on_delete):
30
+ * seedTable has no generators of its own to seed — fieldMap callables are
31
+ * opaque — so this used to be a silent no-op there. Passing it to seedTable
32
+ * now THROWS instead. Build your own `new FakeData(seed)` and close over it
33
+ * in fieldMap: `const fake = new FakeData(42); seedTable(db, table, count,
34
+ * { name: () => fake.name() })`.
35
+ */
25
36
  seed?: number;
26
37
  /** Re-raise on the first failed row instead of skipping it (P1). */
27
38
  strict?: boolean;
@@ -63,8 +74,10 @@ export declare function autoFieldMap(db: DatabaseAdapter, table: string, fake?:
63
74
  * (or a static value). If not provided, no rows are inserted.
64
75
  * @param overrides - (legacy positional) Static values applied to every row.
65
76
  * Prefer `opts.overrides`.
66
- * @param opts - Seed options: `{ overrides, clear, seed, strict }`.
77
+ * @param opts - Seed options: `{ overrides, clear, strict }`. `opts.seed` is
78
+ * NOT honoured here (see {@link SeedOptions.seed}) and throws if supplied.
67
79
  * @returns A SeedSummary `{ seeded, failed, errors }`.
80
+ * @throws {Error} If `opts.seed` is defined (SEED-TABLE-SEED-INERT removal).
68
81
  *
69
82
  * @example
70
83
  * const fake = new FakeData();
@@ -31,18 +31,31 @@ export declare class SQLTranslator {
31
31
  * Does NOT convert if OFFSET is present (TOP doesn't support it).
32
32
  */
33
33
  static limitToTop(sql: string): string;
34
+ /** Replace string literals, quoted identifiers and comments with opaque
35
+ * `\x00N\x00` tokens (doubled-quote escapes handled). */
36
+ private static maskLiterals;
37
+ /** Inverse of maskLiterals. */
38
+ private static restoreLiterals;
39
+ private static readonly PRIMARY;
34
40
  /**
35
- * Convert || concatenation to CONCAT() for MySQL/MSSQL.
41
+ * Convert `||` string concatenation to `CONCAT(...)` for MySQL/MSSQL.
36
42
  *
37
- * 'a' || 'b' || 'c' → CONCAT('a', 'b', 'c')
43
+ * Rewrites ONLY `||` operators joining expression operands OUTSIDE any string
44
+ * literal or comment, and only the operand chain — never the whole statement:
45
+ * SELECT a || b FROM t -> SELECT CONCAT(a, b) FROM t
46
+ * WHERE data = 'a||b' -> WHERE data = 'a||b' (literal untouched)
38
47
  */
39
48
  static concatPipesToFunc(sql: string): string;
40
49
  /**
41
- * Convert TRUE/FALSE to 1/0 for engines without boolean type (Firebird).
50
+ * Convert bare TRUE/FALSE to 1/0 for engines without a boolean type. A
51
+ * TRUE/FALSE INSIDE a string literal is data and is left untouched
52
+ * (`WHERE label = 'TRUE'` is preserved).
42
53
  */
43
54
  static booleanToInt(sql: string): string;
44
55
  /**
45
- * Convert ILIKE to LOWER() LIKE LOWER() for engines without ILIKE.
56
+ * Convert `col ILIKE pattern` to `LOWER(col) LIKE LOWER(pattern)` for engines
57
+ * without ILIKE. The pattern operand is captured whole (a multi-word
58
+ * `'%two words%'` survives) and an ILIKE INSIDE a string literal is untouched.
46
59
  */
47
60
  static ilikeToLike(sql: string): string;
48
61
  /**
@@ -1,4 +1,4 @@
1
- export type FieldType = "string" | "integer" | "number" | "numeric" | "boolean" | "datetime" | "text" | "json" | "foreignKey";
1
+ export type FieldType = "string" | "integer" | "number" | "numeric" | "decimal" | "boolean" | "datetime" | "text" | "json" | "foreignKey";
2
2
  export interface FieldDefinition {
3
3
  type: FieldType;
4
4
  primaryKey?: boolean;
@@ -10,6 +10,15 @@ export interface FieldDefinition {
10
10
  min?: number;
11
11
  max?: number;
12
12
  pattern?: string;
13
+ /**
14
+ * For type "decimal": the fixed precision/scale of a real DECIMAL(p, s)
15
+ * column. `number`/`numeric` stay a floating type (the documented money
16
+ * guidance); a `decimal` field keeps the declared scale in the COLUMN, so
17
+ * createTable emits DECIMAL(precision, scale) — identical on
18
+ * PostgreSQL/MySQL/MSSQL/Firebird/SQLite. Default 10 / 2 when omitted.
19
+ */
20
+ precision?: number;
21
+ scale?: number;
13
22
  /** For type "foreignKey": the referenced model name (string) */
14
23
  references?: string;
15
24
  /** For type "foreignKey": override the has-many property name on the referenced model */
@@ -50,6 +59,12 @@ export interface ColumnInfo {
50
59
  nullable?: boolean;
51
60
  default?: unknown;
52
61
  primaryKey?: boolean;
62
+ /**
63
+ * ADR-0044 amendment (Feature 5 Decision 7, 2026-08-10): null for a
64
+ * non-key column; for a composite key this is the 1-based DECLARED
65
+ * PRIMARY KEY (...) order, not table-column order.
66
+ */
67
+ primaryKeyPosition?: number | null;
53
68
  }
54
69
  export interface DatabaseResult {
55
70
  success: boolean;
@@ -57,44 +72,88 @@ export interface DatabaseResult {
57
72
  lastId?: number | bigint | string;
58
73
  error?: string;
59
74
  }
75
+ /**
76
+ * ADR-0044 (feature 3, plan/v3/fixtures/adapter_contract.json): the exact
77
+ * fourteen adapter capabilities every DatabaseAdapter implementation must
78
+ * provide (DBA-S01). Kept as data so the conformance suite can check it
79
+ * without a second hand-maintained copy of the list.
80
+ */
81
+ export declare const REQUIRED_ADAPTER_CAPABILITIES: readonly ["connect", "close", "getDatabaseType", "execute", "executeMany", "fetch", "fetchOne", "startTransaction", "commit", "rollback", "autocommit", "getTables", "getColumns", "tableExists"];
82
+ /**
83
+ * ADR-0044 NOT_REQUIRED_ON_ADAPTER (DBA-S03): engine-neutral composition that
84
+ * the adapter CONTRACT does not require. Node keeps these as REQUIRED
85
+ * TypeScript interface members anyway (unlike Python/PHP/Ruby's stricter
86
+ * runtime reflection) because `database.ts`/`cachedDatabase.ts` call them
87
+ * directly at dozens of sites with no optional-chaining guard, so making them
88
+ * TS-optional ripples into a much larger refactor than this pass covers;
89
+ * this constant records the ADR's INTENT for the conformance suite to check
90
+ * even though the compiler will not enforce their absence.
91
+ */
92
+ export declare const NOT_REQUIRED_ON_ADAPTER: readonly ["query", "insert", "update", "delete", "truncate", "fetchAll", "createTable", "addColumn", "lastInsertId", "error", "sqlTranslation"];
60
93
  export interface DatabaseAdapter {
94
+ /** Connect (ADR-0044 canonical lifecycle name). May be sync or async. */
95
+ connect(): void | Promise<void>;
96
+ /** Return the canonical, credential-free engine name ("sqlite", "postgres", ...). */
97
+ getDatabaseType(): string;
61
98
  /** Execute a statement (INSERT, UPDATE, DELETE, DDL). */
62
99
  execute(sql: string, params?: unknown[]): unknown;
63
- /** Execute a single SQL statement with multiple parameter sets (batch). */
64
- executeMany(sql: string, paramsList: unknown[][]): {
100
+ /**
101
+ * Execute a single SQL statement with multiple parameter sets as ONE
102
+ * aggregate result (ADR-0044). The shared write shape `DatabaseResult`
103
+ * ({success, affectedRows, lastId?}) is the target; the pre-ADR-0044 async
104
+ * adapters (Postgres/MySQL/MSSQL/Firebird/Mongo) still return their
105
+ * original `{totalAffected, lastId?}` shape internally today — the union
106
+ * covers both while `adapterExecuteMany()` in database.ts normalises
107
+ * whichever shape it receives at the one chokepoint every public batch
108
+ * write flows through.
109
+ */
110
+ executeMany(sql: string, paramsList: unknown[][]): DatabaseResult | {
65
111
  totalAffected: number;
66
112
  lastId?: number | bigint;
67
113
  };
68
- /** Query rows. */
69
- query<T = Record<string, unknown>>(sql: string, params?: unknown[]): T[];
70
- /** Fetch rows with optional pagination (limit/skip). */
114
+ /** Fetch rows with optional pagination (limit/skip). Native list, no envelope. */
71
115
  fetch<T = Record<string, unknown>>(sql: string, params?: unknown[], limit?: number, skip?: number): T[];
72
- /** Fetch a single row or null. */
116
+ /** Fetch a single row or null. No pagination count probe. */
73
117
  fetchOne<T = Record<string, unknown>>(sql: string, params?: unknown[]): T | null;
74
- /** Insert one or more rows into a table, returns result with lastId. */
75
- insert(table: string, data: Record<string, unknown> | Record<string, unknown>[]): DatabaseResult;
76
- /** Update rows in a table matching filter (object or string WHERE), returns affected row count. */
77
- update(table: string, data: Record<string, unknown>, filter: Record<string, unknown> | string, params?: unknown[]): DatabaseResult;
78
- /** Delete rows from a table matching filter (object, string WHERE, or array of objects). */
79
- delete(table: string, filter: Record<string, unknown> | string | Record<string, unknown>[], params?: unknown[]): DatabaseResult;
80
118
  /** Start a transaction. */
81
119
  startTransaction(): void;
82
120
  /** Commit the current transaction. */
83
121
  commit(): void;
84
122
  /** Rollback the current transaction. */
85
123
  rollback(): void;
124
+ /**
125
+ * Native boolean, readable and writable. A plain field is the idiomatic JS
126
+ * shape for "readable and writable" — no getter/setter ceremony needed.
127
+ */
128
+ autocommit: boolean;
129
+ /**
130
+ * ADR-0044 / DBA-P02: whether this adapter's deployment can guarantee an
131
+ * atomic multi-row batch write. Every built-in adapter defaults true (see
132
+ * each adapter's field initializer); a deployment that genuinely cannot (a
133
+ * standalone MongoDB without a replica set is the motivating real case)
134
+ * sets this false so executeMany rejects BEFORE the first write.
135
+ */
136
+ supportsAtomicBatch?: boolean;
86
137
  /** List all tables in the database. */
87
138
  getTables(): string[];
88
139
  /** List columns with types for a table. */
89
140
  getColumns(table: string): ColumnInfo[];
90
- /** Get the last inserted id (auto-increment integer, or a UUID/string PK). */
91
- lastInsertId(): number | bigint | string | null;
92
141
  /** Close the connection. */
93
142
  close(): void;
94
143
  /** Check if a table exists. */
95
144
  tableExists(name: string): boolean;
96
- /** Create a table from field definitions. */
145
+ /** Insert one or more rows into a table, returns result with lastId. */
146
+ insert(table: string, data: Record<string, unknown> | Record<string, unknown>[]): DatabaseResult;
147
+ /** Update rows in a table matching filter (object or string WHERE), returns affected row count. */
148
+ update(table: string, data: Record<string, unknown>, filter: Record<string, unknown> | string, params?: unknown[]): DatabaseResult;
149
+ /** Delete rows from a table matching filter (object, string WHERE, or array of objects). */
150
+ delete(table: string, filter: Record<string, unknown> | string | Record<string, unknown>[], params?: unknown[]): DatabaseResult;
151
+ /** Query rows (legacy convenience, superseded by fetch). */
152
+ query<T = Record<string, unknown>>(sql: string, params?: unknown[]): T[];
153
+ /** Create a table from field definitions (legacy, DDL composition lives above the adapter). */
97
154
  createTable(name: string, columns: Record<string, FieldDefinition>): void;
155
+ /** Get the last inserted id (legacy — prefer DatabaseResult.lastId from execute/executeMany). */
156
+ lastInsertId(): number | bigint | string | null;
98
157
  /** Get raw column info (legacy, used by migration). */
99
158
  getTableColumns?(name: string): Array<{
100
159
  name: string;