fastify-auto-sqlapi 0.1.3 → 0.1.4
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/AGENTS_BACKEND.md +6 -1
- package/AGENTS_FRONTEND.md +7 -5
- package/README.md +40 -1
- package/dist/lib/engine/bulk/bulk-upsert.d.ts.map +1 -1
- package/dist/lib/engine/bulk/bulk-upsert.js +3 -1
- package/dist/lib/engine/rest/insert.d.ts.map +1 -1
- package/dist/lib/engine/rest/insert.js +3 -1
- package/dist/lib/engine/rest/update.d.ts.map +1 -1
- package/dist/lib/engine/rest/update.js +5 -2
- package/dist/lib/engine/search/search.d.ts.map +1 -1
- package/dist/lib/engine/search/search.js +45 -15
- package/dist/lib/schema/bulk-delete.d.ts +1 -1
- package/dist/lib/schema/bulk-delete.d.ts.map +1 -1
- package/dist/lib/schema/bulk-delete.js +2 -2
- package/dist/lib/schema/bulk-upsert.d.ts +1 -1
- package/dist/lib/schema/bulk-upsert.d.ts.map +1 -1
- package/dist/lib/schema/bulk-upsert.js +5 -3
- package/dist/lib/schema/helpers.d.ts +2 -0
- package/dist/lib/schema/helpers.d.ts.map +1 -1
- package/dist/lib/schema/helpers.js +3 -2
- package/dist/lib/schema/insert.d.ts.map +1 -1
- package/dist/lib/schema/insert.js +4 -1
- package/dist/lib/schema/update.d.ts.map +1 -1
- package/dist/lib/schema/update.js +4 -1
- package/dist/lib/sql-api.d.ts +2 -0
- package/dist/lib/sql-api.d.ts.map +1 -1
- package/dist/lib/sql-api.js +1 -0
- package/dist/lib/tenant.d.ts +22 -0
- package/dist/lib/tenant.d.ts.map +1 -1
- package/dist/lib/tenant.js +0 -0
- package/dist/routes/auto/bulk-delete.routes.d.ts.map +1 -1
- package/dist/routes/auto/bulk-delete.routes.js +2 -1
- package/dist/routes/auto/bulk-upsert.routes.d.ts.map +1 -1
- package/dist/routes/auto/bulk-upsert.routes.js +2 -1
- package/dist/routes/auto/search.routes.d.ts.map +1 -1
- package/dist/routes/auto/search.routes.js +8 -0
- package/dist/types/plugin.d.ts +16 -0
- package/dist/types/plugin.d.ts.map +1 -1
- package/dist/types/plugin.js +4 -1
- package/dist/types/search.d.ts +7 -0
- package/dist/types/search.d.ts.map +1 -1
- package/package.json +2 -2
package/AGENTS_BACKEND.md
CHANGED
|
@@ -126,6 +126,8 @@ await app.register(fastifyAutoSqlApi, {
|
|
|
126
126
|
prefix: '/auto', // optional — standard Fastify prefix
|
|
127
127
|
onRequests: [], // optional — global auth hooks (run on every route)
|
|
128
128
|
getTenantId: (request) => request.user?.organizationId ?? null, // optional — multi-tenant
|
|
129
|
+
maxItemsPerPage: 1000, // optional — cap on search page size / no-paginator LIMIT (default 1000)
|
|
130
|
+
maxBulkItems: 1000, // optional — cap on bulk array length (default 1000)
|
|
129
131
|
debug: true, // optional — log all SQL queries and params to console
|
|
130
132
|
});
|
|
131
133
|
|
|
@@ -698,6 +700,7 @@ Behavior:
|
|
|
698
700
|
- **Insert**: auto-injects `organization_id` if single tenant; validates if already present (403 if mismatch); 400 if multi-tenant without explicit value
|
|
699
701
|
- **Update**: strips `organization_id` from SET (can't change tenant), adds to WHERE condition
|
|
700
702
|
- **Bulk upsert**: auto-injects on all records
|
|
703
|
+
- **Upsert conflict guard** (`upsertMap` set): before the `ON CONFLICT DO UPDATE`, a probe rejects (403) any incoming conflict key that matches a row owned by another tenant — an upsert cannot overwrite or re-assign a foreign tenant's row.
|
|
701
704
|
|
|
702
705
|
### Indirect tenant (via JOIN to parent table)
|
|
703
706
|
|
|
@@ -719,7 +722,7 @@ const TableOrder = defineTable({
|
|
|
719
722
|
Behavior:
|
|
720
723
|
- **Read**: INNER JOIN to parent table + WHERE on parent's tenant column
|
|
721
724
|
- **Insert/Bulk upsert**: validates FK references belong to tenant (1 batch query)
|
|
722
|
-
- **Update**: pre-check via SELECT with INNER JOIN (404 if not found)
|
|
725
|
+
- **Update**: pre-check via SELECT with INNER JOIN (404 if not found). If the payload also changes the through-FK (`localField`), the new value is re-validated against the tenant — moving a record into another tenant's scope is rejected (403).
|
|
723
726
|
- **Delete**: subquery `DELETE WHERE pk IN (SELECT ... INNER JOIN ... WHERE tenant IN (...))`
|
|
724
727
|
|
|
725
728
|
### Multi-tenant users
|
|
@@ -760,6 +763,8 @@ Tables without `tenantScope` are unaffected — no filtering regardless of `getT
|
|
|
760
763
|
- **`validateBulk` replaces `validate` in bulk**: when `validateBulk` is defined, it is called once with all items and per-item `validate` is skipped. This allows optimized batch queries instead of N individual checks. When only `validate` is defined, it runs per-item as fallback.
|
|
761
764
|
- **All hooks and validators receive camelCase records**: `validate` and the whole hook matrix (`beforeInsert`/`afterInsert`, `beforeUpdate`/`afterUpdate`, `beforeDelete`/`afterDelete`, `beforeBulkDelete`/`afterBulkDelete`) get records keyed by schema field names (camelCase). Mutations propagate to the SQL (plugin converts to DB column format via `colMap` after the hook). The engine internally uses `snakecaseRecord(..., schema)` after user mutations to map field names to actual DB columns.
|
|
762
765
|
- **Filters validation**: TypeBox schemas use `additionalProperties: false`. By default Fastify strips unknown fields silently. For 400 errors on unknown filters: `Fastify({ ajv: { customOptions: { removeAdditional: false } } })`.
|
|
766
|
+
- **Write-body whitelist**: insert/update/bulk-upsert bodies (`main` + secondaries items) are `additionalProperties: false`. Unknown properties are rejected with 400 — only columns in the generated Schema (as narrowed by `schemaOverrides`/`excludeFromCreation`) can be written, so no mass assignment of unexposed columns. Trim the Schema to keep sensitive columns unwritable.
|
|
767
|
+
- **Request limits**: `maxItemsPerPage` (default 1000) caps the search page size and is applied as the `LIMIT` even when no paginator is sent (so an empty-body search can't dump a whole table); over-limit `itemsPerPage` → 400. `maxBulkItems` (default 1000) caps the bulk array length via schema `maxItems` → 400. Programmatic `sqlApi.*` calls are uncapped.
|
|
763
768
|
- **Tenant filtering**: when `tenantScope` is set on a table and `getTenantId` is provided in plugin options, all CRUD operations are automatically scoped to the tenant. `getTenantId` returning `null`/`undefined` = admin (no filter). Returning an array = multi-tenant user (IN clause).
|
|
764
769
|
|
|
765
770
|
---
|
package/AGENTS_FRONTEND.md
CHANGED
|
@@ -35,10 +35,10 @@ Without prefix, routes are at root: `/search/customer`, `/rest/customer/:id`, et
|
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
- `orderBy` — SQL ORDER BY clause (default: `defaultOrder` from table config). Supports dotted notations for joins, see below.
|
|
38
|
-
- `page` + `itemsPerPage` — pagination (when either is present, response includes `pagination` object; `page` defaults to 1, `itemsPerPage` defaults to 500)
|
|
38
|
+
- `page` + `itemsPerPage` — pagination (when either is present, response includes `pagination` object; `page` defaults to 1, `itemsPerPage` defaults to 500). `itemsPerPage` is capped server-side at `maxItemsPerPage` (default 1000); sending a larger value returns 400.
|
|
39
39
|
- `computeMin`, `computeMax`, `computeSum`, `computeAvg` — aggregate a column, returned in `pagination.computed`
|
|
40
40
|
|
|
41
|
-
### Body (all fields optional, empty `{}` returns
|
|
41
|
+
### Body (all fields optional, empty `{}` returns up to `maxItemsPerPage` records)
|
|
42
42
|
|
|
43
43
|
```json
|
|
44
44
|
{
|
|
@@ -350,7 +350,7 @@ Add `page` or `itemsPerPage` to the query string to enable pagination:
|
|
|
350
350
|
?itemsPerPage=10 # page defaults to 1
|
|
351
351
|
```
|
|
352
352
|
|
|
353
|
-
When either is present, the response includes a `pagination` object with total count, total pages, and the paginator values. `page` defaults to 1, `itemsPerPage` defaults to 500.
|
|
353
|
+
When either is present, the response includes a `pagination` object with total count, total pages, and the paginator values. `page` defaults to 1, `itemsPerPage` defaults to 500. `itemsPerPage` is capped at `maxItemsPerPage` (default 1000) — a larger value returns 400. Even without pagination, a search returns at most `maxItemsPerPage` rows.
|
|
354
354
|
|
|
355
355
|
### Computed values (aggregations on main table)
|
|
356
356
|
|
|
@@ -503,6 +503,8 @@ Insert or update multiple records in a single request.
|
|
|
503
503
|
|
|
504
504
|
All main records are inserted/upserted in a single SQL query. Secondaries and deletions are processed per-item. `secondaries`/`deletions` keys are **aliases**.
|
|
505
505
|
|
|
506
|
+
The array is capped at `maxBulkItems` (default 1000); a longer array returns 400 — chunk client-side if you have more.
|
|
507
|
+
|
|
506
508
|
**Response (200):** Array of `{ main (PK-only), secondaries? (PK-only), deletions? }`.
|
|
507
509
|
|
|
508
510
|
---
|
|
@@ -517,7 +519,7 @@ Delete multiple records by PK.
|
|
|
517
519
|
[{ "id": 1 }, { "id": 2 }, { "id": 3 }]
|
|
518
520
|
```
|
|
519
521
|
|
|
520
|
-
Each object must contain the PK field. All deletions execute as a single `DELETE WHERE pk IN (...)`.
|
|
522
|
+
Each object must contain the PK field. All deletions execute as a single `DELETE WHERE pk IN (...)`. The array is capped at `maxBulkItems` (default 1000) — a longer array returns 400.
|
|
521
523
|
|
|
522
524
|
**Response (200):** Array of PK-only objects.
|
|
523
525
|
|
|
@@ -531,7 +533,7 @@ Each object must contain the PK field. All deletions execute as a single `DELETE
|
|
|
531
533
|
|
|
532
534
|
Insert, update, and bulk-upsert endpoints may return **structured field-level validation errors** (400). Two sources:
|
|
533
535
|
|
|
534
|
-
1. **Schema validation** (TypeBox/Ajv) — type mismatches, missing required fields
|
|
536
|
+
1. **Schema validation** (TypeBox/Ajv) — type mismatches, missing required fields, and **unknown properties** (write bodies reject any field not in the table Schema — send only real columns, don't include extra keys)
|
|
535
537
|
2. **Custom validation** (`validate` / `validateBulk` on the table) — business rules, cross-entity checks
|
|
536
538
|
|
|
537
539
|
Both return the same response format:
|
package/README.md
CHANGED
|
@@ -238,6 +238,8 @@ await app.register(fastifyAutoSqlApi, {
|
|
|
238
238
|
swagger: true, // Optional — enable Swagger UI (or pass SwaggerOptions)
|
|
239
239
|
onRequests: [authMiddleware], // Optional — global hooks applied to every route
|
|
240
240
|
getTenantId: (req) => id, // Optional — multi-tenant function
|
|
241
|
+
maxItemsPerPage: 1000, // Optional — cap on search page size (default: 1000)
|
|
242
|
+
maxBulkItems: 1000, // Optional — cap on bulk array length (default: 1000)
|
|
241
243
|
debug: true, // Optional — log all SQL queries and params
|
|
242
244
|
});
|
|
243
245
|
```
|
|
@@ -250,6 +252,8 @@ await app.register(fastifyAutoSqlApi, {
|
|
|
250
252
|
| `swagger` | `boolean \| SwaggerOptions` | No | Enable Swagger UI |
|
|
251
253
|
| `onRequests` | `Function[]` | No | Global auth/middleware hooks |
|
|
252
254
|
| `getTenantId` | `(req) => id \| null` | No | Tenant resolver for multi-tenant |
|
|
255
|
+
| `maxItemsPerPage` | `number` | No | Max search page size, and the row `LIMIT` applied when no paginator is sent (default: `1000`) |
|
|
256
|
+
| `maxBulkItems` | `number` | No | Max number of items accepted by the bulk endpoints (default: `1000`) |
|
|
253
257
|
| `debug` | `boolean` | No | Log all SQL queries to console |
|
|
254
258
|
|
|
255
259
|
For MySQL/MariaDB, register `mysql2/promise` pool instead of `@fastify/postgres` and pass `dialect: 'mysql'` or `'mariadb'`.
|
|
@@ -294,6 +298,28 @@ are always available to your own code.
|
|
|
294
298
|
**3. Multi-tenancy** — `getTenantId` + `tenantScope` filter every query by tenant (see
|
|
295
299
|
[Multi-Tenant](#multi-tenant)).
|
|
296
300
|
|
|
301
|
+
### Request limits
|
|
302
|
+
|
|
303
|
+
Two hard caps bound how much work a single request can trigger (defense against accidental or
|
|
304
|
+
malicious resource exhaustion — relevant precisely because the API is open by default):
|
|
305
|
+
|
|
306
|
+
- **Search result size** — `itemsPerPage` above `maxItemsPerPage` (default `1000`) is rejected
|
|
307
|
+
with `400`. When a search omits pagination entirely, the same value is applied as a `LIMIT`, so
|
|
308
|
+
an empty-body `POST /search/:table` can never dump an entire table.
|
|
309
|
+
- **Bulk array size** — the `PUT /bulk/:table` and `POST /bulk/:table/delete` bodies reject arrays
|
|
310
|
+
longer than `maxBulkItems` (default `1000`) at schema validation.
|
|
311
|
+
|
|
312
|
+
Both are configurable in the plugin options. The programmatic `fastify.sqlApi.*` methods are not
|
|
313
|
+
capped — they run your own trusted code.
|
|
314
|
+
|
|
315
|
+
### Write whitelist
|
|
316
|
+
|
|
317
|
+
Insert/update/bulk-upsert bodies reject **unknown properties** (`additionalProperties: false`):
|
|
318
|
+
only columns present in the generated Schema (as narrowed by `schemaOverrides` /
|
|
319
|
+
`excludeFromCreation`) can be written. An unexpected field is a `400`, never a silently-written
|
|
320
|
+
column — the Schema is the write whitelist. Trim the generated Schema (or use `excludeFromCreation`)
|
|
321
|
+
to keep sensitive columns out of it.
|
|
322
|
+
|
|
297
323
|
## API Reference
|
|
298
324
|
|
|
299
325
|
### POST /search/{table}
|
|
@@ -358,6 +384,8 @@ Search with filters, advanced conditions, pagination, ordering, joins, and aggre
|
|
|
358
384
|
|
|
359
385
|
**Querystring** (optional): `orderBy`, `page`, `itemsPerPage`, `computeMin`, `computeMax`, `computeSum`, `computeAvg`
|
|
360
386
|
|
|
387
|
+
> `itemsPerPage` is capped at `maxItemsPerPage` (default `1000`); a larger value returns `400`. A search with no `page`/`itemsPerPage` returns at most that many rows (no full-table dumps). See [Request limits](#request-limits).
|
|
388
|
+
|
|
361
389
|
**Response:**
|
|
362
390
|
|
|
363
391
|
```json
|
|
@@ -501,6 +529,17 @@ defineTable({
|
|
|
501
529
|
|
|
502
530
|
Tables without `tenantScope` are unaffected.
|
|
503
531
|
|
|
532
|
+
### Isolation guarantees on writes
|
|
533
|
+
|
|
534
|
+
Tenant isolation is enforced on write paths, not just reads:
|
|
535
|
+
|
|
536
|
+
- **Upsert conflicts are ownership-checked.** On a tenant-scoped table with an `upsertMap`, an
|
|
537
|
+
upsert (`POST /rest/:table` or `PUT /bulk/:table`) whose conflict key matches a row owned by
|
|
538
|
+
another tenant is rejected with `403` — it cannot overwrite or re-assign that row.
|
|
539
|
+
- **The tenant link cannot be changed to another tenant.** For direct scopes the tenant column is
|
|
540
|
+
stripped from update payloads. For indirect scopes, changing the through-FK (`localField`) is
|
|
541
|
+
allowed only to a value the caller owns; otherwise the update returns `403`.
|
|
542
|
+
|
|
504
543
|
## Swagger
|
|
505
544
|
|
|
506
545
|
Enabled by passing `swagger: true` (or a config object) to the plugin options. Requires `@fastify/swagger` and `@fastify/swagger-ui` as peer dependencies.
|
|
@@ -610,7 +649,7 @@ After registering the plugin, `app.sqlApi` is decorated on the Fastify instance
|
|
|
610
649
|
## Requirements
|
|
611
650
|
|
|
612
651
|
- Node.js >= 18
|
|
613
|
-
- Fastify >=
|
|
652
|
+
- Fastify >= 5 (peer dependency)
|
|
614
653
|
- One of:
|
|
615
654
|
- **PostgreSQL** + `pg` + `@fastify/postgres`
|
|
616
655
|
- **MySQL** / **MariaDB** + `mysql2`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bulk-upsert.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/bulk/bulk-upsert.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAEjB,MAAM,mBAAmB,CAAC;AAE3B,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"bulk-upsert.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/bulk/bulk-upsert.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAEjB,MAAM,mBAAmB,CAAC;AAE3B,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAoF5F"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { camelcaseObject } from '../../naming.js';
|
|
2
2
|
import { processSecondaries, processDeletions, prepareInsertRecord } from '../write-helpers.js';
|
|
3
|
-
import { enforceTenantOnWrites } from '../../tenant.js';
|
|
3
|
+
import { enforceTenantOnWrites, assertTenantOwnsConflicts } from '../../tenant.js';
|
|
4
4
|
import { runBulkValidation } from '../validate.js';
|
|
5
5
|
import { primaryAsCols } from '../../../types.js';
|
|
6
6
|
export async function bulkUpsertEngine(params) {
|
|
@@ -26,6 +26,8 @@ export async function bulkUpsertEngine(params) {
|
|
|
26
26
|
let pkRows;
|
|
27
27
|
if (upsertKeys) {
|
|
28
28
|
const conflictCols = upsertKeys.map((k) => schema.col(k));
|
|
29
|
+
// Tenant isolation: a conflicting upsert must not overwrite rows owned by another tenant.
|
|
30
|
+
await assertTenantOwnsConflicts(db, tenant, schema.tableName, conflictCols, preparedMains);
|
|
29
31
|
pkRows = await db.bulkInsertOrUpdate(schema.tableName, preparedMains, conflictCols, pkCol);
|
|
30
32
|
}
|
|
31
33
|
else {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"insert.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/rest/insert.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,YAAY,EACZ,YAAY,EAEb,MAAM,mBAAmB,CAAC;AAE3B,wBAAsB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"insert.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/rest/insert.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,YAAY,EACZ,YAAY,EAEb,MAAM,mBAAmB,CAAC;AAE3B,wBAAsB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAiE9E"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { camelcaseObject } from '../../naming.js';
|
|
2
2
|
import { processSecondaries, prepareInsertRecord } from '../write-helpers.js';
|
|
3
|
-
import { enforceTenantOnWrites } from '../../tenant.js';
|
|
3
|
+
import { enforceTenantOnWrites, assertTenantOwnsConflicts } from '../../tenant.js';
|
|
4
4
|
import { primaryAsCols } from '../../../types.js';
|
|
5
5
|
export async function insertEngine(params) {
|
|
6
6
|
const { db, tableConf, dbTables, request, record, secondaries, tenant } = params;
|
|
@@ -20,6 +20,8 @@ export async function insertEngine(params) {
|
|
|
20
20
|
const upsertKeys = tableConf.upsertMap?.get(schema);
|
|
21
21
|
if (upsertKeys) {
|
|
22
22
|
const conflictCols = upsertKeys.map((k) => schema.col(k));
|
|
23
|
+
// Tenant isolation: a conflicting upsert must not overwrite a row owned by another tenant.
|
|
24
|
+
await assertTenantOwnsConflicts(tx, tenant, schema.tableName, conflictCols, [mainRecord]);
|
|
23
25
|
pkResult = await tx.insertOrUpdate(schema.tableName, mainRecord, conflictCols, pkCol);
|
|
24
26
|
}
|
|
25
27
|
else {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/rest/update.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/rest/update.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,YAAY,EACZ,YAAY,EAEb,MAAM,mBAAmB,CAAC;AAE3B,wBAAsB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAwH9E"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { snakecaseRecord } from '../../naming.js';
|
|
2
2
|
import { processSecondaries, processDeletions } from '../write-helpers.js';
|
|
3
|
-
import { stripTenantColumn, assertTenantOwnership, buildTenantUpdateExtra, } from '../../tenant.js';
|
|
3
|
+
import { stripTenantColumn, assertTenantOwnership, buildTenantUpdateExtra, enforceTenantFKOnUpdate, } from '../../tenant.js';
|
|
4
4
|
import { runValidation } from '../validate.js';
|
|
5
5
|
import { httpError } from '../../errors.js';
|
|
6
6
|
export async function updateEngine(params) {
|
|
@@ -29,9 +29,12 @@ export async function updateEngine(params) {
|
|
|
29
29
|
const updateFields = { ...snaked };
|
|
30
30
|
for (const c of pkCols)
|
|
31
31
|
delete updateFields[c];
|
|
32
|
-
// 6. Strip tenant column (user cannot change tenant of an existing record)
|
|
32
|
+
// 6. Strip tenant column (user cannot change tenant of an existing record).
|
|
33
|
+
// For indirect scopes the tenant link is the through-FK (localField): it may be changed,
|
|
34
|
+
// but only to another value the caller owns — re-validate the new FK against the tenant.
|
|
33
35
|
if (tenant) {
|
|
34
36
|
stripTenantColumn(updateFields, tenant.scope);
|
|
37
|
+
await enforceTenantFKOnUpdate(db, tenant, updateFields);
|
|
35
38
|
}
|
|
36
39
|
// Steps 7-9 are atomic: a failure in secondaries or deletions rolls back the main
|
|
37
40
|
// update too. Degrades to non-transactional when the adapter has no connect().
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/search/search.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EACZ,YAAY,EAcb,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/search/search.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EACZ,YAAY,EAcb,MAAM,mBAAmB,CAAC;AA4jC3B,wBAAsB,YAAY,CAChC,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,YAAY,CAAC,CA2IvB"}
|
|
@@ -171,6 +171,28 @@ function buildAggOrderExpr(db, dbTables, tableConf, alias, fn, field, joinGroup,
|
|
|
171
171
|
const expr = `COALESCE((SELECT ${aggExpr(aggFn, qualifiedCol)} FROM ${refs.joinTable} WHERE ${refs.joinTable}.${refs.fkCol} = ${refs.mainTable}.${refs.mainCol}${filterWhere}), 0)`;
|
|
172
172
|
return { expr, values: filterVals };
|
|
173
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Pre-scan `orderBy` for the joinLeft aliases referenced in 2-parti notation (`<alias>.<field>`).
|
|
176
|
+
* Needed BEFORE building the LEFT JOIN clauses (which must know their aliases) while the actual
|
|
177
|
+
* orderBy SQL — with its parameter placeholders — is only baked later, once the LEFT JOIN value
|
|
178
|
+
* count is known. Validates each alias against `allowedReadJoins` (throws 400), mirroring
|
|
179
|
+
* `validateOrderBy`; 3-parti aggregation entries are ignored (they use joinGroup, not a LEFT JOIN).
|
|
180
|
+
*/
|
|
181
|
+
function collectOrderByLeftAliases(orderBy, tableConf) {
|
|
182
|
+
const aliases = new Set();
|
|
183
|
+
for (const part of orderBy.split(',')) {
|
|
184
|
+
const trimmed = part.trim();
|
|
185
|
+
if (/^(\w+)\.(\w+)\.(\w+)(?:\s+(ASC|DESC))?$/i.test(trimmed))
|
|
186
|
+
continue; // 3-parti aggregation
|
|
187
|
+
const m = trimmed.match(/^(\w+)\.(\w+)(?:\s+(ASC|DESC))?$/i);
|
|
188
|
+
if (m) {
|
|
189
|
+
const alias = m[1];
|
|
190
|
+
requireJoin(tableConf, alias, true);
|
|
191
|
+
aliases.add(alias);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return aliases;
|
|
195
|
+
}
|
|
174
196
|
function validateOrderBy(orderBy, tableConf, db, dbTables, joinGroup, startIdx) {
|
|
175
197
|
const parts = orderBy.split(',');
|
|
176
198
|
const outParts = [];
|
|
@@ -281,12 +303,14 @@ function buildLeftJoinClauses(db, dbTables, tableConf, aliasesNeedingJoin, joinL
|
|
|
281
303
|
return { joinClauses, whereExtras, values };
|
|
282
304
|
}
|
|
283
305
|
// ─── Main query execution ───────────────────────────────────
|
|
284
|
-
async function executeMainQuery(db, tableConf, where, values, orderBy, paginator, extraJoins = [], selectComputed) {
|
|
306
|
+
async function executeMainQuery(db, tableConf, where, values, orderBy, paginator, extraJoins = [], selectComputed, maxRows) {
|
|
285
307
|
const tableName = tableConf.Schema.tableName;
|
|
286
308
|
const order = orderBy || tableConf.defaultOrder || primaryAsString(tableConf.primary);
|
|
309
|
+
// With a paginator, the page size governs the LIMIT. Without one, apply `maxRows` (if set) so an
|
|
310
|
+
// unbounded search cannot dump the whole table; a plain integer is a safe, non-injectable LIMIT.
|
|
287
311
|
const limit = paginator
|
|
288
312
|
? `${paginator.itemsPerPage} OFFSET ${(paginator.page - 1) * paginator.itemsPerPage}`
|
|
289
|
-
: null;
|
|
313
|
+
: (maxRows != null ? String(maxRows) : null);
|
|
290
314
|
// Optional computed projections — bound values, if any, are NOT yet supported
|
|
291
315
|
// here (would require placeholder-aware composition with WHERE values).
|
|
292
316
|
let columns;
|
|
@@ -794,27 +818,23 @@ export async function searchEngine(dbTables, params) {
|
|
|
794
818
|
({ where, values } = appendAggConditions(where, values, conditions, db, dbTables, tableConf, joinGroup));
|
|
795
819
|
}
|
|
796
820
|
}
|
|
797
|
-
// orderBy parsing — collects 2-parti aliases that need LEFT JOIN on the main query
|
|
798
|
-
let safeOrderBy;
|
|
799
|
-
let orderByValues = [];
|
|
800
|
-
let orderByLeftAliases = new Set();
|
|
801
|
-
if (orderBy) {
|
|
802
|
-
const obResult = validateOrderBy(orderBy, tableConf, db, dbTables, joinGroup, values.length + 1);
|
|
803
|
-
safeOrderBy = obResult.sql;
|
|
804
|
-
orderByValues = obResult.values;
|
|
805
|
-
orderByLeftAliases = obResult.leftJoinAliases;
|
|
806
|
-
}
|
|
807
821
|
// Determine which joinLeft aliases need a LEFT JOIN on the main query:
|
|
808
822
|
// - any alias used in 2-parti orderBy
|
|
809
823
|
// - any alias in joinLeft body that has filters or conditions on the parent
|
|
810
|
-
const aliasesNeedingLeftJoin = new Set(
|
|
824
|
+
const aliasesNeedingLeftJoin = new Set();
|
|
825
|
+
if (orderBy) {
|
|
826
|
+
for (const a of collectOrderByLeftAliases(orderBy, tableConf))
|
|
827
|
+
aliasesNeedingLeftJoin.add(a);
|
|
828
|
+
}
|
|
811
829
|
if (joinLeft) {
|
|
812
830
|
for (const [alias, ref] of Object.entries(joinLeft)) {
|
|
813
831
|
if (ref?.filters || ref?.conditions?.length)
|
|
814
832
|
aliasesNeedingLeftJoin.add(alias);
|
|
815
833
|
}
|
|
816
834
|
}
|
|
817
|
-
// Build LEFT JOIN clauses + extra WHERE for filtered parents
|
|
835
|
+
// Build LEFT JOIN clauses + extra WHERE for filtered parents. Their placeholders start right
|
|
836
|
+
// after the WHERE values; the same clause SQL is reused by the pagination COUNT/compute queries,
|
|
837
|
+
// whose value array is [...WHERE, ...leftJoin] — so this base MUST stay `values.length + 1`.
|
|
818
838
|
const extraJoinClauses = [...tenantJoins];
|
|
819
839
|
let leftJoinValues = [];
|
|
820
840
|
if (aliasesNeedingLeftJoin.size > 0) {
|
|
@@ -824,11 +844,21 @@ export async function searchEngine(dbTables, params) {
|
|
|
824
844
|
for (const w of lj.whereExtras)
|
|
825
845
|
where += ` AND ${w}`;
|
|
826
846
|
}
|
|
847
|
+
// orderBy is parsed AFTER the LEFT JOIN values so aggregation-orderBy placeholders start past
|
|
848
|
+
// them (main query binds [...WHERE, ...leftJoin, ...orderBy]). Parsing it earlier with base
|
|
849
|
+
// `values.length + 1` misbinds when BOTH an aggregation orderBy and a filtered joinLeft exist.
|
|
850
|
+
let safeOrderBy;
|
|
851
|
+
let orderByValues = [];
|
|
852
|
+
if (orderBy) {
|
|
853
|
+
const obResult = validateOrderBy(orderBy, tableConf, db, dbTables, joinGroup, values.length + leftJoinValues.length + 1);
|
|
854
|
+
safeOrderBy = obResult.sql;
|
|
855
|
+
orderByValues = obResult.values;
|
|
856
|
+
}
|
|
827
857
|
// Bind WHERE values + LEFT JOIN values + orderBy aggregation values for main query.
|
|
828
858
|
// Pagination COUNT/compute include WHERE + LEFT JOIN values (no orderBy).
|
|
829
859
|
const whereAndJoinValues = [...values, ...leftJoinValues];
|
|
830
860
|
const mainValues = [...whereAndJoinValues, ...orderByValues];
|
|
831
|
-
const main = await executeMainQuery(db, tableConf, where, mainValues, safeOrderBy, paginator, extraJoinClauses, params.selectComputed);
|
|
861
|
+
const main = await executeMainQuery(db, tableConf, where, mainValues, safeOrderBy, paginator, extraJoinClauses, params.selectComputed, params.maxRows);
|
|
832
862
|
let pagination;
|
|
833
863
|
if (paginator) {
|
|
834
864
|
pagination = await buildPagination(db, tableConf, where, whereAndJoinValues, paginator, extraJoinClauses, computeMin, computeMax, computeSum, computeAvg);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DbTables } from '../../types.js';
|
|
2
|
-
export declare function BulkDeleteTableBody(dbTables: DbTables, tableName: string): import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
|
|
2
|
+
export declare function BulkDeleteTableBody(dbTables: DbTables, tableName: string, maxItems?: number): import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
|
|
3
3
|
[x: string]: any;
|
|
4
4
|
}>>;
|
|
5
5
|
export declare function BulkDeleteTableResponse(dbTables: DbTables, tableName: string): import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bulk-delete.d.ts","sourceRoot":"","sources":["../../../src/lib/schema/bulk-delete.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM;;
|
|
1
|
+
{"version":3,"file":"bulk-delete.d.ts","sourceRoot":"","sources":["../../../src/lib/schema/bulk-delete.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;;IAS3F;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM;;;;IAU5E"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Type } from '@sinclair/typebox';
|
|
2
2
|
import { primaryAsString } from '../../types.js';
|
|
3
|
-
export function BulkDeleteTableBody(dbTables, tableName) {
|
|
3
|
+
export function BulkDeleteTableBody(dbTables, tableName, maxItems) {
|
|
4
4
|
const tableConf = dbTables[tableName];
|
|
5
5
|
const pkField = primaryAsString(tableConf.primary);
|
|
6
6
|
const pkType = tableConf.Schema.fields[pkField];
|
|
7
|
-
return Type.Array(Type.Object({ [pkField]: pkType }));
|
|
7
|
+
return Type.Array(Type.Object({ [pkField]: pkType }, { additionalProperties: false }), maxItems != null ? { maxItems } : {});
|
|
8
8
|
}
|
|
9
9
|
export function BulkDeleteTableResponse(dbTables, tableName) {
|
|
10
10
|
const tableConf = dbTables[tableName];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type TSchema } from '@sinclair/typebox';
|
|
2
2
|
import type { DbTables } from '../../types.js';
|
|
3
|
-
export declare function BulkUpsertTableBody(dbTables: DbTables, tableName: string): import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<Record<string, TSchema>>>;
|
|
3
|
+
export declare function BulkUpsertTableBody(dbTables: DbTables, tableName: string, maxItems?: number): import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<Record<string, TSchema>>>;
|
|
4
4
|
export declare function BulkUpsertTableResponse(dbTables: DbTables, tableName: string): import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<Record<string, TSchema>>>;
|
|
5
5
|
//# sourceMappingURL=bulk-upsert.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bulk-upsert.d.ts","sourceRoot":"","sources":["../../../src/lib/schema/bulk-upsert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAS/C,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"bulk-upsert.d.ts","sourceRoot":"","sources":["../../../src/lib/schema/bulk-upsert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAS/C,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,oGAoB3F;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,oGAa5E"}
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { Type } from '@sinclair/typebox';
|
|
2
2
|
import { primaryAsString } from '../../types.js';
|
|
3
3
|
import { pkSchema, applySchemaOverrides, attachWriteJoinSections, writeJoinBodyFields, writeJoinResponseFields, } from './helpers.js';
|
|
4
|
-
export function BulkUpsertTableBody(dbTables, tableName) {
|
|
4
|
+
export function BulkUpsertTableBody(dbTables, tableName, maxItems) {
|
|
5
5
|
const tableConf = dbTables[tableName];
|
|
6
6
|
const schema = tableConf.Schema;
|
|
7
|
-
|
|
7
|
+
// additionalProperties:false makes the schema the real write whitelist (no mass assignment).
|
|
8
|
+
const mainSchema = Type.Partial(Type.Object(applySchemaOverrides({ ...schema.fields }, tableConf), { additionalProperties: false }));
|
|
8
9
|
const itemProperties = {
|
|
9
10
|
main: mainSchema,
|
|
10
11
|
};
|
|
11
12
|
attachWriteJoinSections(itemProperties, tableConf, dbTables, {
|
|
12
13
|
withDeletions: true,
|
|
13
14
|
secondaryFields: writeJoinBodyFields,
|
|
15
|
+
strictItems: true,
|
|
14
16
|
});
|
|
15
|
-
return Type.Array(Type.Object(itemProperties));
|
|
17
|
+
return Type.Array(Type.Object(itemProperties), maxItems != null ? { maxItems } : {});
|
|
16
18
|
}
|
|
17
19
|
export function BulkUpsertTableResponse(dbTables, tableName) {
|
|
18
20
|
const tableConf = dbTables[tableName];
|
|
@@ -24,6 +24,7 @@ type JoinFieldsBuilder = (join: JoinDefinition, secondaryConf: ITable | undefine
|
|
|
24
24
|
*/
|
|
25
25
|
export declare function buildJoinAliasMap(tableConf: ITable, dbTables: DbTables, build: JoinFieldsBuilder, options?: {
|
|
26
26
|
itemsPartial?: boolean;
|
|
27
|
+
strictItems?: boolean;
|
|
27
28
|
}): Record<string, TSchema>;
|
|
28
29
|
/**
|
|
29
30
|
* Attach `secondaries` (and optionally `deletions`) sections to the given target object,
|
|
@@ -37,6 +38,7 @@ export declare function buildJoinAliasMap(tableConf: ITable, dbTables: DbTables,
|
|
|
37
38
|
export declare function attachWriteJoinSections(target: Record<string, TSchema>, tableConf: ITable, dbTables: DbTables, options: {
|
|
38
39
|
withDeletions: boolean;
|
|
39
40
|
secondaryFields: JoinFieldsBuilder;
|
|
41
|
+
strictItems?: boolean;
|
|
40
42
|
}): void;
|
|
41
43
|
/** JoinFieldsBuilder for body schemas: includes all join fields with FK + excluded made Optional. */
|
|
42
44
|
export declare const writeJoinBodyFields: JoinFieldsBuilder;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/lib/schema/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGzF;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQ3H;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,gBAAgB,EAC5B,SAAS,EAAE,MAAM,EACjB,kBAAkB,EAAE,MAAM,GAAG,SAAS,GACrC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgBzB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,SAAS,EAAE,MAAM,GAChB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASzB;AAED,KAAK,iBAAiB,GAAG,CACvB,IAAI,EAAE,cAAc,EACpB,aAAa,EAAE,MAAM,GAAG,SAAS,KAC9B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE7B;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,iBAAiB,EACxB,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAO,
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/lib/schema/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGzF;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQ3H;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,gBAAgB,EAC5B,SAAS,EAAE,MAAM,EACjB,kBAAkB,EAAE,MAAM,GAAG,SAAS,GACrC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgBzB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,SAAS,EAAE,MAAM,GAChB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASzB;AAED,KAAK,iBAAiB,GAAG,CACvB,IAAI,EAAE,cAAc,EACpB,aAAa,EAAE,MAAM,GAAG,SAAS,KAC9B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE7B;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,iBAAiB,EACxB,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GAC9D,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE;IAAE,aAAa,EAAE,OAAO,CAAC;IAAC,eAAe,EAAE,iBAAiB,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,GAC7F,IAAI,CAmBN;AAED,qGAAqG;AACrG,eAAO,MAAM,mBAAmB,EAAE,iBACmB,CAAC;AAEtD,uDAAuD;AACvD,eAAO,MAAM,uBAAuB,EAAE,iBACG,CAAC"}
|
|
@@ -56,9 +56,10 @@ export function buildJoinAliasMap(tableConf, dbTables, build, options = {}) {
|
|
|
56
56
|
const out = {};
|
|
57
57
|
if (!tableConf.allowedWriteJoins?.length)
|
|
58
58
|
return out;
|
|
59
|
+
const objOpts = options.strictItems ? { additionalProperties: false } : {};
|
|
59
60
|
for (const j of tableConf.allowedWriteJoins) {
|
|
60
61
|
const sc = findSecondaryTableConf(dbTables, j.joinSchema.tableName);
|
|
61
|
-
const item = Type.Object(build(j, sc));
|
|
62
|
+
const item = Type.Object(build(j, sc), objOpts);
|
|
62
63
|
out[j.alias] = Type.Array(options.itemsPartial ? Type.Partial(item) : item);
|
|
63
64
|
}
|
|
64
65
|
return out;
|
|
@@ -75,7 +76,7 @@ export function buildJoinAliasMap(tableConf, dbTables, build, options = {}) {
|
|
|
75
76
|
export function attachWriteJoinSections(target, tableConf, dbTables, options) {
|
|
76
77
|
if (!tableConf.allowedWriteJoins?.length)
|
|
77
78
|
return;
|
|
78
|
-
target.secondaries = Type.Optional(Type.Partial(Type.Object(buildJoinAliasMap(tableConf, dbTables, options.secondaryFields))));
|
|
79
|
+
target.secondaries = Type.Optional(Type.Partial(Type.Object(buildJoinAliasMap(tableConf, dbTables, options.secondaryFields, { strictItems: options.strictItems }))));
|
|
79
80
|
if (options.withDeletions) {
|
|
80
81
|
target.deletions = Type.Optional(Type.Partial(Type.Object(buildJoinAliasMap(tableConf, dbTables, ({ joinSchema }) => joinSchema.fields, { itemsPartial: true }))));
|
|
81
82
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"insert.d.ts","sourceRoot":"","sources":["../../../src/lib/schema/insert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,OAAO,EAAgB,MAAM,mBAAmB,CAAC;AAErE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAS/C,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"insert.d.ts","sourceRoot":"","sources":["../../../src/lib/schema/insert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,OAAO,EAAgB,MAAM,mBAAmB,CAAC;AAErE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAS/C,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAyB9E;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAalF"}
|
|
@@ -12,11 +12,14 @@ export function InsertTableBody(dbTables, tableName) {
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
const bodyProperties = {
|
|
15
|
-
|
|
15
|
+
// additionalProperties:false makes the schema the real write whitelist — unknown keys are
|
|
16
|
+
// rejected (400) instead of silently reaching the INSERT as columns (mass assignment).
|
|
17
|
+
main: Type.Object(mainFields, { additionalProperties: false }),
|
|
16
18
|
};
|
|
17
19
|
attachWriteJoinSections(bodyProperties, tableConf, dbTables, {
|
|
18
20
|
withDeletions: false,
|
|
19
21
|
secondaryFields: writeJoinBodyFields,
|
|
22
|
+
strictItems: true,
|
|
20
23
|
});
|
|
21
24
|
return Type.Object(bodyProperties);
|
|
22
25
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../../src/lib/schema/update.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,OAAO,EAAgB,MAAM,mBAAmB,CAAC;AAErE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAS/C,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../../src/lib/schema/update.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,OAAO,EAAgB,MAAM,mBAAmB,CAAC;AAErE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAS/C,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAwB9E;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAalF"}
|
|
@@ -11,11 +11,14 @@ export function UpdateTableBody(dbTables, tableName) {
|
|
|
11
11
|
mainFields[key] = key === primaryAsString(tableConf.primary) ? value : Type.Optional(value);
|
|
12
12
|
}
|
|
13
13
|
const bodyProperties = {
|
|
14
|
-
|
|
14
|
+
// additionalProperties:false makes the schema the real write whitelist — unknown keys are
|
|
15
|
+
// rejected (400) instead of silently reaching the UPDATE as columns (mass assignment).
|
|
16
|
+
main: Type.Object(mainFields, { additionalProperties: false }),
|
|
15
17
|
};
|
|
16
18
|
attachWriteJoinSections(bodyProperties, tableConf, dbTables, {
|
|
17
19
|
withDeletions: true,
|
|
18
20
|
secondaryFields: writeJoinBodyFields,
|
|
21
|
+
strictItems: true,
|
|
19
22
|
});
|
|
20
23
|
return Type.Object(bodyProperties);
|
|
21
24
|
}
|
package/dist/lib/sql-api.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export interface SqlApiSearchParams {
|
|
|
16
16
|
computeSum?: string;
|
|
17
17
|
computeAvg?: string;
|
|
18
18
|
selectComputed?: string[];
|
|
19
|
+
/** Row cap for the main query when no paginator is provided (see SearchParams.maxRows). */
|
|
20
|
+
maxRows?: number;
|
|
19
21
|
}
|
|
20
22
|
export interface SqlApiInsertParams {
|
|
21
23
|
record: Record<string, unknown>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sql-api.d.ts","sourceRoot":"","sources":["../../src/lib/sql-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,OAAO,EAAqB,WAAW,EAAE,MAAM,SAAS,CAAC;AACzD,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAU5D,OAAO,KAAK,EACV,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,eAAe,EACf,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,QAAQ,EAGT,MAAM,aAAa,CAAC;AAIrB,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC9C,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"sql-api.d.ts","sourceRoot":"","sources":["../../src/lib/sql-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,OAAO,EAAqB,WAAW,EAAE,MAAM,SAAS,CAAC;AACzD,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAU5D,OAAO,KAAK,EACV,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,eAAe,EACf,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,QAAQ,EAGT,MAAM,aAAa,CAAC;AAIrB,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC9C,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,2FAA2F;IAC3F,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;CACvD;AAID,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,QAAQ,GAAG,QAAQ,EAAE,GAAG,IAAI,GAAG,SAAS,GAC/E,OAAO,CAAC,QAAQ,GAAG,QAAQ,EAAE,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CACvD;AAED,qBAAa,MAAM;IAKf,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,OAAO;IALjB,OAAO,CAAC,EAAE,CAAc;gBAGtB,QAAQ,EAAE,WAAW,GAAG,SAAS,EACzB,QAAQ,EAAE,QAAQ,EAClB,OAAO,GAAE,aAAkB;IAgBrC,OAAO,CAAC,YAAY;YAMN,SAAS;IAUjB,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,GAAE,kBAAuB,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC;IAwB3G,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,SAAS,CAAC;IAMzF,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC;IActG,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC;IAetG,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC;IAM/F,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAa7G,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;CAKrH;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,WAAW,GAAG,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,CAEnH"}
|
package/dist/lib/sql-api.js
CHANGED
package/dist/lib/tenant.d.ts
CHANGED
|
@@ -15,6 +15,28 @@ export declare function buildTenantDeleteWhere(db: QueryClient, tableName: strin
|
|
|
15
15
|
where: string;
|
|
16
16
|
values: unknown[];
|
|
17
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* Guard the conflict target of an UPSERT (INSERT … ON CONFLICT / ON DUPLICATE KEY) on a
|
|
20
|
+
* tenant-scoped table. Without this, an upsert whose conflict key matches a row owned by
|
|
21
|
+
* ANOTHER tenant would silently UPDATE (and, for direct scopes, re-assign) that row, because
|
|
22
|
+
* the conflict is matched by the unique key alone — the tenant is never part of the ON CONFLICT
|
|
23
|
+
* predicate. This pre-checks that no existing row matching the incoming conflict keys belongs to
|
|
24
|
+
* a different tenant, throwing 403 if one does. No-op when `tenant` is undefined.
|
|
25
|
+
*
|
|
26
|
+
* `conflictCols` and the keys of each record are DB column names (snake_case). Records missing
|
|
27
|
+
* any conflict column are skipped (they can only INSERT, never hit a conflict).
|
|
28
|
+
*/
|
|
29
|
+
export declare function assertTenantOwnsConflicts(db: QueryClient, tenant: TenantContext | undefined, tableName: string, conflictCols: string[], records: Record<string, unknown>[]): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* For an UPDATE on an indirect-tenant table: the record's ownership is verified against its
|
|
32
|
+
* CURRENT through-FK (`assertTenantOwnership`), but if the payload also changes that FK
|
|
33
|
+
* (`through.localField`), the NEW value must be re-validated — otherwise a caller could move
|
|
34
|
+
* their own record into another tenant's scope. Validates the new FK value against the tenant,
|
|
35
|
+
* throwing 403 on mismatch. No-op for direct/no-tenant or when the FK is not being changed.
|
|
36
|
+
*
|
|
37
|
+
* `updateFields` keys are DB column names (snake_case), matching `through.localField`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function enforceTenantFKOnUpdate(db: QueryClient, tenant: TenantContext | undefined, updateFields: Record<string, unknown>): Promise<void>;
|
|
18
40
|
/**
|
|
19
41
|
* Mutates `fields` in-place: removes the tenant column from a SET payload (direct scopes only).
|
|
20
42
|
* No-op for indirect or no scope.
|
package/dist/lib/tenant.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenant.d.ts","sourceRoot":"","sources":["../../src/lib/tenant.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,KAAK,cAAc,EAAE,KAAK,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAE/G,OAAO,KAAK,EACV,MAAM,EACN,mBAAmB,EACnB,QAAQ,EACR,WAAW,EACX,mBAAmB,EACnB,aAAa,EACd,MAAM,aAAa,CAAC;AAMrB,wBAAsB,aAAa,CACjC,OAAO,EAAE,mBAAmB,EAC5B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CASpC;AAED,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,WAAW,EACf,KAAK,EAAE,WAAW,EAClB,SAAS,EAAE,QAAQ,EAAE,GACpB,gBAAgB,CAclB;AAED,wBAAgB,eAAe,CAC7B,EAAE,EAAE,WAAW,EACf,KAAK,EAAE,mBAAmB,EAC1B,aAAa,EAAE,MAAM,GACpB,MAAM,CAMR;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,KAAK,EAAE,WAAW,EAClB,SAAS,EAAE,QAAQ,EAAE,GACpB,IAAI,CAiBN;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,WAAW,EACf,KAAK,EAAE,mBAAmB,EAC1B,SAAS,EAAE,QAAQ,EAAE,EACrB,QAAQ,EAAE,OAAO,EAAE,GAClB,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,WAAW,EACf,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,yBAAyB,GAAG,cAAc,EAAE,EACrD,MAAM,EAAE,aAAa,GACpB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,EAAE,CAAA;CAAE,CAsBtC;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,KAAK,EAAE,WAAW,GACjB,IAAI,CAGN;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,EAAE,EAAE,WAAW,EACf,MAAM,EAAE,aAAa,GAAG,SAAS,EACjC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GACjC,OAAO,CAAC,IAAI,CAAC,CAQf;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,WAAW,EACf,MAAM,EAAE,aAAa,GAAG,SAAS,GAChC,gBAAgB,GAAG,SAAS,CAM9B;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,EAAE,EAAE,WAAW,EACf,MAAM,EAAE,aAAa,GAAG,SAAS,EACjC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EACxB,OAAO,EAAE,cAAc,GAAG,cAAc,EAAE,GACzC,OAAO,CAAC,IAAI,CAAC,CAYf;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,WAAW,EACf,MAAM,EAAE,aAAa,GAAG,SAAS,EACjC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,cAAc,EAAE,GACpB,OAAO,CAAC,IAAI,CAAC,CAgBf"}
|
|
1
|
+
{"version":3,"file":"tenant.d.ts","sourceRoot":"","sources":["../../src/lib/tenant.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/tenant.js
CHANGED
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bulk-delete.routes.d.ts","sourceRoot":"","sources":["../../../src/routes/auto/bulk-delete.routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"bulk-delete.routes.d.ts","sourceRoot":"","sources":["../../../src/routes/auto/bulk-delete.routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAK/C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1D,wBAA8B,gBAAgB,CAC5C,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAkBf"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BulkDeleteTableBody, BulkDeleteTableResponse } from '../../lib/schema/bulk-delete.js';
|
|
2
2
|
import { primaryAsString } from '../../types.js';
|
|
3
3
|
import { registerForAllTables } from './route-helpers.js';
|
|
4
|
+
import { DEFAULT_MAX_BULK_ITEMS } from '../../types.js';
|
|
4
5
|
export default async function bulkDeleteRoutes(fastify, options) {
|
|
5
6
|
await registerForAllTables(fastify, options, {
|
|
6
7
|
operation: 'bulkDelete',
|
|
@@ -8,7 +9,7 @@ export default async function bulkDeleteRoutes(fastify, options) {
|
|
|
8
9
|
url: (tc) => `/bulk/${tc.Schema.tableName}/delete`,
|
|
9
10
|
successStatus: 200,
|
|
10
11
|
schemas: (db, table) => ({
|
|
11
|
-
body: BulkDeleteTableBody(db, table),
|
|
12
|
+
body: BulkDeleteTableBody(db, table, options.maxBulkItems ?? DEFAULT_MAX_BULK_ITEMS),
|
|
12
13
|
response: BulkDeleteTableResponse(db, table),
|
|
13
14
|
}),
|
|
14
15
|
summary: 'Bulk delete',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bulk-upsert.routes.d.ts","sourceRoot":"","sources":["../../../src/routes/auto/bulk-upsert.routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"bulk-upsert.routes.d.ts","sourceRoot":"","sources":["../../../src/routes/auto/bulk-upsert.routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAI/C,OAAO,KAAK,EAAE,mBAAmB,EAAkB,MAAM,gBAAgB,CAAC;AAE1E,wBAA8B,gBAAgB,CAC5C,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAiBf"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BulkUpsertTableBody, BulkUpsertTableResponse } from '../../lib/schema/bulk-upsert.js';
|
|
2
2
|
import { registerForAllTables, buildWriteDescription } from './route-helpers.js';
|
|
3
|
+
import { DEFAULT_MAX_BULK_ITEMS } from '../../types.js';
|
|
3
4
|
export default async function bulkUpsertRoutes(fastify, options) {
|
|
4
5
|
await registerForAllTables(fastify, options, {
|
|
5
6
|
operation: 'bulkUpsert',
|
|
@@ -7,7 +8,7 @@ export default async function bulkUpsertRoutes(fastify, options) {
|
|
|
7
8
|
url: (tc) => `/bulk/${tc.Schema.tableName}`,
|
|
8
9
|
successStatus: 200,
|
|
9
10
|
schemas: (db, table) => ({
|
|
10
|
-
body: BulkUpsertTableBody(db, table),
|
|
11
|
+
body: BulkUpsertTableBody(db, table, options.maxBulkItems ?? DEFAULT_MAX_BULK_ITEMS),
|
|
11
12
|
response: BulkUpsertTableResponse(db, table),
|
|
12
13
|
}),
|
|
13
14
|
summary: 'Bulk upsert',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.routes.d.ts","sourceRoot":"","sources":["../../../src/routes/auto/search.routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"search.routes.d.ts","sourceRoot":"","sources":["../../../src/routes/auto/search.routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAK/C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1D,wBAA8B,YAAY,CACxC,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAyDf"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { SearchTableBodyPost, SearchTableQueryString, SearchTableResponse } from '../../lib/schema/search.js';
|
|
2
2
|
import { registerForAllTables } from './route-helpers.js';
|
|
3
|
+
import { httpError } from '../../lib/errors.js';
|
|
4
|
+
import { DEFAULT_MAX_ITEMS_PER_PAGE } from '../../types.js';
|
|
3
5
|
export default async function searchRoutes(fastify, options) {
|
|
4
6
|
await registerForAllTables(fastify, options, {
|
|
5
7
|
operation: 'search',
|
|
@@ -28,6 +30,11 @@ export default async function searchRoutes(fastify, options) {
|
|
|
28
30
|
const body = request.body;
|
|
29
31
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
30
32
|
const query = request.query;
|
|
33
|
+
// DoS guard: bound the page size and the no-paginator LIMIT.
|
|
34
|
+
const maxRows = options.maxItemsPerPage ?? DEFAULT_MAX_ITEMS_PER_PAGE;
|
|
35
|
+
if (query.itemsPerPage != null && query.itemsPerPage > maxRows) {
|
|
36
|
+
throw httpError(400, `itemsPerPage exceeds the maximum of ${maxRows}`);
|
|
37
|
+
}
|
|
31
38
|
const result = await fastify.sqlApi.search(tableName, {
|
|
32
39
|
filters: body.filters,
|
|
33
40
|
conditions: body.conditions,
|
|
@@ -44,6 +51,7 @@ export default async function searchRoutes(fastify, options) {
|
|
|
44
51
|
computeMax: query.computeMax,
|
|
45
52
|
computeSum: query.computeSum,
|
|
46
53
|
computeAvg: query.computeAvg,
|
|
54
|
+
maxRows,
|
|
47
55
|
}, request);
|
|
48
56
|
return { table: tc.Schema.tableName, ...result };
|
|
49
57
|
},
|
package/dist/types/plugin.d.ts
CHANGED
|
@@ -16,5 +16,21 @@ export interface SqlApiPluginOptions {
|
|
|
16
16
|
dialect?: DialectName;
|
|
17
17
|
getTenantId?: (request: FastifyRequest) => TenantId | TenantId[] | null | undefined | Promise<TenantId | TenantId[] | null | undefined>;
|
|
18
18
|
debug?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Hard cap on the search page size (the `itemsPerPage` query param), and the row LIMIT applied
|
|
21
|
+
* even when no paginator is supplied — so an unbounded `POST /search/:table` cannot dump an
|
|
22
|
+
* entire table. Requests exceeding it get a 400. Defaults to {@link DEFAULT_MAX_ITEMS_PER_PAGE}.
|
|
23
|
+
*/
|
|
24
|
+
maxItemsPerPage?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Hard cap on the number of items accepted by the bulk endpoints (`PUT /bulk/:table`,
|
|
27
|
+
* `POST /bulk/:table/delete`). Larger arrays are rejected at schema validation (400).
|
|
28
|
+
* Defaults to {@link DEFAULT_MAX_BULK_ITEMS}.
|
|
29
|
+
*/
|
|
30
|
+
maxBulkItems?: number;
|
|
19
31
|
}
|
|
32
|
+
/** Default row cap for search (page size and no-paginator LIMIT). Override via `maxItemsPerPage`. */
|
|
33
|
+
export declare const DEFAULT_MAX_ITEMS_PER_PAGE = 1000;
|
|
34
|
+
/** Default cap on bulk endpoint array length. Override via `maxBulkItems`. */
|
|
35
|
+
export declare const DEFAULT_MAX_BULK_ITEMS = 1000;
|
|
20
36
|
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/types/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,QAAQ,CAAC;IACnB,UAAU,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,GAAG,YAAY,CAAC,CAAC,EAAE,CAAC;IAChG,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,GAAG,cAAc,CAAC;IACnC,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,QAAQ,GAAG,QAAQ,EAAE,GAAG,IAAI,GAAG,SAAS,GAC/E,OAAO,CAAC,QAAQ,GAAG,QAAQ,EAAE,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACtD,KAAK,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/types/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,QAAQ,CAAC;IACnB,UAAU,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,GAAG,YAAY,CAAC,CAAC,EAAE,CAAC;IAChG,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,GAAG,cAAc,CAAC;IACnC,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,QAAQ,GAAG,QAAQ,EAAE,GAAG,IAAI,GAAG,SAAS,GAC/E,OAAO,CAAC,QAAQ,GAAG,QAAQ,EAAE,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACtD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,qGAAqG;AACrG,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAE/C,8EAA8E;AAC9E,eAAO,MAAM,sBAAsB,OAAO,CAAC"}
|
package/dist/types/plugin.js
CHANGED
|
@@ -1 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
/** Default row cap for search (page size and no-paginator LIMIT). Override via `maxItemsPerPage`. */
|
|
2
|
+
export const DEFAULT_MAX_ITEMS_PER_PAGE = 1000;
|
|
3
|
+
/** Default cap on bulk endpoint array length. Override via `maxBulkItems`. */
|
|
4
|
+
export const DEFAULT_MAX_BULK_ITEMS = 1000;
|
package/dist/types/search.d.ts
CHANGED
|
@@ -28,6 +28,13 @@ export interface SearchParams {
|
|
|
28
28
|
*/
|
|
29
29
|
selectComputed?: string[];
|
|
30
30
|
tenant?: TenantContext;
|
|
31
|
+
/**
|
|
32
|
+
* Row cap applied to the main query when no `paginator` is supplied, bounding an otherwise
|
|
33
|
+
* unbounded "fetch everything" search. Ignored when a paginator is present (the page size
|
|
34
|
+
* governs the LIMIT). Left undefined by programmatic callers (trusted); the HTTP search route
|
|
35
|
+
* sets it from `maxItemsPerPage`.
|
|
36
|
+
*/
|
|
37
|
+
maxRows?: number;
|
|
31
38
|
}
|
|
32
39
|
export interface PaginationResult {
|
|
33
40
|
total: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/types/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACnF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,WAAW,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC9C,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,MAAM,CAAC,EAAE,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/types/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACnF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,WAAW,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAC9C,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACpD,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fastify-auto-sqlapi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Fastify plugin for automatic CRUD API generation from PostgreSQL, MySQL and MariaDB tables",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"@fastify/postgres": ">=5.0.0",
|
|
83
83
|
"@fastify/swagger": ">=9.0.0",
|
|
84
84
|
"@fastify/swagger-ui": ">=5.0.0",
|
|
85
|
-
"fastify": "5.
|
|
85
|
+
"fastify": ">=5.0.0",
|
|
86
86
|
"mysql2": ">=3.0.0",
|
|
87
87
|
"pg": ">=8.0.0"
|
|
88
88
|
},
|