@open-mercato/shared 0.6.7-develop.6860.1.f796b22e80 → 0.6.7

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 (199) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +6 -57
  3. package/build.mjs +4 -2
  4. package/dist/lib/auth/server.js +2 -15
  5. package/dist/lib/auth/server.js.map +2 -2
  6. package/dist/lib/bootstrap/dynamicLoader.js +43 -117
  7. package/dist/lib/bootstrap/dynamicLoader.js.map +2 -2
  8. package/dist/lib/commands/command-bus.js +1 -5
  9. package/dist/lib/commands/command-bus.js.map +2 -2
  10. package/dist/lib/commands/command-interceptor-runner.js +2 -2
  11. package/dist/lib/commands/command-interceptor-runner.js.map +2 -2
  12. package/dist/lib/crud/enricher-runner.js +11 -2
  13. package/dist/lib/crud/enricher-runner.js.map +2 -2
  14. package/dist/lib/crud/factory.js +4 -63
  15. package/dist/lib/crud/factory.js.map +2 -2
  16. package/dist/lib/crud/interceptor-runner.js +2 -2
  17. package/dist/lib/crud/interceptor-runner.js.map +2 -2
  18. package/dist/lib/crud/mutation-guard-registry.js +2 -2
  19. package/dist/lib/crud/mutation-guard-registry.js.map +2 -2
  20. package/dist/lib/crud/types.js +0 -4
  21. package/dist/lib/crud/types.js.map +3 -3
  22. package/dist/lib/data/engine.js +11 -37
  23. package/dist/lib/data/engine.js.map +2 -2
  24. package/dist/lib/db/mikro.js +12 -3
  25. package/dist/lib/db/mikro.js.map +2 -2
  26. package/dist/lib/db/pg-errors.js +0 -42
  27. package/dist/lib/db/pg-errors.js.map +2 -2
  28. package/dist/lib/di/container.js +6 -48
  29. package/dist/lib/di/container.js.map +2 -2
  30. package/dist/lib/email/send.js +0 -56
  31. package/dist/lib/email/send.js.map +2 -2
  32. package/dist/lib/encryption/subscriber.js +10 -2
  33. package/dist/lib/encryption/subscriber.js.map +2 -2
  34. package/dist/lib/encryption/tenantDataEncryptionService.js +17 -52
  35. package/dist/lib/encryption/tenantDataEncryptionService.js.map +2 -2
  36. package/dist/lib/i18n/config.js +1 -1
  37. package/dist/lib/i18n/config.js.map +2 -2
  38. package/dist/lib/i18n/server.js +4 -4
  39. package/dist/lib/i18n/server.js.map +3 -3
  40. package/dist/lib/logger/index.js +3 -47
  41. package/dist/lib/logger/index.js.map +3 -3
  42. package/dist/lib/modules/registry.js +1 -5
  43. package/dist/lib/modules/registry.js.map +2 -2
  44. package/dist/lib/query/engine.js +40 -54
  45. package/dist/lib/query/engine.js.map +2 -2
  46. package/dist/lib/query/types.js.map +1 -1
  47. package/dist/lib/redis/connection.js +2 -7
  48. package/dist/lib/redis/connection.js.map +2 -2
  49. package/dist/lib/search/config.js +2 -61
  50. package/dist/lib/search/config.js.map +2 -2
  51. package/dist/lib/search/tokenize.js +11 -23
  52. package/dist/lib/search/tokenize.js.map +2 -2
  53. package/dist/lib/version.js +1 -1
  54. package/dist/lib/version.js.map +1 -1
  55. package/dist/lib/webhooks/index.js +1 -13
  56. package/dist/lib/webhooks/index.js.map +2 -2
  57. package/dist/lib/webhooks/verify.js +7 -12
  58. package/dist/lib/webhooks/verify.js.map +2 -2
  59. package/dist/modules/analytics.js.map +2 -2
  60. package/dist/modules/overrides.js +1 -50
  61. package/dist/modules/overrides.js.map +2 -2
  62. package/dist/modules/payment_gateways/types.js +1 -8
  63. package/dist/modules/payment_gateways/types.js.map +2 -2
  64. package/dist/modules/registry.js +0 -1
  65. package/dist/modules/registry.js.map +2 -2
  66. package/dist/security/enabledModulesRegistry.js +11 -56
  67. package/dist/security/enabledModulesRegistry.js.map +2 -2
  68. package/jest.config.cjs +1 -2
  69. package/package.json +9 -19
  70. package/src/lib/ai/llm-provider.ts +0 -33
  71. package/src/lib/auth/__tests__/server.test.ts +0 -23
  72. package/src/lib/auth/server.ts +2 -23
  73. package/src/lib/bootstrap/__tests__/dynamicLoader.commandInterceptors.test.ts +8 -63
  74. package/src/lib/bootstrap/__tests__/dynamicLoader.tsconfig.test.ts +1 -21
  75. package/src/lib/bootstrap/dynamicLoader.ts +65 -213
  76. package/src/lib/commands/__tests__/command-interceptor-runner.test.ts +0 -28
  77. package/src/lib/commands/command-bus.ts +1 -5
  78. package/src/lib/commands/command-interceptor-runner.ts +2 -2
  79. package/src/lib/crud/__tests__/crud-factory.test.ts +0 -298
  80. package/src/lib/crud/__tests__/mutation-guard-registry.test.ts +0 -26
  81. package/src/lib/crud/enricher-runner.ts +11 -2
  82. package/src/lib/crud/factory.ts +1 -69
  83. package/src/lib/crud/interceptor-runner.ts +2 -2
  84. package/src/lib/crud/mutation-guard-registry.ts +2 -2
  85. package/src/lib/crud/types.ts +0 -3
  86. package/src/lib/data/__tests__/engine.bulk-suppress.test.ts +3 -18
  87. package/src/lib/data/engine.ts +16 -50
  88. package/src/lib/db/__tests__/mikro.test.ts +25 -0
  89. package/src/lib/db/mikro.ts +38 -13
  90. package/src/lib/db/pg-errors.ts +0 -58
  91. package/src/lib/di/container.ts +8 -72
  92. package/src/lib/email/__tests__/send.test.ts +1 -37
  93. package/src/lib/email/send.ts +0 -79
  94. package/src/lib/encryption/__tests__/subscriber.test.ts +2 -54
  95. package/src/lib/encryption/__tests__/tenantDataEncryptionService.test.ts +0 -58
  96. package/src/lib/encryption/subscriber.ts +14 -2
  97. package/src/lib/encryption/tenantDataEncryptionService.ts +18 -64
  98. package/src/lib/i18n/config.ts +2 -2
  99. package/src/lib/i18n/server.ts +2 -6
  100. package/src/lib/logger/__tests__/logger.test.ts +0 -77
  101. package/src/lib/logger/index.ts +1 -59
  102. package/src/lib/modules/registry.ts +0 -10
  103. package/src/lib/query/__tests__/engine.test.ts +13 -65
  104. package/src/lib/query/engine.ts +58 -75
  105. package/src/lib/query/types.ts +5 -4
  106. package/src/lib/redis/__tests__/connection.test.ts +0 -16
  107. package/src/lib/redis/connection.ts +7 -10
  108. package/src/lib/search/__tests__/config.test.ts +0 -177
  109. package/src/lib/search/config.ts +8 -106
  110. package/src/lib/search/tokenize.ts +11 -33
  111. package/src/lib/webhooks/__tests__/verify.test.ts +1 -20
  112. package/src/lib/webhooks/index.ts +1 -19
  113. package/src/lib/webhooks/verify.ts +9 -17
  114. package/src/modules/__tests__/registry.test.ts +2 -10
  115. package/src/modules/analytics.ts +0 -7
  116. package/src/modules/customer-auth.ts +0 -1
  117. package/src/modules/encryption.ts +0 -3
  118. package/src/modules/navigation/backendChrome.ts +0 -15
  119. package/src/modules/overrides.ts +0 -103
  120. package/src/modules/payment_gateways/types.ts +0 -41
  121. package/src/modules/registry.ts +0 -3
  122. package/src/security/enabledModulesRegistry.ts +12 -64
  123. package/dist/lib/ai/safety-identifier.js +0 -32
  124. package/dist/lib/ai/safety-identifier.js.map +0 -7
  125. package/dist/lib/auth/organizationScope.js +0 -34
  126. package/dist/lib/auth/organizationScope.js.map +0 -7
  127. package/dist/lib/bootstrap/clientOnlyModules.js +0 -55
  128. package/dist/lib/bootstrap/clientOnlyModules.js.map +0 -7
  129. package/dist/lib/data/consistency.js +0 -19
  130. package/dist/lib/data/consistency.js.map +0 -7
  131. package/dist/lib/db/entityMetadata.js +0 -26
  132. package/dist/lib/db/entityMetadata.js.map +0 -7
  133. package/dist/lib/logger/extension.js +0 -29
  134. package/dist/lib/logger/extension.js.map +0 -7
  135. package/dist/lib/modules/surfaceFingerprint.js +0 -47
  136. package/dist/lib/modules/surfaceFingerprint.js.map +0 -7
  137. package/dist/lib/query/ciphertext-search-warning.js +0 -45
  138. package/dist/lib/query/ciphertext-search-warning.js.map +0 -7
  139. package/dist/lib/search/auto-indexing.js +0 -14
  140. package/dist/lib/search/auto-indexing.js.map +0 -7
  141. package/dist/lib/search/availability.js +0 -107
  142. package/dist/lib/search/availability.js.map +0 -7
  143. package/dist/lib/search/tokenLookup.js +0 -46
  144. package/dist/lib/search/tokenLookup.js.map +0 -7
  145. package/dist/lib/telemetry/runtime.js +0 -35
  146. package/dist/lib/telemetry/runtime.js.map +0 -7
  147. package/dist/lib/webhooks/body.js +0 -57
  148. package/dist/lib/webhooks/body.js.map +0 -7
  149. package/dist/lib/webhooks/inbound-types.js +0 -1
  150. package/dist/lib/webhooks/inbound-types.js.map +0 -7
  151. package/dist/modules/widgets/extension-points.js +0 -96
  152. package/dist/modules/widgets/extension-points.js.map +0 -7
  153. package/dist/security/featurePolicy.js +0 -62
  154. package/dist/security/featurePolicy.js.map +0 -7
  155. package/scripts/versionSource.cjs +0 -65
  156. package/src/lib/__tests__/versionSource.test.ts +0 -46
  157. package/src/lib/ai/__tests__/llm-provider-contract.test.ts +0 -59
  158. package/src/lib/ai/__tests__/safety-identifier.test.ts +0 -71
  159. package/src/lib/ai/safety-identifier.ts +0 -73
  160. package/src/lib/auth/__tests__/organizationScope.test.ts +0 -107
  161. package/src/lib/auth/organizationScope.ts +0 -65
  162. package/src/lib/bootstrap/__tests__/clientOnlyModules.test.ts +0 -189
  163. package/src/lib/bootstrap/__tests__/dynamicLoader.aliasResolver.test.ts +0 -98
  164. package/src/lib/bootstrap/__tests__/dynamicLoader.appDi.test.ts +0 -211
  165. package/src/lib/bootstrap/__tests__/dynamicLoader.generatedCacheRecovery.test.ts +0 -192
  166. package/src/lib/bootstrap/clientOnlyModules.ts +0 -85
  167. package/src/lib/crud/__tests__/crud-factory.cache-user-scope.test.ts +0 -171
  168. package/src/lib/data/__tests__/consistency.test.ts +0 -38
  169. package/src/lib/data/consistency.ts +0 -17
  170. package/src/lib/db/__tests__/entityMetadata.test.ts +0 -38
  171. package/src/lib/db/__tests__/pg-errors.test.ts +0 -48
  172. package/src/lib/db/entityMetadata.ts +0 -43
  173. package/src/lib/di/__tests__/container-app-di-absent.test.ts +0 -155
  174. package/src/lib/di/__tests__/container-app-di.test.ts +0 -173
  175. package/src/lib/di/__tests__/registrar-error-log.test.ts +0 -154
  176. package/src/lib/i18n/__tests__/config.test.ts +0 -8
  177. package/src/lib/i18n/__tests__/server-dictionary-cache.test.ts +0 -30
  178. package/src/lib/i18n/__tests__/server-unbootstrapped.test.ts +0 -29
  179. package/src/lib/logger/extension.ts +0 -59
  180. package/src/lib/modules/__tests__/surfaceFingerprint.test.ts +0 -122
  181. package/src/lib/modules/surfaceFingerprint.ts +0 -87
  182. package/src/lib/query/__tests__/ciphertext-search-warning.test.ts +0 -178
  183. package/src/lib/query/ciphertext-search-warning.ts +0 -95
  184. package/src/lib/search/__tests__/availability.test.ts +0 -211
  185. package/src/lib/search/__tests__/tokenLookup.test.ts +0 -206
  186. package/src/lib/search/__tests__/tokenize.test.ts +0 -49
  187. package/src/lib/search/auto-indexing.ts +0 -22
  188. package/src/lib/search/availability.ts +0 -232
  189. package/src/lib/search/tokenLookup.ts +0 -133
  190. package/src/lib/telemetry/runtime.ts +0 -72
  191. package/src/lib/webhooks/__tests__/body.test.ts +0 -95
  192. package/src/lib/webhooks/body.ts +0 -70
  193. package/src/lib/webhooks/inbound-types.ts +0 -125
  194. package/src/modules/__tests__/nav-group-order-override.test.ts +0 -183
  195. package/src/modules/payment_gateways/__tests__/types.test.ts +0 -37
  196. package/src/modules/widgets/__tests__/extension-points.test.ts +0 -101
  197. package/src/modules/widgets/extension-points.ts +0 -524
  198. package/src/security/__tests__/featurePolicy.test.ts +0 -166
  199. package/src/security/featurePolicy.ts +0 -89
@@ -1,98 +1,16 @@
1
1
  import type { BootstrapData } from './types'
2
- import type { AppDiRegistrar } from '../di/container'
3
2
  import { findAppRoot, type AppRoot } from './appResolver'
4
3
  import { registerEntityIds } from '../encryption/entityIds'
5
- import { createLogger } from '../logger'
6
4
  import {
7
5
  ensureMikroOrmV7GeneratedCacheCompatibility,
8
6
  recoverMikroOrmV7GeneratedCacheFromImportError,
9
7
  } from './generatedCacheRecovery'
10
- import { CLIENT_ONLY_STUB_NAMESPACE, createClientOnlyStubPlugin } from './clientOnlyModules'
11
8
  import path from 'node:path'
12
9
  import fs from 'node:fs'
13
10
  import crypto from 'node:crypto'
14
11
  import { createRequire } from 'node:module'
15
12
  import { pathToFileURL } from 'node:url'
16
13
 
17
- const logger = createLogger('shared').child({ component: 'bootstrap' })
18
-
19
- /**
20
- * Thrown when an expected generated source file is absent.
21
- *
22
- * Optional registries treat this as the supported compatibility case (an app
23
- * that never generated the file), which is what makes it distinguishable from
24
- * a file that exists but fails to compile or import.
25
- */
26
- class GeneratedFileNotFoundError extends Error {
27
- readonly filePath: string
28
-
29
- constructor(filePath: string) {
30
- super(`Generated file not found: ${filePath}`)
31
- this.name = 'GeneratedFileNotFoundError'
32
- this.filePath = filePath
33
- }
34
- }
35
-
36
- /**
37
- * esbuild plugins for the CLI bundle, in resolution order. The client-only stub must come
38
- * first so it wins over the alias and external plugins for `*.client` dynamic imports.
39
- *
40
- * Exported so the wiring itself is testable: a test that only exercises
41
- * `createClientOnlyStubPlugin` in isolation stays green if the plugin is dropped from this
42
- * list, which would silently reintroduce #4623.
43
- */
44
- export function createCliBundlePlugins(appRoot: string): import('esbuild').Plugin[] {
45
- // Plugin to resolve the @/ alias the way the app tsconfig maps it:
46
- // `@/.mercato/*` to the app root, every other `@/*` to the app's src/ directory.
47
- const aliasPlugin: import('esbuild').Plugin = {
48
- name: 'alias-resolver',
49
- setup(build) {
50
- build.onResolve({ filter: /^@\// }, (args) => {
51
- const rest = args.path.slice('@/'.length)
52
- const bases = rest.startsWith('.mercato/')
53
- ? [path.join(appRoot, rest)]
54
- : [path.join(appRoot, 'src', rest), path.join(appRoot, rest)]
55
- for (const base of bases) {
56
- if (fs.existsSync(base) && fs.statSync(base).isFile()) {
57
- return { path: base }
58
- }
59
- for (const suffix of ['.ts', '.tsx', '/index.ts', '/index.tsx']) {
60
- if (fs.existsSync(base + suffix)) {
61
- return { path: base + suffix }
62
- }
63
- }
64
- }
65
- // Nothing matched — hand esbuild the literal mapping so it reports the
66
- // missing file against the path the app author actually wrote.
67
- return { path: path.join(appRoot, rest) }
68
- })
69
- },
70
- }
71
-
72
- // Plugin to mark non-JSON package imports as external
73
- const externalNonJsonPlugin: import('esbuild').Plugin = {
74
- name: 'external-non-json',
75
- setup(build) {
76
- // Mark all package imports as external EXCEPT JSON files
77
- // Filter matches paths that don't start with . or / (package imports like @open-mercato/shared)
78
- build.onResolve({ filter: /^[^./]/ }, (args) => {
79
- // Skip Windows absolute paths (e.g., C:\...) - they're local files, not packages
80
- if (/^[a-zA-Z]:/.test(args.path)) {
81
- return null // Let esbuild handle it
82
- }
83
- // If it's a JSON file, let esbuild bundle it
84
- if (args.path.endsWith('.json')) {
85
- return null // Let esbuild handle it
86
- }
87
- // Otherwise mark as external
88
- return { path: args.path, external: true }
89
- })
90
- },
91
- }
92
-
93
- return [createClientOnlyStubPlugin(), aliasPlugin, externalNonJsonPlugin]
94
- }
95
-
96
14
  const DYNAMIC_LOADER_CACHE_VERSION = 4
97
15
 
98
16
  type DynamicLoaderCacheMetadata = {
@@ -249,7 +167,6 @@ function collectDependencyHashes(
249
167
  ): Record<string, string> {
250
168
  return Object.fromEntries(
251
169
  Object.keys(inputs)
252
- .filter((inputPath) => !inputPath.startsWith(`${CLIENT_ONLY_STUB_NAMESPACE}:`))
253
170
  .map((inputPath) => {
254
171
  const absolutePath = path.isAbsolute(inputPath)
255
172
  ? inputPath
@@ -304,33 +221,14 @@ function cacheIsValid(
304
221
  && dependenciesAreValid(appRoot, metadata.dependencies)
305
222
  }
306
223
 
307
- /**
308
- * Options for `compileAndImport`.
309
- *
310
- * Both paths default to the generated-registry layout (`<appRoot>/.mercato/generated/<file>.ts`
311
- * compiled to a `.mjs` sibling). Sources that live elsewhere in the app — `src/di.ts` — MUST pass
312
- * both explicitly: the default app root is derived by walking three directories up from the source,
313
- * which only holds inside `.mercato/generated`.
314
- */
315
- type CompileAndImportOptions = {
316
- appRoot?: string
317
- outFile?: string
318
- allowRecovery?: boolean
319
- }
320
-
321
224
  /**
322
225
  * Compile a TypeScript file to JavaScript using esbuild bundler.
323
226
  * This bundles the file and all its dependencies, handling JSON imports properly.
324
- * The compiled file is written next to the source file with a .mjs extension unless
325
- * `outFile` says otherwise.
227
+ * The compiled file is written next to the source file with a .mjs extension.
326
228
  */
327
- async function compileAndImport(
328
- tsPath: string,
329
- options: CompileAndImportOptions = {},
330
- ): Promise<Record<string, unknown>> {
331
- const allowRecovery = options.allowRecovery ?? true
332
- const jsPath = options.outFile ?? tsPath.replace(/\.ts$/, '.mjs')
333
- const appRoot = options.appRoot ?? path.dirname(path.dirname(path.dirname(tsPath)))
229
+ async function compileAndImport(tsPath: string, allowRecovery: boolean = true): Promise<Record<string, unknown>> {
230
+ const jsPath = tsPath.replace(/\.ts$/, '.mjs')
231
+ const appRoot = path.dirname(path.dirname(path.dirname(tsPath)))
334
232
  const appTsconfig = path.join(appRoot, 'tsconfig.json')
335
233
  const metadataPath = cacheMetadataPath(jsPath)
336
234
 
@@ -338,7 +236,7 @@ async function compileAndImport(
338
236
  const tsconfigExists = fs.existsSync(appTsconfig)
339
237
 
340
238
  if (!tsExists) {
341
- throw new GeneratedFileNotFoundError(tsPath)
239
+ throw new Error(`Generated file not found: ${tsPath}`)
342
240
  }
343
241
  if (!tsconfigExists) {
344
242
  throw new Error(`App TypeScript config not found: ${appTsconfig}`)
@@ -349,10 +247,50 @@ async function compileAndImport(
349
247
  const needsCompile = !cacheIsValid(appRoot, jsPath, metadataPath, expectedInputHash)
350
248
 
351
249
  if (needsCompile) {
352
- fs.mkdirSync(path.dirname(jsPath), { recursive: true })
353
250
  // Dynamically import esbuild only when needed
354
251
  const esbuild = await import('esbuild')
355
252
 
253
+ // Plugin to resolve @/ alias to app root (works for @app modules)
254
+ const aliasPlugin: import('esbuild').Plugin = {
255
+ name: 'alias-resolver',
256
+ setup(build) {
257
+ // Resolve @/ alias to app root
258
+ build.onResolve({ filter: /^@\// }, (args) => {
259
+ const resolved = path.join(appRoot, args.path.slice(2))
260
+ // Try with .ts extension if base path doesn't exist
261
+ if (!fs.existsSync(resolved) && fs.existsSync(resolved + '.ts')) {
262
+ return { path: resolved + '.ts' }
263
+ }
264
+ // Also check for /index.ts if it's a directory
265
+ if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory() && fs.existsSync(path.join(resolved, 'index.ts'))) {
266
+ return { path: path.join(resolved, 'index.ts') }
267
+ }
268
+ return { path: resolved }
269
+ })
270
+ },
271
+ }
272
+
273
+ // Plugin to mark non-JSON package imports as external
274
+ const externalNonJsonPlugin: import('esbuild').Plugin = {
275
+ name: 'external-non-json',
276
+ setup(build) {
277
+ // Mark all package imports as external EXCEPT JSON files
278
+ // Filter matches paths that don't start with . or / (package imports like @open-mercato/shared)
279
+ build.onResolve({ filter: /^[^./]/ }, (args) => {
280
+ // Skip Windows absolute paths (e.g., C:\...) - they're local files, not packages
281
+ if (/^[a-zA-Z]:/.test(args.path)) {
282
+ return null // Let esbuild handle it
283
+ }
284
+ // If it's a JSON file, let esbuild bundle it
285
+ if (args.path.endsWith('.json')) {
286
+ return null // Let esbuild handle it
287
+ }
288
+ // Otherwise mark as external
289
+ return { path: args.path, external: true }
290
+ })
291
+ },
292
+ }
293
+
356
294
  // Use esbuild.build with bundling to handle JSON imports
357
295
  const result = await esbuild.build({
358
296
  entryPoints: [tsPath],
@@ -364,7 +302,7 @@ async function compileAndImport(
364
302
  platform: 'node',
365
303
  target: 'node18',
366
304
  tsconfig: appTsconfig,
367
- plugins: createCliBundlePlugins(appRoot),
305
+ plugins: [aliasPlugin, externalNonJsonPlugin],
368
306
  // Allow JSON imports
369
307
  loader: { '.json': 'json' },
370
308
  })
@@ -395,45 +333,25 @@ async function compileAndImport(
395
333
  throw error
396
334
  }
397
335
 
398
- return compileAndImport(tsPath, { ...options, allowRecovery: false })
336
+ return compileAndImport(tsPath, false)
399
337
  }
400
338
  }
401
339
 
402
340
 
403
341
  /**
404
- * Load a generated registry that older apps may not have generated yet.
342
+ * Dynamically load bootstrap data from a resolved app directory.
343
+ *
344
+ * IMPORTANT: This only works in unbundled contexts (CLI, tsx).
345
+ * Do NOT use this in Next.js bundled code - use static imports instead.
405
346
  *
406
- * An absent source file is the supported compatibility case and resolves to
407
- * `fallback` quietly. Any other failure — a compile error, a broken import, a
408
- * runtime throw at module scope — still resolves to `fallback` so bootstrap
409
- * keeps working, but is reported at error level: a registry that silently
410
- * degrades to nothing is exactly how command interceptors stopped applying in
411
- * worker/CLI processes (#4327, #4491).
347
+ * For CLI context, we skip loading modules.generated.ts which has Next.js dependencies.
348
+ * CLI commands are discovered separately via the CLI module system.
349
+ *
350
+ * @param appRoot - Optional explicit app root path. If not provided, will search from cwd.
351
+ * @returns The loaded bootstrap data
352
+ * @throws Error if app root cannot be found or generated files are missing
412
353
  */
413
- async function loadOptionalGeneratedModule(
414
- tsPath: string,
415
- fallback: Record<string, unknown>,
416
- ): Promise<Record<string, unknown>> {
417
- try {
418
- return await compileAndImport(tsPath)
419
- } catch (error) {
420
- if (error instanceof GeneratedFileNotFoundError) {
421
- logger.debug('Optional generated registry not present, using empty fallback', {
422
- file: path.basename(tsPath),
423
- })
424
- return fallback
425
- }
426
-
427
- logger.error('Failed to load generated registry, continuing without its entries', {
428
- file: path.basename(tsPath),
429
- filePath: tsPath,
430
- err: error,
431
- })
432
- return fallback
433
- }
434
- }
435
-
436
- function resolveAppRootOrThrow(appRoot?: string): AppRoot {
354
+ export async function loadBootstrapData(appRoot?: string): Promise<BootstrapData> {
437
355
  const resolved: AppRoot | null = appRoot
438
356
  ? {
439
357
  generatedDir: path.join(appRoot, '.mercato', 'generated'),
@@ -450,68 +368,6 @@ function resolveAppRootOrThrow(appRoot?: string): AppRoot {
450
368
  )
451
369
  }
452
370
 
453
- return resolved
454
- }
455
-
456
- /**
457
- * Load the app-level DI registrar (`src/di.ts`) for the dynamic bootstrap path.
458
- *
459
- * The Next.js runtime imports `@/di` statically from its own `src/bootstrap.ts` and hands the
460
- * registrar to `createBootstrap`. Worker, scheduler and CLI processes bootstrap through
461
- * `bootstrapFromAppRoot` instead, where the `@/` alias does not exist — so without this the app's
462
- * DI registrations silently never ran there, and every request container paid a failed
463
- * `import('@/di')` resolution (the compatibility fallback in `lib/di/container.ts`).
464
- *
465
- * An absent `src/di.ts` is the supported case and resolves to `null` quietly. A file that exists
466
- * but cannot be compiled, imported, or does not export `register` is reported at error level and
467
- * still resolves to `null`, so a broken app DI module degrades the same way a broken generated
468
- * registry does (#4327, #4491) instead of taking the whole process down.
469
- */
470
- async function loadAppDiRegistrar(appDir: string): Promise<AppDiRegistrar | null> {
471
- const tsPath = path.join(appDir, 'src', 'di.ts')
472
- if (!fs.existsSync(tsPath)) {
473
- logger.debug('App-level DI module not present, skipping its registrations', { filePath: tsPath })
474
- return null
475
- }
476
-
477
- try {
478
- const appDiModule = await compileAndImport(tsPath, {
479
- appRoot: appDir,
480
- outFile: path.join(appDir, '.mercato', 'generated', 'app-di.compiled.mjs'),
481
- })
482
- const register = appDiModule.register
483
- if (typeof register !== 'function') {
484
- logger.error('App-level DI module exports no register(); its registrations are skipped', {
485
- filePath: tsPath,
486
- })
487
- return null
488
- }
489
- return register as AppDiRegistrar
490
- } catch (error) {
491
- logger.error('Failed to load the app-level DI module; its registrations are skipped', {
492
- filePath: tsPath,
493
- err: error,
494
- })
495
- return null
496
- }
497
- }
498
-
499
- /**
500
- * Dynamically load bootstrap data from a resolved app directory.
501
- *
502
- * IMPORTANT: This only works in unbundled contexts (CLI, tsx).
503
- * Do NOT use this in Next.js bundled code - use static imports instead.
504
- *
505
- * For CLI context, we skip loading modules.generated.ts which has Next.js dependencies.
506
- * CLI commands are discovered separately via the CLI module system.
507
- *
508
- * @param appRoot - Optional explicit app root path. If not provided, will search from cwd.
509
- * @returns The loaded bootstrap data
510
- * @throws Error if app root cannot be found or generated files are missing
511
- */
512
- export async function loadBootstrapData(appRoot?: string): Promise<BootstrapData> {
513
- const resolved = resolveAppRootOrThrow(appRoot)
514
-
515
371
  const { generatedDir } = resolved
516
372
 
517
373
  ensureMikroOrmV7GeneratedCacheCompatibility(resolved.appDir)
@@ -536,12 +392,10 @@ export async function loadBootstrapData(appRoot?: string): Promise<BootstrapData
536
392
  compileAndImport(path.join(generatedDir, 'modules.cli.generated.ts')),
537
393
  compileAndImport(path.join(generatedDir, 'entities.generated.ts')),
538
394
  compileAndImport(path.join(generatedDir, 'di.generated.ts')),
539
- loadOptionalGeneratedModule(path.join(generatedDir, 'search.generated.ts'), { searchModuleConfigs: [] }),
540
- loadOptionalGeneratedModule(path.join(generatedDir, 'command-loaders.generated.ts'), { commandLoaderEntries: [] }),
541
- loadOptionalGeneratedModule(path.join(generatedDir, 'command-interceptors.generated.ts'), {
542
- commandInterceptorEntries: [],
543
- }),
544
- loadOptionalGeneratedModule(path.join(generatedDir, 'workflows.generated.ts'), { allCodeWorkflows: [] }),
395
+ compileAndImport(path.join(generatedDir, 'search.generated.ts')).catch(() => ({ searchModuleConfigs: [] })),
396
+ compileAndImport(path.join(generatedDir, 'command-loaders.generated.ts')).catch(() => ({ commandLoaderEntries: [] })),
397
+ compileAndImport(path.join(generatedDir, 'command-interceptors.generated.ts')).catch(() => ({ commandInterceptorEntries: [] })),
398
+ compileAndImport(path.join(generatedDir, 'workflows.generated.ts')).catch(() => ({ allCodeWorkflows: [] })),
545
399
  ])
546
400
 
547
401
  return {
@@ -583,10 +437,8 @@ export async function loadBootstrapData(appRoot?: string): Promise<BootstrapData
583
437
  */
584
438
  export async function bootstrapFromAppRoot(appRoot?: string): Promise<BootstrapData> {
585
439
  const { createBootstrap, waitForAsyncRegistration } = await import('./factory.js')
586
- const resolved = resolveAppRootOrThrow(appRoot)
587
- const data = await loadBootstrapData(resolved.appDir)
588
- const appDiRegistrar = await loadAppDiRegistrar(resolved.appDir)
589
- const bootstrap = createBootstrap(data, appDiRegistrar ? { appDiRegistrar } : {})
440
+ const data = await loadBootstrapData(appRoot)
441
+ const bootstrap = createBootstrap(data)
590
442
  bootstrap()
591
443
  // In CLI context, wait for async registrations (UI widgets, search configs, etc.)
592
444
  await waitForAsyncRegistration()
@@ -7,10 +7,6 @@ import {
7
7
  } from '../command-interceptor-runner'
8
8
  import type { CommandInterceptor, CommandInterceptorContext, CommandInterceptorUndoContext } from '../command-interceptor'
9
9
  import { createLogger } from '@open-mercato/shared/lib/logger'
10
- import {
11
- applyAclFeatureOverrides,
12
- resetModuleContractOverridesForTests,
13
- } from '@open-mercato/shared/modules/overrides'
14
10
 
15
11
  jest.mock('@open-mercato/shared/lib/logger', () => {
16
12
  const mocked = {
@@ -63,10 +59,6 @@ describe('matchesCommandPattern', () => {
63
59
  })
64
60
 
65
61
  describe('runCommandInterceptorsBefore', () => {
66
- afterEach(() => {
67
- resetModuleContractOverridesForTests()
68
- })
69
-
70
62
  it('returns ok when no interceptors match', async () => {
71
63
  const interceptor = makeInterceptor({
72
64
  id: 'i1',
@@ -166,26 +158,6 @@ describe('runCommandInterceptorsBefore', () => {
166
158
  expect(withWildcard.ok).toBe(false)
167
159
  expect(withWildcard.error?.message).toBe('Blocked by wildcard')
168
160
  })
169
-
170
- it('does not run an interceptor gated by a nulled ACL feature', async () => {
171
- applyAclFeatureOverrides({ 'premium.audit': null })
172
- const interceptor = makeInterceptor({
173
- id: 'i1',
174
- features: ['premium.audit'],
175
- beforeExecute: jest.fn().mockResolvedValue({ ok: false }),
176
- })
177
-
178
- const result = await runCommandInterceptorsBefore(
179
- [interceptor],
180
- 'customers.create-person',
181
- {},
182
- baseContext,
183
- ['*', 'premium.audit'],
184
- )
185
-
186
- expect(result.ok).toBe(true)
187
- expect(interceptor.beforeExecute).not.toHaveBeenCalled()
188
- })
189
161
  })
190
162
 
191
163
  describe('runCommandInterceptorsAfter', () => {
@@ -31,7 +31,6 @@ import {
31
31
  } from './command-interceptor-runner'
32
32
  import type { CommandInterceptorContext } from './command-interceptor'
33
33
  import { CommandInterceptorError } from './errors'
34
- import { isReadProjectionAlwaysConsistent } from '@open-mercato/shared/lib/data/consistency'
35
34
  import { createLogger } from '../logger'
36
35
 
37
36
  const logger = createLogger('shared').child({ component: 'commands' })
@@ -698,10 +697,7 @@ export class CommandBus {
698
697
  try {
699
698
  const dataEngine = (container.resolve('dataEngine') as DataEngine)
700
699
  await dataEngine.flushOrmEntityChanges(suppress)
701
- } catch (error) {
702
- if (isReadProjectionAlwaysConsistent()) {
703
- throw error
704
- }
700
+ } catch {
705
701
  // best-effort: failures should not block command execution
706
702
  }
707
703
  }
@@ -3,7 +3,7 @@ import type {
3
3
  CommandInterceptorContext,
4
4
  CommandInterceptorUndoContext,
5
5
  } from './command-interceptor'
6
- import { authorizeFeatures } from '../../security/featurePolicy'
6
+ import { hasAllFeatures } from '../../security/features'
7
7
  import { createLogger } from '../logger'
8
8
 
9
9
  const logger = createLogger('shared').child({ component: 'commands' })
@@ -33,7 +33,7 @@ function collectMatching(
33
33
  ): CommandInterceptor[] {
34
34
  return interceptors
35
35
  .filter((i) => matchesCommandPattern(i.targetCommand, commandId))
36
- .filter((i) => authorizeFeatures(i.features ?? [], { grantedFeatures: userFeatures }))
36
+ .filter((i) => hasAllFeatures(userFeatures, i.features))
37
37
  .sort((a, b) => (a.priority ?? 50) - (b.priority ?? 50))
38
38
  }
39
39