tina4-nodejs 3.13.95 → 3.13.97

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 (36) hide show
  1. package/CLAUDE.md +3 -4
  2. package/package.json +2 -1
  3. package/packages/cli/dist/bin.js +708 -1012
  4. package/packages/core/dist/index.js +588 -893
  5. package/packages/core/public/css/tina4.min.css +1 -1
  6. package/packages/core/src/index.ts +1 -3
  7. package/packages/core/src/messenger.ts +288 -96
  8. package/packages/core/src/queueBackends/kafkaBackend.ts +23 -2
  9. package/packages/core/src/queueBackends/rabbitmqBackend.ts +29 -17
  10. package/packages/core/src/request.ts +28 -7
  11. package/packages/core/src/server.ts +135 -7
  12. package/packages/core/src/session.ts +8 -1
  13. package/packages/orm/dist/index.js +639 -944
  14. package/packages/orm/src/autoCrud.ts +12 -10
  15. package/packages/orm/src/database.ts +62 -58
  16. package/packages/orm/src/databaseResult.ts +44 -73
  17. package/packages/orm/src/index.ts +0 -3
  18. package/packages/orm/src/migration.ts +26 -8
  19. package/packages/orm/src/model.ts +4 -0
  20. package/packages/orm/src/queryBuilder.ts +12 -5
  21. package/packages/orm/src/types.ts +7 -74
  22. package/packages/swagger/dist/index.js +78 -20
  23. package/packages/swagger/src/generator.ts +172 -29
  24. package/types/core/src/index.d.ts +1 -3
  25. package/types/core/src/messenger.d.ts +45 -4
  26. package/types/core/src/queueBackends/kafkaBackend.d.ts +1 -0
  27. package/types/core/src/queueBackends/rabbitmqBackend.d.ts +2 -1
  28. package/types/core/src/server.d.ts +0 -4
  29. package/types/core/src/session.d.ts +7 -0
  30. package/types/orm/src/database.d.ts +34 -30
  31. package/types/orm/src/databaseResult.d.ts +26 -36
  32. package/types/orm/src/index.d.ts +1 -2
  33. package/types/orm/src/migration.d.ts +4 -3
  34. package/types/orm/src/types.d.ts +7 -34
  35. package/packages/core/src/scss.ts +0 -623
  36. package/types/core/src/scss.d.ts +0 -19
@@ -38,6 +38,40 @@ export declare function adapterTableExists(adapter: DatabaseAdapter, name: strin
38
38
  export declare function adapterTables(adapter: DatabaseAdapter): Promise<string[]>;
39
39
  export declare function adapterColumns(adapter: DatabaseAdapter, table: string): Promise<ColumnInfo[]>;
40
40
  export declare function adapterCreateTable(adapter: DatabaseAdapter, name: string, columns: Record<string, FieldDefinition>): Promise<void>;
41
+ /**
42
+ * The true row count for `sql`, ignoring the pagination the caller applied.
43
+ *
44
+ * `count` on a DatabaseResult is the TRUE TOTAL for the filter, not the number
45
+ * of rows the page returned. Node and Ruby used to populate it with
46
+ * `records.length` while Python and PHP populated it from a probe, so
47
+ * `db.fetch(sql).count` answered 20 here and 250 there for one query against one
48
+ * table, and every `toPaginate()` envelope built on it under-reported (ADR-0043).
49
+ * MEASURED 2026-08-05 on a 250-row table read with limit=20: Node reported total
50
+ * 20 over 2 pages against Python's 250 over 13.
51
+ *
52
+ * This is the single source of truth for that probe. Both read paths that build a
53
+ * DatabaseResult — `Database.fetch()` and `QueryBuilder.get()` — call it, so the
54
+ * two can never drift (QueryBuilder.get used to leave `count` at rows-returned,
55
+ * diverging from db.fetch AND from Python/Ruby, whose get() routes through fetch).
56
+ *
57
+ * Only probed when a limit was actually applied. With no limit the rows returned
58
+ * ARE the whole answer for this SQL, so `records.length` is already the true total
59
+ * and a second round-trip would buy nothing — which is also what keeps
60
+ * `fetchAll()` at one query.
61
+ *
62
+ * BEST EFFORT, and it can never mask a real failure: it runs AFTER the main query
63
+ * (which has already thrown on bad SQL) and returns `undefined` on any error.
64
+ * `undefined` — not 0 — is the miss value, so DatabaseResult falls back to
65
+ * records.length, a true lower bound. Reporting 0 next to 100 real records would
66
+ * be the same "states a wrong number authoritatively" defect this exists to remove.
67
+ *
68
+ * The closing paren goes on its OWN LINE: appended inline, a trailing
69
+ * `-- comment` in the caller's SQL comments it out and the probe dies with
70
+ * "incomplete input". Postgres, MySQL and MSSQL additionally require a name for
71
+ * the derived table; SQLite and Firebird do not, and Firebird rejects `AS` there —
72
+ * so the alias comes from the adapter, not an assumption.
73
+ */
74
+ export declare function probeTotal(adapter: DatabaseAdapter, sql: string, params: unknown[] | undefined, limit: number | undefined): Promise<number | undefined>;
41
75
  /**
42
76
  * Extract the engine-assigned auto-increment id from an `execute()` result.
43
77
  *
@@ -314,36 +348,6 @@ export declare class Database {
314
348
  * inherit it and stop returning every row.
315
349
  */
316
350
  private _fetchWithLimit;
317
- /**
318
- * The true row count for `sql`, ignoring the pagination we appended.
319
- *
320
- * `count` is the TRUE TOTAL for the filter, not the number of rows this page
321
- * returned. Node and Ruby used to populate it with `records.length` while
322
- * Python and PHP populated it from a probe, so `db.fetch(sql).count` answered
323
- * 20 here and 250 there for one query against one table, and every paginated
324
- * response built on it under-reported. MEASURED 2026-08-05 on a 250-row table
325
- * read with limit=20: Node reported total 20 over 2 pages against Python's
326
- * 250 over 13.
327
- *
328
- * Only probed when a limit was actually applied. With no limit the rows
329
- * returned ARE the whole answer for this SQL, so `records.length` is already
330
- * the true total and a second round-trip would buy nothing — which is also
331
- * what keeps `fetchAll()` at one query.
332
- *
333
- * BEST EFFORT, and it can never mask a real failure: it runs AFTER the main
334
- * query (which has already thrown on bad SQL) and returns undefined on any
335
- * error. `undefined` — not 0 — is the miss value, so DatabaseResult falls
336
- * back to records.length, a true lower bound. Reporting 0 next to 100 real
337
- * records would be the same "states a wrong number authoritatively" defect
338
- * this change exists to remove.
339
- *
340
- * The closing paren goes on its OWN LINE: appended inline, a trailing
341
- * `-- comment` in the caller's SQL comments it out and the probe dies with
342
- * "incomplete input". Postgres, MySQL and MSSQL additionally require a name
343
- * for the derived table; SQLite and Firebird do not, and Firebird rejects
344
- * `AS` there — so the alias comes from the adapter, not an assumption.
345
- */
346
- private countProbe;
347
351
  /**
348
352
  * Fetch a single row or null.
349
353
  *
@@ -31,53 +31,43 @@ export declare class DatabaseResult implements Iterable<Record<string, unknown>>
31
31
  toCsv(): string;
32
32
  /** Same as records — plain array of row objects. */
33
33
  toArray(): Record<string, unknown>[];
34
- /** Pagination envelope — accepts either (page, perPage) or (offset, limit) style.
34
+ /**
35
+ * Describe the page this result IS — the canonical pagination envelope.
35
36
  *
36
- * When called with two arguments both >= 0 and the first >= the second
37
- * (i.e. offset-style), pass `{ offset, limit }` as the first argument.
38
- * The simplest way is to always use the default (page, perPage) form and
39
- * let the autoCRUD layer supply offset/limit from the query string.
37
+ * Takes NO arguments and derives every field from the query that produced this
38
+ * result (ADR-0043). Passing ANY argument RAISES: a DatabaseResult holds no
39
+ * connection, so an argument could only re-slice the rows already in memory and
40
+ * then report total_pages for pages it can never reach. To read page N, FETCH
41
+ * page N (limit + offset) and call this with no arguments.
40
42
  *
41
- * Returns a superset of keys for backwards-compatibility across all clients.
42
- */
43
- /**
44
- * Describe the page this result actually IS. Takes no arguments.
43
+ * The envelope is EXACTLY seven snake_case keys, identical across all four
44
+ * frameworks: `records, total, page, per_page, total_pages, limit, offset`.
45
45
  *
46
- * MEASURED 2026-08-05 on a real 250-row table read with limit=20 offset=40
47
- * (page 3 of 13): this reported page 1 of 2 and returned 10 of the 20 rows.
48
- * It ignored the query entirely - defaulting page to 1 and perPage to 10 -
49
- * then re-sliced the rows it was handed, which were already just that page.
50
- * So a caller who paginated correctly at the SQL level had the answer
51
- * silently re-paginated underneath them, with a page number that was simply
52
- * wrong.
46
+ * per_page = the query's limit
47
+ * page = floor(offset / limit) + 1
48
+ * total = the TRUE total for the filter Database.fetch (and
49
+ * QueryBuilder.get) run a COUNT probe whenever a limit was
50
+ * applied NEVER the number of rows returned
51
+ * total_pages = ceil(total / per_page)
52
+ * records = the rows the query returned, VERBATIM (never re-sliced)
53
+ * limit = the SQL limit actually applied
54
+ * offset = the SQL offset actually applied
53
55
  *
54
- * WITH page/perPage it slices this result in memory, the behaviour GitHub
55
- * issue #106 asked for. Valid ONLY when the result holds the WHOLE set
56
- * (records.length >= count). A PARTIAL result cannot be sliced by page number
57
- * without lying: MEASURED on 100,000 rows read under the default cap of 100,
58
- * pages 1-5 of 20 were right and every page from 6 onward came back EMPTY
59
- * while totalPages reported 5,000.
56
+ * The JSON payload is snake_case even though the method name is camelCase — a
57
+ * JSON key is data, not a language surface (ADR-0043). The old duplicate and
58
+ * camelCase keys (`data`, `count`, `perPage`, `totalPages`, `has_next`,
59
+ * `has_prev`) are removed: Node emitted 13 keys, the worst offender of the four.
60
60
  *
61
- * `total` is `count`, and `count` is now the TRUE total for the filter in
62
- * all four frameworks - Database.fetch runs a COUNT probe whenever it applied
63
- * a limit. It used to be ROWS RETURNED here and in Ruby while Python and PHP
64
- * probed, so one query answered 20 in two frameworks and 250 in the other
65
- * two.
61
+ * @throws {TypeError} if called with any argument.
66
62
  */
67
- toPaginate(page?: number, perPage?: number): {
63
+ toPaginate(): {
68
64
  records: Record<string, unknown>[];
69
- data: Record<string, unknown>[];
70
- count: number;
71
65
  total: number;
72
- limit: number;
73
- offset: number;
74
66
  page: number;
75
67
  per_page: number;
76
- perPage: number;
77
- totalPages: number;
78
68
  total_pages: number;
79
- has_next: boolean;
80
- has_prev: boolean;
69
+ limit: number;
70
+ offset: number;
81
71
  };
82
72
  /** Iterable — for (const row of result) */
83
73
  [Symbol.iterator](): Iterator<Record<string, unknown>>;
@@ -1,5 +1,4 @@
1
- export type { FieldType, FieldDefinition, ModelDefinition, DatabaseAdapter, DatabaseResult as DatabaseWriteResult, ColumnInfo, QueryOptions, RelationshipDefinition, PaginatedResult, } from "./types.js";
2
- export { FetchResult } from "./types.js";
1
+ export type { FieldType, FieldDefinition, ModelDefinition, DatabaseAdapter, DatabaseResult as DatabaseWriteResult, ColumnInfo, QueryOptions, RelationshipDefinition, } from "./types.js";
3
2
  export { DatabaseResult } from "./databaseResult.js";
4
3
  export type { ColumnInfoResult } from "./databaseResult.js";
5
4
  export { Database, initDatabase, getAdapter, setAdapter, bindDatabase, createAdapterFromUrl, closeDatabase, parseDatabaseUrl, setNamedAdapter, getNamedAdapter, resolveDbPool, stripTrailingSemicolons, wrapWithCache, resetRequestCaches } from "./database.js";
@@ -206,7 +206,7 @@ export declare function status(adapter?: DatabaseAdapter, options?: {
206
206
  */
207
207
  export declare function createMigration(description: string, options?: {
208
208
  migrationsDir?: string;
209
- kind?: "sql" | "class";
209
+ kind?: "sql" | "code" | "class";
210
210
  }): Promise<string | {
211
211
  upPath: string;
212
212
  downPath: string;
@@ -258,11 +258,12 @@ export declare class Migration {
258
258
  * Scaffold a new migration file.
259
259
  *
260
260
  * kind="sql" — creates {timestamp}_{description}.sql + .down.sql (default)
261
- * kind="class" — creates {timestamp}_{description}.ts with a TypeScript class template
261
+ * kind="code" — creates {timestamp}_{description}.ts with a TypeScript class
262
+ * template. "class" is accepted as a legacy alias.
262
263
  *
263
264
  * Returns the path to the created up file (or class file).
264
265
  */
265
- create(description: string, kind?: "sql" | "class"): Promise<string | {
266
+ create(description: string, kind?: "sql" | "code" | "class"): Promise<string | {
266
267
  upPath: string;
267
268
  downPath: string;
268
269
  }>;
@@ -28,6 +28,13 @@ export interface RelationshipDefinition {
28
28
  }
29
29
  export interface ModelDefinition {
30
30
  tableName: string;
31
+ /**
32
+ * The model CLASS name (e.g. `Item` for tableName `items`), carried from
33
+ * `ModelClass.name` at discovery. Swagger keys `components.schemas` by this —
34
+ * the type name a generated client wants — falling back to a singular
35
+ * PascalCase derivation of tableName when a raw definition carries none.
36
+ */
37
+ className?: string;
31
38
  fields: Record<string, FieldDefinition>;
32
39
  fieldMapping?: Record<string, string>;
33
40
  softDelete?: boolean;
@@ -106,40 +113,6 @@ export interface DatabaseAdapter {
106
113
  */
107
114
  cacheIdentity?: string;
108
115
  }
109
- export interface PaginatedResult<T = Record<string, unknown>> {
110
- data: T[];
111
- page: number;
112
- perPage: number;
113
- total: number;
114
- totalPages: number;
115
- hasNext: boolean;
116
- hasPrev: boolean;
117
- }
118
- /**
119
- * Wraps an array of fetched rows with convenience methods.
120
- *
121
- * Mirrors Python's `DatabaseResult` and Ruby's `Tina4::DatabaseResult`.
122
- */
123
- export declare class FetchResult<T = Record<string, unknown>> {
124
- readonly records: T[];
125
- readonly count: number;
126
- readonly sql: string;
127
- constructor(records: T[], sql?: string);
128
- /** Paginate the in-memory result set. */
129
- toPaginate(page?: number, perPage?: number): PaginatedResult<T>;
130
- /** Return the first record or null. */
131
- first(): T | null;
132
- /** Return the last record or null. */
133
- last(): T | null;
134
- /** Check if result is empty. */
135
- isEmpty(): boolean;
136
- /** Convert to plain array. */
137
- toArray(): T[];
138
- /** Convert to JSON string. */
139
- toJSON(): string;
140
- /** Iterate over records. */
141
- [Symbol.iterator](): Iterator<T>;
142
- }
143
116
  export interface QueryOptions {
144
117
  filter?: Record<string, unknown>;
145
118
  sort?: string;