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.
- package/CLAUDE.md +24 -25
- package/package.json +1 -2
- package/packages/cli/dist/bin.js +20620 -18995
- package/packages/cli/src/bin.ts +28 -71
- package/packages/cli/src/commands/migrate.ts +36 -75
- package/packages/cli/src/commands/migrateRollback.ts +10 -1
- package/packages/cli/src/commands/test.ts +92 -21
- package/packages/core/dist/index.js +20459 -18815
- package/packages/core/public/js/tina4-dev-admin.min.js +23 -19
- package/packages/core/src/ai.ts +28 -12
- package/packages/core/src/api.ts +13 -5
- package/packages/core/src/background.ts +9 -3
- package/packages/core/src/devAdmin.ts +135 -20
- package/packages/core/src/dispatchPipeline.ts +185 -1
- package/packages/core/src/docs.ts +33 -5
- package/packages/core/src/env.ts +1 -1
- package/packages/core/src/errorOverlay.ts +39 -48
- package/packages/core/src/fakeData.ts +15 -0
- package/packages/core/src/index.ts +17 -6
- package/packages/core/src/logger.ts +892 -572
- package/packages/core/src/mcp.ts +9 -1
- package/packages/core/src/messenger.ts +31 -4
- package/packages/core/src/middleware.ts +169 -43
- package/packages/core/src/portTakeover.ts +232 -0
- package/packages/core/src/request.ts +57 -8
- package/packages/core/src/response.ts +67 -0
- package/packages/core/src/router.ts +35 -7
- package/packages/core/src/server.ts +450 -190
- package/packages/core/src/static.ts +81 -12
- package/packages/core/src/testClient.ts +126 -137
- package/packages/core/src/testing.ts +16 -12
- package/packages/core/src/types.ts +21 -9
- package/packages/core/src/version.ts +66 -0
- package/packages/core/src/websocket.ts +2 -2
- package/packages/core/src/websocketBackplane.ts +2 -2
- package/packages/frond/dist/index.js +31 -13
- package/packages/frond/src/engine.ts +39 -7
- package/packages/orm/dist/index.js +10879 -9258
- package/packages/orm/src/adapters/firebird.ts +200 -27
- package/packages/orm/src/adapters/mongodb.ts +160 -10
- package/packages/orm/src/adapters/mssql.ts +38 -11
- package/packages/orm/src/adapters/mysql.ts +24 -1
- package/packages/orm/src/adapters/odbc.ts +127 -29
- package/packages/orm/src/adapters/postgres.ts +18 -0
- package/packages/orm/src/adapters/sqlite.ts +93 -14
- package/packages/orm/src/autoCrud.ts +72 -8
- package/packages/orm/src/baseModel.ts +323 -71
- package/packages/orm/src/cachedDatabase.ts +48 -1
- package/packages/orm/src/database.ts +162 -59
- package/packages/orm/src/fakeData.ts +6 -2
- package/packages/orm/src/index.ts +4 -1
- package/packages/orm/src/migration.ts +95 -52
- package/packages/orm/src/query.ts +16 -4
- package/packages/orm/src/seeder.ts +43 -25
- package/packages/orm/src/sqlTranslator.ts +104 -19
- package/packages/orm/src/types.ts +97 -21
- package/packages/orm/src/validation.ts +5 -1
- package/packages/swagger/dist/index.js +3 -2
- package/packages/swagger/src/generator.ts +19 -4
- package/packages/swagger/src/ui.ts +6 -4
- package/types/cli/src/bin.d.ts +0 -22
- package/types/core/src/api.d.ts +11 -4
- package/types/core/src/background.d.ts +5 -2
- package/types/core/src/devAdmin.d.ts +35 -0
- package/types/core/src/dispatchPipeline.d.ts +41 -1
- package/types/core/src/errorOverlay.d.ts +13 -13
- package/types/core/src/index.d.ts +9 -6
- package/types/core/src/logger.d.ts +111 -185
- package/types/core/src/middleware.d.ts +40 -5
- package/types/core/src/portTakeover.d.ts +50 -0
- package/types/core/src/request.d.ts +15 -0
- package/types/core/src/response.d.ts +29 -0
- package/types/core/src/server.d.ts +92 -0
- package/types/core/src/testClient.d.ts +29 -3
- package/types/core/src/testing.d.ts +16 -12
- package/types/core/src/types.d.ts +21 -9
- package/types/core/src/version.d.ts +11 -0
- package/types/core/src/websocketBackplane.d.ts +1 -1
- package/types/frond/src/engine.d.ts +10 -0
- package/types/orm/src/adapters/firebird.d.ts +61 -2
- package/types/orm/src/adapters/mongodb.d.ts +20 -0
- package/types/orm/src/adapters/mssql.d.ts +11 -0
- package/types/orm/src/adapters/mysql.d.ts +11 -0
- package/types/orm/src/adapters/odbc.d.ts +35 -4
- package/types/orm/src/adapters/postgres.d.ts +11 -0
- package/types/orm/src/adapters/sqlite.d.ts +23 -4
- package/types/orm/src/baseModel.d.ts +45 -25
- package/types/orm/src/cachedDatabase.d.ts +27 -1
- package/types/orm/src/database.d.ts +56 -6
- package/types/orm/src/index.d.ts +3 -2
- package/types/orm/src/migration.d.ts +23 -5
- package/types/orm/src/query.d.ts +3 -0
- package/types/orm/src/seeder.d.ts +15 -2
- package/types/orm/src/sqlTranslator.d.ts +17 -4
- package/types/orm/src/types.d.ts +75 -16
- 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
|
-
|
|
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).
|
|
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
|
|
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
|
|
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(", ");
|