@avleon/core 0.0.20 → 0.0.25
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/README.md +559 -2
- package/dist/application.d.ts +47 -0
- package/dist/application.js +50 -0
- package/dist/cache.d.ts +12 -0
- package/dist/cache.js +78 -0
- package/dist/collection.js +3 -2
- package/dist/container.d.ts +1 -0
- package/dist/container.js +2 -1
- package/dist/environment-variables.js +1 -1
- package/dist/file-storage.js +0 -128
- package/dist/helpers.d.ts +2 -0
- package/dist/helpers.js +41 -4
- package/dist/icore.d.ts +93 -42
- package/dist/icore.js +319 -251
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -2
- package/dist/middleware.js +0 -8
- package/dist/multipart.js +4 -1
- package/dist/openapi.d.ts +37 -37
- package/dist/params.js +2 -2
- package/dist/response.js +6 -2
- package/dist/route-methods.js +1 -1
- package/dist/swagger-schema.js +4 -1
- package/dist/testing.js +6 -3
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +18 -0
- package/dist/utils/optional-require.d.ts +8 -0
- package/dist/utils/optional-require.js +70 -0
- package/dist/validation.d.ts +4 -1
- package/dist/validation.js +10 -5
- package/package.json +26 -9
- package/src/application.ts +125 -0
- package/src/authentication.ts +16 -0
- package/src/cache.ts +91 -0
- package/src/collection.ts +254 -0
- package/src/config.ts +42 -0
- package/src/constants.ts +1 -0
- package/src/container.ts +54 -0
- package/src/controller.ts +127 -0
- package/src/decorators.ts +27 -0
- package/src/environment-variables.ts +46 -0
- package/src/exceptions/http-exceptions.ts +86 -0
- package/src/exceptions/index.ts +1 -0
- package/src/exceptions/system-exception.ts +34 -0
- package/src/file-storage.ts +206 -0
- package/src/helpers.ts +328 -0
- package/src/icore.ts +1140 -0
- package/src/index.ts +30 -0
- package/src/logger.ts +72 -0
- package/src/map-types.ts +159 -0
- package/src/middleware.ts +98 -0
- package/src/multipart.ts +116 -0
- package/src/openapi.ts +372 -0
- package/src/params.ts +111 -0
- package/src/queue.ts +126 -0
- package/src/response.ts +117 -0
- package/src/results.ts +30 -0
- package/src/route-methods.ts +186 -0
- package/src/swagger-schema.ts +213 -0
- package/src/testing.ts +220 -0
- package/src/types/app-builder.interface.ts +19 -0
- package/src/types/application.interface.ts +9 -0
- package/src/utils/hash.ts +5 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/optional-require.ts +50 -0
- package/src/validation.ts +156 -0
- package/src/validator-extend.ts +25 -0
- package/dist/classToOpenapi.d.ts +0 -0
- package/dist/classToOpenapi.js +0 -1
- package/dist/render.d.ts +0 -1
- package/dist/render.js +0 -8
- package/jest.config.ts +0 -9
- package/tsconfig.json +0 -25
- /package/dist/{security.d.ts → utils/hash.d.ts} +0 -0
- /package/dist/{security.js → utils/hash.js} +0 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import Container from "typedi";
|
|
2
|
+
import { Constructor } from "./helpers";
|
|
3
|
+
import fastify, { FastifyInstance, RouteShorthandMethod } from "fastify";
|
|
4
|
+
|
|
5
|
+
export interface AvleonApplication{
|
|
6
|
+
// all public
|
|
7
|
+
useCors: () => void
|
|
8
|
+
useOpenApi:() => void
|
|
9
|
+
useView:()=> void;
|
|
10
|
+
useAuth:() => void
|
|
11
|
+
useMultipart:()=> void
|
|
12
|
+
useDataSource: () => void
|
|
13
|
+
useMiddlewares: () => void
|
|
14
|
+
useControllers: () => void
|
|
15
|
+
useAutoControllers:() => void
|
|
16
|
+
useStaticFiles: () => void
|
|
17
|
+
useCustomErrorHandler: () => void
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
// all mapping
|
|
23
|
+
mapGroup:() => any;
|
|
24
|
+
mapGet:()=> any;
|
|
25
|
+
mapPost:()=> any;
|
|
26
|
+
mapPut: () => any;
|
|
27
|
+
mapPatch: () => any;
|
|
28
|
+
mapDelete: () => any;
|
|
29
|
+
mapView: () => any;
|
|
30
|
+
|
|
31
|
+
// run
|
|
32
|
+
run:(port:number) => void
|
|
33
|
+
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
export interface InlineRoutes{
|
|
38
|
+
get: RouteShorthandMethod;
|
|
39
|
+
post: RouteShorthandMethod;
|
|
40
|
+
put: RouteShorthandMethod;
|
|
41
|
+
patch: RouteShorthandMethod;
|
|
42
|
+
delete: RouteShorthandMethod;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
export interface Application{
|
|
47
|
+
|
|
48
|
+
inlineRoutes:() => InlineRoutes;
|
|
49
|
+
mapGroup:(path?: string | RegExp) => InlineRoutes;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Start the application
|
|
53
|
+
* @param port
|
|
54
|
+
* @returns void
|
|
55
|
+
*/
|
|
56
|
+
start:(port?: number)=> void
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface TestApplication{
|
|
60
|
+
getController: <T>(controller: Constructor<T>)=>T;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class IqraTestApplication implements TestApplication{
|
|
65
|
+
|
|
66
|
+
getController<T>(controller: Constructor<T>){
|
|
67
|
+
const con = Container.get(controller);
|
|
68
|
+
return con;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
class IqraApplication implements Application{
|
|
72
|
+
|
|
73
|
+
private app!: FastifyInstance;
|
|
74
|
+
|
|
75
|
+
constructor(){
|
|
76
|
+
if(!this.app){
|
|
77
|
+
this.app = fastify.prototype;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
inlineRoutes() {
|
|
84
|
+
return {
|
|
85
|
+
get: this.app.get,
|
|
86
|
+
post: this.app.post,
|
|
87
|
+
put: this.app.put,
|
|
88
|
+
patch: this.app.patch,
|
|
89
|
+
delete: this.app.delete
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
mapGroup(path?:string|RegExp){
|
|
95
|
+
return this.inlineRoutes();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
start(port?:number){
|
|
100
|
+
const p = port ? port : 4000
|
|
101
|
+
this.app.listen({port:p});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
export class Builder{
|
|
108
|
+
|
|
109
|
+
static createApplication(): Application{
|
|
110
|
+
const app = new IqraApplication();
|
|
111
|
+
return app;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static createTestApplication(app?: Application): TestApplication {
|
|
115
|
+
const testApp = new IqraTestApplication();
|
|
116
|
+
return testApp;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
const app = Builder.createApplication();
|
|
122
|
+
const route = app.inlineRoutes();
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright 2024
|
|
3
|
+
* @author Tareq Hossain
|
|
4
|
+
* @email xtrinsic96@gmail.com
|
|
5
|
+
* @url https://github.com/xtareq
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type CurrentUser = {};
|
|
9
|
+
|
|
10
|
+
export abstract class BaseAuthetication {
|
|
11
|
+
abstract authenticate(): Promise<Boolean>;
|
|
12
|
+
abstract authorize(): Promise<Boolean>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function Authorized() {}
|
|
16
|
+
export function CurrentUser() {}
|
package/src/cache.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Redis } from 'ioredis';
|
|
2
|
+
|
|
3
|
+
type CacheEntry<T = any> = {
|
|
4
|
+
data: T;
|
|
5
|
+
timestamp: number;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export class CacheManager {
|
|
9
|
+
private store = new Map<string, CacheEntry>();
|
|
10
|
+
private tagsMap = new Map<string, Set<string>>();
|
|
11
|
+
private redis: Redis | null = null;
|
|
12
|
+
|
|
13
|
+
constructor(redisInstance?: Redis) {
|
|
14
|
+
this.redis = redisInstance || null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
private redisTagKey(tag: string) {
|
|
18
|
+
return `cache-tags:${tag}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async get<T>(key: string): Promise<T | null> {
|
|
22
|
+
if (this.redis) {
|
|
23
|
+
const val = await this.redis.get(key);
|
|
24
|
+
return val ? JSON.parse(val) : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const cached = this.store.get(key);
|
|
28
|
+
return cached ? cached.data : null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async set<T>(
|
|
32
|
+
key: string,
|
|
33
|
+
value: T,
|
|
34
|
+
tags: string[] = [],
|
|
35
|
+
ttl: number = 3600
|
|
36
|
+
): Promise<void> {
|
|
37
|
+
const entry: CacheEntry<T> = {
|
|
38
|
+
data: value,
|
|
39
|
+
timestamp: Date.now(),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (this.redis) {
|
|
43
|
+
await this.redis.set(key, JSON.stringify(entry.data), 'EX', ttl);
|
|
44
|
+
for (const tag of tags) {
|
|
45
|
+
await this.redis.sadd(this.redisTagKey(tag), key);
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
this.store.set(key, entry);
|
|
49
|
+
for (const tag of tags) {
|
|
50
|
+
if (!this.tagsMap.has(tag)) this.tagsMap.set(tag, new Set());
|
|
51
|
+
this.tagsMap.get(tag)!.add(key);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async delete(key: string): Promise<void> {
|
|
57
|
+
if (this.redis) {
|
|
58
|
+
await this.redis.del(key);
|
|
59
|
+
|
|
60
|
+
// Also clean up from any tag sets
|
|
61
|
+
const tagKeys = await this.redis.keys('cache-tags:*');
|
|
62
|
+
for (const tagKey of tagKeys) {
|
|
63
|
+
await this.redis.srem(tagKey, key);
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
this.store.delete(key);
|
|
67
|
+
for (const keys of this.tagsMap.values()) {
|
|
68
|
+
keys.delete(key);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async invalidateTag(tag: string): Promise<void> {
|
|
74
|
+
if (this.redis) {
|
|
75
|
+
const tagKey = this.redisTagKey(tag);
|
|
76
|
+
const keys = await this.redis.smembers(tagKey);
|
|
77
|
+
if (keys.length) {
|
|
78
|
+
await this.redis.del(...keys); // delete all cached keys
|
|
79
|
+
await this.redis.del(tagKey); // delete the tag set
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
const keys = this.tagsMap.get(tag);
|
|
83
|
+
if (keys) {
|
|
84
|
+
for (const key of keys) {
|
|
85
|
+
this.store.delete(key);
|
|
86
|
+
}
|
|
87
|
+
this.tagsMap.delete(tag);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright 2024
|
|
3
|
+
* @author Tareq Hossain
|
|
4
|
+
* @email xtrinsic96@gmail.com
|
|
5
|
+
* @url https://github.com/xtareq
|
|
6
|
+
*/
|
|
7
|
+
import Container from "typedi";
|
|
8
|
+
import { NotFoundException } from "./exceptions";
|
|
9
|
+
import {
|
|
10
|
+
DataSource,
|
|
11
|
+
EntityTarget,
|
|
12
|
+
FindOneOptions,
|
|
13
|
+
ObjectLiteral,
|
|
14
|
+
Repository,
|
|
15
|
+
} from "typeorm";
|
|
16
|
+
import { UpsertOptions } from "typeorm/repository/UpsertOptions";
|
|
17
|
+
|
|
18
|
+
type ObjKey<T> = keyof T;
|
|
19
|
+
type ObjKeys<T> = ObjKey<T>[];
|
|
20
|
+
type PaginationOptions = {
|
|
21
|
+
take: number;
|
|
22
|
+
skip?: number;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type Predicate<T> = (item: T) => boolean;
|
|
26
|
+
interface TypeormEnitity extends ObjectLiteral {}
|
|
27
|
+
|
|
28
|
+
export type PaginationResult<T> = {
|
|
29
|
+
total: number;
|
|
30
|
+
data: T[];
|
|
31
|
+
next?: number | null;
|
|
32
|
+
prev?: number | null;
|
|
33
|
+
first?: number | null;
|
|
34
|
+
last?: number | null;
|
|
35
|
+
totalPage?: number;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
type ICollection<T> = {
|
|
39
|
+
findAll(): T[] | Promise<T[]>;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type EntityCollection<T extends ObjectLiteral> = {};
|
|
43
|
+
|
|
44
|
+
class BasicCollection<T> implements ICollection<T> {
|
|
45
|
+
private items: T[];
|
|
46
|
+
|
|
47
|
+
private constructor(items: T[]) {
|
|
48
|
+
this.items = items;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static from<T>(items: T[]): BasicCollection<T> {
|
|
52
|
+
return new BasicCollection(items);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
findAll(predicate?: Predicate<T>) {
|
|
56
|
+
const results = Array.from(this.items);
|
|
57
|
+
return results;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
findOne(predicate: Predicate<T> | FindOneOptions<T>): T | Promise<T | null> {
|
|
61
|
+
if (this.isFunction(predicate)) {
|
|
62
|
+
return this.items.find(predicate as Predicate<T>) as T;
|
|
63
|
+
}
|
|
64
|
+
throw new Error("Invalid predicate type");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Utility function to check if a value is a function
|
|
68
|
+
private isFunction(value: unknown): value is Function {
|
|
69
|
+
return typeof value === "function";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
add(item: Partial<T>): T;
|
|
73
|
+
add(item: Partial<T>): T | Promise<T> {
|
|
74
|
+
this.items.push(item as T);
|
|
75
|
+
return this.items[this.items.length - 1];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
addAll(items: T[]): void {
|
|
79
|
+
this.items.push(...items);
|
|
80
|
+
}
|
|
81
|
+
update(predicate: (item: T) => boolean, updater: Partial<T>): void {
|
|
82
|
+
const item = this.items.find(predicate);
|
|
83
|
+
if (item) {
|
|
84
|
+
const index = this.items.indexOf(item)!;
|
|
85
|
+
this.items[index] = { ...item, ...updater };
|
|
86
|
+
} else {
|
|
87
|
+
throw new NotFoundException("Item not found");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
updateAll(predicate: (item: T) => boolean, updater: (item: T) => T): void {
|
|
92
|
+
for (let i = 0; i < this.items.length; i++) {
|
|
93
|
+
if (predicate(this.items[i])) {
|
|
94
|
+
this.items[i] = updater(this.items[i]);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
delete(predicate: (item: T) => boolean): void {
|
|
99
|
+
const index = this.items.findIndex(predicate);
|
|
100
|
+
if (index !== -1) {
|
|
101
|
+
this.items.splice(index, 1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
deleteAll(predicate: (item: T) => boolean): void {
|
|
105
|
+
this.items = this.items.filter((item) => !predicate(item));
|
|
106
|
+
}
|
|
107
|
+
max<K extends keyof T>(key: K & string): number {
|
|
108
|
+
return Math.max(...this.items.map((item) => item[key] as number));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
min<K extends keyof T>(key: K & string): number {
|
|
112
|
+
return Math.max(...this.items.map((item) => item[key] as number));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
sum<K extends keyof T>(key: K & string): number {
|
|
116
|
+
const nums = this.items.flatMap((x) => x[key]) as number[];
|
|
117
|
+
return nums.reduce((sum, num) => sum + num, 0);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
avg<K extends keyof T>(key: K & string): number {
|
|
121
|
+
const nums = this.items.flatMap((x) => x[key]) as number[];
|
|
122
|
+
return nums.reduce((sum, num) => sum + num, 0) / nums.length;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
paginate(options?: PaginationOptions) {
|
|
126
|
+
const take = options?.take || 10;
|
|
127
|
+
const skip = options?.skip || 0;
|
|
128
|
+
const total = this.items.length;
|
|
129
|
+
const data = this.items.slice(skip, take);
|
|
130
|
+
return {
|
|
131
|
+
total,
|
|
132
|
+
totalPage: Math.ceil(total / take),
|
|
133
|
+
next: skip + take < total ? skip + take : null,
|
|
134
|
+
data,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private getDeepValue(item: any, path: string | keyof T): any {
|
|
139
|
+
if (typeof path !== "string") return item[path];
|
|
140
|
+
return path.split(".").reduce((acc, key) => acc?.[key], item);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
class AsynchronousCollection<T extends ObjectLiteral> {
|
|
145
|
+
private model: EntityTarget<T>;
|
|
146
|
+
private repo?: Repository<T>;
|
|
147
|
+
|
|
148
|
+
private constructor(model: EntityTarget<T>) {
|
|
149
|
+
this.model = model;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
static fromRepository<T extends ObjectLiteral>(
|
|
153
|
+
model: EntityTarget<T>,
|
|
154
|
+
): AsynchronousCollection<T> {
|
|
155
|
+
return new AsynchronousCollection(model);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
getRepository() {
|
|
159
|
+
if (!this.repo) {
|
|
160
|
+
const dataSourceKey = "idatasource";
|
|
161
|
+
const dataSource = Container.get(dataSourceKey) as DataSource;
|
|
162
|
+
console.log('datasource', dataSource);
|
|
163
|
+
const repository = dataSource.getRepository<T>(this.model);
|
|
164
|
+
this.repo = repository;
|
|
165
|
+
return repository;
|
|
166
|
+
}
|
|
167
|
+
return this.repo;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Pagination with query builder
|
|
171
|
+
async paginate(options?: PaginationOptions): Promise<PaginationResult<T>> {
|
|
172
|
+
const take = options?.take || 10;
|
|
173
|
+
const skip = options?.skip || 0;
|
|
174
|
+
|
|
175
|
+
const [data, total] = await this.getRepository().findAndCount({
|
|
176
|
+
take,
|
|
177
|
+
skip,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
total,
|
|
182
|
+
totalPage: Math.ceil(total / take),
|
|
183
|
+
next: skip + take < total ? skip + take : null,
|
|
184
|
+
prev: skip + take < total ? skip + take : null,
|
|
185
|
+
data,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export class Collection {
|
|
191
|
+
private constructor() {}
|
|
192
|
+
|
|
193
|
+
static from<T>(items: T[]): BasicCollection<T> {
|
|
194
|
+
return BasicCollection.from(items);
|
|
195
|
+
}
|
|
196
|
+
// Example refactoring of Collection.fromRepository for better type safety
|
|
197
|
+
static fromRepository<T extends ObjectLiteral>(
|
|
198
|
+
entity: EntityTarget<T>,
|
|
199
|
+
): Repository<T> {
|
|
200
|
+
const asyncCollection = AsynchronousCollection.fromRepository(entity);
|
|
201
|
+
// Assuming AsynchronousCollection has a method to get the Repository<T>
|
|
202
|
+
return asyncCollection.getRepository();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function InjectRepository<T extends Repository<T>>(
|
|
207
|
+
model: EntityTarget<T>,
|
|
208
|
+
) {
|
|
209
|
+
return function (
|
|
210
|
+
object: any,
|
|
211
|
+
propertyName: string | undefined,
|
|
212
|
+
index?: number,
|
|
213
|
+
) {
|
|
214
|
+
let repo!: any | Repository<T>;
|
|
215
|
+
try {
|
|
216
|
+
Container.registerHandler({
|
|
217
|
+
object,
|
|
218
|
+
propertyName,
|
|
219
|
+
index,
|
|
220
|
+
value: (containerInstance) => {
|
|
221
|
+
const dataSource = containerInstance.get<DataSource>("idatasource");
|
|
222
|
+
|
|
223
|
+
repo = dataSource
|
|
224
|
+
.getRepository<T>(model)
|
|
225
|
+
.extend({ paginate: () => {} });
|
|
226
|
+
repo.paginate = async function (
|
|
227
|
+
options: PaginationOptions = { take: 10, skip: 0 },
|
|
228
|
+
): Promise<PaginationResult<T>> {
|
|
229
|
+
const [data, total] = await this.findAndCount({
|
|
230
|
+
take: options.take || 10,
|
|
231
|
+
skip: options.skip || 0,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
total,
|
|
236
|
+
totalPage: Math.ceil(total / (options.take || 10)),
|
|
237
|
+
next:
|
|
238
|
+
options.skip! + options.take! < total
|
|
239
|
+
? options.skip! + options.take!
|
|
240
|
+
: null,
|
|
241
|
+
data,
|
|
242
|
+
};
|
|
243
|
+
};
|
|
244
|
+
return repo;
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
} catch (error: any) {
|
|
248
|
+
console.log(error);
|
|
249
|
+
if (error.name && error.name == "ServiceNotFoundError") {
|
|
250
|
+
console.log("Database didn't initialized.");
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright 2024
|
|
3
|
+
* @author Tareq Hossain
|
|
4
|
+
* @email xtrinsic96@gmail.com
|
|
5
|
+
* @url https://github.com/xtareq
|
|
6
|
+
*/
|
|
7
|
+
import { Container, Service, Constructable } from "typedi";
|
|
8
|
+
import { Environment } from "./environment-variables";
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
export interface IConfig<T = any> {
|
|
14
|
+
config(env: Environment): T;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function Config<T extends IConfig>(target: Constructable<T>) {
|
|
18
|
+
Container.set({ id: target, type: target });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class AppConfig {
|
|
22
|
+
get<T extends IConfig<R>, R>(configClass: Constructable<T>): R {
|
|
23
|
+
const instance = Container.get(configClass);
|
|
24
|
+
if (!instance) {
|
|
25
|
+
throw new Error(`Configuration for ${configClass.name} not found.`);
|
|
26
|
+
}
|
|
27
|
+
return instance.config(new Environment());
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function GetConfig<
|
|
32
|
+
T extends IConfig<R>,
|
|
33
|
+
R = ReturnType<InstanceType<Constructable<T>>["config"]>,
|
|
34
|
+
>(ConfigClass: Constructable<T>): R {
|
|
35
|
+
const instance = Container.get(ConfigClass);
|
|
36
|
+
if (!instance) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Class "${ConfigClass.name}" is not registered as a config.`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return instance.config(new Environment());
|
|
42
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const TEST_DATASOURCE_OPTIONS_KEY = Symbol("itestdatasource");
|
package/src/container.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright 2024
|
|
3
|
+
* @author Tareq Hossain
|
|
4
|
+
* @email xtrinsic96@gmail.com
|
|
5
|
+
* @url https://github.com/xtareq
|
|
6
|
+
*/
|
|
7
|
+
import TypediContainer, { ContainerInstance, Token } from "typedi";
|
|
8
|
+
import { DataSource } from "typeorm";
|
|
9
|
+
|
|
10
|
+
export const FEATURE_KEY = Symbol.for("features");
|
|
11
|
+
export const ROUTE_META_KEY = Symbol("iroute:options");
|
|
12
|
+
export const CONTROLLER_META_KEY = Symbol("icontroller:options");
|
|
13
|
+
export const PARAM_META_KEY = Symbol("iparam:options");
|
|
14
|
+
export const QUERY_META_KEY = Symbol("iparam:options");
|
|
15
|
+
export const REQUEST_BODY_META_KEY = Symbol("iparam:options");
|
|
16
|
+
export const REQUEST_BODY_FILE_KEY = Symbol("iparam:options");
|
|
17
|
+
export const REQUEST_BODY_FILES_KEY = Symbol("iparam:options");
|
|
18
|
+
export const REQUEST_USER_META_KEY = Symbol("iparam:options");
|
|
19
|
+
export const REQUEST_HEADER_META_KEY = Symbol("iheader:options");
|
|
20
|
+
export const DATASOURCE_META_KEY = Symbol("idatasource:options");
|
|
21
|
+
export const AUTHORIZATION_META_KEY = Symbol("idatasource:authorization");
|
|
22
|
+
|
|
23
|
+
const controllerRegistry = new Set<Function>();
|
|
24
|
+
const serviceRegistry = new Set<Function>();
|
|
25
|
+
const optionsRegistry = new Map<string, any>();
|
|
26
|
+
|
|
27
|
+
const Container = TypediContainer;
|
|
28
|
+
|
|
29
|
+
export function registerController(controller: Function) {
|
|
30
|
+
controllerRegistry.add(controller);
|
|
31
|
+
}
|
|
32
|
+
export function registerService(service: Function) {
|
|
33
|
+
Container.set(service, service);
|
|
34
|
+
serviceRegistry.add(service);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getRegisteredServices(): Function[] {
|
|
38
|
+
return Array.from(serviceRegistry);
|
|
39
|
+
}
|
|
40
|
+
export function getRegisteredControllers(): Function[] {
|
|
41
|
+
return Array.from(controllerRegistry);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const API_CONTROLLER_METADATA_KEY = Symbol("apiController");
|
|
45
|
+
|
|
46
|
+
export function isApiController(target: Function): boolean {
|
|
47
|
+
return Reflect.getMetadata(API_CONTROLLER_METADATA_KEY, target) === true;
|
|
48
|
+
}
|
|
49
|
+
Container.set<string>("appName", "Iqra");
|
|
50
|
+
|
|
51
|
+
export function registerDataSource(dataSource: any) {
|
|
52
|
+
Container.set<DataSource>("idatasource", dataSource);
|
|
53
|
+
}
|
|
54
|
+
export default Container;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright 2024
|
|
3
|
+
* @author Tareq Hossain
|
|
4
|
+
* @email xtrinsic96@gmail.com
|
|
5
|
+
* @url https://github.com/xtareq
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import Container, { Service } from "typedi";
|
|
9
|
+
import container, {
|
|
10
|
+
API_CONTROLLER_METADATA_KEY,
|
|
11
|
+
CONTROLLER_META_KEY,
|
|
12
|
+
registerController,
|
|
13
|
+
} from "./container";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Options for configuring a controller.
|
|
17
|
+
* @remarks
|
|
18
|
+
* Controller default options
|
|
19
|
+
* @type {Object} ControllerOptions
|
|
20
|
+
* @property {string} [name] - The name of the controller.
|
|
21
|
+
* @property {string} [path] - The base path for the controller's routes.
|
|
22
|
+
* @property {string} [version] - The version of the controller. If not provided, it will default to the version from `package.json`.
|
|
23
|
+
* @property {string} [since] - The date or version since the controller was introduced.
|
|
24
|
+
* @property {any} [meta] - Additional metadata associated with the controller.
|
|
25
|
+
*/
|
|
26
|
+
export type ControllerOptions = {
|
|
27
|
+
/**
|
|
28
|
+
*@property {string} name
|
|
29
|
+
*@description Name of the controller. If specified it'll used as swagger tags
|
|
30
|
+
*@default Contorller class name
|
|
31
|
+
* */
|
|
32
|
+
name?: string;
|
|
33
|
+
path?: string;
|
|
34
|
+
version?: string; // Will look at package.json if not set
|
|
35
|
+
since?: string;
|
|
36
|
+
meta?: any;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function createControllerDecorator(
|
|
40
|
+
type: "api" | "web" = "web",
|
|
41
|
+
): (
|
|
42
|
+
pathOrOptions?: string | ControllerOptions,
|
|
43
|
+
maybeOptions?: ControllerOptions,
|
|
44
|
+
) => ClassDecorator {
|
|
45
|
+
return function (
|
|
46
|
+
pathOrOptions?: string | ControllerOptions,
|
|
47
|
+
maybeOptions?: ControllerOptions,
|
|
48
|
+
): ClassDecorator {
|
|
49
|
+
return function (target: Function) {
|
|
50
|
+
let path = "/";
|
|
51
|
+
let options: ControllerOptions = {};
|
|
52
|
+
|
|
53
|
+
if (typeof pathOrOptions === "string") {
|
|
54
|
+
path = pathOrOptions;
|
|
55
|
+
options = maybeOptions || {};
|
|
56
|
+
} else if (typeof pathOrOptions === "object") {
|
|
57
|
+
options = pathOrOptions;
|
|
58
|
+
path = options.path || "/";
|
|
59
|
+
}
|
|
60
|
+
Reflect.defineMetadata(API_CONTROLLER_METADATA_KEY, true, target);
|
|
61
|
+
// Ensure Service is applied as a ClassDecorator
|
|
62
|
+
if (typeof Service === "function") {
|
|
63
|
+
registerController(target); // Add to custom registry
|
|
64
|
+
Service()(target); // Apply DI decorator
|
|
65
|
+
Reflect.defineMetadata(
|
|
66
|
+
CONTROLLER_META_KEY,
|
|
67
|
+
{ type, path, options },
|
|
68
|
+
target,
|
|
69
|
+
);
|
|
70
|
+
} else {
|
|
71
|
+
throw new Error("Service decorator is not a function");
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Predefined Controller Decorators
|
|
78
|
+
//export const Controller = createControllerDecorator("web");
|
|
79
|
+
/**
|
|
80
|
+
*@description Api controller's are used for rest . It will populate
|
|
81
|
+
* json on return and all it http methods {get} {post} etc must return
|
|
82
|
+
*Results.*
|
|
83
|
+
* @param path {string} this will used as route prefix
|
|
84
|
+
*
|
|
85
|
+
**/
|
|
86
|
+
|
|
87
|
+
export function ApiController(target: Function): void;
|
|
88
|
+
export function ApiController(path: string): ClassDecorator;
|
|
89
|
+
/**
|
|
90
|
+
*@description Api controller's are used for rest . It will populate
|
|
91
|
+
* json on return and all it http methods {get} {post} etc must return
|
|
92
|
+
* Results.*
|
|
93
|
+
* @param {ControllerOptions} options this will used as route prefix
|
|
94
|
+
*
|
|
95
|
+
**/
|
|
96
|
+
export function ApiController(options: ControllerOptions): ClassDecorator;
|
|
97
|
+
export function ApiController(
|
|
98
|
+
path: string,
|
|
99
|
+
options?: ControllerOptions,
|
|
100
|
+
): ClassDecorator;
|
|
101
|
+
export function ApiController(
|
|
102
|
+
pathOrOptions: Function | string | ControllerOptions = "/",
|
|
103
|
+
mayBeOptions?: ControllerOptions,
|
|
104
|
+
): any {
|
|
105
|
+
if (typeof pathOrOptions == 'function') {
|
|
106
|
+
Reflect.defineMetadata(API_CONTROLLER_METADATA_KEY, true, pathOrOptions);
|
|
107
|
+
// Ensure Service is applied as a ClassDecorator
|
|
108
|
+
if (typeof Service === "function") {
|
|
109
|
+
registerController(pathOrOptions); // Add to custom registry
|
|
110
|
+
Service()(pathOrOptions); // Apply DI decorator
|
|
111
|
+
Reflect.defineMetadata(
|
|
112
|
+
CONTROLLER_META_KEY,
|
|
113
|
+
{ type: 'api', path: "/", options: {} },
|
|
114
|
+
pathOrOptions,
|
|
115
|
+
);
|
|
116
|
+
} else {
|
|
117
|
+
throw new Error("Service decorator is not a function");
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
if (mayBeOptions) {
|
|
121
|
+
return createControllerDecorator("api")(pathOrOptions, mayBeOptions);
|
|
122
|
+
}
|
|
123
|
+
return createControllerDecorator("api")(pathOrOptions);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
}
|
|
127
|
+
|