@open-mercato/shared 0.5.1-develop.2691.d8a0934b37 → 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 (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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/db/mikro.ts"],
4
- "sourcesContent": ["import 'dotenv/config'\nimport 'reflect-metadata'\nimport { MikroORM } from '@mikro-orm/core'\nimport { PostgreSqlDriver } from '@mikro-orm/postgresql'\nimport { getSslConfig } from './ssl'\n\nlet ormInstance: MikroORM<PostgreSqlDriver> | null = null\n\n// Use globalThis so standalone apps survive duplicated shared package module instances.\nconst GLOBAL_ENTITIES_KEY = '__openMercatoOrmEntities__'\n\nfunction getRegisteredEntities(): any[] | null {\n return (globalThis as Record<string, unknown>)[GLOBAL_ENTITIES_KEY] as any[] | null ?? null\n}\n\nfunction setRegisteredEntities(entities: any[]): void {\n ;(globalThis as Record<string, unknown>)[GLOBAL_ENTITIES_KEY] = entities\n}\n\nexport function registerOrmEntities(entities: any[]) {\n if (getRegisteredEntities() !== null && process.env.NODE_ENV === 'development') {\n console.debug('[Bootstrap] ORM entities re-registered (this may occur during HMR)')\n }\n setRegisteredEntities(entities)\n}\n\nexport function getOrmEntities(): any[] {\n const entities = getRegisteredEntities()\n if (!entities) {\n throw new Error('[Bootstrap] ORM entities not registered. Call registerOrmEntities() at bootstrap.')\n }\n return entities\n}\n\nexport async function getOrm() {\n if (ormInstance) {\n return ormInstance\n }\n const entities = getOrmEntities()\n const clientUrl = process.env.DATABASE_URL\n if (!clientUrl) throw new Error('DATABASE_URL is not set')\n \n // Parse connection pool settings from environment\n const poolMin = parseInt(process.env.DB_POOL_MIN || '2')\n const poolMax = parseInt(process.env.DB_POOL_MAX || '20')\n const poolIdleTimeout = parseInt(process.env.DB_POOL_IDLE_TIMEOUT || '3000')\n const poolAcquireTimeout = parseInt(process.env.DB_POOL_ACQUIRE_TIMEOUT || '6000')\n const idleSessionTimeoutEnv = parseInt(process.env.DB_IDLE_SESSION_TIMEOUT_MS || '')\n const idleInTxTimeoutEnv = parseInt(process.env.DB_IDLE_IN_TRANSACTION_TIMEOUT_MS || '')\n const idleSessionTimeoutMs = Number.isFinite(idleSessionTimeoutEnv)\n ? idleSessionTimeoutEnv\n : process.env.NODE_ENV === 'production'\n ? undefined\n : 600_000\n const idleInTransactionTimeoutMs = Number.isFinite(idleInTxTimeoutEnv)\n ? idleInTxTimeoutEnv\n : process.env.NODE_ENV === 'production'\n ? undefined\n : 120_000\n const connectionOptions =\n idleSessionTimeoutMs && idleSessionTimeoutMs > 0\n ? `-c idle_session_timeout=${idleSessionTimeoutMs}`\n : undefined\n\n const sslConfig = getSslConfig()\n\n ormInstance = await MikroORM.init<PostgreSqlDriver>({\n driver: PostgreSqlDriver,\n clientUrl,\n entities,\n debug: false,\n // Connection pooling configuration\n pool: {\n min: poolMin,\n max: poolMax,\n idleTimeoutMillis: poolIdleTimeout,\n acquireTimeoutMillis: poolAcquireTimeout,\n // Close idle connections after 30 seconds\n destroyTimeoutMillis: process.env.NODE_ENV === 'production' ? 30000 : 3000,\n },\n // Connection options\n driverOptions: {\n // Enable connection pooling\n connection: {\n // Maximum number of connections in the pool\n max: poolMax,\n // Minimum number of connections in the pool\n min: poolMin,\n // Close connections after this many milliseconds of inactivity\n idleTimeoutMillis: poolIdleTimeout,\n // Maximum time to wait for a connection from the pool\n acquireTimeoutMillis: poolAcquireTimeout,\n idle_in_transaction_session_timeout: idleInTransactionTimeoutMs,\n options: connectionOptions,\n ssl: sslConfig,\n },\n },\n })\n return ormInstance\n}\n\n\nasync function closeOrmIfLoaded(): Promise<void> {\n if (ormInstance) {\n await ormInstance.close(true)\n ormInstance = null\n }\n}\n\n// In dev mode, handle reloads cleanly without leaving dangling connections.\nif (process.env.NODE_ENV !== 'production') {\n void closeOrmIfLoaded()\n}\n"],
5
- "mappings": "AAAA,OAAO;AACP,OAAO;AACP,SAAS,gBAAgB;AACzB,SAAS,wBAAwB;AACjC,SAAS,oBAAoB;AAE7B,IAAI,cAAiD;AAGrD,MAAM,sBAAsB;AAE5B,SAAS,wBAAsC;AAC7C,SAAQ,WAAuC,mBAAmB,KAAqB;AACzF;AAEA,SAAS,sBAAsB,UAAuB;AACpD;AAAC,EAAC,WAAuC,mBAAmB,IAAI;AAClE;AAEO,SAAS,oBAAoB,UAAiB;AACnD,MAAI,sBAAsB,MAAM,QAAQ,QAAQ,IAAI,aAAa,eAAe;AAC9E,YAAQ,MAAM,oEAAoE;AAAA,EACpF;AACA,wBAAsB,QAAQ;AAChC;AAEO,SAAS,iBAAwB;AACtC,QAAM,WAAW,sBAAsB;AACvC,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,MAAM,mFAAmF;AAAA,EACrG;AACA,SAAO;AACT;AAEA,eAAsB,SAAS;AAC7B,MAAI,aAAa;AACf,WAAO;AAAA,EACT;AACA,QAAM,WAAW,eAAe;AAChC,QAAM,YAAY,QAAQ,IAAI;AAC9B,MAAI,CAAC,UAAW,OAAM,IAAI,MAAM,yBAAyB;AAGzD,QAAM,UAAU,SAAS,QAAQ,IAAI,eAAe,GAAG;AACvD,QAAM,UAAU,SAAS,QAAQ,IAAI,eAAe,IAAI;AACxD,QAAM,kBAAkB,SAAS,QAAQ,IAAI,wBAAwB,MAAM;AAC3E,QAAM,qBAAqB,SAAS,QAAQ,IAAI,2BAA2B,MAAM;AACjF,QAAM,wBAAwB,SAAS,QAAQ,IAAI,8BAA8B,EAAE;AACnF,QAAM,qBAAqB,SAAS,QAAQ,IAAI,qCAAqC,EAAE;AACvF,QAAM,uBAAuB,OAAO,SAAS,qBAAqB,IAC9D,wBACA,QAAQ,IAAI,aAAa,eACvB,SACA;AACN,QAAM,6BAA6B,OAAO,SAAS,kBAAkB,IACjE,qBACA,QAAQ,IAAI,aAAa,eACvB,SACA;AACN,QAAM,oBACJ,wBAAwB,uBAAuB,IAC3C,2BAA2B,oBAAoB,KAC/C;AAEN,QAAM,YAAY,aAAa;AAE/B,gBAAc,MAAM,SAAS,KAAuB;AAAA,IAClD,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA,OAAO;AAAA;AAAA,IAEP,MAAM;AAAA,MACJ,KAAK;AAAA,MACL,KAAK;AAAA,MACL,mBAAmB;AAAA,MACnB,sBAAsB;AAAA;AAAA,MAEtB,sBAAsB,QAAQ,IAAI,aAAa,eAAe,MAAQ;AAAA,IACxE;AAAA;AAAA,IAEA,eAAe;AAAA;AAAA,MAEb,YAAY;AAAA;AAAA,QAEV,KAAK;AAAA;AAAA,QAEL,KAAK;AAAA;AAAA,QAEL,mBAAmB;AAAA;AAAA,QAEnB,sBAAsB;AAAA,QACtB,qCAAqC;AAAA,QACrC,SAAS;AAAA,QACT,KAAK;AAAA,MACP;AAAA,IACF;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAGA,eAAe,mBAAkC;AAC/C,MAAI,aAAa;AACf,UAAM,YAAY,MAAM,IAAI;AAC5B,kBAAc;AAAA,EAChB;AACF;AAGA,IAAI,QAAQ,IAAI,aAAa,cAAc;AACzC,OAAK,iBAAiB;AACxB;",
4
+ "sourcesContent": ["import 'dotenv/config'\nimport 'reflect-metadata'\nimport { MikroORM } from '@mikro-orm/core'\nimport { ReflectMetadataProvider } from '@mikro-orm/decorators/legacy'\nimport { PostgreSqlDriver, type EntityManager as PostgreSqlEntityManager } from '@mikro-orm/postgresql'\nimport { getSslConfig } from './ssl'\n\nexport type AppMikroORM = MikroORM<PostgreSqlDriver, PostgreSqlEntityManager<PostgreSqlDriver>>\n\nlet ormInstance: AppMikroORM | null = null\n\n// Use globalThis so standalone apps survive duplicated shared package module instances.\nconst GLOBAL_ENTITIES_KEY = '__openMercatoOrmEntities__'\n\nfunction getRegisteredEntities(): any[] | null {\n return (globalThis as Record<string, unknown>)[GLOBAL_ENTITIES_KEY] as any[] | null ?? null\n}\n\nfunction setRegisteredEntities(entities: any[]): void {\n (globalThis as Record<string, unknown>)[GLOBAL_ENTITIES_KEY] = entities\n}\n\nexport function registerOrmEntities(entities: any[]) {\n if (getRegisteredEntities() !== null && process.env.NODE_ENV === 'development') {\n console.debug('[Bootstrap] ORM entities re-registered (this may occur during HMR)')\n }\n setRegisteredEntities(entities)\n}\n\nexport function getOrmEntities(): any[] {\n const entities = getRegisteredEntities()\n if (!entities) {\n throw new Error('[Bootstrap] ORM entities not registered. Call registerOrmEntities() at bootstrap.')\n }\n return entities\n}\n\nexport async function getOrm() {\n if (ormInstance) {\n return ormInstance\n }\n\n const entities = getOrmEntities()\n const clientUrl = process.env.DATABASE_URL\n if (!clientUrl) {\n throw new Error('DATABASE_URL is not set')\n }\n\n // Parse connection pool settings from environment\n const poolMin = parseInt(process.env.DB_POOL_MIN || '2')\n const poolMax = parseInt(process.env.DB_POOL_MAX || '20')\n const poolIdleTimeout = parseInt(process.env.DB_POOL_IDLE_TIMEOUT || '3000')\n const poolAcquireTimeout = parseInt(process.env.DB_POOL_ACQUIRE_TIMEOUT || '6000')\n const idleSessionTimeoutEnv = parseInt(process.env.DB_IDLE_SESSION_TIMEOUT_MS || '')\n const idleInTxTimeoutEnv = parseInt(process.env.DB_IDLE_IN_TRANSACTION_TIMEOUT_MS || '')\n const idleSessionTimeoutMs = Number.isFinite(idleSessionTimeoutEnv)\n ? idleSessionTimeoutEnv\n : process.env.NODE_ENV === 'production'\n ? undefined\n : 600_000\n const idleInTransactionTimeoutMs = Number.isFinite(idleInTxTimeoutEnv)\n ? idleInTxTimeoutEnv\n : process.env.NODE_ENV === 'production'\n ? undefined\n : 120_000\n const connectionOptions =\n idleSessionTimeoutMs && idleSessionTimeoutMs > 0\n ? `-c idle_session_timeout=${idleSessionTimeoutMs}`\n : undefined\n\n const sslConfig = getSslConfig()\n\n ormInstance = await MikroORM.init<PostgreSqlDriver, PostgreSqlEntityManager<PostgreSqlDriver>>({\n driver: PostgreSqlDriver,\n clientUrl,\n entities,\n debug: false,\n // v7 no longer defaults to ReflectMetadataProvider. Entities in this repo use\n // `@mikro-orm/decorators/legacy`, which relies on TypeScript `emitDecoratorMetadata`\n // + reflect-metadata for type inference (nullability, column types). Without this,\n // inferred types are silently wrong at runtime.\n metadataProvider: ReflectMetadataProvider,\n // MikroORM v7 pool shape (min/max/idleTimeoutMillis). Knex-era `acquireTimeoutMillis` /\n // `destroyTimeoutMillis` were removed; acquire wait maps to pg `connectionTimeoutMillis`\n // below under `driverOptions`.\n pool: {\n min: poolMin,\n max: poolMax,\n idleTimeoutMillis: poolIdleTimeout,\n },\n // Driver options are merged into pg.PoolConfig (ClientConfig + pg-pool).\n driverOptions: {\n connectionTimeoutMillis: poolAcquireTimeout,\n idle_in_transaction_session_timeout: idleInTransactionTimeoutMs,\n options: connectionOptions,\n ssl: sslConfig,\n },\n })\n\n return ormInstance\n}\n\n\nasync function closeOrmIfLoaded(): Promise<void> {\n if (ormInstance) {\n await ormInstance.close(true)\n ormInstance = null\n }\n}\n\n// In dev mode, handle reloads cleanly without leaving dangling connections.\nif (process.env.NODE_ENV !== 'production') {\n void closeOrmIfLoaded()\n}\n"],
5
+ "mappings": "AAAA,OAAO;AACP,OAAO;AACP,SAAS,gBAAgB;AACzB,SAAS,+BAA+B;AACxC,SAAS,wBAAuE;AAChF,SAAS,oBAAoB;AAI7B,IAAI,cAAkC;AAGtC,MAAM,sBAAsB;AAE5B,SAAS,wBAAsC;AAC7C,SAAQ,WAAuC,mBAAmB,KAAqB;AACzF;AAEA,SAAS,sBAAsB,UAAuB;AACpD,EAAC,WAAuC,mBAAmB,IAAI;AACjE;AAEO,SAAS,oBAAoB,UAAiB;AACnD,MAAI,sBAAsB,MAAM,QAAQ,QAAQ,IAAI,aAAa,eAAe;AAC9E,YAAQ,MAAM,oEAAoE;AAAA,EACpF;AACA,wBAAsB,QAAQ;AAChC;AAEO,SAAS,iBAAwB;AACtC,QAAM,WAAW,sBAAsB;AACvC,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,MAAM,mFAAmF;AAAA,EACrG;AACA,SAAO;AACT;AAEA,eAAsB,SAAS;AAC7B,MAAI,aAAa;AACf,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,eAAe;AAChC,QAAM,YAAY,QAAQ,IAAI;AAC9B,MAAI,CAAC,WAAW;AACd,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC3C;AAGA,QAAM,UAAU,SAAS,QAAQ,IAAI,eAAe,GAAG;AACvD,QAAM,UAAU,SAAS,QAAQ,IAAI,eAAe,IAAI;AACxD,QAAM,kBAAkB,SAAS,QAAQ,IAAI,wBAAwB,MAAM;AAC3E,QAAM,qBAAqB,SAAS,QAAQ,IAAI,2BAA2B,MAAM;AACjF,QAAM,wBAAwB,SAAS,QAAQ,IAAI,8BAA8B,EAAE;AACnF,QAAM,qBAAqB,SAAS,QAAQ,IAAI,qCAAqC,EAAE;AACvF,QAAM,uBAAuB,OAAO,SAAS,qBAAqB,IAC9D,wBACA,QAAQ,IAAI,aAAa,eACvB,SACA;AACN,QAAM,6BAA6B,OAAO,SAAS,kBAAkB,IACjE,qBACA,QAAQ,IAAI,aAAa,eACvB,SACA;AACN,QAAM,oBACJ,wBAAwB,uBAAuB,IAC3C,2BAA2B,oBAAoB,KAC/C;AAEN,QAAM,YAAY,aAAa;AAE/B,gBAAc,MAAM,SAAS,KAAkE;AAAA,IAC7F,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA,IAKP,kBAAkB;AAAA;AAAA;AAAA;AAAA,IAIlB,MAAM;AAAA,MACJ,KAAK;AAAA,MACL,KAAK;AAAA,MACL,mBAAmB;AAAA,IACrB;AAAA;AAAA,IAEA,eAAe;AAAA,MACb,yBAAyB;AAAA,MACzB,qCAAqC;AAAA,MACrC,SAAS;AAAA,MACT,KAAK;AAAA,IACP;AAAA,EACF,CAAC;AAED,SAAO;AACT;AAGA,eAAe,mBAAkC;AAC/C,MAAI,aAAa;AACf,UAAM,YAAY,MAAM,IAAI;AAC5B,kBAAc;AAAA,EAChB;AACF;AAGA,IAAI,QAAQ,IAAI,aAAa,cAAc;AACzC,OAAK,iBAAiB;AACxB;",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,4 @@
1
+ import { sql } from "kysely";
1
2
  const MAX_MESSAGE_LENGTH = 8192;
2
3
  const MAX_STACK_LENGTH = 32768;
3
4
  function truncate(input, limit) {
@@ -33,14 +34,11 @@ function safeJson(value) {
33
34
  return value;
34
35
  }
35
36
  }
36
- function pickKnex(deps) {
37
- if (deps.knex) return deps.knex;
37
+ function pickDb(deps) {
38
+ if (deps.db) return deps.db;
38
39
  if (deps.em) {
39
40
  try {
40
- const connection = deps.em.getConnection();
41
- if (connection && typeof connection.getKnex === "function") {
42
- return connection.getKnex();
43
- }
41
+ return deps.em.getKysely();
44
42
  } catch {
45
43
  return null;
46
44
  }
@@ -48,9 +46,9 @@ function pickKnex(deps) {
48
46
  return null;
49
47
  }
50
48
  async function recordIndexerError(deps, input) {
51
- const knex = pickKnex(deps);
52
- if (!knex) {
53
- console.error("[indexers] Unable to record indexer error (missing knex connection)", {
49
+ const db = pickDb(deps);
50
+ if (!db) {
51
+ console.error("[indexers] Unable to record indexer error (missing db connection)", {
54
52
  source: input.source,
55
53
  handler: input.handler
56
54
  });
@@ -60,18 +58,18 @@ async function recordIndexerError(deps, input) {
60
58
  const payload = safeJson(input.payload);
61
59
  const now = /* @__PURE__ */ new Date();
62
60
  try {
63
- await knex("indexer_error_logs").insert({
61
+ await db.insertInto("indexer_error_logs").values({
64
62
  source: input.source,
65
63
  handler: input.handler,
66
64
  entity_type: input.entityType ?? null,
67
65
  record_id: input.recordId ?? null,
68
66
  tenant_id: input.tenantId ?? null,
69
67
  organization_id: input.organizationId ?? null,
70
- payload,
68
+ payload: payload === null ? null : sql`${JSON.stringify(payload)}::jsonb`,
71
69
  message: truncate(message, MAX_MESSAGE_LENGTH),
72
70
  stack: truncate(stack, MAX_STACK_LENGTH),
73
71
  occurred_at: now
74
- });
72
+ }).execute();
75
73
  } catch (loggingError) {
76
74
  console.error("[indexers] Failed to persist indexer error", loggingError);
77
75
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/indexers/error-log.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { Knex } from 'knex'\n\nexport type IndexerErrorSource = 'query_index' | 'vector' | 'fulltext'\n\nexport type RecordIndexerErrorInput = {\n source: IndexerErrorSource\n handler: string\n error: unknown\n entityType?: string | null\n recordId?: string | null\n tenantId?: string | null\n organizationId?: string | null\n payload?: unknown\n}\n\ntype RecordIndexerErrorDeps = {\n em?: EntityManager\n knex?: Knex\n}\n\nconst MAX_MESSAGE_LENGTH = 8_192\nconst MAX_STACK_LENGTH = 32_768\n\nfunction truncate(input: string | null | undefined, limit: number): string | null {\n if (!input) return null\n return input.length > limit ? `${input.slice(0, limit - 3)}...` : input\n}\n\nfunction normalizeError(error: unknown): { message: string; stack: string | null } {\n if (error instanceof Error) {\n return {\n message: error.message || error.name || 'Unknown error',\n stack: typeof error.stack === 'string' ? error.stack : null,\n }\n }\n if (typeof error === 'string') {\n return { message: error, stack: null }\n }\n try {\n const json = JSON.stringify(error)\n return { message: json, stack: null }\n } catch {\n return { message: String(error ?? 'Unknown error'), stack: null }\n }\n}\n\nfunction safeJson(value: unknown): unknown {\n if (value === undefined) return null\n try {\n return JSON.parse(JSON.stringify(value))\n } catch {\n if (value == null) return null\n if (typeof value === 'object') {\n return { note: 'unserializable', asString: String(value) }\n }\n return value\n }\n}\n\nfunction pickKnex(deps: RecordIndexerErrorDeps): Knex | null {\n if (deps.knex) return deps.knex\n if (deps.em) {\n try {\n const connection = deps.em.getConnection()\n if (connection && typeof connection.getKnex === 'function') {\n return connection.getKnex()\n }\n } catch {\n return null\n }\n }\n return null\n}\n\nexport async function recordIndexerError(deps: RecordIndexerErrorDeps, input: RecordIndexerErrorInput): Promise<void> {\n const knex = pickKnex(deps)\n if (!knex) {\n console.error('[indexers] Unable to record indexer error (missing knex connection)', {\n source: input.source,\n handler: input.handler,\n })\n return\n }\n\n const { message, stack } = normalizeError(input.error)\n const payload = safeJson(input.payload)\n const now = new Date()\n\n try {\n await knex('indexer_error_logs').insert({\n source: input.source,\n handler: input.handler,\n entity_type: input.entityType ?? null,\n record_id: input.recordId ?? null,\n tenant_id: input.tenantId ?? null,\n organization_id: input.organizationId ?? null,\n payload,\n message: truncate(message, MAX_MESSAGE_LENGTH),\n stack: truncate(stack, MAX_STACK_LENGTH),\n occurred_at: now,\n })\n } catch (loggingError) {\n console.error('[indexers] Failed to persist indexer error', loggingError)\n }\n}\n"],
5
- "mappings": "AAqBA,MAAM,qBAAqB;AAC3B,MAAM,mBAAmB;AAEzB,SAAS,SAAS,OAAkC,OAA8B;AAChF,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,MAAM,SAAS,QAAQ,GAAG,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,QAAQ;AACpE;AAEA,SAAS,eAAe,OAA2D;AACjF,MAAI,iBAAiB,OAAO;AAC1B,WAAO;AAAA,MACL,SAAS,MAAM,WAAW,MAAM,QAAQ;AAAA,MACxC,OAAO,OAAO,MAAM,UAAU,WAAW,MAAM,QAAQ;AAAA,IACzD;AAAA,EACF;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,EAAE,SAAS,OAAO,OAAO,KAAK;AAAA,EACvC;AACA,MAAI;AACF,UAAM,OAAO,KAAK,UAAU,KAAK;AACjC,WAAO,EAAE,SAAS,MAAM,OAAO,KAAK;AAAA,EACtC,QAAQ;AACN,WAAO,EAAE,SAAS,OAAO,SAAS,eAAe,GAAG,OAAO,KAAK;AAAA,EAClE;AACF;AAEA,SAAS,SAAS,OAAyB;AACzC,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI;AACF,WAAO,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;AAAA,EACzC,QAAQ;AACN,QAAI,SAAS,KAAM,QAAO;AAC1B,QAAI,OAAO,UAAU,UAAU;AAC7B,aAAO,EAAE,MAAM,kBAAkB,UAAU,OAAO,KAAK,EAAE;AAAA,IAC3D;AACA,WAAO;AAAA,EACT;AACF;AAEA,SAAS,SAAS,MAA2C;AAC3D,MAAI,KAAK,KAAM,QAAO,KAAK;AAC3B,MAAI,KAAK,IAAI;AACX,QAAI;AACF,YAAM,aAAa,KAAK,GAAG,cAAc;AACzC,UAAI,cAAc,OAAO,WAAW,YAAY,YAAY;AAC1D,eAAO,WAAW,QAAQ;AAAA,MAC5B;AAAA,IACF,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,mBAAmB,MAA8B,OAA+C;AACpH,QAAM,OAAO,SAAS,IAAI;AAC1B,MAAI,CAAC,MAAM;AACT,YAAQ,MAAM,uEAAuE;AAAA,MACnF,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM;AAAA,IACjB,CAAC;AACD;AAAA,EACF;AAEA,QAAM,EAAE,SAAS,MAAM,IAAI,eAAe,MAAM,KAAK;AACrD,QAAM,UAAU,SAAS,MAAM,OAAO;AACtC,QAAM,MAAM,oBAAI,KAAK;AAErB,MAAI;AACF,UAAM,KAAK,oBAAoB,EAAE,OAAO;AAAA,MACtC,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM;AAAA,MACf,aAAa,MAAM,cAAc;AAAA,MACjC,WAAW,MAAM,YAAY;AAAA,MAC7B,WAAW,MAAM,YAAY;AAAA,MAC7B,iBAAiB,MAAM,kBAAkB;AAAA,MACzC;AAAA,MACA,SAAS,SAAS,SAAS,kBAAkB;AAAA,MAC7C,OAAO,SAAS,OAAO,gBAAgB;AAAA,MACvC,aAAa;AAAA,IACf,CAAC;AAAA,EACH,SAAS,cAAc;AACrB,YAAQ,MAAM,8CAA8C,YAAY;AAAA,EAC1E;AACF;",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { type Kysely, sql } from 'kysely'\n\nexport type IndexerErrorSource = 'query_index' | 'vector' | 'fulltext'\n\nexport type RecordIndexerErrorInput = {\n source: IndexerErrorSource\n handler: string\n error: unknown\n entityType?: string | null\n recordId?: string | null\n tenantId?: string | null\n organizationId?: string | null\n payload?: unknown\n}\n\ntype RecordIndexerErrorDeps = {\n em?: EntityManager\n db?: Kysely<any>\n}\n\nconst MAX_MESSAGE_LENGTH = 8_192\nconst MAX_STACK_LENGTH = 32_768\n\nfunction truncate(input: string | null | undefined, limit: number): string | null {\n if (!input) return null\n return input.length > limit ? `${input.slice(0, limit - 3)}...` : input\n}\n\nfunction normalizeError(error: unknown): { message: string; stack: string | null } {\n if (error instanceof Error) {\n return {\n message: error.message || error.name || 'Unknown error',\n stack: typeof error.stack === 'string' ? error.stack : null,\n }\n }\n if (typeof error === 'string') {\n return { message: error, stack: null }\n }\n try {\n const json = JSON.stringify(error)\n return { message: json, stack: null }\n } catch {\n return { message: String(error ?? 'Unknown error'), stack: null }\n }\n}\n\nfunction safeJson(value: unknown): unknown {\n if (value === undefined) return null\n try {\n return JSON.parse(JSON.stringify(value))\n } catch {\n if (value == null) return null\n if (typeof value === 'object') {\n return { note: 'unserializable', asString: String(value) }\n }\n return value\n }\n}\n\nfunction pickDb(deps: RecordIndexerErrorDeps): Kysely<any> | null {\n if (deps.db) return deps.db\n if (deps.em) {\n try {\n return deps.em.getKysely<any>()\n } catch {\n return null\n }\n }\n return null\n}\n\nexport async function recordIndexerError(deps: RecordIndexerErrorDeps, input: RecordIndexerErrorInput): Promise<void> {\n const db = pickDb(deps)\n if (!db) {\n console.error('[indexers] Unable to record indexer error (missing db connection)', {\n source: input.source,\n handler: input.handler,\n })\n return\n }\n\n const { message, stack } = normalizeError(input.error)\n const payload = safeJson(input.payload)\n const now = new Date()\n\n try {\n await db\n .insertInto('indexer_error_logs' as any)\n .values({\n source: input.source,\n handler: input.handler,\n entity_type: input.entityType ?? null,\n record_id: input.recordId ?? null,\n tenant_id: input.tenantId ?? null,\n organization_id: input.organizationId ?? null,\n payload: payload === null ? null : sql`${JSON.stringify(payload)}::jsonb`,\n message: truncate(message, MAX_MESSAGE_LENGTH),\n stack: truncate(stack, MAX_STACK_LENGTH),\n occurred_at: now,\n } as any)\n .execute()\n } catch (loggingError) {\n console.error('[indexers] Failed to persist indexer error', loggingError)\n }\n}\n"],
5
+ "mappings": "AACA,SAAsB,WAAW;AAoBjC,MAAM,qBAAqB;AAC3B,MAAM,mBAAmB;AAEzB,SAAS,SAAS,OAAkC,OAA8B;AAChF,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,MAAM,SAAS,QAAQ,GAAG,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,QAAQ;AACpE;AAEA,SAAS,eAAe,OAA2D;AACjF,MAAI,iBAAiB,OAAO;AAC1B,WAAO;AAAA,MACL,SAAS,MAAM,WAAW,MAAM,QAAQ;AAAA,MACxC,OAAO,OAAO,MAAM,UAAU,WAAW,MAAM,QAAQ;AAAA,IACzD;AAAA,EACF;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,EAAE,SAAS,OAAO,OAAO,KAAK;AAAA,EACvC;AACA,MAAI;AACF,UAAM,OAAO,KAAK,UAAU,KAAK;AACjC,WAAO,EAAE,SAAS,MAAM,OAAO,KAAK;AAAA,EACtC,QAAQ;AACN,WAAO,EAAE,SAAS,OAAO,SAAS,eAAe,GAAG,OAAO,KAAK;AAAA,EAClE;AACF;AAEA,SAAS,SAAS,OAAyB;AACzC,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI;AACF,WAAO,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;AAAA,EACzC,QAAQ;AACN,QAAI,SAAS,KAAM,QAAO;AAC1B,QAAI,OAAO,UAAU,UAAU;AAC7B,aAAO,EAAE,MAAM,kBAAkB,UAAU,OAAO,KAAK,EAAE;AAAA,IAC3D;AACA,WAAO;AAAA,EACT;AACF;AAEA,SAAS,OAAO,MAAkD;AAChE,MAAI,KAAK,GAAI,QAAO,KAAK;AACzB,MAAI,KAAK,IAAI;AACX,QAAI;AACF,aAAO,KAAK,GAAG,UAAe;AAAA,IAChC,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,mBAAmB,MAA8B,OAA+C;AACpH,QAAM,KAAK,OAAO,IAAI;AACtB,MAAI,CAAC,IAAI;AACP,YAAQ,MAAM,qEAAqE;AAAA,MACjF,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM;AAAA,IACjB,CAAC;AACD;AAAA,EACF;AAEA,QAAM,EAAE,SAAS,MAAM,IAAI,eAAe,MAAM,KAAK;AACrD,QAAM,UAAU,SAAS,MAAM,OAAO;AACtC,QAAM,MAAM,oBAAI,KAAK;AAErB,MAAI;AACF,UAAM,GACH,WAAW,oBAA2B,EACtC,OAAO;AAAA,MACN,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM;AAAA,MACf,aAAa,MAAM,cAAc;AAAA,MACjC,WAAW,MAAM,YAAY;AAAA,MAC7B,WAAW,MAAM,YAAY;AAAA,MAC7B,iBAAiB,MAAM,kBAAkB;AAAA,MACzC,SAAS,YAAY,OAAO,OAAO,MAAM,KAAK,UAAU,OAAO,CAAC;AAAA,MAChE,SAAS,SAAS,SAAS,kBAAkB;AAAA,MAC7C,OAAO,SAAS,OAAO,gBAAgB;AAAA,MACvC,aAAa;AAAA,IACf,CAAQ,EACP,QAAQ;AAAA,EACb,SAAS,cAAc;AACrB,YAAQ,MAAM,8CAA8C,YAAY;AAAA,EAC1E;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,4 @@
1
+ import { sql } from "kysely";
1
2
  const MAX_MESSAGE_LENGTH = 4096;
2
3
  const MAX_DELETE_BATCH = 5e3;
3
4
  const MAX_LOGS_PER_SOURCE = 1e4;
@@ -17,31 +18,28 @@ function safeJson(value) {
17
18
  return value;
18
19
  }
19
20
  }
20
- function pickKnex(deps) {
21
- if (deps.knex) return deps.knex;
21
+ function pickDb(deps) {
22
+ if (deps.db) return deps.db;
22
23
  if (deps.em) {
23
24
  try {
24
- const connection = deps.em.getConnection();
25
- if (connection && typeof connection.getKnex === "function") {
26
- return connection.getKnex();
27
- }
25
+ return deps.em.getKysely();
28
26
  } catch {
29
27
  return null;
30
28
  }
31
29
  }
32
30
  return null;
33
31
  }
34
- async function pruneExcessLogs(knex, source) {
35
- const rows = await knex("indexer_status_logs").select("id").where("source", source).orderBy("occurred_at", "desc").orderBy("id", "desc").offset(MAX_LOGS_PER_SOURCE).limit(MAX_DELETE_BATCH);
32
+ async function pruneExcessLogs(db, source) {
33
+ const rows = await db.selectFrom("indexer_status_logs").select("id").where("source", "=", source).orderBy("occurred_at", "desc").orderBy("id", "desc").offset(MAX_LOGS_PER_SOURCE).limit(MAX_DELETE_BATCH).execute();
36
34
  if (!rows.length) return;
37
35
  const ids = rows.map((row) => row.id).filter(Boolean);
38
36
  if (!ids.length) return;
39
- await knex("indexer_status_logs").whereIn("id", ids).del();
37
+ await db.deleteFrom("indexer_status_logs").where("id", "in", ids).execute();
40
38
  }
41
39
  async function recordIndexerLog(deps, input) {
42
- const knex = pickKnex(deps);
43
- if (!knex) {
44
- console.warn("[indexers] Unable to record indexer log (missing knex connection)", {
40
+ const db = pickDb(deps);
41
+ if (!db) {
42
+ console.warn("[indexers] Unable to record indexer log (missing db connection)", {
45
43
  source: input.source,
46
44
  handler: input.handler
47
45
  });
@@ -52,7 +50,7 @@ async function recordIndexerLog(deps, input) {
52
50
  const details = safeJson(input.details);
53
51
  const occurredAt = /* @__PURE__ */ new Date();
54
52
  try {
55
- await knex("indexer_status_logs").insert({
53
+ await db.insertInto("indexer_status_logs").values({
56
54
  source: input.source,
57
55
  handler: input.handler,
58
56
  level,
@@ -61,15 +59,15 @@ async function recordIndexerLog(deps, input) {
61
59
  tenant_id: input.tenantId ?? null,
62
60
  organization_id: input.organizationId ?? null,
63
61
  message,
64
- details,
62
+ details: details === null ? null : sql`${JSON.stringify(details)}::jsonb`,
65
63
  occurred_at: occurredAt
66
- });
64
+ }).execute();
67
65
  } catch (error) {
68
66
  console.error("[indexers] Failed to persist indexer log", error);
69
67
  return;
70
68
  }
71
69
  try {
72
- await pruneExcessLogs(knex, input.source);
70
+ await pruneExcessLogs(db, input.source);
73
71
  } catch (pruneError) {
74
72
  console.warn("[indexers] Failed to prune indexer logs", pruneError);
75
73
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/indexers/status-log.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { Knex } from 'knex'\nimport type { IndexerErrorSource } from './error-log'\n\nexport type IndexerLogLevel = 'info' | 'warn'\n\nexport type RecordIndexerLogInput = {\n source: IndexerErrorSource\n handler: string\n message: string\n level?: IndexerLogLevel\n entityType?: string | null\n recordId?: string | null\n tenantId?: string | null\n organizationId?: string | null\n details?: unknown\n}\n\ntype RecordIndexerLogDeps = {\n em?: EntityManager\n knex?: Knex\n}\n\nconst MAX_MESSAGE_LENGTH = 4_096\nconst MAX_DELETE_BATCH = 5_000\nconst MAX_LOGS_PER_SOURCE = 10_000\n\nfunction truncate(input: string | null | undefined, limit: number): string | null {\n if (!input) return null\n return input.length > limit ? `${input.slice(0, limit - 3)}...` : input\n}\n\nfunction safeJson(value: unknown): unknown {\n if (value === undefined) return null\n try {\n return JSON.parse(JSON.stringify(value))\n } catch {\n if (value == null) return null\n if (typeof value === 'object') {\n return { note: 'unserializable', asString: String(value) }\n }\n return value\n }\n}\n\nfunction pickKnex(deps: RecordIndexerLogDeps): Knex | null {\n if (deps.knex) return deps.knex\n if (deps.em) {\n try {\n const connection = deps.em.getConnection()\n if (connection && typeof connection.getKnex === 'function') {\n return connection.getKnex()\n }\n } catch {\n return null\n }\n }\n return null\n}\n\nasync function pruneExcessLogs(knex: Knex, source: IndexerErrorSource): Promise<void> {\n const rows = await knex('indexer_status_logs')\n .select('id')\n .where('source', source)\n .orderBy('occurred_at', 'desc')\n .orderBy('id', 'desc')\n .offset(MAX_LOGS_PER_SOURCE)\n .limit(MAX_DELETE_BATCH)\n\n if (!rows.length) return\n const ids = rows.map((row: any) => row.id).filter(Boolean)\n if (!ids.length) return\n await knex('indexer_status_logs')\n .whereIn('id', ids)\n .del()\n}\n\nexport async function recordIndexerLog(\n deps: RecordIndexerLogDeps,\n input: RecordIndexerLogInput,\n): Promise<void> {\n const knex = pickKnex(deps)\n if (!knex) {\n console.warn('[indexers] Unable to record indexer log (missing knex connection)', {\n source: input.source,\n handler: input.handler,\n })\n return\n }\n\n const level: IndexerLogLevel = input.level === 'warn' ? 'warn' : 'info'\n const message = truncate(input.message, MAX_MESSAGE_LENGTH) ?? '\u2014'\n const details = safeJson(input.details)\n const occurredAt = new Date()\n\n try {\n await knex('indexer_status_logs').insert({\n source: input.source,\n handler: input.handler,\n level,\n entity_type: input.entityType ?? null,\n record_id: input.recordId ?? null,\n tenant_id: input.tenantId ?? null,\n organization_id: input.organizationId ?? null,\n message,\n details,\n occurred_at: occurredAt,\n })\n } catch (error) {\n console.error('[indexers] Failed to persist indexer log', error)\n return\n }\n\n try {\n await pruneExcessLogs(knex, input.source)\n } catch (pruneError) {\n console.warn('[indexers] Failed to prune indexer logs', pruneError)\n }\n}\n"],
5
- "mappings": "AAuBA,MAAM,qBAAqB;AAC3B,MAAM,mBAAmB;AACzB,MAAM,sBAAsB;AAE5B,SAAS,SAAS,OAAkC,OAA8B;AAChF,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,MAAM,SAAS,QAAQ,GAAG,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,QAAQ;AACpE;AAEA,SAAS,SAAS,OAAyB;AACzC,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI;AACF,WAAO,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;AAAA,EACzC,QAAQ;AACN,QAAI,SAAS,KAAM,QAAO;AAC1B,QAAI,OAAO,UAAU,UAAU;AAC7B,aAAO,EAAE,MAAM,kBAAkB,UAAU,OAAO,KAAK,EAAE;AAAA,IAC3D;AACA,WAAO;AAAA,EACT;AACF;AAEA,SAAS,SAAS,MAAyC;AACzD,MAAI,KAAK,KAAM,QAAO,KAAK;AAC3B,MAAI,KAAK,IAAI;AACX,QAAI;AACF,YAAM,aAAa,KAAK,GAAG,cAAc;AACzC,UAAI,cAAc,OAAO,WAAW,YAAY,YAAY;AAC1D,eAAO,WAAW,QAAQ;AAAA,MAC5B;AAAA,IACF,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAe,gBAAgB,MAAY,QAA2C;AACpF,QAAM,OAAO,MAAM,KAAK,qBAAqB,EAC1C,OAAO,IAAI,EACX,MAAM,UAAU,MAAM,EACtB,QAAQ,eAAe,MAAM,EAC7B,QAAQ,MAAM,MAAM,EACpB,OAAO,mBAAmB,EAC1B,MAAM,gBAAgB;AAEzB,MAAI,CAAC,KAAK,OAAQ;AAClB,QAAM,MAAM,KAAK,IAAI,CAAC,QAAa,IAAI,EAAE,EAAE,OAAO,OAAO;AACzD,MAAI,CAAC,IAAI,OAAQ;AACjB,QAAM,KAAK,qBAAqB,EAC7B,QAAQ,MAAM,GAAG,EACjB,IAAI;AACT;AAEA,eAAsB,iBACpB,MACA,OACe;AACf,QAAM,OAAO,SAAS,IAAI;AAC1B,MAAI,CAAC,MAAM;AACT,YAAQ,KAAK,qEAAqE;AAAA,MAChF,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM;AAAA,IACjB,CAAC;AACD;AAAA,EACF;AAEA,QAAM,QAAyB,MAAM,UAAU,SAAS,SAAS;AACjE,QAAM,UAAU,SAAS,MAAM,SAAS,kBAAkB,KAAK;AAC/D,QAAM,UAAU,SAAS,MAAM,OAAO;AACtC,QAAM,aAAa,oBAAI,KAAK;AAE5B,MAAI;AACF,UAAM,KAAK,qBAAqB,EAAE,OAAO;AAAA,MACvC,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM;AAAA,MACf;AAAA,MACA,aAAa,MAAM,cAAc;AAAA,MACjC,WAAW,MAAM,YAAY;AAAA,MAC7B,WAAW,MAAM,YAAY;AAAA,MAC7B,iBAAiB,MAAM,kBAAkB;AAAA,MACzC;AAAA,MACA;AAAA,MACA,aAAa;AAAA,IACf,CAAC;AAAA,EACH,SAAS,OAAO;AACd,YAAQ,MAAM,4CAA4C,KAAK;AAC/D;AAAA,EACF;AAEA,MAAI;AACF,UAAM,gBAAgB,MAAM,MAAM,MAAM;AAAA,EAC1C,SAAS,YAAY;AACnB,YAAQ,KAAK,2CAA2C,UAAU;AAAA,EACpE;AACF;",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { type Kysely, sql } from 'kysely'\nimport type { IndexerErrorSource } from './error-log'\n\nexport type IndexerLogLevel = 'info' | 'warn'\n\nexport type RecordIndexerLogInput = {\n source: IndexerErrorSource\n handler: string\n message: string\n level?: IndexerLogLevel\n entityType?: string | null\n recordId?: string | null\n tenantId?: string | null\n organizationId?: string | null\n details?: unknown\n}\n\ntype RecordIndexerLogDeps = {\n em?: EntityManager\n db?: Kysely<any>\n}\n\nconst MAX_MESSAGE_LENGTH = 4_096\nconst MAX_DELETE_BATCH = 5_000\nconst MAX_LOGS_PER_SOURCE = 10_000\n\nfunction truncate(input: string | null | undefined, limit: number): string | null {\n if (!input) return null\n return input.length > limit ? `${input.slice(0, limit - 3)}...` : input\n}\n\nfunction safeJson(value: unknown): unknown {\n if (value === undefined) return null\n try {\n return JSON.parse(JSON.stringify(value))\n } catch {\n if (value == null) return null\n if (typeof value === 'object') {\n return { note: 'unserializable', asString: String(value) }\n }\n return value\n }\n}\n\nfunction pickDb(deps: RecordIndexerLogDeps): Kysely<any> | null {\n if (deps.db) return deps.db\n if (deps.em) {\n try {\n return deps.em.getKysely<any>()\n } catch {\n return null\n }\n }\n return null\n}\n\nasync function pruneExcessLogs(db: Kysely<any>, source: IndexerErrorSource): Promise<void> {\n const rows = await db\n .selectFrom('indexer_status_logs' as any)\n .select('id' as any)\n .where('source' as any, '=', source)\n .orderBy('occurred_at' as any, 'desc')\n .orderBy('id' as any, 'desc')\n .offset(MAX_LOGS_PER_SOURCE)\n .limit(MAX_DELETE_BATCH)\n .execute()\n\n if (!rows.length) return\n const ids = rows.map((row: any) => row.id).filter(Boolean)\n if (!ids.length) return\n await db\n .deleteFrom('indexer_status_logs' as any)\n .where('id' as any, 'in', ids)\n .execute()\n}\n\nexport async function recordIndexerLog(\n deps: RecordIndexerLogDeps,\n input: RecordIndexerLogInput,\n): Promise<void> {\n const db = pickDb(deps)\n if (!db) {\n console.warn('[indexers] Unable to record indexer log (missing db connection)', {\n source: input.source,\n handler: input.handler,\n })\n return\n }\n\n const level: IndexerLogLevel = input.level === 'warn' ? 'warn' : 'info'\n const message = truncate(input.message, MAX_MESSAGE_LENGTH) ?? '\u2014'\n const details = safeJson(input.details)\n const occurredAt = new Date()\n\n try {\n await db\n .insertInto('indexer_status_logs' as any)\n .values({\n source: input.source,\n handler: input.handler,\n level,\n entity_type: input.entityType ?? null,\n record_id: input.recordId ?? null,\n tenant_id: input.tenantId ?? null,\n organization_id: input.organizationId ?? null,\n message,\n details: details === null ? null : sql`${JSON.stringify(details)}::jsonb`,\n occurred_at: occurredAt,\n } as any)\n .execute()\n } catch (error) {\n console.error('[indexers] Failed to persist indexer log', error)\n return\n }\n\n try {\n await pruneExcessLogs(db, input.source)\n } catch (pruneError) {\n console.warn('[indexers] Failed to prune indexer logs', pruneError)\n }\n}\n"],
5
+ "mappings": "AACA,SAAsB,WAAW;AAsBjC,MAAM,qBAAqB;AAC3B,MAAM,mBAAmB;AACzB,MAAM,sBAAsB;AAE5B,SAAS,SAAS,OAAkC,OAA8B;AAChF,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,MAAM,SAAS,QAAQ,GAAG,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,QAAQ;AACpE;AAEA,SAAS,SAAS,OAAyB;AACzC,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI;AACF,WAAO,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;AAAA,EACzC,QAAQ;AACN,QAAI,SAAS,KAAM,QAAO;AAC1B,QAAI,OAAO,UAAU,UAAU;AAC7B,aAAO,EAAE,MAAM,kBAAkB,UAAU,OAAO,KAAK,EAAE;AAAA,IAC3D;AACA,WAAO;AAAA,EACT;AACF;AAEA,SAAS,OAAO,MAAgD;AAC9D,MAAI,KAAK,GAAI,QAAO,KAAK;AACzB,MAAI,KAAK,IAAI;AACX,QAAI;AACF,aAAO,KAAK,GAAG,UAAe;AAAA,IAChC,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAe,gBAAgB,IAAiB,QAA2C;AACzF,QAAM,OAAO,MAAM,GAChB,WAAW,qBAA4B,EACvC,OAAO,IAAW,EAClB,MAAM,UAAiB,KAAK,MAAM,EAClC,QAAQ,eAAsB,MAAM,EACpC,QAAQ,MAAa,MAAM,EAC3B,OAAO,mBAAmB,EAC1B,MAAM,gBAAgB,EACtB,QAAQ;AAEX,MAAI,CAAC,KAAK,OAAQ;AAClB,QAAM,MAAM,KAAK,IAAI,CAAC,QAAa,IAAI,EAAE,EAAE,OAAO,OAAO;AACzD,MAAI,CAAC,IAAI,OAAQ;AACjB,QAAM,GACH,WAAW,qBAA4B,EACvC,MAAM,MAAa,MAAM,GAAG,EAC5B,QAAQ;AACb;AAEA,eAAsB,iBACpB,MACA,OACe;AACf,QAAM,KAAK,OAAO,IAAI;AACtB,MAAI,CAAC,IAAI;AACP,YAAQ,KAAK,mEAAmE;AAAA,MAC9E,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM;AAAA,IACjB,CAAC;AACD;AAAA,EACF;AAEA,QAAM,QAAyB,MAAM,UAAU,SAAS,SAAS;AACjE,QAAM,UAAU,SAAS,MAAM,SAAS,kBAAkB,KAAK;AAC/D,QAAM,UAAU,SAAS,MAAM,OAAO;AACtC,QAAM,aAAa,oBAAI,KAAK;AAE5B,MAAI;AACF,UAAM,GACH,WAAW,qBAA4B,EACvC,OAAO;AAAA,MACN,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM;AAAA,MACf;AAAA,MACA,aAAa,MAAM,cAAc;AAAA,MACjC,WAAW,MAAM,YAAY;AAAA,MAC7B,WAAW,MAAM,YAAY;AAAA,MAC7B,iBAAiB,MAAM,kBAAkB;AAAA,MACzC;AAAA,MACA,SAAS,YAAY,OAAO,OAAO,MAAM,KAAK,UAAU,OAAO,CAAC;AAAA,MAChE,aAAa;AAAA,IACf,CAAQ,EACP,QAAQ;AAAA,EACb,SAAS,OAAO;AACd,YAAQ,MAAM,4CAA4C,KAAK;AAC/D;AAAA,EACF;AAEA,MAAI;AACF,UAAM,gBAAgB,IAAI,MAAM,MAAM;AAAA,EACxC,SAAS,YAAY;AACnB,YAAQ,KAAK,2CAA2C,UAAU;AAAA,EACpE;AACF;",
6
6
  "names": []
7
7
  }