@open-mercato/shared 0.4.2-canary-c02407ff85

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 (324) hide show
  1. package/build.mjs +101 -0
  2. package/dist/index.js +1 -0
  3. package/dist/index.js.map +7 -0
  4. package/dist/lib/api/crud.js +47 -0
  5. package/dist/lib/api/crud.js.map +7 -0
  6. package/dist/lib/api/scoped.js +140 -0
  7. package/dist/lib/api/scoped.js.map +7 -0
  8. package/dist/lib/auth/jwt.js +34 -0
  9. package/dist/lib/auth/jwt.js.map +7 -0
  10. package/dist/lib/auth/server.js +157 -0
  11. package/dist/lib/auth/server.js.map +7 -0
  12. package/dist/lib/boolean.js +22 -0
  13. package/dist/lib/boolean.js.map +7 -0
  14. package/dist/lib/bootstrap/appResolver.js +43 -0
  15. package/dist/lib/bootstrap/appResolver.js.map +7 -0
  16. package/dist/lib/bootstrap/dynamicLoader.js +108 -0
  17. package/dist/lib/bootstrap/dynamicLoader.js.map +7 -0
  18. package/dist/lib/bootstrap/factory.js +59 -0
  19. package/dist/lib/bootstrap/factory.js.map +7 -0
  20. package/dist/lib/bootstrap/index.js +11 -0
  21. package/dist/lib/bootstrap/index.js.map +7 -0
  22. package/dist/lib/bootstrap/types.js +1 -0
  23. package/dist/lib/bootstrap/types.js.map +7 -0
  24. package/dist/lib/cache/segments.js +36 -0
  25. package/dist/lib/cache/segments.js.map +7 -0
  26. package/dist/lib/cli/progress.js +46 -0
  27. package/dist/lib/cli/progress.js.map +7 -0
  28. package/dist/lib/commands/command-bus.js +285 -0
  29. package/dist/lib/commands/command-bus.js.map +7 -0
  30. package/dist/lib/commands/customFieldSnapshots.js +66 -0
  31. package/dist/lib/commands/customFieldSnapshots.js.map +7 -0
  32. package/dist/lib/commands/helpers.js +98 -0
  33. package/dist/lib/commands/helpers.js.map +7 -0
  34. package/dist/lib/commands/index.js +8 -0
  35. package/dist/lib/commands/index.js.map +7 -0
  36. package/dist/lib/commands/operationMetadata.js +32 -0
  37. package/dist/lib/commands/operationMetadata.js.map +7 -0
  38. package/dist/lib/commands/registry.js +43 -0
  39. package/dist/lib/commands/registry.js.map +7 -0
  40. package/dist/lib/commands/scope.js +44 -0
  41. package/dist/lib/commands/scope.js.map +7 -0
  42. package/dist/lib/commands/types.js +8 -0
  43. package/dist/lib/commands/types.js.map +7 -0
  44. package/dist/lib/crud/cache-stats.js +98 -0
  45. package/dist/lib/crud/cache-stats.js.map +7 -0
  46. package/dist/lib/crud/cache.js +175 -0
  47. package/dist/lib/crud/cache.js.map +7 -0
  48. package/dist/lib/crud/custom-fields-client.js +52 -0
  49. package/dist/lib/crud/custom-fields-client.js.map +7 -0
  50. package/dist/lib/crud/custom-fields.js +467 -0
  51. package/dist/lib/crud/custom-fields.js.map +7 -0
  52. package/dist/lib/crud/errors.js +24 -0
  53. package/dist/lib/crud/errors.js.map +7 -0
  54. package/dist/lib/crud/exporters.js +154 -0
  55. package/dist/lib/crud/exporters.js.map +7 -0
  56. package/dist/lib/crud/factory.js +1311 -0
  57. package/dist/lib/crud/factory.js.map +7 -0
  58. package/dist/lib/crud/types.js +1 -0
  59. package/dist/lib/crud/types.js.map +7 -0
  60. package/dist/lib/custom-fields/normalize.js +36 -0
  61. package/dist/lib/custom-fields/normalize.js.map +7 -0
  62. package/dist/lib/data/engine.js +396 -0
  63. package/dist/lib/data/engine.js.map +7 -0
  64. package/dist/lib/db/escapeLikePattern.js +5 -0
  65. package/dist/lib/db/escapeLikePattern.js.map +7 -0
  66. package/dist/lib/db/mikro.js +82 -0
  67. package/dist/lib/db/mikro.js.map +7 -0
  68. package/dist/lib/di/container.js +94 -0
  69. package/dist/lib/di/container.js.map +7 -0
  70. package/dist/lib/email/send.js +12 -0
  71. package/dist/lib/email/send.js.map +7 -0
  72. package/dist/lib/encryption/aes.js +58 -0
  73. package/dist/lib/encryption/aes.js.map +7 -0
  74. package/dist/lib/encryption/customFieldValues.js +49 -0
  75. package/dist/lib/encryption/customFieldValues.js.map +7 -0
  76. package/dist/lib/encryption/entityFields.js +26 -0
  77. package/dist/lib/encryption/entityFields.js.map +7 -0
  78. package/dist/lib/encryption/entityIds.js +80 -0
  79. package/dist/lib/encryption/entityIds.js.map +7 -0
  80. package/dist/lib/encryption/find.js +45 -0
  81. package/dist/lib/encryption/find.js.map +7 -0
  82. package/dist/lib/encryption/indexDoc.js +69 -0
  83. package/dist/lib/encryption/indexDoc.js.map +7 -0
  84. package/dist/lib/encryption/kms.js +282 -0
  85. package/dist/lib/encryption/kms.js.map +7 -0
  86. package/dist/lib/encryption/subscriber.js +330 -0
  87. package/dist/lib/encryption/subscriber.js.map +7 -0
  88. package/dist/lib/encryption/tenantDataEncryptionService.js +252 -0
  89. package/dist/lib/encryption/tenantDataEncryptionService.js.map +7 -0
  90. package/dist/lib/encryption/toggles.js +18 -0
  91. package/dist/lib/encryption/toggles.js.map +7 -0
  92. package/dist/lib/entities/naming.js +9 -0
  93. package/dist/lib/entities/naming.js.map +7 -0
  94. package/dist/lib/entities/system-entities.js +43 -0
  95. package/dist/lib/entities/system-entities.js.map +7 -0
  96. package/dist/lib/frontend/organizationEvents.js +41 -0
  97. package/dist/lib/frontend/organizationEvents.js.map +7 -0
  98. package/dist/lib/frontend/useOrganizationScope.js +32 -0
  99. package/dist/lib/frontend/useOrganizationScope.js.map +7 -0
  100. package/dist/lib/hotkeys/index.js +128 -0
  101. package/dist/lib/hotkeys/index.js.map +7 -0
  102. package/dist/lib/i18n/app-dictionaries.js +17 -0
  103. package/dist/lib/i18n/app-dictionaries.js.map +7 -0
  104. package/dist/lib/i18n/config.js +7 -0
  105. package/dist/lib/i18n/config.js.map +7 -0
  106. package/dist/lib/i18n/context.js +50 -0
  107. package/dist/lib/i18n/context.js.map +7 -0
  108. package/dist/lib/i18n/server.js +68 -0
  109. package/dist/lib/i18n/server.js.map +7 -0
  110. package/dist/lib/i18n/translate.js +45 -0
  111. package/dist/lib/i18n/translate.js.map +7 -0
  112. package/dist/lib/indexers/error-log.js +82 -0
  113. package/dist/lib/indexers/error-log.js.map +7 -0
  114. package/dist/lib/indexers/status-log.js +80 -0
  115. package/dist/lib/indexers/status-log.js.map +7 -0
  116. package/dist/lib/lib/auth/jwt.js +34 -0
  117. package/dist/lib/lib/auth/jwt.js.map +7 -0
  118. package/dist/lib/lib/auth/server.js +77 -0
  119. package/dist/lib/lib/auth/server.js.map +7 -0
  120. package/dist/lib/lib/email/send.js +12 -0
  121. package/dist/lib/lib/email/send.js.map +7 -0
  122. package/dist/lib/lib/i18n/config.js +7 -0
  123. package/dist/lib/lib/i18n/config.js.map +7 -0
  124. package/dist/lib/lib/i18n/context.js +31 -0
  125. package/dist/lib/lib/i18n/context.js.map +7 -0
  126. package/dist/lib/lib/utils.js +9 -0
  127. package/dist/lib/lib/utils.js.map +7 -0
  128. package/dist/lib/location/countries.js +68 -0
  129. package/dist/lib/location/countries.js.map +7 -0
  130. package/dist/lib/modules/index.js +6 -0
  131. package/dist/lib/modules/index.js.map +7 -0
  132. package/dist/lib/modules/registry.js +18 -0
  133. package/dist/lib/modules/registry.js.map +7 -0
  134. package/dist/lib/openapi/crud.js +137 -0
  135. package/dist/lib/openapi/crud.js.map +7 -0
  136. package/dist/lib/openapi/generator.js +1131 -0
  137. package/dist/lib/openapi/generator.js.map +7 -0
  138. package/dist/lib/openapi/index.js +10 -0
  139. package/dist/lib/openapi/index.js.map +7 -0
  140. package/dist/lib/openapi/sanitize.js +110 -0
  141. package/dist/lib/openapi/sanitize.js.map +7 -0
  142. package/dist/lib/openapi/types.js +1 -0
  143. package/dist/lib/openapi/types.js.map +7 -0
  144. package/dist/lib/profiler/index.js +258 -0
  145. package/dist/lib/profiler/index.js.map +7 -0
  146. package/dist/lib/query/engine.js +729 -0
  147. package/dist/lib/query/engine.js.map +7 -0
  148. package/dist/lib/query/join-utils.js +195 -0
  149. package/dist/lib/query/join-utils.js.map +7 -0
  150. package/dist/lib/query/types.js +9 -0
  151. package/dist/lib/query/types.js.map +7 -0
  152. package/dist/lib/search/config.js +32 -0
  153. package/dist/lib/search/config.js.map +7 -0
  154. package/dist/lib/search/tokenize.js +34 -0
  155. package/dist/lib/search/tokenize.js.map +7 -0
  156. package/dist/lib/slugify.js +24 -0
  157. package/dist/lib/slugify.js.map +7 -0
  158. package/dist/lib/testing/bootstrap.js +51 -0
  159. package/dist/lib/testing/bootstrap.js.map +7 -0
  160. package/dist/lib/testing/index.js +17 -0
  161. package/dist/lib/testing/index.js.map +7 -0
  162. package/dist/lib/testing/renderWithProviders.js +15 -0
  163. package/dist/lib/testing/renderWithProviders.js.map +7 -0
  164. package/dist/lib/url.js +12 -0
  165. package/dist/lib/url.js.map +7 -0
  166. package/dist/lib/utils.js +13 -0
  167. package/dist/lib/utils.js.map +7 -0
  168. package/dist/lib/version.js +7 -0
  169. package/dist/lib/version.js.map +7 -0
  170. package/dist/modules/dashboard/widgets.js +1 -0
  171. package/dist/modules/dashboard/widgets.js.map +7 -0
  172. package/dist/modules/dsl.js +30 -0
  173. package/dist/modules/dsl.js.map +7 -0
  174. package/dist/modules/entities/kinds.js +22 -0
  175. package/dist/modules/entities/kinds.js.map +7 -0
  176. package/dist/modules/entities/options.js +26 -0
  177. package/dist/modules/entities/options.js.map +7 -0
  178. package/dist/modules/entities/validation.js +102 -0
  179. package/dist/modules/entities/validation.js.map +7 -0
  180. package/dist/modules/entities/validators.js +88 -0
  181. package/dist/modules/entities/validators.js.map +7 -0
  182. package/dist/modules/entities.js +1 -0
  183. package/dist/modules/entities.js.map +7 -0
  184. package/dist/modules/navigation/sidebarPreferences.js +50 -0
  185. package/dist/modules/navigation/sidebarPreferences.js.map +7 -0
  186. package/dist/modules/perspectives/types.js +1 -0
  187. package/dist/modules/perspectives/types.js.map +7 -0
  188. package/dist/modules/registry.js +96 -0
  189. package/dist/modules/registry.js.map +7 -0
  190. package/dist/modules/search.js +15 -0
  191. package/dist/modules/search.js.map +7 -0
  192. package/dist/modules/vector.js +1 -0
  193. package/dist/modules/vector.js.map +7 -0
  194. package/dist/modules/widgets/injection-loader.js +180 -0
  195. package/dist/modules/widgets/injection-loader.js.map +7 -0
  196. package/dist/modules/widgets/injection.js +1 -0
  197. package/dist/modules/widgets/injection.js.map +7 -0
  198. package/dist/security/features.js +23 -0
  199. package/dist/security/features.js.map +7 -0
  200. package/dist/types/pg.d.js +1 -0
  201. package/dist/types/pg.d.js.map +7 -0
  202. package/dist/types/react-email.d.js +1 -0
  203. package/dist/types/react-email.d.js.map +7 -0
  204. package/dist/types/resend.d.js +1 -0
  205. package/dist/types/resend.d.js.map +7 -0
  206. package/jest.config.cjs +22 -0
  207. package/package.json +88 -0
  208. package/src/index.ts +0 -0
  209. package/src/lib/api/__tests__/scoped.test.ts +38 -0
  210. package/src/lib/api/crud.ts +59 -0
  211. package/src/lib/api/scoped.ts +239 -0
  212. package/src/lib/auth/jwt.ts +39 -0
  213. package/src/lib/auth/server.ts +199 -0
  214. package/src/lib/boolean.ts +17 -0
  215. package/src/lib/bootstrap/appResolver.ts +85 -0
  216. package/src/lib/bootstrap/dynamicLoader.ts +177 -0
  217. package/src/lib/bootstrap/factory.ts +108 -0
  218. package/src/lib/bootstrap/index.ts +23 -0
  219. package/src/lib/bootstrap/types.ts +31 -0
  220. package/src/lib/cache/segments.ts +56 -0
  221. package/src/lib/cli/progress.ts +55 -0
  222. package/src/lib/commands/__tests__/command-bus.test.ts +84 -0
  223. package/src/lib/commands/__tests__/helpers.test.ts +42 -0
  224. package/src/lib/commands/command-bus.ts +349 -0
  225. package/src/lib/commands/customFieldSnapshots.ts +86 -0
  226. package/src/lib/commands/helpers.ts +143 -0
  227. package/src/lib/commands/index.ts +4 -0
  228. package/src/lib/commands/operationMetadata.ts +40 -0
  229. package/src/lib/commands/registry.ts +46 -0
  230. package/src/lib/commands/scope.ts +59 -0
  231. package/src/lib/commands/types.ts +63 -0
  232. package/src/lib/crud/__tests__/crud-factory.test.ts +333 -0
  233. package/src/lib/crud/__tests__/custom-fields.test.ts +150 -0
  234. package/src/lib/crud/cache-stats.ts +127 -0
  235. package/src/lib/crud/cache.ts +205 -0
  236. package/src/lib/crud/custom-fields-client.ts +54 -0
  237. package/src/lib/crud/custom-fields.ts +607 -0
  238. package/src/lib/crud/errors.ts +23 -0
  239. package/src/lib/crud/exporters.ts +188 -0
  240. package/src/lib/crud/factory.ts +1622 -0
  241. package/src/lib/crud/types.ts +29 -0
  242. package/src/lib/custom-fields/normalize.ts +45 -0
  243. package/src/lib/data/engine.ts +562 -0
  244. package/src/lib/db/escapeLikePattern.ts +2 -0
  245. package/src/lib/db/mikro.ts +100 -0
  246. package/src/lib/di/container.ts +105 -0
  247. package/src/lib/email/send.ts +18 -0
  248. package/src/lib/encryption/__tests__/customFieldValues.test.ts +63 -0
  249. package/src/lib/encryption/__tests__/indexDoc.test.ts +115 -0
  250. package/src/lib/encryption/aes.ts +64 -0
  251. package/src/lib/encryption/customFieldValues.ts +67 -0
  252. package/src/lib/encryption/entityFields.ts +39 -0
  253. package/src/lib/encryption/entityIds.ts +107 -0
  254. package/src/lib/encryption/find.ts +81 -0
  255. package/src/lib/encryption/indexDoc.ts +104 -0
  256. package/src/lib/encryption/kms.ts +337 -0
  257. package/src/lib/encryption/subscriber.ts +416 -0
  258. package/src/lib/encryption/tenantDataEncryptionService.ts +313 -0
  259. package/src/lib/encryption/toggles.ts +15 -0
  260. package/src/lib/entities/naming.ts +6 -0
  261. package/src/lib/entities/system-entities.ts +43 -0
  262. package/src/lib/frontend/organizationEvents.ts +55 -0
  263. package/src/lib/frontend/useOrganizationScope.ts +30 -0
  264. package/src/lib/hotkeys/index.ts +168 -0
  265. package/src/lib/i18n/app-dictionaries.ts +18 -0
  266. package/src/lib/i18n/config.ts +4 -0
  267. package/src/lib/i18n/context.tsx +66 -0
  268. package/src/lib/i18n/server.ts +74 -0
  269. package/src/lib/i18n/translate.ts +54 -0
  270. package/src/lib/indexers/error-log.ts +106 -0
  271. package/src/lib/indexers/status-log.ts +119 -0
  272. package/src/lib/lib/auth/jwt.ts +39 -0
  273. package/src/lib/lib/auth/server.ts +94 -0
  274. package/src/lib/lib/email/send.ts +18 -0
  275. package/src/lib/lib/i18n/config.ts +4 -0
  276. package/src/lib/lib/i18n/context.tsx +38 -0
  277. package/src/lib/lib/utils.ts +6 -0
  278. package/src/lib/location/countries.ts +97 -0
  279. package/src/lib/modules/index.ts +1 -0
  280. package/src/lib/modules/registry.ts +18 -0
  281. package/src/lib/openapi/crud.ts +218 -0
  282. package/src/lib/openapi/generator.ts +1311 -0
  283. package/src/lib/openapi/index.ts +4 -0
  284. package/src/lib/openapi/sanitize.ts +137 -0
  285. package/src/lib/openapi/types.ts +79 -0
  286. package/src/lib/profiler/index.ts +371 -0
  287. package/src/lib/query/__tests__/engine.test.ts +274 -0
  288. package/src/lib/query/engine.ts +837 -0
  289. package/src/lib/query/join-utils.ts +238 -0
  290. package/src/lib/query/types.ts +121 -0
  291. package/src/lib/search/config.ts +49 -0
  292. package/src/lib/search/tokenize.ts +45 -0
  293. package/src/lib/slugify.ts +28 -0
  294. package/src/lib/testing/bootstrap.ts +124 -0
  295. package/src/lib/testing/index.ts +15 -0
  296. package/src/lib/testing/renderWithProviders.tsx +31 -0
  297. package/src/lib/url.ts +12 -0
  298. package/src/lib/utils.ts +17 -0
  299. package/src/lib/version.ts +5 -0
  300. package/src/modules/__tests__/dsl.test.ts +35 -0
  301. package/src/modules/__tests__/registry.test.ts +300 -0
  302. package/src/modules/dashboard/widgets.ts +57 -0
  303. package/src/modules/dsl.ts +32 -0
  304. package/src/modules/entities/__tests__/validation.test.ts +52 -0
  305. package/src/modules/entities/kinds.ts +20 -0
  306. package/src/modules/entities/options.ts +36 -0
  307. package/src/modules/entities/validation.ts +118 -0
  308. package/src/modules/entities/validators.ts +93 -0
  309. package/src/modules/entities.ts +102 -0
  310. package/src/modules/navigation/sidebarPreferences.ts +62 -0
  311. package/src/modules/perspectives/types.ts +40 -0
  312. package/src/modules/registry.ts +249 -0
  313. package/src/modules/search.ts +325 -0
  314. package/src/modules/vector.ts +122 -0
  315. package/src/modules/widgets/__tests__/injection.test.ts +48 -0
  316. package/src/modules/widgets/injection-loader.ts +235 -0
  317. package/src/modules/widgets/injection.ts +120 -0
  318. package/src/security/features.ts +22 -0
  319. package/src/types/pg.d.ts +2 -0
  320. package/src/types/react-email.d.ts +2 -0
  321. package/src/types/resend.d.ts +2 -0
  322. package/tsconfig.build.json +11 -0
  323. package/tsconfig.json +9 -0
  324. package/watch.mjs +6 -0
@@ -0,0 +1,396 @@
1
+ import { setRecordCustomFields } from "@open-mercato/core/modules/entities/lib/helpers";
2
+ import { validateCustomFieldValuesServer } from "@open-mercato/core/modules/entities/lib/validation";
3
+ import { CrudHttpError } from "../crud/errors.js";
4
+ import { normalizeCustomFieldValues } from "../custom-fields/normalize.js";
5
+ import { parseBooleanToken } from "../boolean.js";
6
+ const COVERAGE_REFRESH_INTERVAL_MS = 5 * 60 * 1e3;
7
+ const coverageRefreshTracker = /* @__PURE__ */ new Map();
8
+ function shouldTriggerCoverageRefresh(entityType, tenantId) {
9
+ if (!entityType) return false;
10
+ const key = `${entityType}|${tenantId ?? "__null__"}`;
11
+ const now = Date.now();
12
+ const last = coverageRefreshTracker.get(key) ?? 0;
13
+ if (now - last < COVERAGE_REFRESH_INTERVAL_MS) return false;
14
+ coverageRefreshTracker.set(key, now);
15
+ return true;
16
+ }
17
+ class DefaultDataEngine {
18
+ constructor(em, container) {
19
+ this.em = em;
20
+ this.container = container;
21
+ this.pendingSideEffects = /* @__PURE__ */ new Map();
22
+ }
23
+ async setCustomFields(opts) {
24
+ const { entityId, recordId, organizationId = null, tenantId = null, values } = opts;
25
+ await this.validateCustomFieldValues(entityId, organizationId, tenantId, values);
26
+ let encryptionService = null;
27
+ try {
28
+ encryptionService = this.container.resolve("tenantEncryptionService");
29
+ } catch {
30
+ encryptionService = null;
31
+ }
32
+ await setRecordCustomFields(this.em, {
33
+ entityId,
34
+ recordId,
35
+ organizationId,
36
+ tenantId,
37
+ values,
38
+ encryptionService
39
+ });
40
+ if (opts.notify !== false) {
41
+ let bus = null;
42
+ try {
43
+ bus = this.container.resolve("eventBus");
44
+ } catch {
45
+ bus = null;
46
+ }
47
+ if (bus) {
48
+ const [mod, ent] = (entityId || "").split(":");
49
+ if (mod && ent) {
50
+ try {
51
+ await bus.emitEvent(`${mod}.${ent}.updated`, { id: recordId, organizationId, tenantId }, { persistent: true });
52
+ } catch {
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }
58
+ normalizeDocValues(values) {
59
+ const out = {};
60
+ for (const [k, v] of Object.entries(values || {})) {
61
+ if (k === "id" || k === "entity_id" || k === "entityId") continue;
62
+ if (k.startsWith("cf_")) out[`cf:${k.slice(3)}`] = v;
63
+ else out[k] = v;
64
+ }
65
+ return out;
66
+ }
67
+ backcompatEavEnabled() {
68
+ try {
69
+ return parseBooleanToken(process.env.ENTITIES_BACKCOMPAT_EAV_FOR_CUSTOM ?? "") === true;
70
+ } catch {
71
+ return false;
72
+ }
73
+ }
74
+ async ensureStorageTableExists() {
75
+ const knex = this.em.getConnection().getKnex();
76
+ const exists = await knex("information_schema.tables").where({ table_name: "custom_entities_storage" }).first();
77
+ if (!exists) {
78
+ throw new Error("custom_entities_storage table is missing. Run migrations (yarn db:migrate).");
79
+ }
80
+ }
81
+ normalizeValuesForValidation(values) {
82
+ if (!values) return {};
83
+ const out = {};
84
+ for (const [key, value] of Object.entries(values)) {
85
+ if (value === void 0) continue;
86
+ if (key.startsWith("cf_") || key.startsWith("cf:")) {
87
+ const normalized = key.slice(3);
88
+ if (normalized) out[normalized] = value;
89
+ continue;
90
+ }
91
+ out[key] = value;
92
+ }
93
+ return out;
94
+ }
95
+ async validateCustomFieldValues(entityId, organizationId, tenantId, values) {
96
+ const prepared = this.normalizeValuesForValidation(values);
97
+ if (!entityId || Object.keys(prepared).length === 0) return;
98
+ const result = await validateCustomFieldValuesServer(this.em, {
99
+ entityId,
100
+ organizationId,
101
+ tenantId,
102
+ values: prepared
103
+ });
104
+ if (!result.ok) {
105
+ throw new CrudHttpError(400, { error: "Validation failed", fields: result.fieldErrors });
106
+ }
107
+ }
108
+ async createCustomEntityRecord(opts) {
109
+ const knex = this.em.getConnection().getKnex();
110
+ await this.ensureStorageTableExists();
111
+ await this.validateCustomFieldValues(opts.entityId, opts.organizationId ?? null, opts.tenantId ?? null, opts.values);
112
+ const rawId = String(opts.recordId ?? "").trim();
113
+ const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(rawId);
114
+ const sentinel = rawId.toLowerCase();
115
+ const shouldGenerate = !rawId || !isUuid || sentinel === "create" || sentinel === "new" || sentinel === "null" || sentinel === "undefined";
116
+ const id = shouldGenerate ? (() => {
117
+ const g = globalThis;
118
+ if (g.crypto?.randomUUID) return g.crypto.randomUUID();
119
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
120
+ const r = Math.random() * 16 | 0;
121
+ const v = c === "x" ? r : r & 3 | 8;
122
+ return v.toString(16);
123
+ });
124
+ })() : rawId;
125
+ const orgId = opts.organizationId ?? null;
126
+ const tenantId = opts.tenantId ?? null;
127
+ const doc = { id, ...this.normalizeDocValues(opts.values || {}) };
128
+ const payload = {
129
+ entity_type: opts.entityId,
130
+ entity_id: id,
131
+ organization_id: orgId,
132
+ tenant_id: tenantId,
133
+ doc,
134
+ updated_at: knex.fn.now(),
135
+ created_at: knex.fn.now(),
136
+ deleted_at: null
137
+ };
138
+ try {
139
+ await knex("custom_entities_storage").insert(payload).onConflict(["entity_type", "entity_id", "organization_id"]).merge({ doc: payload.doc, updated_at: knex.fn.now(), deleted_at: null });
140
+ } catch {
141
+ try {
142
+ const updated = await knex("custom_entities_storage").where({ entity_type: opts.entityId, entity_id: id, organization_id: orgId }).update({ doc: payload.doc, updated_at: knex.fn.now(), deleted_at: null });
143
+ if (!updated) {
144
+ await knex("custom_entities_storage").insert(payload);
145
+ }
146
+ } catch (err) {
147
+ throw err;
148
+ }
149
+ }
150
+ if (this.backcompatEavEnabled() && opts.values && Object.keys(opts.values).length > 0) {
151
+ await this.setCustomFields({
152
+ entityId: opts.entityId,
153
+ recordId: id,
154
+ organizationId: orgId,
155
+ tenantId,
156
+ values: normalizeCustomFieldValues(opts.values),
157
+ notify: opts.notify
158
+ // defaults to true
159
+ });
160
+ }
161
+ return { id };
162
+ }
163
+ async updateCustomEntityRecord(opts) {
164
+ const knex = this.em.getConnection().getKnex();
165
+ await this.validateCustomFieldValues(opts.entityId, opts.organizationId ?? null, opts.tenantId ?? null, opts.values);
166
+ const id = String(opts.recordId);
167
+ const orgId = opts.organizationId ?? null;
168
+ const tenantId = opts.tenantId ?? null;
169
+ await this.ensureStorageTableExists();
170
+ const row = await knex("custom_entities_storage").where({ entity_type: opts.entityId, entity_id: id, organization_id: orgId }).first();
171
+ const prevDoc = row?.doc || { id };
172
+ const nextDoc = { ...prevDoc, ...this.normalizeDocValues(opts.values || {}), id };
173
+ try {
174
+ const updated = await knex("custom_entities_storage").where({ entity_type: opts.entityId, entity_id: id, organization_id: orgId }).update({ doc: nextDoc, updated_at: knex.fn.now(), deleted_at: null });
175
+ if (!updated) {
176
+ await knex("custom_entities_storage").insert({
177
+ entity_type: opts.entityId,
178
+ entity_id: id,
179
+ organization_id: orgId,
180
+ tenant_id: tenantId,
181
+ doc: nextDoc,
182
+ created_at: knex.fn.now(),
183
+ updated_at: knex.fn.now(),
184
+ deleted_at: null
185
+ });
186
+ }
187
+ } catch (err) {
188
+ throw err;
189
+ }
190
+ if (this.backcompatEavEnabled() && opts.values && Object.keys(opts.values).length > 0) {
191
+ await this.setCustomFields({
192
+ entityId: opts.entityId,
193
+ recordId: id,
194
+ organizationId: orgId,
195
+ tenantId,
196
+ values: normalizeCustomFieldValues(opts.values),
197
+ notify: opts.notify
198
+ // defaults to true
199
+ });
200
+ }
201
+ }
202
+ async deleteCustomEntityRecord(opts) {
203
+ const knex = this.em.getConnection().getKnex();
204
+ const id = String(opts.recordId);
205
+ const orgId = opts.organizationId ?? null;
206
+ const soft = opts.soft !== false;
207
+ if (soft) {
208
+ await knex("custom_entities_storage").where({ entity_type: opts.entityId, entity_id: id, organization_id: orgId }).update({ deleted_at: knex.fn.now(), updated_at: knex.fn.now() });
209
+ } else {
210
+ await knex("custom_entities_storage").where({ entity_type: opts.entityId, entity_id: id, organization_id: orgId }).delete();
211
+ }
212
+ try {
213
+ const { CustomFieldValue } = await import("@open-mercato/core/modules/entities/data/entities");
214
+ const values = await this.em.find(CustomFieldValue, {
215
+ entityId: opts.entityId,
216
+ recordId: id,
217
+ organizationId: orgId,
218
+ tenantId: opts.tenantId ?? null
219
+ });
220
+ const now = /* @__PURE__ */ new Date();
221
+ const mutated = values.filter((record) => {
222
+ if (record.deletedAt) return false;
223
+ record.deletedAt = now;
224
+ return true;
225
+ });
226
+ if (mutated.length) await this.em.persistAndFlush(values);
227
+ } catch {
228
+ }
229
+ }
230
+ async createOrmEntity(opts) {
231
+ const entity = this.em.create(
232
+ opts.entity,
233
+ opts.data
234
+ );
235
+ await this.em.persistAndFlush(entity);
236
+ return entity;
237
+ }
238
+ async updateOrmEntity(opts) {
239
+ const current = await this.em.findOne(opts.entity, opts.where);
240
+ if (!current) return null;
241
+ await opts.apply(current);
242
+ await this.em.persistAndFlush(current);
243
+ return current;
244
+ }
245
+ async deleteOrmEntity(opts) {
246
+ const current = await this.em.findOne(opts.entity, opts.where);
247
+ if (!current) return null;
248
+ if (opts.soft !== false) {
249
+ const field = opts.softDeleteField || "deletedAt";
250
+ if (typeof current === "object" && current !== null) {
251
+ ;
252
+ current[field] = /* @__PURE__ */ new Date();
253
+ await this.em.persistAndFlush(current);
254
+ }
255
+ } else {
256
+ await this.em.removeAndFlush(current);
257
+ }
258
+ return current;
259
+ }
260
+ async emitOrmEntityEvent(opts) {
261
+ const { action, entity, events, indexer, identifiers } = opts;
262
+ if (!events && !indexer) return;
263
+ if (!identifiers?.id) return;
264
+ let bus = null;
265
+ try {
266
+ bus = this.container.resolve("eventBus");
267
+ } catch {
268
+ bus = null;
269
+ }
270
+ if (!bus) return;
271
+ const ctx = {
272
+ action,
273
+ entity,
274
+ identifiers: {
275
+ id: identifiers.id,
276
+ organizationId: identifiers.organizationId ?? null,
277
+ tenantId: identifiers.tenantId ?? null
278
+ }
279
+ };
280
+ if (events) {
281
+ const eventName = `${events.module}.${events.entity}.${action}`;
282
+ const payload = events.buildPayload ? events.buildPayload(ctx) : {
283
+ id: ctx.identifiers.id,
284
+ organizationId: ctx.identifiers.organizationId,
285
+ tenantId: ctx.identifiers.tenantId
286
+ };
287
+ try {
288
+ await bus.emitEvent(eventName, payload, { persistent: !!events.persistent });
289
+ } catch {
290
+ }
291
+ }
292
+ if (indexer) {
293
+ const resolveCoverageBaseDelta = () => {
294
+ if (action === "created") return 1;
295
+ if (action === "deleted") return -1;
296
+ return void 0;
297
+ };
298
+ const coverageBaseDelta = resolveCoverageBaseDelta();
299
+ if (action === "deleted") {
300
+ const payload = indexer.buildDeletePayload ? indexer.buildDeletePayload(ctx) : {
301
+ entityType: indexer.entityType,
302
+ recordId: ctx.identifiers.id,
303
+ organizationId: ctx.identifiers.organizationId,
304
+ tenantId: ctx.identifiers.tenantId
305
+ };
306
+ const enrichedPayload = payload;
307
+ enrichedPayload.crudAction = action;
308
+ if (coverageBaseDelta !== void 0) enrichedPayload.coverageBaseDelta = coverageBaseDelta;
309
+ try {
310
+ await bus.emitEvent("query_index.delete_one", enrichedPayload);
311
+ } catch {
312
+ }
313
+ } else {
314
+ const payload = indexer.buildUpsertPayload ? indexer.buildUpsertPayload(ctx) : {
315
+ entityType: indexer.entityType,
316
+ recordId: ctx.identifiers.id,
317
+ organizationId: ctx.identifiers.organizationId,
318
+ tenantId: ctx.identifiers.tenantId
319
+ };
320
+ const enrichedPayload = payload;
321
+ enrichedPayload.crudAction = action;
322
+ if (coverageBaseDelta !== void 0) enrichedPayload.coverageBaseDelta = coverageBaseDelta;
323
+ try {
324
+ await bus.emitEvent("query_index.upsert_one", enrichedPayload);
325
+ } catch {
326
+ }
327
+ }
328
+ if (shouldTriggerCoverageRefresh(indexer.entityType, ctx.identifiers.tenantId ?? null)) {
329
+ void bus.emitEvent("query_index.coverage.refresh", {
330
+ entityType: indexer.entityType,
331
+ tenantId: ctx.identifiers.tenantId ?? null,
332
+ organizationId: null,
333
+ delayMs: 0
334
+ }).catch(() => void 0);
335
+ }
336
+ }
337
+ }
338
+ markOrmEntityChange(opts) {
339
+ const { entity, identifiers } = opts;
340
+ if (!entity) return;
341
+ if (!identifiers?.id) return;
342
+ const key = this.buildSideEffectKey(opts.action, identifiers);
343
+ const existing = this.pendingSideEffects.get(key);
344
+ if (existing) {
345
+ existing.entity = entity;
346
+ existing.identifiers = {
347
+ id: identifiers.id,
348
+ organizationId: identifiers.organizationId ?? null,
349
+ tenantId: identifiers.tenantId ?? null
350
+ };
351
+ if (opts.events) existing.events = opts.events;
352
+ if (opts.indexer) existing.indexer = opts.indexer;
353
+ this.pendingSideEffects.set(key, existing);
354
+ return;
355
+ }
356
+ const entry = {
357
+ action: opts.action,
358
+ entity,
359
+ identifiers: {
360
+ id: identifiers.id,
361
+ organizationId: identifiers.organizationId ?? null,
362
+ tenantId: identifiers.tenantId ?? null
363
+ }
364
+ };
365
+ if (opts.events) entry.events = opts.events;
366
+ if (opts.indexer) entry.indexer = opts.indexer;
367
+ this.pendingSideEffects.set(key, entry);
368
+ }
369
+ async flushOrmEntityChanges() {
370
+ if (!this.pendingSideEffects.size) return;
371
+ const entries = Array.from(this.pendingSideEffects.values());
372
+ this.pendingSideEffects.clear();
373
+ for (const entry of entries) {
374
+ try {
375
+ await this.emitOrmEntityEvent({
376
+ action: entry.action,
377
+ entity: entry.entity,
378
+ identifiers: entry.identifiers,
379
+ events: entry.events,
380
+ indexer: entry.indexer
381
+ });
382
+ } catch {
383
+ }
384
+ }
385
+ }
386
+ buildSideEffectKey(action, identifiers) {
387
+ const id = identifiers.id ?? "";
388
+ const org = identifiers.organizationId ?? "";
389
+ const tenant = identifiers.tenantId ?? "";
390
+ return [action, id, org, tenant].join("|");
391
+ }
392
+ }
393
+ export {
394
+ DefaultDataEngine
395
+ };
396
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/data/engine.ts"],
4
+ "sourcesContent": ["import type { EntityData, EntityName, FilterQuery, RequiredEntityData } from '@mikro-orm/core'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\nimport { setRecordCustomFields } from '@open-mercato/core/modules/entities/lib/helpers'\nimport { validateCustomFieldValuesServer } from '@open-mercato/core/modules/entities/lib/validation'\nimport type { EventBus } from '@open-mercato/events/types'\nimport type {\n CrudEventAction,\n CrudEventsConfig,\n CrudIndexerConfig,\n CrudEntityIdentifiers,\n} from '../crud/types'\nimport { CrudHttpError } from '../crud/errors'\nimport { normalizeCustomFieldValues } from '../custom-fields/normalize'\nimport { parseBooleanToken } from '../boolean'\n\nconst COVERAGE_REFRESH_INTERVAL_MS = 5 * 60 * 1000\nconst coverageRefreshTracker = new Map<string, number>()\n\nfunction shouldTriggerCoverageRefresh(entityType: string | undefined, tenantId: string | null): boolean {\n if (!entityType) return false\n const key = `${entityType}|${tenantId ?? '__null__'}`\n const now = Date.now()\n const last = coverageRefreshTracker.get(key) ?? 0\n if (now - last < COVERAGE_REFRESH_INTERVAL_MS) return false\n coverageRefreshTracker.set(key, now)\n return true\n}\n\ntype CustomEntityValues = Record<string, unknown>\n\ntype QueuedCrudSideEffect = {\n action: CrudEventAction\n entity: unknown\n identifiers: CrudEntityIdentifiers\n events?: CrudEventsConfig<unknown>\n indexer?: CrudIndexerConfig<unknown>\n}\n\nexport interface DataEngine {\n setCustomFields(opts: {\n entityId: string\n recordId: string\n organizationId?: string | null\n tenantId?: string | null\n values: Record<string, string | number | boolean | null | undefined | Array<string | number | boolean | null | undefined>>\n notify?: boolean // default true -> emit '<module>.<entity>.updated'\n }): Promise<void>\n\n // Storage for user-defined entities (doc-based)\n createCustomEntityRecord(opts: {\n entityId: string // '<module>:<entity>'\n recordId?: string // optional; auto-generate if not provided\n organizationId?: string | null\n tenantId?: string | null\n values: CustomEntityValues\n notify?: boolean // keep event emitting as it is via setCustomFields (updated)\n }): Promise<{ id: string }>\n\n updateCustomEntityRecord(opts: {\n entityId: string\n recordId: string\n organizationId?: string | null\n tenantId?: string | null\n values: CustomEntityValues\n notify?: boolean // keep event emitting as it is via setCustomFields (updated)\n }): Promise<void>\n\n deleteCustomEntityRecord(opts: {\n entityId: string\n recordId: string\n organizationId?: string | null\n tenantId?: string | null\n soft?: boolean // default true: sets deleted_at\n notify?: boolean // keep event emitting as it is (no extra events here)\n }): Promise<void>\n\n // Generic ORM-backed entity operations used by CrudFactory\n createOrmEntity<T extends object>(opts: {\n entity: EntityName<T>\n data: EntityData<T>\n }): Promise<T>\n\n updateOrmEntity<T extends object>(opts: {\n entity: EntityName<T>\n where: FilterQuery<T>\n apply: (current: T) => Promise<void> | void\n }): Promise<T | null>\n\n deleteOrmEntity<T extends object>(opts: {\n entity: EntityName<T>\n where: FilterQuery<T>\n soft?: boolean\n softDeleteField?: keyof T & string\n }): Promise<T | null>\n\n emitOrmEntityEvent<T>(opts: {\n action: CrudEventAction\n entity: T\n events?: CrudEventsConfig<T>\n indexer?: CrudIndexerConfig<T>\n identifiers: CrudEntityIdentifiers\n }): Promise<void>\n\n markOrmEntityChange<T>(opts: {\n action: CrudEventAction\n entity: T | null | undefined\n events?: CrudEventsConfig<T>\n indexer?: CrudIndexerConfig<T>\n identifiers: CrudEntityIdentifiers\n }): void\n\n flushOrmEntityChanges(): Promise<void>\n}\n\nexport class DefaultDataEngine implements DataEngine {\n private pendingSideEffects = new Map<string, QueuedCrudSideEffect>()\n constructor(private em: EntityManager, private container: AwilixContainer) {}\n\n async setCustomFields(opts: Parameters<DataEngine['setCustomFields']>[0]): Promise<void> {\n const { entityId, recordId, organizationId = null, tenantId = null, values } = opts\n await this.validateCustomFieldValues(entityId, organizationId, tenantId, values as Record<string, unknown>)\n let encryptionService: any = null\n try {\n encryptionService = this.container.resolve('tenantEncryptionService') as any\n } catch {\n encryptionService = null\n }\n await setRecordCustomFields(this.em, {\n entityId,\n recordId,\n organizationId,\n tenantId,\n values,\n encryptionService,\n })\n if (opts.notify !== false) {\n let bus: EventBus | null = null\n try {\n bus = (this.container.resolve('eventBus') as EventBus)\n } catch {\n bus = null\n }\n if (bus) {\n const [mod, ent] = (entityId || '').split(':')\n if (mod && ent) {\n try {\n await bus.emitEvent(`${mod}.${ent}.updated`, { id: recordId, organizationId, tenantId }, { persistent: true })\n } catch {\n // non-blocking\n }\n }\n }\n }\n }\n\n private normalizeDocValues(values: CustomEntityValues): CustomEntityValues {\n const out: CustomEntityValues = {}\n for (const [k, v] of Object.entries(values || {})) {\n // Never allow callers to override reserved identifiers in the doc\n if (k === 'id' || k === 'entity_id' || k === 'entityId') continue\n // Accept both 'cf_<key>' and 'cf:<key>' inputs and normalize to 'cf:<key>'\n if (k.startsWith('cf_')) out[`cf:${k.slice(3)}`] = v\n else out[k] = v\n }\n return out\n }\n\n private backcompatEavEnabled(): boolean {\n try {\n return parseBooleanToken(process.env.ENTITIES_BACKCOMPAT_EAV_FOR_CUSTOM ?? '') === true\n } catch { return false }\n }\n\n private async ensureStorageTableExists(): Promise<void> {\n const knex = this.em.getConnection().getKnex()\n const exists = await knex('information_schema.tables')\n .where({ table_name: 'custom_entities_storage' })\n .first()\n if (!exists) {\n throw new Error('custom_entities_storage table is missing. Run migrations (yarn db:migrate).')\n }\n }\n\n private normalizeValuesForValidation(values: Record<string, unknown> | undefined | null): Record<string, unknown> {\n if (!values) return {}\n const out: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(values)) {\n if (value === undefined) continue\n if (key.startsWith('cf_') || key.startsWith('cf:')) {\n const normalized = key.slice(3)\n if (normalized) out[normalized] = value\n continue\n }\n out[key] = value\n }\n return out\n }\n\n private async validateCustomFieldValues(\n entityId: string,\n organizationId: string | null,\n tenantId: string | null,\n values: Record<string, unknown> | undefined | null,\n ): Promise<void> {\n const prepared = this.normalizeValuesForValidation(values)\n if (!entityId || Object.keys(prepared).length === 0) return\n const result = await validateCustomFieldValuesServer(this.em, {\n entityId,\n organizationId,\n tenantId,\n values: prepared,\n })\n if (!result.ok) {\n throw new CrudHttpError(400, { error: 'Validation failed', fields: result.fieldErrors })\n }\n }\n\n async createCustomEntityRecord(opts: Parameters<DataEngine['createCustomEntityRecord']>[0]): Promise<{ id: string }> {\n const knex = this.em.getConnection().getKnex()\n await this.ensureStorageTableExists()\n await this.validateCustomFieldValues(opts.entityId, opts.organizationId ?? null, opts.tenantId ?? null, opts.values)\n const rawId = String(opts.recordId ?? '').trim()\n const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(rawId)\n const sentinel = rawId.toLowerCase()\n const shouldGenerate = !rawId || !isUuid || sentinel === 'create' || sentinel === 'new' || sentinel === 'null' || sentinel === 'undefined'\n const id = shouldGenerate ? ((): string => {\n const g = globalThis as { crypto?: { randomUUID?: () => string } }\n if (g.crypto?.randomUUID) return g.crypto.randomUUID()\n // Fallback UUIDv4 generator\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0\n const v = c === 'x' ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n })() : rawId\n const orgId = opts.organizationId ?? null\n const tenantId = opts.tenantId ?? null\n const doc: Record<string, unknown> = { id, ...this.normalizeDocValues(opts.values || {}) }\n\n const payload = {\n entity_type: opts.entityId,\n entity_id: id,\n organization_id: orgId,\n tenant_id: tenantId,\n doc,\n updated_at: knex.fn.now(),\n created_at: knex.fn.now(),\n deleted_at: null,\n }\n\n // Upsert by scoped uniqueness\n try {\n await knex('custom_entities_storage')\n .insert(payload)\n .onConflict(['entity_type', 'entity_id', 'organization_id'])\n .merge({ doc: payload.doc, updated_at: knex.fn.now(), deleted_at: null })\n } catch {\n // Fallback for global scope uniqueness\n try {\n const updated = await knex('custom_entities_storage')\n .where({ entity_type: opts.entityId, entity_id: id, organization_id: orgId })\n .update({ doc: payload.doc, updated_at: knex.fn.now(), deleted_at: null })\n if (!updated) {\n await knex('custom_entities_storage').insert(payload)\n }\n } catch (err) {\n // Surface a clear error so it doesn't silently fall back only to EAV\n throw err\n }\n }\n\n // Optional EAV backward compatibility (disabled by default)\n if (this.backcompatEavEnabled() && opts.values && Object.keys(opts.values).length > 0) {\n await this.setCustomFields({\n entityId: opts.entityId,\n recordId: id,\n organizationId: orgId,\n tenantId: tenantId,\n values: normalizeCustomFieldValues(opts.values),\n notify: opts.notify, // defaults to true\n })\n }\n\n return { id }\n }\n\n async updateCustomEntityRecord(opts: Parameters<DataEngine['updateCustomEntityRecord']>[0]): Promise<void> {\n const knex = this.em.getConnection().getKnex()\n await this.validateCustomFieldValues(opts.entityId, opts.organizationId ?? null, opts.tenantId ?? null, opts.values)\n const id = String(opts.recordId)\n const orgId = opts.organizationId ?? null\n const tenantId = opts.tenantId ?? null\n\n // Merge doc shallowly: load existing doc and overlay\n await this.ensureStorageTableExists()\n const row = await knex('custom_entities_storage')\n .where({ entity_type: opts.entityId, entity_id: id, organization_id: orgId })\n .first()\n const prevDoc: Record<string, unknown> = row?.doc || { id }\n const nextDoc: Record<string, unknown> = { ...prevDoc, ...this.normalizeDocValues(opts.values || {}), id }\n try {\n const updated = await knex('custom_entities_storage')\n .where({ entity_type: opts.entityId, entity_id: id, organization_id: orgId })\n .update({ doc: nextDoc, updated_at: knex.fn.now(), deleted_at: null })\n if (!updated) {\n await knex('custom_entities_storage').insert({\n entity_type: opts.entityId,\n entity_id: id,\n organization_id: orgId,\n tenant_id: tenantId,\n doc: nextDoc,\n created_at: knex.fn.now(),\n updated_at: knex.fn.now(),\n deleted_at: null,\n })\n }\n } catch (err) {\n throw err\n }\n\n // Optional EAV backward compatibility (disabled by default)\n if (this.backcompatEavEnabled() && opts.values && Object.keys(opts.values).length > 0) {\n await this.setCustomFields({\n entityId: opts.entityId,\n recordId: id,\n organizationId: orgId,\n tenantId: tenantId,\n values: normalizeCustomFieldValues(opts.values),\n notify: opts.notify, // defaults to true\n })\n }\n }\n\n async deleteCustomEntityRecord(opts: Parameters<DataEngine['deleteCustomEntityRecord']>[0]): Promise<void> {\n const knex = this.em.getConnection().getKnex()\n const id = String(opts.recordId)\n const orgId = opts.organizationId ?? null\n const soft = opts.soft !== false\n\n if (soft) {\n await knex('custom_entities_storage')\n .where({ entity_type: opts.entityId, entity_id: id, organization_id: orgId })\n .update({ deleted_at: knex.fn.now(), updated_at: knex.fn.now() })\n } else {\n await knex('custom_entities_storage')\n .where({ entity_type: opts.entityId, entity_id: id, organization_id: orgId })\n .delete()\n }\n\n // Soft-delete EAV values to preserve current behavior\n try {\n const { CustomFieldValue } = await import('@open-mercato/core/modules/entities/data/entities')\n const values = await this.em.find(CustomFieldValue, {\n entityId: opts.entityId,\n recordId: id,\n organizationId: orgId,\n tenantId: opts.tenantId ?? null,\n })\n const now = new Date()\n const mutated = values.filter((record) => {\n if (record.deletedAt) return false\n record.deletedAt = now\n return true\n })\n if (mutated.length) await this.em.persistAndFlush(values)\n } catch { /* non-blocking */ }\n }\n\n async createOrmEntity<T extends object>(opts: { entity: EntityName<T>; data: EntityData<T> }): Promise<T> {\n const entity = this.em.create(\n opts.entity,\n opts.data as RequiredEntityData<T, never, true>\n )\n await this.em.persistAndFlush(entity)\n return entity\n }\n\n async updateOrmEntity<T extends object>(opts: {\n entity: EntityName<T>\n where: FilterQuery<T>\n apply: (current: T) => Promise<void> | void\n }): Promise<T | null> {\n const current = await this.em.findOne(opts.entity, opts.where)\n if (!current) return null\n await opts.apply(current)\n await this.em.persistAndFlush(current)\n return current\n }\n\n async deleteOrmEntity<T extends object>(opts: {\n entity: EntityName<T>\n where: FilterQuery<T>\n soft?: boolean\n softDeleteField?: keyof T & string\n }): Promise<T | null> {\n const current = await this.em.findOne(opts.entity, opts.where)\n if (!current) return null\n if (opts.soft !== false) {\n const field = opts.softDeleteField || ('deletedAt' as keyof T & string)\n if (typeof current === 'object' && current !== null) {\n ;(current as Record<string, unknown>)[field] = new Date()\n await this.em.persistAndFlush(current)\n }\n } else {\n await this.em.removeAndFlush(current)\n }\n return current\n }\n\n async emitOrmEntityEvent<T>(opts: { action: CrudEventAction; entity: T; events?: CrudEventsConfig<T>; indexer?: CrudIndexerConfig<T>; identifiers: CrudEntityIdentifiers }): Promise<void> {\n const { action, entity, events, indexer, identifiers } = opts\n if (!events && !indexer) return\n if (!identifiers?.id) return\n\n let bus: EventBus | null = null\n try {\n bus = (this.container.resolve('eventBus') as EventBus)\n } catch {\n bus = null\n }\n if (!bus) return\n\n const ctx = {\n action,\n entity,\n identifiers: {\n id: identifiers.id,\n organizationId: identifiers.organizationId ?? null,\n tenantId: identifiers.tenantId ?? null,\n },\n }\n\n if (events) {\n const eventName = `${events.module}.${events.entity}.${action}`\n const payload = events.buildPayload\n ? events.buildPayload(ctx)\n : {\n id: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }\n try {\n await bus.emitEvent(eventName, payload, { persistent: !!events.persistent })\n } catch {\n // non-blocking\n }\n }\n\n if (indexer) {\n const resolveCoverageBaseDelta = (): number | undefined => {\n if (action === 'created') return 1\n if (action === 'deleted') return -1\n return undefined\n }\n const coverageBaseDelta = resolveCoverageBaseDelta()\n\n if (action === 'deleted') {\n const payload = indexer.buildDeletePayload\n ? indexer.buildDeletePayload(ctx)\n : {\n entityType: indexer.entityType,\n recordId: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }\n const enrichedPayload = payload as Record<string, unknown>\n enrichedPayload.crudAction = action\n if (coverageBaseDelta !== undefined) enrichedPayload.coverageBaseDelta = coverageBaseDelta\n try {\n await bus.emitEvent('query_index.delete_one', enrichedPayload)\n } catch {\n // non-blocking\n }\n } else {\n const payload = indexer.buildUpsertPayload\n ? indexer.buildUpsertPayload(ctx)\n : {\n entityType: indexer.entityType,\n recordId: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }\n const enrichedPayload = payload as Record<string, unknown>\n enrichedPayload.crudAction = action\n if (coverageBaseDelta !== undefined) enrichedPayload.coverageBaseDelta = coverageBaseDelta\n try {\n await bus.emitEvent('query_index.upsert_one', enrichedPayload)\n } catch {\n // non-blocking\n }\n }\n\n if (shouldTriggerCoverageRefresh(indexer.entityType, ctx.identifiers.tenantId ?? null)) {\n void bus.emitEvent('query_index.coverage.refresh', {\n entityType: indexer.entityType,\n tenantId: ctx.identifiers.tenantId ?? null,\n organizationId: null,\n delayMs: 0,\n }).catch(() => undefined)\n }\n }\n }\n\n markOrmEntityChange<T>(opts: { action: CrudEventAction; entity: T | null | undefined; events?: CrudEventsConfig<T>; indexer?: CrudIndexerConfig<T>; identifiers: CrudEntityIdentifiers }): void {\n const { entity, identifiers } = opts\n if (!entity) return\n if (!identifiers?.id) return\n const key = this.buildSideEffectKey(opts.action, identifiers)\n const existing = this.pendingSideEffects.get(key)\n if (existing) {\n existing.entity = entity\n existing.identifiers = {\n id: identifiers.id,\n organizationId: identifiers.organizationId ?? null,\n tenantId: identifiers.tenantId ?? null,\n }\n if (opts.events) existing.events = opts.events as CrudEventsConfig<unknown>\n if (opts.indexer) existing.indexer = opts.indexer as CrudIndexerConfig<unknown>\n this.pendingSideEffects.set(key, existing)\n return\n }\n const entry: QueuedCrudSideEffect = {\n action: opts.action,\n entity,\n identifiers: {\n id: identifiers.id,\n organizationId: identifiers.organizationId ?? null,\n tenantId: identifiers.tenantId ?? null,\n },\n }\n if (opts.events) entry.events = opts.events as CrudEventsConfig<unknown>\n if (opts.indexer) entry.indexer = opts.indexer as CrudIndexerConfig<unknown>\n this.pendingSideEffects.set(key, entry)\n }\n\n async flushOrmEntityChanges(): Promise<void> {\n if (!this.pendingSideEffects.size) return\n const entries = Array.from(this.pendingSideEffects.values())\n this.pendingSideEffects.clear()\n for (const entry of entries) {\n try {\n await this.emitOrmEntityEvent({\n action: entry.action,\n entity: entry.entity,\n identifiers: entry.identifiers,\n events: entry.events as CrudEventsConfig<unknown>,\n indexer: entry.indexer as CrudIndexerConfig<unknown>,\n })\n } catch {\n // best-effort; continue with remaining side effects\n }\n }\n }\n\n private buildSideEffectKey(action: CrudEventAction, identifiers: CrudEntityIdentifiers): string {\n const id = identifiers.id ?? ''\n const org = identifiers.organizationId ?? ''\n const tenant = identifiers.tenantId ?? ''\n return [action, id, org, tenant].join('|')\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,5 @@
1
+ const escapeLikePattern = (value) => value.replace(/\\/g, "\\\\").replace(/[%_]/g, "\\$&");
2
+ export {
3
+ escapeLikePattern
4
+ };
5
+ //# sourceMappingURL=escapeLikePattern.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/db/escapeLikePattern.ts"],
4
+ "sourcesContent": ["export const escapeLikePattern = (value: string): string =>\n value.replace(/\\\\/g, '\\\\\\\\').replace(/[%_]/g, '\\\\$&')\n"],
5
+ "mappings": "AAAO,MAAM,oBAAoB,CAAC,UAChC,MAAM,QAAQ,OAAO,MAAM,EAAE,QAAQ,SAAS,MAAM;",
6
+ "names": []
7
+ }
@@ -0,0 +1,82 @@
1
+ import "dotenv/config";
2
+ import "reflect-metadata";
3
+ import { MikroORM } from "@mikro-orm/core";
4
+ import { PostgreSqlDriver } from "@mikro-orm/postgresql";
5
+ let ormInstance = null;
6
+ let _entities = null;
7
+ function registerOrmEntities(entities) {
8
+ if (_entities !== null && process.env.NODE_ENV === "development") {
9
+ console.debug("[Bootstrap] ORM entities re-registered (this may occur during HMR)");
10
+ }
11
+ _entities = entities;
12
+ }
13
+ function getOrmEntities() {
14
+ if (!_entities) {
15
+ throw new Error("[Bootstrap] ORM entities not registered. Call registerOrmEntities() at bootstrap.");
16
+ }
17
+ return _entities;
18
+ }
19
+ async function getOrm() {
20
+ if (ormInstance) {
21
+ return ormInstance;
22
+ }
23
+ const entities = getOrmEntities();
24
+ const clientUrl = process.env.DATABASE_URL;
25
+ if (!clientUrl) throw new Error("DATABASE_URL is not set");
26
+ const poolMin = parseInt(process.env.DB_POOL_MIN || "2");
27
+ const poolMax = parseInt(process.env.DB_POOL_MAX || "50");
28
+ const poolIdleTimeout = parseInt(process.env.DB_POOL_IDLE_TIMEOUT || "3000");
29
+ const poolAcquireTimeout = parseInt(process.env.DB_POOL_ACQUIRE_TIMEOUT || "6000");
30
+ const idleSessionTimeoutEnv = parseInt(process.env.DB_IDLE_SESSION_TIMEOUT_MS || "");
31
+ const idleInTxTimeoutEnv = parseInt(process.env.DB_IDLE_IN_TRANSACTION_TIMEOUT_MS || "");
32
+ const idleSessionTimeoutMs = Number.isFinite(idleSessionTimeoutEnv) ? idleSessionTimeoutEnv : process.env.NODE_ENV === "production" ? void 0 : 6e5;
33
+ const idleInTransactionTimeoutMs = Number.isFinite(idleInTxTimeoutEnv) ? idleInTxTimeoutEnv : process.env.NODE_ENV === "production" ? void 0 : 12e4;
34
+ const connectionOptions = idleSessionTimeoutMs && idleSessionTimeoutMs > 0 ? `-c idle_session_timeout=${idleSessionTimeoutMs}` : void 0;
35
+ ormInstance = await MikroORM.init({
36
+ driver: PostgreSqlDriver,
37
+ clientUrl,
38
+ entities,
39
+ debug: false,
40
+ // Connection pooling configuration
41
+ pool: {
42
+ min: poolMin,
43
+ max: poolMax,
44
+ idleTimeoutMillis: poolIdleTimeout,
45
+ acquireTimeoutMillis: poolAcquireTimeout,
46
+ // Close idle connections after 30 seconds
47
+ destroyTimeoutMillis: process.env.NODE_ENV === "production" ? 3e4 : 3e3
48
+ },
49
+ // Connection options
50
+ driverOptions: {
51
+ // Enable connection pooling
52
+ connection: {
53
+ // Maximum number of connections in the pool
54
+ max: poolMax,
55
+ // Minimum number of connections in the pool
56
+ min: poolMin,
57
+ // Close connections after this many milliseconds of inactivity
58
+ idleTimeoutMillis: poolIdleTimeout,
59
+ // Maximum time to wait for a connection from the pool
60
+ acquireTimeoutMillis: poolAcquireTimeout,
61
+ idle_in_transaction_session_timeout: idleInTransactionTimeoutMs,
62
+ options: connectionOptions
63
+ }
64
+ }
65
+ });
66
+ return ormInstance;
67
+ }
68
+ async function closeOrmIfLoaded() {
69
+ if (ormInstance) {
70
+ await ormInstance.close(true);
71
+ ormInstance = null;
72
+ }
73
+ }
74
+ if (process.env.NODE_ENV !== "production") {
75
+ void closeOrmIfLoaded();
76
+ }
77
+ export {
78
+ getOrm,
79
+ getOrmEntities,
80
+ registerOrmEntities
81
+ };
82
+ //# sourceMappingURL=mikro.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/db/mikro.ts"],
4
+ "sourcesContent": ["import 'dotenv/config'\nimport 'reflect-metadata'\nimport { MikroORM } from '@mikro-orm/core'\nimport { PostgreSqlDriver } from '@mikro-orm/postgresql'\n\nlet ormInstance: MikroORM<PostgreSqlDriver> | null = null\n\n// Registration pattern for publishable packages\nlet _entities: any[] | null = null\n\nexport function registerOrmEntities(entities: any[]) {\n if (_entities !== null && process.env.NODE_ENV === 'development') {\n console.debug('[Bootstrap] ORM entities re-registered (this may occur during HMR)')\n }\n _entities = entities\n}\n\nexport function getOrmEntities(): any[] {\n if (!_entities) {\n throw new Error('[Bootstrap] ORM entities not registered. Call registerOrmEntities() at bootstrap.')\n }\n return _entities\n}\n\nexport async function getOrm() {\n if (ormInstance) {\n return ormInstance\n }\n const entities = getOrmEntities()\n const clientUrl = process.env.DATABASE_URL\n if (!clientUrl) throw new Error('DATABASE_URL is not set')\n \n // Parse connection pool settings from environment\n const poolMin = parseInt(process.env.DB_POOL_MIN || '2')\n const poolMax = parseInt(process.env.DB_POOL_MAX || '50')\n const poolIdleTimeout = parseInt(process.env.DB_POOL_IDLE_TIMEOUT || '3000')\n const poolAcquireTimeout = parseInt(process.env.DB_POOL_ACQUIRE_TIMEOUT || '6000')\n const idleSessionTimeoutEnv = parseInt(process.env.DB_IDLE_SESSION_TIMEOUT_MS || '')\n const idleInTxTimeoutEnv = parseInt(process.env.DB_IDLE_IN_TRANSACTION_TIMEOUT_MS || '')\n const idleSessionTimeoutMs = Number.isFinite(idleSessionTimeoutEnv)\n ? idleSessionTimeoutEnv\n : process.env.NODE_ENV === 'production'\n ? undefined\n : 600_000\n const idleInTransactionTimeoutMs = Number.isFinite(idleInTxTimeoutEnv)\n ? idleInTxTimeoutEnv\n : process.env.NODE_ENV === 'production'\n ? undefined\n : 120_000\n const connectionOptions =\n idleSessionTimeoutMs && idleSessionTimeoutMs > 0\n ? `-c idle_session_timeout=${idleSessionTimeoutMs}`\n : undefined\n \n ormInstance = await MikroORM.init<PostgreSqlDriver>({\n driver: PostgreSqlDriver,\n clientUrl,\n entities,\n debug: false,\n // Connection pooling configuration\n pool: {\n min: poolMin,\n max: poolMax,\n idleTimeoutMillis: poolIdleTimeout,\n acquireTimeoutMillis: poolAcquireTimeout,\n // Close idle connections after 30 seconds\n destroyTimeoutMillis: process.env.NODE_ENV === 'production' ? 30000 : 3000,\n },\n // Connection options\n driverOptions: {\n // Enable connection pooling\n connection: {\n // Maximum number of connections in the pool\n max: poolMax,\n // Minimum number of connections in the pool\n min: poolMin,\n // Close connections after this many milliseconds of inactivity\n idleTimeoutMillis: poolIdleTimeout,\n // Maximum time to wait for a connection from the pool\n acquireTimeoutMillis: poolAcquireTimeout,\n idle_in_transaction_session_timeout: idleInTransactionTimeoutMs,\n options: connectionOptions,\n },\n },\n })\n return ormInstance\n}\n\n\nasync function closeOrmIfLoaded(): Promise<void> {\n if (ormInstance) {\n await ormInstance.close(true)\n ormInstance = null\n }\n}\n\n// In dev mode, handle reloads cleanly without leaving dangling connections.\nif (process.env.NODE_ENV !== 'production') {\n void closeOrmIfLoaded()\n}"],
5
+ "mappings": "AAAA,OAAO;AACP,OAAO;AACP,SAAS,gBAAgB;AACzB,SAAS,wBAAwB;AAEjC,IAAI,cAAiD;AAGrD,IAAI,YAA0B;AAEvB,SAAS,oBAAoB,UAAiB;AACnD,MAAI,cAAc,QAAQ,QAAQ,IAAI,aAAa,eAAe;AAChE,YAAQ,MAAM,oEAAoE;AAAA,EACpF;AACA,cAAY;AACd;AAEO,SAAS,iBAAwB;AACtC,MAAI,CAAC,WAAW;AACd,UAAM,IAAI,MAAM,mFAAmF;AAAA,EACrG;AACA,SAAO;AACT;AAEA,eAAsB,SAAS;AAC7B,MAAI,aAAa;AACf,WAAO;AAAA,EACT;AACA,QAAM,WAAW,eAAe;AAChC,QAAM,YAAY,QAAQ,IAAI;AAC9B,MAAI,CAAC,UAAW,OAAM,IAAI,MAAM,yBAAyB;AAGzD,QAAM,UAAU,SAAS,QAAQ,IAAI,eAAe,GAAG;AACvD,QAAM,UAAU,SAAS,QAAQ,IAAI,eAAe,IAAI;AACxD,QAAM,kBAAkB,SAAS,QAAQ,IAAI,wBAAwB,MAAM;AAC3E,QAAM,qBAAqB,SAAS,QAAQ,IAAI,2BAA2B,MAAM;AACjF,QAAM,wBAAwB,SAAS,QAAQ,IAAI,8BAA8B,EAAE;AACnF,QAAM,qBAAqB,SAAS,QAAQ,IAAI,qCAAqC,EAAE;AACvF,QAAM,uBAAuB,OAAO,SAAS,qBAAqB,IAC9D,wBACA,QAAQ,IAAI,aAAa,eACvB,SACA;AACN,QAAM,6BAA6B,OAAO,SAAS,kBAAkB,IACjE,qBACA,QAAQ,IAAI,aAAa,eACvB,SACA;AACN,QAAM,oBACJ,wBAAwB,uBAAuB,IAC3C,2BAA2B,oBAAoB,KAC/C;AAEN,gBAAc,MAAM,SAAS,KAAuB;AAAA,IAClD,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA,OAAO;AAAA;AAAA,IAEP,MAAM;AAAA,MACJ,KAAK;AAAA,MACL,KAAK;AAAA,MACL,mBAAmB;AAAA,MACnB,sBAAsB;AAAA;AAAA,MAEtB,sBAAsB,QAAQ,IAAI,aAAa,eAAe,MAAQ;AAAA,IACxE;AAAA;AAAA,IAEA,eAAe;AAAA;AAAA,MAEb,YAAY;AAAA;AAAA,QAEV,KAAK;AAAA;AAAA,QAEL,KAAK;AAAA;AAAA,QAEL,mBAAmB;AAAA;AAAA,QAEnB,sBAAsB;AAAA,QACtB,qCAAqC;AAAA,QACrC,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAGA,eAAe,mBAAkC;AAC/C,MAAI,aAAa;AACf,UAAM,YAAY,MAAM,IAAI;AAC5B,kBAAc;AAAA,EAChB;AACF;AAGA,IAAI,QAAQ,IAAI,aAAa,cAAc;AACzC,OAAK,iBAAiB;AACxB;",
6
+ "names": []
7
+ }