drimion 0.1.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.
Files changed (51) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +955 -0
  3. package/dist/cli/index.js +1045 -0
  4. package/dist/cli/templates/aggregate.ts.hbs +22 -0
  5. package/dist/cli/templates/entity.ts.hbs +16 -0
  6. package/dist/cli/templates/repository.ts.hbs +24 -0
  7. package/dist/cli/templates/use-case.ts.hbs +20 -0
  8. package/dist/cli/templates/value-object.ts.hbs +16 -0
  9. package/dist/kernel/core/aggregate.ts +234 -0
  10. package/dist/kernel/core/entity.ts +348 -0
  11. package/dist/kernel/core/id.ts +207 -0
  12. package/dist/kernel/core/index.ts +5 -0
  13. package/dist/kernel/core/repository.ts +81 -0
  14. package/dist/kernel/core/value-object.ts +309 -0
  15. package/dist/kernel/events/browser-event-manager.ts +241 -0
  16. package/dist/kernel/events/domain-event.ts +76 -0
  17. package/dist/kernel/events/event-bus.ts +158 -0
  18. package/dist/kernel/events/event-context.ts +95 -0
  19. package/dist/kernel/events/event-manager.ts +20 -0
  20. package/dist/kernel/events/event-utils.ts +19 -0
  21. package/dist/kernel/events/index.ts +7 -0
  22. package/dist/kernel/events/server-event-manager.ts +169 -0
  23. package/dist/kernel/helpers/auto-mapper.ts +222 -0
  24. package/dist/kernel/helpers/domain-classes.ts +162 -0
  25. package/dist/kernel/helpers/domain-error.ts +52 -0
  26. package/dist/kernel/helpers/getters-setters.ts +385 -0
  27. package/dist/kernel/helpers/index.ts +7 -0
  28. package/dist/kernel/index.ts +73 -0
  29. package/dist/kernel/libs/crypto.ts +33 -0
  30. package/dist/kernel/libs/index.ts +5 -0
  31. package/dist/kernel/libs/iterator.ts +298 -0
  32. package/dist/kernel/libs/result.ts +252 -0
  33. package/dist/kernel/libs/utils.ts +260 -0
  34. package/dist/kernel/libs/validator.ts +353 -0
  35. package/dist/kernel/types/adapter.types.ts +26 -0
  36. package/dist/kernel/types/command.types.ts +37 -0
  37. package/dist/kernel/types/entity.types.ts +60 -0
  38. package/dist/kernel/types/event.types.ts +129 -0
  39. package/dist/kernel/types/index.ts +26 -0
  40. package/dist/kernel/types/iterator.types.ts +39 -0
  41. package/dist/kernel/types/result.types.ts +122 -0
  42. package/dist/kernel/types/uid.types.ts +18 -0
  43. package/dist/kernel/types/utils.types.ts +120 -0
  44. package/dist/kernel/types/value-object.types.ts +20 -0
  45. package/dist/kernel/utils/date.utils.ts +111 -0
  46. package/dist/kernel/utils/index.ts +32 -0
  47. package/dist/kernel/utils/number.utils.ts +341 -0
  48. package/dist/kernel/utils/object.utils.ts +61 -0
  49. package/dist/kernel/utils/string.utils.ts +128 -0
  50. package/dist/kernel/utils/type.utils.ts +33 -0
  51. package/package.json +59 -0
@@ -0,0 +1,169 @@
1
+ import { EventEmitter } from "node:events";
2
+ import type { DomainEventPayload, EventEntry } from "../types/event.types";
3
+ import { BaseEventManager } from "./event-manager";
4
+ import { ValidateEventName } from "./event-utils";
5
+
6
+ /**
7
+ * @description
8
+ * Server-side singleton event manager backed by Node.js `EventEmitter`.
9
+ *
10
+ * Manages application-level event subscriptions and dispatches for server
11
+ * environments (Node.js, Bun, Deno). Supports wildcard event dispatch using
12
+ * `*` as a glob-style pattern.
13
+ *
14
+ * Use `EventContext.resolve()` to obtain this instance rather than
15
+ * constructing it directly. For most use cases, prefer `EventBus` —
16
+ * use this only when you need direct access to the `EventEmitter` transport.
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * const manager = ServerEventManager.instance();
21
+ *
22
+ * manager.subscribe('order:placed', (event) => {
23
+ * console.log(event.detail);
24
+ * });
25
+ *
26
+ * manager.dispatchEvent('order:placed', { orderId: '123' });
27
+ * ```
28
+ */
29
+ export class ServerEventManager extends BaseEventManager {
30
+ private static _instance: ServerEventManager;
31
+ private readonly emitter: EventEmitter;
32
+ private readonly entries: EventEntry[] = [];
33
+
34
+ private constructor() {
35
+ super();
36
+ // Use `process.versions?.node` instead of just `process` to avoid false
37
+ // positives in bundled browser environments where `process` may be polyfilled
38
+ // by Webpack / Vite but `process.versions.node` is never set.
39
+ if (
40
+ typeof process === "undefined" ||
41
+ process.versions?.node === undefined ||
42
+ typeof EventEmitter === "undefined"
43
+ ) {
44
+ throw new Error(
45
+ "ServerEventManager is not supported in this environment.",
46
+ );
47
+ }
48
+ this.emitter = new EventEmitter({ captureRejections: true });
49
+ }
50
+
51
+ /**
52
+ * @description
53
+ * Returns the singleton `ServerEventManager` instance, creating it if necessary.
54
+ *
55
+ * @returns The singleton `ServerEventManager`.
56
+ * @throws {Error} If called outside a Node.js-compatible environment.
57
+ */
58
+ public static instance(): ServerEventManager {
59
+ if (!ServerEventManager._instance) {
60
+ ServerEventManager._instance = new ServerEventManager();
61
+ }
62
+ return ServerEventManager._instance;
63
+ }
64
+
65
+ /**
66
+ * @description
67
+ * Checks whether an event with the given name is currently registered.
68
+ *
69
+ * @param eventName The event name to check.
70
+ * @returns `true` if at least one listener exists for this event name.
71
+ */
72
+ public exists(eventName: string): boolean {
73
+ return (
74
+ this.emitter.listenerCount(eventName) > 0 ||
75
+ this.entries.some((e) => e.eventName === eventName)
76
+ );
77
+ }
78
+
79
+ /**
80
+ * @description
81
+ * Dispatches an event by name, forwarding optional arguments as the `detail` payload.
82
+ *
83
+ * Supports wildcard patterns — if `eventName` contains `*`, all registered events
84
+ * whose names match the resulting regex are dispatched.
85
+ *
86
+ * @param eventName The event name or wildcard pattern to dispatch.
87
+ * @param args Additional arguments forwarded as `detail` to handlers.
88
+ */
89
+ public dispatchEvent(eventName: string, ...args: unknown[]): void {
90
+ ValidateEventName(eventName);
91
+
92
+ if (eventName.includes("*")) {
93
+ const regex = new RegExp(eventName.replace("*", ".*"));
94
+ for (const entry of this.entries) {
95
+ if (regex.test(entry.eventName)) {
96
+ this.emitter.emit(entry.eventName, { detail: args });
97
+ }
98
+ }
99
+ return;
100
+ }
101
+
102
+ this.emitter.emit(eventName, { detail: args });
103
+ }
104
+
105
+ /**
106
+ * @description
107
+ * Removes a registered event and its associated listener.
108
+ *
109
+ * @param eventName The name of the event to remove.
110
+ * @returns `true` if the event was found and removed; `false` otherwise.
111
+ */
112
+ public removeEvent(eventName: string): boolean {
113
+ // const entry = this.entries.find((e) => e.eventName === eventName);
114
+ // if (!entry) return false;
115
+ // this.entries.splice(this.entries.indexOf(entry), 1);
116
+ // this.emitter.removeListener(eventName, entry.callback);
117
+ // return true;
118
+
119
+ const matching = this.entries.filter((e) => e.eventName === eventName);
120
+
121
+ if (matching.length === 0) return false;
122
+ this.entries.splice(
123
+ 0,
124
+ this.entries.length,
125
+ ...this.entries.filter((e) => e.eventName !== eventName),
126
+ );
127
+
128
+ for (const entry of matching) {
129
+ this.emitter.removeListener(eventName, entry.callback);
130
+ }
131
+
132
+ return true;
133
+ }
134
+
135
+ /**
136
+ * @description
137
+ * Subscribes a callback to the specified event name.
138
+ *
139
+ * If an event with the same name is already registered, this call is a no-op.
140
+ * Event names must follow the `context:EventName` format.
141
+ *
142
+ * @param eventName The event name to subscribe to.
143
+ * @param fn The callback to invoke when the event is dispatched.
144
+ *
145
+ * @throws {DomainError} If the event name does not follow the required format.
146
+ */
147
+ public subscribe(
148
+ eventName: string,
149
+ fn: (event: DomainEventPayload) => void | Promise<void>,
150
+ ): void {
151
+ ValidateEventName(eventName);
152
+
153
+ this.entries.push({ eventName, callback: fn });
154
+ this.emitter.addListener(eventName, fn);
155
+ }
156
+
157
+ /**
158
+ * @description
159
+ * Resets the cached event manager instance.
160
+ *
161
+ * Intended for use in tests where environment switching or clean state
162
+ * between test cases is required. Not recommended in production code.
163
+ *
164
+ * @internal
165
+ */
166
+ public static __reset(): void {
167
+ ServerEventManager._instance = undefined as unknown as ServerEventManager;
168
+ }
169
+ }
@@ -0,0 +1,222 @@
1
+ import validator, { type Validator } from "../libs/validator";
2
+ import type { AnyObject } from "../types/utils.types";
3
+
4
+ /**
5
+ * @description
6
+ * Defines the shape of data used for mapping an entity's properties.
7
+ */
8
+ export interface EntityAutoMapperPayload {
9
+ id: string;
10
+ createdAt: Date;
11
+ updatedAt: Date;
12
+ }
13
+
14
+ /**
15
+ * @description
16
+ * The `AutoMapper` class is responsible for transforming domain resources (entities, value objects)
17
+ * into plain objects or primitive values. It provides methods to recursively process nested value objects, IDs,
18
+ * entities, and arrays, ensuring all complex data structures are serialized into a consistent object format.
19
+ */
20
+ export class AutoMapper {
21
+ private validator: Validator = validator;
22
+
23
+ /**
24
+ * @description
25
+ * Transforms an entity into a plain object, including its associated meta properties
26
+ * (`id`, `createdAt`, `updatedAt`).
27
+ *
28
+ * This method:
29
+ * - Resolves IDs to their primitive value forms.
30
+ * - Recursively converts nested entities, aggregates, and value objects to plain objects.
31
+ * - Preserves arrays and transforms their elements as needed.
32
+ *
33
+ * @param entity The entity instance to be transformed into a plain object.
34
+ * @returns A plain object representing the entity, including its metadata and serialized properties.
35
+ */
36
+ public entityToObj(entity: unknown): unknown {
37
+ if (this.validator.isID(entity)) {
38
+ return (entity as { value(): string }).value();
39
+ }
40
+
41
+ if (this.validator.isSymbol(entity)) {
42
+ return (entity as symbol).description;
43
+ }
44
+
45
+ if (
46
+ this.validator.isBoolean(entity) ||
47
+ this.validator.isNumber(entity) ||
48
+ this.validator.isString(entity) ||
49
+ this.validator.isDate(entity) ||
50
+ entity === null
51
+ ) {
52
+ return entity;
53
+ }
54
+
55
+ if (this.validator.isValueObject(entity)) {
56
+ return this.valueObjectToObj(entity);
57
+ }
58
+
59
+ if (this.validator.isArray(entity)) {
60
+ return (entity as unknown[]).map((item) => this.valueObjectToObj(item));
61
+ }
62
+
63
+ if (this.validator.isObject(entity)) {
64
+ const result: AnyObject = {};
65
+ for (const key of Object.keys(entity as object)) {
66
+ result[key] = this.entityToObj((entity as AnyObject)[key]);
67
+ }
68
+ return result;
69
+ }
70
+
71
+ if (this.validator.isEntity(entity) || this.validator.isAggregate(entity)) {
72
+ return this.serializeEntity(entity);
73
+ }
74
+
75
+ return entity;
76
+ }
77
+
78
+ /**
79
+ * @description
80
+ * Serializes an entity or aggregate into a plain object, extracting its identity
81
+ * metadata (`id`, `createdAt`, `updatedAt`) and recursively converting all nested
82
+ * properties into their primitive or serialized equivalents.
83
+ *
84
+ * This method is called internally by `entityToObj` when the target is confirmed
85
+ * to be an entity or aggregate (identified via `__kind` marker).
86
+ *
87
+ * @param entity The entity or aggregate instance to serialize. Expected to have
88
+ * a `props` object and an `id` field exposing a `.value()` method.
89
+ * @returns A plain object representation of the entity, including all metadata
90
+ * and recursively serialized properties.
91
+ */
92
+ private serializeEntity(entity: unknown): unknown {
93
+ const e = entity as AnyObject;
94
+ const props = (e.props ?? {}) as AnyObject;
95
+ const id = (e.id as { value(): string } | undefined)?.value();
96
+
97
+ const result: AnyObject & Partial<EntityAutoMapperPayload> = {
98
+ id,
99
+ createdAt: props.createdAt as Date | undefined,
100
+ updatedAt: props.updatedAt as Date | undefined,
101
+ };
102
+
103
+ for (const key of Object.keys(props)) {
104
+ if (key === "createdAt" || key === "updatedAt") continue;
105
+
106
+ const val = props[key];
107
+
108
+ if (this.validator.isID(val)) {
109
+ result[key] = (val as { value(): string }).value();
110
+ } else if (this.validator.isArray(val)) {
111
+ result[key] = (val as unknown[]).map((item) => this.entityToObj(item));
112
+ } else if (this.validator.isEntity(val)) {
113
+ result[key] = this.entityToObj(val);
114
+ } else if (this.validator.isSymbol(val)) {
115
+ result[key] = (val as symbol).description;
116
+ } else {
117
+ result[key] = this.valueObjectToObj(val);
118
+ }
119
+ }
120
+
121
+ return result;
122
+ }
123
+
124
+ /**
125
+ * @description
126
+ * Converts a value object into a plain object or a primitive value.
127
+ *
128
+ * This method handles multiple scenarios, including:
129
+ * - Null values
130
+ * - Symbol values
131
+ * - ID values
132
+ * - Simple data types (strings, numbers, booleans, dates, objects)
133
+ * - Nested value objects
134
+ * - Arrays containing complex data
135
+ *
136
+ * @param valueObject An instance representing a value object to be transformed.
137
+ * @returns A plain object, primitive value, or serialized structure derived from the given value object.
138
+ */
139
+ public valueObjectToObj(valueObject: unknown): unknown {
140
+ if (valueObject === null) return null;
141
+ if (typeof valueObject === "undefined") return undefined;
142
+
143
+ if (this.validator.isSymbol(valueObject)) {
144
+ return (valueObject as symbol).description;
145
+ }
146
+
147
+ if (this.validator.isID(valueObject)) {
148
+ return (valueObject as { value(): string }).value();
149
+ }
150
+
151
+ if (
152
+ this.validator.isBoolean(valueObject) ||
153
+ this.validator.isNumber(valueObject) ||
154
+ this.validator.isString(valueObject) ||
155
+ this.validator.isDate(valueObject)
156
+ ) {
157
+ return valueObject;
158
+ }
159
+
160
+ if (this.validator.isArray(valueObject)) {
161
+ return (valueObject as unknown[]).map((item) =>
162
+ this.valueObjectToObj(item),
163
+ );
164
+ }
165
+
166
+ if (this.validator.isObject(valueObject)) {
167
+ const result: AnyObject = {};
168
+ for (const key of Object.keys(valueObject as object)) {
169
+ result[key] = this.entityToObj((valueObject as AnyObject)[key]);
170
+ }
171
+ return result;
172
+ }
173
+
174
+ // treat as VO with props
175
+ const vo = valueObject as AnyObject;
176
+ const voProps = vo.props;
177
+
178
+ if (
179
+ this.validator.isBoolean(voProps) ||
180
+ this.validator.isNumber(voProps) ||
181
+ this.validator.isString(voProps) ||
182
+ this.validator.isDate(voProps)
183
+ ) {
184
+ return voProps;
185
+ }
186
+
187
+ if (this.validator.isSymbol(voProps)) {
188
+ return (voProps as symbol).description;
189
+ }
190
+
191
+ if (this.validator.isArray(voProps)) {
192
+ return (voProps as unknown[]).map((item) => this.valueObjectToObj(item));
193
+ }
194
+
195
+ if (this.validator.isObject(voProps)) {
196
+ const props = voProps as AnyObject;
197
+ const result: AnyObject = {};
198
+
199
+ for (const key of Object.keys(props)) {
200
+ const val = props[key];
201
+
202
+ if (this.validator.isValueObject(val)) {
203
+ result[key] = this.valueObjectToObj(val);
204
+ } else if (this.validator.isID(val)) {
205
+ result[key] = (val as { value(): string }).value();
206
+ } else if (this.validator.isSymbol(val)) {
207
+ result[key] = (val as symbol).description;
208
+ } else if (this.validator.isArray(val)) {
209
+ result[key] = (val as unknown[]).map((item) =>
210
+ this.valueObjectToObj(item),
211
+ );
212
+ } else {
213
+ result[key] = val;
214
+ }
215
+ }
216
+
217
+ return result;
218
+ }
219
+
220
+ return this.entityToObj(voProps);
221
+ }
222
+ }
@@ -0,0 +1,162 @@
1
+ import { Iterator } from "../libs/iterator";
2
+ import { Result } from "../libs/result";
3
+ import type { IIterator } from "../types/iterator.types";
4
+ import type { IResult } from "../types/result.types";
5
+
6
+ /**
7
+ * @description
8
+ * Represents the result of a `DomainClasseses.createMany()` operation.
9
+ *
10
+ * Contains both an iterator over each individual creation result and a combined
11
+ * result that reflects the overall success or failure of the batch operation.
12
+ */
13
+ export interface CreateManyResult {
14
+ /**
15
+ * @description
16
+ * An iterator over each individual `Result` produced during the batch creation.
17
+ * Use `data.next()` to access results in the same order as the input entries.
18
+ */
19
+ data: IIterator<IResult<unknown, unknown, unknown>>;
20
+
21
+ /**
22
+ * @description
23
+ * A combined `Result` representing the overall outcome.
24
+ * Will be a failure if any single entry failed to be created.
25
+ */
26
+ result: IResult<unknown, unknown, unknown>;
27
+ }
28
+
29
+ /**
30
+ * @description
31
+ * Represents a pairing of a domain class and the props needed to construct an instance of it.
32
+ * Used internally by the `DomainClasses` fluent builder.
33
+ */
34
+ interface DomainClassesEntry {
35
+ class: {
36
+ create: (props: unknown) => IResult<unknown, unknown, unknown>;
37
+ name: string;
38
+ };
39
+ props: unknown;
40
+ }
41
+
42
+ /**
43
+ * @description
44
+ * Fluent builder for creating multiple domain instances in a single batch operation.
45
+ *
46
+ * Chain `.prepare()` calls to register domain classes and their props,
47
+ * then call `.create()` to execute all instantiations at once.
48
+ *
49
+ * The combined `result` reflects the overall success or failure of the batch —
50
+ * it fails if any single entry fails. Individual results are accessible via the
51
+ * returned `data` iterator in the same order as the `.prepare()` calls.
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * const { result, data } = DomainClasses
56
+ * .prepare(Age, { value: 21 })
57
+ * .prepare(Name, { value: 'Alice' })
58
+ * .create();
59
+ *
60
+ * if (result.isSuccess()) {
61
+ * const age = data.next().value() as Age;
62
+ * const name = data.next().value() as Name;
63
+ * }
64
+ * ```
65
+ */
66
+ export class DomainClasses {
67
+ private readonly entries: DomainClassesEntry[] = [];
68
+
69
+ private constructor() {}
70
+
71
+ /**
72
+ * @description
73
+ * Registers a domain class and its construction props for batch instantiation.
74
+ *
75
+ * Returns the same `DomainClasses` builder instance to allow chaining.
76
+ *
77
+ * @param domainClasses The domain class with a static `create` method.
78
+ * @param props The props to pass to the class's `create` method.
79
+ * @returns The current `DomainClasses` builder instance.
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * DomainClasses.prepare(Money, { amount: 100 })
84
+ * ```
85
+ */
86
+ public prepare<Props>(
87
+ domainClasses: DomainClassesEntry["class"],
88
+ props: Props,
89
+ ): this {
90
+ this.entries.push({ class: domainClasses, props });
91
+ return this;
92
+ }
93
+
94
+ /**
95
+ * @description
96
+ * Executes the batch creation of all registered domain class entries.
97
+ *
98
+ * Iterates over each registered entry and calls its class's static `create` method.
99
+ * If an entry's class does not expose a `create` method, a failure `Result` is
100
+ * recorded for that entry.
101
+ *
102
+ * @returns A `CreateManyResult` containing:
103
+ * - `result`: A combined `IResult` — fails if any entry failed.
104
+ * - `data`: An `IIterator` over each individual `IResult`, in registration order.
105
+ *
106
+ * @example
107
+ * ```typescript
108
+ * const { result, data } = DomainClasses
109
+ * .prepare(Age, { value: 21 })
110
+ * .prepare(Name, { value: 'Alice' })
111
+ * .create();
112
+ * ```
113
+ */
114
+ public create(): CreateManyResult {
115
+ const results: IResult<unknown, unknown, unknown>[] = [];
116
+
117
+ for (const entry of this.entries) {
118
+ if (typeof entry.class?.create !== "function") {
119
+ results.push(
120
+ Result.error(
121
+ `No static 'create' method found in class ${entry.class?.name}.`,
122
+ ),
123
+ );
124
+ continue;
125
+ }
126
+ results.push(entry.class.create(entry.props));
127
+ }
128
+
129
+ const iterator = Iterator.create({
130
+ initialData: results,
131
+ returnCurrentOnReversion: true,
132
+ });
133
+
134
+ return {
135
+ data: iterator,
136
+ result: Result.combine(results),
137
+ };
138
+ }
139
+
140
+ /**
141
+ * @description
142
+ * Entry point for the `DomainClasses` fluent builder.
143
+ *
144
+ * Equivalent to `new DomainClasses().prepare(domainClasses, props)` — starts a new
145
+ * builder chain with the first class-props pair already registered.
146
+ *
147
+ * @param domainClasses The domain class with a static `create` method.
148
+ * @param props The props to pass to the class's `create` method.
149
+ * @returns A new `DomainClasses` builder instance with the first entry registered.
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * DomainClasses.prepare(Age, { value: 21 }).prepare(Name, { value: 'Alice' }).create()
154
+ * ```
155
+ */
156
+ public static prepare<Props>(
157
+ domainClasses: DomainClassesEntry["class"],
158
+ props: Props,
159
+ ): DomainClasses {
160
+ return new DomainClasses().prepare(domainClasses, props);
161
+ }
162
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @description
3
+ * Represents a domain-level error thrown when a business rule or invariant is violated.
4
+ *
5
+ * Unlike generic `Error`, `DomainError` carries structured context about where and why
6
+ * the violation occurred, making it easier to handle, log, and surface meaningful
7
+ * feedback to callers.
8
+ *
9
+ * Use `DomainError` whenever a domain operation fails due to an invariant violation —
10
+ * for example, when `set().to()` receives an invalid value, or when a required domain
11
+ * rule is not satisfied.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * throw new DomainError('Amount must be positive', {
16
+ * field: 'amount',
17
+ * context: 'Money',
18
+ * });
19
+ * ```
20
+ */
21
+ export class DomainError extends Error {
22
+ public readonly field?: string;
23
+ public readonly context?: string;
24
+
25
+ /**
26
+ * @description
27
+ * Creates a new `DomainError` instance.
28
+ *
29
+ * @param message A human-readable description of the violation.
30
+ * @param options Optional structured context about the violation.
31
+ * @param options.field The property or field name that caused the error.
32
+ * @param options.context The domain class or context name where the error originated.
33
+ */
34
+ constructor(
35
+ message: string,
36
+ options?: { field?: string; context?: string; cause?: unknown },
37
+ ) {
38
+ const formattedMessage = options?.context
39
+ ? `[${options.context}] ${message}`
40
+ : message;
41
+
42
+ super(formattedMessage, { cause: options?.cause });
43
+ this.name = "DomainError";
44
+ this.field = options?.field;
45
+ this.context = options?.context;
46
+
47
+ // Maintains proper stack trace in V8 environments (Node.js, Bun, Chrome)
48
+ if (Error.captureStackTrace) {
49
+ Error.captureStackTrace(this, DomainError);
50
+ }
51
+ }
52
+ }