framework-do-dede 5.5.6 → 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 +71 -32
- package/dist/domain/index.d.ts +2 -1
- package/dist/domain/index.js +2 -1
- package/dist/domain/optional.d.ts +13 -0
- package/dist/domain/optional.js +47 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/infra/model/model.d.ts +5 -5
- package/dist/infra/model/model.js +5 -1
- package/dist/interface/validation/class-validator.d.ts +1 -1
- package/dist/protocols/model.d.ts +6 -0
- package/dist/protocols/model.js +2 -0
- package/dist/protocols/repository.d.ts +7 -6
- package/package.json +1 -1
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,65 @@ class CreatePhotoUseCase extends UseCase<void, void> {
|
|
|
340
343
|
|
|
341
344
|
### Entity e Model
|
|
342
345
|
|
|
343
|
-
Entities sao dominio puro.
|
|
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
349
|
import { Entity, Model, model, column } from './src';
|
|
347
350
|
|
|
348
|
-
|
|
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';
|
|
351
|
+
type UserTable = 'users';
|
|
363
352
|
|
|
364
|
-
@model<
|
|
365
|
-
class
|
|
353
|
+
@model<UserTable>('users')
|
|
354
|
+
class UserModel extends Model<UserTable, User> {
|
|
366
355
|
@column('id')
|
|
367
356
|
id!: string;
|
|
368
357
|
|
|
369
|
-
@column('
|
|
358
|
+
@column('email2')
|
|
359
|
+
email!: string;
|
|
360
|
+
|
|
370
361
|
name!: string;
|
|
362
|
+
password?: string;
|
|
363
|
+
|
|
364
|
+
fromEntity(user: User): this {
|
|
365
|
+
this.id = user.getId();
|
|
366
|
+
this.email = user.getEmail();
|
|
367
|
+
this.name = user.getName();
|
|
368
|
+
return this;
|
|
369
|
+
}
|
|
371
370
|
|
|
372
|
-
|
|
373
|
-
|
|
371
|
+
toEntity(): User {
|
|
372
|
+
return new User(this);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
374
375
|
|
|
375
|
-
|
|
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) {
|
|
376
382
|
super();
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
}
|
|
383
|
+
this.id = model.id;
|
|
384
|
+
this.email = model.email;
|
|
385
|
+
this.name = model.name;
|
|
386
|
+
this.generateGetters();
|
|
382
387
|
}
|
|
383
388
|
}
|
|
384
389
|
```
|
|
385
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
|
+
|
|
386
399
|
Regras principais:
|
|
387
400
|
|
|
388
401
|
- `Model` guarda metadados de coluna (via `@column`) e nome da tabela (via `@model`)
|
|
389
|
-
-
|
|
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)
|
|
390
405
|
- `generateGetters()` cria getters para campos (ex.: `getName`, `isActive`, `hasProfile`), mesmo quando o valor não foi definido.
|
|
391
406
|
|
|
392
407
|
### Storage Gateway
|
|
@@ -478,16 +493,40 @@ Quando um erro e lancado, o handler padroniza a resposta. Erros de dominio (`App
|
|
|
478
493
|
|
|
479
494
|
Interfaces tipadas para padrao de repositorio:
|
|
480
495
|
|
|
481
|
-
- `
|
|
482
|
-
- `
|
|
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>`
|
|
483
500
|
- `RepositoryRemove`
|
|
484
|
-
- `RepositoryRestore<T extends
|
|
501
|
+
- `RepositoryRestore<T extends RepositoryModel>`
|
|
485
502
|
- `RepositoryRemoveBy<T>`
|
|
486
503
|
- `RepositoryRestoreBy<T>`
|
|
487
504
|
- `RepositoryExistsBy<T>`
|
|
488
505
|
- `RepositoryNotExistsBy<T>`
|
|
489
506
|
- `RepositoryPagination<T>`
|
|
490
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
|
+
|
|
491
530
|
## Exemplos
|
|
492
531
|
|
|
493
532
|
### Express
|
package/dist/domain/index.d.ts
CHANGED
package/dist/domain/index.js
CHANGED
|
@@ -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
|
@@ -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
|
-
|
|
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 {
|
|
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>):
|
|
11
|
-
abstract fromEntity(entity:
|
|
10
|
+
fromModel(input: Record<string, any> | null | undefined): this;
|
|
11
|
+
abstract fromEntity(entity: TEntity): this;
|
|
12
12
|
toModel(): Record<string, any>;
|
|
13
|
-
abstract toEntity():
|
|
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
|
-
|
|
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:
|
|
8
|
+
export declare function validateWithClassValidator<T extends object>(dtoClass: new () => T, input: Record<string, any>, options?: ValidationErrorOptions): Promise<T & Record<string, any>>;
|
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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
|
|
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
|
|
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<
|
|
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>;
|