fastify-auto-sqlapi 0.0.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/LICENSE +21 -0
- package/README.md +422 -0
- package/dist/bin/generate-schema.d.ts +3 -0
- package/dist/bin/generate-schema.d.ts.map +1 -0
- package/dist/bin/generate-schema.js +122 -0
- package/dist/bin/generate-tables.d.ts +3 -0
- package/dist/bin/generate-tables.d.ts.map +1 -0
- package/dist/bin/generate-tables.js +135 -0
- package/dist/bin/utils.d.ts +18 -0
- package/dist/bin/utils.d.ts.map +1 -0
- package/dist/bin/utils.js +54 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +48 -0
- package/dist/lib/adapters/mysql-adapter.d.ts +9 -0
- package/dist/lib/adapters/mysql-adapter.d.ts.map +1 -0
- package/dist/lib/adapters/mysql-adapter.js +21 -0
- package/dist/lib/adapters/pg-adapter.d.ts +9 -0
- package/dist/lib/adapters/pg-adapter.d.ts.map +1 -0
- package/dist/lib/adapters/pg-adapter.js +11 -0
- package/dist/lib/cli/config.d.ts +3 -0
- package/dist/lib/cli/config.d.ts.map +1 -0
- package/dist/lib/cli/config.js +29 -0
- package/dist/lib/cli/mysql-introspect.d.ts +16 -0
- package/dist/lib/cli/mysql-introspect.d.ts.map +1 -0
- package/dist/lib/cli/mysql-introspect.js +98 -0
- package/dist/lib/cli/pg-introspect.d.ts +4 -0
- package/dist/lib/cli/pg-introspect.d.ts.map +1 -0
- package/dist/lib/cli/pg-introspect.js +36 -0
- package/dist/lib/cli/schema-codegen.d.ts +8 -0
- package/dist/lib/cli/schema-codegen.d.ts.map +1 -0
- package/dist/lib/cli/schema-codegen.js +100 -0
- package/dist/lib/cli/tables-codegen.d.ts +18 -0
- package/dist/lib/cli/tables-codegen.d.ts.map +1 -0
- package/dist/lib/cli/tables-codegen.js +249 -0
- package/dist/lib/condition-methods.d.ts +11 -0
- package/dist/lib/condition-methods.d.ts.map +1 -0
- package/dist/lib/condition-methods.js +20 -0
- package/dist/lib/db.d.ts +28 -0
- package/dist/lib/db.d.ts.map +1 -0
- package/dist/lib/db.js +224 -0
- package/dist/lib/dialect.d.ts +17 -0
- package/dist/lib/dialect.d.ts.map +1 -0
- package/dist/lib/dialect.js +56 -0
- package/dist/lib/engine/bulk/bulk-delete.d.ts +3 -0
- package/dist/lib/engine/bulk/bulk-delete.d.ts.map +1 -0
- package/dist/lib/engine/bulk/bulk-delete.js +30 -0
- package/dist/lib/engine/bulk/bulk-upsert.d.ts +3 -0
- package/dist/lib/engine/bulk/bulk-upsert.d.ts.map +1 -0
- package/dist/lib/engine/bulk/bulk-upsert.js +74 -0
- package/dist/lib/engine/rest/delete.d.ts +3 -0
- package/dist/lib/engine/rest/delete.d.ts.map +1 -0
- package/dist/lib/engine/rest/delete.js +25 -0
- package/dist/lib/engine/rest/get.d.ts +3 -0
- package/dist/lib/engine/rest/get.d.ts.map +1 -0
- package/dist/lib/engine/rest/get.js +32 -0
- package/dist/lib/engine/rest/insert.d.ts +3 -0
- package/dist/lib/engine/rest/insert.d.ts.map +1 -0
- package/dist/lib/engine/rest/insert.js +58 -0
- package/dist/lib/engine/rest/update.d.ts +3 -0
- package/dist/lib/engine/rest/update.d.ts.map +1 -0
- package/dist/lib/engine/rest/update.js +110 -0
- package/dist/lib/engine/search/search.d.ts +3 -0
- package/dist/lib/engine/search/search.d.ts.map +1 -0
- package/dist/lib/engine/search/search.js +300 -0
- package/dist/lib/engine/write-helpers.d.ts +8 -0
- package/dist/lib/engine/write-helpers.d.ts.map +1 -0
- package/dist/lib/engine/write-helpers.js +83 -0
- package/dist/lib/naming.d.ts +6 -0
- package/dist/lib/naming.d.ts.map +1 -0
- package/dist/lib/naming.js +23 -0
- package/dist/lib/schema/bulk-delete.d.ts +10 -0
- package/dist/lib/schema/bulk-delete.d.ts.map +1 -0
- package/dist/lib/schema/bulk-delete.js +16 -0
- package/dist/lib/schema/bulk-upsert.d.ts +5 -0
- package/dist/lib/schema/bulk-upsert.d.ts.map +1 -0
- package/dist/lib/schema/bulk-upsert.js +43 -0
- package/dist/lib/schema/helpers.d.ts +14 -0
- package/dist/lib/schema/helpers.d.ts.map +1 -0
- package/dist/lib/schema/helpers.js +33 -0
- package/dist/lib/schema/insert.d.ts +5 -0
- package/dist/lib/schema/insert.d.ts.map +1 -0
- package/dist/lib/schema/insert.js +43 -0
- package/dist/lib/schema/search.d.ts +14 -0
- package/dist/lib/schema/search.d.ts.map +1 -0
- package/dist/lib/schema/search.js +99 -0
- package/dist/lib/schema/update.d.ts +5 -0
- package/dist/lib/schema/update.d.ts.map +1 -0
- package/dist/lib/schema/update.js +47 -0
- package/dist/lib/setup-swagger.d.ts +4 -0
- package/dist/lib/setup-swagger.d.ts.map +1 -0
- package/dist/lib/setup-swagger.js +27 -0
- package/dist/lib/sql-api.d.ts +49 -0
- package/dist/lib/sql-api.d.ts.map +1 -0
- package/dist/lib/sql-api.js +124 -0
- package/dist/lib/table-helpers.d.ts +17 -0
- package/dist/lib/table-helpers.d.ts.map +1 -0
- package/dist/lib/table-helpers.js +34 -0
- package/dist/lib/tenant.d.ts +19 -0
- package/dist/lib/tenant.d.ts.map +1 -0
- package/dist/lib/tenant.js +110 -0
- package/dist/routes/auto/bulk-delete.routes.d.ts +4 -0
- package/dist/routes/auto/bulk-delete.routes.d.ts.map +1 -0
- package/dist/routes/auto/bulk-delete.routes.js +27 -0
- package/dist/routes/auto/bulk-upsert.routes.d.ts +4 -0
- package/dist/routes/auto/bulk-upsert.routes.d.ts.map +1 -0
- package/dist/routes/auto/bulk-upsert.routes.js +25 -0
- package/dist/routes/auto/delete.routes.d.ts +4 -0
- package/dist/routes/auto/delete.routes.d.ts.map +1 -0
- package/dist/routes/auto/delete.routes.js +29 -0
- package/dist/routes/auto/get.routes.d.ts +4 -0
- package/dist/routes/auto/get.routes.d.ts.map +1 -0
- package/dist/routes/auto/get.routes.js +26 -0
- package/dist/routes/auto/insert.routes.d.ts +4 -0
- package/dist/routes/auto/insert.routes.d.ts.map +1 -0
- package/dist/routes/auto/insert.routes.js +29 -0
- package/dist/routes/auto/plugin.d.ts +11 -0
- package/dist/routes/auto/plugin.d.ts.map +1 -0
- package/dist/routes/auto/plugin.js +81 -0
- package/dist/routes/auto/route-helpers.d.ts +7 -0
- package/dist/routes/auto/route-helpers.d.ts.map +1 -0
- package/dist/routes/auto/route-helpers.js +12 -0
- package/dist/routes/auto/search.routes.d.ts +4 -0
- package/dist/routes/auto/search.routes.d.ts.map +1 -0
- package/dist/routes/auto/search.routes.js +50 -0
- package/dist/routes/auto/update.routes.d.ts +4 -0
- package/dist/routes/auto/update.routes.d.ts.map +1 -0
- package/dist/routes/auto/update.routes.js +30 -0
- package/dist/types.d.ts +240 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/package.json +106 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type DialectName = 'postgres' | 'mysql' | 'mariadb';
|
|
2
|
+
export interface SqlDialect {
|
|
3
|
+
name: DialectName;
|
|
4
|
+
/** Quote identifier: "id" (postgres) or `id` (mysql/mariadb) */
|
|
5
|
+
qi(id: string): string;
|
|
6
|
+
/** Placeholder: $1 (postgres) or ? (mysql/mariadb) */
|
|
7
|
+
ph(index: number): string;
|
|
8
|
+
/** ConditionBuilder dialect name */
|
|
9
|
+
cbDialect: 'postgres' | 'mysql';
|
|
10
|
+
supportsReturning: boolean;
|
|
11
|
+
/** ON CONFLICT / ON DUPLICATE KEY syntax */
|
|
12
|
+
upsertSql(quotedConflictKeys: string, quotedUpdateSet: string): string;
|
|
13
|
+
/** RETURNING pk_col suffix or empty string */
|
|
14
|
+
returningPk(quotedPkCol: string): string;
|
|
15
|
+
}
|
|
16
|
+
export declare function getDialect(name: DialectName): SqlDialect;
|
|
17
|
+
//# sourceMappingURL=dialect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialect.d.ts","sourceRoot":"","sources":["../../src/lib/dialect.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,CAAC;AAE3D,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,WAAW,CAAC;IAClB,gEAAgE;IAChE,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,sDAAsD;IACtD,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,oCAAoC;IACpC,SAAS,EAAE,UAAU,GAAG,OAAO,CAAC;IAChC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,4CAA4C;IAC5C,SAAS,CAAC,kBAAkB,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,MAAM,CAAC;IACvE,8CAA8C;IAC9C,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1C;AAqDD,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,UAAU,CAIxD"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const PostgresDialect = {
|
|
2
|
+
name: 'postgres',
|
|
3
|
+
qi: (id) => `"${id.replace(/"/g, '""')}"`,
|
|
4
|
+
ph: (i) => `$${i}`,
|
|
5
|
+
cbDialect: 'postgres',
|
|
6
|
+
supportsReturning: true,
|
|
7
|
+
upsertSql(quotedConflictKeys, quotedUpdateSet) {
|
|
8
|
+
if (!quotedUpdateSet)
|
|
9
|
+
return `ON CONFLICT (${quotedConflictKeys}) DO NOTHING`;
|
|
10
|
+
return `ON CONFLICT (${quotedConflictKeys}) DO UPDATE SET ${quotedUpdateSet}`;
|
|
11
|
+
},
|
|
12
|
+
returningPk(quotedPkCol) {
|
|
13
|
+
return ` RETURNING ${quotedPkCol}`;
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
const MysqlDialect = {
|
|
17
|
+
name: 'mysql',
|
|
18
|
+
qi: (id) => `\`${id.replace(/`/g, '``')}\``,
|
|
19
|
+
ph: () => '?',
|
|
20
|
+
cbDialect: 'mysql',
|
|
21
|
+
supportsReturning: false,
|
|
22
|
+
upsertSql(_quotedConflictKeys, quotedUpdateSet) {
|
|
23
|
+
if (!quotedUpdateSet)
|
|
24
|
+
return `ON DUPLICATE KEY UPDATE ${_quotedConflictKeys.split(', ')[0]} = ${_quotedConflictKeys.split(', ')[0]}`;
|
|
25
|
+
return `ON DUPLICATE KEY UPDATE ${quotedUpdateSet}`;
|
|
26
|
+
},
|
|
27
|
+
returningPk() {
|
|
28
|
+
return '';
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
const MariadbDialect = {
|
|
32
|
+
name: 'mariadb',
|
|
33
|
+
qi: (id) => `\`${id.replace(/`/g, '``')}\``,
|
|
34
|
+
ph: () => '?',
|
|
35
|
+
cbDialect: 'mysql',
|
|
36
|
+
supportsReturning: true,
|
|
37
|
+
upsertSql(_quotedConflictKeys, quotedUpdateSet) {
|
|
38
|
+
if (!quotedUpdateSet)
|
|
39
|
+
return `ON DUPLICATE KEY UPDATE ${_quotedConflictKeys.split(', ')[0]} = ${_quotedConflictKeys.split(', ')[0]}`;
|
|
40
|
+
return `ON DUPLICATE KEY UPDATE ${quotedUpdateSet}`;
|
|
41
|
+
},
|
|
42
|
+
returningPk(quotedPkCol) {
|
|
43
|
+
return ` RETURNING ${quotedPkCol}`;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
const dialects = {
|
|
47
|
+
postgres: PostgresDialect,
|
|
48
|
+
mysql: MysqlDialect,
|
|
49
|
+
mariadb: MariadbDialect,
|
|
50
|
+
};
|
|
51
|
+
export function getDialect(name) {
|
|
52
|
+
const d = dialects[name];
|
|
53
|
+
if (!d)
|
|
54
|
+
throw new Error(`Unknown dialect: ${name}`);
|
|
55
|
+
return d;
|
|
56
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bulk-delete.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/bulk/bulk-delete.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE5E,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAiC5F"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ConditionBuilder } from 'node-condition-builder';
|
|
2
|
+
import { buildTenantDeleteWhere } from '../../tenant.js';
|
|
3
|
+
import { primaryAsString } from '../../../types.js';
|
|
4
|
+
export async function bulkDeleteEngine(params) {
|
|
5
|
+
const { db, tableConf, ids, tenant } = params;
|
|
6
|
+
if (!ids.length)
|
|
7
|
+
return [];
|
|
8
|
+
const pk = primaryAsString(tableConf.primary);
|
|
9
|
+
const pkCol = tableConf.Schema.col(pk);
|
|
10
|
+
const tableName = tableConf.Schema.tableName;
|
|
11
|
+
let where;
|
|
12
|
+
let values;
|
|
13
|
+
if (tenant) {
|
|
14
|
+
({ where, values } = buildTenantDeleteWhere(db, tableName, pkCol, ids, tenant));
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
const cb = new ConditionBuilder('AND');
|
|
18
|
+
cb.isIn(db.qi(pkCol), ids);
|
|
19
|
+
where = cb.build(1, db.ph);
|
|
20
|
+
values = cb.getValues();
|
|
21
|
+
}
|
|
22
|
+
const result = await db.query(`DELETE FROM ${db.qi(tableName)} WHERE ${where}`, values);
|
|
23
|
+
// Return PK-only for each deleted record
|
|
24
|
+
if (result.affectedRows === ids.length) {
|
|
25
|
+
return ids.map((id) => ({ main: { [pk]: id } }));
|
|
26
|
+
}
|
|
27
|
+
// Partial delete: best effort (no RETURNING on MySQL)
|
|
28
|
+
const deletedCount = result.affectedRows;
|
|
29
|
+
return ids.slice(0, deletedCount).map((id) => ({ main: { [pk]: id } }));
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bulk-upsert.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/bulk/bulk-upsert.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAGjB,MAAM,mBAAmB,CAAC;AAE3B,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAuF5F"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { camelcaseObject, snakecaseRecord } from '../../naming.js';
|
|
2
|
+
import { removeExcludedFields, processSecondaries, processDeletions } from '../write-helpers.js';
|
|
3
|
+
import { injectTenantValue, validateTenantFK } from '../../tenant.js';
|
|
4
|
+
import { primaryAsString } from '../../../types.js';
|
|
5
|
+
export async function bulkUpsertEngine(params) {
|
|
6
|
+
const { db, tableConf, dbTables, request, items, tenant } = params;
|
|
7
|
+
if (!items.length)
|
|
8
|
+
return [];
|
|
9
|
+
const pk = primaryAsString(tableConf.primary);
|
|
10
|
+
const pkCol = tableConf.Schema.col(pk);
|
|
11
|
+
// 1. Prepare all main records
|
|
12
|
+
const preparedMains = items.map((item) => {
|
|
13
|
+
let rec = snakecaseRecord(item.main);
|
|
14
|
+
rec = removeExcludedFields(rec, tableConf);
|
|
15
|
+
return rec;
|
|
16
|
+
});
|
|
17
|
+
// 1b. Tenant: inject or validate FK
|
|
18
|
+
if (tenant) {
|
|
19
|
+
if ('through' in tenant.scope) {
|
|
20
|
+
// Indirect: collect all FK values, validate in batch
|
|
21
|
+
const scope = tenant.scope;
|
|
22
|
+
const fkCol = scope.through.localField;
|
|
23
|
+
const fkValues = preparedMains.map((rec) => rec[fkCol]);
|
|
24
|
+
await validateTenantFK(db, scope, tenant.ids, fkValues);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
for (const rec of preparedMains) {
|
|
28
|
+
injectTenantValue(rec, tenant.scope, tenant.ids);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// 2. beforeInsert hook per record
|
|
33
|
+
if (tableConf.beforeInsert) {
|
|
34
|
+
for (const rec of preparedMains) {
|
|
35
|
+
await tableConf.beforeInsert(db, request, rec);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// 3. Bulk upsert all mains in one query → returns PK-only
|
|
39
|
+
const upsertKeys = tableConf.upsertMap?.get(tableConf.Schema);
|
|
40
|
+
let pkRows;
|
|
41
|
+
if (upsertKeys) {
|
|
42
|
+
const conflictCols = upsertKeys.map((k) => tableConf.Schema.col(k));
|
|
43
|
+
pkRows = await db.bulkInsertOrUpdate(tableConf.Schema.tableName, preparedMains, conflictCols, pkCol);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
pkRows = await db.bulkInsert(tableConf.Schema.tableName, preparedMains, pkCol);
|
|
47
|
+
}
|
|
48
|
+
const pksCamel = pkRows.map((r) => camelcaseObject(r));
|
|
49
|
+
// 4. Process secondaries and deletions per item
|
|
50
|
+
const results = [];
|
|
51
|
+
for (let i = 0; i < items.length; i++) {
|
|
52
|
+
const item = items[i];
|
|
53
|
+
const mainPk = pksCamel[i];
|
|
54
|
+
const result = { main: mainPk };
|
|
55
|
+
// For FK auto-fill in secondaries, merge input record + PK
|
|
56
|
+
const mainForFK = { ...camelcaseObject(preparedMains[i]), ...mainPk };
|
|
57
|
+
if (item.secondaries && Object.keys(item.secondaries).length > 0) {
|
|
58
|
+
const sec = await processSecondaries(db, tableConf, dbTables, mainForFK, item.secondaries);
|
|
59
|
+
if (Object.keys(sec).length > 0)
|
|
60
|
+
result.secondaries = sec;
|
|
61
|
+
}
|
|
62
|
+
if (item.deletions && Object.keys(item.deletions).length > 0) {
|
|
63
|
+
const del = await processDeletions(db, tableConf, item.deletions);
|
|
64
|
+
if (Object.keys(del).length > 0)
|
|
65
|
+
result.deletions = del;
|
|
66
|
+
}
|
|
67
|
+
// 5. afterInsert hook per item
|
|
68
|
+
if (tableConf.afterInsert) {
|
|
69
|
+
await tableConf.afterInsert(db, request, mainForFK, result.secondaries);
|
|
70
|
+
}
|
|
71
|
+
results.push(result);
|
|
72
|
+
}
|
|
73
|
+
return results;
|
|
74
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/rest/delete.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAY,MAAM,mBAAmB,CAAC;AAE9E,wBAAsB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CA8B9E"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { buildTenantDeleteWhere } from '../../tenant.js';
|
|
2
|
+
import { primaryAsString } from '../../../types.js';
|
|
3
|
+
export async function deleteEngine(params) {
|
|
4
|
+
const { db, tableConf, id, tenant } = params;
|
|
5
|
+
const pk = primaryAsString(tableConf.primary);
|
|
6
|
+
const pkCol = tableConf.Schema.col(pk);
|
|
7
|
+
const tableName = tableConf.Schema.tableName;
|
|
8
|
+
if (!tenant) {
|
|
9
|
+
const affectedRows = await db.delete(tableName, { [pkCol]: id });
|
|
10
|
+
if (affectedRows === 0) {
|
|
11
|
+
const err = new Error(`Record not found: ${id}`);
|
|
12
|
+
err.statusCode = 404;
|
|
13
|
+
throw err;
|
|
14
|
+
}
|
|
15
|
+
return { main: { [pk]: id } };
|
|
16
|
+
}
|
|
17
|
+
const { where, values } = buildTenantDeleteWhere(db, tableName, pkCol, id, tenant);
|
|
18
|
+
const result = await db.query(`DELETE FROM ${db.qi(tableName)} WHERE ${where}`, values);
|
|
19
|
+
if (result.affectedRows === 0) {
|
|
20
|
+
const err = new Error(`Record not found: ${id}`);
|
|
21
|
+
err.statusCode = 404;
|
|
22
|
+
throw err;
|
|
23
|
+
}
|
|
24
|
+
return { main: { [pk]: id } };
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/rest/get.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAuB,MAAM,mBAAmB,CAAC;AAEnF,wBAAsB,SAAS,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAiCrE"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ConditionBuilder } from 'node-condition-builder';
|
|
2
|
+
import { camelcaseObject } from '../../naming.js';
|
|
3
|
+
import { buildTenantCondition, buildTenantJoin } from '../../tenant.js';
|
|
4
|
+
import { primaryAsString } from '../../../types.js';
|
|
5
|
+
export async function getEngine(params) {
|
|
6
|
+
const { db, tableConf, id, tenant } = params;
|
|
7
|
+
const pkCol = tableConf.Schema.col(primaryAsString(tableConf.primary));
|
|
8
|
+
const cb = new ConditionBuilder('AND');
|
|
9
|
+
cb.isEqual(db.qi(pkCol), id);
|
|
10
|
+
const joins = [];
|
|
11
|
+
if (tenant) {
|
|
12
|
+
cb.append(buildTenantCondition(db, tenant.scope, tenant.ids));
|
|
13
|
+
if ('through' in tenant.scope) {
|
|
14
|
+
joins.push(buildTenantJoin(db, tenant.scope, tableConf.Schema.tableName));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const where = cb.build(1, db.ph);
|
|
18
|
+
const values = cb.getValues();
|
|
19
|
+
const rows = await db.select({
|
|
20
|
+
tableName: tableConf.Schema.tableName,
|
|
21
|
+
where,
|
|
22
|
+
values,
|
|
23
|
+
limit: '1',
|
|
24
|
+
joins: joins.length > 0 ? joins : undefined,
|
|
25
|
+
});
|
|
26
|
+
if (rows.length === 0) {
|
|
27
|
+
const err = new Error(`Record not found: ${id}`);
|
|
28
|
+
err.statusCode = 404;
|
|
29
|
+
throw err;
|
|
30
|
+
}
|
|
31
|
+
return { main: camelcaseObject(rows[0]) };
|
|
32
|
+
}
|
|
@@ -0,0 +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,EAGb,MAAM,mBAAmB,CAAC;AAE3B,wBAAsB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAsE9E"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { camelcaseObject, snakecaseRecord } from '../../naming.js';
|
|
2
|
+
import { removeExcludedFields, processSecondaries } from '../write-helpers.js';
|
|
3
|
+
import { injectTenantValue, validateTenantFK } from '../../tenant.js';
|
|
4
|
+
import { primaryAsString } from '../../../types.js';
|
|
5
|
+
export async function insertEngine(params) {
|
|
6
|
+
const { db, tableConf, dbTables, request, record, secondaries, tenant } = params;
|
|
7
|
+
const pk = primaryAsString(tableConf.primary);
|
|
8
|
+
const pkCol = tableConf.Schema.col(pk);
|
|
9
|
+
// 1. Prepare main record
|
|
10
|
+
let mainRecord = snakecaseRecord(record);
|
|
11
|
+
mainRecord = removeExcludedFields(mainRecord, tableConf);
|
|
12
|
+
// 1b. Tenant: inject or validate
|
|
13
|
+
if (tenant) {
|
|
14
|
+
if ('through' in tenant.scope) {
|
|
15
|
+
// Indirect: validate FK belongs to tenant
|
|
16
|
+
const scope = tenant.scope;
|
|
17
|
+
const fkCol = scope.through.localField;
|
|
18
|
+
const fkValue = mainRecord[fkCol];
|
|
19
|
+
await validateTenantFK(db, scope, tenant.ids, [fkValue]);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
injectTenantValue(mainRecord, tenant.scope, tenant.ids);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// 2. beforeInsert hook
|
|
26
|
+
if (tableConf.beforeInsert) {
|
|
27
|
+
await tableConf.beforeInsert(db, request, mainRecord);
|
|
28
|
+
}
|
|
29
|
+
// 3. Insert main → returns PK-only
|
|
30
|
+
let pkResult;
|
|
31
|
+
const upsertKeys = tableConf.upsertMap?.get(tableConf.Schema);
|
|
32
|
+
if (upsertKeys) {
|
|
33
|
+
const conflictCols = upsertKeys.map((k) => tableConf.Schema.col(k));
|
|
34
|
+
pkResult = await db.insertOrUpdate(tableConf.Schema.tableName, mainRecord, conflictCols, pkCol);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
pkResult = await db.insert(tableConf.Schema.tableName, mainRecord, pkCol);
|
|
38
|
+
}
|
|
39
|
+
const mainPkCamel = camelcaseObject(pkResult);
|
|
40
|
+
// 4. Secondaries: need full record for FK auto-fill
|
|
41
|
+
let secondaryResults;
|
|
42
|
+
if (secondaries && Object.keys(secondaries).length > 0) {
|
|
43
|
+
// Merge input record + PK for FK auto-fill
|
|
44
|
+
const mainForFK = { ...camelcaseObject(mainRecord), ...mainPkCamel };
|
|
45
|
+
secondaryResults = await processSecondaries(db, tableConf, dbTables, mainForFK, secondaries);
|
|
46
|
+
}
|
|
47
|
+
// 5. afterInsert hook
|
|
48
|
+
if (tableConf.afterInsert) {
|
|
49
|
+
const mainForHook = { ...camelcaseObject(mainRecord), ...mainPkCamel };
|
|
50
|
+
await tableConf.afterInsert(db, request, mainForHook, secondaryResults);
|
|
51
|
+
}
|
|
52
|
+
// 6. Return PK-only
|
|
53
|
+
const result = { main: mainPkCamel };
|
|
54
|
+
if (secondaryResults && Object.keys(secondaryResults).length > 0) {
|
|
55
|
+
result.secondaries = secondaryResults;
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/rest/update.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,YAAY,EACZ,YAAY,EAGb,MAAM,mBAAmB,CAAC;AAE3B,wBAAsB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CA6H9E"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { camelcaseObject, snakecaseRecord } from '../../naming.js';
|
|
2
|
+
import { processSecondaries, processDeletions } from '../write-helpers.js';
|
|
3
|
+
import { stripTenantColumn, buildTenantCondition, buildTenantJoin } from '../../tenant.js';
|
|
4
|
+
import { ConditionBuilder } from 'node-condition-builder';
|
|
5
|
+
import { primaryAsString } from '../../../types.js';
|
|
6
|
+
export async function updateEngine(params) {
|
|
7
|
+
const { db, tableConf, dbTables, request, record, secondaries, deletions, tenant } = params;
|
|
8
|
+
// 1. Prepare: extract PK, snakecase, build update fields
|
|
9
|
+
const pk = primaryAsString(tableConf.primary);
|
|
10
|
+
const pkCol = tableConf.Schema.col(pk);
|
|
11
|
+
const snaked = snakecaseRecord(record);
|
|
12
|
+
const pkValue = snaked[pkCol];
|
|
13
|
+
if (pkValue == null) {
|
|
14
|
+
const error = new Error(`Primary key "${pk}" is required`);
|
|
15
|
+
error.statusCode = 400;
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
const updateFields = { ...snaked };
|
|
19
|
+
delete updateFields[pkCol];
|
|
20
|
+
// 1b. Tenant: strip tenant column from update fields + validate ownership
|
|
21
|
+
if (tenant) {
|
|
22
|
+
stripTenantColumn(updateFields, tenant.scope);
|
|
23
|
+
if ('through' in tenant.scope) {
|
|
24
|
+
// Indirect: verify the record belongs to tenant via subquery
|
|
25
|
+
const scope = tenant.scope;
|
|
26
|
+
const tableName = tableConf.Schema.tableName;
|
|
27
|
+
const cb = new ConditionBuilder('AND');
|
|
28
|
+
cb.isEqual(`${db.qi(tableName)}.${db.qi(pkCol)}`, pkValue);
|
|
29
|
+
cb.append(buildTenantCondition(db, scope, tenant.ids));
|
|
30
|
+
const checkWhere = cb.build(1, db.ph);
|
|
31
|
+
const checkValues = cb.getValues();
|
|
32
|
+
const joinSql = buildTenantJoin(db, scope, tableName);
|
|
33
|
+
const checkSql = `SELECT 1 FROM ${db.qi(tableName)} ${joinSql} WHERE ${checkWhere} LIMIT 1`;
|
|
34
|
+
const checkResult = await db.query(checkSql, checkValues);
|
|
35
|
+
if (checkResult.rows.length === 0) {
|
|
36
|
+
const error = new Error('Record not found');
|
|
37
|
+
error.statusCode = 404;
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// 2. beforeUpdate hook
|
|
43
|
+
if (tableConf.beforeUpdate) {
|
|
44
|
+
await tableConf.beforeUpdate(db, request, updateFields);
|
|
45
|
+
}
|
|
46
|
+
// 3. Update main
|
|
47
|
+
const hasFieldsToUpdate = Object.keys(updateFields).length > 0;
|
|
48
|
+
let extraCondition;
|
|
49
|
+
if (tenant && !('through' in tenant.scope)) {
|
|
50
|
+
// Direct: add tenant to WHERE
|
|
51
|
+
extraCondition = new ConditionBuilder('AND');
|
|
52
|
+
if (tenant.ids.length === 1) {
|
|
53
|
+
extraCondition.isEqual(tenant.scope.column, tenant.ids[0]);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
extraCondition.isIn(tenant.scope.column, tenant.ids);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (hasFieldsToUpdate) {
|
|
60
|
+
const affectedRows = await db.update(tableConf.Schema.tableName, updateFields, { [pkCol]: pkValue }, extraCondition);
|
|
61
|
+
if (affectedRows === 0) {
|
|
62
|
+
const error = new Error(`Record not found`);
|
|
63
|
+
error.statusCode = 404;
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// No fields to update: verify the record exists (for secondaries/deletions)
|
|
69
|
+
let whereSql = `${db.qi(tableConf.Schema.tableName)}.${db.qi(pkCol)} = ${db.ph(1)}`;
|
|
70
|
+
const whereValues = [pkValue];
|
|
71
|
+
if (extraCondition) {
|
|
72
|
+
whereSql += ` AND ${extraCondition.build(2, db.ph)}`;
|
|
73
|
+
whereValues.push(...extraCondition.getValues());
|
|
74
|
+
}
|
|
75
|
+
const rows = await db.select({
|
|
76
|
+
tableName: tableConf.Schema.tableName,
|
|
77
|
+
where: whereSql,
|
|
78
|
+
values: whereValues,
|
|
79
|
+
limit: '1',
|
|
80
|
+
});
|
|
81
|
+
if (rows.length === 0) {
|
|
82
|
+
const error = new Error(`Record not found`);
|
|
83
|
+
error.statusCode = 404;
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Build the main response (PK-only)
|
|
88
|
+
const mainResult = { [pk]: pkValue };
|
|
89
|
+
// 4. Secondaries (upsert/insert with FK auto-fill)
|
|
90
|
+
let secondaryResults;
|
|
91
|
+
if (secondaries && Object.keys(secondaries).length > 0) {
|
|
92
|
+
// Merge input fields + PK for FK auto-fill
|
|
93
|
+
const mainForFK = { ...camelcaseObject(snaked) };
|
|
94
|
+
secondaryResults = await processSecondaries(db, tableConf, dbTables, mainForFK, secondaries);
|
|
95
|
+
}
|
|
96
|
+
// 5. Deletions
|
|
97
|
+
let deletionResults;
|
|
98
|
+
if (deletions && Object.keys(deletions).length > 0) {
|
|
99
|
+
deletionResults = await processDeletions(db, tableConf, deletions);
|
|
100
|
+
}
|
|
101
|
+
// 6. Return PK-only
|
|
102
|
+
const result = { main: mainResult };
|
|
103
|
+
if (secondaryResults && Object.keys(secondaryResults).length > 0) {
|
|
104
|
+
result.secondaries = secondaryResults;
|
|
105
|
+
}
|
|
106
|
+
if (deletionResults && Object.keys(deletionResults).length > 0) {
|
|
107
|
+
result.deletions = deletionResults;
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/search/search.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,QAAQ,EAER,YAAY,EACZ,YAAY,EASb,MAAM,mBAAmB,CAAC;AA+V3B,wBAAsB,YAAY,CAChC,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,YAAY,CAAC,CAyDvB"}
|