@kuckit/domain 1.0.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.
@@ -0,0 +1,313 @@
1
+ //#region src/entities/user.d.ts
2
+ /**
3
+ * User domain entity
4
+ * Pure TypeScript type with no dependencies
5
+ */
6
+ interface User {
7
+ readonly id: string;
8
+ readonly name: string;
9
+ readonly email: string;
10
+ readonly emailVerified: boolean;
11
+ readonly permissions: readonly string[];
12
+ readonly createdAt: Date;
13
+ readonly updatedAt: Date;
14
+ }
15
+ //#endregion
16
+ //#region src/ports/user-repository.d.ts
17
+ /**
18
+ * Repository port (interface) for user persistence
19
+ * Infrastructure layer will implement this
20
+ */
21
+ interface UserRepository {
22
+ findById(id: string): Promise<User | null>;
23
+ findByEmail(email: string): Promise<User | null>;
24
+ save(user: User): Promise<void>;
25
+ }
26
+ //#endregion
27
+ //#region src/ports/clock.d.ts
28
+ /**
29
+ * Clock port for time-related operations
30
+ * Enables testing with fixed time
31
+ */
32
+ interface Clock {
33
+ now(): Date;
34
+ }
35
+ declare class SystemClock implements Clock {
36
+ now(): Date;
37
+ }
38
+ //#endregion
39
+ //#region src/ports/logger.d.ts
40
+ /**
41
+ * Logger port - structured logging for Loki/Prometheus
42
+ * Loki-compatible: labels and structured fields in meta
43
+ */
44
+ interface Logger {
45
+ debug: (message: string, meta?: LogContext) => void;
46
+ info: (message: string, meta?: LogContext) => void;
47
+ warn: (message: string, meta?: LogContext) => void;
48
+ error: (message: string, meta?: LogContext | Error) => void;
49
+ }
50
+ /**
51
+ * Structured log context (Loki labels)
52
+ */
53
+ interface LogContext {
54
+ requestId?: string;
55
+ userId?: string;
56
+ feature?: string;
57
+ [key: string]: any;
58
+ }
59
+ //#endregion
60
+ //#region src/ports/event-publisher.d.ts
61
+ /**
62
+ * Event publisher port
63
+ * Used by use cases to publish domain events
64
+ *
65
+ * Note: This is a simplified interface for use case dependencies.
66
+ * The actual EventBus in infrastructure is more feature-rich.
67
+ */
68
+ interface EventPublisher {
69
+ /**
70
+ * Publish a domain event
71
+ * @param eventName Event name (e.g., 'user.created')
72
+ * @param payload Event payload data
73
+ * @param aggregateId The aggregate ID (e.g., userId)
74
+ */
75
+ publish(eventName: string, payload: Record<string, any>, aggregateId: string): Promise<void>;
76
+ /**
77
+ * Subscribe to events (for event handlers in infrastructure)
78
+ * @param eventName The event name to listen for
79
+ * @param handler The handler function
80
+ * @returns Unsubscribe function
81
+ */
82
+ subscribe(eventName: string, handler: (payload: Record<string, any>) => Promise<void> | void): () => void;
83
+ }
84
+ //#endregion
85
+ //#region src/ports/cache.d.ts
86
+ /**
87
+ * Cache store port interface
88
+ * Defines contract for caching implementations
89
+ */
90
+ interface CacheStore {
91
+ /**
92
+ * Get a value from cache
93
+ * @param key Cache key
94
+ * @returns Cached value or undefined if not found or expired
95
+ */
96
+ get<T>(key: string): Promise<T | undefined>;
97
+ /**
98
+ * Set a value in cache
99
+ * @param key Cache key
100
+ * @param value Value to cache
101
+ * @param ttlMs Time to live in milliseconds (undefined = no expiration)
102
+ */
103
+ set<T>(key: string, value: T, ttlMs?: number): Promise<void>;
104
+ /**
105
+ * Delete a value from cache
106
+ * @param key Cache key
107
+ */
108
+ del(key: string): Promise<void>;
109
+ /**
110
+ * Clear all values from cache
111
+ */
112
+ clear(): Promise<void>;
113
+ /**
114
+ * Check if a key exists in cache
115
+ * @param key Cache key
116
+ */
117
+ has(key: string): Promise<boolean>;
118
+ }
119
+ //#endregion
120
+ //#region src/ports/rate-limiter.d.ts
121
+ /**
122
+ * Rate limiter store port interface
123
+ * Uses token bucket algorithm for rate limiting
124
+ */
125
+ interface RateLimiterStore {
126
+ /**
127
+ * Check if a request should be allowed
128
+ * @param key Rate limit key (per-user, per-IP, etc)
129
+ * @param capacity Bucket capacity (max tokens)
130
+ * @param refillPerSecond Number of tokens to refill per second
131
+ * @returns Object with allowed flag and reset time if rate limited
132
+ */
133
+ checkLimit(key: string, capacity: number, refillPerSecond: number): Promise<{
134
+ allowed: boolean;
135
+ tokensRemaining: number;
136
+ resetAt: Date;
137
+ }>;
138
+ /**
139
+ * Reset rate limit for a key
140
+ * @param key Rate limit key
141
+ */
142
+ reset(key: string): Promise<void>;
143
+ /**
144
+ * Clear all rate limit data
145
+ */
146
+ clear(): Promise<void>;
147
+ }
148
+ //#endregion
149
+ //#region src/errors/index.d.ts
150
+ /**
151
+ * Application error codes (domain-level)
152
+ */
153
+ declare enum ErrorCode {
154
+ USER_NOT_FOUND = "USER_NOT_FOUND",
155
+ USER_ALREADY_EXISTS = "USER_ALREADY_EXISTS",
156
+ INVALID_EMAIL = "INVALID_EMAIL",
157
+ UNAUTHORIZED = "UNAUTHORIZED",
158
+ FORBIDDEN = "FORBIDDEN",
159
+ SESSION_EXPIRED = "SESSION_EXPIRED",
160
+ INVALID_INPUT = "INVALID_INPUT",
161
+ VALIDATION_ERROR = "VALIDATION_ERROR",
162
+ RATE_LIMITED = "RATE_LIMITED",
163
+ NOT_FOUND = "NOT_FOUND",
164
+ CONFLICT = "CONFLICT",
165
+ INTERNAL_SERVER_ERROR = "INTERNAL_SERVER_ERROR",
166
+ }
167
+ /**
168
+ * Severity levels for error classification
169
+ */
170
+ declare enum ErrorSeverity {
171
+ LOW = "LOW",
172
+ // Client error, expected
173
+ MEDIUM = "MEDIUM",
174
+ // Unexpected but recoverable
175
+ HIGH = "HIGH",
176
+ // System error, requires attention
177
+ CRITICAL = "CRITICAL",
178
+ }
179
+ /**
180
+ * Application error base class
181
+ */
182
+ declare class AppError extends Error {
183
+ readonly code: ErrorCode;
184
+ readonly severity: ErrorSeverity;
185
+ readonly statusCode: number;
186
+ readonly meta: Record<string, any>;
187
+ constructor(code: ErrorCode, message: string, options?: {
188
+ statusCode?: number;
189
+ severity?: ErrorSeverity;
190
+ meta?: Record<string, any>;
191
+ });
192
+ toJSON(): {
193
+ name: string;
194
+ code: ErrorCode;
195
+ message: string;
196
+ severity: ErrorSeverity;
197
+ statusCode: number;
198
+ meta: Record<string, any>;
199
+ };
200
+ }
201
+ /**
202
+ * Helper functions to create specific errors
203
+ */
204
+ declare const notFound: (resource: string, meta?: Record<string, any>) => AppError;
205
+ declare const unauthorized: (reason: string, meta?: Record<string, any>) => AppError;
206
+ declare const forbidden: (reason: string, meta?: Record<string, any>) => AppError;
207
+ declare const validationError: (message: string, meta?: Record<string, any>) => AppError;
208
+ declare const conflict: (message: string, meta?: Record<string, any>) => AppError;
209
+ declare const internalError: (message: string, meta?: Record<string, any>) => AppError;
210
+ //#endregion
211
+ //#region src/events/types.d.ts
212
+ /**
213
+ * Event-driven architecture types
214
+ *
215
+ * Event names follow 'module.action' pattern: 'user.created', 'user.updated'
216
+ * All events are strongly typed through EventMap
217
+ */
218
+ /**
219
+ * Event metadata included with every domain event
220
+ */
221
+ interface EventMeta {
222
+ /** Unique identifier for this event */
223
+ readonly eventId: string;
224
+ /** Timestamp when event occurred */
225
+ readonly occurredAt: Date;
226
+ /** Event schema version for migrations */
227
+ readonly version: number;
228
+ /** Aggregate ID (e.g., userId for user events) */
229
+ readonly aggregateId: string;
230
+ /** Correlation ID for tracing across services */
231
+ readonly correlationId: string;
232
+ }
233
+ /**
234
+ * Base domain event type
235
+ * All domain events extend this interface
236
+ */
237
+ interface DomainEvent<TName extends string = string, TPayload extends Record<string, any> = Record<string, any>> {
238
+ /** Event name in 'module.action' format */
239
+ readonly name: TName;
240
+ /** Event payload (action-specific data) */
241
+ readonly payload: TPayload;
242
+ /** Event metadata (standard for all events) */
243
+ readonly meta: EventMeta;
244
+ }
245
+ /**
246
+ * Event map definition - extend this to add typed events
247
+ * Maps event names to their payload types
248
+ *
249
+ * Example:
250
+ * interface EventMap {
251
+ * 'user.created': { userId: string; email: string }
252
+ * 'user.updated': { userId: string; name: string }
253
+ * }
254
+ */
255
+ interface EventMap {
256
+ [key: string]: Record<string, any>;
257
+ }
258
+ /**
259
+ * Valid event names derived from EventMap
260
+ */
261
+ type EventName = keyof EventMap & string;
262
+ /**
263
+ * Typed domain event for a specific event name
264
+ */
265
+ type TypedDomainEvent<TName extends EventName = EventName> = DomainEvent<TName, EventMap[TName]>;
266
+ //#endregion
267
+ //#region src/events/event-bus.d.ts
268
+ /**
269
+ * Event handler function
270
+ * Handles a specific event type
271
+ * Should not throw; errors are caught and logged
272
+ */
273
+ type EventHandler<TName extends EventName = EventName> = (event: TypedDomainEvent<TName>) => void | Promise<void>;
274
+ /**
275
+ * Event bus interface
276
+ * Handles publishing domain events and managing subscriptions
277
+ */
278
+ interface EventBus {
279
+ /**
280
+ * Publish event synchronously
281
+ * Awaits all handlers to complete before returning
282
+ * Errors in handlers are caught and logged, don't block others
283
+ *
284
+ * @param event The domain event to publish
285
+ */
286
+ publish<TName extends EventName>(event: TypedDomainEvent<TName>): Promise<void>;
287
+ /**
288
+ * Publish event asynchronously
289
+ * Uses queueMicrotask to defer execution, returns immediately
290
+ * Errors in handlers are caught and logged
291
+ *
292
+ * @param event The domain event to publish
293
+ */
294
+ publishAsync<TName extends EventName>(event: TypedDomainEvent<TName>): void;
295
+ /**
296
+ * Subscribe to specific event type
297
+ * Returns unsubscribe function to remove handler
298
+ *
299
+ * @param eventName Event name to subscribe to (e.g., 'user.created')
300
+ * @param handler Event handler function
301
+ * @returns Unsubscribe function
302
+ */
303
+ subscribe<TName extends EventName>(eventName: TName, handler: EventHandler<TName>): () => void;
304
+ /**
305
+ * Subscribe to all events
306
+ *
307
+ * @param handler Event handler function
308
+ * @returns Unsubscribe function
309
+ */
310
+ subscribeAll(handler: EventHandler): () => void;
311
+ }
312
+ //#endregion
313
+ export { AppError, CacheStore, Clock, DomainEvent, ErrorCode, ErrorSeverity, EventBus, EventHandler, EventMap, EventMeta, EventName, EventPublisher, LogContext, Logger, RateLimiterStore, SystemClock, TypedDomainEvent, User, UserRepository, conflict, forbidden, internalError, notFound, unauthorized, validationError };
package/dist/index.js ADDED
@@ -0,0 +1,101 @@
1
+ //#region src/ports/clock.ts
2
+ var SystemClock = class {
3
+ now() {
4
+ return /* @__PURE__ */ new Date();
5
+ }
6
+ };
7
+
8
+ //#endregion
9
+ //#region src/errors/index.ts
10
+ /**
11
+ * Application error codes (domain-level)
12
+ */
13
+ let ErrorCode = /* @__PURE__ */ function(ErrorCode$1) {
14
+ ErrorCode$1["USER_NOT_FOUND"] = "USER_NOT_FOUND";
15
+ ErrorCode$1["USER_ALREADY_EXISTS"] = "USER_ALREADY_EXISTS";
16
+ ErrorCode$1["INVALID_EMAIL"] = "INVALID_EMAIL";
17
+ ErrorCode$1["UNAUTHORIZED"] = "UNAUTHORIZED";
18
+ ErrorCode$1["FORBIDDEN"] = "FORBIDDEN";
19
+ ErrorCode$1["SESSION_EXPIRED"] = "SESSION_EXPIRED";
20
+ ErrorCode$1["INVALID_INPUT"] = "INVALID_INPUT";
21
+ ErrorCode$1["VALIDATION_ERROR"] = "VALIDATION_ERROR";
22
+ ErrorCode$1["RATE_LIMITED"] = "RATE_LIMITED";
23
+ ErrorCode$1["NOT_FOUND"] = "NOT_FOUND";
24
+ ErrorCode$1["CONFLICT"] = "CONFLICT";
25
+ ErrorCode$1["INTERNAL_SERVER_ERROR"] = "INTERNAL_SERVER_ERROR";
26
+ return ErrorCode$1;
27
+ }({});
28
+ /**
29
+ * Severity levels for error classification
30
+ */
31
+ let ErrorSeverity = /* @__PURE__ */ function(ErrorSeverity$1) {
32
+ ErrorSeverity$1["LOW"] = "LOW";
33
+ ErrorSeverity$1["MEDIUM"] = "MEDIUM";
34
+ ErrorSeverity$1["HIGH"] = "HIGH";
35
+ ErrorSeverity$1["CRITICAL"] = "CRITICAL";
36
+ return ErrorSeverity$1;
37
+ }({});
38
+ /**
39
+ * Application error base class
40
+ */
41
+ var AppError = class AppError extends Error {
42
+ code;
43
+ severity;
44
+ statusCode;
45
+ meta;
46
+ constructor(code, message, options = {}) {
47
+ super(message);
48
+ this.name = "AppError";
49
+ this.code = code;
50
+ this.severity = options.severity || ErrorSeverity.MEDIUM;
51
+ this.statusCode = options.statusCode || 500;
52
+ this.meta = options.meta || {};
53
+ Object.setPrototypeOf(this, AppError.prototype);
54
+ }
55
+ toJSON() {
56
+ return {
57
+ name: this.name,
58
+ code: this.code,
59
+ message: this.message,
60
+ severity: this.severity,
61
+ statusCode: this.statusCode,
62
+ meta: this.meta
63
+ };
64
+ }
65
+ };
66
+ /**
67
+ * Helper functions to create specific errors
68
+ */
69
+ const notFound = (resource, meta) => new AppError(ErrorCode.NOT_FOUND, `${resource} not found`, {
70
+ statusCode: 404,
71
+ severity: ErrorSeverity.LOW,
72
+ meta
73
+ });
74
+ const unauthorized = (reason, meta) => new AppError(ErrorCode.UNAUTHORIZED, reason, {
75
+ statusCode: 401,
76
+ severity: ErrorSeverity.LOW,
77
+ meta
78
+ });
79
+ const forbidden = (reason, meta) => new AppError(ErrorCode.FORBIDDEN, reason, {
80
+ statusCode: 403,
81
+ severity: ErrorSeverity.LOW,
82
+ meta
83
+ });
84
+ const validationError = (message, meta) => new AppError(ErrorCode.VALIDATION_ERROR, message, {
85
+ statusCode: 400,
86
+ severity: ErrorSeverity.LOW,
87
+ meta
88
+ });
89
+ const conflict = (message, meta) => new AppError(ErrorCode.CONFLICT, message, {
90
+ statusCode: 409,
91
+ severity: ErrorSeverity.LOW,
92
+ meta
93
+ });
94
+ const internalError = (message, meta) => new AppError(ErrorCode.INTERNAL_SERVER_ERROR, message, {
95
+ statusCode: 500,
96
+ severity: ErrorSeverity.HIGH,
97
+ meta
98
+ });
99
+
100
+ //#endregion
101
+ export { AppError, ErrorCode, ErrorSeverity, SystemClock, conflict, forbidden, internalError, notFound, unauthorized, validationError };
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@kuckit/domain",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "src/index.ts",
6
+ "types": "src/index.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "exports": {
11
+ ".": {
12
+ "types": "./src/index.ts",
13
+ "default": "./src/index.ts"
14
+ },
15
+ "./*": {
16
+ "types": "./src/*.ts",
17
+ "default": "./src/*.ts"
18
+ }
19
+ },
20
+ "publishConfig": {
21
+ "main": "dist/index.js",
22
+ "types": "dist/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "default": "./dist/index.js"
27
+ },
28
+ "./*": {
29
+ "types": "./dist/*.d.ts",
30
+ "default": "./dist/*.js"
31
+ }
32
+ }
33
+ },
34
+ "scripts": {
35
+ "build": "tsdown"
36
+ },
37
+ "devDependencies": {
38
+ "tsdown": "catalog:"
39
+ },
40
+ "peerDependencies": {
41
+ "typescript": "^5"
42
+ }
43
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './entities'
2
+ export * from './ports'
3
+ export * from './errors'
4
+ export * from './events'