@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,285 @@
1
+ import { commandRegistry } from "./registry.js";
2
+ import { defaultUndoToken } from "./types.js";
3
+ import {
4
+ canonicalizeResourceTag,
5
+ deriveResourceFromCommandId,
6
+ invalidateCrudCache,
7
+ pickFirstIdentifier,
8
+ isCrudCacheDebugEnabled
9
+ } from "@open-mercato/shared/lib/crud/cache";
10
+ const SKIPPED_ACTION_LOG_RESOURCE_KINDS = /* @__PURE__ */ new Set([
11
+ "audit_logs.access",
12
+ "audit_logs.action",
13
+ "dashboards.layout",
14
+ "dashboards.user_widgets",
15
+ "dashboards.role_widgets"
16
+ ]);
17
+ function asRecord(input) {
18
+ if (!input || typeof input !== "object" || Array.isArray(input)) return null;
19
+ return input;
20
+ }
21
+ function extractAliasList(source) {
22
+ if (!source || typeof source !== "object" || Array.isArray(source)) return [];
23
+ const record = source;
24
+ const raw = record.cacheAliases;
25
+ if (!Array.isArray(raw)) return [];
26
+ const aliases = /* @__PURE__ */ new Set();
27
+ for (const value of raw) {
28
+ if (typeof value !== "string") continue;
29
+ const normalized = canonicalizeResourceTag(value);
30
+ if (normalized) aliases.add(normalized);
31
+ }
32
+ return Array.from(aliases);
33
+ }
34
+ class CommandBus {
35
+ async execute(commandId, options) {
36
+ const handler = this.resolveHandler(commandId);
37
+ const snapshots = await this.prepareSnapshots(handler, options);
38
+ const result = await handler.execute(options.input, options.ctx);
39
+ const afterSnapshot = await this.captureAfter(handler, options, result);
40
+ const snapshotsWithAfter = { ...snapshots, after: afterSnapshot };
41
+ const logMeta = await this.buildLog(handler, options, result, snapshotsWithAfter);
42
+ let mergedMeta = this.mergeMetadata(options.metadata, logMeta);
43
+ const undoable = this.isUndoable(handler);
44
+ if (undoable) {
45
+ mergedMeta = mergedMeta ?? {};
46
+ if (!mergedMeta.undoToken) mergedMeta.undoToken = defaultUndoToken();
47
+ if (mergedMeta.actorUserId === void 0) mergedMeta.actorUserId = options.ctx.auth?.sub ?? null;
48
+ }
49
+ if (afterSnapshot !== void 0 && afterSnapshot !== null) {
50
+ if (!mergedMeta) {
51
+ mergedMeta = { snapshotAfter: afterSnapshot };
52
+ } else if (!mergedMeta.snapshotAfter) {
53
+ mergedMeta.snapshotAfter = afterSnapshot;
54
+ }
55
+ }
56
+ if (snapshots.before) {
57
+ if (!mergedMeta) {
58
+ mergedMeta = { snapshotBefore: snapshots.before };
59
+ } else if (!mergedMeta.snapshotBefore) {
60
+ mergedMeta.snapshotBefore = snapshots.before;
61
+ }
62
+ }
63
+ const logEntry = await this.persistLog(commandId, options, mergedMeta);
64
+ await this.invalidateCacheAfterExecute(commandId, options, result, mergedMeta);
65
+ await this.flushCrudSideEffects(options.ctx.container);
66
+ return { result, logEntry };
67
+ }
68
+ async undo(undoToken, ctx) {
69
+ const service = ctx.container.resolve("actionLogService");
70
+ const log = await service.findByUndoToken(undoToken);
71
+ if (!log) throw new Error("Undo token expired or not found");
72
+ const handler = this.resolveHandler(log.commandId);
73
+ if (!handler.undo || this.isUndoable(handler) === false) {
74
+ throw new Error(`Command ${log.commandId} is not undoable`);
75
+ }
76
+ await handler.undo({
77
+ input: log.commandPayload,
78
+ ctx,
79
+ logEntry: log
80
+ });
81
+ await service.markUndone(log.id);
82
+ await this.invalidateCacheAfterUndo(log, ctx);
83
+ await this.flushCrudSideEffects(ctx.container);
84
+ }
85
+ resolveHandler(commandId) {
86
+ const handler = commandRegistry.get(commandId);
87
+ if (!handler) throw new Error(`Command handler not registered for id ${commandId}`);
88
+ return handler;
89
+ }
90
+ async prepareSnapshots(handler, options) {
91
+ if (!handler.prepare) return {};
92
+ try {
93
+ return await handler.prepare(options.input, options.ctx) || {};
94
+ } catch (err) {
95
+ throw err;
96
+ }
97
+ }
98
+ async captureAfter(handler, options, result) {
99
+ if (!handler.captureAfter) return void 0;
100
+ return handler.captureAfter(options.input, result, options.ctx);
101
+ }
102
+ async buildLog(handler, options, result, snapshots) {
103
+ if (!handler.buildLog) return null;
104
+ const args = {
105
+ input: options.input,
106
+ result,
107
+ ctx: options.ctx,
108
+ snapshots
109
+ };
110
+ return await handler.buildLog(args) || null;
111
+ }
112
+ mergeMetadata(primary, secondary) {
113
+ if (!primary && !secondary) return null;
114
+ return {
115
+ tenantId: primary?.tenantId ?? secondary?.tenantId ?? null,
116
+ organizationId: primary?.organizationId ?? secondary?.organizationId ?? null,
117
+ actorUserId: primary?.actorUserId ?? secondary?.actorUserId ?? null,
118
+ actionLabel: primary?.actionLabel ?? secondary?.actionLabel ?? null,
119
+ resourceKind: primary?.resourceKind ?? secondary?.resourceKind ?? null,
120
+ resourceId: primary?.resourceId ?? secondary?.resourceId ?? null,
121
+ undoToken: primary?.undoToken ?? secondary?.undoToken ?? null,
122
+ payload: primary?.payload ?? secondary?.payload ?? null,
123
+ snapshotBefore: primary?.snapshotBefore ?? secondary?.snapshotBefore ?? null,
124
+ snapshotAfter: primary?.snapshotAfter ?? secondary?.snapshotAfter ?? null,
125
+ changes: primary?.changes ?? secondary?.changes ?? null,
126
+ context: primary?.context ?? secondary?.context ?? null
127
+ };
128
+ }
129
+ async persistLog(commandId, options, metadata) {
130
+ if (!metadata) return null;
131
+ const resourceKind = typeof metadata.resourceKind === "string" ? metadata.resourceKind : null;
132
+ if (resourceKind && SKIPPED_ACTION_LOG_RESOURCE_KINDS.has(resourceKind)) {
133
+ return null;
134
+ }
135
+ let service = null;
136
+ try {
137
+ service = options.ctx.container.resolve("actionLogService");
138
+ } catch {
139
+ service = null;
140
+ }
141
+ if (!service) return null;
142
+ const tenantId = metadata.tenantId ?? options.ctx.auth?.tenantId ?? null;
143
+ const organizationId = metadata.organizationId ?? options.ctx.selectedOrganizationId ?? options.ctx.auth?.orgId ?? null;
144
+ const actorUserId = metadata.actorUserId ?? options.ctx.auth?.sub ?? null;
145
+ const payload = {
146
+ tenantId: tenantId ?? void 0,
147
+ organizationId: organizationId ?? void 0,
148
+ actorUserId: actorUserId ?? void 0,
149
+ commandId
150
+ };
151
+ if (metadata) {
152
+ if ("actionLabel" in metadata && metadata.actionLabel != null) payload.actionLabel = metadata.actionLabel;
153
+ if ("resourceKind" in metadata && metadata.resourceKind != null) payload.resourceKind = metadata.resourceKind;
154
+ if ("resourceId" in metadata && metadata.resourceId != null) payload.resourceId = metadata.resourceId;
155
+ if ("undoToken" in metadata && metadata.undoToken != null) payload.undoToken = metadata.undoToken;
156
+ if ("payload" in metadata && metadata.payload !== void 0) payload.commandPayload = metadata.payload;
157
+ if ("snapshotBefore" in metadata && metadata.snapshotBefore !== void 0) payload.snapshotBefore = metadata.snapshotBefore;
158
+ if ("snapshotAfter" in metadata && metadata.snapshotAfter !== void 0) payload.snapshotAfter = metadata.snapshotAfter;
159
+ if ("changes" in metadata && metadata.changes !== void 0 && metadata.changes !== null) payload.changes = metadata.changes;
160
+ if ("context" in metadata && metadata.context !== void 0 && metadata.context !== null) payload.context = metadata.context;
161
+ }
162
+ const redoEnvelope = wrapRedoPayload("commandPayload" in payload ? payload.commandPayload : void 0, options.input);
163
+ payload.commandPayload = redoEnvelope;
164
+ return await service.log(payload);
165
+ }
166
+ isUndoable(handler) {
167
+ return handler.isUndoable !== false && typeof handler.undo === "function";
168
+ }
169
+ async invalidateCacheAfterExecute(commandId, options, result, metadata) {
170
+ const resource = typeof metadata?.resourceKind === "string" ? metadata.resourceKind : null;
171
+ if (!resource) return;
172
+ try {
173
+ const ctx = options.ctx;
174
+ const resultRecord = asRecord(result);
175
+ const resultEntity = asRecord(resultRecord?.entity);
176
+ const inputRecord = asRecord(options.input);
177
+ const inputEntity = asRecord(inputRecord?.entity);
178
+ const recordId = pickFirstIdentifier(
179
+ metadata?.resourceId,
180
+ resultRecord?.entityId,
181
+ resultRecord?.id,
182
+ resultRecord?.recordId,
183
+ resultEntity?.id,
184
+ inputRecord?.id,
185
+ inputRecord?.entityId,
186
+ inputRecord?.recordId,
187
+ inputEntity?.id
188
+ );
189
+ const organizationId = pickFirstIdentifier(
190
+ metadata?.organizationId,
191
+ resultRecord?.organizationId,
192
+ resultEntity?.organizationId,
193
+ inputRecord?.organizationId,
194
+ inputEntity?.organizationId,
195
+ ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null
196
+ );
197
+ const tenantId = pickFirstIdentifier(
198
+ metadata?.tenantId,
199
+ resultRecord?.tenantId,
200
+ resultEntity?.tenantId,
201
+ inputRecord?.tenantId,
202
+ inputEntity?.tenantId,
203
+ ctx.auth?.tenantId ?? null
204
+ );
205
+ const fallbackTenant = pickFirstIdentifier(metadata?.tenantId, ctx.auth?.tenantId ?? null);
206
+ const aliasSet = /* @__PURE__ */ new Set();
207
+ for (const alias of extractAliasList(metadata?.context ?? null)) {
208
+ aliasSet.add(alias);
209
+ }
210
+ const derived = deriveResourceFromCommandId(commandId);
211
+ if (derived) aliasSet.add(derived);
212
+ const aliasExtras = Array.from(aliasSet);
213
+ await invalidateCrudCache(
214
+ ctx.container,
215
+ resource,
216
+ { id: recordId, organizationId, tenantId },
217
+ fallbackTenant,
218
+ `command:${commandId}:execute`,
219
+ aliasExtras
220
+ );
221
+ } catch (err) {
222
+ if (isCrudCacheDebugEnabled()) {
223
+ try {
224
+ console.debug("[crud][cache] execute-invalidation failed", { commandId, err });
225
+ } catch {
226
+ }
227
+ }
228
+ }
229
+ }
230
+ async invalidateCacheAfterUndo(log, ctx) {
231
+ const resource = typeof log.resourceKind === "string" ? log.resourceKind : null;
232
+ if (!resource) return;
233
+ try {
234
+ const recordId = pickFirstIdentifier(log.resourceId);
235
+ const organizationId = pickFirstIdentifier(log.organizationId, ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null);
236
+ const tenantId = pickFirstIdentifier(log.tenantId, ctx.auth?.tenantId ?? null);
237
+ const fallbackTenant = pickFirstIdentifier(log.tenantId, ctx.auth?.tenantId ?? null);
238
+ const aliasSet = /* @__PURE__ */ new Set();
239
+ for (const alias of extractAliasList(log.contextJson ?? null)) {
240
+ aliasSet.add(alias);
241
+ }
242
+ const derived = deriveResourceFromCommandId(log.commandId);
243
+ if (derived) aliasSet.add(derived);
244
+ const aliasExtras = Array.from(aliasSet);
245
+ await invalidateCrudCache(
246
+ ctx.container,
247
+ resource,
248
+ { id: recordId, organizationId, tenantId },
249
+ fallbackTenant,
250
+ `command:${log.commandId}:undo`,
251
+ aliasExtras
252
+ );
253
+ } catch (err) {
254
+ if (isCrudCacheDebugEnabled()) {
255
+ try {
256
+ console.debug("[crud][cache] undo-invalidation failed", { commandId: log.commandId, err });
257
+ } catch {
258
+ }
259
+ }
260
+ }
261
+ }
262
+ async flushCrudSideEffects(container) {
263
+ try {
264
+ const dataEngine = container.resolve("dataEngine");
265
+ await dataEngine.flushOrmEntityChanges();
266
+ } catch {
267
+ }
268
+ }
269
+ }
270
+ function wrapRedoPayload(existing, input) {
271
+ if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
272
+ const envelope = { __redoInput: input };
273
+ if (existing !== void 0) envelope.value = existing;
274
+ return envelope;
275
+ }
276
+ const current = existing;
277
+ if ("__redoInput" in current && current.__redoInput !== void 0) {
278
+ return current;
279
+ }
280
+ return { __redoInput: input, ...current };
281
+ }
282
+ export {
283
+ CommandBus
284
+ };
285
+ //# sourceMappingURL=command-bus.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/commands/command-bus.ts"],
4
+ "sourcesContent": ["import type { ActionLog } from '@open-mercato/core/modules/audit_logs/data/entities'\nimport type { ActionLogCreateInput } from '@open-mercato/core/modules/audit_logs/data/validators'\nimport { commandRegistry } from './registry'\nimport type {\n CommandExecutionOptions,\n CommandExecuteResult,\n CommandHandler,\n CommandLogBuilderArgs,\n CommandLogMetadata,\n CommandRuntimeContext,\n} from './types'\nimport { defaultUndoToken } from './types'\nimport type { ActionLogService } from '@open-mercato/core/modules/audit_logs/services/actionLogService'\nimport type { AwilixContainer } from 'awilix'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport {\n canonicalizeResourceTag,\n deriveResourceFromCommandId,\n invalidateCrudCache,\n pickFirstIdentifier,\n isCrudCacheDebugEnabled,\n} from '@open-mercato/shared/lib/crud/cache'\n\nconst SKIPPED_ACTION_LOG_RESOURCE_KINDS = new Set<string>([\n 'audit_logs.access',\n 'audit_logs.action',\n 'dashboards.layout',\n 'dashboards.user_widgets',\n 'dashboards.role_widgets',\n])\n\nfunction asRecord(input: unknown): Record<string, unknown> | null {\n if (!input || typeof input !== 'object' || Array.isArray(input)) return null\n return input as Record<string, unknown>\n}\n\nfunction extractAliasList(source: unknown): string[] {\n if (!source || typeof source !== 'object' || Array.isArray(source)) return []\n const record = source as Record<string, unknown>\n const raw = record.cacheAliases\n if (!Array.isArray(raw)) return []\n const aliases = new Set<string>()\n for (const value of raw) {\n if (typeof value !== 'string') continue\n const normalized = canonicalizeResourceTag(value)\n if (normalized) aliases.add(normalized)\n }\n return Array.from(aliases)\n}\n\nexport class CommandBus {\n async execute<TInput = unknown, TResult = unknown>(\n commandId: string,\n options: CommandExecutionOptions<TInput>\n ): Promise<CommandExecuteResult<TResult>> {\n const handler = this.resolveHandler<TInput, TResult>(commandId)\n const snapshots = await this.prepareSnapshots(handler, options)\n const result = await handler.execute(options.input, options.ctx)\n const afterSnapshot = await this.captureAfter(handler, options, result)\n const snapshotsWithAfter = { ...snapshots, after: afterSnapshot }\n const logMeta = await this.buildLog(handler, options, result, snapshotsWithAfter)\n let mergedMeta = this.mergeMetadata(options.metadata, logMeta)\n const undoable = this.isUndoable(handler)\n if (undoable) {\n mergedMeta = mergedMeta ?? {}\n if (!mergedMeta.undoToken) mergedMeta.undoToken = defaultUndoToken()\n if (mergedMeta.actorUserId === undefined) mergedMeta.actorUserId = options.ctx.auth?.sub ?? null\n }\n if (afterSnapshot !== undefined && afterSnapshot !== null) {\n if (!mergedMeta) {\n mergedMeta = { snapshotAfter: afterSnapshot }\n } else if (!mergedMeta.snapshotAfter) {\n mergedMeta.snapshotAfter = afterSnapshot\n }\n }\n if (snapshots.before) {\n if (!mergedMeta) {\n mergedMeta = { snapshotBefore: snapshots.before }\n } else if (!mergedMeta.snapshotBefore) {\n mergedMeta.snapshotBefore = snapshots.before\n }\n }\n const logEntry = await this.persistLog(commandId, options, mergedMeta)\n await this.invalidateCacheAfterExecute(commandId, options, result, mergedMeta)\n await this.flushCrudSideEffects(options.ctx.container)\n return { result, logEntry }\n }\n\n async undo(undoToken: string, ctx: CommandRuntimeContext): Promise<void> {\n const service = (ctx.container.resolve('actionLogService') as ActionLogService)\n const log = await service.findByUndoToken(undoToken)\n if (!log) throw new Error('Undo token expired or not found')\n const handler = this.resolveHandler(log.commandId)\n if (!handler.undo || this.isUndoable(handler) === false) {\n throw new Error(`Command ${log.commandId} is not undoable`)\n }\n await handler.undo({\n input: log.commandPayload as Parameters<NonNullable<typeof handler.undo>>[0]['input'],\n ctx,\n logEntry: log,\n })\n await service.markUndone(log.id)\n await this.invalidateCacheAfterUndo(log, ctx)\n await this.flushCrudSideEffects(ctx.container)\n }\n\n private resolveHandler<TInput, TResult>(commandId: string): CommandHandler<TInput, TResult> {\n const handler = commandRegistry.get<TInput, TResult>(commandId)\n if (!handler) throw new Error(`Command handler not registered for id ${commandId}`)\n return handler\n }\n\n private async prepareSnapshots<TInput, TResult>(\n handler: CommandHandler<TInput, TResult>,\n options: CommandExecutionOptions<TInput>\n ): Promise<{ before?: unknown }> {\n if (!handler.prepare) return {}\n try {\n return (await handler.prepare(options.input, options.ctx)) || {}\n } catch (err) {\n throw err\n }\n }\n\n private async captureAfter<TInput, TResult>(\n handler: CommandHandler<TInput, TResult>,\n options: CommandExecutionOptions<TInput>,\n result: TResult\n ): Promise<unknown> {\n if (!handler.captureAfter) return undefined\n return handler.captureAfter(options.input, result, options.ctx)\n }\n\n private async buildLog<TInput, TResult>(\n handler: CommandHandler<TInput, TResult>,\n options: CommandExecutionOptions<TInput>,\n result: TResult,\n snapshots: { before?: unknown; after?: unknown }\n ): Promise<CommandLogMetadata | null> {\n if (!handler.buildLog) return null\n const args: CommandLogBuilderArgs<TInput, TResult> = {\n input: options.input,\n result,\n ctx: options.ctx,\n snapshots,\n }\n return (await handler.buildLog(args)) || null\n }\n\n private mergeMetadata(primary?: CommandLogMetadata | null, secondary?: CommandLogMetadata | null): CommandLogMetadata | null {\n if (!primary && !secondary) return null\n return {\n tenantId: primary?.tenantId ?? secondary?.tenantId ?? null,\n organizationId: primary?.organizationId ?? secondary?.organizationId ?? null,\n actorUserId: primary?.actorUserId ?? secondary?.actorUserId ?? null,\n actionLabel: primary?.actionLabel ?? secondary?.actionLabel ?? null,\n resourceKind: primary?.resourceKind ?? secondary?.resourceKind ?? null,\n resourceId: primary?.resourceId ?? secondary?.resourceId ?? null,\n undoToken: primary?.undoToken ?? secondary?.undoToken ?? null,\n payload: primary?.payload ?? secondary?.payload ?? null,\n snapshotBefore: primary?.snapshotBefore ?? secondary?.snapshotBefore ?? null,\n snapshotAfter: primary?.snapshotAfter ?? secondary?.snapshotAfter ?? null,\n changes: primary?.changes ?? secondary?.changes ?? null,\n context: primary?.context ?? secondary?.context ?? null,\n }\n }\n\n private async persistLog<TInput>(\n commandId: string,\n options: CommandExecutionOptions<TInput>,\n metadata: CommandLogMetadata | null\n ): Promise<ActionLog | null> {\n if (!metadata) return null\n const resourceKind =\n typeof metadata.resourceKind === 'string' ? metadata.resourceKind : null\n if (resourceKind && SKIPPED_ACTION_LOG_RESOURCE_KINDS.has(resourceKind)) {\n return null\n }\n let service: ActionLogService | null = null\n try {\n service = (options.ctx.container.resolve('actionLogService') as ActionLogService)\n } catch {\n service = null\n }\n if (!service) return null\n\n const tenantId = metadata.tenantId ?? options.ctx.auth?.tenantId ?? null\n const organizationId =\n metadata.organizationId ?? options.ctx.selectedOrganizationId ?? options.ctx.auth?.orgId ?? null\n const actorUserId = metadata.actorUserId ?? options.ctx.auth?.sub ?? null\n const payload: Record<string, unknown> = {\n tenantId: tenantId ?? undefined,\n organizationId: organizationId ?? undefined,\n actorUserId: actorUserId ?? undefined,\n commandId,\n }\n\n if (metadata) {\n if ('actionLabel' in metadata && metadata.actionLabel != null) payload.actionLabel = metadata.actionLabel\n if ('resourceKind' in metadata && metadata.resourceKind != null) payload.resourceKind = metadata.resourceKind\n if ('resourceId' in metadata && metadata.resourceId != null) payload.resourceId = metadata.resourceId\n if ('undoToken' in metadata && metadata.undoToken != null) payload.undoToken = metadata.undoToken\n if ('payload' in metadata && metadata.payload !== undefined) payload.commandPayload = metadata.payload\n if ('snapshotBefore' in metadata && metadata.snapshotBefore !== undefined) payload.snapshotBefore = metadata.snapshotBefore\n if ('snapshotAfter' in metadata && metadata.snapshotAfter !== undefined) payload.snapshotAfter = metadata.snapshotAfter\n if ('changes' in metadata && metadata.changes !== undefined && metadata.changes !== null) payload.changes = metadata.changes\n if ('context' in metadata && metadata.context !== undefined && metadata.context !== null) payload.context = metadata.context\n }\n\n const redoEnvelope = wrapRedoPayload('commandPayload' in payload ? (payload.commandPayload as unknown) : undefined, options.input)\n payload.commandPayload = redoEnvelope\n\n return await service.log(payload as ActionLogCreateInput)\n }\n\n private isUndoable(handler: CommandHandler<unknown, unknown>): boolean {\n return handler.isUndoable !== false && typeof handler.undo === 'function'\n }\n\n private async invalidateCacheAfterExecute<TResult>(\n commandId: string,\n options: CommandExecutionOptions<unknown>,\n result: TResult,\n metadata: CommandLogMetadata | null\n ): Promise<void> {\n const resource = typeof metadata?.resourceKind === 'string' ? metadata.resourceKind : null\n if (!resource) return\n try {\n const ctx = options.ctx\n const resultRecord = asRecord(result)\n const resultEntity = asRecord(resultRecord?.entity)\n const inputRecord = asRecord(options.input)\n const inputEntity = asRecord(inputRecord?.entity)\n\n const recordId = pickFirstIdentifier(\n metadata?.resourceId,\n resultRecord?.entityId,\n resultRecord?.id,\n resultRecord?.recordId,\n resultEntity?.id,\n inputRecord?.id,\n inputRecord?.entityId,\n inputRecord?.recordId,\n inputEntity?.id\n )\n\n const organizationId = pickFirstIdentifier(\n metadata?.organizationId,\n resultRecord?.organizationId,\n resultEntity?.organizationId,\n inputRecord?.organizationId,\n inputEntity?.organizationId,\n ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null\n )\n\n const tenantId = pickFirstIdentifier(\n metadata?.tenantId,\n resultRecord?.tenantId,\n resultEntity?.tenantId,\n inputRecord?.tenantId,\n inputEntity?.tenantId,\n ctx.auth?.tenantId ?? null\n )\n\n const fallbackTenant = pickFirstIdentifier(metadata?.tenantId, ctx.auth?.tenantId ?? null)\n\n const aliasSet = new Set<string>()\n for (const alias of extractAliasList(metadata?.context ?? null)) {\n aliasSet.add(alias)\n }\n const derived = deriveResourceFromCommandId(commandId)\n if (derived) aliasSet.add(derived)\n const aliasExtras = Array.from(aliasSet)\n await invalidateCrudCache(\n ctx.container,\n resource,\n { id: recordId, organizationId, tenantId },\n fallbackTenant,\n `command:${commandId}:execute`,\n aliasExtras\n )\n } catch (err) {\n if (isCrudCacheDebugEnabled()) {\n try {\n console.debug('[crud][cache] execute-invalidation failed', { commandId, err })\n } catch {}\n }\n }\n }\n\n private async invalidateCacheAfterUndo(log: ActionLog, ctx: CommandRuntimeContext): Promise<void> {\n const resource = typeof log.resourceKind === 'string' ? log.resourceKind : null\n if (!resource) return\n try {\n const recordId = pickFirstIdentifier(log.resourceId)\n const organizationId = pickFirstIdentifier(log.organizationId, ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null)\n const tenantId = pickFirstIdentifier(log.tenantId, ctx.auth?.tenantId ?? null)\n const fallbackTenant = pickFirstIdentifier(log.tenantId, ctx.auth?.tenantId ?? null)\n const aliasSet = new Set<string>()\n for (const alias of extractAliasList(log.contextJson ?? null)) {\n aliasSet.add(alias)\n }\n const derived = deriveResourceFromCommandId(log.commandId)\n if (derived) aliasSet.add(derived)\n const aliasExtras = Array.from(aliasSet)\n await invalidateCrudCache(\n ctx.container,\n resource,\n { id: recordId, organizationId, tenantId },\n fallbackTenant,\n `command:${log.commandId}:undo`,\n aliasExtras\n )\n } catch (err) {\n if (isCrudCacheDebugEnabled()) {\n try {\n console.debug('[crud][cache] undo-invalidation failed', { commandId: log.commandId, err })\n } catch {}\n }\n }\n }\n\n private async flushCrudSideEffects(container: AwilixContainer): Promise<void> {\n try {\n const dataEngine = (container.resolve('dataEngine') as DataEngine)\n await dataEngine.flushOrmEntityChanges()\n } catch {\n // best-effort: failures should not block command execution\n }\n }\n}\n\ntype RedoEnvelope = {\n __redoInput: unknown\n [key: string]: unknown\n}\n\nfunction wrapRedoPayload(existing: unknown, input: unknown): RedoEnvelope {\n if (!existing || typeof existing !== 'object' || Array.isArray(existing)) {\n const envelope: RedoEnvelope = { __redoInput: input }\n if (existing !== undefined) envelope.value = existing\n return envelope\n }\n const current = existing as Record<string, unknown>\n if ('__redoInput' in current && current.__redoInput !== undefined) {\n return current as RedoEnvelope\n }\n return { __redoInput: input, ...current }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,66 @@
1
+ import { loadCustomFieldValues } from "@open-mercato/shared/lib/crud/custom-fields";
2
+ async function loadCustomFieldSnapshot(em, { entityId, recordId, tenantId, organizationId, tenantFallbacks }) {
3
+ const tenant = tenantId ?? null;
4
+ const organization = organizationId ?? void 0;
5
+ const records = await loadCustomFieldValues({
6
+ em,
7
+ entityId,
8
+ recordIds: [recordId],
9
+ tenantIdByRecord: { [recordId]: tenant },
10
+ organizationIdByRecord: organization === void 0 ? void 0 : { [recordId]: organization ?? null },
11
+ tenantFallbacks: tenantFallbacks ?? [tenant]
12
+ });
13
+ const raw = records[recordId] ?? {};
14
+ const custom = {};
15
+ for (const [key, value] of Object.entries(raw)) {
16
+ if (key.startsWith("cf_")) custom[key.slice(3)] = value;
17
+ }
18
+ return custom;
19
+ }
20
+ function buildCustomFieldResetMap(before, after) {
21
+ const values = {};
22
+ const keys = /* @__PURE__ */ new Set();
23
+ if (before) for (const key of Object.keys(before)) keys.add(key);
24
+ if (after) for (const key of Object.keys(after)) keys.add(key);
25
+ for (const key of keys) {
26
+ const hasBefore = Boolean(before && Object.prototype.hasOwnProperty.call(before, key));
27
+ if (hasBefore) {
28
+ const beforeValue = before?.[key];
29
+ if (beforeValue === null && Array.isArray(after?.[key])) {
30
+ values[key] = [];
31
+ } else {
32
+ values[key] = beforeValue;
33
+ }
34
+ } else {
35
+ values[key] = Array.isArray(after?.[key]) ? [] : null;
36
+ }
37
+ }
38
+ return values;
39
+ }
40
+ function diffCustomFieldChanges(before, after) {
41
+ const out = {};
42
+ const keys = /* @__PURE__ */ new Set();
43
+ if (before) for (const key of Object.keys(before)) keys.add(key);
44
+ if (after) for (const key of Object.keys(after)) keys.add(key);
45
+ for (const key of keys) {
46
+ const prev = before ? before[key] : void 0;
47
+ const next = after ? after[key] : void 0;
48
+ if (!customFieldValuesEqual(prev, next)) {
49
+ out[key] = { from: prev ?? null, to: next ?? null };
50
+ }
51
+ }
52
+ return out;
53
+ }
54
+ function customFieldValuesEqual(a, b) {
55
+ if (Array.isArray(a) && Array.isArray(b)) {
56
+ if (a.length !== b.length) return false;
57
+ return a.every((value, idx) => customFieldValuesEqual(value, b[idx]));
58
+ }
59
+ return a === b;
60
+ }
61
+ export {
62
+ buildCustomFieldResetMap,
63
+ diffCustomFieldChanges,
64
+ loadCustomFieldSnapshot
65
+ };
66
+ //# sourceMappingURL=customFieldSnapshots.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/commands/customFieldSnapshots.ts"],
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\n\nexport type CustomFieldSnapshot = Record<string, unknown>\n\ntype LoadSnapshotOptions = {\n entityId: string\n recordId: string\n tenantId?: string | null\n organizationId?: string | null\n tenantFallbacks?: Array<string | null | undefined>\n}\n\nexport async function loadCustomFieldSnapshot(\n em: EntityManager,\n { entityId, recordId, tenantId, organizationId, tenantFallbacks }: LoadSnapshotOptions\n): Promise<CustomFieldSnapshot> {\n const tenant = tenantId ?? null\n const organization = organizationId ?? undefined\n const records = await loadCustomFieldValues({\n em,\n entityId: entityId as any,\n recordIds: [recordId],\n tenantIdByRecord: { [recordId]: tenant },\n organizationIdByRecord: organization === undefined ? undefined : { [recordId]: organization ?? null },\n tenantFallbacks: tenantFallbacks ?? [tenant],\n })\n const raw = records[recordId] ?? {}\n const custom: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(raw)) {\n if (key.startsWith('cf_')) custom[key.slice(3)] = value\n }\n return custom\n}\n\nexport function buildCustomFieldResetMap(\n before: CustomFieldSnapshot | undefined,\n after: CustomFieldSnapshot | undefined\n): Record<string, unknown> {\n const values: Record<string, unknown> = {}\n const keys = new Set<string>()\n if (before) for (const key of Object.keys(before)) keys.add(key)\n if (after) for (const key of Object.keys(after)) keys.add(key)\n for (const key of keys) {\n const hasBefore = Boolean(before && Object.prototype.hasOwnProperty.call(before, key))\n if (hasBefore) {\n const beforeValue = before?.[key]\n if (beforeValue === null && Array.isArray(after?.[key])) {\n values[key] = []\n } else {\n values[key] = beforeValue\n }\n } else {\n values[key] = Array.isArray(after?.[key]) ? [] : null\n }\n }\n return values\n}\n\nexport type CustomFieldChangeSet = Record<string, { from: unknown; to: unknown }>\n\nexport function diffCustomFieldChanges(\n before: CustomFieldSnapshot | undefined,\n after: CustomFieldSnapshot | undefined\n): CustomFieldChangeSet {\n const out: CustomFieldChangeSet = {}\n const keys = new Set<string>()\n if (before) for (const key of Object.keys(before)) keys.add(key)\n if (after) for (const key of Object.keys(after)) keys.add(key)\n for (const key of keys) {\n const prev = before ? before[key] : undefined\n const next = after ? after[key] : undefined\n if (!customFieldValuesEqual(prev, next)) {\n out[key] = { from: prev ?? null, to: next ?? null }\n }\n }\n return out\n}\n\nfunction customFieldValuesEqual(a: unknown, b: unknown): boolean {\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false\n return a.every((value, idx) => customFieldValuesEqual(value, b[idx]))\n }\n return a === b\n}\n"],
5
+ "mappings": "AACA,SAAS,6BAA6B;AAYtC,eAAsB,wBACpB,IACA,EAAE,UAAU,UAAU,UAAU,gBAAgB,gBAAgB,GAClC;AAC9B,QAAM,SAAS,YAAY;AAC3B,QAAM,eAAe,kBAAkB;AACvC,QAAM,UAAU,MAAM,sBAAsB;AAAA,IAC1C;AAAA,IACA;AAAA,IACA,WAAW,CAAC,QAAQ;AAAA,IACpB,kBAAkB,EAAE,CAAC,QAAQ,GAAG,OAAO;AAAA,IACvC,wBAAwB,iBAAiB,SAAY,SAAY,EAAE,CAAC,QAAQ,GAAG,gBAAgB,KAAK;AAAA,IACpG,iBAAiB,mBAAmB,CAAC,MAAM;AAAA,EAC7C,CAAC;AACD,QAAM,MAAM,QAAQ,QAAQ,KAAK,CAAC;AAClC,QAAM,SAAkC,CAAC;AACzC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC9C,QAAI,IAAI,WAAW,KAAK,EAAG,QAAO,IAAI,MAAM,CAAC,CAAC,IAAI;AAAA,EACpD;AACA,SAAO;AACT;AAEO,SAAS,yBACd,QACA,OACyB;AACzB,QAAM,SAAkC,CAAC;AACzC,QAAM,OAAO,oBAAI,IAAY;AAC7B,MAAI,OAAQ,YAAW,OAAO,OAAO,KAAK,MAAM,EAAG,MAAK,IAAI,GAAG;AAC/D,MAAI,MAAO,YAAW,OAAO,OAAO,KAAK,KAAK,EAAG,MAAK,IAAI,GAAG;AAC7D,aAAW,OAAO,MAAM;AACtB,UAAM,YAAY,QAAQ,UAAU,OAAO,UAAU,eAAe,KAAK,QAAQ,GAAG,CAAC;AACrF,QAAI,WAAW;AACb,YAAM,cAAc,SAAS,GAAG;AAChC,UAAI,gBAAgB,QAAQ,MAAM,QAAQ,QAAQ,GAAG,CAAC,GAAG;AACvD,eAAO,GAAG,IAAI,CAAC;AAAA,MACjB,OAAO;AACL,eAAO,GAAG,IAAI;AAAA,MAChB;AAAA,IACF,OAAO;AACL,aAAO,GAAG,IAAI,MAAM,QAAQ,QAAQ,GAAG,CAAC,IAAI,CAAC,IAAI;AAAA,IACnD;AAAA,EACF;AACA,SAAO;AACT;AAIO,SAAS,uBACd,QACA,OACsB;AACtB,QAAM,MAA4B,CAAC;AACnC,QAAM,OAAO,oBAAI,IAAY;AAC7B,MAAI,OAAQ,YAAW,OAAO,OAAO,KAAK,MAAM,EAAG,MAAK,IAAI,GAAG;AAC/D,MAAI,MAAO,YAAW,OAAO,OAAO,KAAK,KAAK,EAAG,MAAK,IAAI,GAAG;AAC7D,aAAW,OAAO,MAAM;AACtB,UAAM,OAAO,SAAS,OAAO,GAAG,IAAI;AACpC,UAAM,OAAO,QAAQ,MAAM,GAAG,IAAI;AAClC,QAAI,CAAC,uBAAuB,MAAM,IAAI,GAAG;AACvC,UAAI,GAAG,IAAI,EAAE,MAAM,QAAQ,MAAM,IAAI,QAAQ,KAAK;AAAA,IACpD;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,uBAAuB,GAAY,GAAqB;AAC/D,MAAI,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,CAAC,GAAG;AACxC,QAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,WAAO,EAAE,MAAM,CAAC,OAAO,QAAQ,uBAAuB,OAAO,EAAE,GAAG,CAAC,CAAC;AAAA,EACtE;AACA,SAAO,MAAM;AACf;",
6
+ "names": []
7
+ }
@@ -0,0 +1,98 @@
1
+ import { splitCustomFieldPayload } from "@open-mercato/shared/lib/crud/custom-fields";
2
+ import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
3
+ import { normalizeCustomFieldValues } from "../custom-fields/normalize.js";
4
+ import { normalizeCustomFieldValues as normalizeCustomFieldValues2 } from "../custom-fields/normalize.js";
5
+ function parseWithCustomFields(schema, raw) {
6
+ const { base, custom } = splitCustomFieldPayload(raw);
7
+ const parsed = schema.parse(base);
8
+ return { parsed, custom };
9
+ }
10
+ async function setCustomFieldsIfAny(opts) {
11
+ const { values } = opts;
12
+ if (!values || !Object.keys(values).length) return;
13
+ const { dataEngine, entityId, recordId, tenantId, organizationId, notify = false } = opts;
14
+ const normalized = normalizeCustomFieldValues(values);
15
+ await dataEngine.setCustomFields({
16
+ entityId,
17
+ recordId,
18
+ tenantId,
19
+ organizationId,
20
+ values: normalized,
21
+ notify
22
+ });
23
+ }
24
+ async function emitCrudSideEffects(opts) {
25
+ const { dataEngine, action, entity, identifiers, events, indexer } = opts;
26
+ dataEngine.markOrmEntityChange({
27
+ action,
28
+ entity,
29
+ identifiers,
30
+ events,
31
+ indexer
32
+ });
33
+ }
34
+ async function emitCrudUndoSideEffects(opts) {
35
+ const { dataEngine, action, entity, identifiers, events, indexer } = opts;
36
+ if (!entity) return;
37
+ dataEngine.markOrmEntityChange({
38
+ action,
39
+ entity,
40
+ identifiers,
41
+ events,
42
+ indexer
43
+ });
44
+ }
45
+ async function flushCrudSideEffects(dataEngine) {
46
+ await dataEngine.flushOrmEntityChanges();
47
+ }
48
+ function buildChanges(before, after, keys) {
49
+ if (!before) return {};
50
+ const diff = {};
51
+ for (const key of keys) {
52
+ const prev = before[key];
53
+ const next = after[key];
54
+ if (prev !== next) diff[key] = { from: prev, to: next };
55
+ }
56
+ return diff;
57
+ }
58
+ function requireTenantScope(authTenantId, requested) {
59
+ if (authTenantId && requested && requested !== authTenantId) {
60
+ throw new CrudHttpError(403, { error: "Forbidden" });
61
+ }
62
+ const tenantId = requested || authTenantId;
63
+ if (!tenantId) throw new CrudHttpError(400, { error: "Tenant scope required" });
64
+ return tenantId;
65
+ }
66
+ function requireId(value, message = "ID is required") {
67
+ if (typeof value === "string" && value.trim()) return value;
68
+ if (typeof value === "number" || typeof value === "bigint") return String(value);
69
+ if (value && typeof value === "object") {
70
+ const source = value;
71
+ const candidates = [
72
+ source.id,
73
+ source.recordId,
74
+ isRecord(source.body) ? source.body.id : void 0,
75
+ isRecord(source.query) ? source.query.id : void 0
76
+ ];
77
+ for (const candidate of candidates) {
78
+ if (typeof candidate === "string" && candidate.trim()) return candidate;
79
+ if (typeof candidate === "number" || typeof candidate === "bigint") return String(candidate);
80
+ }
81
+ }
82
+ throw new CrudHttpError(400, { error: message });
83
+ }
84
+ function isRecord(input) {
85
+ return !!input && typeof input === "object";
86
+ }
87
+ export {
88
+ buildChanges,
89
+ emitCrudSideEffects,
90
+ emitCrudUndoSideEffects,
91
+ flushCrudSideEffects,
92
+ normalizeCustomFieldValues2 as normalizeCustomFieldValues,
93
+ parseWithCustomFields,
94
+ requireId,
95
+ requireTenantScope,
96
+ setCustomFieldsIfAny
97
+ };
98
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/commands/helpers.ts"],
4
+ "sourcesContent": ["import { splitCustomFieldPayload } from '@open-mercato/shared/lib/crud/custom-fields'\nimport type { z } from 'zod'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { normalizeCustomFieldValues } from '../custom-fields/normalize'\nexport { normalizeCustomFieldValues } from '../custom-fields/normalize'\nimport type { CrudEventsConfig, CrudIndexerConfig, CrudEmitContext } from '@open-mercato/shared/lib/crud/types'\nimport type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport type { CommandLogMetadata } from '@open-mercato/shared/lib/commands'\n\nexport type ParsedPayload<TSchema extends z.ZodTypeAny> = {\n parsed: z.infer<TSchema>\n custom: Record<string, unknown>\n}\n\nexport function parseWithCustomFields<TSchema extends z.ZodTypeAny>(\n schema: TSchema,\n raw: unknown\n): ParsedPayload<TSchema> {\n const { base, custom } = splitCustomFieldPayload(raw)\n const parsed = schema.parse(base)\n return { parsed, custom }\n}\n\nexport async function setCustomFieldsIfAny(opts: {\n dataEngine: DataEngine\n entityId: string\n recordId: string\n tenantId: string | null\n organizationId: string | null\n values: Record<string, unknown>\n notify?: boolean\n}) {\n const { values } = opts\n if (!values || !Object.keys(values).length) return\n const { dataEngine, entityId, recordId, tenantId, organizationId, notify = false } = opts\n const normalized = normalizeCustomFieldValues(values)\n await dataEngine.setCustomFields({\n entityId,\n recordId,\n tenantId,\n organizationId,\n values: normalized,\n notify,\n })\n}\n\nexport async function emitCrudSideEffects<TEntity>(opts: {\n dataEngine: DataEngine\n action: 'created' | 'updated' | 'deleted'\n entity: TEntity\n identifiers: CrudEmitContext<TEntity>['identifiers']\n events?: CrudEventsConfig<TEntity>\n indexer?: CrudIndexerConfig<TEntity>\n}) {\n const { dataEngine, action, entity, identifiers, events, indexer } = opts\n dataEngine.markOrmEntityChange({\n action,\n entity,\n identifiers,\n events,\n indexer,\n })\n}\n\nexport async function emitCrudUndoSideEffects<TEntity>(opts: {\n dataEngine: DataEngine\n action: 'created' | 'updated' | 'deleted'\n entity: TEntity | null | undefined\n identifiers: CrudEmitContext<TEntity>['identifiers']\n events?: CrudEventsConfig<TEntity>\n indexer?: CrudIndexerConfig<TEntity>\n}) {\n const { dataEngine, action, entity, identifiers, events, indexer } = opts\n if (!entity) return\n dataEngine.markOrmEntityChange({\n action,\n entity,\n identifiers,\n events,\n indexer,\n })\n}\n\nexport async function flushCrudSideEffects(dataEngine: DataEngine): Promise<void> {\n await dataEngine.flushOrmEntityChanges()\n}\n\nexport function buildChanges(\n before: Record<string, unknown> | null | undefined,\n after: Record<string, unknown>,\n keys: readonly string[]\n): Record<string, { from: unknown; to: unknown }> {\n if (!before) return {}\n const diff: Record<string, { from: unknown; to: unknown }> = {}\n for (const key of keys) {\n const prev = before[key]\n const next = after[key]\n if (prev !== next) diff[key] = { from: prev, to: next }\n }\n return diff\n}\n\nexport function requireTenantScope(authTenantId: string | null, requested?: string | null): string {\n if (authTenantId && requested && requested !== authTenantId) {\n throw new CrudHttpError(403, { error: 'Forbidden' })\n }\n const tenantId = requested || authTenantId\n if (!tenantId) throw new CrudHttpError(400, { error: 'Tenant scope required' })\n return tenantId\n}\n\nexport function requireId(value: unknown, message = 'ID is required'): string {\n if (typeof value === 'string' && value.trim()) return value\n if (typeof value === 'number' || typeof value === 'bigint') return String(value)\n if (value && typeof value === 'object') {\n const source = value as Record<string, unknown>\n const candidates: unknown[] = [\n source.id,\n source.recordId,\n isRecord(source.body) ? source.body.id : undefined,\n isRecord(source.query) ? source.query.id : undefined,\n ]\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.trim()) return candidate\n if (typeof candidate === 'number' || typeof candidate === 'bigint') return String(candidate)\n }\n }\n throw new CrudHttpError(400, { error: message })\n}\n\nfunction isRecord(input: unknown): input is { [key: string]: unknown } {\n return !!input && typeof input === 'object'\n}\n\nexport type LogBuilderArgs<TInput, TResult> = {\n input: TInput\n result: TResult\n ctx: CommandRuntimeContext\n snapshots: { before?: unknown; after?: unknown }\n}\n\nexport type LogBuilder<TInput, TResult> = (args: LogBuilderArgs<TInput, TResult>) => CommandLogMetadata | null | Promise<CommandLogMetadata | null>\n"],
5
+ "mappings": "AAAA,SAAS,+BAA+B;AAExC,SAAS,qBAAqB;AAE9B,SAAS,kCAAkC;AAC3C,SAAS,8BAAAA,mCAAkC;AAUpC,SAAS,sBACd,QACA,KACwB;AACxB,QAAM,EAAE,MAAM,OAAO,IAAI,wBAAwB,GAAG;AACpD,QAAM,SAAS,OAAO,MAAM,IAAI;AAChC,SAAO,EAAE,QAAQ,OAAO;AAC1B;AAEA,eAAsB,qBAAqB,MAQxC;AACD,QAAM,EAAE,OAAO,IAAI;AACnB,MAAI,CAAC,UAAU,CAAC,OAAO,KAAK,MAAM,EAAE,OAAQ;AAC5C,QAAM,EAAE,YAAY,UAAU,UAAU,UAAU,gBAAgB,SAAS,MAAM,IAAI;AACrF,QAAM,aAAa,2BAA2B,MAAM;AACpD,QAAM,WAAW,gBAAgB;AAAA,IAC/B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,oBAA6B,MAOhD;AACD,QAAM,EAAE,YAAY,QAAQ,QAAQ,aAAa,QAAQ,QAAQ,IAAI;AACrE,aAAW,oBAAoB;AAAA,IAC7B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,wBAAiC,MAOpD;AACD,QAAM,EAAE,YAAY,QAAQ,QAAQ,aAAa,QAAQ,QAAQ,IAAI;AACrE,MAAI,CAAC,OAAQ;AACb,aAAW,oBAAoB;AAAA,IAC7B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,qBAAqB,YAAuC;AAChF,QAAM,WAAW,sBAAsB;AACzC;AAEO,SAAS,aACd,QACA,OACA,MACgD;AAChD,MAAI,CAAC,OAAQ,QAAO,CAAC;AACrB,QAAM,OAAuD,CAAC;AAC9D,aAAW,OAAO,MAAM;AACtB,UAAM,OAAO,OAAO,GAAG;AACvB,UAAM,OAAO,MAAM,GAAG;AACtB,QAAI,SAAS,KAAM,MAAK,GAAG,IAAI,EAAE,MAAM,MAAM,IAAI,KAAK;AAAA,EACxD;AACA,SAAO;AACT;AAEO,SAAS,mBAAmB,cAA6B,WAAmC;AACjG,MAAI,gBAAgB,aAAa,cAAc,cAAc;AAC3D,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,YAAY,CAAC;AAAA,EACrD;AACA,QAAM,WAAW,aAAa;AAC9B,MAAI,CAAC,SAAU,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,wBAAwB,CAAC;AAC9E,SAAO;AACT;AAEO,SAAS,UAAU,OAAgB,UAAU,kBAA0B;AAC5E,MAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAG,QAAO;AACtD,MAAI,OAAO,UAAU,YAAY,OAAO,UAAU,SAAU,QAAO,OAAO,KAAK;AAC/E,MAAI,SAAS,OAAO,UAAU,UAAU;AACtC,UAAM,SAAS;AACf,UAAM,aAAwB;AAAA,MAC5B,OAAO;AAAA,MACP,OAAO;AAAA,MACP,SAAS,OAAO,IAAI,IAAI,OAAO,KAAK,KAAK;AAAA,MACzC,SAAS,OAAO,KAAK,IAAI,OAAO,MAAM,KAAK;AAAA,IAC7C;AACA,eAAW,aAAa,YAAY;AAClC,UAAI,OAAO,cAAc,YAAY,UAAU,KAAK,EAAG,QAAO;AAC9D,UAAI,OAAO,cAAc,YAAY,OAAO,cAAc,SAAU,QAAO,OAAO,SAAS;AAAA,IAC7F;AAAA,EACF;AACA,QAAM,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,CAAC;AACjD;AAEA,SAAS,SAAS,OAAqD;AACrE,SAAO,CAAC,CAAC,SAAS,OAAO,UAAU;AACrC;",
6
+ "names": ["normalizeCustomFieldValues"]
7
+ }
@@ -0,0 +1,8 @@
1
+ export * from "./types.js";
2
+ export * from "./registry.js";
3
+ import { CommandBus } from "./command-bus.js";
4
+ export * from "./customFieldSnapshots.js";
5
+ export {
6
+ CommandBus
7
+ };
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/commands/index.ts"],
4
+ "sourcesContent": ["export * from './types'\nexport * from './registry'\nexport { CommandBus } from './command-bus'\nexport * from './customFieldSnapshots'\n"],
5
+ "mappings": "AAAA,cAAc;AACd,cAAc;AACd,SAAS,kBAAkB;AAC3B,cAAc;",
6
+ "names": []
7
+ }
@@ -0,0 +1,32 @@
1
+ const HEADER_PREFIX = "omop:";
2
+ function serializeOperationMetadata(payload) {
3
+ const encoded = encodeURIComponent(JSON.stringify(payload));
4
+ return `${HEADER_PREFIX}${encoded}`;
5
+ }
6
+ function deserializeOperationMetadata(value) {
7
+ if (!value || typeof value !== "string") return null;
8
+ const trimmed = value.startsWith(HEADER_PREFIX) ? value.slice(HEADER_PREFIX.length) : value;
9
+ try {
10
+ const parsed = JSON.parse(decodeURIComponent(trimmed));
11
+ if (!parsed || typeof parsed !== "object") return null;
12
+ if (typeof parsed.id !== "string" || typeof parsed.commandId !== "string") return null;
13
+ if (typeof parsed.undoToken !== "string" || !parsed.undoToken) return null;
14
+ if (typeof parsed.executedAt !== "string") return null;
15
+ return {
16
+ id: parsed.id,
17
+ undoToken: parsed.undoToken,
18
+ commandId: parsed.commandId,
19
+ actionLabel: parsed.actionLabel ?? null,
20
+ resourceKind: parsed.resourceKind ?? null,
21
+ resourceId: parsed.resourceId ?? null,
22
+ executedAt: parsed.executedAt
23
+ };
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+ export {
29
+ deserializeOperationMetadata,
30
+ serializeOperationMetadata
31
+ };
32
+ //# sourceMappingURL=operationMetadata.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/lib/commands/operationMetadata.ts"],
4
+ "sourcesContent": ["export type OperationMetadataPayload = {\n id: string\n undoToken: string\n commandId: string\n actionLabel: string | null\n resourceKind: string | null\n resourceId: string | null\n executedAt: string\n}\n\nconst HEADER_PREFIX = 'omop:'\n\nexport function serializeOperationMetadata(payload: OperationMetadataPayload): string {\n const encoded = encodeURIComponent(JSON.stringify(payload))\n return `${HEADER_PREFIX}${encoded}`\n}\n\nexport function deserializeOperationMetadata(value: string | null | undefined): OperationMetadataPayload | null {\n if (!value || typeof value !== 'string') return null\n const trimmed = value.startsWith(HEADER_PREFIX) ? value.slice(HEADER_PREFIX.length) : value\n try {\n const parsed = JSON.parse(decodeURIComponent(trimmed))\n if (!parsed || typeof parsed !== 'object') return null\n if (typeof parsed.id !== 'string' || typeof parsed.commandId !== 'string') return null\n if (typeof parsed.undoToken !== 'string' || !parsed.undoToken) return null\n if (typeof parsed.executedAt !== 'string') return null\n return {\n id: parsed.id,\n undoToken: parsed.undoToken,\n commandId: parsed.commandId,\n actionLabel: parsed.actionLabel ?? null,\n resourceKind: parsed.resourceKind ?? null,\n resourceId: parsed.resourceId ?? null,\n executedAt: parsed.executedAt,\n }\n } catch {\n return null\n }\n}\n\n"],
5
+ "mappings": "AAUA,MAAM,gBAAgB;AAEf,SAAS,2BAA2B,SAA2C;AACpF,QAAM,UAAU,mBAAmB,KAAK,UAAU,OAAO,CAAC;AAC1D,SAAO,GAAG,aAAa,GAAG,OAAO;AACnC;AAEO,SAAS,6BAA6B,OAAmE;AAC9G,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,UAAU,MAAM,WAAW,aAAa,IAAI,MAAM,MAAM,cAAc,MAAM,IAAI;AACtF,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,mBAAmB,OAAO,CAAC;AACrD,QAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAClD,QAAI,OAAO,OAAO,OAAO,YAAY,OAAO,OAAO,cAAc,SAAU,QAAO;AAClF,QAAI,OAAO,OAAO,cAAc,YAAY,CAAC,OAAO,UAAW,QAAO;AACtE,QAAI,OAAO,OAAO,eAAe,SAAU,QAAO;AAClD,WAAO;AAAA,MACL,IAAI,OAAO;AAAA,MACX,WAAW,OAAO;AAAA,MAClB,WAAW,OAAO;AAAA,MAClB,aAAa,OAAO,eAAe;AAAA,MACnC,cAAc,OAAO,gBAAgB;AAAA,MACrC,YAAY,OAAO,cAAc;AAAA,MACjC,YAAY,OAAO;AAAA,IACrB;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,43 @@
1
+ class CommandRegistry {
2
+ constructor() {
3
+ this.handlers = /* @__PURE__ */ new Map();
4
+ }
5
+ register(handler) {
6
+ if (!handler?.id) throw new Error("Command handler must define an id");
7
+ if (this.handlers.has(handler.id)) {
8
+ throw new Error(`Duplicate command registration for id ${handler.id}`);
9
+ }
10
+ this.handlers.set(handler.id, handler);
11
+ }
12
+ unregister(id) {
13
+ this.handlers.delete(id);
14
+ }
15
+ get(id) {
16
+ return this.handlers.get(id) ?? null;
17
+ }
18
+ has(id) {
19
+ return this.handlers.has(id);
20
+ }
21
+ /**
22
+ * List all registered command handler IDs.
23
+ */
24
+ list() {
25
+ return Array.from(this.handlers.keys());
26
+ }
27
+ clear() {
28
+ this.handlers.clear();
29
+ }
30
+ }
31
+ const commandRegistry = new CommandRegistry();
32
+ function registerCommand(handler) {
33
+ commandRegistry.register(handler);
34
+ }
35
+ function unregisterCommand(id) {
36
+ commandRegistry.unregister(id);
37
+ }
38
+ export {
39
+ commandRegistry,
40
+ registerCommand,
41
+ unregisterCommand
42
+ };
43
+ //# sourceMappingURL=registry.js.map