fastify-auto-sqlapi 0.0.4 → 0.1.1
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.md +15 -2
- package/AGENTS_BACKEND.md +160 -32
- package/AGENTS_FRONTEND.md +220 -49
- package/README.md +147 -27
- package/dist/bin/generate-schema.js +12 -29
- package/dist/bin/generate-tables.js +10 -33
- package/dist/bin/utils.d.ts +29 -1
- package/dist/bin/utils.d.ts.map +1 -1
- package/dist/bin/utils.js +71 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/cli/load-dependency.d.ts +8 -0
- package/dist/lib/cli/load-dependency.d.ts.map +1 -0
- package/dist/lib/cli/load-dependency.js +21 -0
- package/dist/lib/cli/mysql-introspect.d.ts.map +1 -1
- package/dist/lib/cli/mysql-introspect.js +2 -11
- package/dist/lib/cli/pg-introspect.d.ts.map +1 -1
- package/dist/lib/cli/pg-introspect.js +2 -17
- package/dist/lib/cli/schema-codegen.d.ts +1 -1
- package/dist/lib/cli/schema-codegen.d.ts.map +1 -1
- package/dist/lib/cli/schema-codegen.js +11 -4
- package/dist/lib/cli/tables-codegen.d.ts.map +1 -1
- package/dist/lib/cli/tables-codegen.js +4 -3
- package/dist/lib/db.d.ts +1 -0
- package/dist/lib/db.d.ts.map +1 -1
- package/dist/lib/db.js +33 -49
- package/dist/lib/dialect.d.ts +17 -0
- package/dist/lib/dialect.d.ts.map +1 -1
- package/dist/lib/dialect.js +32 -0
- package/dist/lib/engine/bulk/bulk-upsert.d.ts.map +1 -1
- package/dist/lib/engine/bulk/bulk-upsert.js +26 -50
- package/dist/lib/engine/rest/delete.d.ts.map +1 -1
- package/dist/lib/engine/rest/delete.js +5 -10
- package/dist/lib/engine/rest/get.d.ts.map +1 -1
- package/dist/lib/engine/rest/get.js +4 -6
- package/dist/lib/engine/rest/insert.d.ts.map +1 -1
- package/dist/lib/engine/rest/insert.js +20 -39
- package/dist/lib/engine/rest/update.d.ts.map +1 -1
- package/dist/lib/engine/rest/update.js +35 -67
- package/dist/lib/engine/search/search.d.ts.map +1 -1
- package/dist/lib/engine/search/search.js +650 -130
- package/dist/lib/engine/write-helpers.d.ts +39 -2
- package/dist/lib/engine/write-helpers.d.ts.map +1 -1
- package/dist/lib/engine/write-helpers.js +58 -15
- package/dist/lib/errors.d.ts +11 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +12 -0
- package/dist/lib/naming.d.ts +11 -2
- package/dist/lib/naming.d.ts.map +1 -1
- package/dist/lib/naming.js +33 -5
- package/dist/lib/schema/bulk-upsert.d.ts.map +1 -1
- package/dist/lib/schema/bulk-upsert.js +9 -25
- package/dist/lib/schema/helpers.d.ts +28 -1
- package/dist/lib/schema/helpers.d.ts.map +1 -1
- package/dist/lib/schema/helpers.js +38 -0
- package/dist/lib/schema/insert.d.ts.map +1 -1
- package/dist/lib/schema/insert.js +9 -19
- package/dist/lib/schema/search.d.ts.map +1 -1
- package/dist/lib/schema/search.js +147 -53
- package/dist/lib/schema/update.d.ts.map +1 -1
- package/dist/lib/schema/update.js +9 -25
- package/dist/lib/sql-api.d.ts +6 -6
- package/dist/lib/sql-api.d.ts.map +1 -1
- package/dist/lib/sql-api.js +8 -8
- package/dist/lib/table-helpers.d.ts +6 -1
- package/dist/lib/table-helpers.d.ts.map +1 -1
- package/dist/lib/table-helpers.js +51 -3
- package/dist/lib/tenant.d.ts +23 -0
- package/dist/lib/tenant.d.ts.map +1 -1
- package/dist/lib/tenant.js +57 -9
- package/dist/routes/auto/bulk-delete.routes.d.ts.map +1 -1
- package/dist/routes/auto/bulk-delete.routes.js +17 -23
- package/dist/routes/auto/bulk-upsert.routes.d.ts.map +1 -1
- package/dist/routes/auto/bulk-upsert.routes.js +16 -22
- package/dist/routes/auto/delete.routes.d.ts.map +1 -1
- package/dist/routes/auto/delete.routes.js +18 -24
- package/dist/routes/auto/get.routes.d.ts.map +1 -1
- package/dist/routes/auto/get.routes.js +16 -23
- package/dist/routes/auto/insert.routes.d.ts.map +1 -1
- package/dist/routes/auto/insert.routes.js +19 -26
- package/dist/routes/auto/route-helpers.d.ts +27 -2
- package/dist/routes/auto/route-helpers.d.ts.map +1 -1
- package/dist/routes/auto/route-helpers.js +39 -1
- package/dist/routes/auto/search.routes.d.ts.map +1 -1
- package/dist/routes/auto/search.routes.js +47 -47
- package/dist/routes/auto/update.routes.d.ts.map +1 -1
- package/dist/routes/auto/update.routes.js +20 -27
- package/dist/types/cli.d.ts +22 -0
- package/dist/types/cli.d.ts.map +1 -0
- package/dist/types/cli.js +1 -0
- package/dist/types/computed.d.ts +29 -0
- package/dist/types/computed.d.ts.map +1 -0
- package/dist/types/computed.js +1 -0
- package/dist/types/conditions.d.ts +16 -0
- package/dist/types/conditions.d.ts.map +1 -0
- package/dist/types/conditions.js +1 -0
- package/dist/types/db.d.ts +22 -0
- package/dist/types/db.d.ts.map +1 -0
- package/dist/types/db.js +1 -0
- package/dist/types/dml.d.ts +64 -0
- package/dist/types/dml.d.ts.map +1 -0
- package/dist/types/dml.js +1 -0
- package/dist/types/join.d.ts +44 -0
- package/dist/types/join.d.ts.map +1 -0
- package/dist/types/join.js +1 -0
- package/dist/types/plugin.d.ts +20 -0
- package/dist/types/plugin.d.ts.map +1 -0
- package/dist/types/plugin.js +1 -0
- package/dist/types/schema.d.ts +10 -0
- package/dist/types/schema.d.ts.map +1 -0
- package/dist/types/schema.js +1 -0
- package/dist/types/search.d.ts +45 -0
- package/dist/types/search.d.ts.map +1 -0
- package/dist/types/search.js +1 -0
- package/dist/types/table.d.ts +52 -0
- package/dist/types/table.d.ts.map +1 -0
- package/dist/types/table.js +7 -0
- package/dist/types/tenant.d.ts +19 -0
- package/dist/types/tenant.d.ts.map +1 -0
- package/dist/types/tenant.js +1 -0
- package/dist/types/validation.d.ts +21 -0
- package/dist/types/validation.d.ts.map +1 -0
- package/dist/types/validation.js +1 -0
- package/dist/types.d.ts +12 -258
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +12 -7
- package/package.json +8 -5
|
@@ -1,58 +1,39 @@
|
|
|
1
|
-
import { camelcaseObject
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { runValidation } from '../validate.js';
|
|
1
|
+
import { camelcaseObject } from '../../naming.js';
|
|
2
|
+
import { processSecondaries, prepareInsertRecord } from '../write-helpers.js';
|
|
3
|
+
import { enforceTenantOnWrites } from '../../tenant.js';
|
|
5
4
|
import { primaryAsString } from '../../../types.js';
|
|
6
5
|
export async function insertEngine(params) {
|
|
7
6
|
const { db, tableConf, dbTables, request, record, secondaries, tenant } = params;
|
|
7
|
+
const schema = tableConf.Schema;
|
|
8
8
|
const pk = primaryAsString(tableConf.primary);
|
|
9
|
-
const pkCol =
|
|
10
|
-
// 1.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if ('through' in tenant.scope) {
|
|
16
|
-
// Indirect: validate FK belongs to tenant
|
|
17
|
-
const scope = tenant.scope;
|
|
18
|
-
const fkCol = scope.through.localField;
|
|
19
|
-
const fkValue = mainRecord[fkCol];
|
|
20
|
-
await validateTenantFK(db, scope, tenant.ids, [fkValue]);
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
injectTenantValue(mainRecord, tenant.scope, tenant.ids);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
// 2. Custom validation (receives original camelCase record)
|
|
27
|
-
await runValidation(db, request, tableConf, record, secondaries);
|
|
28
|
-
// 3. beforeInsert hook
|
|
29
|
-
if (tableConf.beforeInsert) {
|
|
30
|
-
await tableConf.beforeInsert(db, request, mainRecord);
|
|
31
|
-
}
|
|
32
|
-
// 4. Insert main → returns PK-only
|
|
9
|
+
const pkCol = schema.col(pk);
|
|
10
|
+
// 1. Validate, run beforeInsert hook, snakecase, drop excluded fields.
|
|
11
|
+
const { camel: inputRecord, snake: mainRecord } = await prepareInsertRecord({ db, tableConf, request }, record, secondaries);
|
|
12
|
+
// 2. Tenant: inject (direct) or validate FK (indirect) — enforced after user mutations
|
|
13
|
+
await enforceTenantOnWrites(db, tenant, [mainRecord]);
|
|
14
|
+
// 3. Insert main → returns PK-only
|
|
33
15
|
let pkResult;
|
|
34
|
-
const upsertKeys = tableConf.upsertMap?.get(
|
|
16
|
+
const upsertKeys = tableConf.upsertMap?.get(schema);
|
|
35
17
|
if (upsertKeys) {
|
|
36
|
-
const conflictCols = upsertKeys.map((k) =>
|
|
37
|
-
pkResult = await db.insertOrUpdate(
|
|
18
|
+
const conflictCols = upsertKeys.map((k) => schema.col(k));
|
|
19
|
+
pkResult = await db.insertOrUpdate(schema.tableName, mainRecord, conflictCols, pkCol);
|
|
38
20
|
}
|
|
39
21
|
else {
|
|
40
|
-
pkResult = await db.insert(
|
|
22
|
+
pkResult = await db.insert(schema.tableName, mainRecord, pkCol);
|
|
41
23
|
}
|
|
42
|
-
const mainPkCamel = camelcaseObject(pkResult);
|
|
43
|
-
//
|
|
24
|
+
const mainPkCamel = camelcaseObject(pkResult, schema);
|
|
25
|
+
// 4. Secondaries: need full record for FK auto-fill (camelCase)
|
|
44
26
|
let secondaryResults;
|
|
45
27
|
if (secondaries && Object.keys(secondaries).length > 0) {
|
|
46
|
-
|
|
47
|
-
const mainForFK = { ...camelcaseObject(mainRecord), ...mainPkCamel };
|
|
28
|
+
const mainForFK = { ...inputRecord, ...mainPkCamel };
|
|
48
29
|
secondaryResults = await processSecondaries(db, tableConf, dbTables, mainForFK, secondaries);
|
|
49
30
|
}
|
|
50
|
-
//
|
|
31
|
+
// 5. afterInsert hook (camelCase — input merged with generated PK)
|
|
51
32
|
if (tableConf.afterInsert) {
|
|
52
|
-
const mainForHook = { ...
|
|
33
|
+
const mainForHook = { ...inputRecord, ...mainPkCamel };
|
|
53
34
|
await tableConf.afterInsert(db, request, mainForHook, secondaryResults);
|
|
54
35
|
}
|
|
55
|
-
//
|
|
36
|
+
// 6. Return PK-only
|
|
56
37
|
const result = { main: mainPkCamel };
|
|
57
38
|
if (secondaryResults && Object.keys(secondaryResults).length > 0) {
|
|
58
39
|
result.secondaries = secondaryResults;
|
|
@@ -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,CA+F9E"}
|
|
@@ -1,107 +1,75 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { snakecaseRecord } from '../../naming.js';
|
|
2
2
|
import { processSecondaries, processDeletions } from '../write-helpers.js';
|
|
3
|
-
import { stripTenantColumn,
|
|
3
|
+
import { stripTenantColumn, assertTenantOwnership, buildTenantUpdateExtra, } from '../../tenant.js';
|
|
4
4
|
import { runValidation } from '../validate.js';
|
|
5
|
-
import {
|
|
5
|
+
import { httpError } from '../../errors.js';
|
|
6
6
|
import { primaryAsString } from '../../../types.js';
|
|
7
7
|
export async function updateEngine(params) {
|
|
8
8
|
const { db, tableConf, dbTables, request, record, secondaries, deletions, tenant } = params;
|
|
9
|
-
|
|
9
|
+
const schema = tableConf.Schema;
|
|
10
10
|
const pk = primaryAsString(tableConf.primary);
|
|
11
|
-
const pkCol =
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
const pkCol = schema.col(pk);
|
|
12
|
+
const pkValue = record[pk];
|
|
13
|
+
if (pkValue == null)
|
|
14
|
+
throw httpError(400, `Primary key "${pk}" is required`);
|
|
15
|
+
// 1. Tenant ownership check (indirect) — do it early so user mutations don't run on records they can't access
|
|
16
|
+
await assertTenantOwnership(db, tenant, schema.tableName, pkCol, pkValue);
|
|
17
|
+
// 2. Mutable copy of input in camelCase
|
|
18
|
+
const inputRecord = { ...record };
|
|
19
|
+
// 3. Custom validation (camelCase)
|
|
20
|
+
await runValidation(db, request, tableConf, inputRecord, secondaries);
|
|
21
|
+
// 4. beforeUpdate hook (camelCase — user can mutate with schema field names)
|
|
22
|
+
if (tableConf.beforeUpdate) {
|
|
23
|
+
await tableConf.beforeUpdate(db, request, inputRecord);
|
|
18
24
|
}
|
|
25
|
+
// 5. Convert to DB format (after all user mutations)
|
|
26
|
+
const snaked = snakecaseRecord(inputRecord, schema);
|
|
19
27
|
const updateFields = { ...snaked };
|
|
20
28
|
delete updateFields[pkCol];
|
|
21
|
-
//
|
|
29
|
+
// 6. Strip tenant column (user cannot change tenant of an existing record)
|
|
22
30
|
if (tenant) {
|
|
23
31
|
stripTenantColumn(updateFields, tenant.scope);
|
|
24
|
-
if ('through' in tenant.scope) {
|
|
25
|
-
// Indirect: verify the record belongs to tenant via subquery
|
|
26
|
-
const scope = tenant.scope;
|
|
27
|
-
const tableName = tableConf.Schema.tableName;
|
|
28
|
-
const cb = new ConditionBuilder('AND');
|
|
29
|
-
cb.isEqual(`${db.qi(tableName)}.${db.qi(pkCol)}`, pkValue);
|
|
30
|
-
cb.append(buildTenantCondition(db, scope, tenant.ids));
|
|
31
|
-
const checkWhere = cb.build(1, db.ph);
|
|
32
|
-
const checkValues = cb.getValues();
|
|
33
|
-
const joinSql = buildTenantJoin(db, scope, tableName);
|
|
34
|
-
const checkSql = `SELECT 1 FROM ${db.qi(tableName)} ${joinSql} WHERE ${checkWhere} LIMIT 1`;
|
|
35
|
-
const checkResult = await db.query(checkSql, checkValues);
|
|
36
|
-
if (checkResult.rows.length === 0) {
|
|
37
|
-
const error = new Error('Record not found');
|
|
38
|
-
error.statusCode = 404;
|
|
39
|
-
throw error;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
32
|
}
|
|
43
|
-
//
|
|
44
|
-
await runValidation(db, request, tableConf, record, secondaries);
|
|
45
|
-
// 3. beforeUpdate hook
|
|
46
|
-
if (tableConf.beforeUpdate) {
|
|
47
|
-
await tableConf.beforeUpdate(db, request, updateFields);
|
|
48
|
-
}
|
|
49
|
-
// 4. Update main
|
|
33
|
+
// 7. Update main
|
|
50
34
|
const hasFieldsToUpdate = Object.keys(updateFields).length > 0;
|
|
51
|
-
|
|
52
|
-
if (tenant && !('through' in tenant.scope)) {
|
|
53
|
-
// Direct: add tenant to WHERE
|
|
54
|
-
extraCondition = new ConditionBuilder('AND');
|
|
55
|
-
if (tenant.ids.length === 1) {
|
|
56
|
-
extraCondition.isEqual(tenant.scope.column, tenant.ids[0]);
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
extraCondition.isIn(tenant.scope.column, tenant.ids);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
35
|
+
const extraCondition = buildTenantUpdateExtra(tenant);
|
|
62
36
|
if (hasFieldsToUpdate) {
|
|
63
|
-
const affectedRows = await db.update(
|
|
64
|
-
if (affectedRows === 0)
|
|
65
|
-
|
|
66
|
-
error.statusCode = 404;
|
|
67
|
-
throw error;
|
|
68
|
-
}
|
|
37
|
+
const affectedRows = await db.update(schema.tableName, updateFields, { [pkCol]: pkValue }, extraCondition);
|
|
38
|
+
if (affectedRows === 0)
|
|
39
|
+
throw httpError(404, `Record not found`);
|
|
69
40
|
}
|
|
70
41
|
else {
|
|
71
42
|
// No fields to update: verify the record exists (for secondaries/deletions)
|
|
72
|
-
let whereSql = `${db.qi(
|
|
43
|
+
let whereSql = `${db.qi(schema.tableName)}.${db.qi(pkCol)} = ${db.ph(1)}`;
|
|
73
44
|
const whereValues = [pkValue];
|
|
74
45
|
if (extraCondition) {
|
|
75
46
|
whereSql += ` AND ${extraCondition.build(2, db.ph)}`;
|
|
76
47
|
whereValues.push(...extraCondition.getValues());
|
|
77
48
|
}
|
|
78
49
|
const rows = await db.select({
|
|
79
|
-
tableName:
|
|
50
|
+
tableName: schema.tableName,
|
|
80
51
|
where: whereSql,
|
|
81
52
|
values: whereValues,
|
|
82
53
|
limit: '1',
|
|
83
54
|
});
|
|
84
|
-
if (rows.length === 0)
|
|
85
|
-
|
|
86
|
-
error.statusCode = 404;
|
|
87
|
-
throw error;
|
|
88
|
-
}
|
|
55
|
+
if (rows.length === 0)
|
|
56
|
+
throw httpError(404, `Record not found`);
|
|
89
57
|
}
|
|
90
58
|
// Build the main response (PK-only)
|
|
91
59
|
const mainResult = { [pk]: pkValue };
|
|
92
|
-
//
|
|
60
|
+
// 8. Secondaries (upsert/insert with FK auto-fill — camelCase)
|
|
93
61
|
let secondaryResults;
|
|
94
62
|
if (secondaries && Object.keys(secondaries).length > 0) {
|
|
95
|
-
|
|
96
|
-
const mainForFK = { ...camelcaseObject(snaked) };
|
|
63
|
+
const mainForFK = { ...inputRecord };
|
|
97
64
|
secondaryResults = await processSecondaries(db, tableConf, dbTables, mainForFK, secondaries);
|
|
98
65
|
}
|
|
99
|
-
//
|
|
66
|
+
// 9. Deletions (FK auto-fill from main like secondaries)
|
|
100
67
|
let deletionResults;
|
|
101
68
|
if (deletions && Object.keys(deletions).length > 0) {
|
|
102
|
-
|
|
69
|
+
const mainForFK = { ...inputRecord };
|
|
70
|
+
deletionResults = await processDeletions(db, tableConf, mainForFK, deletions);
|
|
103
71
|
}
|
|
104
|
-
//
|
|
72
|
+
// 10. Return PK-only
|
|
105
73
|
const result = { main: mainResult };
|
|
106
74
|
if (secondaryResults && Object.keys(secondaryResults).length > 0) {
|
|
107
75
|
result.secondaries = secondaryResults;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/search/search.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,QAAQ,
|
|
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,EAab,MAAM,mBAAmB,CAAC;AAy/B3B,wBAAsB,YAAY,CAChC,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,YAAY,CAAC,CAoIvB"}
|