@emkodev/emkore 1.0.3
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.
- package/CHANGELOG.md +269 -0
- package/DEVELOPER_GUIDE.md +227 -0
- package/LICENSE +21 -0
- package/README.md +126 -0
- package/bun.lock +22 -0
- package/example/README.md +200 -0
- package/example/create-user.interactor.ts +88 -0
- package/example/dto/user.dto.ts +34 -0
- package/example/entity/user.entity.ts +54 -0
- package/example/index.ts +18 -0
- package/example/interface/create-user.usecase.ts +93 -0
- package/example/interface/user.repository.ts +23 -0
- package/mod.ts +1 -0
- package/package.json +32 -0
- package/src/common/abstract.actor.ts +59 -0
- package/src/common/abstract.entity.ts +59 -0
- package/src/common/abstract.interceptor.ts +17 -0
- package/src/common/abstract.repository.ts +162 -0
- package/src/common/abstract.usecase.ts +113 -0
- package/src/common/config/config-registry.ts +190 -0
- package/src/common/config/config-section.ts +106 -0
- package/src/common/exception/authorization-exception.ts +28 -0
- package/src/common/exception/repository-exception.ts +46 -0
- package/src/common/interceptor/audit-log.interceptor.ts +181 -0
- package/src/common/interceptor/authorization.interceptor.ts +252 -0
- package/src/common/interceptor/performance.interceptor.ts +101 -0
- package/src/common/llm/api-definition.type.ts +185 -0
- package/src/common/pattern/unit-of-work.ts +78 -0
- package/src/common/platform/env.ts +38 -0
- package/src/common/registry/usecase-registry.ts +80 -0
- package/src/common/type/interceptor-context.type.ts +25 -0
- package/src/common/type/json-schema.type.ts +80 -0
- package/src/common/type/json.type.ts +5 -0
- package/src/common/type/lowercase.type.ts +48 -0
- package/src/common/type/metadata.type.ts +5 -0
- package/src/common/type/money.class.ts +384 -0
- package/src/common/type/permission.type.ts +43 -0
- package/src/common/validation/validation-result.ts +52 -0
- package/src/common/validation/validators.ts +441 -0
- package/src/index.ts +95 -0
- package/test/unit/abstract-actor.test.ts +608 -0
- package/test/unit/actor.test.ts +89 -0
- package/test/unit/api-definition.test.ts +628 -0
- package/test/unit/authorization.test.ts +101 -0
- package/test/unit/entity.test.ts +95 -0
- package/test/unit/money.test.ts +480 -0
- package/test/unit/validation.test.ts +138 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit of Work Pattern
|
|
3
|
+
*
|
|
4
|
+
* Maintains a list of objects affected by a business transaction and coordinates
|
|
5
|
+
* writing out changes and resolving concurrency problems.
|
|
6
|
+
*
|
|
7
|
+
* This interface provides the minimal contract for transaction boundaries.
|
|
8
|
+
* Implementations can extend this with additional tracking capabilities:
|
|
9
|
+
*
|
|
10
|
+
* - Change tracking: registerNew(), registerDirty(), registerDeleted()
|
|
11
|
+
* - Identity map: registerClean() to prevent loading duplicates
|
|
12
|
+
* - Batching: Accumulate changes and apply them in a single operation
|
|
13
|
+
* - Optimistic locking: Track versions for concurrent modifications
|
|
14
|
+
*
|
|
15
|
+
* Examples of implementations:
|
|
16
|
+
* - SQL: Use database transactions (BEGIN/COMMIT/ROLLBACK)
|
|
17
|
+
* - REST APIs: Batch requests or implement compensating transactions
|
|
18
|
+
* - Document DBs: Use sessions or batch operations
|
|
19
|
+
* - Event Sourcing: Collect events and append to stream on commit
|
|
20
|
+
* - In-Memory: Track changes in collections, restore on rollback
|
|
21
|
+
*
|
|
22
|
+
* @see https://martinfowler.com/eaaCatalog/unitOfWork.html
|
|
23
|
+
*/
|
|
24
|
+
export interface UnitOfWork {
|
|
25
|
+
readonly tenantId: string;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Commits all changes made within this unit of work.
|
|
29
|
+
* Should be atomic - either all changes succeed or none do.
|
|
30
|
+
*/
|
|
31
|
+
commit(): Promise<void>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Discards all changes made within this unit of work.
|
|
35
|
+
* Should restore state to before the unit of work began.
|
|
36
|
+
*/
|
|
37
|
+
rollback(): Promise<void>;
|
|
38
|
+
|
|
39
|
+
// Optional methods that implementations might want to add:
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Track a newly created entity for insertion
|
|
43
|
+
*/
|
|
44
|
+
registerNew?(entity: unknown): void;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Track a modified entity for update
|
|
48
|
+
*/
|
|
49
|
+
registerDirty?(entity: unknown): void;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Track an entity for deletion
|
|
53
|
+
*/
|
|
54
|
+
registerDeleted?(entity: unknown): void;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Track a clean (unmodified) entity to prevent duplicate loads
|
|
58
|
+
*/
|
|
59
|
+
registerClean?(entity: unknown): void;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Check if the unit of work has any pending changes
|
|
63
|
+
*/
|
|
64
|
+
hasChanges?(): boolean;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get a repository that participates in this unit of work
|
|
68
|
+
* @param type - Repository class constructor or identifier
|
|
69
|
+
*/
|
|
70
|
+
getRepository?<T = unknown>(type: unknown): T;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface UnitOfWorkFactory {
|
|
74
|
+
transaction<T>(
|
|
75
|
+
tenantId: string,
|
|
76
|
+
body: (uow: UnitOfWork) => Promise<T>,
|
|
77
|
+
): Promise<T>;
|
|
78
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform environment variable access
|
|
3
|
+
* Works in Deno, Node.js, and browsers (with fallbacks)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Type-safe access to global objects for cross-platform compatibility
|
|
7
|
+
declare const Deno: { env: { get(key: string): string | undefined } } | undefined;
|
|
8
|
+
declare const process: { env: Record<string, string | undefined> } | undefined;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get environment variable value
|
|
12
|
+
* @param key Environment variable name
|
|
13
|
+
* @returns Value or undefined if not found
|
|
14
|
+
*/
|
|
15
|
+
export function getEnv(key: string): string | undefined {
|
|
16
|
+
// Deno
|
|
17
|
+
if (typeof Deno !== "undefined" && Deno.env?.get) {
|
|
18
|
+
return Deno.env.get(key);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Node.js
|
|
22
|
+
if (typeof process !== "undefined" && process?.env) {
|
|
23
|
+
return process.env[key];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Browser/Worker fallback - no environment variables
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get environment variable with default value
|
|
32
|
+
* @param key Environment variable name
|
|
33
|
+
* @param defaultValue Default value if not found
|
|
34
|
+
* @returns Value or default
|
|
35
|
+
*/
|
|
36
|
+
export function getEnvOrDefault(key: string, defaultValue: string): string {
|
|
37
|
+
return getEnv(key) ?? defaultValue;
|
|
38
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Use Case Registry System
|
|
3
|
+
*
|
|
4
|
+
* Provides types and utilities for auto-discovery and registration
|
|
5
|
+
* of use cases in dynamic routing systems.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { UnitOfWorkFactory } from "../pattern/unit-of-work.ts";
|
|
9
|
+
import type { Usecase } from "../abstract.usecase.ts";
|
|
10
|
+
import type { Permission } from "../type/permission.type.ts";
|
|
11
|
+
import type { ApiDefinition } from "../llm/api-definition.type.ts";
|
|
12
|
+
|
|
13
|
+
export interface UseCaseRegistryEntry<
|
|
14
|
+
TInput = unknown,
|
|
15
|
+
TOutput = unknown,
|
|
16
|
+
TUowFactory extends UnitOfWorkFactory = UnitOfWorkFactory,
|
|
17
|
+
> {
|
|
18
|
+
action: string;
|
|
19
|
+
resource: string;
|
|
20
|
+
createInteractor: () => Promise<Usecase<TInput, TOutput>>;
|
|
21
|
+
createUowFactory: () => Promise<TUowFactory>;
|
|
22
|
+
requiredPermissions: Permission[];
|
|
23
|
+
apiDefinition?: ApiDefinition;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class UseCaseRegistryHelpers {
|
|
27
|
+
/**
|
|
28
|
+
* Get the name for a registry entry
|
|
29
|
+
*/
|
|
30
|
+
static getName(entry: UseCaseRegistryEntry): string {
|
|
31
|
+
return `${entry.action}_${entry.resource}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get the HTTP method for a registry entry
|
|
36
|
+
*/
|
|
37
|
+
static getHttpMethod(
|
|
38
|
+
entry: UseCaseRegistryEntry,
|
|
39
|
+
): "GET" | "POST" | "PUT" | "PATCH" | "DELETE" {
|
|
40
|
+
switch (entry.action) {
|
|
41
|
+
case "create":
|
|
42
|
+
return "POST";
|
|
43
|
+
case "retrieve":
|
|
44
|
+
case "list":
|
|
45
|
+
case "count":
|
|
46
|
+
return "GET";
|
|
47
|
+
case "update":
|
|
48
|
+
return "PATCH";
|
|
49
|
+
case "delete":
|
|
50
|
+
case "bulkDelete":
|
|
51
|
+
return "DELETE";
|
|
52
|
+
default:
|
|
53
|
+
return "POST";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get the API path for a registry entry
|
|
59
|
+
*/
|
|
60
|
+
static getPath(entry: UseCaseRegistryEntry): string {
|
|
61
|
+
const basePath = `/api/v1/${entry.resource}`;
|
|
62
|
+
|
|
63
|
+
switch (entry.action) {
|
|
64
|
+
case "create":
|
|
65
|
+
return basePath;
|
|
66
|
+
case "retrieve":
|
|
67
|
+
case "update":
|
|
68
|
+
case "delete":
|
|
69
|
+
return `${basePath}/:id`;
|
|
70
|
+
case "list":
|
|
71
|
+
return basePath;
|
|
72
|
+
case "count":
|
|
73
|
+
return `${basePath}/count`;
|
|
74
|
+
case "bulkDelete":
|
|
75
|
+
return `${basePath}/bulk`;
|
|
76
|
+
default:
|
|
77
|
+
return `${basePath}/${entry.action}`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Actor } from "../abstract.actor.ts";
|
|
2
|
+
import type { UsecaseName } from "../abstract.usecase.ts";
|
|
3
|
+
import type { Permission } from "./permission.type.ts";
|
|
4
|
+
import type { Metadata } from "./metadata.type.ts";
|
|
5
|
+
|
|
6
|
+
export interface InterceptorContext {
|
|
7
|
+
readonly actor: Actor;
|
|
8
|
+
readonly usecaseName: UsecaseName;
|
|
9
|
+
readonly requiredPermissions: Permission[];
|
|
10
|
+
readonly metadata: Metadata;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function createInterceptorContext(
|
|
14
|
+
actor: Actor,
|
|
15
|
+
usecaseName: UsecaseName,
|
|
16
|
+
requiredPermissions: Permission[],
|
|
17
|
+
metadata: Metadata = {},
|
|
18
|
+
): InterceptorContext {
|
|
19
|
+
return {
|
|
20
|
+
actor,
|
|
21
|
+
usecaseName,
|
|
22
|
+
requiredPermissions,
|
|
23
|
+
metadata,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema types for validation and API definitions.
|
|
3
|
+
* Based on JSON Schema Draft-07 specification.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type JsonSchemaType =
|
|
7
|
+
| "string"
|
|
8
|
+
| "number"
|
|
9
|
+
| "boolean"
|
|
10
|
+
| "object"
|
|
11
|
+
| "array"
|
|
12
|
+
| "null"
|
|
13
|
+
| "integer";
|
|
14
|
+
|
|
15
|
+
export interface JsonSchema {
|
|
16
|
+
readonly type?: JsonSchemaType | JsonSchemaType[];
|
|
17
|
+
readonly properties?: Record<string, JsonSchema>;
|
|
18
|
+
readonly items?: JsonSchema;
|
|
19
|
+
readonly required?: string[];
|
|
20
|
+
readonly enum?: readonly unknown[];
|
|
21
|
+
readonly const?: unknown;
|
|
22
|
+
readonly minimum?: number;
|
|
23
|
+
readonly maximum?: number;
|
|
24
|
+
readonly minLength?: number;
|
|
25
|
+
readonly maxLength?: number;
|
|
26
|
+
readonly pattern?: string;
|
|
27
|
+
readonly format?: string;
|
|
28
|
+
readonly description?: string;
|
|
29
|
+
readonly default?: unknown;
|
|
30
|
+
readonly nullable?: boolean;
|
|
31
|
+
readonly anyOf?: JsonSchema[];
|
|
32
|
+
readonly allOf?: JsonSchema[];
|
|
33
|
+
readonly oneOf?: JsonSchema[];
|
|
34
|
+
readonly not?: JsonSchema;
|
|
35
|
+
readonly additionalProperties?: boolean | JsonSchema;
|
|
36
|
+
readonly minItems?: number;
|
|
37
|
+
readonly maxItems?: number;
|
|
38
|
+
readonly uniqueItems?: boolean;
|
|
39
|
+
readonly minProperties?: number;
|
|
40
|
+
readonly maxProperties?: number;
|
|
41
|
+
readonly multipleOf?: number;
|
|
42
|
+
readonly exclusiveMinimum?: boolean | number;
|
|
43
|
+
readonly exclusiveMaximum?: boolean | number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type JsonSchemaProperties = Record<string, JsonSchema>;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Mutable version of JsonSchema for building schemas dynamically.
|
|
50
|
+
*/
|
|
51
|
+
export interface MutableJsonSchema {
|
|
52
|
+
type?: JsonSchemaType | JsonSchemaType[];
|
|
53
|
+
properties?: Record<string, MutableJsonSchema>;
|
|
54
|
+
items?: MutableJsonSchema | JsonSchema;
|
|
55
|
+
required?: string[] | boolean;
|
|
56
|
+
enum?: unknown[];
|
|
57
|
+
const?: unknown;
|
|
58
|
+
minimum?: number;
|
|
59
|
+
maximum?: number;
|
|
60
|
+
minLength?: number;
|
|
61
|
+
maxLength?: number;
|
|
62
|
+
pattern?: string;
|
|
63
|
+
format?: string;
|
|
64
|
+
description?: string;
|
|
65
|
+
default?: unknown;
|
|
66
|
+
nullable?: boolean;
|
|
67
|
+
anyOf?: JsonSchema[];
|
|
68
|
+
allOf?: JsonSchema[];
|
|
69
|
+
oneOf?: JsonSchema[];
|
|
70
|
+
not?: JsonSchema;
|
|
71
|
+
additionalProperties?: boolean | JsonSchema;
|
|
72
|
+
minItems?: number;
|
|
73
|
+
maxItems?: number;
|
|
74
|
+
uniqueItems?: boolean;
|
|
75
|
+
minProperties?: number;
|
|
76
|
+
maxProperties?: number;
|
|
77
|
+
multipleOf?: number;
|
|
78
|
+
exclusiveMinimum?: boolean | number;
|
|
79
|
+
exclusiveMaximum?: boolean | number;
|
|
80
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-level utility to enforce lowercase string literals in const arrays.
|
|
3
|
+
*
|
|
4
|
+
* This prevents developers from accidentally using uppercase enum values,
|
|
5
|
+
* ensuring consistency with kebab-case naming conventions.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // ✅ CORRECT - lowercase values
|
|
10
|
+
* const USER_STATUS = ["new", "requires-review"] as const satisfies LowercaseArray;
|
|
11
|
+
*
|
|
12
|
+
* // ❌ COMPILE ERROR - uppercase detected
|
|
13
|
+
* const USER_STATUS = ["NEW", "REQUIRES_REVIEW"] as const satisfies LowercaseArray;
|
|
14
|
+
*
|
|
15
|
+
* // ✅ CORRECT - with type extraction
|
|
16
|
+
* const USER_STATUS = ["new", "active", "archived"] as const satisfies LowercaseArray;
|
|
17
|
+
* type UserStatus = typeof USER_STATUS[number]; // "new" | "active" | "archived"
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Ensures a string type contains only lowercase characters, numbers, and hyphens.
|
|
23
|
+
* Produces a compile-time error if uppercase letters are detected.
|
|
24
|
+
*/
|
|
25
|
+
export type Lowercase<T extends string> = T extends
|
|
26
|
+
`${infer First}${infer Rest}`
|
|
27
|
+
? First extends Uppercase<First>
|
|
28
|
+
? First extends Lowercase<First> ? `${First}${Lowercase<Rest>}` // Numbers, hyphens, special chars
|
|
29
|
+
: never // Uppercase letter detected - ERROR
|
|
30
|
+
: `${First}${Lowercase<Rest>}` // Lowercase letter - OK
|
|
31
|
+
: T; // Empty string or base case
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Enforces that all elements in a readonly array are lowercase strings.
|
|
35
|
+
* Use with `satisfies` keyword for compile-time validation.
|
|
36
|
+
*/
|
|
37
|
+
export type LowercaseArray = readonly Lowercase<string>[];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Helper type to validate and extract union type from a lowercase const array.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* const STATUSES = ["new", "in-progress", "done"] as const satisfies LowercaseArray;
|
|
45
|
+
* type Status = LowercaseArrayUnion<typeof STATUSES>; // "new" | "in-progress" | "done"
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export type LowercaseArrayUnion<T extends LowercaseArray> = T[number];
|