@open-mercato/shared 0.5.1-develop.2683.4878a05b8e → 0.5.1-develop.2694.732417c5ec

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 (36) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/build.mjs +13 -102
  3. package/dist/lib/api/crud.js +1 -1
  4. package/dist/lib/api/crud.js.map +2 -2
  5. package/dist/lib/auth/server.js +1 -1
  6. package/dist/lib/auth/server.js.map +2 -2
  7. package/dist/lib/data/engine.js +68 -27
  8. package/dist/lib/data/engine.js.map +2 -2
  9. package/dist/lib/db/mikro.js +18 -22
  10. package/dist/lib/db/mikro.js.map +2 -2
  11. package/dist/lib/indexers/error-log.js +10 -12
  12. package/dist/lib/indexers/error-log.js.map +2 -2
  13. package/dist/lib/indexers/status-log.js +14 -16
  14. package/dist/lib/indexers/status-log.js.map +2 -2
  15. package/dist/lib/query/engine.js +220 -228
  16. package/dist/lib/query/engine.js.map +3 -3
  17. package/dist/lib/query/join-utils.js +28 -23
  18. package/dist/lib/query/join-utils.js.map +2 -2
  19. package/dist/lib/version.js +1 -1
  20. package/dist/lib/version.js.map +1 -1
  21. package/jest.config.cjs +4 -2
  22. package/package.json +1 -1
  23. package/src/lib/api/__tests__/crud.test.ts +5 -3
  24. package/src/lib/api/crud.ts +1 -1
  25. package/src/lib/auth/__tests__/server.apiKeyCache.test.ts +10 -4
  26. package/src/lib/auth/server.ts +1 -1
  27. package/src/lib/bootstrap/types.ts +2 -2
  28. package/src/lib/crud/__tests__/crud-factory.test.ts +27 -17
  29. package/src/lib/data/engine.ts +95 -47
  30. package/src/lib/db/mikro.ts +26 -25
  31. package/src/lib/indexers/error-log.ts +23 -23
  32. package/src/lib/indexers/status-log.ts +36 -33
  33. package/src/lib/query/__tests__/engine.scope-and-or.test.ts +253 -114
  34. package/src/lib/query/__tests__/engine.test.ts +206 -139
  35. package/src/lib/query/engine.ts +306 -263
  36. package/src/lib/query/join-utils.ts +38 -30
@@ -1,2 +1,2 @@
1
- Found 201 entry points
2
- shared built successfully
1
+ [build:shared] found 201 entry points
2
+ [build:shared] built successfully
package/build.mjs CHANGED
@@ -1,116 +1,27 @@
1
- import * as esbuild from 'esbuild'
2
- import { glob } from 'glob'
3
- import { readFileSync, writeFileSync, existsSync } from 'node:fs'
1
+ import { readFileSync } from 'node:fs'
4
2
  import { dirname, join } from 'node:path'
5
3
  import { fileURLToPath } from 'node:url'
4
+ import { buildPackage } from '../../scripts/build-package.mjs'
6
5
 
7
- const __dirname = dirname(fileURLToPath(import.meta.url))
8
-
9
- // Read the package version at build time for injection
10
- const packageJson = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf-8'))
6
+ const packageDir = dirname(fileURLToPath(import.meta.url))
7
+ const packageJson = JSON.parse(readFileSync(join(packageDir, 'package.json'), 'utf-8'))
11
8
  const packageVersion = packageJson.version
12
9
 
13
- // Plugin to inject version at build time (replaces version.ts content)
10
+ // Inject build-time version into lib/version.ts without touching the source file.
14
11
  const injectVersion = {
15
12
  name: 'inject-version',
16
13
  setup(build) {
17
- build.onLoad({ filter: /lib\/version\.ts$/ }, async () => {
18
- return {
19
- contents: `// Build-time generated version
14
+ build.onLoad({ filter: /lib\/version\.ts$/ }, async () => ({
15
+ contents: `// Build-time generated version
20
16
  export const APP_VERSION = '${packageVersion}'
21
17
  export const appVersion = APP_VERSION
22
18
  `,
23
- loader: 'ts'
24
- }
25
- })
26
- }
19
+ loader: 'ts',
20
+ }))
21
+ },
27
22
  }
28
23
 
29
- const entryPoints = await glob('src/**/*.{ts,tsx}', {
30
- cwd: __dirname,
31
- ignore: ['**/__tests__/**', '**/*.test.ts', '**/*.test.tsx'],
32
- absolute: true,
24
+ await buildPackage(packageDir, {
25
+ name: 'shared',
26
+ extraPlugins: [injectVersion],
33
27
  })
34
-
35
- if (entryPoints.length === 0) {
36
- console.error('No entry points found!')
37
- process.exit(1)
38
- }
39
-
40
- console.log(`Found ${entryPoints.length} entry points`)
41
-
42
- // Plugin to add .js extension to relative imports
43
- const addJsExtension = {
44
- name: 'add-js-extension',
45
- setup(build) {
46
- build.onEnd(async (result) => {
47
- if (result.errors.length > 0) return
48
- const outputFiles = await glob('dist/**/*.js', { cwd: __dirname, absolute: true })
49
- for (const file of outputFiles) {
50
- const fileDir = dirname(file)
51
- let content = readFileSync(file, 'utf-8')
52
- // Add .js to relative imports that don't have an extension
53
- content = content.replace(
54
- /from\s+["'](\.[^"']+)["']/g,
55
- (match, path) => {
56
- if (path.endsWith('.js') || path.endsWith('.json')) return match
57
- // Check if it's a directory with index.js
58
- const resolvedPath = join(fileDir, path)
59
- if (existsSync(resolvedPath) && existsSync(join(resolvedPath, 'index.js'))) {
60
- return `from "${path}/index.js"`
61
- }
62
- return `from "${path}.js"`
63
- }
64
- )
65
- content = content.replace(
66
- /import\s*\(\s*["'](\.[^"']+)["']\s*\)/g,
67
- (match, path) => {
68
- if (path.endsWith('.js') || path.endsWith('.json')) return match
69
- // Check if it's a directory with index.js
70
- const resolvedPath = join(fileDir, path)
71
- if (existsSync(resolvedPath) && existsSync(join(resolvedPath, 'index.js'))) {
72
- return `import("${path}/index.js")`
73
- }
74
- return `import("${path}.js")`
75
- }
76
- )
77
- // Handle side-effect imports: import "./path" (no from clause)
78
- content = content.replace(
79
- /import\s+["'](\.[^"']+)["'];/g,
80
- (match, path) => {
81
- if (path.endsWith('.js') || path.endsWith('.json')) return match
82
- // Check if it's a directory with index.js
83
- const resolvedPath = join(fileDir, path)
84
- if (existsSync(resolvedPath) && existsSync(join(resolvedPath, 'index.js'))) {
85
- return `import "${path}/index.js";`
86
- }
87
- return `import "${path}.js";`
88
- }
89
- )
90
- writeFileSync(file, content)
91
- }
92
- })
93
- }
94
- }
95
-
96
- const outdir = join(__dirname, 'dist')
97
-
98
- const result = await esbuild.build({
99
- entryPoints,
100
- outdir,
101
- outbase: join(__dirname, 'src'),
102
- format: 'esm',
103
- platform: 'node',
104
- target: 'node18',
105
- sourcemap: true,
106
- jsx: 'automatic',
107
- write: true,
108
- plugins: [injectVersion, addJsExtension],
109
- })
110
-
111
- if (result.errors.length > 0) {
112
- console.error('Build errors:', result.errors)
113
- process.exit(1)
114
- }
115
-
116
- console.log('shared built successfully')
@@ -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
  }