@open-mercato/core 0.6.7-develop.6795.1.8a3f27921c → 0.6.7-develop.6824.1.21fff90682

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 (228) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/customer_deal_person_link/index.js +2 -0
  3. package/dist/generated/entities/customer_deal_person_link/index.js.map +2 -2
  4. package/dist/generated/entities/customer_user_invitation/index.js +2 -0
  5. package/dist/generated/entities/customer_user_invitation/index.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +2 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/auth/lib/backendChrome.js +10 -9
  9. package/dist/modules/auth/lib/backendChrome.js.map +2 -2
  10. package/dist/modules/auth/lib/tenantAccess.js +7 -0
  11. package/dist/modules/auth/lib/tenantAccess.js.map +2 -2
  12. package/dist/modules/business_rules/api/openmercato-call-options/route.js +85 -0
  13. package/dist/modules/business_rules/api/openmercato-call-options/route.js.map +7 -0
  14. package/dist/modules/business_rules/api/rules/route.js +71 -4
  15. package/dist/modules/business_rules/api/rules/route.js.map +2 -2
  16. package/dist/modules/business_rules/components/ActionBuilder.js +47 -4
  17. package/dist/modules/business_rules/components/ActionBuilder.js.map +3 -3
  18. package/dist/modules/business_rules/components/ActionRow.js +106 -16
  19. package/dist/modules/business_rules/components/ActionRow.js.map +2 -2
  20. package/dist/modules/business_rules/components/utils/actionValidation.js +6 -0
  21. package/dist/modules/business_rules/components/utils/actionValidation.js.map +2 -2
  22. package/dist/modules/business_rules/lib/action-executor.js +148 -0
  23. package/dist/modules/business_rules/lib/action-executor.js.map +2 -2
  24. package/dist/modules/business_rules/lib/openmercato-call-options-types.js +5 -0
  25. package/dist/modules/business_rules/lib/openmercato-call-options-types.js.map +7 -0
  26. package/dist/modules/business_rules/lib/openmercato-call-options.js +231 -0
  27. package/dist/modules/business_rules/lib/openmercato-call-options.js.map +7 -0
  28. package/dist/modules/business_rules/lib/rule-engine.js +5 -1
  29. package/dist/modules/business_rules/lib/rule-engine.js.map +2 -2
  30. package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js +11 -1
  31. package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js.map +2 -2
  32. package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js +11 -6
  33. package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js.map +2 -2
  34. package/dist/modules/configs/lib/upgrade-actions.js +12 -0
  35. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  36. package/dist/modules/customer_accounts/api/admin/users/[id].js +10 -1
  37. package/dist/modules/customer_accounts/api/admin/users/[id].js.map +2 -2
  38. package/dist/modules/customer_accounts/api/admin/users-invite.js +61 -4
  39. package/dist/modules/customer_accounts/api/admin/users-invite.js.map +2 -2
  40. package/dist/modules/customer_accounts/api/portal/users-invite.js +20 -2
  41. package/dist/modules/customer_accounts/api/portal/users-invite.js.map +2 -2
  42. package/dist/modules/customer_accounts/data/entities.js +3 -0
  43. package/dist/modules/customer_accounts/data/entities.js.map +2 -2
  44. package/dist/modules/customer_accounts/data/validators.js +1 -0
  45. package/dist/modules/customer_accounts/data/validators.js.map +2 -2
  46. package/dist/modules/customer_accounts/emails/CustomerInvitationEmail.js +26 -0
  47. package/dist/modules/customer_accounts/emails/CustomerInvitationEmail.js.map +7 -0
  48. package/dist/modules/customer_accounts/lib/customerEntityOwnership.js +29 -3
  49. package/dist/modules/customer_accounts/lib/customerEntityOwnership.js.map +2 -2
  50. package/dist/modules/customer_accounts/lib/customerUrl.js +19 -10
  51. package/dist/modules/customer_accounts/lib/customerUrl.js.map +2 -2
  52. package/dist/modules/customer_accounts/lib/invitationEmail.js +35 -0
  53. package/dist/modules/customer_accounts/lib/invitationEmail.js.map +7 -0
  54. package/dist/modules/customer_accounts/migrations/Migration20260723120000_customer_accounts.js +13 -0
  55. package/dist/modules/customer_accounts/migrations/Migration20260723120000_customer_accounts.js.map +7 -0
  56. package/dist/modules/customer_accounts/services/customerInvitationService.js +32 -2
  57. package/dist/modules/customer_accounts/services/customerInvitationService.js.map +2 -2
  58. package/dist/modules/customer_accounts/subscribers/autoLinkCrm.js +47 -7
  59. package/dist/modules/customer_accounts/subscribers/autoLinkCrm.js.map +2 -2
  60. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +2 -2
  61. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
  62. package/dist/modules/customer_accounts/widgets/injection-table.js +1 -1
  63. package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
  64. package/dist/modules/customers/api/deals/[id]/people/route.js +4 -2
  65. package/dist/modules/customers/api/deals/[id]/people/route.js.map +2 -2
  66. package/dist/modules/customers/api/deals/[id]/route.js +7 -3
  67. package/dist/modules/customers/api/deals/[id]/route.js.map +2 -2
  68. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +11 -1
  69. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  70. package/dist/modules/customers/backend/customers/deals/[id]/page.js +14 -8
  71. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  72. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +21 -6
  73. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  74. package/dist/modules/customers/commands/deals.js +48 -10
  75. package/dist/modules/customers/commands/deals.js.map +2 -2
  76. package/dist/modules/customers/commands/people.js +2 -0
  77. package/dist/modules/customers/commands/people.js.map +2 -2
  78. package/dist/modules/customers/components/detail/CompanyDetailTabs.js +26 -6
  79. package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +2 -2
  80. package/dist/modules/customers/components/detail/DealDetailTabs.js +30 -8
  81. package/dist/modules/customers/components/detail/DealDetailTabs.js.map +2 -2
  82. package/dist/modules/customers/components/detail/PersonDetailTabs.js +30 -8
  83. package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
  84. package/dist/modules/customers/data/entities.js +8 -0
  85. package/dist/modules/customers/data/entities.js.map +2 -2
  86. package/dist/modules/customers/data/validators.js +2 -1
  87. package/dist/modules/customers/data/validators.js.map +2 -2
  88. package/dist/modules/customers/migrations/Migration20260723210000_customers.js +15 -0
  89. package/dist/modules/customers/migrations/Migration20260723210000_customers.js.map +7 -0
  90. package/dist/modules/directory/api/tenants/route.js +8 -2
  91. package/dist/modules/directory/api/tenants/route.js.map +2 -2
  92. package/dist/modules/directory/commands/tenants.js +6 -0
  93. package/dist/modules/directory/commands/tenants.js.map +2 -2
  94. package/dist/modules/entities/api/entity-settings.js +59 -9
  95. package/dist/modules/entities/api/entity-settings.js.map +2 -2
  96. package/dist/modules/entities/backend/config/settings/page.js +10 -0
  97. package/dist/modules/entities/backend/config/settings/page.js.map +7 -0
  98. package/dist/modules/entities/backend/config/settings/page.meta.js +36 -0
  99. package/dist/modules/entities/backend/config/settings/page.meta.js.map +7 -0
  100. package/dist/modules/entities/backend/entities/user/create/page.js +16 -7
  101. package/dist/modules/entities/backend/entities/user/create/page.js.map +2 -2
  102. package/dist/modules/entities/components/EntitySettingsManager.js +136 -0
  103. package/dist/modules/entities/components/EntitySettingsManager.js.map +7 -0
  104. package/dist/modules/inbox_ops/api/webhook/inbound.js +14 -10
  105. package/dist/modules/inbox_ops/api/webhook/inbound.js.map +2 -2
  106. package/dist/modules/payment_gateways/api/sessions/route.js +1 -3
  107. package/dist/modules/payment_gateways/api/sessions/route.js.map +2 -2
  108. package/dist/modules/payment_gateways/api/webhook/[provider]/route.js +11 -1
  109. package/dist/modules/payment_gateways/api/webhook/[provider]/route.js.map +2 -2
  110. package/dist/modules/payment_gateways/data/entities.js +4 -0
  111. package/dist/modules/payment_gateways/data/entities.js.map +2 -2
  112. package/dist/modules/payment_gateways/lib/gateway-service.js +15 -6
  113. package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
  114. package/dist/modules/payment_gateways/lib/session-idempotency.js +33 -0
  115. package/dist/modules/payment_gateways/lib/session-idempotency.js.map +2 -2
  116. package/dist/modules/payment_gateways/migrations/Migration20260803103035_payment_gateways.js +18 -0
  117. package/dist/modules/payment_gateways/migrations/Migration20260803103035_payment_gateways.js.map +7 -0
  118. package/dist/modules/payment_gateways/setup.js +47 -0
  119. package/dist/modules/payment_gateways/setup.js.map +2 -2
  120. package/dist/modules/payment_gateways/workers/session-initialization-prune.js +50 -0
  121. package/dist/modules/payment_gateways/workers/session-initialization-prune.js.map +7 -0
  122. package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js +134 -0
  123. package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js.map +7 -0
  124. package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.meta.js +11 -0
  125. package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.meta.js.map +7 -0
  126. package/dist/modules/portal/lib/navigation.js +7 -0
  127. package/dist/modules/portal/lib/navigation.js.map +7 -0
  128. package/dist/modules/query_index/lib/engine.js +43 -0
  129. package/dist/modules/query_index/lib/engine.js.map +2 -2
  130. package/dist/modules/query_index/lib/search-tokens.js +15 -5
  131. package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
  132. package/dist/modules/shipping_carriers/api/webhook/[provider]/route.js +11 -1
  133. package/dist/modules/shipping_carriers/api/webhook/[provider]/route.js.map +2 -2
  134. package/generated/entities/customer_deal_person_link/index.ts +1 -0
  135. package/generated/entities/customer_user_invitation/index.ts +1 -0
  136. package/generated/entity-fields-registry.ts +2 -0
  137. package/package.json +7 -7
  138. package/src/modules/auth/lib/backendChrome.tsx +15 -9
  139. package/src/modules/auth/lib/tenantAccess.ts +8 -0
  140. package/src/modules/business_rules/api/openmercato-call-options/route.ts +94 -0
  141. package/src/modules/business_rules/api/rules/route.ts +92 -4
  142. package/src/modules/business_rules/components/ActionBuilder.tsx +54 -5
  143. package/src/modules/business_rules/components/ActionRow.tsx +156 -18
  144. package/src/modules/business_rules/components/utils/actionValidation.ts +6 -0
  145. package/src/modules/business_rules/i18n/de.json +12 -0
  146. package/src/modules/business_rules/i18n/en.json +12 -0
  147. package/src/modules/business_rules/i18n/es.json +12 -0
  148. package/src/modules/business_rules/i18n/pl.json +12 -0
  149. package/src/modules/business_rules/lib/action-executor.ts +198 -0
  150. package/src/modules/business_rules/lib/openmercato-call-options-types.ts +31 -0
  151. package/src/modules/business_rules/lib/openmercato-call-options.ts +297 -0
  152. package/src/modules/business_rules/lib/rule-engine.ts +4 -0
  153. package/src/modules/communication_channels/api/post/webhook/[provider]/route.ts +11 -1
  154. package/src/modules/communication_channels/api/post/webhooks/gmail/route.ts +17 -11
  155. package/src/modules/configs/lib/upgrade-actions.ts +14 -0
  156. package/src/modules/customer_accounts/api/admin/users/[id].ts +16 -1
  157. package/src/modules/customer_accounts/api/admin/users-invite.ts +78 -3
  158. package/src/modules/customer_accounts/api/portal/users-invite.ts +22 -1
  159. package/src/modules/customer_accounts/data/entities.ts +3 -0
  160. package/src/modules/customer_accounts/data/validators.ts +1 -0
  161. package/src/modules/customer_accounts/emails/CustomerInvitationEmail.tsx +45 -0
  162. package/src/modules/customer_accounts/i18n/de.json +6 -0
  163. package/src/modules/customer_accounts/i18n/en.json +6 -0
  164. package/src/modules/customer_accounts/i18n/es.json +6 -0
  165. package/src/modules/customer_accounts/i18n/pl.json +6 -0
  166. package/src/modules/customer_accounts/lib/customerEntityOwnership.ts +68 -2
  167. package/src/modules/customer_accounts/lib/customerUrl.ts +27 -11
  168. package/src/modules/customer_accounts/lib/invitationEmail.ts +42 -0
  169. package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +16 -0
  170. package/src/modules/customer_accounts/migrations/Migration20260723120000_customer_accounts.ts +13 -0
  171. package/src/modules/customer_accounts/services/customerInvitationService.ts +56 -3
  172. package/src/modules/customer_accounts/subscribers/autoLinkCrm.ts +75 -8
  173. package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +2 -2
  174. package/src/modules/customer_accounts/widgets/injection-table.ts +4 -1
  175. package/src/modules/customers/api/deals/[id]/people/route.ts +3 -0
  176. package/src/modules/customers/api/deals/[id]/route.ts +10 -2
  177. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +12 -1
  178. package/src/modules/customers/backend/customers/deals/[id]/hooks/types.ts +1 -0
  179. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +15 -9
  180. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +24 -6
  181. package/src/modules/customers/commands/deals.ts +50 -9
  182. package/src/modules/customers/commands/people.ts +3 -0
  183. package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +30 -7
  184. package/src/modules/customers/components/detail/DealDetailTabs.tsx +34 -9
  185. package/src/modules/customers/components/detail/PersonDetailTabs.tsx +34 -9
  186. package/src/modules/customers/data/entities.ts +9 -1
  187. package/src/modules/customers/data/validators.ts +1 -0
  188. package/src/modules/customers/i18n/de.json +1 -0
  189. package/src/modules/customers/i18n/en.json +1 -0
  190. package/src/modules/customers/i18n/es.json +1 -0
  191. package/src/modules/customers/i18n/pl.json +1 -0
  192. package/src/modules/customers/migrations/.snapshot-open-mercato.json +27 -1
  193. package/src/modules/customers/migrations/Migration20260723210000_customers.ts +15 -0
  194. package/src/modules/directory/api/tenants/route.ts +9 -1
  195. package/src/modules/directory/commands/tenants.ts +6 -0
  196. package/src/modules/entities/api/entity-settings.ts +66 -22
  197. package/src/modules/entities/backend/config/settings/page.meta.ts +34 -0
  198. package/src/modules/entities/backend/config/settings/page.tsx +12 -0
  199. package/src/modules/entities/backend/entities/user/create/page.tsx +16 -7
  200. package/src/modules/entities/components/EntitySettingsManager.tsx +160 -0
  201. package/src/modules/entities/i18n/de.json +9 -0
  202. package/src/modules/entities/i18n/en.json +9 -0
  203. package/src/modules/entities/i18n/es.json +9 -0
  204. package/src/modules/entities/i18n/pl.json +9 -0
  205. package/src/modules/inbox_ops/api/webhook/inbound.ts +14 -12
  206. package/src/modules/payment_gateways/api/sessions/route.ts +1 -3
  207. package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +13 -1
  208. package/src/modules/payment_gateways/data/entities.ts +5 -0
  209. package/src/modules/payment_gateways/i18n/de.json +5 -1
  210. package/src/modules/payment_gateways/i18n/en.json +5 -1
  211. package/src/modules/payment_gateways/i18n/es.json +5 -1
  212. package/src/modules/payment_gateways/i18n/pl.json +5 -1
  213. package/src/modules/payment_gateways/lib/gateway-service.ts +17 -8
  214. package/src/modules/payment_gateways/lib/session-idempotency.ts +36 -0
  215. package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +10 -1
  216. package/src/modules/payment_gateways/migrations/Migration20260803103035_payment_gateways.ts +17 -0
  217. package/src/modules/payment_gateways/setup.ts +62 -0
  218. package/src/modules/payment_gateways/workers/session-initialization-prune.ts +63 -0
  219. package/src/modules/portal/frontend/[orgSlug]/portal/invite/page.meta.ts +9 -0
  220. package/src/modules/portal/frontend/[orgSlug]/portal/invite/page.tsx +164 -0
  221. package/src/modules/portal/i18n/de.json +18 -0
  222. package/src/modules/portal/i18n/en.json +18 -0
  223. package/src/modules/portal/i18n/es.json +18 -0
  224. package/src/modules/portal/i18n/pl.json +18 -0
  225. package/src/modules/portal/lib/navigation.ts +3 -0
  226. package/src/modules/query_index/lib/engine.ts +52 -0
  227. package/src/modules/query_index/lib/search-tokens.ts +16 -4
  228. package/src/modules/shipping_carriers/api/webhook/[provider]/route.ts +11 -1
@@ -1,4 +1,9 @@
1
1
  import { resolveSpecialValue } from "./value-resolver.js";
2
+ import {
3
+ findOpenMercatoEndpointOption,
4
+ getCurrentOpenMercatoEndpointOptions,
5
+ resolveOpenMercatoApiKeyProfile
6
+ } from "./openmercato-call-options.js";
2
7
  async function executeActions(actions, context) {
3
8
  const startTime = Date.now();
4
9
  const results = [];
@@ -63,6 +68,7 @@ function getActionHandler(actionType) {
63
68
  NOTIFY: handleNotify,
64
69
  SET_FIELD: handleSetField,
65
70
  CALL_WEBHOOK: handleCallWebhook,
71
+ CALL_OPEN_MERCATO: handleCallOpenMercato,
66
72
  EMIT_EVENT: handleEmitEvent
67
73
  };
68
74
  const handler = handlers[actionType];
@@ -176,6 +182,97 @@ async function handleCallWebhook(action, context) {
176
182
  status: "pending"
177
183
  };
178
184
  }
185
+ async function handleCallOpenMercato(action, context) {
186
+ const endpoint = String(action.config?.endpoint ?? "").trim();
187
+ const method = String(action.config?.method ?? "").trim().toUpperCase();
188
+ const apiKeyId = String(action.config?.apiKeyId ?? "").trim();
189
+ if (!endpoint) {
190
+ throw new Error("CALL_OPEN_MERCATO action requires an endpoint");
191
+ }
192
+ if (!method) {
193
+ throw new Error("CALL_OPEN_MERCATO action requires an HTTP method");
194
+ }
195
+ if (!apiKeyId) {
196
+ throw new Error("CALL_OPEN_MERCATO action requires an API key profile");
197
+ }
198
+ const endpointOptions = context.openMercatoEndpointOptions ?? await getCurrentOpenMercatoEndpointOptions();
199
+ const endpointOption = findOpenMercatoEndpointOption(
200
+ endpoint,
201
+ method,
202
+ endpointOptions
203
+ );
204
+ if (!endpointOption) {
205
+ throw new Error("CALL_OPEN_MERCATO action requires a currently available /api/* endpoint");
206
+ }
207
+ const em = context.em;
208
+ if (!em) {
209
+ throw new Error("CALL_OPEN_MERCATO action requires an EntityManager in action context");
210
+ }
211
+ const tenantId = context.tenantId ?? context.tenant?.id;
212
+ const organizationId = context.organizationId ?? context.organization?.id;
213
+ if (!tenantId || !organizationId) {
214
+ throw new Error("CALL_OPEN_MERCATO action requires tenant and organization context");
215
+ }
216
+ const apiKeyProfile = await resolveOpenMercatoApiKeyProfile(em, apiKeyId, {
217
+ tenantId,
218
+ organizationId
219
+ });
220
+ if (!apiKeyProfile) {
221
+ throw new Error("CALL_OPEN_MERCATO action requires an active API key profile in scope");
222
+ }
223
+ const roleIds = Array.isArray(apiKeyProfile.rolesJson) ? apiKeyProfile.rolesJson.filter((roleId) => typeof roleId === "string" && roleId.length > 0) : [];
224
+ if (roleIds.length === 0) {
225
+ throw new Error("CALL_OPEN_MERCATO action requires an API key profile with at least one role");
226
+ }
227
+ const { withOnetimeApiKey } = await import("../../api_keys/services/apiKeyService.js");
228
+ const fullUrl = buildOpenMercatoApiUrl(endpointOption.path);
229
+ const requestBody = method === "GET" ? void 0 : normalizeOpenMercatoRequestBody(action.config?.body, context);
230
+ return await withOnetimeApiKey(
231
+ em,
232
+ {
233
+ name: `__business_rule_${context.ruleId || "unknown"}__`,
234
+ description: `One-time key for business rule ${context.ruleId || "unknown"}`,
235
+ tenantId,
236
+ organizationId,
237
+ roles: roleIds,
238
+ createdBy: null,
239
+ expiresAt: null
240
+ },
241
+ async (apiKeySecret) => {
242
+ const headers = {
243
+ "Content-Type": "application/json",
244
+ "Authorization": `apikey ${apiKeySecret}`,
245
+ "X-Tenant-Id": tenantId,
246
+ "X-Organization-Id": organizationId
247
+ };
248
+ if (context.ruleId) headers["X-Business-Rule-Id"] = context.ruleId;
249
+ if (context.ruleName) headers["X-Business-Rule-Name"] = context.ruleName;
250
+ if (context.entityType) headers["X-Business-Rule-Entity-Type"] = context.entityType;
251
+ if (context.entityId) headers["X-Business-Rule-Entity-Id"] = context.entityId;
252
+ const response = await fetch(fullUrl, {
253
+ method,
254
+ headers,
255
+ body: requestBody === void 0 ? void 0 : JSON.stringify(requestBody)
256
+ });
257
+ const responseBody = await parseOpenMercatoResponseBody(response);
258
+ if (!response.ok) {
259
+ throwOpenMercatoResponseError(response.status);
260
+ }
261
+ return {
262
+ type: "CALL_OPEN_MERCATO",
263
+ endpoint: endpointOption.path,
264
+ method,
265
+ apiKeyId,
266
+ status: response.status,
267
+ statusText: response.statusText,
268
+ body: responseBody,
269
+ authenticated: true,
270
+ tenantId,
271
+ organizationId
272
+ };
273
+ }
274
+ );
275
+ }
179
276
  async function handleEmitEvent(action, context) {
180
277
  const eventName = action.config?.event;
181
278
  const payload = action.config?.payload || {};
@@ -213,6 +310,57 @@ function resolveValue(value, context) {
213
310
  }
214
311
  return value;
215
312
  }
313
+ function buildOpenMercatoApiUrl(endpoint) {
314
+ if (!endpoint.startsWith("/api/")) {
315
+ throw new Error(`CALL_OPEN_MERCATO only supports /api/* paths, got: ${endpoint}`);
316
+ }
317
+ const appUrl = (process.env.APP_URL || "http://localhost:3000").replace(/\/$/, "");
318
+ return `${appUrl}${endpoint}`;
319
+ }
320
+ function normalizeOpenMercatoRequestBody(value, context) {
321
+ if (value === void 0 || value === null || value === "") return void 0;
322
+ const interpolated = interpolateActionValue(value, context);
323
+ if (typeof interpolated !== "string") return interpolated;
324
+ const trimmed = interpolated.trim();
325
+ if (!trimmed) return void 0;
326
+ try {
327
+ return JSON.parse(trimmed);
328
+ } catch {
329
+ return interpolated;
330
+ }
331
+ }
332
+ function interpolateActionValue(value, context) {
333
+ if (typeof value === "string") {
334
+ return interpolateMessage(value, context);
335
+ }
336
+ if (Array.isArray(value)) {
337
+ return value.map((item) => interpolateActionValue(item, context));
338
+ }
339
+ if (value && typeof value === "object") {
340
+ return Object.fromEntries(
341
+ Object.entries(value).map(([key, entry]) => [key, interpolateActionValue(entry, context)])
342
+ );
343
+ }
344
+ return value;
345
+ }
346
+ async function parseOpenMercatoResponseBody(response) {
347
+ const contentType = response.headers.get("content-type");
348
+ try {
349
+ if (contentType && contentType.includes("application/json")) {
350
+ return await response.json();
351
+ }
352
+ return await response.text();
353
+ } catch {
354
+ return null;
355
+ }
356
+ }
357
+ function throwOpenMercatoResponseError(status) {
358
+ const error = new Error(`CALL_OPEN_MERCATO request failed with status ${status}`);
359
+ if (status >= 500) {
360
+ error.retriable = true;
361
+ }
362
+ throw error;
363
+ }
216
364
  export {
217
365
  executeAction,
218
366
  executeActions,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/business_rules/lib/action-executor.ts"],
4
- "sourcesContent": ["import type { EvaluationContext } from './expression-evaluator'\nimport { getNestedValue, resolveSpecialValue } from './value-resolver'\n\n/**\n * Action definition\n */\nexport interface Action {\n type: string\n config?: Record<string, any>\n}\n\n/**\n * Action execution context\n */\nexport interface ActionContext extends EvaluationContext {\n entityType?: string\n entityId?: string\n eventType?: string\n data?: any\n ruleId?: string\n ruleName?: string\n [key: string]: any\n}\n\n/**\n * Specific action result types\n */\nexport interface AllowTransitionResult {\n type: 'ALLOW_TRANSITION'\n allowed: true\n message: string\n}\n\nexport interface BlockTransitionResult {\n type: 'BLOCK_TRANSITION'\n allowed: false\n message: string\n}\n\nexport interface LogResult {\n type: 'LOG'\n level: string\n message: string\n timestamp: string\n}\n\nexport interface ShowErrorResult {\n type: 'SHOW_ERROR'\n severity: 'error'\n message: string\n}\n\nexport interface ShowWarningResult {\n type: 'SHOW_WARNING'\n severity: 'warning'\n message: string\n}\n\nexport interface ShowInfoResult {\n type: 'SHOW_INFO'\n severity: 'info'\n message: string\n}\n\nexport interface NotifyResult {\n type: 'NOTIFY'\n recipients: string[]\n subject: string\n message: string\n template?: string\n}\n\nexport interface SetFieldResult {\n type: 'SET_FIELD'\n field: string\n value: any\n}\n\nexport interface CallWebhookResult {\n type: 'CALL_WEBHOOK'\n url: string\n method: string\n headers: Record<string, any>\n body?: any\n status: 'pending'\n}\n\nexport interface EmitEventResult {\n type: 'EMIT_EVENT'\n event: string\n payload: Record<string, any>\n}\n\n/**\n * Union type of all action results\n */\nexport type ActionHandlerResult =\n | AllowTransitionResult\n | BlockTransitionResult\n | LogResult\n | ShowErrorResult\n | ShowWarningResult\n | ShowInfoResult\n | NotifyResult\n | SetFieldResult\n | CallWebhookResult\n | EmitEventResult\n\n/**\n * Action execution result\n */\nexport interface ActionResult {\n action: Action\n success: boolean\n result?: ActionHandlerResult\n error?: string\n executionTime: number\n}\n\n/**\n * Action execution outcome (aggregated results)\n */\nexport interface ActionExecutionOutcome {\n success: boolean\n results: ActionResult[]\n totalTime: number\n errors?: string[]\n}\n\n/**\n * Execute multiple actions in sequence\n */\nexport async function executeActions(\n actions: Action[],\n context: ActionContext\n): Promise<ActionExecutionOutcome> {\n const startTime = Date.now()\n const results: ActionResult[] = []\n const errors: string[] = []\n\n for (const action of actions) {\n try {\n const result = await executeAction(action, context)\n results.push(result)\n\n if (result.error) {\n errors.push(`Action ${action.type}: ${result.error}`)\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n errors.push(`Action ${action.type}: ${errorMessage}`)\n\n results.push({\n action,\n success: false,\n error: errorMessage,\n executionTime: 0,\n })\n }\n }\n\n const totalTime = Date.now() - startTime\n\n return {\n success: results.every((r) => r.success),\n results,\n totalTime,\n errors: errors.length > 0 ? errors : undefined,\n }\n}\n\n/**\n * Execute a single action\n */\nexport async function executeAction(action: Action, context: ActionContext): Promise<ActionResult> {\n const startTime = Date.now()\n\n try {\n const handler = getActionHandler(action.type)\n const result = await handler(action, context)\n\n const executionTime = Date.now() - startTime\n\n return {\n action,\n success: true,\n result,\n executionTime,\n }\n } catch (error) {\n const executionTime = Date.now() - startTime\n const errorMessage = error instanceof Error ? error.message : String(error)\n\n return {\n action,\n success: false,\n error: errorMessage,\n executionTime,\n }\n }\n}\n\n/**\n * Action handler function type\n */\ntype ActionHandler = (action: Action, context: ActionContext) => Promise<ActionHandlerResult>\n\n/**\n * Get action handler by type\n */\nfunction getActionHandler(actionType: string): ActionHandler {\n const handlers: Record<string, ActionHandler> = {\n ALLOW_TRANSITION: handleAllowTransition,\n BLOCK_TRANSITION: handleBlockTransition,\n LOG: handleLog,\n SHOW_ERROR: handleShowError,\n SHOW_WARNING: handleShowWarning,\n SHOW_INFO: handleShowInfo,\n NOTIFY: handleNotify,\n SET_FIELD: handleSetField,\n CALL_WEBHOOK: handleCallWebhook,\n EMIT_EVENT: handleEmitEvent,\n }\n\n const handler = handlers[actionType]\n\n if (!handler) {\n throw new Error(`Unknown action type: ${actionType}`)\n }\n\n return handler\n}\n\n/**\n * ALLOW_TRANSITION action handler\n */\nasync function handleAllowTransition(\n action: Action,\n context: ActionContext\n): Promise<AllowTransitionResult> {\n return {\n type: 'ALLOW_TRANSITION',\n allowed: true,\n message: interpolateMessage(action.config?.message || 'Transition allowed', context),\n }\n}\n\n/**\n * BLOCK_TRANSITION action handler\n */\nasync function handleBlockTransition(\n action: Action,\n context: ActionContext\n): Promise<BlockTransitionResult> {\n return {\n type: 'BLOCK_TRANSITION',\n allowed: false,\n message: interpolateMessage(action.config?.message || 'Transition blocked', context),\n }\n}\n\n/**\n * LOG action handler\n */\nasync function handleLog(action: Action, context: ActionContext): Promise<LogResult> {\n const level = action.config?.level || 'info'\n const message = interpolateMessage(action.config?.message || '', context)\n\n return {\n type: 'LOG',\n level,\n message,\n timestamp: new Date().toISOString(),\n }\n}\n\n/**\n * SHOW_ERROR action handler\n */\nasync function handleShowError(action: Action, context: ActionContext): Promise<ShowErrorResult> {\n const message = interpolateMessage(action.config?.message || '', context)\n\n return {\n type: 'SHOW_ERROR',\n severity: 'error',\n message,\n }\n}\n\n/**\n * SHOW_WARNING action handler\n */\nasync function handleShowWarning(\n action: Action,\n context: ActionContext\n): Promise<ShowWarningResult> {\n const message = interpolateMessage(action.config?.message || '', context)\n\n return {\n type: 'SHOW_WARNING',\n severity: 'warning',\n message,\n }\n}\n\n/**\n * SHOW_INFO action handler\n */\nasync function handleShowInfo(action: Action, context: ActionContext): Promise<ShowInfoResult> {\n const message = interpolateMessage(action.config?.message || '', context)\n\n return {\n type: 'SHOW_INFO',\n severity: 'info',\n message,\n }\n}\n\n/**\n * NOTIFY action handler\n */\nasync function handleNotify(action: Action, context: ActionContext): Promise<NotifyResult> {\n const recipients = action.config?.recipients || []\n const message = interpolateMessage(action.config?.message || '', context)\n const subject = interpolateMessage(action.config?.subject || '', context)\n const template = action.config?.template\n\n if (!Array.isArray(recipients)) {\n throw new Error('NOTIFY action requires recipients to be an array')\n }\n\n if (recipients.length === 0) {\n throw new Error('NOTIFY action requires at least one recipient')\n }\n\n return {\n type: 'NOTIFY',\n recipients,\n subject,\n message,\n template,\n }\n}\n\n/**\n * SET_FIELD action handler\n */\nasync function handleSetField(action: Action, context: ActionContext): Promise<SetFieldResult> {\n const field = action.config?.field\n const value = resolveValue(action.config?.value, context)\n\n if (!field) {\n throw new Error('SET_FIELD action requires a field name')\n }\n\n if (typeof field !== 'string' || field.trim() === '') {\n throw new Error('SET_FIELD action requires a non-empty field name')\n }\n\n return {\n type: 'SET_FIELD',\n field,\n value,\n }\n}\n\n/**\n * CALL_WEBHOOK action handler\n */\nasync function handleCallWebhook(\n action: Action,\n context: ActionContext\n): Promise<CallWebhookResult> {\n const url = interpolateMessage(action.config?.url || '', context)\n const method = action.config?.method || 'POST'\n const headers = action.config?.headers || {}\n const body = action.config?.body\n\n if (!url || url.trim() === '') {\n throw new Error('CALL_WEBHOOK action requires a non-empty URL')\n }\n\n const validMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']\n if (!validMethods.includes(method.toUpperCase())) {\n throw new Error(\n `CALL_WEBHOOK action requires a valid HTTP method (${validMethods.join(', ')})`\n )\n }\n\n return {\n type: 'CALL_WEBHOOK',\n url,\n method: method.toUpperCase(),\n headers,\n body,\n status: 'pending',\n }\n}\n\n/**\n * EMIT_EVENT action handler\n */\nasync function handleEmitEvent(action: Action, context: ActionContext): Promise<EmitEventResult> {\n const eventName = action.config?.event\n const payload = action.config?.payload || {}\n\n if (!eventName) {\n throw new Error('EMIT_EVENT action requires an event name')\n }\n\n if (typeof eventName !== 'string' || eventName.trim() === '') {\n throw new Error('EMIT_EVENT action requires a non-empty event name')\n }\n\n return {\n type: 'EMIT_EVENT',\n event: eventName,\n payload,\n }\n}\n\n/**\n * Interpolate message template with context values\n * Supports {{field}} syntax for variable substitution\n */\nexport function interpolateMessage(template: string, context: ActionContext): string {\n if (!template) {\n return ''\n }\n\n return template.replace(/\\{\\{([^}]+)\\}\\}/g, (match, path) => {\n const trimmedPath = path.trim()\n const value = resolveValue(`{{${trimmedPath}}}`, context)\n\n if (value === undefined || value === null) {\n return match\n }\n\n return String(value)\n })\n}\n\n/**\n * Resolve a value from context (supports special values like {{today}}, {{user.id}}, etc.)\n */\nfunction resolveValue(value: any, context: ActionContext): any {\n if (typeof value !== 'string') {\n return value\n }\n\n if (value.startsWith('{{') && value.endsWith('}}')) {\n return resolveSpecialValue(value, context)\n }\n\n return value\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager as PostgreSqlEntityManager } from '@mikro-orm/postgresql'\nimport type { EvaluationContext } from './expression-evaluator'\nimport { getNestedValue, resolveSpecialValue } from './value-resolver'\nimport {\n findOpenMercatoEndpointOption,\n getCurrentOpenMercatoEndpointOptions,\n resolveOpenMercatoApiKeyProfile,\n} from './openmercato-call-options'\nimport type { OpenMercatoEndpointOption } from './openmercato-call-options-types'\n\n/**\n * Action definition\n */\nexport interface Action {\n type: string\n config?: Record<string, any>\n}\n\n/**\n * Action execution context\n */\nexport interface ActionContext extends EvaluationContext {\n entityType?: string\n entityId?: string\n eventType?: string\n data?: any\n ruleId?: string\n ruleName?: string\n tenantId?: string\n organizationId?: string\n executedBy?: string | null\n em?: PostgreSqlEntityManager\n openMercatoEndpointOptions?: OpenMercatoEndpointOption[]\n [key: string]: any\n}\n\n/**\n * Specific action result types\n */\nexport interface AllowTransitionResult {\n type: 'ALLOW_TRANSITION'\n allowed: true\n message: string\n}\n\nexport interface BlockTransitionResult {\n type: 'BLOCK_TRANSITION'\n allowed: false\n message: string\n}\n\nexport interface LogResult {\n type: 'LOG'\n level: string\n message: string\n timestamp: string\n}\n\nexport interface ShowErrorResult {\n type: 'SHOW_ERROR'\n severity: 'error'\n message: string\n}\n\nexport interface ShowWarningResult {\n type: 'SHOW_WARNING'\n severity: 'warning'\n message: string\n}\n\nexport interface ShowInfoResult {\n type: 'SHOW_INFO'\n severity: 'info'\n message: string\n}\n\nexport interface NotifyResult {\n type: 'NOTIFY'\n recipients: string[]\n subject: string\n message: string\n template?: string\n}\n\nexport interface SetFieldResult {\n type: 'SET_FIELD'\n field: string\n value: any\n}\n\nexport interface CallWebhookResult {\n type: 'CALL_WEBHOOK'\n url: string\n method: string\n headers: Record<string, any>\n body?: any\n status: 'pending'\n}\n\nexport interface CallOpenMercatoResult {\n type: 'CALL_OPEN_MERCATO'\n endpoint: string\n method: string\n apiKeyId: string\n status: number\n statusText: string\n body: any\n authenticated: true\n tenantId: string\n organizationId: string\n}\n\nexport interface EmitEventResult {\n type: 'EMIT_EVENT'\n event: string\n payload: Record<string, any>\n}\n\n/**\n * Union type of all action results\n */\nexport type ActionHandlerResult =\n | AllowTransitionResult\n | BlockTransitionResult\n | LogResult\n | ShowErrorResult\n | ShowWarningResult\n | ShowInfoResult\n | NotifyResult\n | SetFieldResult\n | CallWebhookResult\n | CallOpenMercatoResult\n | EmitEventResult\n\n/**\n * Action execution result\n */\nexport interface ActionResult {\n action: Action\n success: boolean\n result?: ActionHandlerResult\n error?: string\n executionTime: number\n}\n\n/**\n * Action execution outcome (aggregated results)\n */\nexport interface ActionExecutionOutcome {\n success: boolean\n results: ActionResult[]\n totalTime: number\n errors?: string[]\n}\n\n/**\n * Execute multiple actions in sequence\n */\nexport async function executeActions(\n actions: Action[],\n context: ActionContext\n): Promise<ActionExecutionOutcome> {\n const startTime = Date.now()\n const results: ActionResult[] = []\n const errors: string[] = []\n\n for (const action of actions) {\n try {\n const result = await executeAction(action, context)\n results.push(result)\n\n if (result.error) {\n errors.push(`Action ${action.type}: ${result.error}`)\n }\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error)\n errors.push(`Action ${action.type}: ${errorMessage}`)\n\n results.push({\n action,\n success: false,\n error: errorMessage,\n executionTime: 0,\n })\n }\n }\n\n const totalTime = Date.now() - startTime\n\n return {\n success: results.every((r) => r.success),\n results,\n totalTime,\n errors: errors.length > 0 ? errors : undefined,\n }\n}\n\n/**\n * Execute a single action\n */\nexport async function executeAction(action: Action, context: ActionContext): Promise<ActionResult> {\n const startTime = Date.now()\n\n try {\n const handler = getActionHandler(action.type)\n const result = await handler(action, context)\n\n const executionTime = Date.now() - startTime\n\n return {\n action,\n success: true,\n result,\n executionTime,\n }\n } catch (error) {\n const executionTime = Date.now() - startTime\n const errorMessage = error instanceof Error ? error.message : String(error)\n\n return {\n action,\n success: false,\n error: errorMessage,\n executionTime,\n }\n }\n}\n\n/**\n * Action handler function type\n */\ntype ActionHandler = (action: Action, context: ActionContext) => Promise<ActionHandlerResult>\n\n/**\n * Get action handler by type\n */\nfunction getActionHandler(actionType: string): ActionHandler {\n const handlers: Record<string, ActionHandler> = {\n ALLOW_TRANSITION: handleAllowTransition,\n BLOCK_TRANSITION: handleBlockTransition,\n LOG: handleLog,\n SHOW_ERROR: handleShowError,\n SHOW_WARNING: handleShowWarning,\n SHOW_INFO: handleShowInfo,\n NOTIFY: handleNotify,\n SET_FIELD: handleSetField,\n CALL_WEBHOOK: handleCallWebhook,\n CALL_OPEN_MERCATO: handleCallOpenMercato,\n EMIT_EVENT: handleEmitEvent,\n }\n\n const handler = handlers[actionType]\n\n if (!handler) {\n throw new Error(`Unknown action type: ${actionType}`)\n }\n\n return handler\n}\n\n/**\n * ALLOW_TRANSITION action handler\n */\nasync function handleAllowTransition(\n action: Action,\n context: ActionContext\n): Promise<AllowTransitionResult> {\n return {\n type: 'ALLOW_TRANSITION',\n allowed: true,\n message: interpolateMessage(action.config?.message || 'Transition allowed', context),\n }\n}\n\n/**\n * BLOCK_TRANSITION action handler\n */\nasync function handleBlockTransition(\n action: Action,\n context: ActionContext\n): Promise<BlockTransitionResult> {\n return {\n type: 'BLOCK_TRANSITION',\n allowed: false,\n message: interpolateMessage(action.config?.message || 'Transition blocked', context),\n }\n}\n\n/**\n * LOG action handler\n */\nasync function handleLog(action: Action, context: ActionContext): Promise<LogResult> {\n const level = action.config?.level || 'info'\n const message = interpolateMessage(action.config?.message || '', context)\n\n return {\n type: 'LOG',\n level,\n message,\n timestamp: new Date().toISOString(),\n }\n}\n\n/**\n * SHOW_ERROR action handler\n */\nasync function handleShowError(action: Action, context: ActionContext): Promise<ShowErrorResult> {\n const message = interpolateMessage(action.config?.message || '', context)\n\n return {\n type: 'SHOW_ERROR',\n severity: 'error',\n message,\n }\n}\n\n/**\n * SHOW_WARNING action handler\n */\nasync function handleShowWarning(\n action: Action,\n context: ActionContext\n): Promise<ShowWarningResult> {\n const message = interpolateMessage(action.config?.message || '', context)\n\n return {\n type: 'SHOW_WARNING',\n severity: 'warning',\n message,\n }\n}\n\n/**\n * SHOW_INFO action handler\n */\nasync function handleShowInfo(action: Action, context: ActionContext): Promise<ShowInfoResult> {\n const message = interpolateMessage(action.config?.message || '', context)\n\n return {\n type: 'SHOW_INFO',\n severity: 'info',\n message,\n }\n}\n\n/**\n * NOTIFY action handler\n */\nasync function handleNotify(action: Action, context: ActionContext): Promise<NotifyResult> {\n const recipients = action.config?.recipients || []\n const message = interpolateMessage(action.config?.message || '', context)\n const subject = interpolateMessage(action.config?.subject || '', context)\n const template = action.config?.template\n\n if (!Array.isArray(recipients)) {\n throw new Error('NOTIFY action requires recipients to be an array')\n }\n\n if (recipients.length === 0) {\n throw new Error('NOTIFY action requires at least one recipient')\n }\n\n return {\n type: 'NOTIFY',\n recipients,\n subject,\n message,\n template,\n }\n}\n\n/**\n * SET_FIELD action handler\n */\nasync function handleSetField(action: Action, context: ActionContext): Promise<SetFieldResult> {\n const field = action.config?.field\n const value = resolveValue(action.config?.value, context)\n\n if (!field) {\n throw new Error('SET_FIELD action requires a field name')\n }\n\n if (typeof field !== 'string' || field.trim() === '') {\n throw new Error('SET_FIELD action requires a non-empty field name')\n }\n\n return {\n type: 'SET_FIELD',\n field,\n value,\n }\n}\n\n/**\n * CALL_WEBHOOK action handler\n */\nasync function handleCallWebhook(\n action: Action,\n context: ActionContext\n): Promise<CallWebhookResult> {\n const url = interpolateMessage(action.config?.url || '', context)\n const method = action.config?.method || 'POST'\n const headers = action.config?.headers || {}\n const body = action.config?.body\n\n if (!url || url.trim() === '') {\n throw new Error('CALL_WEBHOOK action requires a non-empty URL')\n }\n\n const validMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']\n if (!validMethods.includes(method.toUpperCase())) {\n throw new Error(\n `CALL_WEBHOOK action requires a valid HTTP method (${validMethods.join(', ')})`\n )\n }\n\n return {\n type: 'CALL_WEBHOOK',\n url,\n method: method.toUpperCase(),\n headers,\n body,\n status: 'pending',\n }\n}\n\n/**\n * CALL_OPEN_MERCATO action handler\n */\nasync function handleCallOpenMercato(\n action: Action,\n context: ActionContext\n): Promise<CallOpenMercatoResult> {\n const endpoint = String(action.config?.endpoint ?? '').trim()\n const method = String(action.config?.method ?? '').trim().toUpperCase()\n const apiKeyId = String(action.config?.apiKeyId ?? '').trim()\n\n if (!endpoint) {\n throw new Error('CALL_OPEN_MERCATO action requires an endpoint')\n }\n if (!method) {\n throw new Error('CALL_OPEN_MERCATO action requires an HTTP method')\n }\n if (!apiKeyId) {\n throw new Error('CALL_OPEN_MERCATO action requires an API key profile')\n }\n\n const endpointOptions = context.openMercatoEndpointOptions ?? await getCurrentOpenMercatoEndpointOptions()\n const endpointOption = findOpenMercatoEndpointOption(\n endpoint,\n method,\n endpointOptions,\n )\n if (!endpointOption) {\n throw new Error('CALL_OPEN_MERCATO action requires a currently available /api/* endpoint')\n }\n\n const em = context.em\n if (!em) {\n throw new Error('CALL_OPEN_MERCATO action requires an EntityManager in action context')\n }\n\n const tenantId = context.tenantId ?? context.tenant?.id\n const organizationId = context.organizationId ?? context.organization?.id\n if (!tenantId || !organizationId) {\n throw new Error('CALL_OPEN_MERCATO action requires tenant and organization context')\n }\n\n const apiKeyProfile = await resolveOpenMercatoApiKeyProfile(em, apiKeyId, {\n tenantId,\n organizationId,\n })\n if (!apiKeyProfile) {\n throw new Error('CALL_OPEN_MERCATO action requires an active API key profile in scope')\n }\n\n const roleIds = Array.isArray(apiKeyProfile.rolesJson)\n ? apiKeyProfile.rolesJson.filter((roleId): roleId is string => typeof roleId === 'string' && roleId.length > 0)\n : []\n if (roleIds.length === 0) {\n throw new Error('CALL_OPEN_MERCATO action requires an API key profile with at least one role')\n }\n\n const { withOnetimeApiKey } = await import('../../api_keys/services/apiKeyService')\n const fullUrl = buildOpenMercatoApiUrl(endpointOption.path)\n const requestBody = method === 'GET'\n ? undefined\n : normalizeOpenMercatoRequestBody(action.config?.body, context)\n\n return await withOnetimeApiKey(\n em,\n {\n name: `__business_rule_${context.ruleId || 'unknown'}__`,\n description: `One-time key for business rule ${context.ruleId || 'unknown'}`,\n tenantId,\n organizationId,\n roles: roleIds,\n createdBy: null,\n expiresAt: null,\n },\n async (apiKeySecret) => {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Authorization': `apikey ${apiKeySecret}`,\n 'X-Tenant-Id': tenantId,\n 'X-Organization-Id': organizationId,\n }\n if (context.ruleId) headers['X-Business-Rule-Id'] = context.ruleId\n if (context.ruleName) headers['X-Business-Rule-Name'] = context.ruleName\n if (context.entityType) headers['X-Business-Rule-Entity-Type'] = context.entityType\n if (context.entityId) headers['X-Business-Rule-Entity-Id'] = context.entityId\n\n const response = await fetch(fullUrl, {\n method,\n headers,\n body: requestBody === undefined ? undefined : JSON.stringify(requestBody),\n })\n\n const responseBody = await parseOpenMercatoResponseBody(response)\n if (!response.ok) {\n throwOpenMercatoResponseError(response.status)\n }\n\n return {\n type: 'CALL_OPEN_MERCATO',\n endpoint: endpointOption.path,\n method,\n apiKeyId,\n status: response.status,\n statusText: response.statusText,\n body: responseBody,\n authenticated: true,\n tenantId,\n organizationId,\n }\n },\n )\n}\n\n/**\n * EMIT_EVENT action handler\n */\nasync function handleEmitEvent(action: Action, context: ActionContext): Promise<EmitEventResult> {\n const eventName = action.config?.event\n const payload = action.config?.payload || {}\n\n if (!eventName) {\n throw new Error('EMIT_EVENT action requires an event name')\n }\n\n if (typeof eventName !== 'string' || eventName.trim() === '') {\n throw new Error('EMIT_EVENT action requires a non-empty event name')\n }\n\n return {\n type: 'EMIT_EVENT',\n event: eventName,\n payload,\n }\n}\n\n/**\n * Interpolate message template with context values\n * Supports {{field}} syntax for variable substitution\n */\nexport function interpolateMessage(template: string, context: ActionContext): string {\n if (!template) {\n return ''\n }\n\n return template.replace(/\\{\\{([^}]+)\\}\\}/g, (match, path) => {\n const trimmedPath = path.trim()\n const value = resolveValue(`{{${trimmedPath}}}`, context)\n\n if (value === undefined || value === null) {\n return match\n }\n\n return String(value)\n })\n}\n\n/**\n * Resolve a value from context (supports special values like {{today}}, {{user.id}}, etc.)\n */\nfunction resolveValue(value: any, context: ActionContext): any {\n if (typeof value !== 'string') {\n return value\n }\n\n if (value.startsWith('{{') && value.endsWith('}}')) {\n return resolveSpecialValue(value, context)\n }\n\n return value\n}\n\nfunction buildOpenMercatoApiUrl(endpoint: string): string {\n if (!endpoint.startsWith('/api/')) {\n throw new Error(`CALL_OPEN_MERCATO only supports /api/* paths, got: ${endpoint}`)\n }\n const appUrl = (process.env.APP_URL || 'http://localhost:3000').replace(/\\/$/, '')\n return `${appUrl}${endpoint}`\n}\n\nfunction normalizeOpenMercatoRequestBody(value: any, context: ActionContext): any {\n if (value === undefined || value === null || value === '') return undefined\n const interpolated = interpolateActionValue(value, context)\n if (typeof interpolated !== 'string') return interpolated\n\n const trimmed = interpolated.trim()\n if (!trimmed) return undefined\n\n try {\n return JSON.parse(trimmed)\n } catch {\n return interpolated\n }\n}\n\nfunction interpolateActionValue(value: any, context: ActionContext): any {\n if (typeof value === 'string') {\n return interpolateMessage(value, context)\n }\n if (Array.isArray(value)) {\n return value.map((item) => interpolateActionValue(item, context))\n }\n if (value && typeof value === 'object') {\n return Object.fromEntries(\n Object.entries(value).map(([key, entry]) => [key, interpolateActionValue(entry, context)]),\n )\n }\n return value\n}\n\nasync function parseOpenMercatoResponseBody(response: Response): Promise<any> {\n const contentType = response.headers.get('content-type')\n try {\n if (contentType && contentType.includes('application/json')) {\n return await response.json()\n }\n return await response.text()\n } catch {\n return null\n }\n}\n\nfunction throwOpenMercatoResponseError(status: number): never {\n const error: Error & { retriable?: boolean } = new Error(`CALL_OPEN_MERCATO request failed with status ${status}`)\n if (status >= 500) {\n error.retriable = true\n }\n throw error\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,5 @@
1
+ const OPENMERCATO_CALL_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
2
+ export {
3
+ OPENMERCATO_CALL_METHODS
4
+ };
5
+ //# sourceMappingURL=openmercato-call-options-types.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/business_rules/lib/openmercato-call-options-types.ts"],
4
+ "sourcesContent": ["export const OPENMERCATO_CALL_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'] as const\n\nexport type OpenMercatoCallMethod = (typeof OPENMERCATO_CALL_METHODS)[number]\n\nexport type OpenMercatoEndpointOption = {\n id: string\n path: string\n method: OpenMercatoCallMethod\n label: string\n summary: string | null\n operationId: string | null\n}\n\nexport type OpenMercatoApiKeyRoleOption = {\n id: string\n name: string | null\n}\n\nexport type OpenMercatoApiKeyOption = {\n id: string\n name: string\n keyPrefix: string\n organizationId: string | null\n organizationName: string | null\n roles: OpenMercatoApiKeyRoleOption[]\n}\n\nexport type OpenMercatoCallOptionsResponse = {\n endpoints: OpenMercatoEndpointOption[]\n apiKeys: OpenMercatoApiKeyOption[]\n}\n"],
5
+ "mappings": "AAAO,MAAM,2BAA2B,CAAC,OAAO,QAAQ,OAAO,SAAS,QAAQ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,231 @@
1
+ import {
2
+ getApiRouteManifests
3
+ } from "@open-mercato/shared/modules/registry";
4
+ import { getModules } from "@open-mercato/shared/lib/modules/registry";
5
+ import { buildOpenApiDocument } from "@open-mercato/shared/lib/openapi";
6
+ import { ApiKey } from "../../api_keys/data/entities.js";
7
+ import { Role } from "../../auth/data/entities.js";
8
+ import { Organization } from "../../directory/data/entities.js";
9
+ import {
10
+ OPENMERCATO_CALL_METHODS
11
+ } from "./openmercato-call-options-types.js";
12
+ const METHOD_ORDER = new Map(
13
+ OPENMERCATO_CALL_METHODS.map((method, index) => [method, index])
14
+ );
15
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
16
+ function isOpenMercatoCallMethod(value) {
17
+ return OPENMERCATO_CALL_METHODS.includes(value);
18
+ }
19
+ function hasPathSegment(path, segment) {
20
+ return path.split("/").some((part) => part === segment);
21
+ }
22
+ function shouldExposeEndpoint(path, method, operation) {
23
+ if (!isOpenMercatoCallMethod(method.toUpperCase())) return false;
24
+ if (!path.startsWith("/api/")) return false;
25
+ if (path.includes("{")) return false;
26
+ if (path.includes("[")) return false;
27
+ if (path.startsWith("/api/docs")) return false;
28
+ if (hasPathSegment(path, "options")) return false;
29
+ if (path === "/api/business_rules/openmercato-call-options") return false;
30
+ if (operation.deprecated === true) return false;
31
+ return true;
32
+ }
33
+ function dedupeOpenMercatoEndpointOptions(options) {
34
+ const deduped = /* @__PURE__ */ new Map();
35
+ for (const option of options) {
36
+ const existing = deduped.get(option.id);
37
+ if (!existing) {
38
+ deduped.set(option.id, option);
39
+ continue;
40
+ }
41
+ if (!existing.summary && option.summary || !existing.operationId && option.operationId) {
42
+ deduped.set(option.id, {
43
+ ...existing,
44
+ label: option.label,
45
+ summary: option.summary ?? existing.summary,
46
+ operationId: option.operationId ?? existing.operationId
47
+ });
48
+ }
49
+ }
50
+ return Array.from(deduped.values());
51
+ }
52
+ function sortEndpointOptions(options) {
53
+ return dedupeOpenMercatoEndpointOptions(options).sort((a, b) => {
54
+ const pathCompare = a.path.localeCompare(b.path);
55
+ if (pathCompare !== 0) return pathCompare;
56
+ return (METHOD_ORDER.get(a.method) ?? 99) - (METHOD_ORDER.get(b.method) ?? 99);
57
+ });
58
+ }
59
+ function collectOpenMercatoEndpointOptionsFromDocument(doc) {
60
+ const options = [];
61
+ for (const [path, methods] of Object.entries(doc.paths ?? {})) {
62
+ for (const [rawMethod, operation] of Object.entries(methods ?? {})) {
63
+ const method = rawMethod.toUpperCase();
64
+ if (!operation || typeof operation !== "object") continue;
65
+ if (!shouldExposeEndpoint(path, method, operation)) continue;
66
+ const typedMethod = method;
67
+ const summary = typeof operation.summary === "string" ? operation.summary : null;
68
+ const operationId = typeof operation.operationId === "string" ? operation.operationId : null;
69
+ options.push({
70
+ id: `${typedMethod} ${path}`,
71
+ path,
72
+ method: typedMethod,
73
+ label: summary ? `${typedMethod} ${path} - ${summary}` : `${typedMethod} ${path}`,
74
+ summary,
75
+ operationId
76
+ });
77
+ }
78
+ }
79
+ return sortEndpointOptions(options);
80
+ }
81
+ function collectOpenMercatoEndpointOptions(modules) {
82
+ return collectOpenMercatoEndpointOptionsFromDocument(buildOpenApiDocument(modules));
83
+ }
84
+ function normalizeManifestPath(path) {
85
+ const prefixed = path.startsWith("/api/") ? path : `/api${path.startsWith("/") ? path : `/${path}`}`;
86
+ return prefixed.replace(/\/+$/, "") || "/api";
87
+ }
88
+ function getRouteMethodDoc(routeDoc, method) {
89
+ return routeDoc?.methods?.[method];
90
+ }
91
+ async function collectOpenMercatoEndpointOptionsFromApiRouteManifests(routes) {
92
+ const options = [];
93
+ for (const route of routes) {
94
+ const path = normalizeManifestPath(route.path);
95
+ const candidateMethods = route.methods.map((method) => method.toUpperCase()).filter(isOpenMercatoCallMethod);
96
+ if (candidateMethods.length === 0) continue;
97
+ if (!path.startsWith("/api/")) continue;
98
+ if (path.includes("[")) continue;
99
+ if (path.startsWith("/api/docs")) continue;
100
+ if (hasPathSegment(path, "options")) continue;
101
+ if (path === "/api/business_rules/openmercato-call-options") continue;
102
+ let routeDoc;
103
+ try {
104
+ const routeModule = await route.load();
105
+ routeDoc = routeModule.openApi;
106
+ } catch {
107
+ routeDoc = void 0;
108
+ }
109
+ for (const typedMethod of candidateMethods) {
110
+ const methodDoc = getRouteMethodDoc(routeDoc, typedMethod);
111
+ const operation = methodDoc ?? {};
112
+ if (!shouldExposeEndpoint(path, typedMethod, operation)) continue;
113
+ const summary = typeof methodDoc?.summary === "string" ? methodDoc.summary : null;
114
+ const operationId = typeof methodDoc?.operationId === "string" ? methodDoc.operationId : null;
115
+ options.push({
116
+ id: `${typedMethod} ${path}`,
117
+ path,
118
+ method: typedMethod,
119
+ label: summary ? `${typedMethod} ${path} - ${summary}` : `${typedMethod} ${path}`,
120
+ summary,
121
+ operationId
122
+ });
123
+ }
124
+ }
125
+ return sortEndpointOptions(options);
126
+ }
127
+ async function getCurrentOpenMercatoEndpointOptions() {
128
+ try {
129
+ const moduleOptions = collectOpenMercatoEndpointOptions(getModules());
130
+ if (moduleOptions.length > 0) return moduleOptions;
131
+ } catch {
132
+ }
133
+ const apiRoutes = getApiRouteManifests();
134
+ if (apiRoutes.length === 0) return [];
135
+ return collectOpenMercatoEndpointOptionsFromApiRouteManifests(apiRoutes);
136
+ }
137
+ function findOpenMercatoEndpointOption(endpoint, method, options) {
138
+ const normalizedMethod = method.trim().toUpperCase();
139
+ if (!endpoint.startsWith("/api/")) return null;
140
+ if (!isOpenMercatoCallMethod(normalizedMethod)) return null;
141
+ return options.find((option) => option.path === endpoint && option.method === normalizedMethod) ?? null;
142
+ }
143
+ async function listOpenMercatoApiKeyOptions(em, scope) {
144
+ const filters = {
145
+ tenantId: scope.tenantId,
146
+ deletedAt: null
147
+ };
148
+ if (scope.organizationId) {
149
+ filters.organizationId = scope.organizationId;
150
+ }
151
+ const keys = await em.find(ApiKey, filters, { orderBy: { name: "asc" } });
152
+ const now = Date.now();
153
+ const activeKeys = keys.filter((key) => !key.expiresAt || key.expiresAt.getTime() > now);
154
+ const roleIds = /* @__PURE__ */ new Set();
155
+ const organizationIds = /* @__PURE__ */ new Set();
156
+ for (const key of activeKeys) {
157
+ if (key.organizationId) organizationIds.add(String(key.organizationId));
158
+ if (Array.isArray(key.rolesJson)) {
159
+ for (const roleId of key.rolesJson) roleIds.add(String(roleId));
160
+ }
161
+ }
162
+ const [roles, organizations] = await Promise.all([
163
+ roleIds.size > 0 ? em.find(Role, { id: { $in: Array.from(roleIds) }, deletedAt: null }) : [],
164
+ organizationIds.size > 0 ? em.find(Organization, { id: { $in: Array.from(organizationIds) } }) : []
165
+ ]);
166
+ const roleMap = new Map(roles.map((role) => [String(role.id), role.name ?? null]));
167
+ const orgMap = new Map(organizations.map((org) => [String(org.id), org.name ?? null]));
168
+ return activeKeys.map((key) => ({
169
+ id: key.id,
170
+ name: key.name,
171
+ keyPrefix: key.keyPrefix,
172
+ organizationId: key.organizationId ?? null,
173
+ organizationName: key.organizationId ? orgMap.get(String(key.organizationId)) ?? null : null,
174
+ roles: Array.isArray(key.rolesJson) ? key.rolesJson.map((roleId) => ({
175
+ id: String(roleId),
176
+ name: roleMap.get(String(roleId)) ?? null
177
+ })) : []
178
+ }));
179
+ }
180
+ async function resolveOpenMercatoApiKeyProfile(em, apiKeyId, scope) {
181
+ if (!apiKeyId || typeof apiKeyId !== "string") return null;
182
+ if (!UUID_RE.test(apiKeyId)) return null;
183
+ const filters = {
184
+ id: apiKeyId,
185
+ tenantId: scope.tenantId,
186
+ deletedAt: null
187
+ };
188
+ if (scope.organizationId) {
189
+ filters.organizationId = scope.organizationId;
190
+ }
191
+ const key = await em.findOne(ApiKey, filters);
192
+ if (!key) return null;
193
+ if (key.expiresAt && key.expiresAt.getTime() <= Date.now()) return null;
194
+ return key;
195
+ }
196
+ async function validateOpenMercatoCallActions(em, actions, scope, endpointOptions) {
197
+ if (!Array.isArray(actions) || actions.length === 0) return [];
198
+ const errors = [];
199
+ let availableEndpointOptions = endpointOptions;
200
+ for (const [index, action] of actions.entries()) {
201
+ if (!action || typeof action !== "object") continue;
202
+ const typedAction = action;
203
+ if (typedAction.type !== "CALL_OPEN_MERCATO") continue;
204
+ availableEndpointOptions ??= await getCurrentOpenMercatoEndpointOptions();
205
+ const config = typedAction.config ?? {};
206
+ const endpoint = typeof config.endpoint === "string" ? config.endpoint : "";
207
+ const method = typeof config.method === "string" ? config.method : "";
208
+ const apiKeyId = typeof config.apiKeyId === "string" ? config.apiKeyId : "";
209
+ if (!findOpenMercatoEndpointOption(endpoint, method, availableEndpointOptions)) {
210
+ errors.push(`Action ${index + 1}: selected OpenMercato endpoint is not available`);
211
+ }
212
+ const apiKey = await resolveOpenMercatoApiKeyProfile(em, apiKeyId, scope);
213
+ if (!apiKey) {
214
+ errors.push(`Action ${index + 1}: selected API key profile is not available`);
215
+ } else if (!Array.isArray(apiKey.rolesJson) || apiKey.rolesJson.length === 0) {
216
+ errors.push(`Action ${index + 1}: selected API key profile has no roles`);
217
+ }
218
+ }
219
+ return errors;
220
+ }
221
+ export {
222
+ collectOpenMercatoEndpointOptions,
223
+ collectOpenMercatoEndpointOptionsFromDocument,
224
+ dedupeOpenMercatoEndpointOptions,
225
+ findOpenMercatoEndpointOption,
226
+ getCurrentOpenMercatoEndpointOptions,
227
+ listOpenMercatoApiKeyOptions,
228
+ resolveOpenMercatoApiKeyProfile,
229
+ validateOpenMercatoCallActions
230
+ };
231
+ //# sourceMappingURL=openmercato-call-options.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/business_rules/lib/openmercato-call-options.ts"],
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport {\n getApiRouteManifests,\n type ApiRouteManifestEntry,\n type Module,\n} from '@open-mercato/shared/modules/registry'\nimport { getModules } from '@open-mercato/shared/lib/modules/registry'\nimport { buildOpenApiDocument } from '@open-mercato/shared/lib/openapi'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { ApiKey } from '../../api_keys/data/entities'\nimport { Role } from '../../auth/data/entities'\nimport { Organization } from '../../directory/data/entities'\nimport {\n OPENMERCATO_CALL_METHODS,\n type OpenMercatoApiKeyOption,\n type OpenMercatoCallMethod,\n type OpenMercatoEndpointOption,\n} from './openmercato-call-options-types'\n\nexport type OpenMercatoCallScope = {\n tenantId: string\n organizationId?: string | null\n}\n\nconst METHOD_ORDER = new Map<string, number>(\n OPENMERCATO_CALL_METHODS.map((method, index) => [method, index]),\n)\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i\n\nfunction isOpenMercatoCallMethod(value: string): value is OpenMercatoCallMethod {\n return OPENMERCATO_CALL_METHODS.includes(value as OpenMercatoCallMethod)\n}\n\nfunction hasPathSegment(path: string, segment: string): boolean {\n return path.split('/').some((part) => part === segment)\n}\n\nfunction shouldExposeEndpoint(path: string, method: string, operation: Record<string, any>): boolean {\n if (!isOpenMercatoCallMethod(method.toUpperCase())) return false\n if (!path.startsWith('/api/')) return false\n if (path.includes('{')) return false\n if (path.includes('[')) return false\n if (path.startsWith('/api/docs')) return false\n if (hasPathSegment(path, 'options')) return false\n if (path === '/api/business_rules/openmercato-call-options') return false\n if (operation.deprecated === true) return false\n return true\n}\n\nexport function dedupeOpenMercatoEndpointOptions(\n options: OpenMercatoEndpointOption[],\n): OpenMercatoEndpointOption[] {\n const deduped = new Map<string, OpenMercatoEndpointOption>()\n\n for (const option of options) {\n const existing = deduped.get(option.id)\n if (!existing) {\n deduped.set(option.id, option)\n continue\n }\n\n if ((!existing.summary && option.summary) || (!existing.operationId && option.operationId)) {\n deduped.set(option.id, {\n ...existing,\n label: option.label,\n summary: option.summary ?? existing.summary,\n operationId: option.operationId ?? existing.operationId,\n })\n }\n }\n\n return Array.from(deduped.values())\n}\n\nfunction sortEndpointOptions(options: OpenMercatoEndpointOption[]): OpenMercatoEndpointOption[] {\n return dedupeOpenMercatoEndpointOptions(options).sort((a, b) => {\n const pathCompare = a.path.localeCompare(b.path)\n if (pathCompare !== 0) return pathCompare\n return (METHOD_ORDER.get(a.method) ?? 99) - (METHOD_ORDER.get(b.method) ?? 99)\n })\n}\n\nexport function collectOpenMercatoEndpointOptionsFromDocument(doc: { paths?: Record<string, any> }): OpenMercatoEndpointOption[] {\n const options: OpenMercatoEndpointOption[] = []\n\n for (const [path, methods] of Object.entries(doc.paths ?? {})) {\n for (const [rawMethod, operation] of Object.entries(methods ?? {})) {\n const method = rawMethod.toUpperCase()\n if (!operation || typeof operation !== 'object') continue\n if (!shouldExposeEndpoint(path, method, operation as Record<string, any>)) continue\n\n const typedMethod = method as OpenMercatoCallMethod\n const summary = typeof (operation as any).summary === 'string' ? (operation as any).summary : null\n const operationId = typeof (operation as any).operationId === 'string' ? (operation as any).operationId : null\n\n options.push({\n id: `${typedMethod} ${path}`,\n path,\n method: typedMethod,\n label: summary ? `${typedMethod} ${path} - ${summary}` : `${typedMethod} ${path}`,\n summary,\n operationId,\n })\n }\n }\n\n return sortEndpointOptions(options)\n}\n\nexport function collectOpenMercatoEndpointOptions(modules: Module[]): OpenMercatoEndpointOption[] {\n return collectOpenMercatoEndpointOptionsFromDocument(buildOpenApiDocument(modules))\n}\n\nfunction normalizeManifestPath(path: string): string {\n const prefixed = path.startsWith('/api/') ? path : `/api${path.startsWith('/') ? path : `/${path}`}`\n return prefixed.replace(/\\/+$/, '') || '/api'\n}\n\nfunction getRouteMethodDoc(routeDoc: OpenApiRouteDoc | undefined, method: OpenMercatoCallMethod): OpenApiMethodDoc | undefined {\n return routeDoc?.methods?.[method]\n}\n\nasync function collectOpenMercatoEndpointOptionsFromApiRouteManifests(\n routes: ApiRouteManifestEntry[],\n): Promise<OpenMercatoEndpointOption[]> {\n const options: OpenMercatoEndpointOption[] = []\n\n for (const route of routes) {\n const path = normalizeManifestPath(route.path)\n const candidateMethods = route.methods\n .map((method) => method.toUpperCase())\n .filter(isOpenMercatoCallMethod)\n if (candidateMethods.length === 0) continue\n if (!path.startsWith('/api/')) continue\n if (path.includes('[')) continue\n if (path.startsWith('/api/docs')) continue\n if (hasPathSegment(path, 'options')) continue\n if (path === '/api/business_rules/openmercato-call-options') continue\n\n let routeDoc: OpenApiRouteDoc | undefined\n try {\n const routeModule = await route.load()\n routeDoc = routeModule.openApi as OpenApiRouteDoc | undefined\n } catch {\n routeDoc = undefined\n }\n\n for (const typedMethod of candidateMethods) {\n const methodDoc = getRouteMethodDoc(routeDoc, typedMethod)\n const operation = methodDoc ?? {}\n if (!shouldExposeEndpoint(path, typedMethod, operation as Record<string, any>)) continue\n\n const summary = typeof methodDoc?.summary === 'string' ? methodDoc.summary : null\n const operationId = typeof methodDoc?.operationId === 'string' ? methodDoc.operationId : null\n options.push({\n id: `${typedMethod} ${path}`,\n path,\n method: typedMethod,\n label: summary ? `${typedMethod} ${path} - ${summary}` : `${typedMethod} ${path}`,\n summary,\n operationId,\n })\n }\n }\n\n return sortEndpointOptions(options)\n}\n\nexport async function getCurrentOpenMercatoEndpointOptions(): Promise<OpenMercatoEndpointOption[]> {\n try {\n const moduleOptions = collectOpenMercatoEndpointOptions(getModules())\n if (moduleOptions.length > 0) return moduleOptions\n } catch {\n // The runtime app route registry is the source of truth when modules were not\n // registered in this package instance.\n }\n\n const apiRoutes = getApiRouteManifests()\n if (apiRoutes.length === 0) return []\n return collectOpenMercatoEndpointOptionsFromApiRouteManifests(apiRoutes)\n}\n\nexport function findOpenMercatoEndpointOption(\n endpoint: string,\n method: string,\n options: OpenMercatoEndpointOption[],\n): OpenMercatoEndpointOption | null {\n const normalizedMethod = method.trim().toUpperCase()\n if (!endpoint.startsWith('/api/')) return null\n if (!isOpenMercatoCallMethod(normalizedMethod)) return null\n return options.find((option) => option.path === endpoint && option.method === normalizedMethod) ?? null\n}\n\nexport async function listOpenMercatoApiKeyOptions(\n em: EntityManager,\n scope: OpenMercatoCallScope,\n): Promise<OpenMercatoApiKeyOption[]> {\n const filters: Record<string, any> = {\n tenantId: scope.tenantId,\n deletedAt: null,\n }\n if (scope.organizationId) {\n filters.organizationId = scope.organizationId\n }\n\n const keys = (await em.find(ApiKey, filters, { orderBy: { name: 'asc' } })) as ApiKey[]\n const now = Date.now()\n const activeKeys = keys.filter((key) => !key.expiresAt || key.expiresAt.getTime() > now)\n\n const roleIds = new Set<string>()\n const organizationIds = new Set<string>()\n for (const key of activeKeys) {\n if (key.organizationId) organizationIds.add(String(key.organizationId))\n if (Array.isArray(key.rolesJson)) {\n for (const roleId of key.rolesJson) roleIds.add(String(roleId))\n }\n }\n\n const [roles, organizations] = await Promise.all([\n roleIds.size > 0 ? em.find(Role, { id: { $in: Array.from(roleIds) }, deletedAt: null }) : [],\n organizationIds.size > 0 ? em.find(Organization, { id: { $in: Array.from(organizationIds) } }) : [],\n ])\n const roleMap = new Map((roles as Role[]).map((role) => [String(role.id), role.name ?? null]))\n const orgMap = new Map((organizations as Organization[]).map((org) => [String(org.id), org.name ?? null]))\n\n return activeKeys.map((key) => ({\n id: key.id,\n name: key.name,\n keyPrefix: key.keyPrefix,\n organizationId: key.organizationId ?? null,\n organizationName: key.organizationId ? orgMap.get(String(key.organizationId)) ?? null : null,\n roles: Array.isArray(key.rolesJson)\n ? key.rolesJson.map((roleId) => ({\n id: String(roleId),\n name: roleMap.get(String(roleId)) ?? null,\n }))\n : [],\n }))\n}\n\nexport async function resolveOpenMercatoApiKeyProfile(\n em: EntityManager,\n apiKeyId: string,\n scope: OpenMercatoCallScope,\n): Promise<ApiKey | null> {\n if (!apiKeyId || typeof apiKeyId !== 'string') return null\n if (!UUID_RE.test(apiKeyId)) return null\n const filters: Record<string, any> = {\n id: apiKeyId,\n tenantId: scope.tenantId,\n deletedAt: null,\n }\n if (scope.organizationId) {\n filters.organizationId = scope.organizationId\n }\n\n const key = await em.findOne(ApiKey, filters)\n if (!key) return null\n if (key.expiresAt && key.expiresAt.getTime() <= Date.now()) return null\n return key\n}\n\nexport async function validateOpenMercatoCallActions(\n em: EntityManager,\n actions: unknown,\n scope: OpenMercatoCallScope,\n endpointOptions?: OpenMercatoEndpointOption[],\n): Promise<string[]> {\n if (!Array.isArray(actions) || actions.length === 0) return []\n\n const errors: string[] = []\n let availableEndpointOptions = endpointOptions\n for (const [index, action] of actions.entries()) {\n if (!action || typeof action !== 'object') continue\n const typedAction = action as { type?: string; config?: Record<string, any> }\n if (typedAction.type !== 'CALL_OPEN_MERCATO') continue\n availableEndpointOptions ??= await getCurrentOpenMercatoEndpointOptions()\n\n const config = typedAction.config ?? {}\n const endpoint = typeof config.endpoint === 'string' ? config.endpoint : ''\n const method = typeof config.method === 'string' ? config.method : ''\n const apiKeyId = typeof config.apiKeyId === 'string' ? config.apiKeyId : ''\n\n if (!findOpenMercatoEndpointOption(endpoint, method, availableEndpointOptions)) {\n errors.push(`Action ${index + 1}: selected OpenMercato endpoint is not available`)\n }\n\n const apiKey = await resolveOpenMercatoApiKeyProfile(em, apiKeyId, scope)\n if (!apiKey) {\n errors.push(`Action ${index + 1}: selected API key profile is not available`)\n } else if (!Array.isArray(apiKey.rolesJson) || apiKey.rolesJson.length === 0) {\n errors.push(`Action ${index + 1}: selected API key profile has no roles`)\n }\n }\n\n return errors\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -263,7 +263,11 @@ async function executeSingleRule(em, rule, context, options = {}) {
263
263
  ...evalContext,
264
264
  data: context.data,
265
265
  ruleId: rule.ruleId,
266
- ruleName: rule.ruleName
266
+ ruleName: rule.ruleName,
267
+ tenantId: context.tenantId,
268
+ organizationId: context.organizationId,
269
+ executedBy: context.executedBy ?? context.user?.id ?? null,
270
+ em
267
271
  };
268
272
  actionsExecuted = await actionExecutor.executeActions(actions, actionContext);
269
273
  }