framework-do-dede 5.5.6 → 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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: []
@@ -340,53 +343,62 @@ class CreatePhotoUseCase extends UseCase<void, void> {
340
343
 
341
344
  ### Entity e Model
342
345
 
343
- 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`.
344
347
 
345
348
  ```ts
346
- import { Entity, Model, model, column } from './src';
347
-
348
- class Order extends Entity {
349
- private readonly id: string;
350
- private readonly name: string;
351
- private readonly amount: number;
352
-
353
- constructor(id: string, name: string, amount: number) {
354
- super();
355
- this.id = id;
356
- this.name = name;
357
- this.amount = amount;
358
- this.generateGetters();
359
- }
360
- }
361
-
362
- type OrderTable = 'orders';
349
+ import { Entity, Model, column } from './src';
363
350
 
364
- @model<OrderTable>('orders')
365
- class OrderModel extends Model<OrderTable> {
351
+ class UserModel extends Model<User> {
366
352
  @column('id')
367
353
  id!: string;
368
354
 
369
- @column('name')
355
+ @column('email2')
356
+ email!: string;
357
+
370
358
  name!: string;
359
+ password?: string;
360
+
361
+ fromEntity(user: User): this {
362
+ this.id = user.getId();
363
+ this.email = user.getEmail();
364
+ this.name = user.getName();
365
+ return this;
366
+ }
367
+
368
+ toEntity(): User {
369
+ return new User(this);
370
+ }
371
+ }
371
372
 
372
- @column('amount')
373
- amount!: number;
373
+ class User extends Entity {
374
+ private readonly id: string;
375
+ private readonly email: string;
376
+ private readonly name: string;
374
377
 
375
- constructor(order?: Order) {
378
+ constructor(model: UserModel) {
376
379
  super();
377
- if (order) {
378
- this.id = order.getId();
379
- this.name = order.getName();
380
- this.amount = order.getAmount();
381
- }
380
+ this.id = model.id;
381
+ this.email = model.email;
382
+ this.name = model.name;
383
+ this.generateGetters();
382
384
  }
383
385
  }
384
386
  ```
385
387
 
388
+ Carregando do banco:
389
+
390
+ ```ts
391
+ const row = result[0] ?? null;
392
+ const model = row ? new UserModel().fromModel(row) : null;
393
+ return Optional.ofNullable(model);
394
+ ```
395
+
386
396
  Regras principais:
387
397
 
388
- - `Model` guarda metadados de coluna (via `@column`) e nome da tabela (via `@model`)
389
- - a conversao entity -> model acontece no construtor do Model (ou em um factory)
398
+ - `Model` guarda metadados de coluna (via `@column`)
399
+ - `fromModel` aplica o mapeamento coluna -> propriedade e ignora `null/undefined` (retorna o proprio model)
400
+ - `fromEntity` / `toEntity` fazem a conversao entity <-> model
401
+ - `Entity` recebe o `Model` no construtor (acoplamento forte na borda)
390
402
  - `generateGetters()` cria getters para campos (ex.: `getName`, `isActive`, `hasProfile`), mesmo quando o valor não foi definido.
391
403
 
392
404
  ### Storage Gateway
@@ -478,16 +490,40 @@ Quando um erro e lancado, o handler padroniza a resposta. Erros de dominio (`App
478
490
 
479
491
  Interfaces tipadas para padrao de repositorio:
480
492
 
481
- - `RepositoryCreate<T extends Entity>`
482
- - `RepositoryUpdate<T extends Entity>`
493
+ - `Optional<T>` (helper inspirado em micronaut/spring)
494
+ - `RepositoryModel` (base para o acoplamento do repositorio com Model)
495
+ - `RepositoryCreate<T extends RepositoryModel>`
496
+ - `RepositoryUpdate<T extends RepositoryModel>`
483
497
  - `RepositoryRemove`
484
- - `RepositoryRestore<T extends Entity>`
498
+ - `RepositoryRestore<T extends RepositoryModel>`
485
499
  - `RepositoryRemoveBy<T>`
486
500
  - `RepositoryRestoreBy<T>`
487
501
  - `RepositoryExistsBy<T>`
488
502
  - `RepositoryNotExistsBy<T>`
489
503
  - `RepositoryPagination<T>`
490
504
 
505
+ `RepositoryRestore` e `RepositoryRestoreBy` retornam `Optional<T>` para desacoplar a mensagem de erro do repositório:
506
+
507
+ ```ts
508
+ import { Optional } from './src';
509
+
510
+ class UserRepository {
511
+ async restore(id: string): Promise<Optional<UserModel>> {
512
+ const result = await this.orm
513
+ .select()
514
+ .from(userTable)
515
+ .where(eq(userTable.id, id));
516
+
517
+ const row = result[0] ?? null;
518
+ const model = row ? new UserModel().fromModel(row) : null;
519
+ return Optional.ofNullable(model);
520
+ }
521
+ }
522
+
523
+ const user = await repo.restore('1').orElseThrow('Usuario nao encontrado');
524
+ const userByEmail = await repo.restoreByEmail('a@b.com').orElseNull();
525
+ ```
526
+
491
527
  ## Exemplos
492
528
 
493
529
  ### Express
@@ -1,8 +1,8 @@
1
1
  import { Controller, Post, Get, Put, Delete, Patch, UseMiddleware, UseMiddlewares, Tracing, Version, PresetIgnore, type Middleware, type Input, type Tracer, type TracerData } from './controller';
2
2
  import { Entity, Restrict, VirtualProperty, GetterPrefix, Transform } from '../infra/serialization/entity';
3
- import { Model, model, column } from '../infra/model/model';
3
+ import { Model, column } from '../infra/model/model';
4
4
  import { DecorateUseCase, UseCase, Hook, BeforeHook, AfterHook, HookBefore, HookAfter } from './usecase';
5
- export { Controller, UseMiddleware, UseMiddlewares, Post, Get, Put, Delete, Patch, Tracing, Version, PresetIgnore, DecorateUseCase, UseCase, Hook, BeforeHook, AfterHook, HookBefore, HookAfter, Entity, Restrict, VirtualProperty, GetterPrefix, Transform, Model, model, column, };
5
+ export { Controller, UseMiddleware, UseMiddlewares, Post, Get, Put, Delete, Patch, Tracing, Version, PresetIgnore, DecorateUseCase, UseCase, Hook, BeforeHook, AfterHook, HookBefore, HookAfter, Entity, Restrict, VirtualProperty, GetterPrefix, Transform, Model, column, };
6
6
  export { Storage, CacheGateway, EventDispatcher } from './services';
7
7
  export type { Middleware, Input, Tracer, TracerData };
8
8
  export type { StorageGateway } from './services';
@@ -1,6 +1,6 @@
1
1
  import { Controller, Post, Get, Put, Delete, Patch, UseMiddleware, UseMiddlewares, Tracing, Version, PresetIgnore } from './controller';
2
2
  import { Entity, Restrict, VirtualProperty, GetterPrefix, Transform } from '../infra/serialization/entity';
3
- import { Model, model, column } from '../infra/model/model';
3
+ import { Model, column } from '../infra/model/model';
4
4
  import { DecorateUseCase, UseCase, Hook, BeforeHook, AfterHook, HookBefore, HookAfter, } from './usecase';
5
- export { Controller, UseMiddleware, UseMiddlewares, Post, Get, Put, Delete, Patch, Tracing, Version, PresetIgnore, DecorateUseCase, UseCase, Hook, BeforeHook, AfterHook, HookBefore, HookAfter, Entity, Restrict, VirtualProperty, GetterPrefix, Transform, Model, model, column, };
5
+ export { Controller, UseMiddleware, UseMiddlewares, Post, Get, Put, Delete, Patch, Tracing, Version, PresetIgnore, DecorateUseCase, UseCase, Hook, BeforeHook, AfterHook, HookBefore, HookAfter, Entity, Restrict, VirtualProperty, GetterPrefix, Transform, Model, column, };
6
6
  export { Storage, CacheGateway, EventDispatcher } from './services';
@@ -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
+ }
package/dist/index.d.ts CHANGED
@@ -1,10 +1,12 @@
1
- import { Post, Get, Put, Delete, Patch, Controller, 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 } from "./application";
1
+ import { Post, Get, Put, Delete, Patch, Controller, Input, Version, PresetIgnore, Middleware, UseMiddleware, UseMiddlewares, Tracer, Tracing, TracerData, Entity, Restrict, VirtualProperty, GetterPrefix, Transform, Model, column, UseCase, DecorateUseCase, Hook, BeforeHook, AfterHook, HookBefore, HookAfter, 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
+ 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, 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
@@ -3,7 +3,7 @@ import {
3
3
  Post, Get, Put, Delete, Patch, Controller, Version, PresetIgnore, UseMiddleware, UseMiddlewares, Tracing,
4
4
  // controller
5
5
  // entity
6
- Entity, Restrict, VirtualProperty, GetterPrefix, Transform, Model, model, column,
6
+ Entity, Restrict, VirtualProperty, GetterPrefix, Transform, Model, column,
7
7
  // entity
8
8
  // usecase
9
9
  UseCase, DecorateUseCase, Hook, BeforeHook, AfterHook, HookBefore, HookAfter,
@@ -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, 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,15 @@
1
- import { Entity } from "../../application";
1
+ import { Entity } from "../../infra/serialization/entity";
2
+ import { RepositoryModel } from "../../protocols/model";
2
3
  export type ColumnDefinition = {
3
4
  column: string;
4
5
  property: string;
5
6
  };
6
- export declare abstract class Model<TTable = string> {
7
- table: TTable;
7
+ export declare abstract class Model<TEntity extends Entity = Entity> extends RepositoryModel<TEntity> {
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
- export declare function model<TTable>(table: TTable): (target: any) => void;
16
15
  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)) {
@@ -28,11 +32,6 @@ export class Model {
28
32
  return record;
29
33
  }
30
34
  }
31
- export function model(table) {
32
- return function (target) {
33
- target.prototype.table = table;
34
- };
35
- }
36
35
  export function column(columnName) {
37
36
  return function (target, propertyKey) {
38
37
  target.columns = target.columns || [];
@@ -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.6",
3
+ "version": "6.0.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",