bradb 1.2.4 → 2.0.0
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/dist/bin/brad.d.ts +2 -0
- package/dist/bin/brad.js +52 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/src/commands.d.ts +20 -0
- package/dist/src/commands.js +116 -0
- package/dist/src/controller.d.ts +20 -8
- package/dist/src/controller.js +27 -16
- package/dist/src/filters.d.ts +3 -1
- package/dist/src/filters.js +8 -0
- package/dist/src/service.d.ts +24 -0
- package/dist/src/service.js +153 -0
- package/dist/src/types.d.ts +9 -9
- package/package.json +4 -1
- package/dist/src/standard.d.ts +0 -19
- package/dist/src/standard.js +0 -92
package/dist/bin/brad.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const commands_1 = __importDefault(require("../src/commands"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
const main = async () => {
|
|
12
|
+
// Load config file
|
|
13
|
+
const configPath = path_1.default.resolve(process.cwd(), 'brad.config.mjs');
|
|
14
|
+
let config = {};
|
|
15
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
16
|
+
try {
|
|
17
|
+
const loadedConfig = require(configPath);
|
|
18
|
+
config = loadedConfig.default || loadedConfig;
|
|
19
|
+
console.log(config);
|
|
20
|
+
console.log("Loaded brad.config.js");
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
console.error("Error loading brad.config.js:", e);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
console.warn("Warning: brad.config.js not found. Using default settings.");
|
|
29
|
+
}
|
|
30
|
+
if (args.length === 0) {
|
|
31
|
+
console.log("Brad CLI - Available commands: make:entity");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const command = args[0];
|
|
35
|
+
if (command === 'make:entity') {
|
|
36
|
+
const entityName = args[1];
|
|
37
|
+
if (!entityName) {
|
|
38
|
+
console.error("Error: Entity name is required. Usage: brad make:entity <EntityName>");
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
let basePath = 'src'; // Default path
|
|
42
|
+
const pathArg = args.find(arg => arg.startsWith('--path='));
|
|
43
|
+
if (pathArg) {
|
|
44
|
+
basePath = pathArg.split('=')[1];
|
|
45
|
+
}
|
|
46
|
+
await commands_1.default.createEntity(entityName, basePath, config);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
console.error(`Error: Unknown command '${command}'`);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
main();
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./src/types"), exports);
|
|
18
18
|
__exportStar(require("./src/controller"), exports);
|
|
19
|
-
__exportStar(require("./src/
|
|
19
|
+
__exportStar(require("./src/service"), exports);
|
|
20
20
|
__exportStar(require("./src/relational"), exports);
|
|
21
21
|
__exportStar(require("./src/filters"), exports);
|
|
22
22
|
__exportStar(require("./src/errors"), exports);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
type BradConfig = {
|
|
2
|
+
dbConnection?: string;
|
|
3
|
+
schemas?: string;
|
|
4
|
+
routesDir?: string;
|
|
5
|
+
controllerDir?: string;
|
|
6
|
+
serviceDir?: string;
|
|
7
|
+
};
|
|
8
|
+
declare class Commander {
|
|
9
|
+
config: Required<Omit<BradConfig, 'dbConnection' | 'schemas'>>;
|
|
10
|
+
constructor(config?: BradConfig);
|
|
11
|
+
private capitalize;
|
|
12
|
+
private toCamelCase;
|
|
13
|
+
private formatImportPath;
|
|
14
|
+
createEntity(entityName: string, basePath?: string, config?: BradConfig): Promise<void>;
|
|
15
|
+
private createServiceFile;
|
|
16
|
+
private createControllerFile;
|
|
17
|
+
private createRouterFile;
|
|
18
|
+
}
|
|
19
|
+
declare const _default: Commander;
|
|
20
|
+
export default _default;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const defaultConfig = {
|
|
9
|
+
routesDir: "routers",
|
|
10
|
+
controllerDir: "controllers",
|
|
11
|
+
serviceDir: "services"
|
|
12
|
+
};
|
|
13
|
+
class Commander {
|
|
14
|
+
constructor(config = {}) {
|
|
15
|
+
this.config = { ...defaultConfig, ...config };
|
|
16
|
+
}
|
|
17
|
+
capitalize(str) {
|
|
18
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
19
|
+
}
|
|
20
|
+
toCamelCase(str) {
|
|
21
|
+
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
22
|
+
}
|
|
23
|
+
formatImportPath(importPath) {
|
|
24
|
+
const parsed = path_1.default.parse(importPath);
|
|
25
|
+
return path_1.default.join(parsed.dir, parsed.name).replace(/\\/g, '/');
|
|
26
|
+
}
|
|
27
|
+
async createEntity(entityName, basePath = 'src', config = {}) {
|
|
28
|
+
const capitalizedName = this.capitalize(entityName);
|
|
29
|
+
const camelCaseName = this.toCamelCase(entityName);
|
|
30
|
+
const controllerDir = path_1.default.resolve(basePath, this.config.controllerDir);
|
|
31
|
+
const serviceDir = path_1.default.resolve(basePath, this.config.serviceDir);
|
|
32
|
+
const routerDir = path_1.default.resolve(basePath, this.config.routesDir);
|
|
33
|
+
try {
|
|
34
|
+
await fs_1.promises.mkdir(controllerDir, { recursive: true });
|
|
35
|
+
await fs_1.promises.mkdir(serviceDir, { recursive: true });
|
|
36
|
+
await fs_1.promises.mkdir(routerDir, { recursive: true });
|
|
37
|
+
await this.createServiceFile(serviceDir, entityName, camelCaseName, config);
|
|
38
|
+
await this.createControllerFile(controllerDir, entityName, camelCaseName);
|
|
39
|
+
await this.createRouterFile(routerDir, entityName, camelCaseName);
|
|
40
|
+
console.log(`Entity ${entityName} created successfully!`);
|
|
41
|
+
console.log(`- ${path_1.default.join(serviceDir, `${entityName}.service.ts`)}`);
|
|
42
|
+
console.log(`- ${path_1.default.join(controllerDir, `${entityName}.controller.ts`)}`);
|
|
43
|
+
console.log(`- ${path_1.default.join(routerDir, `${entityName}.router.ts`)}`);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
console.error("Error creating entity:", error);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async createServiceFile(dir, name, camelCaseName, config) {
|
|
50
|
+
let dbImport = `import { db } from '../db'; // fallback`;
|
|
51
|
+
let schemaImport = `import { ${camelCaseName}Table } from '../schema'; // fallback`;
|
|
52
|
+
if (config.dbConnection) {
|
|
53
|
+
const dbPath = path_1.default.relative(dir, path_1.default.resolve(config.dbConnection));
|
|
54
|
+
dbImport = `import { db } from '${this.formatImportPath(dbPath)}';`;
|
|
55
|
+
}
|
|
56
|
+
if (config.schemas) {
|
|
57
|
+
const schemaPath = path_1.default.relative(dir, path_1.default.resolve(config.schemas));
|
|
58
|
+
schemaImport = `import { ${camelCaseName}Table } from '${this.formatImportPath(schemaPath)}';`;
|
|
59
|
+
}
|
|
60
|
+
const serviceTemplate = `import { ServiceBuilder } from 'bradb';
|
|
61
|
+
${dbImport}
|
|
62
|
+
${schemaImport}
|
|
63
|
+
|
|
64
|
+
// TODO: Define your filter map
|
|
65
|
+
// import { ${camelCaseName}FilterMap } from '../filter';
|
|
66
|
+
|
|
67
|
+
const builder = new ServiceBuilder(db, ${camelCaseName}Table, {});
|
|
68
|
+
|
|
69
|
+
export const ${camelCaseName}Service = {
|
|
70
|
+
create: builder.create(),
|
|
71
|
+
update: builder.update(),
|
|
72
|
+
delete: builder.softDelete(),
|
|
73
|
+
findOne: builder.findOne(
|
|
74
|
+
db.select().from(${camelCaseName}Table).$dynamic()
|
|
75
|
+
),
|
|
76
|
+
findAll: builder.findAll(
|
|
77
|
+
db.select().from(${camelCaseName}Table).$dynamic()
|
|
78
|
+
),
|
|
79
|
+
count: builder.count()
|
|
80
|
+
};
|
|
81
|
+
`;
|
|
82
|
+
await fs_1.promises.writeFile(path_1.default.join(dir, `${name}.service.ts`), serviceTemplate);
|
|
83
|
+
}
|
|
84
|
+
async createControllerFile(dir, name, camelCaseName) {
|
|
85
|
+
const controllerTemplate = `import { createBuilder, findAllBuilder, findOneBuilder, updateBuilder, deleteBuilder } from 'bradb';
|
|
86
|
+
import { ${camelCaseName}Service } from '../services/${name}.service';
|
|
87
|
+
// import { ${camelCaseName}CreateSchema, ${camelCaseName}UpdateSchema } from '../validators'; // Adjust this import
|
|
88
|
+
|
|
89
|
+
export const ${camelCaseName}Controller = {
|
|
90
|
+
create: createBuilder(${camelCaseName}Service), // Add schema validator as second argument
|
|
91
|
+
findAll: findAllBuilder(${camelCaseName}Service),
|
|
92
|
+
findOne: findOneBuilder(${camelCaseName}Service),
|
|
93
|
+
update: updateBuilder(${camelCaseName}Service), // Add schema validator as second argument
|
|
94
|
+
delete: deleteBuilder(${camelCaseName}Service)
|
|
95
|
+
};
|
|
96
|
+
`;
|
|
97
|
+
await fs_1.promises.writeFile(path_1.default.join(dir, `${name}.controller.ts`), controllerTemplate);
|
|
98
|
+
}
|
|
99
|
+
async createRouterFile(dir, name, camelCaseName) {
|
|
100
|
+
const routerTemplate = `import { Router } from 'express';
|
|
101
|
+
import { ${camelCaseName}Controller } from '../controllers/${name}.controller';
|
|
102
|
+
|
|
103
|
+
const router = Router();
|
|
104
|
+
|
|
105
|
+
router.get('/', ${camelCaseName}Controller.findAll);
|
|
106
|
+
router.get('/:id', ${camelCaseName}Controller.findOne);
|
|
107
|
+
router.post('/', ${camelCaseName}Controller.create);
|
|
108
|
+
router.put('/:id', ${camelCaseName}Controller.update);
|
|
109
|
+
router.delete('/:id', ${camelCaseName}Controller.delete);
|
|
110
|
+
|
|
111
|
+
export default router;
|
|
112
|
+
`;
|
|
113
|
+
await fs_1.promises.writeFile(path_1.default.join(dir, `${name}.router.ts`), routerTemplate);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
exports.default = new Commander();
|
package/dist/src/controller.d.ts
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
import { PgTable } from "drizzle-orm/pg-core";
|
|
2
2
|
import { Request, Response } from "express";
|
|
3
3
|
import { z, ZodObject } from "zod";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
export declare function
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export declare function
|
|
4
|
+
import { Filter } from "./types";
|
|
5
|
+
import { InferInsertModel } from "drizzle-orm";
|
|
6
|
+
export declare function findOneBuilder<TReturn>(service: {
|
|
7
|
+
findOne: (id: any) => Promise<TReturn>;
|
|
8
|
+
}, hook?: (item: TReturn) => void): (req: Request, res: Response) => Promise<void>;
|
|
9
|
+
export declare function findAllBuilder<FSchema extends ZodObject, TReturn>(service: {
|
|
10
|
+
findAll: (filters?: Filter<FSchema>, page?: number, pageSize?: number) => Promise<TReturn[]>;
|
|
11
|
+
count: (filters?: Filter<FSchema>) => Promise<number>;
|
|
12
|
+
}, filter: FSchema, hook?: (items: TReturn[], total: number) => void, hasPagination?: boolean): (req: Request, res: Response) => Promise<Response<any, Record<string, any>>>;
|
|
13
|
+
export declare function createBuilder<CSchema extends ZodObject, TReturn>(service: {
|
|
14
|
+
create: (data: z.core.output<CSchema>) => Promise<TReturn>;
|
|
15
|
+
}, schema: CSchema, hook?: (req: Request, res: Response, data: z.core.output<CSchema>, item: TReturn) => Promise<object>): (req: Request, res: Response) => Promise<Response<any, Record<string, any>>>;
|
|
16
|
+
export declare function updateBuilder<USchema extends ZodObject, TReturn>(service: {
|
|
17
|
+
update: (id: any, data: z.core.output<USchema>) => Promise<TReturn>;
|
|
18
|
+
}, schema: USchema): (req: Request, res: Response) => Promise<Response<any, Record<string, any>>>;
|
|
19
|
+
export declare function deleteBuilder(service: {
|
|
20
|
+
delete: (id: any) => Promise<void>;
|
|
21
|
+
}): (req: Request, res: Response) => Promise<Response<any, Record<string, any>>>;
|
|
10
22
|
export declare function validate<T extends ZodObject>(schema: T, fn: (req: Request, res: Response, data: z.output<T>) => Promise<Response>): (req: Request, res: Response) => Promise<Response<any, Record<string, any>>>;
|
|
11
23
|
interface CRUDService<FSchema extends ZodObject> {
|
|
12
24
|
findOne: (id: any) => Promise<any>;
|
|
13
|
-
findAll: (filters?: Filter<FSchema>, page?: number, pageSize?: number) => Promise<any>;
|
|
25
|
+
findAll: (filters?: Filter<FSchema>, page?: number, pageSize?: number) => Promise<any[]>;
|
|
14
26
|
count: (filters: Filter<FSchema>) => Promise<number>;
|
|
15
27
|
create: (data: any) => Promise<any>;
|
|
16
28
|
update: (id: any, data: Partial<any>) => Promise<any>;
|
|
@@ -21,7 +33,7 @@ export declare class BaseController<T extends PgTable, FSchema extends z.ZodObje
|
|
|
21
33
|
private filterSchema;
|
|
22
34
|
private createSchema;
|
|
23
35
|
private updateSchema;
|
|
24
|
-
constructor(service: CRUDService<FSchema>, base: z.ZodObject, filter: FSchema);
|
|
36
|
+
constructor(service: CRUDService<FSchema>, base: z.ZodObject, filter: FSchema, createSchema?: z.ZodSchema<InferInsertModel<T>>, updateSchema?: Partial<InferInsertModel<T>>);
|
|
25
37
|
getAll: (req: Request, res: Response) => Promise<void>;
|
|
26
38
|
getById: (req: Request, res: Response) => Promise<void>;
|
|
27
39
|
create: (req: Request, res: Response) => Promise<void>;
|
package/dist/src/controller.js
CHANGED
|
@@ -11,39 +11,50 @@ function findOneBuilder(service, hook) {
|
|
|
11
11
|
return async (req, res) => {
|
|
12
12
|
const item = await service.findOne(req.params.id);
|
|
13
13
|
if (hook !== undefined) {
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
else {
|
|
17
|
-
res.status(200).json(item);
|
|
14
|
+
hook(item);
|
|
18
15
|
}
|
|
16
|
+
res.status(200).json(item);
|
|
19
17
|
};
|
|
20
18
|
}
|
|
21
|
-
function findAllBuilder(service, filter, hook) {
|
|
19
|
+
function findAllBuilder(service, filter, hook, hasPagination = true) {
|
|
22
20
|
return async (req, res) => {
|
|
23
21
|
const filters = filter.parse({
|
|
24
22
|
...req.params,
|
|
25
23
|
...req.query
|
|
26
24
|
});
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
service.
|
|
31
|
-
]);
|
|
32
|
-
if (hook !== undefined) {
|
|
33
|
-
return hook(items, total);
|
|
25
|
+
let pagination, itemsProm;
|
|
26
|
+
if (hasPagination) {
|
|
27
|
+
pagination = getPagination(req);
|
|
28
|
+
itemsProm = service.findAll(filters, pagination.page, pagination.pageSize);
|
|
34
29
|
}
|
|
35
30
|
else {
|
|
31
|
+
itemsProm = service.findAll(filters);
|
|
32
|
+
}
|
|
33
|
+
const totalProm = service.count(filters);
|
|
34
|
+
const [items, total] = await Promise.all([itemsProm, totalProm]);
|
|
35
|
+
hook?.(items, total);
|
|
36
|
+
if (hasPagination) {
|
|
36
37
|
return res.status(200).json({
|
|
37
38
|
pagination,
|
|
38
39
|
items,
|
|
39
40
|
total
|
|
40
41
|
});
|
|
41
42
|
}
|
|
43
|
+
else {
|
|
44
|
+
return res.status(200).json({
|
|
45
|
+
items,
|
|
46
|
+
total
|
|
47
|
+
});
|
|
48
|
+
}
|
|
42
49
|
};
|
|
43
50
|
}
|
|
44
|
-
function createBuilder(service, schema) {
|
|
51
|
+
function createBuilder(service, schema, hook) {
|
|
45
52
|
return validate(schema, async (req, res, data) => {
|
|
46
53
|
const item = await service.create(data);
|
|
54
|
+
if (hook) {
|
|
55
|
+
const i = await hook(req, res, data, item);
|
|
56
|
+
return res.status(201).json(i);
|
|
57
|
+
}
|
|
47
58
|
return res.status(201).json(item);
|
|
48
59
|
});
|
|
49
60
|
}
|
|
@@ -74,7 +85,7 @@ function getPagination(req) {
|
|
|
74
85
|
};
|
|
75
86
|
}
|
|
76
87
|
class BaseController {
|
|
77
|
-
constructor(service, base, filter) {
|
|
88
|
+
constructor(service, base, filter, createSchema, updateSchema) {
|
|
78
89
|
this.getAll = async (req, res) => {
|
|
79
90
|
const filters = getFilters(req, this.filterSchema);
|
|
80
91
|
const pagination = getPagination(req);
|
|
@@ -108,12 +119,12 @@ class BaseController {
|
|
|
108
119
|
};
|
|
109
120
|
this.service = service;
|
|
110
121
|
// @ts-expect-error infer schema
|
|
111
|
-
this.createSchema = base.omit({
|
|
122
|
+
this.createSchema = createSchema || base.omit({
|
|
112
123
|
id: true,
|
|
113
124
|
deletedAt: true
|
|
114
125
|
});
|
|
115
126
|
// @ts-expect-error infer schema
|
|
116
|
-
this.updateSchema = base.partial();
|
|
127
|
+
this.updateSchema = updateSchema || base.partial();
|
|
117
128
|
this.filterSchema = filter;
|
|
118
129
|
}
|
|
119
130
|
}
|
package/dist/src/filters.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { SQL } from "drizzle-orm";
|
|
2
|
-
import { Filter, FilterMap } from "./types";
|
|
2
|
+
import { Filter, FilterMap, PrimaryKeyData } from "./types";
|
|
3
3
|
import { ZodObject } from "zod";
|
|
4
|
+
import { AnyPgTable, PgColumn } from "drizzle-orm/pg-core";
|
|
4
5
|
export declare function buildFilters<FSchema extends ZodObject>(map: FilterMap<FSchema>, filters?: Filter<FSchema>): SQL<unknown> | undefined;
|
|
6
|
+
export declare function buildPKFilters<T extends AnyPgTable>(pks: PgColumn[], values: PrimaryKeyData<T>): SQL<unknown> | undefined;
|
package/dist/src/filters.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.buildFilters = buildFilters;
|
|
4
|
+
exports.buildPKFilters = buildPKFilters;
|
|
4
5
|
const drizzle_orm_1 = require("drizzle-orm");
|
|
5
6
|
function buildFilters(map, filters) {
|
|
6
7
|
const conditions = [];
|
|
@@ -17,3 +18,10 @@ function buildFilters(map, filters) {
|
|
|
17
18
|
}
|
|
18
19
|
return conditions.length ? (0, drizzle_orm_1.and)(...conditions) : undefined;
|
|
19
20
|
}
|
|
21
|
+
function buildPKFilters(pks, values) {
|
|
22
|
+
if (pks.length === 0) {
|
|
23
|
+
throw new Error(`Table has no detectable primary keys`);
|
|
24
|
+
}
|
|
25
|
+
const conditions = pks.map(pk => (0, drizzle_orm_1.eq)(pk, values[pk.name]));
|
|
26
|
+
return conditions.length === 1 ? conditions[0] : (0, drizzle_orm_1.and)(...conditions);
|
|
27
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Filter, FilterMap, PrimaryKeyData, PrimaryKeyType, Table } from "./types";
|
|
2
|
+
import { PgSelect, PgTransaction } from "drizzle-orm/pg-core";
|
|
3
|
+
import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
4
|
+
import { InferInsertModel, SQL } from "drizzle-orm";
|
|
5
|
+
import { ZodObject } from "zod";
|
|
6
|
+
export declare class ServiceBuilder<T extends Table, TSchema extends Record<string, unknown>, FSchema extends ZodObject> {
|
|
7
|
+
private readonly db;
|
|
8
|
+
private readonly table;
|
|
9
|
+
private readonly map;
|
|
10
|
+
private readonly pks;
|
|
11
|
+
readonly haveSoftDelete: boolean;
|
|
12
|
+
readonly findAllConditions: (f?: Filter<FSchema>) => SQL | undefined;
|
|
13
|
+
readonly findOneConditions: (id: PrimaryKeyType<T>) => SQL | undefined;
|
|
14
|
+
readonly tableName: string;
|
|
15
|
+
constructor(db: NodePgDatabase<TSchema>, table: T, map: FilterMap<FSchema>);
|
|
16
|
+
findOne<S extends PgSelect>(select: S): (pkFilter: PrimaryKeyData<T>) => Promise<Awaited<ReturnType<S["execute"]>>[number]>;
|
|
17
|
+
findAll<S extends PgSelect>(select: S, paginated?: boolean): (filters?: Filter<FSchema>, page?: number, pageSize?: number) => Promise<Awaited<ReturnType<S["execute"]>>[number][]>;
|
|
18
|
+
count(): (filters?: Filter<FSchema>) => Promise<number>;
|
|
19
|
+
create(pre?: (data: any, tx?: PgTransaction<any, TSchema, any>) => Promise<InferInsertModel<T>>): (data: InferInsertModel<T>, tx?: PgTransaction<any, TSchema, any>) => Promise<{ [Key in keyof T["_"]["columns"] & string as Key]: T["_"]["columns"][Key]["_"]["notNull"] extends true ? T["_"]["columns"][Key]["_"]["data"] : T["_"]["columns"][Key]["_"]["data"] | null; } extends infer T_1 ? { [K in keyof T_1]: T_1[K]; } : never>;
|
|
20
|
+
update(): (id: PrimaryKeyType<T>, data: Partial<InferInsertModel<T>>, tx?: PgTransaction<any, TSchema, any>) => Promise<{ [Key in keyof T["_"]["columns"] & string as Key]: T["_"]["columns"][Key]["_"]["notNull"] extends true ? T["_"]["columns"][Key]["_"]["data"] : T["_"]["columns"][Key]["_"]["data"] | null; } extends infer T_1 ? { [K in keyof T_1]: T_1[K]; } : never>;
|
|
21
|
+
delete(): (id: PrimaryKeyType<T>, tx?: PgTransaction<any, TSchema, any>) => Promise<void>;
|
|
22
|
+
softDelete(): (id: PrimaryKeyType<T>, tx?: PgTransaction<any, TSchema, any>) => Promise<void>;
|
|
23
|
+
hardDelete(): (id: PrimaryKeyType<T>, tx?: PgTransaction<any, TSchema, any>) => Promise<void>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ServiceBuilder = void 0;
|
|
4
|
+
const pg_core_1 = require("drizzle-orm/pg-core");
|
|
5
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
6
|
+
const filters_1 = require("./filters");
|
|
7
|
+
const errors_1 = require("./errors");
|
|
8
|
+
class ServiceBuilder {
|
|
9
|
+
constructor(db, table, map) {
|
|
10
|
+
this.db = db;
|
|
11
|
+
this.table = table;
|
|
12
|
+
this.tableName = (0, drizzle_orm_1.getTableName)(table);
|
|
13
|
+
this.pks = getPKs(table);
|
|
14
|
+
this.map = map;
|
|
15
|
+
this.haveSoftDelete = haveSoftDelete(table);
|
|
16
|
+
if (this.haveSoftDelete) {
|
|
17
|
+
this.findAllConditions = (f) => (0, drizzle_orm_1.and)((0, drizzle_orm_1.isNull)(this.table.deletedAt), (0, filters_1.buildFilters)(this.map, f));
|
|
18
|
+
this.findOneConditions = (id /* pkFilter: PrimaryKeyData<T> */) => (0, drizzle_orm_1.and)((0, drizzle_orm_1.isNull)(this.table.deletedAt),
|
|
19
|
+
// buildPKFilters(this.pks, pkFilter)
|
|
20
|
+
(0, drizzle_orm_1.eq)(this.table.id, id));
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
this.findAllConditions = (f) => (0, filters_1.buildFilters)(this.map, f);
|
|
24
|
+
this.findOneConditions = (id) => (0, drizzle_orm_1.eq)(this.table.id, id); // buildPKFilters(this.pks, id);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
findOne(select) {
|
|
28
|
+
return async (pkFilter) => {
|
|
29
|
+
const result = await select.where(this.findOneConditions(pkFilter));
|
|
30
|
+
if (result.length == 0)
|
|
31
|
+
throw notFoundWithId(this.tableName, pkFilter);
|
|
32
|
+
return result[0];
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
findAll(select, paginated = true) {
|
|
36
|
+
const base = (f) => select.where(this.findAllConditions(f));
|
|
37
|
+
if (paginated) {
|
|
38
|
+
return async (filters, page = 1, pageSize = 10) => {
|
|
39
|
+
const offset = (page - 1) * pageSize;
|
|
40
|
+
return await base(filters)
|
|
41
|
+
.limit(pageSize)
|
|
42
|
+
.offset(offset);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
return async (filters) => {
|
|
47
|
+
return await base(filters);
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
count() {
|
|
52
|
+
return async (filters) => {
|
|
53
|
+
const [result] = await this.db
|
|
54
|
+
.select({ count: (0, drizzle_orm_1.count)() })
|
|
55
|
+
.from(this.table)
|
|
56
|
+
.where(this.findAllConditions(filters));
|
|
57
|
+
return result.count;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
create(pre) {
|
|
61
|
+
return async (data, tx) => {
|
|
62
|
+
if (pre) {
|
|
63
|
+
data = await pre(data);
|
|
64
|
+
}
|
|
65
|
+
const executor = tx || this.db;
|
|
66
|
+
const [result] = await executor
|
|
67
|
+
.insert(this.table)
|
|
68
|
+
.values(data)
|
|
69
|
+
.returning()
|
|
70
|
+
.catch(errors_1.handleSqlError);
|
|
71
|
+
return result;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
update() {
|
|
75
|
+
return async (
|
|
76
|
+
// pkFilter: PrimaryKeyData<T>,
|
|
77
|
+
id, data, tx) => {
|
|
78
|
+
if (Object.keys(data).length == 0) {
|
|
79
|
+
throw new errors_1.BadRequest("update needs at least one field");
|
|
80
|
+
}
|
|
81
|
+
const executor = tx || this.db;
|
|
82
|
+
const [result] = await executor
|
|
83
|
+
.update(this.table)
|
|
84
|
+
.set(data)
|
|
85
|
+
.where(this.findOneConditions(id))
|
|
86
|
+
.returning()
|
|
87
|
+
.catch(errors_1.handleSqlError);
|
|
88
|
+
if (!result) {
|
|
89
|
+
throw notFoundWithId(this.tableName, { id: id });
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
delete() {
|
|
95
|
+
if (this.haveSoftDelete) {
|
|
96
|
+
return this.softDelete();
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
return this.hardDelete();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
softDelete() {
|
|
103
|
+
return async (id, tx) => {
|
|
104
|
+
const executor = tx || this.db;
|
|
105
|
+
const { rowCount } = await executor
|
|
106
|
+
.update(this.table)
|
|
107
|
+
.set({ deletedAt: new Date() })
|
|
108
|
+
.where(this.findOneConditions(id));
|
|
109
|
+
if (rowCount == 0) {
|
|
110
|
+
throw notFoundWithId(this.tableName, { id: id });
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
hardDelete() {
|
|
115
|
+
return async (
|
|
116
|
+
// id: PrimaryKeyData<T>,
|
|
117
|
+
id, tx) => {
|
|
118
|
+
const executor = tx || this.db;
|
|
119
|
+
const { rowCount } = await executor
|
|
120
|
+
.delete(this.table)
|
|
121
|
+
.where(this.findOneConditions(id));
|
|
122
|
+
if (rowCount == 0) {
|
|
123
|
+
throw notFoundWithId(this.tableName, { id: id });
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
exports.ServiceBuilder = ServiceBuilder;
|
|
129
|
+
function haveSoftDelete(table, columnName = "deleted_at") {
|
|
130
|
+
const { columns } = (0, pg_core_1.getTableConfig)(table);
|
|
131
|
+
for (const col of columns) {
|
|
132
|
+
if (col.name == columnName) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
function getPKs(table) {
|
|
139
|
+
const pks = [];
|
|
140
|
+
const { columns } = (0, pg_core_1.getTableConfig)(table);
|
|
141
|
+
for (const col of columns) {
|
|
142
|
+
if (col.primary) {
|
|
143
|
+
pks.push(col);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (pks.length <= 0)
|
|
147
|
+
throw new Error("Service builder needs at least one primary key field");
|
|
148
|
+
return pks;
|
|
149
|
+
}
|
|
150
|
+
function notFoundWithId(tableName, pkFilter) {
|
|
151
|
+
// TODO: proper message
|
|
152
|
+
return new errors_1.NotFound(`${tableName} not found`);
|
|
153
|
+
}
|
package/dist/src/types.d.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { SQL } from "drizzle-orm";
|
|
2
|
-
import { AnyPgTable, PgColumn, PgTable } from "drizzle-orm/pg-core";
|
|
2
|
+
import { AnyPgTable, PgColumn, PgTable, PgTransaction } from "drizzle-orm/pg-core";
|
|
3
3
|
import z, { ZodObject } from "zod";
|
|
4
4
|
export interface RetrieverService<FSchema extends ZodObject, TTable extends PgTable> {
|
|
5
5
|
findAll: (filters?: Filter<FSchema>, page?: number, pageSize?: number) => Promise<TTable["$inferSelect"][]>;
|
|
6
6
|
count: (filters: Filter<FSchema>) => Promise<number>;
|
|
7
7
|
}
|
|
8
8
|
export interface FindOneService<TTable extends PgTable> {
|
|
9
|
-
findOne: (id:
|
|
9
|
+
findOne: (id: PrimaryKeyType<TTable>) => Promise<Partial<TTable>>;
|
|
10
10
|
}
|
|
11
|
-
export interface CreateService<TTable extends PgTable
|
|
12
|
-
create: (data: TTable["$inferInsert"]) => Promise<TTable["$inferSelect"]>;
|
|
11
|
+
export interface CreateService<TTable extends PgTable, TSchema extends Record<string, unknown>> {
|
|
12
|
+
create: (data: TTable["$inferInsert"], tx?: PgTransaction<any, TSchema, any>) => Promise<TTable["$inferSelect"]>;
|
|
13
13
|
}
|
|
14
|
-
export interface UpdateService<TTable extends PgTable
|
|
15
|
-
update: (id:
|
|
14
|
+
export interface UpdateService<TTable extends PgTable, TSchema extends Record<string, unknown>> {
|
|
15
|
+
update: (id: PrimaryKeyType<TTable>, data: Partial<TTable["$inferInsert"]>, tx?: PgTransaction<any, TSchema, any>) => Promise<TTable["$inferSelect"]>;
|
|
16
16
|
}
|
|
17
|
-
export interface DeleteService<TTable extends PgTable
|
|
18
|
-
delete: (id: any) => Promise<
|
|
17
|
+
export interface DeleteService<TTable extends PgTable, TSchema extends Record<string, unknown>> {
|
|
18
|
+
delete: (id: PrimaryKeyType<TTable>, tx?: PgTransaction<any, TSchema, any>) => Promise<void>;
|
|
19
19
|
}
|
|
20
20
|
export type PrimaryKeyData<TTable extends AnyPgTable> = {
|
|
21
21
|
[K in keyof TTable["_"]["columns"] as TTable["_"]["columns"][K] extends {
|
|
@@ -29,8 +29,8 @@ export interface PaginationParams {
|
|
|
29
29
|
pageSize: number;
|
|
30
30
|
}
|
|
31
31
|
export type Table = AnyPgTable & {
|
|
32
|
-
id: PgColumn;
|
|
33
32
|
deletedAt: PgColumn;
|
|
33
|
+
id: any;
|
|
34
34
|
};
|
|
35
35
|
export type PrimaryKeyType<T extends AnyPgTable> = T["_"]["columns"]["id"]["_"]["data"];
|
|
36
36
|
export type FilterMap<Schema extends z.ZodObject, Out = z.infer<Schema>> = {
|
package/package.json
CHANGED
package/dist/src/standard.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Filter, FilterMap, PrimaryKeyType, Table } from "./types";
|
|
2
|
-
import { PgSelect } from "drizzle-orm/pg-core";
|
|
3
|
-
import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
4
|
-
import { InferInsertModel } from "drizzle-orm";
|
|
5
|
-
import { ZodObject } from "zod";
|
|
6
|
-
export declare class ServiceBuilder<T extends Table, TSchema extends Record<string, unknown>, FSchema extends ZodObject> {
|
|
7
|
-
private readonly db;
|
|
8
|
-
private readonly table;
|
|
9
|
-
private readonly map;
|
|
10
|
-
readonly tableName: string;
|
|
11
|
-
constructor(db: NodePgDatabase<TSchema>, table: T, map: FilterMap<FSchema>);
|
|
12
|
-
findOne<S extends PgSelect>(select: S): (id: number) => Promise<Awaited<ReturnType<S["execute"]>>[number]>;
|
|
13
|
-
findAll<S extends PgSelect>(select: S): (filters?: Filter<FSchema>, page?: number, pageSize?: number) => Promise<Awaited<ReturnType<S["execute"]>>[number][]>;
|
|
14
|
-
create(): (data: InferInsertModel<T>) => Promise<{ [Key in keyof T["_"]["columns"] & string as Key]: T["_"]["columns"][Key]["_"]["notNull"] extends true ? T["_"]["columns"][Key]["_"]["data"] : T["_"]["columns"][Key]["_"]["data"] | null; } extends infer T_1 ? { [K in keyof T_1]: T_1[K]; } : never>;
|
|
15
|
-
update(): (id: PrimaryKeyType<T>, data: Partial<InferInsertModel<T>>) => Promise<{ [Key in keyof T["_"]["columns"] & string as Key]: T["_"]["columns"][Key]["_"]["notNull"] extends true ? T["_"]["columns"][Key]["_"]["data"] : T["_"]["columns"][Key]["_"]["data"] | null; } extends infer T_1 ? { [K in keyof T_1]: T_1[K]; } : never>;
|
|
16
|
-
softDelete(): (id: PrimaryKeyType<T>) => Promise<void>;
|
|
17
|
-
hardDelete(): (id: PrimaryKeyType<T>) => Promise<void>;
|
|
18
|
-
count(): (filters?: Filter<FSchema>) => Promise<number>;
|
|
19
|
-
}
|
package/dist/src/standard.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ServiceBuilder = void 0;
|
|
4
|
-
const drizzle_orm_1 = require("drizzle-orm");
|
|
5
|
-
const filters_1 = require("./filters");
|
|
6
|
-
const errors_1 = require("./errors");
|
|
7
|
-
class ServiceBuilder {
|
|
8
|
-
constructor(db, table, map) {
|
|
9
|
-
this.db = db;
|
|
10
|
-
this.table = table;
|
|
11
|
-
this.tableName = (0, drizzle_orm_1.getTableName)(table);
|
|
12
|
-
this.map = map;
|
|
13
|
-
}
|
|
14
|
-
findOne(select) {
|
|
15
|
-
return async (id) => {
|
|
16
|
-
const result = await select.where((0, drizzle_orm_1.eq)(this.table.id, id));
|
|
17
|
-
if (result.length == 0)
|
|
18
|
-
throw notFoundWithId(this.tableName, id);
|
|
19
|
-
return result[0];
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
findAll(select) {
|
|
23
|
-
return async (filters, page = 1, pageSize = 10) => {
|
|
24
|
-
const offset = (page - 1) * pageSize;
|
|
25
|
-
return await select
|
|
26
|
-
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.isNull)(this.table.deletedAt), (0, filters_1.buildFilters)(this.map, filters)))
|
|
27
|
-
.limit(pageSize)
|
|
28
|
-
.offset(offset);
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
create() {
|
|
32
|
-
return async (data) => {
|
|
33
|
-
const [result] = await this.db
|
|
34
|
-
.insert(this.table)
|
|
35
|
-
.values(data)
|
|
36
|
-
.returning()
|
|
37
|
-
.catch(errors_1.handleSqlError);
|
|
38
|
-
return result;
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
update() {
|
|
42
|
-
return async (id, data) => {
|
|
43
|
-
if (Object.keys(data).length == 0) {
|
|
44
|
-
throw new errors_1.BadRequest("update needs at least one field");
|
|
45
|
-
}
|
|
46
|
-
const [result] = await this.db
|
|
47
|
-
.update(this.table)
|
|
48
|
-
.set(data)
|
|
49
|
-
.where((0, drizzle_orm_1.eq)(this.table.id, id))
|
|
50
|
-
.returning()
|
|
51
|
-
.catch((e) => (0, errors_1.handleSqlError)(e));
|
|
52
|
-
if (!result) {
|
|
53
|
-
throw notFoundWithId(this.tableName, id);
|
|
54
|
-
}
|
|
55
|
-
return result;
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
softDelete() {
|
|
59
|
-
return async (id) => {
|
|
60
|
-
const { rowCount } = await this.db
|
|
61
|
-
.update(this.table)
|
|
62
|
-
.set({ deletedAt: new Date() })
|
|
63
|
-
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.table.id, id), (0, drizzle_orm_1.isNull)(this.table.deletedAt)));
|
|
64
|
-
if (rowCount == 0) {
|
|
65
|
-
throw notFoundWithId(this.tableName, id);
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
hardDelete() {
|
|
70
|
-
return async (id) => {
|
|
71
|
-
const { rowCount } = await this.db
|
|
72
|
-
.delete(this.table)
|
|
73
|
-
.where((0, drizzle_orm_1.eq)(this.table.id, id));
|
|
74
|
-
if (rowCount == 0) {
|
|
75
|
-
throw notFoundWithId(this.tableName, id);
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
count() {
|
|
80
|
-
return async (filters) => {
|
|
81
|
-
const [result] = await this.db
|
|
82
|
-
.select({ count: (0, drizzle_orm_1.count)() })
|
|
83
|
-
.from(this.table)
|
|
84
|
-
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.isNull)(this.table.deletedAt), (0, filters_1.buildFilters)(this.map, filters)));
|
|
85
|
-
return result.count;
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
exports.ServiceBuilder = ServiceBuilder;
|
|
90
|
-
function notFoundWithId(tableName, id) {
|
|
91
|
-
return new errors_1.NotFound(`${tableName} with id ${id} not found`);
|
|
92
|
-
}
|