@open-mercato/shared 0.6.7 → 0.6.8-canary.320.1.bf889f49e4

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 (224) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +62 -6
  3. package/build.mjs +2 -4
  4. package/dist/lib/ai/safety-identifier.js +32 -0
  5. package/dist/lib/ai/safety-identifier.js.map +7 -0
  6. package/dist/lib/auth/organizationScope.js +34 -0
  7. package/dist/lib/auth/organizationScope.js.map +7 -0
  8. package/dist/lib/auth/server.js +15 -2
  9. package/dist/lib/auth/server.js.map +2 -2
  10. package/dist/lib/bootstrap/clientOnlyModules.js +55 -0
  11. package/dist/lib/bootstrap/clientOnlyModules.js.map +7 -0
  12. package/dist/lib/bootstrap/dynamicLoader.js +190 -70
  13. package/dist/lib/bootstrap/dynamicLoader.js.map +2 -2
  14. package/dist/lib/bootstrap/factory.js +18 -13
  15. package/dist/lib/bootstrap/factory.js.map +2 -2
  16. package/dist/lib/commands/command-bus.js +9 -3
  17. package/dist/lib/commands/command-bus.js.map +2 -2
  18. package/dist/lib/commands/command-interceptor-runner.js +9 -4
  19. package/dist/lib/commands/command-interceptor-runner.js.map +2 -2
  20. package/dist/lib/commands/errors.js +25 -4
  21. package/dist/lib/commands/errors.js.map +2 -2
  22. package/dist/lib/commands/index.js +7 -1
  23. package/dist/lib/commands/index.js.map +2 -2
  24. package/dist/lib/crud/enricher-runner.js +2 -11
  25. package/dist/lib/crud/enricher-runner.js.map +2 -2
  26. package/dist/lib/crud/factory.js +104 -23
  27. package/dist/lib/crud/factory.js.map +2 -2
  28. package/dist/lib/crud/interceptor-runner.js +2 -2
  29. package/dist/lib/crud/interceptor-runner.js.map +2 -2
  30. package/dist/lib/crud/mutation-guard-registry.js +2 -2
  31. package/dist/lib/crud/mutation-guard-registry.js.map +2 -2
  32. package/dist/lib/crud/types.js +4 -0
  33. package/dist/lib/crud/types.js.map +3 -3
  34. package/dist/lib/data/consistency.js +19 -0
  35. package/dist/lib/data/consistency.js.map +7 -0
  36. package/dist/lib/data/engine.js +37 -11
  37. package/dist/lib/data/engine.js.map +2 -2
  38. package/dist/lib/db/entityMetadata.js +26 -0
  39. package/dist/lib/db/entityMetadata.js.map +7 -0
  40. package/dist/lib/db/pg-errors.js +42 -0
  41. package/dist/lib/db/pg-errors.js.map +2 -2
  42. package/dist/lib/di/container.js +48 -6
  43. package/dist/lib/di/container.js.map +2 -2
  44. package/dist/lib/email/send.js +56 -0
  45. package/dist/lib/email/send.js.map +2 -2
  46. package/dist/lib/encryption/find.js +22 -0
  47. package/dist/lib/encryption/find.js.map +2 -2
  48. package/dist/lib/encryption/likeFilterWarning.js +66 -0
  49. package/dist/lib/encryption/likeFilterWarning.js.map +7 -0
  50. package/dist/lib/encryption/subscriber.js +2 -10
  51. package/dist/lib/encryption/subscriber.js.map +2 -2
  52. package/dist/lib/encryption/tenantDataEncryptionService.js +52 -17
  53. package/dist/lib/encryption/tenantDataEncryptionService.js.map +2 -2
  54. package/dist/lib/i18n/config.js +1 -1
  55. package/dist/lib/i18n/config.js.map +2 -2
  56. package/dist/lib/i18n/dictionary-cache.js +5 -0
  57. package/dist/lib/i18n/dictionary-cache.js.map +2 -2
  58. package/dist/lib/i18n/server.js +4 -4
  59. package/dist/lib/i18n/server.js.map +3 -3
  60. package/dist/lib/logger/extension.js +29 -0
  61. package/dist/lib/logger/extension.js.map +7 -0
  62. package/dist/lib/logger/index.js +47 -3
  63. package/dist/lib/logger/index.js.map +3 -3
  64. package/dist/lib/modules/registry.js +36 -6
  65. package/dist/lib/modules/registry.js.map +2 -2
  66. package/dist/lib/modules/surfaceFingerprint.js +47 -0
  67. package/dist/lib/modules/surfaceFingerprint.js.map +7 -0
  68. package/dist/lib/query/ciphertext-search-warning.js +51 -0
  69. package/dist/lib/query/ciphertext-search-warning.js.map +7 -0
  70. package/dist/lib/query/engine.js +60 -43
  71. package/dist/lib/query/engine.js.map +2 -2
  72. package/dist/lib/query/types.js.map +1 -1
  73. package/dist/lib/redis/connection.js +7 -2
  74. package/dist/lib/redis/connection.js.map +2 -2
  75. package/dist/lib/search/auto-indexing.js +14 -0
  76. package/dist/lib/search/auto-indexing.js.map +7 -0
  77. package/dist/lib/search/availability.js +107 -0
  78. package/dist/lib/search/availability.js.map +7 -0
  79. package/dist/lib/search/config.js +61 -2
  80. package/dist/lib/search/config.js.map +2 -2
  81. package/dist/lib/search/tokenLookup.js +46 -0
  82. package/dist/lib/search/tokenLookup.js.map +7 -0
  83. package/dist/lib/search/tokenize.js +23 -11
  84. package/dist/lib/search/tokenize.js.map +2 -2
  85. package/dist/lib/telemetry/runtime.js +35 -0
  86. package/dist/lib/telemetry/runtime.js.map +7 -0
  87. package/dist/lib/version.js +1 -1
  88. package/dist/lib/version.js.map +1 -1
  89. package/dist/lib/webhooks/body.js +57 -0
  90. package/dist/lib/webhooks/body.js.map +7 -0
  91. package/dist/lib/webhooks/inbound-types.js +1 -0
  92. package/dist/lib/webhooks/inbound-types.js.map +7 -0
  93. package/dist/lib/webhooks/index.js +13 -1
  94. package/dist/lib/webhooks/index.js.map +2 -2
  95. package/dist/lib/webhooks/verify.js +12 -7
  96. package/dist/lib/webhooks/verify.js.map +2 -2
  97. package/dist/modules/analytics.js.map +2 -2
  98. package/dist/modules/overrides.js +50 -1
  99. package/dist/modules/overrides.js.map +2 -2
  100. package/dist/modules/payment_gateways/types.js +8 -1
  101. package/dist/modules/payment_gateways/types.js.map +2 -2
  102. package/dist/modules/registry.js +1 -0
  103. package/dist/modules/registry.js.map +2 -2
  104. package/dist/modules/widgets/extension-points.js +96 -0
  105. package/dist/modules/widgets/extension-points.js.map +7 -0
  106. package/dist/security/enabledModulesRegistry.js +56 -11
  107. package/dist/security/enabledModulesRegistry.js.map +2 -2
  108. package/dist/security/featurePolicy.js +62 -0
  109. package/dist/security/featurePolicy.js.map +7 -0
  110. package/jest.config.cjs +20 -10
  111. package/package.json +19 -9
  112. package/scripts/versionSource.cjs +65 -0
  113. package/src/lib/__tests__/versionSource.test.ts +64 -0
  114. package/src/lib/ai/__tests__/llm-provider-contract.test.ts +59 -0
  115. package/src/lib/ai/__tests__/safety-identifier.test.ts +71 -0
  116. package/src/lib/ai/llm-provider.ts +33 -0
  117. package/src/lib/ai/safety-identifier.ts +73 -0
  118. package/src/lib/auth/__tests__/organizationScope.test.ts +107 -0
  119. package/src/lib/auth/__tests__/server.test.ts +23 -0
  120. package/src/lib/auth/organizationScope.ts +65 -0
  121. package/src/lib/auth/server.ts +23 -2
  122. package/src/lib/bootstrap/__tests__/clientOnlyModules.test.ts +189 -0
  123. package/src/lib/bootstrap/__tests__/dynamicLoader.aliasResolver.test.ts +98 -0
  124. package/src/lib/bootstrap/__tests__/dynamicLoader.appDi.test.ts +211 -0
  125. package/src/lib/bootstrap/__tests__/dynamicLoader.commandInterceptors.test.ts +63 -8
  126. package/src/lib/bootstrap/__tests__/dynamicLoader.generatedCacheRecovery.test.ts +256 -0
  127. package/src/lib/bootstrap/__tests__/dynamicLoader.test.ts +126 -0
  128. package/src/lib/bootstrap/__tests__/dynamicLoader.tsconfig.test.ts +21 -1
  129. package/src/lib/bootstrap/__tests__/factory.test.ts +78 -0
  130. package/src/lib/bootstrap/clientOnlyModules.ts +85 -0
  131. package/src/lib/bootstrap/dynamicLoader.ts +346 -96
  132. package/src/lib/bootstrap/factory.ts +21 -15
  133. package/src/lib/bootstrap/types.ts +6 -0
  134. package/src/lib/commands/__tests__/command-bus.test.ts +63 -0
  135. package/src/lib/commands/__tests__/command-interceptor-error.test.ts +91 -1
  136. package/src/lib/commands/__tests__/command-interceptor-runner.test.ts +101 -0
  137. package/src/lib/commands/command-bus.ts +9 -3
  138. package/src/lib/commands/command-interceptor-runner.ts +27 -6
  139. package/src/lib/commands/command-interceptor.ts +8 -0
  140. package/src/lib/commands/errors.ts +67 -2
  141. package/src/lib/commands/index.ts +7 -1
  142. package/src/lib/crud/__tests__/crud-factory.cache-user-scope.test.ts +171 -0
  143. package/src/lib/crud/__tests__/crud-factory.test.ts +564 -0
  144. package/src/lib/crud/__tests__/mutation-guard-registry.test.ts +26 -0
  145. package/src/lib/crud/enricher-runner.ts +2 -11
  146. package/src/lib/crud/factory.ts +157 -22
  147. package/src/lib/crud/interceptor-runner.ts +2 -2
  148. package/src/lib/crud/mutation-guard-registry.ts +2 -2
  149. package/src/lib/crud/types.ts +3 -0
  150. package/src/lib/data/__tests__/consistency.test.ts +38 -0
  151. package/src/lib/data/__tests__/engine.bulk-suppress.test.ts +18 -3
  152. package/src/lib/data/consistency.ts +17 -0
  153. package/src/lib/data/engine.ts +50 -16
  154. package/src/lib/db/__tests__/entityMetadata.test.ts +38 -0
  155. package/src/lib/db/__tests__/pg-errors.test.ts +48 -0
  156. package/src/lib/db/entityMetadata.ts +43 -0
  157. package/src/lib/db/pg-errors.ts +58 -0
  158. package/src/lib/di/__tests__/container-app-di-absent.test.ts +155 -0
  159. package/src/lib/di/__tests__/container-app-di.test.ts +173 -0
  160. package/src/lib/di/__tests__/registrar-error-log.test.ts +154 -0
  161. package/src/lib/di/container.ts +72 -8
  162. package/src/lib/email/__tests__/send.test.ts +37 -1
  163. package/src/lib/email/send.ts +79 -0
  164. package/src/lib/encryption/__tests__/likeFilterWarning.test.ts +167 -0
  165. package/src/lib/encryption/__tests__/subscriber.test.ts +54 -2
  166. package/src/lib/encryption/__tests__/tenantDataEncryptionService.test.ts +58 -0
  167. package/src/lib/encryption/find.ts +22 -0
  168. package/src/lib/encryption/likeFilterWarning.ts +102 -0
  169. package/src/lib/encryption/subscriber.ts +2 -14
  170. package/src/lib/encryption/tenantDataEncryptionService.ts +64 -18
  171. package/src/lib/i18n/__tests__/config.test.ts +8 -0
  172. package/src/lib/i18n/__tests__/dictionary-cache.test.ts +23 -0
  173. package/src/lib/i18n/__tests__/server-dictionary-cache.test.ts +30 -0
  174. package/src/lib/i18n/__tests__/server-unbootstrapped.test.ts +29 -0
  175. package/src/lib/i18n/config.ts +2 -2
  176. package/src/lib/i18n/dictionary-cache.ts +5 -0
  177. package/src/lib/i18n/server.ts +6 -2
  178. package/src/lib/logger/__tests__/logger.test.ts +77 -0
  179. package/src/lib/logger/extension.ts +59 -0
  180. package/src/lib/logger/index.ts +59 -1
  181. package/src/lib/modules/__tests__/registry.test.ts +26 -0
  182. package/src/lib/modules/__tests__/surfaceFingerprint.test.ts +122 -0
  183. package/src/lib/modules/registry.ts +46 -7
  184. package/src/lib/modules/surfaceFingerprint.ts +87 -0
  185. package/src/lib/query/__tests__/ciphertext-search-warning.test.ts +178 -0
  186. package/src/lib/query/__tests__/engine.scope-and-or.test.ts +67 -0
  187. package/src/lib/query/__tests__/engine.test.ts +213 -13
  188. package/src/lib/query/ciphertext-search-warning.ts +106 -0
  189. package/src/lib/query/engine.ts +102 -61
  190. package/src/lib/query/types.ts +4 -5
  191. package/src/lib/redis/__tests__/connection.test.ts +16 -0
  192. package/src/lib/redis/connection.ts +10 -7
  193. package/src/lib/search/__tests__/availability.test.ts +211 -0
  194. package/src/lib/search/__tests__/config.test.ts +177 -0
  195. package/src/lib/search/__tests__/tokenLookup.test.ts +206 -0
  196. package/src/lib/search/__tests__/tokenize.test.ts +49 -0
  197. package/src/lib/search/auto-indexing.ts +22 -0
  198. package/src/lib/search/availability.ts +232 -0
  199. package/src/lib/search/config.ts +106 -8
  200. package/src/lib/search/tokenLookup.ts +133 -0
  201. package/src/lib/search/tokenize.ts +33 -11
  202. package/src/lib/telemetry/runtime.ts +72 -0
  203. package/src/lib/webhooks/__tests__/body.test.ts +95 -0
  204. package/src/lib/webhooks/__tests__/verify.test.ts +20 -1
  205. package/src/lib/webhooks/body.ts +70 -0
  206. package/src/lib/webhooks/inbound-types.ts +125 -0
  207. package/src/lib/webhooks/index.ts +19 -1
  208. package/src/lib/webhooks/verify.ts +17 -9
  209. package/src/modules/__tests__/nav-group-order-override.test.ts +183 -0
  210. package/src/modules/__tests__/registry.test.ts +10 -2
  211. package/src/modules/analytics.ts +7 -0
  212. package/src/modules/customer-auth.ts +1 -0
  213. package/src/modules/encryption.ts +3 -0
  214. package/src/modules/entities.ts +15 -0
  215. package/src/modules/navigation/backendChrome.ts +15 -0
  216. package/src/modules/overrides.ts +103 -0
  217. package/src/modules/payment_gateways/__tests__/types.test.ts +37 -0
  218. package/src/modules/payment_gateways/types.ts +41 -0
  219. package/src/modules/registry.ts +3 -0
  220. package/src/modules/widgets/__tests__/extension-points.test.ts +101 -0
  221. package/src/modules/widgets/extension-points.ts +524 -0
  222. package/src/security/__tests__/featurePolicy.test.ts +166 -0
  223. package/src/security/enabledModulesRegistry.ts +64 -12
  224. package/src/security/featurePolicy.ts +89 -0
@@ -0,0 +1,171 @@
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
+ })