@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.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +6 -57
- package/build.mjs +4 -2
- package/dist/lib/auth/server.js +2 -15
- package/dist/lib/auth/server.js.map +2 -2
- package/dist/lib/bootstrap/dynamicLoader.js +43 -117
- package/dist/lib/bootstrap/dynamicLoader.js.map +2 -2
- package/dist/lib/commands/command-bus.js +1 -5
- package/dist/lib/commands/command-bus.js.map +2 -2
- package/dist/lib/commands/command-interceptor-runner.js +2 -2
- package/dist/lib/commands/command-interceptor-runner.js.map +2 -2
- package/dist/lib/crud/enricher-runner.js +11 -2
- package/dist/lib/crud/enricher-runner.js.map +2 -2
- package/dist/lib/crud/factory.js +4 -63
- package/dist/lib/crud/factory.js.map +2 -2
- package/dist/lib/crud/interceptor-runner.js +2 -2
- package/dist/lib/crud/interceptor-runner.js.map +2 -2
- package/dist/lib/crud/mutation-guard-registry.js +2 -2
- package/dist/lib/crud/mutation-guard-registry.js.map +2 -2
- package/dist/lib/crud/types.js +0 -4
- package/dist/lib/crud/types.js.map +3 -3
- package/dist/lib/data/engine.js +11 -37
- package/dist/lib/data/engine.js.map +2 -2
- package/dist/lib/db/mikro.js +12 -3
- package/dist/lib/db/mikro.js.map +2 -2
- package/dist/lib/db/pg-errors.js +0 -42
- package/dist/lib/db/pg-errors.js.map +2 -2
- package/dist/lib/di/container.js +6 -48
- package/dist/lib/di/container.js.map +2 -2
- package/dist/lib/email/send.js +0 -56
- package/dist/lib/email/send.js.map +2 -2
- package/dist/lib/encryption/subscriber.js +10 -2
- package/dist/lib/encryption/subscriber.js.map +2 -2
- package/dist/lib/encryption/tenantDataEncryptionService.js +17 -52
- package/dist/lib/encryption/tenantDataEncryptionService.js.map +2 -2
- package/dist/lib/i18n/config.js +1 -1
- package/dist/lib/i18n/config.js.map +2 -2
- package/dist/lib/i18n/server.js +4 -4
- package/dist/lib/i18n/server.js.map +3 -3
- package/dist/lib/logger/index.js +3 -47
- package/dist/lib/logger/index.js.map +3 -3
- package/dist/lib/modules/registry.js +1 -5
- package/dist/lib/modules/registry.js.map +2 -2
- package/dist/lib/query/engine.js +40 -54
- package/dist/lib/query/engine.js.map +2 -2
- package/dist/lib/query/types.js.map +1 -1
- package/dist/lib/redis/connection.js +2 -7
- package/dist/lib/redis/connection.js.map +2 -2
- package/dist/lib/search/config.js +2 -61
- package/dist/lib/search/config.js.map +2 -2
- package/dist/lib/search/tokenize.js +11 -23
- package/dist/lib/search/tokenize.js.map +2 -2
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/dist/lib/webhooks/index.js +1 -13
- package/dist/lib/webhooks/index.js.map +2 -2
- package/dist/lib/webhooks/verify.js +7 -12
- package/dist/lib/webhooks/verify.js.map +2 -2
- package/dist/modules/analytics.js.map +2 -2
- package/dist/modules/overrides.js +1 -50
- package/dist/modules/overrides.js.map +2 -2
- package/dist/modules/payment_gateways/types.js +1 -8
- package/dist/modules/payment_gateways/types.js.map +2 -2
- package/dist/modules/registry.js +0 -1
- package/dist/modules/registry.js.map +2 -2
- package/dist/security/enabledModulesRegistry.js +11 -56
- package/dist/security/enabledModulesRegistry.js.map +2 -2
- package/jest.config.cjs +1 -2
- package/package.json +9 -19
- package/src/lib/ai/llm-provider.ts +0 -33
- package/src/lib/auth/__tests__/server.test.ts +0 -23
- package/src/lib/auth/server.ts +2 -23
- package/src/lib/bootstrap/__tests__/dynamicLoader.commandInterceptors.test.ts +8 -63
- package/src/lib/bootstrap/__tests__/dynamicLoader.tsconfig.test.ts +1 -21
- package/src/lib/bootstrap/dynamicLoader.ts +65 -213
- package/src/lib/commands/__tests__/command-interceptor-runner.test.ts +0 -28
- package/src/lib/commands/command-bus.ts +1 -5
- package/src/lib/commands/command-interceptor-runner.ts +2 -2
- package/src/lib/crud/__tests__/crud-factory.test.ts +0 -298
- package/src/lib/crud/__tests__/mutation-guard-registry.test.ts +0 -26
- package/src/lib/crud/enricher-runner.ts +11 -2
- package/src/lib/crud/factory.ts +1 -69
- package/src/lib/crud/interceptor-runner.ts +2 -2
- package/src/lib/crud/mutation-guard-registry.ts +2 -2
- package/src/lib/crud/types.ts +0 -3
- package/src/lib/data/__tests__/engine.bulk-suppress.test.ts +3 -18
- package/src/lib/data/engine.ts +16 -50
- package/src/lib/db/__tests__/mikro.test.ts +25 -0
- package/src/lib/db/mikro.ts +38 -13
- package/src/lib/db/pg-errors.ts +0 -58
- package/src/lib/di/container.ts +8 -72
- package/src/lib/email/__tests__/send.test.ts +1 -37
- package/src/lib/email/send.ts +0 -79
- package/src/lib/encryption/__tests__/subscriber.test.ts +2 -54
- package/src/lib/encryption/__tests__/tenantDataEncryptionService.test.ts +0 -58
- package/src/lib/encryption/subscriber.ts +14 -2
- package/src/lib/encryption/tenantDataEncryptionService.ts +18 -64
- package/src/lib/i18n/config.ts +2 -2
- package/src/lib/i18n/server.ts +2 -6
- package/src/lib/logger/__tests__/logger.test.ts +0 -77
- package/src/lib/logger/index.ts +1 -59
- package/src/lib/modules/registry.ts +0 -10
- package/src/lib/query/__tests__/engine.test.ts +13 -65
- package/src/lib/query/engine.ts +58 -75
- package/src/lib/query/types.ts +5 -4
- package/src/lib/redis/__tests__/connection.test.ts +0 -16
- package/src/lib/redis/connection.ts +7 -10
- package/src/lib/search/__tests__/config.test.ts +0 -177
- package/src/lib/search/config.ts +8 -106
- package/src/lib/search/tokenize.ts +11 -33
- package/src/lib/webhooks/__tests__/verify.test.ts +1 -20
- package/src/lib/webhooks/index.ts +1 -19
- package/src/lib/webhooks/verify.ts +9 -17
- package/src/modules/__tests__/registry.test.ts +2 -10
- package/src/modules/analytics.ts +0 -7
- package/src/modules/customer-auth.ts +0 -1
- package/src/modules/encryption.ts +0 -3
- package/src/modules/navigation/backendChrome.ts +0 -15
- package/src/modules/overrides.ts +0 -103
- package/src/modules/payment_gateways/types.ts +0 -41
- package/src/modules/registry.ts +0 -3
- package/src/security/enabledModulesRegistry.ts +12 -64
- package/dist/lib/ai/safety-identifier.js +0 -32
- package/dist/lib/ai/safety-identifier.js.map +0 -7
- package/dist/lib/auth/organizationScope.js +0 -34
- package/dist/lib/auth/organizationScope.js.map +0 -7
- package/dist/lib/bootstrap/clientOnlyModules.js +0 -55
- package/dist/lib/bootstrap/clientOnlyModules.js.map +0 -7
- package/dist/lib/data/consistency.js +0 -19
- package/dist/lib/data/consistency.js.map +0 -7
- package/dist/lib/db/entityMetadata.js +0 -26
- package/dist/lib/db/entityMetadata.js.map +0 -7
- package/dist/lib/logger/extension.js +0 -29
- package/dist/lib/logger/extension.js.map +0 -7
- package/dist/lib/modules/surfaceFingerprint.js +0 -47
- package/dist/lib/modules/surfaceFingerprint.js.map +0 -7
- package/dist/lib/query/ciphertext-search-warning.js +0 -45
- package/dist/lib/query/ciphertext-search-warning.js.map +0 -7
- package/dist/lib/search/auto-indexing.js +0 -14
- package/dist/lib/search/auto-indexing.js.map +0 -7
- package/dist/lib/search/availability.js +0 -107
- package/dist/lib/search/availability.js.map +0 -7
- package/dist/lib/search/tokenLookup.js +0 -46
- package/dist/lib/search/tokenLookup.js.map +0 -7
- package/dist/lib/telemetry/runtime.js +0 -35
- package/dist/lib/telemetry/runtime.js.map +0 -7
- package/dist/lib/webhooks/body.js +0 -57
- package/dist/lib/webhooks/body.js.map +0 -7
- package/dist/lib/webhooks/inbound-types.js +0 -1
- package/dist/lib/webhooks/inbound-types.js.map +0 -7
- package/dist/modules/widgets/extension-points.js +0 -96
- package/dist/modules/widgets/extension-points.js.map +0 -7
- package/dist/security/featurePolicy.js +0 -62
- package/dist/security/featurePolicy.js.map +0 -7
- package/scripts/versionSource.cjs +0 -65
- package/src/lib/__tests__/versionSource.test.ts +0 -46
- package/src/lib/ai/__tests__/llm-provider-contract.test.ts +0 -59
- package/src/lib/ai/__tests__/safety-identifier.test.ts +0 -71
- package/src/lib/ai/safety-identifier.ts +0 -73
- package/src/lib/auth/__tests__/organizationScope.test.ts +0 -107
- package/src/lib/auth/organizationScope.ts +0 -65
- package/src/lib/bootstrap/__tests__/clientOnlyModules.test.ts +0 -189
- package/src/lib/bootstrap/__tests__/dynamicLoader.aliasResolver.test.ts +0 -98
- package/src/lib/bootstrap/__tests__/dynamicLoader.appDi.test.ts +0 -211
- package/src/lib/bootstrap/__tests__/dynamicLoader.generatedCacheRecovery.test.ts +0 -256
- package/src/lib/bootstrap/clientOnlyModules.ts +0 -85
- package/src/lib/crud/__tests__/crud-factory.cache-user-scope.test.ts +0 -171
- package/src/lib/data/__tests__/consistency.test.ts +0 -38
- package/src/lib/data/consistency.ts +0 -17
- package/src/lib/db/__tests__/entityMetadata.test.ts +0 -38
- package/src/lib/db/__tests__/pg-errors.test.ts +0 -48
- package/src/lib/db/entityMetadata.ts +0 -43
- package/src/lib/di/__tests__/container-app-di-absent.test.ts +0 -155
- package/src/lib/di/__tests__/container-app-di.test.ts +0 -173
- package/src/lib/di/__tests__/registrar-error-log.test.ts +0 -154
- package/src/lib/i18n/__tests__/config.test.ts +0 -8
- package/src/lib/i18n/__tests__/server-dictionary-cache.test.ts +0 -30
- package/src/lib/i18n/__tests__/server-unbootstrapped.test.ts +0 -29
- package/src/lib/logger/extension.ts +0 -59
- package/src/lib/modules/__tests__/surfaceFingerprint.test.ts +0 -122
- package/src/lib/modules/surfaceFingerprint.ts +0 -87
- package/src/lib/query/__tests__/ciphertext-search-warning.test.ts +0 -178
- package/src/lib/query/ciphertext-search-warning.ts +0 -95
- package/src/lib/search/__tests__/availability.test.ts +0 -211
- package/src/lib/search/__tests__/tokenLookup.test.ts +0 -206
- package/src/lib/search/__tests__/tokenize.test.ts +0 -49
- package/src/lib/search/auto-indexing.ts +0 -22
- package/src/lib/search/availability.ts +0 -232
- package/src/lib/search/tokenLookup.ts +0 -133
- package/src/lib/telemetry/runtime.ts +0 -72
- package/src/lib/webhooks/__tests__/body.test.ts +0 -95
- package/src/lib/webhooks/body.ts +0 -70
- package/src/lib/webhooks/inbound-types.ts +0 -125
- package/src/modules/__tests__/nav-group-order-override.test.ts +0 -183
- package/src/modules/payment_gateways/__tests__/types.test.ts +0 -37
- package/src/modules/widgets/__tests__/extension-points.test.ts +0 -101
- package/src/modules/widgets/extension-points.ts +0 -524
- package/src/security/__tests__/featurePolicy.test.ts +0 -166
- package/src/security/featurePolicy.ts +0 -89
|
@@ -196,64 +196,6 @@ describe('TenantDataEncryptionService.encryptFields (issue #2720)', () => {
|
|
|
196
196
|
})
|
|
197
197
|
})
|
|
198
198
|
|
|
199
|
-
describe('TenantDataEncryptionService system-scoped maps', () => {
|
|
200
|
-
const originalToggle = process.env.TENANT_DATA_ENCRYPTION
|
|
201
|
-
|
|
202
|
-
beforeEach(() => {
|
|
203
|
-
process.env.TENANT_DATA_ENCRYPTION = 'yes'
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
afterEach(() => {
|
|
207
|
-
if (originalToggle === undefined) delete process.env.TENANT_DATA_ENCRYPTION
|
|
208
|
-
else process.env.TENANT_DATA_ENCRYPTION = originalToggle
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
it('encrypts tenant-less payloads with a stable entity-scoped KMS key', async () => {
|
|
212
|
-
const entityId = 'onboarding:onboarding_request'
|
|
213
|
-
const systemKeyId = `system:${entityId}`
|
|
214
|
-
const getTenantDek = jest.fn(async (keyId: string) => (
|
|
215
|
-
keyId === systemKeyId
|
|
216
|
-
? { tenantId: keyId, key: fixedKey, fetchedAt: new Date() }
|
|
217
|
-
: null
|
|
218
|
-
))
|
|
219
|
-
const service = new TenantDataEncryptionService(
|
|
220
|
-
{
|
|
221
|
-
getConnection: () => ({ execute: jest.fn(async () => []) }),
|
|
222
|
-
} as never,
|
|
223
|
-
{
|
|
224
|
-
kms: {
|
|
225
|
-
getTenantDek,
|
|
226
|
-
createTenantDek: jest.fn(async () => null),
|
|
227
|
-
isHealthy: () => true,
|
|
228
|
-
},
|
|
229
|
-
defaultEncryptionMaps: [
|
|
230
|
-
{
|
|
231
|
-
entityId,
|
|
232
|
-
keyScope: 'system',
|
|
233
|
-
fields: [
|
|
234
|
-
{ field: 'email', hashField: 'email_hash' },
|
|
235
|
-
{ field: 'password_hash' },
|
|
236
|
-
],
|
|
237
|
-
},
|
|
238
|
-
],
|
|
239
|
-
} as never,
|
|
240
|
-
)
|
|
241
|
-
|
|
242
|
-
const encrypted = await service.encryptEntityPayload(entityId, {
|
|
243
|
-
email: 'person@example.com',
|
|
244
|
-
email_hash: null,
|
|
245
|
-
password_hash: 'bcrypt-value',
|
|
246
|
-
}, null, null)
|
|
247
|
-
|
|
248
|
-
expect(getTenantDek).toHaveBeenCalledWith(systemKeyId)
|
|
249
|
-
expect(encrypted.email).not.toBe('person@example.com')
|
|
250
|
-
expect(encrypted.password_hash).not.toBe('bcrypt-value')
|
|
251
|
-
expect(encrypted.email_hash).toBe(hashForLookup('person@example.com'))
|
|
252
|
-
expect(decryptWithAesGcm(encrypted.email as string, fixedKey)).toBe('person@example.com')
|
|
253
|
-
expect(decryptWithAesGcm(encrypted.password_hash as string, fixedKey)).toBe('bcrypt-value')
|
|
254
|
-
})
|
|
255
|
-
})
|
|
256
|
-
|
|
257
199
|
describe('TenantDataEncryptionService.getEncryptedFieldNames', () => {
|
|
258
200
|
it('returns active encryption-map field names for query planning', async () => {
|
|
259
201
|
const service = new TenantDataEncryptionService({} as never)
|
|
@@ -6,7 +6,6 @@ import { isTenantDataEncryptionEnabled } from './toggles'
|
|
|
6
6
|
import { isEncryptionDebugEnabled } from './toggles'
|
|
7
7
|
import { resolveTenantEncryptionService } from './customFieldValues'
|
|
8
8
|
import { createLogger } from '../logger'
|
|
9
|
-
import { listEntityMetadataFromRegistry } from '../db/entityMetadata'
|
|
10
9
|
|
|
11
10
|
const logger = createLogger('shared').child({ component: 'encryption' })
|
|
12
11
|
|
|
@@ -86,8 +85,13 @@ export class TenantEncryptionSubscriber implements EventSubscriber<any> {
|
|
|
86
85
|
try { return registry.find?.(ctor) } catch {}
|
|
87
86
|
try { return registry.get?.(name) } catch {}
|
|
88
87
|
try { return registry.get?.(ctor) } catch {}
|
|
88
|
+
const all =
|
|
89
|
+
(typeof registry.getAll === 'function' && registry.getAll()) ||
|
|
90
|
+
(Array.isArray((registry as any).metadata) ? (registry as any).metadata : undefined) ||
|
|
91
|
+
(registry as any).metadata ||
|
|
92
|
+
{}
|
|
89
93
|
try {
|
|
90
|
-
const entries =
|
|
94
|
+
const entries = Array.isArray(all) ? all : Object.values<any>(all)
|
|
91
95
|
const match = entries.find(
|
|
92
96
|
(m: any) =>
|
|
93
97
|
m?.className === name ||
|
|
@@ -220,6 +224,10 @@ export class TenantEncryptionSubscriber implements EventSubscriber<any> {
|
|
|
220
224
|
return
|
|
221
225
|
}
|
|
222
226
|
const { tenantId, organizationId } = resolveScope(target)
|
|
227
|
+
if (!tenantId) {
|
|
228
|
+
debug('⚪️ subscriber.skip', { reason: 'no-tenant', entityId })
|
|
229
|
+
return
|
|
230
|
+
}
|
|
223
231
|
const encrypted = await this.service.encryptEntityPayload(entityId, target, tenantId, organizationId)
|
|
224
232
|
const metaProps: Record<string, unknown> = resolvedMeta?.properties && typeof resolvedMeta.properties === 'object'
|
|
225
233
|
? resolvedMeta.properties
|
|
@@ -317,6 +325,10 @@ export class TenantEncryptionSubscriber implements EventSubscriber<any> {
|
|
|
317
325
|
const { tenantId, organizationId } = resolveScope(target)
|
|
318
326
|
const scopedTenantId = tenantId ?? fallbackScope?.tenantId ?? null
|
|
319
327
|
const scopedOrgId = organizationId ?? fallbackScope?.organizationId ?? null
|
|
328
|
+
if (!scopedTenantId) {
|
|
329
|
+
debug('⚪️ subscriber.skip', { reason: 'no-tenant', entityId })
|
|
330
|
+
return
|
|
331
|
+
}
|
|
320
332
|
// Capture pending (un-flushed) changes BEFORE decrypt mutates the target. Re-baselining a
|
|
321
333
|
// managed entity that a command already mutated would clear its dirty changeset and silently
|
|
322
334
|
// drop the pending write (e.g. an undo handler that mutates an entity, then loads a related
|
|
@@ -4,7 +4,6 @@ import { decryptWithAesGcm, encryptWithAesGcm, hashForLookup } from './aes'
|
|
|
4
4
|
import { createKmsService, type KmsService, type TenantDek } from './kms'
|
|
5
5
|
import { isTenantDataEncryptionEnabled, isEncryptionDebugEnabled } from './toggles'
|
|
6
6
|
import { createLogger } from '../logger'
|
|
7
|
-
import type { EncryptionKeyScope, ModuleEncryptionMap } from '../../modules/encryption'
|
|
8
7
|
|
|
9
8
|
const logger = createLogger('shared').child({ component: 'tenant-encryption' })
|
|
10
9
|
|
|
@@ -15,7 +14,6 @@ export type EncryptedFieldRule = {
|
|
|
15
14
|
|
|
16
15
|
export type EncryptionMapRecord = {
|
|
17
16
|
entityId: string
|
|
18
|
-
keyScope?: EncryptionKeyScope
|
|
19
17
|
fields: EncryptedFieldRule[]
|
|
20
18
|
}
|
|
21
19
|
|
|
@@ -156,23 +154,13 @@ export class TenantDataEncryptionService {
|
|
|
156
154
|
private readonly inflightDeks = TenantDataEncryptionService.globalInflightDeks
|
|
157
155
|
private readonly inflightMaps = TenantDataEncryptionService.globalInflightMaps
|
|
158
156
|
private readonly missCache = TenantDataEncryptionService.globalMissCache
|
|
159
|
-
private readonly systemDefaultMaps: Map<string, ModuleEncryptionMap>
|
|
160
157
|
|
|
161
158
|
constructor(
|
|
162
159
|
private em: EntityManager,
|
|
163
|
-
opts?: {
|
|
164
|
-
cache?: CacheStrategy
|
|
165
|
-
kms?: KmsService
|
|
166
|
-
defaultEncryptionMaps?: readonly ModuleEncryptionMap[]
|
|
167
|
-
}
|
|
160
|
+
opts?: { cache?: CacheStrategy; kms?: KmsService }
|
|
168
161
|
) {
|
|
169
162
|
this.cache = opts?.cache
|
|
170
163
|
this.kms = opts?.kms ?? createKmsService()
|
|
171
|
-
this.systemDefaultMaps = new Map(
|
|
172
|
-
(opts?.defaultEncryptionMaps ?? [])
|
|
173
|
-
.filter((map) => map.keyScope === 'system')
|
|
174
|
-
.map((map) => [map.entityId, map]),
|
|
175
|
-
)
|
|
176
164
|
}
|
|
177
165
|
|
|
178
166
|
isEnabled(): boolean {
|
|
@@ -251,24 +239,6 @@ export class TenantDataEncryptionService {
|
|
|
251
239
|
}
|
|
252
240
|
}
|
|
253
241
|
|
|
254
|
-
private applySystemDefault(record: EncryptionMapRecord | null, entityId: string): EncryptionMapRecord | null {
|
|
255
|
-
const declared = this.systemDefaultMaps.get(entityId)
|
|
256
|
-
if (!declared) return record
|
|
257
|
-
const fields: EncryptedFieldRule[] = declared.fields.map((field) => ({
|
|
258
|
-
field: field.field,
|
|
259
|
-
hashField: field.hashField ?? null,
|
|
260
|
-
}))
|
|
261
|
-
const declaredFields = new Set(fields.map((field) => field.field))
|
|
262
|
-
for (const field of record?.fields ?? []) {
|
|
263
|
-
if (!declaredFields.has(field.field)) fields.push(field)
|
|
264
|
-
}
|
|
265
|
-
return {
|
|
266
|
-
entityId,
|
|
267
|
-
keyScope: 'system',
|
|
268
|
-
fields,
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
242
|
private async getMap(key: MapCacheKey): Promise<EncryptionMapRecord | null> {
|
|
273
243
|
const shouldSkipLookup = (tag: string) => {
|
|
274
244
|
const expiresAt = this.missCache.get(tag)
|
|
@@ -292,13 +262,13 @@ export class TenantDataEncryptionService {
|
|
|
292
262
|
if (this.inflightMaps.has(tag)) {
|
|
293
263
|
const pending = this.inflightMaps.get(tag)!
|
|
294
264
|
const resolved = await pending
|
|
295
|
-
if (resolved) return
|
|
265
|
+
if (resolved) return resolved
|
|
296
266
|
}
|
|
297
267
|
const mem = this.memoryCache.get(tag)
|
|
298
|
-
if (mem) return
|
|
268
|
+
if (mem) return mem
|
|
299
269
|
if (this.cache && typeof this.cache.get === 'function') {
|
|
300
270
|
const cached = await this.cache.get(tag)
|
|
301
|
-
if (cached) return
|
|
271
|
+
if (cached) return cached as EncryptionMapRecord
|
|
302
272
|
}
|
|
303
273
|
const pending = this.fetchMap(candidate)
|
|
304
274
|
this.inflightMaps.set(tag, pending)
|
|
@@ -318,9 +288,9 @@ export class TenantDataEncryptionService {
|
|
|
318
288
|
if (this.cache && typeof this.cache.set === 'function') {
|
|
319
289
|
await this.cache.set(tag, loaded, { ttl: 300 })
|
|
320
290
|
}
|
|
321
|
-
return
|
|
291
|
+
return loaded
|
|
322
292
|
}
|
|
323
|
-
return
|
|
293
|
+
return null
|
|
324
294
|
}
|
|
325
295
|
|
|
326
296
|
private async fetchAllOrganizationFieldNames(entityId: string, tenantId: string | null): Promise<string[]> {
|
|
@@ -366,26 +336,12 @@ export class TenantDataEncryptionService {
|
|
|
366
336
|
this.kms.invalidateDek?.(tenantId)
|
|
367
337
|
}
|
|
368
338
|
|
|
369
|
-
/**
|
|
370
|
-
* Lists the fields an encryption map marks as encrypted at rest.
|
|
371
|
-
*
|
|
372
|
-
* `isEnabled()` folds the environment toggle together with KMS health, so by default an
|
|
373
|
-
* unhealthy KMS reports "nothing is encrypted" — which is safe for write paths but wrong for
|
|
374
|
-
* readers that must decide whether a stored column holds ciphertext. `ignoreRuntimeHealth`
|
|
375
|
-
* answers the on-disk question instead: it consults the map even when the KMS cannot currently
|
|
376
|
-
* resolve a DEK, so callers can fail closed rather than treat ciphertext as plaintext (#4622).
|
|
377
|
-
*/
|
|
378
339
|
async getEncryptedFieldNames(
|
|
379
340
|
entityId: string,
|
|
380
341
|
tenantId: string | null | undefined,
|
|
381
|
-
organizationId?: string | null
|
|
382
|
-
options?: { ignoreRuntimeHealth?: boolean }
|
|
342
|
+
organizationId?: string | null
|
|
383
343
|
): Promise<string[]> {
|
|
384
|
-
if (
|
|
385
|
-
if (!isTenantDataEncryptionEnabled()) return []
|
|
386
|
-
} else if (!this.isEnabled()) {
|
|
387
|
-
return []
|
|
388
|
-
}
|
|
344
|
+
if (!this.isEnabled()) return []
|
|
389
345
|
const map = await this.getMap({ entityId, tenantId: tenantId ?? null, organizationId: organizationId ?? null })
|
|
390
346
|
const fields = new Set(normalizeEncryptedFieldNames(map?.fields))
|
|
391
347
|
if (organizationId == null) {
|
|
@@ -465,17 +421,16 @@ export class TenantDataEncryptionService {
|
|
|
465
421
|
debug('⚪️ encrypt.skip.disabled', { entityId, tenantId })
|
|
466
422
|
return payload
|
|
467
423
|
}
|
|
424
|
+
const dek = await this.resolveDekForEncrypt(tenantId ?? null)
|
|
425
|
+
if (!dek) {
|
|
426
|
+
debug('⚠️ encrypt.skip.no-dek', { entityId, tenantId })
|
|
427
|
+
return payload
|
|
428
|
+
}
|
|
468
429
|
const map = await this.getMap({ entityId, tenantId: tenantId ?? null, organizationId: organizationId ?? null })
|
|
469
430
|
if (!map || !map.fields?.length) {
|
|
470
431
|
debug('⚪️ encrypt.skip.no-map', { entityId, tenantId })
|
|
471
432
|
return payload
|
|
472
433
|
}
|
|
473
|
-
const keyId = map.keyScope === 'system' ? `system:${entityId}` : tenantId ?? null
|
|
474
|
-
const dek = await this.resolveDekForEncrypt(keyId)
|
|
475
|
-
if (!dek) {
|
|
476
|
-
debug('⚠️ encrypt.skip.no-dek', { entityId, tenantId, keyScope: map.keyScope ?? 'tenant' })
|
|
477
|
-
return payload
|
|
478
|
-
}
|
|
479
434
|
debug('🔒 encrypt_entity', { entityId, tenantId, organizationId, fields: map.fields.length })
|
|
480
435
|
return this.encryptFields(payload, map.fields, dek)
|
|
481
436
|
}
|
|
@@ -490,17 +445,16 @@ export class TenantDataEncryptionService {
|
|
|
490
445
|
debug('⚪️ decrypt.skip.disabled', { entityId, tenantId })
|
|
491
446
|
return payload
|
|
492
447
|
}
|
|
448
|
+
const dek = await this.getDek(tenantId ?? null)
|
|
449
|
+
if (!dek) {
|
|
450
|
+
debug('⚠️ decrypt.skip.no-dek', { entityId, tenantId })
|
|
451
|
+
return payload
|
|
452
|
+
}
|
|
493
453
|
const map = await this.getMap({ entityId, tenantId: tenantId ?? null, organizationId: organizationId ?? null })
|
|
494
454
|
if (!map || !map.fields?.length) {
|
|
495
455
|
debug('⚪️ decrypt.skip.no-map', { entityId, tenantId })
|
|
496
456
|
return payload
|
|
497
457
|
}
|
|
498
|
-
const keyId = map.keyScope === 'system' ? `system:${entityId}` : tenantId ?? null
|
|
499
|
-
const dek = await this.getDek(keyId)
|
|
500
|
-
if (!dek) {
|
|
501
|
-
debug('⚠️ decrypt.skip.no-dek', { entityId, tenantId, keyScope: map.keyScope ?? 'tenant' })
|
|
502
|
-
return payload
|
|
503
|
-
}
|
|
504
458
|
debug('🔓 decrypt_entity', { entityId, tenantId, organizationId, fields: map.fields.length })
|
|
505
459
|
return this.decryptFields(payload, map.fields, dek)
|
|
506
460
|
}
|
package/src/lib/i18n/config.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type Locale = 'en' | 'pl' | 'es' | 'de'
|
|
1
|
+
export type Locale = 'en' | 'pl' | 'es' | 'de'
|
|
2
2
|
|
|
3
|
-
export const locales: Locale[] = ['en', 'pl', 'es', 'de'
|
|
3
|
+
export const locales: Locale[] = ['en', 'pl', 'es', 'de']
|
|
4
4
|
export const defaultLocale: Locale = 'en'
|
package/src/lib/i18n/server.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { defaultLocale, locales, type Locale } from './config'
|
|
|
2
2
|
import type { Dict } from './context'
|
|
3
3
|
import { resolveForcedLocale, resolveLocaleFromAcceptLanguage } from './locale'
|
|
4
4
|
import { createFallbackTranslator, createTranslator } from './translate'
|
|
5
|
-
import {
|
|
5
|
+
import { getModules } from '../modules/registry'
|
|
6
6
|
import { loadAppDictionary } from './app-dictionaries'
|
|
7
7
|
import { getCachedDictionary, setCachedDictionary } from './dictionary-cache'
|
|
8
8
|
|
|
@@ -61,11 +61,7 @@ export async function loadDictionary(locale: Locale): Promise<Dict> {
|
|
|
61
61
|
// Load from registry instead of @/ import (works in standalone packages)
|
|
62
62
|
const baseRaw = await loadAppDictionary(locale)
|
|
63
63
|
const merged: Dict = { ...flattenDictionary(baseRaw) }
|
|
64
|
-
|
|
65
|
-
// when they are exercised in isolation without a bootstrapped registry. The
|
|
66
|
-
// app dictionary alone is the right degraded answer there — `registerModules`
|
|
67
|
-
// invalidates this cache, so a later bootstrap still gets the merged result.
|
|
68
|
-
const modules = tryGetModules() ?? []
|
|
64
|
+
const modules = getModules()
|
|
69
65
|
for (const m of modules) {
|
|
70
66
|
const dict = m.translations?.[locale]
|
|
71
67
|
if (dict) Object.assign(merged, flattenDictionary(dict))
|
|
@@ -3,9 +3,7 @@ import {
|
|
|
3
3
|
createLogger,
|
|
4
4
|
getLogLevel,
|
|
5
5
|
isLevelEnabled,
|
|
6
|
-
registerLoggerExtension,
|
|
7
6
|
resetLogLevelCache,
|
|
8
|
-
resetLoggerExtension,
|
|
9
7
|
resetLoggerRegistry,
|
|
10
8
|
} from '../index'
|
|
11
9
|
import { resolveLevel, type LogLevel } from '../level'
|
|
@@ -67,7 +65,6 @@ function resetLoggerState(): void {
|
|
|
67
65
|
resetLoggerRegistry()
|
|
68
66
|
resetServerLoggerCache()
|
|
69
67
|
resetLogPrettyCache()
|
|
70
|
-
resetLoggerExtension()
|
|
71
68
|
}
|
|
72
69
|
|
|
73
70
|
function forcePinoTransport(): void {
|
|
@@ -244,80 +241,6 @@ describe('structured logging facade', () => {
|
|
|
244
241
|
})
|
|
245
242
|
})
|
|
246
243
|
|
|
247
|
-
describe('process-wide logger extension', () => {
|
|
248
|
-
beforeEach(() => {
|
|
249
|
-
forcePinoTransport()
|
|
250
|
-
})
|
|
251
|
-
|
|
252
|
-
it('keeps one local line and observes one correlated record with child bindings', () => {
|
|
253
|
-
const fake = createFakePinoHarness()
|
|
254
|
-
mockPinoLoader(fake.factory)
|
|
255
|
-
const records: unknown[] = []
|
|
256
|
-
registerLoggerExtension({
|
|
257
|
-
enrich: () => ({ trace_id: 'trace-1', span_id: 'span-1' }),
|
|
258
|
-
emit: (record) => records.push(record),
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
createLogger('orders')
|
|
262
|
-
.child({ module: 'sales' })
|
|
263
|
-
.info('Order placed', { orderId: 'o-1' })
|
|
264
|
-
|
|
265
|
-
expect(fake.calls).toEqual([{
|
|
266
|
-
level: 'info',
|
|
267
|
-
args: [{
|
|
268
|
-
orderId: 'o-1',
|
|
269
|
-
trace_id: 'trace-1',
|
|
270
|
-
span_id: 'span-1',
|
|
271
|
-
}, 'Order placed'],
|
|
272
|
-
}])
|
|
273
|
-
expect(records).toHaveLength(1)
|
|
274
|
-
expect(records[0]).toMatchObject({
|
|
275
|
-
level: 'info',
|
|
276
|
-
namespace: 'orders',
|
|
277
|
-
message: 'Order placed',
|
|
278
|
-
fields: {
|
|
279
|
-
module: 'sales',
|
|
280
|
-
orderId: 'o-1',
|
|
281
|
-
trace_id: 'trace-1',
|
|
282
|
-
span_id: 'span-1',
|
|
283
|
-
},
|
|
284
|
-
})
|
|
285
|
-
})
|
|
286
|
-
|
|
287
|
-
it('applies the shared level gate to local and remote output', () => {
|
|
288
|
-
process.env.OM_LOG_LEVEL = 'warn'
|
|
289
|
-
resetLogLevelCache()
|
|
290
|
-
const fake = createFakePinoHarness()
|
|
291
|
-
mockPinoLoader(fake.factory)
|
|
292
|
-
const emit = jest.fn()
|
|
293
|
-
registerLoggerExtension({ emit })
|
|
294
|
-
const logger = createLogger('gated-extension')
|
|
295
|
-
|
|
296
|
-
logger.info('quiet')
|
|
297
|
-
logger.warn('visible')
|
|
298
|
-
|
|
299
|
-
expect(fake.calls.map((call) => call.level)).toEqual(['warn'])
|
|
300
|
-
expect(emit).toHaveBeenCalledTimes(1)
|
|
301
|
-
expect(emit.mock.calls[0][0]).toMatchObject({ level: 'warn', message: 'visible' })
|
|
302
|
-
})
|
|
303
|
-
|
|
304
|
-
it('isolates extension failures from application logging', () => {
|
|
305
|
-
const fake = createFakePinoHarness()
|
|
306
|
-
mockPinoLoader(fake.factory)
|
|
307
|
-
registerLoggerExtension({
|
|
308
|
-
enrich: () => {
|
|
309
|
-
throw new Error('context failed')
|
|
310
|
-
},
|
|
311
|
-
emit: () => {
|
|
312
|
-
throw new Error('sink failed')
|
|
313
|
-
},
|
|
314
|
-
})
|
|
315
|
-
|
|
316
|
-
expect(() => createLogger('resilient-extension').error('Still local')).not.toThrow()
|
|
317
|
-
expect(fake.calls).toEqual([{ level: 'error', args: [{}, 'Still local'] }])
|
|
318
|
-
})
|
|
319
|
-
})
|
|
320
|
-
|
|
321
244
|
describe('isomorphism', () => {
|
|
322
245
|
beforeEach(() => {
|
|
323
246
|
forcePinoTransport()
|
package/src/lib/logger/index.ts
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
import { selectTransport } from './transport'
|
|
2
|
-
import { getLoggerExtension } from './extension'
|
|
3
|
-
import { isLevelEnabled, type LogLevel } from './level'
|
|
4
2
|
|
|
5
3
|
export type { LogLevel } from './level'
|
|
6
4
|
export { getLogLevel, isLevelEnabled, resetLogLevelCache, OM_LOG_LEVEL_ENV } from './level'
|
|
7
5
|
export { resetServerLoggerCache, OM_LOG_DESTINATION_ENV } from './transport.server'
|
|
8
6
|
export { resetLogPrettyCache, OM_LOG_PRETTY_ENV } from './transport.pretty'
|
|
9
|
-
export {
|
|
10
|
-
getLoggerExtension,
|
|
11
|
-
registerLoggerExtension,
|
|
12
|
-
resetLoggerExtension,
|
|
13
|
-
} from './extension'
|
|
14
|
-
export type { LoggerExtension, LoggerExtensionRecord } from './extension'
|
|
15
7
|
|
|
16
8
|
export type LogBindings = Record<string, unknown>
|
|
17
9
|
|
|
@@ -26,61 +18,11 @@ export interface Logger {
|
|
|
26
18
|
|
|
27
19
|
const loggerRegistry = new Map<string, Logger>()
|
|
28
20
|
|
|
29
|
-
function createExtendedLogger(
|
|
30
|
-
namespace: string,
|
|
31
|
-
transport: Logger,
|
|
32
|
-
bindings: LogBindings = {},
|
|
33
|
-
): Logger {
|
|
34
|
-
const emit = (
|
|
35
|
-
level: LogLevel,
|
|
36
|
-
msg: string,
|
|
37
|
-
fields?: LogBindings,
|
|
38
|
-
): void => {
|
|
39
|
-
if (!isLevelEnabled(level)) return
|
|
40
|
-
|
|
41
|
-
const extension = getLoggerExtension()
|
|
42
|
-
let context: LogBindings = {}
|
|
43
|
-
try {
|
|
44
|
-
context = extension?.enrich?.() ?? {}
|
|
45
|
-
} catch {
|
|
46
|
-
// Observability must never alter application behavior.
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const mergedFields = { ...fields, ...context }
|
|
50
|
-
transport[level](msg, mergedFields)
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
extension?.emit?.({
|
|
54
|
-
level,
|
|
55
|
-
namespace,
|
|
56
|
-
message: msg,
|
|
57
|
-
fields: { ...bindings, ...mergedFields },
|
|
58
|
-
time: Date.now(),
|
|
59
|
-
})
|
|
60
|
-
} catch {
|
|
61
|
-
// Remote logging is best-effort; the local line has already been written.
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
debug: (msg, fields) => emit('debug', msg, fields),
|
|
67
|
-
info: (msg, fields) => emit('info', msg, fields),
|
|
68
|
-
warn: (msg, fields) => emit('warn', msg, fields),
|
|
69
|
-
error: (msg, fields) => emit('error', msg, fields),
|
|
70
|
-
child: (childBindings) =>
|
|
71
|
-
createExtendedLogger(
|
|
72
|
-
namespace,
|
|
73
|
-
transport.child(childBindings),
|
|
74
|
-
{ ...bindings, ...childBindings },
|
|
75
|
-
),
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
21
|
/** Create (or reuse) a namespaced logger. `namespace` is attached as `name`. */
|
|
80
22
|
export function createLogger(namespace: string): Logger {
|
|
81
23
|
const existing = loggerRegistry.get(namespace)
|
|
82
24
|
if (existing) return existing
|
|
83
|
-
const created =
|
|
25
|
+
const created = selectTransport(namespace)
|
|
84
26
|
loggerRegistry.set(namespace, created)
|
|
85
27
|
return created
|
|
86
28
|
}
|
|
@@ -69,13 +69,3 @@ export function getModules(): Module[] {
|
|
|
69
69
|
}
|
|
70
70
|
return modules
|
|
71
71
|
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Non-throwing counterpart of `getModules()` for call sites that have a
|
|
75
|
-
* meaningful degraded behavior when bootstrap has not run — route unit tests
|
|
76
|
-
* exercise a single handler without `registerModules()`, and a hard throw there
|
|
77
|
-
* turns an unrelated assertion into a bootstrap error.
|
|
78
|
-
*/
|
|
79
|
-
export function tryGetModules(): Module[] | null {
|
|
80
|
-
return getGlobalModules()
|
|
81
|
-
}
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import { BasicQueryEngine } from '../engine'
|
|
2
2
|
import { SortDir } from '../types'
|
|
3
3
|
import { registerModules } from '../../i18n/server'
|
|
4
|
-
import { clearSearchTokenPresenceCache } from '../../search/availability'
|
|
5
|
-
|
|
6
|
-
// The token-presence answer is cached process-wide (TTL); without clearing it,
|
|
7
|
-
// probe-count assertions would observe hits from earlier tests in this file.
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
clearSearchTokenPresenceCache()
|
|
10
|
-
})
|
|
11
4
|
|
|
12
5
|
// Mock modules with one entity extension
|
|
13
6
|
const mockModules = [
|
|
@@ -362,8 +355,6 @@ describe('BasicQueryEngine (Kysely)', () => {
|
|
|
362
355
|
const fakeDb = createFakeKysely({
|
|
363
356
|
customer_entities: [],
|
|
364
357
|
customer_people: [],
|
|
365
|
-
search_tokens: [{ one: 1 }],
|
|
366
|
-
'information_schema.tables': [{ table_name: 'search_tokens' }],
|
|
367
358
|
'information_schema.columns': [
|
|
368
359
|
{ table_name: 'customer_entities', column_name: 'tenant_id' },
|
|
369
360
|
{ table_name: 'customer_people', column_name: 'id' },
|
|
@@ -371,6 +362,8 @@ describe('BasicQueryEngine (Kysely)', () => {
|
|
|
371
362
|
],
|
|
372
363
|
})
|
|
373
364
|
const engine = new BasicQueryEngine({} as any, () => fakeDb as any)
|
|
365
|
+
jest.spyOn(engine as any, 'tableExists').mockResolvedValue(true)
|
|
366
|
+
jest.spyOn(engine as any, 'hasSearchTokens').mockResolvedValue(true)
|
|
374
367
|
const applySearchTokensSpy = jest.spyOn(engine as any, 'applySearchTokens')
|
|
375
368
|
|
|
376
369
|
await engine.query('customers:customer_entity', {
|
|
@@ -416,6 +409,7 @@ describe('BasicQueryEngine (Kysely)', () => {
|
|
|
416
409
|
],
|
|
417
410
|
})
|
|
418
411
|
const engine = new BasicQueryEngine({} as any, () => fakeDb as any)
|
|
412
|
+
jest.spyOn(engine as any, 'tableExists').mockResolvedValue(false)
|
|
419
413
|
const applySearchTokensSpy = jest.spyOn(engine as any, 'applySearchTokens')
|
|
420
414
|
|
|
421
415
|
await engine.query('customers:customer_entity', {
|
|
@@ -451,10 +445,10 @@ describe('BasicQueryEngine (Kysely)', () => {
|
|
|
451
445
|
test('uses search tokens for index document fields on base entities', async () => {
|
|
452
446
|
const fakeDb = createFakeKysely({
|
|
453
447
|
todos: [],
|
|
454
|
-
search_tokens: [{ one: 1 }],
|
|
455
|
-
'information_schema.tables': [{ table_name: 'search_tokens' }],
|
|
456
448
|
})
|
|
457
449
|
const engine = new BasicQueryEngine({} as any, () => fakeDb as any)
|
|
450
|
+
const tableExistsSpy = jest.spyOn(engine as any, 'tableExists').mockResolvedValue(true)
|
|
451
|
+
const hasSearchTokensSpy = jest.spyOn(engine as any, 'hasSearchTokens').mockResolvedValue(true)
|
|
458
452
|
const applySearchTokensSpy = jest.spyOn(engine as any, 'applySearchTokens')
|
|
459
453
|
|
|
460
454
|
await engine.query('example:todo', {
|
|
@@ -467,15 +461,12 @@ describe('BasicQueryEngine (Kysely)', () => {
|
|
|
467
461
|
page: { page: 1, pageSize: 10 },
|
|
468
462
|
})
|
|
469
463
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
call._ops.table === 'search_tokens' &&
|
|
477
|
-
call._ops.wheres.some((where) => where[0] === 'entity_type' && where[2] === 'example:todo'))
|
|
478
|
-
expect(tokenProbe).toBeTruthy()
|
|
464
|
+
expect(tableExistsSpy).toHaveBeenCalledWith('search_tokens')
|
|
465
|
+
expect(hasSearchTokensSpy).toHaveBeenCalledWith(
|
|
466
|
+
'example:todo',
|
|
467
|
+
't1',
|
|
468
|
+
expect.objectContaining({ ids: ['org1'] }),
|
|
469
|
+
)
|
|
479
470
|
expect(applySearchTokensSpy).toHaveBeenCalledWith(
|
|
480
471
|
expect.anything(),
|
|
481
472
|
expect.objectContaining({
|
|
@@ -497,6 +488,7 @@ describe('BasicQueryEngine (Kysely)', () => {
|
|
|
497
488
|
],
|
|
498
489
|
})
|
|
499
490
|
const engine = new BasicQueryEngine({} as any, () => fakeDb as any)
|
|
491
|
+
const hasSearchTokensSpy = jest.spyOn(engine as any, 'hasSearchTokens').mockResolvedValue(true)
|
|
500
492
|
const applySearchTokensSpy = jest.spyOn(engine as any, 'applySearchTokens')
|
|
501
493
|
|
|
502
494
|
await engine.query('example:todo', {
|
|
@@ -509,8 +501,7 @@ describe('BasicQueryEngine (Kysely)', () => {
|
|
|
509
501
|
page: { page: 1, pageSize: 10 },
|
|
510
502
|
})
|
|
511
503
|
|
|
512
|
-
|
|
513
|
-
expect(calls.some((call) => call._ops.table === 'search_tokens')).toBe(false)
|
|
504
|
+
expect(hasSearchTokensSpy).not.toHaveBeenCalled()
|
|
514
505
|
expect(applySearchTokensSpy).not.toHaveBeenCalled()
|
|
515
506
|
const baseCall = fakeDb._calls.find((builder: any) => builder._ops.table === 'todos')
|
|
516
507
|
expect(baseCall?._ops.wheres).toContainEqual(['todos.search_text', 'ilike', '%avision%'])
|
|
@@ -942,47 +933,4 @@ describe('BasicQueryEngine (Kysely)', () => {
|
|
|
942
933
|
expect(baseCall._ops.limits).toBe(10)
|
|
943
934
|
expect(baseCall._ops.offsets).toBe(10)
|
|
944
935
|
})
|
|
945
|
-
|
|
946
|
-
describe('search_tokens coverage probe (#4723 parity)', () => {
|
|
947
|
-
type ProbeDbLog = { _calls: Array<{ _ops: { table: string } }> }
|
|
948
|
-
|
|
949
|
-
const countProbes = (fakeDb: ProbeDbLog): number =>
|
|
950
|
-
fakeDb._calls.filter((call) => call._ops.table === 'search_tokens').length
|
|
951
|
-
|
|
952
|
-
const buildEngine = (fakeDb: unknown): BasicQueryEngine => new BasicQueryEngine(
|
|
953
|
-
{} as ConstructorParameters<typeof BasicQueryEngine>[0],
|
|
954
|
-
(() => fakeDb) as unknown as NonNullable<ConstructorParameters<typeof BasicQueryEngine>[1]>,
|
|
955
|
-
)
|
|
956
|
-
|
|
957
|
-
const buildDb = () => createFakeKysely({
|
|
958
|
-
users: [],
|
|
959
|
-
'information_schema.tables': [{ table_name: 'search_tokens' }],
|
|
960
|
-
})
|
|
961
|
-
|
|
962
|
-
test('is skipped when the query carries no like/ilike filter', async () => {
|
|
963
|
-
const fakeDb = buildDb()
|
|
964
|
-
const engine = buildEngine(fakeDb)
|
|
965
|
-
|
|
966
|
-
await engine.query('auth:user', {
|
|
967
|
-
tenantId: 't1',
|
|
968
|
-
organizationId: 'org1',
|
|
969
|
-
filters: { is_active: { $eq: true } },
|
|
970
|
-
})
|
|
971
|
-
|
|
972
|
-
expect(countProbes(fakeDb)).toBe(0)
|
|
973
|
-
})
|
|
974
|
-
|
|
975
|
-
test('still runs when the query actually searches', async () => {
|
|
976
|
-
const fakeDb = buildDb()
|
|
977
|
-
const engine = buildEngine(fakeDb)
|
|
978
|
-
|
|
979
|
-
await engine.query('auth:user', {
|
|
980
|
-
tenantId: 't1',
|
|
981
|
-
organizationId: 'org1',
|
|
982
|
-
filters: { email: { $ilike: '%abc%' } },
|
|
983
|
-
})
|
|
984
|
-
|
|
985
|
-
expect(countProbes(fakeDb)).toBeGreaterThan(0)
|
|
986
|
-
})
|
|
987
|
-
})
|
|
988
936
|
})
|