@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,416 @@
1
+ import type { EntityMetadata, EventArgs, EventSubscriber } from '@mikro-orm/core'
2
+ import { ReferenceKind } from '@mikro-orm/core'
3
+ import { resolveEntityIdFromMetadata } from './entityIds'
4
+ import { TenantDataEncryptionService } from './tenantDataEncryptionService'
5
+ import { isTenantDataEncryptionEnabled } from './toggles'
6
+ import { isEncryptionDebugEnabled } from './toggles'
7
+ import { resolveTenantEncryptionService } from './customFieldValues'
8
+
9
+ type Scoped = {
10
+ tenantId?: string | null
11
+ tenant_id?: string | null
12
+ tenant?: { id?: string | null } | null
13
+ organizationId?: string | null
14
+ organization_id?: string | null
15
+ organization?: { id?: string | null } | null
16
+ }
17
+
18
+ type Scope = { tenantId: string | null; organizationId: string | null }
19
+
20
+ function resolveScope(entity: Scoped): Scope {
21
+ const tenantId = entity.tenantId ?? entity.tenant_id ?? entity.tenant?.id ?? null
22
+ const organizationId = entity.organizationId ?? entity.organization_id ?? entity.organization?.id ?? null
23
+ return {
24
+ tenantId: tenantId ? String(tenantId) : null,
25
+ organizationId: organizationId ? String(organizationId) : null,
26
+ }
27
+ }
28
+
29
+ function debug(event: string, payload: Record<string, unknown>) {
30
+ if (!isEncryptionDebugEnabled()) return
31
+ try {
32
+ // eslint-disable-next-line no-console
33
+ console.debug(event, payload)
34
+ } catch {
35
+ // ignore
36
+ }
37
+ }
38
+
39
+ const registeredEventManagers = new WeakSet<object>()
40
+
41
+ const toSnakeCase = (value: string): string =>
42
+ value.replace(/([A-Z])/g, '_$1').replace(/__/g, '_').toLowerCase()
43
+
44
+ export class TenantEncryptionSubscriber implements EventSubscriber<any> {
45
+ constructor(private readonly service: TenantDataEncryptionService) {}
46
+
47
+ getSubscribedEntities() {
48
+ return [] // listen to all entities
49
+ }
50
+
51
+ private resolveMeta(
52
+ meta: EntityMetadata<any> | undefined,
53
+ entity: Record<string, unknown>,
54
+ em?: { getMetadata?: () => any },
55
+ ): EntityMetadata<any> | undefined {
56
+ if (meta) return meta
57
+ const ctor = (entity as any)?.constructor
58
+ const name = ctor?.name
59
+ const registry = em?.getMetadata?.()
60
+ if (!registry || !name) return meta
61
+ try { return registry.find?.(name) } catch {}
62
+ try { return registry.find?.(ctor) } catch {}
63
+ try { return registry.get?.(name) } catch {}
64
+ try { return registry.get?.(ctor) } catch {}
65
+ const all =
66
+ (typeof registry.getAll === 'function' && registry.getAll()) ||
67
+ (Array.isArray((registry as any).metadata) ? (registry as any).metadata : undefined) ||
68
+ (registry as any).metadata ||
69
+ {}
70
+ try {
71
+ const entries = Array.isArray(all) ? all : Object.values<any>(all)
72
+ const match = entries.find(
73
+ (m: any) =>
74
+ m?.className === name ||
75
+ m?.name === name ||
76
+ m?.entityName === name ||
77
+ m?.collection === ctor?.prototype?.__meta?.tableName ||
78
+ m?.tableName === ctor?.prototype?.__meta?.tableName,
79
+ )
80
+ if (match) return match as EntityMetadata<any>
81
+ } catch {
82
+ // best-effort
83
+ }
84
+ return meta
85
+ }
86
+
87
+ private resolveEntityId(meta: EntityMetadata<any> | undefined): string | null {
88
+ try {
89
+ return resolveEntityIdFromMetadata(meta)
90
+ } catch {
91
+ return null
92
+ }
93
+ }
94
+
95
+ private syncOriginalEntityData(
96
+ target: Record<string, unknown>,
97
+ meta: EntityMetadata<any> | undefined,
98
+ em?: { getComparator?: () => any },
99
+ ) {
100
+ const helper = (target as any)?.__helper
101
+ if (!helper || typeof helper !== 'object') return
102
+
103
+ // Prefer MikroORM comparator snapshot so change detection uses the expected shape.
104
+ try {
105
+ const comparator = em?.getComparator?.()
106
+ if (comparator?.prepareEntity) {
107
+ helper.__originalEntityData = comparator.prepareEntity(target)
108
+ helper.__touched = false
109
+ return
110
+ }
111
+ } catch (err) {
112
+ debug('⚪️ subscriber.sync_original.comparator_failed', {
113
+ entity: meta?.className || meta?.name,
114
+ message: (err as Error)?.message ?? String(err),
115
+ })
116
+ }
117
+
118
+ // Fallback: shallow snapshot of scalar/owner props to keep entities clean without comparator.
119
+ const properties = meta?.properties ? Object.values(meta.properties) : []
120
+ if (properties.length === 0) return
121
+ const snapshot: Record<string, unknown> = { ...(helper.__originalEntityData ?? {}) }
122
+ for (const prop of properties) {
123
+ if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes((prop as any).kind)) continue
124
+ const name = (prop as any).name
125
+ if (typeof name !== 'string' || !name.length) continue
126
+ snapshot[name] = (target as Record<string, unknown>)[name]
127
+ }
128
+ helper.__originalEntityData = snapshot
129
+ helper.__touched = false
130
+ }
131
+
132
+ private async encrypt(
133
+ target: Record<string, unknown>,
134
+ meta: EntityMetadata<any> | undefined,
135
+ em?: { getMetadata?: () => any; getComparator?: () => any },
136
+ changeSet?: { payload?: Record<string, unknown> },
137
+ ) {
138
+ if (!isTenantDataEncryptionEnabled() || !this.service.isEnabled()) {
139
+ debug('⚪️ subscriber.skip', { reason: 'disabled', entity: meta?.className || meta?.name })
140
+ return
141
+ }
142
+ const resolvedMeta = this.resolveMeta(meta, target, em)
143
+ const entityId = this.resolveEntityId(resolvedMeta)
144
+ if (!entityId) {
145
+ debug('⚠️ subscriber.decrypt.skip.entity_id_missing', {
146
+ metaName: resolvedMeta?.className || resolvedMeta?.name,
147
+ table: (resolvedMeta as any)?.tableName,
148
+ })
149
+ return
150
+ }
151
+ const { tenantId, organizationId } = resolveScope(target)
152
+ if (!tenantId) {
153
+ debug('⚪️ subscriber.skip', { reason: 'no-tenant', entityId })
154
+ return
155
+ }
156
+ const encrypted = await this.service.encryptEntityPayload(entityId, target, tenantId, organizationId)
157
+ const metaProps: Record<string, unknown> = resolvedMeta?.properties && typeof resolvedMeta.properties === 'object'
158
+ ? resolvedMeta.properties
159
+ : {}
160
+ const payloadObj: Record<string, unknown> | null =
161
+ changeSet && typeof changeSet === 'object'
162
+ ? (typeof changeSet.payload === 'object' && changeSet.payload
163
+ ? (changeSet.payload as Record<string, unknown>)
164
+ : ((changeSet.payload = {}) as Record<string, unknown>))
165
+ : null
166
+ const updates: Record<string, unknown> = {}
167
+ const columnNameFor = (propKey: string, prop: Record<string, unknown> | undefined): string => {
168
+ try {
169
+ if (prop && typeof prop === 'object') {
170
+ const explicit = (prop as any)?.fieldName
171
+ if (typeof explicit === 'string' && explicit.length) return explicit
172
+ const name = (prop as any)?.name
173
+ if (typeof name === 'string' && name.length) return name
174
+ }
175
+ } catch (err) {
176
+ debug('⚠️ subscriber.column_name.resolve', {
177
+ entityId,
178
+ propKey,
179
+ message: (err as Error)?.message ?? String(err),
180
+ })
181
+ }
182
+ return toSnakeCase(propKey)
183
+ }
184
+
185
+ for (const [key, value] of Object.entries(encrypted)) {
186
+ const prop = (metaProps as Record<string, any>)[key]
187
+ if (!prop || typeof prop !== 'object') continue
188
+ if ((target as Record<string, unknown>)[key] === value) continue
189
+ updates[key] = value
190
+ }
191
+ if (Object.keys(updates).length === 0) return
192
+ Object.assign(target, updates)
193
+ if (payloadObj) {
194
+ try {
195
+ const ensureColumnKey = (propKey: string, value: unknown) => {
196
+ const columnName = columnNameFor(propKey, (metaProps as Record<string, any>)[propKey])
197
+ const canonicalKey = columnName || toSnakeCase(propKey)
198
+ const aliases = new Set(
199
+ [propKey, toSnakeCase(propKey), columnName, columnName ? toSnakeCase(columnName) : undefined].filter(
200
+ (v): v is string => typeof v === 'string' && v.length > 0,
201
+ ),
202
+ )
203
+ for (const alias of aliases) {
204
+ if (Object.prototype.hasOwnProperty.call(payloadObj, alias)) delete payloadObj[alias]
205
+ }
206
+ const finalKey = columnName || toSnakeCase(propKey)
207
+ payloadObj[finalKey] = value
208
+ }
209
+ for (const key of Object.keys(updates)) {
210
+ ensureColumnKey(key, updates[key])
211
+ }
212
+ } catch (err) {
213
+ debug('⚠️ subscriber.payload.normalize.error', {
214
+ entityId,
215
+ message: (err as Error)?.message ?? String(err),
216
+ })
217
+ }
218
+ }
219
+ }
220
+
221
+ async decryptEntityGraph(
222
+ target: Record<string, unknown>,
223
+ meta: EntityMetadata<any> | undefined,
224
+ em?: { getMetadata?: () => any; getComparator?: () => any },
225
+ opts: { syncOriginal?: boolean; seen?: WeakSet<object>; fallbackScope?: Scope } = {},
226
+ ) {
227
+ await this.decrypt(target, meta, em, opts)
228
+ }
229
+
230
+ private async decrypt(
231
+ target: Record<string, unknown>,
232
+ meta: EntityMetadata<any> | undefined,
233
+ em?: { getMetadata?: () => any; getComparator?: () => any },
234
+ {
235
+ syncOriginal = false,
236
+ seen,
237
+ fallbackScope,
238
+ }: { syncOriginal?: boolean; seen?: WeakSet<object>; fallbackScope?: Scope } = {},
239
+ ) {
240
+ const visited = seen ?? new WeakSet<object>()
241
+ if (visited.has(target as object)) return
242
+ visited.add(target as object)
243
+ if (!isTenantDataEncryptionEnabled() || !this.service.isEnabled()) {
244
+ debug('⚪️ subscriber.skip', { reason: 'disabled', entity: meta?.className || meta?.name })
245
+ return
246
+ }
247
+ const resolvedMeta = this.resolveMeta(meta, target, em)
248
+ const entityId = this.resolveEntityId(resolvedMeta)
249
+ if (!entityId) return
250
+ const { tenantId, organizationId } = resolveScope(target)
251
+ const scopedTenantId = tenantId ?? fallbackScope?.tenantId ?? null
252
+ const scopedOrgId = organizationId ?? fallbackScope?.organizationId ?? null
253
+ if (!scopedTenantId) {
254
+ debug('⚪️ subscriber.skip', { reason: 'no-tenant', entityId })
255
+ return
256
+ }
257
+ const decrypted = await this.service.decryptEntityPayload(entityId, target, scopedTenantId, scopedOrgId)
258
+ Object.assign(target, decrypted)
259
+ if (syncOriginal) {
260
+ this.syncOriginalEntityData(target, resolvedMeta, em as any)
261
+ }
262
+ const nextFallback =
263
+ fallbackScope ??
264
+ (tenantId || organizationId
265
+ ? { tenantId: tenantId ?? null, organizationId: organizationId ?? null }
266
+ : { tenantId: scopedTenantId, organizationId: scopedOrgId })
267
+ // Best-effort deep decrypt for loaded relations so populated graphs get cleaned too.
268
+ try {
269
+ const extractEntities = (value: any): any[] => {
270
+ if (!value) return []
271
+ // MikroORM Reference wrapper
272
+ if (typeof value === 'object' && typeof (value as any).isInitialized === 'function') {
273
+ try {
274
+ if ((value as any).isInitialized()) {
275
+ const unwrapped = typeof (value as any).unwrap === 'function' ? (value as any).unwrap() : (value as any).__entity ?? (value as any)
276
+ if (unwrapped && typeof unwrapped === 'object') return [unwrapped]
277
+ }
278
+ } catch {
279
+ // ignore
280
+ }
281
+ return []
282
+ }
283
+ // Collection wrapper
284
+ if (typeof value === 'object' && typeof (value as any).isInitialized === 'function' && typeof (value as any).getItems === 'function') {
285
+ try {
286
+ return (value as any).isInitialized() ? (value as any).getItems() ?? [] : []
287
+ } catch {
288
+ return []
289
+ }
290
+ }
291
+ if (Array.isArray(value)) return value
292
+ if (typeof value === 'object') return [value]
293
+ return []
294
+ }
295
+ const props = resolvedMeta?.properties ? Object.values(resolvedMeta.properties) : []
296
+ for (const prop of props) {
297
+ const kind = (prop as any)?.kind
298
+ const name = (prop as any)?.name
299
+ if (typeof name !== 'string' || !name.length) continue
300
+ const value = (target as any)[name]
301
+ if (!value) continue
302
+ // Single-valued relation
303
+ if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(kind)) {
304
+ const nestedEntities = extractEntities(value)
305
+ for (const nested of nestedEntities) {
306
+ const nestedMeta = this.resolveMeta((nested as any).__meta ?? (nested as any).__helper?.__meta, nested, em)
307
+ await this.decrypt(nested as Record<string, unknown>, nestedMeta, em, {
308
+ syncOriginal: true,
309
+ seen: visited,
310
+ fallbackScope: nextFallback,
311
+ })
312
+ }
313
+ continue
314
+ }
315
+ // Collections
316
+ if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(kind)) {
317
+ const items = extractEntities(value)
318
+ for (const item of items) {
319
+ if (!item || typeof item !== 'object') continue
320
+ const nestedMeta = this.resolveMeta((item as any).__meta ?? (item as any).__helper?.__meta, item, em)
321
+ await this.decrypt(item as Record<string, unknown>, nestedMeta, em, {
322
+ syncOriginal: true,
323
+ seen: visited,
324
+ fallbackScope: nextFallback,
325
+ })
326
+ }
327
+ }
328
+ }
329
+ } catch (err) {
330
+ debug('⚠️ subscriber.deep_decrypt.error', {
331
+ entityId,
332
+ message: (err as Error)?.message ?? String(err),
333
+ })
334
+ }
335
+ }
336
+
337
+ async beforeCreate(args: EventArgs<any>) {
338
+ await this.encrypt(args.entity as Record<string, unknown>, args.meta, args.em, args.changeSet as any)
339
+ }
340
+
341
+ async beforeUpdate(args: EventArgs<any>) {
342
+ await this.decrypt(args.entity as Record<string, unknown>, args.meta, args.em)
343
+ await this.encrypt(args.entity as Record<string, unknown>, args.meta, args.em, args.changeSet as any)
344
+ }
345
+
346
+ async afterCreate(args: EventArgs<any>) {
347
+ await this.decrypt(args.entity as Record<string, unknown>, args.meta, args.em, { syncOriginal: true })
348
+ }
349
+
350
+ async afterUpdate(args: EventArgs<any>) {
351
+ await this.decrypt(args.entity as Record<string, unknown>, args.meta, args.em, { syncOriginal: true })
352
+ }
353
+
354
+ async afterUpsert(args: EventArgs<any>) {
355
+ await this.decrypt(args.entity as Record<string, unknown>, args.meta, args.em, { syncOriginal: true })
356
+ }
357
+
358
+ async onLoad(args: EventArgs<any>) {
359
+ await this.decrypt(args.entity as Record<string, unknown>, args.meta, args.em, { syncOriginal: true })
360
+ }
361
+
362
+ async afterFind(args: EventArgs<any> & { entities?: unknown[] }) {
363
+ const entities = Array.isArray(args.entities) ? args.entities : []
364
+ for (const entity of entities) {
365
+ await this.decrypt(entity as Record<string, unknown>, args.meta, args.em, { syncOriginal: true })
366
+ }
367
+ }
368
+ }
369
+
370
+ export function registerTenantEncryptionSubscriber(
371
+ em: { getEventManager?: () => { registerSubscriber?: (subscriber: EventSubscriber<any>) => void } } | null | undefined,
372
+ service: TenantDataEncryptionService,
373
+ ): void {
374
+ const eventManager = em?.getEventManager?.()
375
+ if (!eventManager || typeof eventManager.registerSubscriber !== 'function') return
376
+ if (registeredEventManagers.has(eventManager)) return
377
+ eventManager.registerSubscriber(new TenantEncryptionSubscriber(service))
378
+ registeredEventManagers.add(eventManager)
379
+ }
380
+
381
+ export async function decryptEntitiesWithFallbackScope(
382
+ targets: unknown | unknown[],
383
+ {
384
+ em,
385
+ tenantId,
386
+ organizationId,
387
+ encryptionService,
388
+ }: {
389
+ em: { getMetadata?: () => any; getComparator?: () => any }
390
+ tenantId?: string | null
391
+ organizationId?: string | null
392
+ encryptionService?: TenantDataEncryptionService | null
393
+ },
394
+ ): Promise<void> {
395
+ if (!isTenantDataEncryptionEnabled()) return
396
+ const list = Array.isArray(targets) ? targets : [targets]
397
+ if (!list.length) return
398
+ const service = encryptionService ?? resolveTenantEncryptionService(em as any)
399
+ if (!service || !service.isEnabled()) return
400
+ const subscriber = new TenantEncryptionSubscriber(service)
401
+ const fallback: Scope | undefined =
402
+ tenantId || organizationId
403
+ ? {
404
+ tenantId: tenantId ?? null,
405
+ organizationId: organizationId ?? null,
406
+ }
407
+ : undefined
408
+ for (const entity of list) {
409
+ if (!entity || typeof entity !== 'object') continue
410
+ const meta = (entity as any).__meta ?? (entity as any).__helper?.__meta
411
+ await subscriber.decryptEntityGraph(entity as Record<string, unknown>, meta, em as any, {
412
+ syncOriginal: true,
413
+ fallbackScope: fallback,
414
+ })
415
+ }
416
+ }