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,99 @@
|
|
|
1
|
+
import { Type } from '@sinclair/typebox';
|
|
2
|
+
import { ALLOWED_METHODS } from '../condition-methods.js';
|
|
3
|
+
const JoinGroupResultItem = Type.Object({
|
|
4
|
+
sum: Type.Optional(Type.Record(Type.String(), Type.Any())),
|
|
5
|
+
min: Type.Optional(Type.Record(Type.String(), Type.Any())),
|
|
6
|
+
max: Type.Optional(Type.Record(Type.String(), Type.Any())),
|
|
7
|
+
distinctCount: Type.Optional(Type.Record(Type.String(), Type.Any())),
|
|
8
|
+
rows: Type.Optional(Type.Array(Type.Any())),
|
|
9
|
+
});
|
|
10
|
+
export function SearchTableBodyPost(dbTables, tableName) {
|
|
11
|
+
const tableConf = dbTables[tableName];
|
|
12
|
+
const schema = tableConf.Schema;
|
|
13
|
+
// Filters: schema fields + extraFilters
|
|
14
|
+
const filterFields = {
|
|
15
|
+
...schema.fields,
|
|
16
|
+
...tableConf.extraFilters,
|
|
17
|
+
};
|
|
18
|
+
const filtersSchema = Type.Optional(Type.Partial(Type.Object(filterFields)));
|
|
19
|
+
// Joins & JoinGroups from allowedReadJoins
|
|
20
|
+
const joinProperties = {};
|
|
21
|
+
const joinFilterProperties = {};
|
|
22
|
+
const joinGroupProperties = {};
|
|
23
|
+
if (tableConf.allowedReadJoins) {
|
|
24
|
+
for (const [joinSchema] of tableConf.allowedReadJoins) {
|
|
25
|
+
const joinTableName = joinSchema.tableName;
|
|
26
|
+
const joinTableConf = dbTables[joinTableName];
|
|
27
|
+
const joinFilterFields = joinTableConf
|
|
28
|
+
? { ...joinSchema.fields, ...joinTableConf.extraFilters }
|
|
29
|
+
: { ...joinSchema.fields };
|
|
30
|
+
joinProperties[joinTableName] = Type.Object({
|
|
31
|
+
filters: Type.Optional(Type.Partial(Type.Object(joinFilterFields))),
|
|
32
|
+
});
|
|
33
|
+
joinFilterProperties[joinTableName] = Type.Partial(Type.Object(joinFilterFields));
|
|
34
|
+
joinGroupProperties[joinTableName] = Type.Object({
|
|
35
|
+
aggregations: Type.Object({
|
|
36
|
+
by: Type.Optional(Type.String()),
|
|
37
|
+
distinctCount: Type.Optional(Type.Array(Type.String())),
|
|
38
|
+
min: Type.Optional(Type.Array(Type.String())),
|
|
39
|
+
max: Type.Optional(Type.Array(Type.String())),
|
|
40
|
+
sum: Type.Optional(Type.Array(Type.String())),
|
|
41
|
+
}),
|
|
42
|
+
filters: Type.Optional(Type.Partial(Type.Object(joinFilterFields))),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Conditions: advanced filters with ConditionBuilder methods
|
|
47
|
+
const methodEnum = Type.Union(ALLOWED_METHODS.map((m) => Type.Literal(m)));
|
|
48
|
+
const conditionItemSchema = Type.Object({
|
|
49
|
+
field: Type.String(),
|
|
50
|
+
method: methodEnum,
|
|
51
|
+
params: Type.Optional(Type.Array(Type.Any())),
|
|
52
|
+
});
|
|
53
|
+
const bodyProperties = {
|
|
54
|
+
filters: filtersSchema,
|
|
55
|
+
conditions: Type.Optional(Type.Array(conditionItemSchema)),
|
|
56
|
+
};
|
|
57
|
+
if (Object.keys(joinProperties).length > 0) {
|
|
58
|
+
bodyProperties.joinFilters = Type.Optional(Type.Partial(Type.Object(joinFilterProperties)));
|
|
59
|
+
bodyProperties.joins = Type.Optional(Type.Partial(Type.Object(joinProperties)));
|
|
60
|
+
bodyProperties.joinGroups = Type.Optional(Type.Partial(Type.Object(joinGroupProperties)));
|
|
61
|
+
}
|
|
62
|
+
return Type.Object(bodyProperties);
|
|
63
|
+
}
|
|
64
|
+
export const SearchTableQueryString = Type.Object({
|
|
65
|
+
orderBy: Type.Optional(Type.String()),
|
|
66
|
+
page: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
67
|
+
itemsPerPage: Type.Optional(Type.Integer({ minimum: 1, maximum: 5000, default: 500 })),
|
|
68
|
+
computeMin: Type.Optional(Type.String()),
|
|
69
|
+
computeMax: Type.Optional(Type.String()),
|
|
70
|
+
computeSum: Type.Optional(Type.String()),
|
|
71
|
+
computeAvg: Type.Optional(Type.String()),
|
|
72
|
+
});
|
|
73
|
+
export function SearchTableResponse(dbTables, tableName) {
|
|
74
|
+
const tableConf = dbTables[tableName];
|
|
75
|
+
const mainItem = Type.Partial(Type.Object(tableConf.Schema.fields));
|
|
76
|
+
const joinResponseProperties = {};
|
|
77
|
+
const joinGroupResponseProperties = {};
|
|
78
|
+
if (tableConf.allowedReadJoins) {
|
|
79
|
+
for (const [joinSchema] of tableConf.allowedReadJoins) {
|
|
80
|
+
joinResponseProperties[joinSchema.tableName] = Type.Array(Type.Partial(Type.Object(joinSchema.fields)));
|
|
81
|
+
joinGroupResponseProperties[joinSchema.tableName] = JoinGroupResultItem;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return Type.Object({
|
|
85
|
+
table: Type.String(),
|
|
86
|
+
main: Type.Array(mainItem),
|
|
87
|
+
joins: Type.Optional(Type.Partial(Type.Object(joinResponseProperties))),
|
|
88
|
+
joinGroups: Type.Optional(Type.Partial(Type.Object(joinGroupResponseProperties))),
|
|
89
|
+
pagination: Type.Optional(Type.Object({
|
|
90
|
+
total: Type.Integer(),
|
|
91
|
+
pages: Type.Integer(),
|
|
92
|
+
computed: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
93
|
+
paginator: Type.Object({
|
|
94
|
+
page: Type.Integer(),
|
|
95
|
+
itemsPerPage: Type.Integer(),
|
|
96
|
+
}),
|
|
97
|
+
})),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type TObject } from '@sinclair/typebox';
|
|
2
|
+
import type { DbTables } from '../../types.js';
|
|
3
|
+
export declare function UpdateTableBody(dbTables: DbTables, tableName: string): TObject;
|
|
4
|
+
export declare function UpdateTableResponse(dbTables: DbTables, tableName: string): TObject;
|
|
5
|
+
//# sourceMappingURL=update.d.ts.map
|
|
@@ -0,0 +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;AAI/C,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAqC9E;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CA8BlF"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Type } from '@sinclair/typebox';
|
|
2
|
+
import { primaryAsString } from '../../types.js';
|
|
3
|
+
import { findSecondaryTableConf } from '../engine/write-helpers.js';
|
|
4
|
+
import { pkSchema, buildSecondaryFields } from './helpers.js';
|
|
5
|
+
export function UpdateTableBody(dbTables, tableName) {
|
|
6
|
+
const tableConf = dbTables[tableName];
|
|
7
|
+
const schema = tableConf.Schema;
|
|
8
|
+
// Main: PK required, all other fields optional
|
|
9
|
+
const mainFields = {};
|
|
10
|
+
for (const [key, value] of Object.entries(schema.fields)) {
|
|
11
|
+
mainFields[key] = key === primaryAsString(tableConf.primary) ? value : Type.Optional(value);
|
|
12
|
+
}
|
|
13
|
+
const bodyProperties = {
|
|
14
|
+
main: Type.Object(mainFields),
|
|
15
|
+
};
|
|
16
|
+
if (tableConf.allowedWriteJoins?.length) {
|
|
17
|
+
const secondaryProperties = {};
|
|
18
|
+
const deletionProperties = {};
|
|
19
|
+
for (const [joinSchema, joinField] of tableConf.allowedWriteJoins) {
|
|
20
|
+
const secondaryTableConf = findSecondaryTableConf(dbTables, joinSchema.tableName);
|
|
21
|
+
const joinFields = buildSecondaryFields(joinSchema, joinField, secondaryTableConf);
|
|
22
|
+
secondaryProperties[joinSchema.tableName] = Type.Array(Type.Object(joinFields));
|
|
23
|
+
deletionProperties[joinSchema.tableName] = Type.Array(Type.Partial(Type.Object(joinSchema.fields)));
|
|
24
|
+
}
|
|
25
|
+
bodyProperties.secondaries = Type.Optional(Type.Partial(Type.Object(secondaryProperties)));
|
|
26
|
+
bodyProperties.deletions = Type.Optional(Type.Partial(Type.Object(deletionProperties)));
|
|
27
|
+
}
|
|
28
|
+
return Type.Object(bodyProperties);
|
|
29
|
+
}
|
|
30
|
+
export function UpdateTableResponse(dbTables, tableName) {
|
|
31
|
+
const tableConf = dbTables[tableName];
|
|
32
|
+
const responseProperties = {
|
|
33
|
+
main: Type.Object(pkSchema(tableConf, tableConf.Schema, primaryAsString(tableConf.primary))),
|
|
34
|
+
};
|
|
35
|
+
if (tableConf.allowedWriteJoins?.length) {
|
|
36
|
+
const secondaryProperties = {};
|
|
37
|
+
const deletionProperties = {};
|
|
38
|
+
for (const [joinSchema, joinField] of tableConf.allowedWriteJoins) {
|
|
39
|
+
const secondaryTableConf = findSecondaryTableConf(dbTables, joinSchema.tableName);
|
|
40
|
+
secondaryProperties[joinSchema.tableName] = Type.Array(Type.Object(pkSchema(secondaryTableConf, joinSchema, joinField)));
|
|
41
|
+
deletionProperties[joinSchema.tableName] = Type.Array(Type.Partial(Type.Object(joinSchema.fields)));
|
|
42
|
+
}
|
|
43
|
+
responseProperties.secondaries = Type.Optional(Type.Partial(Type.Object(secondaryProperties)));
|
|
44
|
+
responseProperties.deletions = Type.Optional(Type.Partial(Type.Object(deletionProperties)));
|
|
45
|
+
}
|
|
46
|
+
return Type.Object(responseProperties);
|
|
47
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify';
|
|
2
|
+
import type { SqlApiPluginOptions } from '../types.js';
|
|
3
|
+
export declare function setupSwagger(fastify: FastifyInstance, options: Pick<SqlApiPluginOptions, 'swagger'>): Promise<void>;
|
|
4
|
+
//# sourceMappingURL=setup-swagger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-swagger.d.ts","sourceRoot":"","sources":["../../src/lib/setup-swagger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,mBAAmB,EAAkB,MAAM,aAAa,CAAC;AAEvE,wBAAsB,YAAY,CAChC,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,IAAI,CAAC,mBAAmB,EAAE,SAAS,CAAC,GAC5C,OAAO,CAAC,IAAI,CAAC,CA6Bf"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export async function setupSwagger(fastify, options) {
|
|
2
|
+
if (!options.swagger)
|
|
3
|
+
return;
|
|
4
|
+
if (fastify.hasDecorator('swagger'))
|
|
5
|
+
return;
|
|
6
|
+
try {
|
|
7
|
+
const swaggerMod = await import('@fastify/swagger');
|
|
8
|
+
const swaggerUiMod = await import('@fastify/swagger-ui');
|
|
9
|
+
const swaggerConf = typeof options.swagger === 'object' ? options.swagger : {};
|
|
10
|
+
await fastify.register(swaggerMod.default, {
|
|
11
|
+
openapi: {
|
|
12
|
+
info: {
|
|
13
|
+
title: swaggerConf.title || 'SqlAPI Documentation',
|
|
14
|
+
description: swaggerConf.description || 'Auto-generated API documentation',
|
|
15
|
+
version: swaggerConf.version || '1.0.0',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
await fastify.register(swaggerUiMod.default, {
|
|
20
|
+
routePrefix: swaggerConf.routePrefix || '/documentation',
|
|
21
|
+
indexPrefix: fastify.prefix,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
fastify.log.warn('swagger: true requires @fastify/swagger and @fastify/swagger-ui. Install them: npm i @fastify/swagger @fastify/swagger-ui');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { FastifyRequest } from 'fastify';
|
|
2
|
+
import { QueryClient } from './db.js';
|
|
3
|
+
import { type DialectName } from './dialect.js';
|
|
4
|
+
import type { DbTables, Queryable, FilterRecord, SearchCondition, Paginator, JoinGroupRequest, SearchResult, GetResult, InsertResult, UpdateResult, DeleteResult, BulkUpsertItem, BulkUpsertResult, BulkDeleteResult, TenantId } from '../types.js';
|
|
5
|
+
export interface SqlApiSearchParams {
|
|
6
|
+
filters?: FilterRecord;
|
|
7
|
+
conditions?: SearchCondition[];
|
|
8
|
+
joinFilters?: Record<string, FilterRecord>;
|
|
9
|
+
joins?: Record<string, {
|
|
10
|
+
filters?: FilterRecord;
|
|
11
|
+
}>;
|
|
12
|
+
joinGroups?: Record<string, JoinGroupRequest>;
|
|
13
|
+
orderBy?: string;
|
|
14
|
+
paginator?: Paginator;
|
|
15
|
+
computeMin?: string;
|
|
16
|
+
computeMax?: string;
|
|
17
|
+
computeSum?: string;
|
|
18
|
+
computeAvg?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface SqlApiInsertParams {
|
|
21
|
+
record: Record<string, unknown>;
|
|
22
|
+
secondaries?: Record<string, Record<string, unknown>[]>;
|
|
23
|
+
}
|
|
24
|
+
export interface SqlApiUpdateParams {
|
|
25
|
+
record: Record<string, unknown>;
|
|
26
|
+
secondaries?: Record<string, Record<string, unknown>[]>;
|
|
27
|
+
deletions?: Record<string, Record<string, unknown>[]>;
|
|
28
|
+
}
|
|
29
|
+
export interface SqlApiOptions {
|
|
30
|
+
dialect?: DialectName;
|
|
31
|
+
getTenantId?: (request: FastifyRequest) => TenantId | TenantId[] | null | undefined | Promise<TenantId | TenantId[] | null | undefined>;
|
|
32
|
+
}
|
|
33
|
+
export declare class SqlApi {
|
|
34
|
+
private dbTables;
|
|
35
|
+
private options;
|
|
36
|
+
private db;
|
|
37
|
+
constructor(dbOrPool: QueryClient | Queryable, dbTables: DbTables, options?: SqlApiOptions);
|
|
38
|
+
private getTableConf;
|
|
39
|
+
private getTenant;
|
|
40
|
+
search(tableName: string, params?: SqlApiSearchParams, request?: FastifyRequest): Promise<SearchResult>;
|
|
41
|
+
get(tableName: string, id: string | number, request?: FastifyRequest): Promise<GetResult>;
|
|
42
|
+
insert(tableName: string, params: SqlApiInsertParams, request?: FastifyRequest): Promise<InsertResult>;
|
|
43
|
+
update(tableName: string, params: SqlApiUpdateParams, request?: FastifyRequest): Promise<UpdateResult>;
|
|
44
|
+
delete(tableName: string, id: string | number, request?: FastifyRequest): Promise<DeleteResult>;
|
|
45
|
+
bulkUpsert(tableName: string, items: BulkUpsertItem[], request?: FastifyRequest): Promise<BulkUpsertResult[]>;
|
|
46
|
+
bulkDelete(tableName: string, ids: (string | number)[], request?: FastifyRequest): Promise<BulkDeleteResult[]>;
|
|
47
|
+
}
|
|
48
|
+
export declare function createSqlApi(dbOrPool: QueryClient | Queryable, dbTables: DbTables, options?: SqlApiOptions): SqlApi;
|
|
49
|
+
//# sourceMappingURL=sql-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql-api.d.ts","sourceRoot":"","sources":["../../src/lib/sql-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { ConditionBuilder } from 'node-condition-builder';
|
|
2
|
+
import { createQueryClient, QueryClient } from './db.js';
|
|
3
|
+
import { getDialect } from './dialect.js';
|
|
4
|
+
import { resolveTenant } from './tenant.js';
|
|
5
|
+
import { searchEngine } from './engine/search/search.js';
|
|
6
|
+
import { getEngine } from './engine/rest/get.js';
|
|
7
|
+
import { insertEngine } from './engine/rest/insert.js';
|
|
8
|
+
import { updateEngine } from './engine/rest/update.js';
|
|
9
|
+
import { deleteEngine } from './engine/rest/delete.js';
|
|
10
|
+
import { bulkUpsertEngine } from './engine/bulk/bulk-upsert.js';
|
|
11
|
+
import { bulkDeleteEngine } from './engine/bulk/bulk-delete.js';
|
|
12
|
+
export class SqlApi {
|
|
13
|
+
dbTables;
|
|
14
|
+
options;
|
|
15
|
+
db;
|
|
16
|
+
constructor(dbOrPool, dbTables, options = {}) {
|
|
17
|
+
this.dbTables = dbTables;
|
|
18
|
+
this.options = options;
|
|
19
|
+
// Accept either a QueryClient or a raw Queryable pool
|
|
20
|
+
if (dbOrPool instanceof QueryClient) {
|
|
21
|
+
this.db = dbOrPool;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
this.db = createQueryClient(dbOrPool, options.dialect);
|
|
25
|
+
}
|
|
26
|
+
// Set ConditionBuilder dialect (used by filters built via exportTableInfo)
|
|
27
|
+
if (options.dialect) {
|
|
28
|
+
const dialect = getDialect(options.dialect);
|
|
29
|
+
ConditionBuilder.DIALECT = dialect.cbDialect;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
getTableConf(tableName) {
|
|
33
|
+
const conf = this.dbTables[tableName];
|
|
34
|
+
if (!conf) {
|
|
35
|
+
const err = new Error(`Table "${tableName}" not found in DbTables`);
|
|
36
|
+
err.statusCode = 400;
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
return conf;
|
|
40
|
+
}
|
|
41
|
+
async getTenant(tableName, request) {
|
|
42
|
+
if (!request || !this.options.getTenantId)
|
|
43
|
+
return undefined;
|
|
44
|
+
const tableConf = this.getTableConf(tableName);
|
|
45
|
+
return resolveTenant({ DbTables: this.dbTables, getTenantId: this.options.getTenantId }, tableConf, request);
|
|
46
|
+
}
|
|
47
|
+
async search(tableName, params = {}, request) {
|
|
48
|
+
const tableConf = this.getTableConf(tableName);
|
|
49
|
+
const tenant = await this.getTenant(tableName, request);
|
|
50
|
+
return searchEngine(this.dbTables, {
|
|
51
|
+
db: this.db,
|
|
52
|
+
tableConf,
|
|
53
|
+
filters: params.filters,
|
|
54
|
+
conditions: params.conditions,
|
|
55
|
+
joinFilters: params.joinFilters,
|
|
56
|
+
joins: params.joins,
|
|
57
|
+
joinGroups: params.joinGroups,
|
|
58
|
+
orderBy: params.orderBy,
|
|
59
|
+
paginator: params.paginator,
|
|
60
|
+
computeMin: params.computeMin,
|
|
61
|
+
computeMax: params.computeMax,
|
|
62
|
+
computeSum: params.computeSum,
|
|
63
|
+
computeAvg: params.computeAvg,
|
|
64
|
+
tenant,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async get(tableName, id, request) {
|
|
68
|
+
const tableConf = this.getTableConf(tableName);
|
|
69
|
+
const tenant = await this.getTenant(tableName, request);
|
|
70
|
+
return getEngine({ db: this.db, tableConf, id, tenant });
|
|
71
|
+
}
|
|
72
|
+
async insert(tableName, params, request) {
|
|
73
|
+
const tableConf = this.getTableConf(tableName);
|
|
74
|
+
const tenant = await this.getTenant(tableName, request);
|
|
75
|
+
return insertEngine({
|
|
76
|
+
db: this.db,
|
|
77
|
+
tableConf,
|
|
78
|
+
dbTables: this.dbTables,
|
|
79
|
+
request: request,
|
|
80
|
+
record: params.record,
|
|
81
|
+
secondaries: params.secondaries,
|
|
82
|
+
tenant,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async update(tableName, params, request) {
|
|
86
|
+
const tableConf = this.getTableConf(tableName);
|
|
87
|
+
const tenant = await this.getTenant(tableName, request);
|
|
88
|
+
return updateEngine({
|
|
89
|
+
db: this.db,
|
|
90
|
+
tableConf,
|
|
91
|
+
dbTables: this.dbTables,
|
|
92
|
+
request: request,
|
|
93
|
+
record: params.record,
|
|
94
|
+
secondaries: params.secondaries,
|
|
95
|
+
deletions: params.deletions,
|
|
96
|
+
tenant,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
async delete(tableName, id, request) {
|
|
100
|
+
const tableConf = this.getTableConf(tableName);
|
|
101
|
+
const tenant = await this.getTenant(tableName, request);
|
|
102
|
+
return deleteEngine({ db: this.db, tableConf, id, tenant });
|
|
103
|
+
}
|
|
104
|
+
async bulkUpsert(tableName, items, request) {
|
|
105
|
+
const tableConf = this.getTableConf(tableName);
|
|
106
|
+
const tenant = await this.getTenant(tableName, request);
|
|
107
|
+
return bulkUpsertEngine({
|
|
108
|
+
db: this.db,
|
|
109
|
+
tableConf,
|
|
110
|
+
dbTables: this.dbTables,
|
|
111
|
+
request: request,
|
|
112
|
+
items,
|
|
113
|
+
tenant,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
async bulkDelete(tableName, ids, request) {
|
|
117
|
+
const tableConf = this.getTableConf(tableName);
|
|
118
|
+
const tenant = await this.getTenant(tableName, request);
|
|
119
|
+
return bulkDeleteEngine({ db: this.db, tableConf, ids, tenant });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
export function createSqlApi(dbOrPool, dbTables, options) {
|
|
123
|
+
return new SqlApi(dbOrPool, dbTables, options);
|
|
124
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ConditionBuilder, type ConditionValueOrUndefined } from 'node-condition-builder';
|
|
2
|
+
import type { TSchema, TObject } from '@sinclair/typebox';
|
|
3
|
+
import type { SchemaDefinition, JoinDefinition, TableFilterFn, ITable } from '../types.js';
|
|
4
|
+
type ExtraProps<EF> = EF extends TObject<infer P> ? P : EF extends Record<string, TSchema> ? EF : Record<string, never>;
|
|
5
|
+
export declare function exportTableInfo<F extends Record<string, TSchema>, EF extends TObject | Record<string, TSchema> = Record<never, TSchema>>(Schema: SchemaDefinition<F>, extraFilters?: EF, extendedCondition?: (condition: ConditionBuilder, filters: {
|
|
6
|
+
[K in keyof F | keyof ExtraProps<EF>]?: ConditionValueOrUndefined;
|
|
7
|
+
}) => void): {
|
|
8
|
+
Schema: SchemaDefinition<F>;
|
|
9
|
+
filters: TableFilterFn;
|
|
10
|
+
extraFilters: Record<string, TSchema>;
|
|
11
|
+
};
|
|
12
|
+
export declare function defineTable<F extends Record<string, TSchema>>(config: ITable<F>): ITable<F>;
|
|
13
|
+
export declare function buildRelation<M extends Record<string, TSchema>, J extends Record<string, TSchema>>(mainSchema: SchemaDefinition<M>, mainField: string & keyof M | (string & keyof M)[], joinSchema: SchemaDefinition<J>, joinField: string & keyof J, selection?: string): JoinDefinition;
|
|
14
|
+
export declare function buildUpsertRule<F extends Record<string, TSchema>>(schema: SchemaDefinition<F>, columns: (string & keyof F)[]): [SchemaDefinition, string[]];
|
|
15
|
+
export declare function buildUpsertRules(...rules: [SchemaDefinition, string[]][]): Map<SchemaDefinition, string[]>;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=table-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table-helpers.d.ts","sourceRoot":"","sources":["../../src/lib/table-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,KAAK,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EACV,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,MAAM,EACP,MAAM,aAAa,CAAC;AAGrB,KAAK,UAAU,CAAC,EAAE,IAAI,EAAE,SAAS,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAExH,wBAAgB,eAAe,CAC7B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,EAAE,SAAS,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,EAErE,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC3B,YAAY,GAAE,EAAa,EAC3B,iBAAiB,CAAC,EAAE,CAClB,SAAS,EAAE,gBAAgB,EAC3B,OAAO,EAAE;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,yBAAyB;CAAE,KAC3E,IAAI,GACR;IAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAAC,OAAO,EAAE,aAAa,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CA0BhG;AAED,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3D,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,GAChB,MAAM,CAAC,CAAC,CAAC,CAEX;AAED,wBAAgB,aAAa,CAC3B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAEjC,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC/B,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,EAAE,EAClD,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC/B,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,EAC3B,SAAS,GAAE,MAAY,GACtB,cAAc,CAEhB;AAED,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC3B,OAAO,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,EAAE,GAC5B,CAAC,gBAAgB,EAAE,MAAM,EAAE,CAAC,CAE9B;AAED,wBAAgB,gBAAgB,CAC9B,GAAG,KAAK,EAAE,CAAC,gBAAgB,EAAE,MAAM,EAAE,CAAC,EAAE,GACvC,GAAG,CAAC,gBAAgB,EAAE,MAAM,EAAE,CAAC,CAEjC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ConditionBuilder } from 'node-condition-builder';
|
|
2
|
+
export function exportTableInfo(Schema, extraFilters = {}, extendedCondition) {
|
|
3
|
+
const filters = (filterValues) => {
|
|
4
|
+
const condition = new ConditionBuilder('AND');
|
|
5
|
+
// Only auto-match real schema fields (DB columns)
|
|
6
|
+
for (const field of Object.keys(Schema.fields)) {
|
|
7
|
+
if (field in filterValues && filterValues[field] !== null && filterValues[field] !== undefined) {
|
|
8
|
+
condition.isEqual(Schema.col(field), filterValues[field]);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
// extraFilters are handled exclusively by extendedCondition
|
|
12
|
+
if (extendedCondition) {
|
|
13
|
+
extendedCondition(condition, filterValues);
|
|
14
|
+
}
|
|
15
|
+
return condition;
|
|
16
|
+
};
|
|
17
|
+
// Extract properties from TObject or use as-is
|
|
18
|
+
const efRecord = (extraFilters && typeof extraFilters === 'object' && 'properties' in extraFilters)
|
|
19
|
+
? extraFilters.properties
|
|
20
|
+
: extraFilters;
|
|
21
|
+
return { Schema, filters, extraFilters: efRecord };
|
|
22
|
+
}
|
|
23
|
+
export function defineTable(config) {
|
|
24
|
+
return config;
|
|
25
|
+
}
|
|
26
|
+
export function buildRelation(mainSchema, mainField, joinSchema, joinField, selection = '*') {
|
|
27
|
+
return [joinSchema, joinField, mainField, selection];
|
|
28
|
+
}
|
|
29
|
+
export function buildUpsertRule(schema, columns) {
|
|
30
|
+
return [schema, columns];
|
|
31
|
+
}
|
|
32
|
+
export function buildUpsertRules(...rules) {
|
|
33
|
+
return new Map(rules);
|
|
34
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { FastifyRequest } from 'fastify';
|
|
2
|
+
import type { QueryClient } from './db.js';
|
|
3
|
+
import { ConditionBuilder, type ConditionValue, type ConditionValueOrUndefined } from 'node-condition-builder';
|
|
4
|
+
import type { ITable, SqlApiPluginOptions, TenantId, TenantScope, TenantScopeIndirect, TenantContext } from '../types.js';
|
|
5
|
+
export declare function resolveTenant(options: SqlApiPluginOptions, tableConf: ITable, request: FastifyRequest): Promise<TenantContext | undefined>;
|
|
6
|
+
export declare function buildTenantCondition(db: QueryClient, scope: TenantScope, tenantIds: TenantId[]): ConditionBuilder;
|
|
7
|
+
export declare function buildTenantJoin(db: QueryClient, scope: TenantScopeIndirect, mainTableName: string): string;
|
|
8
|
+
export declare function injectTenantValue(record: Record<string, unknown>, scope: TenantScope, tenantIds: TenantId[]): void;
|
|
9
|
+
export declare function validateTenantFK(db: QueryClient, scope: TenantScopeIndirect, tenantIds: TenantId[], fkValues: unknown[]): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Build WHERE clause for DELETE with tenant filtering.
|
|
12
|
+
* Handles both direct (simple AND) and indirect (subquery via JOIN) scopes.
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildTenantDeleteWhere(db: QueryClient, tableName: string, pkCol: string, pkValue: ConditionValueOrUndefined | ConditionValue[], tenant: TenantContext): {
|
|
15
|
+
where: string;
|
|
16
|
+
values: unknown[];
|
|
17
|
+
};
|
|
18
|
+
export declare function stripTenantColumn(fields: Record<string, unknown>, scope: TenantScope): void;
|
|
19
|
+
//# sourceMappingURL=tenant.d.ts.map
|
|
@@ -0,0 +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;AAC/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,CAqBN;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,CA2Bf;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,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,KAAK,EAAE,WAAW,GACjB,IAAI,CAGN"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { ConditionBuilder } from 'node-condition-builder';
|
|
2
|
+
function isIndirect(scope) {
|
|
3
|
+
return 'through' in scope;
|
|
4
|
+
}
|
|
5
|
+
export async function resolveTenant(options, tableConf, request) {
|
|
6
|
+
if (!options.getTenantId)
|
|
7
|
+
return undefined;
|
|
8
|
+
if (!tableConf.tenantScope)
|
|
9
|
+
return undefined;
|
|
10
|
+
const raw = await options.getTenantId(request);
|
|
11
|
+
if (raw == null)
|
|
12
|
+
return undefined; // admin
|
|
13
|
+
const ids = Array.isArray(raw) ? raw : [raw];
|
|
14
|
+
return { ids, scope: tableConf.tenantScope };
|
|
15
|
+
}
|
|
16
|
+
export function buildTenantCondition(db, scope, tenantIds) {
|
|
17
|
+
const col = scope.column;
|
|
18
|
+
let qualifier;
|
|
19
|
+
if (isIndirect(scope)) {
|
|
20
|
+
const throughTable = scope.through.schema.tableName;
|
|
21
|
+
qualifier = `${db.qi(throughTable)}.${db.qi(col)}`;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
qualifier = db.qi(col);
|
|
25
|
+
}
|
|
26
|
+
const cb = new ConditionBuilder('AND');
|
|
27
|
+
cb.isIn(qualifier, tenantIds);
|
|
28
|
+
return cb;
|
|
29
|
+
}
|
|
30
|
+
export function buildTenantJoin(db, scope, mainTableName) {
|
|
31
|
+
const throughTable = scope.through.schema.tableName;
|
|
32
|
+
const localField = scope.through.localField;
|
|
33
|
+
const foreignField = scope.through.foreignField;
|
|
34
|
+
return `INNER JOIN ${db.qi(throughTable)} ON ${db.qi(mainTableName)}.${db.qi(localField)} = ${db.qi(throughTable)}.${db.qi(foreignField)}`;
|
|
35
|
+
}
|
|
36
|
+
export function injectTenantValue(record, scope, tenantIds) {
|
|
37
|
+
if (isIndirect(scope))
|
|
38
|
+
return;
|
|
39
|
+
const col = scope.column;
|
|
40
|
+
if (col in record) {
|
|
41
|
+
if (!tenantIds.includes(record[col])) {
|
|
42
|
+
const err = new Error('Access denied: tenant value does not match');
|
|
43
|
+
err.statusCode = 403;
|
|
44
|
+
throw err;
|
|
45
|
+
}
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (tenantIds.length === 1) {
|
|
49
|
+
record[col] = tenantIds[0];
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const err = new Error('Ambiguous tenant: specify the tenant value');
|
|
53
|
+
err.statusCode = 400;
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
export async function validateTenantFK(db, scope, tenantIds, fkValues) {
|
|
57
|
+
if (!fkValues.length)
|
|
58
|
+
return;
|
|
59
|
+
const uniqueFKs = [...new Set(fkValues.filter((v) => v != null))];
|
|
60
|
+
if (!uniqueFKs.length)
|
|
61
|
+
return;
|
|
62
|
+
const throughTable = scope.through.schema.tableName;
|
|
63
|
+
const foreignField = scope.through.foreignField;
|
|
64
|
+
const tenantCol = scope.column;
|
|
65
|
+
const cb = new ConditionBuilder('AND');
|
|
66
|
+
cb.isIn(db.qi(foreignField), uniqueFKs);
|
|
67
|
+
cb.isNotIn(db.qi(tenantCol), tenantIds);
|
|
68
|
+
const where = cb.build(1, db.ph);
|
|
69
|
+
const values = cb.getValues();
|
|
70
|
+
const sql = `SELECT ${db.qi(foreignField)} FROM ${db.qi(throughTable)} ` +
|
|
71
|
+
`WHERE ${where}`;
|
|
72
|
+
const result = await db.query(sql, values);
|
|
73
|
+
if (result.rows.length > 0) {
|
|
74
|
+
const err = new Error('Access denied: records do not belong to tenant');
|
|
75
|
+
err.statusCode = 403;
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Build WHERE clause for DELETE with tenant filtering.
|
|
81
|
+
* Handles both direct (simple AND) and indirect (subquery via JOIN) scopes.
|
|
82
|
+
*/
|
|
83
|
+
export function buildTenantDeleteWhere(db, tableName, pkCol, pkValue, tenant) {
|
|
84
|
+
if (isIndirect(tenant.scope)) {
|
|
85
|
+
const innerCb = new ConditionBuilder('AND');
|
|
86
|
+
innerCb.isIn(`${db.qi(tableName)}.${db.qi(pkCol)}`, (Array.isArray(pkValue) ? pkValue : [pkValue]));
|
|
87
|
+
innerCb.append(buildTenantCondition(db, tenant.scope, tenant.ids));
|
|
88
|
+
const innerWhere = innerCb.build(1, db.ph);
|
|
89
|
+
const values = innerCb.getValues();
|
|
90
|
+
const joinSql = buildTenantJoin(db, tenant.scope, tableName);
|
|
91
|
+
const where = `${db.qi(pkCol)} IN (SELECT ${db.qi(tableName)}.${db.qi(pkCol)} FROM ${db.qi(tableName)} ${joinSql} WHERE ${innerWhere})`;
|
|
92
|
+
return { where, values };
|
|
93
|
+
}
|
|
94
|
+
const cb = new ConditionBuilder('AND');
|
|
95
|
+
if (Array.isArray(pkValue)) {
|
|
96
|
+
cb.isIn(db.qi(pkCol), pkValue);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
cb.isEqual(db.qi(pkCol), pkValue);
|
|
100
|
+
}
|
|
101
|
+
cb.append(buildTenantCondition(db, tenant.scope, tenant.ids));
|
|
102
|
+
const where = cb.build(1, db.ph);
|
|
103
|
+
const values = cb.getValues();
|
|
104
|
+
return { where, values };
|
|
105
|
+
}
|
|
106
|
+
export function stripTenantColumn(fields, scope) {
|
|
107
|
+
if (isIndirect(scope))
|
|
108
|
+
return;
|
|
109
|
+
delete fields[scope.column];
|
|
110
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify';
|
|
2
|
+
import type { SqlApiPluginOptions } from '../../types.js';
|
|
3
|
+
export default function bulkDeleteRoutes(fastify: FastifyInstance, options: SqlApiPluginOptions): Promise<void>;
|
|
4
|
+
//# sourceMappingURL=bulk-delete.routes.d.ts.map
|
|
@@ -0,0 +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;AAI/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,CAyBf"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { BulkDeleteTableBody, BulkDeleteTableResponse } from '../../lib/schema/bulk-delete.js';
|
|
2
|
+
import { primaryAsString } from '../../types.js';
|
|
3
|
+
import { mergeOnRequests } from './route-helpers.js';
|
|
4
|
+
export default async function bulkDeleteRoutes(fastify, options) {
|
|
5
|
+
const { DbTables } = options;
|
|
6
|
+
for (const [tableName, tableConf] of Object.entries(DbTables)) {
|
|
7
|
+
const bodySchema = BulkDeleteTableBody(DbTables, tableName);
|
|
8
|
+
const responseSchema = BulkDeleteTableResponse(DbTables, tableName);
|
|
9
|
+
fastify.route({
|
|
10
|
+
method: 'POST',
|
|
11
|
+
url: `/bulk/${tableConf.Schema.tableName}/delete`,
|
|
12
|
+
schema: {
|
|
13
|
+
body: bodySchema,
|
|
14
|
+
response: { 200: responseSchema },
|
|
15
|
+
tags: [`SqlAPI-${tableName}`],
|
|
16
|
+
summary: `Bulk delete ${tableName}`,
|
|
17
|
+
description: `Delete multiple records from ${tableName} by primary key`,
|
|
18
|
+
},
|
|
19
|
+
onRequest: mergeOnRequests(options, tableConf),
|
|
20
|
+
handler: async (request, reply) => {
|
|
21
|
+
const items = request.body;
|
|
22
|
+
const ids = items.map((item) => item[primaryAsString(tableConf.primary)]);
|
|
23
|
+
reply.send(await fastify.sqlApi.bulkDelete(tableName, ids, request));
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify';
|
|
2
|
+
import type { SqlApiPluginOptions } from '../../types.js';
|
|
3
|
+
export default function bulkUpsertRoutes(fastify: FastifyInstance, options: SqlApiPluginOptions): Promise<void>;
|
|
4
|
+
//# sourceMappingURL=bulk-upsert.routes.d.ts.map
|
|
@@ -0,0 +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;AAG/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,CAwBf"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { BulkUpsertTableBody, BulkUpsertTableResponse } from '../../lib/schema/bulk-upsert.js';
|
|
2
|
+
import { mergeOnRequests, buildWriteDescription } from './route-helpers.js';
|
|
3
|
+
export default async function bulkUpsertRoutes(fastify, options) {
|
|
4
|
+
const { DbTables } = options;
|
|
5
|
+
for (const [tableName, tableConf] of Object.entries(DbTables)) {
|
|
6
|
+
const bodySchema = BulkUpsertTableBody(DbTables, tableName);
|
|
7
|
+
const responseSchema = BulkUpsertTableResponse(DbTables, tableName);
|
|
8
|
+
fastify.route({
|
|
9
|
+
method: 'PUT',
|
|
10
|
+
url: `/bulk/${tableConf.Schema.tableName}`,
|
|
11
|
+
schema: {
|
|
12
|
+
body: bodySchema,
|
|
13
|
+
response: { 200: responseSchema },
|
|
14
|
+
tags: [`SqlAPI-${tableName}`],
|
|
15
|
+
summary: `Bulk upsert ${tableName}`,
|
|
16
|
+
description: buildWriteDescription('Bulk upsert records in', tableName, tableConf),
|
|
17
|
+
},
|
|
18
|
+
onRequest: mergeOnRequests(options, tableConf),
|
|
19
|
+
handler: async (request, reply) => {
|
|
20
|
+
const items = request.body;
|
|
21
|
+
reply.send(await fastify.sqlApi.bulkUpsert(tableName, items, request));
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|