@open-mercato/core 0.4.2-canary-7732371765 → 0.4.2-canary-ed15f2e753

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 (173) hide show
  1. package/dist/generated/entities/api_key/index.js +2 -0
  2. package/dist/generated/entities/api_key/index.js.map +2 -2
  3. package/dist/generated/entities/workflow_event_trigger/index.js +33 -0
  4. package/dist/generated/entities/workflow_event_trigger/index.js.map +7 -0
  5. package/dist/generated/entities.ids.generated.js +1 -0
  6. package/dist/generated/entities.ids.generated.js.map +2 -2
  7. package/dist/generated/entity-fields-registry.js +2 -0
  8. package/dist/generated/entity-fields-registry.js.map +2 -2
  9. package/dist/modules/api_keys/data/entities.js +3 -0
  10. package/dist/modules/api_keys/data/entities.js.map +2 -2
  11. package/dist/modules/api_keys/migrations/Migration20260125204102.js +13 -0
  12. package/dist/modules/api_keys/migrations/Migration20260125204102.js.map +7 -0
  13. package/dist/modules/api_keys/services/apiKeyService.js +41 -0
  14. package/dist/modules/api_keys/services/apiKeyService.js.map +3 -3
  15. package/dist/modules/auth/events.js +30 -0
  16. package/dist/modules/auth/events.js.map +7 -0
  17. package/dist/modules/auth/services/rbacService.js.map +2 -2
  18. package/dist/modules/business_rules/api/execute/[ruleId]/route.js +145 -0
  19. package/dist/modules/business_rules/api/execute/[ruleId]/route.js.map +7 -0
  20. package/dist/modules/business_rules/data/validators.js +34 -0
  21. package/dist/modules/business_rules/data/validators.js.map +2 -2
  22. package/dist/modules/business_rules/index.js +21 -1
  23. package/dist/modules/business_rules/index.js.map +2 -2
  24. package/dist/modules/business_rules/lib/rule-engine.js +182 -1
  25. package/dist/modules/business_rules/lib/rule-engine.js.map +2 -2
  26. package/dist/modules/catalog/events.js +34 -0
  27. package/dist/modules/catalog/events.js.map +7 -0
  28. package/dist/modules/customers/events.js +49 -0
  29. package/dist/modules/customers/events.js.map +7 -0
  30. package/dist/modules/directory/events.js +23 -0
  31. package/dist/modules/directory/events.js.map +7 -0
  32. package/dist/modules/sales/acl.js +1 -0
  33. package/dist/modules/sales/acl.js.map +2 -2
  34. package/dist/modules/sales/backend/sales/documents/[id]/page.js +12 -0
  35. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  36. package/dist/modules/sales/commands/documents.js +62 -0
  37. package/dist/modules/sales/commands/documents.js.map +2 -2
  38. package/dist/modules/sales/events.js +63 -0
  39. package/dist/modules/sales/events.js.map +7 -0
  40. package/dist/modules/sales/lib/dictionaries.js +3 -0
  41. package/dist/modules/sales/lib/dictionaries.js.map +2 -2
  42. package/dist/modules/sales/lib/frontend/documentDataEvents.js +25 -0
  43. package/dist/modules/sales/lib/frontend/documentDataEvents.js.map +7 -0
  44. package/dist/modules/workflows/acl.js +2 -0
  45. package/dist/modules/workflows/acl.js.map +2 -2
  46. package/dist/modules/workflows/api/instances/route.js +18 -6
  47. package/dist/modules/workflows/api/instances/route.js.map +2 -2
  48. package/dist/modules/workflows/api/tasks/route.js +6 -1
  49. package/dist/modules/workflows/api/tasks/route.js.map +2 -2
  50. package/dist/modules/workflows/backend/definitions/[id]/page.js +9 -1
  51. package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
  52. package/dist/modules/workflows/backend/definitions/[id]/page.meta.js +1 -1
  53. package/dist/modules/workflows/backend/definitions/[id]/page.meta.js.map +2 -2
  54. package/dist/modules/workflows/backend/definitions/create/page.js +24 -15
  55. package/dist/modules/workflows/backend/definitions/create/page.js.map +2 -2
  56. package/dist/modules/workflows/backend/definitions/create/page.meta.js +1 -1
  57. package/dist/modules/workflows/backend/definitions/create/page.meta.js.map +2 -2
  58. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +150 -132
  59. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  60. package/dist/modules/workflows/backend/definitions/visual-editor/page.meta.js +1 -1
  61. package/dist/modules/workflows/backend/definitions/visual-editor/page.meta.js.map +2 -2
  62. package/dist/modules/workflows/backend/events/[id]/page.js +1 -1
  63. package/dist/modules/workflows/backend/events/[id]/page.js.map +2 -2
  64. package/dist/modules/workflows/backend/events/[id]/page.meta.js +2 -2
  65. package/dist/modules/workflows/backend/events/[id]/page.meta.js.map +2 -2
  66. package/dist/modules/workflows/backend/instances/[id]/page.meta.js +2 -2
  67. package/dist/modules/workflows/backend/instances/[id]/page.meta.js.map +2 -2
  68. package/dist/modules/workflows/backend/tasks/[id]/page.js +1 -1
  69. package/dist/modules/workflows/backend/tasks/[id]/page.js.map +2 -2
  70. package/dist/modules/workflows/backend/tasks/[id]/page.meta.js +2 -2
  71. package/dist/modules/workflows/backend/tasks/[id]/page.meta.js.map +2 -2
  72. package/dist/modules/workflows/backend/tasks/page.js +5 -6
  73. package/dist/modules/workflows/backend/tasks/page.js.map +2 -2
  74. package/dist/modules/workflows/cli.js +81 -3
  75. package/dist/modules/workflows/cli.js.map +3 -3
  76. package/dist/modules/workflows/components/DefinitionTriggersEditor.js +481 -0
  77. package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +7 -0
  78. package/dist/modules/workflows/components/EventTriggersEditor.js +553 -0
  79. package/dist/modules/workflows/components/EventTriggersEditor.js.map +7 -0
  80. package/dist/modules/workflows/data/entities.js +64 -1
  81. package/dist/modules/workflows/data/entities.js.map +2 -2
  82. package/dist/modules/workflows/data/validators.js +115 -0
  83. package/dist/modules/workflows/data/validators.js.map +2 -2
  84. package/dist/modules/workflows/events.js +38 -0
  85. package/dist/modules/workflows/events.js.map +7 -0
  86. package/dist/modules/workflows/examples/checkout-demo-definition.json +1 -5
  87. package/dist/modules/workflows/examples/order-approval-definition.json +257 -0
  88. package/dist/modules/workflows/examples/order-approval-guard-rules.json +32 -0
  89. package/dist/modules/workflows/lib/activity-executor.js +75 -13
  90. package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
  91. package/dist/modules/workflows/lib/event-trigger-service.js +308 -0
  92. package/dist/modules/workflows/lib/event-trigger-service.js.map +7 -0
  93. package/dist/modules/workflows/lib/graph-utils.js +71 -2
  94. package/dist/modules/workflows/lib/graph-utils.js.map +2 -2
  95. package/dist/modules/workflows/lib/seeds.js +22 -5
  96. package/dist/modules/workflows/lib/seeds.js.map +2 -2
  97. package/dist/modules/workflows/lib/start-validator.js +33 -23
  98. package/dist/modules/workflows/lib/start-validator.js.map +2 -2
  99. package/dist/modules/workflows/lib/transition-handler.js +157 -45
  100. package/dist/modules/workflows/lib/transition-handler.js.map +3 -3
  101. package/dist/modules/workflows/migrations/Migration20260123143500.js +36 -0
  102. package/dist/modules/workflows/migrations/Migration20260123143500.js.map +7 -0
  103. package/dist/modules/workflows/subscribers/event-trigger.js +78 -0
  104. package/dist/modules/workflows/subscribers/event-trigger.js.map +7 -0
  105. package/dist/modules/workflows/widgets/injection/order-approval/widget.client.js +323 -0
  106. package/dist/modules/workflows/widgets/injection/order-approval/widget.client.js.map +7 -0
  107. package/dist/modules/workflows/widgets/injection/order-approval/widget.js +17 -0
  108. package/dist/modules/workflows/widgets/injection/order-approval/widget.js.map +7 -0
  109. package/dist/modules/workflows/widgets/injection-table.js +19 -0
  110. package/dist/modules/workflows/widgets/injection-table.js.map +7 -0
  111. package/generated/entities/api_key/index.ts +1 -0
  112. package/generated/entities/workflow_event_trigger/index.ts +15 -0
  113. package/generated/entities.ids.generated.ts +1 -0
  114. package/generated/entity-fields-registry.ts +2 -0
  115. package/package.json +2 -2
  116. package/src/modules/api_keys/data/entities.ts +4 -0
  117. package/src/modules/api_keys/migrations/.snapshot-open-mercato.json +9 -0
  118. package/src/modules/api_keys/migrations/Migration20260125204102.ts +13 -0
  119. package/src/modules/api_keys/services/apiKeyService.ts +85 -0
  120. package/src/modules/auth/events.ts +39 -0
  121. package/src/modules/auth/services/rbacService.ts +1 -1
  122. package/src/modules/business_rules/api/execute/[ruleId]/route.ts +163 -0
  123. package/src/modules/business_rules/data/validators.ts +40 -0
  124. package/src/modules/business_rules/index.ts +25 -0
  125. package/src/modules/business_rules/lib/rule-engine.ts +281 -1
  126. package/src/modules/catalog/events.ts +45 -0
  127. package/src/modules/customers/events.ts +63 -0
  128. package/src/modules/directory/events.ts +31 -0
  129. package/src/modules/sales/acl.ts +1 -0
  130. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +16 -0
  131. package/src/modules/sales/commands/documents.ts +74 -1
  132. package/src/modules/sales/events.ts +82 -0
  133. package/src/modules/sales/lib/dictionaries.ts +3 -0
  134. package/src/modules/sales/lib/frontend/documentDataEvents.ts +28 -0
  135. package/src/modules/workflows/acl.ts +2 -0
  136. package/src/modules/workflows/api/__tests__/instances.route.test.ts +5 -2
  137. package/src/modules/workflows/api/instances/route.ts +21 -7
  138. package/src/modules/workflows/api/tasks/route.ts +7 -1
  139. package/src/modules/workflows/backend/definitions/[id]/page.meta.ts +1 -1
  140. package/src/modules/workflows/backend/definitions/[id]/page.tsx +9 -0
  141. package/src/modules/workflows/backend/definitions/create/page.meta.ts +1 -1
  142. package/src/modules/workflows/backend/definitions/create/page.tsx +9 -0
  143. package/src/modules/workflows/backend/definitions/visual-editor/page.meta.ts +1 -1
  144. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +21 -3
  145. package/src/modules/workflows/backend/events/[id]/page.meta.ts +2 -2
  146. package/src/modules/workflows/backend/events/[id]/page.tsx +1 -1
  147. package/src/modules/workflows/backend/instances/[id]/page.meta.ts +2 -2
  148. package/src/modules/workflows/backend/tasks/[id]/page.meta.ts +2 -2
  149. package/src/modules/workflows/backend/tasks/[id]/page.tsx +1 -1
  150. package/src/modules/workflows/backend/tasks/page.tsx +5 -6
  151. package/src/modules/workflows/cli.ts +111 -0
  152. package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +581 -0
  153. package/src/modules/workflows/components/EventTriggersEditor.tsx +664 -0
  154. package/src/modules/workflows/data/entities.ts +124 -0
  155. package/src/modules/workflows/data/validators.ts +138 -0
  156. package/src/modules/workflows/events.ts +49 -0
  157. package/src/modules/workflows/examples/checkout-demo-definition.json +1 -5
  158. package/src/modules/workflows/examples/order-approval-definition.json +257 -0
  159. package/src/modules/workflows/examples/order-approval-guard-rules.json +32 -0
  160. package/src/modules/workflows/i18n/en.json +71 -0
  161. package/src/modules/workflows/lib/__tests__/activity-executor.test.ts +43 -36
  162. package/src/modules/workflows/lib/__tests__/transition-handler.test.ts +170 -90
  163. package/src/modules/workflows/lib/activity-executor.ts +129 -16
  164. package/src/modules/workflows/lib/event-trigger-service.ts +557 -0
  165. package/src/modules/workflows/lib/graph-utils.ts +117 -2
  166. package/src/modules/workflows/lib/seeds.ts +34 -8
  167. package/src/modules/workflows/lib/start-validator.ts +38 -28
  168. package/src/modules/workflows/lib/transition-handler.ts +208 -55
  169. package/src/modules/workflows/migrations/Migration20260123143500.ts +38 -0
  170. package/src/modules/workflows/subscribers/event-trigger.ts +109 -0
  171. package/src/modules/workflows/widgets/injection/order-approval/widget.client.tsx +446 -0
  172. package/src/modules/workflows/widgets/injection/order-approval/widget.ts +16 -0
  173. package/src/modules/workflows/widgets/injection-table.ts +21 -0
@@ -4,9 +4,60 @@ import { hash, compare } from 'bcryptjs'
4
4
  import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
5
5
  import { Role } from '@open-mercato/core/modules/auth/data/entities'
6
6
  import { ApiKey } from '../data/entities'
7
+ import { createKmsService } from '@open-mercato/shared/lib/encryption/kms'
8
+ import { encryptWithAesGcm, decryptWithAesGcm } from '@open-mercato/shared/lib/encryption/aes'
7
9
 
8
10
  const BCRYPT_COST = 10
9
11
 
12
+ // =============================================================================
13
+ // Session Secret Encryption Helpers
14
+ // =============================================================================
15
+
16
+ /**
17
+ * Encrypt an API key secret for storage.
18
+ * Uses tenant-specific DEK if available, otherwise returns null.
19
+ */
20
+ async function encryptSessionSecret(
21
+ secret: string,
22
+ tenantId: string | null
23
+ ): Promise<string | null> {
24
+ if (!tenantId) return null
25
+
26
+ const kms = createKmsService()
27
+ if (!kms.isHealthy()) return null
28
+
29
+ const dek = await kms.getTenantDek(tenantId)
30
+ if (!dek) {
31
+ // Try to create a DEK if one doesn't exist
32
+ const created = await kms.createTenantDek(tenantId)
33
+ if (!created) return null
34
+ const encrypted = encryptWithAesGcm(secret, created.key)
35
+ return encrypted.value
36
+ }
37
+
38
+ const encrypted = encryptWithAesGcm(secret, dek.key)
39
+ return encrypted.value
40
+ }
41
+
42
+ /**
43
+ * Decrypt an API key secret from storage.
44
+ * Returns null if decryption fails or no DEK available.
45
+ */
46
+ async function decryptSessionSecret(
47
+ encrypted: string,
48
+ tenantId: string | null
49
+ ): Promise<string | null> {
50
+ if (!tenantId || !encrypted) return null
51
+
52
+ const kms = createKmsService()
53
+ if (!kms.isHealthy()) return null
54
+
55
+ const dek = await kms.getTenantDek(tenantId)
56
+ if (!dek) return null
57
+
58
+ return decryptWithAesGcm(encrypted, dek.key)
59
+ }
60
+
10
61
  export type CreateApiKeyInput = {
11
62
  name: string
12
63
  description?: string | null
@@ -117,6 +168,7 @@ export function generateSessionToken(): string {
117
168
  /**
118
169
  * Create an ephemeral API key scoped to a chat session.
119
170
  * The key inherits the user's roles and expires after ttlMinutes (default 30).
171
+ * The API key secret is encrypted and stored so it can be recovered for API calls.
120
172
  */
121
173
  export async function createSessionApiKey(
122
174
  em: EntityManager,
@@ -127,6 +179,9 @@ export async function createSessionApiKey(
127
179
  const expiresAt = new Date(Date.now() + ttl * 60 * 1000)
128
180
  const keyHash = await hashApiKey(secret)
129
181
 
182
+ // Encrypt the secret for later retrieval (used by MCP server for API calls)
183
+ const encryptedSecret = await encryptSessionSecret(secret, input.tenantId ?? null)
184
+
130
185
  const record = em.create(ApiKey, {
131
186
  name: `__session_${input.sessionToken}__`,
132
187
  description: 'Ephemeral session API key for AI chat',
@@ -138,6 +193,7 @@ export async function createSessionApiKey(
138
193
  createdBy: input.userId,
139
194
  sessionToken: input.sessionToken,
140
195
  sessionUserId: input.userId,
196
+ sessionSecretEncrypted: encryptedSecret,
141
197
  expiresAt,
142
198
  createdAt: new Date(),
143
199
  })
@@ -172,6 +228,35 @@ export async function findApiKeyBySessionToken(
172
228
  return record
173
229
  }
174
230
 
231
+ /**
232
+ * Find a session API key with its decrypted secret.
233
+ * Returns null if not found, expired, deleted, or decryption fails.
234
+ * This is used by the MCP server to recover the API key secret for making
235
+ * authenticated API calls on behalf of the user.
236
+ */
237
+ export async function findSessionApiKeyWithSecret(
238
+ em: EntityManager,
239
+ sessionToken: string
240
+ ): Promise<{ key: ApiKey; secret: string } | null> {
241
+ const record = await findApiKeyBySessionToken(em, sessionToken)
242
+ if (!record) return null
243
+
244
+ // If no encrypted secret stored, cannot recover
245
+ if (!record.sessionSecretEncrypted) {
246
+ console.warn('[ApiKeyService] Session key has no encrypted secret:', sessionToken.slice(0, 12))
247
+ return null
248
+ }
249
+
250
+ // Decrypt the secret
251
+ const secret = await decryptSessionSecret(record.sessionSecretEncrypted, record.tenantId ?? null)
252
+ if (!secret) {
253
+ console.warn('[ApiKeyService] Failed to decrypt session secret:', sessionToken.slice(0, 12))
254
+ return null
255
+ }
256
+
257
+ return { key: record, secret }
258
+ }
259
+
175
260
  /**
176
261
  * Delete an ephemeral API key by its session token.
177
262
  */
@@ -0,0 +1,39 @@
1
+ import { createModuleEvents } from '@open-mercato/shared/modules/events'
2
+
3
+ /**
4
+ * Auth Module Events
5
+ *
6
+ * Declares all events that can be emitted by the auth module.
7
+ */
8
+ const events = [
9
+ // Users
10
+ { id: 'auth.users.created', label: 'User Created', entity: 'users', category: 'crud' },
11
+ { id: 'auth.users.updated', label: 'User Updated', entity: 'users', category: 'crud' },
12
+ { id: 'auth.users.deleted', label: 'User Deleted', entity: 'users', category: 'crud' },
13
+
14
+ // Roles
15
+ { id: 'auth.roles.created', label: 'Role Created', entity: 'roles', category: 'crud' },
16
+ { id: 'auth.roles.updated', label: 'Role Updated', entity: 'roles', category: 'crud' },
17
+ { id: 'auth.roles.deleted', label: 'Role Deleted', entity: 'roles', category: 'crud' },
18
+
19
+ // Authentication events
20
+ { id: 'auth.login.success', label: 'Login Successful', category: 'lifecycle' },
21
+ { id: 'auth.login.failed', label: 'Login Failed', category: 'lifecycle' },
22
+ { id: 'auth.logout', label: 'User Logged Out', category: 'lifecycle' },
23
+ { id: 'auth.password.changed', label: 'Password Changed', category: 'lifecycle' },
24
+ { id: 'auth.password.reset.requested', label: 'Password Reset Requested', category: 'lifecycle' },
25
+ { id: 'auth.password.reset.completed', label: 'Password Reset Completed', category: 'lifecycle' },
26
+ ] as const
27
+
28
+ export const eventsConfig = createModuleEvents({
29
+ moduleId: 'auth',
30
+ events,
31
+ })
32
+
33
+ /** Type-safe event emitter for auth module */
34
+ export const emitAuthEvent = eventsConfig.emit
35
+
36
+ /** Event IDs that can be emitted by the auth module */
37
+ export type AuthEventId = typeof events[number]['id']
38
+
39
+ export default eventsConfig
@@ -69,7 +69,7 @@ export class RbacService {
69
69
  return granted === required
70
70
  }
71
71
 
72
- private hasAllFeatures(required: string[], granted: string[]): boolean {
72
+ public hasAllFeatures(required: string[], granted: string[]): boolean {
73
73
  if (!required.length) return true
74
74
  if (!granted.length) return false
75
75
  return required.every((req) => granted.some((g) => this.matchFeature(req, g)))
@@ -0,0 +1,163 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
4
+ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
5
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
6
+ import type { EntityManager } from '@mikro-orm/postgresql'
7
+ import * as ruleEngine from '../../../lib/rule-engine'
8
+
9
+ const executeByIdRequestSchema = z.object({
10
+ data: z.any(),
11
+ dryRun: z.boolean().optional().default(false),
12
+ entityType: z.string().optional(),
13
+ entityId: z.string().optional(),
14
+ eventType: z.string().optional(),
15
+ })
16
+
17
+ const executeByIdResponseSchema = z.object({
18
+ success: z.boolean(),
19
+ ruleId: z.string(),
20
+ ruleName: z.string(),
21
+ conditionResult: z.boolean(),
22
+ actionsExecuted: z.object({
23
+ success: z.boolean(),
24
+ results: z.array(z.object({
25
+ type: z.string(),
26
+ success: z.boolean(),
27
+ error: z.string().optional(),
28
+ })),
29
+ }).nullable(),
30
+ executionTime: z.number(),
31
+ error: z.string().optional(),
32
+ logId: z.string().optional(),
33
+ })
34
+
35
+ const errorResponseSchema = z.object({
36
+ error: z.string(),
37
+ })
38
+
39
+ const routeMetadata = {
40
+ POST: { requireAuth: true, requireFeatures: ['business_rules.execute'] },
41
+ }
42
+
43
+ export const metadata = routeMetadata
44
+
45
+ interface RouteContext {
46
+ params: Promise<{ ruleId: string }>
47
+ }
48
+
49
+ export async function POST(req: Request, context: RouteContext) {
50
+ const auth = await getAuthFromRequest(req)
51
+ if (!auth) {
52
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
53
+ }
54
+
55
+ const params = await context.params
56
+ const ruleId = params.ruleId
57
+
58
+ if (!ruleId || !z.uuid().safeParse(ruleId).success) {
59
+ return NextResponse.json({ error: 'Invalid rule ID' }, { status: 400 })
60
+ }
61
+
62
+ const container = await createRequestContainer()
63
+ const em = container.resolve('em') as EntityManager
64
+
65
+ let body: any
66
+ try {
67
+ body = await req.json()
68
+ } catch {
69
+ return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })
70
+ }
71
+
72
+ const parsed = executeByIdRequestSchema.safeParse(body)
73
+ if (!parsed.success) {
74
+ const errors = parsed.error.issues.map(e => `${e.path.join('.')}: ${e.message}`)
75
+ return NextResponse.json({ error: `Validation failed: ${errors.join(', ')}` }, { status: 400 })
76
+ }
77
+
78
+ const { data, dryRun, entityType, entityId, eventType } = parsed.data
79
+
80
+ const execContext: ruleEngine.DirectRuleExecutionContext = {
81
+ ruleId,
82
+ data,
83
+ user: {
84
+ id: auth.sub,
85
+ email: auth.email,
86
+ role: (auth.role as string) ?? undefined,
87
+ },
88
+ tenantId: auth.tenantId ?? '',
89
+ organizationId: auth.orgId ?? '',
90
+ executedBy: auth.sub ?? auth.email ?? undefined,
91
+ dryRun,
92
+ entityType,
93
+ entityId,
94
+ eventType,
95
+ }
96
+
97
+ try {
98
+ const result = await ruleEngine.executeRuleById(em, execContext)
99
+
100
+ const response = {
101
+ success: result.success,
102
+ ruleId: result.ruleId,
103
+ ruleName: result.ruleName,
104
+ conditionResult: result.conditionResult,
105
+ actionsExecuted: result.actionsExecuted ? {
106
+ success: result.actionsExecuted.success,
107
+ results: result.actionsExecuted.results.map(ar => ({
108
+ type: ar.action.type,
109
+ success: ar.success,
110
+ error: ar.error,
111
+ })),
112
+ } : null,
113
+ executionTime: result.executionTime,
114
+ error: result.error,
115
+ logId: result.logId,
116
+ }
117
+
118
+ // Return appropriate status based on result
119
+ const status = result.success ? 200 : (result.error === 'Rule not found' ? 404 : 200)
120
+ return NextResponse.json(response, { status })
121
+ } catch (error) {
122
+ const errorMessage = error instanceof Error ? error.message : String(error)
123
+ return NextResponse.json(
124
+ { error: `Rule execution failed: ${errorMessage}` },
125
+ { status: 500 }
126
+ )
127
+ }
128
+ }
129
+
130
+ export const openApi: OpenApiRouteDoc = {
131
+ tag: 'Business Rules',
132
+ summary: 'Execute a specific business rule by ID',
133
+ methods: {
134
+ POST: {
135
+ summary: 'Execute a specific rule by its database UUID',
136
+ description: 'Directly executes a specific business rule identified by its UUID, bypassing the normal entityType/eventType discovery mechanism. Useful for workflows and targeted rule execution.',
137
+ pathParams: z.object({
138
+ ruleId: z.string().uuid().describe('The database UUID of the business rule to execute'),
139
+ }),
140
+ requestBody: {
141
+ contentType: 'application/json',
142
+ schema: executeByIdRequestSchema,
143
+ },
144
+ responses: [
145
+ {
146
+ status: 200,
147
+ description: 'Rule executed successfully',
148
+ schema: executeByIdResponseSchema,
149
+ },
150
+ {
151
+ status: 404,
152
+ description: 'Rule not found',
153
+ schema: errorResponseSchema,
154
+ },
155
+ ],
156
+ errors: [
157
+ { status: 400, description: 'Invalid request payload or rule ID', schema: errorResponseSchema },
158
+ { status: 401, description: 'Unauthorized', schema: errorResponseSchema },
159
+ { status: 500, description: 'Execution error', schema: errorResponseSchema },
160
+ ],
161
+ },
162
+ },
163
+ }
@@ -287,3 +287,43 @@ export const ruleDiscoveryOptionsSchema = z.object({
287
287
  })
288
288
 
289
289
  export type RuleDiscoveryOptionsInput = z.infer<typeof ruleDiscoveryOptionsSchema>
290
+
291
+ // Direct Rule Execution Context Schema (for executing a specific rule by ID)
292
+ export const directRuleExecutionContextSchema = z.object({
293
+ ruleId: z.uuid('ruleId must be a valid UUID'),
294
+ data: z.any(),
295
+ user: z.looseObject({
296
+ id: z.string().optional(),
297
+ email: z.string().optional(),
298
+ role: z.string().optional(),
299
+ }).optional(),
300
+ tenantId: z.uuid('tenantId must be a valid UUID'),
301
+ organizationId: z.uuid('organizationId must be a valid UUID'),
302
+ executedBy: z.string().optional(),
303
+ dryRun: z.boolean().optional(),
304
+ entityType: z.string().optional(),
305
+ entityId: z.string().optional(),
306
+ eventType: z.string().optional(),
307
+ })
308
+
309
+ export type DirectRuleExecutionContextInput = z.infer<typeof directRuleExecutionContextSchema>
310
+
311
+ // Rule ID Execution Context Schema (for executing a specific rule by its string rule_id identifier)
312
+ export const ruleIdExecutionContextSchema = z.object({
313
+ ruleId: z.string().min(1, 'ruleId must be a non-empty string').max(50),
314
+ data: z.any(),
315
+ user: z.looseObject({
316
+ id: z.string().optional(),
317
+ email: z.string().optional(),
318
+ role: z.string().optional(),
319
+ }).optional(),
320
+ tenantId: z.uuid('tenantId must be a valid UUID'),
321
+ organizationId: z.uuid('organizationId must be a valid UUID'),
322
+ executedBy: z.string().optional(),
323
+ dryRun: z.boolean().optional(),
324
+ entityType: z.string().optional(),
325
+ entityId: z.string().optional(),
326
+ eventType: z.string().optional(),
327
+ })
328
+
329
+ export type RuleIdExecutionContextInput = z.infer<typeof ruleIdExecutionContextSchema>
@@ -8,3 +8,28 @@ export const metadata: ModuleInfo = {
8
8
  author: 'Patryk Lewczuk',
9
9
  license: 'Proprietary',
10
10
  }
11
+
12
+ // Export rule engine types and functions for programmatic usage
13
+ export {
14
+ executeRules,
15
+ executeRuleById,
16
+ executeRuleByRuleId,
17
+ executeSingleRule,
18
+ findApplicableRules,
19
+ logRuleExecution,
20
+ type RuleEngineContext,
21
+ type RuleEngineResult,
22
+ type RuleExecutionResult,
23
+ type RuleDiscoveryOptions,
24
+ type DirectRuleExecutionContext,
25
+ type DirectRuleExecutionResult,
26
+ type RuleIdExecutionContext,
27
+ } from './lib/rule-engine'
28
+
29
+ // Export validator schemas
30
+ export {
31
+ directRuleExecutionContextSchema,
32
+ ruleIdExecutionContextSchema,
33
+ type DirectRuleExecutionContextInput,
34
+ type RuleIdExecutionContextInput,
35
+ } from './data/validators'