@open-mercato/core 0.6.6-develop.6493.1.333455a884 → 0.6.6-develop.6494.1.8479db6c38

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.
@@ -35,6 +35,12 @@ async function drainQueueFromAppRoot(queueName, options) {
35
35
  );
36
36
  const handled = result.processed + result.failed;
37
37
  processedJobs += handled;
38
+ if (result.failed > 0) {
39
+ process.stderr.write(
40
+ `[queue-drain] ${result.failed} job(s) FAILED in "${queueName}" (processed ${result.processed}); failed jobs retry with backoff and dead-letter after max attempts
41
+ `
42
+ );
43
+ }
38
44
  if (handled === 0) return processedJobs;
39
45
  }
40
46
  } finally {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/helpers/integration/queue-runner.ts"],
4
- "sourcesContent": ["import path from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { bootstrapFromAppRoot } from '@open-mercato/shared/lib/bootstrap/dynamicLoader'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { createQueue } from '@open-mercato/queue'\n\nexport type QueueDrainRunnerOptions = {\n appRoot: string\n jobLimit: number\n queueBaseDir?: string\n}\n\ntype BootstrapData = Awaited<ReturnType<typeof bootstrapFromAppRoot>>\n\nconst bootstrapCache = new Map<string, Promise<BootstrapData>>()\n\nfunction getBootstrapData(appRoot: string): Promise<BootstrapData> {\n const resolvedAppRoot = path.resolve(appRoot)\n const cached = bootstrapCache.get(resolvedAppRoot)\n if (cached) return cached\n const promise = bootstrapFromAppRoot(resolvedAppRoot)\n bootstrapCache.set(resolvedAppRoot, promise)\n return promise\n}\n\nexport async function drainQueueFromAppRoot(\n queueName: string,\n options: QueueDrainRunnerOptions,\n): Promise<number> {\n const data = await getBootstrapData(options.appRoot)\n const worker = data.modules\n .flatMap((module) => module.workers ?? [])\n .find((entry) => entry.queue === queueName)\n if (!worker) return 0\n\n const container = await createRequestContainer()\n const queue = createQueue(queueName, 'local', {\n baseDir: path.resolve(\n options.queueBaseDir?.trim()\n || process.env.QUEUE_BASE_DIR?.trim()\n || path.resolve(options.appRoot, '.mercato/queue'),\n ),\n concurrency: 1,\n })\n const resolve = <T = unknown>(name: string): T => container.resolve(name) as T\n\n try {\n let processedJobs = 0\n while (true) {\n const result = await queue.process(\n async (job, ctx) => {\n await Promise.resolve(worker.handler(job, { ...ctx, resolve }))\n },\n { limit: options.jobLimit },\n )\n const handled = result.processed + result.failed\n processedJobs += handled\n if (handled === 0) return processedJobs\n }\n } finally {\n await queue.close()\n }\n}\n\nexport async function runQueueDrainFromEnv(): Promise<void> {\n const queueName = process.env.OM_INTEGRATION_QUEUE_NAME?.trim()\n const appRoot = process.env.OM_INTEGRATION_APP_ROOT?.trim()\n const jobLimit = Number.parseInt(process.env.OM_INTEGRATION_QUEUE_JOB_LIMIT || '100', 10) || 100\n if (!queueName || !appRoot) throw new Error('Missing queue drain environment')\n\n const processed = await drainQueueFromAppRoot(queueName, {\n appRoot: path.resolve(appRoot),\n jobLimit,\n queueBaseDir: process.env.QUEUE_BASE_DIR,\n })\n process.stdout.write(`${JSON.stringify({ processed })}\\n`)\n}\n\nconst currentFile = fileURLToPath(import.meta.url)\nconst invokedFile = process.argv[1] ? path.resolve(process.argv[1]) : null\n\nif (invokedFile === currentFile) {\n runQueueDrainFromEnv()\n .then(() => {\n process.exit(0)\n })\n .catch((error) => {\n process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\\n`)\n process.exit(1)\n })\n}\n"],
5
- "mappings": "AAAA,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAC9B,SAAS,4BAA4B;AACrC,SAAS,8BAA8B;AACvC,SAAS,mBAAmB;AAU5B,MAAM,iBAAiB,oBAAI,IAAoC;AAE/D,SAAS,iBAAiB,SAAyC;AACjE,QAAM,kBAAkB,KAAK,QAAQ,OAAO;AAC5C,QAAM,SAAS,eAAe,IAAI,eAAe;AACjD,MAAI,OAAQ,QAAO;AACnB,QAAM,UAAU,qBAAqB,eAAe;AACpD,iBAAe,IAAI,iBAAiB,OAAO;AAC3C,SAAO;AACT;AAEA,eAAsB,sBACpB,WACA,SACiB;AACjB,QAAM,OAAO,MAAM,iBAAiB,QAAQ,OAAO;AACnD,QAAM,SAAS,KAAK,QACjB,QAAQ,CAAC,WAAW,OAAO,WAAW,CAAC,CAAC,EACxC,KAAK,CAAC,UAAU,MAAM,UAAU,SAAS;AAC5C,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,QAAQ,YAAY,WAAW,SAAS;AAAA,IAC5C,SAAS,KAAK;AAAA,MACZ,QAAQ,cAAc,KAAK,KACxB,QAAQ,IAAI,gBAAgB,KAAK,KACjC,KAAK,QAAQ,QAAQ,SAAS,gBAAgB;AAAA,IACnD;AAAA,IACA,aAAa;AAAA,EACf,CAAC;AACD,QAAM,UAAU,CAAc,SAAoB,UAAU,QAAQ,IAAI;AAExE,MAAI;AACF,QAAI,gBAAgB;AACpB,WAAO,MAAM;AACX,YAAM,SAAS,MAAM,MAAM;AAAA,QACzB,OAAO,KAAK,QAAQ;AAClB,gBAAM,QAAQ,QAAQ,OAAO,QAAQ,KAAK,EAAE,GAAG,KAAK,QAAQ,CAAC,CAAC;AAAA,QAChE;AAAA,QACA,EAAE,OAAO,QAAQ,SAAS;AAAA,MAC5B;AACA,YAAM,UAAU,OAAO,YAAY,OAAO;AAC1C,uBAAiB;AACjB,UAAI,YAAY,EAAG,QAAO;AAAA,IAC5B;AAAA,EACF,UAAE;AACA,UAAM,MAAM,MAAM;AAAA,EACpB;AACF;AAEA,eAAsB,uBAAsC;AAC1D,QAAM,YAAY,QAAQ,IAAI,2BAA2B,KAAK;AAC9D,QAAM,UAAU,QAAQ,IAAI,yBAAyB,KAAK;AAC1D,QAAM,WAAW,OAAO,SAAS,QAAQ,IAAI,kCAAkC,OAAO,EAAE,KAAK;AAC7F,MAAI,CAAC,aAAa,CAAC,QAAS,OAAM,IAAI,MAAM,iCAAiC;AAE7E,QAAM,YAAY,MAAM,sBAAsB,WAAW;AAAA,IACvD,SAAS,KAAK,QAAQ,OAAO;AAAA,IAC7B;AAAA,IACA,cAAc,QAAQ,IAAI;AAAA,EAC5B,CAAC;AACD,UAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,EAAE,UAAU,CAAC,CAAC;AAAA,CAAI;AAC3D;AAEA,MAAM,cAAc,cAAc,YAAY,GAAG;AACjD,MAAM,cAAc,QAAQ,KAAK,CAAC,IAAI,KAAK,QAAQ,QAAQ,KAAK,CAAC,CAAC,IAAI;AAEtE,IAAI,gBAAgB,aAAa;AAC/B,uBAAqB,EAClB,KAAK,MAAM;AACV,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC,EACA,MAAM,CAAC,UAAU;AAChB,YAAQ,OAAO,MAAM,GAAG,iBAAiB,QAAQ,MAAM,SAAS,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,CAAI;AACjG,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACL;",
4
+ "sourcesContent": ["import path from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { bootstrapFromAppRoot } from '@open-mercato/shared/lib/bootstrap/dynamicLoader'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { createQueue } from '@open-mercato/queue'\n\nexport type QueueDrainRunnerOptions = {\n appRoot: string\n jobLimit: number\n queueBaseDir?: string\n}\n\ntype BootstrapData = Awaited<ReturnType<typeof bootstrapFromAppRoot>>\n\nconst bootstrapCache = new Map<string, Promise<BootstrapData>>()\n\nfunction getBootstrapData(appRoot: string): Promise<BootstrapData> {\n const resolvedAppRoot = path.resolve(appRoot)\n const cached = bootstrapCache.get(resolvedAppRoot)\n if (cached) return cached\n const promise = bootstrapFromAppRoot(resolvedAppRoot)\n bootstrapCache.set(resolvedAppRoot, promise)\n return promise\n}\n\nexport async function drainQueueFromAppRoot(\n queueName: string,\n options: QueueDrainRunnerOptions,\n): Promise<number> {\n const data = await getBootstrapData(options.appRoot)\n const worker = data.modules\n .flatMap((module) => module.workers ?? [])\n .find((entry) => entry.queue === queueName)\n if (!worker) return 0\n\n const container = await createRequestContainer()\n const queue = createQueue(queueName, 'local', {\n baseDir: path.resolve(\n options.queueBaseDir?.trim()\n || process.env.QUEUE_BASE_DIR?.trim()\n || path.resolve(options.appRoot, '.mercato/queue'),\n ),\n concurrency: 1,\n })\n const resolve = <T = unknown>(name: string): T => container.resolve(name) as T\n\n try {\n let processedJobs = 0\n while (true) {\n const result = await queue.process(\n async (job, ctx) => {\n await Promise.resolve(worker.handler(job, { ...ctx, resolve }))\n },\n { limit: options.jobLimit },\n )\n const handled = result.processed + result.failed\n processedJobs += handled\n if (result.failed > 0) {\n process.stderr.write(\n `[queue-drain] ${result.failed} job(s) FAILED in \"${queueName}\" (processed ${result.processed}); failed jobs retry with backoff and dead-letter after max attempts\\n`,\n )\n }\n if (handled === 0) return processedJobs\n }\n } finally {\n await queue.close()\n }\n}\n\nexport async function runQueueDrainFromEnv(): Promise<void> {\n const queueName = process.env.OM_INTEGRATION_QUEUE_NAME?.trim()\n const appRoot = process.env.OM_INTEGRATION_APP_ROOT?.trim()\n const jobLimit = Number.parseInt(process.env.OM_INTEGRATION_QUEUE_JOB_LIMIT || '100', 10) || 100\n if (!queueName || !appRoot) throw new Error('Missing queue drain environment')\n\n const processed = await drainQueueFromAppRoot(queueName, {\n appRoot: path.resolve(appRoot),\n jobLimit,\n queueBaseDir: process.env.QUEUE_BASE_DIR,\n })\n process.stdout.write(`${JSON.stringify({ processed })}\\n`)\n}\n\nconst currentFile = fileURLToPath(import.meta.url)\nconst invokedFile = process.argv[1] ? path.resolve(process.argv[1]) : null\n\nif (invokedFile === currentFile) {\n runQueueDrainFromEnv()\n .then(() => {\n process.exit(0)\n })\n .catch((error) => {\n process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\\n`)\n process.exit(1)\n })\n}\n"],
5
+ "mappings": "AAAA,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAC9B,SAAS,4BAA4B;AACrC,SAAS,8BAA8B;AACvC,SAAS,mBAAmB;AAU5B,MAAM,iBAAiB,oBAAI,IAAoC;AAE/D,SAAS,iBAAiB,SAAyC;AACjE,QAAM,kBAAkB,KAAK,QAAQ,OAAO;AAC5C,QAAM,SAAS,eAAe,IAAI,eAAe;AACjD,MAAI,OAAQ,QAAO;AACnB,QAAM,UAAU,qBAAqB,eAAe;AACpD,iBAAe,IAAI,iBAAiB,OAAO;AAC3C,SAAO;AACT;AAEA,eAAsB,sBACpB,WACA,SACiB;AACjB,QAAM,OAAO,MAAM,iBAAiB,QAAQ,OAAO;AACnD,QAAM,SAAS,KAAK,QACjB,QAAQ,CAAC,WAAW,OAAO,WAAW,CAAC,CAAC,EACxC,KAAK,CAAC,UAAU,MAAM,UAAU,SAAS;AAC5C,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,QAAQ,YAAY,WAAW,SAAS;AAAA,IAC5C,SAAS,KAAK;AAAA,MACZ,QAAQ,cAAc,KAAK,KACxB,QAAQ,IAAI,gBAAgB,KAAK,KACjC,KAAK,QAAQ,QAAQ,SAAS,gBAAgB;AAAA,IACnD;AAAA,IACA,aAAa;AAAA,EACf,CAAC;AACD,QAAM,UAAU,CAAc,SAAoB,UAAU,QAAQ,IAAI;AAExE,MAAI;AACF,QAAI,gBAAgB;AACpB,WAAO,MAAM;AACX,YAAM,SAAS,MAAM,MAAM;AAAA,QACzB,OAAO,KAAK,QAAQ;AAClB,gBAAM,QAAQ,QAAQ,OAAO,QAAQ,KAAK,EAAE,GAAG,KAAK,QAAQ,CAAC,CAAC;AAAA,QAChE;AAAA,QACA,EAAE,OAAO,QAAQ,SAAS;AAAA,MAC5B;AACA,YAAM,UAAU,OAAO,YAAY,OAAO;AAC1C,uBAAiB;AACjB,UAAI,OAAO,SAAS,GAAG;AACrB,gBAAQ,OAAO;AAAA,UACb,iBAAiB,OAAO,MAAM,sBAAsB,SAAS,gBAAgB,OAAO,SAAS;AAAA;AAAA,QAC/F;AAAA,MACF;AACA,UAAI,YAAY,EAAG,QAAO;AAAA,IAC5B;AAAA,EACF,UAAE;AACA,UAAM,MAAM,MAAM;AAAA,EACpB;AACF;AAEA,eAAsB,uBAAsC;AAC1D,QAAM,YAAY,QAAQ,IAAI,2BAA2B,KAAK;AAC9D,QAAM,UAAU,QAAQ,IAAI,yBAAyB,KAAK;AAC1D,QAAM,WAAW,OAAO,SAAS,QAAQ,IAAI,kCAAkC,OAAO,EAAE,KAAK;AAC7F,MAAI,CAAC,aAAa,CAAC,QAAS,OAAM,IAAI,MAAM,iCAAiC;AAE7E,QAAM,YAAY,MAAM,sBAAsB,WAAW;AAAA,IACvD,SAAS,KAAK,QAAQ,OAAO;AAAA,IAC7B;AAAA,IACA,cAAc,QAAQ,IAAI;AAAA,EAC5B,CAAC;AACD,UAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,EAAE,UAAU,CAAC,CAAC;AAAA,CAAI;AAC3D;AAEA,MAAM,cAAc,cAAc,YAAY,GAAG;AACjD,MAAM,cAAc,QAAQ,KAAK,CAAC,IAAI,KAAK,QAAQ,QAAQ,KAAK,CAAC,CAAC,IAAI;AAEtE,IAAI,gBAAgB,aAAa;AAC/B,uBAAqB,EAClB,KAAK,MAAM;AACV,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC,EACA,MAAM,CAAC,UAAU;AAChB,YAAQ,OAAO,MAAM,GAAG,iBAAiB,QAAQ,MAAM,SAAS,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,CAAI;AACjG,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACL;",
6
6
  "names": []
7
7
  }
@@ -36,6 +36,17 @@ function drainQueueInAppProcess(queueName, options) {
36
36
  });
37
37
  child.on("error", reject);
38
38
  child.on("close", (code) => {
39
+ if (process.env.OM_DRAIN_DEBUG === "1") {
40
+ process.stderr.write(`[drain-child ${queueName} exit=${code}] STDOUT:
41
+ ${stdout}
42
+ STDERR:
43
+ ${stderr}
44
+ `);
45
+ } else if (stderr.trim().length > 0) {
46
+ process.stderr.write(`[drain-child ${queueName} exit=${code}] STDERR:
47
+ ${stderr}
48
+ `);
49
+ }
39
50
  if (code !== 0) {
40
51
  reject(new Error(`Queue drain failed for "${queueName}" in ${options.appRoot} (exit ${code}).
41
52
  ${stderr || stdout}`));
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/helpers/integration/queue.ts"],
4
- "sourcesContent": ["import { spawn } from 'node:child_process'\nimport { createRequire } from 'node:module'\nimport path from 'node:path'\nimport { drainQueueFromAppRoot } from './queue-runner'\n\ntype DrainQueueOptions = {\n appRoot?: string\n jobLimit?: number\n}\n\nconst DEFAULT_JOB_LIMIT = 100\n\nfunction resolveAppRoot(input?: string): string {\n return path.resolve(input?.trim() || process.env.OM_TEST_APP_ROOT?.trim() || path.resolve(process.cwd(), 'apps/mercato'))\n}\n\nasync function drainQueueInCurrentProcess(queueName: string, options: Required<DrainQueueOptions>): Promise<number> {\n return drainQueueFromAppRoot(queueName, options)\n}\n\nfunction resolveAppRunnerPath(appRoot: string): string {\n const requireFromApp = createRequire(path.join(appRoot, 'package.json'))\n return requireFromApp.resolve('@open-mercato/core/helpers/integration/queue-runner')\n}\n\nfunction drainQueueInAppProcess(queueName: string, options: Required<DrainQueueOptions>): Promise<number> {\n return new Promise((resolve, reject) => {\n const child = spawn(process.execPath, [resolveAppRunnerPath(options.appRoot)], {\n cwd: options.appRoot,\n env: {\n ...process.env,\n OM_INTEGRATION_APP_ROOT: options.appRoot,\n OM_INTEGRATION_QUEUE_NAME: queueName,\n OM_INTEGRATION_QUEUE_JOB_LIMIT: String(options.jobLimit),\n QUEUE_BASE_DIR: process.env.QUEUE_BASE_DIR?.trim() || path.resolve(options.appRoot, '.mercato/queue'),\n },\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n\n let stdout = ''\n let stderr = ''\n child.stdout.on('data', (chunk) => { stdout += chunk.toString() })\n child.stderr.on('data', (chunk) => { stderr += chunk.toString() })\n child.on('error', reject)\n child.on('close', (code) => {\n if (code !== 0) {\n reject(new Error(`Queue drain failed for \"${queueName}\" in ${options.appRoot} (exit ${code}).\\n${stderr || stdout}`))\n return\n }\n const lastLine = stdout.trim().split(/\\r?\\n/).reverse().find((line) => line.trim().startsWith('{')) ?? '{}'\n try {\n const parsed = JSON.parse(lastLine) as { processed?: unknown }\n resolve(Number(parsed.processed ?? 0))\n } catch {\n resolve(0)\n }\n })\n })\n}\n\nexport async function drainIntegrationQueue(queueName: string, options: DrainQueueOptions = {}): Promise<number> {\n const resolved = {\n appRoot: resolveAppRoot(options.appRoot),\n jobLimit: options.jobLimit ?? DEFAULT_JOB_LIMIT,\n }\n if (process.env.OM_TEST_APP_ROOT?.trim()) {\n return drainQueueInAppProcess(queueName, resolved)\n }\n return drainQueueInCurrentProcess(queueName, resolved)\n}\n"],
5
- "mappings": "AAAA,SAAS,aAAa;AACtB,SAAS,qBAAqB;AAC9B,OAAO,UAAU;AACjB,SAAS,6BAA6B;AAOtC,MAAM,oBAAoB;AAE1B,SAAS,eAAe,OAAwB;AAC9C,SAAO,KAAK,QAAQ,OAAO,KAAK,KAAK,QAAQ,IAAI,kBAAkB,KAAK,KAAK,KAAK,QAAQ,QAAQ,IAAI,GAAG,cAAc,CAAC;AAC1H;AAEA,eAAe,2BAA2B,WAAmB,SAAuD;AAClH,SAAO,sBAAsB,WAAW,OAAO;AACjD;AAEA,SAAS,qBAAqB,SAAyB;AACrD,QAAM,iBAAiB,cAAc,KAAK,KAAK,SAAS,cAAc,CAAC;AACvE,SAAO,eAAe,QAAQ,qDAAqD;AACrF;AAEA,SAAS,uBAAuB,WAAmB,SAAuD;AACxG,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,QAAQ,MAAM,QAAQ,UAAU,CAAC,qBAAqB,QAAQ,OAAO,CAAC,GAAG;AAAA,MAC7E,KAAK,QAAQ;AAAA,MACb,KAAK;AAAA,QACH,GAAG,QAAQ;AAAA,QACX,yBAAyB,QAAQ;AAAA,QACjC,2BAA2B;AAAA,QAC3B,gCAAgC,OAAO,QAAQ,QAAQ;AAAA,QACvD,gBAAgB,QAAQ,IAAI,gBAAgB,KAAK,KAAK,KAAK,QAAQ,QAAQ,SAAS,gBAAgB;AAAA,MACtG;AAAA,MACA,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,IAClC,CAAC;AAED,QAAI,SAAS;AACb,QAAI,SAAS;AACb,UAAM,OAAO,GAAG,QAAQ,CAAC,UAAU;AAAE,gBAAU,MAAM,SAAS;AAAA,IAAE,CAAC;AACjE,UAAM,OAAO,GAAG,QAAQ,CAAC,UAAU;AAAE,gBAAU,MAAM,SAAS;AAAA,IAAE,CAAC;AACjE,UAAM,GAAG,SAAS,MAAM;AACxB,UAAM,GAAG,SAAS,CAAC,SAAS;AAC1B,UAAI,SAAS,GAAG;AACd,eAAO,IAAI,MAAM,2BAA2B,SAAS,QAAQ,QAAQ,OAAO,UAAU,IAAI;AAAA,EAAO,UAAU,MAAM,EAAE,CAAC;AACpH;AAAA,MACF;AACA,YAAM,WAAW,OAAO,KAAK,EAAE,MAAM,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,KAAK,KAAK,EAAE,WAAW,GAAG,CAAC,KAAK;AACvG,UAAI;AACF,cAAM,SAAS,KAAK,MAAM,QAAQ;AAClC,gBAAQ,OAAO,OAAO,aAAa,CAAC,CAAC;AAAA,MACvC,QAAQ;AACN,gBAAQ,CAAC;AAAA,MACX;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;AAEA,eAAsB,sBAAsB,WAAmB,UAA6B,CAAC,GAAoB;AAC/G,QAAM,WAAW;AAAA,IACf,SAAS,eAAe,QAAQ,OAAO;AAAA,IACvC,UAAU,QAAQ,YAAY;AAAA,EAChC;AACA,MAAI,QAAQ,IAAI,kBAAkB,KAAK,GAAG;AACxC,WAAO,uBAAuB,WAAW,QAAQ;AAAA,EACnD;AACA,SAAO,2BAA2B,WAAW,QAAQ;AACvD;",
4
+ "sourcesContent": ["import { spawn } from 'node:child_process'\nimport { createRequire } from 'node:module'\nimport path from 'node:path'\nimport { drainQueueFromAppRoot } from './queue-runner'\n\ntype DrainQueueOptions = {\n appRoot?: string\n jobLimit?: number\n}\n\nconst DEFAULT_JOB_LIMIT = 100\n\nfunction resolveAppRoot(input?: string): string {\n return path.resolve(input?.trim() || process.env.OM_TEST_APP_ROOT?.trim() || path.resolve(process.cwd(), 'apps/mercato'))\n}\n\nasync function drainQueueInCurrentProcess(queueName: string, options: Required<DrainQueueOptions>): Promise<number> {\n return drainQueueFromAppRoot(queueName, options)\n}\n\nfunction resolveAppRunnerPath(appRoot: string): string {\n const requireFromApp = createRequire(path.join(appRoot, 'package.json'))\n return requireFromApp.resolve('@open-mercato/core/helpers/integration/queue-runner')\n}\n\nfunction drainQueueInAppProcess(queueName: string, options: Required<DrainQueueOptions>): Promise<number> {\n return new Promise((resolve, reject) => {\n const child = spawn(process.execPath, [resolveAppRunnerPath(options.appRoot)], {\n cwd: options.appRoot,\n env: {\n ...process.env,\n OM_INTEGRATION_APP_ROOT: options.appRoot,\n OM_INTEGRATION_QUEUE_NAME: queueName,\n OM_INTEGRATION_QUEUE_JOB_LIMIT: String(options.jobLimit),\n QUEUE_BASE_DIR: process.env.QUEUE_BASE_DIR?.trim() || path.resolve(options.appRoot, '.mercato/queue'),\n },\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n\n let stdout = ''\n let stderr = ''\n child.stdout.on('data', (chunk) => { stdout += chunk.toString() })\n child.stderr.on('data', (chunk) => { stderr += chunk.toString() })\n child.on('error', reject)\n child.on('close', (code) => {\n // The child's diagnostics (pino job logs on stdout, cache-fallback\n // console.warn on stderr) are otherwise discarded on exit 0, which makes\n // silently-failing jobs and silent memory-cache fallbacks in the drain\n // child undebuggable from CI logs. OM_DRAIN_DEBUG=1 forwards everything;\n // a non-empty stderr is forwarded unconditionally because it only carries\n // warning/error-class output.\n if (process.env.OM_DRAIN_DEBUG === '1') {\n process.stderr.write(`[drain-child ${queueName} exit=${code}] STDOUT:\\n${stdout}\\nSTDERR:\\n${stderr}\\n`)\n } else if (stderr.trim().length > 0) {\n process.stderr.write(`[drain-child ${queueName} exit=${code}] STDERR:\\n${stderr}\\n`)\n }\n if (code !== 0) {\n reject(new Error(`Queue drain failed for \"${queueName}\" in ${options.appRoot} (exit ${code}).\\n${stderr || stdout}`))\n return\n }\n const lastLine = stdout.trim().split(/\\r?\\n/).reverse().find((line) => line.trim().startsWith('{')) ?? '{}'\n try {\n const parsed = JSON.parse(lastLine) as { processed?: unknown }\n resolve(Number(parsed.processed ?? 0))\n } catch {\n resolve(0)\n }\n })\n })\n}\n\nexport async function drainIntegrationQueue(queueName: string, options: DrainQueueOptions = {}): Promise<number> {\n const resolved = {\n appRoot: resolveAppRoot(options.appRoot),\n jobLimit: options.jobLimit ?? DEFAULT_JOB_LIMIT,\n }\n if (process.env.OM_TEST_APP_ROOT?.trim()) {\n return drainQueueInAppProcess(queueName, resolved)\n }\n return drainQueueInCurrentProcess(queueName, resolved)\n}\n"],
5
+ "mappings": "AAAA,SAAS,aAAa;AACtB,SAAS,qBAAqB;AAC9B,OAAO,UAAU;AACjB,SAAS,6BAA6B;AAOtC,MAAM,oBAAoB;AAE1B,SAAS,eAAe,OAAwB;AAC9C,SAAO,KAAK,QAAQ,OAAO,KAAK,KAAK,QAAQ,IAAI,kBAAkB,KAAK,KAAK,KAAK,QAAQ,QAAQ,IAAI,GAAG,cAAc,CAAC;AAC1H;AAEA,eAAe,2BAA2B,WAAmB,SAAuD;AAClH,SAAO,sBAAsB,WAAW,OAAO;AACjD;AAEA,SAAS,qBAAqB,SAAyB;AACrD,QAAM,iBAAiB,cAAc,KAAK,KAAK,SAAS,cAAc,CAAC;AACvE,SAAO,eAAe,QAAQ,qDAAqD;AACrF;AAEA,SAAS,uBAAuB,WAAmB,SAAuD;AACxG,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,QAAQ,MAAM,QAAQ,UAAU,CAAC,qBAAqB,QAAQ,OAAO,CAAC,GAAG;AAAA,MAC7E,KAAK,QAAQ;AAAA,MACb,KAAK;AAAA,QACH,GAAG,QAAQ;AAAA,QACX,yBAAyB,QAAQ;AAAA,QACjC,2BAA2B;AAAA,QAC3B,gCAAgC,OAAO,QAAQ,QAAQ;AAAA,QACvD,gBAAgB,QAAQ,IAAI,gBAAgB,KAAK,KAAK,KAAK,QAAQ,QAAQ,SAAS,gBAAgB;AAAA,MACtG;AAAA,MACA,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,IAClC,CAAC;AAED,QAAI,SAAS;AACb,QAAI,SAAS;AACb,UAAM,OAAO,GAAG,QAAQ,CAAC,UAAU;AAAE,gBAAU,MAAM,SAAS;AAAA,IAAE,CAAC;AACjE,UAAM,OAAO,GAAG,QAAQ,CAAC,UAAU;AAAE,gBAAU,MAAM,SAAS;AAAA,IAAE,CAAC;AACjE,UAAM,GAAG,SAAS,MAAM;AACxB,UAAM,GAAG,SAAS,CAAC,SAAS;AAO1B,UAAI,QAAQ,IAAI,mBAAmB,KAAK;AACtC,gBAAQ,OAAO,MAAM,gBAAgB,SAAS,SAAS,IAAI;AAAA,EAAc,MAAM;AAAA;AAAA,EAAc,MAAM;AAAA,CAAI;AAAA,MACzG,WAAW,OAAO,KAAK,EAAE,SAAS,GAAG;AACnC,gBAAQ,OAAO,MAAM,gBAAgB,SAAS,SAAS,IAAI;AAAA,EAAc,MAAM;AAAA,CAAI;AAAA,MACrF;AACA,UAAI,SAAS,GAAG;AACd,eAAO,IAAI,MAAM,2BAA2B,SAAS,QAAQ,QAAQ,OAAO,UAAU,IAAI;AAAA,EAAO,UAAU,MAAM,EAAE,CAAC;AACpH;AAAA,MACF;AACA,YAAM,WAAW,OAAO,KAAK,EAAE,MAAM,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,KAAK,KAAK,EAAE,WAAW,GAAG,CAAC,KAAK;AACvG,UAAI;AACF,cAAM,SAAS,KAAK,MAAM,QAAQ;AAClC,gBAAQ,OAAO,OAAO,aAAa,CAAC,CAAC;AAAA,MACvC,QAAQ;AACN,gBAAQ,CAAC;AAAA,MACX;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;AAEA,eAAsB,sBAAsB,WAAmB,UAA6B,CAAC,GAAoB;AAC/G,QAAM,WAAW;AAAA,IACf,SAAS,eAAe,QAAQ,OAAO;AAAA,IACvC,UAAU,QAAQ,YAAY;AAAA,EAChC;AACA,MAAI,QAAQ,IAAI,kBAAkB,KAAK,GAAG;AACxC,WAAO,uBAAuB,WAAW,QAAQ;AAAA,EACnD;AACA,SAAO,2BAA2B,WAAW,QAAQ;AACvD;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.6-develop.6493.1.333455a884",
3
+ "version": "0.6.6-develop.6494.1.8479db6c38",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -253,16 +253,16 @@
253
253
  "zod": "^4.4.3"
254
254
  },
255
255
  "peerDependencies": {
256
- "@open-mercato/ai-assistant": "0.6.6-develop.6493.1.333455a884",
257
- "@open-mercato/shared": "0.6.6-develop.6493.1.333455a884",
258
- "@open-mercato/ui": "0.6.6-develop.6493.1.333455a884",
256
+ "@open-mercato/ai-assistant": "0.6.6-develop.6494.1.8479db6c38",
257
+ "@open-mercato/shared": "0.6.6-develop.6494.1.8479db6c38",
258
+ "@open-mercato/ui": "0.6.6-develop.6494.1.8479db6c38",
259
259
  "react": "^19.0.0",
260
260
  "react-dom": "^19.0.0"
261
261
  },
262
262
  "devDependencies": {
263
- "@open-mercato/ai-assistant": "0.6.6-develop.6493.1.333455a884",
264
- "@open-mercato/shared": "0.6.6-develop.6493.1.333455a884",
265
- "@open-mercato/ui": "0.6.6-develop.6493.1.333455a884",
263
+ "@open-mercato/ai-assistant": "0.6.6-develop.6494.1.8479db6c38",
264
+ "@open-mercato/shared": "0.6.6-develop.6494.1.8479db6c38",
265
+ "@open-mercato/ui": "0.6.6-develop.6494.1.8479db6c38",
266
266
  "@testing-library/dom": "^10.4.1",
267
267
  "@testing-library/jest-dom": "^6.9.1",
268
268
  "@testing-library/react": "^16.3.1",
@@ -55,6 +55,11 @@ export async function drainQueueFromAppRoot(
55
55
  )
56
56
  const handled = result.processed + result.failed
57
57
  processedJobs += handled
58
+ if (result.failed > 0) {
59
+ process.stderr.write(
60
+ `[queue-drain] ${result.failed} job(s) FAILED in "${queueName}" (processed ${result.processed}); failed jobs retry with backoff and dead-letter after max attempts\n`,
61
+ )
62
+ }
58
63
  if (handled === 0) return processedJobs
59
64
  }
60
65
  } finally {
@@ -43,6 +43,17 @@ function drainQueueInAppProcess(queueName: string, options: Required<DrainQueueO
43
43
  child.stderr.on('data', (chunk) => { stderr += chunk.toString() })
44
44
  child.on('error', reject)
45
45
  child.on('close', (code) => {
46
+ // The child's diagnostics (pino job logs on stdout, cache-fallback
47
+ // console.warn on stderr) are otherwise discarded on exit 0, which makes
48
+ // silently-failing jobs and silent memory-cache fallbacks in the drain
49
+ // child undebuggable from CI logs. OM_DRAIN_DEBUG=1 forwards everything;
50
+ // a non-empty stderr is forwarded unconditionally because it only carries
51
+ // warning/error-class output.
52
+ if (process.env.OM_DRAIN_DEBUG === '1') {
53
+ process.stderr.write(`[drain-child ${queueName} exit=${code}] STDOUT:\n${stdout}\nSTDERR:\n${stderr}\n`)
54
+ } else if (stderr.trim().length > 0) {
55
+ process.stderr.write(`[drain-child ${queueName} exit=${code}] STDERR:\n${stderr}\n`)
56
+ }
46
57
  if (code !== 0) {
47
58
  reject(new Error(`Queue drain failed for "${queueName}" in ${options.appRoot} (exit ${code}).\n${stderr || stdout}`))
48
59
  return