framework-do-dede 5.5.5 → 6.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 CHANGED
@@ -40,7 +40,7 @@ bun run example/express_app/server.ts
40
40
  ## Quickstart
41
41
 
42
42
  ```ts
43
- import { Controller, Get, Post, UseCase, Dede } from './src';
43
+ import { Controller, Get, Post, UseCase, Dede, Optional } from './src';
44
44
 
45
45
  @Controller('/hello')
46
46
  class HelloController {
@@ -63,6 +63,9 @@ class HelloUseCase extends UseCase<{ name?: string }, { message: string }> {
63
63
  }
64
64
  }
65
65
 
66
+ const maybeName = Optional.ofNullable(process.env.DEFAULT_NAME);
67
+ const defaultName = maybeName.orElseUndefined();
68
+
66
69
  const app = await Dede.create({
67
70
  framework: { use: 'express', port: 3000 },
68
71
  registries: []
@@ -218,6 +221,7 @@ vira:
218
221
  ### Middlewares
219
222
 
220
223
  Middlewares devem implementar `execute(input: Input<any>)`. Podem ser classe, factory ou instancia.
224
+ Podem ser aplicados no controller (valem para todas as rotas) e/ou no método.
221
225
 
222
226
  ```ts
223
227
  import { Middleware, UseMiddleware, UseMiddlewares, Input } from './src';
@@ -228,7 +232,14 @@ class AuthMiddleware implements Middleware {
228
232
  }
229
233
  }
230
234
 
235
+ class LoggerMiddleware implements Middleware {
236
+ async execute(input: Input<any>) {
237
+ input.context.logged = true;
238
+ }
239
+ }
240
+
231
241
  @Controller('/secure')
242
+ @UseMiddleware(LoggerMiddleware)
232
243
  class SecureController {
233
244
  @Get()
234
245
  @UseMiddleware(AuthMiddleware)
@@ -332,53 +343,65 @@ class CreatePhotoUseCase extends UseCase<void, void> {
332
343
 
333
344
  ### Entity e Model
334
345
 
335
- Entities sao dominio puro. Use `Model` para mapear coluna/property e construir o objeto de persistencia.
346
+ Entities sao dominio puro. `Model` vive na borda e faz o mapeamento banco <-> model, alem de converter `Entity` <-> `Model`. Repositorios trabalham com `Model`, nao com `Entity`.
336
347
 
337
348
  ```ts
338
349
  import { Entity, Model, model, column } from './src';
339
350
 
340
- class Order extends Entity {
341
- private readonly id: string;
342
- private readonly name: string;
343
- private readonly amount: number;
344
-
345
- constructor(id: string, name: string, amount: number) {
346
- super();
347
- this.id = id;
348
- this.name = name;
349
- this.amount = amount;
350
- this.generateGetters();
351
- }
352
- }
353
-
354
- type OrderTable = 'orders';
351
+ type UserTable = 'users';
355
352
 
356
- @model<OrderTable>('orders')
357
- class OrderModel extends Model<OrderTable> {
353
+ @model<UserTable>('users')
354
+ class UserModel extends Model<UserTable, User> {
358
355
  @column('id')
359
356
  id!: string;
360
357
 
361
- @column('name')
358
+ @column('email2')
359
+ email!: string;
360
+
362
361
  name!: string;
362
+ password?: string;
363
363
 
364
- @column('amount')
365
- amount!: number;
364
+ fromEntity(user: User): this {
365
+ this.id = user.getId();
366
+ this.email = user.getEmail();
367
+ this.name = user.getName();
368
+ return this;
369
+ }
370
+
371
+ toEntity(): User {
372
+ return new User(this);
373
+ }
374
+ }
366
375
 
367
- constructor(order?: Order) {
376
+ class User extends Entity {
377
+ private readonly id: string;
378
+ private readonly email: string;
379
+ private readonly name: string;
380
+
381
+ constructor(model: UserModel) {
368
382
  super();
369
- if (order) {
370
- this.id = order.getId();
371
- this.name = order.getName();
372
- this.amount = order.getAmount();
373
- }
383
+ this.id = model.id;
384
+ this.email = model.email;
385
+ this.name = model.name;
386
+ this.generateGetters();
374
387
  }
375
388
  }
376
389
  ```
377
390
 
391
+ Carregando do banco:
392
+
393
+ ```ts
394
+ const row = result[0] ?? null;
395
+ const model = row ? new UserModel().fromModel(row) : null;
396
+ return Optional.ofNullable(model);
397
+ ```
398
+
378
399
  Regras principais:
379
400
 
380
401
  - `Model` guarda metadados de coluna (via `@column`) e nome da tabela (via `@model`)
381
- - a conversao entity -> model acontece no construtor do Model (ou em um factory)
402
+ - `fromModel` aplica o mapeamento coluna -> propriedade e ignora `null/undefined` (retorna o proprio model)
403
+ - `fromEntity` / `toEntity` fazem a conversao entity <-> model
404
+ - `Entity` recebe o `Model` no construtor (acoplamento forte na borda)
382
405
  - `generateGetters()` cria getters para campos (ex.: `getName`, `isActive`, `hasProfile`), mesmo quando o valor não foi definido.
383
406
 
384
407
  ### Storage Gateway
@@ -470,16 +493,40 @@ Quando um erro e lancado, o handler padroniza a resposta. Erros de dominio (`App
470
493
 
471
494
  Interfaces tipadas para padrao de repositorio:
472
495
 
473
- - `RepositoryCreate<T extends Entity>`
474
- - `RepositoryUpdate<T extends Entity>`
496
+ - `Optional<T>` (helper inspirado em micronaut/spring)
497
+ - `RepositoryModel` (base para o acoplamento do repositorio com Model)
498
+ - `RepositoryCreate<T extends RepositoryModel>`
499
+ - `RepositoryUpdate<T extends RepositoryModel>`
475
500
  - `RepositoryRemove`
476
- - `RepositoryRestore<T extends Entity>`
501
+ - `RepositoryRestore<T extends RepositoryModel>`
477
502
  - `RepositoryRemoveBy<T>`
478
503
  - `RepositoryRestoreBy<T>`
479
504
  - `RepositoryExistsBy<T>`
480
505
  - `RepositoryNotExistsBy<T>`
481
506
  - `RepositoryPagination<T>`
482
507
 
508
+ `RepositoryRestore` e `RepositoryRestoreBy` retornam `Optional<T>` para desacoplar a mensagem de erro do repositório:
509
+
510
+ ```ts
511
+ import { Optional } from './src';
512
+
513
+ class UserRepository {
514
+ async restore(id: string): Promise<Optional<UserModel>> {
515
+ const result = await this.orm
516
+ .select()
517
+ .from(userTable)
518
+ .where(eq(userTable.id, id));
519
+
520
+ const row = result[0] ?? null;
521
+ const model = row ? new UserModel().fromModel(row) : null;
522
+ return Optional.ofNullable(model);
523
+ }
524
+ }
525
+
526
+ const user = await repo.restore('1').orElseThrow('Usuario nao encontrado');
527
+ const userByEmail = await repo.restoreByEmail('a@b.com').orElseNull();
528
+ ```
529
+
483
530
  ## Exemplos
484
531
 
485
532
  ### Express
@@ -31,8 +31,8 @@ export declare function Controller(basePath?: string): (target: any) => void;
31
31
  export declare function Tracing<R>(tracer: Tracer<R>): (target: any, propertyKey?: string) => void;
32
32
  export declare function Version(version: number): (target: any, propertyKey?: string) => void;
33
33
  export declare function PresetIgnore(ignorePrefix?: boolean, ignoreVersion?: boolean): (target: any, propertyKey?: string) => void;
34
- export declare function UseMiddleware(middlewareClass: MiddlewareDefinition): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;
35
- export declare function UseMiddlewares(middlewareClasses: MiddlewareDefinition[]): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;
34
+ export declare function UseMiddleware(middlewareClass: MiddlewareDefinition): (target: any, propertyKey?: string, descriptor?: PropertyDescriptor) => void;
35
+ export declare function UseMiddlewares(middlewareClasses: MiddlewareDefinition[]): (target: any, propertyKey?: string, descriptor?: PropertyDescriptor) => void;
36
36
  export declare function Post(config?: {
37
37
  path?: string;
38
38
  statusCode?: number;
@@ -52,15 +52,31 @@ export function UseMiddleware(middlewareClass) {
52
52
  if (typeof middlewareClass === 'function' && isClass(middlewareClass) && !middlewareClass.prototype?.execute) {
53
53
  throw new FrameworkError('Middleware must implement execute()');
54
54
  }
55
+ const metadataTarget = propertyKey ? target : target;
56
+ const metadataKey = propertyKey ?? undefined;
55
57
  if (typeof middlewareClass === 'function' && middlewareClass.prototype?.execute) {
56
- const middlewares = Reflect.getMetadata('middlewares', target, propertyKey) || [];
58
+ const middlewares = metadataKey
59
+ ? Reflect.getMetadata('middlewares', metadataTarget, metadataKey) || []
60
+ : Reflect.getMetadata('middlewares', metadataTarget) || [];
57
61
  middlewares.push(middlewareClass);
58
- Reflect.defineMetadata('middlewares', middlewares, target, propertyKey);
62
+ if (metadataKey) {
63
+ Reflect.defineMetadata('middlewares', middlewares, metadataTarget, metadataKey);
64
+ }
65
+ else {
66
+ Reflect.defineMetadata('middlewares', middlewares, metadataTarget);
67
+ }
59
68
  return;
60
69
  }
61
- const middlewares = Reflect.getMetadata('middlewares', target, propertyKey) || [];
70
+ const middlewares = metadataKey
71
+ ? Reflect.getMetadata('middlewares', metadataTarget, metadataKey) || []
72
+ : Reflect.getMetadata('middlewares', metadataTarget) || [];
62
73
  middlewares.push(middlewareClass);
63
- Reflect.defineMetadata('middlewares', middlewares, target, propertyKey);
74
+ if (metadataKey) {
75
+ Reflect.defineMetadata('middlewares', middlewares, metadataTarget, metadataKey);
76
+ }
77
+ else {
78
+ Reflect.defineMetadata('middlewares', middlewares, metadataTarget);
79
+ }
64
80
  };
65
81
  }
66
82
  export function UseMiddlewares(middlewareClasses) {
@@ -73,9 +89,18 @@ export function UseMiddlewares(middlewareClasses) {
73
89
  throw new FrameworkError('Middleware must implement execute()');
74
90
  }
75
91
  }
76
- const existingMiddlewares = Reflect.getMetadata('middlewares', target, propertyKey) || [];
92
+ const metadataTarget = propertyKey ? target : target;
93
+ const metadataKey = propertyKey ?? undefined;
94
+ const existingMiddlewares = metadataKey
95
+ ? Reflect.getMetadata('middlewares', metadataTarget, metadataKey) || []
96
+ : Reflect.getMetadata('middlewares', metadataTarget) || [];
77
97
  existingMiddlewares.push(...middlewareClasses);
78
- Reflect.defineMetadata('middlewares', existingMiddlewares, target, propertyKey);
98
+ if (metadataKey) {
99
+ Reflect.defineMetadata('middlewares', existingMiddlewares, metadataTarget, metadataKey);
100
+ }
101
+ else {
102
+ Reflect.defineMetadata('middlewares', existingMiddlewares, metadataTarget);
103
+ }
79
104
  };
80
105
  }
81
106
  export function Post(config = {}) {
@@ -1,2 +1,3 @@
1
1
  import { Entity } from './entity';
2
- export { Entity };
2
+ import { Optional } from './optional';
3
+ export { Entity, Optional };
@@ -1,2 +1,3 @@
1
1
  import { Entity } from './entity';
2
- export { Entity };
2
+ import { Optional } from './optional';
3
+ export { Entity, Optional };
@@ -0,0 +1,13 @@
1
+ export type OptionalError = Error | string | (() => Error);
2
+ export declare class Optional<T> {
3
+ private readonly value;
4
+ private constructor();
5
+ static of<T>(value: T): Optional<T>;
6
+ static ofNullable<T>(value: T | null | undefined): Optional<T>;
7
+ isPresent(): boolean;
8
+ isEmpty(): boolean;
9
+ get(): T;
10
+ orElseNull(): T | null;
11
+ orElseUndefined(): T | undefined;
12
+ orElseThrow(error?: OptionalError): T;
13
+ }
@@ -0,0 +1,47 @@
1
+ export class Optional {
2
+ constructor(value) {
3
+ this.value = value;
4
+ }
5
+ static of(value) {
6
+ if (value === null || value === undefined) {
7
+ throw new Error('Optional.of() cannot be null or undefined');
8
+ }
9
+ return new Optional(value);
10
+ }
11
+ static ofNullable(value) {
12
+ return new Optional(value);
13
+ }
14
+ isPresent() {
15
+ return this.value !== null && this.value !== undefined;
16
+ }
17
+ isEmpty() {
18
+ return !this.isPresent();
19
+ }
20
+ get() {
21
+ if (!this.isPresent()) {
22
+ throw new Error('Optional is empty');
23
+ }
24
+ return this.value;
25
+ }
26
+ orElseNull() {
27
+ return this.isPresent() ? this.value : null;
28
+ }
29
+ orElseUndefined() {
30
+ return this.isPresent() ? this.value : undefined;
31
+ }
32
+ orElseThrow(error) {
33
+ if (this.isPresent()) {
34
+ return this.value;
35
+ }
36
+ if (typeof error === 'function') {
37
+ throw error();
38
+ }
39
+ if (typeof error === 'string') {
40
+ throw new Error(error);
41
+ }
42
+ if (error instanceof Error) {
43
+ throw error;
44
+ }
45
+ throw new Error('Optional is empty');
46
+ }
47
+ }
@@ -98,10 +98,12 @@ export default class ControllerHandler {
98
98
  let tracer = Reflect.getMetadata('tracer', controller) || null;
99
99
  const controllerVersion = Reflect.getMetadata('version', controller);
100
100
  const controllerPresetIgnore = Reflect.getMetadata('presetIgnore', controller);
101
+ const controllerMiddlewares = Reflect.getMetadata('middlewares', controller) || [];
101
102
  const instance = new controller();
102
103
  for (const methodName of methodNames) {
103
104
  const routeConfig = Reflect.getMetadata('route', controller.prototype, methodName);
104
- const middlewares = Reflect.getMetadata('middlewares', controller.prototype, methodName);
105
+ const methodMiddlewares = Reflect.getMetadata('middlewares', controller.prototype, methodName) || [];
106
+ const middlewares = [...controllerMiddlewares, ...methodMiddlewares];
105
107
  const responseType = Reflect.getMetadata('responseType', controller.prototype, methodName) || 'json';
106
108
  tracer = Reflect.getMetadata('tracer', controller.prototype, methodName) || tracer;
107
109
  const methodVersion = Reflect.getMetadata('version', controller.prototype, methodName);
@@ -128,7 +130,7 @@ export default class ControllerHandler {
128
130
  tracer
129
131
  },
130
132
  responseType,
131
- middlewares: middlewares ? middlewares.map(middleware => this.resolveMiddleware(middleware)) : [],
133
+ middlewares: middlewares.length ? middlewares.map(middleware => this.resolveMiddleware(middleware)) : [],
132
134
  });
133
135
  }
134
136
  }
package/dist/index.d.ts CHANGED
@@ -3,8 +3,10 @@ import { Container, DefaultContainer, Inject, setDefaultContainer } from './infr
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
+ import { Optional } from './domain/optional';
6
7
  import type { ValidatorDefinition } from './interface/validation/validator';
7
8
  import type { StorageGateway, Event, EventPayload } from './application';
9
+ import { RepositoryModel } from './protocols/model';
8
10
  import type { RepositoryCreate, RepositoryUpdate, RepositoryRemove, RepositoryRemoveBy, RepositoryExistsBy, RepositoryRestore, RepositoryRestoreBy, RepositoryNotExistsBy, RepositoryPagination } from './protocols/repository';
9
- export { Controller, Post, Get, Put, Delete, Patch, Input, Version, PresetIgnore, Middleware, UseMiddleware, UseMiddlewares, Tracer, Tracing, TracerData, Entity, Restrict, VirtualProperty, GetterPrefix, Transform, Model, model, column, UseCase, DecorateUseCase, Hook, BeforeHook, AfterHook, HookBefore, HookAfter, 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 };
11
+ export { Controller, Post, Get, Put, Delete, Patch, Input, Version, PresetIgnore, Middleware, UseMiddleware, UseMiddlewares, Tracer, Tracing, TracerData, Entity, Restrict, VirtualProperty, GetterPrefix, Transform, Model, model, column, UseCase, DecorateUseCase, Hook, BeforeHook, AfterHook, HookBefore, HookAfter, Storage, CacheGateway, EventDispatcher, Inject, Container, DefaultContainer, setDefaultContainer, Dede, Options, Register, ServerError, NotFound, Forbidden, Conflict, Unauthorized, UnprocessableEntity, BadRequest, InternalServerError, CustomServerError, AppError, Optional, RepositoryModel, RepositoryCreate, RepositoryUpdate, RepositoryRemove, RepositoryRemoveBy, RepositoryRestore, RepositoryExistsBy, RepositoryRestoreBy, RepositoryNotExistsBy, RepositoryPagination };
10
12
  export type { ValidatorDefinition, StorageGateway, Event, EventPayload };
package/dist/index.js CHANGED
@@ -16,4 +16,6 @@ import { Container, DefaultContainer, Inject, setDefaultContainer } from './infr
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, Version, PresetIgnore, UseMiddleware, UseMiddlewares, Tracing, Entity, Restrict, VirtualProperty, GetterPrefix, Transform, Model, model, column, UseCase, DecorateUseCase, Hook, BeforeHook, AfterHook, HookBefore, HookAfter, Storage, CacheGateway, EventDispatcher, Inject, Container, DefaultContainer, setDefaultContainer, Dede, ServerError, NotFound, Forbidden, Conflict, Unauthorized, UnprocessableEntity, BadRequest, InternalServerError, CustomServerError, AppError };
19
+ import { Optional } from './domain/optional';
20
+ import { RepositoryModel } from './protocols/model';
21
+ export { Controller, Post, Get, Put, Delete, Patch, Version, PresetIgnore, UseMiddleware, UseMiddlewares, Tracing, Entity, Restrict, VirtualProperty, GetterPrefix, Transform, Model, model, column, UseCase, DecorateUseCase, Hook, BeforeHook, AfterHook, HookBefore, HookAfter, Storage, CacheGateway, EventDispatcher, Inject, Container, DefaultContainer, setDefaultContainer, Dede, ServerError, NotFound, Forbidden, Conflict, Unauthorized, UnprocessableEntity, BadRequest, InternalServerError, CustomServerError, AppError, Optional, RepositoryModel };
@@ -1,16 +1,16 @@
1
- import { Entity } from "../../application";
1
+ import { RepositoryModel } from "../../protocols/model";
2
2
  export type ColumnDefinition = {
3
3
  column: string;
4
4
  property: string;
5
5
  };
6
- export declare abstract class Model<TTable = string> {
6
+ export declare abstract class Model<TTable = string, TEntity = any> extends RepositoryModel<TEntity> {
7
7
  table: TTable;
8
8
  columns: ColumnDefinition[];
9
9
  [property: string]: any;
10
- fromModel(input: Record<string, any>): Model;
11
- abstract fromEntity(entity: Entity): Model;
10
+ fromModel(input: Record<string, any> | null | undefined): this;
11
+ abstract fromEntity(entity: TEntity): this;
12
12
  toModel(): Record<string, any>;
13
- abstract toEntity(): Entity;
13
+ abstract toEntity(): TEntity;
14
14
  }
15
15
  export declare function model<TTable>(table: TTable): (target: any) => void;
16
16
  export declare function column(columnName: string): (target: any, propertyKey: string) => void;
@@ -1,5 +1,9 @@
1
- export class Model {
1
+ import { RepositoryModel } from "../../protocols/model";
2
+ export class Model extends RepositoryModel {
2
3
  fromModel(input) {
4
+ if (input === null || input === undefined) {
5
+ return this;
6
+ }
3
7
  const columns = this.columns ?? [];
4
8
  const columnByName = new Map(columns.map((column) => [column.column, column.property]));
5
9
  for (const [property, value] of Object.entries(input)) {
@@ -5,4 +5,4 @@ export type ValidationErrorOptions = {
5
5
  errorName?: string;
6
6
  validatorOptions?: ValidatorOptions;
7
7
  };
8
- export declare function validateWithClassValidator<T extends object>(dtoClass: new () => T, input: T, options?: ValidationErrorOptions): Promise<T>;
8
+ export declare function validateWithClassValidator<T extends object>(dtoClass: new () => T, input: Record<string, any>, options?: ValidationErrorOptions): Promise<T & Record<string, any>>;
@@ -0,0 +1,6 @@
1
+ export declare abstract class RepositoryModel<TEntity = any> {
2
+ abstract fromModel(input: Record<string, any> | null | undefined): this;
3
+ abstract toModel(): Record<string, any>;
4
+ abstract fromEntity(entity: TEntity): this;
5
+ abstract toEntity(): TEntity;
6
+ }
@@ -0,0 +1,2 @@
1
+ export class RepositoryModel {
2
+ }
@@ -1,21 +1,22 @@
1
- import { Entity } from "../domain";
2
- export interface RepositoryCreate<T extends Entity> {
1
+ import { Optional } from "../domain";
2
+ import { RepositoryModel } from "../protocols/model";
3
+ export interface RepositoryCreate<T extends RepositoryModel> {
3
4
  create(input: T): Promise<void>;
4
5
  }
5
- export interface RepositoryUpdate<T extends Entity> {
6
+ export interface RepositoryUpdate<T extends RepositoryModel> {
6
7
  update(input: T): Promise<void>;
7
8
  }
8
9
  export interface RepositoryRemove {
9
10
  remove(id: string | number): Promise<void>;
10
11
  }
11
- export interface RepositoryRestore<T extends Entity> {
12
- restore(id: string | number): Promise<T>;
12
+ export interface RepositoryRestore<T extends RepositoryModel> {
13
+ restore(id: string | number): Promise<Optional<T>>;
13
14
  }
14
15
  export type RepositoryRemoveBy<T> = {
15
16
  [K in keyof T & string as `removeBy${Capitalize<K>}`]: (value: T[K]) => Promise<void>;
16
17
  };
17
18
  export type RepositoryRestoreBy<T> = {
18
- [K in keyof T & string as `restoreBy${Capitalize<K>}`]: (value: T[K]) => Promise<any>;
19
+ [K in keyof T & string as `restoreBy${Capitalize<K>}`]: (value: T[K]) => Promise<Optional<T>>;
19
20
  };
20
21
  export type RepositoryExistsBy<T> = {
21
22
  [K in keyof T & string as `existsBy${Capitalize<K>}`]: (value: T[K]) => Promise<boolean>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "framework-do-dede",
3
- "version": "5.5.5",
3
+ "version": "6.0.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",