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,385 @@
1
+ import { ID } from "../core/id";
2
+ import utils, { type Utils } from "../libs/utils";
3
+ import validator, { type Validator } from "../libs/validator";
4
+ import type { UID } from "../types/uid.types";
5
+ import type { AnyObject } from "../types/utils.types";
6
+ import { DomainError } from "./domain-error";
7
+
8
+ /**
9
+ * @description
10
+ * Discriminator indicating which domain primitive this instance belongs to.
11
+ * Used internally to apply different mutation behaviors for `ValueObject` vs `Entity`.
12
+ */
13
+ export type ParentKind = "ValueObject" | "Entity";
14
+
15
+ /**
16
+ * @description
17
+ * Configuration options for enabling or disabling getter and setter access
18
+ * on a domain object instance.
19
+ *
20
+ * Disabling getters or setters can be useful when enforcing strict encapsulation
21
+ * or building read-only / write-only domain primitives.
22
+ */
23
+ export interface GettersSettersConfig {
24
+ /**
25
+ * @description
26
+ * When `true`, calling `get()` on this instance will throw a `DomainError`.
27
+ * @default false
28
+ */
29
+ disableGetters?: boolean;
30
+
31
+ /**
32
+ * @description
33
+ * When `true`, calling `set().to()` or `change()` on this instance will throw a `DomainError`.
34
+ * @default false
35
+ */
36
+ disableSetters?: boolean;
37
+ }
38
+
39
+ /**
40
+ * @description
41
+ * Base class providing typed getter, setter, and validation infrastructure
42
+ * for all domain primitives (`ValueObject` and `Entity`).
43
+ *
44
+ * `GettersAndSetters` is not intended to be instantiated directly. It is extended
45
+ * by `ValueObject` and `Entity`, which pass their `parentKind` to enable
46
+ * kind-specific behavior (e.g., auto-updating `updatedAt` on Entity mutations).
47
+ *
48
+ * Subclasses may override `validation()` to enforce per-key domain invariants
49
+ * that are checked automatically on every `set().to()` or `change()` call.
50
+ *
51
+ * @template Props The shape of the domain object's properties.
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * class Age extends ValueObject<{ value: number }> {
56
+ * validation<Key extends keyof { value: number }>(
57
+ * value: { value: number }[Key],
58
+ * key: Key,
59
+ * ): boolean {
60
+ * if (key === 'value') return (value as number) > 0;
61
+ * return true;
62
+ * }
63
+ * }
64
+ * ```
65
+ */
66
+ export abstract class GettersAndSetters<Props> {
67
+ protected static readonly util: Utils = utils;
68
+ protected static readonly validator: Validator = validator;
69
+ protected readonly util: Utils = utils;
70
+ protected readonly validator: Validator = validator;
71
+ protected parentKind: ParentKind = "ValueObject";
72
+ protected config: GettersSettersConfig = {
73
+ disableGetters: false,
74
+ disableSetters: false,
75
+ };
76
+
77
+ constructor(
78
+ protected props: Props,
79
+ parentKind: ParentKind,
80
+ config?: GettersSettersConfig,
81
+ ) {
82
+ this.parentKind = parentKind;
83
+ this.config.disableGetters = !!config?.disableGetters;
84
+ this.config.disableSetters = !!config?.disableSetters;
85
+ }
86
+
87
+ /**
88
+ * @description
89
+ * Handles the special case of assigning a new identity (`id`) to an Entity.
90
+ *
91
+ * Accepts a string, number, or existing `UID` instance. In all cases, the
92
+ * internal `_id` field and `props.id` are updated in sync, and `updatedAt`
93
+ * is refreshed to reflect the mutation.
94
+ *
95
+ * This method is only called when `key === 'id'` and `parentKind === 'Entity'`.
96
+ *
97
+ * @param value The new identity value — a string, number, or `UID` instance.
98
+ */
99
+ private applyEntityId<Key extends keyof Props>(value: Props[Key]): void {
100
+ const self = this as unknown as AnyObject;
101
+
102
+ if (this.validator.isString(value) || this.validator.isNumber(value)) {
103
+ const newId = ID.create(value as string | number);
104
+ self._id = newId;
105
+ (this.props as AnyObject).id = newId.value();
106
+ this.touchUpdatedAt();
107
+ return;
108
+ }
109
+
110
+ if (this.validator.isID(value)) {
111
+ self._id = value;
112
+ (this.props as AnyObject).id = (value as unknown as UID).value();
113
+ this.touchUpdatedAt();
114
+ }
115
+ }
116
+
117
+ /**
118
+ * @description
119
+ * Applies a validated value to the specified property key.
120
+ *
121
+ * Delegates to `applyEntityId()` when the key is `'id'` and the parent
122
+ * is an `Entity`. Otherwise, directly assigns the value and, for entities,
123
+ * updates the `updatedAt` timestamp via `touchUpdatedAt()`.
124
+ *
125
+ * @param key The property key to assign to.
126
+ * @param value The validated value to apply.
127
+ */
128
+ private applyValue<Key extends keyof Props>(
129
+ key: Key,
130
+ value: Props[Key],
131
+ ): void {
132
+ if (key === "id" && this.parentKind === "Entity") {
133
+ this.applyEntityId(value);
134
+ return;
135
+ }
136
+
137
+ (this.props as AnyObject)[key as string] = value;
138
+
139
+ if (this.parentKind === "Entity") {
140
+ this.touchUpdatedAt();
141
+ }
142
+ }
143
+
144
+ /**
145
+ * @description
146
+ * Asserts that setters are currently enabled on this instance.
147
+ * Throws a `DomainError` if `disableSetters` is `true` in the config.
148
+ *
149
+ * @param key The property key being targeted, used to build the error message.
150
+ * @throws {DomainError} If setters are disabled.
151
+ */
152
+ private assertSettersEnabled<Key extends keyof Props>(key: Key): void {
153
+ if (this.config.disableSetters) {
154
+ const name = Reflect.getPrototypeOf(this)?.constructor.name;
155
+ throw new DomainError(`Set: setters are disabled for "${String(key)}".`, {
156
+ context: name,
157
+ field: String(key),
158
+ });
159
+ }
160
+ }
161
+
162
+ /**
163
+ * @description
164
+ * Runs validation for a property assignment, applying both the external
165
+ * validation function (if provided) and the instance's `validation()` hook.
166
+ *
167
+ * External validation is checked first. If it returns `false`, a `DomainError`
168
+ * is thrown immediately without calling `validation()`.
169
+ *
170
+ * @param key The property key being validated.
171
+ * @param value The value being assigned.
172
+ * @param externalValidation An optional caller-provided validation function.
173
+ * @throws {DomainError} If either validation step fails.
174
+ */
175
+ private assertValid<Key extends keyof Props>(
176
+ key: Key,
177
+ value: Props[Key],
178
+ externalValidation?: (value: Props[Key]) => boolean,
179
+ ): void {
180
+ const name = Reflect.getPrototypeOf(this)?.constructor.name;
181
+
182
+ if (
183
+ typeof externalValidation === "function" &&
184
+ !externalValidation(value)
185
+ ) {
186
+ throw new DomainError(`Set: validation failed for "${String(key)}".`, {
187
+ context: name,
188
+ field: String(key),
189
+ });
190
+ }
191
+
192
+ if (!this.validation(value, key)) {
193
+ throw new DomainError(`Set: invariant violated for "${String(key)}".`, {
194
+ context: name,
195
+ field: String(key),
196
+ });
197
+ }
198
+ }
199
+
200
+ /**
201
+ * @description
202
+ * Direct setter — validates then assigns a value to the specified property key.
203
+ * Equivalent to `set(key).to(value, validation)` but without the fluent chain.
204
+ * Throws a `DomainError` if setters are disabled or validation fails.
205
+ *
206
+ * @param key The property key to change.
207
+ * @param value The new value.
208
+ * @param validation Optional validation function.
209
+ * @returns `true` if successfully changed.
210
+ *
211
+ * @throws {DomainError} If setters are disabled or validation fails.
212
+ *
213
+ * @example
214
+ * ```typescript
215
+ * entity.change('age', 25, (age) => age > 0);
216
+ * ```
217
+ */
218
+ public change<Key extends keyof Props>(
219
+ key: Key,
220
+ value: Props[Key],
221
+ validation?: (value: Props[Key]) => boolean,
222
+ ): boolean {
223
+ this.assertSettersEnabled(key);
224
+ this.assertValid(key, value, validation);
225
+ this.applyValue(key, value);
226
+ return true;
227
+ }
228
+
229
+ /**
230
+ * @description
231
+ * Retrieves the value of a specified property key.
232
+ * Throws a `DomainError` if getters are disabled on this instance.
233
+ *
234
+ * For primitive props (string, number, boolean), use the key `'value'`
235
+ * to retrieve the raw value directly.
236
+ *
237
+ * @param key The property key to retrieve.
238
+ * @returns The readonly value of the specified key.
239
+ *
240
+ * @throws {DomainError} If getters are disabled.
241
+ *
242
+ * @example
243
+ * ```typescript
244
+ * const age = person.get('age'); // 30
245
+ * const raw = stringVo.get('value'); // 'hello'
246
+ * ```
247
+ */
248
+ public get<Key extends keyof Props>(key: Key): Readonly<Props[Key]>;
249
+ public get(
250
+ key: "value",
251
+ ): Readonly<"value" extends keyof Props ? Props["value"] : Props>;
252
+ /** biome-ignore lint/suspicious/noExplicitAny: TS cannot correlate overload branches with runtime narrowing. */
253
+ public get(key: any): any {
254
+ if (this.config.disableGetters) {
255
+ const name = Reflect.getPrototypeOf(this)?.constructor.name;
256
+ throw new DomainError(`Get: getters are disabled for "${String(key)}".`, {
257
+ context: name,
258
+ field: String(key),
259
+ });
260
+ }
261
+
262
+ if (key === "value") {
263
+ if (
264
+ this.props &&
265
+ typeof this.props === "object" &&
266
+ "value" in (this.props as AnyObject)
267
+ ) {
268
+ return (this.props as AnyObject).value;
269
+ }
270
+
271
+ if (
272
+ this.validator.isBoolean(this.props) ||
273
+ this.validator.isNumber(this.props) ||
274
+ this.validator.isString(this.props) ||
275
+ this.validator.isDate(this.props)
276
+ ) {
277
+ return this.props;
278
+ }
279
+
280
+ if (this.validator.isSymbol(this.props)) {
281
+ return (this.props as symbol).description;
282
+ }
283
+
284
+ if (this.validator.isID(this.props)) {
285
+ return (this.props as UID).value();
286
+ }
287
+
288
+ if (this.validator.isArray(this.props)) {
289
+ return this.props;
290
+ }
291
+ }
292
+
293
+ const obj = this.props as AnyObject;
294
+
295
+ if (!(key in obj)) {
296
+ const name = Reflect.getPrototypeOf(this)?.constructor.name;
297
+ throw new DomainError(`Get: key "${String(key)}" does not exist.`, {
298
+ context: name,
299
+ field: String(key),
300
+ });
301
+ }
302
+
303
+ return obj[key] ?? null;
304
+ }
305
+
306
+ /**
307
+ * @description
308
+ * Returns the raw properties of the domain instance as a frozen, read-only object.
309
+ *
310
+ * @returns A frozen copy of the internal props.
311
+ */
312
+ public getRaw(): Readonly<Props> {
313
+ return Object.freeze(this.props);
314
+ }
315
+
316
+ /**
317
+ * @description
318
+ * Fluent setter — validates then assigns a value to the specified property key.
319
+ * Throws a `DomainError` if setters are disabled or validation fails.
320
+ *
321
+ * @param key The property key to set.
322
+ * @returns An object with a `to` method to complete the assignment.
323
+ *
324
+ * @example
325
+ * ```typescript
326
+ * entity.set('age').to(30, (age) => age > 0);
327
+ * ```
328
+ */
329
+ public set<Key extends keyof Props>(
330
+ key: Key,
331
+ ): {
332
+ to: (
333
+ value: Props[Key],
334
+ validation?: (value: Props[Key]) => boolean,
335
+ ) => boolean;
336
+ } {
337
+ return {
338
+ to: (
339
+ value: Props[Key],
340
+ validation?: (value: Props[Key]) => boolean,
341
+ ): boolean => {
342
+ this.assertSettersEnabled(key);
343
+ this.assertValid(key, value, validation);
344
+ this.applyValue(key, value);
345
+ return true;
346
+ },
347
+ };
348
+ }
349
+
350
+ /**
351
+ * @description
352
+ * Updates the `updatedAt` field on `props` to the current timestamp.
353
+ *
354
+ * Called automatically after any successful mutation on an `Entity` instance
355
+ * to reflect that the domain object's state has changed.
356
+ */
357
+ private touchUpdatedAt(): void {
358
+ (this.props as AnyObject).updatedAt = new Date();
359
+ }
360
+
361
+ /**
362
+ * @description
363
+ * Hook for subclasses to define per-key validation rules.
364
+ * Called automatically by `set().to()` and `change()` before applying a value.
365
+ * Override this in your domain class to enforce invariants.
366
+ *
367
+ * @param _value The value to validate.
368
+ * @param _key The property key being validated.
369
+ * @returns `true` if the value is valid; `false` otherwise.
370
+ *
371
+ * @example
372
+ * ```typescript
373
+ * validation<Key extends keyof Props>(value: Props[Key], key: Key): boolean {
374
+ * if (key === 'age') return (value as number) > 0;
375
+ * return true;
376
+ * }
377
+ * ```
378
+ */
379
+ public validation<Key extends keyof Props>(
380
+ _value: Props[Key],
381
+ _key: Key,
382
+ ): boolean {
383
+ return true;
384
+ }
385
+ }
@@ -0,0 +1,7 @@
1
+ export type { EntityAutoMapperPayload } from "./auto-mapper";
2
+ export { AutoMapper } from "./auto-mapper";
3
+ export type { CreateManyResult } from "./domain-classes";
4
+ export { DomainClasses } from "./domain-classes";
5
+ export { DomainError } from "./domain-error";
6
+ export type { GettersSettersConfig, ParentKind } from "./getters-setters";
7
+ export { GettersAndSetters } from "./getters-setters";
@@ -0,0 +1,73 @@
1
+ // ── Core domain primitives ─────────────────────────────────────────────────
2
+ export { Aggregate } from "./core/aggregate";
3
+ export { Entity } from "./core/entity";
4
+ export { ID, Id } from "./core/id";
5
+ export { BaseRepository } from "./core/repository";
6
+ export { ValueObject } from "./core/value-object";
7
+ // ── Event system ───────────────────────────────────────────────────────────
8
+ export type { WindowLike } from "./events/browser-event-manager";
9
+ export { BrowserEventManager } from "./events/browser-event-manager";
10
+ export { BaseDomainEvent } from "./events/domain-event";
11
+ export { EventBus } from "./events/event-bus";
12
+ export { EventContext } from "./events/event-context";
13
+ export { BaseEventManager } from "./events/event-manager";
14
+ export { ValidateEventName } from "./events/event-utils";
15
+ export { ServerEventManager } from "./events/server-event-manager";
16
+ // ── Helpers ────────────────────────────────────────────────────────────────
17
+ export type { EntityAutoMapperPayload } from "./helpers/auto-mapper";
18
+ export { AutoMapper } from "./helpers/auto-mapper";
19
+ export type { CreateManyResult } from "./helpers/domain-classes";
20
+ export { DomainClasses } from "./helpers/domain-classes";
21
+ export { DomainError } from "./helpers/domain-error";
22
+ export type {
23
+ GettersSettersConfig,
24
+ ParentKind,
25
+ } from "./helpers/getters-setters";
26
+ export { GettersAndSetters } from "./helpers/getters-setters";
27
+
28
+ // ── Libs ───────────────────────────────────────────────────────────────────
29
+ export { Iterator } from "./libs/iterator";
30
+ export { Result } from "./libs/result";
31
+ export { Utils } from "./libs/utils";
32
+ export { Validator } from "./libs/validator";
33
+
34
+ // ── Types ──────────────────────────────────────────────────────────────────
35
+ export type { Adapter, IAdapter } from "./types/adapter.types";
36
+ export type { ICommand, IQuery, IUseCase } from "./types/command.types";
37
+ export type {
38
+ AggregateConstructor,
39
+ EntityConstructor,
40
+ EntityProps,
41
+ IEntityProps,
42
+ IEntitySettings,
43
+ } from "./types/entity.types";
44
+ export type {
45
+ DomainEvent,
46
+ DomainEventPayload,
47
+ EventEntry,
48
+ IEventBus,
49
+ } from "./types/event.types";
50
+ export type { IIterator, IIteratorConfig } from "./types/iterator.types";
51
+ export type {
52
+ IResult,
53
+ IResultExecuteFn,
54
+ IResultHook,
55
+ IResultObject,
56
+ IResultOption,
57
+ } from "./types/result.types";
58
+ export type { UID } from "./types/uid.types";
59
+ export type {
60
+ AnyObject,
61
+ BuiltIns,
62
+ CalcOpt,
63
+ Primitive,
64
+ ReadonlyDeep,
65
+ } from "./types/utils.types";
66
+ export type {
67
+ IValueObjectSettings,
68
+ ValueObjectConstructor,
69
+ } from "./types/value-object.types";
70
+
71
+ // ── Utils ──────────────────────────────────────────────────────────────────
72
+ export { DeepFreeze, StableStringify } from "./utils/object.utils";
73
+ export { InvalidPropsType } from "./utils/type.utils";
@@ -0,0 +1,33 @@
1
+ import * as crypto from "node:crypto";
2
+
3
+ const customCrypto = {
4
+ randomUUID: (): string => {
5
+ const hexChars = "0123456789abcdef";
6
+ let uuid = "";
7
+ for (let i = 0; i < 36; i++) {
8
+ if (i === 8 || i === 13 || i === 18 || i === 23) {
9
+ uuid += "-";
10
+ } else if (i === 14) {
11
+ uuid += "4";
12
+ } else if (i === 19) {
13
+ uuid += hexChars.charAt(Math.floor(Math.random() * 4) + 8);
14
+ } else {
15
+ uuid += hexChars.charAt(Math.floor(Math.random() * 16));
16
+ }
17
+ }
18
+ return uuid;
19
+ },
20
+ };
21
+
22
+ // Node.js
23
+ if (typeof process !== "undefined" && crypto?.randomUUID) {
24
+ customCrypto.randomUUID = crypto.randomUUID;
25
+ }
26
+ // Browser
27
+ else if (typeof globalThis !== "undefined" && globalThis.crypto?.randomUUID) {
28
+ customCrypto.randomUUID = globalThis.crypto.randomUUID.bind(
29
+ globalThis.crypto,
30
+ );
31
+ }
32
+
33
+ export const UUID = customCrypto.randomUUID;
@@ -0,0 +1,5 @@
1
+ export { UUID } from "./crypto";
2
+ export { Iterator } from "./iterator";
3
+ export { Result } from "./result";
4
+ export { Utils } from "./utils";
5
+ export { Validator } from "./validator";