framework-do-dede 4.1.0 → 5.2.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 +65 -101
- package/dist/application/index.d.ts +5 -3
- package/dist/application/index.js +4 -3
- package/dist/application/services.d.ts +9 -0
- package/dist/application/services.js +7 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/dist/infra/model/model.d.ts +12 -0
- package/dist/infra/model/model.js +21 -0
- package/dist/infra/serialization/entity.d.ts +4 -16
- package/dist/infra/serialization/entity.js +15 -159
- 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,57 @@ 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 mapear coluna/property e construir o objeto de persistencia.
|
|
262
256
|
|
|
263
257
|
```ts
|
|
264
|
-
import { Entity,
|
|
265
|
-
|
|
266
|
-
class User extends Entity {
|
|
267
|
-
@Serialize((value: Email) => value.getValue())
|
|
268
|
-
private readonly email: Email;
|
|
258
|
+
import { Entity, Model, model, column } from './src';
|
|
269
259
|
|
|
270
|
-
|
|
271
|
-
private readonly
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
private readonly profile?: Profile;
|
|
275
|
-
|
|
276
|
-
@VirtualProperty('displayName')
|
|
277
|
-
private display() {
|
|
278
|
-
return 'User ' + this.email.getValue();
|
|
279
|
-
}
|
|
260
|
+
class Order extends Entity {
|
|
261
|
+
private readonly id: string;
|
|
262
|
+
private readonly name: string;
|
|
263
|
+
private readonly amount: number;
|
|
280
264
|
|
|
281
|
-
constructor(
|
|
265
|
+
constructor(id: string, name: string, amount: number) {
|
|
282
266
|
super();
|
|
283
|
-
this.
|
|
284
|
-
this.
|
|
267
|
+
this.id = id;
|
|
268
|
+
this.name = name;
|
|
269
|
+
this.amount = amount;
|
|
285
270
|
this.generateGetters();
|
|
286
271
|
}
|
|
287
272
|
}
|
|
288
273
|
|
|
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.
|
|
274
|
+
type OrderTable = 'orders';
|
|
303
275
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
276
|
+
@model<OrderTable>('orders')
|
|
277
|
+
class OrderModel extends Model<OrderTable> {
|
|
278
|
+
@column('id')
|
|
279
|
+
id!: string;
|
|
308
280
|
|
|
309
|
-
|
|
310
|
-
|
|
281
|
+
@column('name')
|
|
282
|
+
name!: string;
|
|
311
283
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
private readonly s3Key: string;
|
|
284
|
+
@column('amount')
|
|
285
|
+
amount!: number;
|
|
315
286
|
|
|
316
|
-
constructor(
|
|
287
|
+
constructor(order?: Order) {
|
|
317
288
|
super();
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
private before(payload: Record<string, any>) {
|
|
324
|
-
payload.rawTouched = true;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
@AfterToEntity()
|
|
328
|
-
private async after(payload: Record<string, any>) {
|
|
329
|
-
await saveToS3(payload.s3Key);
|
|
289
|
+
if (order) {
|
|
290
|
+
this.id = order.getId();
|
|
291
|
+
this.name = order.getName();
|
|
292
|
+
this.amount = order.getAmount();
|
|
293
|
+
}
|
|
330
294
|
}
|
|
331
295
|
}
|
|
332
296
|
```
|
|
333
297
|
|
|
298
|
+
Regras principais:
|
|
299
|
+
|
|
300
|
+
- `Model` guarda metadados de coluna (via `@column`) e nome da tabela (via `@model`)
|
|
301
|
+
- a conversao entity -> model acontece no construtor do Model (ou em um factory)
|
|
302
|
+
- `generateGetters()` cria getters para campos (ex.: `getName`, `isActive`, `hasProfile`)
|
|
303
|
+
|
|
334
304
|
### Storage Gateway
|
|
335
305
|
|
|
336
306
|
Use `@Storage` para injetar gateways com interface `StorageGateway`.
|
|
@@ -350,6 +320,25 @@ class FileService {
|
|
|
350
320
|
}
|
|
351
321
|
```
|
|
352
322
|
|
|
323
|
+
### Event Dispatcher
|
|
324
|
+
|
|
325
|
+
Use `@EventDispatcher` para enfileirar tarefas ou eventos de background com interface `EventDispatcher`.
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
import { EventDispatcher } from './src';
|
|
329
|
+
|
|
330
|
+
type QueueEvent = { name: string; payload?: Record<string, any> };
|
|
331
|
+
|
|
332
|
+
class QueueService {
|
|
333
|
+
@EventDispatcher('QueueDispatcher')
|
|
334
|
+
private readonly dispatcher!: { dispatch: (event: QueueEvent) => Promise<void> };
|
|
335
|
+
|
|
336
|
+
async enqueue(payload: Record<string, any>) {
|
|
337
|
+
await this.dispatcher.dispatch({ name: 'jobs.create', payload });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
353
342
|
### DI (Container/Inject)
|
|
354
343
|
|
|
355
344
|
Registre dependencias ao iniciar o server (usando o container padrão):
|
|
@@ -447,49 +436,24 @@ app.registerControllers([ExampleController]);
|
|
|
447
436
|
app.listen();
|
|
448
437
|
```
|
|
449
438
|
|
|
450
|
-
###
|
|
439
|
+
### Background com EventDispatcher
|
|
451
440
|
|
|
452
441
|
```ts
|
|
453
|
-
import {
|
|
442
|
+
import { EventDispatcher } from './src';
|
|
454
443
|
|
|
455
|
-
type
|
|
444
|
+
type QueueEvent = { name: string; payload: Record<string, any> };
|
|
456
445
|
|
|
457
|
-
|
|
458
|
-
|
|
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;
|
|
468
|
-
|
|
469
|
-
private constructor({ name, s3Key }: { name: string; s3Key: string }) {
|
|
470
|
-
super();
|
|
471
|
-
this.name = name;
|
|
472
|
-
this.s3Key = s3Key;
|
|
473
|
-
}
|
|
446
|
+
class FileService {
|
|
447
|
+
@EventDispatcher('QueueDispatcher')
|
|
448
|
+
private readonly dispatcher!: { dispatch: (event: QueueEvent) => Promise<void> };
|
|
474
449
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
payload: {
|
|
480
|
-
name: payload.name,
|
|
481
|
-
s3Key: payload.s3Key
|
|
482
|
-
}
|
|
450
|
+
async enqueueFile(input: { name: string; s3Key: string }) {
|
|
451
|
+
await this.dispatcher.dispatch({
|
|
452
|
+
name: 'files.create',
|
|
453
|
+
payload: input
|
|
483
454
|
});
|
|
484
455
|
}
|
|
485
|
-
|
|
486
|
-
static create(input: { name: string; s3Key: string }) {
|
|
487
|
-
return new FileEntity(input);
|
|
488
|
-
}
|
|
489
456
|
}
|
|
490
|
-
|
|
491
|
-
const entity = FileEntity.create({ name: 'report', s3Key: 's3://bucket/report.pdf' });
|
|
492
|
-
const serialized = entity.toEntity();
|
|
493
457
|
```
|
|
494
458
|
|
|
495
459
|
## Testes
|
|
@@ -1,7 +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, Transform } from '../infra/serialization/entity';
|
|
3
|
+
import { Model, model, column } from '../infra/model/model';
|
|
3
4
|
import { DecorateUseCase, UseCase } from './usecase';
|
|
4
|
-
export { Controller, UseMiddleware, UseMiddlewares, Post, Get, Put, Delete, Patch, Tracing, DecorateUseCase, UseCase, Entity, Restrict, VirtualProperty,
|
|
5
|
-
export { Storage, CacheGateway } from './services';
|
|
5
|
+
export { Controller, UseMiddleware, UseMiddlewares, Post, Get, Put, Delete, Patch, Tracing, DecorateUseCase, UseCase, Entity, Restrict, VirtualProperty, GetterPrefix, Transform, Model, model, column, };
|
|
6
|
+
export { Storage, CacheGateway, EventDispatcher } from './services';
|
|
6
7
|
export type { Middleware, Input, Tracer, TracerData };
|
|
7
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, Transform } from '../infra/serialization/entity';
|
|
3
|
+
import { Model, model, column } from '../infra/model/model';
|
|
3
4
|
import { DecorateUseCase, UseCase } from './usecase';
|
|
4
|
-
export { Controller, UseMiddleware, UseMiddlewares, Post, Get, Put, Delete, Patch, Tracing, DecorateUseCase, UseCase, Entity, Restrict, VirtualProperty,
|
|
5
|
-
export { Storage, CacheGateway } from './services';
|
|
5
|
+
export { Controller, UseMiddleware, UseMiddlewares, Post, Get, Put, Delete, Patch, Tracing, DecorateUseCase, UseCase, Entity, Restrict, VirtualProperty, GetterPrefix, Transform, Model, model, column, };
|
|
6
|
+
export { Storage, CacheGateway, EventDispatcher } from './services';
|
|
@@ -10,5 +10,14 @@ export interface CacheGateway {
|
|
|
10
10
|
set<T = unknown>(key: string, value: T, ttl?: number): Promise<void> | void;
|
|
11
11
|
delete(key: string): Promise<boolean> | boolean;
|
|
12
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
|
+
}
|
|
13
21
|
export declare function Storage(gatewayName: string, container?: Container): (target: any, propertyKey: string) => void;
|
|
14
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;
|
|
@@ -30,3 +30,10 @@ export function CacheGateway(gatewayName, container) {
|
|
|
30
30
|
defineGatewayProperty(resolvedTarget, resolvedProperty, gatewayName, container, (dependency) => !!dependency?.get && !!dependency?.set && !!dependency?.delete, `${gatewayName} is not a valid CacheGateway`);
|
|
31
31
|
};
|
|
32
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`);
|
|
38
|
+
};
|
|
39
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +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, model, column, 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 { StorageGateway } from './application';
|
|
7
|
+
import type { StorageGateway, Event, EventPayload } from './application';
|
|
8
8
|
import type { 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,
|
|
10
|
-
export type { ValidatorDefinition, StorageGateway };
|
|
9
|
+
export { Controller, Post, Get, Put, Delete, Patch, Input, Middleware, UseMiddleware, UseMiddlewares, Tracer, Tracing, TracerData, Entity, Restrict, VirtualProperty, GetterPrefix, Model, model, column, UseCase, DecorateUseCase, Storage, CacheGateway, EventDispatcher, Inject, Container, DefaultContainer, setDefaultContainer, Dede, Options, Register, ServerError, NotFound, Forbidden, Conflict, Unauthorized, UnprocessableEntity, BadRequest, InternalServerError, CustomServerError, AppError, RepositoryCreate, RepositoryUpdate, RepositoryRemove, RepositoryRemoveBy, RepositoryRestore, RepositoryExistsBy, RepositoryRestoreBy, RepositoryNotExistsBy, RepositoryPagination };
|
|
10
|
+
export type { ValidatorDefinition, StorageGateway, Event, EventPayload };
|
package/dist/index.js
CHANGED
|
@@ -3,17 +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, model, column,
|
|
7
7
|
// entity
|
|
8
8
|
// usecase
|
|
9
9
|
UseCase, DecorateUseCase,
|
|
10
10
|
// usecase
|
|
11
11
|
// storage
|
|
12
|
-
Storage, CacheGateway
|
|
12
|
+
Storage, CacheGateway, EventDispatcher
|
|
13
13
|
// storage
|
|
14
14
|
} from "./application";
|
|
15
15
|
import { Container, DefaultContainer, Inject, setDefaultContainer } from './infra/di/registry';
|
|
16
16
|
import { Dede } from './dede';
|
|
17
17
|
import { ServerError, NotFound, Forbidden, Conflict, Unauthorized, UnprocessableEntity, BadRequest, InternalServerError, CustomServerError } from './http/errors/server';
|
|
18
18
|
import { AppError } from './domain/errors/app-error';
|
|
19
|
-
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, model, column, UseCase, DecorateUseCase, Storage, CacheGateway, EventDispatcher, Inject, Container, DefaultContainer, setDefaultContainer, Dede, ServerError, NotFound, Forbidden, Conflict, Unauthorized, UnprocessableEntity, BadRequest, InternalServerError, CustomServerError, AppError };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type ColumnDefinition = {
|
|
2
|
+
column: string;
|
|
3
|
+
property: string;
|
|
4
|
+
};
|
|
5
|
+
export declare abstract class Model<TTable = string> {
|
|
6
|
+
table: TTable;
|
|
7
|
+
columns: ColumnDefinition[];
|
|
8
|
+
[property: string]: any;
|
|
9
|
+
toPersistence(): Record<string, any>;
|
|
10
|
+
}
|
|
11
|
+
export declare function model<TTable>(table: TTable): (target: any) => void;
|
|
12
|
+
export declare function column(columnName: string): (target: any, propertyKey: string) => void;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export class Model {
|
|
2
|
+
toPersistence() {
|
|
3
|
+
const record = {};
|
|
4
|
+
const columns = this.columns ?? [];
|
|
5
|
+
for (const column of columns) {
|
|
6
|
+
record[column.column] = this[column.property];
|
|
7
|
+
}
|
|
8
|
+
return record;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function model(table) {
|
|
12
|
+
return function (target) {
|
|
13
|
+
target.prototype.table = table;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function column(columnName) {
|
|
17
|
+
return function (target, propertyKey) {
|
|
18
|
+
target.columns = target.columns || [];
|
|
19
|
+
target.columns.push({ column: columnName, property: propertyKey });
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -1,23 +1,11 @@
|
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
private runEntityHooks;
|
|
7
|
-
toEntity(): Record<string, any>;
|
|
8
|
-
toAsyncEntity(): Promise<Record<string, any>>;
|
|
9
|
-
toData({ serialize }?: {
|
|
10
|
-
serialize?: boolean;
|
|
11
|
-
}): Record<string, any>;
|
|
12
|
-
toAsyncData({ serialize }?: {
|
|
13
|
-
serialize?: boolean;
|
|
14
|
-
}): Promise<Record<string, any>>;
|
|
4
|
+
from(): Record<string, any>;
|
|
5
|
+
to(transform?: boolean): Record<string, any>;
|
|
15
6
|
protected generateGetters(): void;
|
|
16
7
|
}
|
|
17
8
|
export declare function Restrict(): (target: any, propertyKey: string) => void;
|
|
18
9
|
export declare function VirtualProperty(propertyName: string): (target: any, methodName: string) => void;
|
|
19
|
-
export declare function
|
|
10
|
+
export declare function Transform(callback: (value: any) => any): PropertyDecorator;
|
|
20
11
|
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,98 +1,6 @@
|
|
|
1
1
|
import { Entity as DomainEntity } from "../../domain/entity";
|
|
2
|
-
export class
|
|
3
|
-
|
|
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
|
-
}
|
|
56
|
-
toEntity() {
|
|
57
|
-
const raw = this.buildRawEntityObject();
|
|
58
|
-
this.runEntityHooks(BEFORE_TO_ENTITY, raw, false);
|
|
59
|
-
// @ts-ignore
|
|
60
|
-
const propertiesConfigs = this.constructor.propertiesConfigs;
|
|
61
|
-
const result = {};
|
|
62
|
-
for (const [propName] of Object.entries(this)) {
|
|
63
|
-
let propertyName = propName;
|
|
64
|
-
let value = this[propName];
|
|
65
|
-
if (typeof value === 'function')
|
|
66
|
-
continue;
|
|
67
|
-
if (value === undefined)
|
|
68
|
-
continue;
|
|
69
|
-
// @ts-ignore
|
|
70
|
-
if (propertiesConfigs && propertiesConfigs[propName]?.serialize && value) {
|
|
71
|
-
const serializedValue = propertiesConfigs[propName].serialize(value);
|
|
72
|
-
if (serializedValue && typeof serializedValue === 'object' && !Array.isArray(serializedValue)) {
|
|
73
|
-
const entries = Object.entries(serializedValue);
|
|
74
|
-
for (const [serializedKey, serializedPropValue] of entries) {
|
|
75
|
-
let currentValue = serializedPropValue;
|
|
76
|
-
if (!currentValue)
|
|
77
|
-
currentValue = null;
|
|
78
|
-
result[serializedKey] = currentValue;
|
|
79
|
-
}
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
value = serializedValue;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
if (value === undefined || value === null)
|
|
87
|
-
value = null;
|
|
88
|
-
result[propertyName] = value;
|
|
89
|
-
}
|
|
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);
|
|
2
|
+
export class Entity extends DomainEntity {
|
|
3
|
+
from() {
|
|
96
4
|
// @ts-ignore
|
|
97
5
|
const propertiesConfigs = this.constructor.propertiesConfigs;
|
|
98
6
|
const result = {};
|
|
@@ -104,30 +12,29 @@ export class SerializableEntity extends DomainEntity {
|
|
|
104
12
|
if (value === undefined)
|
|
105
13
|
continue;
|
|
106
14
|
// @ts-ignore
|
|
107
|
-
if (propertiesConfigs && propertiesConfigs[propName]?.
|
|
108
|
-
const
|
|
109
|
-
if (
|
|
110
|
-
const entries = Object.entries(
|
|
111
|
-
for (const [
|
|
112
|
-
let currentValue =
|
|
15
|
+
if (propertiesConfigs && propertiesConfigs[propName]?.transform && value) {
|
|
16
|
+
const transformedValue = propertiesConfigs[propName].transform(value);
|
|
17
|
+
if (transformedValue && typeof transformedValue === 'object' && !Array.isArray(transformedValue)) {
|
|
18
|
+
const entries = Object.entries(transformedValue);
|
|
19
|
+
for (const [transformedKey, transformedPropValue] of entries) {
|
|
20
|
+
let currentValue = transformedPropValue;
|
|
113
21
|
if (!currentValue)
|
|
114
22
|
currentValue = null;
|
|
115
|
-
result[
|
|
23
|
+
result[transformedKey] = currentValue;
|
|
116
24
|
}
|
|
117
25
|
continue;
|
|
118
26
|
}
|
|
119
27
|
else {
|
|
120
|
-
value =
|
|
28
|
+
value = transformedValue;
|
|
121
29
|
}
|
|
122
30
|
}
|
|
123
31
|
if (value === undefined || value === null)
|
|
124
32
|
value = null;
|
|
125
33
|
result[propertyName] = value;
|
|
126
34
|
}
|
|
127
|
-
await this.runEntityHooks(AFTER_TO_ENTITY, result, true);
|
|
128
35
|
return result;
|
|
129
36
|
}
|
|
130
|
-
|
|
37
|
+
to(transform = true) {
|
|
131
38
|
// @ts-ignore
|
|
132
39
|
const propertiesConfigs = this.constructor.propertiesConfigs;
|
|
133
40
|
// @ts-ignore
|
|
@@ -140,8 +47,8 @@ export class SerializableEntity extends DomainEntity {
|
|
|
140
47
|
continue;
|
|
141
48
|
// @ts-ignore
|
|
142
49
|
let value = this[propName];
|
|
143
|
-
if (
|
|
144
|
-
value = propertiesConfigs[propName].
|
|
50
|
+
if (transform && propertiesConfigs && propertiesConfigs[propName]?.transform && value) {
|
|
51
|
+
value = propertiesConfigs[propName].transform(value);
|
|
145
52
|
}
|
|
146
53
|
result[propName] = value;
|
|
147
54
|
}
|
|
@@ -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
|
}
|
|
@@ -198,10 +78,10 @@ export function VirtualProperty(propertyName) {
|
|
|
198
78
|
cls.virtualProperties[methodName] = propertyName;
|
|
199
79
|
};
|
|
200
80
|
}
|
|
201
|
-
export function
|
|
81
|
+
export function Transform(callback) {
|
|
202
82
|
return function (target, propertyKey) {
|
|
203
83
|
loadPropertiesConfig(target, propertyKey);
|
|
204
|
-
target.constructor.propertiesConfigs[propertyKey].
|
|
84
|
+
target.constructor.propertiesConfigs[propertyKey].transform = callback;
|
|
205
85
|
};
|
|
206
86
|
}
|
|
207
87
|
export function GetterPrefix(prefix) {
|
|
@@ -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
|
-
}
|