tina4-nodejs 3.13.92 → 3.13.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/CLAUDE.md +170 -28
  2. package/README.md +2 -2
  3. package/package.json +13 -9
  4. package/packages/cli/dist/bin.js +33126 -30055
  5. package/packages/cli/src/commands/metrics.ts +17 -11
  6. package/packages/cli/src/commands/serve.ts +10 -9
  7. package/packages/core/dist/index.js +33062 -29908
  8. package/packages/core/src/ai.ts +7 -1
  9. package/packages/core/src/auth.ts +191 -39
  10. package/packages/core/src/background.ts +19 -19
  11. package/packages/core/src/cache.ts +492 -49
  12. package/packages/core/src/devAdmin.ts +79 -32
  13. package/packages/core/src/devMailbox.ts +20 -44
  14. package/packages/core/src/dispatchPipeline.ts +285 -0
  15. package/packages/core/src/dotenv.ts +185 -40
  16. package/packages/core/src/index.ts +7 -6
  17. package/packages/core/src/logger.ts +257 -36
  18. package/packages/core/src/mcp.ts +1 -1
  19. package/packages/core/src/messenger.ts +81 -13
  20. package/packages/core/src/metrics.ts +199 -961
  21. package/packages/core/src/middleware.ts +390 -123
  22. package/packages/core/src/queue.ts +188 -32
  23. package/packages/core/src/queueBackends/kafkaBackend.ts +109 -13
  24. package/packages/core/src/queueBackends/liteBackend.ts +13 -0
  25. package/packages/core/src/queueBackends/mongoBackend.ts +101 -9
  26. package/packages/core/src/queueBackends/rabbitmqBackend.ts +22 -4
  27. package/packages/core/src/rateLimiter.ts +10 -5
  28. package/packages/core/src/request.ts +6 -9
  29. package/packages/core/src/response.ts +46 -1
  30. package/packages/core/src/router.ts +29 -4
  31. package/packages/core/src/server.ts +751 -414
  32. package/packages/core/src/session.ts +244 -27
  33. package/packages/core/src/sessionHandlers/childError.ts +72 -0
  34. package/packages/core/src/sessionHandlers/databaseHandler.ts +338 -48
  35. package/packages/core/src/sessionHandlers/memcachedHandler.ts +181 -0
  36. package/packages/core/src/sessionHandlers/mongoClient.ts +293 -202
  37. package/packages/core/src/sessionHandlers/mongoHandler.ts +88 -8
  38. package/packages/core/src/sessionHandlers/respClient.ts +16 -143
  39. package/packages/core/src/sessionHandlers/sqlClient.ts +290 -0
  40. package/packages/core/src/sessionHandlers/syncBridge.ts +190 -0
  41. package/packages/core/src/sessionHandlers/syncSocket.ts +236 -0
  42. package/packages/core/src/testClient.ts +18 -5
  43. package/packages/core/src/trustedProxy.ts +249 -0
  44. package/packages/core/src/types.ts +29 -5
  45. package/packages/core/src/websocket.ts +66 -0
  46. package/packages/frond/dist/index.js +74 -31
  47. package/packages/frond/src/engine.ts +99 -33
  48. package/packages/orm/dist/index.js +26554 -23400
  49. package/packages/orm/src/adapters/firebird.ts +183 -56
  50. package/packages/orm/src/adapters/mongodb.ts +25 -4
  51. package/packages/orm/src/adapters/mssql.ts +114 -29
  52. package/packages/orm/src/adapters/mysql.ts +103 -40
  53. package/packages/orm/src/adapters/odbc.ts +44 -21
  54. package/packages/orm/src/adapters/postgres.ts +118 -26
  55. package/packages/orm/src/adapters/sqlDialect.ts +120 -0
  56. package/packages/orm/src/adapters/sqlite.ts +64 -25
  57. package/packages/orm/src/baseModel.ts +135 -40
  58. package/packages/orm/src/cachedDatabase.ts +43 -19
  59. package/packages/orm/src/connectTimeout.ts +265 -0
  60. package/packages/orm/src/database.ts +338 -198
  61. package/packages/orm/src/databaseResult.ts +65 -13
  62. package/packages/orm/src/databaseUrl.ts +484 -0
  63. package/packages/orm/src/docstore.ts +386 -145
  64. package/packages/orm/src/index.ts +13 -3
  65. package/packages/orm/src/migration.ts +18 -3
  66. package/packages/orm/src/queryBuilder.ts +38 -4
  67. package/packages/orm/src/sqlTranslator.ts +310 -4
  68. package/packages/orm/src/types.ts +15 -4
  69. package/types/cli/src/bin.d.ts +92 -0
  70. package/types/cli/src/commands/build.d.ts +2 -0
  71. package/types/cli/src/commands/generate.d.ts +47 -0
  72. package/types/cli/src/commands/init.d.ts +1 -0
  73. package/types/cli/src/commands/metrics.d.ts +6 -0
  74. package/types/cli/src/commands/migrate.d.ts +1 -0
  75. package/types/cli/src/commands/migrateCreate.d.ts +1 -0
  76. package/types/cli/src/commands/migrateRollback.d.ts +1 -0
  77. package/types/cli/src/commands/migrateStatus.d.ts +1 -0
  78. package/types/cli/src/commands/queue.d.ts +20 -0
  79. package/types/cli/src/commands/routes.d.ts +1 -0
  80. package/types/cli/src/commands/seed.d.ts +1 -0
  81. package/types/cli/src/commands/serve.d.ts +6 -0
  82. package/types/cli/src/commands/test.d.ts +1 -0
  83. package/types/core/src/ai.d.ts +64 -0
  84. package/types/core/src/api.d.ts +262 -0
  85. package/types/core/src/auth.d.ts +177 -0
  86. package/types/core/src/authGate.d.ts +20 -0
  87. package/types/core/src/background.d.ts +34 -0
  88. package/types/core/src/cache.d.ts +163 -0
  89. package/types/core/src/constants.d.ts +38 -0
  90. package/types/core/src/container.d.ts +44 -0
  91. package/types/core/src/context/chunker.d.ts +31 -0
  92. package/types/core/src/context/index.d.ts +93 -0
  93. package/types/core/src/devAdmin.d.ts +179 -0
  94. package/types/core/src/devMailbox.d.ts +54 -0
  95. package/types/core/src/dispatchPipeline.d.ts +117 -0
  96. package/types/core/src/docs.d.ts +141 -0
  97. package/types/core/src/docsAutoDiscovery.d.ts +6 -0
  98. package/types/core/src/dotenv.d.ts +87 -0
  99. package/types/core/src/env.d.ts +28 -0
  100. package/types/core/src/errorOverlay.d.ts +36 -0
  101. package/types/core/src/events.d.ts +75 -0
  102. package/types/core/src/fakeData.d.ts +55 -0
  103. package/types/core/src/feedback.d.ts +90 -0
  104. package/types/core/src/graphql.d.ts +207 -0
  105. package/types/core/src/health.d.ts +22 -0
  106. package/types/core/src/htmlElement.d.ts +75 -0
  107. package/types/core/src/i18n.d.ts +37 -0
  108. package/types/core/src/index.d.ts +92 -0
  109. package/types/core/src/job.d.ts +39 -0
  110. package/types/core/src/logger.d.ts +200 -0
  111. package/types/core/src/mcp.d.ts +248 -0
  112. package/types/core/src/messenger.d.ts +191 -0
  113. package/types/core/src/metrics.d.ts +41 -0
  114. package/types/core/src/middleware.d.ts +330 -0
  115. package/types/core/src/mqtt.d.ts +257 -0
  116. package/types/core/src/mqttMessage.d.ts +67 -0
  117. package/types/core/src/plan.d.ts +96 -0
  118. package/types/core/src/projectIndex.d.ts +56 -0
  119. package/types/core/src/queue.d.ts +268 -0
  120. package/types/core/src/queueBackends/kafkaBackend.d.ts +117 -0
  121. package/types/core/src/queueBackends/liteBackend.d.ts +128 -0
  122. package/types/core/src/queueBackends/mongoBackend.d.ts +119 -0
  123. package/types/core/src/queueBackends/rabbitmqBackend.d.ts +55 -0
  124. package/types/core/src/rateLimiter.d.ts +49 -0
  125. package/types/core/src/request.d.ts +25 -0
  126. package/types/core/src/response.d.ts +28 -0
  127. package/types/core/src/routeDiscovery.d.ts +12 -0
  128. package/types/core/src/router.d.ts +366 -0
  129. package/types/core/src/scss.d.ts +19 -0
  130. package/types/core/src/server.d.ts +146 -0
  131. package/types/core/src/service.d.ts +115 -0
  132. package/types/core/src/session.d.ts +341 -0
  133. package/types/core/src/sessionHandlers/childError.d.ts +34 -0
  134. package/types/core/src/sessionHandlers/databaseHandler.d.ts +97 -0
  135. package/types/core/src/sessionHandlers/memcachedHandler.d.ts +60 -0
  136. package/types/core/src/sessionHandlers/mongoClient.d.ts +35 -0
  137. package/types/core/src/sessionHandlers/mongoHandler.d.ts +109 -0
  138. package/types/core/src/sessionHandlers/respClient.d.ts +22 -0
  139. package/types/core/src/sessionHandlers/sqlClient.d.ts +39 -0
  140. package/types/core/src/sessionHandlers/syncBridge.d.ts +91 -0
  141. package/types/core/src/sessionHandlers/syncSocket.d.ts +49 -0
  142. package/types/core/src/sessionHandlers/valkeyHandler.d.ts +65 -0
  143. package/types/core/src/static.d.ts +2 -0
  144. package/types/core/src/test.d.ts +94 -0
  145. package/types/core/src/testClient.d.ts +36 -0
  146. package/types/core/src/testing.d.ts +58 -0
  147. package/types/core/src/trustedProxy.d.ts +44 -0
  148. package/types/core/src/types.d.ts +242 -0
  149. package/types/core/src/validator.d.ts +52 -0
  150. package/types/core/src/websocket.d.ts +402 -0
  151. package/types/core/src/websocketBackplane.d.ts +166 -0
  152. package/types/core/src/websocketConnection.d.ts +54 -0
  153. package/types/core/src/wsdl.d.ts +101 -0
  154. package/types/frond/src/engine.d.ts +263 -0
  155. package/types/frond/src/index.d.ts +2 -0
  156. package/types/orm/src/adapters/firebird.d.ts +183 -0
  157. package/types/orm/src/adapters/mongodb.d.ts +81 -0
  158. package/types/orm/src/adapters/mssql.d.ts +77 -0
  159. package/types/orm/src/adapters/mysql.d.ts +67 -0
  160. package/types/orm/src/adapters/odbc.d.ts +94 -0
  161. package/types/orm/src/adapters/postgres.d.ts +86 -0
  162. package/types/orm/src/adapters/sqlDialect.d.ts +71 -0
  163. package/types/orm/src/adapters/sqlite.d.ts +68 -0
  164. package/types/orm/src/autoCrud.d.ts +73 -0
  165. package/types/orm/src/baseModel.d.ts +427 -0
  166. package/types/orm/src/cachedDatabase.d.ts +190 -0
  167. package/types/orm/src/connectTimeout.d.ts +100 -0
  168. package/types/orm/src/database.d.ts +655 -0
  169. package/types/orm/src/databaseResult.d.ts +109 -0
  170. package/types/orm/src/databaseUrl.d.ts +125 -0
  171. package/types/orm/src/docstore.d.ts +241 -0
  172. package/types/orm/src/fakeData.d.ts +22 -0
  173. package/types/orm/src/index.d.ts +43 -0
  174. package/types/orm/src/migration.d.ts +275 -0
  175. package/types/orm/src/model.d.ts +7 -0
  176. package/types/orm/src/query.d.ts +14 -0
  177. package/types/orm/src/queryBuilder.d.ts +193 -0
  178. package/types/orm/src/realtime/index.d.ts +7 -0
  179. package/types/orm/src/realtime/models/attachment.d.ts +43 -0
  180. package/types/orm/src/realtime/models/channel.d.ts +32 -0
  181. package/types/orm/src/realtime/models/channelMember.d.ts +32 -0
  182. package/types/orm/src/realtime/models/message.d.ts +36 -0
  183. package/types/orm/src/realtime/models/workspace.d.ts +26 -0
  184. package/types/orm/src/realtime/realtime.d.ts +24 -0
  185. package/types/orm/src/realtime/storage.d.ts +61 -0
  186. package/types/orm/src/seeder.d.ts +118 -0
  187. package/types/orm/src/sqlTranslator.d.ts +258 -0
  188. package/types/orm/src/types.d.ts +148 -0
  189. package/types/orm/src/validation.d.ts +6 -0
  190. package/types/swagger/src/generator.d.ts +46 -0
  191. package/types/swagger/src/index.d.ts +2 -0
  192. package/types/swagger/src/ui.d.ts +11 -0
  193. package/packages/core/src/sessionHandlers/redisHandler.ts +0 -206
@@ -0,0 +1,24 @@
1
+ import { type StorageBackend } from "./storage.js";
2
+ export interface RealtimeOptions {
3
+ prefix?: string;
4
+ authorize?: (identity: string, channelId: number) => boolean | Promise<boolean>;
5
+ storage?: StorageBackend;
6
+ features?: string[];
7
+ }
8
+ interface IceServer {
9
+ urls: string[];
10
+ username?: string;
11
+ credential?: string;
12
+ }
13
+ /** Build the ICE server list from the environment (STUN always; ephemeral TURN when configured). */
14
+ export declare function iceServers(): IceServer[];
15
+ /**
16
+ * Mount the realtime surface and return the resolved path map (also served
17
+ * from the config endpoint so the client can discover it).
18
+ *
19
+ * Returns a Promise because the framework-owned chat tables are created via the
20
+ * async ORM (Node's DB layer is async) - `await realtime(...)` so the tables
21
+ * exist before the first request. Route registration itself is synchronous.
22
+ */
23
+ export declare function realtime(options?: RealtimeOptions): Promise<Record<string, string>>;
24
+ export {};
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Pluggable file storage for the realtime "files" feature. StorageBackend is
3
+ * the interface; LocalStorage (default, zero-dependency, filesystem) and
4
+ * S3Storage (opt-in, S3-compatible via @aws-sdk/client-s3) are the shipped
5
+ * implementations. Selection mirrors the cache/session/queue backend pattern:
6
+ * TINA4_STORAGE_BACKEND (local default | s3) plus per-backend env vars, with a
7
+ * graceful fallback to local if an s3 backend cannot be constructed - a real
8
+ * persistent store, never a silent no-op. Parity with Python's storage.py.
9
+ */
10
+ export interface StorageBackend {
11
+ put(key: string, data: Buffer | string, mime?: string): void | Promise<void>;
12
+ get(key: string): Buffer | null | Promise<Buffer | null>;
13
+ /** A directly-fetchable URL when the backend supports one, else null. */
14
+ url(key: string, ttl?: number): string | null | Promise<string | null>;
15
+ delete(key: string): void | Promise<void>;
16
+ exists(key: string): boolean | Promise<boolean>;
17
+ }
18
+ /**
19
+ * Generate an opaque, collision-free storage key, preserving the extension.
20
+ * The key carries no user-controlled path segment, so it can never traverse
21
+ * outside the storage root.
22
+ */
23
+ export declare function storageKey(filename?: string): string;
24
+ /** Zero-dependency filesystem store. The default backend. */
25
+ export declare class LocalStorage implements StorageBackend {
26
+ private directory;
27
+ constructor(directory?: string);
28
+ private pathFor;
29
+ put(key: string, data: Buffer | string): void;
30
+ get(key: string): Buffer | null;
31
+ url(): string | null;
32
+ delete(key: string): void;
33
+ exists(key: string): boolean;
34
+ }
35
+ /**
36
+ * S3-compatible store (AWS S3, MinIO, ...). Opt-in; needs @aws-sdk/client-s3.
37
+ * Presigned GET URLs let clients fetch large blobs straight from object
38
+ * storage instead of streaming through the app.
39
+ */
40
+ export declare class S3Storage implements StorageBackend {
41
+ private client;
42
+ private bucket;
43
+ constructor(opts?: {
44
+ endpoint?: string;
45
+ key?: string;
46
+ secret?: string;
47
+ bucket?: string;
48
+ region?: string;
49
+ });
50
+ put(key: string, data: Buffer | string, mime?: string): Promise<void>;
51
+ get(key: string): Promise<Buffer | null>;
52
+ url(key: string, ttl?: number): Promise<string | null>;
53
+ delete(key: string): Promise<void>;
54
+ exists(key: string): Promise<boolean>;
55
+ }
56
+ /**
57
+ * Resolve the storage backend from an explicit instance or the environment.
58
+ * Falls back to LocalStorage (logging why) when an s3 backend cannot be built
59
+ * (driver missing or config incomplete) - a real store, never a silent no-op.
60
+ */
61
+ export declare function selectStorage(storage?: StorageBackend): StorageBackend;
@@ -0,0 +1,118 @@
1
+ import { FakeData } from "./fakeData.js";
2
+ import type { DatabaseAdapter, FieldDefinition } from "./types.js";
3
+ /**
4
+ * Result of a seed run — `{ seeded, failed, errors }`.
5
+ *
6
+ * `errors` is a list of `{ row, message }` describing every skipped row
7
+ * (`row` is the 0-based index). Mirrors the Python `SeedSummary`; Node tests
8
+ * compare `.seeded` / `.failed` rather than the bare integer.
9
+ */
10
+ export interface SeedSummary {
11
+ seeded: number;
12
+ failed: number;
13
+ errors: Array<{
14
+ row: number;
15
+ message: string;
16
+ }>;
17
+ }
18
+ /** Options shared by seedTable / seedOrm / seedModels. */
19
+ export interface SeedOptions {
20
+ /** Static values applied to every row (overrides generated values). */
21
+ overrides?: Record<string, unknown>;
22
+ /** Delete every existing row in the target before seeding (P2). */
23
+ clear?: boolean;
24
+ /** PRNG seed for reproducible FakeData output (P3). */
25
+ seed?: number;
26
+ /** Re-raise on the first failed row instead of skipping it (P1). */
27
+ strict?: boolean;
28
+ }
29
+ /**
30
+ * Introspect `table`'s columns and build a column->generator field map for
31
+ * {@link seedTable}, skipping the auto-increment / `id` primary key (the engine
32
+ * assigns it). Mirrors the Python master's `auto_field_map`
33
+ * (tina4_python/seeder/__init__.py): the shared "seed a table I did not
34
+ * hand-write generators for" helper that both the dev-admin seed endpoint and
35
+ * the MCP `seed_table` dev tool use — `seedTable` itself stays explicit
36
+ * (no map = no rows), and this is how a caller opts into automatic generation.
37
+ *
38
+ * Reuses `FakeData.forField()` (column-name + type heuristics) so the generated
39
+ * data matches every other Tina4 seeding path. Returns an empty map when the
40
+ * table has no seedable columns, so `seedTable` then seeds nothing rather than
41
+ * crashing.
42
+ *
43
+ * @param db - A DatabaseAdapter instance (pass `getAdapter()`, NOT the Database
44
+ * wrapper — the wrapper has no `columns()`).
45
+ * @param table - The table to introspect.
46
+ * @param fake - Optional shared FakeData (pass one seeded via `new FakeData(n)`
47
+ * for reproducible output).
48
+ * @returns `{ column -> () => value }`, ready to hand to `seedTable`.
49
+ */
50
+ export declare function autoFieldMap(db: DatabaseAdapter, table: string, fake?: FakeData): Promise<Record<string, () => unknown>>;
51
+ /**
52
+ * Seed a database table with fake data using raw SQL inserts.
53
+ *
54
+ * Visible-but-resilient (P1): each row is wrapped. On a row failure the cause
55
+ * is logged (with the row index) and the row is skipped — unless `strict: true`,
56
+ * in which case the first failure RE-RAISES. At the end a one-line summary is
57
+ * logged ("seeded N, M failed").
58
+ *
59
+ * @param db - A DatabaseAdapter instance
60
+ * @param tableName - The table to insert into
61
+ * @param count - Number of rows to insert (default 10)
62
+ * @param fieldMap - Dict of column_name -> callable that generates a value
63
+ * (or a static value). If not provided, no rows are inserted.
64
+ * @param overrides - (legacy positional) Static values applied to every row.
65
+ * Prefer `opts.overrides`.
66
+ * @param opts - Seed options: `{ overrides, clear, seed, strict }`.
67
+ * @returns A SeedSummary `{ seeded, failed, errors }`.
68
+ *
69
+ * @example
70
+ * const fake = new FakeData();
71
+ * await seedTable(db, "users", 50, {
72
+ * name: () => fake.name(),
73
+ * email: () => fake.email(),
74
+ * }, undefined, { clear: true });
75
+ */
76
+ export declare function seedTable(db: DatabaseAdapter, tableName: string, count?: number, fieldMap?: Record<string, (() => unknown) | unknown>, overrides?: Record<string, unknown>, opts?: SeedOptions): Promise<SeedSummary>;
77
+ /** A model-like shape the seeder can drive (real BaseModel subclass or mock). */
78
+ interface SeedableModel {
79
+ tableName: string;
80
+ fields: Record<string, FieldDefinition>;
81
+ _db?: string;
82
+ getDb?: () => DatabaseAdapter;
83
+ name?: string;
84
+ }
85
+ /**
86
+ * Seed an ORM model class with auto-generated fake data, based on field
87
+ * definitions. Visible-but-resilient (P1): each row is wrapped, failures are
88
+ * logged + counted + skipped (or re-raised under `strict`).
89
+ *
90
+ * @param ormClass - A model with static `tableName`, `fields`, and optionally
91
+ * `getDb`/`_db`.
92
+ * @param count - Number of rows to insert (default 10)
93
+ * @param overrides - (legacy positional) Static field overrides.
94
+ * @param seed - (legacy positional) PRNG seed for deterministic output.
95
+ * @param opts - Seed options `{ overrides, clear, seed, strict }`. Options
96
+ * supersede the legacy positional args when both are supplied.
97
+ * @param fkPools - (internal) pre-resolved FK value pools from seedModels.
98
+ * @returns A SeedSummary `{ seeded, failed, errors }`.
99
+ */
100
+ export declare function seedOrm(ormClass: SeedableModel, count?: number, overrides?: Record<string, unknown>, seed?: number, opts?: SeedOptions, fkPools?: Record<string, unknown[]>): Promise<SeedSummary>;
101
+ /**
102
+ * Batch-seed several ORM models, ordering by their foreignKey dependency graph
103
+ * (P4a). Parent tables seed before children (topological sort); when
104
+ * `clear: true` the clear runs in REVERSE order so children are removed before
105
+ * parents — no FK violations regardless of the order the caller lists models.
106
+ * FK columns are resolved to real parent PKs so child rows reference an
107
+ * existing parent. Mirrors Python `seed_models`.
108
+ *
109
+ * @param ormClasses - List of model classes to seed.
110
+ * @param count - Rows per model (default 10).
111
+ * @param opts - Seed options. `overrides` may be a flat dict applied to every
112
+ * model, or a Map/record keyed by model to apply per-model overrides.
113
+ * @returns `{ [modelName]: SeedSummary }` for each model seeded.
114
+ */
115
+ export declare function seedModels(ormClasses: SeedableModel[], count?: number, opts?: SeedOptions & {
116
+ overrides?: Record<string, unknown> | Map<SeedableModel, Record<string, unknown>>;
117
+ }): Promise<Record<string, SeedSummary>>;
118
+ export {};
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Tina4 SQL Translation — Cross-engine SQL translator.
3
+ *
4
+ * Translates SQL dialect differences between engines so that application
5
+ * code can use a single SQL style and have it adapted at runtime.
6
+ *
7
+ * import { SQLTranslator } from "@tina4/orm";
8
+ *
9
+ * // Firebird: LIMIT/OFFSET → ROWS X TO Y
10
+ * SQLTranslator.limitToRows("SELECT * FROM users LIMIT 10 OFFSET 5");
11
+ * // → "SELECT * FROM users ROWS 6 TO 15"
12
+ *
13
+ * // MSSQL: LIMIT → TOP N
14
+ * SQLTranslator.limitToTop("SELECT * FROM users LIMIT 10");
15
+ * // → "SELECT TOP 10 * FROM users"
16
+ *
17
+ * Also includes a query cache with TTL support.
18
+ */
19
+ export declare class SQLTranslator {
20
+ /**
21
+ * Convert LIMIT/OFFSET to Firebird ROWS...TO syntax.
22
+ *
23
+ * LIMIT 10 OFFSET 5 → ROWS 6 TO 15
24
+ * LIMIT 10 → ROWS 1 TO 10
25
+ */
26
+ static limitToRows(sql: string): string;
27
+ /**
28
+ * Convert LIMIT to MSSQL TOP syntax.
29
+ *
30
+ * SELECT ... LIMIT 10 → SELECT TOP 10 ...
31
+ * Does NOT convert if OFFSET is present (TOP doesn't support it).
32
+ */
33
+ static limitToTop(sql: string): string;
34
+ /**
35
+ * Convert || concatenation to CONCAT() for MySQL/MSSQL.
36
+ *
37
+ * 'a' || 'b' || 'c' → CONCAT('a', 'b', 'c')
38
+ */
39
+ static concatPipesToFunc(sql: string): string;
40
+ /**
41
+ * Convert TRUE/FALSE to 1/0 for engines without boolean type (Firebird).
42
+ */
43
+ static booleanToInt(sql: string): string;
44
+ /**
45
+ * Convert ILIKE to LOWER() LIKE LOWER() for engines without ILIKE.
46
+ */
47
+ static ilikeToLike(sql: string): string;
48
+ /**
49
+ * Translate AUTOINCREMENT across engines in DDL.
50
+ */
51
+ static autoIncrementSyntax(sql: string, engine: string): string;
52
+ /**
53
+ * Convert ? placeholders to engine-specific style.
54
+ *
55
+ * ? → %s (MySQL, PostgreSQL)
56
+ * ? → :1, :2, :3 (Oracle, Firebird)
57
+ */
58
+ static placeholderStyle(sql: string, style: string): string;
59
+ /**
60
+ * Detect and strip RETURNING clause from INSERT/UPDATE statements.
61
+ * Returns the cleaned SQL and the list of RETURNING columns.
62
+ *
63
+ * "INSERT INTO t (x) VALUES (1) RETURNING id, name"
64
+ * → { sql: "INSERT INTO t (x) VALUES (1)", columns: ["id", "name"] }
65
+ */
66
+ static parseReturning(sql: string): {
67
+ sql: string;
68
+ columns: string[];
69
+ };
70
+ /**
71
+ * v3.13.14 (#48): split a possibly-qualified table name into [schema, table].
72
+ *
73
+ * A model whose table name is qualified — PostgreSQL "gift_cards.gift_card",
74
+ * MSSQL "dbo.widget", MySQL "otherdb.table", SQLite "attached.table" — lives
75
+ * in that schema/catalog, not the default. Adapters use this so tableExists /
76
+ * getColumns query the right namespace instead of matching the whole dotted
77
+ * string as one flat name. Returns [null, name] for a bare name. Splits on the
78
+ * first dot. Firebird has no schemas, so its adapter ignores this.
79
+ */
80
+ static splitSchema(name: string): [string | null, string];
81
+ /**
82
+ * Hard per-statement bind-parameter ceiling per engine. 0 = never collapse.
83
+ * Sourced from test/fixtures/batch_write_contract.json, byte-identical in all
84
+ * four frameworks.
85
+ */
86
+ static readonly MAX_BIND_PARAMS: Record<string, number>;
87
+ /**
88
+ * The four frameworks do not agree on what an engine calls itself — Python
89
+ * and PHP report "postgresql", Ruby and Node report "postgres". Without
90
+ * normalising, the cap lookup misses and the collapse silently does nothing
91
+ * on the engine with the largest win.
92
+ */
93
+ static readonly ENGINE_ALIASES: Record<string, string>;
94
+ private static readonly INSERT_VALUES;
95
+ /**
96
+ * Engines whose lastInsertId reports the FIRST generated id of a multi-row
97
+ * INSERT rather than the last. Verified live, not assumed: a 3-row insert
98
+ * into a fresh MySQL table reports 1 while MAX(id) is 3. SQLite, PostgreSQL
99
+ * and MSSQL already report the last, so collapsing does not change them.
100
+ */
101
+ static readonly FIRST_ID_ENGINES: readonly string[];
102
+ /**
103
+ * Normalise a collapsed batch's last id to the LAST row's id.
104
+ *
105
+ * A row-at-a-time batch reports the last row's id simply because the last
106
+ * statement inserted the last row. Collapsing rows into one statement changes
107
+ * that on any engine that reports the FIRST generated id, so this restores
108
+ * the contract instead of quietly redefining it. The ids in one statement are
109
+ * consecutive, so the last is `first + rows - 1`.
110
+ */
111
+ static batchLastId(reportedId: unknown, rowsInChunk: number, engine: string): unknown;
112
+ /**
113
+ * Collapse a row-at-a-time INSERT batch into chunked multi-row VALUES.
114
+ *
115
+ * A batch that loops one INSERT per row pays a full network round-trip per
116
+ * row, and the round-trip — not SQL building — is the entire cost of a batch
117
+ * write. Measured over 500 rows: PostgreSQL 9848ms row-at-a-time against
118
+ * 15.8ms as a single multi-row statement (625x), MySQL 216x, MSSQL 121x.
119
+ *
120
+ * PURE: no I/O and no engine contact, so the chunking rules are checkable
121
+ * without a database. The live-engine runners prove the rows land.
122
+ *
123
+ * @returns Statements to run INSTEAD of the loop, or an EMPTY array meaning
124
+ * "not collapsible — keep looping", which is always correct.
125
+ */
126
+ static buildBatchInserts(sql: string, paramSets: unknown[][], engine: string): Array<[string, unknown[]]>;
127
+ /**
128
+ * Blank out string literals, quoted identifiers and comments, so a keyword
129
+ * search sees only real SQL. Blanks are spaces of the SAME LENGTH (newlines
130
+ * preserved), so offsets and line structure still line up with the original.
131
+ *
132
+ * This exists because "does the caller's SQL already have a LIMIT?" used to be
133
+ * `sql.toUpperCase().split("--")[0].includes("LIMIT")`, and MEASURED on a real
134
+ * 150-row table with the 100-row cap in force, every one of these returned
135
+ * ALL 150 ROWS instead of 100:
136
+ *
137
+ * SELECT * FROM t WHERE label != 'LIMIT' ORDER BY id -- literal
138
+ * SELECT * FROM t ORDER BY id -- LIMIT 5 -- line comment
139
+ * SELECT * FROM t ORDER BY id /* LIMIT 5 *\/ -- block comment
140
+ *
141
+ * A column named `rate_limit` does it too. That is a silently UNCAPPED read of
142
+ * a whole table, which is the exact production incident the row cap exists to
143
+ * prevent, reachable through an ordinary column name.
144
+ *
145
+ * @param sql Raw SQL, exactly as the caller wrote it.
146
+ * @returns The same string with literals and comments replaced by spaces.
147
+ */
148
+ static scrubSqlText(sql: string): string;
149
+ /**
150
+ * True when the statement ENDS with its own LIMIT clause, so appending another
151
+ * would be wrong (and on SQLite, a syntax error).
152
+ *
153
+ * Anchored to the END on purpose. A bare "contains LIMIT" test also matches a
154
+ * LIMIT inside a subquery, where the OUTER statement still needs its cap. This
155
+ * is tina4-php's `SqlNormalizerTrait::hasTrailingLimit` regex, ported verbatim
156
+ * so all four frameworks answer identically: it accepts a numeric value, `?`,
157
+ * `$1` and `:name` placeholders, MySQL's `LIMIT a, b`, and a trailing OFFSET.
158
+ *
159
+ * @param sql Raw SQL; literals and comments are scrubbed before matching.
160
+ */
161
+ static hasTrailingLimit(sql: string): boolean;
162
+ /**
163
+ * Append `LIMIT`/`OFFSET` to a statement unless it already carries its own.
164
+ *
165
+ * The clause goes on a NEW LINE. Appending it inline is the second half of the
166
+ * same bug: `SELECT * FROM t -- note` + ` LIMIT 100` puts the clause INSIDE the
167
+ * trailing comment, where SQLite silently ignores it and the whole table comes
168
+ * back. A newline cannot be commented out by a `--` that started on the line
169
+ * above. Trailing semicolons are stripped first for the same reason
170
+ * (`SELECT * FROM t;` + `LIMIT 100` is a syntax error).
171
+ *
172
+ * @param sql The caller's statement.
173
+ * @param limit Row cap to apply; a non-positive value means "no cap".
174
+ * @param offset Rows to skip; omitted or 0 emits no OFFSET.
175
+ */
176
+ static appendLimit(sql: string, limit?: number, offset?: number): string;
177
+ }
178
+ /**
179
+ * Simple in-memory query cache with TTL support.
180
+ */
181
+ export declare class QueryCache {
182
+ private store;
183
+ private defaultTtl;
184
+ private maxSize;
185
+ constructor(options?: {
186
+ defaultTtl?: number;
187
+ maxSize?: number;
188
+ });
189
+ /**
190
+ * Stable identity of the DATABASE a cache entry came from.
191
+ *
192
+ * `engine://host:port/database` - and deliberately NOTHING else.
193
+ *
194
+ * WHY IT EXISTS: the key used to be `query:${sql}:${params}` with nothing
195
+ * naming the connection, so on any SHARED backend two databases cross-served
196
+ * each other's rows. Two apps pointed at one Redis, or one app with a primary
197
+ * and an analytics connection, silently read each other's data. Identical SQL
198
+ * text across tenants is the COMMON case, not an edge case, so the collision
199
+ * was the normal outcome.
200
+ *
201
+ * WHY NO CREDENTIALS: a password in the key means every rotation silently
202
+ * cold-starts the cache, and a shared backend's key namespace is visible to
203
+ * every tenant of that backend - a secret must never be folded into it. The
204
+ * username is out for the same reason plus a second: two connections
205
+ * differing only by role read the SAME rows and should share the entry.
206
+ *
207
+ * WHY NOTHING PER-PROCESS: no pid, no object id, no salt. Those would isolate
208
+ * the databases by ACCIDENT and destroy the point of a shared cache, because
209
+ * no instance would ever hit another instance's entry.
210
+ */
211
+ static cacheIdentity(url: string): string;
212
+ /**
213
+ * Generate a cache key from DATABASE IDENTITY + SQL + params.
214
+ *
215
+ * The NUL separators keep the three parts from running together, so a table
216
+ * named after the tail of a database name cannot forge another database's
217
+ * key. The key is not hashed here: the only backend with a key-length limit
218
+ * is memcached, and its backend already SHA-256-hashes whatever it is given.
219
+ */
220
+ static queryKey(sql: string, params?: unknown[], identity?: string): string;
221
+ /**
222
+ * Get a cached value. Returns undefined if expired or missing.
223
+ */
224
+ get<T>(key: string): T | undefined;
225
+ /**
226
+ * Set a cached value with optional TTL (seconds) and tags for grouped
227
+ * invalidation via clearTag().
228
+ */
229
+ set<T>(key: string, value: T, ttl?: number, tags?: string[]): void;
230
+ /**
231
+ * Remove all entries that carry the given tag. Returns the number removed.
232
+ */
233
+ clearTag(tag: string): number;
234
+ /**
235
+ * Check if a key exists and is not expired.
236
+ */
237
+ has(key: string): boolean;
238
+ /**
239
+ * Delete a specific key.
240
+ */
241
+ delete(key: string): boolean;
242
+ /**
243
+ * Remove all expired entries.
244
+ */
245
+ sweep(): number;
246
+ /**
247
+ * Clear all cached entries.
248
+ */
249
+ clear(): void;
250
+ /**
251
+ * Get the number of cached entries.
252
+ */
253
+ size(): number;
254
+ /**
255
+ * Get or set a value using a factory function.
256
+ */
257
+ remember<T>(key: string, ttl: number, factory: () => T): T;
258
+ }
@@ -0,0 +1,148 @@
1
+ export type FieldType = "string" | "integer" | "number" | "numeric" | "boolean" | "datetime" | "text" | "json" | "foreignKey";
2
+ export interface FieldDefinition {
3
+ type: FieldType;
4
+ primaryKey?: boolean;
5
+ autoIncrement?: boolean;
6
+ required?: boolean;
7
+ default?: unknown;
8
+ minLength?: number;
9
+ maxLength?: number;
10
+ min?: number;
11
+ max?: number;
12
+ pattern?: string;
13
+ /** For type "foreignKey": the referenced model name (string) */
14
+ references?: string;
15
+ /** For type "foreignKey": override the has-many property name on the referenced model */
16
+ relatedName?: string;
17
+ }
18
+ export interface RelationshipDefinition {
19
+ model: string;
20
+ foreignKey: string;
21
+ /**
22
+ * The relationship accessor/include name on the OWNING model. For an
23
+ * FK-auto-wired has-many this is the declaring class name lowercased + "s"
24
+ * (Python master rule) or the `relatedName` override. Used by eager-load
25
+ * include resolution so an `include: ["posts"]` matches the wired relation.
26
+ */
27
+ relatedName?: string;
28
+ }
29
+ export interface ModelDefinition {
30
+ tableName: string;
31
+ fields: Record<string, FieldDefinition>;
32
+ fieldMapping?: Record<string, string>;
33
+ softDelete?: boolean;
34
+ tableFilter?: string;
35
+ hasOne?: RelationshipDefinition[];
36
+ hasMany?: RelationshipDefinition[];
37
+ belongsTo?: RelationshipDefinition[];
38
+ dbName?: string;
39
+ }
40
+ export interface ColumnInfo {
41
+ name: string;
42
+ type: string;
43
+ nullable?: boolean;
44
+ default?: unknown;
45
+ primaryKey?: boolean;
46
+ }
47
+ export interface DatabaseResult {
48
+ success: boolean;
49
+ affectedRows: number;
50
+ lastId?: number | bigint | string;
51
+ error?: string;
52
+ }
53
+ export interface DatabaseAdapter {
54
+ /** Execute a statement (INSERT, UPDATE, DELETE, DDL). */
55
+ execute(sql: string, params?: unknown[]): unknown;
56
+ /** Execute a single SQL statement with multiple parameter sets (batch). */
57
+ executeMany(sql: string, paramsList: unknown[][]): {
58
+ totalAffected: number;
59
+ lastId?: number | bigint;
60
+ };
61
+ /** Query rows. */
62
+ query<T = Record<string, unknown>>(sql: string, params?: unknown[]): T[];
63
+ /** Fetch rows with optional pagination (limit/skip). */
64
+ fetch<T = Record<string, unknown>>(sql: string, params?: unknown[], limit?: number, skip?: number): T[];
65
+ /** Fetch a single row or null. */
66
+ fetchOne<T = Record<string, unknown>>(sql: string, params?: unknown[]): T | null;
67
+ /** Insert one or more rows into a table, returns result with lastId. */
68
+ insert(table: string, data: Record<string, unknown> | Record<string, unknown>[]): DatabaseResult;
69
+ /** Update rows in a table matching filter (object or string WHERE), returns affected row count. */
70
+ update(table: string, data: Record<string, unknown>, filter: Record<string, unknown> | string, params?: unknown[]): DatabaseResult;
71
+ /** Delete rows from a table matching filter (object, string WHERE, or array of objects). */
72
+ delete(table: string, filter: Record<string, unknown> | string | Record<string, unknown>[], params?: unknown[]): DatabaseResult;
73
+ /** Start a transaction. */
74
+ startTransaction(): void;
75
+ /** Commit the current transaction. */
76
+ commit(): void;
77
+ /** Rollback the current transaction. */
78
+ rollback(): void;
79
+ /** List all tables in the database. */
80
+ getTables(): string[];
81
+ /** List columns with types for a table. */
82
+ getColumns(table: string): ColumnInfo[];
83
+ /** Get the last inserted id (auto-increment integer, or a UUID/string PK). */
84
+ lastInsertId(): number | bigint | string | null;
85
+ /** Close the connection. */
86
+ close(): void;
87
+ /** Check if a table exists. */
88
+ tableExists(name: string): boolean;
89
+ /** Create a table from field definitions. */
90
+ createTable(name: string, columns: Record<string, FieldDefinition>): void;
91
+ /** Get raw column info (legacy, used by migration). */
92
+ getTableColumns?(name: string): Array<{
93
+ name: string;
94
+ type: string;
95
+ }>;
96
+ /** Add a column to an existing table (legacy, used by migration). */
97
+ addColumn?(table: string, colName: string, def: FieldDefinition): void;
98
+ /**
99
+ * Stable identity of the DATABASE this adapter is connected to, as
100
+ * `engine://host:port/database` with NO credentials - set by whoever built
101
+ * the adapter from a URL or config.
102
+ *
103
+ * The query cache folds this into every key. Without it two databases sharing
104
+ * one cache backend cross-serve each other's rows, because identical SQL text
105
+ * across tenants is the common case.
106
+ */
107
+ cacheIdentity?: string;
108
+ }
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
+ export interface QueryOptions {
144
+ filter?: Record<string, unknown>;
145
+ sort?: string;
146
+ page?: number;
147
+ limit?: number;
148
+ }
@@ -0,0 +1,6 @@
1
+ import type { FieldDefinition } from "./types.js";
2
+ export interface ValidationError {
3
+ field: string;
4
+ message: string;
5
+ }
6
+ export declare function validate(data: Record<string, unknown>, fields: Record<string, FieldDefinition>, isUpdate?: boolean): ValidationError[];
@@ -0,0 +1,46 @@
1
+ import type { RouteDefinition } from "../../core/src/index.js";
2
+ import type { ModelDefinition } from "../../orm/src/index.js";
3
+ interface OpenAPISpecInfo {
4
+ title: string;
5
+ version: string;
6
+ description?: string;
7
+ contact?: {
8
+ name?: string;
9
+ url?: string;
10
+ email?: string;
11
+ };
12
+ license?: {
13
+ name: string;
14
+ url?: string;
15
+ };
16
+ }
17
+ interface OpenAPISpec {
18
+ openapi: string;
19
+ info: OpenAPISpecInfo;
20
+ servers?: {
21
+ url: string;
22
+ }[];
23
+ paths: Record<string, Record<string, unknown>>;
24
+ components?: {
25
+ schemas?: Record<string, unknown>;
26
+ securitySchemes?: Record<string, unknown>;
27
+ };
28
+ tags?: {
29
+ name: string;
30
+ }[];
31
+ }
32
+ /**
33
+ * Register a named OpenAPI security scheme (e.g. an oauth2 scheme with scopes,
34
+ * or a custom apiKey). Call at app bootstrap, before generate(). A registered
35
+ * scheme may override the built-in bearerAuth.
36
+ */
37
+ export declare function addSecurityScheme(name: string, definition: Record<string, unknown>): void;
38
+ /**
39
+ * Register a reusable component schema, referenceable via meta.requestSchema /
40
+ * meta.responseSchemas or a raw $ref.
41
+ */
42
+ export declare function addSchema(name: string, schema: Record<string, unknown>): void;
43
+ /** Clear the security-scheme and schema registries (test helper). */
44
+ export declare function resetRegistry(): void;
45
+ export declare function generate(routes: RouteDefinition[], models?: ModelDefinition[]): OpenAPISpec;
46
+ export {};
@@ -0,0 +1,2 @@
1
+ export { generate, addSecurityScheme, addSchema, resetRegistry } from "./generator.js";
2
+ export { createSwaggerRoutes, swaggerEnabled } from "./ui.js";
@@ -0,0 +1,11 @@
1
+ import type { RouteDefinition } from "../../core/src/index.js";
2
+ /**
3
+ * Whether the Swagger UI + spec routes should be registered at boot.
4
+ *
5
+ * Default: enabled when `TINA4_DEBUG=true`, disabled otherwise. Operators
6
+ * can force either state with `TINA4_SWAGGER_ENABLED=true|false`. Matches
7
+ * Python parity: dev-only by default to keep production attack surface
8
+ * minimal, but easy to expose intentionally for public APIs.
9
+ */
10
+ export declare function swaggerEnabled(): boolean;
11
+ export declare function createSwaggerRoutes(getSpec: () => unknown): RouteDefinition[];