@open-mercato/cli 0.6.6-develop.6507.1.cc8b36505d → 0.6.6-develop.6515.1.01911d00e2
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.
- package/.turbo/turbo-build.log +2 -2
- package/dist/agentic/guides/module-facts.json +53 -53
- package/dist/agentic/guides/modules/ai_assistant.md +1 -1
- package/dist/agentic/guides/modules/api_docs.md +1 -1
- package/dist/agentic/guides/modules/api_keys.md +1 -1
- package/dist/agentic/guides/modules/attachments.md +1 -1
- package/dist/agentic/guides/modules/audit_logs.md +1 -1
- package/dist/agentic/guides/modules/auth.md +1 -1
- package/dist/agentic/guides/modules/business_rules.md +1 -1
- package/dist/agentic/guides/modules/catalog.md +1 -1
- package/dist/agentic/guides/modules/channel_gmail.md +1 -1
- package/dist/agentic/guides/modules/channel_imap.md +1 -1
- package/dist/agentic/guides/modules/checkout.md +1 -1
- package/dist/agentic/guides/modules/communication_channels.md +1 -1
- package/dist/agentic/guides/modules/configs.md +1 -1
- package/dist/agentic/guides/modules/content.md +1 -1
- package/dist/agentic/guides/modules/currencies.md +1 -1
- package/dist/agentic/guides/modules/customer_accounts.md +1 -1
- package/dist/agentic/guides/modules/customers.md +1 -1
- package/dist/agentic/guides/modules/dashboards.md +1 -1
- package/dist/agentic/guides/modules/data_sync.md +1 -1
- package/dist/agentic/guides/modules/dictionaries.md +1 -1
- package/dist/agentic/guides/modules/directory.md +1 -1
- package/dist/agentic/guides/modules/entities.md +1 -1
- package/dist/agentic/guides/modules/events.md +1 -1
- package/dist/agentic/guides/modules/feature_toggles.md +1 -1
- package/dist/agentic/guides/modules/gateway_stripe.md +1 -1
- package/dist/agentic/guides/modules/generators.md +1 -1
- package/dist/agentic/guides/modules/inbox_ops.md +1 -1
- package/dist/agentic/guides/modules/integrations.md +1 -1
- package/dist/agentic/guides/modules/messages.md +1 -1
- package/dist/agentic/guides/modules/notifications.md +1 -1
- package/dist/agentic/guides/modules/onboarding.md +1 -1
- package/dist/agentic/guides/modules/payment_gateways.md +1 -1
- package/dist/agentic/guides/modules/perspectives.md +1 -1
- package/dist/agentic/guides/modules/planner.md +1 -1
- package/dist/agentic/guides/modules/portal.md +1 -1
- package/dist/agentic/guides/modules/progress.md +1 -1
- package/dist/agentic/guides/modules/query_index.md +1 -1
- package/dist/agentic/guides/modules/record_locks.md +1 -1
- package/dist/agentic/guides/modules/resources.md +1 -1
- package/dist/agentic/guides/modules/sales.md +1 -1
- package/dist/agentic/guides/modules/scheduler.md +1 -1
- package/dist/agentic/guides/modules/search.md +1 -1
- package/dist/agentic/guides/modules/security.md +1 -1
- package/dist/agentic/guides/modules/shipping_carriers.md +1 -1
- package/dist/agentic/guides/modules/sso.md +1 -1
- package/dist/agentic/guides/modules/staff.md +1 -1
- package/dist/agentic/guides/modules/storage_s3.md +1 -1
- package/dist/agentic/guides/modules/sync_akeneo.md +1 -1
- package/dist/agentic/guides/modules/sync_excel.md +1 -1
- package/dist/agentic/guides/modules/system_status_overlays.md +1 -1
- package/dist/agentic/guides/modules/translations.md +1 -1
- package/dist/agentic/guides/modules/webhooks.md +1 -1
- package/dist/agentic/guides/modules/workflows.md +1 -1
- package/dist/lib/queue-worker-supervisor.js +35 -6
- package/dist/lib/queue-worker-supervisor.js.map +2 -2
- package/dist/mercato.js +40 -4
- package/dist/mercato.js.map +2 -2
- package/package.json +5 -5
- package/src/lib/__tests__/queue-worker-supervisor.test.ts +91 -0
- package/src/lib/queue-worker-supervisor.ts +43 -8
- package/src/mercato.ts +54 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# feature_toggles — module facts (generated, do not edit)
|
|
2
|
-
<!-- generated from @open-mercato/core 0.6.6-develop.
|
|
2
|
+
<!-- generated from @open-mercato/core 0.6.6-develop.6515.1.01911d00e2 — R1 staleness stamp -->
|
|
3
3
|
|
|
4
4
|
## Entities
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# gateway_stripe — module facts (generated, do not edit)
|
|
2
|
-
<!-- generated from @open-mercato/core 0.6.6-develop.
|
|
2
|
+
<!-- generated from @open-mercato/core 0.6.6-develop.6515.1.01911d00e2 — R1 staleness stamp -->
|
|
3
3
|
|
|
4
4
|
## Entities
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# payment_gateways — module facts (generated, do not edit)
|
|
2
|
-
<!-- generated from @open-mercato/core 0.6.6-develop.
|
|
2
|
+
<!-- generated from @open-mercato/core 0.6.6-develop.6515.1.01911d00e2 — R1 staleness stamp -->
|
|
3
3
|
|
|
4
4
|
## Entities
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# shipping_carriers — module facts (generated, do not edit)
|
|
2
|
-
<!-- generated from @open-mercato/core 0.6.6-develop.
|
|
2
|
+
<!-- generated from @open-mercato/core 0.6.6-develop.6515.1.01911d00e2 — R1 staleness stamp -->
|
|
3
3
|
|
|
4
4
|
## Entities
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# system_status_overlays — module facts (generated, do not edit)
|
|
2
|
-
<!-- generated from @open-mercato/core 0.6.6-develop.
|
|
2
|
+
<!-- generated from @open-mercato/core 0.6.6-develop.6515.1.01911d00e2 — R1 staleness stamp -->
|
|
3
3
|
|
|
4
4
|
## Entities
|
|
5
5
|
|
|
@@ -134,18 +134,22 @@ function startLazyWorkerSupervisor(options) {
|
|
|
134
134
|
})();
|
|
135
135
|
});
|
|
136
136
|
}
|
|
137
|
-
function spawnSharedWorker(triggerQueueNames) {
|
|
137
|
+
function spawnSharedWorker(triggerQueueNames, schedulerTriggered = false) {
|
|
138
138
|
if (stopping) return;
|
|
139
139
|
if (activeSharedChild) return;
|
|
140
140
|
if (startingSharedWorker) return;
|
|
141
141
|
startingSharedWorker = true;
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
if (schedulerTriggered) {
|
|
143
|
+
logger.log("[lazy-supervisor] Enabled schedule detected \u2014 starting shared worker for all queues");
|
|
144
|
+
} else {
|
|
145
|
+
const triggerSummary = triggerQueueNames.length > 0 ? ` in ${triggerQueueNames.join(", ")}` : "";
|
|
146
|
+
logger.log(`[lazy-supervisor] Pending job detected${triggerSummary} \u2014 starting shared worker for all queues`);
|
|
147
|
+
}
|
|
144
148
|
let child;
|
|
145
149
|
try {
|
|
146
150
|
child = spawnFn(
|
|
147
151
|
"node",
|
|
148
|
-
[options.mercatoBin, "queue", "worker", "--all"],
|
|
152
|
+
[options.mercatoBin, "queue", "worker", "--all", ...options.sharedWorkerArgs ?? []],
|
|
149
153
|
{
|
|
150
154
|
stdio: "inherit",
|
|
151
155
|
env: options.runtimeEnv,
|
|
@@ -199,6 +203,18 @@ function startLazyWorkerSupervisor(options) {
|
|
|
199
203
|
if (readyQueues.length > 0) {
|
|
200
204
|
logger.warn("[lazy-supervisor] Restarting shared worker because jobs remain pending.");
|
|
201
205
|
spawnSharedWorker(readyQueues);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (options.shouldRestartSharedWorker) {
|
|
209
|
+
try {
|
|
210
|
+
if (await options.shouldRestartSharedWorker()) {
|
|
211
|
+
logger.warn("[lazy-supervisor] Restarting shared worker because an enabled schedule remains.");
|
|
212
|
+
spawnSharedWorker([], true);
|
|
213
|
+
}
|
|
214
|
+
} catch (err) {
|
|
215
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
216
|
+
logger.warn(`[lazy-supervisor] Shared-worker restart condition failed: ${message}`);
|
|
217
|
+
}
|
|
202
218
|
}
|
|
203
219
|
})();
|
|
204
220
|
});
|
|
@@ -242,8 +258,21 @@ function startLazyWorkerSupervisor(options) {
|
|
|
242
258
|
spawnQueueWorker(group.queueName);
|
|
243
259
|
}
|
|
244
260
|
}
|
|
245
|
-
if (spawnMode === "shared"
|
|
246
|
-
|
|
261
|
+
if (spawnMode === "shared") {
|
|
262
|
+
if (readySharedQueues.length > 0) {
|
|
263
|
+
spawnSharedWorker(readySharedQueues);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (options.shouldStartSharedWorker) {
|
|
267
|
+
try {
|
|
268
|
+
if (await options.shouldStartSharedWorker()) {
|
|
269
|
+
spawnSharedWorker([], true);
|
|
270
|
+
}
|
|
271
|
+
} catch (err) {
|
|
272
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
273
|
+
logger.warn(`[lazy-supervisor] Shared-worker start condition failed: ${message}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
247
276
|
}
|
|
248
277
|
}
|
|
249
278
|
function scheduleNext() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/queue-worker-supervisor.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Lazy queue worker supervisor.\n *\n * Replaces `mercato queue worker --all` with a lightweight watcher that\n * starts a per-queue worker process only after the first ready job appears\n * on that queue. Discovered queues with no jobs stay completely idle.\n *\n * Design:\n * - Probes every queue on a configurable interval using\n * `getQueuePendingProbe()` from `@open-mercato/queue`.\n * - The first non-error probe with `ready > 0` triggers `spawn('node', [\n * mercatoBin, 'queue', 'worker', queueName ])`.\n * - Each spawned child inherits stdio so its logs flow through the\n * surrounding dev runtime.\n * - On unexpected exit while jobs are still pending, the supervisor\n * restarts the worker if `restartOnUnexpectedExit` is enabled.\n * - On `close()`, all active children receive SIGTERM and the supervisor\n * waits for them to exit before resolving.\n *\n * Probes never invoke handlers, never create BullMQ Workers, and never\n * import generated worker handler modules. The contract is preserved by\n * `getQueuePendingProbe()`.\n */\n\nimport { spawn as nodeSpawn } from 'node:child_process'\nimport type { ChildProcess, SpawnOptions } from 'node:child_process'\nimport {\n getQueuePendingProbe,\n type QueuePendingProbeResult,\n type QueueStrategyType,\n} from '@open-mercato/queue'\nimport type { ModuleWorker } from '@open-mercato/shared/modules/registry'\nimport type { LazyWorkerSpawnMode } from './auto-spawn-workers'\n\nexport type LazySupervisorSpawnFn = (\n command: string,\n args: readonly string[],\n options: SpawnOptions,\n) => ChildProcess\n\nexport type LazySupervisorProbeFn = (\n queueName: string,\n strategy: QueueStrategyType,\n) => Promise<QueuePendingProbeResult>\n\nexport type LazyWorkerSupervisorOptions = {\n mercatoBin: string\n appDir: string\n runtimeEnv: NodeJS.ProcessEnv\n workers: ModuleWorker[]\n pollMs: number\n restartOnUnexpectedExit: boolean\n spawnMode?: LazyWorkerSpawnMode\n strategy?: QueueStrategyType\n /** Override for tests. Defaults to `child_process.spawn`. */\n spawnFn?: LazySupervisorSpawnFn\n /** Override for tests. Defaults to `getQueuePendingProbe`. */\n probeFn?: LazySupervisorProbeFn\n /** Logger override. Defaults to `console`. */\n logger?: Pick<Console, 'log' | 'warn' | 'error'>\n onSpawn?: (queueName: string, child: ChildProcess) => void\n onChildExit?: (\n queueName: string,\n code: number | null,\n signal: NodeJS.Signals | null,\n ) => void\n}\n\nexport type LazyWorkerSupervisorHandle = {\n /** Set of queue names whose worker has started at least once. */\n readonly startedQueues: ReadonlySet<string>\n /** Currently running child process per queue (if any). */\n getActiveChild(queueName: string): ChildProcess | undefined\n /** Stop polling, kill children, wait for exit. Idempotent. */\n close(): Promise<void>\n /** Resolves when the watcher loop has stopped (after `close()`). */\n done: Promise<void>\n}\n\ntype QueueGroup = {\n queueName: string\n concurrency: number\n workerCount: number\n}\n\nfunction groupWorkersByQueue(workers: ModuleWorker[]): QueueGroup[] {\n const groups = new Map<string, QueueGroup>()\n for (const worker of workers) {\n const existing = groups.get(worker.queue)\n if (existing) {\n existing.workerCount += 1\n if (worker.concurrency > existing.concurrency) {\n existing.concurrency = worker.concurrency\n }\n } else {\n groups.set(worker.queue, {\n queueName: worker.queue,\n concurrency: Math.max(worker.concurrency, 1),\n workerCount: 1,\n })\n }\n }\n return [...groups.values()].sort((a, b) => a.queueName.localeCompare(b.queueName))\n}\n\nfunction resolveStrategyFromEnv(env: NodeJS.ProcessEnv): QueueStrategyType {\n return env.QUEUE_STRATEGY === 'async' ? 'async' : 'local'\n}\n\nconst PROBE_ERROR_LOG_THROTTLE_MS = 60_000\n\nexport function startLazyWorkerSupervisor(\n options: LazyWorkerSupervisorOptions,\n): LazyWorkerSupervisorHandle {\n const logger = options.logger ?? console\n const strategy: QueueStrategyType = options.strategy ?? resolveStrategyFromEnv(options.runtimeEnv)\n const spawnMode: LazyWorkerSpawnMode = options.spawnMode ?? 'per-queue'\n const probe: LazySupervisorProbeFn = options.probeFn\n ?? ((queueName) => getQueuePendingProbe(queueName, strategy))\n const groups = groupWorkersByQueue(options.workers)\n\n const startedQueues = new Set<string>()\n const activeChildren = new Map<string, ChildProcess>()\n const startingQueues = new Set<string>()\n let activeSharedChild: ChildProcess | undefined\n let startingSharedWorker = false\n const probeErrorLastLoggedAt = new Map<string, number>()\n\n let stopping = false\n let pollTimer: ReturnType<typeof setTimeout> | null = null\n let watchPromiseResolve: (() => void) | null = null\n const done = new Promise<void>((resolve) => {\n watchPromiseResolve = resolve\n })\n\n if (groups.length === 0) {\n logger.warn(\n '[lazy-supervisor] No queues discovered. Idle until queues are registered or run `yarn generate`.',\n )\n } else {\n logger.log(\n `[lazy-supervisor] Watching ${groups.length} queue(s): ${groups\n .map((g) => g.queueName)\n .join(', ')}`,\n )\n }\n\n const spawnFn: LazySupervisorSpawnFn =\n options.spawnFn ?? ((command, args, opts) => nodeSpawn(command, args as string[], opts))\n\n const markSharedWorkerStarted = () => {\n for (const group of groups) {\n startedQueues.add(group.queueName)\n activeChildren.set(group.queueName, activeSharedChild as ChildProcess)\n }\n }\n\n const clearSharedWorker = () => {\n for (const group of groups) {\n if (activeChildren.get(group.queueName) === activeSharedChild) {\n activeChildren.delete(group.queueName)\n }\n }\n activeSharedChild = undefined\n }\n\n function logProbeError(queueName: string, message: string): void {\n const now = Date.now()\n const lastAt = probeErrorLastLoggedAt.get(queueName) ?? 0\n if (now - lastAt < PROBE_ERROR_LOG_THROTTLE_MS) return\n probeErrorLastLoggedAt.set(queueName, now)\n logger.warn(`[lazy-supervisor] Probe failed for queue \"${queueName}\": ${message}`)\n }\n\n function spawnQueueWorker(queueName: string): void {\n if (stopping) return\n if (activeChildren.has(queueName)) return\n if (startingQueues.has(queueName)) return\n startingQueues.add(queueName)\n\n logger.log(`[lazy-supervisor] Pending job detected \u2014 starting worker for queue \"${queueName}\"`)\n let child: ChildProcess\n try {\n child = spawnFn(\n 'node',\n [options.mercatoBin, 'queue', 'worker', queueName],\n {\n stdio: 'inherit',\n env: options.runtimeEnv,\n cwd: options.appDir,\n },\n )\n } catch (err) {\n startingQueues.delete(queueName)\n const message = err instanceof Error ? err.message : String(err)\n logger.error(`[lazy-supervisor] Failed to spawn worker for \"${queueName}\": ${message}`)\n return\n }\n\n activeChildren.set(queueName, child)\n startedQueues.add(queueName)\n startingQueues.delete(queueName)\n if (options.onSpawn) {\n try {\n options.onSpawn(queueName, child)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[lazy-supervisor] onSpawn callback threw for \"${queueName}\": ${message}`)\n }\n }\n\n child.on('exit', (code, signal) => {\n activeChildren.delete(queueName)\n if (options.onChildExit) {\n try {\n options.onChildExit(queueName, code, signal)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[lazy-supervisor] onChildExit callback threw for \"${queueName}\": ${message}`)\n }\n }\n if (stopping) return\n const expected = signal === 'SIGTERM' || signal === 'SIGINT'\n if (expected) return\n const reason = code !== null ? `code ${code}` : `signal ${signal ?? 'unknown'}`\n logger.warn(`[lazy-supervisor] Worker for \"${queueName}\" exited (${reason}).`)\n if (!options.restartOnUnexpectedExit) return\n void (async () => {\n const result = await safeProbe(queueName)\n if (!result || result.error) return\n if (result.ready > 0) {\n logger.warn(`[lazy-supervisor] Restarting worker for \"${queueName}\" because jobs remain pending.`)\n spawnQueueWorker(queueName)\n }\n })()\n })\n }\n\n function spawnSharedWorker(triggerQueueNames: string[]): void {\n if (stopping) return\n if (activeSharedChild) return\n if (startingSharedWorker) return\n startingSharedWorker = true\n\n const triggerSummary = triggerQueueNames.length > 0\n ? ` after pending job(s) on ${triggerQueueNames.join(', ')}`\n : ''\n logger.log(`[lazy-supervisor] Pending job detected${triggerSummary} \u2014 starting shared worker for all queues`)\n let child: ChildProcess\n try {\n child = spawnFn(\n 'node',\n [options.mercatoBin, 'queue', 'worker', '--all'],\n {\n stdio: 'inherit',\n env: options.runtimeEnv,\n cwd: options.appDir,\n },\n )\n } catch (err) {\n startingSharedWorker = false\n const message = err instanceof Error ? err.message : String(err)\n logger.error(`[lazy-supervisor] Failed to spawn shared worker: ${message}`)\n return\n }\n\n activeSharedChild = child\n markSharedWorkerStarted()\n startingSharedWorker = false\n for (const group of groups) {\n if (options.onSpawn) {\n try {\n options.onSpawn(group.queueName, child)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[lazy-supervisor] onSpawn callback threw for \"${group.queueName}\": ${message}`)\n }\n }\n }\n\n child.on('exit', (code, signal) => {\n clearSharedWorker()\n for (const group of groups) {\n if (options.onChildExit) {\n try {\n options.onChildExit(group.queueName, code, signal)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[lazy-supervisor] onChildExit callback threw for \"${group.queueName}\": ${message}`)\n }\n }\n }\n if (stopping) return\n const expected = signal === 'SIGTERM' || signal === 'SIGINT'\n if (expected) return\n const reason = code !== null ? `code ${code}` : `signal ${signal ?? 'unknown'}`\n logger.warn(`[lazy-supervisor] Shared worker exited (${reason}).`)\n if (!options.restartOnUnexpectedExit) return\n void (async () => {\n const readyQueues: string[] = []\n for (const group of groups) {\n const result = await safeProbe(group.queueName)\n if (!result || result.error) continue\n if (result.ready > 0) readyQueues.push(group.queueName)\n }\n if (readyQueues.length > 0) {\n logger.warn('[lazy-supervisor] Restarting shared worker because jobs remain pending.')\n spawnSharedWorker(readyQueues)\n }\n })()\n })\n }\n\n async function safeProbe(queueName: string): Promise<QueuePendingProbeResult | null> {\n try {\n return await probe(queueName, strategy)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logProbeError(queueName, message)\n return null\n }\n }\n\n async function tickOnce(): Promise<void> {\n if (stopping) return\n if (groups.length === 0) return\n if (spawnMode === 'shared' && (activeSharedChild || startingSharedWorker)) return\n\n const toCheck = groups.filter((g) => !activeChildren.has(g.queueName) && !startingQueues.has(g.queueName))\n if (toCheck.length === 0) return\n\n const probes = await Promise.all(\n toCheck.map(async (group) => ({\n group,\n result: await safeProbe(group.queueName),\n })),\n )\n\n if (stopping) return\n\n const readySharedQueues: string[] = []\n for (const { group, result } of probes) {\n if (!result) continue\n if (result.error) {\n if (result.errorMessage) {\n logProbeError(group.queueName, result.errorMessage)\n }\n continue\n }\n if (result.ready > 0) {\n if (spawnMode === 'shared') {\n readySharedQueues.push(group.queueName)\n continue\n }\n spawnQueueWorker(group.queueName)\n }\n }\n if (spawnMode === 'shared' && readySharedQueues.length > 0) {\n spawnSharedWorker(readySharedQueues)\n }\n }\n\n function scheduleNext(): void {\n if (stopping) return\n pollTimer = setTimeout(() => {\n void (async () => {\n try {\n await tickOnce()\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[lazy-supervisor] Poll cycle failed: ${message}`)\n } finally {\n if (!stopping) scheduleNext()\n }\n })()\n }, options.pollMs)\n pollTimer.unref?.()\n }\n\n // Kick off immediately so the first probe doesn't wait one full poll interval.\n void (async () => {\n try {\n await tickOnce()\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[lazy-supervisor] Initial poll failed: ${message}`)\n } finally {\n scheduleNext()\n }\n })()\n\n async function close(): Promise<void> {\n if (stopping) {\n await done\n return\n }\n stopping = true\n if (pollTimer) {\n clearTimeout(pollTimer)\n pollTimer = null\n }\n\n const exitWaiters: Promise<void>[] = []\n for (const [queueName, child] of activeChildren) {\n exitWaiters.push(\n new Promise<void>((resolve) => {\n if (child.exitCode !== null || child.signalCode !== null) return resolve()\n child.once('exit', () => resolve())\n }),\n )\n try {\n if (!child.killed) child.kill('SIGTERM')\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[lazy-supervisor] Failed to send SIGTERM to \"${queueName}\": ${message}`)\n }\n }\n await Promise.all(exitWaiters)\n activeChildren.clear()\n if (watchPromiseResolve) {\n watchPromiseResolve()\n watchPromiseResolve = null\n }\n }\n\n return {\n startedQueues,\n getActiveChild: (queueName) => activeChildren.get(queueName),\n close,\n done,\n }\n}\n"],
|
|
5
|
-
"mappings": "AAwBA,SAAS,SAAS,iBAAiB;AAEnC;AAAA,EACE;AAAA,OAGK;
|
|
4
|
+
"sourcesContent": ["/**\n * Lazy queue worker supervisor.\n *\n * Replaces `mercato queue worker --all` with a lightweight watcher that\n * starts a per-queue worker process only after the first ready job appears\n * on that queue. Discovered queues with no jobs stay completely idle.\n *\n * Design:\n * - Probes every queue on a configurable interval using\n * `getQueuePendingProbe()` from `@open-mercato/queue`.\n * - The first non-error probe with `ready > 0` triggers `spawn('node', [\n * mercatoBin, 'queue', 'worker', queueName ])`.\n * - Each spawned child inherits stdio so its logs flow through the\n * surrounding dev runtime.\n * - On unexpected exit while jobs are still pending, the supervisor\n * restarts the worker if `restartOnUnexpectedExit` is enabled.\n * - On `close()`, all active children receive SIGTERM and the supervisor\n * waits for them to exit before resolving.\n *\n * Probes never invoke handlers, never create BullMQ Workers, and never\n * import generated worker handler modules. The contract is preserved by\n * `getQueuePendingProbe()`.\n */\n\nimport { spawn as nodeSpawn } from 'node:child_process'\nimport type { ChildProcess, SpawnOptions } from 'node:child_process'\nimport {\n getQueuePendingProbe,\n type QueuePendingProbeResult,\n type QueueStrategyType,\n} from '@open-mercato/queue'\nimport type { ModuleWorker } from '@open-mercato/shared/modules/registry'\nimport type { LazyWorkerSpawnMode } from './auto-spawn-workers'\n\nexport type LazySupervisorSpawnFn = (\n command: string,\n args: readonly string[],\n options: SpawnOptions,\n) => ChildProcess\n\nexport type LazySupervisorProbeFn = (\n queueName: string,\n strategy: QueueStrategyType,\n) => Promise<QueuePendingProbeResult>\n\nexport type LazyWorkerSupervisorOptions = {\n mercatoBin: string\n appDir: string\n runtimeEnv: NodeJS.ProcessEnv\n workers: ModuleWorker[]\n pollMs: number\n restartOnUnexpectedExit: boolean\n spawnMode?: LazyWorkerSpawnMode\n strategy?: QueueStrategyType\n /** Start the shared worker even with no ready queue jobs (for an embedded scheduler). */\n shouldStartSharedWorker?: () => Promise<boolean>\n /** Restart the shared worker with no ready queue jobs after an unexpected exit. */\n shouldRestartSharedWorker?: () => Promise<boolean>\n /** Extra arguments for the shared worker command only. */\n sharedWorkerArgs?: readonly string[]\n /** Override for tests. Defaults to `child_process.spawn`. */\n spawnFn?: LazySupervisorSpawnFn\n /** Override for tests. Defaults to `getQueuePendingProbe`. */\n probeFn?: LazySupervisorProbeFn\n /** Logger override. Defaults to `console`. */\n logger?: Pick<Console, 'log' | 'warn' | 'error'>\n onSpawn?: (queueName: string, child: ChildProcess) => void\n onChildExit?: (\n queueName: string,\n code: number | null,\n signal: NodeJS.Signals | null,\n ) => void\n}\n\nexport type LazyWorkerSupervisorHandle = {\n /** Set of queue names whose worker has started at least once. */\n readonly startedQueues: ReadonlySet<string>\n /** Currently running child process per queue (if any). */\n getActiveChild(queueName: string): ChildProcess | undefined\n /** Stop polling, kill children, wait for exit. Idempotent. */\n close(): Promise<void>\n /** Resolves when the watcher loop has stopped (after `close()`). */\n done: Promise<void>\n}\n\ntype QueueGroup = {\n queueName: string\n concurrency: number\n workerCount: number\n}\n\nfunction groupWorkersByQueue(workers: ModuleWorker[]): QueueGroup[] {\n const groups = new Map<string, QueueGroup>()\n for (const worker of workers) {\n const existing = groups.get(worker.queue)\n if (existing) {\n existing.workerCount += 1\n if (worker.concurrency > existing.concurrency) {\n existing.concurrency = worker.concurrency\n }\n } else {\n groups.set(worker.queue, {\n queueName: worker.queue,\n concurrency: Math.max(worker.concurrency, 1),\n workerCount: 1,\n })\n }\n }\n return [...groups.values()].sort((a, b) => a.queueName.localeCompare(b.queueName))\n}\n\nfunction resolveStrategyFromEnv(env: NodeJS.ProcessEnv): QueueStrategyType {\n return env.QUEUE_STRATEGY === 'async' ? 'async' : 'local'\n}\n\nconst PROBE_ERROR_LOG_THROTTLE_MS = 60_000\n\nexport function startLazyWorkerSupervisor(\n options: LazyWorkerSupervisorOptions,\n): LazyWorkerSupervisorHandle {\n const logger = options.logger ?? console\n const strategy: QueueStrategyType = options.strategy ?? resolveStrategyFromEnv(options.runtimeEnv)\n const spawnMode: LazyWorkerSpawnMode = options.spawnMode ?? 'per-queue'\n const probe: LazySupervisorProbeFn = options.probeFn\n ?? ((queueName) => getQueuePendingProbe(queueName, strategy))\n const groups = groupWorkersByQueue(options.workers)\n\n const startedQueues = new Set<string>()\n const activeChildren = new Map<string, ChildProcess>()\n const startingQueues = new Set<string>()\n let activeSharedChild: ChildProcess | undefined\n let startingSharedWorker = false\n const probeErrorLastLoggedAt = new Map<string, number>()\n\n let stopping = false\n let pollTimer: ReturnType<typeof setTimeout> | null = null\n let watchPromiseResolve: (() => void) | null = null\n const done = new Promise<void>((resolve) => {\n watchPromiseResolve = resolve\n })\n\n if (groups.length === 0) {\n logger.warn(\n '[lazy-supervisor] No queues discovered. Idle until queues are registered or run `yarn generate`.',\n )\n } else {\n logger.log(\n `[lazy-supervisor] Watching ${groups.length} queue(s): ${groups\n .map((g) => g.queueName)\n .join(', ')}`,\n )\n }\n\n const spawnFn: LazySupervisorSpawnFn =\n options.spawnFn ?? ((command, args, opts) => nodeSpawn(command, args as string[], opts))\n\n const markSharedWorkerStarted = () => {\n for (const group of groups) {\n startedQueues.add(group.queueName)\n activeChildren.set(group.queueName, activeSharedChild as ChildProcess)\n }\n }\n\n const clearSharedWorker = () => {\n for (const group of groups) {\n if (activeChildren.get(group.queueName) === activeSharedChild) {\n activeChildren.delete(group.queueName)\n }\n }\n activeSharedChild = undefined\n }\n\n function logProbeError(queueName: string, message: string): void {\n const now = Date.now()\n const lastAt = probeErrorLastLoggedAt.get(queueName) ?? 0\n if (now - lastAt < PROBE_ERROR_LOG_THROTTLE_MS) return\n probeErrorLastLoggedAt.set(queueName, now)\n logger.warn(`[lazy-supervisor] Probe failed for queue \"${queueName}\": ${message}`)\n }\n\n function spawnQueueWorker(queueName: string): void {\n if (stopping) return\n if (activeChildren.has(queueName)) return\n if (startingQueues.has(queueName)) return\n startingQueues.add(queueName)\n\n logger.log(`[lazy-supervisor] Pending job detected \u2014 starting worker for queue \"${queueName}\"`)\n let child: ChildProcess\n try {\n child = spawnFn(\n 'node',\n [options.mercatoBin, 'queue', 'worker', queueName],\n {\n stdio: 'inherit',\n env: options.runtimeEnv,\n cwd: options.appDir,\n },\n )\n } catch (err) {\n startingQueues.delete(queueName)\n const message = err instanceof Error ? err.message : String(err)\n logger.error(`[lazy-supervisor] Failed to spawn worker for \"${queueName}\": ${message}`)\n return\n }\n\n activeChildren.set(queueName, child)\n startedQueues.add(queueName)\n startingQueues.delete(queueName)\n if (options.onSpawn) {\n try {\n options.onSpawn(queueName, child)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[lazy-supervisor] onSpawn callback threw for \"${queueName}\": ${message}`)\n }\n }\n\n child.on('exit', (code, signal) => {\n activeChildren.delete(queueName)\n if (options.onChildExit) {\n try {\n options.onChildExit(queueName, code, signal)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[lazy-supervisor] onChildExit callback threw for \"${queueName}\": ${message}`)\n }\n }\n if (stopping) return\n const expected = signal === 'SIGTERM' || signal === 'SIGINT'\n if (expected) return\n const reason = code !== null ? `code ${code}` : `signal ${signal ?? 'unknown'}`\n logger.warn(`[lazy-supervisor] Worker for \"${queueName}\" exited (${reason}).`)\n if (!options.restartOnUnexpectedExit) return\n void (async () => {\n const result = await safeProbe(queueName)\n if (!result || result.error) return\n if (result.ready > 0) {\n logger.warn(`[lazy-supervisor] Restarting worker for \"${queueName}\" because jobs remain pending.`)\n spawnQueueWorker(queueName)\n }\n })()\n })\n }\n\n function spawnSharedWorker(triggerQueueNames: string[], schedulerTriggered = false): void {\n if (stopping) return\n if (activeSharedChild) return\n if (startingSharedWorker) return\n startingSharedWorker = true\n\n if (schedulerTriggered) {\n logger.log('[lazy-supervisor] Enabled schedule detected \u2014 starting shared worker for all queues')\n } else {\n const triggerSummary = triggerQueueNames.length > 0\n ? ` in ${triggerQueueNames.join(', ')}`\n : ''\n logger.log(`[lazy-supervisor] Pending job detected${triggerSummary} \u2014 starting shared worker for all queues`)\n }\n let child: ChildProcess\n try {\n child = spawnFn(\n 'node',\n [options.mercatoBin, 'queue', 'worker', '--all', ...(options.sharedWorkerArgs ?? [])],\n {\n stdio: 'inherit',\n env: options.runtimeEnv,\n cwd: options.appDir,\n },\n )\n } catch (err) {\n startingSharedWorker = false\n const message = err instanceof Error ? err.message : String(err)\n logger.error(`[lazy-supervisor] Failed to spawn shared worker: ${message}`)\n return\n }\n\n activeSharedChild = child\n markSharedWorkerStarted()\n startingSharedWorker = false\n for (const group of groups) {\n if (options.onSpawn) {\n try {\n options.onSpawn(group.queueName, child)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[lazy-supervisor] onSpawn callback threw for \"${group.queueName}\": ${message}`)\n }\n }\n }\n\n child.on('exit', (code, signal) => {\n clearSharedWorker()\n for (const group of groups) {\n if (options.onChildExit) {\n try {\n options.onChildExit(group.queueName, code, signal)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[lazy-supervisor] onChildExit callback threw for \"${group.queueName}\": ${message}`)\n }\n }\n }\n if (stopping) return\n const expected = signal === 'SIGTERM' || signal === 'SIGINT'\n if (expected) return\n const reason = code !== null ? `code ${code}` : `signal ${signal ?? 'unknown'}`\n logger.warn(`[lazy-supervisor] Shared worker exited (${reason}).`)\n if (!options.restartOnUnexpectedExit) return\n void (async () => {\n const readyQueues: string[] = []\n for (const group of groups) {\n const result = await safeProbe(group.queueName)\n if (!result || result.error) continue\n if (result.ready > 0) readyQueues.push(group.queueName)\n }\n if (readyQueues.length > 0) {\n logger.warn('[lazy-supervisor] Restarting shared worker because jobs remain pending.')\n spawnSharedWorker(readyQueues)\n return\n }\n if (options.shouldRestartSharedWorker) {\n try {\n if (await options.shouldRestartSharedWorker()) {\n logger.warn('[lazy-supervisor] Restarting shared worker because an enabled schedule remains.')\n spawnSharedWorker([], true)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[lazy-supervisor] Shared-worker restart condition failed: ${message}`)\n }\n }\n })()\n })\n }\n\n async function safeProbe(queueName: string): Promise<QueuePendingProbeResult | null> {\n try {\n return await probe(queueName, strategy)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logProbeError(queueName, message)\n return null\n }\n }\n\n async function tickOnce(): Promise<void> {\n if (stopping) return\n if (groups.length === 0) return\n if (spawnMode === 'shared' && (activeSharedChild || startingSharedWorker)) return\n\n const toCheck = groups.filter((g) => !activeChildren.has(g.queueName) && !startingQueues.has(g.queueName))\n if (toCheck.length === 0) return\n\n const probes = await Promise.all(\n toCheck.map(async (group) => ({\n group,\n result: await safeProbe(group.queueName),\n })),\n )\n\n if (stopping) return\n\n const readySharedQueues: string[] = []\n for (const { group, result } of probes) {\n if (!result) continue\n if (result.error) {\n if (result.errorMessage) {\n logProbeError(group.queueName, result.errorMessage)\n }\n continue\n }\n if (result.ready > 0) {\n if (spawnMode === 'shared') {\n readySharedQueues.push(group.queueName)\n continue\n }\n spawnQueueWorker(group.queueName)\n }\n }\n if (spawnMode === 'shared') {\n if (readySharedQueues.length > 0) {\n spawnSharedWorker(readySharedQueues)\n return\n }\n if (options.shouldStartSharedWorker) {\n try {\n if (await options.shouldStartSharedWorker()) {\n spawnSharedWorker([], true)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[lazy-supervisor] Shared-worker start condition failed: ${message}`)\n }\n }\n }\n }\n\n function scheduleNext(): void {\n if (stopping) return\n pollTimer = setTimeout(() => {\n void (async () => {\n try {\n await tickOnce()\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[lazy-supervisor] Poll cycle failed: ${message}`)\n } finally {\n if (!stopping) scheduleNext()\n }\n })()\n }, options.pollMs)\n pollTimer.unref?.()\n }\n\n // Kick off immediately so the first probe doesn't wait one full poll interval.\n void (async () => {\n try {\n await tickOnce()\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[lazy-supervisor] Initial poll failed: ${message}`)\n } finally {\n scheduleNext()\n }\n })()\n\n async function close(): Promise<void> {\n if (stopping) {\n await done\n return\n }\n stopping = true\n if (pollTimer) {\n clearTimeout(pollTimer)\n pollTimer = null\n }\n\n const exitWaiters: Promise<void>[] = []\n for (const [queueName, child] of activeChildren) {\n exitWaiters.push(\n new Promise<void>((resolve) => {\n if (child.exitCode !== null || child.signalCode !== null) return resolve()\n child.once('exit', () => resolve())\n }),\n )\n try {\n if (!child.killed) child.kill('SIGTERM')\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n logger.warn(`[lazy-supervisor] Failed to send SIGTERM to \"${queueName}\": ${message}`)\n }\n }\n await Promise.all(exitWaiters)\n activeChildren.clear()\n if (watchPromiseResolve) {\n watchPromiseResolve()\n watchPromiseResolve = null\n }\n }\n\n return {\n startedQueues,\n getActiveChild: (queueName) => activeChildren.get(queueName),\n close,\n done,\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/mercato.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { runWorker } from "@open-mercato/queue/worker";
|
|
1
|
+
import { registerWorkerShutdownHook, runWorker } from "@open-mercato/queue/worker";
|
|
2
2
|
import { getCliModules, hasCliModules, registerCliModules } from "./registry.js";
|
|
3
3
|
import { parseBooleanToken } from "@open-mercato/shared/lib/boolean";
|
|
4
4
|
import { getSslConfig } from "@open-mercato/shared/lib/db/ssl";
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
resolveLazySchedulerPollMs,
|
|
23
23
|
resolveLazySchedulerRestart
|
|
24
24
|
} from "./lib/auto-spawn-scheduler.js";
|
|
25
|
-
import { startLazySchedulerSupervisor } from "./lib/scheduler-supervisor.js";
|
|
25
|
+
import { probeEnabledSchedules, startLazySchedulerSupervisor } from "./lib/scheduler-supervisor.js";
|
|
26
26
|
import {
|
|
27
27
|
startInProcessGenerateWatcher
|
|
28
28
|
} from "./lib/in-process-generate-watcher.js";
|
|
@@ -62,6 +62,9 @@ function getRegisteredCliWorkers(modules = getCliModules()) {
|
|
|
62
62
|
}
|
|
63
63
|
return allWorkers;
|
|
64
64
|
}
|
|
65
|
+
function shouldEmbedLocalSchedulerInSharedWorker(env = process.env) {
|
|
66
|
+
return parseBooleanToken(env.OM_DEV_EMBED_SCHEDULER_IN_SHARED_WORKER) === true;
|
|
67
|
+
}
|
|
65
68
|
function padByCodePointWidth(value, targetWidth) {
|
|
66
69
|
const valueWidth = [...value].length;
|
|
67
70
|
if (valueWidth >= targetWidth) return value;
|
|
@@ -1241,6 +1244,7 @@ async function run(argv = process.argv) {
|
|
|
1241
1244
|
command: "worker",
|
|
1242
1245
|
run: async (args) => {
|
|
1243
1246
|
const isAllQueues = args.includes("--all");
|
|
1247
|
+
const runSchedulerInWorker = isAllQueues && args.includes("--with-scheduler");
|
|
1244
1248
|
const queueName = isAllQueues ? null : args[0];
|
|
1245
1249
|
const allWorkers = getRegisteredCliWorkers();
|
|
1246
1250
|
const discoveredQueues = [...new Set(allWorkers.map((w) => w.queue))];
|
|
@@ -1288,6 +1292,22 @@ async function run(argv = process.argv) {
|
|
|
1288
1292
|
});
|
|
1289
1293
|
});
|
|
1290
1294
|
await Promise.all(workerPromises);
|
|
1295
|
+
if (runSchedulerInWorker) {
|
|
1296
|
+
const queueStrategy = process.env.QUEUE_STRATEGY || "local";
|
|
1297
|
+
if (queueStrategy !== "local") {
|
|
1298
|
+
throw new Error("[worker] --with-scheduler is supported only with QUEUE_STRATEGY=local.");
|
|
1299
|
+
}
|
|
1300
|
+
const schedulerContainer = await createRequestContainer();
|
|
1301
|
+
const localScheduler = schedulerContainer.resolve("localSchedulerService");
|
|
1302
|
+
if (!localScheduler?.start || !localScheduler.stop) {
|
|
1303
|
+
throw new Error("[worker] Local scheduler service is unavailable.");
|
|
1304
|
+
}
|
|
1305
|
+
await localScheduler.start();
|
|
1306
|
+
registerWorkerShutdownHook(async () => {
|
|
1307
|
+
await localScheduler.stop?.();
|
|
1308
|
+
});
|
|
1309
|
+
console.log("[worker] Local scheduler started in the shared worker process.");
|
|
1310
|
+
}
|
|
1291
1311
|
console.log("[worker] All workers started. Press Ctrl+C to stop");
|
|
1292
1312
|
await new Promise(() => {
|
|
1293
1313
|
});
|
|
@@ -1694,6 +1714,7 @@ async function run(argv = process.argv) {
|
|
|
1694
1714
|
const autoSpawnSchedulerMode = resolveAutoSpawnSchedulerMode(process.env);
|
|
1695
1715
|
const queueStrategy = process.env.QUEUE_STRATEGY || "local";
|
|
1696
1716
|
const schedulerCommand = lookupModuleCommand(getCliModules(), "scheduler", "start");
|
|
1717
|
+
const embedSchedulerInSharedWorker = shouldEmbedLocalSchedulerInSharedWorker(process.env) && queueStrategy === "local" && autoSpawnWorkersMode === "lazy" && resolveLazySpawnMode(process.env) === "shared" && autoSpawnSchedulerMode !== "off" && schedulerCommand.status === "ok";
|
|
1697
1718
|
const nextRuntime = startNextDev(runtimeEnv);
|
|
1698
1719
|
const restartPromise = waitForDevRestart();
|
|
1699
1720
|
const backgroundStartAbort = new AbortController();
|
|
@@ -1735,7 +1756,20 @@ async function run(argv = process.argv) {
|
|
|
1735
1756
|
workers: discoveredWorkers,
|
|
1736
1757
|
pollMs: resolveLazyPollMs(process.env),
|
|
1737
1758
|
restartOnUnexpectedExit: resolveLazyRestart(process.env),
|
|
1738
|
-
spawnMode: lazySpawnMode
|
|
1759
|
+
spawnMode: lazySpawnMode,
|
|
1760
|
+
...embedSchedulerInSharedWorker ? {
|
|
1761
|
+
sharedWorkerArgs: ["--with-scheduler"],
|
|
1762
|
+
shouldStartSharedWorker: async () => {
|
|
1763
|
+
const schedules = await probeEnabledSchedules(runtimeEnv);
|
|
1764
|
+
return !schedules.error && schedules.enabledSchedules > 0;
|
|
1765
|
+
},
|
|
1766
|
+
...resolveLazySchedulerRestart(process.env) ? {
|
|
1767
|
+
shouldRestartSharedWorker: async () => {
|
|
1768
|
+
const schedules = await probeEnabledSchedules(runtimeEnv);
|
|
1769
|
+
return !schedules.error && schedules.enabledSchedules > 0;
|
|
1770
|
+
}
|
|
1771
|
+
} : {}
|
|
1772
|
+
} : {}
|
|
1739
1773
|
});
|
|
1740
1774
|
} else {
|
|
1741
1775
|
console.log("[server] Eager worker auto-spawn enabled - starting workers for all queues...");
|
|
@@ -1748,7 +1782,9 @@ async function run(argv = process.argv) {
|
|
|
1748
1782
|
waitForManagedProcessExit(workerProcess, formatQueueWorkerLabel(discoveredWorkerQueues)).then(backgroundExitResolve);
|
|
1749
1783
|
}
|
|
1750
1784
|
}
|
|
1751
|
-
if (
|
|
1785
|
+
if (embedSchedulerInSharedWorker && activeLazySupervisor) {
|
|
1786
|
+
console.log("[server] Local scheduler will run inside the lazy shared worker process.");
|
|
1787
|
+
} else if (autoSpawnSchedulerMode !== "off" && queueStrategy === "local") {
|
|
1752
1788
|
if (schedulerCommand.status !== "ok") {
|
|
1753
1789
|
console.log(`[server] Skipping scheduler auto-start \u2014 ${describeMissingModuleCommand(schedulerCommand)}`);
|
|
1754
1790
|
} else if (autoSpawnSchedulerMode === "lazy") {
|