@woltz/rich-domain 1.9.0 → 1.9.2

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 (144) hide show
  1. package/dist/cjs/core/aggregate-changes.d.ts +14 -0
  2. package/dist/cjs/core/aggregate-changes.d.ts.map +1 -1
  3. package/dist/cjs/core/aggregate-changes.js +18 -0
  4. package/dist/cjs/core/aggregate-changes.js.map +1 -1
  5. package/dist/cjs/core/base-entity.d.ts +2 -0
  6. package/dist/cjs/core/base-entity.d.ts.map +1 -1
  7. package/dist/cjs/core/base-entity.js +39 -41
  8. package/dist/cjs/core/base-entity.js.map +1 -1
  9. package/dist/cjs/core/change-tracker.d.ts +8 -0
  10. package/dist/cjs/core/change-tracker.d.ts.map +1 -1
  11. package/dist/cjs/core/change-tracker.js +36 -6
  12. package/dist/cjs/core/change-tracker.js.map +1 -1
  13. package/dist/cjs/core/domain-event.d.ts +3 -0
  14. package/dist/cjs/core/domain-event.d.ts.map +1 -1
  15. package/dist/cjs/core/domain-event.js +8 -1
  16. package/dist/cjs/core/domain-event.js.map +1 -1
  17. package/dist/cjs/core/value-object.d.ts.map +1 -1
  18. package/dist/cjs/core/value-object.js +3 -5
  19. package/dist/cjs/core/value-object.js.map +1 -1
  20. package/dist/cjs/criteria.d.ts +1 -1
  21. package/dist/cjs/index.d.ts +1 -1
  22. package/dist/cjs/index.d.ts.map +1 -1
  23. package/dist/cjs/repository/entity-schema-registry.d.ts +56 -3
  24. package/dist/cjs/repository/entity-schema-registry.d.ts.map +1 -1
  25. package/dist/cjs/repository/entity-schema-registry.js +61 -6
  26. package/dist/cjs/repository/entity-schema-registry.js.map +1 -1
  27. package/dist/cjs/types/index.d.ts +1 -0
  28. package/dist/cjs/types/index.d.ts.map +1 -1
  29. package/dist/cjs/types/index.js +1 -0
  30. package/dist/cjs/types/index.js.map +1 -1
  31. package/dist/cjs/types/outbox-store.d.ts +91 -0
  32. package/dist/cjs/types/outbox-store.d.ts.map +1 -0
  33. package/dist/cjs/types/outbox-store.js +3 -0
  34. package/dist/cjs/types/outbox-store.js.map +1 -0
  35. package/dist/cjs/utils/helpers.d.ts +1 -0
  36. package/dist/cjs/utils/helpers.d.ts.map +1 -1
  37. package/dist/cjs/utils/helpers.js +4 -0
  38. package/dist/cjs/utils/helpers.js.map +1 -1
  39. package/dist/esm/core/aggregate-changes.d.ts +14 -0
  40. package/dist/esm/core/aggregate-changes.d.ts.map +1 -1
  41. package/dist/esm/core/aggregate-changes.js +18 -0
  42. package/dist/esm/core/aggregate-changes.js.map +1 -1
  43. package/dist/esm/core/base-entity.d.ts +2 -0
  44. package/dist/esm/core/base-entity.d.ts.map +1 -1
  45. package/dist/esm/core/base-entity.js +37 -39
  46. package/dist/esm/core/base-entity.js.map +1 -1
  47. package/dist/esm/core/change-tracker.d.ts +8 -0
  48. package/dist/esm/core/change-tracker.d.ts.map +1 -1
  49. package/dist/esm/core/change-tracker.js +36 -6
  50. package/dist/esm/core/change-tracker.js.map +1 -1
  51. package/dist/esm/core/domain-event.d.ts +3 -0
  52. package/dist/esm/core/domain-event.d.ts.map +1 -1
  53. package/dist/esm/core/domain-event.js +5 -1
  54. package/dist/esm/core/domain-event.js.map +1 -1
  55. package/dist/esm/core/value-object.d.ts.map +1 -1
  56. package/dist/esm/core/value-object.js +1 -3
  57. package/dist/esm/core/value-object.js.map +1 -1
  58. package/dist/esm/criteria.d.ts +1 -1
  59. package/dist/esm/index.d.ts +1 -1
  60. package/dist/esm/index.d.ts.map +1 -1
  61. package/dist/esm/repository/entity-schema-registry.d.ts +56 -3
  62. package/dist/esm/repository/entity-schema-registry.d.ts.map +1 -1
  63. package/dist/esm/repository/entity-schema-registry.js +61 -6
  64. package/dist/esm/repository/entity-schema-registry.js.map +1 -1
  65. package/dist/esm/types/index.d.ts +1 -0
  66. package/dist/esm/types/index.d.ts.map +1 -1
  67. package/dist/esm/types/index.js +1 -0
  68. package/dist/esm/types/index.js.map +1 -1
  69. package/dist/esm/types/outbox-store.d.ts +91 -0
  70. package/dist/esm/types/outbox-store.d.ts.map +1 -0
  71. package/dist/esm/types/outbox-store.js +2 -0
  72. package/dist/esm/types/outbox-store.js.map +1 -0
  73. package/dist/esm/utils/helpers.d.ts +1 -0
  74. package/dist/esm/utils/helpers.d.ts.map +1 -1
  75. package/dist/esm/utils/helpers.js +3 -0
  76. package/dist/esm/utils/helpers.js.map +1 -1
  77. package/dist/tsconfig.cjs.tsbuildinfo +1 -1
  78. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  79. package/dist/tsconfig.types.tsbuildinfo +1 -1
  80. package/dist/types/core/aggregate-changes.d.ts +14 -0
  81. package/dist/types/core/aggregate-changes.d.ts.map +1 -1
  82. package/dist/types/core/base-entity.d.ts +2 -0
  83. package/dist/types/core/base-entity.d.ts.map +1 -1
  84. package/dist/types/core/change-tracker.d.ts +8 -0
  85. package/dist/types/core/change-tracker.d.ts.map +1 -1
  86. package/dist/types/core/domain-event.d.ts +3 -0
  87. package/dist/types/core/domain-event.d.ts.map +1 -1
  88. package/dist/types/core/value-object.d.ts.map +1 -1
  89. package/dist/types/criteria.d.ts +1 -1
  90. package/dist/types/index.d.ts +1 -1
  91. package/dist/types/index.d.ts.map +1 -1
  92. package/dist/types/repository/entity-schema-registry.d.ts +56 -3
  93. package/dist/types/repository/entity-schema-registry.d.ts.map +1 -1
  94. package/dist/types/types/index.d.ts +1 -0
  95. package/dist/types/types/index.d.ts.map +1 -1
  96. package/dist/types/types/outbox-store.d.ts +91 -0
  97. package/dist/types/types/outbox-store.d.ts.map +1 -0
  98. package/dist/types/utils/helpers.d.ts +1 -0
  99. package/dist/types/utils/helpers.d.ts.map +1 -1
  100. package/package.json +68 -67
  101. package/src/constants.ts +82 -0
  102. package/src/core/aggregate-changes.ts +466 -0
  103. package/src/core/base-aggregate.ts +76 -0
  104. package/src/core/base-entity.ts +552 -0
  105. package/src/core/change-tracker.ts +1327 -0
  106. package/src/core/domain-event.ts +41 -0
  107. package/src/core/entity-changes.ts +146 -0
  108. package/src/core/entity.ts +13 -0
  109. package/src/core/id.ts +124 -0
  110. package/src/core/index.ts +9 -0
  111. package/src/core/value-object.ts +179 -0
  112. package/src/criteria.ts +574 -0
  113. package/src/exceptions.ts +549 -0
  114. package/src/index.ts +74 -0
  115. package/src/repository/base-repository.ts +81 -0
  116. package/src/repository/entity-schema-registry.ts +620 -0
  117. package/src/repository/index.ts +5 -0
  118. package/src/repository/mapper.ts +7 -0
  119. package/src/repository/paginated-result.ts +251 -0
  120. package/src/repository/unit-of-work.ts +76 -0
  121. package/src/types/change-tracker.ts +268 -0
  122. package/src/types/criteria.ts +197 -0
  123. package/src/types/domain-event.ts +29 -0
  124. package/src/types/domain.ts +41 -0
  125. package/src/types/event-bus.ts +17 -0
  126. package/src/types/index.ts +9 -0
  127. package/src/types/outbox-store.ts +97 -0
  128. package/src/types/standard-schema.ts +19 -0
  129. package/src/types/unit-of-work.ts +46 -0
  130. package/src/types/utils.ts +24 -0
  131. package/src/utils/criteria-operator-validation.ts +209 -0
  132. package/src/utils/crypto.ts +31 -0
  133. package/src/utils/helpers.ts +50 -0
  134. package/src/validation-error.ts +219 -0
  135. package/dist/cjs/t.d.ts +0 -2
  136. package/dist/cjs/t.d.ts.map +0 -1
  137. package/dist/cjs/t.js +0 -96
  138. package/dist/cjs/t.js.map +0 -1
  139. package/dist/esm/t.d.ts +0 -2
  140. package/dist/esm/t.d.ts.map +0 -1
  141. package/dist/esm/t.js +0 -94
  142. package/dist/esm/t.js.map +0 -1
  143. package/dist/types/t.d.ts +0 -2
  144. package/dist/types/t.d.ts.map +0 -1
@@ -0,0 +1,197 @@
1
+ import { Primitive } from "./utils.js";
2
+
3
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
4
+ const FILTER_OPERATORS = [
5
+ "equals",
6
+ "notEquals",
7
+ "greaterThan",
8
+ "greaterThanOrEqual",
9
+ "lessThan",
10
+ "lessThanOrEqual",
11
+ "contains",
12
+ "startsWith",
13
+ "endsWith",
14
+ "in",
15
+ "notIn",
16
+ "between",
17
+ "isNull",
18
+ "isNotNull",
19
+ ] as const;
20
+
21
+ export type FilterOperator = (typeof FILTER_OPERATORS)[number];
22
+
23
+ export type StringOperators =
24
+ | "equals"
25
+ | "notEquals"
26
+ | "contains"
27
+ | "startsWith"
28
+ | "endsWith"
29
+ | "in"
30
+ | "notIn"
31
+ | "isNull"
32
+ | "isNotNull";
33
+
34
+ export type NumberOperators =
35
+ | "equals"
36
+ | "notEquals"
37
+ | "greaterThan"
38
+ | "greaterThanOrEqual"
39
+ | "lessThan"
40
+ | "lessThanOrEqual"
41
+ | "in"
42
+ | "notIn"
43
+ | "between"
44
+ | "isNull"
45
+ | "isNotNull";
46
+
47
+ export type DateOperators =
48
+ | "equals"
49
+ | "notEquals"
50
+ | "greaterThan"
51
+ | "greaterThanOrEqual"
52
+ | "lessThan"
53
+ | "lessThanOrEqual"
54
+ | "in"
55
+ | "notIn"
56
+ | "between"
57
+ | "isNull"
58
+ | "isNotNull";
59
+
60
+ export type BooleanOperators = "equals" | "notEquals" | "isNull" | "isNotNull";
61
+
62
+ export type ArrayOperators = "in" | "notIn" | "isNull" | "isNotNull";
63
+
64
+ export type OperatorsForType<T> = T extends string
65
+ ? StringOperators
66
+ : T extends number
67
+ ? NumberOperators
68
+ : T extends Date
69
+ ? DateOperators
70
+ : T extends boolean
71
+ ? BooleanOperators
72
+ : T extends Array<any>
73
+ ? ArrayOperators
74
+ : FilterOperator;
75
+
76
+ export type FilterValueFor<T> =
77
+ | T
78
+ | (T extends number | Date ? [T, T] : never)
79
+ | T[]
80
+ | null;
81
+
82
+ /**
83
+ * Resolve individual entity prop types for criteria:
84
+ * - Date → Date (preserved, so DateOperators are used)
85
+ * - Array<U> → recurse into elements
86
+ * - Id / ValueObject (value + equals) → unwrap to primitive value
87
+ * - Everything else → keep as-is
88
+ */
89
+ type ResolveEntityProp<T> = T extends Date
90
+ ? Date
91
+ : T extends Array<infer U>
92
+ ? ResolveEntityProp<U>[]
93
+ : T extends { value: infer V; equals(...args: any[]): any }
94
+ ? V
95
+ : T;
96
+
97
+ /**
98
+ * Extract the plain object type from an entity for criteria field resolution.
99
+ * - For entities (with `props`), resolves from raw props preserving Date types.
100
+ * - For objects with toJSON(), uses the return type of toJSON().
101
+ * - Otherwise, uses the type as is.
102
+ */
103
+ type ExtractPlainType<T> = T extends { props: infer P }
104
+ ? { [K in keyof P]: ResolveEntityProp<P[K]> }
105
+ : T extends { toJSON(): infer R }
106
+ ? R
107
+ : T;
108
+
109
+ export type PathValue<
110
+ T,
111
+ P extends string,
112
+ > = P extends `${infer K}.${infer Rest}`
113
+ ? K extends keyof ExtractPlainType<T>
114
+ ? ExtractPlainType<T>[K] extends Array<infer U>
115
+ ? PathValue<U, Rest>
116
+ : PathValue<ExtractPlainType<T>[K], Rest>
117
+ : never
118
+ : P extends keyof ExtractPlainType<T>
119
+ ? ExtractPlainType<T>[P]
120
+ : never;
121
+
122
+ export interface Filter<TField = string, TValue = unknown> {
123
+ field: TField;
124
+ operator: unknown extends TValue ? FilterOperator : OperatorsForType<TValue>;
125
+ value: TValue;
126
+ options?: CriteriaOptions;
127
+ }
128
+
129
+ export type TypedFilter<T> = {
130
+ [K in FieldPath<T>]: {
131
+ field: K;
132
+ operator: OperatorsForType<NonNullable<PathValue<T, K>>>;
133
+ value: FilterValueFor<NonNullable<PathValue<T, K>>>;
134
+ options?: CriteriaOptions;
135
+ };
136
+ }[FieldPath<T>];
137
+
138
+ export type CriteriaAdapter<Input, Output> = {
139
+ [K in FieldPath<Input>]?: FieldPath<Output>;
140
+ };
141
+
142
+ export type OrderDirection = "asc" | "desc";
143
+
144
+ export interface Order {
145
+ field: string;
146
+ direction: OrderDirection;
147
+ }
148
+
149
+ export type TypedOrder<T> = {
150
+ field: FieldPath<T>;
151
+ direction: OrderDirection;
152
+ };
153
+
154
+ export interface Pagination {
155
+ page: number;
156
+ limit: number;
157
+ offset: number;
158
+ }
159
+
160
+ export type Search = string;
161
+
162
+ export type QueryParamsObject = {
163
+ filters?: Record<string, unknown>;
164
+ pagination?: Omit<Pagination, "offset">;
165
+ orderBy?: string[];
166
+ search?: string;
167
+ };
168
+
169
+ export interface PaginationMeta {
170
+ page: number;
171
+ limit: number;
172
+ total: number;
173
+ totalPages: number;
174
+ hasNext: boolean;
175
+ hasPrevious: boolean;
176
+ }
177
+
178
+ export interface CriteriaOptions {
179
+ quantifier?: "some" | "every" | "none";
180
+ }
181
+
182
+ type ExcludeBuiltInKeys<T> = Exclude<keyof T, keyof any[] | number | symbol>;
183
+
184
+ export type FieldPath<T> =
185
+ ExtractPlainType<T> extends Primitive
186
+ ? never
187
+ : {
188
+ [K in ExcludeBuiltInKeys<ExtractPlainType<T>> & string]: NonNullable<
189
+ ExtractPlainType<T>[K]
190
+ > extends Primitive
191
+ ? K
192
+ : NonNullable<ExtractPlainType<T>[K]> extends Array<infer U>
193
+ ? U extends Primitive
194
+ ? K
195
+ : K | `${K}.${FieldPath<U>}`
196
+ : `${K}.${FieldPath<NonNullable<ExtractPlainType<T>[K]>>}`;
197
+ }[ExcludeBuiltInKeys<ExtractPlainType<T>> & string];
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Interface for all domain events
3
+ */
4
+ export interface IDomainEvent<P = unknown> {
5
+ /**
6
+ * Unique identifier for this event occurrence
7
+ */
8
+ readonly eventId: string;
9
+
10
+ /**
11
+ * When the event occurred
12
+ */
13
+ readonly occurredOn: Date;
14
+
15
+ /**
16
+ * Name/type of the event (e.g., "UserCreated", "OrderPlaced")
17
+ */
18
+ readonly eventName: string;
19
+
20
+ /**
21
+ * Queue name for the event (optional)
22
+ */
23
+ readonly queueName?: string;
24
+
25
+ /**
26
+ * Payload of the event
27
+ */
28
+ readonly payload: P;
29
+ }
@@ -0,0 +1,41 @@
1
+ import { Id } from "../core/index";
2
+ import { StandardSchema } from "./standard-schema.js";
3
+
4
+ export interface BaseProps {
5
+ id: Id;
6
+ }
7
+
8
+ interface DomainValidation<T> {
9
+ schema: StandardSchema<T>;
10
+ config?: ValidationConfig;
11
+ }
12
+
13
+ export type EntityValidation<T> = DomainValidation<T>;
14
+ export type VOValidation<T> = DomainValidation<T>;
15
+
16
+ export interface VOHooks<V, VO> {
17
+ onBeforeCreate?: (value: V) => void;
18
+ rules?: (valueObject: VO) => void;
19
+ }
20
+
21
+ export interface EntityHooks<T extends BaseProps, E> {
22
+ onBeforeCreate?: (props: T) => void;
23
+ onBeforeUpdate?: (entity: E, snapshot: T) => boolean;
24
+ onCreate?: (entity: E) => void;
25
+ rules?: (entity: E) => void;
26
+ }
27
+
28
+ export interface ValidationConfig {
29
+ /** When true, validates the entity on creation. Default: true */
30
+ onCreate?: boolean;
31
+ /** When true, validates the entity on update. Default: true */
32
+ onUpdate?: boolean;
33
+ /** When true, throws a ValidationError when validation fails. Default: true */
34
+ throwOnError?: boolean;
35
+ /**
36
+ * When `throwOnError` is `false`, controls whether invalid updates are kept on the entity.
37
+ * - `true` (default): apply mutations and refresh `validationErrors` (dirty / form mode).
38
+ * - `false`: block mutations when errors already exist, and revert updates that fail validation.
39
+ */
40
+ persistInvalidMutations?: boolean;
41
+ }
@@ -0,0 +1,17 @@
1
+ import { IDomainEvent } from "./domain-event";
2
+
3
+ export interface IDomainEventBus {
4
+ /**
5
+ * Publish a single domain event.
6
+ *
7
+ * @param event - The domain event to publish
8
+ */
9
+ publish(event: IDomainEvent): Promise<void>;
10
+
11
+ /**
12
+ * Publish multiple domain events.
13
+ *
14
+ * @param events - Array of domain events to publish
15
+ */
16
+ publishAll(events: IDomainEvent[]): Promise<void>;
17
+ }
@@ -0,0 +1,9 @@
1
+ export * from "./criteria.js";
2
+ export * from "./domain.js";
3
+ export * from "./standard-schema.js";
4
+ export * from "./utils.js";
5
+ export * from "./unit-of-work.js";
6
+ export * from "./domain-event.js";
7
+ export * from "./change-tracker.js";
8
+ export * from "./event-bus.js";
9
+ export * from "./outbox-store.js";
@@ -0,0 +1,97 @@
1
+ import { IDomainEvent } from "./domain-event.js";
2
+
3
+ /**
4
+ * Possible statuses for an outbox entry.
5
+ *
6
+ * - `pending`: The event has been persisted but not yet published.
7
+ * - `published`: The event has been successfully published to the message broker.
8
+ * - `failed`: The event failed to publish after the maximum number of retries.
9
+ */
10
+ export type OutboxStatus = "pending" | "published" | "failed";
11
+
12
+ /**
13
+ * The data shape of a single outbox entry returned by {@link IOutboxStore.fetchPending}.
14
+ * This is the flat wire format — no class logic, just data.
15
+ */
16
+ export interface OutboxEntryData {
17
+ /** The event's own `eventId`, used as the primary key of the outbox table. */
18
+ id: string;
19
+ /** The event class name (e.g. `"OrderPlaced"`). */
20
+ eventName: string;
21
+ /** The event payload (the `P` generic from `DomainEvent<P>`). */
22
+ payload: unknown;
23
+ /** When the domain event was originally created. */
24
+ occurredOn: Date;
25
+ /** Current publish status. */
26
+ status: OutboxStatus;
27
+ /** Number of publish attempts so far. */
28
+ retries: number;
29
+ /** Error message from the most recent failed publish attempt, if any. */
30
+ lastError: string | null;
31
+ /** When this outbox row was inserted. */
32
+ createdAt: Date;
33
+ }
34
+
35
+ /**
36
+ * Result returned by {@link IOutboxStore.fetchPending}.
37
+ */
38
+ export interface OutboxFetchResult {
39
+ entries: OutboxEntryData[];
40
+ }
41
+
42
+ /**
43
+ * Minimal, framework-agnostic contract for an outbox persistence store.
44
+ *
45
+ * Implementations exist for Prisma, Drizzle, TypeORM, and raw SQL.
46
+ * The interface lives in `@woltz/rich-domain` (core) so that the
47
+ * `@woltz/rich-domain-outbox` package can depend on it without
48
+ * coupling to any specific ORM or database.
49
+ */
50
+ export interface IOutboxStore {
51
+ /**
52
+ * Persist one or more domain events to the outbox table.
53
+ *
54
+ * MUST be called within the same database transaction as the aggregate write
55
+ * so that both succeed or both roll back together.
56
+ *
57
+ * Each event's `eventId` becomes the primary key (`id`) of the outbox row.
58
+ * The `status` column is always set to `"pending"` initially.
59
+ *
60
+ * @param events - The domain events to persist.
61
+ * @param tx - Optional ORM-specific transaction context (e.g. a Prisma interactive tx client, a Drizzle tx, or a TypeORM EntityManager).
62
+ */
63
+ save(events: IDomainEvent[], tx?: unknown): Promise<void>;
64
+
65
+ /**
66
+ * Fetch a batch of events whose status is still `"pending"`.
67
+ * Entries are returned in insertion order (oldest first) so that
68
+ * earlier events are retried before later ones.
69
+ *
70
+ * The publisher calls this periodically and attempts to publish each entry.
71
+ * On success it calls {@link markPublished}; on failure it calls {@link markFailed}.
72
+ *
73
+ * @param batchSize - Maximum number of entries to return. Default 50.
74
+ * @returns A result object containing the matching entries.
75
+ */
76
+ fetchPending(batchSize?: number): Promise<OutboxFetchResult>;
77
+
78
+ /**
79
+ * Mark an outbox entry as successfully published.
80
+ *
81
+ * Mapping: `UPDATE outbox SET status = 'published' WHERE id = :eventId`
82
+ *
83
+ * @param id - The event's `eventId` (same value used as the outbox primary key).
84
+ */
85
+ markPublished(id: string): Promise<void>;
86
+
87
+ /**
88
+ * Mark an outbox entry as failed, incrementing its retry counter
89
+ * and recording the error that caused the failure.
90
+ *
91
+ * Mapping: `UPDATE outbox SET status = 'failed', retries = retries + 1, lastError = :error WHERE id = :eventId`
92
+ *
93
+ * @param id - The event's `eventId`.
94
+ * @param error - The error message.
95
+ */
96
+ markFailed(id: string, error: string): Promise<void>;
97
+ }
@@ -0,0 +1,19 @@
1
+ export interface StandardSchemaIssue {
2
+ message: string;
3
+ path?: ReadonlyArray<unknown>;
4
+ }
5
+
6
+ export interface StandardSchemaResult<T> {
7
+ value?: T;
8
+ issues?: ReadonlyArray<StandardSchemaIssue>;
9
+ }
10
+
11
+ export interface StandardSchemaProps<T> {
12
+ validate: (
13
+ value: unknown
14
+ ) => StandardSchemaResult<T> | Promise<StandardSchemaResult<T>>;
15
+ }
16
+
17
+ export interface StandardSchema<T = unknown> {
18
+ "~standard": StandardSchemaProps<T>;
19
+ }
@@ -0,0 +1,46 @@
1
+ import { Aggregate } from "../core/index";
2
+ import { Repository } from "../repository/base-repository.js";
3
+
4
+ /**
5
+ * Transaction context for Unit of Work
6
+ */
7
+ export interface TransactionContext {
8
+ /**
9
+ * Commit all changes
10
+ */
11
+ commit(): Promise<void>;
12
+
13
+ /**
14
+ * Rollback all changes
15
+ */
16
+ rollback(): Promise<void>;
17
+
18
+ /**
19
+ * Check if transaction is active
20
+ */
21
+ isActive(): boolean;
22
+ }
23
+
24
+ /**
25
+ * Unit of Work interface
26
+ * Manages transactions across multiple repositories
27
+ */
28
+ export interface IUnitOfWork {
29
+ /**
30
+ * Start a new transaction
31
+ */
32
+ begin(): Promise<TransactionContext>;
33
+
34
+ /**
35
+ * Execute work within a transaction
36
+ * Auto-commits on success, rolls back on error
37
+ */
38
+ transaction<T>(work: (ctx: TransactionContext) => Promise<T>): Promise<T>;
39
+
40
+ /**
41
+ * Get repository within transaction context
42
+ */
43
+ getRepository<TDomain extends Aggregate<any>>(
44
+ repository: new (...args: any[]) => Repository<TDomain>
45
+ ): Repository<TDomain>;
46
+ }
@@ -0,0 +1,24 @@
1
+ import { Id, ValueObject } from "../core/index";
2
+
3
+ type JsonPrimitive = string | number | boolean | null;
4
+
5
+ export type DeepJsonResult<T> = T extends Id
6
+ ? string
7
+ : T extends ValueObject<infer U>
8
+ ? U
9
+ : T extends Date
10
+ ? string
11
+ : T extends Array<infer U>
12
+ ? DeepJsonResult<U>[]
13
+ : T extends { toJSON(): infer R }
14
+ ? DeepJsonResult<R>
15
+ : T extends object
16
+ ? { [K in keyof T]: DeepJsonResult<T[K]> }
17
+ : T extends JsonPrimitive
18
+ ? T
19
+ : never;
20
+
21
+ export type Primitive = string | number | boolean | Date | null | undefined;
22
+ export type UnwrapArray<T> = T extends Array<infer U> ? U : never;
23
+ export type IsArray<T> = T extends Array<any> ? true : false;
24
+ export type NonUndefined<T> = T extends undefined ? never : T;
@@ -0,0 +1,209 @@
1
+ import { FILTER_OPERATORS } from "../index.js";
2
+ import {
3
+ ArrayOperators,
4
+ BooleanOperators,
5
+ DateOperators,
6
+ FilterOperator,
7
+ NumberOperators,
8
+ StringOperators,
9
+ } from "../types/index.js";
10
+
11
+ const FORCE_STRING_OPERATORS = new Set(["contains", "startsWith", "endsWith"]);
12
+
13
+ export function sanitizeFieldValue(
14
+ value: unknown,
15
+ operator: FilterOperator
16
+ ): unknown {
17
+ if (value === null || value === undefined) {
18
+ return value;
19
+ }
20
+
21
+ if (Array.isArray(value)) {
22
+ return value.map((item) => sanitizeFieldValue(item, operator));
23
+ }
24
+
25
+ if (value instanceof Date) {
26
+ return value;
27
+ }
28
+
29
+ const stringValue = String(value).trim();
30
+
31
+ if (stringValue === "") {
32
+ return "";
33
+ }
34
+
35
+ if (operator && FORCE_STRING_OPERATORS.has(operator)) {
36
+ return stringValue;
37
+ }
38
+
39
+ if (stringValue === "true" || stringValue === "false") {
40
+ return stringValue === "true";
41
+ }
42
+
43
+ const numberValue = Number(stringValue);
44
+ if (!Number.isNaN(numberValue)) {
45
+ return numberValue;
46
+ }
47
+
48
+ const dateObj = new Date(stringValue);
49
+ if (!Number.isNaN(dateObj.getTime())) {
50
+ return dateObj;
51
+ }
52
+
53
+ return stringValue;
54
+ }
55
+
56
+ export function isValidOperatorForType(
57
+ value: unknown,
58
+ operator: FilterOperator
59
+ ): boolean {
60
+ const sanitizedValue = sanitizeFieldValue(value, operator);
61
+
62
+ if (
63
+ operator === "between" &&
64
+ Array.isArray(sanitizedValue) &&
65
+ sanitizedValue.length === 2
66
+ ) {
67
+ const elementType = typeof sanitizedValue[0];
68
+ if (elementType === "number" || sanitizedValue[0] instanceof Date) {
69
+ return true;
70
+ }
71
+ return false;
72
+ }
73
+
74
+ const valueType = typeof sanitizedValue;
75
+
76
+ if (valueType === "string") {
77
+ const validOps: StringOperators[] = [
78
+ "equals",
79
+ "notEquals",
80
+ "contains",
81
+ "startsWith",
82
+ "endsWith",
83
+ "in",
84
+ "notIn",
85
+ "isNull",
86
+ "isNotNull",
87
+ ];
88
+ return validOps.includes(operator as StringOperators);
89
+ }
90
+
91
+ if (valueType === "number") {
92
+ const validOps: NumberOperators[] = [
93
+ "equals",
94
+ "notEquals",
95
+ "greaterThan",
96
+ "greaterThanOrEqual",
97
+ "lessThan",
98
+ "lessThanOrEqual",
99
+ "in",
100
+ "notIn",
101
+ "between",
102
+ "isNull",
103
+ "isNotNull",
104
+ ];
105
+ return validOps.includes(operator as NumberOperators);
106
+ }
107
+
108
+ if (valueType === "boolean") {
109
+ const validOps: BooleanOperators[] = [
110
+ "equals",
111
+ "notEquals",
112
+ "isNull",
113
+ "isNotNull",
114
+ ];
115
+ return validOps.includes(operator as BooleanOperators);
116
+ }
117
+
118
+ if (sanitizedValue instanceof Date) {
119
+ const validOps: DateOperators[] = [
120
+ "equals",
121
+ "notEquals",
122
+ "greaterThan",
123
+ "greaterThanOrEqual",
124
+ "lessThan",
125
+ "lessThanOrEqual",
126
+ "in",
127
+ "notIn",
128
+ "between",
129
+ "isNull",
130
+ "isNotNull",
131
+ ];
132
+ return validOps.includes(operator as DateOperators);
133
+ }
134
+
135
+ if (Array.isArray(sanitizedValue)) {
136
+ const validOps: ArrayOperators[] = ["in", "notIn", "isNull", "isNotNull"];
137
+ return validOps.includes(operator as ArrayOperators);
138
+ }
139
+
140
+ return true;
141
+ }
142
+
143
+ export function getValidOperatorsForType(value: unknown): FilterOperator[] {
144
+ if (value === null || value === undefined) {
145
+ return ["isNull", "isNotNull", "equals", "notEquals"];
146
+ }
147
+
148
+ const valueType = typeof value;
149
+
150
+ if (valueType === "string" && Number.isNaN(Number(value))) {
151
+ return [
152
+ "equals",
153
+ "notEquals",
154
+ "contains",
155
+ "startsWith",
156
+ "endsWith",
157
+ "in",
158
+ "notIn",
159
+ "isNull",
160
+ "isNotNull",
161
+ ];
162
+ }
163
+
164
+ if (valueType === "number") {
165
+ return [
166
+ "equals",
167
+ "notEquals",
168
+ "greaterThan",
169
+ "greaterThanOrEqual",
170
+ "lessThan",
171
+ "lessThanOrEqual",
172
+ "in",
173
+ "notIn",
174
+ "between",
175
+ "isNull",
176
+ "isNotNull",
177
+ ];
178
+ }
179
+
180
+ if (valueType === "boolean") {
181
+ return ["equals", "notEquals", "isNull", "isNotNull"];
182
+ }
183
+
184
+ if (value instanceof Date) {
185
+ return [
186
+ "equals",
187
+ "notEquals",
188
+ "greaterThan",
189
+ "greaterThanOrEqual",
190
+ "lessThan",
191
+ "lessThanOrEqual",
192
+ "in",
193
+ "notIn",
194
+ "between",
195
+ "isNull",
196
+ "isNotNull",
197
+ ];
198
+ }
199
+
200
+ if (Array.isArray(value)) {
201
+ return ["in", "notIn", "isNull", "isNotNull"];
202
+ }
203
+
204
+ return [...FILTER_OPERATORS];
205
+ }
206
+
207
+ export function isOperator(value: string): value is FilterOperator {
208
+ return FILTER_OPERATORS.includes(value as FilterOperator);
209
+ }