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 +71 -35
- package/dist/application/index.d.ts +2 -2
- package/dist/application/index.js +2 -2
- 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 +4 -2
- package/dist/index.js +4 -2
- package/dist/infra/model/model.d.ts +6 -7
- package/dist/infra/model/model.js +5 -6
- 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,62 @@ 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
|
-
import { Entity, Model,
|
|
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
|
-
|
|
365
|
-
class OrderModel extends Model<OrderTable> {
|
|
351
|
+
class UserModel extends Model<User> {
|
|
366
352
|
@column('id')
|
|
367
353
|
id!: string;
|
|
368
354
|
|
|
369
|
-
@column('
|
|
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
|
-
|
|
373
|
-
|
|
373
|
+
class User extends Entity {
|
|
374
|
+
private readonly id: string;
|
|
375
|
+
private readonly email: string;
|
|
376
|
+
private readonly name: string;
|
|
374
377
|
|
|
375
|
-
constructor(
|
|
378
|
+
constructor(model: UserModel) {
|
|
376
379
|
super();
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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`)
|
|
389
|
-
-
|
|
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
|
-
- `
|
|
482
|
-
- `
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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';
|
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
|
@@ -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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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 "../../
|
|
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<
|
|
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>):
|
|
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
|
-
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
|
-
|
|
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:
|
|
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>;
|