@open-mercato/shared 0.6.7-develop.6768.1.9d2c4efc43 → 0.6.7-develop.6770.1.b52298cf20
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.
- package/dist/lib/crud/factory.js +46 -0
- package/dist/lib/crud/factory.js.map +2 -2
- package/dist/lib/di/container.js +3 -2
- package/dist/lib/di/container.js.map +2 -2
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/package.json +2 -2
- package/src/lib/crud/__tests__/crud-factory.cache-user-scope.test.ts +171 -0
- package/src/lib/crud/__tests__/crud-factory.test.ts +189 -0
- package/src/lib/crud/factory.ts +52 -0
- package/src/lib/di/__tests__/registrar-error-log.test.ts +154 -0
- package/src/lib/di/container.ts +8 -3
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// A module DI registrar that throws is skipped fail-open (one broken module
|
|
2
|
+
// must not take down every request container), but the failure MUST be logged —
|
|
3
|
+
// a silent catch leaves the module's services missing with no trace until an
|
|
4
|
+
// unrelated Awilix resolution error surfaces much later.
|
|
5
|
+
|
|
6
|
+
import { asValue } from 'awilix'
|
|
7
|
+
import type { AwilixContainer } from 'awilix'
|
|
8
|
+
|
|
9
|
+
const mockLoggerError = jest.fn()
|
|
10
|
+
|
|
11
|
+
jest.mock('@open-mercato/shared/lib/logger', () => {
|
|
12
|
+
const makeLogger = (): Record<string, unknown> => ({
|
|
13
|
+
error: (...args: unknown[]) => mockLoggerError(...args),
|
|
14
|
+
warn: jest.fn(),
|
|
15
|
+
info: jest.fn(),
|
|
16
|
+
debug: jest.fn(),
|
|
17
|
+
trace: jest.fn(),
|
|
18
|
+
child: () => makeLogger(),
|
|
19
|
+
})
|
|
20
|
+
return {
|
|
21
|
+
__esModule: true,
|
|
22
|
+
createLogger: () => makeLogger(),
|
|
23
|
+
getLogLevel: () => 'info',
|
|
24
|
+
isLevelEnabled: () => false,
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
jest.mock(
|
|
29
|
+
'@open-mercato/shared/lib/db/mikro',
|
|
30
|
+
() => {
|
|
31
|
+
const baseEm: any = {
|
|
32
|
+
fork: () => baseEm,
|
|
33
|
+
getRepository: () => ({ find: async () => [], findOne: async () => null }),
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
__esModule: true,
|
|
37
|
+
getOrm: async () => ({ em: baseEm }),
|
|
38
|
+
getOrmEntities: () => [],
|
|
39
|
+
registerOrmEntities: () => {},
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{ virtual: false },
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
jest.mock(
|
|
46
|
+
'@mikro-orm/core',
|
|
47
|
+
() => ({
|
|
48
|
+
__esModule: true,
|
|
49
|
+
RequestContext: { getEntityManager: () => null },
|
|
50
|
+
}),
|
|
51
|
+
{ virtual: true },
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
jest.mock(
|
|
55
|
+
'@open-mercato/shared/lib/query/engine',
|
|
56
|
+
() => ({ __esModule: true, BasicQueryEngine: class {} }),
|
|
57
|
+
{ virtual: false },
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
jest.mock(
|
|
61
|
+
'@open-mercato/shared/lib/data/engine',
|
|
62
|
+
() => ({ __esModule: true, DefaultDataEngine: class {} }),
|
|
63
|
+
{ virtual: false },
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
jest.mock(
|
|
67
|
+
'@open-mercato/shared/lib/commands',
|
|
68
|
+
() => ({
|
|
69
|
+
__esModule: true,
|
|
70
|
+
commandRegistry: {},
|
|
71
|
+
CommandBus: class { constructor() {} },
|
|
72
|
+
}),
|
|
73
|
+
{ virtual: false },
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
jest.mock(
|
|
77
|
+
'@open-mercato/shared/modules/overrides',
|
|
78
|
+
() => ({
|
|
79
|
+
__esModule: true,
|
|
80
|
+
applyDiOverridesToContainer: () => {},
|
|
81
|
+
applyModuleOverridesToModules: (modules: unknown[]) => modules,
|
|
82
|
+
applyComponentOverridesToEntries: (entries: unknown[]) => entries,
|
|
83
|
+
}),
|
|
84
|
+
{ virtual: false },
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
jest.mock(
|
|
88
|
+
'@open-mercato/core/bootstrap',
|
|
89
|
+
() => ({
|
|
90
|
+
__esModule: true,
|
|
91
|
+
bootstrap: async () => {},
|
|
92
|
+
}),
|
|
93
|
+
{ virtual: true },
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
jest.mock(
|
|
97
|
+
'@open-mercato/shared/lib/encryption/subscriber',
|
|
98
|
+
() => ({
|
|
99
|
+
__esModule: true,
|
|
100
|
+
registerTenantEncryptionSubscriber: () => {},
|
|
101
|
+
}),
|
|
102
|
+
{ virtual: true },
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
const {
|
|
106
|
+
createRequestContainer,
|
|
107
|
+
registerAppDiRegistrar,
|
|
108
|
+
registerDiRegistrars,
|
|
109
|
+
resetBootstrapCache,
|
|
110
|
+
} = require('@open-mercato/shared/lib/di/container')
|
|
111
|
+
|
|
112
|
+
describe('createRequestContainer — module DI registrar failures', () => {
|
|
113
|
+
beforeEach(() => {
|
|
114
|
+
resetBootstrapCache()
|
|
115
|
+
registerAppDiRegistrar(null)
|
|
116
|
+
mockLoggerError.mockClear()
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
afterAll(() => {
|
|
120
|
+
registerDiRegistrars([])
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('logs the error and continues with the remaining registrars', async () => {
|
|
124
|
+
const boom = new Error('di exploded')
|
|
125
|
+
const workingRegistrar = jest.fn((container: AwilixContainer) => {
|
|
126
|
+
container.register({ registrarProbeService: asValue('registered') })
|
|
127
|
+
})
|
|
128
|
+
registerDiRegistrars([
|
|
129
|
+
() => { throw boom },
|
|
130
|
+
workingRegistrar,
|
|
131
|
+
])
|
|
132
|
+
|
|
133
|
+
const container = await createRequestContainer()
|
|
134
|
+
|
|
135
|
+
expect(workingRegistrar).toHaveBeenCalledTimes(1)
|
|
136
|
+
expect(container.resolve('registrarProbeService')).toBe('registered')
|
|
137
|
+
expect(mockLoggerError).toHaveBeenCalledWith('Module DI registrar failed', {
|
|
138
|
+
registrarIndex: 0,
|
|
139
|
+
err: boom,
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('does not log when every registrar succeeds', async () => {
|
|
144
|
+
registerDiRegistrars([
|
|
145
|
+
(container: AwilixContainer) => {
|
|
146
|
+
container.register({ registrarProbeService: asValue('ok') })
|
|
147
|
+
},
|
|
148
|
+
])
|
|
149
|
+
|
|
150
|
+
await createRequestContainer()
|
|
151
|
+
|
|
152
|
+
expect(mockLoggerError).not.toHaveBeenCalled()
|
|
153
|
+
})
|
|
154
|
+
})
|
package/src/lib/di/container.ts
CHANGED
|
@@ -197,9 +197,14 @@ export async function createRequestContainer(): Promise<AppContainer> {
|
|
|
197
197
|
createCommandOptimisticLockGuardService(),
|
|
198
198
|
).scoped(),
|
|
199
199
|
})
|
|
200
|
-
// Allow modules to override/extend
|
|
201
|
-
|
|
202
|
-
|
|
200
|
+
// Allow modules to override/extend. Fail-open by design (one broken module's
|
|
201
|
+
// di.ts must not take down every request container), but never silently: the
|
|
202
|
+
// module's services would otherwise vanish with no trace until an unrelated
|
|
203
|
+
// Awilix resolution error surfaces much later.
|
|
204
|
+
for (const [registrarIndex, reg] of diRegistrars.entries()) {
|
|
205
|
+
try { reg?.(container) } catch (error) {
|
|
206
|
+
logger.error('Module DI registrar failed', { registrarIndex, err: error })
|
|
207
|
+
}
|
|
203
208
|
}
|
|
204
209
|
// Core bootstrap (cache, event bus, encryption subscriber/KMS, module subscribers)
|
|
205
210
|
// Phase 5 — process-scoped once-guard. The first request runs the full
|