@multitenantkit/domain 0.1.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/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/organization-memberships/index.d.ts +3 -0
- package/dist/organization-memberships/index.d.ts.map +1 -0
- package/dist/organization-memberships/index.js +3 -0
- package/dist/organization-memberships/index.js.map +1 -0
- package/dist/organization-memberships/use-cases/accept-organization-invitation/AcceptOrganizationInvitation.d.ts +26 -0
- package/dist/organization-memberships/use-cases/accept-organization-invitation/AcceptOrganizationInvitation.d.ts.map +1 -0
- package/dist/organization-memberships/use-cases/accept-organization-invitation/AcceptOrganizationInvitation.js +85 -0
- package/dist/organization-memberships/use-cases/accept-organization-invitation/AcceptOrganizationInvitation.js.map +1 -0
- package/dist/organization-memberships/use-cases/add-organization-member/AddOrganizationMember.d.ts +19 -0
- package/dist/organization-memberships/use-cases/add-organization-member/AddOrganizationMember.d.ts.map +1 -0
- package/dist/organization-memberships/use-cases/add-organization-member/AddOrganizationMember.js +126 -0
- package/dist/organization-memberships/use-cases/add-organization-member/AddOrganizationMember.js.map +1 -0
- package/dist/organization-memberships/use-cases/index.d.ts +6 -0
- package/dist/organization-memberships/use-cases/index.d.ts.map +1 -0
- package/dist/organization-memberships/use-cases/index.js +8 -0
- package/dist/organization-memberships/use-cases/index.js.map +1 -0
- package/dist/organization-memberships/use-cases/leave-organization/LeaveOrganization.d.ts +15 -0
- package/dist/organization-memberships/use-cases/leave-organization/LeaveOrganization.d.ts.map +1 -0
- package/dist/organization-memberships/use-cases/leave-organization/LeaveOrganization.js +64 -0
- package/dist/organization-memberships/use-cases/leave-organization/LeaveOrganization.js.map +1 -0
- package/dist/organization-memberships/use-cases/remove-organization-member/RemoveOrganizationMember.d.ts +16 -0
- package/dist/organization-memberships/use-cases/remove-organization-member/RemoveOrganizationMember.d.ts.map +1 -0
- package/dist/organization-memberships/use-cases/remove-organization-member/RemoveOrganizationMember.js +87 -0
- package/dist/organization-memberships/use-cases/remove-organization-member/RemoveOrganizationMember.js.map +1 -0
- package/dist/organization-memberships/use-cases/update-organization-member-role/UpdateOrganizationMemberRole.d.ts +16 -0
- package/dist/organization-memberships/use-cases/update-organization-member-role/UpdateOrganizationMemberRole.d.ts.map +1 -0
- package/dist/organization-memberships/use-cases/update-organization-member-role/UpdateOrganizationMemberRole.js +85 -0
- package/dist/organization-memberships/use-cases/update-organization-member-role/UpdateOrganizationMemberRole.js.map +1 -0
- package/dist/organizations/index.d.ts +2 -0
- package/dist/organizations/index.d.ts.map +1 -0
- package/dist/organizations/index.js +3 -0
- package/dist/organizations/index.js.map +1 -0
- package/dist/organizations/use-cases/archive-organization/ArchiveOrganization.d.ts +38 -0
- package/dist/organizations/use-cases/archive-organization/ArchiveOrganization.d.ts.map +1 -0
- package/dist/organizations/use-cases/archive-organization/ArchiveOrganization.js +117 -0
- package/dist/organizations/use-cases/archive-organization/ArchiveOrganization.js.map +1 -0
- package/dist/organizations/use-cases/create-organization/CreateOrganization.d.ts +19 -0
- package/dist/organizations/use-cases/create-organization/CreateOrganization.d.ts.map +1 -0
- package/dist/organizations/use-cases/create-organization/CreateOrganization.js +96 -0
- package/dist/organizations/use-cases/create-organization/CreateOrganization.js.map +1 -0
- package/dist/organizations/use-cases/delete-organization/DeleteOrganization.d.ts +34 -0
- package/dist/organizations/use-cases/delete-organization/DeleteOrganization.d.ts.map +1 -0
- package/dist/organizations/use-cases/delete-organization/DeleteOrganization.js +103 -0
- package/dist/organizations/use-cases/delete-organization/DeleteOrganization.js.map +1 -0
- package/dist/organizations/use-cases/get-organization/GetOrganization.d.ts +18 -0
- package/dist/organizations/use-cases/get-organization/GetOrganization.d.ts.map +1 -0
- package/dist/organizations/use-cases/get-organization/GetOrganization.js +51 -0
- package/dist/organizations/use-cases/get-organization/GetOrganization.js.map +1 -0
- package/dist/organizations/use-cases/index.d.ts +9 -0
- package/dist/organizations/use-cases/index.d.ts.map +1 -0
- package/dist/organizations/use-cases/index.js +11 -0
- package/dist/organizations/use-cases/index.js.map +1 -0
- package/dist/organizations/use-cases/list-organization-members/ListOrganizationMembers.d.ts +46 -0
- package/dist/organizations/use-cases/list-organization-members/ListOrganizationMembers.d.ts.map +1 -0
- package/dist/organizations/use-cases/list-organization-members/ListOrganizationMembers.js +130 -0
- package/dist/organizations/use-cases/list-organization-members/ListOrganizationMembers.js.map +1 -0
- package/dist/organizations/use-cases/restore-organization/RestoreOrganization.d.ts +30 -0
- package/dist/organizations/use-cases/restore-organization/RestoreOrganization.d.ts.map +1 -0
- package/dist/organizations/use-cases/restore-organization/RestoreOrganization.js +98 -0
- package/dist/organizations/use-cases/restore-organization/RestoreOrganization.js.map +1 -0
- package/dist/organizations/use-cases/transfer-organization-ownership/TransferOrganizationOwnership.d.ts +30 -0
- package/dist/organizations/use-cases/transfer-organization-ownership/TransferOrganizationOwnership.d.ts.map +1 -0
- package/dist/organizations/use-cases/transfer-organization-ownership/TransferOrganizationOwnership.js +139 -0
- package/dist/organizations/use-cases/transfer-organization-ownership/TransferOrganizationOwnership.js.map +1 -0
- package/dist/organizations/use-cases/update-organization/UpdateOrganization.d.ts +22 -0
- package/dist/organizations/use-cases/update-organization/UpdateOrganization.d.ts.map +1 -0
- package/dist/organizations/use-cases/update-organization/UpdateOrganization.js +100 -0
- package/dist/organizations/use-cases/update-organization/UpdateOrganization.js.map +1 -0
- package/dist/shared/index.d.ts +3 -0
- package/dist/shared/index.d.ts.map +1 -0
- package/dist/shared/index.js +4 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/shared/result/Result.d.ts +30 -0
- package/dist/shared/result/Result.d.ts.map +1 -0
- package/dist/shared/result/Result.js +69 -0
- package/dist/shared/result/Result.js.map +1 -0
- package/dist/shared/result/index.d.ts +4 -0
- package/dist/shared/result/index.d.ts.map +1 -0
- package/dist/shared/result/index.js +2 -0
- package/dist/shared/result/index.js.map +1 -0
- package/dist/shared/use-case/BaseUseCase.d.ts +164 -0
- package/dist/shared/use-case/BaseUseCase.d.ts.map +1 -0
- package/dist/shared/use-case/BaseUseCase.js +366 -0
- package/dist/shared/use-case/BaseUseCase.js.map +1 -0
- package/dist/shared/use-case/UseCaseHelpers.d.ts +43 -0
- package/dist/shared/use-case/UseCaseHelpers.d.ts.map +1 -0
- package/dist/shared/use-case/UseCaseHelpers.js +56 -0
- package/dist/shared/use-case/UseCaseHelpers.js.map +1 -0
- package/dist/shared/use-case/index.d.ts +3 -0
- package/dist/shared/use-case/index.d.ts.map +1 -0
- package/dist/shared/use-case/index.js +4 -0
- package/dist/shared/use-case/index.js.map +1 -0
- package/dist/users/index.d.ts +2 -0
- package/dist/users/index.d.ts.map +1 -0
- package/dist/users/index.js +3 -0
- package/dist/users/index.js.map +1 -0
- package/dist/users/use-cases/create-user/CreateUser.d.ts +21 -0
- package/dist/users/use-cases/create-user/CreateUser.d.ts.map +1 -0
- package/dist/users/use-cases/create-user/CreateUser.js +81 -0
- package/dist/users/use-cases/create-user/CreateUser.js.map +1 -0
- package/dist/users/use-cases/delete-user/DeleteUser.d.ts +35 -0
- package/dist/users/use-cases/delete-user/DeleteUser.d.ts.map +1 -0
- package/dist/users/use-cases/delete-user/DeleteUser.js +120 -0
- package/dist/users/use-cases/delete-user/DeleteUser.js.map +1 -0
- package/dist/users/use-cases/get-user/GetUser.d.ts +18 -0
- package/dist/users/use-cases/get-user/GetUser.d.ts.map +1 -0
- package/dist/users/use-cases/get-user/GetUser.js +29 -0
- package/dist/users/use-cases/get-user/GetUser.js.map +1 -0
- package/dist/users/use-cases/index.d.ts +6 -0
- package/dist/users/use-cases/index.d.ts.map +1 -0
- package/dist/users/use-cases/index.js +8 -0
- package/dist/users/use-cases/index.js.map +1 -0
- package/dist/users/use-cases/list-user-organizations/ListUserOrganizations.d.ts +20 -0
- package/dist/users/use-cases/list-user-organizations/ListUserOrganizations.d.ts.map +1 -0
- package/dist/users/use-cases/list-user-organizations/ListUserOrganizations.js +68 -0
- package/dist/users/use-cases/list-user-organizations/ListUserOrganizations.js.map +1 -0
- package/dist/users/use-cases/update-user/UpdateUser.d.ts +19 -0
- package/dist/users/use-cases/update-user/UpdateUser.d.ts.map +1 -0
- package/dist/users/use-cases/update-user/UpdateUser.js +73 -0
- package/dist/users/use-cases/update-user/UpdateUser.js.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { AbortedError, InfrastructureError, NotFoundError, ValidationError } from '@multitenantkit/domain-contracts';
|
|
3
|
+
import { Result } from '../result/Result';
|
|
4
|
+
/**
|
|
5
|
+
* Base class for all use cases following a consistent execution pipeline.
|
|
6
|
+
*
|
|
7
|
+
* Provides automatic:
|
|
8
|
+
* - Input validation with Zod schemas
|
|
9
|
+
* - Output parsing/transformation
|
|
10
|
+
* - Error handling and wrapping
|
|
11
|
+
* - Hook lifecycle management with abort support
|
|
12
|
+
* - Consistent structure across all use cases
|
|
13
|
+
*
|
|
14
|
+
* @template TInput - Input type for the use case
|
|
15
|
+
* @template TOutput - Output type returned by the use case
|
|
16
|
+
* @template TError - Domain error types that can be returned
|
|
17
|
+
* @template TCustomFields - Custom fields extension for entities
|
|
18
|
+
* @template TOrganizationCustomFields - Custom fields for organizations (toolkit options compatibility)
|
|
19
|
+
* @template TOrganizationMembershipCustomFields - Custom fields for organization memberships (toolkit options compatibility)
|
|
20
|
+
*/
|
|
21
|
+
export class BaseUseCase {
|
|
22
|
+
adapters;
|
|
23
|
+
toolkitOptions;
|
|
24
|
+
inputSchema;
|
|
25
|
+
outputSchema;
|
|
26
|
+
errorMessage;
|
|
27
|
+
/**
|
|
28
|
+
* Abort state tracking
|
|
29
|
+
* These track whether a hook has requested abort and the reason
|
|
30
|
+
*/
|
|
31
|
+
abortRequested = false;
|
|
32
|
+
abortReason = '';
|
|
33
|
+
/**
|
|
34
|
+
* Shared state object for hooks
|
|
35
|
+
* Reset at the start of each execution
|
|
36
|
+
*/
|
|
37
|
+
sharedState = {};
|
|
38
|
+
/**
|
|
39
|
+
* Step results object for hooks
|
|
40
|
+
* Stores results from each pipeline step
|
|
41
|
+
*/
|
|
42
|
+
stepResults = {};
|
|
43
|
+
/**
|
|
44
|
+
* Execution ID for tracking
|
|
45
|
+
* Generated at the start of each execution
|
|
46
|
+
*/
|
|
47
|
+
executionId = '';
|
|
48
|
+
/**
|
|
49
|
+
* Use case name in format "entity-useCaseName"
|
|
50
|
+
* Example: "user-updateUser", "organization-createOrganization"
|
|
51
|
+
* This helps prevent name collisions between different entities
|
|
52
|
+
*/
|
|
53
|
+
useCaseName;
|
|
54
|
+
constructor(useCaseName, adapters, toolkitOptions, inputSchema, outputSchema, errorMessage) {
|
|
55
|
+
this.adapters = adapters;
|
|
56
|
+
this.toolkitOptions = toolkitOptions;
|
|
57
|
+
this.inputSchema = inputSchema;
|
|
58
|
+
this.outputSchema = outputSchema;
|
|
59
|
+
this.errorMessage = errorMessage;
|
|
60
|
+
this.useCaseName = useCaseName;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Initialize execution state for a new execution
|
|
64
|
+
* Resets abort state, shared state, step results, and generates execution ID
|
|
65
|
+
*/
|
|
66
|
+
initializeExecutionState() {
|
|
67
|
+
this.abortRequested = false;
|
|
68
|
+
this.abortReason = '';
|
|
69
|
+
this.sharedState = {};
|
|
70
|
+
this.stepResults = {};
|
|
71
|
+
this.executionId = randomUUID();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Create abort function for hook context
|
|
75
|
+
* When called, sets abort state which will be checked after each hook
|
|
76
|
+
*
|
|
77
|
+
* @returns Abort function that hooks can call
|
|
78
|
+
*/
|
|
79
|
+
createAbortFunction() {
|
|
80
|
+
return (reason) => {
|
|
81
|
+
this.abortRequested = true;
|
|
82
|
+
this.abortReason = reason;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Check if abort was requested
|
|
87
|
+
* Should be called after each hook execution
|
|
88
|
+
*
|
|
89
|
+
* @returns true if abort was requested
|
|
90
|
+
*/
|
|
91
|
+
checkAborted() {
|
|
92
|
+
return this.abortRequested;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Build HookContext object for passing to hooks
|
|
96
|
+
* Provides immutable access to input/stepResults and mutable shared state
|
|
97
|
+
*
|
|
98
|
+
* @param input - Original input to the use case
|
|
99
|
+
* @param context - Operation context
|
|
100
|
+
* @returns HookContext object
|
|
101
|
+
*/
|
|
102
|
+
buildHookContext(input, context) {
|
|
103
|
+
return {
|
|
104
|
+
executionId: this.executionId,
|
|
105
|
+
useCaseName: this.useCaseName,
|
|
106
|
+
input: input, // Readonly via type
|
|
107
|
+
stepResults: this.stepResults, // Readonly via type
|
|
108
|
+
shared: this.sharedState,
|
|
109
|
+
adapters: this.adapters,
|
|
110
|
+
context: context,
|
|
111
|
+
abort: this.createAbortFunction()
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Get configured hooks for this use case from ToolkitOptions
|
|
116
|
+
*
|
|
117
|
+
* @returns UseCaseHooks if configured, undefined otherwise
|
|
118
|
+
*/
|
|
119
|
+
getHooksConfig() {
|
|
120
|
+
if (!this.toolkitOptions?.useCaseHooks) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
const useCaseName = this.constructor.name;
|
|
124
|
+
return this.toolkitOptions.useCaseHooks[useCaseName];
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Execute a hook safely with error handling and metrics logging
|
|
128
|
+
* If hook throws an error, it propagates (hooks can abort execution)
|
|
129
|
+
*
|
|
130
|
+
* @param hookFn - Hook function to execute
|
|
131
|
+
* @param ctx - HookContext to pass to the hook
|
|
132
|
+
* @param hookName - Name of the hook for logging purposes
|
|
133
|
+
*/
|
|
134
|
+
async executeHook(hookFn, ctx, hookName) {
|
|
135
|
+
// 🔥 Fire-and-forget: Log hook execution (without await)
|
|
136
|
+
this.logMetrics(hookName, ctx);
|
|
137
|
+
if (!hookFn) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
await hookFn(ctx);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Log hook execution metrics (fire-and-forget)
|
|
144
|
+
* Errors are silently caught to not affect execution flow
|
|
145
|
+
*
|
|
146
|
+
* @param hookName - Name of the hook being executed
|
|
147
|
+
* @param ctx - Hook context (will be filtered for PII)
|
|
148
|
+
*/
|
|
149
|
+
logMetrics(hookName, ctx) {
|
|
150
|
+
try {
|
|
151
|
+
// Fire-and-forget (no await)
|
|
152
|
+
this.adapters.observability
|
|
153
|
+
?.logHookExecution({
|
|
154
|
+
requestId: ctx.context.requestId || 'unknown',
|
|
155
|
+
useCaseName: this.useCaseName,
|
|
156
|
+
hookName,
|
|
157
|
+
executionId: ctx.executionId,
|
|
158
|
+
timestamp: new Date(),
|
|
159
|
+
params: ctx
|
|
160
|
+
})
|
|
161
|
+
.catch(() => {
|
|
162
|
+
// Silent fail - metrics should never break execution
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// Silent fail - if extracting data fails, don't break execution
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Get user from external ID (auth provider ID)
|
|
171
|
+
*
|
|
172
|
+
* This helper maps the external auth provider ID to the internal User entity.
|
|
173
|
+
* It's used by use cases that operate on the authenticated user.
|
|
174
|
+
*
|
|
175
|
+
* @param externalId - Auth provider user ID (from Principal.authProviderId)
|
|
176
|
+
* @returns User entity with internal ID
|
|
177
|
+
* @throws NotFoundError if user doesn't exist
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```typescript
|
|
181
|
+
* // In a use case
|
|
182
|
+
* const user = await this.getUserFromExternalId(context.principal.authProviderId);
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
async getUserFromExternalId(externalId) {
|
|
186
|
+
if (!this.adapters.persistence.userRepository) {
|
|
187
|
+
return Result.fail(new InfrastructureError('UserRepository is required for getUserFromExternalId'));
|
|
188
|
+
}
|
|
189
|
+
const user = await this.adapters.persistence.userRepository.findByExternalId(externalId);
|
|
190
|
+
if (!user) {
|
|
191
|
+
return Result.fail(new NotFoundError('User', externalId, {
|
|
192
|
+
message: 'User not found with the provided external ID from auth provider. Please ensure the user is registered in the system.'
|
|
193
|
+
}));
|
|
194
|
+
}
|
|
195
|
+
return Result.ok(user);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Main execution method - defines the pipeline with hook integration
|
|
199
|
+
*
|
|
200
|
+
* Pipeline stages:
|
|
201
|
+
* 1. Initialize execution state
|
|
202
|
+
* 2. onStart hook
|
|
203
|
+
* 3. Validate input with schema
|
|
204
|
+
* 4. afterValidation hook
|
|
205
|
+
* 5. Authorize (optional)
|
|
206
|
+
* 6. beforeExecution hook (renamed from afterAuthorization)
|
|
207
|
+
* 7. Execute business logic
|
|
208
|
+
* 8. afterExecution hook
|
|
209
|
+
* 9. Parse output with schema
|
|
210
|
+
* 10. onError hook (if error occurs)
|
|
211
|
+
* 11. onAbort hook (if abort requested)
|
|
212
|
+
* 12. onFinally hook (always executes)
|
|
213
|
+
*/
|
|
214
|
+
async execute(input, context) {
|
|
215
|
+
// Initialize execution state
|
|
216
|
+
this.initializeExecutionState();
|
|
217
|
+
const hooks = this.getHooksConfig();
|
|
218
|
+
let result = await this.withErrorHandling(async () => {
|
|
219
|
+
// Build hook context (will be updated as execution progresses)
|
|
220
|
+
const hookCtx = this.buildHookContext(input, context);
|
|
221
|
+
// HOOK 1: onStart - before validation
|
|
222
|
+
await this.executeHook(hooks?.onStart, hookCtx, 'onStart');
|
|
223
|
+
if (this.checkAborted()) {
|
|
224
|
+
return Result.fail(new AbortedError(this.abortReason));
|
|
225
|
+
}
|
|
226
|
+
// 1. Validate input
|
|
227
|
+
const validatedInput = await this.validateInput(input);
|
|
228
|
+
if (validatedInput.isFailure) {
|
|
229
|
+
return validatedInput;
|
|
230
|
+
}
|
|
231
|
+
// Store validated input in step results
|
|
232
|
+
this.stepResults.validatedInput = validatedInput.getValue();
|
|
233
|
+
// HOOK 2: afterValidation - after successful validation
|
|
234
|
+
await this.executeHook(hooks?.afterValidation, hookCtx, 'afterValidation');
|
|
235
|
+
if (this.checkAborted()) {
|
|
236
|
+
return Result.fail(new AbortedError(this.abortReason));
|
|
237
|
+
}
|
|
238
|
+
// 2. Authorize (optional)
|
|
239
|
+
const authResult = await this.authorize(validatedInput.getValue(), context);
|
|
240
|
+
if (authResult.isFailure) {
|
|
241
|
+
return authResult;
|
|
242
|
+
}
|
|
243
|
+
// Store authorization result in step results
|
|
244
|
+
this.stepResults.authorized = true;
|
|
245
|
+
// HOOK 3: beforeExecution - after successful authorization, before business logic
|
|
246
|
+
await this.executeHook(hooks?.beforeExecution, hookCtx, 'beforeExecution');
|
|
247
|
+
if (this.checkAborted()) {
|
|
248
|
+
return Result.fail(new AbortedError(this.abortReason));
|
|
249
|
+
}
|
|
250
|
+
// 3. Execute business logic
|
|
251
|
+
const businessResult = await this.executeBusinessLogic(validatedInput.getValue(), context);
|
|
252
|
+
if (businessResult.isFailure) {
|
|
253
|
+
return businessResult;
|
|
254
|
+
}
|
|
255
|
+
// Store business output in step results
|
|
256
|
+
this.stepResults.output = businessResult.getValue();
|
|
257
|
+
// HOOK 4: afterExecution - after successful business logic
|
|
258
|
+
await this.executeHook(hooks?.afterExecution, hookCtx, 'afterExecution');
|
|
259
|
+
if (this.checkAborted()) {
|
|
260
|
+
return Result.fail(new AbortedError(this.abortReason));
|
|
261
|
+
}
|
|
262
|
+
// 4. Parse and return output
|
|
263
|
+
return this.parseOutput(businessResult.getValue());
|
|
264
|
+
});
|
|
265
|
+
// If result is AbortedError, execute onAbort hook
|
|
266
|
+
if (result.isFailure && result.getError() instanceof AbortedError) {
|
|
267
|
+
try {
|
|
268
|
+
const hookCtx = this.buildHookContext(input, context);
|
|
269
|
+
const onAbortHook = hooks?.onAbort;
|
|
270
|
+
if (onAbortHook) {
|
|
271
|
+
this.logMetrics('onAbort', hookCtx);
|
|
272
|
+
await onAbortHook({ ...hookCtx, reason: this.abortReason });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
catch (abortError) {
|
|
276
|
+
// onAbort errors are logged but don't affect the abort result
|
|
277
|
+
console.error(`Error in onAbort hook for ${this.useCaseName}:`, abortError);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// If result is failure (but not abort), execute onError hook
|
|
281
|
+
else if (result.isFailure) {
|
|
282
|
+
try {
|
|
283
|
+
const hookCtx = this.buildHookContext(input, context);
|
|
284
|
+
const onErrorHook = hooks?.onError;
|
|
285
|
+
if (onErrorHook) {
|
|
286
|
+
// HOOK 5: onError - after failure
|
|
287
|
+
this.logMetrics('onError', hookCtx);
|
|
288
|
+
await onErrorHook({ ...hookCtx, error: result.getError() });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
// If onError hook throws, create new error result
|
|
293
|
+
result = Result.fail(new ValidationError(this.errorMessage, undefined, {
|
|
294
|
+
originalError: result.getError(),
|
|
295
|
+
error
|
|
296
|
+
}));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// HOOK 6: onFinally - always executes, errors are caught and logged
|
|
300
|
+
try {
|
|
301
|
+
const hookCtx = this.buildHookContext(input, context);
|
|
302
|
+
const onFinallyHook = hooks?.onFinally;
|
|
303
|
+
if (onFinallyHook) {
|
|
304
|
+
this.logMetrics('onFinally', hookCtx);
|
|
305
|
+
await onFinallyHook({ ...hookCtx, result });
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
catch (finallyError) {
|
|
309
|
+
// onFinally errors don't affect the result, just log them
|
|
310
|
+
console.error(`Error in onFinally hook for ${this.useCaseName}:`, finallyError);
|
|
311
|
+
}
|
|
312
|
+
return result;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Validates input using the provided Zod schema
|
|
316
|
+
*/
|
|
317
|
+
async validateInput(input) {
|
|
318
|
+
const validationResult = this.inputSchema.safeParse(input);
|
|
319
|
+
if (!validationResult.success) {
|
|
320
|
+
const firstError = validationResult.error.errors[0];
|
|
321
|
+
return Result.fail(new ValidationError(firstError.message, firstError.path.join('.')));
|
|
322
|
+
}
|
|
323
|
+
return Result.ok(validationResult.data);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Authorization hook - override for permission checks
|
|
327
|
+
* Default: no authorization
|
|
328
|
+
*/
|
|
329
|
+
async authorize(_input, _context) {
|
|
330
|
+
return Result.ok(undefined);
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Parses output using the provided Zod schema
|
|
334
|
+
*/
|
|
335
|
+
parseOutput(data) {
|
|
336
|
+
try {
|
|
337
|
+
// Handle void/undefined case - no need to spread
|
|
338
|
+
const valueToParse = data === undefined
|
|
339
|
+
? undefined
|
|
340
|
+
: Array.isArray(data)
|
|
341
|
+
? data
|
|
342
|
+
: { ...data };
|
|
343
|
+
const parsed = this.outputSchema.parse(valueToParse);
|
|
344
|
+
return Result.ok(parsed);
|
|
345
|
+
}
|
|
346
|
+
catch (error) {
|
|
347
|
+
return Result.fail(new ValidationError('Failed to parse output', undefined, {
|
|
348
|
+
originalError: error
|
|
349
|
+
}));
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Wraps execution in try-catch for consistent error handling
|
|
354
|
+
*/
|
|
355
|
+
async withErrorHandling(operation) {
|
|
356
|
+
try {
|
|
357
|
+
return await operation();
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
return Result.fail(new ValidationError(this.errorMessage, undefined, {
|
|
361
|
+
originalError: error
|
|
362
|
+
}));
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
//# sourceMappingURL=BaseUseCase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BaseUseCase.js","sourceRoot":"","sources":["../../../src/shared/use-case/BaseUseCase.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { OperationContext } from '@multitenantkit/domain-contracts';
|
|
2
|
+
import { NotFoundError } from '@multitenantkit/domain-contracts';
|
|
3
|
+
import { Result } from '../result/Result';
|
|
4
|
+
/**
|
|
5
|
+
* Helper functions for common use case patterns
|
|
6
|
+
*
|
|
7
|
+
* Philosophy: Keep it minimal. Only extract truly repetitive patterns.
|
|
8
|
+
* If a helper reduces readability, don't use it.
|
|
9
|
+
*/
|
|
10
|
+
export declare class UseCaseHelpers {
|
|
11
|
+
/**
|
|
12
|
+
* Find entity by ID or return NotFoundError
|
|
13
|
+
*
|
|
14
|
+
* Common pattern across many use cases that retrieve entities by ID
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const userResult = await UseCaseHelpers.findByIdOrFail(
|
|
18
|
+
* this.adapters.persistence.userRepository,
|
|
19
|
+
* input.userId,
|
|
20
|
+
* "User"
|
|
21
|
+
* );
|
|
22
|
+
* if (userResult.isFailure) return userResult;
|
|
23
|
+
* const user = userResult.getValue();
|
|
24
|
+
*/
|
|
25
|
+
static findByIdOrFail<T>(repository: {
|
|
26
|
+
findById: (id: string) => Promise<T | null>;
|
|
27
|
+
}, id: string, entityName: string): Promise<Result<T, NotFoundError>>;
|
|
28
|
+
/**
|
|
29
|
+
* Enrich audit context with specific action and metadata
|
|
30
|
+
*
|
|
31
|
+
* Ensures consistent audit context across all use cases
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* const auditContext = UseCaseHelpers.enrichAuditContext(
|
|
35
|
+
* context,
|
|
36
|
+
* "CREATE_USER",
|
|
37
|
+
* undefined,
|
|
38
|
+
* { source: "admin-panel" }
|
|
39
|
+
* );
|
|
40
|
+
*/
|
|
41
|
+
static enrichAuditContext(context: OperationContext, action: string, organizationId?: string, metadata?: Record<string, any>): OperationContext;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=UseCaseHelpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UseCaseHelpers.d.ts","sourceRoot":"","sources":["../../../src/shared/use-case/UseCaseHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C;;;;;GAKG;AAGH,qBAAa,cAAc;IACvB;;;;;;;;;;;;;OAaG;WACU,cAAc,CAAC,CAAC,EACzB,UAAU,EAAE;QAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;KAAE,EAC3D,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC;IAYpC;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,kBAAkB,CACrB,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,MAAM,EACd,cAAc,CAAC,EAAE,MAAM,EACvB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC/B,gBAAgB;CAQtB"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { NotFoundError } from '@multitenantkit/domain-contracts';
|
|
2
|
+
import { Result } from '../result/Result';
|
|
3
|
+
/**
|
|
4
|
+
* Helper functions for common use case patterns
|
|
5
|
+
*
|
|
6
|
+
* Philosophy: Keep it minimal. Only extract truly repetitive patterns.
|
|
7
|
+
* If a helper reduces readability, don't use it.
|
|
8
|
+
*/
|
|
9
|
+
// biome-ignore lint/complexity/noStaticOnlyClass: ignore
|
|
10
|
+
export class UseCaseHelpers {
|
|
11
|
+
/**
|
|
12
|
+
* Find entity by ID or return NotFoundError
|
|
13
|
+
*
|
|
14
|
+
* Common pattern across many use cases that retrieve entities by ID
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const userResult = await UseCaseHelpers.findByIdOrFail(
|
|
18
|
+
* this.adapters.persistence.userRepository,
|
|
19
|
+
* input.userId,
|
|
20
|
+
* "User"
|
|
21
|
+
* );
|
|
22
|
+
* if (userResult.isFailure) return userResult;
|
|
23
|
+
* const user = userResult.getValue();
|
|
24
|
+
*/
|
|
25
|
+
static async findByIdOrFail(repository, id, entityName) {
|
|
26
|
+
const entity = await repository.findById(id.trim());
|
|
27
|
+
if (!entity) {
|
|
28
|
+
return Result.fail(new NotFoundError(entityName, id, {
|
|
29
|
+
reason: `${entityName} not found`
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
return Result.ok(entity);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Enrich audit context with specific action and metadata
|
|
36
|
+
*
|
|
37
|
+
* Ensures consistent audit context across all use cases
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* const auditContext = UseCaseHelpers.enrichAuditContext(
|
|
41
|
+
* context,
|
|
42
|
+
* "CREATE_USER",
|
|
43
|
+
* undefined,
|
|
44
|
+
* { source: "admin-panel" }
|
|
45
|
+
* );
|
|
46
|
+
*/
|
|
47
|
+
static enrichAuditContext(context, action, organizationId, metadata) {
|
|
48
|
+
return {
|
|
49
|
+
...context,
|
|
50
|
+
auditAction: action,
|
|
51
|
+
organizationId: organizationId ?? context.organizationId,
|
|
52
|
+
metadata: { ...context.metadata, ...metadata }
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=UseCaseHelpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UseCaseHelpers.js","sourceRoot":"","sources":["../../../src/shared/use-case/UseCaseHelpers.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C;;;;;GAKG;AAEH,yDAAyD;AACzD,MAAM,OAAO,cAAc;IACvB;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,KAAK,CAAC,cAAc,CACvB,UAA2D,EAC3D,EAAU,EACV,UAAkB;QAElB,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;QACpD,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,OAAO,MAAM,CAAC,IAAI,CACd,IAAI,aAAa,CAAC,UAAU,EAAE,EAAE,EAAE;gBAC9B,MAAM,EAAE,GAAG,UAAU,YAAY;aACpC,CAAC,CACL,CAAC;QACN,CAAC;QACD,OAAO,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,kBAAkB,CACrB,OAAyB,EACzB,MAAc,EACd,cAAuB,EACvB,QAA8B;QAE9B,OAAO;YACH,GAAG,OAAO;YACV,WAAW,EAAE,MAAM;YACnB,cAAc,EAAE,cAAc,IAAI,OAAO,CAAC,cAAc;YACxD,QAAQ,EAAE,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,GAAG,QAAQ,EAAE;SACjD,CAAC;IACN,CAAC;CACJ"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/use-case/index.ts"],"names":[],"mappings":"AACA,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/shared/use-case/index.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/users/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/users/index.ts"],"names":[],"mappings":"AAAA,qBAAqB;AACrB,cAAc,aAAa,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Adapters } from '@multitenantkit/domain-contracts';
|
|
2
|
+
import type { OperationContext, ToolkitOptions } from '@multitenantkit/domain-contracts/shared';
|
|
3
|
+
import { type DomainError } from '@multitenantkit/domain-contracts/shared/errors/index';
|
|
4
|
+
import type { CreateUserInput, CreateUserOutput, ICreateUser } from '@multitenantkit/domain-contracts/users';
|
|
5
|
+
import { Result } from '../../../shared/result/Result';
|
|
6
|
+
import { BaseUseCase } from '../../../shared/use-case';
|
|
7
|
+
/**
|
|
8
|
+
* CreateUser use case
|
|
9
|
+
* Handles the business logic for creating a new user
|
|
10
|
+
*
|
|
11
|
+
* Generic support for custom fields:
|
|
12
|
+
* @template TUserCustomFields - Custom fields added to User
|
|
13
|
+
* @template TOrganizationCustomFields - Custom fields added to Organization (for toolkit options compatibility)
|
|
14
|
+
* @template TOrganizationMembershipCustomFields - Custom fields added to OrganizationMembership (for toolkit options compatibility)
|
|
15
|
+
*/
|
|
16
|
+
export declare class CreateUser<TUserCustomFields = {}, TOrganizationCustomFields = {}, TOrganizationMembershipCustomFields = {}> extends BaseUseCase<CreateUserInput & TUserCustomFields, CreateUserOutput, DomainError, TUserCustomFields, TOrganizationCustomFields, TOrganizationMembershipCustomFields> implements ICreateUser {
|
|
17
|
+
private readonly customSchema?;
|
|
18
|
+
constructor(adapters: Adapters<TUserCustomFields, TOrganizationCustomFields, TOrganizationMembershipCustomFields>, toolkitOptions?: ToolkitOptions<TUserCustomFields, TOrganizationCustomFields, TOrganizationMembershipCustomFields>);
|
|
19
|
+
protected executeBusinessLogic(input: CreateUserInput & TUserCustomFields, context: OperationContext): Promise<Result<CreateUserOutput, DomainError>>;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=CreateUser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CreateUser.d.ts","sourceRoot":"","sources":["../../../../src/users/use-cases/create-user/CreateUser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AAChG,OAAO,EAEH,KAAK,WAAW,EAEnB,MAAM,sDAAsD,CAAC;AAC9D,OAAO,KAAK,EACR,eAAe,EACf,gBAAgB,EAChB,WAAW,EAEd,MAAM,wCAAwC,CAAC;AAOhD,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD;;;;;;;;GAQG;AACH,qBAAa,UAAU,CAEf,iBAAiB,GAAG,EAAE,EAEtB,yBAAyB,GAAG,EAAE,EAE9B,mCAAmC,GAAG,EAAE,CAE5C,SAAQ,WAAW,CACf,eAAe,GAAG,iBAAiB,EACnC,gBAAgB,EAChB,WAAW,EACX,iBAAiB,EACjB,yBAAyB,EACzB,mCAAmC,CAEvC,YAAW,WAAW;IAEtB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAmB;gBAG7C,QAAQ,EAAE,QAAQ,CACd,iBAAiB,EACjB,yBAAyB,EACzB,mCAAmC,CACtC,EACD,cAAc,CAAC,EAAE,cAAc,CAC3B,iBAAiB,EACjB,yBAAyB,EACzB,mCAAmC,CACtC;cA4BW,oBAAoB,CAChC,KAAK,EAAE,eAAe,GAAG,iBAAiB,EAC1C,OAAO,EAAE,gBAAgB,GAC1B,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,WAAW,CAAC,CAAC;CA8DpD"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { ConflictError, ValidationError } from '@multitenantkit/domain-contracts/shared/errors/index';
|
|
2
|
+
import { CreateUserInputSchema, CreateUserOutputSchema, UserSchema } from '@multitenantkit/domain-contracts/users';
|
|
3
|
+
import { Result } from '../../../shared/result/Result';
|
|
4
|
+
import { BaseUseCase } from '../../../shared/use-case';
|
|
5
|
+
/**
|
|
6
|
+
* CreateUser use case
|
|
7
|
+
* Handles the business logic for creating a new user
|
|
8
|
+
*
|
|
9
|
+
* Generic support for custom fields:
|
|
10
|
+
* @template TUserCustomFields - Custom fields added to User
|
|
11
|
+
* @template TOrganizationCustomFields - Custom fields added to Organization (for toolkit options compatibility)
|
|
12
|
+
* @template TOrganizationMembershipCustomFields - Custom fields added to OrganizationMembership (for toolkit options compatibility)
|
|
13
|
+
*/
|
|
14
|
+
export class CreateUser extends BaseUseCase {
|
|
15
|
+
customSchema;
|
|
16
|
+
constructor(adapters, toolkitOptions) {
|
|
17
|
+
const customSchema = toolkitOptions?.users?.customFields?.customSchema;
|
|
18
|
+
// Build input schema with custom fields if provided
|
|
19
|
+
const inputSchema = (customSchema
|
|
20
|
+
? CreateUserInputSchema.and(customSchema.partial())
|
|
21
|
+
: CreateUserInputSchema);
|
|
22
|
+
// Output is a User; merge custom fields if provided
|
|
23
|
+
const outputSchema = (customSchema
|
|
24
|
+
? CreateUserOutputSchema.merge(customSchema)
|
|
25
|
+
: CreateUserOutputSchema);
|
|
26
|
+
super('user-createUser', adapters, toolkitOptions, inputSchema, outputSchema, 'Failed to create user');
|
|
27
|
+
this.customSchema = customSchema;
|
|
28
|
+
}
|
|
29
|
+
async executeBusinessLogic(input, context) {
|
|
30
|
+
if (input.externalId) {
|
|
31
|
+
const existingUser = await this.adapters.persistence.userRepository.findByExternalId(input.externalId);
|
|
32
|
+
if (existingUser) {
|
|
33
|
+
return Result.fail(new ConflictError('User', input.externalId, {
|
|
34
|
+
reason: 'External ID already registered'
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// 2. Generate ID, externalId and timestamp
|
|
39
|
+
const userId = input.id ?? this.adapters.system.uuid.generate();
|
|
40
|
+
const externalId = input.externalId ?? userId; // If externalId is not provided, use userId as externalId
|
|
41
|
+
const now = this.adapters.system.clock.now();
|
|
42
|
+
// 3. Create User data with custom fields
|
|
43
|
+
const user = {
|
|
44
|
+
...input,
|
|
45
|
+
id: userId,
|
|
46
|
+
externalId,
|
|
47
|
+
username: input.username, // username is required in input
|
|
48
|
+
createdAt: now,
|
|
49
|
+
updatedAt: now
|
|
50
|
+
};
|
|
51
|
+
// 4. Validate the user data with custom schema if provided
|
|
52
|
+
const validationSchema = this.customSchema
|
|
53
|
+
? UserSchema.strip().and(this.customSchema.strip())
|
|
54
|
+
: UserSchema.strip();
|
|
55
|
+
const validationResult = validationSchema.safeParse(user);
|
|
56
|
+
if (!validationResult.success) {
|
|
57
|
+
const firstError = validationResult.error.errors[0];
|
|
58
|
+
return Result.fail(new ValidationError(firstError.message, firstError.path.join('.')));
|
|
59
|
+
}
|
|
60
|
+
const validatedUser = validationResult.data;
|
|
61
|
+
// 5. Persist the user using Unit of Work (transaction) with audit context
|
|
62
|
+
const auditContext = {
|
|
63
|
+
...context,
|
|
64
|
+
organizationId: undefined, // Users don't belong directly to a organization
|
|
65
|
+
auditAction: 'CREATE_USER'
|
|
66
|
+
};
|
|
67
|
+
try {
|
|
68
|
+
await this.adapters.persistence.uow.transaction(async (repos) => {
|
|
69
|
+
await repos.users.insert(validatedUser, auditContext);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
return Result.fail(new ValidationError('Failed to save user', undefined, {
|
|
74
|
+
originalError: error
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
// 6. Return success response; BaseUseCase will parse output
|
|
78
|
+
return Result.ok({ ...validatedUser });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=CreateUser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CreateUser.js","sourceRoot":"","sources":["../../../../src/users/use-cases/create-user/CreateUser.ts"],"names":[],"mappings":"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"}
|