@lyrolab/nest-shared 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/.husky/commit-msg +1 -0
- package/.husky/pre-commit +1 -0
- package/.nvmrc +1 -0
- package/.prettierrc +5 -0
- package/README.md +98 -0
- package/dist/ai/ai.module.d.ts +2 -0
- package/dist/ai/ai.module.js +21 -0
- package/dist/ai/ai.module.js.map +1 -0
- package/dist/ai/services/ai.service.d.ts +10 -0
- package/dist/ai/services/ai.service.js +54 -0
- package/dist/ai/services/ai.service.js.map +1 -0
- package/dist/app.module.d.ts +2 -0
- package/dist/app.module.js +21 -0
- package/dist/app.module.js.map +1 -0
- package/dist/bull/shared-bull.module.d.ts +4 -0
- package/dist/bull/shared-bull.module.js +40 -0
- package/dist/bull/shared-bull.module.js.map +1 -0
- package/dist/cache/shared-cache.module.d.ts +4 -0
- package/dist/cache/shared-cache.module.js +40 -0
- package/dist/cache/shared-cache.module.js.map +1 -0
- package/dist/database/filters/typeorm-exception.filter.d.ts +7 -0
- package/dist/database/filters/typeorm-exception.filter.js +35 -0
- package/dist/database/filters/typeorm-exception.filter.js.map +1 -0
- package/dist/database/shared-database.module.d.ts +18 -0
- package/dist/database/shared-database.module.js +119 -0
- package/dist/database/shared-database.module.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/queue/controllers/queue.controller.d.ts +7 -0
- package/dist/queue/controllers/queue.controller.js +40 -0
- package/dist/queue/controllers/queue.controller.js.map +1 -0
- package/dist/queue/decorators/queue.decorator.d.ts +1 -0
- package/dist/queue/decorators/queue.decorator.js +6 -0
- package/dist/queue/decorators/queue.decorator.js.map +1 -0
- package/dist/queue/models/dto/queue-add.dto.d.ts +4 -0
- package/dist/queue/models/dto/queue-add.dto.js +28 -0
- package/dist/queue/models/dto/queue-add.dto.js.map +1 -0
- package/dist/queue/models/job-processor-interface.d.ts +4 -0
- package/dist/queue/models/job-processor-interface.js +3 -0
- package/dist/queue/models/job-processor-interface.js.map +1 -0
- package/dist/queue/processors/queue.processor.d.ts +11 -0
- package/dist/queue/processors/queue.processor.js +63 -0
- package/dist/queue/processors/queue.processor.js.map +1 -0
- package/dist/queue/queue.constants.d.ts +1 -0
- package/dist/queue/queue.constants.js +5 -0
- package/dist/queue/queue.constants.js.map +1 -0
- package/dist/queue/queue.module.d.ts +2 -0
- package/dist/queue/queue.module.js +28 -0
- package/dist/queue/queue.module.js.map +1 -0
- package/dist/queue/services/queue.service.d.ts +6 -0
- package/dist/queue/services/queue.service.js +35 -0
- package/dist/queue/services/queue.service.js.map +1 -0
- package/dist/redis/redis.config.d.ts +4 -0
- package/dist/redis/redis.config.js +11 -0
- package/dist/redis/redis.config.js.map +1 -0
- package/dist/redis/shared-redis.module.d.ts +9 -0
- package/dist/redis/shared-redis.module.js +68 -0
- package/dist/redis/shared-redis.module.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/eslint.config.mjs +35 -0
- package/nest-cli.json +9 -0
- package/package.json +126 -0
- package/src/ai/ai.module.ts +8 -0
- package/src/ai/services/ai.service.ts +51 -0
- package/src/app.module.ts +8 -0
- package/src/bull/shared-bull.module.ts +27 -0
- package/src/cache/shared-cache.module.ts +30 -0
- package/src/database/filters/typeorm-exception.filter.ts +35 -0
- package/src/database/shared-database.module.ts +136 -0
- package/src/index.ts +12 -0
- package/src/queue/controllers/queue.controller.ts +13 -0
- package/src/queue/decorators/queue.decorator.ts +3 -0
- package/src/queue/models/dto/queue-add.dto.ts +10 -0
- package/src/queue/models/job-processor-interface.ts +5 -0
- package/src/queue/processors/queue.processor.ts +53 -0
- package/src/queue/queue.constants.ts +1 -0
- package/src/queue/queue.module.ts +14 -0
- package/src/queue/services/queue.service.ts +17 -0
- package/src/redis/redis.config.ts +3 -0
- package/src/redis/shared-redis.module.ts +66 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import eslint from '@eslint/js';
|
|
3
|
+
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
|
4
|
+
import globals from 'globals';
|
|
5
|
+
import tseslint from 'typescript-eslint';
|
|
6
|
+
|
|
7
|
+
export default tseslint.config(
|
|
8
|
+
{
|
|
9
|
+
ignores: ['eslint.config.mjs'],
|
|
10
|
+
},
|
|
11
|
+
eslint.configs.recommended,
|
|
12
|
+
...tseslint.configs.recommendedTypeChecked,
|
|
13
|
+
eslintPluginPrettierRecommended,
|
|
14
|
+
{
|
|
15
|
+
languageOptions: {
|
|
16
|
+
globals: {
|
|
17
|
+
...globals.node,
|
|
18
|
+
...globals.jest,
|
|
19
|
+
},
|
|
20
|
+
ecmaVersion: 5,
|
|
21
|
+
sourceType: 'module',
|
|
22
|
+
parserOptions: {
|
|
23
|
+
projectService: true,
|
|
24
|
+
tsconfigRootDir: import.meta.dirname,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
rules: {
|
|
30
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
31
|
+
'@typescript-eslint/no-floating-promises': 'warn',
|
|
32
|
+
'@typescript-eslint/no-unsafe-argument': 'warn'
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
);
|
package/nest-cli.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lyrolab/nest-shared",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"author": "",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"commitlint": {
|
|
8
|
+
"extends": [
|
|
9
|
+
"@commitlint/config-conventional"
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
"main": "dist/index.js",
|
|
13
|
+
"types": "dist/index.d.ts",
|
|
14
|
+
"scripts": {
|
|
15
|
+
"prebuild": "rimraf dist",
|
|
16
|
+
"build": "tsc -p tsconfig.build.json",
|
|
17
|
+
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"libs/**/*.ts\"",
|
|
18
|
+
"start": "nest start",
|
|
19
|
+
"start:dev": "nest start --watch",
|
|
20
|
+
"start:debug": "nest start --debug --watch",
|
|
21
|
+
"start:prod": "node dist/main",
|
|
22
|
+
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
|
23
|
+
"test": "jest",
|
|
24
|
+
"test:watch": "jest --watch",
|
|
25
|
+
"test:cov": "jest --coverage",
|
|
26
|
+
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
|
27
|
+
"test:e2e": "jest --config ./test/jest-e2e.json",
|
|
28
|
+
"prepare": "husky && npm run build",
|
|
29
|
+
"lint-staged": "npx lint-staged"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@nestjs/common": "^11.0.1",
|
|
33
|
+
"@nestjs/platform-express": "^11.0.1",
|
|
34
|
+
"reflect-metadata": "^0.2.2",
|
|
35
|
+
"rxjs": "^7.8.1"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@nestjs/bullmq": "^11.0.0",
|
|
39
|
+
"@nestjs/cache-manager": "^3.0.0",
|
|
40
|
+
"@nestjs/config": "^4.0.0",
|
|
41
|
+
"@nestjs/typeorm": "^11.0.0",
|
|
42
|
+
"@openrouter/ai-sdk-provider": "^0.4.0",
|
|
43
|
+
"ai": "^4.2.0",
|
|
44
|
+
"bullmq": "^5.0.0",
|
|
45
|
+
"cache-manager": "^6.0.0",
|
|
46
|
+
"class-validator": "^0.14.0",
|
|
47
|
+
"testcontainers": "^10.0.0",
|
|
48
|
+
"typeorm": "^0.3.22"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@commitlint/cli": "^19.8.0",
|
|
52
|
+
"@commitlint/config-conventional": "^19.8.0",
|
|
53
|
+
"@eslint/eslintrc": "^3.2.0",
|
|
54
|
+
"@eslint/js": "^9.18.0",
|
|
55
|
+
"@keyv/redis": "^4.3.2",
|
|
56
|
+
"@nestjs/bullmq": "^11.0.2",
|
|
57
|
+
"@nestjs/cache-manager": "^3.0.1",
|
|
58
|
+
"@nestjs/cli": "^11.0.0",
|
|
59
|
+
"@nestjs/config": "^4.0.2",
|
|
60
|
+
"@nestjs/core": "^11.0.13",
|
|
61
|
+
"@nestjs/schematics": "^11.0.0",
|
|
62
|
+
"@nestjs/testing": "^11.0.1",
|
|
63
|
+
"@nestjs/typeorm": "^11.0.0",
|
|
64
|
+
"@openrouter/ai-sdk-provider": "^0.4.5",
|
|
65
|
+
"@swc/cli": "^0.6.0",
|
|
66
|
+
"@swc/core": "^1.10.7",
|
|
67
|
+
"@types/express": "^5.0.0",
|
|
68
|
+
"@types/jest": "^29.5.14",
|
|
69
|
+
"@types/node": "^22.10.7",
|
|
70
|
+
"@types/supertest": "^6.0.2",
|
|
71
|
+
"ai": "^4.2.11",
|
|
72
|
+
"bullmq": "^5.46.1",
|
|
73
|
+
"cache-manager": "^6.4.1",
|
|
74
|
+
"class-validator": "^0.14.1",
|
|
75
|
+
"eslint": "^9.18.0",
|
|
76
|
+
"eslint-config-prettier": "^10.1.1",
|
|
77
|
+
"eslint-plugin-prettier": "^5.2.6",
|
|
78
|
+
"globals": "^16.0.0",
|
|
79
|
+
"husky": "^9.1.7",
|
|
80
|
+
"jest": "^29.7.0",
|
|
81
|
+
"prettier": "^3.5.3",
|
|
82
|
+
"rimraf": "^6.0.1",
|
|
83
|
+
"source-map-support": "^0.5.21",
|
|
84
|
+
"supertest": "^7.0.0",
|
|
85
|
+
"testcontainers": "^10.24.0",
|
|
86
|
+
"ts-jest": "^29.2.5",
|
|
87
|
+
"ts-loader": "^9.5.2",
|
|
88
|
+
"ts-node": "^10.9.2",
|
|
89
|
+
"tsconfig-paths": "^4.2.0",
|
|
90
|
+
"typeorm": "^0.3.22",
|
|
91
|
+
"typescript": "^5.7.3",
|
|
92
|
+
"typescript-eslint": "^8.20.0"
|
|
93
|
+
},
|
|
94
|
+
"jest": {
|
|
95
|
+
"moduleFileExtensions": [
|
|
96
|
+
"js",
|
|
97
|
+
"json",
|
|
98
|
+
"ts"
|
|
99
|
+
],
|
|
100
|
+
"rootDir": ".",
|
|
101
|
+
"testRegex": ".*\\.spec\\.ts$",
|
|
102
|
+
"transform": {
|
|
103
|
+
"^.+\\.(t|j)s$": "ts-jest"
|
|
104
|
+
},
|
|
105
|
+
"collectCoverageFrom": [
|
|
106
|
+
"**/*.(t|j)s"
|
|
107
|
+
],
|
|
108
|
+
"coverageDirectory": "./coverage",
|
|
109
|
+
"testEnvironment": "node",
|
|
110
|
+
"roots": [
|
|
111
|
+
"<rootDir>/src/",
|
|
112
|
+
"<rootDir>/libs/"
|
|
113
|
+
],
|
|
114
|
+
"moduleNameMapper": {
|
|
115
|
+
"^@app/nest-shared(|/.*)$": "<rootDir>/libs/nest-shared/src/$1"
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"lint-staged": {
|
|
119
|
+
"*.{js,ts,json,css,scss,md}": [
|
|
120
|
+
"prettier --write"
|
|
121
|
+
],
|
|
122
|
+
"*.{js,ts}": [
|
|
123
|
+
"eslint --fix"
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { CACHE_MANAGER } from "@nestjs/cache-manager"
|
|
2
|
+
import { Inject, Injectable } from "@nestjs/common"
|
|
3
|
+
import {
|
|
4
|
+
createOpenRouter,
|
|
5
|
+
OpenRouterProvider,
|
|
6
|
+
} from "@openrouter/ai-sdk-provider"
|
|
7
|
+
import {
|
|
8
|
+
LanguageModelV1,
|
|
9
|
+
LanguageModelV1Middleware,
|
|
10
|
+
wrapLanguageModel,
|
|
11
|
+
} from "ai"
|
|
12
|
+
import { Cache } from "cache-manager"
|
|
13
|
+
|
|
14
|
+
@Injectable()
|
|
15
|
+
export class AiService {
|
|
16
|
+
private readonly openrouter: OpenRouterProvider
|
|
17
|
+
private readonly openrouterChat: LanguageModelV1
|
|
18
|
+
|
|
19
|
+
constructor(@Inject(CACHE_MANAGER) private cache: Cache) {
|
|
20
|
+
this.openrouter = createOpenRouter({
|
|
21
|
+
apiKey: process.env.OPENROUTER_API_KEY,
|
|
22
|
+
})
|
|
23
|
+
this.openrouterChat = wrapLanguageModel({
|
|
24
|
+
model: this.openrouter.chat("google/gemini-2.0-flash-001"),
|
|
25
|
+
middleware: [{ wrapGenerate: (options) => this.wrapGenerate(options) }],
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get model() {
|
|
30
|
+
return this.openrouterChat
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private async wrapGenerate({
|
|
34
|
+
doGenerate,
|
|
35
|
+
params,
|
|
36
|
+
}: Parameters<
|
|
37
|
+
NonNullable<LanguageModelV1Middleware["wrapGenerate"]>
|
|
38
|
+
>[0]): Promise<Awaited<ReturnType<LanguageModelV1["doGenerate"]>>> {
|
|
39
|
+
const cacheKey = "ai:" + JSON.stringify(params)
|
|
40
|
+
|
|
41
|
+
const cachedResult = await this.cache.get(cacheKey)
|
|
42
|
+
if (cachedResult) {
|
|
43
|
+
return cachedResult as Awaited<ReturnType<LanguageModelV1["doGenerate"]>>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const result = await doGenerate()
|
|
47
|
+
|
|
48
|
+
await this.cache.set(cacheKey, result)
|
|
49
|
+
return result
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { BullModule } from "@nestjs/bullmq"
|
|
2
|
+
import { DynamicModule, Module } from "@nestjs/common"
|
|
3
|
+
import { ConfigModule } from "@nestjs/config"
|
|
4
|
+
import { SharedRedisModule } from "../redis/shared-redis.module"
|
|
5
|
+
import { RedisConfig } from "../redis/redis.config"
|
|
6
|
+
|
|
7
|
+
@Module({})
|
|
8
|
+
export class SharedBullModule {
|
|
9
|
+
static forRoot(): DynamicModule {
|
|
10
|
+
return {
|
|
11
|
+
module: SharedBullModule,
|
|
12
|
+
imports: [
|
|
13
|
+
SharedRedisModule,
|
|
14
|
+
BullModule.forRootAsync({
|
|
15
|
+
imports: [ConfigModule],
|
|
16
|
+
inject: [RedisConfig],
|
|
17
|
+
useFactory: (redisConfig: RedisConfig) => ({
|
|
18
|
+
connection: {
|
|
19
|
+
url: redisConfig.url,
|
|
20
|
+
},
|
|
21
|
+
}),
|
|
22
|
+
}),
|
|
23
|
+
],
|
|
24
|
+
exports: [BullModule],
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import KeyvRedis from "@keyv/redis"
|
|
2
|
+
import { CacheModule } from "@nestjs/cache-manager"
|
|
3
|
+
import { DynamicModule, Module } from "@nestjs/common"
|
|
4
|
+
import { ConfigService } from "@nestjs/config"
|
|
5
|
+
import { RedisConfig } from "../redis/redis.config"
|
|
6
|
+
import { SharedRedisModule } from "../redis/shared-redis.module"
|
|
7
|
+
|
|
8
|
+
@Module({})
|
|
9
|
+
export class SharedCacheModule {
|
|
10
|
+
static forRoot(): DynamicModule {
|
|
11
|
+
return {
|
|
12
|
+
module: SharedCacheModule,
|
|
13
|
+
imports: [
|
|
14
|
+
SharedRedisModule,
|
|
15
|
+
CacheModule.registerAsync({
|
|
16
|
+
isGlobal: true,
|
|
17
|
+
useFactory: (
|
|
18
|
+
configService: ConfigService,
|
|
19
|
+
redisConfig: RedisConfig,
|
|
20
|
+
) => ({
|
|
21
|
+
ttl: +configService.get("CACHE_TTL") * 1000,
|
|
22
|
+
stores: [new KeyvRedis(redisConfig.url)],
|
|
23
|
+
}),
|
|
24
|
+
inject: [ConfigService, RedisConfig],
|
|
25
|
+
}),
|
|
26
|
+
],
|
|
27
|
+
exports: [CacheModule],
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ExceptionFilter,
|
|
3
|
+
Catch,
|
|
4
|
+
ArgumentsHost,
|
|
5
|
+
NotFoundException,
|
|
6
|
+
Logger,
|
|
7
|
+
} from "@nestjs/common"
|
|
8
|
+
import { Response } from "express"
|
|
9
|
+
import { EntityNotFoundError } from "typeorm"
|
|
10
|
+
|
|
11
|
+
@Catch(EntityNotFoundError)
|
|
12
|
+
export class TypeOrmExceptionFilter implements ExceptionFilter {
|
|
13
|
+
private readonly logger = new Logger(TypeOrmExceptionFilter.name)
|
|
14
|
+
|
|
15
|
+
catch(exception: EntityNotFoundError, host: ArgumentsHost) {
|
|
16
|
+
const ctx = host.switchToHttp()
|
|
17
|
+
const response = ctx.getResponse<Response>()
|
|
18
|
+
|
|
19
|
+
this.logger.debug(`Original TypeORM error: ${exception.message}`)
|
|
20
|
+
|
|
21
|
+
const entityName = this.extractEntityName(exception.message)
|
|
22
|
+
const message = entityName ? `${entityName} not found` : "Entity not found"
|
|
23
|
+
|
|
24
|
+
const notFoundException = new NotFoundException(message)
|
|
25
|
+
|
|
26
|
+
response
|
|
27
|
+
.status(notFoundException.getStatus())
|
|
28
|
+
.json(notFoundException.getResponse())
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private extractEntityName(errorMessage: string): string | null {
|
|
32
|
+
const match = errorMessage.match(/entity of type "([^"]+)"/)
|
|
33
|
+
return match ? match[1] : null
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { DynamicModule, Module, OnModuleDestroy } from "@nestjs/common"
|
|
2
|
+
import { ConfigModule, ConfigService } from "@nestjs/config"
|
|
3
|
+
import { TypeOrmModule, TypeOrmModuleOptions } from "@nestjs/typeorm"
|
|
4
|
+
import { join } from "path"
|
|
5
|
+
import { GenericContainer, StartedTestContainer, Wait } from "testcontainers"
|
|
6
|
+
import { DataSource } from "typeorm"
|
|
7
|
+
|
|
8
|
+
type SharedDatabaseModuleOptions = {
|
|
9
|
+
entities?: string[]
|
|
10
|
+
migrations?: string[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@Module({})
|
|
14
|
+
export class SharedDatabaseModule implements OnModuleDestroy {
|
|
15
|
+
private static testContainer: StartedTestContainer
|
|
16
|
+
private static testDataSource: DataSource
|
|
17
|
+
|
|
18
|
+
static forRoot(options: SharedDatabaseModuleOptions = {}): DynamicModule {
|
|
19
|
+
return {
|
|
20
|
+
module: SharedDatabaseModule,
|
|
21
|
+
imports: [
|
|
22
|
+
TypeOrmModule.forRootAsync({
|
|
23
|
+
imports: [ConfigModule],
|
|
24
|
+
inject: [ConfigService],
|
|
25
|
+
useFactory: async (configService: ConfigService) => {
|
|
26
|
+
const isTestEnvironment = process.env.NODE_ENV === "test"
|
|
27
|
+
|
|
28
|
+
if (isTestEnvironment) {
|
|
29
|
+
return await this.createTestConfiguration(options)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return this.createProductionConfiguration(configService, options)
|
|
33
|
+
},
|
|
34
|
+
}),
|
|
35
|
+
],
|
|
36
|
+
exports: [TypeOrmModule],
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private static async createTestConfiguration(
|
|
41
|
+
options: SharedDatabaseModuleOptions,
|
|
42
|
+
): Promise<TypeOrmModuleOptions> {
|
|
43
|
+
if (!this.testContainer) {
|
|
44
|
+
this.testContainer = await new GenericContainer("postgres")
|
|
45
|
+
.withExposedPorts(5432)
|
|
46
|
+
.withEnvironment({
|
|
47
|
+
POSTGRES_PASSWORD: "secret",
|
|
48
|
+
POSTGRES_DB: "test",
|
|
49
|
+
})
|
|
50
|
+
.withWaitStrategy(
|
|
51
|
+
Wait.forAll([
|
|
52
|
+
Wait.forListeningPorts(),
|
|
53
|
+
Wait.forLogMessage(
|
|
54
|
+
"database system is ready to accept connections",
|
|
55
|
+
),
|
|
56
|
+
]),
|
|
57
|
+
)
|
|
58
|
+
.start()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const connectionUri = `postgres://postgres:secret@localhost:${this.testContainer.getMappedPort(
|
|
62
|
+
5432,
|
|
63
|
+
)}/test`
|
|
64
|
+
|
|
65
|
+
const entities = options.entities || [
|
|
66
|
+
join(__dirname, "../../../**/*.entity{.ts,.js}"),
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
const migrations = options.migrations || [
|
|
70
|
+
join(__dirname, "../../../migrations/**/*.{ts,js}"),
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
if (!this.testDataSource) {
|
|
74
|
+
this.testDataSource = new DataSource({
|
|
75
|
+
type: "postgres",
|
|
76
|
+
url: connectionUri,
|
|
77
|
+
entities,
|
|
78
|
+
synchronize: true,
|
|
79
|
+
migrations,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
await this.testDataSource.initialize()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
type: "postgres",
|
|
87
|
+
url: connectionUri,
|
|
88
|
+
entities,
|
|
89
|
+
synchronize: true,
|
|
90
|
+
autoLoadEntities: true,
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private static createProductionConfiguration(
|
|
95
|
+
configService: ConfigService,
|
|
96
|
+
options: SharedDatabaseModuleOptions,
|
|
97
|
+
): TypeOrmModuleOptions {
|
|
98
|
+
return {
|
|
99
|
+
type: "postgres",
|
|
100
|
+
url: configService.get("DATABASE_URL"),
|
|
101
|
+
entities: options.entities || [
|
|
102
|
+
join(__dirname, "../../../**/*.entity{.ts,.js}"),
|
|
103
|
+
],
|
|
104
|
+
migrations: options.migrations || [
|
|
105
|
+
join(__dirname, "../../../migrations/**/*.{ts,js}"),
|
|
106
|
+
],
|
|
107
|
+
synchronize: false,
|
|
108
|
+
autoLoadEntities: true,
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
static getTestDataSource(): DataSource {
|
|
113
|
+
return this.testDataSource
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
static async closeTestConnection(): Promise<void> {
|
|
117
|
+
if (this.testDataSource) {
|
|
118
|
+
await this.testDataSource.destroy()
|
|
119
|
+
}
|
|
120
|
+
if (this.testContainer) {
|
|
121
|
+
await this.testContainer.stop()
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
static async clearTestDatabase(): Promise<void> {
|
|
126
|
+
if (this.testDataSource) {
|
|
127
|
+
await this.testDataSource.synchronize(true)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async onModuleDestroy() {
|
|
132
|
+
if (SharedDatabaseModule.testDataSource) {
|
|
133
|
+
await SharedDatabaseModule.testDataSource.destroy()
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from "./ai/ai.module"
|
|
2
|
+
export * from "./ai/services/ai.service"
|
|
3
|
+
export * from "./bull/shared-bull.module"
|
|
4
|
+
export * from "./cache/shared-cache.module"
|
|
5
|
+
export * from "./database/shared-database.module"
|
|
6
|
+
export * from "./database/filters/typeorm-exception.filter"
|
|
7
|
+
export * from "./queue/queue.module"
|
|
8
|
+
export * from "./queue/services/queue.service"
|
|
9
|
+
export * from "./queue/models/job-processor-interface"
|
|
10
|
+
export * from "./queue/decorators/queue.decorator"
|
|
11
|
+
export * from "./redis/shared-redis.module"
|
|
12
|
+
export * from "./redis/redis.config"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Body, Controller, Post } from "@nestjs/common"
|
|
2
|
+
import { QueueService } from "../services/queue.service"
|
|
3
|
+
import { QueueAddDto } from "../models/dto/queue-add.dto"
|
|
4
|
+
|
|
5
|
+
@Controller("queue")
|
|
6
|
+
export class QueueController {
|
|
7
|
+
constructor(private readonly queueService: QueueService) {}
|
|
8
|
+
|
|
9
|
+
@Post("add")
|
|
10
|
+
async add(@Body() body: QueueAddDto) {
|
|
11
|
+
return this.queueService.add(body.name, body.data)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { InjectQueue, Processor, WorkerHost } from "@nestjs/bullmq"
|
|
2
|
+
import { DiscoveryService } from "@nestjs/core"
|
|
3
|
+
import { Job, Queue } from "bullmq"
|
|
4
|
+
import { JobProcessor } from "../decorators/queue.decorator"
|
|
5
|
+
import { JobProcessorInterface } from "../models/job-processor-interface"
|
|
6
|
+
import { DEFAULT_QUEUE } from "../queue.constants"
|
|
7
|
+
|
|
8
|
+
@Processor(DEFAULT_QUEUE, { concurrency: 100 })
|
|
9
|
+
export class QueueProcessor extends WorkerHost {
|
|
10
|
+
constructor(
|
|
11
|
+
private readonly discoveryService: DiscoveryService,
|
|
12
|
+
@InjectQueue(DEFAULT_QUEUE) private readonly queue: Queue,
|
|
13
|
+
) {
|
|
14
|
+
super()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async process(job: Job) {
|
|
18
|
+
const jobProcessor = this.jobProcessorFor(job.name)
|
|
19
|
+
if (!jobProcessor) return
|
|
20
|
+
|
|
21
|
+
const shouldProcess = await this.shouldProcessJob(job)
|
|
22
|
+
if (!shouldProcess) return
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
await jobProcessor.process(job)
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error(error)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private jobProcessorFor(jobName: string) {
|
|
32
|
+
return this.discoveryService
|
|
33
|
+
.getProviders({ metadataKey: JobProcessor.KEY })
|
|
34
|
+
.find(
|
|
35
|
+
(provider) =>
|
|
36
|
+
this.discoveryService.getMetadataByDecorator(
|
|
37
|
+
JobProcessor,
|
|
38
|
+
provider,
|
|
39
|
+
) === jobName,
|
|
40
|
+
)?.instance as JobProcessorInterface
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private async shouldProcessJob(job: Job) {
|
|
44
|
+
if (!job.repeatJobKey) return true
|
|
45
|
+
|
|
46
|
+
const activeJobs = await this.queue.getActive()
|
|
47
|
+
const activeJobsOfSameCronjob = activeJobs.filter(
|
|
48
|
+
({ name, id }) => name === job.name && id !== job.id,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return activeJobsOfSameCronjob.length === 0
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DEFAULT_QUEUE = "default"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BullModule } from "@nestjs/bullmq"
|
|
2
|
+
import { Module } from "@nestjs/common"
|
|
3
|
+
import { DiscoveryModule } from "@nestjs/core"
|
|
4
|
+
import { QueueProcessor } from "./processors/queue.processor"
|
|
5
|
+
import { DEFAULT_QUEUE } from "./queue.constants"
|
|
6
|
+
import { QueueService } from "./services/queue.service"
|
|
7
|
+
import { QueueController } from "./controllers/queue.controller"
|
|
8
|
+
@Module({
|
|
9
|
+
imports: [BullModule.registerQueue({ name: DEFAULT_QUEUE }), DiscoveryModule],
|
|
10
|
+
providers: [QueueService, QueueProcessor],
|
|
11
|
+
exports: [QueueService],
|
|
12
|
+
controllers: [QueueController],
|
|
13
|
+
})
|
|
14
|
+
export class QueueModule {}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { InjectQueue } from "@nestjs/bullmq"
|
|
2
|
+
import { Injectable } from "@nestjs/common"
|
|
3
|
+
import { Job, JobsOptions, Queue } from "bullmq"
|
|
4
|
+
import { DEFAULT_QUEUE } from "../queue.constants"
|
|
5
|
+
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class QueueService {
|
|
8
|
+
constructor(@InjectQueue(DEFAULT_QUEUE) private readonly queue: Queue) {}
|
|
9
|
+
|
|
10
|
+
add(
|
|
11
|
+
name: string,
|
|
12
|
+
data: any,
|
|
13
|
+
opts?: JobsOptions,
|
|
14
|
+
): Promise<Job<any, any, string>> {
|
|
15
|
+
return this.queue.add(name, data, opts)
|
|
16
|
+
}
|
|
17
|
+
}
|