@stacksjs/ts-cloud-core 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +321 -0
  3. package/package.json +31 -0
  4. package/src/advanced-features.test.ts +465 -0
  5. package/src/aws/cloudformation.ts +421 -0
  6. package/src/aws/cloudfront.ts +158 -0
  7. package/src/aws/credentials.test.ts +132 -0
  8. package/src/aws/credentials.ts +545 -0
  9. package/src/aws/index.ts +87 -0
  10. package/src/aws/s3.test.ts +188 -0
  11. package/src/aws/s3.ts +1088 -0
  12. package/src/aws/signature.test.ts +670 -0
  13. package/src/aws/signature.ts +1155 -0
  14. package/src/backup/disaster-recovery.test.ts +726 -0
  15. package/src/backup/disaster-recovery.ts +500 -0
  16. package/src/backup/index.ts +34 -0
  17. package/src/backup/manager.test.ts +498 -0
  18. package/src/backup/manager.ts +432 -0
  19. package/src/cicd/circleci.ts +430 -0
  20. package/src/cicd/github-actions.ts +424 -0
  21. package/src/cicd/gitlab-ci.ts +255 -0
  22. package/src/cicd/index.ts +8 -0
  23. package/src/cli/history.ts +396 -0
  24. package/src/cli/index.ts +10 -0
  25. package/src/cli/progress.ts +458 -0
  26. package/src/cli/repl.ts +454 -0
  27. package/src/cli/suggestions.ts +327 -0
  28. package/src/cli/table.test.ts +319 -0
  29. package/src/cli/table.ts +332 -0
  30. package/src/cloudformation/builder.test.ts +327 -0
  31. package/src/cloudformation/builder.ts +378 -0
  32. package/src/cloudformation/builders/api-gateway.ts +449 -0
  33. package/src/cloudformation/builders/cache.ts +334 -0
  34. package/src/cloudformation/builders/cdn.ts +278 -0
  35. package/src/cloudformation/builders/compute.ts +485 -0
  36. package/src/cloudformation/builders/database.ts +392 -0
  37. package/src/cloudformation/builders/functions.ts +343 -0
  38. package/src/cloudformation/builders/messaging.ts +140 -0
  39. package/src/cloudformation/builders/monitoring.ts +300 -0
  40. package/src/cloudformation/builders/network.ts +264 -0
  41. package/src/cloudformation/builders/queue.ts +147 -0
  42. package/src/cloudformation/builders/security.ts +399 -0
  43. package/src/cloudformation/builders/storage.ts +285 -0
  44. package/src/cloudformation/index.ts +30 -0
  45. package/src/cloudformation/types.ts +173 -0
  46. package/src/compliance/aws-config.ts +543 -0
  47. package/src/compliance/cloudtrail.ts +376 -0
  48. package/src/compliance/compliance.test.ts +423 -0
  49. package/src/compliance/guardduty.ts +446 -0
  50. package/src/compliance/index.ts +66 -0
  51. package/src/compliance/security-hub.ts +456 -0
  52. package/src/containers/build-optimization.ts +416 -0
  53. package/src/containers/containers.test.ts +508 -0
  54. package/src/containers/image-scanning.ts +360 -0
  55. package/src/containers/index.ts +9 -0
  56. package/src/containers/registry.ts +293 -0
  57. package/src/containers/service-mesh.ts +520 -0
  58. package/src/database/database.test.ts +762 -0
  59. package/src/database/index.ts +9 -0
  60. package/src/database/migrations.ts +444 -0
  61. package/src/database/performance.ts +528 -0
  62. package/src/database/replicas.ts +534 -0
  63. package/src/database/users.ts +494 -0
  64. package/src/dependency-graph.ts +143 -0
  65. package/src/deployment/ab-testing.ts +582 -0
  66. package/src/deployment/blue-green.ts +452 -0
  67. package/src/deployment/canary.ts +500 -0
  68. package/src/deployment/deployment.test.ts +526 -0
  69. package/src/deployment/index.ts +61 -0
  70. package/src/deployment/progressive.ts +62 -0
  71. package/src/dns/dns.test.ts +641 -0
  72. package/src/dns/dnssec.ts +315 -0
  73. package/src/dns/index.ts +8 -0
  74. package/src/dns/resolver.ts +496 -0
  75. package/src/dns/routing.ts +593 -0
  76. package/src/email/advanced/analytics.ts +445 -0
  77. package/src/email/advanced/index.ts +11 -0
  78. package/src/email/advanced/rules.ts +465 -0
  79. package/src/email/advanced/scheduling.ts +352 -0
  80. package/src/email/advanced/search.ts +412 -0
  81. package/src/email/advanced/shared-mailboxes.ts +404 -0
  82. package/src/email/advanced/templates.ts +455 -0
  83. package/src/email/advanced/threading.ts +281 -0
  84. package/src/email/analytics.ts +467 -0
  85. package/src/email/bounce-handling.ts +425 -0
  86. package/src/email/email.test.ts +431 -0
  87. package/src/email/handlers/__tests__/inbound.test.ts +38 -0
  88. package/src/email/handlers/__tests__/outbound.test.ts +37 -0
  89. package/src/email/handlers/converter.ts +227 -0
  90. package/src/email/handlers/feedback.ts +228 -0
  91. package/src/email/handlers/inbound.ts +169 -0
  92. package/src/email/handlers/outbound.ts +178 -0
  93. package/src/email/index.ts +15 -0
  94. package/src/email/reputation.ts +303 -0
  95. package/src/email/templates.ts +352 -0
  96. package/src/errors/index.test.ts +434 -0
  97. package/src/errors/index.ts +416 -0
  98. package/src/health-checks/index.ts +40 -0
  99. package/src/index.ts +360 -0
  100. package/src/intrinsic-functions.ts +118 -0
  101. package/src/lambda/concurrency.ts +330 -0
  102. package/src/lambda/destinations.ts +345 -0
  103. package/src/lambda/dlq.ts +425 -0
  104. package/src/lambda/index.ts +11 -0
  105. package/src/lambda/lambda.test.ts +840 -0
  106. package/src/lambda/layers.ts +263 -0
  107. package/src/lambda/versions.ts +376 -0
  108. package/src/lambda/vpc.ts +399 -0
  109. package/src/local/config.ts +114 -0
  110. package/src/local/index.ts +6 -0
  111. package/src/local/mock-aws.ts +351 -0
  112. package/src/modules/ai.ts +340 -0
  113. package/src/modules/api.ts +478 -0
  114. package/src/modules/auth.ts +805 -0
  115. package/src/modules/cache.ts +417 -0
  116. package/src/modules/cdn.ts +1062 -0
  117. package/src/modules/communication.ts +1094 -0
  118. package/src/modules/compute.ts +3348 -0
  119. package/src/modules/database.ts +554 -0
  120. package/src/modules/deployment.ts +1079 -0
  121. package/src/modules/dns.ts +337 -0
  122. package/src/modules/email.ts +1538 -0
  123. package/src/modules/filesystem.ts +515 -0
  124. package/src/modules/index.ts +32 -0
  125. package/src/modules/messaging.ts +486 -0
  126. package/src/modules/monitoring.ts +2086 -0
  127. package/src/modules/network.ts +664 -0
  128. package/src/modules/parameter-store.ts +325 -0
  129. package/src/modules/permissions.ts +1081 -0
  130. package/src/modules/phone.ts +494 -0
  131. package/src/modules/queue.ts +1260 -0
  132. package/src/modules/redirects.ts +464 -0
  133. package/src/modules/registry.ts +699 -0
  134. package/src/modules/search.ts +401 -0
  135. package/src/modules/secrets.ts +416 -0
  136. package/src/modules/security.ts +731 -0
  137. package/src/modules/sms.ts +389 -0
  138. package/src/modules/storage.ts +1120 -0
  139. package/src/modules/workflow.ts +680 -0
  140. package/src/multi-account/config.ts +521 -0
  141. package/src/multi-account/index.ts +7 -0
  142. package/src/multi-account/manager.ts +427 -0
  143. package/src/multi-region/cross-region.ts +410 -0
  144. package/src/multi-region/index.ts +8 -0
  145. package/src/multi-region/manager.ts +483 -0
  146. package/src/multi-region/regions.ts +435 -0
  147. package/src/network-security/index.ts +48 -0
  148. package/src/observability/index.ts +9 -0
  149. package/src/observability/logs.ts +522 -0
  150. package/src/observability/metrics.ts +460 -0
  151. package/src/observability/observability.test.ts +782 -0
  152. package/src/observability/synthetics.ts +568 -0
  153. package/src/observability/xray.ts +358 -0
  154. package/src/phone/advanced/analytics.ts +349 -0
  155. package/src/phone/advanced/callbacks.ts +428 -0
  156. package/src/phone/advanced/index.ts +8 -0
  157. package/src/phone/advanced/ivr-builder.ts +504 -0
  158. package/src/phone/advanced/recording.ts +310 -0
  159. package/src/phone/handlers/__tests__/incoming-call.test.ts +40 -0
  160. package/src/phone/handlers/incoming-call.ts +117 -0
  161. package/src/phone/handlers/missed-call.ts +116 -0
  162. package/src/phone/handlers/voicemail.ts +179 -0
  163. package/src/phone/index.ts +9 -0
  164. package/src/presets/api-backend.ts +134 -0
  165. package/src/presets/data-pipeline.ts +204 -0
  166. package/src/presets/extend.test.ts +295 -0
  167. package/src/presets/extend.ts +297 -0
  168. package/src/presets/fullstack-app.ts +144 -0
  169. package/src/presets/index.ts +27 -0
  170. package/src/presets/jamstack.ts +135 -0
  171. package/src/presets/microservices.ts +167 -0
  172. package/src/presets/ml-api.ts +208 -0
  173. package/src/presets/nodejs-server.ts +104 -0
  174. package/src/presets/nodejs-serverless.ts +114 -0
  175. package/src/presets/realtime-app.ts +184 -0
  176. package/src/presets/static-site.ts +64 -0
  177. package/src/presets/traditional-web-app.ts +339 -0
  178. package/src/presets/wordpress.ts +138 -0
  179. package/src/preview/github.test.ts +249 -0
  180. package/src/preview/github.ts +297 -0
  181. package/src/preview/index.ts +37 -0
  182. package/src/preview/manager.test.ts +440 -0
  183. package/src/preview/manager.ts +326 -0
  184. package/src/preview/notifications.test.ts +582 -0
  185. package/src/preview/notifications.ts +341 -0
  186. package/src/queue/batch-processing.ts +402 -0
  187. package/src/queue/dlq-monitoring.ts +402 -0
  188. package/src/queue/fifo.ts +342 -0
  189. package/src/queue/index.ts +9 -0
  190. package/src/queue/management.ts +428 -0
  191. package/src/queue/queue.test.ts +429 -0
  192. package/src/resource-mgmt/index.ts +39 -0
  193. package/src/resource-naming.ts +62 -0
  194. package/src/s3/index.ts +523 -0
  195. package/src/schema/cloud-config.schema.json +554 -0
  196. package/src/schema/index.ts +68 -0
  197. package/src/security/certificate-manager.ts +492 -0
  198. package/src/security/index.ts +9 -0
  199. package/src/security/scanning.ts +545 -0
  200. package/src/security/secrets-manager.ts +476 -0
  201. package/src/security/secrets-rotation.ts +456 -0
  202. package/src/security/security.test.ts +738 -0
  203. package/src/sms/advanced/ab-testing.ts +389 -0
  204. package/src/sms/advanced/analytics.ts +336 -0
  205. package/src/sms/advanced/campaigns.ts +523 -0
  206. package/src/sms/advanced/chatbot.ts +224 -0
  207. package/src/sms/advanced/index.ts +10 -0
  208. package/src/sms/advanced/link-tracking.ts +248 -0
  209. package/src/sms/advanced/mms.ts +308 -0
  210. package/src/sms/handlers/__tests__/send.test.ts +40 -0
  211. package/src/sms/handlers/delivery-status.ts +133 -0
  212. package/src/sms/handlers/receive.ts +162 -0
  213. package/src/sms/handlers/send.ts +174 -0
  214. package/src/sms/index.ts +9 -0
  215. package/src/stack-diff.ts +389 -0
  216. package/src/static-site/index.ts +85 -0
  217. package/src/template-builder.ts +110 -0
  218. package/src/template-validator.ts +574 -0
  219. package/src/utils/cache.ts +291 -0
  220. package/src/utils/diff.ts +269 -0
  221. package/src/utils/hash.ts +227 -0
  222. package/src/utils/index.ts +8 -0
  223. package/src/utils/parallel.ts +294 -0
  224. package/src/validators/credentials.test.ts +274 -0
  225. package/src/validators/credentials.ts +233 -0
  226. package/src/validators/quotas.test.ts +434 -0
  227. package/src/validators/quotas.ts +217 -0
  228. package/test/ai.test.ts +327 -0
  229. package/test/api.test.ts +511 -0
  230. package/test/auth.test.ts +632 -0
  231. package/test/cache.test.ts +406 -0
  232. package/test/cdn.test.ts +247 -0
  233. package/test/compute.test.ts +861 -0
  234. package/test/database.test.ts +523 -0
  235. package/test/deployment.test.ts +499 -0
  236. package/test/dns.test.ts +270 -0
  237. package/test/email.test.ts +439 -0
  238. package/test/filesystem.test.ts +382 -0
  239. package/test/integration.test.ts +350 -0
  240. package/test/messaging.test.ts +514 -0
  241. package/test/monitoring.test.ts +634 -0
  242. package/test/network.test.ts +425 -0
  243. package/test/permissions.test.ts +488 -0
  244. package/test/queue.test.ts +484 -0
  245. package/test/registry.test.ts +306 -0
  246. package/test/security.test.ts +462 -0
  247. package/test/storage.test.ts +463 -0
  248. package/test/template-validator.test.ts +559 -0
  249. package/test/workflow.test.ts +592 -0
  250. package/tsconfig.json +16 -0
  251. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,568 @@
1
+ /**
2
+ * Synthetic Monitoring
3
+ * CloudWatch Synthetics Canaries for proactive monitoring
4
+ */
5
+
6
+ export interface SyntheticCanary {
7
+ id: string
8
+ name: string
9
+ description?: string
10
+ runtimeVersion: string
11
+ handler: string
12
+ code: CanaryCode
13
+ schedule: CanarySchedule
14
+ runConfig?: CanaryRunConfig
15
+ vpcConfig?: VpcConfig
16
+ artifactS3Location: string
17
+ successRetentionPeriod?: number
18
+ failureRetentionPeriod?: number
19
+ alarms?: CanaryAlarm[]
20
+ }
21
+
22
+ export interface CanaryCode {
23
+ type: 'script' | 's3'
24
+ script?: string
25
+ s3Bucket?: string
26
+ s3Key?: string
27
+ s3Version?: string
28
+ }
29
+
30
+ export interface CanarySchedule {
31
+ expression: string // rate() or cron()
32
+ durationInSeconds?: number
33
+ }
34
+
35
+ export interface CanaryRunConfig {
36
+ timeoutInSeconds: number
37
+ memoryInMB: number
38
+ environmentVariables?: Record<string, string>
39
+ activeTracing?: boolean
40
+ }
41
+
42
+ export interface VpcConfig {
43
+ subnetIds: string[]
44
+ securityGroupIds: string[]
45
+ }
46
+
47
+ export interface CanaryAlarm {
48
+ id: string
49
+ name: string
50
+ metric: 'SuccessPercent' | 'Duration' | 'Failed'
51
+ threshold: number
52
+ evaluationPeriods: number
53
+ snsTopicArn?: string
54
+ }
55
+
56
+ export interface HeartbeatMonitor {
57
+ id: string
58
+ name: string
59
+ url: string
60
+ interval: number // minutes
61
+ timeout: number // seconds
62
+ expectedStatus?: number
63
+ }
64
+
65
+ export interface ApiMonitor {
66
+ id: string
67
+ name: string
68
+ baseUrl: string
69
+ endpoints: ApiEndpoint[]
70
+ headers?: Record<string, string>
71
+ interval: number
72
+ }
73
+
74
+ export interface ApiEndpoint {
75
+ path: string
76
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
77
+ expectedStatus: number
78
+ body?: any
79
+ assertions?: ApiAssertion[]
80
+ }
81
+
82
+ export interface ApiAssertion {
83
+ type: 'status' | 'header' | 'body' | 'latency'
84
+ field?: string
85
+ operator: 'equals' | 'contains' | 'lessThan' | 'greaterThan'
86
+ value: any
87
+ }
88
+
89
+ /**
90
+ * Synthetics manager
91
+ */
92
+ export class SyntheticsManager {
93
+ private canaries: Map<string, SyntheticCanary> = new Map()
94
+ private heartbeats: Map<string, HeartbeatMonitor> = new Map()
95
+ private apiMonitors: Map<string, ApiMonitor> = new Map()
96
+ private canaryCounter = 0
97
+ private heartbeatCounter = 0
98
+ private apiMonitorCounter = 0
99
+ private alarmCounter = 0
100
+
101
+ /**
102
+ * Latest runtime versions
103
+ */
104
+ static readonly RuntimeVersions = {
105
+ NODEJS_PUPPETEER_3_9: 'syn-nodejs-puppeteer-3.9',
106
+ NODEJS_PUPPETEER_4_0: 'syn-nodejs-puppeteer-4.0',
107
+ PYTHON_SELENIUM_1_3: 'syn-python-selenium-1.3',
108
+ }
109
+
110
+ /**
111
+ * Create synthetic canary
112
+ */
113
+ createCanary(canary: Omit<SyntheticCanary, 'id'>): SyntheticCanary {
114
+ const id = `canary-${Date.now()}-${this.canaryCounter++}`
115
+
116
+ const syntheticCanary: SyntheticCanary = {
117
+ id,
118
+ ...canary,
119
+ }
120
+
121
+ this.canaries.set(id, syntheticCanary)
122
+
123
+ return syntheticCanary
124
+ }
125
+
126
+ /**
127
+ * Create heartbeat canary
128
+ */
129
+ createHeartbeatCanary(options: {
130
+ name: string
131
+ url: string
132
+ interval: number // minutes
133
+ s3Bucket: string
134
+ }): SyntheticCanary {
135
+ const script = `
136
+ const synthetics = require('Synthetics');
137
+ const log = require('SyntheticsLogger');
138
+
139
+ const heartbeat = async function () {
140
+ const url = '${options.url}';
141
+
142
+ const page = await synthetics.getPage();
143
+ const response = await page.goto(url, {waitUntil: 'domcontentloaded', timeout: 30000});
144
+
145
+ if (response.status() !== 200) {
146
+ throw new Error(\`Failed with status \${response.status()}\`);
147
+ }
148
+
149
+ log.info('Heartbeat check passed');
150
+ };
151
+
152
+ exports.handler = async () => {
153
+ return await heartbeat();
154
+ };
155
+ `
156
+
157
+ return this.createCanary({
158
+ name: options.name,
159
+ description: `Heartbeat monitor for ${options.url}`,
160
+ runtimeVersion: SyntheticsManager.RuntimeVersions.NODEJS_PUPPETEER_4_0,
161
+ handler: 'index.handler',
162
+ code: {
163
+ type: 'script',
164
+ script,
165
+ },
166
+ schedule: {
167
+ expression: `rate(${options.interval} minutes)`,
168
+ },
169
+ runConfig: {
170
+ timeoutInSeconds: 60,
171
+ memoryInMB: 960,
172
+ activeTracing: true,
173
+ },
174
+ artifactS3Location: `s3://${options.s3Bucket}/canary-artifacts/${options.name}/`,
175
+ successRetentionPeriod: 31,
176
+ failureRetentionPeriod: 31,
177
+ })
178
+ }
179
+
180
+ /**
181
+ * Create API monitoring canary
182
+ */
183
+ createAPICanary(options: {
184
+ name: string
185
+ baseUrl: string
186
+ endpoints: ApiEndpoint[]
187
+ interval: number
188
+ s3Bucket: string
189
+ }): SyntheticCanary {
190
+ const endpointChecks = options.endpoints
191
+ .map(
192
+ (ep, i) => `
193
+ // Endpoint ${i + 1}: ${ep.method} ${ep.path}
194
+ response = await page.goto('${options.baseUrl}${ep.path}', {
195
+ waitUntil: 'domcontentloaded',
196
+ timeout: 30000
197
+ });
198
+
199
+ if (response.status() !== ${ep.expectedStatus}) {
200
+ throw new Error('Endpoint ${ep.path} failed: expected ${ep.expectedStatus}, got ' + response.status());
201
+ }
202
+
203
+ log.info('Endpoint ${ep.path} check passed');
204
+ `,
205
+ )
206
+ .join('\n')
207
+
208
+ const script = `
209
+ const synthetics = require('Synthetics');
210
+ const log = require('SyntheticsLogger');
211
+
212
+ const apiCheck = async function () {
213
+ const page = await synthetics.getPage();
214
+ let response;
215
+
216
+ ${endpointChecks}
217
+
218
+ log.info('All API checks passed');
219
+ };
220
+
221
+ exports.handler = async () => {
222
+ return await apiCheck();
223
+ };
224
+ `
225
+
226
+ return this.createCanary({
227
+ name: options.name,
228
+ description: `API monitor for ${options.baseUrl}`,
229
+ runtimeVersion: SyntheticsManager.RuntimeVersions.NODEJS_PUPPETEER_4_0,
230
+ handler: 'index.handler',
231
+ code: {
232
+ type: 'script',
233
+ script,
234
+ },
235
+ schedule: {
236
+ expression: `rate(${options.interval} minutes)`,
237
+ },
238
+ runConfig: {
239
+ timeoutInSeconds: 120,
240
+ memoryInMB: 960,
241
+ activeTracing: true,
242
+ },
243
+ artifactS3Location: `s3://${options.s3Bucket}/canary-artifacts/${options.name}/`,
244
+ successRetentionPeriod: 31,
245
+ failureRetentionPeriod: 31,
246
+ })
247
+ }
248
+
249
+ /**
250
+ * Create visual regression canary
251
+ */
252
+ createVisualRegressionCanary(options: {
253
+ name: string
254
+ url: string
255
+ screenshotName: string
256
+ interval: number
257
+ s3Bucket: string
258
+ }): SyntheticCanary {
259
+ const script = `
260
+ const synthetics = require('Synthetics');
261
+ const log = require('SyntheticsLogger');
262
+
263
+ const visualCheck = async function () {
264
+ const page = await synthetics.getPage();
265
+
266
+ await page.goto('${options.url}', {waitUntil: 'networkidle0', timeout: 30000});
267
+
268
+ await page.screenshot({
269
+ path: '/tmp/${options.screenshotName}.png',
270
+ fullPage: true
271
+ });
272
+
273
+ log.info('Visual regression check completed');
274
+ };
275
+
276
+ exports.handler = async () => {
277
+ return await visualCheck();
278
+ };
279
+ `
280
+
281
+ return this.createCanary({
282
+ name: options.name,
283
+ description: `Visual regression monitor for ${options.url}`,
284
+ runtimeVersion: SyntheticsManager.RuntimeVersions.NODEJS_PUPPETEER_4_0,
285
+ handler: 'index.handler',
286
+ code: {
287
+ type: 'script',
288
+ script,
289
+ },
290
+ schedule: {
291
+ expression: `rate(${options.interval} minutes)`,
292
+ },
293
+ runConfig: {
294
+ timeoutInSeconds: 120,
295
+ memoryInMB: 1024,
296
+ activeTracing: true,
297
+ },
298
+ artifactS3Location: `s3://${options.s3Bucket}/canary-artifacts/${options.name}/`,
299
+ successRetentionPeriod: 31,
300
+ failureRetentionPeriod: 31,
301
+ })
302
+ }
303
+
304
+ /**
305
+ * Create workflow canary (multi-step user journey)
306
+ */
307
+ createWorkflowCanary(options: {
308
+ name: string
309
+ description: string
310
+ steps: WorkflowStep[]
311
+ interval: number
312
+ s3Bucket: string
313
+ }): SyntheticCanary {
314
+ const stepScripts = options.steps
315
+ .map(
316
+ (step, i) => `
317
+ // Step ${i + 1}: ${step.description}
318
+ await page.goto('${step.url}', {waitUntil: 'domcontentloaded', timeout: 30000});
319
+ ${step.actions?.map(action => this.generateActionScript(action)).join('\n ') || ''}
320
+ log.info('Step ${i + 1} completed: ${step.description}');
321
+ `,
322
+ )
323
+ .join('\n')
324
+
325
+ const script = `
326
+ const synthetics = require('Synthetics');
327
+ const log = require('SyntheticsLogger');
328
+
329
+ const workflowCheck = async function () {
330
+ const page = await synthetics.getPage();
331
+
332
+ ${stepScripts}
333
+
334
+ log.info('Workflow completed successfully');
335
+ };
336
+
337
+ exports.handler = async () => {
338
+ return await workflowCheck();
339
+ };
340
+ `
341
+
342
+ return this.createCanary({
343
+ name: options.name,
344
+ description: options.description,
345
+ runtimeVersion: SyntheticsManager.RuntimeVersions.NODEJS_PUPPETEER_4_0,
346
+ handler: 'index.handler',
347
+ code: {
348
+ type: 'script',
349
+ script,
350
+ },
351
+ schedule: {
352
+ expression: `rate(${options.interval} minutes)`,
353
+ },
354
+ runConfig: {
355
+ timeoutInSeconds: 180,
356
+ memoryInMB: 1024,
357
+ activeTracing: true,
358
+ },
359
+ artifactS3Location: `s3://${options.s3Bucket}/canary-artifacts/${options.name}/`,
360
+ successRetentionPeriod: 31,
361
+ failureRetentionPeriod: 31,
362
+ })
363
+ }
364
+
365
+ /**
366
+ * Generate action script for workflow steps
367
+ */
368
+ private generateActionScript(action: WorkflowAction): string {
369
+ switch (action.type) {
370
+ case 'click':
371
+ return `await page.click('${action.selector}');`
372
+ case 'type':
373
+ return `await page.type('${action.selector}', '${action.value}');`
374
+ case 'wait':
375
+ return `await page.waitForTimeout(${action.duration});`
376
+ case 'waitForSelector':
377
+ return `await page.waitForSelector('${action.selector}');`
378
+ default:
379
+ return ''
380
+ }
381
+ }
382
+
383
+ /**
384
+ * Create canary alarm
385
+ */
386
+ createAlarm(canaryId: string, alarm: Omit<CanaryAlarm, 'id'>): CanaryAlarm {
387
+ const canary = this.canaries.get(canaryId)
388
+
389
+ if (!canary) {
390
+ throw new Error(`Canary not found: ${canaryId}`)
391
+ }
392
+
393
+ const id = `alarm-${Date.now()}-${this.alarmCounter++}`
394
+
395
+ const canaryAlarm: CanaryAlarm = {
396
+ id,
397
+ ...alarm,
398
+ }
399
+
400
+ if (!canary.alarms) {
401
+ canary.alarms = []
402
+ }
403
+
404
+ canary.alarms.push(canaryAlarm)
405
+
406
+ return canaryAlarm
407
+ }
408
+
409
+ /**
410
+ * Get canary
411
+ */
412
+ getCanary(id: string): SyntheticCanary | undefined {
413
+ return this.canaries.get(id)
414
+ }
415
+
416
+ /**
417
+ * List canaries
418
+ */
419
+ listCanaries(): SyntheticCanary[] {
420
+ return Array.from(this.canaries.values())
421
+ }
422
+
423
+ /**
424
+ * Generate CloudFormation for canary
425
+ */
426
+ generateCanaryCF(canary: SyntheticCanary): any {
427
+ const cf: any = {
428
+ Type: 'AWS::Synthetics::Canary',
429
+ Properties: {
430
+ Name: canary.name,
431
+ RuntimeVersion: canary.runtimeVersion,
432
+ ExecutionRoleArn: { 'Fn::GetAtt': ['CanaryExecutionRole', 'Arn'] },
433
+ Schedule: {
434
+ Expression: canary.schedule.expression,
435
+ ...(canary.schedule.durationInSeconds && {
436
+ DurationInSeconds: canary.schedule.durationInSeconds,
437
+ }),
438
+ },
439
+ ArtifactS3Location: canary.artifactS3Location,
440
+ StartCanaryAfterCreation: true,
441
+ },
442
+ }
443
+
444
+ if (canary.code.type === 'script') {
445
+ cf.Properties.Code = {
446
+ Handler: canary.handler,
447
+ Script: canary.code.script,
448
+ }
449
+ }
450
+ else {
451
+ cf.Properties.Code = {
452
+ Handler: canary.handler,
453
+ S3Bucket: canary.code.s3Bucket,
454
+ S3Key: canary.code.s3Key,
455
+ ...(canary.code.s3Version && { S3ObjectVersion: canary.code.s3Version }),
456
+ }
457
+ }
458
+
459
+ if (canary.runConfig) {
460
+ cf.Properties.RunConfig = {
461
+ TimeoutInSeconds: canary.runConfig.timeoutInSeconds,
462
+ MemoryInMB: canary.runConfig.memoryInMB,
463
+ ...(canary.runConfig.environmentVariables && {
464
+ EnvironmentVariables: canary.runConfig.environmentVariables,
465
+ }),
466
+ ...(canary.runConfig.activeTracing !== undefined && {
467
+ ActiveTracing: canary.runConfig.activeTracing,
468
+ }),
469
+ }
470
+ }
471
+
472
+ if (canary.vpcConfig) {
473
+ cf.Properties.VPCConfig = {
474
+ SubnetIds: canary.vpcConfig.subnetIds,
475
+ SecurityGroupIds: canary.vpcConfig.securityGroupIds,
476
+ }
477
+ }
478
+
479
+ if (canary.successRetentionPeriod) {
480
+ cf.Properties.SuccessRetentionPeriod = canary.successRetentionPeriod
481
+ }
482
+
483
+ if (canary.failureRetentionPeriod) {
484
+ cf.Properties.FailureRetentionPeriod = canary.failureRetentionPeriod
485
+ }
486
+
487
+ return cf
488
+ }
489
+
490
+ /**
491
+ * Generate CloudFormation for canary execution role
492
+ */
493
+ generateCanaryRoleCF(): any {
494
+ return {
495
+ Type: 'AWS::IAM::Role',
496
+ Properties: {
497
+ AssumeRolePolicyDocument: {
498
+ Version: '2012-10-17',
499
+ Statement: [
500
+ {
501
+ Effect: 'Allow',
502
+ Principal: {
503
+ Service: 'lambda.amazonaws.com',
504
+ },
505
+ Action: 'sts:AssumeRole',
506
+ },
507
+ ],
508
+ },
509
+ ManagedPolicyArns: [
510
+ 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole',
511
+ 'arn:aws:iam::aws:policy/CloudWatchSyntheticsFullAccess',
512
+ ],
513
+ Policies: [
514
+ {
515
+ PolicyName: 'CanaryS3Policy',
516
+ PolicyDocument: {
517
+ Version: '2012-10-17',
518
+ Statement: [
519
+ {
520
+ Effect: 'Allow',
521
+ Action: ['s3:PutObject', 's3:GetBucketLocation'],
522
+ Resource: ['arn:aws:s3:::*/*'],
523
+ },
524
+ ],
525
+ },
526
+ },
527
+ ],
528
+ },
529
+ }
530
+ }
531
+
532
+ /**
533
+ * Clear all data
534
+ */
535
+ clear(): void {
536
+ this.canaries.clear()
537
+ this.heartbeats.clear()
538
+ this.apiMonitors.clear()
539
+ this.canaryCounter = 0
540
+ this.heartbeatCounter = 0
541
+ this.apiMonitorCounter = 0
542
+ this.alarmCounter = 0
543
+ }
544
+ }
545
+
546
+ /**
547
+ * Workflow step interface
548
+ */
549
+ export interface WorkflowStep {
550
+ description: string
551
+ url: string
552
+ actions?: WorkflowAction[]
553
+ }
554
+
555
+ /**
556
+ * Workflow action interface
557
+ */
558
+ export interface WorkflowAction {
559
+ type: 'click' | 'type' | 'wait' | 'waitForSelector'
560
+ selector?: string
561
+ value?: string
562
+ duration?: number
563
+ }
564
+
565
+ /**
566
+ * Global synthetics manager instance
567
+ */
568
+ export const syntheticsManager: SyntheticsManager = new SyntheticsManager()