@open-mercato/core 0.4.2-canary-15e78de280 → 0.4.2-canary-f075c3eb92

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 (87) hide show
  1. package/dist/modules/api_keys/setup.js +11 -0
  2. package/dist/modules/api_keys/setup.js.map +7 -0
  3. package/dist/modules/attachments/components/AttachmentLibrary.js +1 -1
  4. package/dist/modules/attachments/components/AttachmentLibrary.js.map +2 -2
  5. package/dist/modules/attachments/lib/assignmentDetails.js +31 -17
  6. package/dist/modules/attachments/lib/assignmentDetails.js.map +2 -2
  7. package/dist/modules/attachments/lib/partitions.js +3 -3
  8. package/dist/modules/attachments/lib/partitions.js.map +2 -2
  9. package/dist/modules/attachments/setup.js +11 -0
  10. package/dist/modules/attachments/setup.js.map +7 -0
  11. package/dist/modules/audit_logs/setup.js +12 -0
  12. package/dist/modules/audit_logs/setup.js.map +7 -0
  13. package/dist/modules/auth/lib/setup-app.js +29 -159
  14. package/dist/modules/auth/lib/setup-app.js.map +2 -2
  15. package/dist/modules/auth/setup.js +11 -0
  16. package/dist/modules/auth/setup.js.map +7 -0
  17. package/dist/modules/business_rules/setup.js +11 -0
  18. package/dist/modules/business_rules/setup.js.map +7 -0
  19. package/dist/modules/catalog/setup.js +22 -0
  20. package/dist/modules/catalog/setup.js.map +7 -0
  21. package/dist/modules/configs/lib/upgrade-actions.js +65 -15
  22. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  23. package/dist/modules/configs/setup.js +16 -0
  24. package/dist/modules/configs/setup.js.map +7 -0
  25. package/dist/modules/currencies/setup.js +16 -0
  26. package/dist/modules/currencies/setup.js.map +7 -0
  27. package/dist/modules/customers/setup.js +36 -0
  28. package/dist/modules/customers/setup.js.map +7 -0
  29. package/dist/modules/dashboards/setup.js +12 -0
  30. package/dist/modules/dashboards/setup.js.map +7 -0
  31. package/dist/modules/dictionaries/setup.js +12 -0
  32. package/dist/modules/dictionaries/setup.js.map +7 -0
  33. package/dist/modules/directory/setup.js +12 -0
  34. package/dist/modules/directory/setup.js.map +7 -0
  35. package/dist/modules/entities/setup.js +11 -0
  36. package/dist/modules/entities/setup.js.map +7 -0
  37. package/dist/modules/feature_toggles/setup.js +11 -0
  38. package/dist/modules/feature_toggles/setup.js.map +7 -0
  39. package/dist/modules/perspectives/setup.js +12 -0
  40. package/dist/modules/perspectives/setup.js.map +7 -0
  41. package/dist/modules/planner/setup.js +21 -0
  42. package/dist/modules/planner/setup.js.map +7 -0
  43. package/dist/modules/query_index/setup.js +11 -0
  44. package/dist/modules/query_index/setup.js.map +7 -0
  45. package/dist/modules/resources/setup.js +21 -0
  46. package/dist/modules/resources/setup.js.map +7 -0
  47. package/dist/modules/sales/setup.js +99 -0
  48. package/dist/modules/sales/setup.js.map +7 -0
  49. package/dist/modules/staff/setup.js +27 -0
  50. package/dist/modules/staff/setup.js.map +7 -0
  51. package/dist/modules/workflows/lib/seeds.js +3 -15
  52. package/dist/modules/workflows/lib/seeds.js.map +2 -2
  53. package/dist/modules/workflows/migrations/Migration20251207131955.js +76 -72
  54. package/dist/modules/workflows/migrations/Migration20251207131955.js.map +2 -2
  55. package/dist/modules/workflows/setup.js +16 -0
  56. package/dist/modules/workflows/setup.js.map +7 -0
  57. package/package.json +2 -2
  58. package/src/__tests__/module-decoupling.test.ts +356 -0
  59. package/src/modules/api_keys/setup.ts +9 -0
  60. package/src/modules/attachments/components/AttachmentLibrary.tsx +2 -2
  61. package/src/modules/attachments/lib/assignmentDetails.ts +32 -16
  62. package/src/modules/attachments/lib/partitions.ts +3 -3
  63. package/src/modules/attachments/setup.ts +9 -0
  64. package/src/modules/audit_logs/setup.ts +10 -0
  65. package/src/modules/auth/__tests__/cli-setup-acl.test.ts +30 -0
  66. package/src/modules/auth/lib/setup-app.ts +40 -177
  67. package/src/modules/auth/setup.ts +9 -0
  68. package/src/modules/business_rules/setup.ts +9 -0
  69. package/src/modules/catalog/setup.ts +22 -0
  70. package/src/modules/configs/lib/upgrade-actions.ts +78 -17
  71. package/src/modules/configs/setup.ts +14 -0
  72. package/src/modules/currencies/setup.ts +15 -0
  73. package/src/modules/customers/setup.ts +36 -0
  74. package/src/modules/dashboards/setup.ts +10 -0
  75. package/src/modules/dictionaries/setup.ts +10 -0
  76. package/src/modules/directory/setup.ts +10 -0
  77. package/src/modules/entities/setup.ts +9 -0
  78. package/src/modules/feature_toggles/setup.ts +9 -0
  79. package/src/modules/perspectives/setup.ts +10 -0
  80. package/src/modules/planner/setup.ts +21 -0
  81. package/src/modules/query_index/setup.ts +9 -0
  82. package/src/modules/resources/setup.ts +21 -0
  83. package/src/modules/sales/setup.ts +108 -0
  84. package/src/modules/staff/setup.ts +27 -0
  85. package/src/modules/workflows/lib/seeds.ts +4 -16
  86. package/src/modules/workflows/migrations/Migration20251207131955.ts +77 -143
  87. package/src/modules/workflows/setup.ts +15 -0
@@ -0,0 +1,16 @@
1
+ import { seedExampleWorkflows } from "./lib/seeds.js";
2
+ const setup = {
3
+ seedDefaults: async (ctx) => {
4
+ const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId };
5
+ await seedExampleWorkflows(ctx.em, scope);
6
+ },
7
+ defaultRoleFeatures: {
8
+ admin: ["workflows.*"]
9
+ }
10
+ };
11
+ var setup_default = setup;
12
+ export {
13
+ setup_default as default,
14
+ setup
15
+ };
16
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/workflows/setup.ts"],
4
+ "sourcesContent": ["import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'\nimport { seedExampleWorkflows } from './lib/seeds'\n\nexport const setup: ModuleSetupConfig = {\n seedDefaults: async (ctx) => {\n const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }\n await seedExampleWorkflows(ctx.em, scope)\n },\n\n defaultRoleFeatures: {\n admin: ['workflows.*'],\n },\n}\n\nexport default setup\n"],
5
+ "mappings": "AACA,SAAS,4BAA4B;AAE9B,MAAM,QAA2B;AAAA,EACtC,cAAc,OAAO,QAAQ;AAC3B,UAAM,QAAQ,EAAE,UAAU,IAAI,UAAU,gBAAgB,IAAI,eAAe;AAC3E,UAAM,qBAAqB,IAAI,IAAI,KAAK;AAAA,EAC1C;AAAA,EAEA,qBAAqB;AAAA,IACnB,OAAO,CAAC,aAAa;AAAA,EACvB;AACF;AAEA,IAAO,gBAAQ;",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.4.2-canary-15e78de280",
3
+ "version": "0.4.2-canary-f075c3eb92",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -207,7 +207,7 @@
207
207
  }
208
208
  },
209
209
  "dependencies": {
210
- "@open-mercato/shared": "0.4.2-canary-15e78de280",
210
+ "@open-mercato/shared": "0.4.2-canary-f075c3eb92",
211
211
  "@xyflow/react": "^12.6.0",
212
212
  "date-fns": "^4.1.0",
213
213
  "date-fns-tz": "^3.2.0"
@@ -0,0 +1,356 @@
1
+ /**
2
+ * Module Decoupling Tests
3
+ *
4
+ * Verifies that the application remains functional when optional modules
5
+ * (catalog, sales, api_keys) are disabled — removed from the generated
6
+ * module registry and entity IDs.
7
+ */
8
+
9
+ // Build a reduced entity IDs registry excluding catalog, sales, api_keys
10
+ const reducedE = {
11
+ dashboards: {
12
+ dashboard_layout: 'dashboards:dashboard_layout',
13
+ dashboard_role_widgets: 'dashboards:dashboard_role_widgets',
14
+ dashboard_user_widgets: 'dashboards:dashboard_user_widgets',
15
+ },
16
+ auth: {
17
+ password_reset: 'auth:password_reset',
18
+ role: 'auth:role',
19
+ role_acl: 'auth:role_acl',
20
+ role_sidebar_preference: 'auth:role_sidebar_preference',
21
+ session: 'auth:session',
22
+ user: 'auth:user',
23
+ user_acl: 'auth:user_acl',
24
+ user_role: 'auth:user_role',
25
+ user_sidebar_preference: 'auth:user_sidebar_preference',
26
+ },
27
+ directory: {
28
+ organization: 'directory:organization',
29
+ tenant: 'directory:tenant',
30
+ },
31
+ customers: {
32
+ customer_activity: 'customers:customer_activity',
33
+ customer_address: 'customers:customer_address',
34
+ customer_comment: 'customers:customer_comment',
35
+ customer_company_profile: 'customers:customer_company_profile',
36
+ customer_deal: 'customers:customer_deal',
37
+ customer_deal_company_link: 'customers:customer_deal_company_link',
38
+ customer_deal_person_link: 'customers:customer_deal_person_link',
39
+ customer_dictionary_entry: 'customers:customer_dictionary_entry',
40
+ customer_entity: 'customers:customer_entity',
41
+ customer_person_profile: 'customers:customer_person_profile',
42
+ customer_settings: 'customers:customer_settings',
43
+ customer_tag: 'customers:customer_tag',
44
+ customer_tag_assignment: 'customers:customer_tag_assignment',
45
+ customer_todo_link: 'customers:customer_todo_link',
46
+ },
47
+ entities: {
48
+ custom_entity: 'entities:custom_entity',
49
+ custom_entity_storage: 'entities:custom_entity_storage',
50
+ custom_field_def: 'entities:custom_field_def',
51
+ custom_field_entity_config: 'entities:custom_field_entity_config',
52
+ custom_field_value: 'entities:custom_field_value',
53
+ encryption_map: 'entities:encryption_map',
54
+ },
55
+ attachments: {
56
+ attachment: 'attachments:attachment',
57
+ attachment_partition: 'attachments:attachment_partition',
58
+ },
59
+ } as const
60
+
61
+ const reducedM = {
62
+ dashboards: 'dashboards',
63
+ auth: 'auth',
64
+ directory: 'directory',
65
+ customers: 'customers',
66
+ entities: 'entities',
67
+ attachments: 'attachments',
68
+ } as const
69
+
70
+ // Mock generated entity IDs to exclude catalog, sales, api_keys
71
+ jest.mock('#generated/entities.ids.generated', () => ({
72
+ E: reducedE,
73
+ M: reducedM,
74
+ }))
75
+
76
+ // Mock cache dependency used by upgrade-actions
77
+ jest.mock('@open-mercato/cache', () => ({
78
+ runWithCacheTenant: jest.fn((_: unknown, fn: () => unknown) => fn()),
79
+ }))
80
+ jest.mock('@open-mercato/shared/lib/crud/cache-stats', () => ({
81
+ collectCrudCacheStats: jest.fn().mockResolvedValue({ segments: [] }),
82
+ purgeCrudCacheSegment: jest.fn(),
83
+ }))
84
+ jest.mock('@open-mercato/shared/lib/crud/cache', () => ({
85
+ isCrudCacheEnabled: jest.fn().mockReturnValue(false),
86
+ resolveCrudCache: jest.fn(),
87
+ }))
88
+
89
+ import { registerEntityIds, getEntityIds } from '@open-mercato/shared/lib/encryption/entityIds'
90
+ import { registerModules } from '@open-mercato/shared/lib/modules/registry'
91
+ import type { Module } from '@open-mercato/shared/modules/registry'
92
+ import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'
93
+
94
+ // Build module list that mirrors enabled modules without catalog/sales/api_keys
95
+ function buildReducedModules(): Module[] {
96
+ const moduleSetups: Record<string, ModuleSetupConfig> = {
97
+ dashboards: {
98
+ defaultRoleFeatures: {
99
+ admin: ['dashboards.*', 'dashboards.admin.assign-widgets'],
100
+ employee: ['dashboards.view', 'dashboards.configure'],
101
+ },
102
+ },
103
+ auth: {
104
+ defaultRoleFeatures: { admin: ['auth.*'] },
105
+ },
106
+ directory: {
107
+ defaultRoleFeatures: {
108
+ superadmin: ['directory.tenants.*'],
109
+ admin: ['directory.organizations.view', 'directory.organizations.manage'],
110
+ },
111
+ },
112
+ customers: {
113
+ defaultRoleFeatures: {
114
+ admin: [
115
+ 'customers.*',
116
+ 'customers.people.view',
117
+ 'customers.people.manage',
118
+ 'customers.companies.view',
119
+ 'customers.companies.manage',
120
+ 'customers.deals.view',
121
+ 'customers.deals.manage',
122
+ ],
123
+ employee: [
124
+ 'customers.*',
125
+ 'customers.people.view',
126
+ 'customers.people.manage',
127
+ 'customers.companies.view',
128
+ 'customers.companies.manage',
129
+ ],
130
+ },
131
+ },
132
+ entities: {
133
+ defaultRoleFeatures: { admin: ['entities.*'] },
134
+ },
135
+ attachments: {
136
+ defaultRoleFeatures: { admin: ['attachments.*', 'attachments.view', 'attachments.manage'] },
137
+ },
138
+ audit_logs: {
139
+ defaultRoleFeatures: { admin: ['audit_logs.*'], employee: ['audit_logs.undo_self'] },
140
+ },
141
+ dictionaries: {
142
+ defaultRoleFeatures: { admin: ['dictionaries.view', 'dictionaries.manage'], employee: ['dictionaries.view'] },
143
+ },
144
+ perspectives: {
145
+ defaultRoleFeatures: { admin: ['perspectives.use', 'perspectives.role_defaults'], employee: ['perspectives.use'] },
146
+ },
147
+ configs: {
148
+ defaultRoleFeatures: { admin: ['configs.system_status.view', 'configs.cache.view', 'configs.cache.manage', 'configs.manage'] },
149
+ },
150
+ query_index: {
151
+ defaultRoleFeatures: { admin: ['query_index.*'] },
152
+ },
153
+ feature_toggles: {
154
+ defaultRoleFeatures: { admin: ['feature_toggles.*'] },
155
+ },
156
+ business_rules: {
157
+ defaultRoleFeatures: { admin: ['business_rules.*'] },
158
+ },
159
+ workflows: {
160
+ defaultRoleFeatures: { admin: ['workflows.*'] },
161
+ },
162
+ currencies: {
163
+ defaultRoleFeatures: { admin: ['currencies.*'] },
164
+ },
165
+ staff: {
166
+ defaultRoleFeatures: {
167
+ admin: ['staff.*', 'staff.leave_requests.manage'],
168
+ employee: [
169
+ 'staff.leave_requests.send',
170
+ 'staff.my_availability.view',
171
+ 'staff.my_availability.manage',
172
+ 'staff.my_leave_requests.view',
173
+ 'staff.my_leave_requests.send',
174
+ ],
175
+ },
176
+ },
177
+ resources: {
178
+ defaultRoleFeatures: { admin: ['resources.*'] },
179
+ },
180
+ planner: {
181
+ defaultRoleFeatures: { admin: ['planner.*'], employee: ['planner.view'] },
182
+ },
183
+ search: {
184
+ defaultRoleFeatures: { admin: ['search.*', 'vector.*'], employee: ['vector.*'] },
185
+ },
186
+ }
187
+
188
+ return Object.entries(moduleSetups).map(([id, setup]) => ({
189
+ id,
190
+ setup,
191
+ }))
192
+ }
193
+
194
+ const reducedModules = buildReducedModules()
195
+
196
+ beforeAll(() => {
197
+ registerEntityIds(reducedE as any)
198
+ registerModules(reducedModules)
199
+ })
200
+
201
+ describe('Module Decoupling', () => {
202
+ describe('1. Entity IDs registry excludes disabled modules', () => {
203
+ it('getEntityIds() has no catalog, sales, or api_keys keys', () => {
204
+ const entityIds = getEntityIds()
205
+ expect(entityIds).not.toHaveProperty('catalog')
206
+ expect(entityIds).not.toHaveProperty('sales')
207
+ expect(entityIds).not.toHaveProperty('api_keys')
208
+ })
209
+
210
+ it('still contains core modules', () => {
211
+ const entityIds = getEntityIds()
212
+ expect(entityIds).toHaveProperty('auth')
213
+ expect(entityIds).toHaveProperty('attachments')
214
+ expect(entityIds).toHaveProperty('customers')
215
+ expect(entityIds).toHaveProperty('dashboards')
216
+ expect(entityIds).toHaveProperty('directory')
217
+ expect(entityIds).toHaveProperty('entities')
218
+ })
219
+ })
220
+
221
+ describe('2. attachments/partitions.ts — resolveDefaultPartitionCode', () => {
222
+ it('returns privateAttachments for null, undefined, arbitrary strings, and catalog entity IDs', async () => {
223
+ const { resolveDefaultPartitionCode } = await import(
224
+ '@open-mercato/core/modules/attachments/lib/partitions'
225
+ )
226
+
227
+ expect(resolveDefaultPartitionCode(null)).toBe('privateAttachments')
228
+ expect(resolveDefaultPartitionCode(undefined)).toBe('privateAttachments')
229
+ expect(resolveDefaultPartitionCode('some-entity')).toBe('privateAttachments')
230
+ // When catalog is disabled, the literal string also falls through
231
+ expect(resolveDefaultPartitionCode('catalog:catalog_product')).toBe('privateAttachments')
232
+ })
233
+ })
234
+
235
+ describe('3. attachments/assignmentDetails.ts — resolveAssignmentEnrichments', () => {
236
+ it('does not crash with empty assignments and null queryEngine', async () => {
237
+ const { resolveAssignmentEnrichments } = await import(
238
+ '@open-mercato/core/modules/attachments/lib/assignmentDetails'
239
+ )
240
+
241
+ const result = await resolveAssignmentEnrichments([], {
242
+ queryEngine: null,
243
+ tenantId: 'tenant-1',
244
+ organizationId: 'org-1',
245
+ })
246
+ expect(result).toBeInstanceOf(Map)
247
+ expect(result.size).toBe(0)
248
+ })
249
+
250
+ it('does not crash with assignments referencing catalog entity IDs', async () => {
251
+ const { resolveAssignmentEnrichments } = await import(
252
+ '@open-mercato/core/modules/attachments/lib/assignmentDetails'
253
+ )
254
+
255
+ const result = await resolveAssignmentEnrichments(
256
+ [{ type: 'catalog:catalog_product', id: 'some-uuid' }],
257
+ {
258
+ queryEngine: null,
259
+ tenantId: 'tenant-1',
260
+ organizationId: 'org-1',
261
+ },
262
+ )
263
+ expect(result).toBeInstanceOf(Map)
264
+ expect(result.size).toBe(0)
265
+ })
266
+ })
267
+
268
+ describe('4. Role feature merging', () => {
269
+ it('admin features contain enabled module features', () => {
270
+ const adminFeatures: string[] = []
271
+ for (const mod of reducedModules) {
272
+ const roleFeatures = mod.setup?.defaultRoleFeatures
273
+ if (roleFeatures?.admin) adminFeatures.push(...roleFeatures.admin)
274
+ }
275
+
276
+ expect(adminFeatures).toContain('auth.*')
277
+ expect(adminFeatures).toContain('attachments.*')
278
+ expect(adminFeatures).toContain('dashboards.*')
279
+ expect(adminFeatures).toContain('customers.*')
280
+ expect(adminFeatures).toContain('entities.*')
281
+ expect(adminFeatures).toContain('query_index.*')
282
+ expect(adminFeatures).toContain('configs.manage')
283
+ expect(adminFeatures).toContain('directory.organizations.manage')
284
+ })
285
+
286
+ it('admin features do NOT contain disabled module features', () => {
287
+ const adminFeatures: string[] = []
288
+ for (const mod of reducedModules) {
289
+ const roleFeatures = mod.setup?.defaultRoleFeatures
290
+ if (roleFeatures?.admin) adminFeatures.push(...roleFeatures.admin)
291
+ }
292
+
293
+ expect(adminFeatures).not.toContain('catalog.*')
294
+ expect(adminFeatures).not.toContain('sales.*')
295
+ expect(adminFeatures).not.toContain('api_keys.*')
296
+ })
297
+
298
+ it('employee features similarly exclude disabled module features', () => {
299
+ const employeeFeatures: string[] = []
300
+ for (const mod of reducedModules) {
301
+ const roleFeatures = mod.setup?.defaultRoleFeatures
302
+ if (roleFeatures?.employee) employeeFeatures.push(...roleFeatures.employee)
303
+ }
304
+
305
+ expect(employeeFeatures).not.toContain('catalog.*')
306
+ expect(employeeFeatures).not.toContain('sales.*')
307
+ expect(employeeFeatures).not.toContain('api_keys.*')
308
+
309
+ // Enabled modules' employee features are present
310
+ expect(employeeFeatures).toContain('customers.*')
311
+ expect(employeeFeatures).toContain('dashboards.view')
312
+ expect(employeeFeatures).toContain('audit_logs.undo_self')
313
+ expect(employeeFeatures).toContain('vector.*')
314
+ })
315
+
316
+ it('superadmin features come from enabled modules only', () => {
317
+ const superadminFeatures: string[] = []
318
+ for (const mod of reducedModules) {
319
+ const roleFeatures = mod.setup?.defaultRoleFeatures
320
+ if (roleFeatures?.superadmin) superadminFeatures.push(...roleFeatures.superadmin)
321
+ }
322
+
323
+ expect(superadminFeatures).toContain('directory.tenants.*')
324
+ expect(superadminFeatures).not.toContain('catalog.*')
325
+ expect(superadminFeatures).not.toContain('sales.*')
326
+ })
327
+ })
328
+
329
+ describe('5. configs/upgrade-actions.ts loads without crashing', () => {
330
+ it('imports and exposes upgradeActions array and compareVersions function', async () => {
331
+ const mod = await import(
332
+ '@open-mercato/core/modules/configs/lib/upgrade-actions'
333
+ )
334
+
335
+ expect(Array.isArray(mod.upgradeActions)).toBe(true)
336
+ expect(typeof mod.compareVersions).toBe('function')
337
+ })
338
+
339
+ it('upgradeActions array is defined and non-empty', async () => {
340
+ const mod = await import(
341
+ '@open-mercato/core/modules/configs/lib/upgrade-actions'
342
+ )
343
+
344
+ expect(mod.upgradeActions.length).toBeGreaterThan(0)
345
+ })
346
+
347
+ it('actionsUpToVersion returns actions without crashing', async () => {
348
+ const mod = await import(
349
+ '@open-mercato/core/modules/configs/lib/upgrade-actions'
350
+ )
351
+
352
+ const actions = mod.actionsUpToVersion('99.99.99')
353
+ expect(Array.isArray(actions)).toBe(true)
354
+ })
355
+ })
356
+ })
@@ -0,0 +1,9 @@
1
+ import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'
2
+
3
+ export const setup: ModuleSetupConfig = {
4
+ defaultRoleFeatures: {
5
+ admin: ['api_keys.*'],
6
+ },
7
+ }
8
+
9
+ export default setup
@@ -893,8 +893,8 @@ export function AttachmentLibrary() {
893
893
  {assignments.map((assignment) => {
894
894
  const label = assignment.label?.trim() || assignment.id
895
895
  const hideType =
896
- assignment.type === E.catalog.catalog_product ||
897
- assignment.type === E.catalog.catalog_product_variant
896
+ assignment.type === (E as any).catalog?.catalog_product ||
897
+ assignment.type === (E as any).catalog?.catalog_product_variant
898
898
  const content = hideType ? label : `${assignment.type}: ${label}`
899
899
  return assignment.href ? (
900
900
  <a
@@ -15,12 +15,16 @@ let _entityLinkSpecsCache: Record<string, AssignmentLinkSpec> | null = null
15
15
  function getEntityLinkSpecs(): Record<string, AssignmentLinkSpec> {
16
16
  if (_entityLinkSpecsCache) return _entityLinkSpecsCache
17
17
  const E = getEntityIds() as any
18
- _entityLinkSpecsCache = {
19
- [E.catalog.catalog_product]: {
18
+ const specs: Record<string, AssignmentLinkSpec> = {}
19
+
20
+ if (E.catalog?.catalog_product) {
21
+ specs[E.catalog.catalog_product] = {
20
22
  labelFields: ['title', 'sku', 'handle'],
21
23
  buildHref: (record) => buildSimpleHref('/backend/catalog/products', record.id),
22
- },
23
- [E.catalog.catalog_product_variant]: {
24
+ }
25
+ }
26
+ if (E.catalog?.catalog_product_variant) {
27
+ specs[E.catalog.catalog_product_variant] = {
24
28
  labelFields: ['name', 'sku'],
25
29
  extraFields: ['product_id'],
26
30
  buildHref: (record) => {
@@ -28,8 +32,10 @@ function getEntityLinkSpecs(): Record<string, AssignmentLinkSpec> {
28
32
  if (!productId) return null
29
33
  return `/backend/catalog/products/${encodeURIComponent(productId)}/variants/${encodeURIComponent(String(record.id ?? ''))}`
30
34
  },
31
- },
32
- [E.customers.customer_entity]: {
35
+ }
36
+ }
37
+ if (E.customers?.customer_entity) {
38
+ specs[E.customers.customer_entity] = {
33
39
  labelFields: ['display_name'],
34
40
  extraFields: ['kind'],
35
41
  buildHref: (record) => {
@@ -38,32 +44,42 @@ function getEntityLinkSpecs(): Record<string, AssignmentLinkSpec> {
38
44
  if (kind === 'person') return buildSimpleHref('/backend/customers/people', record.id)
39
45
  return null
40
46
  },
41
- },
42
- [E.customers.customer_person_profile]: {
47
+ }
48
+ }
49
+ if (E.customers?.customer_person_profile) {
50
+ specs[E.customers.customer_person_profile] = {
43
51
  labelFields: ['preferred_name', 'display_name', 'first_name', 'last_name'],
44
52
  extraFields: ['entity_id', 'first_name', 'last_name'],
45
53
  buildHref: (record) => {
46
54
  const entityId = readRecordValue(record, 'entity_id')
47
55
  return entityId ? buildSimpleHref('/backend/customers/people', entityId) : null
48
56
  },
49
- },
50
- [E.customers.customer_company_profile]: {
57
+ }
58
+ }
59
+ if (E.customers?.customer_company_profile) {
60
+ specs[E.customers.customer_company_profile] = {
51
61
  labelFields: ['brand_name', 'display_name', 'legal_name'],
52
62
  extraFields: ['entity_id'],
53
63
  buildHref: (record) => {
54
64
  const entityId = readRecordValue(record, 'entity_id')
55
65
  return entityId ? buildSimpleHref('/backend/customers/companies', entityId) : null
56
66
  },
57
- },
58
- [E.customers.customer_deal]: {
67
+ }
68
+ }
69
+ if (E.customers?.customer_deal) {
70
+ specs[E.customers.customer_deal] = {
59
71
  labelFields: ['title'],
60
72
  buildHref: (record) => buildSimpleHref('/backend/customers/deals', record.id),
61
- },
62
- [E.sales.sales_channel]: {
73
+ }
74
+ }
75
+ if (E.sales?.sales_channel) {
76
+ specs[E.sales.sales_channel] = {
63
77
  labelFields: ['name', 'title'],
64
78
  buildHref: (record) => buildSimpleHref('/backend/sales/channels', record.id, '/edit'),
65
- },
79
+ }
66
80
  }
81
+
82
+ _entityLinkSpecsCache = specs
67
83
  return _entityLinkSpecsCache
68
84
  }
69
85
 
@@ -161,7 +177,7 @@ function isUuid(value: string | null | undefined): boolean {
161
177
 
162
178
  function filterIdsForEntity(entityId: string, ids: string[]): string[] {
163
179
  const E = getEntityIds() as any
164
- if (entityId === E.catalog.catalog_product_variant || entityId === E.catalog.catalog_product) {
180
+ if (entityId === E.catalog?.catalog_product_variant || entityId === E.catalog?.catalog_product) {
165
181
  return ids.filter((id) => isUuid(id))
166
182
  }
167
183
  return ids
@@ -26,9 +26,9 @@ export const DEFAULT_ATTACHMENT_PARTITIONS: AttachmentPartitionSeed[] = [
26
26
  },
27
27
  ]
28
28
 
29
- const PRODUCT_MEDIA_ENTITY_IDS = new Set<string>([
30
- E.catalog.catalog_product,
31
- ])
29
+ const PRODUCT_MEDIA_ENTITY_IDS = new Set<string>(
30
+ [(E as any).catalog?.catalog_product].filter(Boolean) as string[]
31
+ )
32
32
 
33
33
  const FALLBACK_PARTITION = 'privateAttachments'
34
34
 
@@ -0,0 +1,9 @@
1
+ import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'
2
+
3
+ export const setup: ModuleSetupConfig = {
4
+ defaultRoleFeatures: {
5
+ admin: ['attachments.*', 'attachments.view', 'attachments.manage'],
6
+ },
7
+ }
8
+
9
+ export default setup
@@ -0,0 +1,10 @@
1
+ import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'
2
+
3
+ export const setup: ModuleSetupConfig = {
4
+ defaultRoleFeatures: {
5
+ admin: ['audit_logs.*'],
6
+ employee: ['audit_logs.undo_self'],
7
+ },
8
+ }
9
+
10
+ export default setup
@@ -1,6 +1,36 @@
1
1
  /** @jest-environment node */
2
+ import { registerModules } from '@open-mercato/shared/lib/modules/registry'
3
+ import type { Module } from '@open-mercato/shared/modules/registry'
2
4
  import cli from '@open-mercato/core/modules/auth/cli'
3
5
 
6
+ // Register modules so that ensureDefaultRoleAcls can read defaultRoleFeatures
7
+ const testModules: Module[] = [
8
+ { id: 'auth', setup: { defaultRoleFeatures: { admin: ['auth.*'] } } },
9
+ { id: 'entities', setup: { defaultRoleFeatures: { admin: ['entities.*'] } } },
10
+ { id: 'attachments', setup: { defaultRoleFeatures: { admin: ['attachments.*', 'attachments.view', 'attachments.manage'] } } },
11
+ { id: 'query_index', setup: { defaultRoleFeatures: { admin: ['query_index.*'] } } },
12
+ { id: 'configs', setup: { defaultRoleFeatures: { admin: ['configs.system_status.view', 'configs.cache.view', 'configs.cache.manage', 'configs.manage'] } } },
13
+ { id: 'directory', setup: { defaultRoleFeatures: { superadmin: ['directory.tenants.*'], admin: ['directory.organizations.view', 'directory.organizations.manage'] } } },
14
+ { id: 'customers', setup: { defaultRoleFeatures: { admin: ['customers.*', 'customers.people.view', 'customers.people.manage', 'customers.companies.view', 'customers.companies.manage', 'customers.deals.view', 'customers.deals.manage'], employee: ['customers.*', 'customers.people.view', 'customers.people.manage', 'customers.companies.view', 'customers.companies.manage'] } } },
15
+ { id: 'catalog', setup: { defaultRoleFeatures: { admin: ['catalog.*', 'catalog.variants.manage', 'catalog.pricing.manage'], employee: ['catalog.*', 'catalog.variants.manage', 'catalog.pricing.manage'] } } },
16
+ { id: 'sales', setup: { defaultRoleFeatures: { admin: ['sales.*'], employee: ['sales.*'] } } },
17
+ { id: 'dictionaries', setup: { defaultRoleFeatures: { admin: ['dictionaries.view', 'dictionaries.manage'], employee: ['dictionaries.view'] } } },
18
+ { id: 'audit_logs', setup: { defaultRoleFeatures: { admin: ['audit_logs.*'], employee: ['audit_logs.undo_self'] } } },
19
+ { id: 'dashboards', setup: { defaultRoleFeatures: { admin: ['dashboards.*', 'dashboards.admin.assign-widgets'], employee: ['dashboards.view', 'dashboards.configure'] } } },
20
+ { id: 'api_keys', setup: { defaultRoleFeatures: { admin: ['api_keys.*'] } } },
21
+ { id: 'perspectives', setup: { defaultRoleFeatures: { admin: ['perspectives.use', 'perspectives.role_defaults'], employee: ['perspectives.use'] } } },
22
+ { id: 'feature_toggles', setup: { defaultRoleFeatures: { admin: ['feature_toggles.*'] } } },
23
+ { id: 'business_rules', setup: { defaultRoleFeatures: { admin: ['business_rules.*'] } } },
24
+ { id: 'workflows', setup: { defaultRoleFeatures: { admin: ['workflows.*'] } } },
25
+ { id: 'search', setup: { defaultRoleFeatures: { admin: ['search.*', 'vector.*'], employee: ['vector.*'] } } },
26
+ { id: 'currencies', setup: { defaultRoleFeatures: { admin: ['currencies.*'] } } },
27
+ { id: 'planner', setup: { defaultRoleFeatures: { admin: ['planner.*'], employee: ['planner.view'] } } },
28
+ { id: 'resources', setup: { defaultRoleFeatures: { admin: ['resources.*'] } } },
29
+ { id: 'staff', setup: { defaultRoleFeatures: { admin: ['staff.*', 'staff.leave_requests.manage'], employee: ['staff.leave_requests.send', 'staff.my_availability.view', 'staff.my_availability.manage', 'staff.my_leave_requests.view', 'staff.my_leave_requests.send'] } } },
30
+ { id: 'example', setup: { defaultRoleFeatures: { admin: ['example.*'], employee: ['example.*', 'example.widgets.*'] } } },
31
+ ]
32
+ registerModules(testModules)
33
+
4
34
  // Mock DI container and EM
5
35
  const persistAndFlush = jest.fn()
6
36
  const findOne = jest.fn()