@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,177 @@
1
+ import type { BootstrapData } from './types'
2
+ import { findAppRoot, type AppRoot } from './appResolver'
3
+ import { registerEntityIds } from '../encryption/entityIds'
4
+ import path from 'node:path'
5
+ import fs from 'node:fs'
6
+ import { pathToFileURL } from 'node:url'
7
+
8
+ /**
9
+ * Compile a TypeScript file to JavaScript using esbuild bundler.
10
+ * This bundles the file and all its dependencies, handling JSON imports properly.
11
+ * The compiled file is written next to the source file with a .mjs extension.
12
+ */
13
+ async function compileAndImport(tsPath: string): Promise<Record<string, unknown>> {
14
+ const jsPath = tsPath.replace(/\.ts$/, '.mjs')
15
+
16
+ // Check if we need to recompile (source newer than compiled)
17
+ const tsExists = fs.existsSync(tsPath)
18
+ const jsExists = fs.existsSync(jsPath)
19
+
20
+ if (!tsExists) {
21
+ throw new Error(`Generated file not found: ${tsPath}`)
22
+ }
23
+
24
+ const needsCompile = !jsExists ||
25
+ fs.statSync(tsPath).mtimeMs > fs.statSync(jsPath).mtimeMs
26
+
27
+ if (needsCompile) {
28
+ // Dynamically import esbuild only when needed
29
+ const esbuild = await import('esbuild')
30
+
31
+ // The app root is 2 levels up from .mercato/generated/
32
+ const appRoot = path.dirname(path.dirname(path.dirname(tsPath)))
33
+
34
+ // Plugin to resolve @/ alias to app root (works for @app modules)
35
+ const aliasPlugin: import('esbuild').Plugin = {
36
+ name: 'alias-resolver',
37
+ setup(build) {
38
+ // Resolve @/ alias to app root
39
+ build.onResolve({ filter: /^@\// }, (args) => {
40
+ const resolved = path.join(appRoot, args.path.slice(2))
41
+ // Try with .ts extension if base path doesn't exist
42
+ if (!fs.existsSync(resolved) && fs.existsSync(resolved + '.ts')) {
43
+ return { path: resolved + '.ts' }
44
+ }
45
+ // Also check for /index.ts if it's a directory
46
+ if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory() && fs.existsSync(path.join(resolved, 'index.ts'))) {
47
+ return { path: path.join(resolved, 'index.ts') }
48
+ }
49
+ return { path: resolved }
50
+ })
51
+ },
52
+ }
53
+
54
+ // Plugin to mark non-JSON package imports as external
55
+ const externalNonJsonPlugin: import('esbuild').Plugin = {
56
+ name: 'external-non-json',
57
+ setup(build) {
58
+ // Mark all package imports as external EXCEPT JSON files
59
+ build.onResolve({ filter: /^[^./]/ }, (args) => {
60
+ // If it's a JSON file, let esbuild bundle it
61
+ if (args.path.endsWith('.json')) {
62
+ return null // Let esbuild handle it
63
+ }
64
+ // Otherwise mark as external
65
+ return { path: args.path, external: true }
66
+ })
67
+ },
68
+ }
69
+
70
+ // Use esbuild.build with bundling to handle JSON imports
71
+ await esbuild.build({
72
+ entryPoints: [tsPath],
73
+ outfile: jsPath,
74
+ bundle: true,
75
+ format: 'esm',
76
+ platform: 'node',
77
+ target: 'node18',
78
+ plugins: [aliasPlugin, externalNonJsonPlugin],
79
+ // Allow JSON imports
80
+ loader: { '.json': 'json' },
81
+ })
82
+ }
83
+
84
+ // Import the compiled JavaScript
85
+ const fileUrl = pathToFileURL(jsPath).href
86
+ return import(fileUrl)
87
+ }
88
+
89
+
90
+ /**
91
+ * Dynamically load bootstrap data from a resolved app directory.
92
+ *
93
+ * IMPORTANT: This only works in unbundled contexts (CLI, tsx).
94
+ * Do NOT use this in Next.js bundled code - use static imports instead.
95
+ *
96
+ * For CLI context, we skip loading modules.generated.ts which has Next.js dependencies.
97
+ * CLI commands are discovered separately via the CLI module system.
98
+ *
99
+ * @param appRoot - Optional explicit app root path. If not provided, will search from cwd.
100
+ * @returns The loaded bootstrap data
101
+ * @throws Error if app root cannot be found or generated files are missing
102
+ */
103
+ export async function loadBootstrapData(appRoot?: string): Promise<BootstrapData> {
104
+ const resolved: AppRoot | null = appRoot
105
+ ? {
106
+ generatedDir: path.join(appRoot, '.mercato', 'generated'),
107
+ appDir: appRoot,
108
+ mercatoDir: path.join(appRoot, '.mercato'),
109
+ }
110
+ : findAppRoot()
111
+
112
+ if (!resolved) {
113
+ throw new Error(
114
+ 'Could not find app root with .mercato/generated directory. ' +
115
+ 'Make sure you run this command from within a Next.js app directory, ' +
116
+ 'or run "yarn mercato generate" first to create the generated files.',
117
+ )
118
+ }
119
+
120
+ const { generatedDir } = resolved
121
+
122
+ // IMPORTANT: Load entity IDs FIRST and register them before loading modules.
123
+ // This is because modules (e.g., ce.ts files) use E.xxx.xxx at module scope,
124
+ // and they need entity IDs to be available when they're imported.
125
+ const entityIdsModule = await compileAndImport(path.join(generatedDir, 'entities.ids.generated.ts'))
126
+ registerEntityIds(entityIdsModule.E as BootstrapData['entityIds'])
127
+
128
+ // Now load the rest of the generated files.
129
+ // modules.cli.generated.ts excludes Next.js-dependent code (routes, APIs, widgets)
130
+ const [
131
+ modulesModule,
132
+ entitiesModule,
133
+ diModule,
134
+ searchModule,
135
+ ] = await Promise.all([
136
+ compileAndImport(path.join(generatedDir, 'modules.cli.generated.ts')),
137
+ compileAndImport(path.join(generatedDir, 'entities.generated.ts')),
138
+ compileAndImport(path.join(generatedDir, 'di.generated.ts')),
139
+ compileAndImport(path.join(generatedDir, 'search.generated.ts')).catch(() => ({ searchModuleConfigs: [] })),
140
+ ])
141
+
142
+ return {
143
+ modules: modulesModule.modules as BootstrapData['modules'],
144
+ entities: entitiesModule.entities as BootstrapData['entities'],
145
+ diRegistrars: diModule.diRegistrars as BootstrapData['diRegistrars'],
146
+ entityIds: entityIdsModule.E as BootstrapData['entityIds'],
147
+ // Search configs are needed by workers for indexing
148
+ searchModuleConfigs: (searchModule.searchModuleConfigs ?? []) as BootstrapData['searchModuleConfigs'],
149
+ // Empty UI-related data - not needed for CLI
150
+ dashboardWidgetEntries: [],
151
+ injectionWidgetEntries: [],
152
+ injectionTables: [],
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Create and execute bootstrap in CLI context.
158
+ *
159
+ * This is a convenience function that finds the app root, loads the generated
160
+ * data dynamically, and runs bootstrap. Use this in CLI entry points.
161
+ *
162
+ * Returns the loaded bootstrap data so the CLI can register modules directly
163
+ * (avoids module resolution issues when importing @open-mercato/cli/mercato).
164
+ *
165
+ * @param appRoot - Optional explicit app root path
166
+ * @returns The loaded bootstrap data (modules, entities, etc.)
167
+ */
168
+ export async function bootstrapFromAppRoot(appRoot?: string): Promise<BootstrapData> {
169
+ const { createBootstrap, waitForAsyncRegistration } = await import('./factory.js')
170
+ const data = await loadBootstrapData(appRoot)
171
+ const bootstrap = createBootstrap(data)
172
+ bootstrap()
173
+ // In CLI context, wait for async registrations (UI widgets, search configs, etc.)
174
+ await waitForAsyncRegistration()
175
+
176
+ return data
177
+ }
@@ -0,0 +1,108 @@
1
+ import type { BootstrapData, BootstrapOptions } from './types'
2
+ import { registerOrmEntities } from '../db/mikro'
3
+ import { registerDiRegistrars } from '../di/container'
4
+ import { registerModules } from '../modules/registry'
5
+ import { registerEntityIds } from '../encryption/entityIds'
6
+ import { registerEntityFields } from '../encryption/entityFields'
7
+ import { registerSearchModuleConfigs } from '../../modules/search'
8
+
9
+ let _bootstrapped = false
10
+
11
+ // Store the async registration promise so callers can await it if needed
12
+ let _asyncRegistrationPromise: Promise<void> | null = null
13
+
14
+ /**
15
+ * Creates a bootstrap function that registers all application dependencies.
16
+ *
17
+ * The returned function should be called once at application startup.
18
+ * In development mode, it can be called multiple times (for HMR).
19
+ *
20
+ * @param data - All generated registry data from .mercato/generated/
21
+ * @param options - Optional configuration
22
+ * @returns A bootstrap function to call at app startup
23
+ */
24
+ export function createBootstrap(data: BootstrapData, options: BootstrapOptions = {}) {
25
+ return function bootstrap(): void {
26
+ // In development, always re-run registrations to handle HMR
27
+ // (Module state may be reset when Turbopack reloads packages)
28
+ if (_bootstrapped && process.env.NODE_ENV !== 'development') return
29
+ _bootstrapped = true
30
+
31
+ // === 1. Foundation: ORM entities and DI registrars ===
32
+ registerOrmEntities(data.entities)
33
+ registerDiRegistrars(data.diRegistrars.filter((r): r is NonNullable<typeof r> => r != null))
34
+
35
+ // === 2. Modules registry (required by i18n, query engine, dashboards, CLI) ===
36
+ registerModules(data.modules)
37
+
38
+ // === 3. Entity IDs (required by encryption, indexing, entity links) ===
39
+ registerEntityIds(data.entityIds)
40
+
41
+ // === 4. Entity fields registry (for encryption manager, Turbopack compatibility) ===
42
+ if (data.entityFieldsRegistry) {
43
+ registerEntityFields(data.entityFieldsRegistry)
44
+ }
45
+
46
+ // === 5. Search module configs (for search service registration in DI) ===
47
+ if (data.searchModuleConfigs) {
48
+ registerSearchModuleConfigs(data.searchModuleConfigs)
49
+ }
50
+
51
+ // === 6-7. UI Widgets and Optional packages (async to avoid circular deps) ===
52
+ // Store the promise so CLI context can await it
53
+ _asyncRegistrationPromise = registerWidgetsAndOptionalPackages(data, options)
54
+ void _asyncRegistrationPromise
55
+
56
+ options.onRegistrationComplete?.()
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Wait for async registrations (CLI modules, widgets, etc.) to complete.
62
+ * Call this after bootstrap() in CLI context where you need modules immediately.
63
+ */
64
+ export async function waitForAsyncRegistration(): Promise<void> {
65
+ if (_asyncRegistrationPromise) {
66
+ await _asyncRegistrationPromise
67
+ }
68
+ }
69
+
70
+ async function registerWidgetsAndOptionalPackages(data: BootstrapData, options: BootstrapOptions): Promise<void> {
71
+ // Register UI widgets (dynamic imports to avoid circular deps with ui/core packages)
72
+ try {
73
+ const [dashboardRegistry, injectionRegistry, coreInjection] = await Promise.all([
74
+ import('@open-mercato/ui/backend/dashboard/widgetRegistry'),
75
+ import('@open-mercato/ui/backend/injection/widgetRegistry'),
76
+ import('@open-mercato/core/modules/widgets/lib/injection'),
77
+ ])
78
+
79
+ dashboardRegistry.registerDashboardWidgets(data.dashboardWidgetEntries)
80
+ injectionRegistry.registerInjectionWidgets(data.injectionWidgetEntries)
81
+ coreInjection.registerCoreInjectionWidgets(data.injectionWidgetEntries)
82
+ coreInjection.registerCoreInjectionTables(data.injectionTables)
83
+ } catch {
84
+ // UI packages may not be available in all contexts
85
+ }
86
+
87
+ // Note: Search module configs are registered synchronously in the main bootstrap.
88
+ // The actual registerSearchModule() call happens in core/bootstrap.ts when the
89
+ // DI container is created, using getSearchModuleConfigs() from the global registry.
90
+
91
+ // Note: CLI module registration is handled separately in CLI context
92
+ // via bootstrapFromAppRoot in dynamicLoader. We don't import CLI here
93
+ // to avoid Turbopack tracing through the CLI package in Next.js context.
94
+ }
95
+
96
+ /**
97
+ * Check if bootstrap has been called.
98
+ */
99
+ export function isBootstrapped(): boolean {
100
+ return _bootstrapped
101
+ }
102
+
103
+ /**
104
+ * Reset bootstrap state. Useful for testing.
105
+ */
106
+ export function resetBootstrapState(): void {
107
+ _bootstrapped = false
108
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Bootstrap module for Open Mercato applications.
3
+ *
4
+ * This module provides utilities for bootstrapping the application:
5
+ *
6
+ * - `createBootstrap(data)` - Factory to create a bootstrap function from generated data
7
+ * - `isBootstrapped()` - Check if bootstrap has been called
8
+ * - `resetBootstrapState()` - Reset bootstrap state (for testing)
9
+ * - `findAppRoot()` - Find the Next.js app root directory
10
+ *
11
+ * For CLI/dynamic contexts, import the dynamic loader directly:
12
+ * ```ts
13
+ * import { bootstrapFromAppRoot } from '@open-mercato/shared/lib/bootstrap/dynamicLoader'
14
+ * ```
15
+ */
16
+
17
+ export * from './types'
18
+ export { createBootstrap, isBootstrapped, resetBootstrapState } from './factory'
19
+ export { findAppRoot, findAllApps, type AppRoot } from './appResolver'
20
+
21
+ // Note: dynamicLoader is intentionally NOT exported from index
22
+ // It should be imported directly when needed to make it clear
23
+ // that it only works in unbundled contexts (CLI, tsx)
@@ -0,0 +1,31 @@
1
+ import type { DiRegistrar } from '../di/container'
2
+ import type { EntityIds } from '../encryption/entityIds'
3
+ import type { EntityFieldsRegistry } from '../encryption/entityFields'
4
+ import type { Module, ModuleDashboardWidgetEntry, ModuleInjectionWidgetEntry } from '../../modules/registry'
5
+ import type { ModuleInjectionTable } from '../../modules/widgets/injection'
6
+ import type { SearchModuleConfig } from '../../modules/search'
7
+ import type { EntityClass, EntityClassGroup } from '@mikro-orm/core'
8
+
9
+ export type OrmEntity = EntityClass<unknown> | EntityClassGroup<unknown>
10
+
11
+ export interface InjectionTableEntry {
12
+ moduleId: string
13
+ table: ModuleInjectionTable
14
+ }
15
+
16
+ export interface BootstrapData {
17
+ modules: Module[]
18
+ entities: OrmEntity[]
19
+ diRegistrars: (DiRegistrar | undefined)[]
20
+ entityIds: EntityIds
21
+ entityFieldsRegistry?: EntityFieldsRegistry
22
+ dashboardWidgetEntries: ModuleDashboardWidgetEntry[]
23
+ injectionWidgetEntries: ModuleInjectionWidgetEntry[]
24
+ injectionTables: InjectionTableEntry[]
25
+ searchModuleConfigs: SearchModuleConfig[]
26
+ }
27
+
28
+ export interface BootstrapOptions {
29
+ skipSearchConfigs?: boolean
30
+ onRegistrationComplete?: () => void
31
+ }
@@ -0,0 +1,56 @@
1
+ import type { CacheStrategy } from '@open-mercato/cache'
2
+
3
+ export type CacheSegmentAnalysisOptions = {
4
+ keysPattern: string
5
+ deriveSegment: (key: string) => string | null
6
+ filterKey?: (key: string) => boolean
7
+ }
8
+
9
+ export type CacheSegmentInfo = {
10
+ segment: string
11
+ keys: string[]
12
+ }
13
+
14
+ export async function analyzeCacheSegments(
15
+ cache: CacheStrategy,
16
+ options: CacheSegmentAnalysisOptions
17
+ ): Promise<CacheSegmentInfo[]> {
18
+ const keys = await cache.keys(options.keysPattern)
19
+ const segments = new Map<string, Set<string>>()
20
+
21
+ for (const key of keys) {
22
+ if (options.filterKey && !options.filterKey(key)) continue
23
+ const segment = options.deriveSegment(key)
24
+ if (!segment) continue
25
+ if (!segments.has(segment)) segments.set(segment, new Set<string>())
26
+ segments.get(segment)!.add(key)
27
+ }
28
+
29
+ const results: CacheSegmentInfo[] = []
30
+ for (const [segment, keySet] of segments.entries()) {
31
+ results.push({
32
+ segment,
33
+ keys: Array.from(keySet).sort(),
34
+ })
35
+ }
36
+ results.sort((a, b) => a.segment.localeCompare(b.segment))
37
+ return results
38
+ }
39
+
40
+ export async function purgeCacheSegment(
41
+ cache: CacheStrategy,
42
+ options: CacheSegmentAnalysisOptions,
43
+ segment: string
44
+ ): Promise<{ deleted: number; keys: string[] }> {
45
+ const analyses = await analyzeCacheSegments(cache, options)
46
+ const target = analyses.find((entry) => entry.segment === segment)
47
+ if (!target) return { deleted: 0, keys: [] }
48
+
49
+ let deleted = 0
50
+ for (const key of target.keys) {
51
+ const removed = await cache.delete(key)
52
+ if (removed) deleted += 1
53
+ }
54
+
55
+ return { deleted, keys: target.keys }
56
+ }
@@ -0,0 +1,55 @@
1
+ export type ProgressBar = {
2
+ update(completed: number): void
3
+ complete(): void
4
+ }
5
+
6
+ export function createProgressBar(label: string, total: number): ProgressBar {
7
+ const width = 28
8
+ let lastPercent = -1
9
+ let lastCompleted = -1
10
+ let finished = false
11
+ const startedAt = Date.now()
12
+ const minPercentStep =
13
+ total >= 1_000_000 ? 0.01 : total >= 100_000 ? 0.05 : total >= 10_000 ? 0.1 : 0.5
14
+ const minCompletedStep = Math.max(1, Math.floor(total / 1000))
15
+
16
+ const render = (completed: number) => {
17
+ if (total <= 0 || finished) return
18
+ const ratio = Math.min(1, Math.max(0, completed / total))
19
+ const percentRaw = ratio * 100
20
+ if (
21
+ completed < total &&
22
+ percentRaw - lastPercent < minPercentStep &&
23
+ completed - lastCompleted < minCompletedStep
24
+ ) {
25
+ return
26
+ }
27
+ lastPercent = percentRaw
28
+ lastCompleted = completed
29
+ const filled = Math.round(ratio * width)
30
+ const bar = '#'.repeat(filled).padEnd(width, '-')
31
+ const percentLabel =
32
+ (percentRaw >= 10 ? percentRaw.toFixed(1) : percentRaw.toFixed(2)).padStart(6, ' ')
33
+ const countsLabel = `${completed.toLocaleString()}/${total.toLocaleString()}`
34
+ const elapsedMs = Math.max(1, Date.now() - startedAt)
35
+ const recordsPerSecond = completed > 0 ? (completed / elapsedMs) * 1000 : 0
36
+ const rateLabel = `${recordsPerSecond.toFixed(1)} r/s`
37
+ process.stdout.write(`\r${label} [${bar}] ${percentLabel}% (${countsLabel}) ${rateLabel}`)
38
+ if (completed >= total) {
39
+ finished = true
40
+ process.stdout.write('\n')
41
+ }
42
+ }
43
+
44
+ return {
45
+ update: render,
46
+ complete() {
47
+ if (!finished && total > 0) {
48
+ render(total)
49
+ } else if (!finished) {
50
+ finished = true
51
+ process.stdout.write('\n')
52
+ }
53
+ },
54
+ }
55
+ }
@@ -0,0 +1,84 @@
1
+ import { createContainer, asValue, InjectionMode } from 'awilix'
2
+ import { unregisterCommand, registerCommand, CommandBus } from '@open-mercato/shared/lib/commands'
3
+
4
+ describe('CommandBus', () => {
5
+ afterEach(() => {
6
+ unregisterCommand('test.command')
7
+ unregisterCommand('test.command.with-capture')
8
+ })
9
+
10
+ it('executes registered command and logs action metadata', async () => {
11
+ const logMock = jest.fn(async () => ({ id: 'log-entry' }))
12
+ registerCommand({
13
+ id: 'test.command',
14
+ execute: jest.fn(async () => ({ ok: true })),
15
+ buildLog: jest.fn(() => ({ actionLabel: 'Test', resourceKind: 'test', resourceId: '123' })),
16
+ })
17
+
18
+ const container = createContainer({ injectionMode: InjectionMode.CLASSIC })
19
+ container.register({ actionLogService: asValue({ log: logMock }) })
20
+
21
+ const bus = new CommandBus()
22
+ const ctx = {
23
+ container,
24
+ auth: { sub: 'user-1', tenantId: 'tenant-1', orgId: null },
25
+ organizationScope: null,
26
+ selectedOrganizationId: null,
27
+ organizationIds: null,
28
+ }
29
+
30
+ const { result, logEntry } = await bus.execute('test.command', { input: {}, ctx })
31
+
32
+ expect(result).toEqual({ ok: true })
33
+ expect(logMock).toHaveBeenCalledWith(
34
+ expect.objectContaining({
35
+ commandId: 'test.command',
36
+ tenantId: 'tenant-1',
37
+ actorUserId: 'user-1',
38
+ resourceId: '123',
39
+ })
40
+ )
41
+ expect(logEntry).toEqual({ id: 'log-entry' })
42
+ })
43
+
44
+ it('passes captureAfter snapshot to buildLog as snapshots.after', async () => {
45
+ const logMock = jest.fn(async () => ({ id: 'log-entry-2' }))
46
+ const buildLogMock = jest.fn(() => ({
47
+ actionLabel: 'Test with capture',
48
+ resourceKind: 'test',
49
+ resourceId: '456',
50
+ }))
51
+
52
+ registerCommand({
53
+ id: 'test.command.with-capture',
54
+ prepare: jest.fn(async () => ({ before: { state: 'before-snapshot' } })),
55
+ execute: jest.fn(async () => ({ id: 'result-123' })),
56
+ captureAfter: jest.fn(async (_input, result) => ({ state: 'after-snapshot', resultId: result.id })),
57
+ buildLog: buildLogMock,
58
+ })
59
+
60
+ const container = createContainer({ injectionMode: InjectionMode.CLASSIC })
61
+ container.register({ actionLogService: asValue({ log: logMock }) })
62
+
63
+ const bus = new CommandBus()
64
+ const ctx = {
65
+ container,
66
+ auth: { sub: 'user-2', tenantId: 'tenant-2', orgId: null },
67
+ organizationScope: null,
68
+ selectedOrganizationId: null,
69
+ organizationIds: null,
70
+ }
71
+
72
+ await bus.execute('test.command.with-capture', { input: { foo: 'bar' }, ctx })
73
+
74
+ // Verify buildLog received both before and after snapshots
75
+ expect(buildLogMock).toHaveBeenCalledWith(
76
+ expect.objectContaining({
77
+ snapshots: {
78
+ before: { state: 'before-snapshot' },
79
+ after: { state: 'after-snapshot', resultId: 'result-123' },
80
+ },
81
+ })
82
+ )
83
+ })
84
+ })
@@ -0,0 +1,42 @@
1
+ import { buildChanges, requireTenantScope, requireId } from '@open-mercato/shared/lib/commands/helpers'
2
+
3
+ describe('command helpers', () => {
4
+ describe('buildChanges', () => {
5
+ it('returns diff for changed keys', () => {
6
+ const diff = buildChanges({ a: 1, b: 2 }, { a: 1, b: 3, c: 4 }, ['a', 'b'])
7
+ expect(diff).toEqual({ b: { from: 2, to: 3 } })
8
+ })
9
+
10
+ it('handles missing before snapshot', () => {
11
+ expect(buildChanges(null, { a: 1 }, ['a'])).toEqual({})
12
+ })
13
+ })
14
+
15
+ describe('requireTenantScope', () => {
16
+ it('prefers requested when allowed', () => {
17
+ expect(requireTenantScope('tenant-1', 'tenant-1')).toBe('tenant-1')
18
+ })
19
+
20
+ it('throws when requested mismatches auth tenant', () => {
21
+ expect(() => requireTenantScope('tenant-1', 'tenant-2')).toThrow('Forbidden')
22
+ })
23
+
24
+ it('throws when tenant missing', () => {
25
+ expect(() => requireTenantScope(null, null)).toThrow('Tenant scope required')
26
+ })
27
+ })
28
+
29
+ describe('requireId', () => {
30
+ it('returns string id directly', () => {
31
+ expect(requireId('123')).toBe('123')
32
+ })
33
+
34
+ it('extracts from object tokens', () => {
35
+ expect(requireId({ body: { id: 'abc' } })).toBe('abc')
36
+ })
37
+
38
+ it('throws when missing', () => {
39
+ expect(() => requireId(null)).toThrow('ID is required')
40
+ })
41
+ })
42
+ })