framework-do-dede 4.0.5 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -110
- package/dist/application/index.d.ts +7 -4
- package/dist/application/index.js +4 -3
- package/dist/application/services.d.ts +15 -0
- package/dist/application/services.js +33 -16
- package/dist/index.d.ts +5 -4
- package/dist/index.js +5 -3
- package/dist/infra/model/model.d.ts +5 -0
- package/dist/infra/model/model.js +2 -0
- package/dist/infra/serialization/entity.d.ts +1 -11
- package/dist/infra/serialization/entity.js +1 -145
- package/dist/protocols/repository.d.ts +20 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Framework do Dedé
|
|
2
2
|
|
|
3
|
-
Um framework TypeScript simples para construir APIs HTTP com controllers, use cases e entities, com suporte a Express ou Elysia, DI leve e
|
|
3
|
+
Um framework TypeScript simples para construir APIs HTTP com controllers, use cases e entities, com suporte a Express ou Elysia, DI leve e camada de Model.
|
|
4
4
|
|
|
5
5
|
## Índice
|
|
6
6
|
|
|
@@ -12,8 +12,8 @@ Um framework TypeScript simples para construir APIs HTTP com controllers, use ca
|
|
|
12
12
|
- Middlewares
|
|
13
13
|
- Tracing
|
|
14
14
|
- UseCase e Decorators
|
|
15
|
-
- Entity e
|
|
16
|
-
-
|
|
15
|
+
- Entity e Model
|
|
16
|
+
- Event Dispatcher
|
|
17
17
|
- Storage Gateway
|
|
18
18
|
- DI (Container/Inject)
|
|
19
19
|
- Errors
|
|
@@ -21,7 +21,7 @@ Um framework TypeScript simples para construir APIs HTTP com controllers, use ca
|
|
|
21
21
|
- Exemplos
|
|
22
22
|
- Express
|
|
23
23
|
- Elysia
|
|
24
|
-
-
|
|
24
|
+
- Background com EventDispatcher
|
|
25
25
|
- Testes
|
|
26
26
|
- Benchmark
|
|
27
27
|
|
|
@@ -250,87 +250,44 @@ class CreateUserUseCase extends UseCase<{ name: string }, { id: string }> {
|
|
|
250
250
|
}
|
|
251
251
|
```
|
|
252
252
|
|
|
253
|
-
### Entity e
|
|
253
|
+
### Entity e Model
|
|
254
254
|
|
|
255
|
-
Entities
|
|
256
|
-
|
|
257
|
-
- `toEntity()` e `toAsyncEntity()`
|
|
258
|
-
- `toData()` e `toAsyncData()`
|
|
259
|
-
- `@Serialize`, `@Restrict`, `@VirtualProperty`, `@GetterPrefix`
|
|
260
|
-
|
|
261
|
-
Obs: a serializacao fica na camada de infraestrutura, mas a API continua sendo exposta pelo framework (importe direto de `./src`).
|
|
255
|
+
Entities sao dominio puro. Use `Model` para converter entre Entity e o objeto do banco.
|
|
262
256
|
|
|
263
257
|
```ts
|
|
264
|
-
import { Entity,
|
|
258
|
+
import { Entity, Model } from './src';
|
|
265
259
|
|
|
266
260
|
class User extends Entity {
|
|
267
|
-
|
|
268
|
-
private readonly email:
|
|
269
|
-
|
|
270
|
-
@Restrict()
|
|
271
|
-
private readonly passwordHash: string;
|
|
272
|
-
|
|
273
|
-
@GetterPrefix('has')
|
|
274
|
-
private readonly profile?: Profile;
|
|
275
|
-
|
|
276
|
-
@VirtualProperty('displayName')
|
|
277
|
-
private display() {
|
|
278
|
-
return 'User ' + this.email.getValue();
|
|
279
|
-
}
|
|
261
|
+
private readonly id: string;
|
|
262
|
+
private readonly email: string;
|
|
280
263
|
|
|
281
|
-
constructor(
|
|
264
|
+
constructor(id: string, email: string) {
|
|
282
265
|
super();
|
|
283
|
-
this.
|
|
284
|
-
this.
|
|
266
|
+
this.id = id;
|
|
267
|
+
this.email = email;
|
|
285
268
|
this.generateGetters();
|
|
286
269
|
}
|
|
287
270
|
}
|
|
288
271
|
|
|
289
|
-
|
|
290
|
-
const serialized = user.toEntity();
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
Regras principais:
|
|
294
|
-
|
|
295
|
-
- `@Serialize` pode retornar objeto: cada chave vira uma propriedade do resultado
|
|
296
|
-
- `@Restrict` remove campo em `toData`
|
|
297
|
-
- `@VirtualProperty` mapeia metodos para campos virtuais
|
|
298
|
-
- `generateGetters()` cria getters para campos (ex.: `getName`, `isActive`, `hasProfile`)
|
|
299
|
-
|
|
300
|
-
### Hooks Before/After ToEntity
|
|
301
|
-
|
|
302
|
-
Use `@BeforeToEntity()` e `@AfterToEntity()` em metodos de Entities.
|
|
303
|
-
|
|
304
|
-
- Before recebe objeto bruto (antes de serializacao)
|
|
305
|
-
- After recebe objeto tratado (resultado final)
|
|
306
|
-
- `toEntity()` executa hooks sem aguardar promessas
|
|
307
|
-
- `toAsyncEntity()` aguarda hooks async
|
|
308
|
-
|
|
309
|
-
```ts
|
|
310
|
-
import { Entity, AfterToEntity, BeforeToEntity } from './src';
|
|
311
|
-
|
|
312
|
-
class FileEntity extends Entity {
|
|
313
|
-
private readonly name: string;
|
|
314
|
-
private readonly s3Key: string;
|
|
272
|
+
type UserRow = { id: string; email: string };
|
|
315
273
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
this.s3Key = s3Key;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
@BeforeToEntity()
|
|
323
|
-
private before(payload: Record<string, any>) {
|
|
324
|
-
payload.rawTouched = true;
|
|
274
|
+
class UserModel extends Model<User, UserRow> {
|
|
275
|
+
toModel(entity: User): UserRow {
|
|
276
|
+
return { id: entity.getId(), email: entity.getEmail() };
|
|
325
277
|
}
|
|
326
278
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
await saveToS3(payload.s3Key);
|
|
279
|
+
toEntity(model: UserRow): User {
|
|
280
|
+
return new User(model.id, model.email);
|
|
330
281
|
}
|
|
331
282
|
}
|
|
332
283
|
```
|
|
333
284
|
|
|
285
|
+
Regras principais:
|
|
286
|
+
|
|
287
|
+
- `Model` centraliza conversoes entre dominio e persistencia
|
|
288
|
+
- use `toModel` e `toEntity`
|
|
289
|
+
- `generateGetters()` cria getters para campos (ex.: `getName`, `isActive`, `hasProfile`)
|
|
290
|
+
|
|
334
291
|
### Storage Gateway
|
|
335
292
|
|
|
336
293
|
Use `@Storage` para injetar gateways com interface `StorageGateway`.
|
|
@@ -350,6 +307,25 @@ class FileService {
|
|
|
350
307
|
}
|
|
351
308
|
```
|
|
352
309
|
|
|
310
|
+
### Event Dispatcher
|
|
311
|
+
|
|
312
|
+
Use `@EventDispatcher` para enfileirar tarefas ou eventos de background com interface `EventDispatcher`.
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
import { EventDispatcher } from './src';
|
|
316
|
+
|
|
317
|
+
type QueueEvent = { name: string; payload?: Record<string, any> };
|
|
318
|
+
|
|
319
|
+
class QueueService {
|
|
320
|
+
@EventDispatcher('QueueDispatcher')
|
|
321
|
+
private readonly dispatcher!: { dispatch: (event: QueueEvent) => Promise<void> };
|
|
322
|
+
|
|
323
|
+
async enqueue(payload: Record<string, any>) {
|
|
324
|
+
await this.dispatcher.dispatch({ name: 'jobs.create', payload });
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
353
329
|
### DI (Container/Inject)
|
|
354
330
|
|
|
355
331
|
Registre dependencias ao iniciar o server (usando o container padrão):
|
|
@@ -401,15 +377,16 @@ Quando um erro e lancado, o handler padroniza a resposta. Erros de dominio (`App
|
|
|
401
377
|
|
|
402
378
|
Interfaces tipadas para padrao de repositorio:
|
|
403
379
|
|
|
404
|
-
- `
|
|
405
|
-
- `
|
|
380
|
+
- `RepositoryModel<E extends Entity, M>`
|
|
381
|
+
- `RepositoryCreate<E extends Entity, M>`
|
|
382
|
+
- `RepositoryUpdate<E extends Entity, M>`
|
|
406
383
|
- `RepositoryRemove`
|
|
407
|
-
- `RepositoryRestore<
|
|
408
|
-
- `RepositoryRemoveBy<
|
|
409
|
-
- `RepositoryRestoreBy<
|
|
410
|
-
- `RepositoryExistsBy<
|
|
411
|
-
- `RepositoryNotExistsBy<
|
|
412
|
-
- `RepositoryPagination<
|
|
384
|
+
- `RepositoryRestore<E extends Entity, M>`
|
|
385
|
+
- `RepositoryRemoveBy<E>`
|
|
386
|
+
- `RepositoryRestoreBy<E>`
|
|
387
|
+
- `RepositoryExistsBy<E>`
|
|
388
|
+
- `RepositoryNotExistsBy<E>`
|
|
389
|
+
- `RepositoryPagination<E extends Entity, M>`
|
|
413
390
|
|
|
414
391
|
## Exemplos
|
|
415
392
|
|
|
@@ -447,49 +424,24 @@ app.registerControllers([ExampleController]);
|
|
|
447
424
|
app.listen();
|
|
448
425
|
```
|
|
449
426
|
|
|
450
|
-
###
|
|
427
|
+
### Background com EventDispatcher
|
|
451
428
|
|
|
452
429
|
```ts
|
|
453
|
-
import {
|
|
454
|
-
|
|
455
|
-
type QueueJob = { type: string; payload: Record<string, any> };
|
|
456
|
-
|
|
457
|
-
type Queue = { enqueue(job: QueueJob): Promise<void> };
|
|
430
|
+
import { EventDispatcher } from './src';
|
|
458
431
|
|
|
459
|
-
|
|
460
|
-
async enqueue(job) {
|
|
461
|
-
console.log('queued job', job);
|
|
462
|
-
}
|
|
463
|
-
};
|
|
464
|
-
|
|
465
|
-
class FileEntity extends Entity {
|
|
466
|
-
private readonly name: string;
|
|
467
|
-
private readonly s3Key: string;
|
|
432
|
+
type QueueEvent = { name: string; payload: Record<string, any> };
|
|
468
433
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
this.s3Key = s3Key;
|
|
473
|
-
}
|
|
434
|
+
class FileService {
|
|
435
|
+
@EventDispatcher('QueueDispatcher')
|
|
436
|
+
private readonly dispatcher!: { dispatch: (event: QueueEvent) => Promise<void> };
|
|
474
437
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
payload: {
|
|
480
|
-
name: payload.name,
|
|
481
|
-
s3Key: payload.s3Key
|
|
482
|
-
}
|
|
438
|
+
async enqueueFile(input: { name: string; s3Key: string }) {
|
|
439
|
+
await this.dispatcher.dispatch({
|
|
440
|
+
name: 'files.create',
|
|
441
|
+
payload: input
|
|
483
442
|
});
|
|
484
443
|
}
|
|
485
|
-
|
|
486
|
-
static create(input: { name: string; s3Key: string }) {
|
|
487
|
-
return new FileEntity(input);
|
|
488
|
-
}
|
|
489
444
|
}
|
|
490
|
-
|
|
491
|
-
const entity = FileEntity.create({ name: 'report', s3Key: 's3://bucket/report.pdf' });
|
|
492
|
-
const serialized = entity.toEntity();
|
|
493
445
|
```
|
|
494
446
|
|
|
495
447
|
## Testes
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { Controller, Post, Get, Put, Delete, Patch, UseMiddleware, UseMiddlewares, Tracing, type Middleware, type Input, type Tracer, type TracerData } from './controller';
|
|
2
|
-
import { Entity, Restrict, VirtualProperty,
|
|
2
|
+
import { Entity, Restrict, VirtualProperty, GetterPrefix, Serialize } from '../infra/serialization/entity';
|
|
3
|
+
import { Model } from '../infra/model/model';
|
|
3
4
|
import { DecorateUseCase, UseCase } from './usecase';
|
|
4
|
-
|
|
5
|
-
export {
|
|
6
|
-
export type { Middleware, Input,
|
|
5
|
+
export { Controller, UseMiddleware, UseMiddlewares, Post, Get, Put, Delete, Patch, Tracing, DecorateUseCase, UseCase, Entity, Restrict, VirtualProperty, GetterPrefix, Serialize, Model, };
|
|
6
|
+
export { Storage, CacheGateway, EventDispatcher } from './services';
|
|
7
|
+
export type { Middleware, Input, Tracer, TracerData };
|
|
8
|
+
export type { StorageGateway } from './services';
|
|
9
|
+
export type { Event, EventPayload } from './services';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Controller, Post, Get, Put, Delete, Patch, UseMiddleware, UseMiddlewares, Tracing } from './controller';
|
|
2
|
-
import { Entity, Restrict, VirtualProperty,
|
|
2
|
+
import { Entity, Restrict, VirtualProperty, GetterPrefix, Serialize } from '../infra/serialization/entity';
|
|
3
|
+
import { Model } from '../infra/model/model';
|
|
3
4
|
import { DecorateUseCase, UseCase } from './usecase';
|
|
4
|
-
|
|
5
|
-
export {
|
|
5
|
+
export { Controller, UseMiddleware, UseMiddlewares, Post, Get, Put, Delete, Patch, Tracing, DecorateUseCase, UseCase, Entity, Restrict, VirtualProperty, GetterPrefix, Serialize, Model, };
|
|
6
|
+
export { Storage, CacheGateway, EventDispatcher } from './services';
|
|
@@ -5,4 +5,19 @@ export interface StorageGateway {
|
|
|
5
5
|
get(key: string): Promise<string>;
|
|
6
6
|
delete(key: string): Promise<boolean>;
|
|
7
7
|
}
|
|
8
|
+
export interface CacheGateway {
|
|
9
|
+
get<T = unknown>(key: string): Promise<T | null> | T | null;
|
|
10
|
+
set<T = unknown>(key: string, value: T, ttl?: number): Promise<void> | void;
|
|
11
|
+
delete(key: string): Promise<boolean> | boolean;
|
|
12
|
+
}
|
|
13
|
+
export type EventPayload = Record<string, any>;
|
|
14
|
+
export type Event = {
|
|
15
|
+
name: string;
|
|
16
|
+
payload?: EventPayload;
|
|
17
|
+
};
|
|
18
|
+
export interface EventDispatcher {
|
|
19
|
+
dispatch(event: Event): Promise<void> | void;
|
|
20
|
+
}
|
|
8
21
|
export declare function Storage(gatewayName: string, container?: Container): (target: any, propertyKey: string) => void;
|
|
22
|
+
export declare function CacheGateway(gatewayName: string, container?: Container): (target: any, propertyKey?: string) => void;
|
|
23
|
+
export declare function EventDispatcher(dispatcherName: string, container?: Container): (target: any, propertyKey?: string) => void;
|
|
@@ -1,22 +1,39 @@
|
|
|
1
1
|
import { DefaultContainer } from "../infra/di/registry";
|
|
2
2
|
import 'reflect-metadata';
|
|
3
|
+
function defineGatewayProperty(target, propertyKey, gatewayName, container, validator, errorMessage) {
|
|
4
|
+
Object.defineProperty(target, propertyKey, {
|
|
5
|
+
get: function () {
|
|
6
|
+
return new Proxy({}, {
|
|
7
|
+
get(_, prop) {
|
|
8
|
+
const resolvedContainer = container ?? DefaultContainer;
|
|
9
|
+
const dependency = resolvedContainer.inject(gatewayName);
|
|
10
|
+
if (validator && !validator(dependency)) {
|
|
11
|
+
throw new Error(errorMessage ?? `${gatewayName} is not a valid dependency`);
|
|
12
|
+
}
|
|
13
|
+
return dependency[prop];
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
},
|
|
17
|
+
enumerable: true,
|
|
18
|
+
configurable: true
|
|
19
|
+
});
|
|
20
|
+
}
|
|
3
21
|
export function Storage(gatewayName, container) {
|
|
4
22
|
return function (target, propertyKey) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
});
|
|
23
|
+
defineGatewayProperty(target, propertyKey, gatewayName, container, (dependency) => !!dependency?.save && !!dependency?.get && !!dependency?.delete, `${gatewayName} is not a valid StorageGateway`);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function CacheGateway(gatewayName, container) {
|
|
27
|
+
return function (target, propertyKey) {
|
|
28
|
+
const resolvedProperty = propertyKey ?? 'cache';
|
|
29
|
+
const resolvedTarget = propertyKey ? target : target.prototype;
|
|
30
|
+
defineGatewayProperty(resolvedTarget, resolvedProperty, gatewayName, container, (dependency) => !!dependency?.get && !!dependency?.set && !!dependency?.delete, `${gatewayName} is not a valid CacheGateway`);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function EventDispatcher(dispatcherName, container) {
|
|
34
|
+
return function (target, propertyKey) {
|
|
35
|
+
const resolvedProperty = propertyKey ?? 'eventDispatcher';
|
|
36
|
+
const resolvedTarget = propertyKey ? target : target.prototype;
|
|
37
|
+
defineGatewayProperty(resolvedTarget, resolvedProperty, dispatcherName, container, (dependency) => !!dependency?.dispatch, `${dispatcherName} is not a valid EventDispatcher`);
|
|
21
38
|
};
|
|
22
39
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Post, Get, Put, Delete, Patch, Controller, Input, Middleware, UseMiddleware, UseMiddlewares, Tracer, Tracing, TracerData, Entity, Restrict, VirtualProperty, GetterPrefix,
|
|
1
|
+
import { Post, Get, Put, Delete, Patch, Controller, Input, Middleware, UseMiddleware, UseMiddlewares, Tracer, Tracing, TracerData, Entity, Restrict, VirtualProperty, GetterPrefix, Model, UseCase, DecorateUseCase, Storage, CacheGateway, EventDispatcher } from "./application";
|
|
2
2
|
import { Container, DefaultContainer, Inject, setDefaultContainer } from './infra/di/registry';
|
|
3
3
|
import { Dede, type Options, Register } from './dede';
|
|
4
4
|
import { ServerError, NotFound, Forbidden, Conflict, Unauthorized, UnprocessableEntity, BadRequest, InternalServerError, CustomServerError } from './http/errors/server';
|
|
5
5
|
import { AppError } from './domain/errors/app-error';
|
|
6
6
|
import type { ValidatorDefinition } from './interface/validation/validator';
|
|
7
|
-
import type {
|
|
8
|
-
|
|
9
|
-
export
|
|
7
|
+
import type { StorageGateway, Event, EventPayload } from './application';
|
|
8
|
+
import type { RepositoryModel, RepositoryCreate, RepositoryUpdate, RepositoryRemove, RepositoryRemoveBy, RepositoryExistsBy, RepositoryRestore, RepositoryRestoreBy, RepositoryNotExistsBy, RepositoryPagination } from './protocols/repository';
|
|
9
|
+
export { Controller, Post, Get, Put, Delete, Patch, Input, Middleware, UseMiddleware, UseMiddlewares, Tracer, Tracing, TracerData, Entity, Restrict, VirtualProperty, GetterPrefix, Model, UseCase, DecorateUseCase, Storage, CacheGateway, EventDispatcher, Inject, Container, DefaultContainer, setDefaultContainer, Dede, Options, Register, ServerError, NotFound, Forbidden, Conflict, Unauthorized, UnprocessableEntity, BadRequest, InternalServerError, CustomServerError, AppError, RepositoryModel, RepositoryCreate, RepositoryUpdate, RepositoryRemove, RepositoryRemoveBy, RepositoryRestore, RepositoryExistsBy, RepositoryRestoreBy, RepositoryNotExistsBy, RepositoryPagination };
|
|
10
|
+
export type { ValidatorDefinition, StorageGateway, Event, EventPayload };
|
package/dist/index.js
CHANGED
|
@@ -3,15 +3,17 @@ import {
|
|
|
3
3
|
Post, Get, Put, Delete, Patch, Controller, UseMiddleware, UseMiddlewares, Tracing,
|
|
4
4
|
// controller
|
|
5
5
|
// entity
|
|
6
|
-
Entity, Restrict, VirtualProperty, GetterPrefix,
|
|
6
|
+
Entity, Restrict, VirtualProperty, GetterPrefix, Model,
|
|
7
7
|
// entity
|
|
8
8
|
// usecase
|
|
9
9
|
UseCase, DecorateUseCase,
|
|
10
10
|
// usecase
|
|
11
11
|
// storage
|
|
12
|
-
Storage
|
|
12
|
+
Storage, CacheGateway, EventDispatcher
|
|
13
|
+
// storage
|
|
14
|
+
} from "./application";
|
|
13
15
|
import { Container, DefaultContainer, Inject, setDefaultContainer } from './infra/di/registry';
|
|
14
16
|
import { Dede } from './dede';
|
|
15
17
|
import { ServerError, NotFound, Forbidden, Conflict, Unauthorized, UnprocessableEntity, BadRequest, InternalServerError, CustomServerError } from './http/errors/server';
|
|
16
18
|
import { AppError } from './domain/errors/app-error';
|
|
17
|
-
export { Controller, Post, Get, Put, Delete, Patch, UseMiddleware, UseMiddlewares, Tracing, Entity, Restrict, VirtualProperty, GetterPrefix,
|
|
19
|
+
export { Controller, Post, Get, Put, Delete, Patch, UseMiddleware, UseMiddlewares, Tracing, Entity, Restrict, VirtualProperty, GetterPrefix, Model, UseCase, DecorateUseCase, Storage, CacheGateway, EventDispatcher, Inject, Container, DefaultContainer, setDefaultContainer, Dede, ServerError, NotFound, Forbidden, Conflict, Unauthorized, UnprocessableEntity, BadRequest, InternalServerError, CustomServerError, AppError };
|
|
@@ -1,23 +1,13 @@
|
|
|
1
1
|
import { Entity as DomainEntity } from "../../domain/entity";
|
|
2
|
-
export declare abstract class
|
|
2
|
+
export declare abstract class Entity extends DomainEntity {
|
|
3
3
|
[x: string]: any;
|
|
4
|
-
private buildRawEntityObject;
|
|
5
|
-
private getEntityHooks;
|
|
6
|
-
private runEntityHooks;
|
|
7
4
|
toEntity(): Record<string, any>;
|
|
8
|
-
toAsyncEntity(): Promise<Record<string, any>>;
|
|
9
5
|
toData({ serialize }?: {
|
|
10
6
|
serialize?: boolean;
|
|
11
7
|
}): Record<string, any>;
|
|
12
|
-
toAsyncData({ serialize }?: {
|
|
13
|
-
serialize?: boolean;
|
|
14
|
-
}): Promise<Record<string, any>>;
|
|
15
8
|
protected generateGetters(): void;
|
|
16
9
|
}
|
|
17
10
|
export declare function Restrict(): (target: any, propertyKey: string) => void;
|
|
18
11
|
export declare function VirtualProperty(propertyName: string): (target: any, methodName: string) => void;
|
|
19
12
|
export declare function Serialize(callback: (value: any) => any): PropertyDecorator;
|
|
20
13
|
export declare function GetterPrefix(prefix: string): (target: any, propertyKey: string) => void;
|
|
21
|
-
export { SerializableEntity as Entity };
|
|
22
|
-
export declare function BeforeToEntity(): MethodDecorator;
|
|
23
|
-
export declare function AfterToEntity(): MethodDecorator;
|
|
@@ -1,61 +1,6 @@
|
|
|
1
1
|
import { Entity as DomainEntity } from "../../domain/entity";
|
|
2
|
-
export class
|
|
3
|
-
buildRawEntityObject() {
|
|
4
|
-
const result = {};
|
|
5
|
-
for (const [propName] of Object.entries(this)) {
|
|
6
|
-
let value = this[propName];
|
|
7
|
-
if (typeof value === 'function')
|
|
8
|
-
continue;
|
|
9
|
-
if (value === undefined)
|
|
10
|
-
continue;
|
|
11
|
-
result[propName] = value;
|
|
12
|
-
}
|
|
13
|
-
return result;
|
|
14
|
-
}
|
|
15
|
-
getEntityHooks(hookKey) {
|
|
16
|
-
const hooks = [];
|
|
17
|
-
let current = this.constructor;
|
|
18
|
-
while (current && current !== SerializableEntity) {
|
|
19
|
-
const currentHooks = current[hookKey];
|
|
20
|
-
if (currentHooks && currentHooks.length) {
|
|
21
|
-
hooks.unshift(...currentHooks);
|
|
22
|
-
}
|
|
23
|
-
current = Object.getPrototypeOf(current);
|
|
24
|
-
}
|
|
25
|
-
return hooks;
|
|
26
|
-
}
|
|
27
|
-
runEntityHooks(hookKey, payload, awaitHooks) {
|
|
28
|
-
const hooks = this.getEntityHooks(hookKey);
|
|
29
|
-
if (!hooks.length)
|
|
30
|
-
return;
|
|
31
|
-
if (awaitHooks) {
|
|
32
|
-
return (async () => {
|
|
33
|
-
for (const hookName of hooks) {
|
|
34
|
-
const hook = this[hookName];
|
|
35
|
-
if (typeof hook !== 'function')
|
|
36
|
-
continue;
|
|
37
|
-
await hook.call(this, payload);
|
|
38
|
-
}
|
|
39
|
-
})();
|
|
40
|
-
}
|
|
41
|
-
for (const hookName of hooks) {
|
|
42
|
-
const hook = this[hookName];
|
|
43
|
-
if (typeof hook !== 'function')
|
|
44
|
-
continue;
|
|
45
|
-
try {
|
|
46
|
-
const result = hook.call(this, payload);
|
|
47
|
-
if (result && typeof result.then === 'function') {
|
|
48
|
-
void result.catch(() => undefined);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
throw error;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
2
|
+
export class Entity extends DomainEntity {
|
|
56
3
|
toEntity() {
|
|
57
|
-
const raw = this.buildRawEntityObject();
|
|
58
|
-
this.runEntityHooks(BEFORE_TO_ENTITY, raw, false);
|
|
59
4
|
// @ts-ignore
|
|
60
5
|
const propertiesConfigs = this.constructor.propertiesConfigs;
|
|
61
6
|
const result = {};
|
|
@@ -87,44 +32,6 @@ export class SerializableEntity extends DomainEntity {
|
|
|
87
32
|
value = null;
|
|
88
33
|
result[propertyName] = value;
|
|
89
34
|
}
|
|
90
|
-
this.runEntityHooks(AFTER_TO_ENTITY, result, false);
|
|
91
|
-
return result;
|
|
92
|
-
}
|
|
93
|
-
async toAsyncEntity() {
|
|
94
|
-
const raw = this.buildRawEntityObject();
|
|
95
|
-
await this.runEntityHooks(BEFORE_TO_ENTITY, raw, true);
|
|
96
|
-
// @ts-ignore
|
|
97
|
-
const propertiesConfigs = this.constructor.propertiesConfigs;
|
|
98
|
-
const result = {};
|
|
99
|
-
for (const [propName] of Object.entries(this)) {
|
|
100
|
-
let propertyName = propName;
|
|
101
|
-
let value = this[propName];
|
|
102
|
-
if (typeof value === 'function')
|
|
103
|
-
continue;
|
|
104
|
-
if (value === undefined)
|
|
105
|
-
continue;
|
|
106
|
-
// @ts-ignore
|
|
107
|
-
if (propertiesConfigs && propertiesConfigs[propName]?.serialize && (value || value === 0)) {
|
|
108
|
-
const serializedValue = await propertiesConfigs[propName].serialize(value);
|
|
109
|
-
if (serializedValue && typeof serializedValue === 'object' && !Array.isArray(serializedValue)) {
|
|
110
|
-
const entries = Object.entries(serializedValue);
|
|
111
|
-
for (const [serializedKey, serializedPropValue] of entries) {
|
|
112
|
-
let currentValue = serializedPropValue;
|
|
113
|
-
if (!currentValue)
|
|
114
|
-
currentValue = null;
|
|
115
|
-
result[serializedKey] = currentValue;
|
|
116
|
-
}
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
value = serializedValue;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
if (value === undefined || value === null)
|
|
124
|
-
value = null;
|
|
125
|
-
result[propertyName] = value;
|
|
126
|
-
}
|
|
127
|
-
await this.runEntityHooks(AFTER_TO_ENTITY, result, true);
|
|
128
35
|
return result;
|
|
129
36
|
}
|
|
130
37
|
toData({ serialize = false } = {}) {
|
|
@@ -154,33 +61,6 @@ export class SerializableEntity extends DomainEntity {
|
|
|
154
61
|
}
|
|
155
62
|
return result;
|
|
156
63
|
}
|
|
157
|
-
async toAsyncData({ serialize = true } = {}) {
|
|
158
|
-
// @ts-ignore
|
|
159
|
-
const propertiesConfigs = this.constructor.propertiesConfigs;
|
|
160
|
-
// @ts-ignore
|
|
161
|
-
const virtualProperties = this.constructor.virtualProperties;
|
|
162
|
-
const result = {};
|
|
163
|
-
for (const [propName] of Object.entries(this)) {
|
|
164
|
-
if (typeof this[propName] === 'function')
|
|
165
|
-
continue;
|
|
166
|
-
if (propertiesConfigs && propertiesConfigs[propName]?.restrict)
|
|
167
|
-
continue;
|
|
168
|
-
// @ts-ignore
|
|
169
|
-
let value = this[propName];
|
|
170
|
-
if (serialize && propertiesConfigs && propertiesConfigs[propName]?.serialize && value) {
|
|
171
|
-
value = await propertiesConfigs[propName].serialize(value);
|
|
172
|
-
}
|
|
173
|
-
result[propName] = value;
|
|
174
|
-
}
|
|
175
|
-
if (virtualProperties) {
|
|
176
|
-
for (const [methodName, propName] of Object.entries(virtualProperties)) {
|
|
177
|
-
if (this.__proto__[methodName]) {
|
|
178
|
-
result[propName] = await this[methodName]();
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return result;
|
|
183
|
-
}
|
|
184
64
|
generateGetters() {
|
|
185
65
|
super.generateGetters();
|
|
186
66
|
}
|
|
@@ -218,27 +98,3 @@ const loadPropertiesConfig = (target, propertyKey) => {
|
|
|
218
98
|
target.constructor.propertiesConfigs[propertyKey] = {};
|
|
219
99
|
}
|
|
220
100
|
};
|
|
221
|
-
const BEFORE_TO_ENTITY = Symbol('beforeToEntity');
|
|
222
|
-
const AFTER_TO_ENTITY = Symbol('afterToEntity');
|
|
223
|
-
const assertEntityDecoratorTarget = (target, decoratorName) => {
|
|
224
|
-
if (!SerializableEntity.prototype.isPrototypeOf(target)) {
|
|
225
|
-
throw new Error(`${decoratorName} can only be used on Entity classes`);
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
export { SerializableEntity as Entity };
|
|
229
|
-
export function BeforeToEntity() {
|
|
230
|
-
return function (target, propertyKey) {
|
|
231
|
-
assertEntityDecoratorTarget(target, 'BeforeToEntity');
|
|
232
|
-
const cls = target.constructor;
|
|
233
|
-
cls[BEFORE_TO_ENTITY] = cls[BEFORE_TO_ENTITY] || [];
|
|
234
|
-
cls[BEFORE_TO_ENTITY].push(propertyKey);
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
export function AfterToEntity() {
|
|
238
|
-
return function (target, propertyKey) {
|
|
239
|
-
assertEntityDecoratorTarget(target, 'AfterToEntity');
|
|
240
|
-
const cls = target.constructor;
|
|
241
|
-
cls[AFTER_TO_ENTITY] = cls[AFTER_TO_ENTITY] || [];
|
|
242
|
-
cls[AFTER_TO_ENTITY].push(propertyKey);
|
|
243
|
-
};
|
|
244
|
-
}
|
|
@@ -1,34 +1,38 @@
|
|
|
1
1
|
import { Entity } from "../domain";
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import { Model } from "../infra/model/model";
|
|
3
|
+
export interface RepositoryModel<E extends Entity, M> {
|
|
4
|
+
model: Model<E, M>;
|
|
4
5
|
}
|
|
5
|
-
export interface
|
|
6
|
-
|
|
6
|
+
export interface RepositoryCreate<E extends Entity, M> extends RepositoryModel<E, M> {
|
|
7
|
+
create(input: E): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export interface RepositoryUpdate<E extends Entity, M> extends RepositoryModel<E, M> {
|
|
10
|
+
update(input: E): Promise<void>;
|
|
7
11
|
}
|
|
8
12
|
export interface RepositoryRemove {
|
|
9
13
|
remove(id: string | number): Promise<void>;
|
|
10
14
|
}
|
|
11
|
-
export interface RepositoryRestore<
|
|
12
|
-
restore(id: string | number): Promise<
|
|
15
|
+
export interface RepositoryRestore<E extends Entity, M> extends RepositoryModel<E, M> {
|
|
16
|
+
restore(id: string | number): Promise<E>;
|
|
13
17
|
}
|
|
14
|
-
export type RepositoryRemoveBy<
|
|
15
|
-
[K in keyof
|
|
18
|
+
export type RepositoryRemoveBy<E> = {
|
|
19
|
+
[K in keyof E & string as `removeBy${Capitalize<K>}`]: (value: E[K]) => Promise<void>;
|
|
16
20
|
};
|
|
17
|
-
export type RepositoryRestoreBy<
|
|
18
|
-
[K in keyof
|
|
21
|
+
export type RepositoryRestoreBy<E> = {
|
|
22
|
+
[K in keyof E & string as `restoreBy${Capitalize<K>}`]: (value: E[K]) => Promise<any>;
|
|
19
23
|
};
|
|
20
|
-
export type RepositoryExistsBy<
|
|
21
|
-
[K in keyof
|
|
24
|
+
export type RepositoryExistsBy<E> = {
|
|
25
|
+
[K in keyof E & string as `existsBy${Capitalize<K>}`]: (value: E[K]) => Promise<boolean>;
|
|
22
26
|
};
|
|
23
|
-
export type RepositoryNotExistsBy<
|
|
24
|
-
[K in keyof
|
|
27
|
+
export type RepositoryNotExistsBy<E> = {
|
|
28
|
+
[K in keyof E & string as `notExistsBy${Capitalize<K>}`]: (value: E[K]) => Promise<boolean>;
|
|
25
29
|
};
|
|
26
|
-
export interface RepositoryPagination<
|
|
30
|
+
export interface RepositoryPagination<E extends Entity, M> extends RepositoryModel<E, M> {
|
|
27
31
|
restoreMany({ filter, pagination }: {
|
|
28
32
|
filter?: Record<string, any>;
|
|
29
33
|
pagination?: {
|
|
30
34
|
offset: number;
|
|
31
35
|
limit: number;
|
|
32
36
|
};
|
|
33
|
-
}): Promise<
|
|
37
|
+
}): Promise<E[]>;
|
|
34
38
|
}
|