@open-mercato/cli 0.10.1-develop.7841.1.af87ac614d → 0.10.1-develop.7857.1.259928eb85

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/agentic/guides/module-facts.json +170 -169
  2. package/dist/agentic/guides/module-facts.v2.json +170 -169
  3. package/dist/agentic/guides/modules/agent_orchestrator/index.md +1 -1
  4. package/dist/agentic/guides/modules/ai_assistant/index.md +1 -1
  5. package/dist/agentic/guides/modules/api_docs/index.md +1 -1
  6. package/dist/agentic/guides/modules/api_keys/index.md +1 -1
  7. package/dist/agentic/guides/modules/attachments/index.md +1 -1
  8. package/dist/agentic/guides/modules/audit_logs/index.md +1 -1
  9. package/dist/agentic/guides/modules/auth/index.md +1 -1
  10. package/dist/agentic/guides/modules/availability/index.md +1 -1
  11. package/dist/agentic/guides/modules/business_rules/index.md +1 -1
  12. package/dist/agentic/guides/modules/carrier_inpost/index.md +1 -1
  13. package/dist/agentic/guides/modules/cart/index.md +1 -1
  14. package/dist/agentic/guides/modules/catalog/index.md +1 -1
  15. package/dist/agentic/guides/modules/channel_apns/index.md +1 -1
  16. package/dist/agentic/guides/modules/channel_discord/index.md +1 -1
  17. package/dist/agentic/guides/modules/channel_expo/index.md +1 -1
  18. package/dist/agentic/guides/modules/channel_fcm/index.md +1 -1
  19. package/dist/agentic/guides/modules/channel_gmail/index.md +1 -1
  20. package/dist/agentic/guides/modules/channel_imap/index.md +1 -1
  21. package/dist/agentic/guides/modules/channel_resend/index.md +1 -1
  22. package/dist/agentic/guides/modules/channel_ses/index.md +1 -1
  23. package/dist/agentic/guides/modules/channel_smtp/index.md +1 -1
  24. package/dist/agentic/guides/modules/checkout/index.md +1 -1
  25. package/dist/agentic/guides/modules/communication_channels/index.md +1 -1
  26. package/dist/agentic/guides/modules/configs/index.md +1 -1
  27. package/dist/agentic/guides/modules/content/index.md +1 -1
  28. package/dist/agentic/guides/modules/currencies/index.md +1 -1
  29. package/dist/agentic/guides/modules/customer_accounts/index.md +1 -1
  30. package/dist/agentic/guides/modules/customer_groups/index.md +1 -1
  31. package/dist/agentic/guides/modules/customers/index.md +1 -1
  32. package/dist/agentic/guides/modules/dashboards/index.md +1 -1
  33. package/dist/agentic/guides/modules/data_sync/index.md +1 -1
  34. package/dist/agentic/guides/modules/design_system/index.md +1 -1
  35. package/dist/agentic/guides/modules/devices/index.md +1 -1
  36. package/dist/agentic/guides/modules/dictionaries/index.md +1 -1
  37. package/dist/agentic/guides/modules/directory/active-extension-bindings.md +1 -1
  38. package/dist/agentic/guides/modules/directory/index.md +1 -1
  39. package/dist/agentic/guides/modules/documents/index.md +1 -1
  40. package/dist/agentic/guides/modules/ecommerce/index.md +1 -1
  41. package/dist/agentic/guides/modules/entities/index.md +1 -1
  42. package/dist/agentic/guides/modules/eudr/index.md +1 -1
  43. package/dist/agentic/guides/modules/events/index.md +1 -1
  44. package/dist/agentic/guides/modules/feature_toggles/index.md +1 -1
  45. package/dist/agentic/guides/modules/forms/index.md +1 -1
  46. package/dist/agentic/guides/modules/gateway_autopay/index.md +1 -1
  47. package/dist/agentic/guides/modules/gateway_stripe/index.md +1 -1
  48. package/dist/agentic/guides/modules/gateway_tpay/index.md +1 -1
  49. package/dist/agentic/guides/modules/generators/index.md +1 -1
  50. package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
  51. package/dist/agentic/guides/modules/integrations/index.md +1 -1
  52. package/dist/agentic/guides/modules/messages/index.md +1 -1
  53. package/dist/agentic/guides/modules/notifications/index.md +1 -1
  54. package/dist/agentic/guides/modules/onboarding/index.md +1 -1
  55. package/dist/agentic/guides/modules/payment_gateways/index.md +1 -1
  56. package/dist/agentic/guides/modules/perspectives/index.md +1 -1
  57. package/dist/agentic/guides/modules/phone_calls/index.md +1 -1
  58. package/dist/agentic/guides/modules/planner/index.md +1 -1
  59. package/dist/agentic/guides/modules/portal/index.md +1 -1
  60. package/dist/agentic/guides/modules/progress/index.md +1 -1
  61. package/dist/agentic/guides/modules/promotions/index.md +1 -1
  62. package/dist/agentic/guides/modules/push_notifications/index.md +1 -1
  63. package/dist/agentic/guides/modules/query_index/index.md +1 -1
  64. package/dist/agentic/guides/modules/record_locks/index.md +1 -1
  65. package/dist/agentic/guides/modules/resources/index.md +1 -1
  66. package/dist/agentic/guides/modules/sales/index.md +1 -1
  67. package/dist/agentic/guides/modules/scheduler/index.md +1 -1
  68. package/dist/agentic/guides/modules/search/index.md +1 -1
  69. package/dist/agentic/guides/modules/security/index.md +1 -1
  70. package/dist/agentic/guides/modules/seeds/index.md +1 -1
  71. package/dist/agentic/guides/modules/shipping_carriers/index.md +1 -1
  72. package/dist/agentic/guides/modules/sso/index.md +1 -1
  73. package/dist/agentic/guides/modules/staff/index.md +1 -1
  74. package/dist/agentic/guides/modules/storage_s3/di-registrations-rich.md +3 -3
  75. package/dist/agentic/guides/modules/storage_s3/di-service-tokens.md +1 -1
  76. package/dist/agentic/guides/modules/storage_s3/exact-override-targets.md +3 -3
  77. package/dist/agentic/guides/modules/storage_s3/index.md +1 -1
  78. package/dist/agentic/guides/modules/sync_akeneo/index.md +1 -1
  79. package/dist/agentic/guides/modules/sync_excel/index.md +1 -1
  80. package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
  81. package/dist/agentic/guides/modules/telemetry/index.md +1 -1
  82. package/dist/agentic/guides/modules/tillio/index.md +1 -1
  83. package/dist/agentic/guides/modules/translations/index.md +1 -1
  84. package/dist/agentic/guides/modules/warranty_claims/index.md +1 -1
  85. package/dist/agentic/guides/modules/webhooks/index.md +1 -1
  86. package/dist/agentic/guides/modules/wms/index.md +1 -1
  87. package/dist/agentic/guides/modules/wms/owned-contract-module-metadata.md +1 -1
  88. package/dist/agentic/guides/modules/workflows/index.md +1 -1
  89. package/dist/agentic/guides/reference-module-facts.json +1 -1
  90. package/dist/agentic/guides/upstream/manifest.json +1 -1
  91. package/dist/lib/queue-worker-supervisor.js +3 -0
  92. package/dist/lib/queue-worker-supervisor.js.map +2 -2
  93. package/jest.config.cjs +1 -1
  94. package/package.json +7 -7
  95. package/src/lib/__tests__/queue-worker-supervisor.test.ts +145 -1
  96. package/src/lib/queue-worker-supervisor.ts +3 -0
@@ -1,5 +1,5 @@
1
1
  # sso — module facts (generated, do not edit)
2
- <!-- generated from @open-mercato/enterprise 0.10.1-develop.7841.1.af87ac614d; core 0.10.1-develop.7841.1.af87ac614d — R1 staleness stamp -->
2
+ <!-- generated from @open-mercato/enterprise 0.10.1-develop.7857.1.259928eb85; core 0.10.1-develop.7857.1.259928eb85 — R1 staleness stamp -->
3
3
  Source root: node_modules/@open-mercato/enterprise/src/modules/sso
4
4
 
5
5
  ## Sections
@@ -1,5 +1,5 @@
1
1
  # staff — module facts (generated, do not edit)
2
- <!-- generated from @open-mercato/core 0.10.1-develop.7841.1.af87ac614d; core 0.10.1-develop.7841.1.af87ac614d — R1 staleness stamp -->
2
+ <!-- generated from @open-mercato/core 0.10.1-develop.7857.1.259928eb85; core 0.10.1-develop.7857.1.259928eb85 — R1 staleness stamp -->
3
3
  Source root: node_modules/@open-mercato/core/src/modules/staff
4
4
 
5
5
  ## Sections
@@ -6,8 +6,8 @@
6
6
 
7
7
  | Token | Kind | Lifetime | Injection | Provider | Source |
8
8
  |---|---|---|---|---|---|
9
- | s3HealthCheck | value | — | — | — | [di.ts:71](../../../../node_modules/@open-mercato/storage-s3/src/modules/storage_s3/di.ts#L71) |
10
- | storageS3QuotaRecoveryScheduler | value | — | — | — | [di.ts:72](../../../../node_modules/@open-mercato/storage-s3/src/modules/storage_s3/di.ts#L72) |
11
- | storageService | function | scoped | proxy | — | [di.ts:73](../../../../node_modules/@open-mercato/storage-s3/src/modules/storage_s3/di.ts#L73) |
9
+ | s3HealthCheck | value | — | — | — | [di.ts:76](../../../../node_modules/@open-mercato/storage-s3/src/modules/storage_s3/di.ts#L76) |
10
+ | storageS3QuotaRecoveryScheduler | value | — | — | — | [di.ts:77](../../../../node_modules/@open-mercato/storage-s3/src/modules/storage_s3/di.ts#L77) |
11
+ | storageService | function | scoped | proxy | — | [di.ts:78](../../../../node_modules/@open-mercato/storage-s3/src/modules/storage_s3/di.ts#L78) |
12
12
 
13
13
  <!-- end module facts section: storage_s3/di-registrations-rich -->
@@ -6,6 +6,6 @@
6
6
 
7
7
  | Token | Source |
8
8
  |---|---|
9
- | storageService | [di.ts:73](../../../../node_modules/@open-mercato/storage-s3/src/modules/storage_s3/di.ts#L73) |
9
+ | storageService | [di.ts:78](../../../../node_modules/@open-mercato/storage-s3/src/modules/storage_s3/di.ts#L78) |
10
10
 
11
11
  <!-- end module facts section: storage_s3/di-service-tokens -->
@@ -9,9 +9,9 @@
9
9
  | acl | `overrides.acl.features["storage_providers.manage"]` | disable-replace | aclFeatures:storage_providers.manage | [acl.ts](../../../../node_modules/@open-mercato/storage-s3/src/modules/storage_s3/acl.ts) |
10
10
  | cli | `overrides.cli["configure-from-env"]` | disable-replace | cliCommands:configure-from-env | [cli.ts](../../../../node_modules/@open-mercato/storage-s3/src/modules/storage_s3/cli.ts) |
11
11
  | cli | `overrides.cli["help"]` | disable-replace | cliCommands:help | [cli.ts](../../../../node_modules/@open-mercato/storage-s3/src/modules/storage_s3/cli.ts) |
12
- | di | `overrides.di["s3HealthCheck"]` | disable-replace | ownedContracts.di-registration:s3HealthCheck | [di.ts:71](../../../../node_modules/@open-mercato/storage-s3/src/modules/storage_s3/di.ts#L71) |
13
- | di | `overrides.di["storageS3QuotaRecoveryScheduler"]` | disable-replace | ownedContracts.di-registration:storageS3QuotaRecoveryScheduler | [di.ts:72](../../../../node_modules/@open-mercato/storage-s3/src/modules/storage_s3/di.ts#L72) |
14
- | di | `overrides.di["storageService"]` | disable-replace | ownedContracts.di-registration:storageService | [di.ts:73](../../../../node_modules/@open-mercato/storage-s3/src/modules/storage_s3/di.ts#L73) |
12
+ | di | `overrides.di["s3HealthCheck"]` | disable-replace | ownedContracts.di-registration:s3HealthCheck | [di.ts:76](../../../../node_modules/@open-mercato/storage-s3/src/modules/storage_s3/di.ts#L76) |
13
+ | di | `overrides.di["storageS3QuotaRecoveryScheduler"]` | disable-replace | ownedContracts.di-registration:storageS3QuotaRecoveryScheduler | [di.ts:77](../../../../node_modules/@open-mercato/storage-s3/src/modules/storage_s3/di.ts#L77) |
14
+ | di | `overrides.di["storageService"]` | disable-replace | ownedContracts.di-registration:storageService | [di.ts:78](../../../../node_modules/@open-mercato/storage-s3/src/modules/storage_s3/di.ts#L78) |
15
15
  | setup | `overrides.setup.defaultRoleFeatures` | replace | ownedContracts.setup:storage_s3:setup | [setup.ts:11](../../../../node_modules/@open-mercato/storage-s3/src/modules/storage_s3/setup.ts#L11) |
16
16
  | setup | `overrides.setup.onTenantCreated` | replace | ownedContracts.setup:storage_s3:setup | [setup.ts:11](../../../../node_modules/@open-mercato/storage-s3/src/modules/storage_s3/setup.ts#L11) |
17
17
  | workers | `overrides.workers["storage_s3:quota-recovery"]` | disable-replace | ownedContracts.worker:storage_s3:quota-recovery | [workers/quota-recovery.ts:6](../../../../node_modules/@open-mercato/storage-s3/src/modules/storage_s3/workers/quota-recovery.ts#L6) |
@@ -1,5 +1,5 @@
1
1
  # storage_s3 — module facts (generated, do not edit)
2
- <!-- generated from @open-mercato/storage-s3 0.10.1-develop.7841.1.af87ac614d; core 0.10.1-develop.7841.1.af87ac614d — R1 staleness stamp -->
2
+ <!-- generated from @open-mercato/storage-s3 0.10.1-develop.7857.1.259928eb85; core 0.10.1-develop.7857.1.259928eb85 — R1 staleness stamp -->
3
3
  Source root: node_modules/@open-mercato/storage-s3/src/modules/storage_s3
4
4
 
5
5
  ## Sections
@@ -1,5 +1,5 @@
1
1
  # sync_akeneo — module facts (generated, do not edit)
2
- <!-- generated from @open-mercato/sync-akeneo 0.10.1-develop.7841.1.af87ac614d; core 0.10.1-develop.7841.1.af87ac614d — R1 staleness stamp -->
2
+ <!-- generated from @open-mercato/sync-akeneo 0.10.1-develop.7857.1.259928eb85; core 0.10.1-develop.7857.1.259928eb85 — R1 staleness stamp -->
3
3
  Source root: node_modules/@open-mercato/sync-akeneo/src/modules/sync_akeneo
4
4
 
5
5
  ## Sections
@@ -1,5 +1,5 @@
1
1
  # sync_excel — module facts (generated, do not edit)
2
- <!-- generated from @open-mercato/core 0.10.1-develop.7841.1.af87ac614d; core 0.10.1-develop.7841.1.af87ac614d — R1 staleness stamp -->
2
+ <!-- generated from @open-mercato/core 0.10.1-develop.7857.1.259928eb85; core 0.10.1-develop.7857.1.259928eb85 — R1 staleness stamp -->
3
3
  Source root: node_modules/@open-mercato/core/src/modules/sync_excel
4
4
 
5
5
  ## Sections
@@ -1,5 +1,5 @@
1
1
  # system_status_overlays — module facts (generated, do not edit)
2
- <!-- generated from @open-mercato/enterprise 0.10.1-develop.7841.1.af87ac614d; core 0.10.1-develop.7841.1.af87ac614d — R1 staleness stamp -->
2
+ <!-- generated from @open-mercato/enterprise 0.10.1-develop.7857.1.259928eb85; core 0.10.1-develop.7857.1.259928eb85 — R1 staleness stamp -->
3
3
  Source root: node_modules/@open-mercato/enterprise/src/modules/system_status_overlays
4
4
 
5
5
  ## Sections
@@ -1,5 +1,5 @@
1
1
  # telemetry — module facts (generated, do not edit)
2
- <!-- generated from @open-mercato/telemetry 0.10.1-develop.7841.1.af87ac614d; core 0.10.1-develop.7841.1.af87ac614d — R1 staleness stamp -->
2
+ <!-- generated from @open-mercato/telemetry 0.10.1-develop.7857.1.259928eb85; core 0.10.1-develop.7857.1.259928eb85 — R1 staleness stamp -->
3
3
  Source root: node_modules/@open-mercato/telemetry/src/modules/telemetry
4
4
 
5
5
  ## Sections
@@ -1,5 +1,5 @@
1
1
  # tillio — module facts (generated, do not edit)
2
- <!-- generated from @open-mercato/tillio 0.10.1-develop.7841.1.af87ac614d; core 0.10.1-develop.7841.1.af87ac614d — R1 staleness stamp -->
2
+ <!-- generated from @open-mercato/tillio 0.10.1-develop.7857.1.259928eb85; core 0.10.1-develop.7857.1.259928eb85 — R1 staleness stamp -->
3
3
  Source root: node_modules/@open-mercato/tillio/src/modules/tillio
4
4
 
5
5
  ## Sections
@@ -1,5 +1,5 @@
1
1
  # translations — module facts (generated, do not edit)
2
- <!-- generated from @open-mercato/core 0.10.1-develop.7841.1.af87ac614d; core 0.10.1-develop.7841.1.af87ac614d — R1 staleness stamp -->
2
+ <!-- generated from @open-mercato/core 0.10.1-develop.7857.1.259928eb85; core 0.10.1-develop.7857.1.259928eb85 — R1 staleness stamp -->
3
3
  Source root: node_modules/@open-mercato/core/src/modules/translations
4
4
 
5
5
  ## Sections
@@ -1,5 +1,5 @@
1
1
  # warranty_claims — module facts (generated, do not edit)
2
- <!-- generated from @open-mercato/core 0.10.1-develop.7841.1.af87ac614d; core 0.10.1-develop.7841.1.af87ac614d — R1 staleness stamp -->
2
+ <!-- generated from @open-mercato/core 0.10.1-develop.7857.1.259928eb85; core 0.10.1-develop.7857.1.259928eb85 — R1 staleness stamp -->
3
3
  Source root: node_modules/@open-mercato/core/src/modules/warranty_claims
4
4
 
5
5
  ## Sections
@@ -1,5 +1,5 @@
1
1
  # webhooks — module facts (generated, do not edit)
2
- <!-- generated from @open-mercato/webhooks 0.10.1-develop.7841.1.af87ac614d; core 0.10.1-develop.7841.1.af87ac614d — R1 staleness stamp -->
2
+ <!-- generated from @open-mercato/webhooks 0.10.1-develop.7857.1.259928eb85; core 0.10.1-develop.7857.1.259928eb85 — R1 staleness stamp -->
3
3
  Source root: node_modules/@open-mercato/webhooks/src/modules/webhooks
4
4
 
5
5
  ## Sections
@@ -1,5 +1,5 @@
1
1
  # wms — module facts (generated, do not edit)
2
- <!-- generated from @open-mercato/core 0.10.1-develop.7841.1.af87ac614d; core 0.10.1-develop.7841.1.af87ac614d — R1 staleness stamp -->
2
+ <!-- generated from @open-mercato/core 0.10.1-develop.7857.1.259928eb85; core 0.10.1-develop.7857.1.259928eb85 — R1 staleness stamp -->
3
3
  Source root: node_modules/@open-mercato/core/src/modules/wms
4
4
 
5
5
  ## Sections
@@ -6,6 +6,6 @@
6
6
 
7
7
  | ID | Metadata | Source |
8
8
  |---|---|---|
9
- | wms | ejectable=true; name=wms; requires=catalog, sales, feature_toggles; version=0.1.0 | [index.ts:4](../../../../node_modules/@open-mercato/core/src/modules/wms/index.ts#L4) |
9
+ | wms | ejectable=true; name=wms; requires=catalog, customers, sales, feature_toggles; version=0.1.0 | [index.ts:4](../../../../node_modules/@open-mercato/core/src/modules/wms/index.ts#L4) |
10
10
 
11
11
  <!-- end module facts section: wms/owned-contract-module-metadata -->
@@ -1,5 +1,5 @@
1
1
  # workflows — module facts (generated, do not edit)
2
- <!-- generated from @open-mercato/core 0.10.1-develop.7841.1.af87ac614d; core 0.10.1-develop.7841.1.af87ac614d — R1 staleness stamp -->
2
+ <!-- generated from @open-mercato/core 0.10.1-develop.7857.1.259928eb85; core 0.10.1-develop.7857.1.259928eb85 — R1 staleness stamp -->
3
3
  Source root: node_modules/@open-mercato/core/src/modules/workflows
4
4
 
5
5
  ## Sections
@@ -1185,7 +1185,7 @@
1185
1185
  "facts": {
1186
1186
  "title": "Example Module",
1187
1187
  "description": "Demonstrates pages, APIs, entities and DI.",
1188
- "coreVersion": "0.10.1-develop.7841.1.af87ac614d",
1188
+ "coreVersion": "0.10.1-develop.7857.1.259928eb85",
1189
1189
  "sourcePackage": null,
1190
1190
  "sourceVersion": null,
1191
1191
  "sourceRoot": "src/modules/example",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "generator": "@open-mercato/cli@0.10.1-develop.7841.1.af87ac614d",
3
+ "generator": "@open-mercato/cli@0.10.1-develop.7857.1.259928eb85",
4
4
  "files": {
5
5
  "AGENTS.md": "b27342ee5f61e805ba52703fdb7ec822b030490f734b4d80f48dfd1e05fa4cf5",
6
6
  "BACKWARD_COMPATIBILITY.md": "d47c2fbbf55f4363511576ac9ad009895afb5f4433c5b1527342e2ea1037b701"
@@ -312,7 +312,10 @@ function startLazyWorkerSupervisor(options) {
312
312
  pollTimer = null;
313
313
  }
314
314
  const exitWaiters = [];
315
+ const closingChildren = /* @__PURE__ */ new Set();
315
316
  for (const [queueName, child] of activeChildren) {
317
+ if (closingChildren.has(child)) continue;
318
+ closingChildren.add(child);
316
319
  exitWaiters.push(
317
320
  new Promise((resolve) => {
318
321
  if (child.exitCode !== null || child.signalCode !== null) return resolve();
@@ -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 SupervisorWorkerDescriptor = Pick<\n ModuleWorker,\n 'id' | 'moduleId' | 'queue' | 'concurrency'\n>\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: readonly SupervisorWorkerDescriptor[]\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: readonly SupervisorWorkerDescriptor[]): 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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",
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 SupervisorWorkerDescriptor = Pick<\n ModuleWorker,\n 'id' | 'moduleId' | 'queue' | 'concurrency'\n>\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: readonly SupervisorWorkerDescriptor[]\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: readonly SupervisorWorkerDescriptor[]): 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 const closingChildren = new Set<ChildProcess>()\n for (const [queueName, child] of activeChildren) {\n if (closingChildren.has(child)) continue\n closingChildren.add(child)\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/jest.config.cjs CHANGED
@@ -26,7 +26,7 @@ module.exports = {
26
26
  '^@open-mercato/queue/(.*)$': '<rootDir>/../queue/src/$1',
27
27
  },
28
28
  transformIgnorePatterns: [
29
- 'node_modules/(?!(@mikro-orm|kysely|ai|@ai-sdk|ai-sdk-ollama|@workflow|@standard-schema|@tanstack/react-table|@tanstack/table-core|@tanstack/react-store|@tanstack/store)/)',
29
+ 'node_modules/(?!(sanitize-html|htmlparser2|domhandler|domutils|dom-serializer|domelementtype|entities|@mikro-orm|kysely|ai|@ai-sdk|ai-sdk-ollama|@workflow|@standard-schema|@tanstack/react-table|@tanstack/table-core|@tanstack/react-store|@tanstack/store)/)',
30
30
  ],
31
31
  testMatch: ['<rootDir>/src/**/__tests__/**/*.test.(ts|tsx)'],
32
32
  passWithNoTests: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/cli",
3
- "version": "0.10.1-develop.7841.1.af87ac614d",
3
+ "version": "0.10.1-develop.7857.1.259928eb85",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -60,10 +60,10 @@
60
60
  "@mikro-orm/decorators": "^7.1.14",
61
61
  "@mikro-orm/migrations": "^7.1.14",
62
62
  "@mikro-orm/postgresql": "^7.1.14",
63
- "@open-mercato/events": "0.10.1-develop.7841.1.af87ac614d",
64
- "@open-mercato/queue": "0.10.1-develop.7841.1.af87ac614d",
65
- "@open-mercato/shared": "0.10.1-develop.7841.1.af87ac614d",
66
- "@open-mercato/telemetry": "0.10.1-develop.7841.1.af87ac614d",
63
+ "@open-mercato/events": "0.10.1-develop.7857.1.259928eb85",
64
+ "@open-mercato/queue": "0.10.1-develop.7857.1.259928eb85",
65
+ "@open-mercato/shared": "0.10.1-develop.7857.1.259928eb85",
66
+ "@open-mercato/telemetry": "0.10.1-develop.7857.1.259928eb85",
67
67
  "cross-spawn": "^7.0.6",
68
68
  "minisearch": "7.2.0",
69
69
  "pg": "8.23.0",
@@ -75,10 +75,10 @@
75
75
  "typescript-js": "npm:typescript@6.0.3"
76
76
  },
77
77
  "peerDependencies": {
78
- "@open-mercato/shared": "0.10.1-develop.7841.1.af87ac614d"
78
+ "@open-mercato/shared": "0.10.1-develop.7857.1.259928eb85"
79
79
  },
80
80
  "devDependencies": {
81
- "@open-mercato/shared": "0.10.1-develop.7841.1.af87ac614d",
81
+ "@open-mercato/shared": "0.10.1-develop.7857.1.259928eb85",
82
82
  "@types/jest": "^30.0.0",
83
83
  "jest": "^30.4.2",
84
84
  "ts-jest": "^29.4.12"
@@ -1,5 +1,5 @@
1
1
  import { EventEmitter } from 'node:events'
2
- import type { ChildProcess } from 'node:child_process'
2
+ import { ChildProcess } from 'node:child_process'
3
3
  import type { ModuleWorker } from '@open-mercato/shared/modules/registry'
4
4
  import {
5
5
  startLazyWorkerSupervisor,
@@ -203,6 +203,150 @@ describe('startLazyWorkerSupervisor', () => {
203
203
  expect((child as any).kill).toHaveBeenCalledWith('SIGTERM')
204
204
  })
205
205
 
206
+ it.each(['shared', 'per-queue'] as const)(
207
+ 'waits once per child for deferred exits in %s mode across repeated lifecycles',
208
+ async (spawnMode) => {
209
+ for (let cycle = 0; cycle < 3; cycle += 1) {
210
+ const children: ChildProcess[] = []
211
+ const spawnFn = jest.fn(() => {
212
+ const child = new ChildProcess()
213
+ jest.spyOn(child, 'kill').mockReturnValue(true)
214
+ children.push(child)
215
+ return child
216
+ })
217
+ const queueNames = Array.from({ length: 12 }, (_, index) => `queue-${index}`)
218
+ const onChildExit = jest.fn()
219
+ const handle = startLazyWorkerSupervisor({
220
+ mercatoBin: '/tmp/mercato',
221
+ appDir: '/tmp/app',
222
+ runtimeEnv: {},
223
+ workers: queueNames.map((queueName) => makeWorker(queueName)),
224
+ pollMs: 250,
225
+ restartOnUnexpectedExit: true,
226
+ spawnMode,
227
+ spawnFn,
228
+ probeFn: async (queueName, strategy) => readyProbe(queueName, strategy),
229
+ logger: silentLogger,
230
+ onChildExit,
231
+ })
232
+
233
+ await flushAsync(20)
234
+ expect(children).toHaveLength(spawnMode === 'shared' ? 1 : 12)
235
+ for (const queueName of queueNames) {
236
+ expect(handle.getActiveChild(queueName)).toBeDefined()
237
+ }
238
+ let closed = false
239
+ const closing = Promise.all([handle.close(), handle.close(), handle.done]).then(() => {
240
+ closed = true
241
+ })
242
+ try {
243
+ for (const child of children) {
244
+ expect(child.listenerCount('exit')).toBe(2)
245
+ expect(child.kill).toHaveBeenCalledTimes(1)
246
+ expect(child.kill).toHaveBeenCalledWith('SIGTERM')
247
+ }
248
+ await flushAsync(20)
249
+ expect(closed).toBe(false)
250
+ for (const child of children.slice(0, -1)) {
251
+ child.emit('exit', 0, null)
252
+ }
253
+ await flushAsync(20)
254
+ expect(closed).toBe(false)
255
+ } finally {
256
+ for (const child of children) {
257
+ if (child.listenerCount('exit') > 1) child.emit('exit', 0, null)
258
+ }
259
+ await closing
260
+ }
261
+ expect(closed).toBe(true)
262
+ expect(onChildExit).toHaveBeenCalledTimes(12)
263
+ expect(handle.startedQueues.size).toBe(12)
264
+ for (const queueName of queueNames) {
265
+ expect(handle.getActiveChild(queueName)).toBeUndefined()
266
+ }
267
+ await handle.close()
268
+ for (const child of children) {
269
+ expect(child.listenerCount('exit')).toBe(1)
270
+ expect(child.kill).toHaveBeenCalledTimes(1)
271
+ }
272
+ expect(spawnFn).toHaveBeenCalledTimes(spawnMode === 'shared' ? 1 : 12)
273
+ }
274
+ },
275
+ )
276
+
277
+ it.each([
278
+ { exitCode: 0, signalCode: null },
279
+ { exitCode: null, signalCode: 'SIGTERM' },
280
+ ])('does not wait for an already-exited shared child (%j)', async (exitState) => {
281
+ const child = new ChildProcess()
282
+ const kill = jest.spyOn(child, 'kill').mockReturnValue(true)
283
+ const handle = startLazyWorkerSupervisor({
284
+ mercatoBin: '/tmp/mercato',
285
+ appDir: '/tmp/app',
286
+ runtimeEnv: {},
287
+ workers: Array.from({ length: 12 }, (_, index) => makeWorker(`queue-${index}`)),
288
+ pollMs: 250,
289
+ restartOnUnexpectedExit: false,
290
+ spawnMode: 'shared',
291
+ spawnFn: () => child,
292
+ probeFn: async (queueName, strategy) => readyProbe(queueName, strategy),
293
+ logger: silentLogger,
294
+ })
295
+ await flushAsync(20)
296
+ Object.assign(child, exitState)
297
+
298
+ await handle.close()
299
+ await handle.done
300
+ expect(child.listenerCount('exit')).toBe(1)
301
+ expect(kill).toHaveBeenCalledTimes(1)
302
+ expect(handle.getActiveChild('queue-0')).toBeUndefined()
303
+ })
304
+
305
+ it('keeps shutdown waiters bounded after repeated unexpected shared-worker restarts', async () => {
306
+ const children: ChildProcess[] = []
307
+ const spawnFn = jest.fn(() => {
308
+ const child = new ChildProcess()
309
+ jest.spyOn(child, 'kill').mockReturnValue(true)
310
+ children.push(child)
311
+ return child
312
+ })
313
+ const handle = startLazyWorkerSupervisor({
314
+ mercatoBin: '/tmp/mercato',
315
+ appDir: '/tmp/app',
316
+ runtimeEnv: {},
317
+ workers: Array.from({ length: 12 }, (_, index) => makeWorker(`queue-${index}`)),
318
+ pollMs: 250,
319
+ restartOnUnexpectedExit: true,
320
+ spawnMode: 'shared',
321
+ spawnFn,
322
+ probeFn: async (queueName, strategy) => readyProbe(queueName, strategy),
323
+ logger: silentLogger,
324
+ })
325
+ await flushAsync(40)
326
+ for (const restart of [0, 1, 2]) {
327
+ const child = children[restart]
328
+ expect(child.listenerCount('exit')).toBe(1)
329
+ Object.assign(child, { exitCode: 1 })
330
+ child.emit('exit', 1, null)
331
+ await flushAsync(100)
332
+ expect(spawnFn).toHaveBeenCalledTimes(restart + 2)
333
+ expect(child.kill).not.toHaveBeenCalled()
334
+ expect(handle.getActiveChild('queue-0')).toBe(children[restart + 1])
335
+ }
336
+ const lastChild = children[3]
337
+ const closing = handle.close()
338
+ try {
339
+ expect(lastChild.listenerCount('exit')).toBe(2)
340
+ expect(lastChild.kill).toHaveBeenCalledTimes(1)
341
+ } finally {
342
+ lastChild.emit('exit', null, 'SIGTERM')
343
+ await closing
344
+ }
345
+ await flushAsync(40)
346
+ expect(spawnFn).toHaveBeenCalledTimes(4)
347
+ for (const child of children) expect(child.listenerCount('exit')).toBe(1)
348
+ })
349
+
206
350
  it('starts the shared worker with extra arguments when an embedded scheduler needs it', async () => {
207
351
  const child = createFakeChild()
208
352
  const spawnFn = jest.fn(() => child) as unknown as jest.MockedFunction<LazySupervisorSpawnFn>
@@ -441,7 +441,10 @@ export function startLazyWorkerSupervisor(
441
441
  }
442
442
 
443
443
  const exitWaiters: Promise<void>[] = []
444
+ const closingChildren = new Set<ChildProcess>()
444
445
  for (const [queueName, child] of activeChildren) {
446
+ if (closingChildren.has(child)) continue
447
+ closingChildren.add(child)
445
448
  exitWaiters.push(
446
449
  new Promise<void>((resolve) => {
447
450
  if (child.exitCode !== null || child.signalCode !== null) return resolve()