@open-mercato/cli 0.6.6-develop.6389.1.135a466c47 → 0.6.6-develop.6391.1.276b99bf26

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.
@@ -2,6 +2,7 @@
2
2
  Copied create-app/agentic/ → dist/agentic/
3
3
  Discovered 7 standalone guides → dist/agentic/guides/
4
4
  [module-facts] module registry unavailable (registryPath not provided); API route auth omitted for auth
5
+ Generated 9 module fact-sheets → dist/agentic/guides/modules/
5
6
  [module-facts] module registry unavailable (registryPath not provided); API route auth omitted for catalog
6
7
  [module-facts] module registry unavailable (registryPath not provided); API route auth omitted for currencies
7
8
  [module-facts] module registry unavailable (registryPath not provided); API route auth omitted for customer_accounts
@@ -10,6 +11,5 @@ Discovered 7 standalone guides → dist/agentic/guides/
10
11
  [module-facts] module registry unavailable (registryPath not provided); API route auth omitted for integrations
11
12
  [module-facts] module registry unavailable (registryPath not provided); API route auth omitted for sales
12
13
  [module-facts] module registry unavailable (registryPath not provided); API route auth omitted for workflows
13
- Generated 9 module fact-sheets → dist/agentic/guides/modules/
14
14
  Wrote 9 legacy core.<module>.md redirect stubs → dist/agentic/guides/
15
15
  [build:cli] built successfully
@@ -2,7 +2,7 @@
2
2
  "auth": {
3
3
  "title": "Authentication & Accounts",
4
4
  "description": "User accounts, sessions, roles and password resets.",
5
- "coreVersion": "0.6.6-develop.6389.1.135a466c47",
5
+ "coreVersion": "0.6.6-develop.6391.1.276b99bf26",
6
6
  "entities": [
7
7
  {
8
8
  "id": "auth:user",
@@ -191,7 +191,7 @@
191
191
  "catalog": {
192
192
  "title": "Product Catalog",
193
193
  "description": "Configurable catalog for products, variants, and pricing used by the sales module.",
194
- "coreVersion": "0.6.6-develop.6389.1.135a466c47",
194
+ "coreVersion": "0.6.6-develop.6391.1.276b99bf26",
195
195
  "entities": [
196
196
  {
197
197
  "id": "catalog:catalog_option_schema_template",
@@ -405,7 +405,7 @@
405
405
  "currencies": {
406
406
  "title": "Currencies",
407
407
  "description": "Currencies and Exchange rate management",
408
- "coreVersion": "0.6.6-develop.6389.1.135a466c47",
408
+ "coreVersion": "0.6.6-develop.6391.1.276b99bf26",
409
409
  "entities": [
410
410
  {
411
411
  "id": "currencies:currency",
@@ -489,7 +489,7 @@
489
489
  "customer_accounts": {
490
490
  "title": "Customer Identity & Portal Authentication",
491
491
  "description": "Customer-facing authentication with two-tier identity model and full RBAC.",
492
- "coreVersion": "0.6.6-develop.6389.1.135a466c47",
492
+ "coreVersion": "0.6.6-develop.6391.1.276b99bf26",
493
493
  "entities": [
494
494
  {
495
495
  "id": "customer_accounts:customer_user",
@@ -729,7 +729,7 @@
729
729
  "customers": {
730
730
  "title": "Customer Relationship Management",
731
731
  "description": "Core CRM capabilities for people, companies, deals, and activities.",
732
- "coreVersion": "0.6.6-develop.6389.1.135a466c47",
732
+ "coreVersion": "0.6.6-develop.6391.1.276b99bf26",
733
733
  "entities": [
734
734
  {
735
735
  "id": "customers:customer_entity",
@@ -1211,7 +1211,7 @@
1211
1211
  "data_sync": {
1212
1212
  "title": "Data Sync",
1213
1213
  "description": "Streaming data sync hub for import/export integrations.",
1214
- "coreVersion": "0.6.6-develop.6389.1.135a466c47",
1214
+ "coreVersion": "0.6.6-develop.6391.1.276b99bf26",
1215
1215
  "entities": [
1216
1216
  {
1217
1217
  "id": "data_sync:sync_run",
@@ -1289,7 +1289,7 @@
1289
1289
  "integrations": {
1290
1290
  "title": "Integrations",
1291
1291
  "description": "Core integration framework — external ID mapping, status badges, and integration registry.",
1292
- "coreVersion": "0.6.6-develop.6389.1.135a466c47",
1292
+ "coreVersion": "0.6.6-develop.6391.1.276b99bf26",
1293
1293
  "entities": [
1294
1294
  {
1295
1295
  "id": "integrations:sync_external_id_mapping",
@@ -1365,7 +1365,7 @@
1365
1365
  "sales": {
1366
1366
  "title": "Sales Management",
1367
1367
  "description": "Quoting, ordering, fulfillment, and billing capabilities built on modular pricing and tax pipelines.",
1368
- "coreVersion": "0.6.6-develop.6389.1.135a466c47",
1368
+ "coreVersion": "0.6.6-develop.6391.1.276b99bf26",
1369
1369
  "entities": [
1370
1370
  {
1371
1371
  "id": "sales:sales_channel",
@@ -1838,7 +1838,7 @@
1838
1838
  "workflows": {
1839
1839
  "title": "Workflow Engine",
1840
1840
  "description": "Orchestrate business processes with state machines, transitions, and activities",
1841
- "coreVersion": "0.6.6-develop.6389.1.135a466c47",
1841
+ "coreVersion": "0.6.6-develop.6391.1.276b99bf26",
1842
1842
  "entities": [
1843
1843
  {
1844
1844
  "id": "workflows:workflow_definition",
@@ -1,5 +1,5 @@
1
1
  # auth — module facts (generated, do not edit)
2
- <!-- generated from @open-mercato/core 0.6.6-develop.6389.1.135a466c47 — R1 staleness stamp -->
2
+ <!-- generated from @open-mercato/core 0.6.6-develop.6391.1.276b99bf26 — R1 staleness stamp -->
3
3
 
4
4
  ## Entities
5
5
 
@@ -1,5 +1,5 @@
1
1
  # catalog — module facts (generated, do not edit)
2
- <!-- generated from @open-mercato/core 0.6.6-develop.6389.1.135a466c47 — R1 staleness stamp -->
2
+ <!-- generated from @open-mercato/core 0.6.6-develop.6391.1.276b99bf26 — R1 staleness stamp -->
3
3
 
4
4
  ## Entities
5
5
 
@@ -1,5 +1,5 @@
1
1
  # currencies — module facts (generated, do not edit)
2
- <!-- generated from @open-mercato/core 0.6.6-develop.6389.1.135a466c47 — R1 staleness stamp -->
2
+ <!-- generated from @open-mercato/core 0.6.6-develop.6391.1.276b99bf26 — R1 staleness stamp -->
3
3
 
4
4
  ## Entities
5
5
 
@@ -1,5 +1,5 @@
1
1
  # customer_accounts — module facts (generated, do not edit)
2
- <!-- generated from @open-mercato/core 0.6.6-develop.6389.1.135a466c47 — R1 staleness stamp -->
2
+ <!-- generated from @open-mercato/core 0.6.6-develop.6391.1.276b99bf26 — R1 staleness stamp -->
3
3
 
4
4
  ## Entities
5
5
 
@@ -1,5 +1,5 @@
1
1
  # customers — module facts (generated, do not edit)
2
- <!-- generated from @open-mercato/core 0.6.6-develop.6389.1.135a466c47 — R1 staleness stamp -->
2
+ <!-- generated from @open-mercato/core 0.6.6-develop.6391.1.276b99bf26 — R1 staleness stamp -->
3
3
 
4
4
  ## Entities
5
5
 
@@ -1,5 +1,5 @@
1
1
  # data_sync — module facts (generated, do not edit)
2
- <!-- generated from @open-mercato/core 0.6.6-develop.6389.1.135a466c47 — R1 staleness stamp -->
2
+ <!-- generated from @open-mercato/core 0.6.6-develop.6391.1.276b99bf26 — R1 staleness stamp -->
3
3
 
4
4
  ## Entities
5
5
 
@@ -1,5 +1,5 @@
1
1
  # integrations — module facts (generated, do not edit)
2
- <!-- generated from @open-mercato/core 0.6.6-develop.6389.1.135a466c47 — R1 staleness stamp -->
2
+ <!-- generated from @open-mercato/core 0.6.6-develop.6391.1.276b99bf26 — R1 staleness stamp -->
3
3
 
4
4
  ## Entities
5
5
 
@@ -1,5 +1,5 @@
1
1
  # sales — module facts (generated, do not edit)
2
- <!-- generated from @open-mercato/core 0.6.6-develop.6389.1.135a466c47 — R1 staleness stamp -->
2
+ <!-- generated from @open-mercato/core 0.6.6-develop.6391.1.276b99bf26 — R1 staleness stamp -->
3
3
 
4
4
  ## Entities
5
5
 
@@ -1,5 +1,5 @@
1
1
  # workflows — module facts (generated, do not edit)
2
- <!-- generated from @open-mercato/core 0.6.6-develop.6389.1.135a466c47 — R1 staleness stamp -->
2
+ <!-- generated from @open-mercato/core 0.6.6-develop.6391.1.276b99bf26 — R1 staleness stamp -->
3
3
 
4
4
  ## Entities
5
5
 
@@ -23,6 +23,12 @@ function resolveLazyPollMs(env = process.env) {
23
23
  if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_LAZY_POLL_MS;
24
24
  return Math.max(MIN_LAZY_POLL_MS, Math.floor(parsed));
25
25
  }
26
+ function resolveLazySpawnMode(env = process.env) {
27
+ const raw = env.OM_AUTO_SPAWN_WORKERS_LAZY_MODE?.trim().toLowerCase();
28
+ if (raw === "shared") return "shared";
29
+ if (raw === "per-queue") return "per-queue";
30
+ return "per-queue";
31
+ }
26
32
  function resolveLazyRestart(env = process.env) {
27
33
  const parsed = parseBooleanToken(env.OM_AUTO_SPAWN_WORKERS_LAZY_RESTART);
28
34
  return parsed === null ? true : parsed;
@@ -32,6 +38,7 @@ export {
32
38
  resolveAutoSpawnWorkersLazy,
33
39
  resolveAutoSpawnWorkersMode,
34
40
  resolveLazyPollMs,
35
- resolveLazyRestart
41
+ resolveLazyRestart,
42
+ resolveLazySpawnMode
36
43
  };
37
44
  //# sourceMappingURL=auto-spawn-workers.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/auto-spawn-workers.ts"],
4
- "sourcesContent": ["import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\nexport type AutoSpawnWorkersMode = 'off' | 'eager' | 'lazy'\n\nexport type AutoSpawnEnvSource = Pick<NodeJS.ProcessEnv, string> | Record<string, string | undefined>\n\nconst DEFAULT_LAZY_POLL_MS = 1000\nconst MIN_LAZY_POLL_MS = 250\n\nexport function resolveAutoSpawnWorkersEnabled(env: AutoSpawnEnvSource = process.env): boolean {\n const legacy = parseBooleanToken(env.AUTO_SPAWN_WORKERS)\n if (legacy !== null) return legacy\n const aliased = parseBooleanToken(env.OM_AUTO_SPAWN_WORKERS)\n if (aliased !== null) return aliased\n return true\n}\n\nexport function resolveAutoSpawnWorkersLazy(env: AutoSpawnEnvSource = process.env): boolean {\n return parseBooleanToken(env.OM_AUTO_SPAWN_WORKERS_LAZY) === true\n}\n\nexport function resolveAutoSpawnWorkersMode(env: AutoSpawnEnvSource = process.env): AutoSpawnWorkersMode {\n if (!resolveAutoSpawnWorkersEnabled(env)) return 'off'\n if (resolveAutoSpawnWorkersLazy(env)) return 'lazy'\n return 'eager'\n}\n\nexport function resolveLazyPollMs(env: AutoSpawnEnvSource = process.env): number {\n const raw = env.OM_AUTO_SPAWN_WORKERS_LAZY_POLL_MS\n if (typeof raw !== 'string' || raw.trim() === '') return DEFAULT_LAZY_POLL_MS\n const parsed = Number(raw)\n if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_LAZY_POLL_MS\n return Math.max(MIN_LAZY_POLL_MS, Math.floor(parsed))\n}\n\nexport function resolveLazyRestart(env: AutoSpawnEnvSource = process.env): boolean {\n const parsed = parseBooleanToken(env.OM_AUTO_SPAWN_WORKERS_LAZY_RESTART)\n return parsed === null ? true : parsed\n}\n"],
5
- "mappings": "AAAA,SAAS,yBAAyB;AAMlC,MAAM,uBAAuB;AAC7B,MAAM,mBAAmB;AAElB,SAAS,+BAA+B,MAA0B,QAAQ,KAAc;AAC7F,QAAM,SAAS,kBAAkB,IAAI,kBAAkB;AACvD,MAAI,WAAW,KAAM,QAAO;AAC5B,QAAM,UAAU,kBAAkB,IAAI,qBAAqB;AAC3D,MAAI,YAAY,KAAM,QAAO;AAC7B,SAAO;AACT;AAEO,SAAS,4BAA4B,MAA0B,QAAQ,KAAc;AAC1F,SAAO,kBAAkB,IAAI,0BAA0B,MAAM;AAC/D;AAEO,SAAS,4BAA4B,MAA0B,QAAQ,KAA2B;AACvG,MAAI,CAAC,+BAA+B,GAAG,EAAG,QAAO;AACjD,MAAI,4BAA4B,GAAG,EAAG,QAAO;AAC7C,SAAO;AACT;AAEO,SAAS,kBAAkB,MAA0B,QAAQ,KAAa;AAC/E,QAAM,MAAM,IAAI;AAChB,MAAI,OAAO,QAAQ,YAAY,IAAI,KAAK,MAAM,GAAI,QAAO;AACzD,QAAM,SAAS,OAAO,GAAG;AACzB,MAAI,CAAC,OAAO,SAAS,MAAM,KAAK,UAAU,EAAG,QAAO;AACpD,SAAO,KAAK,IAAI,kBAAkB,KAAK,MAAM,MAAM,CAAC;AACtD;AAEO,SAAS,mBAAmB,MAA0B,QAAQ,KAAc;AACjF,QAAM,SAAS,kBAAkB,IAAI,kCAAkC;AACvE,SAAO,WAAW,OAAO,OAAO;AAClC;",
4
+ "sourcesContent": ["import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\n\nexport type AutoSpawnWorkersMode = 'off' | 'eager' | 'lazy'\nexport type LazyWorkerSpawnMode = 'per-queue' | 'shared'\n\nexport type AutoSpawnEnvSource = Pick<NodeJS.ProcessEnv, string> | Record<string, string | undefined>\n\nconst DEFAULT_LAZY_POLL_MS = 1000\nconst MIN_LAZY_POLL_MS = 250\n\nexport function resolveAutoSpawnWorkersEnabled(env: AutoSpawnEnvSource = process.env): boolean {\n const legacy = parseBooleanToken(env.AUTO_SPAWN_WORKERS)\n if (legacy !== null) return legacy\n const aliased = parseBooleanToken(env.OM_AUTO_SPAWN_WORKERS)\n if (aliased !== null) return aliased\n return true\n}\n\nexport function resolveAutoSpawnWorkersLazy(env: AutoSpawnEnvSource = process.env): boolean {\n return parseBooleanToken(env.OM_AUTO_SPAWN_WORKERS_LAZY) === true\n}\n\nexport function resolveAutoSpawnWorkersMode(env: AutoSpawnEnvSource = process.env): AutoSpawnWorkersMode {\n if (!resolveAutoSpawnWorkersEnabled(env)) return 'off'\n if (resolveAutoSpawnWorkersLazy(env)) return 'lazy'\n return 'eager'\n}\n\nexport function resolveLazyPollMs(env: AutoSpawnEnvSource = process.env): number {\n const raw = env.OM_AUTO_SPAWN_WORKERS_LAZY_POLL_MS\n if (typeof raw !== 'string' || raw.trim() === '') return DEFAULT_LAZY_POLL_MS\n const parsed = Number(raw)\n if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_LAZY_POLL_MS\n return Math.max(MIN_LAZY_POLL_MS, Math.floor(parsed))\n}\n\nexport function resolveLazySpawnMode(env: AutoSpawnEnvSource = process.env): LazyWorkerSpawnMode {\n const raw = env.OM_AUTO_SPAWN_WORKERS_LAZY_MODE?.trim().toLowerCase()\n if (raw === 'shared') return 'shared'\n if (raw === 'per-queue') return 'per-queue'\n return 'per-queue'\n}\n\nexport function resolveLazyRestart(env: AutoSpawnEnvSource = process.env): boolean {\n const parsed = parseBooleanToken(env.OM_AUTO_SPAWN_WORKERS_LAZY_RESTART)\n return parsed === null ? true : parsed\n}\n"],
5
+ "mappings": "AAAA,SAAS,yBAAyB;AAOlC,MAAM,uBAAuB;AAC7B,MAAM,mBAAmB;AAElB,SAAS,+BAA+B,MAA0B,QAAQ,KAAc;AAC7F,QAAM,SAAS,kBAAkB,IAAI,kBAAkB;AACvD,MAAI,WAAW,KAAM,QAAO;AAC5B,QAAM,UAAU,kBAAkB,IAAI,qBAAqB;AAC3D,MAAI,YAAY,KAAM,QAAO;AAC7B,SAAO;AACT;AAEO,SAAS,4BAA4B,MAA0B,QAAQ,KAAc;AAC1F,SAAO,kBAAkB,IAAI,0BAA0B,MAAM;AAC/D;AAEO,SAAS,4BAA4B,MAA0B,QAAQ,KAA2B;AACvG,MAAI,CAAC,+BAA+B,GAAG,EAAG,QAAO;AACjD,MAAI,4BAA4B,GAAG,EAAG,QAAO;AAC7C,SAAO;AACT;AAEO,SAAS,kBAAkB,MAA0B,QAAQ,KAAa;AAC/E,QAAM,MAAM,IAAI;AAChB,MAAI,OAAO,QAAQ,YAAY,IAAI,KAAK,MAAM,GAAI,QAAO;AACzD,QAAM,SAAS,OAAO,GAAG;AACzB,MAAI,CAAC,OAAO,SAAS,MAAM,KAAK,UAAU,EAAG,QAAO;AACpD,SAAO,KAAK,IAAI,kBAAkB,KAAK,MAAM,MAAM,CAAC;AACtD;AAEO,SAAS,qBAAqB,MAA0B,QAAQ,KAA0B;AAC/F,QAAM,MAAM,IAAI,iCAAiC,KAAK,EAAE,YAAY;AACpE,MAAI,QAAQ,SAAU,QAAO;AAC7B,MAAI,QAAQ,YAAa,QAAO;AAChC,SAAO;AACT;AAEO,SAAS,mBAAmB,MAA0B,QAAQ,KAAc;AACjF,QAAM,SAAS,kBAAkB,IAAI,kCAAkC;AACvE,SAAO,WAAW,OAAO,OAAO;AAClC;",
6
6
  "names": []
7
7
  }
@@ -28,11 +28,14 @@ const PROBE_ERROR_LOG_THROTTLE_MS = 6e4;
28
28
  function startLazyWorkerSupervisor(options) {
29
29
  const logger = options.logger ?? console;
30
30
  const strategy = options.strategy ?? resolveStrategyFromEnv(options.runtimeEnv);
31
+ const spawnMode = options.spawnMode ?? "per-queue";
31
32
  const probe = options.probeFn ?? ((queueName) => getQueuePendingProbe(queueName, strategy));
32
33
  const groups = groupWorkersByQueue(options.workers);
33
34
  const startedQueues = /* @__PURE__ */ new Set();
34
35
  const activeChildren = /* @__PURE__ */ new Map();
35
36
  const startingQueues = /* @__PURE__ */ new Set();
37
+ let activeSharedChild;
38
+ let startingSharedWorker = false;
36
39
  const probeErrorLastLoggedAt = /* @__PURE__ */ new Map();
37
40
  let stopping = false;
38
41
  let pollTimer = null;
@@ -50,6 +53,20 @@ function startLazyWorkerSupervisor(options) {
50
53
  );
51
54
  }
52
55
  const spawnFn = options.spawnFn ?? ((command, args, opts) => nodeSpawn(command, args, opts));
56
+ const markSharedWorkerStarted = () => {
57
+ for (const group of groups) {
58
+ startedQueues.add(group.queueName);
59
+ activeChildren.set(group.queueName, activeSharedChild);
60
+ }
61
+ };
62
+ const clearSharedWorker = () => {
63
+ for (const group of groups) {
64
+ if (activeChildren.get(group.queueName) === activeSharedChild) {
65
+ activeChildren.delete(group.queueName);
66
+ }
67
+ }
68
+ activeSharedChild = void 0;
69
+ };
53
70
  function logProbeError(queueName, message) {
54
71
  const now = Date.now();
55
72
  const lastAt = probeErrorLastLoggedAt.get(queueName) ?? 0;
@@ -117,6 +134,75 @@ function startLazyWorkerSupervisor(options) {
117
134
  })();
118
135
  });
119
136
  }
137
+ function spawnSharedWorker(triggerQueueNames) {
138
+ if (stopping) return;
139
+ if (activeSharedChild) return;
140
+ if (startingSharedWorker) return;
141
+ startingSharedWorker = true;
142
+ const triggerSummary = triggerQueueNames.length > 0 ? ` after pending job(s) on ${triggerQueueNames.join(", ")}` : "";
143
+ logger.log(`[lazy-supervisor] Pending job detected${triggerSummary} \u2014 starting shared worker for all queues`);
144
+ let child;
145
+ try {
146
+ child = spawnFn(
147
+ "node",
148
+ [options.mercatoBin, "queue", "worker", "--all"],
149
+ {
150
+ stdio: "inherit",
151
+ env: options.runtimeEnv,
152
+ cwd: options.appDir
153
+ }
154
+ );
155
+ } catch (err) {
156
+ startingSharedWorker = false;
157
+ const message = err instanceof Error ? err.message : String(err);
158
+ logger.error(`[lazy-supervisor] Failed to spawn shared worker: ${message}`);
159
+ return;
160
+ }
161
+ activeSharedChild = child;
162
+ markSharedWorkerStarted();
163
+ startingSharedWorker = false;
164
+ for (const group of groups) {
165
+ if (options.onSpawn) {
166
+ try {
167
+ options.onSpawn(group.queueName, child);
168
+ } catch (err) {
169
+ const message = err instanceof Error ? err.message : String(err);
170
+ logger.warn(`[lazy-supervisor] onSpawn callback threw for "${group.queueName}": ${message}`);
171
+ }
172
+ }
173
+ }
174
+ child.on("exit", (code, signal) => {
175
+ clearSharedWorker();
176
+ for (const group of groups) {
177
+ if (options.onChildExit) {
178
+ try {
179
+ options.onChildExit(group.queueName, code, signal);
180
+ } catch (err) {
181
+ const message = err instanceof Error ? err.message : String(err);
182
+ logger.warn(`[lazy-supervisor] onChildExit callback threw for "${group.queueName}": ${message}`);
183
+ }
184
+ }
185
+ }
186
+ if (stopping) return;
187
+ const expected = signal === "SIGTERM" || signal === "SIGINT";
188
+ if (expected) return;
189
+ const reason = code !== null ? `code ${code}` : `signal ${signal ?? "unknown"}`;
190
+ logger.warn(`[lazy-supervisor] Shared worker exited (${reason}).`);
191
+ if (!options.restartOnUnexpectedExit) return;
192
+ void (async () => {
193
+ const readyQueues = [];
194
+ for (const group of groups) {
195
+ const result = await safeProbe(group.queueName);
196
+ if (!result || result.error) continue;
197
+ if (result.ready > 0) readyQueues.push(group.queueName);
198
+ }
199
+ if (readyQueues.length > 0) {
200
+ logger.warn("[lazy-supervisor] Restarting shared worker because jobs remain pending.");
201
+ spawnSharedWorker(readyQueues);
202
+ }
203
+ })();
204
+ });
205
+ }
120
206
  async function safeProbe(queueName) {
121
207
  try {
122
208
  return await probe(queueName, strategy);
@@ -129,6 +215,7 @@ function startLazyWorkerSupervisor(options) {
129
215
  async function tickOnce() {
130
216
  if (stopping) return;
131
217
  if (groups.length === 0) return;
218
+ if (spawnMode === "shared" && (activeSharedChild || startingSharedWorker)) return;
132
219
  const toCheck = groups.filter((g) => !activeChildren.has(g.queueName) && !startingQueues.has(g.queueName));
133
220
  if (toCheck.length === 0) return;
134
221
  const probes = await Promise.all(
@@ -138,6 +225,7 @@ function startLazyWorkerSupervisor(options) {
138
225
  }))
139
226
  );
140
227
  if (stopping) return;
228
+ const readySharedQueues = [];
141
229
  for (const { group, result } of probes) {
142
230
  if (!result) continue;
143
231
  if (result.error) {
@@ -147,9 +235,16 @@ function startLazyWorkerSupervisor(options) {
147
235
  continue;
148
236
  }
149
237
  if (result.ready > 0) {
238
+ if (spawnMode === "shared") {
239
+ readySharedQueues.push(group.queueName);
240
+ continue;
241
+ }
150
242
  spawnQueueWorker(group.queueName);
151
243
  }
152
244
  }
245
+ if (spawnMode === "shared" && readySharedQueues.length > 0) {
246
+ spawnSharedWorker(readySharedQueues);
247
+ }
153
248
  }
154
249
  function scheduleNext() {
155
250
  if (stopping) return;
@@ -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'\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 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 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 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 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 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\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 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 spawnQueueWorker(group.queueName)\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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",
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": "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",
6
6
  "names": []
7
7
  }
package/dist/mercato.js CHANGED
@@ -6,6 +6,7 @@ import { getRedisUrl, getRedisUrlOrThrow } from "@open-mercato/shared/lib/redis/
6
6
  import { resolveInitDerivedSecrets } from "./lib/init-secrets.js";
7
7
  import {
8
8
  resolveAutoSpawnWorkersMode,
9
+ resolveLazySpawnMode,
9
10
  resolveLazyPollMs,
10
11
  resolveLazyRestart
11
12
  } from "./lib/auto-spawn-workers.js";
@@ -1723,14 +1724,17 @@ async function run(argv = process.argv) {
1723
1724
  if (discoveredWorkerQueues.length === 0) {
1724
1725
  console.error("[server] AUTO_SPAWN_WORKERS is enabled, but no queues were discovered from CLI modules. Run `yarn generate` and verify `.mercato/generated/modules.cli.generated.ts` contains worker entries. Continuing without auto-spawned workers.");
1725
1726
  } else if (autoSpawnWorkersMode === "lazy") {
1726
- console.log(`[server] Lazy worker auto-spawn enabled \u2014 workers will start on first job (${discoveredWorkerQueues.length} queue(s) watched).`);
1727
+ const lazySpawnMode = resolveLazySpawnMode(process.env);
1728
+ const lazyModeHint = lazySpawnMode === "shared" ? "shared worker mode \u2014 one `queue worker --all` process; set OM_AUTO_SPAWN_WORKERS_LAZY_MODE=per-queue to change" : "per-queue worker mode \u2014 one process per queue; set OM_AUTO_SPAWN_WORKERS_LAZY_MODE=shared to change";
1729
+ console.log(`[server] Lazy worker auto-spawn enabled \u2014 workers will start on first job (${discoveredWorkerQueues.length} queue(s) watched, ${lazyModeHint}).`);
1727
1730
  activeLazySupervisor = startLazyWorkerSupervisor({
1728
1731
  mercatoBin,
1729
1732
  appDir,
1730
1733
  runtimeEnv,
1731
1734
  workers: discoveredWorkers,
1732
1735
  pollMs: resolveLazyPollMs(process.env),
1733
- restartOnUnexpectedExit: resolveLazyRestart(process.env)
1736
+ restartOnUnexpectedExit: resolveLazyRestart(process.env),
1737
+ spawnMode: lazySpawnMode
1734
1738
  });
1735
1739
  } else {
1736
1740
  console.log("[server] Eager worker auto-spawn enabled - starting workers for all queues...");
@@ -1899,14 +1903,17 @@ async function run(argv = process.argv) {
1899
1903
  if (discoveredWorkerQueues.length === 0) {
1900
1904
  console.error("[server] AUTO_SPAWN_WORKERS is enabled, but no queues were discovered from CLI modules. Run `yarn generate` and verify `.mercato/generated/modules.cli.generated.ts` contains worker entries. Continuing without auto-spawned workers.");
1901
1905
  } else if (autoSpawnWorkersMode === "lazy") {
1902
- console.log(`[server] Lazy worker auto-spawn enabled \u2014 workers will start on first job (${discoveredWorkerQueues.length} queue(s) watched).`);
1906
+ const lazySpawnMode = resolveLazySpawnMode(process.env);
1907
+ const lazyModeHint = lazySpawnMode === "shared" ? "shared worker mode \u2014 one `queue worker --all` process; set OM_AUTO_SPAWN_WORKERS_LAZY_MODE=per-queue to change" : "per-queue worker mode \u2014 one process per queue; set OM_AUTO_SPAWN_WORKERS_LAZY_MODE=shared to change";
1908
+ console.log(`[server] Lazy worker auto-spawn enabled \u2014 workers will start on first job (${discoveredWorkerQueues.length} queue(s) watched, ${lazyModeHint}).`);
1903
1909
  activeLazySupervisor = startLazyWorkerSupervisor({
1904
1910
  mercatoBin,
1905
1911
  appDir,
1906
1912
  runtimeEnv,
1907
1913
  workers: discoveredWorkers,
1908
1914
  pollMs: resolveLazyPollMs(process.env),
1909
- restartOnUnexpectedExit: resolveLazyRestart(process.env)
1915
+ restartOnUnexpectedExit: resolveLazyRestart(process.env),
1916
+ spawnMode: lazySpawnMode
1910
1917
  });
1911
1918
  } else {
1912
1919
  console.log("[server] Eager worker auto-spawn enabled - starting workers for all queues...");