framework-do-dede 5.3.3 → 5.4.3

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
@@ -235,7 +235,7 @@ class CreateUserUseCase extends UseCase<{ name: string }, { id: string }> {
235
235
  }
236
236
  ```
237
237
 
238
- Decorator `@DecorateUseCase` permite executar use cases antes do principal (chaining).
238
+ Decorator `@DecorateUseCase` permite compor use cases ao redor do método.
239
239
 
240
240
  ```ts
241
241
  import { UseCase, DecorateUseCase } from './src';
@@ -250,6 +250,46 @@ class CreateUserUseCase extends UseCase<{ name: string }, { id: string }> {
250
250
  }
251
251
  ```
252
252
 
253
+ Hooks oferecem uma alternativa simples para acoplar eventos sem depender de `data`/`context` da request. O use case dispara o hook e decide qual payload enviar. Cada use case registra **um HookBefore e/ou um HookAfter**.
254
+
255
+ ```ts
256
+ import { UseCase, HookAfter, HookBefore, AfterHook, BeforeHook } from './src';
257
+
258
+ class SavePhoto extends AfterHook<{ id: string }> {
259
+ async use(payload: { id: string }) {
260
+ // this.notify() é chamado automaticamente após o execute
261
+ console.log('photo saved:', payload.id);
262
+ }
263
+ }
264
+
265
+ class ValidatePhoto extends BeforeHook<{ name: string }> {
266
+ async use(payload: { name: string }) {
267
+ console.log('validating:', payload.name);
268
+ }
269
+ }
270
+
271
+ @HookBefore(ValidatePhoto)
272
+ @HookAfter(SavePhoto)
273
+ class CreatePhotoUseCase extends UseCase<{ name: string }, { id: string }> {
274
+ async execute() {
275
+ const photo = { id: 'photo-1', name: this.data?.name ?? 'no-name' };
276
+ this.afterHook.use({ id: photo.id });
277
+ return photo;
278
+ }
279
+ }
280
+ ```
281
+
282
+ `HookAfter` não executa quando o método lança erro. Para executar mesmo em erro:
283
+
284
+ ```ts
285
+ @HookAfter(SavePhoto, { runOnError: true })
286
+ class CreatePhotoUseCase extends UseCase<void, void> {
287
+ async execute() {
288
+ throw new Error('boom');
289
+ }
290
+ }
291
+ ```
292
+
253
293
  ### Entity e Model
254
294
 
255
295
  Entities sao dominio puro. Use `Model` para mapear coluna/property e construir o objeto de persistencia.
@@ -1,8 +1,8 @@
1
1
  import { Controller, Post, Get, Put, Delete, Patch, UseMiddleware, UseMiddlewares, Tracing, type Middleware, type Input, type Tracer, type TracerData } from './controller';
2
2
  import { Entity, Restrict, VirtualProperty, GetterPrefix, Transform } from '../infra/serialization/entity';
3
3
  import { Model, model, column } from '../infra/model/model';
4
- import { DecorateUseCase, UseCase } from './usecase';
5
- export { Controller, UseMiddleware, UseMiddlewares, Post, Get, Put, Delete, Patch, Tracing, DecorateUseCase, UseCase, Entity, Restrict, VirtualProperty, GetterPrefix, Transform, Model, model, column, };
4
+ import { DecorateUseCase, UseCase, Hook, BeforeHook, AfterHook, HookBefore, HookAfter } from './usecase';
5
+ export { Controller, UseMiddleware, UseMiddlewares, Post, Get, Put, Delete, Patch, Tracing, DecorateUseCase, UseCase, Hook, BeforeHook, AfterHook, HookBefore, HookAfter, Entity, Restrict, VirtualProperty, GetterPrefix, Transform, Model, 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 } from './controller';
2
2
  import { Entity, Restrict, VirtualProperty, GetterPrefix, Transform } from '../infra/serialization/entity';
3
3
  import { Model, model, column } from '../infra/model/model';
4
- import { DecorateUseCase, UseCase } from './usecase';
5
- export { Controller, UseMiddleware, UseMiddlewares, Post, Get, Put, Delete, Patch, Tracing, DecorateUseCase, UseCase, Entity, Restrict, VirtualProperty, GetterPrefix, Transform, Model, model, column, };
4
+ import { DecorateUseCase, UseCase, Hook, BeforeHook, AfterHook, HookBefore, HookAfter, } from './usecase';
5
+ export { Controller, UseMiddleware, UseMiddlewares, Post, Get, Put, Delete, Patch, Tracing, DecorateUseCase, UseCase, Hook, BeforeHook, AfterHook, HookBefore, HookAfter, Entity, Restrict, VirtualProperty, GetterPrefix, Transform, Model, model, column, };
6
6
  export { Storage, CacheGateway, EventDispatcher } from './services';
@@ -3,13 +3,43 @@ export declare const USE_CASE_DECORATORS: unique symbol;
3
3
  export declare abstract class UseCase<UseCaseInput, UseCaseOutput, UseCaseContext = any> {
4
4
  protected readonly data?: UseCaseInput;
5
5
  protected readonly context?: UseCaseContext;
6
+ protected readonly beforeHook: HookManager;
7
+ protected readonly afterHook: HookManager;
6
8
  constructor(input?: Input<UseCaseInput>);
7
9
  abstract execute(): Promise<UseCaseOutput>;
8
10
  }
9
11
  type UseCaseConstructor = new (...args: any[]) => UseCase<any, any>;
12
+ type HookConstructor = new () => Hook<any, any>;
13
+ type HookPosition = 'before' | 'after';
14
+ interface HookOptions {
15
+ runOnError?: boolean;
16
+ }
10
17
  interface DecorateUseCaseOptions {
11
18
  useCase: UseCaseConstructor | UseCaseConstructor[];
12
19
  params?: Record<string, any>;
13
20
  }
21
+ export declare abstract class Hook<Payload = any, Context = any> {
22
+ protected payload?: Payload;
23
+ protected context?: Context;
24
+ setPayload(payload: Payload, context?: Context): void;
25
+ notify(): Promise<void>;
26
+ abstract use(payload: Payload, context?: Context): Promise<void> | void;
27
+ }
28
+ export declare abstract class AfterHook<Payload = any, Context = any> extends Hook<Payload, Context> {
29
+ }
30
+ export declare abstract class BeforeHook<Payload = any, Context = any> extends Hook<Payload, Context> {
31
+ }
32
+ declare class HookManager {
33
+ private readonly entry?;
34
+ private readonly owner;
35
+ private readonly position;
36
+ constructor(owner: UseCase<any, any>, position: HookPosition);
37
+ use(payload: unknown): void;
38
+ notifyBefore(): Promise<void>;
39
+ notifyAfter(onError: boolean): Promise<void>;
40
+ private buildEntry;
41
+ }
14
42
  export declare function DecorateUseCase(options: DecorateUseCaseOptions): <T extends UseCaseConstructor>(target: T) => T;
43
+ export declare function HookBefore(hookClass: HookConstructor): <T extends UseCaseConstructor>(target: T) => T;
44
+ export declare function HookAfter(hookClass: HookConstructor, options?: HookOptions): <T extends UseCaseConstructor>(target: T) => T;
15
45
  export {};
@@ -5,35 +5,154 @@ export class UseCase {
5
5
  if (input?.context) {
6
6
  this.context = input.context;
7
7
  }
8
+ this.beforeHook = new HookManager(this, 'before');
9
+ this.afterHook = new HookManager(this, 'after');
8
10
  }
9
11
  }
12
+ const USE_CASE_HOOKS = Symbol('USE_CASE_HOOKS');
13
+ const USE_CASE_HOOKS_WRAPPED = Symbol('USE_CASE_HOOKS_WRAPPED');
14
+ function getHookMetadata(target) {
15
+ return target[USE_CASE_HOOKS] ?? [];
16
+ }
17
+ function registerHook(target, metadata) {
18
+ const existing = getHookMetadata(target);
19
+ if (existing.some((entry) => entry.position === metadata.position)) {
20
+ throw new Error(`Only one ${metadata.position} hook can be registered per use case`);
21
+ }
22
+ target[USE_CASE_HOOKS] = [...existing, metadata];
23
+ }
24
+ function ensureHookedExecution(target) {
25
+ const prototype = target.prototype;
26
+ const original = prototype.execute;
27
+ if (typeof original !== 'function') {
28
+ return target;
29
+ }
30
+ if (original[USE_CASE_HOOKS_WRAPPED]) {
31
+ return target;
32
+ }
33
+ const wrapped = async function executeWithHooks(...args) {
34
+ const beforeHook = this.beforeHook;
35
+ const afterHook = this.afterHook;
36
+ await beforeHook.notifyBefore();
37
+ let result;
38
+ let originalError;
39
+ try {
40
+ result = await original.apply(this, args);
41
+ }
42
+ catch (error) {
43
+ originalError = error;
44
+ }
45
+ await afterHook.notifyAfter(!!originalError);
46
+ if (originalError) {
47
+ throw originalError;
48
+ }
49
+ return result;
50
+ };
51
+ wrapped[USE_CASE_HOOKS_WRAPPED] = true;
52
+ prototype.execute = wrapped;
53
+ return target;
54
+ }
55
+ export class Hook {
56
+ setPayload(payload, context) {
57
+ this.payload = payload;
58
+ if (context !== undefined) {
59
+ this.context = context;
60
+ }
61
+ }
62
+ async notify() {
63
+ await this.use(this.payload, this.context);
64
+ }
65
+ }
66
+ export class AfterHook extends Hook {
67
+ }
68
+ export class BeforeHook extends Hook {
69
+ }
70
+ class HookManager {
71
+ constructor(owner, position) {
72
+ this.owner = owner;
73
+ this.position = position;
74
+ this.entry = this.buildEntry();
75
+ }
76
+ use(payload) {
77
+ this.entry?.setPayload(payload);
78
+ }
79
+ async notifyBefore() {
80
+ if (this.position === 'before' && this.entry?.metadata.position === 'before') {
81
+ await this.entry.instance.notify();
82
+ }
83
+ }
84
+ async notifyAfter(onError) {
85
+ if (!this.entry || this.position !== 'after' || this.entry.metadata.position !== 'after') {
86
+ return;
87
+ }
88
+ if (onError && !this.entry.metadata.runOnError) {
89
+ return;
90
+ }
91
+ await this.entry.instance.notify();
92
+ }
93
+ buildEntry() {
94
+ const metadata = getHookMetadata(this.owner.constructor);
95
+ const entry = metadata.find((item) => item.position === this.position);
96
+ if (!entry) {
97
+ return undefined;
98
+ }
99
+ const instance = new entry.hookClass();
100
+ return {
101
+ metadata: entry,
102
+ instance,
103
+ setPayload: (payload) => {
104
+ instance.setPayload(payload, this.owner.context);
105
+ },
106
+ };
107
+ }
108
+ }
109
+ function buildUseCaseInstances(owner, options) {
110
+ const useCases = Array.isArray(options.useCase)
111
+ ? options.useCase
112
+ : [options.useCase];
113
+ if (useCases.length === 0) {
114
+ return [];
115
+ }
116
+ return useCases.map((UseCaseClass) => {
117
+ return new UseCaseClass({
118
+ data: owner?.data,
119
+ context: {
120
+ ...(owner?.context ?? {}),
121
+ options: options.params || {},
122
+ },
123
+ });
124
+ });
125
+ }
10
126
  export function DecorateUseCase(options) {
11
127
  return (target) => {
128
+ const stateKey = Symbol('decoratorUseCases');
12
129
  return class extends target {
13
130
  constructor(...args) {
14
131
  super(...args);
15
- const useCases = Array.isArray(options.useCase)
16
- ? options.useCase
17
- : [options.useCase];
18
- const self = this;
19
- self._decoratorUseCases = useCases.map((UseCaseClass) => {
20
- return new UseCaseClass({
21
- data: this.data,
22
- context: {
23
- ...this.context,
24
- options: options.params || {}
25
- },
26
- });
27
- });
28
- self._originalMethod = target.prototype.execute;
132
+ this[stateKey] = {
133
+ useCases: buildUseCaseInstances(this, options),
134
+ original: target.prototype.execute,
135
+ };
29
136
  }
30
137
  async execute() {
31
- const self = this;
32
- for (const useCase of self._decoratorUseCases) {
138
+ const state = this[stateKey];
139
+ for (const useCase of state.useCases) {
33
140
  await useCase.execute();
34
141
  }
35
- return await self._originalMethod.call(this);
142
+ return await state.original.call(this);
36
143
  }
37
144
  };
38
145
  };
39
146
  }
147
+ export function HookBefore(hookClass) {
148
+ return (target) => {
149
+ registerHook(target, { hookClass, position: 'before' });
150
+ return ensureHookedExecution(target);
151
+ };
152
+ }
153
+ export function HookAfter(hookClass, options = {}) {
154
+ return (target) => {
155
+ registerHook(target, { hookClass, position: 'after', runOnError: options.runOnError });
156
+ return ensureHookedExecution(target);
157
+ };
158
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
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";
1
+ import { Post, Get, Put, Delete, Patch, Controller, Input, Middleware, UseMiddleware, UseMiddlewares, Tracer, Tracing, TracerData, Entity, Restrict, VirtualProperty, GetterPrefix, Model, 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';
@@ -6,5 +6,5 @@ import { AppError } from './domain/errors/app-error';
6
6
  import type { ValidatorDefinition } from './interface/validation/validator';
7
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, 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 };
9
+ export { Controller, Post, Get, Put, Delete, Patch, Input, Middleware, UseMiddleware, UseMiddlewares, Tracer, Tracing, TracerData, Entity, Restrict, VirtualProperty, GetterPrefix, 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 };
10
10
  export type { ValidatorDefinition, StorageGateway, Event, EventPayload };
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ Post, Get, Put, Delete, Patch, Controller, UseMiddleware, UseMiddlewares, Tracin
6
6
  Entity, Restrict, VirtualProperty, GetterPrefix, Model, model, column,
7
7
  // entity
8
8
  // usecase
9
- UseCase, DecorateUseCase,
9
+ UseCase, DecorateUseCase, Hook, BeforeHook, AfterHook, HookBefore, HookAfter,
10
10
  // usecase
11
11
  // storage
12
12
  Storage, CacheGateway, EventDispatcher
@@ -16,4 +16,4 @@ 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, 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 };
19
+ export { Controller, Post, Get, Put, Delete, Patch, UseMiddleware, UseMiddlewares, Tracing, Entity, Restrict, VirtualProperty, GetterPrefix, 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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "framework-do-dede",
3
- "version": "5.3.3",
3
+ "version": "5.4.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",