@open-mercato/shared 0.6.7-develop.6862.1.c11a64ce0a → 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 -256
  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,171 +0,0 @@
1
- // Regression coverage for the CRUD list cache key missing a caller-identity
2
- // segment: two callers in the same tenant/org scope requesting the identical
3
- // URL must NOT share cache entries, because list payloads can vary per caller —
4
- // buildFilters may narrow by ctx.auth (e.g. ?mine=true on staff time-projects,
5
- // the isSuperAdmin branch on scheduler jobs), before-interceptor query rewrites
6
- // are feature-gated per user, and afterList/after-interceptor output is
7
- // embedded in the stored payload.
8
-
9
- jest.mock('@open-mercato/cache', () => ({
10
- runWithCacheTenant: async (_tenantId: string | null, fn: () => Promise<unknown>) => fn(),
11
- }), { virtual: true })
12
-
13
- import { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'
14
- import { registerApiInterceptors } from '@open-mercato/shared/lib/crud/interceptor-registry'
15
- import { z } from 'zod'
16
-
17
- const defaultOrganizationId = 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa'
18
- const defaultTenantId = '123e4567-e89b-12d3-a456-426614174000'
19
-
20
- let mockAuthSub = 'user-a'
21
- let mockAuthKeyId: string | null = null
22
-
23
- const em = {}
24
-
25
- // Returns rows derived from the per-caller filter buildFilters produced, so a
26
- // cross-user cache hit is observable as one user's rows in another's response.
27
- const queryEngine = {
28
- query: jest.fn(async (_entity: string, opts: Record<string, any>) => {
29
- const owner = opts?.filters?.owner_user_id?.$eq ?? 'unfiltered'
30
- return {
31
- items: [{ id: `todo-of-${owner}`, title: `Owned by ${owner}`, organization_id: defaultOrganizationId, tenant_id: defaultTenantId }],
32
- total: 1,
33
- }
34
- }),
35
- }
36
-
37
- const store = new Map<string, unknown>()
38
- const cache = {
39
- get: jest.fn(async (key: string) => (store.has(key) ? store.get(key) : null)),
40
- set: jest.fn(async (key: string, value: unknown) => { store.set(key, value) }),
41
- delete: jest.fn(async (key: string) => { store.delete(key) }),
42
- deleteByTags: jest.fn(async () => 0),
43
- }
44
-
45
- const accessLogService = { log: jest.fn(async () => {}) }
46
-
47
- jest.mock('@open-mercato/shared/lib/di/container', () => ({
48
- createRequestContainer: async () => ({
49
- resolve: (name: string) => ({
50
- em,
51
- queryEngine,
52
- cache,
53
- accessLogService,
54
- } as any)[name],
55
- }),
56
- }))
57
-
58
- jest.mock('@open-mercato/shared/lib/auth/server', () => {
59
- const buildAuth = () => ({
60
- sub: mockAuthSub,
61
- keyId: mockAuthKeyId ?? undefined,
62
- orgId: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa',
63
- tenantId: '123e4567-e89b-12d3-a456-426614174000',
64
- roles: ['admin'],
65
- })
66
- return {
67
- getAuthFromCookies: async () => buildAuth(),
68
- getAuthFromRequest: async () => buildAuth(),
69
- }
70
- })
71
-
72
- jest.mock('@open-mercato/core/modules/directory/utils/organizationScope', () => ({
73
- resolveOrganizationScopeForRequest: jest.fn(async () => ({
74
- selectedId: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa',
75
- filterIds: ['aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa'],
76
- allowedIds: ['aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa'],
77
- tenantId: '123e4567-e89b-12d3-a456-426614174000',
78
- })),
79
- }))
80
-
81
- jest.mock('@open-mercato/core/modules/entities/lib/helpers', () => ({
82
- setRecordCustomFields: jest.fn(async () => {}),
83
- }))
84
-
85
- class Todo {}
86
-
87
- const querySchema = z.object({
88
- page: z.coerce.number().default(1),
89
- pageSize: z.coerce.number().default(50),
90
- sortField: z.string().default('id'),
91
- sortDir: z.enum(['asc', 'desc']).default('asc'),
92
- })
93
-
94
- const route = makeCrudRoute({
95
- metadata: { GET: { requireAuth: true } },
96
- orm: { entity: Todo, idField: 'id', orgField: 'organizationId', tenantField: 'tenantId', softDeleteField: 'deletedAt' },
97
- indexer: { entityType: 'example.todo' },
98
- list: {
99
- schema: querySchema,
100
- entityId: 'example.todo',
101
- fields: ['id', 'title'],
102
- sortFieldMap: { id: 'id', title: 'title' },
103
- buildFilters: (_query: any, ctx: any) => ({ owner_user_id: { $eq: ctx.auth?.sub ?? null } } as any),
104
- transformItem: (i: any) => ({ id: i.id, title: i.title }),
105
- },
106
- })
107
-
108
- const url = 'http://x/api/example/todos?page=1&pageSize=10&sortField=id&sortDir=asc'
109
-
110
- describe('CRUD Factory — list cache is partitioned per caller identity', () => {
111
- const previousCacheFlag = process.env.ENABLE_CRUD_API_CACHE
112
-
113
- beforeAll(() => {
114
- process.env.ENABLE_CRUD_API_CACHE = 'true'
115
- })
116
-
117
- afterAll(() => {
118
- if (previousCacheFlag === undefined) delete process.env.ENABLE_CRUD_API_CACHE
119
- else process.env.ENABLE_CRUD_API_CACHE = previousCacheFlag
120
- })
121
-
122
- beforeEach(() => {
123
- jest.clearAllMocks()
124
- store.clear()
125
- mockAuthSub = 'user-a'
126
- mockAuthKeyId = null
127
- registerApiInterceptors([])
128
- })
129
-
130
- it('caches per user and still serves same-user repeat requests from cache', async () => {
131
- const first = await route.GET(new Request(url))
132
- expect(first.status).toBe(200)
133
- expect(first.headers.get('x-om-cache')).toBe('miss')
134
- expect((await first.json()).items[0].id).toBe('todo-of-user-a')
135
-
136
- const second = await route.GET(new Request(url))
137
- expect(second.headers.get('x-om-cache')).toBe('hit')
138
- expect((await second.json()).items[0].id).toBe('todo-of-user-a')
139
- expect(queryEngine.query).toHaveBeenCalledTimes(1)
140
- })
141
-
142
- it('does not serve one user\'s cached rows to another user on the identical URL', async () => {
143
- mockAuthSub = 'user-a'
144
- const aRes = await route.GET(new Request(url))
145
- expect((await aRes.json()).items[0].id).toBe('todo-of-user-a')
146
- expect(store.size).toBe(1)
147
-
148
- mockAuthSub = 'user-b'
149
- const bRes = await route.GET(new Request(url))
150
- expect(bRes.headers.get('x-om-cache')).toBe('miss')
151
- expect((await bRes.json()).items[0].id).toBe('todo-of-user-b')
152
- expect(store.size).toBe(2)
153
-
154
- mockAuthSub = 'user-a'
155
- const aAgain = await route.GET(new Request(url))
156
- expect(aAgain.headers.get('x-om-cache')).toBe('hit')
157
- expect((await aAgain.json()).items[0].id).toBe('todo-of-user-a')
158
- })
159
-
160
- it('keys every entry with an identity segment, preferring the API key id over the user id', async () => {
161
- await route.GET(new Request(url))
162
- const userKey = Array.from(store.keys())[0] as string
163
- expect(userKey).toContain('user:user-a')
164
-
165
- store.clear()
166
- mockAuthKeyId = 'api-key-9'
167
- await route.GET(new Request(url))
168
- const apiKeyKey = Array.from(store.keys())[0] as string
169
- expect(apiKeyKey).toContain('user:api-key-9')
170
- })
171
- })
@@ -1,38 +0,0 @@
1
- import {
2
- __resetAlwaysConsistentCacheForTests,
3
- isReadProjectionAlwaysConsistent,
4
- parseAlwaysConsistentEnv,
5
- } from '../consistency'
6
-
7
- describe('read projection consistency flag', () => {
8
- const originalEnv = process.env.OM_CACHE_SAFETY_ALWAYS_CONSISTENT
9
-
10
- afterEach(() => {
11
- if (originalEnv === undefined) {
12
- delete process.env.OM_CACHE_SAFETY_ALWAYS_CONSISTENT
13
- } else {
14
- process.env.OM_CACHE_SAFETY_ALWAYS_CONSISTENT = originalEnv
15
- }
16
- __resetAlwaysConsistentCacheForTests()
17
- })
18
-
19
- it.each([undefined, null, '', ' ', 'off', 'false', '0', 'no', 'disabled', 'none', 'unexpected'])(
20
- 'parses %p as OFF',
21
- (raw) => {
22
- expect(parseAlwaysConsistentEnv(raw)).toBe(false)
23
- },
24
- )
25
-
26
- it.each(['on', 'true', '1', 'yes', 'enabled'])('parses %p as ON', (raw) => {
27
- expect(parseAlwaysConsistentEnv(raw)).toBe(true)
28
- })
29
-
30
- it('memoizes the env value until reset for tests', () => {
31
- process.env.OM_CACHE_SAFETY_ALWAYS_CONSISTENT = 'on'
32
- expect(isReadProjectionAlwaysConsistent()).toBe(true)
33
- process.env.OM_CACHE_SAFETY_ALWAYS_CONSISTENT = 'off'
34
- expect(isReadProjectionAlwaysConsistent()).toBe(true)
35
- __resetAlwaysConsistentCacheForTests()
36
- expect(isReadProjectionAlwaysConsistent()).toBe(false)
37
- })
38
- })
@@ -1,17 +0,0 @@
1
- import { parseBooleanWithDefault } from '../boolean'
2
-
3
- let alwaysConsistentFlag: boolean | null = null
4
-
5
- export function parseAlwaysConsistentEnv(raw: string | undefined | null): boolean {
6
- return parseBooleanWithDefault(raw, false)
7
- }
8
-
9
- export function isReadProjectionAlwaysConsistent(): boolean {
10
- if (alwaysConsistentFlag !== null) return alwaysConsistentFlag
11
- alwaysConsistentFlag = parseAlwaysConsistentEnv(process.env.OM_CACHE_SAFETY_ALWAYS_CONSISTENT)
12
- return alwaysConsistentFlag
13
- }
14
-
15
- export function __resetAlwaysConsistentCacheForTests(): void {
16
- alwaysConsistentFlag = null
17
- }
@@ -1,38 +0,0 @@
1
- import { listEntityMetadata, listEntityMetadataFromRegistry } from '../entityMetadata'
2
-
3
- const personMeta = { className: 'Person', tableName: 'people' }
4
- const orderMeta = { className: 'Order', tableName: 'orders' }
5
-
6
- describe('listEntityMetadataFromRegistry', () => {
7
- it('reads the MikroORM v7 Map returned by getAll()', () => {
8
- const registry = { getAll: () => new Map([[Object, personMeta], [Array, orderMeta]]) }
9
- expect(listEntityMetadataFromRegistry(registry)).toEqual([personMeta, orderMeta])
10
- })
11
-
12
- it('reads the MikroORM v6 dictionary returned by getAll()', () => {
13
- const registry = { getAll: () => ({ Person: personMeta, Order: orderMeta }) }
14
- expect(listEntityMetadataFromRegistry(registry)).toEqual([personMeta, orderMeta])
15
- })
16
-
17
- it('falls back to the metadata property when getAll() yields nothing', () => {
18
- const registry = { getAll: () => new Map(), metadata: { Person: personMeta } }
19
- expect(listEntityMetadataFromRegistry(registry)).toEqual([personMeta])
20
- })
21
-
22
- it('returns an empty array for a missing registry', () => {
23
- expect(listEntityMetadataFromRegistry(null)).toEqual([])
24
- expect(listEntityMetadataFromRegistry({})).toEqual([])
25
- })
26
- })
27
-
28
- describe('listEntityMetadata', () => {
29
- it('resolves the registry through getMetadata()', () => {
30
- const em = { getMetadata: () => ({ getAll: () => new Map([[Object, personMeta]]) }) }
31
- expect(listEntityMetadata(em)).toEqual([personMeta])
32
- })
33
-
34
- it('returns an empty array when the source exposes no registry', () => {
35
- expect(listEntityMetadata({})).toEqual([])
36
- expect(listEntityMetadata(undefined)).toEqual([])
37
- })
38
- })
@@ -1,48 +0,0 @@
1
- import { isTransientDbError, isUniqueViolation } from '../pg-errors'
2
-
3
- describe('isTransientDbError', () => {
4
- it('is true for the max_connections SQLSTATE', () => {
5
- expect(isTransientDbError({ code: '53300' })).toBe(true)
6
- })
7
-
8
- it('is true for connection-family SQLSTATEs', () => {
9
- for (const code of ['53400', '57P01', '57P02', '57P03', '08000', '08001', '08003', '08006']) {
10
- expect(isTransientDbError({ code })).toBe(true)
11
- }
12
- })
13
-
14
- it('is true for driver/ORM-wrapped connection messages that drop the SQLSTATE', () => {
15
- expect(isTransientDbError(new Error('sorry, too many clients already'))).toBe(true)
16
- expect(isTransientDbError(new Error('Knex: Timeout acquiring a connection. The pool is probably full.'))).toBe(true)
17
- expect(isTransientDbError(new Error('Unable to acquire a connection'))).toBe(true)
18
- expect(isTransientDbError(new Error('Connection terminated unexpectedly'))).toBe(true)
19
- expect(isTransientDbError(new Error('the database system is starting up'))).toBe(true)
20
- })
21
-
22
- it('is false for bare socket errors with no Postgres signal (avoids false positives)', () => {
23
- // Generic socket failures can come from any outbound connection (HTTP, cache,
24
- // queue), so they must NOT be attributed to the database.
25
- expect(isTransientDbError({ code: 'ECONNREFUSED' })).toBe(false)
26
- expect(isTransientDbError({ code: 'ETIMEDOUT' })).toBe(false)
27
- expect(isTransientDbError(Object.assign(new Error('read ECONNRESET'), { code: 'ECONNRESET' }))).toBe(false)
28
- })
29
-
30
- it('is false for a unique violation and other query-level errors', () => {
31
- expect(isTransientDbError({ code: '23505' })).toBe(false)
32
- expect(isTransientDbError({ code: '40P01' })).toBe(false) // deadlock — not a connection failure
33
- expect(isTransientDbError({ code: '40001' })).toBe(false) // serialization — not a connection failure
34
- })
35
-
36
- it('is false for non-DB and empty errors', () => {
37
- expect(isTransientDbError(new Error('something unrelated broke'))).toBe(false)
38
- expect(isTransientDbError(null)).toBe(false)
39
- expect(isTransientDbError(undefined)).toBe(false)
40
- expect(isTransientDbError('a string')).toBe(false)
41
- })
42
-
43
- it('does not overlap with isUniqueViolation', () => {
44
- const uniqueErr = { code: '23505', message: 'duplicate key value violates unique constraint' }
45
- expect(isUniqueViolation(uniqueErr)).toBe(true)
46
- expect(isTransientDbError(uniqueErr)).toBe(false)
47
- })
48
- })
@@ -1,43 +0,0 @@
1
- import type { EntityMetadata } from '@mikro-orm/core'
2
-
3
- type MetadataRegistryLike = {
4
- getAll?: () => unknown
5
- metadata?: unknown
6
- }
7
-
8
- type MetadataHolder = {
9
- getMetadata?: () => unknown
10
- }
11
-
12
- /**
13
- * MikroORM v7 returns a `Map` from `MetadataStorage.getAll()`; v6 returned a plain
14
- * dictionary. `Object.values(someMap)` evaluates to `[]`, so a v6-era read of the
15
- * registry silently reports zero entities instead of failing loudly.
16
- */
17
- function toMetadataList(value: unknown): EntityMetadata[] {
18
- if (!value) return []
19
- if (value instanceof Map) return Array.from(value.values()) as EntityMetadata[]
20
- if (Array.isArray(value)) return value as EntityMetadata[]
21
- if (typeof value === 'object') return Object.values(value as Record<string, EntityMetadata>)
22
- return []
23
- }
24
-
25
- export function listEntityMetadataFromRegistry(registry: unknown): EntityMetadata[] {
26
- const source = registry as MetadataRegistryLike | null | undefined
27
- if (!source) return []
28
- if (typeof source.getAll === 'function') {
29
- const entries = toMetadataList(source.getAll())
30
- if (entries.length) return entries
31
- }
32
- return toMetadataList(source.metadata)
33
- }
34
-
35
- /**
36
- * List every discovered entity metadata from an `EntityManager` or `MikroORM` instance.
37
- * Returns an empty array when the source exposes no metadata registry.
38
- */
39
- export function listEntityMetadata(source: unknown): EntityMetadata[] {
40
- const holder = source as MetadataHolder | null | undefined
41
- if (!holder || typeof holder.getMetadata !== 'function') return []
42
- return listEntityMetadataFromRegistry(holder.getMetadata())
43
- }
@@ -1,155 +0,0 @@
1
- // The app-level DI override module (`@/di`) is OPTIONAL: most apps ship no
2
- // src/di.ts, and packages contexts cannot resolve the alias at all. Its
3
- // absence must not produce warnings — only real load/register failures do
4
- // (covered in container-app-di.test.ts). This file deliberately does NOT
5
- // mock '@/di', so the import fails with module-not-found, exercising the
6
- // quiet branch.
7
-
8
- jest.mock(
9
- '@open-mercato/shared/lib/db/mikro',
10
- () => {
11
- const baseEm: any = {
12
- fork: () => baseEm,
13
- getRepository: () => ({ find: async () => [], findOne: async () => null }),
14
- }
15
- return {
16
- __esModule: true,
17
- getOrm: async () => ({ em: baseEm }),
18
- getOrmEntities: () => [],
19
- registerOrmEntities: () => {},
20
- }
21
- },
22
- { virtual: false },
23
- )
24
-
25
- jest.mock(
26
- '@mikro-orm/core',
27
- () => ({
28
- __esModule: true,
29
- RequestContext: { getEntityManager: () => null },
30
- }),
31
- { virtual: true },
32
- )
33
-
34
- jest.mock(
35
- '@open-mercato/shared/lib/query/engine',
36
- () => ({ __esModule: true, BasicQueryEngine: class {} }),
37
- { virtual: false },
38
- )
39
-
40
- jest.mock(
41
- '@open-mercato/shared/lib/data/engine',
42
- () => ({ __esModule: true, DefaultDataEngine: class {} }),
43
- { virtual: false },
44
- )
45
-
46
- jest.mock(
47
- '@open-mercato/shared/lib/commands',
48
- () => ({
49
- __esModule: true,
50
- commandRegistry: {},
51
- CommandBus: class { constructor() {} },
52
- }),
53
- { virtual: false },
54
- )
55
-
56
- jest.mock(
57
- '@open-mercato/shared/modules/overrides',
58
- () => ({
59
- __esModule: true,
60
- applyDiOverridesToContainer: () => {},
61
- }),
62
- { virtual: false },
63
- )
64
-
65
- // Keep this suite hermetic: without this mock the REAL core bootstrap module
66
- // loads (and runs its module-level side effects) whenever generated files
67
- // exist on disk, polluting globalThis state shared with sibling test files.
68
- jest.mock(
69
- '@open-mercato/core/bootstrap',
70
- () => ({
71
- __esModule: true,
72
- bootstrap: async () => {},
73
- }),
74
- { virtual: true },
75
- )
76
-
77
- jest.mock(
78
- '@open-mercato/shared/lib/encryption/subscriber',
79
- () => ({
80
- __esModule: true,
81
- registerTenantEncryptionSubscriber: () => {},
82
- }),
83
- { virtual: true },
84
- )
85
-
86
- const mockWarn = jest.fn()
87
- const mockDebug = jest.fn()
88
- jest.mock('../../logger', () => ({
89
- createLogger: () => ({
90
- debug: (...args: unknown[]) => mockDebug(...args),
91
- info: jest.fn(),
92
- warn: (...args: unknown[]) => mockWarn(...args),
93
- error: jest.fn(),
94
- child() { return this },
95
- }),
96
- }))
97
-
98
- const {
99
- createRequestContainer,
100
- registerAppDiRegistrar,
101
- registerDiRegistrars,
102
- resetBootstrapCache,
103
- } = require('@open-mercato/shared/lib/di/container')
104
-
105
- describe('app-level DI override hook when @/di is absent', () => {
106
- beforeEach(() => {
107
- resetBootstrapCache()
108
- registerAppDiRegistrar(null)
109
- registerDiRegistrars([])
110
- mockWarn.mockClear()
111
- mockDebug.mockClear()
112
- })
113
-
114
- it('creates the container without warning', async () => {
115
- const firstContainer = await createRequestContainer()
116
- const secondContainer = await createRequestContainer()
117
- expect(firstContainer).toBeDefined()
118
- expect(secondContainer).toBeDefined()
119
- expect(mockWarn).not.toHaveBeenCalled()
120
- })
121
-
122
- // Worker and CLI processes create one request container per job against built package
123
- // output, where the app's `@/` alias does not exist. Retrying the doomed import per
124
- // container repeats a failed module resolution — and its log line — once per job.
125
- it('stops retrying the unresolvable @/di import after the first container', async () => {
126
- await createRequestContainer()
127
- await createRequestContainer()
128
- await createRequestContainer()
129
-
130
- const unresolvableLogs = mockDebug.mock.calls.filter(
131
- ([message]) => message === 'App-level DI override module (@/di) not resolvable; skipping',
132
- )
133
- expect(unresolvableLogs).toHaveLength(1)
134
- })
135
-
136
- it('warns once when @/di fails because a nested app alias is missing', async () => {
137
- const nestedAliasError = Object.assign(new Error("Cannot find module '@/di/helpers'"), {
138
- code: 'MODULE_NOT_FOUND',
139
- })
140
- jest.doMock('@/di', () => {
141
- throw nestedAliasError
142
- }, { virtual: true })
143
-
144
- const firstContainer = await createRequestContainer()
145
- const secondContainer = await createRequestContainer()
146
-
147
- expect(firstContainer).toBeDefined()
148
- expect(secondContainer).toBeDefined()
149
- expect(mockWarn).toHaveBeenCalledTimes(1)
150
- expect(mockWarn).toHaveBeenCalledWith(
151
- 'App-level DI override module (@/di) failed to load; its registrations are skipped',
152
- { err: nestedAliasError },
153
- )
154
- })
155
- })
@@ -1,173 +0,0 @@
1
- // Tests for the app-level DI override hook (`src/di.ts` → `@/di`) in
2
- // `createRequestContainer()`. The hook used to swallow EVERY failure with
3
- // bare `catch {}` blocks, so a src/di.ts whose register() threw — or a @/di
4
- // module that failed to load for any reason other than being absent — was
5
- // skipped without a trace and the documented override point silently did
6
- // nothing. Absence of the optional module must stay quiet; real failures
7
- // must be logged.
8
-
9
- import { asValue } from 'awilix'
10
-
11
- jest.mock(
12
- '@open-mercato/shared/lib/db/mikro',
13
- () => {
14
- const baseEm: any = {
15
- fork: () => baseEm,
16
- getRepository: () => ({ find: async () => [], findOne: async () => null }),
17
- }
18
- return {
19
- __esModule: true,
20
- getOrm: async () => ({ em: baseEm }),
21
- getOrmEntities: () => [],
22
- registerOrmEntities: () => {},
23
- }
24
- },
25
- { virtual: false },
26
- )
27
-
28
- jest.mock(
29
- '@mikro-orm/core',
30
- () => ({
31
- __esModule: true,
32
- RequestContext: { getEntityManager: () => null },
33
- }),
34
- { virtual: true },
35
- )
36
-
37
- jest.mock(
38
- '@open-mercato/shared/lib/query/engine',
39
- () => ({ __esModule: true, BasicQueryEngine: class {} }),
40
- { virtual: false },
41
- )
42
-
43
- jest.mock(
44
- '@open-mercato/shared/lib/data/engine',
45
- () => ({ __esModule: true, DefaultDataEngine: class {} }),
46
- { virtual: false },
47
- )
48
-
49
- jest.mock(
50
- '@open-mercato/shared/lib/commands',
51
- () => ({
52
- __esModule: true,
53
- commandRegistry: {},
54
- CommandBus: class { constructor() {} },
55
- }),
56
- { virtual: false },
57
- )
58
-
59
- jest.mock(
60
- '@open-mercato/shared/modules/overrides',
61
- () => ({
62
- __esModule: true,
63
- applyDiOverridesToContainer: () => {},
64
- }),
65
- { virtual: false },
66
- )
67
-
68
- // Keep this suite hermetic: without this mock the REAL core bootstrap module
69
- // loads (and runs its module-level side effects) whenever generated files
70
- // exist on disk, polluting globalThis state shared with sibling test files.
71
- jest.mock(
72
- '@open-mercato/core/bootstrap',
73
- () => ({
74
- __esModule: true,
75
- bootstrap: async () => {},
76
- }),
77
- { virtual: true },
78
- )
79
-
80
- jest.mock(
81
- '@open-mercato/shared/lib/encryption/subscriber',
82
- () => ({
83
- __esModule: true,
84
- registerTenantEncryptionSubscriber: () => {},
85
- }),
86
- { virtual: true },
87
- )
88
-
89
- const mockWarn = jest.fn()
90
- jest.mock('../../logger', () => ({
91
- createLogger: () => ({
92
- debug: jest.fn(),
93
- info: jest.fn(),
94
- warn: (...args: unknown[]) => mockWarn(...args),
95
- error: jest.fn(),
96
- child() { return this },
97
- }),
98
- }))
99
-
100
- const mockAppDiRegister = jest.fn()
101
- jest.mock(
102
- '@/di',
103
- () => ({
104
- __esModule: true,
105
- register: (...args: unknown[]) => mockAppDiRegister(...args),
106
- }),
107
- { virtual: true },
108
- )
109
-
110
- const {
111
- createRequestContainer,
112
- registerAppDiRegistrar,
113
- registerDiRegistrars,
114
- resetBootstrapCache,
115
- } = require('@open-mercato/shared/lib/di/container')
116
-
117
- describe('app-level DI override hook (@/di)', () => {
118
- beforeEach(() => {
119
- resetBootstrapCache()
120
- registerAppDiRegistrar(null)
121
- registerDiRegistrars([])
122
- mockWarn.mockClear()
123
- mockAppDiRegister.mockReset()
124
- })
125
-
126
- it('calls register() with the request container and applies its registrations', async () => {
127
- mockAppDiRegister.mockImplementation((container: any) => {
128
- container.register({ appOverrideProbe: asValue('from-app-di') })
129
- })
130
- const container = await createRequestContainer()
131
- expect(mockAppDiRegister).toHaveBeenCalledTimes(1)
132
- expect(mockAppDiRegister).toHaveBeenCalledWith(container)
133
- expect(container.resolve('appOverrideProbe')).toBe('from-app-di')
134
- expect(mockWarn).not.toHaveBeenCalled()
135
- })
136
-
137
- it('awaits an async register()', async () => {
138
- mockAppDiRegister.mockImplementation(async (container: any) => {
139
- await Promise.resolve()
140
- container.register({ appOverrideProbe: asValue('async-app-di') })
141
- })
142
- const container = await createRequestContainer()
143
- expect(container.resolve('appOverrideProbe')).toBe('async-app-di')
144
- expect(mockWarn).not.toHaveBeenCalled()
145
- })
146
-
147
- it('warns instead of silently swallowing when register() throws', async () => {
148
- mockAppDiRegister.mockImplementation(() => {
149
- throw new Error('boom from app di')
150
- })
151
- const firstContainer = await createRequestContainer()
152
- const secondContainer = await createRequestContainer()
153
- expect(firstContainer).toBeDefined()
154
- expect(secondContainer).toBeDefined()
155
- expect(mockAppDiRegister).toHaveBeenCalledTimes(2)
156
- expect(mockWarn).toHaveBeenCalledTimes(1)
157
- const [message, fields] = mockWarn.mock.calls[0]
158
- expect(String(message)).toContain('register()')
159
- expect((fields as { err: Error }).err.message).toBe('boom from app di')
160
- })
161
-
162
- it('warns when an async register() rejects', async () => {
163
- mockAppDiRegister.mockImplementation(async () => {
164
- throw new Error('async boom from app di')
165
- })
166
- const firstContainer = await createRequestContainer()
167
- const secondContainer = await createRequestContainer()
168
- expect(firstContainer).toBeDefined()
169
- expect(secondContainer).toBeDefined()
170
- expect(mockAppDiRegister).toHaveBeenCalledTimes(2)
171
- expect(mockWarn).toHaveBeenCalledTimes(1)
172
- })
173
- })