@open-mercato/cli 0.6.5-develop.5337.1.534b781eac → 0.6.5

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.
@@ -0,0 +1,20 @@
1
+ function createPerJobWorkerHandler(workers, createContainer) {
2
+ return async (job, ctx) => {
3
+ const container = await createContainer();
4
+ try {
5
+ for (const worker of workers) {
6
+ await worker.handler(job, { ...ctx, resolve: container.resolve.bind(container) });
7
+ }
8
+ } finally {
9
+ try {
10
+ const em = container.resolve("em");
11
+ em?.clear?.();
12
+ } catch {
13
+ }
14
+ }
15
+ };
16
+ }
17
+ export {
18
+ createPerJobWorkerHandler
19
+ };
20
+ //# sourceMappingURL=worker-job-handler.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/lib/worker-job-handler.ts"],
4
+ "sourcesContent": ["import type { JobContext, JobHandler } from '@open-mercato/queue'\nimport type { ModuleWorker } from '@open-mercato/shared/modules/registry'\n\nexport type WorkerJobContainer = {\n resolve: <T = unknown>(name: string) => T\n}\n\nexport type WorkerJobContainerFactory = () => Promise<WorkerJobContainer>\n\ntype ClearableEntityManager = {\n clear?: () => void\n}\n\n/**\n * Builds a queue job handler that isolates every job in its own request\n * container, instead of sharing a single process-wide `EntityManager` fork\n * across all concurrent jobs.\n *\n * Under the async (BullMQ) strategy jobs run with real concurrency, so a\n * shared EM would interleave unit-of-work flushes between unrelated jobs and\n * never release its identity map. Creating one container per job removes both\n * the cross-job flush race and the unbounded identity-map growth, while\n * keeping the `ctx.resolve` contract and DI keys unchanged. See issue #2970.\n */\nexport function createPerJobWorkerHandler(\n workers: ModuleWorker[],\n createContainer: WorkerJobContainerFactory,\n): JobHandler {\n return async (job, ctx: JobContext) => {\n const container = await createContainer()\n try {\n for (const worker of workers) {\n await worker.handler(job, { ...ctx, resolve: container.resolve.bind(container) })\n }\n } finally {\n try {\n const em = container.resolve('em') as ClearableEntityManager | null\n em?.clear?.()\n } catch {\n // Best-effort: clearing the identity map is a memory optimization and\n // must never mask a job's own outcome.\n }\n }\n }\n}\n"],
5
+ "mappings": "AAwBO,SAAS,0BACd,SACA,iBACY;AACZ,SAAO,OAAO,KAAK,QAAoB;AACrC,UAAM,YAAY,MAAM,gBAAgB;AACxC,QAAI;AACF,iBAAW,UAAU,SAAS;AAC5B,cAAM,OAAO,QAAQ,KAAK,EAAE,GAAG,KAAK,SAAS,UAAU,QAAQ,KAAK,SAAS,EAAE,CAAC;AAAA,MAClF;AAAA,IACF,UAAE;AACA,UAAI;AACF,cAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,YAAI,QAAQ;AAAA,MACd,QAAQ;AAAA,MAGR;AAAA,IACF;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
package/dist/mercato.js CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  resolveLazyRestart
11
11
  } from "./lib/auto-spawn-workers.js";
12
12
  import { startLazyWorkerSupervisor } from "./lib/queue-worker-supervisor.js";
13
+ import { createPerJobWorkerHandler } from "./lib/worker-job-handler.js";
13
14
  import {
14
15
  resolveAutoSpawnSchedulerMode,
15
16
  resolveLazySchedulerPollMs,
@@ -25,6 +26,7 @@ import {
25
26
  import { parseModuleInstallArgs } from "./lib/module-install-args.js";
26
27
  import { resolveNextBuildIdCandidate } from "./lib/next-build-id.js";
27
28
  import { acquireServerStartLock } from "./lib/server-start-lock.js";
29
+ import { assertSingleInstanceStrategies } from "./lib/single-instance-strategy-guard.js";
28
30
  import { createDevEnvReloader, watchDevEnvFiles } from "./lib/dev-env-reload.js";
29
31
  const lazyIntegration = () => import("./lib/testing/integration.js");
30
32
  import path from "node:path";
@@ -1224,7 +1226,6 @@ async function run(argv = process.argv) {
1224
1226
  return;
1225
1227
  }
1226
1228
  const { createRequestContainer } = await import("@open-mercato/shared/lib/di/container");
1227
- const container = await createRequestContainer();
1228
1229
  console.log(`[worker] Starting workers for all queues: ${discoveredQueues.join(", ")}`);
1229
1230
  const workerPromises = discoveredQueues.map(async (queue) => {
1230
1231
  const queueWorkers = allWorkers.filter((w) => w.queue === queue);
@@ -1236,11 +1237,7 @@ async function run(argv = process.argv) {
1236
1237
  connection: queueRedisUrl ? { url: queueRedisUrl } : void 0,
1237
1238
  concurrency,
1238
1239
  background: true,
1239
- handler: async (job, ctx) => {
1240
- for (const worker of queueWorkers) {
1241
- await worker.handler(job, { ...ctx, resolve: container.resolve.bind(container) });
1242
- }
1243
- }
1240
+ handler: createPerJobWorkerHandler(queueWorkers, createRequestContainer)
1244
1241
  });
1245
1242
  });
1246
1243
  await Promise.all(workerPromises);
@@ -1251,7 +1248,6 @@ async function run(argv = process.argv) {
1251
1248
  const queueWorkers = allWorkers.filter((w) => w.queue === queueName);
1252
1249
  if (queueWorkers.length > 0) {
1253
1250
  const { createRequestContainer } = await import("@open-mercato/shared/lib/di/container");
1254
- const container = await createRequestContainer();
1255
1251
  const concurrency = concurrencyOverride ?? Math.max(...queueWorkers.map((w) => w.concurrency), 1);
1256
1252
  console.log(`[worker] Found ${queueWorkers.length} worker(s) for queue "${queueName}"`);
1257
1253
  const queueRedisUrl = getRedisUrl("QUEUE");
@@ -1259,11 +1255,7 @@ async function run(argv = process.argv) {
1259
1255
  queueName,
1260
1256
  connection: queueRedisUrl ? { url: queueRedisUrl } : void 0,
1261
1257
  concurrency,
1262
- handler: async (job, ctx) => {
1263
- for (const worker of queueWorkers) {
1264
- await worker.handler(job, { ...ctx, resolve: container.resolve.bind(container) });
1265
- }
1266
- }
1258
+ handler: createPerJobWorkerHandler(queueWorkers, createRequestContainer)
1267
1259
  });
1268
1260
  } else {
1269
1261
  console.error(`No workers found for queue "${queueName}"`);
@@ -1781,6 +1773,7 @@ async function run(argv = process.argv) {
1781
1773
  const autoSpawnSchedulerMode = resolveAutoSpawnSchedulerMode(process.env);
1782
1774
  const queueStrategy = process.env.QUEUE_STRATEGY || "local";
1783
1775
  const runtimeEnv = buildServerProcessEnvironment(process.env);
1776
+ assertSingleInstanceStrategies(runtimeEnv);
1784
1777
  const schedulerCommand = lookupModuleCommand(getCliModules(), "scheduler", "start");
1785
1778
  const serverStartLock = acquireServerStartLock(appDir, {
1786
1779
  port: runtimeEnv.PORT ?? process.env.PORT ?? null