@stacksjs/ts-cloud-core 0.1.3 → 0.1.7
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/README.md +98 -13
- package/dist/advanced-features.test.d.ts +0 -0
- package/dist/aws/cloudformation.d.ts +69 -0
- package/dist/aws/cloudfront.d.ts +21 -0
- package/dist/aws/credentials.d.ts +66 -0
- package/dist/aws/credentials.test.d.ts +0 -0
- package/{src/aws/index.ts → dist/aws/index.d.ts} +37 -51
- package/dist/aws/s3.d.ts +130 -0
- package/dist/aws/s3.test.d.ts +0 -0
- package/dist/aws/signature.d.ts +101 -0
- package/dist/aws/signature.test.d.ts +0 -0
- package/dist/backup/disaster-recovery.d.ts +98 -0
- package/dist/backup/disaster-recovery.test.d.ts +0 -0
- package/{src/backup/index.ts → dist/backup/index.d.ts} +10 -20
- package/dist/backup/manager.d.ts +112 -0
- package/dist/backup/manager.test.d.ts +0 -0
- package/dist/cicd/circleci.d.ts +47 -0
- package/dist/cicd/github-actions.d.ts +55 -0
- package/dist/cicd/gitlab-ci.d.ts +46 -0
- package/dist/cicd/index.d.ts +3 -0
- package/dist/cli/history.d.ts +66 -0
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/progress.d.ts +97 -0
- package/dist/cli/repl.d.ts +76 -0
- package/dist/cli/suggestions.d.ts +67 -0
- package/dist/cli/table.d.ts +70 -0
- package/dist/cli/table.test.d.ts +0 -0
- package/dist/cloudformation/builder.d.ts +59 -0
- package/dist/cloudformation/builder.test.d.ts +0 -0
- package/dist/cloudformation/builders/api-gateway.d.ts +30 -0
- package/dist/cloudformation/builders/cache.d.ts +35 -0
- package/dist/cloudformation/builders/cdn.d.ts +34 -0
- package/dist/cloudformation/builders/compute.d.ts +66 -0
- package/dist/cloudformation/builders/database.d.ts +61 -0
- package/dist/cloudformation/builders/functions.d.ts +32 -0
- package/dist/cloudformation/builders/messaging.d.ts +17 -0
- package/dist/cloudformation/builders/monitoring.d.ts +36 -0
- package/dist/cloudformation/builders/network.d.ts +14 -0
- package/dist/cloudformation/builders/queue.d.ts +8 -0
- package/dist/cloudformation/builders/security.d.ts +31 -0
- package/dist/cloudformation/builders/storage.d.ts +8 -0
- package/dist/cloudformation/index.d.ts +24 -0
- package/dist/cloudformation/types.d.ts +132 -0
- package/dist/compliance/aws-config.d.ts +88 -0
- package/dist/compliance/cloudtrail.d.ts +96 -0
- package/dist/compliance/compliance.test.d.ts +0 -0
- package/dist/compliance/guardduty.d.ts +110 -0
- package/{src/compliance/index.ts → dist/compliance/index.d.ts} +20 -36
- package/dist/compliance/security-hub.d.ts +110 -0
- package/dist/containers/build-optimization.d.ts +110 -0
- package/dist/containers/containers.test.d.ts +0 -0
- package/dist/containers/image-scanning.d.ts +96 -0
- package/dist/containers/index.d.ts +4 -0
- package/dist/containers/registry.d.ts +99 -0
- package/dist/containers/service-mesh.d.ts +206 -0
- package/dist/database/database.test.d.ts +0 -0
- package/dist/database/index.d.ts +4 -0
- package/dist/database/migrations.d.ts +102 -0
- package/dist/database/performance.d.ts +168 -0
- package/dist/database/replicas.d.ts +146 -0
- package/dist/database/users.d.ts +102 -0
- package/dist/dependency-graph.d.ts +19 -0
- package/dist/deployment/ab-testing.d.ts +114 -0
- package/dist/deployment/blue-green.d.ts +98 -0
- package/dist/deployment/canary.d.ts +103 -0
- package/dist/deployment/deployment.test.d.ts +0 -0
- package/{src/deployment/index.ts → dist/deployment/index.d.ts} +20 -36
- package/dist/deployment/progressive.d.ts +34 -0
- package/dist/dns/dns.test.d.ts +0 -0
- package/dist/dns/dnssec.d.ts +75 -0
- package/dist/dns/index.d.ts +3 -0
- package/dist/dns/resolver.d.ts +150 -0
- package/dist/dns/routing.d.ts +217 -0
- package/dist/email/advanced/analytics.d.ts +78 -0
- package/dist/email/advanced/index.d.ts +7 -0
- package/dist/email/advanced/rules.d.ts +60 -0
- package/dist/email/advanced/scheduling.d.ts +63 -0
- package/dist/email/advanced/search.d.ts +76 -0
- package/dist/email/advanced/shared-mailboxes.d.ts +66 -0
- package/dist/email/advanced/templates.d.ts +39 -0
- package/dist/email/advanced/threading.d.ts +53 -0
- package/dist/email/analytics.d.ts +144 -0
- package/dist/email/bounce-handling.d.ts +120 -0
- package/dist/email/email.test.d.ts +0 -0
- package/dist/email/handlers/__tests__/inbound.test.d.ts +0 -0
- package/dist/email/handlers/__tests__/outbound.test.d.ts +0 -0
- package/{src/email/handlers/converter.ts → dist/email/handlers/converter.d.ts} +3 -5
- package/{src/email/handlers/feedback.ts → dist/email/handlers/feedback.d.ts} +3 -5
- package/{src/email/handlers/inbound.ts → dist/email/handlers/inbound.d.ts} +3 -5
- package/{src/email/handlers/outbound.ts → dist/email/handlers/outbound.d.ts} +3 -5
- package/dist/email/index.d.ts +6 -0
- package/dist/email/reputation.d.ts +97 -0
- package/dist/email/templates.d.ts +82 -0
- package/dist/errors/index.d.ts +186 -0
- package/dist/errors/index.test.d.ts +0 -0
- package/dist/health-checks/index.d.ts +35 -0
- package/dist/index.d.ts +256 -0
- package/dist/index.js +63499 -0
- package/dist/intrinsic-functions.d.ts +37 -0
- package/dist/lambda/concurrency.d.ts +98 -0
- package/dist/lambda/destinations.d.ts +99 -0
- package/dist/lambda/dlq.d.ts +109 -0
- package/dist/lambda/index.d.ts +6 -0
- package/dist/lambda/lambda.test.d.ts +0 -0
- package/dist/lambda/layers.d.ts +81 -0
- package/dist/lambda/versions.d.ts +91 -0
- package/dist/lambda/vpc.d.ts +116 -0
- package/dist/local/config.d.ts +44 -0
- package/dist/local/index.d.ts +2 -0
- package/dist/local/mock-aws.d.ts +60 -0
- package/dist/modules/ai.d.ts +47 -0
- package/dist/modules/api.d.ts +98 -0
- package/dist/modules/auth.d.ts +165 -0
- package/dist/modules/cache.d.ts +73 -0
- package/dist/modules/cdn.d.ts +125 -0
- package/dist/modules/communication.d.ts +98 -0
- package/dist/modules/compute.d.ts +309 -0
- package/dist/modules/database.d.ts +105 -0
- package/dist/modules/deployment.d.ts +181 -0
- package/dist/modules/dns.d.ts +45 -0
- package/dist/modules/email.d.ts +217 -0
- package/dist/modules/filesystem.d.ts +94 -0
- package/dist/modules/index.d.ts +27 -0
- package/dist/modules/messaging.d.ts +108 -0
- package/dist/modules/monitoring.d.ts +127 -0
- package/dist/modules/network.d.ts +102 -0
- package/dist/modules/parameter-store.d.ts +33 -0
- package/dist/modules/permissions.d.ts +132 -0
- package/dist/modules/phone.d.ts +80 -0
- package/dist/modules/queue.d.ts +210 -0
- package/dist/modules/redirects.d.ts +59 -0
- package/dist/modules/registry.d.ts +73 -0
- package/dist/modules/search.d.ts +56 -0
- package/dist/modules/secrets.d.ts +80 -0
- package/dist/modules/security.d.ts +100 -0
- package/dist/modules/sms.d.ts +52 -0
- package/dist/modules/storage.d.ts +160 -0
- package/dist/modules/workflow.d.ts +205 -0
- package/dist/multi-account/config.d.ts +315 -0
- package/dist/multi-account/index.d.ts +2 -0
- package/dist/multi-account/manager.d.ts +100 -0
- package/dist/multi-region/cross-region.d.ts +114 -0
- package/dist/multi-region/index.d.ts +3 -0
- package/dist/multi-region/manager.d.ts +72 -0
- package/dist/multi-region/regions.d.ts +98 -0
- package/dist/network-security/index.d.ts +39 -0
- package/dist/observability/index.d.ts +4 -0
- package/dist/observability/logs.d.ts +129 -0
- package/dist/observability/metrics.d.ts +153 -0
- package/dist/observability/observability.test.d.ts +0 -0
- package/dist/observability/synthetics.d.ts +146 -0
- package/dist/observability/xray.d.ts +129 -0
- package/dist/phone/advanced/analytics.d.ts +66 -0
- package/dist/phone/advanced/callbacks.d.ts +50 -0
- package/dist/phone/advanced/index.d.ts +4 -0
- package/dist/phone/advanced/ivr-builder.d.ts +83 -0
- package/dist/phone/advanced/recording.d.ts +48 -0
- package/dist/phone/handlers/__tests__/incoming-call.test.d.ts +0 -0
- package/{src/phone/handlers/incoming-call.ts → dist/phone/handlers/incoming-call.d.ts} +3 -5
- package/{src/phone/handlers/missed-call.ts → dist/phone/handlers/missed-call.d.ts} +3 -5
- package/{src/phone/handlers/voicemail.ts → dist/phone/handlers/voicemail.d.ts} +3 -5
- package/dist/phone/index.d.ts +2 -0
- package/dist/presets/api-backend.d.ts +11 -0
- package/dist/presets/data-pipeline.d.ts +11 -0
- package/{src/presets/extend.ts → dist/presets/extend.d.ts} +11 -114
- package/dist/presets/extend.test.d.ts +0 -0
- package/dist/presets/fullstack-app.d.ts +12 -0
- package/dist/presets/index.d.ts +24 -0
- package/dist/presets/jamstack.d.ts +12 -0
- package/dist/presets/microservices.d.ts +18 -0
- package/dist/presets/ml-api.d.ts +13 -0
- package/dist/presets/nodejs-server.d.ts +14 -0
- package/dist/presets/nodejs-serverless.d.ts +14 -0
- package/dist/presets/realtime-app.d.ts +11 -0
- package/dist/presets/static-site.d.ts +12 -0
- package/dist/presets/traditional-web-app.d.ts +16 -0
- package/dist/presets/wordpress.d.ts +12 -0
- package/dist/preview/github.d.ts +32 -0
- package/dist/preview/github.test.d.ts +0 -0
- package/{src/preview/index.ts → dist/preview/index.d.ts} +16 -26
- package/dist/preview/manager.d.ts +58 -0
- package/dist/preview/manager.test.d.ts +0 -0
- package/dist/preview/notifications.d.ts +55 -0
- package/dist/preview/notifications.test.d.ts +0 -0
- package/dist/queue/batch-processing.d.ts +87 -0
- package/dist/queue/dlq-monitoring.d.ts +95 -0
- package/dist/queue/fifo.d.ts +90 -0
- package/dist/queue/index.d.ts +4 -0
- package/dist/queue/management.d.ts +105 -0
- package/dist/queue/queue.test.d.ts +0 -0
- package/dist/resource-mgmt/index.d.ts +29 -0
- package/dist/resource-naming.d.ts +26 -0
- package/dist/s3/index.d.ts +173 -0
- package/dist/schema/index.d.ts +9 -0
- package/dist/security/certificate-manager.d.ts +121 -0
- package/dist/security/index.d.ts +4 -0
- package/dist/security/scanning.d.ts +147 -0
- package/dist/security/secrets-manager.d.ts +144 -0
- package/dist/security/secrets-rotation.d.ts +115 -0
- package/dist/security/security.test.d.ts +0 -0
- package/dist/sms/advanced/ab-testing.d.ts +54 -0
- package/dist/sms/advanced/analytics.d.ts +56 -0
- package/dist/sms/advanced/campaigns.d.ts +82 -0
- package/dist/sms/advanced/chatbot.d.ts +48 -0
- package/dist/sms/advanced/index.d.ts +6 -0
- package/dist/sms/advanced/link-tracking.d.ts +42 -0
- package/dist/sms/advanced/mms.d.ts +35 -0
- package/dist/sms/handlers/__tests__/send.test.d.ts +0 -0
- package/{src/sms/handlers/delivery-status.ts → dist/sms/handlers/delivery-status.d.ts} +3 -5
- package/{src/sms/handlers/receive.ts → dist/sms/handlers/receive.d.ts} +3 -5
- package/{src/sms/handlers/send.ts → dist/sms/handlers/send.d.ts} +3 -5
- package/dist/sms/index.d.ts +2 -0
- package/dist/stack-diff.d.ts +34 -0
- package/dist/static-site/index.d.ts +49 -0
- package/dist/template-builder.d.ts +14 -0
- package/dist/template-validator.d.ts +24 -0
- package/dist/utils/cache.d.ts +55 -0
- package/dist/utils/diff.d.ts +48 -0
- package/dist/utils/hash.d.ts +58 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/parallel.d.ts +60 -0
- package/dist/validators/credentials.d.ts +23 -0
- package/dist/validators/credentials.test.d.ts +0 -0
- package/dist/validators/quotas.d.ts +60 -0
- package/dist/validators/quotas.test.d.ts +0 -0
- package/package.json +13 -4
- package/src/advanced-features.test.ts +0 -465
- package/src/aws/cloudformation.ts +0 -421
- package/src/aws/cloudfront.ts +0 -158
- package/src/aws/credentials.test.ts +0 -132
- package/src/aws/credentials.ts +0 -545
- package/src/aws/s3.test.ts +0 -188
- package/src/aws/s3.ts +0 -1088
- package/src/aws/signature.test.ts +0 -670
- package/src/aws/signature.ts +0 -1155
- package/src/backup/disaster-recovery.test.ts +0 -726
- package/src/backup/disaster-recovery.ts +0 -500
- package/src/backup/manager.test.ts +0 -498
- package/src/backup/manager.ts +0 -432
- package/src/cicd/circleci.ts +0 -430
- package/src/cicd/github-actions.ts +0 -424
- package/src/cicd/gitlab-ci.ts +0 -255
- package/src/cicd/index.ts +0 -8
- package/src/cli/history.ts +0 -396
- package/src/cli/index.ts +0 -10
- package/src/cli/progress.ts +0 -458
- package/src/cli/repl.ts +0 -454
- package/src/cli/suggestions.ts +0 -327
- package/src/cli/table.test.ts +0 -319
- package/src/cli/table.ts +0 -332
- package/src/cloudformation/builder.test.ts +0 -327
- package/src/cloudformation/builder.ts +0 -378
- package/src/cloudformation/builders/api-gateway.ts +0 -449
- package/src/cloudformation/builders/cache.ts +0 -334
- package/src/cloudformation/builders/cdn.ts +0 -278
- package/src/cloudformation/builders/compute.ts +0 -485
- package/src/cloudformation/builders/database.ts +0 -392
- package/src/cloudformation/builders/functions.ts +0 -343
- package/src/cloudformation/builders/messaging.ts +0 -140
- package/src/cloudformation/builders/monitoring.ts +0 -300
- package/src/cloudformation/builders/network.ts +0 -264
- package/src/cloudformation/builders/queue.ts +0 -147
- package/src/cloudformation/builders/security.ts +0 -399
- package/src/cloudformation/builders/storage.ts +0 -285
- package/src/cloudformation/index.ts +0 -30
- package/src/cloudformation/types.ts +0 -173
- package/src/compliance/aws-config.ts +0 -543
- package/src/compliance/cloudtrail.ts +0 -376
- package/src/compliance/compliance.test.ts +0 -423
- package/src/compliance/guardduty.ts +0 -446
- package/src/compliance/security-hub.ts +0 -456
- package/src/containers/build-optimization.ts +0 -416
- package/src/containers/containers.test.ts +0 -508
- package/src/containers/image-scanning.ts +0 -360
- package/src/containers/index.ts +0 -9
- package/src/containers/registry.ts +0 -293
- package/src/containers/service-mesh.ts +0 -520
- package/src/database/database.test.ts +0 -762
- package/src/database/index.ts +0 -9
- package/src/database/migrations.ts +0 -444
- package/src/database/performance.ts +0 -528
- package/src/database/replicas.ts +0 -534
- package/src/database/users.ts +0 -494
- package/src/dependency-graph.ts +0 -143
- package/src/deployment/ab-testing.ts +0 -582
- package/src/deployment/blue-green.ts +0 -452
- package/src/deployment/canary.ts +0 -500
- package/src/deployment/deployment.test.ts +0 -526
- package/src/deployment/progressive.ts +0 -62
- package/src/dns/dns.test.ts +0 -641
- package/src/dns/dnssec.ts +0 -315
- package/src/dns/index.ts +0 -8
- package/src/dns/resolver.ts +0 -496
- package/src/dns/routing.ts +0 -593
- package/src/email/advanced/analytics.ts +0 -445
- package/src/email/advanced/index.ts +0 -11
- package/src/email/advanced/rules.ts +0 -465
- package/src/email/advanced/scheduling.ts +0 -352
- package/src/email/advanced/search.ts +0 -412
- package/src/email/advanced/shared-mailboxes.ts +0 -404
- package/src/email/advanced/templates.ts +0 -455
- package/src/email/advanced/threading.ts +0 -281
- package/src/email/analytics.ts +0 -467
- package/src/email/bounce-handling.ts +0 -425
- package/src/email/email.test.ts +0 -431
- package/src/email/handlers/__tests__/inbound.test.ts +0 -38
- package/src/email/handlers/__tests__/outbound.test.ts +0 -37
- package/src/email/index.ts +0 -15
- package/src/email/reputation.ts +0 -303
- package/src/email/templates.ts +0 -352
- package/src/errors/index.test.ts +0 -434
- package/src/errors/index.ts +0 -416
- package/src/health-checks/index.ts +0 -40
- package/src/index.ts +0 -360
- package/src/intrinsic-functions.ts +0 -118
- package/src/lambda/concurrency.ts +0 -330
- package/src/lambda/destinations.ts +0 -345
- package/src/lambda/dlq.ts +0 -425
- package/src/lambda/index.ts +0 -11
- package/src/lambda/lambda.test.ts +0 -840
- package/src/lambda/layers.ts +0 -263
- package/src/lambda/versions.ts +0 -376
- package/src/lambda/vpc.ts +0 -399
- package/src/local/config.ts +0 -114
- package/src/local/index.ts +0 -6
- package/src/local/mock-aws.ts +0 -351
- package/src/modules/ai.ts +0 -340
- package/src/modules/api.ts +0 -478
- package/src/modules/auth.ts +0 -805
- package/src/modules/cache.ts +0 -417
- package/src/modules/cdn.ts +0 -1062
- package/src/modules/communication.ts +0 -1094
- package/src/modules/compute.ts +0 -3348
- package/src/modules/database.ts +0 -554
- package/src/modules/deployment.ts +0 -1079
- package/src/modules/dns.ts +0 -337
- package/src/modules/email.ts +0 -1538
- package/src/modules/filesystem.ts +0 -515
- package/src/modules/index.ts +0 -32
- package/src/modules/messaging.ts +0 -486
- package/src/modules/monitoring.ts +0 -2086
- package/src/modules/network.ts +0 -664
- package/src/modules/parameter-store.ts +0 -325
- package/src/modules/permissions.ts +0 -1081
- package/src/modules/phone.ts +0 -494
- package/src/modules/queue.ts +0 -1260
- package/src/modules/redirects.ts +0 -464
- package/src/modules/registry.ts +0 -699
- package/src/modules/search.ts +0 -401
- package/src/modules/secrets.ts +0 -416
- package/src/modules/security.ts +0 -731
- package/src/modules/sms.ts +0 -389
- package/src/modules/storage.ts +0 -1120
- package/src/modules/workflow.ts +0 -680
- package/src/multi-account/config.ts +0 -521
- package/src/multi-account/index.ts +0 -7
- package/src/multi-account/manager.ts +0 -427
- package/src/multi-region/cross-region.ts +0 -410
- package/src/multi-region/index.ts +0 -8
- package/src/multi-region/manager.ts +0 -483
- package/src/multi-region/regions.ts +0 -435
- package/src/network-security/index.ts +0 -48
- package/src/observability/index.ts +0 -9
- package/src/observability/logs.ts +0 -522
- package/src/observability/metrics.ts +0 -460
- package/src/observability/observability.test.ts +0 -782
- package/src/observability/synthetics.ts +0 -568
- package/src/observability/xray.ts +0 -358
- package/src/phone/advanced/analytics.ts +0 -349
- package/src/phone/advanced/callbacks.ts +0 -428
- package/src/phone/advanced/index.ts +0 -8
- package/src/phone/advanced/ivr-builder.ts +0 -504
- package/src/phone/advanced/recording.ts +0 -310
- package/src/phone/handlers/__tests__/incoming-call.test.ts +0 -40
- package/src/phone/index.ts +0 -9
- package/src/presets/api-backend.ts +0 -134
- package/src/presets/data-pipeline.ts +0 -204
- package/src/presets/extend.test.ts +0 -295
- package/src/presets/fullstack-app.ts +0 -144
- package/src/presets/index.ts +0 -27
- package/src/presets/jamstack.ts +0 -135
- package/src/presets/microservices.ts +0 -167
- package/src/presets/ml-api.ts +0 -208
- package/src/presets/nodejs-server.ts +0 -104
- package/src/presets/nodejs-serverless.ts +0 -114
- package/src/presets/realtime-app.ts +0 -184
- package/src/presets/static-site.ts +0 -64
- package/src/presets/traditional-web-app.ts +0 -339
- package/src/presets/wordpress.ts +0 -138
- package/src/preview/github.test.ts +0 -249
- package/src/preview/github.ts +0 -297
- package/src/preview/manager.test.ts +0 -440
- package/src/preview/manager.ts +0 -326
- package/src/preview/notifications.test.ts +0 -582
- package/src/preview/notifications.ts +0 -341
- package/src/queue/batch-processing.ts +0 -402
- package/src/queue/dlq-monitoring.ts +0 -402
- package/src/queue/fifo.ts +0 -342
- package/src/queue/index.ts +0 -9
- package/src/queue/management.ts +0 -428
- package/src/queue/queue.test.ts +0 -429
- package/src/resource-mgmt/index.ts +0 -39
- package/src/resource-naming.ts +0 -62
- package/src/s3/index.ts +0 -523
- package/src/schema/cloud-config.schema.json +0 -554
- package/src/schema/index.ts +0 -68
- package/src/security/certificate-manager.ts +0 -492
- package/src/security/index.ts +0 -9
- package/src/security/scanning.ts +0 -545
- package/src/security/secrets-manager.ts +0 -476
- package/src/security/secrets-rotation.ts +0 -456
- package/src/security/security.test.ts +0 -738
- package/src/sms/advanced/ab-testing.ts +0 -389
- package/src/sms/advanced/analytics.ts +0 -336
- package/src/sms/advanced/campaigns.ts +0 -523
- package/src/sms/advanced/chatbot.ts +0 -224
- package/src/sms/advanced/index.ts +0 -10
- package/src/sms/advanced/link-tracking.ts +0 -248
- package/src/sms/advanced/mms.ts +0 -308
- package/src/sms/handlers/__tests__/send.test.ts +0 -40
- package/src/sms/index.ts +0 -9
- package/src/stack-diff.ts +0 -389
- package/src/static-site/index.ts +0 -85
- package/src/template-builder.ts +0 -110
- package/src/template-validator.ts +0 -574
- package/src/utils/cache.ts +0 -291
- package/src/utils/diff.ts +0 -269
- package/src/utils/hash.ts +0 -227
- package/src/utils/index.ts +0 -8
- package/src/utils/parallel.ts +0 -294
- package/src/validators/credentials.test.ts +0 -274
- package/src/validators/credentials.ts +0 -233
- package/src/validators/quotas.test.ts +0 -434
- package/src/validators/quotas.ts +0 -217
- package/test/ai.test.ts +0 -327
- package/test/api.test.ts +0 -511
- package/test/auth.test.ts +0 -632
- package/test/cache.test.ts +0 -406
- package/test/cdn.test.ts +0 -247
- package/test/compute.test.ts +0 -861
- package/test/database.test.ts +0 -523
- package/test/deployment.test.ts +0 -499
- package/test/dns.test.ts +0 -270
- package/test/email.test.ts +0 -439
- package/test/filesystem.test.ts +0 -382
- package/test/integration.test.ts +0 -350
- package/test/messaging.test.ts +0 -514
- package/test/monitoring.test.ts +0 -634
- package/test/network.test.ts +0 -425
- package/test/permissions.test.ts +0 -488
- package/test/queue.test.ts +0 -484
- package/test/registry.test.ts +0 -306
- package/test/security.test.ts +0 -462
- package/test/storage.test.ts +0 -463
- package/test/template-validator.test.ts +0 -559
- package/test/workflow.test.ts +0 -592
- package/tsconfig.json +0 -16
- package/tsconfig.tsbuildinfo +0 -1
package/src/modules/queue.ts
DELETED
|
@@ -1,1260 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
EventBridgeEcsParameters,
|
|
3
|
-
EventBridgeRule,
|
|
4
|
-
EventBridgeTarget,
|
|
5
|
-
SQSQueue,
|
|
6
|
-
} from '@stacksjs/ts-cloud-aws-types'
|
|
7
|
-
import type { EnvironmentType } from '@stacksjs/ts-cloud-types'
|
|
8
|
-
import { Fn } from '../intrinsic-functions'
|
|
9
|
-
import { generateLogicalId, generateResourceName } from '../resource-naming'
|
|
10
|
-
|
|
11
|
-
export interface QueueOptions {
|
|
12
|
-
slug: string
|
|
13
|
-
environment: EnvironmentType
|
|
14
|
-
name?: string
|
|
15
|
-
delaySeconds?: number
|
|
16
|
-
visibilityTimeout?: number
|
|
17
|
-
messageRetentionPeriod?: number
|
|
18
|
-
maxMessageSize?: number
|
|
19
|
-
receiveMessageWaitTime?: number
|
|
20
|
-
fifo?: boolean
|
|
21
|
-
contentBasedDeduplication?: boolean
|
|
22
|
-
encrypted?: boolean
|
|
23
|
-
kmsKeyId?: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface DeadLetterQueueOptions {
|
|
27
|
-
slug: string
|
|
28
|
-
environment: EnvironmentType
|
|
29
|
-
maxReceiveCount?: number
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface ScheduleOptions {
|
|
33
|
-
slug: string
|
|
34
|
-
environment: EnvironmentType
|
|
35
|
-
name?: string
|
|
36
|
-
description?: string
|
|
37
|
-
enabled?: boolean
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface EcsScheduleOptions extends ScheduleOptions {
|
|
41
|
-
taskDefinitionArn: string
|
|
42
|
-
clusterArn: string
|
|
43
|
-
subnets: string[]
|
|
44
|
-
securityGroups?: string[]
|
|
45
|
-
assignPublicIp?: boolean
|
|
46
|
-
taskCount?: number
|
|
47
|
-
containerOverrides?: Array<{
|
|
48
|
-
name: string
|
|
49
|
-
environment?: Array<{
|
|
50
|
-
name: string
|
|
51
|
-
value: string
|
|
52
|
-
}>
|
|
53
|
-
command?: string[]
|
|
54
|
-
}>
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export interface LambdaScheduleOptions extends ScheduleOptions {
|
|
58
|
-
functionArn: string
|
|
59
|
-
input?: Record<string, unknown>
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export interface SqsTargetOptions extends ScheduleOptions {
|
|
63
|
-
queueArn: string
|
|
64
|
-
messageGroupId?: string
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Queue & Scheduling Module - EventBridge + SQS
|
|
69
|
-
* Provides clean API for creating queues, cron jobs, and scheduled tasks
|
|
70
|
-
*/
|
|
71
|
-
export class Queue {
|
|
72
|
-
/**
|
|
73
|
-
* Create an SQS queue
|
|
74
|
-
*/
|
|
75
|
-
static createQueue(options: QueueOptions): {
|
|
76
|
-
queue: SQSQueue
|
|
77
|
-
logicalId: string
|
|
78
|
-
} {
|
|
79
|
-
const {
|
|
80
|
-
slug,
|
|
81
|
-
environment,
|
|
82
|
-
name,
|
|
83
|
-
delaySeconds = 0,
|
|
84
|
-
visibilityTimeout = 30,
|
|
85
|
-
messageRetentionPeriod = 345600, // 4 days
|
|
86
|
-
maxMessageSize = 262144, // 256 KB
|
|
87
|
-
receiveMessageWaitTime = 0,
|
|
88
|
-
fifo = false,
|
|
89
|
-
contentBasedDeduplication = false,
|
|
90
|
-
encrypted = true,
|
|
91
|
-
kmsKeyId,
|
|
92
|
-
} = options
|
|
93
|
-
|
|
94
|
-
const resourceName = name || generateResourceName({
|
|
95
|
-
slug,
|
|
96
|
-
environment,
|
|
97
|
-
resourceType: 'queue',
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
const queueName = fifo ? `${resourceName}.fifo` : resourceName
|
|
101
|
-
const logicalId = generateLogicalId(resourceName)
|
|
102
|
-
|
|
103
|
-
const queue: SQSQueue = {
|
|
104
|
-
Type: 'AWS::SQS::Queue',
|
|
105
|
-
Properties: {
|
|
106
|
-
QueueName: queueName,
|
|
107
|
-
DelaySeconds: delaySeconds,
|
|
108
|
-
MaximumMessageSize: maxMessageSize,
|
|
109
|
-
MessageRetentionPeriod: messageRetentionPeriod,
|
|
110
|
-
ReceiveMessageWaitTimeSeconds: receiveMessageWaitTime,
|
|
111
|
-
VisibilityTimeout: visibilityTimeout,
|
|
112
|
-
Tags: [
|
|
113
|
-
{ Key: 'Name', Value: queueName },
|
|
114
|
-
{ Key: 'Environment', Value: environment },
|
|
115
|
-
],
|
|
116
|
-
},
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (fifo) {
|
|
120
|
-
queue.Properties!.FifoQueue = true
|
|
121
|
-
queue.Properties!.ContentBasedDeduplication = contentBasedDeduplication
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (encrypted) {
|
|
125
|
-
if (kmsKeyId) {
|
|
126
|
-
queue.Properties!.KmsMasterKeyId = kmsKeyId
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
queue.Properties!.SqsManagedSseEnabled = true
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return { queue, logicalId }
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Create a dead letter queue and attach it to a source queue
|
|
138
|
-
*/
|
|
139
|
-
static createDeadLetterQueue(
|
|
140
|
-
sourceQueueLogicalId: string,
|
|
141
|
-
options: DeadLetterQueueOptions,
|
|
142
|
-
): {
|
|
143
|
-
deadLetterQueue: SQSQueue
|
|
144
|
-
updatedSourceQueue: SQSQueue
|
|
145
|
-
deadLetterLogicalId: string
|
|
146
|
-
} {
|
|
147
|
-
const {
|
|
148
|
-
slug,
|
|
149
|
-
environment,
|
|
150
|
-
maxReceiveCount = 3,
|
|
151
|
-
} = options
|
|
152
|
-
|
|
153
|
-
// Create DLQ
|
|
154
|
-
const dlqResourceName = generateResourceName({
|
|
155
|
-
slug,
|
|
156
|
-
environment,
|
|
157
|
-
resourceType: 'dlq',
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
const deadLetterLogicalId = generateLogicalId(dlqResourceName)
|
|
161
|
-
|
|
162
|
-
const deadLetterQueue: SQSQueue = {
|
|
163
|
-
Type: 'AWS::SQS::Queue',
|
|
164
|
-
Properties: {
|
|
165
|
-
QueueName: dlqResourceName,
|
|
166
|
-
MessageRetentionPeriod: 1209600, // 14 days
|
|
167
|
-
Tags: [
|
|
168
|
-
{ Key: 'Name', Value: dlqResourceName },
|
|
169
|
-
{ Key: 'Environment', Value: environment },
|
|
170
|
-
{ Key: 'Type', Value: 'DeadLetterQueue' },
|
|
171
|
-
],
|
|
172
|
-
},
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Update source queue with redrive policy
|
|
176
|
-
const updatedSourceQueue: SQSQueue = {
|
|
177
|
-
Type: 'AWS::SQS::Queue',
|
|
178
|
-
Properties: {
|
|
179
|
-
RedrivePolicy: {
|
|
180
|
-
deadLetterTargetArn: Fn.GetAtt(deadLetterLogicalId, 'Arn') as unknown as string,
|
|
181
|
-
maxReceiveCount,
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return {
|
|
187
|
-
deadLetterQueue,
|
|
188
|
-
updatedSourceQueue,
|
|
189
|
-
deadLetterLogicalId,
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Create an EventBridge rule with a cron schedule
|
|
195
|
-
*/
|
|
196
|
-
static createSchedule(
|
|
197
|
-
cronExpression: string,
|
|
198
|
-
options: ScheduleOptions,
|
|
199
|
-
): {
|
|
200
|
-
rule: EventBridgeRule
|
|
201
|
-
logicalId: string
|
|
202
|
-
} {
|
|
203
|
-
const {
|
|
204
|
-
slug,
|
|
205
|
-
environment,
|
|
206
|
-
name,
|
|
207
|
-
description,
|
|
208
|
-
enabled = true,
|
|
209
|
-
} = options
|
|
210
|
-
|
|
211
|
-
const resourceName = name || generateResourceName({
|
|
212
|
-
slug,
|
|
213
|
-
environment,
|
|
214
|
-
resourceType: 'schedule',
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
const logicalId = generateLogicalId(resourceName)
|
|
218
|
-
|
|
219
|
-
const rule: EventBridgeRule = {
|
|
220
|
-
Type: 'AWS::Events::Rule',
|
|
221
|
-
Properties: {
|
|
222
|
-
Name: resourceName,
|
|
223
|
-
Description: description,
|
|
224
|
-
ScheduleExpression: cronExpression,
|
|
225
|
-
State: enabled ? 'ENABLED' : 'DISABLED',
|
|
226
|
-
Targets: [],
|
|
227
|
-
},
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return { rule, logicalId }
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Schedule an ECS Fargate task with cron
|
|
235
|
-
*/
|
|
236
|
-
static scheduleEcsTask(
|
|
237
|
-
cronExpression: string,
|
|
238
|
-
roleArn: string,
|
|
239
|
-
options: EcsScheduleOptions,
|
|
240
|
-
): {
|
|
241
|
-
rule: EventBridgeRule
|
|
242
|
-
logicalId: string
|
|
243
|
-
} {
|
|
244
|
-
const {
|
|
245
|
-
slug,
|
|
246
|
-
environment,
|
|
247
|
-
name,
|
|
248
|
-
description,
|
|
249
|
-
enabled = true,
|
|
250
|
-
taskDefinitionArn,
|
|
251
|
-
clusterArn,
|
|
252
|
-
subnets,
|
|
253
|
-
securityGroups = [],
|
|
254
|
-
assignPublicIp = false,
|
|
255
|
-
taskCount = 1,
|
|
256
|
-
containerOverrides,
|
|
257
|
-
} = options
|
|
258
|
-
|
|
259
|
-
const resourceName = name || generateResourceName({
|
|
260
|
-
slug,
|
|
261
|
-
environment,
|
|
262
|
-
resourceType: 'ecs-schedule',
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
const logicalId = generateLogicalId(resourceName)
|
|
266
|
-
|
|
267
|
-
const ecsParameters: EventBridgeEcsParameters = {
|
|
268
|
-
TaskDefinitionArn: taskDefinitionArn,
|
|
269
|
-
TaskCount: taskCount,
|
|
270
|
-
LaunchType: 'FARGATE',
|
|
271
|
-
NetworkConfiguration: {
|
|
272
|
-
awsvpcConfiguration: {
|
|
273
|
-
Subnets: subnets,
|
|
274
|
-
SecurityGroups: securityGroups,
|
|
275
|
-
AssignPublicIp: assignPublicIp ? 'ENABLED' : 'DISABLED',
|
|
276
|
-
},
|
|
277
|
-
},
|
|
278
|
-
PlatformVersion: 'LATEST',
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const target: EventBridgeTarget = {
|
|
282
|
-
Id: '1',
|
|
283
|
-
Arn: clusterArn,
|
|
284
|
-
RoleArn: roleArn,
|
|
285
|
-
EcsParameters: ecsParameters,
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// Add container overrides if specified
|
|
289
|
-
if (containerOverrides && containerOverrides.length > 0) {
|
|
290
|
-
target.Input = JSON.stringify({
|
|
291
|
-
containerOverrides,
|
|
292
|
-
})
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const rule: EventBridgeRule = {
|
|
296
|
-
Type: 'AWS::Events::Rule',
|
|
297
|
-
Properties: {
|
|
298
|
-
Name: resourceName,
|
|
299
|
-
Description: description || `Scheduled ECS task: ${taskDefinitionArn}`,
|
|
300
|
-
ScheduleExpression: cronExpression,
|
|
301
|
-
State: enabled ? 'ENABLED' : 'DISABLED',
|
|
302
|
-
Targets: [target],
|
|
303
|
-
},
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
return { rule, logicalId }
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Schedule a Lambda function with cron
|
|
311
|
-
*/
|
|
312
|
-
static scheduleLambda(
|
|
313
|
-
cronExpression: string,
|
|
314
|
-
options: LambdaScheduleOptions,
|
|
315
|
-
): {
|
|
316
|
-
rule: EventBridgeRule
|
|
317
|
-
logicalId: string
|
|
318
|
-
} {
|
|
319
|
-
const {
|
|
320
|
-
slug,
|
|
321
|
-
environment,
|
|
322
|
-
name,
|
|
323
|
-
description,
|
|
324
|
-
enabled = true,
|
|
325
|
-
functionArn,
|
|
326
|
-
input,
|
|
327
|
-
} = options
|
|
328
|
-
|
|
329
|
-
const resourceName = name || generateResourceName({
|
|
330
|
-
slug,
|
|
331
|
-
environment,
|
|
332
|
-
resourceType: 'lambda-schedule',
|
|
333
|
-
})
|
|
334
|
-
|
|
335
|
-
const logicalId = generateLogicalId(resourceName)
|
|
336
|
-
|
|
337
|
-
const target: EventBridgeTarget = {
|
|
338
|
-
Id: '1',
|
|
339
|
-
Arn: functionArn,
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
if (input) {
|
|
343
|
-
target.Input = JSON.stringify(input)
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
const rule: EventBridgeRule = {
|
|
347
|
-
Type: 'AWS::Events::Rule',
|
|
348
|
-
Properties: {
|
|
349
|
-
Name: resourceName,
|
|
350
|
-
Description: description || `Scheduled Lambda: ${functionArn}`,
|
|
351
|
-
ScheduleExpression: cronExpression,
|
|
352
|
-
State: enabled ? 'ENABLED' : 'DISABLED',
|
|
353
|
-
Targets: [target],
|
|
354
|
-
},
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
return { rule, logicalId }
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Schedule an SQS message with cron
|
|
362
|
-
*/
|
|
363
|
-
static scheduleSqsMessage(
|
|
364
|
-
cronExpression: string,
|
|
365
|
-
options: SqsTargetOptions,
|
|
366
|
-
): {
|
|
367
|
-
rule: EventBridgeRule
|
|
368
|
-
logicalId: string
|
|
369
|
-
} {
|
|
370
|
-
const {
|
|
371
|
-
slug,
|
|
372
|
-
environment,
|
|
373
|
-
name,
|
|
374
|
-
description,
|
|
375
|
-
enabled = true,
|
|
376
|
-
queueArn,
|
|
377
|
-
messageGroupId,
|
|
378
|
-
} = options
|
|
379
|
-
|
|
380
|
-
const resourceName = name || generateResourceName({
|
|
381
|
-
slug,
|
|
382
|
-
environment,
|
|
383
|
-
resourceType: 'sqs-schedule',
|
|
384
|
-
})
|
|
385
|
-
|
|
386
|
-
const logicalId = generateLogicalId(resourceName)
|
|
387
|
-
|
|
388
|
-
const target: EventBridgeTarget = {
|
|
389
|
-
Id: '1',
|
|
390
|
-
Arn: queueArn,
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
if (messageGroupId) {
|
|
394
|
-
target.SqsParameters = {
|
|
395
|
-
MessageGroupId: messageGroupId,
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const rule: EventBridgeRule = {
|
|
400
|
-
Type: 'AWS::Events::Rule',
|
|
401
|
-
Properties: {
|
|
402
|
-
Name: resourceName,
|
|
403
|
-
Description: description || `Scheduled SQS message: ${queueArn}`,
|
|
404
|
-
ScheduleExpression: cronExpression,
|
|
405
|
-
State: enabled ? 'ENABLED' : 'DISABLED',
|
|
406
|
-
Targets: [target],
|
|
407
|
-
},
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
return { rule, logicalId }
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* Add a target to an existing EventBridge rule
|
|
415
|
-
*/
|
|
416
|
-
static addTarget(
|
|
417
|
-
rule: EventBridgeRule,
|
|
418
|
-
target: {
|
|
419
|
-
id: string
|
|
420
|
-
arn: string
|
|
421
|
-
roleArn?: string
|
|
422
|
-
input?: Record<string, unknown>
|
|
423
|
-
},
|
|
424
|
-
): EventBridgeRule {
|
|
425
|
-
if (!rule.Properties.Targets) {
|
|
426
|
-
rule.Properties.Targets = []
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
const eventTarget: EventBridgeTarget = {
|
|
430
|
-
Id: target.id,
|
|
431
|
-
Arn: target.arn,
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
if (target.roleArn) {
|
|
435
|
-
eventTarget.RoleArn = target.roleArn
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
if (target.input) {
|
|
439
|
-
eventTarget.Input = JSON.stringify(target.input)
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
rule.Properties.Targets.push(eventTarget)
|
|
443
|
-
|
|
444
|
-
return rule
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
/**
|
|
448
|
-
* Helper: Convert cron expression to rate expression
|
|
449
|
-
* EventBridge supports both cron() and rate() expressions
|
|
450
|
-
*/
|
|
451
|
-
static toCronExpression(expression: string): string {
|
|
452
|
-
// If already in cron() or rate() format, return as-is
|
|
453
|
-
if (expression.startsWith('cron(') || expression.startsWith('rate(')) {
|
|
454
|
-
return expression
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
// Otherwise, wrap in cron()
|
|
458
|
-
return `cron(${expression})`
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/**
|
|
462
|
-
* Helper: Create rate expression
|
|
463
|
-
*/
|
|
464
|
-
static rateExpression(value: number, unit: 'minute' | 'minutes' | 'hour' | 'hours' | 'day' | 'days'): string {
|
|
465
|
-
return `rate(${value} ${unit})`
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
/**
|
|
469
|
-
* Common cron expressions
|
|
470
|
-
*/
|
|
471
|
-
static readonly CronExpressions = {
|
|
472
|
-
EveryMinute: 'cron(* * * * ? *)',
|
|
473
|
-
Every5Minutes: 'cron(*/5 * * * ? *)',
|
|
474
|
-
Every15Minutes: 'cron(*/15 * * * ? *)',
|
|
475
|
-
Every30Minutes: 'cron(*/30 * * * ? *)',
|
|
476
|
-
Hourly: 'cron(0 * * * ? *)',
|
|
477
|
-
Daily: 'cron(0 0 * * ? *)',
|
|
478
|
-
DailyAt9AM: 'cron(0 9 * * ? *)',
|
|
479
|
-
DailyAtMidnight: 'cron(0 0 * * ? *)',
|
|
480
|
-
Weekly: 'cron(0 0 ? * SUN *)',
|
|
481
|
-
Monthly: 'cron(0 0 1 * ? *)',
|
|
482
|
-
} as const
|
|
483
|
-
|
|
484
|
-
/**
|
|
485
|
-
* Common rate expressions
|
|
486
|
-
*/
|
|
487
|
-
static readonly RateExpressions = {
|
|
488
|
-
Every1Minute: 'rate(1 minute)',
|
|
489
|
-
Every5Minutes: 'rate(5 minutes)',
|
|
490
|
-
Every10Minutes: 'rate(10 minutes)',
|
|
491
|
-
Every15Minutes: 'rate(15 minutes)',
|
|
492
|
-
Every30Minutes: 'rate(30 minutes)',
|
|
493
|
-
Every1Hour: 'rate(1 hour)',
|
|
494
|
-
Every6Hours: 'rate(6 hours)',
|
|
495
|
-
Every12Hours: 'rate(12 hours)',
|
|
496
|
-
Every1Day: 'rate(1 day)',
|
|
497
|
-
} as const
|
|
498
|
-
|
|
499
|
-
/**
|
|
500
|
-
* Convert a human-readable rate string to a cron/rate expression
|
|
501
|
-
* Supports formats like: "every 5 minutes", "every hour", "daily at 9am", etc.
|
|
502
|
-
*/
|
|
503
|
-
static rateStringToExpression(rateString: string): string {
|
|
504
|
-
const normalized = rateString.toLowerCase().trim()
|
|
505
|
-
|
|
506
|
-
// Every X minutes/hours/days patterns
|
|
507
|
-
const everyMatch = normalized.match(/^every\s+(\d+)\s+(minute|minutes|hour|hours|day|days)$/i)
|
|
508
|
-
if (everyMatch) {
|
|
509
|
-
const value = parseInt(everyMatch[1], 10)
|
|
510
|
-
const unit = everyMatch[2].toLowerCase()
|
|
511
|
-
const singularUnit = unit.endsWith('s') && value === 1 ? unit.slice(0, -1) : unit
|
|
512
|
-
const pluralUnit = !unit.endsWith('s') && value > 1 ? `${unit}s` : unit
|
|
513
|
-
return `rate(${value} ${value === 1 ? singularUnit : pluralUnit})`
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// "every minute", "every hour", "every day"
|
|
517
|
-
const singleMatch = normalized.match(/^every\s+(minute|hour|day)$/i)
|
|
518
|
-
if (singleMatch) {
|
|
519
|
-
return `rate(1 ${singleMatch[1]})`
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// Time-based patterns
|
|
523
|
-
const timePatterns: Record<string, string> = {
|
|
524
|
-
'every minute': 'rate(1 minute)',
|
|
525
|
-
'every hour': 'rate(1 hour)',
|
|
526
|
-
'every day': 'rate(1 day)',
|
|
527
|
-
'hourly': 'cron(0 * * * ? *)',
|
|
528
|
-
'daily': 'cron(0 0 * * ? *)',
|
|
529
|
-
'daily at midnight': 'cron(0 0 * * ? *)',
|
|
530
|
-
'weekly': 'cron(0 0 ? * SUN *)',
|
|
531
|
-
'monthly': 'cron(0 0 1 * ? *)',
|
|
532
|
-
'yearly': 'cron(0 0 1 1 ? *)',
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
if (timePatterns[normalized]) {
|
|
536
|
-
return timePatterns[normalized]
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
// "daily at Xam/pm" pattern
|
|
540
|
-
const dailyAtMatch = normalized.match(/^daily\s+at\s+(\d{1,2})\s*(am|pm)?$/i)
|
|
541
|
-
if (dailyAtMatch) {
|
|
542
|
-
let hour = parseInt(dailyAtMatch[1], 10)
|
|
543
|
-
const meridiem = dailyAtMatch[2]?.toLowerCase()
|
|
544
|
-
|
|
545
|
-
if (meridiem === 'pm' && hour !== 12) {
|
|
546
|
-
hour += 12
|
|
547
|
-
}
|
|
548
|
-
else if (meridiem === 'am' && hour === 12) {
|
|
549
|
-
hour = 0
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
return `cron(0 ${hour} * * ? *)`
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
// "at HH:MM" pattern
|
|
556
|
-
const atTimeMatch = normalized.match(/^at\s+(\d{1,2}):(\d{2})$/i)
|
|
557
|
-
if (atTimeMatch) {
|
|
558
|
-
const hour = parseInt(atTimeMatch[1], 10)
|
|
559
|
-
const minute = parseInt(atTimeMatch[2], 10)
|
|
560
|
-
return `cron(${minute} ${hour} * * ? *)`
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// Weekday patterns
|
|
564
|
-
const weekdayMap: Record<string, string> = {
|
|
565
|
-
monday: 'MON',
|
|
566
|
-
tuesday: 'TUE',
|
|
567
|
-
wednesday: 'WED',
|
|
568
|
-
thursday: 'THU',
|
|
569
|
-
friday: 'FRI',
|
|
570
|
-
saturday: 'SAT',
|
|
571
|
-
sunday: 'SUN',
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
const weekdayMatch = normalized.match(/^every\s+(monday|tuesday|wednesday|thursday|friday|saturday|sunday)$/i)
|
|
575
|
-
if (weekdayMatch) {
|
|
576
|
-
const day = weekdayMap[weekdayMatch[1].toLowerCase()]
|
|
577
|
-
return `cron(0 0 ? * ${day} *)`
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// If nothing matched, assume it's already a cron expression
|
|
581
|
-
return Queue.toCronExpression(rateString)
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
/**
|
|
585
|
-
* Job configuration interface
|
|
586
|
-
*/
|
|
587
|
-
static readonly JobConfig = {
|
|
588
|
-
/**
|
|
589
|
-
* Create job configuration with retry settings
|
|
590
|
-
*/
|
|
591
|
-
create: (options: {
|
|
592
|
-
name: string
|
|
593
|
-
handler: string
|
|
594
|
-
retries?: number
|
|
595
|
-
backoff?: 'linear' | 'exponential' | 'fixed'
|
|
596
|
-
backoffDelay?: number
|
|
597
|
-
maxDelay?: number
|
|
598
|
-
timeout?: number
|
|
599
|
-
jitter?: boolean
|
|
600
|
-
}): JobConfiguration => {
|
|
601
|
-
const {
|
|
602
|
-
name,
|
|
603
|
-
handler,
|
|
604
|
-
retries = 3,
|
|
605
|
-
backoff = 'exponential',
|
|
606
|
-
backoffDelay = 1000, // 1 second
|
|
607
|
-
maxDelay = 30000, // 30 seconds
|
|
608
|
-
timeout = 60000, // 1 minute
|
|
609
|
-
jitter = true,
|
|
610
|
-
} = options
|
|
611
|
-
|
|
612
|
-
return {
|
|
613
|
-
name,
|
|
614
|
-
handler,
|
|
615
|
-
retries,
|
|
616
|
-
backoff,
|
|
617
|
-
backoffDelay,
|
|
618
|
-
maxDelay,
|
|
619
|
-
timeout,
|
|
620
|
-
jitter,
|
|
621
|
-
}
|
|
622
|
-
},
|
|
623
|
-
|
|
624
|
-
/**
|
|
625
|
-
* Calculate delay for a given retry attempt
|
|
626
|
-
*/
|
|
627
|
-
calculateDelay: (
|
|
628
|
-
attempt: number,
|
|
629
|
-
config: Pick<JobConfiguration, 'backoff' | 'backoffDelay' | 'maxDelay' | 'jitter'>,
|
|
630
|
-
): number => {
|
|
631
|
-
const { backoff, backoffDelay, maxDelay, jitter } = config
|
|
632
|
-
let delay: number
|
|
633
|
-
|
|
634
|
-
switch (backoff) {
|
|
635
|
-
case 'linear':
|
|
636
|
-
delay = backoffDelay * attempt
|
|
637
|
-
break
|
|
638
|
-
case 'exponential':
|
|
639
|
-
delay = backoffDelay * 2 ** (attempt - 1)
|
|
640
|
-
break
|
|
641
|
-
case 'fixed':
|
|
642
|
-
default:
|
|
643
|
-
delay = backoffDelay
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
// Cap at maxDelay
|
|
647
|
-
delay = Math.min(delay, maxDelay || Number.MAX_SAFE_INTEGER)
|
|
648
|
-
|
|
649
|
-
// Add jitter if enabled (0-25% random variation)
|
|
650
|
-
if (jitter) {
|
|
651
|
-
const jitterAmount = delay * 0.25 * Math.random()
|
|
652
|
-
delay += jitterAmount
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
return Math.floor(delay)
|
|
656
|
-
},
|
|
657
|
-
|
|
658
|
-
/**
|
|
659
|
-
* Common job configurations
|
|
660
|
-
*/
|
|
661
|
-
presets: {
|
|
662
|
-
/**
|
|
663
|
-
* Fast retry for transient failures
|
|
664
|
-
*/
|
|
665
|
-
fastRetry: {
|
|
666
|
-
retries: 5,
|
|
667
|
-
backoff: 'exponential' as const,
|
|
668
|
-
backoffDelay: 100,
|
|
669
|
-
maxDelay: 5000,
|
|
670
|
-
jitter: true,
|
|
671
|
-
},
|
|
672
|
-
|
|
673
|
-
/**
|
|
674
|
-
* Standard job with moderate retries
|
|
675
|
-
*/
|
|
676
|
-
standard: {
|
|
677
|
-
retries: 3,
|
|
678
|
-
backoff: 'exponential' as const,
|
|
679
|
-
backoffDelay: 1000,
|
|
680
|
-
maxDelay: 30000,
|
|
681
|
-
jitter: true,
|
|
682
|
-
},
|
|
683
|
-
|
|
684
|
-
/**
|
|
685
|
-
* Long-running job with extended timeouts
|
|
686
|
-
*/
|
|
687
|
-
longRunning: {
|
|
688
|
-
retries: 2,
|
|
689
|
-
backoff: 'exponential' as const,
|
|
690
|
-
backoffDelay: 5000,
|
|
691
|
-
maxDelay: 60000,
|
|
692
|
-
timeout: 300000, // 5 minutes
|
|
693
|
-
jitter: true,
|
|
694
|
-
},
|
|
695
|
-
|
|
696
|
-
/**
|
|
697
|
-
* Critical job with many retries
|
|
698
|
-
*/
|
|
699
|
-
critical: {
|
|
700
|
-
retries: 10,
|
|
701
|
-
backoff: 'exponential' as const,
|
|
702
|
-
backoffDelay: 500,
|
|
703
|
-
maxDelay: 60000,
|
|
704
|
-
jitter: true,
|
|
705
|
-
},
|
|
706
|
-
|
|
707
|
-
/**
|
|
708
|
-
* No retry (fire and forget)
|
|
709
|
-
*/
|
|
710
|
-
noRetry: {
|
|
711
|
-
retries: 0,
|
|
712
|
-
backoff: 'fixed' as const,
|
|
713
|
-
backoffDelay: 0,
|
|
714
|
-
maxDelay: 0,
|
|
715
|
-
jitter: false,
|
|
716
|
-
},
|
|
717
|
-
},
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
/**
|
|
721
|
-
* Create ECS container override for job execution
|
|
722
|
-
*/
|
|
723
|
-
static createJobContainerOverride(options: {
|
|
724
|
-
containerName: string
|
|
725
|
-
jobClass: string
|
|
726
|
-
jobData?: Record<string, unknown>
|
|
727
|
-
environment?: Record<string, string>
|
|
728
|
-
}): {
|
|
729
|
-
name: string
|
|
730
|
-
command: string[]
|
|
731
|
-
environment: Array<{ name: string, value: string }>
|
|
732
|
-
} {
|
|
733
|
-
const { containerName, jobClass, jobData, environment = {} } = options
|
|
734
|
-
|
|
735
|
-
const envVars: Array<{ name: string, value: string }> = [
|
|
736
|
-
{ name: 'JOB_CLASS', value: jobClass },
|
|
737
|
-
]
|
|
738
|
-
|
|
739
|
-
if (jobData) {
|
|
740
|
-
envVars.push({ name: 'JOB_DATA', value: JSON.stringify(jobData) })
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
for (const [key, value] of Object.entries(environment)) {
|
|
744
|
-
envVars.push({ name: key, value })
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
return {
|
|
748
|
-
name: containerName,
|
|
749
|
-
command: ['bun', 'run', 'app/Jobs/runner.ts'],
|
|
750
|
-
environment: envVars,
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
/**
|
|
755
|
-
* Generate scheduled job resources
|
|
756
|
-
*/
|
|
757
|
-
static createScheduledJob(options: {
|
|
758
|
-
slug: string
|
|
759
|
-
environment: EnvironmentType
|
|
760
|
-
schedule: string
|
|
761
|
-
jobClass: string
|
|
762
|
-
jobData?: Record<string, unknown>
|
|
763
|
-
taskDefinitionArn: string
|
|
764
|
-
clusterArn: string
|
|
765
|
-
subnets: string[]
|
|
766
|
-
securityGroups?: string[]
|
|
767
|
-
roleArn: string
|
|
768
|
-
containerName?: string
|
|
769
|
-
}): {
|
|
770
|
-
rule: EventBridgeRule
|
|
771
|
-
logicalId: string
|
|
772
|
-
} {
|
|
773
|
-
const {
|
|
774
|
-
slug,
|
|
775
|
-
environment,
|
|
776
|
-
schedule,
|
|
777
|
-
jobClass,
|
|
778
|
-
jobData,
|
|
779
|
-
taskDefinitionArn,
|
|
780
|
-
clusterArn,
|
|
781
|
-
subnets,
|
|
782
|
-
securityGroups = [],
|
|
783
|
-
roleArn,
|
|
784
|
-
containerName = 'app',
|
|
785
|
-
} = options
|
|
786
|
-
|
|
787
|
-
// Convert schedule string to expression
|
|
788
|
-
const scheduleExpression = Queue.rateStringToExpression(schedule)
|
|
789
|
-
|
|
790
|
-
// Create container override
|
|
791
|
-
const containerOverride = Queue.createJobContainerOverride({
|
|
792
|
-
containerName,
|
|
793
|
-
jobClass,
|
|
794
|
-
jobData,
|
|
795
|
-
})
|
|
796
|
-
|
|
797
|
-
return Queue.scheduleEcsTask(scheduleExpression, roleArn, {
|
|
798
|
-
slug,
|
|
799
|
-
environment,
|
|
800
|
-
name: `${slug}-${jobClass.toLowerCase().replace(/[^a-z0-9]/g, '-')}`,
|
|
801
|
-
description: `Scheduled job: ${jobClass}`,
|
|
802
|
-
taskDefinitionArn,
|
|
803
|
-
clusterArn,
|
|
804
|
-
subnets,
|
|
805
|
-
securityGroups,
|
|
806
|
-
containerOverrides: [containerOverride],
|
|
807
|
-
})
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
/**
|
|
811
|
-
* Queue presets for common use cases
|
|
812
|
-
*/
|
|
813
|
-
static readonly QueuePresets = {
|
|
814
|
-
/**
|
|
815
|
-
* High-throughput queue with short visibility
|
|
816
|
-
*/
|
|
817
|
-
highThroughput: (slug: string, environment: EnvironmentType): {
|
|
818
|
-
queue: SQSQueue
|
|
819
|
-
logicalId: string
|
|
820
|
-
} =>
|
|
821
|
-
Queue.createQueue({
|
|
822
|
-
slug,
|
|
823
|
-
environment,
|
|
824
|
-
visibilityTimeout: 30,
|
|
825
|
-
receiveMessageWaitTime: 0,
|
|
826
|
-
delaySeconds: 0,
|
|
827
|
-
}),
|
|
828
|
-
|
|
829
|
-
/**
|
|
830
|
-
* Long-running job queue
|
|
831
|
-
*/
|
|
832
|
-
longRunning: (slug: string, environment: EnvironmentType): {
|
|
833
|
-
queue: SQSQueue
|
|
834
|
-
logicalId: string
|
|
835
|
-
} =>
|
|
836
|
-
Queue.createQueue({
|
|
837
|
-
slug,
|
|
838
|
-
environment,
|
|
839
|
-
visibilityTimeout: 300, // 5 minutes
|
|
840
|
-
messageRetentionPeriod: 1209600, // 14 days
|
|
841
|
-
}),
|
|
842
|
-
|
|
843
|
-
/**
|
|
844
|
-
* FIFO queue for ordered processing
|
|
845
|
-
*/
|
|
846
|
-
fifo: (slug: string, environment: EnvironmentType): {
|
|
847
|
-
queue: SQSQueue
|
|
848
|
-
logicalId: string
|
|
849
|
-
} =>
|
|
850
|
-
Queue.createQueue({
|
|
851
|
-
slug,
|
|
852
|
-
environment,
|
|
853
|
-
fifo: true,
|
|
854
|
-
contentBasedDeduplication: true,
|
|
855
|
-
}),
|
|
856
|
-
|
|
857
|
-
/**
|
|
858
|
-
* Delayed queue for scheduled messages
|
|
859
|
-
*/
|
|
860
|
-
delayed: (slug: string, environment: EnvironmentType, delaySeconds: number = 60): {
|
|
861
|
-
queue: SQSQueue
|
|
862
|
-
logicalId: string
|
|
863
|
-
} =>
|
|
864
|
-
Queue.createQueue({
|
|
865
|
-
slug,
|
|
866
|
-
environment,
|
|
867
|
-
delaySeconds,
|
|
868
|
-
}),
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
/**
|
|
873
|
-
* Job configuration type
|
|
874
|
-
*/
|
|
875
|
-
export interface JobConfiguration {
|
|
876
|
-
name: string
|
|
877
|
-
handler: string
|
|
878
|
-
retries: number
|
|
879
|
-
backoff: 'linear' | 'exponential' | 'fixed'
|
|
880
|
-
backoffDelay: number
|
|
881
|
-
maxDelay: number
|
|
882
|
-
timeout: number
|
|
883
|
-
jitter: boolean
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
/**
|
|
887
|
-
* Discovered job definition from file scanning
|
|
888
|
-
*/
|
|
889
|
-
export interface DiscoveredJob {
|
|
890
|
-
name: string
|
|
891
|
-
path: string
|
|
892
|
-
schedule?: string
|
|
893
|
-
handler: string
|
|
894
|
-
enabled: boolean
|
|
895
|
-
retries?: number
|
|
896
|
-
backoff?: 'linear' | 'exponential' | 'fixed'
|
|
897
|
-
timeout?: number
|
|
898
|
-
description?: string
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
/**
|
|
902
|
-
* Discovered action definition from file scanning
|
|
903
|
-
*/
|
|
904
|
-
export interface DiscoveredAction {
|
|
905
|
-
name: string
|
|
906
|
-
path: string
|
|
907
|
-
handler: string
|
|
908
|
-
description?: string
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
/**
|
|
912
|
-
* Dynamic job and action loader for Stacks framework integration
|
|
913
|
-
*/
|
|
914
|
-
export class JobLoader {
|
|
915
|
-
/**
|
|
916
|
-
* Discover jobs from app/Jobs directory
|
|
917
|
-
* Scans *.ts files and extracts job metadata from exports
|
|
918
|
-
*/
|
|
919
|
-
static async discoverJobs(options: {
|
|
920
|
-
projectRoot: string
|
|
921
|
-
jobsPath?: string
|
|
922
|
-
}): Promise<DiscoveredJob[]> {
|
|
923
|
-
const { projectRoot, jobsPath = 'app/Jobs' } = options
|
|
924
|
-
const fullPath = `${projectRoot}/${jobsPath}`
|
|
925
|
-
const jobs: DiscoveredJob[] = []
|
|
926
|
-
|
|
927
|
-
try {
|
|
928
|
-
// Use dynamic import to check if path functions exist (Node/Bun compatibility)
|
|
929
|
-
const fs = await import('node:fs')
|
|
930
|
-
const path = await import('node:path')
|
|
931
|
-
|
|
932
|
-
if (!fs.existsSync(fullPath)) {
|
|
933
|
-
return []
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
const files = fs.readdirSync(fullPath)
|
|
937
|
-
.filter((f: string) => f.endsWith('.ts') && !f.startsWith('_') && f !== 'runner.ts')
|
|
938
|
-
|
|
939
|
-
for (const file of files) {
|
|
940
|
-
const filePath = path.join(fullPath, file)
|
|
941
|
-
const jobName = file.replace('.ts', '')
|
|
942
|
-
|
|
943
|
-
// Read file content to extract metadata
|
|
944
|
-
const content = fs.readFileSync(filePath, 'utf-8')
|
|
945
|
-
const metadata = JobLoader.parseJobMetadata(content, jobName, filePath)
|
|
946
|
-
|
|
947
|
-
if (metadata) {
|
|
948
|
-
jobs.push(metadata)
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
catch {
|
|
953
|
-
// File system not available (browser context), return empty
|
|
954
|
-
return []
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
return jobs
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
/**
|
|
961
|
-
* Parse job metadata from file content
|
|
962
|
-
* Looks for exported schedule, handle function, and config
|
|
963
|
-
*/
|
|
964
|
-
static parseJobMetadata(
|
|
965
|
-
content: string,
|
|
966
|
-
name: string,
|
|
967
|
-
path: string,
|
|
968
|
-
): DiscoveredJob | null {
|
|
969
|
-
// Look for schedule export
|
|
970
|
-
const scheduleMatch = content.match(/export\s+const\s+schedule\s*=\s*['"`]([^'"`]+)['"`]/)
|
|
971
|
-
|| content.match(/schedule:\s*['"`]([^'"`]+)['"`]/)
|
|
972
|
-
|
|
973
|
-
// Look for enabled flag
|
|
974
|
-
const enabledMatch = content.match(/export\s+const\s+enabled\s*=\s*(true|false)/)
|
|
975
|
-
|| content.match(/enabled:\s*(true|false)/)
|
|
976
|
-
|
|
977
|
-
// Look for retries
|
|
978
|
-
const retriesMatch = content.match(/export\s+const\s+retries\s*=\s*(\d+)/)
|
|
979
|
-
|| content.match(/retries:\s*(\d+)/)
|
|
980
|
-
|
|
981
|
-
// Look for timeout
|
|
982
|
-
const timeoutMatch = content.match(/export\s+const\s+timeout\s*=\s*(\d+)/)
|
|
983
|
-
|| content.match(/timeout:\s*(\d+)/)
|
|
984
|
-
|
|
985
|
-
// Look for backoff strategy
|
|
986
|
-
const backoffMatch = content.match(/export\s+const\s+backoff\s*=\s*['"`](linear|exponential|fixed)['"`]/)
|
|
987
|
-
|| content.match(/backoff:\s*['"`](linear|exponential|fixed)['"`]/)
|
|
988
|
-
|
|
989
|
-
// Look for description
|
|
990
|
-
const descriptionMatch = content.match(/export\s+const\s+description\s*=\s*['"`]([^'"`]+)['"`]/)
|
|
991
|
-
|| content.match(/description:\s*['"`]([^'"`]+)['"`]/)
|
|
992
|
-
|
|
993
|
-
// Check if there's a handle function or default export
|
|
994
|
-
const hasHandle = content.includes('export async function handle')
|
|
995
|
-
|| content.includes('export function handle')
|
|
996
|
-
|| content.includes('export default')
|
|
997
|
-
|
|
998
|
-
if (!hasHandle) {
|
|
999
|
-
return null
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
return {
|
|
1003
|
-
name,
|
|
1004
|
-
path,
|
|
1005
|
-
schedule: scheduleMatch?.[1],
|
|
1006
|
-
handler: `${name}.handle`,
|
|
1007
|
-
enabled: enabledMatch?.[1] !== 'false',
|
|
1008
|
-
retries: retriesMatch ? parseInt(retriesMatch[1], 10) : undefined,
|
|
1009
|
-
backoff: backoffMatch?.[1] as 'linear' | 'exponential' | 'fixed' | undefined,
|
|
1010
|
-
timeout: timeoutMatch ? parseInt(timeoutMatch[1], 10) : undefined,
|
|
1011
|
-
description: descriptionMatch?.[1],
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
/**
|
|
1016
|
-
* Discover actions from app/Actions directory
|
|
1017
|
-
* Scans *.ts files and extracts action metadata
|
|
1018
|
-
*/
|
|
1019
|
-
static async discoverActions(options: {
|
|
1020
|
-
projectRoot: string
|
|
1021
|
-
actionsPath?: string
|
|
1022
|
-
}): Promise<DiscoveredAction[]> {
|
|
1023
|
-
const { projectRoot, actionsPath = 'app/Actions' } = options
|
|
1024
|
-
const fullPath = `${projectRoot}/${actionsPath}`
|
|
1025
|
-
const actions: DiscoveredAction[] = []
|
|
1026
|
-
|
|
1027
|
-
try {
|
|
1028
|
-
const fs = await import('node:fs')
|
|
1029
|
-
const path = await import('node:path')
|
|
1030
|
-
|
|
1031
|
-
if (!fs.existsSync(fullPath)) {
|
|
1032
|
-
return []
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
const files = fs.readdirSync(fullPath)
|
|
1036
|
-
.filter((f: string) => f.endsWith('.ts') && !f.startsWith('_'))
|
|
1037
|
-
|
|
1038
|
-
for (const file of files) {
|
|
1039
|
-
const filePath = path.join(fullPath, file)
|
|
1040
|
-
const actionName = file.replace('.ts', '')
|
|
1041
|
-
|
|
1042
|
-
const content = fs.readFileSync(filePath, 'utf-8')
|
|
1043
|
-
const metadata = JobLoader.parseActionMetadata(content, actionName, filePath)
|
|
1044
|
-
|
|
1045
|
-
if (metadata) {
|
|
1046
|
-
actions.push(metadata)
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
catch {
|
|
1051
|
-
return []
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
return actions
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
/**
|
|
1058
|
-
* Parse action metadata from file content
|
|
1059
|
-
*/
|
|
1060
|
-
static parseActionMetadata(
|
|
1061
|
-
content: string,
|
|
1062
|
-
name: string,
|
|
1063
|
-
path: string,
|
|
1064
|
-
): DiscoveredAction | null {
|
|
1065
|
-
// Check if there's a handle function or default export
|
|
1066
|
-
const hasHandle = content.includes('export async function handle')
|
|
1067
|
-
|| content.includes('export function handle')
|
|
1068
|
-
|| content.includes('export default')
|
|
1069
|
-
|
|
1070
|
-
if (!hasHandle) {
|
|
1071
|
-
return null
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
// Look for description
|
|
1075
|
-
const descriptionMatch = content.match(/export\s+const\s+description\s*=\s*['"`]([^'"`]+)['"`]/)
|
|
1076
|
-
|| content.match(/description:\s*['"`]([^'"`]+)['"`]/)
|
|
1077
|
-
|
|
1078
|
-
return {
|
|
1079
|
-
name,
|
|
1080
|
-
path,
|
|
1081
|
-
handler: `${name}.handle`,
|
|
1082
|
-
description: descriptionMatch?.[1],
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
/**
|
|
1087
|
-
* Generate scheduled job resources from discovered jobs
|
|
1088
|
-
*/
|
|
1089
|
-
static generateScheduledJobResources(options: {
|
|
1090
|
-
slug: string
|
|
1091
|
-
environment: EnvironmentType
|
|
1092
|
-
jobs: DiscoveredJob[]
|
|
1093
|
-
taskDefinitionArn: string
|
|
1094
|
-
clusterArn: string
|
|
1095
|
-
subnets: string[]
|
|
1096
|
-
securityGroups?: string[]
|
|
1097
|
-
roleArn: string
|
|
1098
|
-
containerName?: string
|
|
1099
|
-
}): {
|
|
1100
|
-
rules: Record<string, EventBridgeRule>
|
|
1101
|
-
count: number
|
|
1102
|
-
} {
|
|
1103
|
-
const {
|
|
1104
|
-
slug,
|
|
1105
|
-
environment,
|
|
1106
|
-
jobs,
|
|
1107
|
-
taskDefinitionArn,
|
|
1108
|
-
clusterArn,
|
|
1109
|
-
subnets,
|
|
1110
|
-
securityGroups = [],
|
|
1111
|
-
roleArn,
|
|
1112
|
-
containerName = 'app',
|
|
1113
|
-
} = options
|
|
1114
|
-
|
|
1115
|
-
const rules: Record<string, EventBridgeRule> = {}
|
|
1116
|
-
let count = 0
|
|
1117
|
-
|
|
1118
|
-
for (const job of jobs) {
|
|
1119
|
-
// Skip jobs without schedules or disabled jobs
|
|
1120
|
-
if (!job.schedule || !job.enabled) {
|
|
1121
|
-
continue
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
const { rule, logicalId } = Queue.createScheduledJob({
|
|
1125
|
-
slug,
|
|
1126
|
-
environment,
|
|
1127
|
-
schedule: job.schedule,
|
|
1128
|
-
jobClass: job.name,
|
|
1129
|
-
taskDefinitionArn,
|
|
1130
|
-
clusterArn,
|
|
1131
|
-
subnets,
|
|
1132
|
-
securityGroups,
|
|
1133
|
-
roleArn,
|
|
1134
|
-
containerName,
|
|
1135
|
-
})
|
|
1136
|
-
|
|
1137
|
-
rules[logicalId] = rule
|
|
1138
|
-
count++
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
return { rules, count }
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
/**
|
|
1145
|
-
* Generate a job runner script for ECS tasks
|
|
1146
|
-
*/
|
|
1147
|
-
static generateJobRunnerScript(): string {
|
|
1148
|
-
return `#!/usr/bin/env bun
|
|
1149
|
-
/**
|
|
1150
|
-
* Job Runner Script
|
|
1151
|
-
* This script is invoked by ECS scheduled tasks to run jobs
|
|
1152
|
-
* Auto-generated by ts-cloud
|
|
1153
|
-
*/
|
|
1154
|
-
const jobClass = process.env.JOB_CLASS
|
|
1155
|
-
const jobDataRaw = process.env.JOB_DATA
|
|
1156
|
-
|
|
1157
|
-
if (!jobClass) {
|
|
1158
|
-
console.error('JOB_CLASS environment variable is required')
|
|
1159
|
-
process.exit(1)
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
async function run() {
|
|
1163
|
-
try {
|
|
1164
|
-
const jobData = jobDataRaw ? JSON.parse(jobDataRaw) : {}
|
|
1165
|
-
|
|
1166
|
-
// Dynamic import of the job module
|
|
1167
|
-
const jobModule = await import(\`./$\{jobClass}.ts\`)
|
|
1168
|
-
|
|
1169
|
-
// Check for handle function
|
|
1170
|
-
const handler = jobModule.handle || jobModule.default
|
|
1171
|
-
|
|
1172
|
-
if (typeof handler !== 'function') {
|
|
1173
|
-
throw new Error(\`Job $\{jobClass\} does not export a handle function\`)
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
console.log(\`[Job Runner] Starting job: $\{jobClass\}\`)
|
|
1177
|
-
const startTime = Date.now()
|
|
1178
|
-
|
|
1179
|
-
await handler(jobData)
|
|
1180
|
-
|
|
1181
|
-
const duration = Date.now() - startTime
|
|
1182
|
-
console.log(\`[Job Runner] Job $\{jobClass\} completed in $\{duration\}ms\`)
|
|
1183
|
-
|
|
1184
|
-
process.exit(0)
|
|
1185
|
-
} catch (error) {
|
|
1186
|
-
console.error(\`[Job Runner] Job $\{jobClass\} failed:\`, error)
|
|
1187
|
-
process.exit(1)
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
run()
|
|
1192
|
-
`
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
/**
|
|
1196
|
-
* Generate job manifest file for CI/CD deployments
|
|
1197
|
-
*/
|
|
1198
|
-
static async generateJobManifest(options: {
|
|
1199
|
-
projectRoot: string
|
|
1200
|
-
jobsPath?: string
|
|
1201
|
-
}): Promise<{
|
|
1202
|
-
jobs: DiscoveredJob[]
|
|
1203
|
-
scheduledCount: number
|
|
1204
|
-
totalCount: number
|
|
1205
|
-
}> {
|
|
1206
|
-
const jobs = await JobLoader.discoverJobs(options)
|
|
1207
|
-
|
|
1208
|
-
const scheduledJobs = jobs.filter(j => j.schedule && j.enabled)
|
|
1209
|
-
|
|
1210
|
-
return {
|
|
1211
|
-
jobs,
|
|
1212
|
-
scheduledCount: scheduledJobs.length,
|
|
1213
|
-
totalCount: jobs.length,
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
/**
|
|
1219
|
-
* Stacks framework job/action integration helpers
|
|
1220
|
-
*/
|
|
1221
|
-
export const StacksIntegration: {
|
|
1222
|
-
loadJobs: typeof JobLoader.discoverJobs
|
|
1223
|
-
loadActions: typeof JobLoader.discoverActions
|
|
1224
|
-
generateScheduledJobs: typeof JobLoader.generateScheduledJobResources
|
|
1225
|
-
generateRunner: typeof JobLoader.generateJobRunnerScript
|
|
1226
|
-
paths: {
|
|
1227
|
-
jobs: string
|
|
1228
|
-
actions: string
|
|
1229
|
-
runner: string
|
|
1230
|
-
}
|
|
1231
|
-
} = {
|
|
1232
|
-
/**
|
|
1233
|
-
* Load jobs from Stacks app/Jobs directory
|
|
1234
|
-
*/
|
|
1235
|
-
loadJobs: JobLoader.discoverJobs,
|
|
1236
|
-
|
|
1237
|
-
/**
|
|
1238
|
-
* Load actions from Stacks app/Actions directory
|
|
1239
|
-
*/
|
|
1240
|
-
loadActions: JobLoader.discoverActions,
|
|
1241
|
-
|
|
1242
|
-
/**
|
|
1243
|
-
* Generate all scheduled job resources
|
|
1244
|
-
*/
|
|
1245
|
-
generateScheduledJobs: JobLoader.generateScheduledJobResources,
|
|
1246
|
-
|
|
1247
|
-
/**
|
|
1248
|
-
* Generate job runner script
|
|
1249
|
-
*/
|
|
1250
|
-
generateRunner: JobLoader.generateJobRunnerScript,
|
|
1251
|
-
|
|
1252
|
-
/**
|
|
1253
|
-
* Default paths for Stacks framework
|
|
1254
|
-
*/
|
|
1255
|
-
paths: {
|
|
1256
|
-
jobs: 'app/Jobs',
|
|
1257
|
-
actions: 'app/Actions',
|
|
1258
|
-
runner: 'app/Jobs/runner.ts',
|
|
1259
|
-
},
|
|
1260
|
-
}
|