tina4-nodejs 3.13.98 → 3.13.99

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 (96) hide show
  1. package/CLAUDE.md +24 -25
  2. package/package.json +1 -2
  3. package/packages/cli/dist/bin.js +20620 -18995
  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 +20459 -18815
  9. package/packages/core/public/js/tina4-dev-admin.min.js +23 -19
  10. package/packages/core/src/ai.ts +28 -12
  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 +31 -13
  37. package/packages/frond/src/engine.ts +39 -7
  38. package/packages/orm/dist/index.js +10879 -9258
  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/api.d.ts +11 -4
  63. package/types/core/src/background.d.ts +5 -2
  64. package/types/core/src/devAdmin.d.ts +35 -0
  65. package/types/core/src/dispatchPipeline.d.ts +41 -1
  66. package/types/core/src/errorOverlay.d.ts +13 -13
  67. package/types/core/src/index.d.ts +9 -6
  68. package/types/core/src/logger.d.ts +111 -185
  69. package/types/core/src/middleware.d.ts +40 -5
  70. package/types/core/src/portTakeover.d.ts +50 -0
  71. package/types/core/src/request.d.ts +15 -0
  72. package/types/core/src/response.d.ts +29 -0
  73. package/types/core/src/server.d.ts +92 -0
  74. package/types/core/src/testClient.d.ts +29 -3
  75. package/types/core/src/testing.d.ts +16 -12
  76. package/types/core/src/types.d.ts +21 -9
  77. package/types/core/src/version.d.ts +11 -0
  78. package/types/core/src/websocketBackplane.d.ts +1 -1
  79. package/types/frond/src/engine.d.ts +10 -0
  80. package/types/orm/src/adapters/firebird.d.ts +61 -2
  81. package/types/orm/src/adapters/mongodb.d.ts +20 -0
  82. package/types/orm/src/adapters/mssql.d.ts +11 -0
  83. package/types/orm/src/adapters/mysql.d.ts +11 -0
  84. package/types/orm/src/adapters/odbc.d.ts +35 -4
  85. package/types/orm/src/adapters/postgres.d.ts +11 -0
  86. package/types/orm/src/adapters/sqlite.d.ts +23 -4
  87. package/types/orm/src/baseModel.d.ts +45 -25
  88. package/types/orm/src/cachedDatabase.d.ts +27 -1
  89. package/types/orm/src/database.d.ts +56 -6
  90. package/types/orm/src/index.d.ts +3 -2
  91. package/types/orm/src/migration.d.ts +23 -5
  92. package/types/orm/src/query.d.ts +3 -0
  93. package/types/orm/src/seeder.d.ts +15 -2
  94. package/types/orm/src/sqlTranslator.d.ts +17 -4
  95. package/types/orm/src/types.d.ts +75 -16
  96. package/packages/core/src/errorOverlay.test.ts +0 -122
@@ -1,10 +1,44 @@
1
1
  import type { RouteDefinition, Tina4Request, Tina4Response } from "../../core/src/index.js";
2
2
  import type { DiscoveredModel } from "./model.js";
3
+ import type { FieldDefinition } from "./types.js";
3
4
  import { getAdapter, adapterQuery, adapterExecute } from "./database.js";
4
5
  import { DatabaseResult } from "./databaseResult.js";
5
6
  import { buildQuery, parseQueryString } from "./query.js";
6
7
  import { validate } from "./validation.js";
7
8
 
9
+ /**
10
+ * CRUD-MASS-ASSIGNMENT: filter a write body down to writable columns before
11
+ * it is mapped to DB columns. Only DECLARED fields pass through (an unknown
12
+ * key -- including a real-but-undeclared column like a framework-injected
13
+ * `is_deleted` -- is dropped by construction, since it can never be `in
14
+ * fields`); `is_deleted` is ALSO explicitly guarded for the case a model
15
+ * chooses to declare it (soft-delete is mutated only by the DELETE handler,
16
+ * never a POST/PUT body); and the primary key is stripped except a
17
+ * genuinely natural (non-autoIncrement) key on CREATE, the documented way to
18
+ * choose one (an autoIncrement CREATE has the database assign it -- a
19
+ * client-supplied id previously passed straight into the INSERT column
20
+ * list -- and every UPDATE strips it, because the row is addressed by the
21
+ * URL `{id}` alone and a body PK would otherwise rename the row's own
22
+ * identity column via the SET clause).
23
+ */
24
+ function allowListedBody(
25
+ fields: Record<string, FieldDefinition>,
26
+ pkField: string,
27
+ body: Record<string, unknown> | null | undefined,
28
+ isCreate: boolean,
29
+ ): Record<string, unknown> {
30
+ const pkDef = fields[pkField];
31
+ const stripPk = isCreate ? pkDef?.autoIncrement === true : true;
32
+ const allowed: Record<string, unknown> = {};
33
+ for (const [key, value] of Object.entries(body ?? {})) {
34
+ if (!(key in fields)) continue;
35
+ if (key === "is_deleted") continue;
36
+ if (stripPk && key === pkField) continue;
37
+ allowed[key] = value;
38
+ }
39
+ return allowed;
40
+ }
41
+
8
42
  /**
9
43
  * Auto-CRUD — discovers ORM models and auto-generates REST endpoints.
10
44
  *
@@ -153,20 +187,20 @@ export function generateCrudRoutes(models: DiscoveredModel[], options: AutoCrudO
153
187
 
154
188
  // Parse query params for filtering / sorting / pagination
155
189
  const qp = parseQueryString(req.query ?? {});
156
- const { sql, countSql, params } = buildQuery(tableName, qp, extraConditions);
190
+ // limit/offset/page are the CLAMPED/CAPPED values buildQuery actually used
191
+ // for the SQL (PAGE-DEC-01: page >= 1, per-page <= DEFAULT_ROW_CAP) — read
192
+ // them back here instead of recomputing from the raw qp, so the envelope
193
+ // can never drift from the query that ran.
194
+ const { sql, countSql, params, limit, offset } = buildQuery(tableName, qp, extraConditions);
157
195
 
158
196
  // params includes limit and offset at the end; countSql doesn't need them
159
197
  const countParams = params.slice(0, -2);
160
198
  const rows = await adapterQuery(adapter, sql, params);
161
199
 
162
200
  // total is the TRUE total for the filter (a COUNT probe), NEVER the number
163
- // of rows this page returned (ADR-0043). limit/offset are exactly what the
164
- // SQL applied — buildQuery derives offset as (page - 1) * limit.
201
+ // of rows this page returned (ADR-0043).
165
202
  const countRow = await adapterQuery(adapter, countSql, countParams);
166
203
  const total = Number(countRow[0]?.total ?? 0);
167
- const limit = qp.limit ?? 100;
168
- const page = qp.page ?? 1;
169
- const offset = (page - 1) * limit;
170
204
 
171
205
  // The REST list envelope IS the canonical paginate envelope: exactly the
172
206
  // seven snake_case keys DatabaseResult.toPaginate() builds — records, total,
@@ -214,7 +248,11 @@ export function generateCrudRoutes(models: DiscoveredModel[], options: AutoCrudO
214
248
  },
215
249
  handler: async (req: Tina4Request, res: Tina4Response) => {
216
250
  const adapter = getAdapter();
217
- const body = req.body as Record<string, unknown>;
251
+ const rawBody = req.body as Record<string, unknown>;
252
+
253
+ // CRUD-MASS-ASSIGNMENT: allow-list before anything downstream (both
254
+ // validation and persistence) ever sees the body.
255
+ const body = allowListedBody(fields, pkField, rawBody, true);
218
256
 
219
257
  // Validate against field definitions
220
258
  const errors = validate(body, fields);
@@ -276,7 +314,25 @@ export function generateCrudRoutes(models: DiscoveredModel[], options: AutoCrudO
276
314
  },
277
315
  handler: async (req: Tina4Request, res: Tina4Response) => {
278
316
  const adapter = getAdapter();
279
- const body = req.body as Record<string, unknown>;
317
+ const rawBody = req.body as Record<string, unknown>;
318
+
319
+ // CRUD-MASS-ASSIGNMENT: allow-list -- the row is addressed by the URL
320
+ // {id} alone, so a body PK is stripped (never lets the write rename
321
+ // the row's own identity column or, on a mis-wired WHERE, redirect
322
+ // to a different row); is_deleted is guarded the same as create.
323
+ const body = allowListedBody(fields, pkField, rawBody, false);
324
+
325
+ // Feature 19 (VALID-NODE-PUT-NOVALIDATE): the update path validates the
326
+ // request body too — previously only POST did, so a PUT could write
327
+ // type/length/pattern-violating data a create would reject. isUpdate=true
328
+ // wires the partial-update mode: an absent field is not spuriously
329
+ // "required" (the row already has it), but a field that IS present is
330
+ // still held to its type/length/pattern/range constraints.
331
+ const errors = validate(body, fields, true);
332
+ if (errors.length > 0) {
333
+ res.status(422).json({ error: "Validation failed", errors });
334
+ return;
335
+ }
280
336
 
281
337
  const conditions = [`"${pkColumn}" = ?`, ...extraConditions];
282
338
  const existing = await adapterQuery(adapter,
@@ -294,6 +350,14 @@ export function generateCrudRoutes(models: DiscoveredModel[], options: AutoCrudO
294
350
  dbBody[getDbCol(key)] = value;
295
351
  }
296
352
 
353
+ // Nothing left to write once guarded/unknown keys are stripped (e.g.
354
+ // a body of only `{is_deleted: 1}`) -- a no-op update, not a broken
355
+ // empty SET clause.
356
+ if (Object.keys(dbBody).length === 0) {
357
+ res.json({ data: existing[0] });
358
+ return;
359
+ }
360
+
297
361
  const setClauses = Object.keys(dbBody)
298
362
  .map((col) => `"${col}" = ?`)
299
363
  .join(", ");