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,207 @@
1
+ import { UUID } from "../libs/crypto";
2
+ import validator from "../libs/validator";
3
+ import type { UID } from "../types/uid.types";
4
+
5
+ /**
6
+ * @description
7
+ * Represents a unique identifier for Entities or Aggregates, providing methods
8
+ * to generate and manipulate ID values, including the ability to create new IDs, convert them
9
+ * to a shorter format, clone them, and check for equality.
10
+ */
11
+ export class ID implements UID<string | number> {
12
+ protected readonly __kind = "ID" as const;
13
+
14
+ readonly #maxSize: number = 16;
15
+ private _isNew: boolean;
16
+ private _value: string;
17
+ #createdAt: Date;
18
+
19
+ private constructor(id?: string | number) {
20
+ this.#createdAt = new Date();
21
+
22
+ if (typeof id === "undefined") {
23
+ const uuid = UUID();
24
+ this._value = uuid;
25
+ this._isNew = true;
26
+ return;
27
+ }
28
+
29
+ if (!validator.isString(id) && !validator.isNumber(id)) {
30
+ throw new TypeError("ID must be a string or number");
31
+ }
32
+
33
+ this._value = String(id);
34
+ this._isNew = false;
35
+ }
36
+
37
+ /**
38
+ * @description
39
+ * Creates a new `ID` instance. If no `id` value is provided, a new
40
+ * UUID is generated. If an `id` is provided, that value is used directly.
41
+ *
42
+ * @param id Optional value to initialize the ID with. If not provided, a new UUID is created.
43
+ * @returns A `UID` instance.
44
+ */
45
+ public static create(id?: string | number): UID<string> {
46
+ return new ID(id) as unknown as UID<string>;
47
+ }
48
+
49
+ /**
50
+ * @description
51
+ * Creates a clone of the current `UID` instance with the same value
52
+ * and properties, but without marking it as new.
53
+ *
54
+ * @returns A cloned `UID` instance identical to the current one.
55
+ */
56
+ public clone(): UID<string | number> {
57
+ return new ID(this._value) as unknown as UID<string | number>;
58
+ }
59
+
60
+ /**
61
+ * @description
62
+ * Creates a new `UID` instance cloned from the current one, but marks
63
+ * the new clone as a new ID.
64
+ *
65
+ * @returns A cloned `UID` instance that is considered new.
66
+ */
67
+ public cloneAsNew(): UID<string> {
68
+ const newUUID = new ID(this._value);
69
+ newUUID.setAsNew();
70
+ return newUUID;
71
+ }
72
+
73
+ /**
74
+ * @description
75
+ * Retrieves the creation date of this `ID` instance.
76
+ *
77
+ * @returns The `Date` object representing when this ID was created or last modified.
78
+ */
79
+ public createdAt(): Date {
80
+ return this.#createdAt;
81
+ }
82
+
83
+ /**
84
+ * @description
85
+ * Performs a deep comparison by serializing both IDs and checking if
86
+ * the JSON representation is identical.
87
+ *
88
+ * @param id Another `UID` to compare to.
89
+ * @returns `true` if both IDs are deeply equal; otherwise `false`.
90
+ */
91
+ public deepEqual(id: UID<unknown>): boolean {
92
+ const A = JSON.stringify(this);
93
+ const B = JSON.stringify(id);
94
+ return A === B;
95
+ }
96
+
97
+ /**
98
+ * @description
99
+ * Compares the current ID against another `UID` instance by value.
100
+ *
101
+ * @param id Another `UID` to compare to.
102
+ * @returns `true` if both IDs share the same value type and string value, otherwise `false`.
103
+ */
104
+ public equal(id: UID<unknown>): boolean {
105
+ return (
106
+ typeof this._value === typeof id?.value() &&
107
+ (this._value as unknown) === id?.value()
108
+ );
109
+ }
110
+
111
+ /**
112
+ * @description
113
+ * Alias for `equal`. Compares the current ID against another `UID` instance by value.
114
+ *
115
+ * @param id Another `UID` to compare to.
116
+ * @returns `true` if both IDs share the same value, otherwise `false`.
117
+ */
118
+ public isEqual(id: UID<unknown>): boolean {
119
+ return this.equal(id);
120
+ }
121
+
122
+ /**
123
+ * @description
124
+ * Indicates whether this `ID` instance was created as a new,
125
+ * previously uninitialized ID (no value passed to `create()`), or if it was
126
+ * initialized from a provided value.
127
+ *
128
+ * @returns `true` if the ID is new; otherwise, `false`.
129
+ */
130
+ public isNew(): boolean {
131
+ return this._isNew;
132
+ }
133
+
134
+ /**
135
+ * @description
136
+ * Checks if the current ID is short (16 bytes).
137
+ *
138
+ * @returns `true` if the ID is 16 bytes long, `false` otherwise.
139
+ */
140
+ public isShort(): boolean {
141
+ return this._value.length === this.#maxSize;
142
+ }
143
+
144
+ /**
145
+ * @description
146
+ * Marks the current ID as new.
147
+ */
148
+ private setAsNew(): void {
149
+ this._isNew = true;
150
+ }
151
+
152
+ /**
153
+ * @description
154
+ * Creates a new short (16-byte) ID. If no `id` value is provided,
155
+ * a new UUID is generated and then shortened.
156
+ *
157
+ * @param id An optional string or number to use as the base value.
158
+ * @returns A `UID` instance with a short, 16-byte value.
159
+ */
160
+ public static short(id?: string | number): UID<string> {
161
+ const _id = new ID(id);
162
+ if (typeof id === "undefined") _id.setAsNew();
163
+ _id.toShort();
164
+ return _id;
165
+ }
166
+
167
+ /**
168
+ * @description
169
+ * Convert the current ID value into a shortened, 16-byte version.
170
+ *
171
+ * If the current ID value is shorter than 16 bytes, a UUID is prepended to ensure
172
+ * sufficient length.
173
+ *
174
+ * @returns An updated `ID` instance with a short, 16-byte value.
175
+ */
176
+ public toShort(): UID<string> {
177
+ let short = "";
178
+ let longValue = this._value;
179
+
180
+ if (longValue.length < this.#maxSize) {
181
+ longValue = UUID() + longValue;
182
+ }
183
+
184
+ longValue = longValue.toUpperCase().replace(/-/g, "");
185
+ const chars = longValue.split("");
186
+
187
+ while (short.length < this.#maxSize) {
188
+ const lastChar = chars.pop();
189
+ short = lastChar + short;
190
+ }
191
+ this.#createdAt = new Date();
192
+ this._value = short;
193
+ return this as unknown as UID<string>;
194
+ }
195
+
196
+ /**
197
+ * @description
198
+ * Retrieves the current ID value.
199
+ *
200
+ * @returns The ID value as a string.
201
+ */
202
+ public value(): string {
203
+ return this._value;
204
+ }
205
+ }
206
+
207
+ export const Id = ID.create;
@@ -0,0 +1,5 @@
1
+ export { Aggregate } from "./aggregate";
2
+ export { Entity } from "./entity";
3
+ export { ID, Id } from "./id";
4
+ export { BaseRepository } from "./repository";
5
+ export { ValueObject } from "./value-object";
@@ -0,0 +1,81 @@
1
+ import type { Entity } from "../core/entity";
2
+ import type { IEntityProps } from "../types/entity.types";
3
+ import type { IResult } from "../types/result.types";
4
+ import type { UID } from "../types/uid.types";
5
+
6
+ /**
7
+ * @description
8
+ * Abstract base class defining the standard contract for a domain repository.
9
+ *
10
+ * A repository abstracts the persistence layer, allowing domain code to remain
11
+ * ignorant of storage details. Each aggregate root should have its own repository
12
+ * implementation.
13
+ *
14
+ * Only the methods defined here are guaranteed by the contract. Implementations
15
+ * may expose additional query methods specific to their aggregate.
16
+ *
17
+ * @template T The entity or aggregate type managed by this repository.
18
+ * @template ID The type of the entity's identifier. Defaults to `string`.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * class UserRepository extends BaseRepository<User> {
23
+ * async findById(id: string): Promise<IResult<User, string>> {
24
+ * const row = await db.users.findOne({ id });
25
+ * if (!row) return Result.error('User not found');
26
+ * return User.create(row);
27
+ * }
28
+ *
29
+ * async save(user: User): Promise<IResult<void, string>> {
30
+ * await db.users.upsert(user.toObject());
31
+ * return Result.success();
32
+ * }
33
+ *
34
+ * async delete(id: string): Promise<IResult<void, string>> {
35
+ * await db.users.delete({ id });
36
+ * return Result.success();
37
+ * }
38
+ *
39
+ * async exists(id: string): Promise<boolean> {
40
+ * return db.users.exists({ id });
41
+ * }
42
+ * }
43
+ * ```
44
+ */
45
+ export abstract class BaseRepository<T extends Entity<IEntityProps>, ID = UID> {
46
+ /**
47
+ * @description
48
+ * Finds an entity by its unique identifier.
49
+ *
50
+ * @param id The entity's unique identifier.
51
+ * @returns A `Result` containing the entity if found, or an error if not.
52
+ */
53
+ abstract findById(id: ID): Promise<IResult<T, string>>;
54
+
55
+ /**
56
+ * @description
57
+ * Persists an entity — either inserting or updating as appropriate.
58
+ *
59
+ * @param entity The entity instance to save.
60
+ * @returns A `Result` indicating success or failure.
61
+ */
62
+ abstract save(entity: T): Promise<IResult<void, string>>;
63
+
64
+ /**
65
+ * @description
66
+ * Removes an entity by its unique identifier.
67
+ *
68
+ * @param id The unique identifier of the entity to delete.
69
+ * @returns A `Result` indicating success or failure.
70
+ */
71
+ abstract delete(id: ID): Promise<IResult<void, string>>;
72
+
73
+ /**
74
+ * @description
75
+ * Checks whether an entity with the given identifier exists in the store.
76
+ *
77
+ * @param id The unique identifier to check.
78
+ * @returns `true` if the entity exists; `false` otherwise.
79
+ */
80
+ abstract exists(id: ID): Promise<boolean>;
81
+ }
@@ -0,0 +1,309 @@
1
+ /** biome-ignore-all lint/suspicious/noExplicitAny: TS cannot model private constructors with generic static factories. */
2
+ /** biome-ignore-all lint/complexity/noThisInStatic: Required for polymorphic `this` in base factory (DDD pattern). */
3
+
4
+ import { AutoMapper } from "../helpers/auto-mapper";
5
+ import { DomainError } from "../helpers/domain-error";
6
+ import { GettersAndSetters } from "../helpers/getters-setters";
7
+ import { Result } from "../libs/result";
8
+ import type { Adapter, IAdapter } from "../types/adapter.types";
9
+ import type { IResult } from "../types/result.types";
10
+ import type { UID } from "../types/uid.types";
11
+ import type { AnyObject } from "../types/utils.types";
12
+ import type {
13
+ IValueObjectSettings,
14
+ ValueObjectConstructor,
15
+ } from "../types/value-object.types";
16
+ import { DeepFreeze, StableStringify } from "../utils/object.utils";
17
+
18
+ /**
19
+ * @description
20
+ * Represents a domain object characterized entirely by its properties rather than
21
+ * a unique identifier. Two value objects with identical properties are considered equal.
22
+ *
23
+ * Value objects are immutable by design — mutations return new instances rather than
24
+ * modifying the existing one. They encapsulate domain validation logic, ensuring that
25
+ * only valid states can be represented.
26
+ *
27
+ * Extend this class to define your own value objects with custom business rules:
28
+ * - Override `isValidProps()` to define what constitutes a valid state.
29
+ * - Override `validation()` (inherited) to enforce per-key invariants on `set().to()`.
30
+ * - Use `create()` as the sole public factory — keep constructors `private`.
31
+ * - Use `init()` when you need a throwing factory (e.g., in seeding or tests).
32
+ *
33
+ * @template Props The shape of the value object's properties.
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * interface MoneyProps { amount: number }
38
+ *
39
+ * class Money extends ValueObject<MoneyProps> {
40
+ * private constructor(props: MoneyProps) { super(props); }
41
+ *
42
+ * public static isValidProps({ amount }: MoneyProps): boolean {
43
+ * return amount >= 0;
44
+ * }
45
+ *
46
+ * public static create(amount: number): IResult<Money> {
47
+ * if (!this.isValidProps({ amount })) return Result.error('Amount must be positive');
48
+ * return Result.success(new Money({ amount }));
49
+ * }
50
+ * }
51
+ * ```
52
+ */
53
+ export abstract class ValueObject<Props> extends GettersAndSetters<Props> {
54
+ /**
55
+ * @description
56
+ * Marker used internally by `Validator` to identify this instance as a `ValueObject`
57
+ * without requiring a direct `instanceof` check (avoiding circular imports).
58
+ *
59
+ * @internal
60
+ */
61
+ protected readonly __kind = "ValueObject" as const;
62
+ private readonly autoMapper: AutoMapper;
63
+
64
+ /**
65
+ * @description
66
+ * Initializes a new `ValueObject` instance with the provided props and optional config.
67
+ *
68
+ * @param props The properties that define this value object's state.
69
+ * @param config Optional settings to disable getters or setters.
70
+ */
71
+ constructor(props: Props, config?: IValueObjectSettings) {
72
+ super(props, "ValueObject", { ...config, disableSetters: true });
73
+ this.autoMapper = new AutoMapper();
74
+ }
75
+
76
+ /**
77
+ * @description
78
+ * Creates a deep clone of this value object, optionally overriding some properties.
79
+ *
80
+ * For object-based props, a shallow merge is performed before cloning — the result
81
+ * is always a new instance of the same subclass, not the base `ValueObject`.
82
+ *
83
+ * @param props Optional partial properties to override in the cloned instance.
84
+ * @returns A new instance of the same `ValueObject` subclass with the merged props.
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * const discounted = price.clone({ amount: price.get('amount') * 0.9 });
89
+ * ```
90
+ */
91
+ public clone(props?: Props extends object ? Partial<Props> : never): this {
92
+ const proto = Reflect.getPrototypeOf(this);
93
+ const ctor = proto?.constructor ?? this.constructor;
94
+ if (
95
+ typeof this.props === "object" &&
96
+ !(this.props instanceof Date) &&
97
+ !Array.isArray(this.props)
98
+ ) {
99
+ const merged = props ? { ...this.props, ...props } : { ...this.props };
100
+ return Reflect.construct(ctor, [merged, this.config]);
101
+ }
102
+ return Reflect.construct(ctor, [this.props, this.config]);
103
+ }
104
+
105
+ /**
106
+ * @description
107
+ * Determines structural equality between this value object and another of the same type.
108
+ *
109
+ * Equality is determined by value, not reference. The comparison strategy varies by prop type:
110
+ * - Strings, booleans, numbers, bigints — compared by value.
111
+ * - Dates — compared by timestamp.
112
+ * - Arrays and functions — compared by JSON serialization.
113
+ * - IDs — compared by `.value()`.
114
+ * - Symbols — compared by `.description`.
115
+ * - Objects — compared by JSON serialization, excluding `createdAt` and `updatedAt`.
116
+ *
117
+ * @param other The value object to compare against.
118
+ * @returns `true` if both value objects are structurally equal; `false` otherwise.
119
+ */
120
+ public isEqual(other: this): boolean {
121
+ if (!other || other.__kind !== this.__kind) return false;
122
+ const props = this.props;
123
+ const otherProps = other?.props;
124
+
125
+ const omitTimestamps = (obj: AnyObject): string => {
126
+ if (!obj) return "";
127
+ const { createdAt: _c, updatedAt: _u, ...rest } = obj;
128
+ return StableStringify(rest);
129
+ };
130
+
131
+ if (this.validator.isArray(props) || this.validator.isFunction(props)) {
132
+ return StableStringify(props) === StableStringify(otherProps);
133
+ }
134
+
135
+ if (this.validator.isBoolean(props)) return props === otherProps;
136
+
137
+ if (this.validator.isDate(props)) {
138
+ return (props as Date).getTime() === (otherProps as Date)?.getTime();
139
+ }
140
+
141
+ if (this.validator.isID(props)) {
142
+ return (props as UID).value() === (otherProps as UID)?.value();
143
+ }
144
+
145
+ if (this.validator.isNull(props) || this.validator.isUndefined(props)) {
146
+ return props === otherProps;
147
+ }
148
+
149
+ if (this.validator.isNumber(props) || typeof props === "bigint") {
150
+ return this.validator
151
+ .number(props as number)
152
+ .isEqualTo(otherProps as number);
153
+ }
154
+
155
+ if (this.validator.isString(props)) {
156
+ return this.validator
157
+ .string(props as string)
158
+ .isEqual(otherProps as string);
159
+ }
160
+
161
+ if (this.validator.isSymbol(props)) {
162
+ return (
163
+ (props as symbol).description === (otherProps as symbol)?.description
164
+ );
165
+ }
166
+
167
+ return (
168
+ omitTimestamps(props as AnyObject) ===
169
+ omitTimestamps(otherProps as AnyObject)
170
+ );
171
+ }
172
+
173
+ /**
174
+ * @description
175
+ * Serializes this value object into a plain, deeply frozen object.
176
+ *
177
+ * If an `adapter` is provided, the adapter's transformation is applied instead
178
+ * of the default `AutoMapper` serialization:
179
+ * - `Adapter` (with `adaptOne`) — synchronous transformation.
180
+ * - `IAdapter` (with `build`) — Result-wrapped transformation, value is unwrapped.
181
+ *
182
+ * The default output is a deeply frozen `unknown` — cast to your expected shape
183
+ * at the call site if needed.
184
+ *
185
+ * @param adapter Optional adapter to transform the output into a custom shape.
186
+ * @returns A deeply frozen serialized representation of this value object.
187
+ *
188
+ * @example
189
+ * ```typescript
190
+ * const plain = money.toObject();
191
+ * // { amount: 100 }
192
+ *
193
+ * const dto = money.toObject(new MoneyDtoAdapter());
194
+ * ```
195
+ */
196
+ public toObject<T = Props>(
197
+ adapter?: Adapter<this, T> | IAdapter<this, T>,
198
+ ): Readonly<T> {
199
+ if (
200
+ adapter &&
201
+ typeof (adapter as Adapter<this, T>).adaptOne === "function"
202
+ ) {
203
+ return (adapter as Adapter<this, T>).adaptOne(this);
204
+ }
205
+ if (adapter && typeof (adapter as IAdapter<this, T>).build === "function") {
206
+ return (adapter as IAdapter<this, T>).build(this).value();
207
+ }
208
+
209
+ const mapped = this.autoMapper.valueObjectToObj(this);
210
+ if (typeof mapped === "object" && mapped !== null) {
211
+ return DeepFreeze(mapped) as Readonly<T>;
212
+ }
213
+
214
+ return mapped as Readonly<T>;
215
+ }
216
+
217
+ /**
218
+ * @description
219
+ * Creates a new `ValueObject` instance wrapped in a `Result`.
220
+ *
221
+ * Returns `Result.error()` if `isValidProps()` returns `false`.
222
+ * Subclasses should override both this method and `isValidProps()` to enforce
223
+ * domain-specific construction rules.
224
+ *
225
+ * @param props The properties to validate and construct the value object with.
226
+ * @returns A `Result` containing the new instance on success, or an error on failure.
227
+ *
228
+ * @example
229
+ * ```typescript
230
+ * const result = Money.create(100);
231
+ * if (result.isSuccess()) {
232
+ * const money = result.value();
233
+ * }
234
+ * ```
235
+ */
236
+ public static create<Props, T extends ValueObject<Props>>(
237
+ this: ValueObjectConstructor<Props, T>,
238
+ props: Props,
239
+ ): IResult<T> {
240
+ if (!this.isValidProps(props)) {
241
+ return Result.error(
242
+ `Invalid props for ${this.name}: failed domain validation.`,
243
+ );
244
+ }
245
+
246
+ const instance = new (this as any)(props);
247
+ return Result.success(instance);
248
+ }
249
+
250
+ /**
251
+ * @description
252
+ * Initializes a new `ValueObject` instance directly, throwing a `DomainError` if
253
+ * the provided props are invalid.
254
+ *
255
+ * Prefer `create()` for production code. Use `init()` in tests, seeders, or contexts
256
+ * where you can guarantee the props are valid and prefer exceptions over `Result`.
257
+ *
258
+ * @param props The properties to validate and construct the value object with.
259
+ * @returns A new instance of this value object subclass.
260
+ * @throws {DomainError} If `isValidProps()` returns `false`.
261
+ */
262
+ public static init<Props, T extends ValueObject<Props>>(
263
+ this: ValueObjectConstructor<Props, T>,
264
+ props: Props,
265
+ ): T {
266
+ if (!this.isValidProps(props)) {
267
+ throw new DomainError(`Init: invalid props.`, { context: this.name });
268
+ }
269
+
270
+ const instance = new (this as any)(props);
271
+ return instance;
272
+ }
273
+
274
+ /**
275
+ * @description
276
+ * Alias for `isValidProps()`. Provided for consistency with the `Entity` API.
277
+ * Subclasses should override `isValidProps()` rather than this method.
278
+ *
279
+ * @param value The value to validate.
280
+ * @returns `true` if valid; `false` otherwise.
281
+ */
282
+ public static isValid(value: unknown): boolean {
283
+ return this.isValidProps(value);
284
+ }
285
+
286
+ /**
287
+ * @description
288
+ * Validates the provided props before constructing a new instance.
289
+ *
290
+ * The base implementation accepts anything that is not `null` or `undefined`.
291
+ * Override this in your subclass to enforce domain-specific rules.
292
+ *
293
+ * @param props The props to validate.
294
+ * @returns `true` if the props represent a valid state; `false` otherwise.
295
+ *
296
+ * @example
297
+ * ```typescript
298
+ * public static isValidProps({ amount }: MoneyProps): boolean {
299
+ * return amount >= 0;
300
+ * }
301
+ * ```
302
+ */
303
+ public static isValidProps(props: unknown): boolean {
304
+ return (
305
+ !ValueObject.validator.isUndefined(props) &&
306
+ !ValueObject.validator.isNull(props)
307
+ );
308
+ }
309
+ }