@open-mercato/shared 0.5.1-develop.2691.d8a0934b37 → 0.5.1-develop.2699.f8b50c8046

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 (34) hide show
  1. package/dist/lib/api/crud.js +1 -1
  2. package/dist/lib/api/crud.js.map +2 -2
  3. package/dist/lib/auth/server.js +1 -1
  4. package/dist/lib/auth/server.js.map +2 -2
  5. package/dist/lib/data/engine.js +68 -27
  6. package/dist/lib/data/engine.js.map +2 -2
  7. package/dist/lib/db/mikro.js +18 -22
  8. package/dist/lib/db/mikro.js.map +2 -2
  9. package/dist/lib/indexers/error-log.js +10 -12
  10. package/dist/lib/indexers/error-log.js.map +2 -2
  11. package/dist/lib/indexers/status-log.js +14 -16
  12. package/dist/lib/indexers/status-log.js.map +2 -2
  13. package/dist/lib/query/engine.js +220 -228
  14. package/dist/lib/query/engine.js.map +3 -3
  15. package/dist/lib/query/join-utils.js +28 -23
  16. package/dist/lib/query/join-utils.js.map +2 -2
  17. package/dist/lib/version.js +1 -1
  18. package/dist/lib/version.js.map +1 -1
  19. package/jest.config.cjs +4 -2
  20. package/package.json +1 -1
  21. package/src/lib/api/__tests__/crud.test.ts +5 -3
  22. package/src/lib/api/crud.ts +1 -1
  23. package/src/lib/auth/__tests__/server.apiKeyCache.test.ts +10 -4
  24. package/src/lib/auth/server.ts +1 -1
  25. package/src/lib/bootstrap/types.ts +2 -2
  26. package/src/lib/crud/__tests__/crud-factory.test.ts +27 -17
  27. package/src/lib/data/engine.ts +95 -47
  28. package/src/lib/db/mikro.ts +26 -25
  29. package/src/lib/indexers/error-log.ts +23 -23
  30. package/src/lib/indexers/status-log.ts +36 -33
  31. package/src/lib/query/__tests__/engine.scope-and-or.test.ts +253 -114
  32. package/src/lib/query/__tests__/engine.test.ts +206 -139
  33. package/src/lib/query/engine.ts +306 -263
  34. package/src/lib/query/join-utils.ts +38 -30
@@ -36,7 +36,7 @@ async function findOneScoped(em, entity, id, scope) {
36
36
  async function softDelete(em, entity) {
37
37
  ;
38
38
  entity.deletedAt = /* @__PURE__ */ new Date();
39
- await em.persistAndFlush(entity);
39
+ await em.persist(entity).flush();
40
40
  }
41
41
  export {
42
42
  buildScopedWhere,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/api/crud.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/core'\n\ntype Scope = { organizationId?: string | null; organizationIds?: string[] | null; tenantId?: string | null }\n\nexport function buildScopedWhere(\n base: Record<string, any>,\n scope: Scope & { orgField?: string | null; tenantField?: string | null; softDeleteField?: string | null }\n): Record<string, any> {\n const where: any = { ...base }\n const orgField = scope.orgField === null ? null : (scope.orgField as string) || 'organizationId'\n const tenantField = scope.tenantField === null ? null : (scope.tenantField as string) || 'tenantId'\n const softField = scope.softDeleteField === null ? null : (scope.softDeleteField as string) || 'deletedAt'\n\n if (orgField) {\n if (scope.organizationIds !== undefined) {\n const ids = (scope.organizationIds ?? [])\n .map((id) => (typeof id === 'string' ? id.trim() : id))\n .filter((id): id is string => typeof id === 'string' && id.length > 0)\n if (ids.length === 0) {\n where[orgField] = { $in: [] }\n } else if (ids.length === 1) {\n where[orgField] = ids[0]\n } else {\n where[orgField] = { $in: ids }\n }\n } else if (scope.organizationId !== undefined) {\n where[orgField] = scope.organizationId\n }\n }\n\n if (tenantField && scope.tenantId !== undefined) where[tenantField] = scope.tenantId\n if (softField) where[softField] = null\n return where\n}\n\nexport function extractScopeFromAuth(auth: { orgId?: string | null; tenantId?: string | null } | null | undefined): { organizationId?: string | null; tenantId?: string | null } {\n if (!auth) return {}\n return { organizationId: auth.orgId ?? null, tenantId: auth.tenantId ?? null }\n}\n\nexport async function findOneScoped<T extends { id: string }>(\n em: EntityManager,\n entity: { new (): T },\n id: string,\n scope: Scope & { orgField?: keyof T; tenantField?: keyof T }\n): Promise<T | null> {\n const orgField = (scope.orgField as string) || 'organizationId'\n const tenantField = (scope.tenantField as string) || 'tenantId'\n const where: any = { id }\n if (scope.organizationId != null) where[orgField] = scope.organizationId\n if (scope.tenantId != null) where[tenantField] = scope.tenantId\n return em.getRepository(entity).findOne(where as any)\n}\n\nexport async function softDelete<T extends { deletedAt?: Date | null }>(\n em: EntityManager,\n entity: T\n): Promise<void> {\n ;(entity as any).deletedAt = new Date()\n await em.persistAndFlush(entity)\n}\n"],
5
- "mappings": "AAIO,SAAS,iBACd,MACA,OACqB;AACrB,QAAM,QAAa,EAAE,GAAG,KAAK;AAC7B,QAAM,WAAW,MAAM,aAAa,OAAO,OAAQ,MAAM,YAAuB;AAChF,QAAM,cAAc,MAAM,gBAAgB,OAAO,OAAQ,MAAM,eAA0B;AACzF,QAAM,YAAY,MAAM,oBAAoB,OAAO,OAAQ,MAAM,mBAA8B;AAE/F,MAAI,UAAU;AACZ,QAAI,MAAM,oBAAoB,QAAW;AACvC,YAAM,OAAO,MAAM,mBAAmB,CAAC,GACpC,IAAI,CAAC,OAAQ,OAAO,OAAO,WAAW,GAAG,KAAK,IAAI,EAAG,EACrD,OAAO,CAAC,OAAqB,OAAO,OAAO,YAAY,GAAG,SAAS,CAAC;AACvE,UAAI,IAAI,WAAW,GAAG;AACpB,cAAM,QAAQ,IAAI,EAAE,KAAK,CAAC,EAAE;AAAA,MAC9B,WAAW,IAAI,WAAW,GAAG;AAC3B,cAAM,QAAQ,IAAI,IAAI,CAAC;AAAA,MACzB,OAAO;AACL,cAAM,QAAQ,IAAI,EAAE,KAAK,IAAI;AAAA,MAC/B;AAAA,IACF,WAAW,MAAM,mBAAmB,QAAW;AAC7C,YAAM,QAAQ,IAAI,MAAM;AAAA,IAC1B;AAAA,EACF;AAEA,MAAI,eAAe,MAAM,aAAa,OAAW,OAAM,WAAW,IAAI,MAAM;AAC5E,MAAI,UAAW,OAAM,SAAS,IAAI;AAClC,SAAO;AACT;AAEO,SAAS,qBAAqB,MAA4I;AAC/K,MAAI,CAAC,KAAM,QAAO,CAAC;AACnB,SAAO,EAAE,gBAAgB,KAAK,SAAS,MAAM,UAAU,KAAK,YAAY,KAAK;AAC/E;AAEA,eAAsB,cACpB,IACA,QACA,IACA,OACmB;AACnB,QAAM,WAAY,MAAM,YAAuB;AAC/C,QAAM,cAAe,MAAM,eAA0B;AACrD,QAAM,QAAa,EAAE,GAAG;AACxB,MAAI,MAAM,kBAAkB,KAAM,OAAM,QAAQ,IAAI,MAAM;AAC1D,MAAI,MAAM,YAAY,KAAM,OAAM,WAAW,IAAI,MAAM;AACvD,SAAO,GAAG,cAAc,MAAM,EAAE,QAAQ,KAAY;AACtD;AAEA,eAAsB,WACpB,IACA,QACe;AACf;AAAC,EAAC,OAAe,YAAY,oBAAI,KAAK;AACtC,QAAM,GAAG,gBAAgB,MAAM;AACjC;",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/core'\n\ntype Scope = { organizationId?: string | null; organizationIds?: string[] | null; tenantId?: string | null }\n\nexport function buildScopedWhere(\n base: Record<string, any>,\n scope: Scope & { orgField?: string | null; tenantField?: string | null; softDeleteField?: string | null }\n): Record<string, any> {\n const where: any = { ...base }\n const orgField = scope.orgField === null ? null : (scope.orgField as string) || 'organizationId'\n const tenantField = scope.tenantField === null ? null : (scope.tenantField as string) || 'tenantId'\n const softField = scope.softDeleteField === null ? null : (scope.softDeleteField as string) || 'deletedAt'\n\n if (orgField) {\n if (scope.organizationIds !== undefined) {\n const ids = (scope.organizationIds ?? [])\n .map((id) => (typeof id === 'string' ? id.trim() : id))\n .filter((id): id is string => typeof id === 'string' && id.length > 0)\n if (ids.length === 0) {\n where[orgField] = { $in: [] }\n } else if (ids.length === 1) {\n where[orgField] = ids[0]\n } else {\n where[orgField] = { $in: ids }\n }\n } else if (scope.organizationId !== undefined) {\n where[orgField] = scope.organizationId\n }\n }\n\n if (tenantField && scope.tenantId !== undefined) where[tenantField] = scope.tenantId\n if (softField) where[softField] = null\n return where\n}\n\nexport function extractScopeFromAuth(auth: { orgId?: string | null; tenantId?: string | null } | null | undefined): { organizationId?: string | null; tenantId?: string | null } {\n if (!auth) return {}\n return { organizationId: auth.orgId ?? null, tenantId: auth.tenantId ?? null }\n}\n\nexport async function findOneScoped<T extends { id: string }>(\n em: EntityManager,\n entity: { new (): T },\n id: string,\n scope: Scope & { orgField?: keyof T; tenantField?: keyof T }\n): Promise<T | null> {\n const orgField = (scope.orgField as string) || 'organizationId'\n const tenantField = (scope.tenantField as string) || 'tenantId'\n const where: any = { id }\n if (scope.organizationId != null) where[orgField] = scope.organizationId\n if (scope.tenantId != null) where[tenantField] = scope.tenantId\n return em.getRepository(entity).findOne(where as any)\n}\n\nexport async function softDelete<T extends { deletedAt?: Date | null }>(\n em: EntityManager,\n entity: T\n): Promise<void> {\n ;(entity as any).deletedAt = new Date()\n await em.persist(entity).flush()\n}\n"],
5
+ "mappings": "AAIO,SAAS,iBACd,MACA,OACqB;AACrB,QAAM,QAAa,EAAE,GAAG,KAAK;AAC7B,QAAM,WAAW,MAAM,aAAa,OAAO,OAAQ,MAAM,YAAuB;AAChF,QAAM,cAAc,MAAM,gBAAgB,OAAO,OAAQ,MAAM,eAA0B;AACzF,QAAM,YAAY,MAAM,oBAAoB,OAAO,OAAQ,MAAM,mBAA8B;AAE/F,MAAI,UAAU;AACZ,QAAI,MAAM,oBAAoB,QAAW;AACvC,YAAM,OAAO,MAAM,mBAAmB,CAAC,GACpC,IAAI,CAAC,OAAQ,OAAO,OAAO,WAAW,GAAG,KAAK,IAAI,EAAG,EACrD,OAAO,CAAC,OAAqB,OAAO,OAAO,YAAY,GAAG,SAAS,CAAC;AACvE,UAAI,IAAI,WAAW,GAAG;AACpB,cAAM,QAAQ,IAAI,EAAE,KAAK,CAAC,EAAE;AAAA,MAC9B,WAAW,IAAI,WAAW,GAAG;AAC3B,cAAM,QAAQ,IAAI,IAAI,CAAC;AAAA,MACzB,OAAO;AACL,cAAM,QAAQ,IAAI,EAAE,KAAK,IAAI;AAAA,MAC/B;AAAA,IACF,WAAW,MAAM,mBAAmB,QAAW;AAC7C,YAAM,QAAQ,IAAI,MAAM;AAAA,IAC1B;AAAA,EACF;AAEA,MAAI,eAAe,MAAM,aAAa,OAAW,OAAM,WAAW,IAAI,MAAM;AAC5E,MAAI,UAAW,OAAM,SAAS,IAAI;AAClC,SAAO;AACT;AAEO,SAAS,qBAAqB,MAA4I;AAC/K,MAAI,CAAC,KAAM,QAAO,CAAC;AACnB,SAAO,EAAE,gBAAgB,KAAK,SAAS,MAAM,UAAU,KAAK,YAAY,KAAK;AAC/E;AAEA,eAAsB,cACpB,IACA,QACA,IACA,OACmB;AACnB,QAAM,WAAY,MAAM,YAAuB;AAC/C,QAAM,cAAe,MAAM,eAA0B;AACrD,QAAM,QAAa,EAAE,GAAG;AACxB,MAAI,MAAM,kBAAkB,KAAM,OAAM,QAAQ,IAAI,MAAM;AAC1D,MAAI,MAAM,YAAY,KAAM,OAAM,WAAW,IAAI,MAAM;AACvD,SAAO,GAAG,cAAc,MAAM,EAAE,QAAQ,KAAY;AACtD;AAEA,eAAsB,WACpB,IACA,QACe;AACf;AAAC,EAAC,OAAe,YAAY,oBAAI,KAAK;AACtC,QAAM,GAAG,QAAQ,MAAM,EAAE,MAAM;AACjC;",
6
6
  "names": []
7
7
  }
@@ -102,7 +102,7 @@ async function resolveApiKeyAuth(secret) {
102
102
  if (cache.shouldWriteLastUsed(record.id)) {
103
103
  try {
104
104
  record.lastUsedAt = /* @__PURE__ */ new Date();
105
- await em.persistAndFlush(record);
105
+ await em.persist(record).flush();
106
106
  } catch {
107
107
  }
108
108
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/auth/server.ts"],
4
- "sourcesContent": ["import { cookies } from 'next/headers.js'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { verifyJwt } from './jwt'\nimport { getSharedApiKeyAuthCache } from './apiKeyAuthCache'\n\nconst TENANT_COOKIE_NAME = 'om_selected_tenant'\nconst ORGANIZATION_COOKIE_NAME = 'om_selected_org'\nconst ALL_ORGANIZATIONS_COOKIE_VALUE = '__all__'\nconst SUPERADMIN_ROLE = 'superadmin'\n\nexport type AuthContext = {\n sub: string\n sid?: string | null\n tenantId: string | null\n orgId: string | null\n email?: string\n roles?: string[]\n isApiKey?: boolean\n userId?: string\n keyId?: string\n keyName?: string\n [k: string]: unknown\n} | null\n\ntype CookieOverride = { applied: boolean; value: string | null }\ntype AuthResolutionStatus = 'authenticated' | 'missing' | 'invalid'\ntype AuthResolution = {\n auth: AuthContext\n status: AuthResolutionStatus\n}\n\nfunction decodeCookieValue(raw: string | undefined): string | null {\n if (raw === undefined) return null\n try {\n const decoded = decodeURIComponent(raw)\n return decoded ?? null\n } catch {\n return raw ?? null\n }\n}\n\nfunction readCookieFromHeader(header: string | null | undefined, name: string): string | undefined {\n if (!header) return undefined\n const parts = header.split(';')\n for (const part of parts) {\n const trimmed = part.trim()\n if (trimmed.startsWith(`${name}=`)) {\n return trimmed.slice(name.length + 1)\n }\n }\n return undefined\n}\n\nfunction resolveTenantOverride(raw: string | undefined): CookieOverride {\n if (raw === undefined) return { applied: false, value: null }\n const decoded = decodeCookieValue(raw)\n if (!decoded) return { applied: true, value: null }\n const trimmed = decoded.trim()\n if (!trimmed) return { applied: true, value: null }\n return { applied: true, value: trimmed }\n}\n\nfunction resolveOrganizationOverride(raw: string | undefined): CookieOverride {\n if (raw === undefined) return { applied: false, value: null }\n const decoded = decodeCookieValue(raw)\n if (!decoded || decoded === ALL_ORGANIZATIONS_COOKIE_VALUE) {\n return { applied: true, value: null }\n }\n const trimmed = decoded.trim()\n if (!trimmed || trimmed === ALL_ORGANIZATIONS_COOKIE_VALUE) {\n return { applied: true, value: null }\n }\n return { applied: true, value: trimmed }\n}\n\nfunction isSuperAdminAuth(auth: AuthContext | null | undefined): boolean {\n if (!auth) return false\n return (auth as Record<string, unknown>).isSuperAdmin === true\n}\n\nfunction applySuperAdminScope(\n auth: AuthContext,\n tenantCookie: string | undefined,\n orgCookie: string | undefined\n): AuthContext {\n if (!auth || !isSuperAdminAuth(auth)) return auth\n\n const tenantOverride = resolveTenantOverride(tenantCookie)\n const orgOverride = resolveOrganizationOverride(orgCookie)\n if (!tenantOverride.applied && !orgOverride.applied) return auth\n\n type MutableAuthContext = Exclude<AuthContext, null> & {\n actorTenantId?: string | null\n actorOrgId?: string | null\n }\n const baseAuth = auth as Exclude<AuthContext, null>\n const next: MutableAuthContext = { ...baseAuth }\n if (tenantOverride.applied) {\n if (!('actorTenantId' in next)) next.actorTenantId = auth?.tenantId ?? null\n next.tenantId = tenantOverride.value\n }\n if (orgOverride.applied) {\n if (!('actorOrgId' in next)) next.actorOrgId = auth?.orgId ?? null\n next.orgId = orgOverride.value\n }\n next.isSuperAdmin = true\n const existingRoles = Array.isArray(next.roles) ? next.roles : []\n if (!existingRoles.some((role) => typeof role === 'string' && role.trim().toLowerCase() === SUPERADMIN_ROLE)) {\n next.roles = [...existingRoles, 'superadmin']\n }\n return next\n}\n\nasync function resolveApiKeyAuth(secret: string): Promise<AuthContext> {\n if (!secret) return null\n const cache = getSharedApiKeyAuthCache()\n const cached = cache.get(secret)\n if (cached !== undefined) return cached as AuthContext\n try {\n const { createRequestContainer } = await import('@open-mercato/shared/lib/di/container')\n const container = await createRequestContainer()\n const em = (container.resolve('em') as EntityManager)\n const { findApiKeyBySecret } = await import('@open-mercato/core/modules/api_keys/services/apiKeyService')\n const { Role, RoleAcl, User } = await import('@open-mercato/core/modules/auth/data/entities')\n const { Organization, Tenant } = await import('@open-mercato/core/modules/directory/data/entities')\n\n const record = await findApiKeyBySecret(em, secret)\n if (!record) {\n cache.setMiss(secret)\n return null\n }\n\n const roleIds = Array.isArray(record.rolesJson)\n ? record.rolesJson.filter((value): value is string => typeof value === 'string' && value.length > 0)\n : []\n const roles = roleIds.length\n ? await em.find(Role, { id: { $in: roleIds }, deletedAt: null })\n : []\n const roleNames = roles.map((role) => role.name).filter((name): name is string => typeof name === 'string' && name.length > 0)\n\n let keyIsSuperAdmin = false\n if (roleIds.length) {\n const superAcl = await em.findOne(\n RoleAcl,\n { role: { $in: roleIds } as any, isSuperAdmin: true, deletedAt: null } as any,\n )\n keyIsSuperAdmin = !!(superAcl && (superAcl as { isSuperAdmin?: boolean }).isSuperAdmin)\n }\n\n if (cache.shouldWriteLastUsed(record.id)) {\n try {\n record.lastUsedAt = new Date()\n await em.persistAndFlush(record)\n } catch {\n // best-effort update; ignore write failures\n }\n }\n\n // For session keys, use sessionUserId; for regular keys, use createdBy\n const actualUserId = record.sessionUserId ?? record.createdBy ?? null\n\n if (actualUserId) {\n const user = await em.findOne(User, { id: actualUserId, deletedAt: null })\n if (!user) {\n cache.setMiss(secret)\n return null\n }\n if ((user.tenantId ?? null) !== (record.tenantId ?? null)) {\n cache.setMiss(secret)\n return null\n }\n if ((user.organizationId ?? null) !== (record.organizationId ?? null)) {\n cache.setMiss(secret)\n return null\n }\n } else {\n if (record.tenantId) {\n const tenant = await em.findOne(Tenant, { id: record.tenantId, deletedAt: null, isActive: true })\n if (!tenant) {\n cache.setMiss(secret)\n return null\n }\n }\n if (record.organizationId) {\n const organization = await em.findOne(Organization, { id: record.organizationId, deletedAt: null, isActive: true })\n if (!organization) {\n cache.setMiss(secret)\n return null\n }\n if (record.tenantId && String(organization.tenant.id) !== record.tenantId) {\n cache.setMiss(secret)\n return null\n }\n }\n }\n\n const auth: Exclude<AuthContext, null> = {\n sub: `api_key:${record.id}`,\n tenantId: record.tenantId ?? null,\n orgId: record.organizationId ?? null,\n roles: roleNames,\n isApiKey: true,\n isSuperAdmin: keyIsSuperAdmin,\n keyId: record.id,\n keyName: record.name,\n ...(actualUserId ? { userId: actualUserId } : {}),\n }\n cache.setSuccess(secret, auth, record.expiresAt ? record.expiresAt.getTime() : null)\n return auth\n } catch {\n return null\n }\n}\n\nfunction extractApiKey(req: Request): string | null {\n const header = (req.headers.get('x-api-key') || '').trim()\n if (header) return header\n const authHeader = (req.headers.get('authorization') || '').trim()\n if (authHeader.toLowerCase().startsWith('apikey ')) {\n return authHeader.slice(7).trim()\n }\n return null\n}\n\nasync function resolveCanonicalInteractiveAuthContext(auth: AuthContext): Promise<AuthContext> {\n if (!auth || auth.isApiKey) return auth\n if (typeof auth.sub !== 'string' || auth.sub.trim().length === 0) return null\n\n try {\n const [{ createRequestContainer }, { resolveCanonicalStaffAuthContext }] = await Promise.all([\n import('@open-mercato/shared/lib/di/container'),\n import('@open-mercato/core/modules/auth/lib/sessionIntegrity'),\n ])\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n return await resolveCanonicalStaffAuthContext(em, auth)\n } catch {\n return null\n }\n}\n\nexport async function resolveAuthFromCookiesDetailed(): Promise<AuthResolution> {\n const cookieStore = await cookies()\n const token = cookieStore.get('auth_token')?.value\n if (!token) return { auth: null, status: 'missing' }\n try {\n const payload = verifyJwt(token) as AuthContext\n if (!payload) return { auth: null, status: 'invalid' }\n if (payload.type === 'customer') return { auth: null, status: 'invalid' }\n const canonicalAuth = await resolveCanonicalInteractiveAuthContext(payload)\n if (!canonicalAuth) return { auth: null, status: 'invalid' }\n const tenantCookie = cookieStore.get(TENANT_COOKIE_NAME)?.value\n const orgCookie = cookieStore.get(ORGANIZATION_COOKIE_NAME)?.value\n return {\n auth: applySuperAdminScope(canonicalAuth, tenantCookie, orgCookie),\n status: 'authenticated',\n }\n } catch {\n return { auth: null, status: 'invalid' }\n }\n}\n\nexport async function getAuthFromCookies(): Promise<AuthContext> {\n return (await resolveAuthFromCookiesDetailed()).auth\n}\n\nexport async function resolveAuthFromRequestDetailed(req: Request): Promise<AuthResolution> {\n const cookieHeader = req.headers.get('cookie') || ''\n const tenantCookie = readCookieFromHeader(cookieHeader, TENANT_COOKIE_NAME)\n const orgCookie = readCookieFromHeader(cookieHeader, ORGANIZATION_COOKIE_NAME)\n const authHeader = (req.headers.get('authorization') || '').trim()\n let token: string | undefined\n let hadInvalidInteractiveToken = false\n if (authHeader.toLowerCase().startsWith('bearer ')) token = authHeader.slice(7).trim()\n if (!token) {\n const match = cookieHeader.match(/(?:^|;\\s*)auth_token=([^;]+)/)\n if (match) token = decodeURIComponent(match[1])\n }\n if (token) {\n try {\n const payload = verifyJwt(token) as AuthContext\n if (payload && payload.type === 'customer') return { auth: null, status: 'invalid' }\n if (payload) {\n const canonicalAuth = await resolveCanonicalInteractiveAuthContext(payload)\n if (canonicalAuth) {\n return {\n auth: applySuperAdminScope(canonicalAuth, tenantCookie, orgCookie),\n status: 'authenticated',\n }\n }\n hadInvalidInteractiveToken = true\n }\n } catch {\n hadInvalidInteractiveToken = true\n }\n }\n\n const apiKey = extractApiKey(req)\n if (!apiKey) {\n return { auth: null, status: hadInvalidInteractiveToken ? 'invalid' : 'missing' }\n }\n const apiAuth = await resolveApiKeyAuth(apiKey)\n if (!apiAuth) {\n return { auth: null, status: hadInvalidInteractiveToken ? 'invalid' : 'missing' }\n }\n return {\n auth: applySuperAdminScope(apiAuth, tenantCookie, orgCookie),\n status: 'authenticated',\n }\n}\n\nexport async function getAuthFromRequest(req: Request): Promise<AuthContext> {\n return (await resolveAuthFromRequestDetailed(req)).auth\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { cookies } from 'next/headers.js'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { verifyJwt } from './jwt'\nimport { getSharedApiKeyAuthCache } from './apiKeyAuthCache'\n\nconst TENANT_COOKIE_NAME = 'om_selected_tenant'\nconst ORGANIZATION_COOKIE_NAME = 'om_selected_org'\nconst ALL_ORGANIZATIONS_COOKIE_VALUE = '__all__'\nconst SUPERADMIN_ROLE = 'superadmin'\n\nexport type AuthContext = {\n sub: string\n sid?: string | null\n tenantId: string | null\n orgId: string | null\n email?: string\n roles?: string[]\n isApiKey?: boolean\n userId?: string\n keyId?: string\n keyName?: string\n [k: string]: unknown\n} | null\n\ntype CookieOverride = { applied: boolean; value: string | null }\ntype AuthResolutionStatus = 'authenticated' | 'missing' | 'invalid'\ntype AuthResolution = {\n auth: AuthContext\n status: AuthResolutionStatus\n}\n\nfunction decodeCookieValue(raw: string | undefined): string | null {\n if (raw === undefined) return null\n try {\n const decoded = decodeURIComponent(raw)\n return decoded ?? null\n } catch {\n return raw ?? null\n }\n}\n\nfunction readCookieFromHeader(header: string | null | undefined, name: string): string | undefined {\n if (!header) return undefined\n const parts = header.split(';')\n for (const part of parts) {\n const trimmed = part.trim()\n if (trimmed.startsWith(`${name}=`)) {\n return trimmed.slice(name.length + 1)\n }\n }\n return undefined\n}\n\nfunction resolveTenantOverride(raw: string | undefined): CookieOverride {\n if (raw === undefined) return { applied: false, value: null }\n const decoded = decodeCookieValue(raw)\n if (!decoded) return { applied: true, value: null }\n const trimmed = decoded.trim()\n if (!trimmed) return { applied: true, value: null }\n return { applied: true, value: trimmed }\n}\n\nfunction resolveOrganizationOverride(raw: string | undefined): CookieOverride {\n if (raw === undefined) return { applied: false, value: null }\n const decoded = decodeCookieValue(raw)\n if (!decoded || decoded === ALL_ORGANIZATIONS_COOKIE_VALUE) {\n return { applied: true, value: null }\n }\n const trimmed = decoded.trim()\n if (!trimmed || trimmed === ALL_ORGANIZATIONS_COOKIE_VALUE) {\n return { applied: true, value: null }\n }\n return { applied: true, value: trimmed }\n}\n\nfunction isSuperAdminAuth(auth: AuthContext | null | undefined): boolean {\n if (!auth) return false\n return (auth as Record<string, unknown>).isSuperAdmin === true\n}\n\nfunction applySuperAdminScope(\n auth: AuthContext,\n tenantCookie: string | undefined,\n orgCookie: string | undefined\n): AuthContext {\n if (!auth || !isSuperAdminAuth(auth)) return auth\n\n const tenantOverride = resolveTenantOverride(tenantCookie)\n const orgOverride = resolveOrganizationOverride(orgCookie)\n if (!tenantOverride.applied && !orgOverride.applied) return auth\n\n type MutableAuthContext = Exclude<AuthContext, null> & {\n actorTenantId?: string | null\n actorOrgId?: string | null\n }\n const baseAuth = auth as Exclude<AuthContext, null>\n const next: MutableAuthContext = { ...baseAuth }\n if (tenantOverride.applied) {\n if (!('actorTenantId' in next)) next.actorTenantId = auth?.tenantId ?? null\n next.tenantId = tenantOverride.value\n }\n if (orgOverride.applied) {\n if (!('actorOrgId' in next)) next.actorOrgId = auth?.orgId ?? null\n next.orgId = orgOverride.value\n }\n next.isSuperAdmin = true\n const existingRoles = Array.isArray(next.roles) ? next.roles : []\n if (!existingRoles.some((role) => typeof role === 'string' && role.trim().toLowerCase() === SUPERADMIN_ROLE)) {\n next.roles = [...existingRoles, 'superadmin']\n }\n return next\n}\n\nasync function resolveApiKeyAuth(secret: string): Promise<AuthContext> {\n if (!secret) return null\n const cache = getSharedApiKeyAuthCache()\n const cached = cache.get(secret)\n if (cached !== undefined) return cached as AuthContext\n try {\n const { createRequestContainer } = await import('@open-mercato/shared/lib/di/container')\n const container = await createRequestContainer()\n const em = (container.resolve('em') as EntityManager)\n const { findApiKeyBySecret } = await import('@open-mercato/core/modules/api_keys/services/apiKeyService')\n const { Role, RoleAcl, User } = await import('@open-mercato/core/modules/auth/data/entities')\n const { Organization, Tenant } = await import('@open-mercato/core/modules/directory/data/entities')\n\n const record = await findApiKeyBySecret(em, secret)\n if (!record) {\n cache.setMiss(secret)\n return null\n }\n\n const roleIds = Array.isArray(record.rolesJson)\n ? record.rolesJson.filter((value): value is string => typeof value === 'string' && value.length > 0)\n : []\n const roles = roleIds.length\n ? await em.find(Role, { id: { $in: roleIds }, deletedAt: null })\n : []\n const roleNames = roles.map((role) => role.name).filter((name): name is string => typeof name === 'string' && name.length > 0)\n\n let keyIsSuperAdmin = false\n if (roleIds.length) {\n const superAcl = await em.findOne(\n RoleAcl,\n { role: { $in: roleIds } as any, isSuperAdmin: true, deletedAt: null } as any,\n )\n keyIsSuperAdmin = !!(superAcl && (superAcl as { isSuperAdmin?: boolean }).isSuperAdmin)\n }\n\n if (cache.shouldWriteLastUsed(record.id)) {\n try {\n record.lastUsedAt = new Date()\n await em.persist(record).flush()\n } catch {\n // best-effort update; ignore write failures\n }\n }\n\n // For session keys, use sessionUserId; for regular keys, use createdBy\n const actualUserId = record.sessionUserId ?? record.createdBy ?? null\n\n if (actualUserId) {\n const user = await em.findOne(User, { id: actualUserId, deletedAt: null })\n if (!user) {\n cache.setMiss(secret)\n return null\n }\n if ((user.tenantId ?? null) !== (record.tenantId ?? null)) {\n cache.setMiss(secret)\n return null\n }\n if ((user.organizationId ?? null) !== (record.organizationId ?? null)) {\n cache.setMiss(secret)\n return null\n }\n } else {\n if (record.tenantId) {\n const tenant = await em.findOne(Tenant, { id: record.tenantId, deletedAt: null, isActive: true })\n if (!tenant) {\n cache.setMiss(secret)\n return null\n }\n }\n if (record.organizationId) {\n const organization = await em.findOne(Organization, { id: record.organizationId, deletedAt: null, isActive: true })\n if (!organization) {\n cache.setMiss(secret)\n return null\n }\n if (record.tenantId && String(organization.tenant.id) !== record.tenantId) {\n cache.setMiss(secret)\n return null\n }\n }\n }\n\n const auth: Exclude<AuthContext, null> = {\n sub: `api_key:${record.id}`,\n tenantId: record.tenantId ?? null,\n orgId: record.organizationId ?? null,\n roles: roleNames,\n isApiKey: true,\n isSuperAdmin: keyIsSuperAdmin,\n keyId: record.id,\n keyName: record.name,\n ...(actualUserId ? { userId: actualUserId } : {}),\n }\n cache.setSuccess(secret, auth, record.expiresAt ? record.expiresAt.getTime() : null)\n return auth\n } catch {\n return null\n }\n}\n\nfunction extractApiKey(req: Request): string | null {\n const header = (req.headers.get('x-api-key') || '').trim()\n if (header) return header\n const authHeader = (req.headers.get('authorization') || '').trim()\n if (authHeader.toLowerCase().startsWith('apikey ')) {\n return authHeader.slice(7).trim()\n }\n return null\n}\n\nasync function resolveCanonicalInteractiveAuthContext(auth: AuthContext): Promise<AuthContext> {\n if (!auth || auth.isApiKey) return auth\n if (typeof auth.sub !== 'string' || auth.sub.trim().length === 0) return null\n\n try {\n const [{ createRequestContainer }, { resolveCanonicalStaffAuthContext }] = await Promise.all([\n import('@open-mercato/shared/lib/di/container'),\n import('@open-mercato/core/modules/auth/lib/sessionIntegrity'),\n ])\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n return await resolveCanonicalStaffAuthContext(em, auth)\n } catch {\n return null\n }\n}\n\nexport async function resolveAuthFromCookiesDetailed(): Promise<AuthResolution> {\n const cookieStore = await cookies()\n const token = cookieStore.get('auth_token')?.value\n if (!token) return { auth: null, status: 'missing' }\n try {\n const payload = verifyJwt(token) as AuthContext\n if (!payload) return { auth: null, status: 'invalid' }\n if (payload.type === 'customer') return { auth: null, status: 'invalid' }\n const canonicalAuth = await resolveCanonicalInteractiveAuthContext(payload)\n if (!canonicalAuth) return { auth: null, status: 'invalid' }\n const tenantCookie = cookieStore.get(TENANT_COOKIE_NAME)?.value\n const orgCookie = cookieStore.get(ORGANIZATION_COOKIE_NAME)?.value\n return {\n auth: applySuperAdminScope(canonicalAuth, tenantCookie, orgCookie),\n status: 'authenticated',\n }\n } catch {\n return { auth: null, status: 'invalid' }\n }\n}\n\nexport async function getAuthFromCookies(): Promise<AuthContext> {\n return (await resolveAuthFromCookiesDetailed()).auth\n}\n\nexport async function resolveAuthFromRequestDetailed(req: Request): Promise<AuthResolution> {\n const cookieHeader = req.headers.get('cookie') || ''\n const tenantCookie = readCookieFromHeader(cookieHeader, TENANT_COOKIE_NAME)\n const orgCookie = readCookieFromHeader(cookieHeader, ORGANIZATION_COOKIE_NAME)\n const authHeader = (req.headers.get('authorization') || '').trim()\n let token: string | undefined\n let hadInvalidInteractiveToken = false\n if (authHeader.toLowerCase().startsWith('bearer ')) token = authHeader.slice(7).trim()\n if (!token) {\n const match = cookieHeader.match(/(?:^|;\\s*)auth_token=([^;]+)/)\n if (match) token = decodeURIComponent(match[1])\n }\n if (token) {\n try {\n const payload = verifyJwt(token) as AuthContext\n if (payload && payload.type === 'customer') return { auth: null, status: 'invalid' }\n if (payload) {\n const canonicalAuth = await resolveCanonicalInteractiveAuthContext(payload)\n if (canonicalAuth) {\n return {\n auth: applySuperAdminScope(canonicalAuth, tenantCookie, orgCookie),\n status: 'authenticated',\n }\n }\n hadInvalidInteractiveToken = true\n }\n } catch {\n hadInvalidInteractiveToken = true\n }\n }\n\n const apiKey = extractApiKey(req)\n if (!apiKey) {\n return { auth: null, status: hadInvalidInteractiveToken ? 'invalid' : 'missing' }\n }\n const apiAuth = await resolveApiKeyAuth(apiKey)\n if (!apiAuth) {\n return { auth: null, status: hadInvalidInteractiveToken ? 'invalid' : 'missing' }\n }\n return {\n auth: applySuperAdminScope(apiAuth, tenantCookie, orgCookie),\n status: 'authenticated',\n }\n}\n\nexport async function getAuthFromRequest(req: Request): Promise<AuthContext> {\n return (await resolveAuthFromRequestDetailed(req)).auth\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,4 @@
1
+ import { sql } from "kysely";
1
2
  import { setRecordCustomFields } from "@open-mercato/core/modules/entities/lib/helpers";
2
3
  import { validateCustomFieldValuesServer } from "@open-mercato/core/modules/entities/lib/validation";
3
4
  import { sanitizeCustomFieldHtmlRichTextValuesServer } from "@open-mercato/core/modules/entities/lib/htmlRichTextSanitizer";
@@ -93,9 +94,12 @@ class DefaultDataEngine {
93
94
  return false;
94
95
  }
95
96
  }
97
+ getKysely() {
98
+ return this.em.getKysely();
99
+ }
96
100
  async ensureStorageTableExists() {
97
- const knex = this.em.getConnection().getKnex();
98
- const exists = await knex("information_schema.tables").where({ table_name: "custom_entities_storage" }).first();
101
+ const db = this.getKysely();
102
+ const exists = await db.selectFrom("information_schema.tables").select(sql`1`.as("present")).where("table_name", "=", "custom_entities_storage").executeTakeFirst();
99
103
  if (!exists) {
100
104
  throw new Error("custom_entities_storage table is missing. Run migrations (yarn db:migrate).");
101
105
  }
@@ -128,7 +132,7 @@ class DefaultDataEngine {
128
132
  }
129
133
  }
130
134
  async createCustomEntityRecord(opts) {
131
- const knex = this.em.getConnection().getKnex();
135
+ const db = this.getKysely();
132
136
  await this.ensureStorageTableExists();
133
137
  const sanitizedValues = await sanitizeCustomFieldHtmlRichTextValuesServer(this.em, {
134
138
  entityId: opts.entityId,
@@ -153,23 +157,32 @@ class DefaultDataEngine {
153
157
  const orgId = opts.organizationId ?? null;
154
158
  const tenantId = opts.tenantId ?? null;
155
159
  const doc = { id, ...this.normalizeDocValues(sanitizedValues || {}) };
160
+ const now = sql`now()`;
156
161
  const payload = {
157
162
  entity_type: opts.entityId,
158
163
  entity_id: id,
159
164
  organization_id: orgId,
160
165
  tenant_id: tenantId,
161
- doc,
162
- updated_at: knex.fn.now(),
163
- created_at: knex.fn.now(),
166
+ doc: sql`${JSON.stringify(doc)}::jsonb`,
167
+ updated_at: now,
168
+ created_at: now,
164
169
  deleted_at: null
165
170
  };
166
171
  try {
167
- await knex("custom_entities_storage").insert(payload).onConflict(["entity_type", "entity_id", "organization_id"]).merge({ doc: payload.doc, updated_at: knex.fn.now(), deleted_at: null });
172
+ await db.insertInto("custom_entities_storage").values(payload).onConflict((oc) => oc.columns(["entity_type", "entity_id", "organization_id"]).doUpdateSet({
173
+ doc: sql`${JSON.stringify(doc)}::jsonb`,
174
+ updated_at: sql`now()`,
175
+ deleted_at: null
176
+ })).execute();
168
177
  } catch {
169
178
  try {
170
- const updated = await knex("custom_entities_storage").where({ entity_type: opts.entityId, entity_id: id, organization_id: orgId }).update({ doc: payload.doc, updated_at: knex.fn.now(), deleted_at: null });
171
- if (!updated) {
172
- await knex("custom_entities_storage").insert(payload);
179
+ const updated = await db.updateTable("custom_entities_storage").set({
180
+ doc: sql`${JSON.stringify(doc)}::jsonb`,
181
+ updated_at: sql`now()`,
182
+ deleted_at: null
183
+ }).where("entity_type", "=", opts.entityId).where("entity_id", "=", id).where("organization_id", orgId === null ? "is" : "=", orgId).executeTakeFirst();
184
+ if (!updated || Number(updated.numUpdatedRows ?? 0) === 0) {
185
+ await db.insertInto("custom_entities_storage").values(payload).execute();
173
186
  }
174
187
  } catch (err) {
175
188
  throw err;
@@ -189,7 +202,7 @@ class DefaultDataEngine {
189
202
  return { id };
190
203
  }
191
204
  async updateCustomEntityRecord(opts) {
192
- const knex = this.em.getConnection().getKnex();
205
+ const db = this.getKysely();
193
206
  const sanitizedValues = await sanitizeCustomFieldHtmlRichTextValuesServer(this.em, {
194
207
  entityId: opts.entityId,
195
208
  organizationId: opts.organizationId ?? null,
@@ -201,22 +214,36 @@ class DefaultDataEngine {
201
214
  const orgId = opts.organizationId ?? null;
202
215
  const tenantId = opts.tenantId ?? null;
203
216
  await this.ensureStorageTableExists();
204
- const row = await knex("custom_entities_storage").where({ entity_type: opts.entityId, entity_id: id, organization_id: orgId }).first();
217
+ const applyScope = (q) => {
218
+ let chain = q.where("entity_type", "=", opts.entityId);
219
+ chain = chain.where("entity_id", "=", id);
220
+ chain = orgId === null ? chain.where("organization_id", "is", null) : chain.where("organization_id", "=", orgId);
221
+ return chain;
222
+ };
223
+ const row = await applyScope(
224
+ db.selectFrom("custom_entities_storage").select(["doc"])
225
+ ).executeTakeFirst();
205
226
  const prevDoc = row?.doc || { id };
206
227
  const nextDoc = { ...prevDoc, ...this.normalizeDocValues(sanitizedValues || {}), id };
207
228
  try {
208
- const updated = await knex("custom_entities_storage").where({ entity_type: opts.entityId, entity_id: id, organization_id: orgId }).update({ doc: nextDoc, updated_at: knex.fn.now(), deleted_at: null });
209
- if (!updated) {
210
- await knex("custom_entities_storage").insert({
229
+ const updated = await applyScope(
230
+ db.updateTable("custom_entities_storage").set({
231
+ doc: sql`${JSON.stringify(nextDoc)}::jsonb`,
232
+ updated_at: sql`now()`,
233
+ deleted_at: null
234
+ })
235
+ ).executeTakeFirst();
236
+ if (!updated || Number(updated.numUpdatedRows ?? 0) === 0) {
237
+ await db.insertInto("custom_entities_storage").values({
211
238
  entity_type: opts.entityId,
212
239
  entity_id: id,
213
240
  organization_id: orgId,
214
241
  tenant_id: tenantId,
215
- doc: nextDoc,
216
- created_at: knex.fn.now(),
217
- updated_at: knex.fn.now(),
242
+ doc: sql`${JSON.stringify(nextDoc)}::jsonb`,
243
+ created_at: sql`now()`,
244
+ updated_at: sql`now()`,
218
245
  deleted_at: null
219
- });
246
+ }).execute();
220
247
  }
221
248
  } catch (err) {
222
249
  throw err;
@@ -234,14 +261,25 @@ class DefaultDataEngine {
234
261
  }
235
262
  }
236
263
  async deleteCustomEntityRecord(opts) {
237
- const knex = this.em.getConnection().getKnex();
264
+ const db = this.getKysely();
238
265
  const id = String(opts.recordId);
239
266
  const orgId = opts.organizationId ?? null;
240
267
  const soft = opts.soft !== false;
268
+ const applyScope = (q) => {
269
+ let chain = q.where("entity_type", "=", opts.entityId);
270
+ chain = chain.where("entity_id", "=", id);
271
+ chain = orgId === null ? chain.where("organization_id", "is", null) : chain.where("organization_id", "=", orgId);
272
+ return chain;
273
+ };
241
274
  if (soft) {
242
- await knex("custom_entities_storage").where({ entity_type: opts.entityId, entity_id: id, organization_id: orgId }).update({ deleted_at: knex.fn.now(), updated_at: knex.fn.now() });
275
+ await applyScope(
276
+ db.updateTable("custom_entities_storage").set({
277
+ deleted_at: sql`now()`,
278
+ updated_at: sql`now()`
279
+ })
280
+ ).execute();
243
281
  } else {
244
- await knex("custom_entities_storage").where({ entity_type: opts.entityId, entity_id: id, organization_id: orgId }).delete();
282
+ await applyScope(db.deleteFrom("custom_entities_storage")).execute();
245
283
  }
246
284
  try {
247
285
  const { CustomFieldValue } = await import("@open-mercato/core/modules/entities/data/entities");
@@ -257,7 +295,10 @@ class DefaultDataEngine {
257
295
  record.deletedAt = now;
258
296
  return true;
259
297
  });
260
- if (mutated.length) await this.em.persistAndFlush(values);
298
+ if (mutated.length) {
299
+ for (const record of values) this.em.persist(record);
300
+ await this.em.flush();
301
+ }
261
302
  } catch {
262
303
  }
263
304
  }
@@ -266,14 +307,14 @@ class DefaultDataEngine {
266
307
  opts.entity,
267
308
  opts.data
268
309
  );
269
- await this.em.persistAndFlush(entity);
310
+ await this.em.persist(entity).flush();
270
311
  return entity;
271
312
  }
272
313
  async updateOrmEntity(opts) {
273
314
  const current = await this.em.findOne(opts.entity, opts.where);
274
315
  if (!current) return null;
275
316
  await opts.apply(current);
276
- await this.em.persistAndFlush(current);
317
+ await this.em.persist(current).flush();
277
318
  return current;
278
319
  }
279
320
  async deleteOrmEntity(opts) {
@@ -284,10 +325,10 @@ class DefaultDataEngine {
284
325
  if (typeof current === "object" && current !== null) {
285
326
  ;
286
327
  current[field] = /* @__PURE__ */ new Date();
287
- await this.em.persistAndFlush(current);
328
+ await this.em.persist(current).flush();
288
329
  }
289
330
  } else {
290
- await this.em.removeAndFlush(current);
331
+ await this.em.remove(current).flush();
291
332
  }
292
333
  return current;
293
334
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/data/engine.ts"],
4
- "sourcesContent": ["import type { EntityData, EntityName, FilterQuery, RequiredEntityData } from '@mikro-orm/core'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\nimport { setRecordCustomFields } from '@open-mercato/core/modules/entities/lib/helpers'\nimport { validateCustomFieldValuesServer } from '@open-mercato/core/modules/entities/lib/validation'\nimport { sanitizeCustomFieldHtmlRichTextValuesServer } from '@open-mercato/core/modules/entities/lib/htmlRichTextSanitizer'\nimport type { EventBus } from '@open-mercato/events/types'\nimport type {\n CrudEventAction,\n CrudEventsConfig,\n CrudIndexerConfig,\n CrudEntityIdentifiers,\n} from '../crud/types'\nimport { CrudHttpError } from '../crud/errors'\nimport { normalizeCustomFieldValues } from '../custom-fields/normalize'\nimport { parseBooleanToken } from '../boolean'\nimport { isEventDeclared } from '../../modules/events'\n\nconst undeclaredEventWarned = new Set<string>()\n\nfunction warnIfUndeclaredEvent(eventName: string, context: string): void {\n if (isEventDeclared(eventName)) return\n if (undeclaredEventWarned.has(eventName)) return\n undeclaredEventWarned.add(eventName)\n console.warn(\n `[data-engine] ${context} is emitting undeclared event \"${eventName}\". ` +\n `Declare it in the owning module's events.ts (createModuleEvents) so the event registry stays authoritative.`,\n )\n}\n\n/** Internal: clear the undeclared-event warning cache. Exposed for tests. */\nexport function __resetUndeclaredEventWarningsForTests(): void {\n undeclaredEventWarned.clear()\n}\n\nconst COVERAGE_REFRESH_INTERVAL_MS = 5 * 60 * 1000\nconst coverageRefreshTracker = new Map<string, number>()\n\nfunction shouldTriggerCoverageRefresh(entityType: string | undefined, tenantId: string | null): boolean {\n if (!entityType) return false\n const key = `${entityType}|${tenantId ?? '__null__'}`\n const now = Date.now()\n const last = coverageRefreshTracker.get(key) ?? 0\n if (now - last < COVERAGE_REFRESH_INTERVAL_MS) return false\n coverageRefreshTracker.set(key, now)\n return true\n}\n\ntype CustomEntityValues = Record<string, unknown>\n\ntype QueuedCrudSideEffect = {\n action: CrudEventAction\n entity: unknown\n identifiers: CrudEntityIdentifiers\n syncOrigin?: string | null\n events?: CrudEventsConfig<unknown>\n indexer?: CrudIndexerConfig<unknown>\n}\n\nexport interface DataEngine {\n setCustomFields(opts: {\n entityId: string\n recordId: string\n organizationId?: string | null\n tenantId?: string | null\n values: Record<string, string | number | boolean | null | undefined | Array<string | number | boolean | null | undefined>>\n notify?: boolean // default true -> emit '<module>.<entity>.updated'\n }): Promise<void>\n\n // Storage for user-defined entities (doc-based)\n createCustomEntityRecord(opts: {\n entityId: string // '<module>:<entity>'\n recordId?: string // optional; auto-generate if not provided\n organizationId?: string | null\n tenantId?: string | null\n values: CustomEntityValues\n notify?: boolean // keep event emitting as it is via setCustomFields (updated)\n }): Promise<{ id: string }>\n\n updateCustomEntityRecord(opts: {\n entityId: string\n recordId: string\n organizationId?: string | null\n tenantId?: string | null\n values: CustomEntityValues\n notify?: boolean // keep event emitting as it is via setCustomFields (updated)\n }): Promise<void>\n\n deleteCustomEntityRecord(opts: {\n entityId: string\n recordId: string\n organizationId?: string | null\n tenantId?: string | null\n soft?: boolean // default true: sets deleted_at\n notify?: boolean // keep event emitting as it is (no extra events here)\n }): Promise<void>\n\n // Generic ORM-backed entity operations used by CrudFactory\n createOrmEntity<T extends object>(opts: {\n entity: EntityName<T>\n data: EntityData<T>\n }): Promise<T>\n\n updateOrmEntity<T extends object>(opts: {\n entity: EntityName<T>\n where: FilterQuery<T>\n apply: (current: T) => Promise<void> | void\n }): Promise<T | null>\n\n deleteOrmEntity<T extends object>(opts: {\n entity: EntityName<T>\n where: FilterQuery<T>\n soft?: boolean\n softDeleteField?: keyof T & string\n }): Promise<T | null>\n\n emitOrmEntityEvent<T>(opts: {\n action: CrudEventAction\n entity: T\n events?: CrudEventsConfig<T>\n indexer?: CrudIndexerConfig<T>\n identifiers: CrudEntityIdentifiers\n syncOrigin?: string | null\n }): Promise<void>\n\n markOrmEntityChange<T>(opts: {\n action: CrudEventAction\n entity: T | null | undefined\n events?: CrudEventsConfig<T>\n indexer?: CrudIndexerConfig<T>\n identifiers: CrudEntityIdentifiers\n syncOrigin?: string | null\n }): void\n\n flushOrmEntityChanges(): Promise<void>\n}\n\nexport class DefaultDataEngine implements DataEngine {\n private pendingSideEffects = new Map<string, QueuedCrudSideEffect>()\n constructor(private em: EntityManager, private container: AwilixContainer) {}\n\n async setCustomFields(opts: Parameters<DataEngine['setCustomFields']>[0]): Promise<void> {\n const { entityId, recordId, organizationId = null, tenantId = null, values } = opts\n const sanitizedValues = await sanitizeCustomFieldHtmlRichTextValuesServer(this.em, {\n entityId,\n organizationId,\n tenantId,\n values,\n })\n await this.validateCustomFieldValues(entityId, organizationId, tenantId, sanitizedValues as Record<string, unknown>)\n let encryptionService: any = null\n try {\n encryptionService = this.container.resolve('tenantEncryptionService') as any\n } catch {\n encryptionService = null\n }\n await setRecordCustomFields(this.em, {\n entityId,\n recordId,\n organizationId,\n tenantId,\n values: sanitizedValues,\n encryptionService,\n })\n if (opts.notify !== false) {\n let bus: EventBus | null = null\n try {\n bus = (this.container.resolve('eventBus') as EventBus)\n } catch {\n bus = null\n }\n if (bus) {\n const [mod, ent] = (entityId || '').split(':')\n if (mod && ent) {\n const eventName = `${mod}.${ent}.updated`\n warnIfUndeclaredEvent(eventName, 'setCustomFields')\n try {\n await bus.emitEvent(eventName, { id: recordId, organizationId, tenantId }, { persistent: true })\n } catch {\n // non-blocking\n }\n }\n }\n }\n }\n\n private normalizeDocValues(values: CustomEntityValues): CustomEntityValues {\n const out: CustomEntityValues = {}\n for (const [k, v] of Object.entries(values || {})) {\n // Never allow callers to override reserved identifiers in the doc\n if (k === 'id' || k === 'entity_id' || k === 'entityId') continue\n // Accept both 'cf_<key>' and 'cf:<key>' inputs and normalize to 'cf:<key>'\n if (k.startsWith('cf_')) out[`cf:${k.slice(3)}`] = v\n else out[k] = v\n }\n return out\n }\n\n private backcompatEavEnabled(): boolean {\n try {\n return parseBooleanToken(process.env.ENTITIES_BACKCOMPAT_EAV_FOR_CUSTOM ?? '') === true\n } catch { return false }\n }\n\n private async ensureStorageTableExists(): Promise<void> {\n const knex = this.em.getConnection().getKnex()\n const exists = await knex('information_schema.tables')\n .where({ table_name: 'custom_entities_storage' })\n .first()\n if (!exists) {\n throw new Error('custom_entities_storage table is missing. Run migrations (yarn db:migrate).')\n }\n }\n\n private normalizeValuesForValidation(values: Record<string, unknown> | undefined | null): Record<string, unknown> {\n if (!values) return {}\n const out: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(values)) {\n if (value === undefined) continue\n if (key.startsWith('cf_') || key.startsWith('cf:')) {\n const normalized = key.slice(3)\n if (normalized) out[normalized] = value\n continue\n }\n out[key] = value\n }\n return out\n }\n\n private async validateCustomFieldValues(\n entityId: string,\n organizationId: string | null,\n tenantId: string | null,\n values: Record<string, unknown> | undefined | null,\n ): Promise<void> {\n const prepared = this.normalizeValuesForValidation(values)\n if (!entityId || Object.keys(prepared).length === 0) return\n const result = await validateCustomFieldValuesServer(this.em, {\n entityId,\n organizationId,\n tenantId,\n values: prepared,\n })\n if (!result.ok) {\n throw new CrudHttpError(400, { error: 'Validation failed', fields: result.fieldErrors })\n }\n }\n\n async createCustomEntityRecord(opts: Parameters<DataEngine['createCustomEntityRecord']>[0]): Promise<{ id: string }> {\n const knex = this.em.getConnection().getKnex()\n await this.ensureStorageTableExists()\n const sanitizedValues = await sanitizeCustomFieldHtmlRichTextValuesServer(this.em, {\n entityId: opts.entityId,\n organizationId: opts.organizationId ?? null,\n tenantId: opts.tenantId ?? null,\n values: opts.values || {},\n })\n await this.validateCustomFieldValues(opts.entityId, opts.organizationId ?? null, opts.tenantId ?? null, sanitizedValues)\n const rawId = String(opts.recordId ?? '').trim()\n const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(rawId)\n const sentinel = rawId.toLowerCase()\n const shouldGenerate = !rawId || !isUuid || sentinel === 'create' || sentinel === 'new' || sentinel === 'null' || sentinel === 'undefined'\n const id = shouldGenerate ? ((): string => {\n const g = globalThis as { crypto?: { randomUUID?: () => string } }\n if (g.crypto?.randomUUID) return g.crypto.randomUUID()\n // Fallback UUIDv4 generator\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0\n const v = c === 'x' ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n })() : rawId\n const orgId = opts.organizationId ?? null\n const tenantId = opts.tenantId ?? null\n const doc: Record<string, unknown> = { id, ...this.normalizeDocValues(sanitizedValues || {}) }\n\n const payload = {\n entity_type: opts.entityId,\n entity_id: id,\n organization_id: orgId,\n tenant_id: tenantId,\n doc,\n updated_at: knex.fn.now(),\n created_at: knex.fn.now(),\n deleted_at: null,\n }\n\n // Upsert by scoped uniqueness\n try {\n await knex('custom_entities_storage')\n .insert(payload)\n .onConflict(['entity_type', 'entity_id', 'organization_id'])\n .merge({ doc: payload.doc, updated_at: knex.fn.now(), deleted_at: null })\n } catch {\n // Fallback for global scope uniqueness\n try {\n const updated = await knex('custom_entities_storage')\n .where({ entity_type: opts.entityId, entity_id: id, organization_id: orgId })\n .update({ doc: payload.doc, updated_at: knex.fn.now(), deleted_at: null })\n if (!updated) {\n await knex('custom_entities_storage').insert(payload)\n }\n } catch (err) {\n // Surface a clear error so it doesn't silently fall back only to EAV\n throw err\n }\n }\n\n // Optional EAV backward compatibility (disabled by default)\n if (this.backcompatEavEnabled() && sanitizedValues && Object.keys(sanitizedValues).length > 0) {\n await this.setCustomFields({\n entityId: opts.entityId,\n recordId: id,\n organizationId: orgId,\n tenantId: tenantId,\n values: normalizeCustomFieldValues(sanitizedValues),\n notify: opts.notify, // defaults to true\n })\n }\n\n return { id }\n }\n\n async updateCustomEntityRecord(opts: Parameters<DataEngine['updateCustomEntityRecord']>[0]): Promise<void> {\n const knex = this.em.getConnection().getKnex()\n const sanitizedValues = await sanitizeCustomFieldHtmlRichTextValuesServer(this.em, {\n entityId: opts.entityId,\n organizationId: opts.organizationId ?? null,\n tenantId: opts.tenantId ?? null,\n values: opts.values || {},\n })\n await this.validateCustomFieldValues(opts.entityId, opts.organizationId ?? null, opts.tenantId ?? null, sanitizedValues)\n const id = String(opts.recordId)\n const orgId = opts.organizationId ?? null\n const tenantId = opts.tenantId ?? null\n\n // Merge doc shallowly: load existing doc and overlay\n await this.ensureStorageTableExists()\n const row = await knex('custom_entities_storage')\n .where({ entity_type: opts.entityId, entity_id: id, organization_id: orgId })\n .first()\n const prevDoc: Record<string, unknown> = row?.doc || { id }\n const nextDoc: Record<string, unknown> = { ...prevDoc, ...this.normalizeDocValues(sanitizedValues || {}), id }\n try {\n const updated = await knex('custom_entities_storage')\n .where({ entity_type: opts.entityId, entity_id: id, organization_id: orgId })\n .update({ doc: nextDoc, updated_at: knex.fn.now(), deleted_at: null })\n if (!updated) {\n await knex('custom_entities_storage').insert({\n entity_type: opts.entityId,\n entity_id: id,\n organization_id: orgId,\n tenant_id: tenantId,\n doc: nextDoc,\n created_at: knex.fn.now(),\n updated_at: knex.fn.now(),\n deleted_at: null,\n })\n }\n } catch (err) {\n throw err\n }\n\n // Optional EAV backward compatibility (disabled by default)\n if (this.backcompatEavEnabled() && sanitizedValues && Object.keys(sanitizedValues).length > 0) {\n await this.setCustomFields({\n entityId: opts.entityId,\n recordId: id,\n organizationId: orgId,\n tenantId: tenantId,\n values: normalizeCustomFieldValues(sanitizedValues),\n notify: opts.notify, // defaults to true\n })\n }\n }\n\n async deleteCustomEntityRecord(opts: Parameters<DataEngine['deleteCustomEntityRecord']>[0]): Promise<void> {\n const knex = this.em.getConnection().getKnex()\n const id = String(opts.recordId)\n const orgId = opts.organizationId ?? null\n const soft = opts.soft !== false\n\n if (soft) {\n await knex('custom_entities_storage')\n .where({ entity_type: opts.entityId, entity_id: id, organization_id: orgId })\n .update({ deleted_at: knex.fn.now(), updated_at: knex.fn.now() })\n } else {\n await knex('custom_entities_storage')\n .where({ entity_type: opts.entityId, entity_id: id, organization_id: orgId })\n .delete()\n }\n\n // Soft-delete EAV values to preserve current behavior\n try {\n const { CustomFieldValue } = await import('@open-mercato/core/modules/entities/data/entities')\n const values = await this.em.find(CustomFieldValue, {\n entityId: opts.entityId,\n recordId: id,\n organizationId: orgId,\n tenantId: opts.tenantId ?? null,\n })\n const now = new Date()\n const mutated = values.filter((record) => {\n if (record.deletedAt) return false\n record.deletedAt = now\n return true\n })\n if (mutated.length) await this.em.persistAndFlush(values)\n } catch { /* non-blocking */ }\n }\n\n async createOrmEntity<T extends object>(opts: { entity: EntityName<T>; data: EntityData<T> }): Promise<T> {\n const entity = this.em.create(\n opts.entity,\n opts.data as RequiredEntityData<T, never, true>\n )\n await this.em.persistAndFlush(entity)\n return entity\n }\n\n async updateOrmEntity<T extends object>(opts: {\n entity: EntityName<T>\n where: FilterQuery<T>\n apply: (current: T) => Promise<void> | void\n }): Promise<T | null> {\n const current = await this.em.findOne(opts.entity, opts.where)\n if (!current) return null\n await opts.apply(current)\n await this.em.persistAndFlush(current)\n return current\n }\n\n async deleteOrmEntity<T extends object>(opts: {\n entity: EntityName<T>\n where: FilterQuery<T>\n soft?: boolean\n softDeleteField?: keyof T & string\n }): Promise<T | null> {\n const current = await this.em.findOne(opts.entity, opts.where)\n if (!current) return null\n if (opts.soft !== false) {\n const field = opts.softDeleteField || ('deletedAt' as keyof T & string)\n if (typeof current === 'object' && current !== null) {\n ;(current as Record<string, unknown>)[field] = new Date()\n await this.em.persistAndFlush(current)\n }\n } else {\n await this.em.removeAndFlush(current)\n }\n return current\n }\n\n async emitOrmEntityEvent<T>(opts: {\n action: CrudEventAction\n entity: T\n events?: CrudEventsConfig<T>\n indexer?: CrudIndexerConfig<T>\n identifiers: CrudEntityIdentifiers\n syncOrigin?: string | null\n }): Promise<void> {\n const { action, entity, events, indexer, identifiers, syncOrigin } = opts\n if (!events && !indexer) return\n if (!identifiers?.id) return\n\n let bus: EventBus | null = null\n try {\n bus = (this.container.resolve('eventBus') as EventBus)\n } catch {\n bus = null\n }\n if (!bus) return\n\n const ctx = {\n action,\n entity,\n identifiers: {\n id: identifiers.id,\n organizationId: identifiers.organizationId ?? null,\n tenantId: identifiers.tenantId ?? null,\n },\n syncOrigin: syncOrigin ?? null,\n }\n\n if (events) {\n const eventName = `${events.module}.${events.entity}.${action}`\n warnIfUndeclaredEvent(eventName, 'emitOrmEntityEvent')\n const payload = events.buildPayload\n ? events.buildPayload(ctx)\n : {\n id: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n ...(ctx.syncOrigin ? { syncOrigin: ctx.syncOrigin } : {}),\n }\n try {\n await bus.emitEvent(eventName, payload, {\n persistent: !!events.persistent,\n tenantId: ctx.identifiers.tenantId ?? null,\n organizationId: ctx.identifiers.organizationId ?? null,\n })\n } catch {\n // non-blocking\n }\n }\n\n if (indexer) {\n const resolveCoverageBaseDelta = (): number | undefined => {\n if (action === 'created') return 1\n if (action === 'deleted') return -1\n return undefined\n }\n const coverageBaseDelta = resolveCoverageBaseDelta()\n\n if (action === 'deleted') {\n const payload = indexer.buildDeletePayload\n ? indexer.buildDeletePayload(ctx)\n : {\n entityType: indexer.entityType,\n recordId: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }\n const enrichedPayload = payload as Record<string, unknown>\n enrichedPayload.crudAction = action\n if (coverageBaseDelta !== undefined) enrichedPayload.coverageBaseDelta = coverageBaseDelta\n if (ctx.syncOrigin) enrichedPayload.syncOrigin = ctx.syncOrigin\n try {\n await bus.emitEvent('query_index.delete_one', enrichedPayload)\n } catch {\n // non-blocking\n }\n } else {\n const payload = indexer.buildUpsertPayload\n ? indexer.buildUpsertPayload(ctx)\n : {\n entityType: indexer.entityType,\n recordId: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }\n const enrichedPayload = payload as Record<string, unknown>\n enrichedPayload.crudAction = action\n if (coverageBaseDelta !== undefined) enrichedPayload.coverageBaseDelta = coverageBaseDelta\n if (ctx.syncOrigin) enrichedPayload.syncOrigin = ctx.syncOrigin\n try {\n await bus.emitEvent('query_index.upsert_one', enrichedPayload)\n } catch {\n // non-blocking\n }\n }\n\n if (shouldTriggerCoverageRefresh(indexer.entityType, ctx.identifiers.tenantId ?? null)) {\n void bus.emitEvent('query_index.coverage.refresh', {\n entityType: indexer.entityType,\n tenantId: ctx.identifiers.tenantId ?? null,\n organizationId: null,\n delayMs: 0,\n }).catch(() => undefined)\n }\n }\n }\n\n markOrmEntityChange<T>(opts: {\n action: CrudEventAction\n entity: T | null | undefined\n events?: CrudEventsConfig<T>\n indexer?: CrudIndexerConfig<T>\n identifiers: CrudEntityIdentifiers\n syncOrigin?: string | null\n }): void {\n const { entity, identifiers } = opts\n if (!entity) return\n if (!identifiers?.id) return\n const key = this.buildSideEffectKey(opts.action, identifiers)\n const existing = this.pendingSideEffects.get(key)\n if (existing) {\n existing.entity = entity\n existing.identifiers = {\n id: identifiers.id,\n organizationId: identifiers.organizationId ?? null,\n tenantId: identifiers.tenantId ?? null,\n }\n existing.syncOrigin = opts.syncOrigin ?? null\n if (opts.events) existing.events = opts.events as CrudEventsConfig<unknown>\n if (opts.indexer) existing.indexer = opts.indexer as CrudIndexerConfig<unknown>\n this.pendingSideEffects.set(key, existing)\n return\n }\n const entry: QueuedCrudSideEffect = {\n action: opts.action,\n entity,\n identifiers: {\n id: identifiers.id,\n organizationId: identifiers.organizationId ?? null,\n tenantId: identifiers.tenantId ?? null,\n },\n syncOrigin: opts.syncOrigin ?? null,\n }\n if (opts.events) entry.events = opts.events as CrudEventsConfig<unknown>\n if (opts.indexer) entry.indexer = opts.indexer as CrudIndexerConfig<unknown>\n this.pendingSideEffects.set(key, entry)\n }\n\n async flushOrmEntityChanges(): Promise<void> {\n if (!this.pendingSideEffects.size) return\n const entries = Array.from(this.pendingSideEffects.values())\n this.pendingSideEffects.clear()\n for (const entry of entries) {\n try {\n await this.emitOrmEntityEvent({\n action: entry.action,\n entity: entry.entity,\n identifiers: entry.identifiers,\n syncOrigin: entry.syncOrigin ?? null,\n events: entry.events as CrudEventsConfig<unknown>,\n indexer: entry.indexer as CrudIndexerConfig<unknown>,\n })\n } catch {\n // best-effort; continue with remaining side effects\n }\n }\n }\n\n private buildSideEffectKey(action: CrudEventAction, identifiers: CrudEntityIdentifiers): string {\n const id = identifiers.id ?? ''\n const org = identifiers.organizationId ?? ''\n const tenant = identifiers.tenantId ?? ''\n return [action, id, org, tenant].join('|')\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityData, EntityName, FilterQuery, RequiredEntityData } from '@mikro-orm/core'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\nimport { type Kysely, sql } from 'kysely'\nimport { setRecordCustomFields } from '@open-mercato/core/modules/entities/lib/helpers'\nimport { validateCustomFieldValuesServer } from '@open-mercato/core/modules/entities/lib/validation'\nimport { sanitizeCustomFieldHtmlRichTextValuesServer } from '@open-mercato/core/modules/entities/lib/htmlRichTextSanitizer'\nimport type { EventBus } from '@open-mercato/events/types'\nimport type {\n CrudEventAction,\n CrudEventsConfig,\n CrudIndexerConfig,\n CrudEntityIdentifiers,\n} from '../crud/types'\nimport { CrudHttpError } from '../crud/errors'\nimport { normalizeCustomFieldValues } from '../custom-fields/normalize'\nimport { parseBooleanToken } from '../boolean'\nimport { isEventDeclared } from '../../modules/events'\n\nconst undeclaredEventWarned = new Set<string>()\n\nfunction warnIfUndeclaredEvent(eventName: string, context: string): void {\n if (isEventDeclared(eventName)) return\n if (undeclaredEventWarned.has(eventName)) return\n undeclaredEventWarned.add(eventName)\n console.warn(\n `[data-engine] ${context} is emitting undeclared event \"${eventName}\". ` +\n `Declare it in the owning module's events.ts (createModuleEvents) so the event registry stays authoritative.`,\n )\n}\n\n/** Internal: clear the undeclared-event warning cache. Exposed for tests. */\nexport function __resetUndeclaredEventWarningsForTests(): void {\n undeclaredEventWarned.clear()\n}\n\nconst COVERAGE_REFRESH_INTERVAL_MS = 5 * 60 * 1000\nconst coverageRefreshTracker = new Map<string, number>()\n\nfunction shouldTriggerCoverageRefresh(entityType: string | undefined, tenantId: string | null): boolean {\n if (!entityType) return false\n const key = `${entityType}|${tenantId ?? '__null__'}`\n const now = Date.now()\n const last = coverageRefreshTracker.get(key) ?? 0\n if (now - last < COVERAGE_REFRESH_INTERVAL_MS) return false\n coverageRefreshTracker.set(key, now)\n return true\n}\n\ntype CustomEntityValues = Record<string, unknown>\n\ntype QueuedCrudSideEffect = {\n action: CrudEventAction\n entity: unknown\n identifiers: CrudEntityIdentifiers\n syncOrigin?: string | null\n events?: CrudEventsConfig<unknown>\n indexer?: CrudIndexerConfig<unknown>\n}\n\nexport interface DataEngine {\n setCustomFields(opts: {\n entityId: string\n recordId: string\n organizationId?: string | null\n tenantId?: string | null\n values: Record<string, string | number | boolean | null | undefined | Array<string | number | boolean | null | undefined>>\n notify?: boolean // default true -> emit '<module>.<entity>.updated'\n }): Promise<void>\n\n // Storage for user-defined entities (doc-based)\n createCustomEntityRecord(opts: {\n entityId: string // '<module>:<entity>'\n recordId?: string // optional; auto-generate if not provided\n organizationId?: string | null\n tenantId?: string | null\n values: CustomEntityValues\n notify?: boolean // keep event emitting as it is via setCustomFields (updated)\n }): Promise<{ id: string }>\n\n updateCustomEntityRecord(opts: {\n entityId: string\n recordId: string\n organizationId?: string | null\n tenantId?: string | null\n values: CustomEntityValues\n notify?: boolean // keep event emitting as it is via setCustomFields (updated)\n }): Promise<void>\n\n deleteCustomEntityRecord(opts: {\n entityId: string\n recordId: string\n organizationId?: string | null\n tenantId?: string | null\n soft?: boolean // default true: sets deleted_at\n notify?: boolean // keep event emitting as it is (no extra events here)\n }): Promise<void>\n\n // Generic ORM-backed entity operations used by CrudFactory\n createOrmEntity<T extends object>(opts: {\n entity: EntityName<T>\n data: EntityData<T>\n }): Promise<T>\n\n updateOrmEntity<T extends object>(opts: {\n entity: EntityName<T>\n where: FilterQuery<T>\n apply: (current: T) => Promise<void> | void\n }): Promise<T | null>\n\n deleteOrmEntity<T extends object>(opts: {\n entity: EntityName<T>\n where: FilterQuery<T>\n soft?: boolean\n softDeleteField?: keyof T & string\n }): Promise<T | null>\n\n emitOrmEntityEvent<T>(opts: {\n action: CrudEventAction\n entity: T\n events?: CrudEventsConfig<T>\n indexer?: CrudIndexerConfig<T>\n identifiers: CrudEntityIdentifiers\n syncOrigin?: string | null\n }): Promise<void>\n\n markOrmEntityChange<T>(opts: {\n action: CrudEventAction\n entity: T | null | undefined\n events?: CrudEventsConfig<T>\n indexer?: CrudIndexerConfig<T>\n identifiers: CrudEntityIdentifiers\n syncOrigin?: string | null\n }): void\n\n flushOrmEntityChanges(): Promise<void>\n}\n\nexport class DefaultDataEngine implements DataEngine {\n private pendingSideEffects = new Map<string, QueuedCrudSideEffect>()\n constructor(private em: EntityManager, private container: AwilixContainer) {}\n\n async setCustomFields(opts: Parameters<DataEngine['setCustomFields']>[0]): Promise<void> {\n const { entityId, recordId, organizationId = null, tenantId = null, values } = opts\n const sanitizedValues = await sanitizeCustomFieldHtmlRichTextValuesServer(this.em, {\n entityId,\n organizationId,\n tenantId,\n values,\n })\n await this.validateCustomFieldValues(entityId, organizationId, tenantId, sanitizedValues as Record<string, unknown>)\n let encryptionService: any = null\n try {\n encryptionService = this.container.resolve('tenantEncryptionService') as any\n } catch {\n encryptionService = null\n }\n await setRecordCustomFields(this.em, {\n entityId,\n recordId,\n organizationId,\n tenantId,\n values: sanitizedValues,\n encryptionService,\n })\n if (opts.notify !== false) {\n let bus: EventBus | null = null\n try {\n bus = (this.container.resolve('eventBus') as EventBus)\n } catch {\n bus = null\n }\n if (bus) {\n const [mod, ent] = (entityId || '').split(':')\n if (mod && ent) {\n const eventName = `${mod}.${ent}.updated`\n warnIfUndeclaredEvent(eventName, 'setCustomFields')\n try {\n await bus.emitEvent(eventName, { id: recordId, organizationId, tenantId }, { persistent: true })\n } catch {\n // non-blocking\n }\n }\n }\n }\n }\n\n private normalizeDocValues(values: CustomEntityValues): CustomEntityValues {\n const out: CustomEntityValues = {}\n for (const [k, v] of Object.entries(values || {})) {\n // Never allow callers to override reserved identifiers in the doc\n if (k === 'id' || k === 'entity_id' || k === 'entityId') continue\n // Accept both 'cf_<key>' and 'cf:<key>' inputs and normalize to 'cf:<key>'\n if (k.startsWith('cf_')) out[`cf:${k.slice(3)}`] = v\n else out[k] = v\n }\n return out\n }\n\n private backcompatEavEnabled(): boolean {\n try {\n return parseBooleanToken(process.env.ENTITIES_BACKCOMPAT_EAV_FOR_CUSTOM ?? '') === true\n } catch { return false }\n }\n\n private getKysely(): Kysely<any> {\n return this.em.getKysely<any>()\n }\n\n private async ensureStorageTableExists(): Promise<void> {\n const db = this.getKysely()\n const exists = await db\n .selectFrom('information_schema.tables' as any)\n .select(sql`1`.as('present'))\n .where('table_name' as any, '=', 'custom_entities_storage')\n .executeTakeFirst()\n if (!exists) {\n throw new Error('custom_entities_storage table is missing. Run migrations (yarn db:migrate).')\n }\n }\n\n private normalizeValuesForValidation(values: Record<string, unknown> | undefined | null): Record<string, unknown> {\n if (!values) return {}\n const out: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(values)) {\n if (value === undefined) continue\n if (key.startsWith('cf_') || key.startsWith('cf:')) {\n const normalized = key.slice(3)\n if (normalized) out[normalized] = value\n continue\n }\n out[key] = value\n }\n return out\n }\n\n private async validateCustomFieldValues(\n entityId: string,\n organizationId: string | null,\n tenantId: string | null,\n values: Record<string, unknown> | undefined | null,\n ): Promise<void> {\n const prepared = this.normalizeValuesForValidation(values)\n if (!entityId || Object.keys(prepared).length === 0) return\n const result = await validateCustomFieldValuesServer(this.em, {\n entityId,\n organizationId,\n tenantId,\n values: prepared,\n })\n if (!result.ok) {\n throw new CrudHttpError(400, { error: 'Validation failed', fields: result.fieldErrors })\n }\n }\n\n async createCustomEntityRecord(opts: Parameters<DataEngine['createCustomEntityRecord']>[0]): Promise<{ id: string }> {\n const db = this.getKysely()\n await this.ensureStorageTableExists()\n const sanitizedValues = await sanitizeCustomFieldHtmlRichTextValuesServer(this.em, {\n entityId: opts.entityId,\n organizationId: opts.organizationId ?? null,\n tenantId: opts.tenantId ?? null,\n values: opts.values || {},\n })\n await this.validateCustomFieldValues(opts.entityId, opts.organizationId ?? null, opts.tenantId ?? null, sanitizedValues)\n const rawId = String(opts.recordId ?? '').trim()\n const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(rawId)\n const sentinel = rawId.toLowerCase()\n const shouldGenerate = !rawId || !isUuid || sentinel === 'create' || sentinel === 'new' || sentinel === 'null' || sentinel === 'undefined'\n const id = shouldGenerate ? ((): string => {\n const g = globalThis as { crypto?: { randomUUID?: () => string } }\n if (g.crypto?.randomUUID) return g.crypto.randomUUID()\n // Fallback UUIDv4 generator\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0\n const v = c === 'x' ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n })() : rawId\n const orgId = opts.organizationId ?? null\n const tenantId = opts.tenantId ?? null\n const doc: Record<string, unknown> = { id, ...this.normalizeDocValues(sanitizedValues || {}) }\n\n const now = sql`now()`\n const payload = {\n entity_type: opts.entityId,\n entity_id: id,\n organization_id: orgId,\n tenant_id: tenantId,\n doc: sql`${JSON.stringify(doc)}::jsonb`,\n updated_at: now,\n created_at: now,\n deleted_at: null,\n }\n\n // Upsert by scoped uniqueness\n try {\n await db\n .insertInto('custom_entities_storage' as any)\n .values(payload as any)\n .onConflict((oc) => oc\n .columns(['entity_type', 'entity_id', 'organization_id'])\n .doUpdateSet({\n doc: sql`${JSON.stringify(doc)}::jsonb`,\n updated_at: sql`now()`,\n deleted_at: null,\n } as any))\n .execute()\n } catch {\n // Fallback for global scope uniqueness\n try {\n const updated = await db\n .updateTable('custom_entities_storage' as any)\n .set({\n doc: sql`${JSON.stringify(doc)}::jsonb`,\n updated_at: sql`now()`,\n deleted_at: null,\n } as any)\n .where('entity_type' as any, '=', opts.entityId)\n .where('entity_id' as any, '=', id)\n .where('organization_id' as any, orgId === null ? 'is' : '=', orgId as any)\n .executeTakeFirst()\n if (!updated || Number(updated.numUpdatedRows ?? 0) === 0) {\n await db.insertInto('custom_entities_storage' as any).values(payload as any).execute()\n }\n } catch (err) {\n // Surface a clear error so it doesn't silently fall back only to EAV\n throw err\n }\n }\n\n // Optional EAV backward compatibility (disabled by default)\n if (this.backcompatEavEnabled() && sanitizedValues && Object.keys(sanitizedValues).length > 0) {\n await this.setCustomFields({\n entityId: opts.entityId,\n recordId: id,\n organizationId: orgId,\n tenantId: tenantId,\n values: normalizeCustomFieldValues(sanitizedValues),\n notify: opts.notify, // defaults to true\n })\n }\n\n return { id }\n }\n\n async updateCustomEntityRecord(opts: Parameters<DataEngine['updateCustomEntityRecord']>[0]): Promise<void> {\n const db = this.getKysely()\n const sanitizedValues = await sanitizeCustomFieldHtmlRichTextValuesServer(this.em, {\n entityId: opts.entityId,\n organizationId: opts.organizationId ?? null,\n tenantId: opts.tenantId ?? null,\n values: opts.values || {},\n })\n await this.validateCustomFieldValues(opts.entityId, opts.organizationId ?? null, opts.tenantId ?? null, sanitizedValues)\n const id = String(opts.recordId)\n const orgId = opts.organizationId ?? null\n const tenantId = opts.tenantId ?? null\n\n // Merge doc shallowly: load existing doc and overlay\n await this.ensureStorageTableExists()\n const applyScope = <T extends { where: (col: any, op: any, val?: any) => T }>(q: T) => {\n let chain = q.where('entity_type' as any, '=', opts.entityId)\n chain = chain.where('entity_id' as any, '=', id)\n chain = orgId === null\n ? chain.where('organization_id' as any, 'is', null as any)\n : chain.where('organization_id' as any, '=', orgId)\n return chain\n }\n const row = await applyScope(\n db.selectFrom('custom_entities_storage' as any).select(['doc' as any])\n ).executeTakeFirst()\n const prevDoc: Record<string, unknown> = (row as any)?.doc || { id }\n const nextDoc: Record<string, unknown> = { ...prevDoc, ...this.normalizeDocValues(sanitizedValues || {}), id }\n try {\n const updated = await applyScope(\n db.updateTable('custom_entities_storage' as any).set({\n doc: sql`${JSON.stringify(nextDoc)}::jsonb`,\n updated_at: sql`now()`,\n deleted_at: null,\n } as any) as any\n ).executeTakeFirst()\n if (!updated || Number((updated as any).numUpdatedRows ?? 0) === 0) {\n await db.insertInto('custom_entities_storage' as any).values({\n entity_type: opts.entityId,\n entity_id: id,\n organization_id: orgId,\n tenant_id: tenantId,\n doc: sql`${JSON.stringify(nextDoc)}::jsonb`,\n created_at: sql`now()`,\n updated_at: sql`now()`,\n deleted_at: null,\n } as any).execute()\n }\n } catch (err) {\n throw err\n }\n\n // Optional EAV backward compatibility (disabled by default)\n if (this.backcompatEavEnabled() && sanitizedValues && Object.keys(sanitizedValues).length > 0) {\n await this.setCustomFields({\n entityId: opts.entityId,\n recordId: id,\n organizationId: orgId,\n tenantId: tenantId,\n values: normalizeCustomFieldValues(sanitizedValues),\n notify: opts.notify, // defaults to true\n })\n }\n }\n\n async deleteCustomEntityRecord(opts: Parameters<DataEngine['deleteCustomEntityRecord']>[0]): Promise<void> {\n const db = this.getKysely()\n const id = String(opts.recordId)\n const orgId = opts.organizationId ?? null\n const soft = opts.soft !== false\n\n const applyScope = <T extends { where: (col: any, op: any, val?: any) => T }>(q: T) => {\n let chain = q.where('entity_type' as any, '=', opts.entityId)\n chain = chain.where('entity_id' as any, '=', id)\n chain = orgId === null\n ? chain.where('organization_id' as any, 'is', null as any)\n : chain.where('organization_id' as any, '=', orgId)\n return chain\n }\n\n if (soft) {\n await applyScope(\n db.updateTable('custom_entities_storage' as any).set({\n deleted_at: sql`now()`,\n updated_at: sql`now()`,\n } as any) as any\n ).execute()\n } else {\n await applyScope(db.deleteFrom('custom_entities_storage' as any) as any).execute()\n }\n\n // Soft-delete EAV values to preserve current behavior\n try {\n const { CustomFieldValue } = await import('@open-mercato/core/modules/entities/data/entities')\n const values = await this.em.find(CustomFieldValue, {\n entityId: opts.entityId,\n recordId: id,\n organizationId: orgId,\n tenantId: opts.tenantId ?? null,\n })\n const now = new Date()\n const mutated = values.filter((record) => {\n if (record.deletedAt) return false\n record.deletedAt = now\n return true\n })\n if (mutated.length) {\n for (const record of values) this.em.persist(record)\n await this.em.flush()\n }\n } catch { /* non-blocking */ }\n }\n\n async createOrmEntity<T extends object>(opts: { entity: EntityName<T>; data: EntityData<T> }): Promise<T> {\n const entity = this.em.create(\n opts.entity as EntityName<T>,\n opts.data as unknown as RequiredEntityData<T>\n )\n await this.em.persist(entity).flush()\n return entity\n }\n\n async updateOrmEntity<T extends object>(opts: {\n entity: EntityName<T>\n where: FilterQuery<T>\n apply: (current: T) => Promise<void> | void\n }): Promise<T | null> {\n const current = await this.em.findOne(opts.entity as EntityName<T>, opts.where as FilterQuery<NoInfer<T>>)\n if (!current) return null\n await opts.apply(current)\n await this.em.persist(current).flush()\n return current\n }\n\n async deleteOrmEntity<T extends object>(opts: {\n entity: EntityName<T>\n where: FilterQuery<T>\n soft?: boolean\n softDeleteField?: keyof T & string\n }): Promise<T | null> {\n const current = await this.em.findOne(opts.entity as EntityName<T>, opts.where as FilterQuery<NoInfer<T>>)\n if (!current) return null\n if (opts.soft !== false) {\n const field = opts.softDeleteField || ('deletedAt' as keyof T & string)\n if (typeof current === 'object' && current !== null) {\n ;(current as Record<string, unknown>)[field] = new Date()\n await this.em.persist(current).flush()\n }\n } else {\n await this.em.remove(current).flush()\n }\n return current\n }\n\n async emitOrmEntityEvent<T>(opts: {\n action: CrudEventAction\n entity: T\n events?: CrudEventsConfig<T>\n indexer?: CrudIndexerConfig<T>\n identifiers: CrudEntityIdentifiers\n syncOrigin?: string | null\n }): Promise<void> {\n const { action, entity, events, indexer, identifiers, syncOrigin } = opts\n if (!events && !indexer) return\n if (!identifiers?.id) return\n\n let bus: EventBus | null = null\n try {\n bus = (this.container.resolve('eventBus') as EventBus)\n } catch {\n bus = null\n }\n if (!bus) return\n\n const ctx = {\n action,\n entity,\n identifiers: {\n id: identifiers.id,\n organizationId: identifiers.organizationId ?? null,\n tenantId: identifiers.tenantId ?? null,\n },\n syncOrigin: syncOrigin ?? null,\n }\n\n if (events) {\n const eventName = `${events.module}.${events.entity}.${action}`\n warnIfUndeclaredEvent(eventName, 'emitOrmEntityEvent')\n const payload = events.buildPayload\n ? events.buildPayload(ctx)\n : {\n id: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n ...(ctx.syncOrigin ? { syncOrigin: ctx.syncOrigin } : {}),\n }\n try {\n await bus.emitEvent(eventName, payload, {\n persistent: !!events.persistent,\n tenantId: ctx.identifiers.tenantId ?? null,\n organizationId: ctx.identifiers.organizationId ?? null,\n })\n } catch {\n // non-blocking\n }\n }\n\n if (indexer) {\n const resolveCoverageBaseDelta = (): number | undefined => {\n if (action === 'created') return 1\n if (action === 'deleted') return -1\n return undefined\n }\n const coverageBaseDelta = resolveCoverageBaseDelta()\n\n if (action === 'deleted') {\n const payload = indexer.buildDeletePayload\n ? indexer.buildDeletePayload(ctx)\n : {\n entityType: indexer.entityType,\n recordId: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }\n const enrichedPayload = payload as Record<string, unknown>\n enrichedPayload.crudAction = action\n if (coverageBaseDelta !== undefined) enrichedPayload.coverageBaseDelta = coverageBaseDelta\n if (ctx.syncOrigin) enrichedPayload.syncOrigin = ctx.syncOrigin\n try {\n await bus.emitEvent('query_index.delete_one', enrichedPayload)\n } catch {\n // non-blocking\n }\n } else {\n const payload = indexer.buildUpsertPayload\n ? indexer.buildUpsertPayload(ctx)\n : {\n entityType: indexer.entityType,\n recordId: ctx.identifiers.id,\n organizationId: ctx.identifiers.organizationId,\n tenantId: ctx.identifiers.tenantId,\n }\n const enrichedPayload = payload as Record<string, unknown>\n enrichedPayload.crudAction = action\n if (coverageBaseDelta !== undefined) enrichedPayload.coverageBaseDelta = coverageBaseDelta\n if (ctx.syncOrigin) enrichedPayload.syncOrigin = ctx.syncOrigin\n try {\n await bus.emitEvent('query_index.upsert_one', enrichedPayload)\n } catch {\n // non-blocking\n }\n }\n\n if (shouldTriggerCoverageRefresh(indexer.entityType, ctx.identifiers.tenantId ?? null)) {\n void bus.emitEvent('query_index.coverage.refresh', {\n entityType: indexer.entityType,\n tenantId: ctx.identifiers.tenantId ?? null,\n organizationId: null,\n delayMs: 0,\n }).catch(() => undefined)\n }\n }\n }\n\n markOrmEntityChange<T>(opts: {\n action: CrudEventAction\n entity: T | null | undefined\n events?: CrudEventsConfig<T>\n indexer?: CrudIndexerConfig<T>\n identifiers: CrudEntityIdentifiers\n syncOrigin?: string | null\n }): void {\n const { entity, identifiers } = opts\n if (!entity) return\n if (!identifiers?.id) return\n const key = this.buildSideEffectKey(opts.action, identifiers)\n const existing = this.pendingSideEffects.get(key)\n if (existing) {\n existing.entity = entity\n existing.identifiers = {\n id: identifiers.id,\n organizationId: identifiers.organizationId ?? null,\n tenantId: identifiers.tenantId ?? null,\n }\n existing.syncOrigin = opts.syncOrigin ?? null\n if (opts.events) existing.events = opts.events as CrudEventsConfig<unknown>\n if (opts.indexer) existing.indexer = opts.indexer as CrudIndexerConfig<unknown>\n this.pendingSideEffects.set(key, existing)\n return\n }\n const entry: QueuedCrudSideEffect = {\n action: opts.action,\n entity,\n identifiers: {\n id: identifiers.id,\n organizationId: identifiers.organizationId ?? null,\n tenantId: identifiers.tenantId ?? null,\n },\n syncOrigin: opts.syncOrigin ?? null,\n }\n if (opts.events) entry.events = opts.events as CrudEventsConfig<unknown>\n if (opts.indexer) entry.indexer = opts.indexer as CrudIndexerConfig<unknown>\n this.pendingSideEffects.set(key, entry)\n }\n\n async flushOrmEntityChanges(): Promise<void> {\n if (!this.pendingSideEffects.size) return\n const entries = Array.from(this.pendingSideEffects.values())\n this.pendingSideEffects.clear()\n for (const entry of entries) {\n try {\n await this.emitOrmEntityEvent({\n action: entry.action,\n entity: entry.entity,\n identifiers: entry.identifiers,\n syncOrigin: entry.syncOrigin ?? null,\n events: entry.events as CrudEventsConfig<unknown>,\n indexer: entry.indexer as CrudIndexerConfig<unknown>,\n })\n } catch {\n // best-effort; continue with remaining side effects\n }\n }\n }\n\n private buildSideEffectKey(action: CrudEventAction, identifiers: CrudEntityIdentifiers): string {\n const id = identifiers.id ?? ''\n const org = identifiers.organizationId ?? ''\n const tenant = identifiers.tenantId ?? ''\n return [action, id, org, tenant].join('|')\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,7 @@
1
1
  import "dotenv/config";
2
2
  import "reflect-metadata";
3
3
  import { MikroORM } from "@mikro-orm/core";
4
+ import { ReflectMetadataProvider } from "@mikro-orm/decorators/legacy";
4
5
  import { PostgreSqlDriver } from "@mikro-orm/postgresql";
5
6
  import { getSslConfig } from "./ssl.js";
6
7
  let ormInstance = null;
@@ -9,7 +10,6 @@ function getRegisteredEntities() {
9
10
  return globalThis[GLOBAL_ENTITIES_KEY] ?? null;
10
11
  }
11
12
  function setRegisteredEntities(entities) {
12
- ;
13
13
  globalThis[GLOBAL_ENTITIES_KEY] = entities;
14
14
  }
15
15
  function registerOrmEntities(entities) {
@@ -31,7 +31,9 @@ async function getOrm() {
31
31
  }
32
32
  const entities = getOrmEntities();
33
33
  const clientUrl = process.env.DATABASE_URL;
34
- if (!clientUrl) throw new Error("DATABASE_URL is not set");
34
+ if (!clientUrl) {
35
+ throw new Error("DATABASE_URL is not set");
36
+ }
35
37
  const poolMin = parseInt(process.env.DB_POOL_MIN || "2");
36
38
  const poolMax = parseInt(process.env.DB_POOL_MAX || "20");
37
39
  const poolIdleTimeout = parseInt(process.env.DB_POOL_IDLE_TIMEOUT || "3000");
@@ -47,31 +49,25 @@ async function getOrm() {
47
49
  clientUrl,
48
50
  entities,
49
51
  debug: false,
50
- // Connection pooling configuration
52
+ // v7 no longer defaults to ReflectMetadataProvider. Entities in this repo use
53
+ // `@mikro-orm/decorators/legacy`, which relies on TypeScript `emitDecoratorMetadata`
54
+ // + reflect-metadata for type inference (nullability, column types). Without this,
55
+ // inferred types are silently wrong at runtime.
56
+ metadataProvider: ReflectMetadataProvider,
57
+ // MikroORM v7 pool shape (min/max/idleTimeoutMillis). Knex-era `acquireTimeoutMillis` /
58
+ // `destroyTimeoutMillis` were removed; acquire wait maps to pg `connectionTimeoutMillis`
59
+ // below under `driverOptions`.
51
60
  pool: {
52
61
  min: poolMin,
53
62
  max: poolMax,
54
- idleTimeoutMillis: poolIdleTimeout,
55
- acquireTimeoutMillis: poolAcquireTimeout,
56
- // Close idle connections after 30 seconds
57
- destroyTimeoutMillis: process.env.NODE_ENV === "production" ? 3e4 : 3e3
63
+ idleTimeoutMillis: poolIdleTimeout
58
64
  },
59
- // Connection options
65
+ // Driver options are merged into pg.PoolConfig (ClientConfig + pg-pool).
60
66
  driverOptions: {
61
- // Enable connection pooling
62
- connection: {
63
- // Maximum number of connections in the pool
64
- max: poolMax,
65
- // Minimum number of connections in the pool
66
- min: poolMin,
67
- // Close connections after this many milliseconds of inactivity
68
- idleTimeoutMillis: poolIdleTimeout,
69
- // Maximum time to wait for a connection from the pool
70
- acquireTimeoutMillis: poolAcquireTimeout,
71
- idle_in_transaction_session_timeout: idleInTransactionTimeoutMs,
72
- options: connectionOptions,
73
- ssl: sslConfig
74
- }
67
+ connectionTimeoutMillis: poolAcquireTimeout,
68
+ idle_in_transaction_session_timeout: idleInTransactionTimeoutMs,
69
+ options: connectionOptions,
70
+ ssl: sslConfig
75
71
  }
76
72
  });
77
73
  return ormInstance;