@gennext/lb-infra 0.3.0 → 0.3.4
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 +414 -342
- package/dist/base/applications/default.application.d.ts.map +1 -1
- package/dist/base/applications/default.application.js +9 -0
- package/dist/base/applications/default.application.js.map +1 -1
- package/dist/base/base.sequence.js +3 -4
- package/dist/base/base.sequence.js.map +1 -1
- package/dist/base/controllers/common.d.ts +9 -0
- package/dist/base/controllers/common.d.ts.map +1 -1
- package/dist/base/controllers/common.js +20 -1
- package/dist/base/controllers/common.js.map +1 -1
- package/dist/base/controllers/crud.controller.d.ts +33 -22
- package/dist/base/controllers/crud.controller.d.ts.map +1 -1
- package/dist/base/controllers/crud.controller.js +20 -332
- package/dist/base/controllers/crud.controller.js.map +1 -1
- package/dist/base/controllers/crud.executor.d.ts +69 -0
- package/dist/base/controllers/crud.executor.d.ts.map +1 -0
- package/dist/base/controllers/crud.executor.js +140 -0
- package/dist/base/controllers/crud.executor.js.map +1 -0
- package/dist/base/controllers/crud.mixin.d.ts +77 -0
- package/dist/base/controllers/crud.mixin.d.ts.map +1 -0
- package/dist/base/controllers/crud.mixin.js +269 -0
- package/dist/base/controllers/crud.mixin.js.map +1 -0
- package/dist/base/controllers/index.d.ts +2 -0
- package/dist/base/controllers/index.d.ts.map +1 -1
- package/dist/base/controllers/index.js +2 -0
- package/dist/base/controllers/index.js.map +1 -1
- package/dist/base/controllers/kv.controller.js +2 -2
- package/dist/base/controllers/kv.controller.js.map +1 -1
- package/dist/base/controllers/relational.controller.d.ts.map +1 -1
- package/dist/base/controllers/relational.controller.js +67 -118
- package/dist/base/controllers/relational.controller.js.map +1 -1
- package/dist/base/controllers/service-crud.controller.d.ts +11 -10
- package/dist/base/controllers/service-crud.controller.d.ts.map +1 -1
- package/dist/base/controllers/service-crud.controller.js +24 -373
- package/dist/base/controllers/service-crud.controller.js.map +1 -1
- package/dist/base/repositories/base.repository.d.ts.map +1 -1
- package/dist/base/repositories/base.repository.js +2 -2
- package/dist/base/repositories/base.repository.js.map +1 -1
- package/dist/base/repositories/relations/has-many-polymorphic/factory.d.ts.map +1 -1
- package/dist/base/repositories/relations/has-many-polymorphic/factory.js +1 -5
- package/dist/base/repositories/relations/has-many-polymorphic/factory.js.map +1 -1
- package/dist/base/repositories/relations/has-many-through-polymorphic/factory.d.ts.map +1 -1
- package/dist/base/repositories/relations/has-many-through-polymorphic/factory.js +9 -10
- package/dist/base/repositories/relations/has-many-through-polymorphic/factory.js.map +1 -1
- package/dist/base/repositories/searchable-tz-crud.repository.d.ts.map +1 -1
- package/dist/base/repositories/searchable-tz-crud.repository.js +8 -9
- package/dist/base/repositories/searchable-tz-crud.repository.js.map +1 -1
- package/dist/base/repositories/tz-crud.repository.d.ts.map +1 -1
- package/dist/base/repositories/tz-crud.repository.js +3 -6
- package/dist/base/repositories/tz-crud.repository.js.map +1 -1
- package/dist/common/constants.d.ts +1 -0
- package/dist/common/constants.d.ts.map +1 -1
- package/dist/common/constants.js +1 -0
- package/dist/common/constants.js.map +1 -1
- package/dist/common/environments.d.ts +19 -0
- package/dist/common/environments.d.ts.map +1 -1
- package/dist/common/environments.js +104 -1
- package/dist/common/environments.js.map +1 -1
- package/dist/common/keys.d.ts +16 -0
- package/dist/common/keys.d.ts.map +1 -1
- package/dist/common/keys.js +23 -0
- package/dist/common/keys.js.map +1 -1
- package/dist/components/authenticate/component.js +1 -1
- package/dist/components/authenticate/component.js.map +1 -1
- package/dist/components/authenticate/controllers/auth.controller.d.ts +8 -0
- package/dist/components/authenticate/controllers/auth.controller.d.ts.map +1 -1
- package/dist/components/authenticate/controllers/auth.controller.js +9 -8
- package/dist/components/authenticate/controllers/auth.controller.js.map +1 -1
- package/dist/components/authenticate/controllers/oauth2.controller.d.ts +48 -16
- package/dist/components/authenticate/controllers/oauth2.controller.d.ts.map +1 -1
- package/dist/components/authenticate/controllers/oauth2.controller.js +4 -4
- package/dist/components/authenticate/controllers/oauth2.controller.js.map +1 -1
- package/dist/components/authenticate/oauth2-handlers/base.d.ts.map +1 -1
- package/dist/components/authenticate/oauth2-handlers/base.js +8 -12
- package/dist/components/authenticate/oauth2-handlers/base.js.map +1 -1
- package/dist/components/authenticate/oauth2-handlers/data/user-data-fetcher.d.ts.map +1 -1
- package/dist/components/authenticate/oauth2-handlers/data/user-data-fetcher.js +2 -1
- package/dist/components/authenticate/oauth2-handlers/data/user-data-fetcher.js.map +1 -1
- package/dist/components/authenticate/services/basic.strategy.d.ts.map +1 -1
- package/dist/components/authenticate/services/basic.strategy.js +4 -3
- package/dist/components/authenticate/services/basic.strategy.js.map +1 -1
- package/dist/components/authenticate/services/jwt.strategy.d.ts.map +1 -1
- package/dist/components/authenticate/services/jwt.strategy.js +4 -3
- package/dist/components/authenticate/services/jwt.strategy.js.map +1 -1
- package/dist/components/authenticate/services/oauth2-scope.service.d.ts.map +1 -1
- package/dist/components/authenticate/services/oauth2-scope.service.js +2 -1
- package/dist/components/authenticate/services/oauth2-scope.service.js.map +1 -1
- package/dist/components/authenticate/services/oauth2.service.js +1 -1
- package/dist/components/authenticate/services/oauth2.service.js.map +1 -1
- package/dist/components/crash-report/component.js +1 -1
- package/dist/components/crash-report/providers/provider.d.ts.map +1 -1
- package/dist/components/crash-report/providers/provider.js +5 -4
- package/dist/components/crash-report/providers/provider.js.map +1 -1
- package/dist/components/crash-report/services/{mt-crash-report.service.d.ts → gn-crash-report.service.d.ts} +2 -2
- package/dist/components/crash-report/services/{mt-crash-report.service.d.ts.map → gn-crash-report.service.d.ts.map} +1 -1
- package/dist/components/crash-report/services/{mt-crash-report.service.js → gn-crash-report.service.js} +6 -7
- package/dist/components/crash-report/services/gn-crash-report.service.js.map +1 -0
- package/dist/components/crash-report/services/index.d.ts +1 -1
- package/dist/components/crash-report/services/index.js +1 -1
- package/dist/components/grpc/helpers/grpc-server.d.ts.map +1 -1
- package/dist/components/grpc/helpers/grpc-server.js +4 -6
- package/dist/components/grpc/helpers/grpc-server.js.map +1 -1
- package/dist/components/migration/component.d.ts.map +1 -1
- package/dist/components/migration/component.js +0 -4
- package/dist/components/migration/component.js.map +1 -1
- package/dist/datasources/postgres/datasource.js +1 -2
- package/dist/datasources/postgres/datasource.js.map +1 -1
- package/dist/helpers/testing/test-case.js +1 -2
- package/dist/helpers/testing/test-case.js.map +1 -1
- package/dist/interceptors/content-range.interceptor.d.ts.map +1 -1
- package/dist/interceptors/content-range.interceptor.js +6 -5
- package/dist/interceptors/content-range.interceptor.js.map +1 -1
- package/dist/migrations/handler.js +1 -1
- package/dist/migrations/handler.js.map +1 -1
- package/dist/utilities/parse.utility.d.ts.map +1 -1
- package/dist/utilities/parse.utility.js +1 -2
- package/dist/utilities/parse.utility.js.map +1 -1
- package/package.json +1 -1
- package/dist/components/crash-report/services/mt-crash-report.service.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,24 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
Gennex Technology - LoopBack 4 infrastructure framework.
|
|
4
4
|
|
|
5
|
-
`@gennext/lb-infra`
|
|
5
|
+
`@gennext/lb-infra` cung cấp các lớp cơ sở (base classes), components, helpers, datasources, model mixins, repositories và các controller generator dùng chung để xây dựng các dịch vụ backend chất lượng cao trên nền tảng LoopBack 4.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
---
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- Bun `>= 1.3.2`
|
|
11
|
-
- TypeScript
|
|
9
|
+
## 1. Requirements
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
- **Node.js**: `>= 18`
|
|
12
|
+
- **Bun**: `>= 1.3.2`
|
|
13
|
+
- **TypeScript**
|
|
14
|
+
|
|
15
|
+
Cài đặt dependencies từ thư mục gốc của repository:
|
|
14
16
|
|
|
15
17
|
```bash
|
|
16
18
|
bun install
|
|
17
19
|
```
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Run commands from the repository root:
|
|
22
|
-
|
|
21
|
+
### Các lệnh chạy trong package:
|
|
23
22
|
```bash
|
|
24
23
|
bun run --filter "@gennext/lb-infra" build
|
|
25
24
|
bun run --filter "@gennext/lb-infra" clean
|
|
@@ -28,33 +27,28 @@ bun run --filter "@gennext/lb-infra" lint
|
|
|
28
27
|
bun run --filter "@gennext/lb-infra" test
|
|
29
28
|
```
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
Lệnh tắt từ root:
|
|
33
31
|
```bash
|
|
34
32
|
bun run rebuild:lb-infra
|
|
35
33
|
```
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
- `src/components/authenticate/views`
|
|
40
|
-
- `static`
|
|
41
|
-
- `tsconfig.json` as `dist/tsconfig.base.json`
|
|
35
|
+
---
|
|
42
36
|
|
|
43
|
-
## CLI
|
|
37
|
+
## 2. CLI
|
|
44
38
|
|
|
45
|
-
|
|
39
|
+
Sau khi cài đặt package, binary `lb-infra` có thể được sử dụng để khởi tạo cấu trúc thư mục mặc định cho dự án consumer-app:
|
|
46
40
|
|
|
47
41
|
```bash
|
|
48
42
|
lb-infra init
|
|
49
43
|
```
|
|
50
44
|
|
|
51
|
-
|
|
45
|
+
Hoặc trỏ tới một thư mục cụ thể:
|
|
52
46
|
|
|
53
47
|
```bash
|
|
54
48
|
lb-infra init ./apps/api
|
|
55
49
|
```
|
|
56
50
|
|
|
57
|
-
|
|
51
|
+
Cấu trúc thư mục được sinh ra:
|
|
58
52
|
|
|
59
53
|
```text
|
|
60
54
|
src
|
|
@@ -72,13 +66,17 @@ src
|
|
|
72
66
|
public
|
|
73
67
|
```
|
|
74
68
|
|
|
75
|
-
|
|
69
|
+
Các file placeholder `index.ts` và `.gitkeep` sẽ được tự động tạo. Sử dụng cờ `--force` để ghi đè các file đã có:
|
|
76
70
|
|
|
77
71
|
```bash
|
|
78
72
|
lb-infra init ./apps/api --force
|
|
79
73
|
```
|
|
80
74
|
|
|
81
|
-
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 3. Main Imports & Re-exports
|
|
78
|
+
|
|
79
|
+
Sử dụng trực tiếp các thành phần chính được cung cấp bởi framework:
|
|
82
80
|
|
|
83
81
|
```ts
|
|
84
82
|
import {
|
|
@@ -91,7 +89,7 @@ import {
|
|
|
91
89
|
} from '@gennext/lb-infra';
|
|
92
90
|
```
|
|
93
91
|
|
|
94
|
-
|
|
92
|
+
Sử dụng các alias re-export của LoopBack để đảm bảo đồng bộ phiên bản:
|
|
95
93
|
|
|
96
94
|
```ts
|
|
97
95
|
import {inject, injectable, BindingScope} from '@gennext/lb-infra/lb-core';
|
|
@@ -100,229 +98,452 @@ import {model, property, repository} from '@gennext/lb-infra/lb-repo';
|
|
|
100
98
|
import {authenticate, TokenServiceBindings} from '@gennext/lb-infra/lb-auth';
|
|
101
99
|
```
|
|
102
100
|
|
|
103
|
-
|
|
101
|
+
Các tính năng tùy chọn:
|
|
104
102
|
|
|
105
103
|
```ts
|
|
106
104
|
import {SocketIOComponent} from '@gennext/lb-infra/socket-io';
|
|
107
105
|
import {GrpcServerComponent} from '@gennext/lb-infra/grpc';
|
|
108
106
|
```
|
|
109
107
|
|
|
110
|
-
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 4. Cấu trúc thư mục (Project Layout)
|
|
111
111
|
|
|
112
112
|
```text
|
|
113
|
-
src
|
|
114
|
-
├── base
|
|
115
|
-
├── common
|
|
116
|
-
├── components
|
|
117
|
-
├── datasources
|
|
118
|
-
├── helpers
|
|
119
|
-
├── interceptors
|
|
120
|
-
├── middlewares
|
|
121
|
-
├── migrations
|
|
122
|
-
├── mixins
|
|
123
|
-
└── utilities
|
|
113
|
+
src/
|
|
114
|
+
├── base/ # Các base class cốt lõi (Application, Sequence, Models, Repositories, Controllers, Services)
|
|
115
|
+
├── common/ # Định nghĩa constants, kiểu dữ liệu dùng chung (Types), keys và environments
|
|
116
|
+
├── components/ # Các LoopBack 4 Components cắm ngoài (Auth, Authorize, Mail, Health, v.v.)
|
|
117
|
+
├── datasources/ # Nguồn cấp dữ liệu (Postgres, Redis, In-memory KV)
|
|
118
|
+
├── helpers/ # Các wrapper helper: logger, hàng đợi queue, cryptography, testing, cronjob
|
|
119
|
+
├── interceptors/ # Global Interceptors (ví dụ: Content-Range header)
|
|
120
|
+
├── middlewares/ # Express Middlewares (Request body parser, Request spy)
|
|
121
|
+
├── migrations/ # Script chạy migration cơ sở dữ liệu
|
|
122
|
+
├── mixins/ # TypeScript mixins dùng cho LoopBack Models
|
|
123
|
+
└── utilities/ # Hàm tiện ích (parse data, query helper, format, error handler)
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
Use `BaseApplication` when you want full control over configuration:
|
|
129
|
-
|
|
130
|
-
```ts
|
|
131
|
-
import {BaseApplication} from '@gennext/lb-infra';
|
|
132
|
-
import {ApplicationConfig} from '@gennext/lb-infra/lb-core';
|
|
133
|
-
|
|
134
|
-
export class MyApplication extends BaseApplication {
|
|
135
|
-
constructor(options: ApplicationConfig = {}) {
|
|
136
|
-
super({
|
|
137
|
-
scope: 'MyApplication',
|
|
138
|
-
serverOptions: options,
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
staticConfigure(): void {
|
|
143
|
-
// Bind components, datasources, repositories, services and controllers.
|
|
144
|
-
}
|
|
126
|
+
---
|
|
145
127
|
|
|
146
|
-
|
|
147
|
-
return __dirname;
|
|
148
|
-
}
|
|
128
|
+
## 5. Kiến trúc ứng dụng & Vòng đời (Architecture & Lifecycle)
|
|
149
129
|
|
|
150
|
-
|
|
151
|
-
return {result: true};
|
|
152
|
-
}
|
|
130
|
+
Ứng dụng kế thừa từ `BaseApplication` hoặc `DefaultRestApplication` tuân theo thứ tự khởi động và nạp cấu hình nghiêm ngặt:
|
|
153
131
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
132
|
+
```text
|
|
133
|
+
New Application Instance
|
|
134
|
+
│
|
|
135
|
+
▼
|
|
136
|
+
staticConfigure()
|
|
137
|
+
│
|
|
138
|
+
▼
|
|
139
|
+
initialize()
|
|
140
|
+
│
|
|
141
|
+
▼
|
|
142
|
+
validateEnv()
|
|
143
|
+
│
|
|
144
|
+
▼
|
|
145
|
+
declareModels()
|
|
146
|
+
│
|
|
147
|
+
▼
|
|
148
|
+
preConfigure()
|
|
149
|
+
│
|
|
150
|
+
▼
|
|
151
|
+
Loopback Bootstrapping
|
|
152
|
+
│
|
|
153
|
+
▼
|
|
154
|
+
postConfigure()
|
|
155
|
+
│
|
|
156
|
+
▼
|
|
157
|
+
Application Ready
|
|
162
158
|
```
|
|
163
159
|
|
|
164
|
-
|
|
160
|
+
### Chi tiết các lifecycle hooks:
|
|
165
161
|
|
|
166
|
-
|
|
162
|
+
1. **`staticConfigure()`**: Được gọi đầu tiên trong constructor của `BaseApplication`.
|
|
163
|
+
- *Khi nào dùng*: Thực hiện bind trực tiếp các giá trị tĩnh, cấu hình component, datasources tĩnh trước khi nạp sequence hay cấu hình động.
|
|
164
|
+
2. **`validateEnv()`**: Được gọi khi initialize.
|
|
165
|
+
- *Khi nào dùng*: Thực hiện kiểm tra các biến môi trường thông qua `validateEnvironment()` theo schema định sẵn. Nếu trả về `result: false`, ứng dụng sẽ dừng boot ngay lập tức kèm thông tin lỗi chi tiết.
|
|
166
|
+
3. **`declareModels()`**: Được gọi ngay sau kiểm tra môi trường.
|
|
167
|
+
- *Khi nào dùng*: Đăng ký danh sách các model của consumer app với metadata schema.
|
|
168
|
+
4. **`preConfigure()`**: Chạy trước khi LoopBack thực hiện boot process quét thư mục.
|
|
169
|
+
- *Khi nào dùng*: Bind cấu hình dynamic, đăng ký interceptors, khai báo global middleware, thiết lập kết nối datasource.
|
|
170
|
+
5. **`postConfigure()`**: Chạy sau khi ứng dụng hoàn tất quá trình boot.
|
|
171
|
+
- *Khi nào dùng*: Thực hiện các tác vụ khởi tạo muộn, kiểm tra tính sẵn sàng của database hoặc thiết lập lắng nghe hàng đợi (worker).
|
|
167
172
|
|
|
168
|
-
|
|
173
|
+
---
|
|
169
174
|
|
|
170
|
-
|
|
171
|
-
- `BaseKVEntity`
|
|
172
|
-
- `BaseTzEntity`
|
|
173
|
-
- `BaseUserAuditTzEntity`
|
|
174
|
-
- `BaseTextSearchTzEntity`
|
|
175
|
-
- `BaseObjectSearchTzEntity`
|
|
176
|
-
- `BaseSearchableTzEntity`
|
|
177
|
-
- `BaseSoftDeleteTzEntity`
|
|
175
|
+
## 6. Quản lý phụ thuộc (Dependency Graph & DI Binding Keys)
|
|
178
176
|
|
|
179
|
-
|
|
177
|
+
Framework chuẩn hóa cách đặt tên binding keys để tránh sử dụng các chuỗi string thô không an toàn thông qua lớp static `BindingKeys`:
|
|
180
178
|
|
|
181
|
-
- `
|
|
182
|
-
- `
|
|
183
|
-
- `
|
|
184
|
-
- `SoftDeleteModelMixin`
|
|
185
|
-
- `SoftPersistentMixin`
|
|
186
|
-
- `TextSearchMixin`
|
|
187
|
-
- `ObjectSearchMixin`
|
|
188
|
-
- `VectorMixin`
|
|
189
|
-
- `DuplicatableMixin`
|
|
190
|
-
- `DataTypeMixin`
|
|
191
|
-
- `PrincipalMixin`
|
|
179
|
+
- **Datasource bindings**: `datasources.postgres`, `datasources.redis`
|
|
180
|
+
- **Repository bindings**: `repositories.XxxRepository` (được tạo tự động thông qua `BindingKeys.repositoryFor('XxxRepository')`)
|
|
181
|
+
- **Service bindings**: `services.XxxService` (được tạo tự động thông qua `BindingKeys.serviceFor('XxxService')`)
|
|
192
182
|
|
|
193
|
-
|
|
183
|
+
### Ví dụ Wire Controller - Service - Repository thông qua `@inject`:
|
|
194
184
|
|
|
195
185
|
```ts
|
|
196
|
-
import {
|
|
197
|
-
import {
|
|
186
|
+
import {BindingKeys} from '@gennext/lb-infra';
|
|
187
|
+
import {inject, injectable, BindingScope} from '@gennext/lb-infra/lb-core';
|
|
198
188
|
|
|
199
|
-
@
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
189
|
+
@injectable({scope: BindingScope.SINGLETON})
|
|
190
|
+
export class ProductRepository extends TzCrudRepository<Product> {
|
|
191
|
+
constructor(
|
|
192
|
+
@inject('datasources.postgres') dataSource: PostgresDataSource
|
|
193
|
+
) {
|
|
194
|
+
super(Product, dataSource);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
@injectable({scope: BindingScope.TRANSIENT})
|
|
199
|
+
export class ProductService extends BaseCrudService<Product> {
|
|
200
|
+
constructor(
|
|
201
|
+
@inject(BindingKeys.repositoryFor('ProductRepository'))
|
|
202
|
+
protected productRepository: ProductRepository
|
|
203
|
+
) {
|
|
204
|
+
super({ scope: ProductService.name, repository: productRepository });
|
|
205
|
+
}
|
|
211
206
|
}
|
|
212
207
|
```
|
|
213
208
|
|
|
214
|
-
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## 7. Hệ thống Models & Mixins
|
|
215
212
|
|
|
216
|
-
|
|
213
|
+
Framework cung cấp sẵn các base class tích hợp mixin nhằm tái sử dụng thuộc tính dữ liệu:
|
|
217
214
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
215
|
+
| Base Model Class | Các Mixin tích hợp sẵn | Mục đích / Khi nào nên dùng |
|
|
216
|
+
| :--- | :--- | :--- |
|
|
217
|
+
| `BaseEntity` | Không | Model cơ bản nhất không có ID và audit log |
|
|
218
|
+
| `BaseKVEntity` | Không | Dành cho các cặp dữ liệu dạng Key-Value (chứa trường `payload`) |
|
|
219
|
+
| `BaseTzEntity` | `TzMixin` | Tự động tạo và cập nhật `createdAt`, `modifiedAt` |
|
|
220
|
+
| `BaseUserAuditTzEntity` | `TzMixin`, `UserAuditMixin` | Tự động cập nhật timestamp cùng thông tin user tạo/sửa (`createdBy`, `modifiedBy`) |
|
|
221
|
+
| `BaseTextSearchTzEntity` | `TzMixin`, `TextSearchMixin` | Tự động build trường text search `textSearch` |
|
|
222
|
+
| `BaseObjectSearchTzEntity` | `TzMixin`, `ObjectSearchMixin` | Tự động build trường search JSONB `objectSearch` |
|
|
223
|
+
| `BaseSearchableTzEntity` | `TzMixin`, `TextSearchMixin`, `ObjectSearchMixin` | Model tìm kiếm tổng hợp (cả text và object search) |
|
|
224
|
+
| `BaseSoftDeleteTzEntity` | `TzMixin`, `SoftDeleteModelMixin` | Hỗ trợ xóa mềm (đánh dấu cờ `isDeleted` và `deletedAt`) |
|
|
225
|
+
| `BaseVectorEntity` | `TzMixin`, `VectorMixin` | Hỗ trợ lưu trữ vector embedding (cho tìm kiếm AI / pgvector) |
|
|
221
226
|
|
|
222
|
-
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 8. Repository nâng cao
|
|
230
|
+
|
|
231
|
+
Framework cung cấp lớp `TzCrudRepository` và `SearchableTzCrudRepository` kế thừa từ LoopBack repository gốc nhưng được bổ sung cơ chế xử lý timezone và audit user tự động:
|
|
223
232
|
|
|
224
233
|
```ts
|
|
225
234
|
import {PostgresDataSource, TzCrudRepository} from '@gennext/lb-infra';
|
|
226
|
-
import {inject
|
|
235
|
+
import {inject} from '@gennext/lb-infra/lb-core';
|
|
227
236
|
|
|
228
|
-
|
|
229
|
-
export class UserRepository extends TzCrudRepository<User> {
|
|
237
|
+
export class ProductRepository extends TzCrudRepository<Product> {
|
|
230
238
|
constructor(@inject('datasources.postgres') dataSource: PostgresDataSource) {
|
|
231
|
-
super(
|
|
239
|
+
super(Product, dataSource);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Ví dụ custom query phức tạp (sử dụng Knex query builder của PostgresDataSource)
|
|
243
|
+
async findActiveProductsWithMinPrice(minPrice: number): Promise<Product[]> {
|
|
244
|
+
const table = this.modelClass.definition.settings.postgresql.table;
|
|
245
|
+
const query = this.dataSource
|
|
246
|
+
.connector!.execute(`SELECT * FROM ${table} WHERE price >= $1 AND is_deleted = false`, [minPrice]);
|
|
247
|
+
return query;
|
|
232
248
|
}
|
|
233
249
|
}
|
|
234
250
|
```
|
|
235
251
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
### CRUD
|
|
252
|
+
---
|
|
239
253
|
|
|
240
|
-
|
|
254
|
+
## 9. Bộ tạo Controller tự động (Controller Generator)
|
|
241
255
|
|
|
242
|
-
|
|
243
|
-
- `POST /`
|
|
244
|
-
- `PATCH /`
|
|
245
|
-
- `GET /count`
|
|
246
|
-
- `GET /find-one`
|
|
247
|
-
- `GET /{id}`
|
|
248
|
-
- `PATCH /{id}`
|
|
249
|
-
- `PUT /{id}`
|
|
250
|
-
- `DELETE /{id}`
|
|
256
|
+
Framework cho phép sinh nhanh các route REST chuẩn RESTful chỉ qua cấu hình tùy chọn:
|
|
251
257
|
|
|
252
|
-
|
|
258
|
+
### 1. defineCrudController
|
|
259
|
+
Sinh ra các endpoints: `GET /`, `POST /`, `PATCH /`, `GET /count`, `GET /find-one`, `GET /{id}`, `PATCH /{id}`, `PUT /{id}`, `DELETE /{id}`.
|
|
253
260
|
|
|
254
261
|
```ts
|
|
255
262
|
import {defineCrudController} from '@gennext/lb-infra';
|
|
256
263
|
import {api} from '@gennext/lb-infra/lb-rest';
|
|
257
264
|
|
|
258
|
-
const
|
|
259
|
-
entity:
|
|
260
|
-
repository: {name: '
|
|
265
|
+
const BaseController = defineCrudController<Product>({
|
|
266
|
+
entity: Product,
|
|
267
|
+
repository: {name: 'ProductRepository'},
|
|
261
268
|
controller: {
|
|
262
|
-
basePath: '/
|
|
263
|
-
readonly: false,
|
|
269
|
+
basePath: '/products',
|
|
270
|
+
readonly: false, // Nếu true, chỉ sinh ra các route GET/count/find-one
|
|
271
|
+
defaultLimit: 100
|
|
264
272
|
},
|
|
273
|
+
doInjectCurrentUser: true, // Tự động inject context user vào audit log
|
|
274
|
+
doDeleteWithReturn: true // delete trả về object {id}
|
|
265
275
|
});
|
|
266
276
|
|
|
267
|
-
@api({basePath: '/
|
|
268
|
-
export class
|
|
277
|
+
@api({basePath: '/products'})
|
|
278
|
+
export class ProductController extends BaseController {
|
|
279
|
+
// Bạn có thể kế thừa và viết đè hoặc bổ sung custom method tại đây
|
|
280
|
+
}
|
|
269
281
|
```
|
|
270
282
|
|
|
271
|
-
|
|
283
|
+
### 2. defineKVController
|
|
284
|
+
Dành cho mô hình Key-Value lưu vào memory hoặc Redis:
|
|
285
|
+
- Endpoints sinh ra: `GET /{key}`, `POST /{key}`, `DELETE /{key}`.
|
|
272
286
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
search() {}
|
|
276
|
-
```
|
|
287
|
+
### 3. defineRelationCrudController / defineRelationViewController
|
|
288
|
+
Dành cho quan hệ (HasMany, BelongsTo, HasManyThrough). Sinh ra các REST API quản lý mối quan hệ giữa Source và Target model (ví dụ: `POST /users/{id}/posts`, `GET /users/{id}/posts`).
|
|
277
289
|
|
|
278
|
-
|
|
290
|
+
---
|
|
279
291
|
|
|
280
|
-
|
|
292
|
+
## 10. Hướng dẫn thiết kế Custom Controller - Service - Repository
|
|
281
293
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
controller: {
|
|
288
|
-
basePath: '/sessions',
|
|
289
|
-
readonly: false,
|
|
290
|
-
},
|
|
291
|
-
}) {}
|
|
292
|
-
```
|
|
294
|
+
Framework tuân thủ nghiêm ngặt quy tắc phân tách trách nhiệm (Separation of Concerns):
|
|
295
|
+
1. **Model**: Định nghĩa cấu trúc dữ liệu và các mixin.
|
|
296
|
+
2. **Repository**: Thực hiện truy vấn DB qua SQL/Knex.
|
|
297
|
+
3. **Service**: Chứa logic nghiệp vụ, tính toán, gọi API ngoài, điều phối nghiệp vụ.
|
|
298
|
+
4. **Controller**: Xử lý HTTP routing, nhận request, kiểm tra định dạng và gọi Service.
|
|
293
299
|
|
|
294
|
-
|
|
300
|
+
#### Bước 1: Khai báo Model
|
|
301
|
+
```ts
|
|
302
|
+
import {BaseUserAuditTzEntity} from '@gennext/lb-infra';
|
|
303
|
+
import {model, property} from '@gennext/lb-infra/lb-repo';
|
|
295
304
|
|
|
296
|
-
|
|
305
|
+
@model({
|
|
306
|
+
settings: {
|
|
307
|
+
postgresql: {
|
|
308
|
+
schema: 'public',
|
|
309
|
+
table: 'order',
|
|
310
|
+
},
|
|
311
|
+
strict: true,
|
|
312
|
+
},
|
|
313
|
+
})
|
|
314
|
+
export class Order extends BaseUserAuditTzEntity {
|
|
315
|
+
@property({type: 'string', required: true})
|
|
316
|
+
code: string;
|
|
297
317
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
entities: {source: User, target: Post},
|
|
302
|
-
relation: {name: 'posts', type: EntityRelations.HAS_MANY},
|
|
303
|
-
endPoint: 'posts',
|
|
304
|
-
}) {}
|
|
318
|
+
@property({type: 'number', required: true})
|
|
319
|
+
amount: number;
|
|
320
|
+
}
|
|
305
321
|
```
|
|
306
322
|
|
|
307
|
-
|
|
323
|
+
#### Bước 2: Tạo Repository
|
|
324
|
+
```ts
|
|
325
|
+
import {PostgresDataSource, TzCrudRepository} from '@gennext/lb-infra';
|
|
326
|
+
import {inject} from '@gennext/lb-infra/lb-core';
|
|
308
327
|
|
|
309
|
-
|
|
328
|
+
export class OrderRepository extends TzCrudRepository<Order> {
|
|
329
|
+
constructor(
|
|
330
|
+
@inject('datasources.postgres') dataSource: PostgresDataSource
|
|
331
|
+
) {
|
|
332
|
+
super(Order, dataSource);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
```
|
|
310
336
|
|
|
311
|
-
|
|
337
|
+
#### Bước 3: Tạo Service chứa Business Logic
|
|
338
|
+
```ts
|
|
339
|
+
import {BaseCrudService, BindingKeys} from '@gennext/lb-infra';
|
|
340
|
+
import {inject} from '@gennext/lb-infra/lb-core';
|
|
341
|
+
import {OrderRepository} from '../repositories/order.repository';
|
|
342
|
+
import {Order} from '../models/order.model';
|
|
343
|
+
|
|
344
|
+
export class OrderService extends BaseCrudService<Order> {
|
|
345
|
+
constructor(
|
|
346
|
+
@inject(BindingKeys.repositoryFor('OrderRepository'))
|
|
347
|
+
private orderRepository: OrderRepository
|
|
348
|
+
) {
|
|
349
|
+
// Gọi constructor của BaseCrudService nhận 1 object { scope, repository }
|
|
350
|
+
super({ scope: OrderService.name, repository: orderRepository });
|
|
351
|
+
}
|
|
312
352
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
353
|
+
// Phương thức chứa logic nghiệp vụ đặc thù
|
|
354
|
+
async createOrderWithValidation(data: Order): Promise<Order> {
|
|
355
|
+
if (data.amount <= 0) {
|
|
356
|
+
throw new Error('Order amount must be positive');
|
|
357
|
+
}
|
|
358
|
+
// Sử dụng context trống nếu không truyền currentUser
|
|
359
|
+
return this.orderRepository.create(data, {});
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
```
|
|
318
363
|
|
|
319
|
-
|
|
364
|
+
#### Bước 4: Tạo Custom Controller
|
|
365
|
+
```ts
|
|
366
|
+
import {BaseController, BindingKeys} from '@gennext/lb-infra';
|
|
367
|
+
import {api, post, requestBody} from '@gennext/lb-infra/lb-rest';
|
|
368
|
+
import {inject} from '@gennext/lb-infra/lb-core';
|
|
369
|
+
import {OrderService} from '../services/order.service';
|
|
370
|
+
import {Order} from '../models/order.model';
|
|
371
|
+
|
|
372
|
+
@api({basePath: '/orders'})
|
|
373
|
+
export class OrderController extends BaseController {
|
|
374
|
+
constructor(
|
|
375
|
+
@inject(BindingKeys.serviceFor('OrderService'))
|
|
376
|
+
private orderService: OrderService
|
|
377
|
+
) {
|
|
378
|
+
super({scope: OrderController.name});
|
|
379
|
+
}
|
|
320
380
|
|
|
321
|
-
|
|
381
|
+
@post('/')
|
|
382
|
+
async create(@requestBody() data: Order): Promise<Order> {
|
|
383
|
+
try {
|
|
384
|
+
return await this.orderService.createOrderWithValidation(data);
|
|
385
|
+
} catch (err) {
|
|
386
|
+
this.logger.error('Failed to create order: %s', err.message);
|
|
387
|
+
throw err;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
```
|
|
322
392
|
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## 11. Các Components tích hợp sẵn
|
|
396
|
+
|
|
397
|
+
Framework tích hợp sẵn các LoopBack 4 Components hỗ trợ bootstrap nhanh:
|
|
398
|
+
|
|
399
|
+
1. **`AuthenticateComponent`**
|
|
400
|
+
- *Mục đích*: Quản lý phiên đăng nhập và định danh (JWT, Basic, OAuth2).
|
|
401
|
+
- *Cấu hình*: Bind cấu hình qua `AuthenticateKeys.TOKEN_OPTIONS` và `AuthenticateKeys.REST_OPTIONS`.
|
|
402
|
+
2. **`AuthorizeComponent`**
|
|
403
|
+
- *Mục đích*: Phân quyền chi tiết người dùng sử dụng Casbin authorization adapter.
|
|
404
|
+
3. **`MigrationComponent`**
|
|
405
|
+
- *Mục đích*: Quản lý chạy tự động các tệp tin schema migration SQL/TS lúc boot ứng dụng.
|
|
406
|
+
4. **`HealthCheckComponent`**
|
|
407
|
+
- *Mục đích*: Endpoint `/health` cung cấp thông tin trạng thái hoạt động của hệ thống.
|
|
408
|
+
5. **`MailComponent`**
|
|
409
|
+
- *Mục đích*: Quản lý gửi mail tích hợp BullMQ để gửi bất đồng bộ qua SMTP / Mailgun.
|
|
410
|
+
6. **`SocketIOComponent`**
|
|
411
|
+
- *Mục đích*: Quản lý Socket.IO server tích hợp Redis adapter phục vụ phân tán.
|
|
412
|
+
7. **`GrpcServerComponent`**
|
|
413
|
+
- *Mục đích*: Khởi tạo và khởi chạy gRPC server của hệ thống.
|
|
414
|
+
8. **`CrashReportComponent`**
|
|
415
|
+
- *Mục đích*: Ghi lại dấu vết lỗi hệ thống sang các bên thứ 3 (Sentry / MT service).
|
|
416
|
+
9. **`StaticAssetComponent`**
|
|
417
|
+
- *Mục đích*: Hỗ trợ upload, quản lý file và tích hợp với Object Storage (MinIO/S3).
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## 12. Hệ thống Helpers dùng chung
|
|
422
|
+
|
|
423
|
+
Các helper cung cấp giao diện API đồng bộ và dễ sử dụng:
|
|
424
|
+
|
|
425
|
+
* **Logger Helper**:
|
|
426
|
+
```ts
|
|
427
|
+
import {LoggerFactory} from '@gennext/lb-infra';
|
|
428
|
+
|
|
429
|
+
const logger = LoggerFactory.getLogger('CustomScope');
|
|
430
|
+
logger.info('Message log: %s', 'value');
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
* **Redis Helper**:
|
|
434
|
+
```ts
|
|
435
|
+
import {RedisHelper} from '@gennext/lb-infra';
|
|
436
|
+
|
|
437
|
+
const redis = new RedisHelper({host: '127.0.0.1', port: 6379});
|
|
438
|
+
await redis.set('key', 'value');
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
* **Queue Helpers**:
|
|
442
|
+
```ts
|
|
443
|
+
// 1. QueueHelper (Hàng đợi trong bộ nhớ)
|
|
444
|
+
import {QueueHelper} from '@gennext/lb-infra';
|
|
445
|
+
|
|
446
|
+
const inMemoryQueue = new QueueHelper<string>({
|
|
447
|
+
identifier: 'my-in-memory-queue',
|
|
448
|
+
onMessage: async ({ queueElement }) => {
|
|
449
|
+
console.log('Processing:', queueElement.payload);
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
await inMemoryQueue.enqueue('task-payload');
|
|
453
|
+
|
|
454
|
+
// 2. BullMQHelper (Hàng đợi Redis-backed)
|
|
455
|
+
import {BullMQHelper} from '@gennext/lb-infra';
|
|
456
|
+
import Redis from 'ioredis';
|
|
457
|
+
|
|
458
|
+
const connection = new Redis({ host: '127.0.0.1', port: 6379 });
|
|
459
|
+
|
|
460
|
+
// Client Role: queue (để đẩy job vào hàng đợi)
|
|
461
|
+
const bullQueue = new BullMQHelper<string>({
|
|
462
|
+
queueName: 'email-tasks',
|
|
463
|
+
identifier: 'email-queue-client',
|
|
464
|
+
role: 'queue',
|
|
465
|
+
connection,
|
|
466
|
+
});
|
|
467
|
+
await bullQueue.queue.add('send-email', 'payload');
|
|
468
|
+
|
|
469
|
+
// Server Role: worker (để xử lý job)
|
|
470
|
+
const bullWorker = new BullMQHelper<string>({
|
|
471
|
+
queueName: 'email-tasks',
|
|
472
|
+
identifier: 'email-worker-server',
|
|
473
|
+
role: 'worker',
|
|
474
|
+
connection,
|
|
475
|
+
onWorkerData: async (job) => {
|
|
476
|
+
console.log('Processing job data:', job.data);
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
* **Crypto Helper**:
|
|
482
|
+
```ts
|
|
483
|
+
import {AES} from '@gennext/lb-infra';
|
|
484
|
+
|
|
485
|
+
const aes = AES.withAlgorithm('aes-256-cbc');
|
|
486
|
+
const encrypted = aes.encrypt('plain-text', 'secret-key');
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
* **Storage Helper (MinIO)**:
|
|
490
|
+
```ts
|
|
491
|
+
import {MinioHelper} from '@gennext/lb-infra';
|
|
492
|
+
|
|
493
|
+
const storage = new MinioHelper({
|
|
494
|
+
endPoint: '127.0.0.1',
|
|
495
|
+
port: 9000,
|
|
496
|
+
useSSL: false,
|
|
497
|
+
accessKey: 'minioadmin',
|
|
498
|
+
secretKey: 'minioadmin',
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
const files = [{
|
|
502
|
+
originalname: 'file.txt',
|
|
503
|
+
mimetype: 'text/plain',
|
|
504
|
+
buffer: Buffer.from('hello world'),
|
|
505
|
+
size: 11,
|
|
506
|
+
encoding: 'utf-8',
|
|
507
|
+
}];
|
|
508
|
+
|
|
509
|
+
const result = await storage.upload({
|
|
510
|
+
bucket: 'bucket-name',
|
|
511
|
+
files,
|
|
512
|
+
});
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## 13. Cơ chế mở rộng (Extension Point)
|
|
518
|
+
|
|
519
|
+
Consumer Application có thể mở rộng các chức năng của framework mà không cần sửa mã nguồn gốc bằng các cách sau:
|
|
520
|
+
|
|
521
|
+
- **Custom Mixins**: Tạo các mixin riêng bằng cách bọc quanh `BaseEntity` hoặc các model hiện có và truyền vào cấu hình của mình.
|
|
522
|
+
- **Custom Components**: Viết các LoopBack component tuân thủ giao diện `Component` của Loopback và cắm vào thông qua `this.component(MyComponent)`.
|
|
523
|
+
- **Custom Datasource**: Đăng ký các Connector mới bằng cách viết các lớp kế thừa `juggler.DataSource`.
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
## 14. Best Practices trong phát triển dự án
|
|
528
|
+
|
|
529
|
+
1. **Sử dụng Package Aliases**: Luôn dùng `@gennext/lb-infra/lb-core`, `@gennext/lb-infra/lb-rest`, `@gennext/lb-infra/lb-repo` để tránh xung đột phiên bản của các thư viện LoopBack.
|
|
530
|
+
2. **Quản lý Binding Keys đúng cách**: Phân biệt rõ hai loại binding key để tránh lỗi runtime khi gọi `@inject`:
|
|
531
|
+
- *Với repository/service NỘI BỘ của framework*: Sử dụng các key tĩnh định nghĩa sẵn như `BindingKeys.REPOSITORIES.User`, `BindingKeys.SERVICES.JWTToken`.
|
|
532
|
+
- *Với repository/service do ứng dụng TỰ TẠO*: Sử dụng các static helper dynamic để sinh key như `BindingKeys.repositoryFor('ProductRepository')` hoặc `BindingKeys.serviceFor('ProductService')`.
|
|
533
|
+
3. **Phân tách tầng logic**: Luôn tuân thủ quy tắc: HTTP validation / Response formatting nằm ở Controller -> Logic xử lý nghiệp vụ ở Service -> Query Database / Transaction ở Repository.
|
|
534
|
+
4. **Không đổi schema đa ngôn ngữ (i18n)**: Lưu trữ các trường đa ngôn ngữ trực tiếp dưới dạng literal text indexable thay vì dynamic path-lookup để tối ưu hiệu năng tìm kiếm Postgres.
|
|
535
|
+
5. **Clamp limit truy vấn**: Luôn sử dụng helper `applyLimit` để đảm bảo hệ thống tự động clamp các limit quá lớn từ client về `App.MAX_QUERY_LIMIT` (giá trị thực tế là `1000`) nhằm tránh lỗi quá tải bộ nhớ.
|
|
536
|
+
|
|
537
|
+
---
|
|
538
|
+
|
|
539
|
+
## 15. Hướng dẫn sử dụng gRPC (5 bước)
|
|
540
|
+
|
|
541
|
+
Module gRPC được xuất từ `@gennext/lb-infra/grpc`. Quy trình triển khai dịch vụ gRPC gồm 5 bước:
|
|
542
|
+
|
|
543
|
+
### Bước 1: Tạo file định nghĩa Proto
|
|
544
|
+
Tệp: `src/protos/user.proto`
|
|
323
545
|
```proto
|
|
324
546
|
syntax = "proto3";
|
|
325
|
-
|
|
326
547
|
package demo;
|
|
327
548
|
|
|
328
549
|
service UserService {
|
|
@@ -336,25 +557,13 @@ message GetUserRequest {
|
|
|
336
557
|
message GetUserResponse {
|
|
337
558
|
int32 id = 1;
|
|
338
559
|
string email = 2;
|
|
339
|
-
string fullName = 3;
|
|
340
560
|
}
|
|
341
561
|
```
|
|
342
562
|
|
|
343
|
-
### gRPC Controller
|
|
344
|
-
|
|
563
|
+
### Bước 2: Tạo gRPC Controller
|
|
345
564
|
```ts
|
|
346
565
|
import {BaseGrpcController, grpcController, grpcMethod} from '@gennext/lb-infra/grpc';
|
|
347
566
|
|
|
348
|
-
type GetUserRequest = {
|
|
349
|
-
id: number;
|
|
350
|
-
};
|
|
351
|
-
|
|
352
|
-
type GetUserResponse = {
|
|
353
|
-
id: number;
|
|
354
|
-
email: string;
|
|
355
|
-
fullName: string;
|
|
356
|
-
};
|
|
357
|
-
|
|
358
567
|
@grpcController()
|
|
359
568
|
export class UserGrpcController extends BaseGrpcController {
|
|
360
569
|
constructor() {
|
|
@@ -366,32 +575,21 @@ export class UserGrpcController extends BaseGrpcController {
|
|
|
366
575
|
service: 'demo.UserService',
|
|
367
576
|
method: 'GetUser',
|
|
368
577
|
})
|
|
369
|
-
async getUser(request:
|
|
578
|
+
async getUser(request: {id: number}): Promise<{id: number; email: string}> {
|
|
370
579
|
return {
|
|
371
580
|
id: request.id,
|
|
372
581
|
email: `user-${request.id}@example.com`,
|
|
373
|
-
fullName: `User ${request.id}`,
|
|
374
582
|
};
|
|
375
583
|
}
|
|
376
584
|
}
|
|
377
585
|
```
|
|
378
586
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
`method` must match the RPC method name in the proto. If `method` is omitted, the TypeScript method name is used.
|
|
382
|
-
|
|
383
|
-
### Register Server In Application
|
|
384
|
-
|
|
587
|
+
### Bước 3: Cấu hình gRPC Options & Register Component
|
|
385
588
|
```ts
|
|
386
589
|
import {BaseApplication} from '@gennext/lb-infra';
|
|
387
|
-
import {
|
|
388
|
-
GrpcServerComponent,
|
|
389
|
-
GrpcServerKeys,
|
|
390
|
-
IGrpcServerOptions,
|
|
391
|
-
} from '@gennext/lb-infra/grpc';
|
|
590
|
+
import {GrpcServerComponent, GrpcServerKeys, IGrpcServerOptions} from '@gennext/lb-infra/grpc';
|
|
392
591
|
import {ServerCredentials} from '@grpc/grpc-js';
|
|
393
592
|
import {join} from 'node:path';
|
|
394
|
-
|
|
395
593
|
import {UserGrpcController} from './controllers/user-grpc.controller';
|
|
396
594
|
|
|
397
595
|
export class MyApplication extends BaseApplication {
|
|
@@ -399,92 +597,34 @@ export class MyApplication extends BaseApplication {
|
|
|
399
597
|
this.bind<IGrpcServerOptions>(GrpcServerKeys.GRPC_OPTIONS).to({
|
|
400
598
|
identifier: 'my-grpc-server',
|
|
401
599
|
protoFolder: join(__dirname, 'protos'),
|
|
402
|
-
address:
|
|
600
|
+
address: '0.0.0.0:50051',
|
|
403
601
|
credentials: ServerCredentials.createInsecure(),
|
|
404
602
|
});
|
|
405
603
|
|
|
406
604
|
this.grpcController(UserGrpcController);
|
|
407
605
|
this.component(GrpcServerComponent);
|
|
408
606
|
}
|
|
409
|
-
|
|
410
|
-
getProjectRoot(): string {
|
|
411
|
-
return __dirname;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
validateEnv() {
|
|
415
|
-
return {result: true};
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
declareModels(): Set<string> {
|
|
419
|
-
return new Set([]);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
preConfigure(): void {}
|
|
423
|
-
|
|
424
|
-
postConfigure(): void {}
|
|
425
607
|
}
|
|
426
608
|
```
|
|
427
609
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
### Client Example
|
|
431
|
-
|
|
610
|
+
### Bước 4: Gọi gRPC Client thông thường
|
|
432
611
|
```ts
|
|
433
|
-
import * as grpc from '@grpc/grpc-js';
|
|
434
|
-
import * as protoLoader from '@grpc/proto-loader';
|
|
435
612
|
import {initializeGrpcClient} from '@gennext/lb-infra/grpc';
|
|
436
|
-
import
|
|
437
|
-
import {join} from 'node:path';
|
|
613
|
+
import * as grpc from '@grpc/grpc-js';
|
|
438
614
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
email: string;
|
|
442
|
-
fullName: string;
|
|
443
|
-
};
|
|
444
|
-
|
|
445
|
-
type UserServiceClient = grpc.Client & {
|
|
446
|
-
GetUser(
|
|
447
|
-
request: {id: number},
|
|
448
|
-
callback: (error: grpc.ServiceError | null, response: GetUserResponse) => void,
|
|
449
|
-
): void;
|
|
450
|
-
};
|
|
451
|
-
|
|
452
|
-
const protoPath = join(__dirname, 'protos/user.proto');
|
|
453
|
-
const packageDefinition = protoLoader.loadSync(protoPath);
|
|
454
|
-
const proto = grpc.loadPackageDefinition(packageDefinition);
|
|
455
|
-
|
|
456
|
-
const UserService = get(proto, 'demo.UserService') as unknown as new (
|
|
457
|
-
address: string,
|
|
458
|
-
credentials: grpc.ChannelCredentials,
|
|
459
|
-
options?: grpc.ClientOptions,
|
|
460
|
-
) => UserServiceClient;
|
|
461
|
-
|
|
462
|
-
const userClient = initializeGrpcClient<UserServiceClient>({
|
|
463
|
-
serviceClass: UserService,
|
|
615
|
+
const userClient = initializeGrpcClient<any>({
|
|
616
|
+
serviceClass: UserService, // UserService loaded from proto
|
|
464
617
|
address: '127.0.0.1:50051',
|
|
465
618
|
credentials: grpc.ChannelCredentials.createInsecure(),
|
|
466
|
-
autoConnect: true,
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
userClient.client.GetUser({id: 1}, (error, response) => {
|
|
470
|
-
if (error) {
|
|
471
|
-
console.error(error);
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
console.log(response);
|
|
476
619
|
});
|
|
477
620
|
```
|
|
478
621
|
|
|
479
|
-
### Repository-Style
|
|
480
|
-
|
|
481
|
-
For app code that prefers datasource/repository style, use `GrpcDataSource` and `GrpcRepository`:
|
|
482
|
-
|
|
622
|
+
### Bước 5: Gọi gRPC dưới dạng Repository-Style
|
|
483
623
|
```ts
|
|
484
|
-
import * as grpc from '@grpc/grpc-js';
|
|
485
624
|
import {GrpcDataSource, GrpcRepository} from '@gennext/lb-infra/grpc';
|
|
625
|
+
import * as grpc from '@grpc/grpc-js';
|
|
486
626
|
|
|
487
|
-
const dataSource = new GrpcDataSource<
|
|
627
|
+
const dataSource = new GrpcDataSource<any>({
|
|
488
628
|
dsConfig: {
|
|
489
629
|
host: '127.0.0.1',
|
|
490
630
|
port: 50051,
|
|
@@ -493,86 +633,18 @@ const dataSource = new GrpcDataSource<UserServiceClient>({
|
|
|
493
633
|
},
|
|
494
634
|
});
|
|
495
635
|
|
|
496
|
-
class UserGrpcRepository extends GrpcRepository<
|
|
636
|
+
class UserGrpcRepository extends GrpcRepository<any> {
|
|
497
637
|
constructor() {
|
|
498
638
|
super({dataSource, scope: UserGrpcRepository.name});
|
|
499
639
|
}
|
|
500
640
|
|
|
501
|
-
getUser(id: number): Promise<
|
|
641
|
+
async getUser(id: number): Promise<any> {
|
|
502
642
|
return new Promise((resolve, reject) => {
|
|
503
|
-
this.getServiceClient().GetUser({id}, (error, response) => {
|
|
504
|
-
if (error)
|
|
505
|
-
reject(error);
|
|
506
|
-
return;
|
|
507
|
-
}
|
|
508
|
-
|
|
643
|
+
this.getServiceClient().GetUser({id}, (error: any, response: any) => {
|
|
644
|
+
if (error) return reject(error);
|
|
509
645
|
resolve(response);
|
|
510
646
|
});
|
|
511
647
|
});
|
|
512
648
|
}
|
|
513
649
|
}
|
|
514
650
|
```
|
|
515
|
-
|
|
516
|
-
The current helper examples above use standard unary request/response calls.
|
|
517
|
-
|
|
518
|
-
## Components
|
|
519
|
-
|
|
520
|
-
Available components include:
|
|
521
|
-
|
|
522
|
-
- `AuthenticateComponent`: JWT/basic/oauth2 strategies, auth middleware, token services and auth controller generator.
|
|
523
|
-
- `AuthorizeComponent`: Casbin authorization, role/permission models, adapters, provider and interceptor.
|
|
524
|
-
- `MigrationComponent`: migration model, repository and runtime wiring.
|
|
525
|
-
- `HealthCheckComponent`: application and datasource health checks.
|
|
526
|
-
- `StaticAssetComponent`: MinIO/static asset controllers.
|
|
527
|
-
- `MailComponent`: mail service, template engine, transports and queue executors.
|
|
528
|
-
- `SocketIOComponent`: Socket.IO server/client helpers and Redis adapter support.
|
|
529
|
-
- `GrpcServerComponent`: gRPC server support.
|
|
530
|
-
- `CrashReportComponent`: crash report providers and services.
|
|
531
|
-
|
|
532
|
-
## Helpers
|
|
533
|
-
|
|
534
|
-
Important helper groups:
|
|
535
|
-
|
|
536
|
-
- Logger: `LoggerFactory`, `ApplicationLogger`
|
|
537
|
-
- Redis: `RedisHelper`, `RedisClusterHelper`
|
|
538
|
-
- Queue: `QueueHelper`, `BullMQHelper`, `MQTTClientHelper`
|
|
539
|
-
- Network: HTTP request, TCP, TLS TCP and UDP helpers
|
|
540
|
-
- Crypto: `AES`, `RSA`
|
|
541
|
-
- Storage: `MinioHelper`, `DIContainerHelper`
|
|
542
|
-
- Worker thread helpers
|
|
543
|
-
- Testing helpers
|
|
544
|
-
- `CronHelper`
|
|
545
|
-
|
|
546
|
-
## Datasources
|
|
547
|
-
|
|
548
|
-
Built-in datasources:
|
|
549
|
-
|
|
550
|
-
- `PostgresDataSource`
|
|
551
|
-
- `RedisDataSource`
|
|
552
|
-
- `KvMemDataSource`
|
|
553
|
-
|
|
554
|
-
Common environment keys are defined in `src/common/environments.ts`, including server, PostgreSQL and datasource settings.
|
|
555
|
-
|
|
556
|
-
## Development Checklist
|
|
557
|
-
|
|
558
|
-
Before opening a change:
|
|
559
|
-
|
|
560
|
-
```bash
|
|
561
|
-
bun run --filter "@gennext/lb-infra" build
|
|
562
|
-
bun run --filter "@gennext/lb-infra" lint
|
|
563
|
-
bun run --filter "@gennext/lb-infra" test
|
|
564
|
-
```
|
|
565
|
-
|
|
566
|
-
When adding or changing exports:
|
|
567
|
-
|
|
568
|
-
1. Update `src/index.ts` or the relevant module `index.ts`.
|
|
569
|
-
2. Update `package.json` `exports` when adding a new public subpath.
|
|
570
|
-
3. Build the package.
|
|
571
|
-
4. Verify the consumer app can import the new path.
|
|
572
|
-
|
|
573
|
-
## Notes For Consumer Apps
|
|
574
|
-
|
|
575
|
-
- Prefer the package aliases `lb-core`, `lb-rest`, `lb-repo` and `lb-auth` to keep LoopBack dependency versions consistent.
|
|
576
|
-
- Add `@api({basePath: ...})` on the final exported class when using generated controllers.
|
|
577
|
-
- Bind required component options before starting the application, especially authentication token options and auth service bindings.
|
|
578
|
-
- Ensure build output includes copied runtime assets when publishing or packaging.
|