@dismissible/nestjs-dismissible 0.0.2-canary.8976e84.0 → 0.0.2-canary.b0d8bfe.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 +58 -74
- package/jest.config.ts +1 -1
- package/package.json +8 -11
- package/project.json +1 -1
- package/src/api/dismissible-item-response.dto.ts +0 -8
- package/src/api/dismissible-item.mapper.spec.ts +0 -12
- package/src/api/dismissible-item.mapper.ts +2 -8
- package/src/api/index.ts +2 -3
- package/src/api/use-cases/dismiss/dismiss.controller.spec.ts +1 -2
- package/src/api/use-cases/dismiss/dismiss.controller.ts +9 -10
- package/src/api/use-cases/get-or-create/get-or-create.controller.spec.ts +2 -42
- package/src/api/use-cases/get-or-create/get-or-create.controller.ts +11 -58
- package/src/api/use-cases/get-or-create/index.ts +0 -1
- package/src/api/use-cases/restore/restore.controller.spec.ts +1 -2
- package/src/api/use-cases/restore/restore.controller.ts +9 -10
- package/src/api/validation/index.ts +2 -0
- package/src/api/validation/param-validation.pipe.spec.ts +313 -0
- package/src/api/validation/param-validation.pipe.ts +38 -0
- package/src/api/validation/param.decorators.ts +32 -0
- package/src/core/dismissible-core.service.spec.ts +75 -29
- package/src/core/dismissible-core.service.ts +40 -28
- package/src/core/dismissible.service.spec.ts +106 -24
- package/src/core/dismissible.service.ts +93 -54
- package/src/core/hook-runner.service.spec.ts +495 -54
- package/src/core/hook-runner.service.ts +125 -24
- package/src/core/index.ts +0 -1
- package/src/core/lifecycle-hook.interface.ts +7 -122
- package/src/core/service-responses.interface.ts +9 -9
- package/src/dismissible.module.integration.spec.ts +704 -0
- package/src/dismissible.module.ts +10 -11
- package/src/events/dismissible.events.ts +17 -40
- package/src/index.ts +1 -1
- package/src/response/http-exception-filter.spec.ts +179 -0
- package/src/response/http-exception-filter.ts +3 -3
- package/src/response/response.service.spec.ts +0 -14
- package/src/testing/factories.ts +24 -9
- package/src/utils/dismissible.helper.ts +2 -2
- package/src/validation/dismissible-input.dto.ts +47 -0
- package/src/validation/index.ts +1 -0
- package/tsconfig.json +3 -0
- package/tsconfig.spec.json +12 -0
- package/src/api/use-cases/get-or-create/get-or-create.request.dto.ts +0 -17
- package/src/core/create-options.ts +0 -9
- package/src/request/index.ts +0 -2
- package/src/request/request-context.decorator.ts +0 -14
- package/src/request/request-context.interface.ts +0 -6
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
DISMISSIBLE_HOOKS,
|
|
4
4
|
IDismissibleLifecycleHook,
|
|
5
5
|
IHookResult,
|
|
6
|
-
} from '
|
|
6
|
+
} from '@dismissible/nestjs-dismissible-hooks';
|
|
7
7
|
import { DISMISSIBLE_LOGGER, IDismissibleLogger } from '@dismissible/nestjs-logger';
|
|
8
|
-
import {
|
|
9
|
-
import { IRequestContext } from '
|
|
8
|
+
import { DismissibleItemDto } from '@dismissible/nestjs-dismissible-item';
|
|
9
|
+
import { IRequestContext } from '@dismissible/nestjs-dismissible-request';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Result from running pre-hooks.
|
|
@@ -32,41 +32,67 @@ export interface IHookRunResult {
|
|
|
32
32
|
* Service responsible for running lifecycle hooks.
|
|
33
33
|
*/
|
|
34
34
|
@Injectable()
|
|
35
|
-
export class HookRunner
|
|
36
|
-
private readonly sortedHooks: IDismissibleLifecycleHook
|
|
35
|
+
export class HookRunner {
|
|
36
|
+
private readonly sortedHooks: IDismissibleLifecycleHook[];
|
|
37
37
|
|
|
38
38
|
constructor(
|
|
39
39
|
@Optional()
|
|
40
40
|
@Inject(DISMISSIBLE_HOOKS)
|
|
41
|
-
hooks: IDismissibleLifecycleHook
|
|
41
|
+
hooks: IDismissibleLifecycleHook[] = [],
|
|
42
42
|
@Inject(DISMISSIBLE_LOGGER)
|
|
43
43
|
private readonly logger: IDismissibleLogger,
|
|
44
44
|
) {
|
|
45
|
-
// Sort hooks by priority (lower numbers first)
|
|
46
45
|
this.sortedHooks = [...hooks].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
/**
|
|
50
|
-
* Run pre-
|
|
49
|
+
* Run pre-request hooks (global - runs at start of any operation).
|
|
50
|
+
* Use for authentication, rate limiting, request validation.
|
|
51
51
|
*/
|
|
52
|
-
async
|
|
52
|
+
async runPreRequest(
|
|
53
53
|
itemId: string,
|
|
54
54
|
userId: string,
|
|
55
55
|
context?: IRequestContext,
|
|
56
56
|
): Promise<IHookRunResult> {
|
|
57
|
-
return this.runPreHooks('
|
|
57
|
+
return this.runPreHooks('onBeforeRequest', itemId, userId, context);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
|
-
* Run post-
|
|
61
|
+
* Run post-request hooks (global - runs at end of any operation).
|
|
62
|
+
* Use for audit logging, metrics, cleanup.
|
|
62
63
|
*/
|
|
63
|
-
async
|
|
64
|
+
async runPostRequest(
|
|
64
65
|
itemId: string,
|
|
65
|
-
item: DismissibleItemDto
|
|
66
|
+
item: DismissibleItemDto,
|
|
66
67
|
userId: string,
|
|
67
68
|
context?: IRequestContext,
|
|
68
69
|
): Promise<void> {
|
|
69
|
-
await this.runPostHooks('
|
|
70
|
+
await this.runPostHooks('onAfterRequest', itemId, item, userId, context);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Run pre-get hooks (when item exists and is about to be returned).
|
|
75
|
+
* Receives the item for access control based on item state.
|
|
76
|
+
*/
|
|
77
|
+
async runPreGet(
|
|
78
|
+
itemId: string,
|
|
79
|
+
item: DismissibleItemDto,
|
|
80
|
+
userId: string,
|
|
81
|
+
context?: IRequestContext,
|
|
82
|
+
): Promise<IHookRunResult> {
|
|
83
|
+
return this.runPreHooksWithItem('onBeforeGet', itemId, item, userId, context);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Run post-get hooks (after item is returned).
|
|
88
|
+
*/
|
|
89
|
+
async runPostGet(
|
|
90
|
+
itemId: string,
|
|
91
|
+
item: DismissibleItemDto,
|
|
92
|
+
userId: string,
|
|
93
|
+
context?: IRequestContext,
|
|
94
|
+
): Promise<void> {
|
|
95
|
+
await this.runPostHooks('onAfterGet', itemId, item, userId, context);
|
|
70
96
|
}
|
|
71
97
|
|
|
72
98
|
/**
|
|
@@ -85,7 +111,7 @@ export class HookRunner<TMetadata extends BaseMetadata = BaseMetadata> {
|
|
|
85
111
|
*/
|
|
86
112
|
async runPostCreate(
|
|
87
113
|
itemId: string,
|
|
88
|
-
item: DismissibleItemDto
|
|
114
|
+
item: DismissibleItemDto,
|
|
89
115
|
userId: string,
|
|
90
116
|
context?: IRequestContext,
|
|
91
117
|
): Promise<void> {
|
|
@@ -108,7 +134,7 @@ export class HookRunner<TMetadata extends BaseMetadata = BaseMetadata> {
|
|
|
108
134
|
*/
|
|
109
135
|
async runPostDismiss(
|
|
110
136
|
itemId: string,
|
|
111
|
-
item: DismissibleItemDto
|
|
137
|
+
item: DismissibleItemDto,
|
|
112
138
|
userId: string,
|
|
113
139
|
context?: IRequestContext,
|
|
114
140
|
): Promise<void> {
|
|
@@ -131,7 +157,7 @@ export class HookRunner<TMetadata extends BaseMetadata = BaseMetadata> {
|
|
|
131
157
|
*/
|
|
132
158
|
async runPostRestore(
|
|
133
159
|
itemId: string,
|
|
134
|
-
item: DismissibleItemDto
|
|
160
|
+
item: DismissibleItemDto,
|
|
135
161
|
userId: string,
|
|
136
162
|
context?: IRequestContext,
|
|
137
163
|
): Promise<void> {
|
|
@@ -142,7 +168,7 @@ export class HookRunner<TMetadata extends BaseMetadata = BaseMetadata> {
|
|
|
142
168
|
* Internal method to run pre-hooks.
|
|
143
169
|
*/
|
|
144
170
|
private async runPreHooks(
|
|
145
|
-
hookName: keyof IDismissibleLifecycleHook
|
|
171
|
+
hookName: keyof IDismissibleLifecycleHook,
|
|
146
172
|
itemId: string,
|
|
147
173
|
userId: string,
|
|
148
174
|
context?: IRequestContext,
|
|
@@ -180,7 +206,84 @@ export class HookRunner<TMetadata extends BaseMetadata = BaseMetadata> {
|
|
|
180
206
|
};
|
|
181
207
|
}
|
|
182
208
|
|
|
183
|
-
|
|
209
|
+
if (result.mutations) {
|
|
210
|
+
if (result.mutations.id !== undefined) {
|
|
211
|
+
currentId = result.mutations.id;
|
|
212
|
+
}
|
|
213
|
+
if (result.mutations.userId !== undefined) {
|
|
214
|
+
currentUserId = result.mutations.userId;
|
|
215
|
+
}
|
|
216
|
+
if (result.mutations.context && currentContext) {
|
|
217
|
+
currentContext = { ...currentContext, ...result.mutations.context };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
} catch (error) {
|
|
221
|
+
this.logger.error(
|
|
222
|
+
`Error in hook ${hook.constructor.name}.${hookName}`,
|
|
223
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
224
|
+
{
|
|
225
|
+
itemId: currentId,
|
|
226
|
+
userId: currentUserId,
|
|
227
|
+
},
|
|
228
|
+
);
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
proceed: true,
|
|
236
|
+
id: currentId,
|
|
237
|
+
userId: currentUserId,
|
|
238
|
+
context: currentContext,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Internal method to run pre-hooks that receive the item (e.g., onBeforeGet).
|
|
244
|
+
* Unlike standard pre-hooks, these receive the item for inspection/access control.
|
|
245
|
+
*/
|
|
246
|
+
private async runPreHooksWithItem(
|
|
247
|
+
hookName: keyof IDismissibleLifecycleHook,
|
|
248
|
+
itemId: string,
|
|
249
|
+
item: DismissibleItemDto,
|
|
250
|
+
userId: string,
|
|
251
|
+
context?: IRequestContext,
|
|
252
|
+
): Promise<IHookRunResult> {
|
|
253
|
+
let currentId = itemId;
|
|
254
|
+
let currentUserId = userId;
|
|
255
|
+
let currentContext = context ? { ...context } : undefined;
|
|
256
|
+
|
|
257
|
+
for (const hook of this.sortedHooks) {
|
|
258
|
+
const hookFn = hook[hookName] as
|
|
259
|
+
| ((
|
|
260
|
+
itemId: string,
|
|
261
|
+
item: DismissibleItemDto,
|
|
262
|
+
userId: string,
|
|
263
|
+
context?: IRequestContext,
|
|
264
|
+
) => Promise<IHookResult> | IHookResult)
|
|
265
|
+
| undefined;
|
|
266
|
+
|
|
267
|
+
if (hookFn) {
|
|
268
|
+
try {
|
|
269
|
+
const result = await hookFn.call(hook, currentId, item, currentUserId, currentContext);
|
|
270
|
+
|
|
271
|
+
if (!result.proceed) {
|
|
272
|
+
this.logger.debug(`Hook ${hook.constructor.name}.${hookName} blocked operation`, {
|
|
273
|
+
itemId: currentId,
|
|
274
|
+
userId: currentUserId,
|
|
275
|
+
reason: result.reason,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
proceed: false,
|
|
280
|
+
id: currentId,
|
|
281
|
+
userId: currentUserId,
|
|
282
|
+
context: currentContext,
|
|
283
|
+
reason: result.reason,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
184
287
|
if (result.mutations) {
|
|
185
288
|
if (result.mutations.id !== undefined) {
|
|
186
289
|
currentId = result.mutations.id;
|
|
@@ -219,20 +322,19 @@ export class HookRunner<TMetadata extends BaseMetadata = BaseMetadata> {
|
|
|
219
322
|
* Post-hooks run in reverse priority order.
|
|
220
323
|
*/
|
|
221
324
|
private async runPostHooks(
|
|
222
|
-
hookName: keyof IDismissibleLifecycleHook
|
|
325
|
+
hookName: keyof IDismissibleLifecycleHook,
|
|
223
326
|
itemId: string,
|
|
224
|
-
item: DismissibleItemDto
|
|
327
|
+
item: DismissibleItemDto,
|
|
225
328
|
userId: string,
|
|
226
329
|
context?: IRequestContext,
|
|
227
330
|
): Promise<void> {
|
|
228
|
-
// Run in reverse order for post-hooks
|
|
229
331
|
const reversedHooks = [...this.sortedHooks].reverse();
|
|
230
332
|
|
|
231
333
|
for (const hook of reversedHooks) {
|
|
232
334
|
const hookFn = hook[hookName] as
|
|
233
335
|
| ((
|
|
234
336
|
itemId: string,
|
|
235
|
-
item: DismissibleItemDto
|
|
337
|
+
item: DismissibleItemDto,
|
|
236
338
|
userId: string,
|
|
237
339
|
context?: IRequestContext,
|
|
238
340
|
) => Promise<void> | void)
|
|
@@ -250,7 +352,6 @@ export class HookRunner<TMetadata extends BaseMetadata = BaseMetadata> {
|
|
|
250
352
|
userId,
|
|
251
353
|
},
|
|
252
354
|
);
|
|
253
|
-
// Post-hooks errors are logged but don't block the operation
|
|
254
355
|
}
|
|
255
356
|
}
|
|
256
357
|
}
|
package/src/core/index.ts
CHANGED
|
@@ -1,122 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Mutations that can be applied by pre-hooks.
|
|
11
|
-
*/
|
|
12
|
-
export interface IHookMutations {
|
|
13
|
-
/** Mutated item ID */
|
|
14
|
-
id?: string;
|
|
15
|
-
|
|
16
|
-
/** Mutated user ID */
|
|
17
|
-
userId?: string;
|
|
18
|
-
|
|
19
|
-
/** Mutated request context */
|
|
20
|
-
context?: Partial<IRequestContext>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Result returned by pre-hooks.
|
|
25
|
-
*/
|
|
26
|
-
export interface IHookResult {
|
|
27
|
-
/** Whether the operation should proceed */
|
|
28
|
-
proceed: boolean;
|
|
29
|
-
|
|
30
|
-
/** Optional reason if the operation is blocked */
|
|
31
|
-
reason?: string;
|
|
32
|
-
|
|
33
|
-
/** Optional mutations to apply */
|
|
34
|
-
mutations?: IHookMutations;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Interface for lifecycle hooks that can intercept dismissible operations.
|
|
39
|
-
*/
|
|
40
|
-
export interface IDismissibleLifecycleHook<TMetadata extends BaseMetadata = BaseMetadata> {
|
|
41
|
-
/**
|
|
42
|
-
* Priority for hook execution (lower numbers run first).
|
|
43
|
-
* Default is 0.
|
|
44
|
-
*/
|
|
45
|
-
readonly priority?: number;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Called before getOrCreate operation.
|
|
49
|
-
*/
|
|
50
|
-
onBeforeGetOrCreate?(
|
|
51
|
-
itemId: string,
|
|
52
|
-
userId: string,
|
|
53
|
-
context?: IRequestContext,
|
|
54
|
-
): Promise<IHookResult> | IHookResult;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Called after getOrCreate operation.
|
|
58
|
-
*/
|
|
59
|
-
onAfterGetOrCreate?(
|
|
60
|
-
itemId: string,
|
|
61
|
-
item: DismissibleItemDto<TMetadata>,
|
|
62
|
-
userId: string,
|
|
63
|
-
context?: IRequestContext,
|
|
64
|
-
): Promise<void> | void;
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Called before creating a new item.
|
|
68
|
-
*/
|
|
69
|
-
onBeforeCreate?(
|
|
70
|
-
itemId: string,
|
|
71
|
-
userId: string,
|
|
72
|
-
context?: IRequestContext,
|
|
73
|
-
): Promise<IHookResult> | IHookResult;
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Called after creating a new item.
|
|
77
|
-
*/
|
|
78
|
-
onAfterCreate?(
|
|
79
|
-
itemId: string,
|
|
80
|
-
item: DismissibleItemDto<TMetadata>,
|
|
81
|
-
userId: string,
|
|
82
|
-
context?: IRequestContext,
|
|
83
|
-
): Promise<void> | void;
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Called before dismissing an item.
|
|
87
|
-
*/
|
|
88
|
-
onBeforeDismiss?(
|
|
89
|
-
itemId: string,
|
|
90
|
-
userId: string,
|
|
91
|
-
context?: IRequestContext,
|
|
92
|
-
): Promise<IHookResult> | IHookResult;
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Called after dismissing an item.
|
|
96
|
-
*/
|
|
97
|
-
onAfterDismiss?(
|
|
98
|
-
itemId: string,
|
|
99
|
-
item: DismissibleItemDto<TMetadata>,
|
|
100
|
-
userId: string,
|
|
101
|
-
context?: IRequestContext,
|
|
102
|
-
): Promise<void> | void;
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Called before restoring an item.
|
|
106
|
-
*/
|
|
107
|
-
onBeforeRestore?(
|
|
108
|
-
itemId: string,
|
|
109
|
-
userId: string,
|
|
110
|
-
context?: IRequestContext,
|
|
111
|
-
): Promise<IHookResult> | IHookResult;
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Called after restoring an item.
|
|
115
|
-
*/
|
|
116
|
-
onAfterRestore?(
|
|
117
|
-
itemId: string,
|
|
118
|
-
item: DismissibleItemDto<TMetadata>,
|
|
119
|
-
userId: string,
|
|
120
|
-
context?: IRequestContext,
|
|
121
|
-
): Promise<void> | void;
|
|
122
|
-
}
|
|
1
|
+
// Re-export from hooks library for backward compatibility
|
|
2
|
+
export {
|
|
3
|
+
DISMISSIBLE_HOOKS,
|
|
4
|
+
IHookMutations,
|
|
5
|
+
IHookResult,
|
|
6
|
+
IDismissibleLifecycleHook,
|
|
7
|
+
} from '@dismissible/nestjs-dismissible-hooks';
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DismissibleItemDto } from '@dismissible/nestjs-dismissible-item';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Response from getOrCreate operation.
|
|
5
5
|
*/
|
|
6
|
-
export interface IGetOrCreateServiceResponse
|
|
6
|
+
export interface IGetOrCreateServiceResponse {
|
|
7
7
|
/** The item (either retrieved or created) */
|
|
8
|
-
item: DismissibleItemDto
|
|
8
|
+
item: DismissibleItemDto;
|
|
9
9
|
|
|
10
10
|
/** Whether the item was newly created */
|
|
11
11
|
created: boolean;
|
|
@@ -14,21 +14,21 @@ export interface IGetOrCreateServiceResponse<TMetadata extends BaseMetadata = Ba
|
|
|
14
14
|
/**
|
|
15
15
|
* Response from dismiss operation.
|
|
16
16
|
*/
|
|
17
|
-
export interface IDismissServiceResponse
|
|
17
|
+
export interface IDismissServiceResponse {
|
|
18
18
|
/** The dismissed item */
|
|
19
|
-
item: DismissibleItemDto
|
|
19
|
+
item: DismissibleItemDto;
|
|
20
20
|
|
|
21
21
|
/** The item state before dismissal */
|
|
22
|
-
previousItem: DismissibleItemDto
|
|
22
|
+
previousItem: DismissibleItemDto;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Response from restore operation.
|
|
27
27
|
*/
|
|
28
|
-
export interface IRestoreServiceResponse
|
|
28
|
+
export interface IRestoreServiceResponse {
|
|
29
29
|
/** The restored item */
|
|
30
|
-
item: DismissibleItemDto
|
|
30
|
+
item: DismissibleItemDto;
|
|
31
31
|
|
|
32
32
|
/** The item state before restoration */
|
|
33
|
-
previousItem: DismissibleItemDto
|
|
33
|
+
previousItem: DismissibleItemDto;
|
|
34
34
|
}
|