@emkodev/emkore 1.0.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +269 -0
  2. package/DEVELOPER_GUIDE.md +227 -0
  3. package/LICENSE +21 -0
  4. package/README.md +126 -0
  5. package/bun.lock +22 -0
  6. package/example/README.md +200 -0
  7. package/example/create-user.interactor.ts +88 -0
  8. package/example/dto/user.dto.ts +34 -0
  9. package/example/entity/user.entity.ts +54 -0
  10. package/example/index.ts +18 -0
  11. package/example/interface/create-user.usecase.ts +93 -0
  12. package/example/interface/user.repository.ts +23 -0
  13. package/mod.ts +1 -0
  14. package/package.json +32 -0
  15. package/src/common/abstract.actor.ts +59 -0
  16. package/src/common/abstract.entity.ts +59 -0
  17. package/src/common/abstract.interceptor.ts +17 -0
  18. package/src/common/abstract.repository.ts +162 -0
  19. package/src/common/abstract.usecase.ts +113 -0
  20. package/src/common/config/config-registry.ts +190 -0
  21. package/src/common/config/config-section.ts +106 -0
  22. package/src/common/exception/authorization-exception.ts +28 -0
  23. package/src/common/exception/repository-exception.ts +46 -0
  24. package/src/common/interceptor/audit-log.interceptor.ts +181 -0
  25. package/src/common/interceptor/authorization.interceptor.ts +252 -0
  26. package/src/common/interceptor/performance.interceptor.ts +101 -0
  27. package/src/common/llm/api-definition.type.ts +185 -0
  28. package/src/common/pattern/unit-of-work.ts +78 -0
  29. package/src/common/platform/env.ts +38 -0
  30. package/src/common/registry/usecase-registry.ts +80 -0
  31. package/src/common/type/interceptor-context.type.ts +25 -0
  32. package/src/common/type/json-schema.type.ts +80 -0
  33. package/src/common/type/json.type.ts +5 -0
  34. package/src/common/type/lowercase.type.ts +48 -0
  35. package/src/common/type/metadata.type.ts +5 -0
  36. package/src/common/type/money.class.ts +384 -0
  37. package/src/common/type/permission.type.ts +43 -0
  38. package/src/common/validation/validation-result.ts +52 -0
  39. package/src/common/validation/validators.ts +441 -0
  40. package/src/index.ts +95 -0
  41. package/test/unit/abstract-actor.test.ts +608 -0
  42. package/test/unit/actor.test.ts +89 -0
  43. package/test/unit/api-definition.test.ts +628 -0
  44. package/test/unit/authorization.test.ts +101 -0
  45. package/test/unit/entity.test.ts +95 -0
  46. package/test/unit/money.test.ts +480 -0
  47. package/test/unit/validation.test.ts +138 -0
  48. package/tsconfig.json +18 -0
@@ -0,0 +1,162 @@
1
+ import type { Dto } from "./abstract.entity.ts";
2
+
3
+ /**
4
+ * Base filter type for repository queries.
5
+ *
6
+ * Extracts business fields from DTO (excludes id, ownerId, createdAt, updatedAt, deletedAt)
7
+ * and adds back the base fields with appropriate query types:
8
+ * - ownerId: exact match filtering
9
+ * - createdAt/updatedAt/deletedAt: range-based date filtering
10
+ *
11
+ * @template TDto - The DTO type to create a filter for
12
+ */
13
+ export type BaseFilter<TDto extends Dto> = Omit<TDto, keyof Dto> & {
14
+ readonly ownerId?: string;
15
+ readonly createdAt?: { from?: Date; to?: Date };
16
+ readonly updatedAt?: { from?: Date; to?: Date };
17
+ readonly deletedAt?: { from?: Date; to?: Date };
18
+ };
19
+
20
+ /**
21
+ * Base repository interface defining common CRUD operations for DTOs.
22
+ *
23
+ * @template T - The DTO type this repository works with, must extend Dto
24
+ * @template F - The filter type for queries, defaults to Partial<T>
25
+ */
26
+ export abstract class Repository<T extends Dto, F = Partial<T>> {
27
+ /**
28
+ * Creates a new DTO in the repository.
29
+ * @param dto - The DTO to create
30
+ * @returns Promise resolving to the created DTO
31
+ * @throws ValidationException if DTO data is invalid
32
+ * @throws EntityAlreadyExistsException if DTO with same ID already exists
33
+ * @throws DatabaseException on database operation failure
34
+ */
35
+ abstract create(dto: T): Promise<T>;
36
+
37
+ /**
38
+ * Retrieves a DTO by ID.
39
+ * @param id - The DTO ID
40
+ * @returns Promise resolving to the found DTO
41
+ * @throws ValidationException if ID is invalid
42
+ * @throws EntityNotFoundException if DTO not found
43
+ * @throws DatabaseException on database operation failure
44
+ */
45
+ abstract retrieve(id: string): Promise<T>;
46
+
47
+ /**
48
+ * Updates an existing DTO.
49
+ * @param dto - The DTO to update
50
+ * @returns Promise resolving to the updated DTO
51
+ * @throws ValidationException if DTO data is invalid
52
+ * @throws EntityNotFoundException if DTO not found
53
+ * @throws DatabaseException on database operation failure
54
+ */
55
+ abstract update(dto: T): Promise<T>;
56
+
57
+ /**
58
+ * Deletes a DTO by ID.
59
+ * @param id - The DTO ID
60
+ * @returns Promise resolving when deletion is complete
61
+ * @throws ValidationException if ID is invalid
62
+ * @throws EntityNotFoundException if DTO not found
63
+ * @throws DatabaseException on database operation failure
64
+ */
65
+ abstract delete(id: string): Promise<void>;
66
+
67
+ /**
68
+ * Lists DTOs with pagination and filtering.
69
+ * @param options - Query options including filter, pagination
70
+ * @returns Promise resolving to list of DTOs
71
+ * @throws ValidationException if query parameters are invalid
72
+ * @throws DatabaseException on database operation failure
73
+ */
74
+ abstract list(options?: {
75
+ filter?: Partial<F>;
76
+ limit?: number;
77
+ offset?: number;
78
+ sortBy?: string;
79
+ sortOrder?: "asc" | "desc";
80
+ }): Promise<T[]>;
81
+
82
+ /**
83
+ * Counts DTOs with filtering.
84
+ * @param filter - Optional filter criteria
85
+ * @returns Promise resolving to DTO count
86
+ * @throws ValidationException if filter parameters are invalid
87
+ * @throws DatabaseException on database operation failure
88
+ */
89
+ abstract count(filter?: Partial<F>): Promise<number>;
90
+
91
+ /**
92
+ * Checks if a DTO exists by ID.
93
+ * Default implementation uses retrieve and catches EntityNotFoundException.
94
+ * Can be overridden for more efficient implementation.
95
+ * @param id - The DTO ID
96
+ * @returns Promise resolving to boolean existence check
97
+ */
98
+ async exists(id: string): Promise<boolean> {
99
+ try {
100
+ await this.retrieve(id);
101
+ return true;
102
+ } catch (error) {
103
+ if (error instanceof Error && error.name === "EntityNotFoundException") {
104
+ return false;
105
+ }
106
+ throw error;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Deletes multiple DTOs by IDs in a single transaction.
112
+ * Default implementation calls delete for each ID.
113
+ * Should be overridden for batch operations in production repositories.
114
+ * @param ids - Array of DTO IDs to delete
115
+ * @returns Promise resolving to number of DTOs actually deleted
116
+ */
117
+ async bulkDelete(ids: string[]): Promise<number> {
118
+ let deletedCount = 0;
119
+ for (const id of ids) {
120
+ try {
121
+ await this.delete(id);
122
+ deletedCount++;
123
+ } catch (error) {
124
+ // Log error but continue with other deletions
125
+ console.error(`Failed to delete DTO ${id}:`, error);
126
+ }
127
+ }
128
+ return deletedCount;
129
+ }
130
+
131
+ /**
132
+ * Creates multiple DTOs in a single transaction.
133
+ * Default implementation calls create for each DTO.
134
+ * Should be overridden for batch operations in production repositories.
135
+ * @param dtos - Array of DTOs to create
136
+ * @returns Promise resolving to array of created DTOs
137
+ */
138
+ async bulkCreate(dtos: T[]): Promise<T[]> {
139
+ const created: T[] = [];
140
+ for (const dto of dtos) {
141
+ const result = await this.create(dto);
142
+ created.push(result);
143
+ }
144
+ return created;
145
+ }
146
+
147
+ /**
148
+ * Updates multiple DTOs in a single transaction.
149
+ * Default implementation calls update for each DTO.
150
+ * Should be overridden for batch operations in production repositories.
151
+ * @param dtos - Array of DTOs to update
152
+ * @returns Promise resolving to array of updated DTOs
153
+ */
154
+ async bulkUpdate(dtos: T[]): Promise<T[]> {
155
+ const updated: T[] = [];
156
+ for (const dto of dtos) {
157
+ const result = await this.update(dto);
158
+ updated.push(result);
159
+ }
160
+ return updated;
161
+ }
162
+ }
@@ -0,0 +1,113 @@
1
+ import type { Actor } from "./abstract.actor.ts";
2
+ import type { Interceptor } from "./abstract.interceptor.ts";
3
+ import { createInterceptorContext } from "./type/interceptor-context.type.ts";
4
+ import type { ApiDefinition } from "./llm/api-definition.type.ts";
5
+ import type { Permission } from "./type/permission.type.ts";
6
+
7
+ export type UsecaseName = readonly [Lowercase<string>, Lowercase<string>];
8
+
9
+ export abstract class Usecase<Input, Output> {
10
+ /* This is necessary for Permissions management and code generation. */
11
+ abstract readonly usecaseName: UsecaseName;
12
+
13
+ /* API definition schema makes it possible to generate API docs, MCP tools, JSON-RPC definitions. It also make it possible for business people to describe the Usecase in more or less plain text. */
14
+ abstract readonly apiDefinition: ApiDefinition;
15
+
16
+ /* Global interceptors are applied to each Interactor (Usecases implementation). Could be used for logging, sanitization, auth, etc. */
17
+ private static readonly _globalInterceptors = new Set<
18
+ // deno-lint-ignore no-explicit-any
19
+ Interceptor<any, any>
20
+ >();
21
+
22
+ /* Local interceptors are applied ONLY to the interactors instances you explicitly called .use() for. */
23
+ private readonly _localInterceptors = new Set<Interceptor<Input, Output>>();
24
+
25
+ /* Global interceptors registry. call `Usecase.use(...)` to add one or more interceptors. */
26
+ // deno-lint-ignore no-explicit-any
27
+ static use(interceptors: Interceptor<any, any>[]): void {
28
+ interceptors.forEach((interceptor) =>
29
+ this._globalInterceptors.add(interceptor)
30
+ );
31
+ }
32
+
33
+ /* Global interceptors registry. Instantiate the desired interactor and call `interactor.use(...)` to add one or more interceptors. */
34
+ use(interceptors: Interceptor<Input, Output>[]): void {
35
+ interceptors.forEach((interceptor) =>
36
+ this._localInterceptors.add(interceptor)
37
+ );
38
+ }
39
+
40
+ /* Call this method in your code. It will call `_execute` internally, which in turn must be implemented by your Interactors. */
41
+ async execute(actor: Actor, input: Input): Promise<Output> {
42
+ const context = createInterceptorContext(
43
+ actor,
44
+ this.usecaseName,
45
+ this.requiredPermissions,
46
+ {},
47
+ );
48
+
49
+ try {
50
+ let processedInput = input;
51
+
52
+ // Global interceptors beforeExecute
53
+ for (const interceptor of Usecase._globalInterceptors) {
54
+ if (interceptor.beforeExecute) {
55
+ processedInput = await interceptor.beforeExecute(
56
+ processedInput,
57
+ context,
58
+ );
59
+ }
60
+ }
61
+
62
+ // Local interceptors beforeExecute
63
+ for (const interceptor of this._localInterceptors) {
64
+ if (interceptor.beforeExecute) {
65
+ processedInput = await interceptor.beforeExecute(
66
+ processedInput,
67
+ context,
68
+ );
69
+ }
70
+ }
71
+
72
+ let output = await this._execute(actor, processedInput);
73
+
74
+ // Local interceptors afterExecute (reversed)
75
+ for (const interceptor of [...this._localInterceptors].reverse()) {
76
+ if (interceptor.afterExecute) {
77
+ output = await interceptor.afterExecute(output, context);
78
+ }
79
+ }
80
+
81
+ // Global interceptors afterExecute (reversed)
82
+ for (const interceptor of [...Usecase._globalInterceptors].reverse()) {
83
+ if (interceptor.afterExecute) {
84
+ output = await interceptor.afterExecute(output, context);
85
+ }
86
+ }
87
+
88
+ return output;
89
+ } catch (error) {
90
+ // Local interceptors onError (reversed)
91
+ for (const interceptor of [...this._localInterceptors].reverse()) {
92
+ if (interceptor.onError) {
93
+ await interceptor.onError(error as Error, context);
94
+ }
95
+ }
96
+
97
+ // Global interceptors onError (reversed)
98
+ for (const interceptor of [...Usecase._globalInterceptors].reverse()) {
99
+ if (interceptor.onError) {
100
+ await interceptor.onError(error as Error, context);
101
+ }
102
+ }
103
+
104
+ throw error;
105
+ }
106
+ }
107
+
108
+ /* DO NOT call this method directly. DO implement it in your Interactors. */
109
+ protected abstract _execute(actor: Actor, input: Input): Promise<Output>;
110
+
111
+ /* Define the permissions required for this usecase. Return empty array if no permissions needed. */
112
+ abstract get requiredPermissions(): Permission[];
113
+ }
@@ -0,0 +1,190 @@
1
+ import { ValidationError } from "../validation/validators.ts";
2
+ import { ValidationResult } from "../validation/validation-result.ts";
3
+ import type { ConfigSection } from "./config-section.ts";
4
+
5
+ /**
6
+ * 12-Factor App compliant configuration registry.
7
+ *
8
+ * Features:
9
+ * - Modular: Register only the config sections you need
10
+ * - Environment-based: All values from environment variables
11
+ * - No hardcoded environments: No dev/staging/prod classes
12
+ * - Composable: Build your configuration dynamically
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * const config = new ConfigRegistry();
17
+ *
18
+ * // Register only what you need
19
+ * config.register("database", new DatabaseConfig());
20
+ * config.register("logging", new LoggingConfig());
21
+ *
22
+ * // Validate all sections
23
+ * const result = config.validate();
24
+ * if (!result.isValid) {
25
+ * throw new Error(`Config validation failed: ${result.errors.join(", ")}`);
26
+ * }
27
+ *
28
+ * // Access config sections
29
+ * const db = config.get<DatabaseConfig>("database");
30
+ * ```
31
+ */
32
+ export class ConfigRegistry {
33
+ private readonly sections = new Map<string, ConfigSection>();
34
+ private _validated = false;
35
+
36
+ /**
37
+ * Register a configuration section
38
+ * @param name Unique name for this section
39
+ * @param section The configuration section to register
40
+ */
41
+ register(name: string, section: ConfigSection): void {
42
+ if (this.sections.has(name)) {
43
+ throw new Error(`Configuration section '${name}' is already registered`);
44
+ }
45
+ this.sections.set(name, section);
46
+ this._validated = false; // Reset validation state
47
+ }
48
+
49
+ /**
50
+ * Get a configuration section by name
51
+ * @param name The name of the section to retrieve
52
+ * @returns The config section or undefined if not found
53
+ */
54
+ get<T extends ConfigSection>(name: string): T | undefined {
55
+ return this.sections.get(name) as T | undefined;
56
+ }
57
+
58
+ /**
59
+ * Get a configuration section by name, throwing if not found
60
+ * @param name The name of the section to retrieve
61
+ * @returns The config section
62
+ * @throws Error if section not found
63
+ */
64
+ require<T extends ConfigSection>(name: string): T {
65
+ const section = this.get<T>(name);
66
+ if (!section) {
67
+ throw new Error(`Required configuration section '${name}' not found`);
68
+ }
69
+ return section;
70
+ }
71
+
72
+ /**
73
+ * Check if a configuration section is registered
74
+ * @param name The name of the section to check
75
+ */
76
+ has(name: string): boolean {
77
+ return this.sections.has(name);
78
+ }
79
+
80
+ /**
81
+ * Remove a configuration section
82
+ * @param name The name of the section to remove
83
+ */
84
+ unregister(name: string): boolean {
85
+ const result = this.sections.delete(name);
86
+ if (result) {
87
+ this._validated = false;
88
+ }
89
+ return result;
90
+ }
91
+
92
+ /**
93
+ * Get all registered section names
94
+ */
95
+ getSectionNames(): string[] {
96
+ return Array.from(this.sections.keys());
97
+ }
98
+
99
+ /**
100
+ * Validate all registered configuration sections
101
+ * @returns ValidationResult with any errors found
102
+ */
103
+ validate(): ValidationResult {
104
+ const errors: ValidationError[] = [];
105
+
106
+ for (const [name, section] of this.sections) {
107
+ const sectionErrors = section.validate();
108
+ // Add section name context to errors
109
+ for (const error of sectionErrors) {
110
+ errors.push(
111
+ new ValidationError(`[${name}] ${error.message}`),
112
+ );
113
+ }
114
+ }
115
+
116
+ this._validated = errors.length === 0;
117
+ return new ValidationResult(
118
+ errors.length === 0,
119
+ errors.map((e) => e.message),
120
+ );
121
+ }
122
+
123
+ /**
124
+ * Check if configuration has been validated successfully
125
+ */
126
+ get isValidated(): boolean {
127
+ return this._validated;
128
+ }
129
+
130
+ /**
131
+ * Initialize the configuration (validate and throw if invalid)
132
+ * @throws Error if validation fails
133
+ */
134
+ initialize(): void {
135
+ const result = this.validate();
136
+ if (!result.isValid) {
137
+ throw new Error(
138
+ `Configuration validation failed:\n${result.errors.join("\n")}`,
139
+ );
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Convert all sections to a plain object for serialization/debugging
145
+ */
146
+ toMap(): Record<string, unknown> {
147
+ const result: Record<string, unknown> = {};
148
+ for (const [name, section] of this.sections) {
149
+ result[name] = section.toMap();
150
+ }
151
+ return result;
152
+ }
153
+
154
+ /**
155
+ * Clear all registered sections
156
+ */
157
+ clear(): void {
158
+ this.sections.clear();
159
+ this._validated = false;
160
+ }
161
+
162
+ /**
163
+ * Create a ConfigRegistry from environment variables with auto-detection.
164
+ * Detects which configs to load based on presence of marker env vars.
165
+ *
166
+ * @example
167
+ * ```typescript
168
+ * // Auto-detect and load configs based on environment
169
+ * const config = ConfigRegistry.fromEnvironment({
170
+ * database: () => Deno.env.get("DATABASE_HOST") ? new DatabaseConfig() : undefined,
171
+ * cache: () => Deno.env.get("REDIS_HOST") ? new RedisConfig() : undefined,
172
+ * logging: () => new LoggingConfig(), // Always include
173
+ * });
174
+ * ```
175
+ */
176
+ static fromEnvironment(
177
+ detectors: Record<string, () => ConfigSection | undefined>,
178
+ ): ConfigRegistry {
179
+ const registry = new ConfigRegistry();
180
+
181
+ for (const [name, detector] of Object.entries(detectors)) {
182
+ const section = detector();
183
+ if (section) {
184
+ registry.register(name, section);
185
+ }
186
+ }
187
+
188
+ return registry;
189
+ }
190
+ }
@@ -0,0 +1,106 @@
1
+ import type { ValidationError } from "../validation/validators.ts";
2
+ import { getEnv, getEnvOrDefault } from "../platform/env.ts";
3
+
4
+ /**
5
+ * Base interface for configuration sections.
6
+ * Each section is responsible for:
7
+ * - Loading its values from environment variables
8
+ * - Validating its configuration
9
+ * - Providing a serializable representation
10
+ */
11
+ export interface ConfigSection {
12
+ /**
13
+ * Validate this configuration section
14
+ * @returns Array of validation errors, empty if valid
15
+ */
16
+ validate(): ValidationError[];
17
+
18
+ /**
19
+ * Convert to a plain object for serialization/debugging
20
+ */
21
+ toMap(): Record<string, unknown>;
22
+ }
23
+
24
+ /**
25
+ * Base class providing common functionality for config sections
26
+ */
27
+ export abstract class BaseConfigSection implements ConfigSection {
28
+ abstract validate(): ValidationError[];
29
+ abstract toMap(): Record<string, unknown>;
30
+
31
+ /**
32
+ * Helper to get required environment variable
33
+ */
34
+ protected getRequiredEnv(key: string): string {
35
+ const value = getEnv(key);
36
+ if (!value) {
37
+ throw new Error(`Required environment variable ${key} is not set`);
38
+ }
39
+ return value;
40
+ }
41
+
42
+ /**
43
+ * Helper to get optional environment variable with default
44
+ */
45
+ protected getEnvString(key: string, defaultValue: string): string {
46
+ return getEnvOrDefault(key, defaultValue);
47
+ }
48
+
49
+ /**
50
+ * Helper to parse integer from environment variable
51
+ */
52
+ protected getEnvInt(key: string, defaultValue: number): number {
53
+ const value = getEnv(key);
54
+ if (!value) return defaultValue;
55
+ const parsed = parseInt(value, 10);
56
+ return isNaN(parsed) ? defaultValue : parsed;
57
+ }
58
+
59
+ /**
60
+ * Helper to parse boolean from environment variable
61
+ */
62
+ protected getEnvBool(key: string, defaultValue: boolean): boolean {
63
+ const value = getEnv(key);
64
+ if (!value) return defaultValue;
65
+ return value.toLowerCase() === "true" || value === "1";
66
+ }
67
+
68
+ /**
69
+ * Helper to parse duration from environment variable
70
+ * Supports: 30s, 5m, 2h, 1d
71
+ */
72
+ protected getEnvDuration(key: string, defaultValue: number): number {
73
+ const value = getEnv(key);
74
+ if (!value) return defaultValue;
75
+
76
+ const match = /^(\d+)(ms|s|m|h|d)$/.exec(value.toLowerCase());
77
+ if (!match) return defaultValue;
78
+
79
+ const amount = parseInt(match[1]!, 10);
80
+ const unit = match[2];
81
+
82
+ switch (unit) {
83
+ case "ms":
84
+ return amount;
85
+ case "s":
86
+ return amount * 1000;
87
+ case "m":
88
+ return amount * 60 * 1000;
89
+ case "h":
90
+ return amount * 60 * 60 * 1000;
91
+ case "d":
92
+ return amount * 24 * 60 * 60 * 1000;
93
+ default:
94
+ return defaultValue;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Helper to parse comma-separated list from environment variable
100
+ */
101
+ protected getEnvList(key: string, defaultValue: string[] = []): string[] {
102
+ const value = getEnv(key);
103
+ if (!value) return defaultValue;
104
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
105
+ }
106
+ }
@@ -0,0 +1,28 @@
1
+ import type { Metadata } from "../type/metadata.type.ts";
2
+
3
+ export class AuthorizationException extends Error {
4
+ public readonly details: Metadata;
5
+
6
+ constructor(message: string, details: Metadata = {}) {
7
+ super(message);
8
+ this.name = "AuthorizationException";
9
+ this.details = details;
10
+ }
11
+
12
+ static insufficientPermissions(options: {
13
+ actorId: string;
14
+ resource: string;
15
+ action: string;
16
+ missingPermissions: string[];
17
+ }): AuthorizationException {
18
+ return new AuthorizationException(
19
+ `Actor ${options.actorId} lacks required permissions for ${options.action} on ${options.resource}`,
20
+ {
21
+ actorId: options.actorId,
22
+ resource: options.resource,
23
+ action: options.action,
24
+ missingPermissions: options.missingPermissions,
25
+ },
26
+ );
27
+ }
28
+ }
@@ -0,0 +1,46 @@
1
+ export abstract class RepositoryException extends Error {
2
+ constructor(message: string, override readonly cause?: Error) {
3
+ super(message);
4
+ this.name = "RepositoryException";
5
+ }
6
+ }
7
+
8
+ export class NetworkException extends RepositoryException {
9
+ constructor(
10
+ message: string,
11
+ public readonly statusCode?: number,
12
+ public readonly responseBody?: string,
13
+ cause?: Error,
14
+ ) {
15
+ super(message, cause);
16
+ this.name = "NetworkException";
17
+ }
18
+ }
19
+
20
+ export class ValidationException extends RepositoryException {
21
+ constructor(message: string, cause?: Error) {
22
+ super(message, cause);
23
+ this.name = "ValidationException";
24
+ }
25
+ }
26
+
27
+ export class EntityNotFoundException extends RepositoryException {
28
+ constructor(message: string, cause?: Error) {
29
+ super(message, cause);
30
+ this.name = "EntityNotFoundException";
31
+ }
32
+ }
33
+
34
+ export class EntityAlreadyExistsException extends RepositoryException {
35
+ constructor(message: string, cause?: Error) {
36
+ super(message, cause);
37
+ this.name = "EntityAlreadyExistsException";
38
+ }
39
+ }
40
+
41
+ export class DatabaseException extends RepositoryException {
42
+ constructor(message: string, cause?: Error) {
43
+ super(message, cause);
44
+ this.name = "DatabaseException";
45
+ }
46
+ }