@open-mercato/cli 0.10.1-develop.7847.1.7d83dc5794 → 0.10.1-develop.7862.1.ee520f7662
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agentic/guides/module-facts.json +170 -170
- package/dist/agentic/guides/module-facts.v2.json +170 -170
- package/dist/agentic/guides/modules/agent_orchestrator/index.md +1 -1
- package/dist/agentic/guides/modules/ai_assistant/index.md +1 -1
- package/dist/agentic/guides/modules/api_docs/index.md +1 -1
- package/dist/agentic/guides/modules/api_keys/index.md +1 -1
- package/dist/agentic/guides/modules/attachments/index.md +1 -1
- package/dist/agentic/guides/modules/audit_logs/index.md +1 -1
- package/dist/agentic/guides/modules/auth/index.md +1 -1
- package/dist/agentic/guides/modules/availability/index.md +1 -1
- package/dist/agentic/guides/modules/business_rules/index.md +1 -1
- package/dist/agentic/guides/modules/carrier_inpost/index.md +1 -1
- package/dist/agentic/guides/modules/cart/index.md +1 -1
- package/dist/agentic/guides/modules/catalog/index.md +1 -1
- package/dist/agentic/guides/modules/channel_apns/index.md +1 -1
- package/dist/agentic/guides/modules/channel_discord/index.md +1 -1
- package/dist/agentic/guides/modules/channel_expo/index.md +1 -1
- package/dist/agentic/guides/modules/channel_fcm/index.md +1 -1
- package/dist/agentic/guides/modules/channel_gmail/index.md +1 -1
- package/dist/agentic/guides/modules/channel_imap/index.md +1 -1
- package/dist/agentic/guides/modules/channel_resend/index.md +1 -1
- package/dist/agentic/guides/modules/channel_ses/index.md +1 -1
- package/dist/agentic/guides/modules/channel_smtp/index.md +1 -1
- package/dist/agentic/guides/modules/checkout/index.md +1 -1
- package/dist/agentic/guides/modules/communication_channels/index.md +1 -1
- package/dist/agentic/guides/modules/configs/index.md +1 -1
- package/dist/agentic/guides/modules/content/index.md +1 -1
- package/dist/agentic/guides/modules/currencies/index.md +1 -1
- package/dist/agentic/guides/modules/customer_accounts/index.md +1 -1
- package/dist/agentic/guides/modules/customer_groups/index.md +1 -1
- package/dist/agentic/guides/modules/customers/index.md +1 -1
- package/dist/agentic/guides/modules/dashboards/index.md +1 -1
- package/dist/agentic/guides/modules/data_sync/index.md +1 -1
- package/dist/agentic/guides/modules/design_system/index.md +1 -1
- package/dist/agentic/guides/modules/devices/index.md +1 -1
- package/dist/agentic/guides/modules/dictionaries/index.md +1 -1
- package/dist/agentic/guides/modules/directory/index.md +1 -1
- package/dist/agentic/guides/modules/documents/index.md +1 -1
- package/dist/agentic/guides/modules/ecommerce/index.md +1 -1
- package/dist/agentic/guides/modules/entities/index.md +1 -1
- package/dist/agentic/guides/modules/eudr/index.md +1 -1
- package/dist/agentic/guides/modules/events/index.md +1 -1
- package/dist/agentic/guides/modules/feature_toggles/index.md +1 -1
- package/dist/agentic/guides/modules/forms/index.md +1 -1
- package/dist/agentic/guides/modules/gateway_autopay/index.md +1 -1
- package/dist/agentic/guides/modules/gateway_stripe/index.md +1 -1
- package/dist/agentic/guides/modules/gateway_tpay/index.md +1 -1
- package/dist/agentic/guides/modules/generators/index.md +1 -1
- package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
- package/dist/agentic/guides/modules/integrations/index.md +1 -1
- package/dist/agentic/guides/modules/messages/index.md +1 -1
- package/dist/agentic/guides/modules/notifications/index.md +1 -1
- package/dist/agentic/guides/modules/onboarding/index.md +1 -1
- package/dist/agentic/guides/modules/payment_gateways/index.md +1 -1
- package/dist/agentic/guides/modules/perspectives/index.md +1 -1
- package/dist/agentic/guides/modules/phone_calls/index.md +1 -1
- package/dist/agentic/guides/modules/planner/index.md +1 -1
- package/dist/agentic/guides/modules/portal/index.md +1 -1
- package/dist/agentic/guides/modules/progress/index.md +1 -1
- package/dist/agentic/guides/modules/promotions/index.md +1 -1
- package/dist/agentic/guides/modules/push_notifications/index.md +1 -1
- package/dist/agentic/guides/modules/query_index/index.md +1 -1
- package/dist/agentic/guides/modules/record_locks/index.md +1 -1
- package/dist/agentic/guides/modules/resources/index.md +1 -1
- package/dist/agentic/guides/modules/sales/index.md +1 -1
- package/dist/agentic/guides/modules/scheduler/index.md +1 -1
- package/dist/agentic/guides/modules/search/exact-override-targets.md +1 -1
- package/dist/agentic/guides/modules/search/index.md +1 -1
- package/dist/agentic/guides/modules/search/workers.md +1 -1
- package/dist/agentic/guides/modules/security/index.md +1 -1
- package/dist/agentic/guides/modules/seeds/index.md +1 -1
- package/dist/agentic/guides/modules/shipping_carriers/index.md +1 -1
- package/dist/agentic/guides/modules/sso/index.md +1 -1
- package/dist/agentic/guides/modules/staff/index.md +1 -1
- package/dist/agentic/guides/modules/storage_s3/di-registrations-rich.md +3 -3
- package/dist/agentic/guides/modules/storage_s3/di-service-tokens.md +1 -1
- package/dist/agentic/guides/modules/storage_s3/exact-override-targets.md +3 -3
- package/dist/agentic/guides/modules/storage_s3/index.md +1 -1
- package/dist/agentic/guides/modules/sync_akeneo/index.md +1 -1
- package/dist/agentic/guides/modules/sync_excel/index.md +1 -1
- package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
- package/dist/agentic/guides/modules/telemetry/index.md +1 -1
- package/dist/agentic/guides/modules/tillio/index.md +1 -1
- package/dist/agentic/guides/modules/translations/index.md +1 -1
- package/dist/agentic/guides/modules/warranty_claims/index.md +1 -1
- package/dist/agentic/guides/modules/webhooks/index.md +1 -1
- package/dist/agentic/guides/modules/wms/index.md +1 -1
- package/dist/agentic/guides/modules/workflows/index.md +1 -1
- package/dist/agentic/guides/reference-module-facts.json +1 -1
- package/dist/agentic/guides/upstream/manifest.json +1 -1
- package/dist/lib/queue-worker-supervisor.js +3 -0
- package/dist/lib/queue-worker-supervisor.js.map +2 -2
- package/jest.config.cjs +1 -1
- package/package.json +7 -7
- package/src/lib/__tests__/queue-worker-supervisor.test.ts +145 -1
- package/src/lib/queue-worker-supervisor.ts +3 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# seeds — module facts (generated, do not edit)
|
|
2
|
-
<!-- generated from @open-mercato/core 0.10.1-develop.
|
|
2
|
+
<!-- generated from @open-mercato/core 0.10.1-develop.7862.1.ee520f7662; core 0.10.1-develop.7862.1.ee520f7662 — R1 staleness stamp -->
|
|
3
3
|
Source root: node_modules/@open-mercato/core/src/modules/seeds
|
|
4
4
|
|
|
5
5
|
## Sections
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# shipping_carriers — module facts (generated, do not edit)
|
|
2
|
-
<!-- generated from @open-mercato/core 0.10.1-develop.
|
|
2
|
+
<!-- generated from @open-mercato/core 0.10.1-develop.7862.1.ee520f7662; core 0.10.1-develop.7862.1.ee520f7662 — R1 staleness stamp -->
|
|
3
3
|
Source root: node_modules/@open-mercato/core/src/modules/shipping_carriers
|
|
4
4
|
|
|
5
5
|
## Sections
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# sso — module facts (generated, do not edit)
|
|
2
|
-
<!-- generated from @open-mercato/enterprise 0.10.1-develop.
|
|
2
|
+
<!-- generated from @open-mercato/enterprise 0.10.1-develop.7862.1.ee520f7662; core 0.10.1-develop.7862.1.ee520f7662 — 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.
|
|
2
|
+
<!-- generated from @open-mercato/core 0.10.1-develop.7862.1.ee520f7662; core 0.10.1-develop.7862.1.ee520f7662 — 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:
|
|
10
|
-
| storageS3QuotaRecoveryScheduler | value | — | — | — | [di.ts:
|
|
11
|
-
| storageService | function | scoped | proxy | — | [di.ts:
|
|
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:
|
|
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:
|
|
13
|
-
| di | `overrides.di["storageS3QuotaRecoveryScheduler"]` | disable-replace | ownedContracts.di-registration:storageS3QuotaRecoveryScheduler | [di.ts:
|
|
14
|
-
| di | `overrides.di["storageService"]` | disable-replace | ownedContracts.di-registration:storageService | [di.ts:
|
|
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.
|
|
2
|
+
<!-- generated from @open-mercato/storage-s3 0.10.1-develop.7862.1.ee520f7662; core 0.10.1-develop.7862.1.ee520f7662 — 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.
|
|
2
|
+
<!-- generated from @open-mercato/sync-akeneo 0.10.1-develop.7862.1.ee520f7662; core 0.10.1-develop.7862.1.ee520f7662 — 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.
|
|
2
|
+
<!-- generated from @open-mercato/core 0.10.1-develop.7862.1.ee520f7662; core 0.10.1-develop.7862.1.ee520f7662 — 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.
|
|
2
|
+
<!-- generated from @open-mercato/enterprise 0.10.1-develop.7862.1.ee520f7662; core 0.10.1-develop.7862.1.ee520f7662 — 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.
|
|
2
|
+
<!-- generated from @open-mercato/telemetry 0.10.1-develop.7862.1.ee520f7662; core 0.10.1-develop.7862.1.ee520f7662 — 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.
|
|
2
|
+
<!-- generated from @open-mercato/tillio 0.10.1-develop.7862.1.ee520f7662; core 0.10.1-develop.7862.1.ee520f7662 — 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.
|
|
2
|
+
<!-- generated from @open-mercato/core 0.10.1-develop.7862.1.ee520f7662; core 0.10.1-develop.7862.1.ee520f7662 — 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.
|
|
2
|
+
<!-- generated from @open-mercato/core 0.10.1-develop.7862.1.ee520f7662; core 0.10.1-develop.7862.1.ee520f7662 — 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.
|
|
2
|
+
<!-- generated from @open-mercato/webhooks 0.10.1-develop.7862.1.ee520f7662; core 0.10.1-develop.7862.1.ee520f7662 — 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.
|
|
2
|
+
<!-- generated from @open-mercato/core 0.10.1-develop.7862.1.ee520f7662; core 0.10.1-develop.7862.1.ee520f7662 — R1 staleness stamp -->
|
|
3
3
|
Source root: node_modules/@open-mercato/core/src/modules/wms
|
|
4
4
|
|
|
5
5
|
## Sections
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# workflows — module facts (generated, do not edit)
|
|
2
|
-
<!-- generated from @open-mercato/core 0.10.1-develop.
|
|
2
|
+
<!-- generated from @open-mercato/core 0.10.1-develop.7862.1.ee520f7662; core 0.10.1-develop.7862.1.ee520f7662 — 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.
|
|
1188
|
+
"coreVersion": "0.10.1-develop.7862.1.ee520f7662",
|
|
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.
|
|
3
|
+
"generator": "@open-mercato/cli@0.10.1-develop.7862.1.ee520f7662",
|
|
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": "AAwBA,SAAS,SAAS,iBAAiB;AAEnC;AAAA,EACE;AAAA,OAGK;AAkEP,SAAS,oBAAoB,SAA8D;AACzF,QAAM,SAAS,oBAAI,IAAwB;AAC3C,aAAW,UAAU,SAAS;AAC5B,UAAM,WAAW,OAAO,IAAI,OAAO,KAAK;AACxC,QAAI,UAAU;AACZ,eAAS,eAAe;AACxB,UAAI,OAAO,cAAc,SAAS,aAAa;AAC7C,iBAAS,cAAc,OAAO;AAAA,MAChC;AAAA,IACF,OAAO;AACL,aAAO,IAAI,OAAO,OAAO;AAAA,QACvB,WAAW,OAAO;AAAA,QAClB,aAAa,KAAK,IAAI,OAAO,aAAa,CAAC;AAAA,QAC3C,aAAa;AAAA,MACf,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO,CAAC,GAAG,OAAO,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,SAAS,CAAC;AACnF;AAEA,SAAS,uBAAuB,KAA2C;AACzE,SAAO,IAAI,mBAAmB,UAAU,UAAU;AACpD;AAEA,MAAM,8BAA8B;AAE7B,SAAS,0BACd,SAC4B;AAC5B,QAAM,SAAS,QAAQ,UAAU;AACjC,QAAM,WAA8B,QAAQ,YAAY,uBAAuB,QAAQ,UAAU;AACjG,QAAM,YAAiC,QAAQ,aAAa;AAC5D,QAAM,QAA+B,QAAQ,YACvC,CAAC,cAAc,qBAAqB,WAAW,QAAQ;AAC7D,QAAM,SAAS,oBAAoB,QAAQ,OAAO;AAElD,QAAM,gBAAgB,oBAAI,IAAY;AACtC,QAAM,iBAAiB,oBAAI,IAA0B;AACrD,QAAM,iBAAiB,oBAAI,IAAY;AACvC,MAAI;AACJ,MAAI,uBAAuB;AAC3B,QAAM,yBAAyB,oBAAI,IAAoB;AAEvD,MAAI,WAAW;AACf,MAAI,YAAkD;AACtD,MAAI,sBAA2C;AAC/C,QAAM,OAAO,IAAI,QAAc,CAAC,YAAY;AAC1C,0BAAsB;AAAA,EACxB,CAAC;AAED,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO;AAAA,MACL;AAAA,IACF;AAAA,EACF,OAAO;AACL,WAAO;AAAA,MACL,8BAA8B,OAAO,MAAM,cAAc,OACtD,IAAI,CAAC,MAAM,EAAE,SAAS,EACtB,KAAK,IAAI,CAAC;AAAA,IACf;AAAA,EACF;AAEA,QAAM,UACJ,QAAQ,YAAY,CAAC,SAAS,MAAM,SAAS,UAAU,SAAS,MAAkB,IAAI;AAExF,QAAM,0BAA0B,MAAM;AACpC,eAAW,SAAS,QAAQ;AAC1B,oBAAc,IAAI,MAAM,SAAS;AACjC,qBAAe,IAAI,MAAM,WAAW,iBAAiC;AAAA,IACvE;AAAA,EACF;AAEA,QAAM,oBAAoB,MAAM;AAC9B,eAAW,SAAS,QAAQ;AAC1B,UAAI,eAAe,IAAI,MAAM,SAAS,MAAM,mBAAmB;AAC7D,uBAAe,OAAO,MAAM,SAAS;AAAA,MACvC;AAAA,IACF;AACA,wBAAoB;AAAA,EACtB;AAEA,WAAS,cAAc,WAAmB,SAAuB;AAC/D,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,SAAS,uBAAuB,IAAI,SAAS,KAAK;AACxD,QAAI,MAAM,SAAS,4BAA6B;AAChD,2BAAuB,IAAI,WAAW,GAAG;AACzC,WAAO,KAAK,6CAA6C,SAAS,MAAM,OAAO,EAAE;AAAA,EACnF;AAEA,WAAS,iBAAiB,WAAyB;AACjD,QAAI,SAAU;AACd,QAAI,eAAe,IAAI,SAAS,EAAG;AACnC,QAAI,eAAe,IAAI,SAAS,EAAG;AACnC,mBAAe,IAAI,SAAS;AAE5B,WAAO,IAAI,4EAAuE,SAAS,GAAG;AAC9F,QAAI;AACJ,QAAI;AACF,cAAQ;AAAA,QACN;AAAA,QACA,CAAC,QAAQ,YAAY,SAAS,UAAU,SAAS;AAAA,QACjD;AAAA,UACE,OAAO;AAAA,UACP,KAAK,QAAQ;AAAA,UACb,KAAK,QAAQ;AAAA,QACf;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,qBAAe,OAAO,SAAS;AAC/B,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,aAAO,MAAM,iDAAiD,SAAS,MAAM,OAAO,EAAE;AACtF;AAAA,IACF;AAEA,mBAAe,IAAI,WAAW,KAAK;AACnC,kBAAc,IAAI,SAAS;AAC3B,mBAAe,OAAO,SAAS;AAC/B,QAAI,QAAQ,SAAS;AACnB,UAAI;AACF,gBAAQ,QAAQ,WAAW,KAAK;AAAA,MAClC,SAAS,KAAK;AACZ,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,eAAO,KAAK,iDAAiD,SAAS,MAAM,OAAO,EAAE;AAAA,MACvF;AAAA,IACF;AAEA,UAAM,GAAG,QAAQ,CAAC,MAAM,WAAW;AACjC,qBAAe,OAAO,SAAS;AAC/B,UAAI,QAAQ,aAAa;AACvB,YAAI;AACF,kBAAQ,YAAY,WAAW,MAAM,MAAM;AAAA,QAC7C,SAAS,KAAK;AACZ,gBAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,iBAAO,KAAK,qDAAqD,SAAS,MAAM,OAAO,EAAE;AAAA,QAC3F;AAAA,MACF;AACA,UAAI,SAAU;AACd,YAAM,WAAW,WAAW,aAAa,WAAW;AACpD,UAAI,SAAU;AACd,YAAM,SAAS,SAAS,OAAO,QAAQ,IAAI,KAAK,UAAU,UAAU,SAAS;AAC7E,aAAO,KAAK,iCAAiC,SAAS,aAAa,MAAM,IAAI;AAC7E,UAAI,CAAC,QAAQ,wBAAyB;AACtC,YAAM,YAAY;AAChB,cAAM,SAAS,MAAM,UAAU,SAAS;AACxC,YAAI,CAAC,UAAU,OAAO,MAAO;AAC7B,YAAI,OAAO,QAAQ,GAAG;AACpB,iBAAO,KAAK,4CAA4C,SAAS,gCAAgC;AACjG,2BAAiB,SAAS;AAAA,QAC5B;AAAA,MACF,GAAG;AAAA,IACL,CAAC;AAAA,EACH;AAEA,WAAS,kBAAkB,mBAA6B,qBAAqB,OAAa;AACxF,QAAI,SAAU;AACd,QAAI,kBAAmB;AACvB,QAAI,qBAAsB;AAC1B,2BAAuB;AAEvB,QAAI,oBAAoB;AACtB,aAAO,IAAI,0FAAqF;AAAA,IAClG,OAAO;AACL,YAAM,iBAAiB,kBAAkB,SAAS,IAC9C,OAAO,kBAAkB,KAAK,IAAI,CAAC,KACnC;AACJ,aAAO,IAAI,yCAAyC,cAAc,+CAA0C;AAAA,IAC9G;AACA,QAAI;AACJ,QAAI;AACF,cAAQ;AAAA,QACN;AAAA,QACA,CAAC,QAAQ,YAAY,SAAS,UAAU,SAAS,GAAI,QAAQ,oBAAoB,CAAC,CAAE;AAAA,QACpF;AAAA,UACE,OAAO;AAAA,UACP,KAAK,QAAQ;AAAA,UACb,KAAK,QAAQ;AAAA,QACf;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,6BAAuB;AACvB,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,aAAO,MAAM,oDAAoD,OAAO,EAAE;AAC1E;AAAA,IACF;AAEA,wBAAoB;AACpB,4BAAwB;AACxB,2BAAuB;AACvB,eAAW,SAAS,QAAQ;AAC1B,UAAI,QAAQ,SAAS;AACnB,YAAI;AACF,kBAAQ,QAAQ,MAAM,WAAW,KAAK;AAAA,QACxC,SAAS,KAAK;AACZ,gBAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,iBAAO,KAAK,iDAAiD,MAAM,SAAS,MAAM,OAAO,EAAE;AAAA,QAC7F;AAAA,MACF;AAAA,IACF;AAEA,UAAM,GAAG,QAAQ,CAAC,MAAM,WAAW;AACjC,wBAAkB;AAClB,iBAAW,SAAS,QAAQ;AAC1B,YAAI,QAAQ,aAAa;AACvB,cAAI;AACF,oBAAQ,YAAY,MAAM,WAAW,MAAM,MAAM;AAAA,UACnD,SAAS,KAAK;AACZ,kBAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,mBAAO,KAAK,qDAAqD,MAAM,SAAS,MAAM,OAAO,EAAE;AAAA,UACjG;AAAA,QACF;AAAA,MACF;AACA,UAAI,SAAU;AACd,YAAM,WAAW,WAAW,aAAa,WAAW;AACpD,UAAI,SAAU;AACd,YAAM,SAAS,SAAS,OAAO,QAAQ,IAAI,KAAK,UAAU,UAAU,SAAS;AAC7E,aAAO,KAAK,2CAA2C,MAAM,IAAI;AACjE,UAAI,CAAC,QAAQ,wBAAyB;AACtC,YAAM,YAAY;AAChB,cAAM,cAAwB,CAAC;AAC/B,mBAAW,SAAS,QAAQ;AAC1B,gBAAM,SAAS,MAAM,UAAU,MAAM,SAAS;AAC9C,cAAI,CAAC,UAAU,OAAO,MAAO;AAC7B,cAAI,OAAO,QAAQ,EAAG,aAAY,KAAK,MAAM,SAAS;AAAA,QACxD;AACA,YAAI,YAAY,SAAS,GAAG;AAC1B,iBAAO,KAAK,yEAAyE;AACrF,4BAAkB,WAAW;AAC7B;AAAA,QACF;AACA,YAAI,QAAQ,2BAA2B;AACrC,cAAI;AACF,gBAAI,MAAM,QAAQ,0BAA0B,GAAG;AAC7C,qBAAO,KAAK,iFAAiF;AAC7F,gCAAkB,CAAC,GAAG,IAAI;AAAA,YAC5B;AAAA,UACF,SAAS,KAAK;AACZ,kBAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,mBAAO,KAAK,6DAA6D,OAAO,EAAE;AAAA,UACpF;AAAA,QACF;AAAA,MACF,GAAG;AAAA,IACL,CAAC;AAAA,EACH;AAEA,iBAAe,UAAU,WAA4D;AACnF,QAAI;AACF,aAAO,MAAM,MAAM,WAAW,QAAQ;AAAA,IACxC,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,oBAAc,WAAW,OAAO;AAChC,aAAO;AAAA,IACT;AAAA,EACF;AAEA,iBAAe,WAA0B;AACvC,QAAI,SAAU;AACd,QAAI,OAAO,WAAW,EAAG;AACzB,QAAI,cAAc,aAAa,qBAAqB,sBAAuB;AAE3E,UAAM,UAAU,OAAO,OAAO,CAAC,MAAM,CAAC,eAAe,IAAI,EAAE,SAAS,KAAK,CAAC,eAAe,IAAI,EAAE,SAAS,CAAC;AACzG,QAAI,QAAQ,WAAW,EAAG;AAE1B,UAAM,SAAS,MAAM,QAAQ;AAAA,MAC3B,QAAQ,IAAI,OAAO,WAAW;AAAA,QAC5B;AAAA,QACA,QAAQ,MAAM,UAAU,MAAM,SAAS;AAAA,MACzC,EAAE;AAAA,IACJ;AAEA,QAAI,SAAU;AAEd,UAAM,oBAA8B,CAAC;AACrC,eAAW,EAAE,OAAO,OAAO,KAAK,QAAQ;AACtC,UAAI,CAAC,OAAQ;AACb,UAAI,OAAO,OAAO;AAChB,YAAI,OAAO,cAAc;AACvB,wBAAc,MAAM,WAAW,OAAO,YAAY;AAAA,QACpD;AACA;AAAA,MACF;AACA,UAAI,OAAO,QAAQ,GAAG;AACpB,YAAI,cAAc,UAAU;AAC1B,4BAAkB,KAAK,MAAM,SAAS;AACtC;AAAA,QACF;AACA,yBAAiB,MAAM,SAAS;AAAA,MAClC;AAAA,IACF;AACA,QAAI,cAAc,UAAU;AAC1B,UAAI,kBAAkB,SAAS,GAAG;AAChC,0BAAkB,iBAAiB;AACnC;AAAA,MACF;AACA,UAAI,QAAQ,yBAAyB;AACnC,YAAI;AACF,cAAI,MAAM,QAAQ,wBAAwB,GAAG;AAC3C,8BAAkB,CAAC,GAAG,IAAI;AAAA,UAC5B;AAAA,QACF,SAAS,KAAK;AACZ,gBAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,iBAAO,KAAK,2DAA2D,OAAO,EAAE;AAAA,QAClF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,WAAS,eAAqB;AAC5B,QAAI,SAAU;AACd,gBAAY,WAAW,MAAM;AAC3B,YAAM,YAAY;AAChB,YAAI;AACF,gBAAM,SAAS;AAAA,QACjB,SAAS,KAAK;AACZ,gBAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,iBAAO,KAAK,wCAAwC,OAAO,EAAE;AAAA,QAC/D,UAAE;AACA,cAAI,CAAC,SAAU,cAAa;AAAA,QAC9B;AAAA,MACF,GAAG;AAAA,IACL,GAAG,QAAQ,MAAM;AACjB,cAAU,QAAQ;AAAA,EACpB;AAGA,QAAM,YAAY;AAChB,QAAI;AACF,YAAM,SAAS;AAAA,IACjB,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,aAAO,KAAK,0CAA0C,OAAO,EAAE;AAAA,IACjE,UAAE;AACA,mBAAa;AAAA,IACf;AAAA,EACF,GAAG;AAEH,iBAAe,QAAuB;AACpC,QAAI,UAAU;AACZ,YAAM;AACN;AAAA,IACF;AACA,eAAW;AACX,QAAI,WAAW;AACb,mBAAa,SAAS;AACtB,kBAAY;AAAA,IACd;AAEA,UAAM,cAA+B,CAAC;AACtC,eAAW,CAAC,WAAW,KAAK,KAAK,gBAAgB;AAC/C,kBAAY;AAAA,QACV,IAAI,QAAc,CAAC,YAAY;AAC7B,cAAI,MAAM,aAAa,QAAQ,MAAM,eAAe,KAAM,QAAO,QAAQ;AACzE,gBAAM,KAAK,QAAQ,MAAM,QAAQ,CAAC;AAAA,QACpC,CAAC;AAAA,MACH;AACA,UAAI;AACF,YAAI,CAAC,MAAM,OAAQ,OAAM,KAAK,SAAS;AAAA,MACzC,SAAS,KAAK;AACZ,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,eAAO,KAAK,gDAAgD,SAAS,MAAM,OAAO,EAAE;AAAA,MACtF;AAAA,IACF;AACA,UAAM,QAAQ,IAAI,WAAW;AAC7B,mBAAe,MAAM;AACrB,QAAI,qBAAqB;AACvB,0BAAoB;AACpB,4BAAsB;AAAA,IACxB;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,gBAAgB,CAAC,cAAc,eAAe,IAAI,SAAS;AAAA,IAC3D;AAAA,IACA;AAAA,EACF;AACF;",
|
|
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/(?!(
|
|
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.
|
|
3
|
+
"version": "0.10.1-develop.7862.1.ee520f7662",
|
|
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.
|
|
64
|
-
"@open-mercato/queue": "0.10.1-develop.
|
|
65
|
-
"@open-mercato/shared": "0.10.1-develop.
|
|
66
|
-
"@open-mercato/telemetry": "0.10.1-develop.
|
|
63
|
+
"@open-mercato/events": "0.10.1-develop.7862.1.ee520f7662",
|
|
64
|
+
"@open-mercato/queue": "0.10.1-develop.7862.1.ee520f7662",
|
|
65
|
+
"@open-mercato/shared": "0.10.1-develop.7862.1.ee520f7662",
|
|
66
|
+
"@open-mercato/telemetry": "0.10.1-develop.7862.1.ee520f7662",
|
|
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.
|
|
78
|
+
"@open-mercato/shared": "0.10.1-develop.7862.1.ee520f7662"
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|
|
81
|
-
"@open-mercato/shared": "0.10.1-develop.
|
|
81
|
+
"@open-mercato/shared": "0.10.1-develop.7862.1.ee520f7662",
|
|
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
|
|
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()
|