@executor-js/emulate 0.6.0

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 (48) hide show
  1. package/README.md +1044 -0
  2. package/dist/api.d.ts +24 -0
  3. package/dist/api.js +2665 -0
  4. package/dist/api.js.map +1 -0
  5. package/dist/chunk-D6EKRYGP.js +1615 -0
  6. package/dist/chunk-D6EKRYGP.js.map +1 -0
  7. package/dist/chunk-WVQMFHQM.js +83 -0
  8. package/dist/chunk-WVQMFHQM.js.map +1 -0
  9. package/dist/dist-7FDUSG5I.js +24368 -0
  10. package/dist/dist-7FDUSG5I.js.map +1 -0
  11. package/dist/dist-7N4COJHK.js +1814 -0
  12. package/dist/dist-7N4COJHK.js.map +1 -0
  13. package/dist/dist-BTEY33DJ.js +2334 -0
  14. package/dist/dist-BTEY33DJ.js.map +1 -0
  15. package/dist/dist-DK26ESP2.js +595 -0
  16. package/dist/dist-DK26ESP2.js.map +1 -0
  17. package/dist/dist-IYZPDKJW.js +1284 -0
  18. package/dist/dist-IYZPDKJW.js.map +1 -0
  19. package/dist/dist-JJ2ZRCAX.js +189 -0
  20. package/dist/dist-JJ2ZRCAX.js.map +1 -0
  21. package/dist/dist-K4CVTD6K.js +1570 -0
  22. package/dist/dist-K4CVTD6K.js.map +1 -0
  23. package/dist/dist-M3GVASMR.js +1254 -0
  24. package/dist/dist-M3GVASMR.js.map +1 -0
  25. package/dist/dist-OYYGWKZQ.js +1533 -0
  26. package/dist/dist-OYYGWKZQ.js.map +1 -0
  27. package/dist/dist-P3SBBRFR.js +3169 -0
  28. package/dist/dist-P3SBBRFR.js.map +1 -0
  29. package/dist/dist-RMPDKZUA.js +1183 -0
  30. package/dist/dist-RMPDKZUA.js.map +1 -0
  31. package/dist/dist-WBKONLOE.js +2154 -0
  32. package/dist/dist-WBKONLOE.js.map +1 -0
  33. package/dist/dist-XM5HSBDC.js +1090 -0
  34. package/dist/dist-XM5HSBDC.js.map +1 -0
  35. package/dist/dist-XVVIYXQG.js +4241 -0
  36. package/dist/dist-XVVIYXQG.js.map +1 -0
  37. package/dist/dist-YPRJYQHW.js +5109 -0
  38. package/dist/dist-YPRJYQHW.js.map +1 -0
  39. package/dist/dist-ZEC77OKZ.js +913 -0
  40. package/dist/dist-ZEC77OKZ.js.map +1 -0
  41. package/dist/fonts/GeistPixel-Square.woff2 +0 -0
  42. package/dist/fonts/favicon.ico +0 -0
  43. package/dist/fonts/geist-sans.woff2 +0 -0
  44. package/dist/helpers-LXLP3DFE-LBOTATT5.js +17 -0
  45. package/dist/helpers-LXLP3DFE-LBOTATT5.js.map +1 -0
  46. package/dist/index.js +3005 -0
  47. package/dist/index.js.map +1 -0
  48. package/package.json +83 -0
@@ -0,0 +1,1814 @@
1
+ // ../@emulators/aws/dist/index.js
2
+ import { randomBytes, createHash } from "crypto";
3
+ import { randomBytes as randomBytes2 } from "crypto";
4
+ function getAwsStore(store) {
5
+ return {
6
+ s3Buckets: store.collection("aws.s3_buckets", ["bucket_name"]),
7
+ s3Objects: store.collection("aws.s3_objects", ["key", "bucket_name"]),
8
+ sqsQueues: store.collection("aws.sqs_queues", ["queue_name", "queue_url"]),
9
+ sqsMessages: store.collection("aws.sqs_messages", ["message_id", "queue_name"]),
10
+ iamUsers: store.collection("aws.iam_users", ["user_name", "user_id"]),
11
+ iamRoles: store.collection("aws.iam_roles", ["role_name", "role_id"])
12
+ };
13
+ }
14
+ var ACCOUNT_ID = "123456789012";
15
+ var DEFAULT_REGION = "us-east-1";
16
+ function generateAwsId(prefix) {
17
+ return prefix + randomBytes(8).toString("hex").toUpperCase();
18
+ }
19
+ function generateMessageId() {
20
+ return [
21
+ randomBytes(4).toString("hex"),
22
+ randomBytes(2).toString("hex"),
23
+ randomBytes(2).toString("hex"),
24
+ randomBytes(2).toString("hex"),
25
+ randomBytes(6).toString("hex")
26
+ ].join("-");
27
+ }
28
+ function generateReceiptHandle() {
29
+ return randomBytes(48).toString("base64url");
30
+ }
31
+ function md5(content) {
32
+ return createHash("md5").update(content).digest("hex");
33
+ }
34
+ function getAccountId() {
35
+ return ACCOUNT_ID;
36
+ }
37
+ function getDefaultRegion() {
38
+ return DEFAULT_REGION;
39
+ }
40
+ function awsXmlResponse(c, xml, status = 200) {
41
+ return c.text(xml, status, { "Content-Type": "application/xml" });
42
+ }
43
+ function awsErrorXml(c, code, message, status = 400) {
44
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
45
+ <ErrorResponse>
46
+ <Error>
47
+ <Code>${escapeXml(code)}</Code>
48
+ <Message>${escapeXml(message)}</Message>
49
+ </Error>
50
+ <RequestId>${generateMessageId()}</RequestId>
51
+ </ErrorResponse>`;
52
+ return c.text(xml, status, { "Content-Type": "application/xml" });
53
+ }
54
+ function escapeXml(str) {
55
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
56
+ }
57
+ function parseQueryString(body) {
58
+ const params = new URLSearchParams(body);
59
+ const result = {};
60
+ for (const [key, value] of params) {
61
+ result[key] = value;
62
+ }
63
+ return result;
64
+ }
65
+ function s3Routes(ctx) {
66
+ const { app, store, baseUrl } = ctx;
67
+ const aws = () => getAwsStore(store);
68
+ const handleListBuckets = (c) => {
69
+ const buckets = aws().s3Buckets.all();
70
+ const bucketXml = buckets.map(
71
+ (b) => ` <Bucket>
72
+ <Name>${escapeXml(b.bucket_name)}</Name>
73
+ <CreationDate>${b.creation_date}</CreationDate>
74
+ </Bucket>`
75
+ ).join("\n");
76
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
77
+ <ListAllMyBucketsResult>
78
+ <Owner>
79
+ <ID>owner-id</ID>
80
+ <DisplayName>emulate</DisplayName>
81
+ </Owner>
82
+ <Buckets>
83
+ ${bucketXml}
84
+ </Buckets>
85
+ </ListAllMyBucketsResult>`;
86
+ return awsXmlResponse(c, xml);
87
+ };
88
+ const handleCreateBucket = (c) => {
89
+ const bucketName = c.req.param("bucket");
90
+ const existing = aws().s3Buckets.findOneBy("bucket_name", bucketName);
91
+ if (existing) {
92
+ return awsErrorXml(
93
+ c,
94
+ "BucketAlreadyOwnedByYou",
95
+ "Your previous request to create the named bucket succeeded and you already own it.",
96
+ 409
97
+ );
98
+ }
99
+ aws().s3Buckets.insert({
100
+ bucket_name: bucketName,
101
+ region: "us-east-1",
102
+ creation_date: (/* @__PURE__ */ new Date()).toISOString(),
103
+ acl: "private",
104
+ versioning_enabled: false
105
+ });
106
+ return c.text("", 200, { Location: `/${bucketName}` });
107
+ };
108
+ const handleDeleteBucket = (c) => {
109
+ const bucketName = c.req.param("bucket");
110
+ const bucket = aws().s3Buckets.findOneBy("bucket_name", bucketName);
111
+ if (!bucket) {
112
+ return awsErrorXml(c, "NoSuchBucket", "The specified bucket does not exist.", 404);
113
+ }
114
+ const objects = aws().s3Objects.findBy("bucket_name", bucketName);
115
+ if (objects.length > 0) {
116
+ return awsErrorXml(c, "BucketNotEmpty", "The bucket you tried to delete is not empty.", 409);
117
+ }
118
+ aws().s3Buckets.delete(bucket.id);
119
+ return c.body(null, 204);
120
+ };
121
+ const handleHeadBucket = (c) => {
122
+ const bucketName = c.req.param("bucket");
123
+ const bucket = aws().s3Buckets.findOneBy("bucket_name", bucketName);
124
+ if (!bucket) {
125
+ return c.text("", 404);
126
+ }
127
+ return c.text("", 200, { "x-amz-bucket-region": bucket.region });
128
+ };
129
+ const handleListObjects = (c) => {
130
+ const bucketName = c.req.param("bucket");
131
+ const bucket = aws().s3Buckets.findOneBy("bucket_name", bucketName);
132
+ if (!bucket) {
133
+ return awsErrorXml(c, "NoSuchBucket", "The specified bucket does not exist.", 404);
134
+ }
135
+ const prefix = c.req.query("prefix") ?? "";
136
+ const delimiter = c.req.query("delimiter") ?? "";
137
+ const maxKeys = Math.min(parseInt(c.req.query("max-keys") ?? "1000", 10), 1e3);
138
+ const continuationToken = c.req.query("continuation-token");
139
+ const startAfter = c.req.query("start-after");
140
+ let objects = aws().s3Objects.findBy("bucket_name", bucketName);
141
+ if (prefix) {
142
+ objects = objects.filter((o) => o.key.startsWith(prefix));
143
+ }
144
+ objects.sort((a, b) => a.key.localeCompare(b.key));
145
+ const marker = continuationToken ?? startAfter;
146
+ if (marker) {
147
+ const startIndex = objects.findIndex((o) => o.key > marker);
148
+ objects = startIndex >= 0 ? objects.slice(startIndex) : [];
149
+ }
150
+ const commonPrefixes = [];
151
+ let contents = objects;
152
+ if (delimiter) {
153
+ const prefixSet = /* @__PURE__ */ new Set();
154
+ contents = [];
155
+ for (const obj of objects) {
156
+ const remaining = obj.key.slice(prefix.length);
157
+ const delimIndex = remaining.indexOf(delimiter);
158
+ if (delimIndex >= 0) {
159
+ prefixSet.add(prefix + remaining.slice(0, delimIndex + delimiter.length));
160
+ } else {
161
+ contents.push(obj);
162
+ }
163
+ }
164
+ commonPrefixes.push(...Array.from(prefixSet).sort());
165
+ }
166
+ const truncated = contents.length > maxKeys;
167
+ const page = contents.slice(0, maxKeys);
168
+ const nextToken = truncated ? page[page.length - 1].key : void 0;
169
+ const contentsXml = page.map(
170
+ (o) => ` <Contents>
171
+ <Key>${escapeXml(o.key)}</Key>
172
+ <LastModified>${o.last_modified}</LastModified>
173
+ <ETag>"${o.etag}"</ETag>
174
+ <Size>${o.content_length}</Size>
175
+ <StorageClass>STANDARD</StorageClass>
176
+ </Contents>`
177
+ ).join("\n");
178
+ const prefixesXml = commonPrefixes.map((p) => ` <CommonPrefixes><Prefix>${escapeXml(p)}</Prefix></CommonPrefixes>`).join("\n");
179
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
180
+ <ListBucketResult>
181
+ <Name>${escapeXml(bucketName)}</Name>
182
+ <Prefix>${escapeXml(prefix)}</Prefix>
183
+ <MaxKeys>${maxKeys}</MaxKeys>
184
+ <IsTruncated>${truncated}</IsTruncated>
185
+ <KeyCount>${page.length}</KeyCount>${continuationToken ? `
186
+ <ContinuationToken>${escapeXml(continuationToken)}</ContinuationToken>` : ""}${nextToken ? `
187
+ <NextContinuationToken>${escapeXml(nextToken)}</NextContinuationToken>` : ""}${startAfter ? `
188
+ <StartAfter>${escapeXml(startAfter)}</StartAfter>` : ""}
189
+ ${contentsXml}
190
+ ${prefixesXml}
191
+ </ListBucketResult>`;
192
+ return awsXmlResponse(c, xml);
193
+ };
194
+ const handlePresignedPost = async (c) => {
195
+ const bucketName = c.req.param("bucket");
196
+ const bucket = aws().s3Buckets.findOneBy("bucket_name", bucketName);
197
+ if (!bucket) {
198
+ return awsErrorXml(c, "NoSuchBucket", "The specified bucket does not exist.", 404);
199
+ }
200
+ const body = await c.req.parseBody();
201
+ const key = body["key"];
202
+ if (!key) {
203
+ return awsErrorXml(c, "InvalidArgument", "Bucket POST must contain a field named 'key'.", 400);
204
+ }
205
+ const file = body["file"];
206
+ if (!file || !(file instanceof File)) {
207
+ return awsErrorXml(c, "InvalidArgument", "Bucket POST must contain a file field.", 400);
208
+ }
209
+ const policyB64 = body["Policy"];
210
+ if (policyB64) {
211
+ let policy;
212
+ try {
213
+ policy = JSON.parse(Buffer.from(policyB64, "base64").toString());
214
+ } catch {
215
+ return awsErrorXml(c, "InvalidPolicyDocument", "Invalid Policy: Invalid JSON.", 400);
216
+ }
217
+ if (policy.expiration) {
218
+ const expDate = new Date(policy.expiration);
219
+ if (expDate.getTime() < Date.now()) {
220
+ return awsErrorXml(c, "AccessDenied", "Invalid according to Policy: Policy expired.", 403);
221
+ }
222
+ }
223
+ if (Array.isArray(policy.conditions)) {
224
+ for (const condition of policy.conditions) {
225
+ if (!Array.isArray(condition)) continue;
226
+ if (condition[0] === "content-length-range") {
227
+ const min = condition[1];
228
+ const max = condition[2];
229
+ if (file.size < min || file.size > max) {
230
+ return awsErrorXml(c, "EntityTooLarge", "Your proposed upload exceeds the maximum allowed size.", 400);
231
+ }
232
+ } else if (condition[0] === "starts-with") {
233
+ const field = condition[1].replace(/^\$/, "");
234
+ const prefix = condition[2];
235
+ const value = body[field] ?? "";
236
+ if (!value.startsWith(prefix)) {
237
+ return awsErrorXml(
238
+ c,
239
+ "AccessDenied",
240
+ `Invalid according to Policy: Policy Condition failed: ["starts-with", "$${field}", "${prefix}"]`,
241
+ 403
242
+ );
243
+ }
244
+ }
245
+ }
246
+ }
247
+ }
248
+ const fileContent = await file.text();
249
+ const contentType = body["Content-Type"] ?? file.type ?? "application/octet-stream";
250
+ const etag = md5(fileContent);
251
+ const contentLength = new TextEncoder().encode(fileContent).byteLength;
252
+ const existing = aws().s3Objects.findBy("bucket_name", bucketName).find((o) => o.key === key);
253
+ if (existing) {
254
+ aws().s3Objects.update(existing.id, {
255
+ body: fileContent,
256
+ content_type: contentType,
257
+ content_length: contentLength,
258
+ etag,
259
+ last_modified: (/* @__PURE__ */ new Date()).toISOString(),
260
+ metadata: {}
261
+ });
262
+ } else {
263
+ aws().s3Objects.insert({
264
+ bucket_name: bucketName,
265
+ key,
266
+ body: fileContent,
267
+ content_type: contentType,
268
+ content_length: contentLength,
269
+ etag,
270
+ last_modified: (/* @__PURE__ */ new Date()).toISOString(),
271
+ metadata: {}
272
+ });
273
+ }
274
+ const successStatus = parseInt(body["success_action_status"], 10);
275
+ if (successStatus === 201) {
276
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
277
+ <PostResponse>
278
+ <Location>${escapeXml(baseUrl)}/${escapeXml(bucketName)}/${escapeXml(key)}</Location>
279
+ <Bucket>${escapeXml(bucketName)}</Bucket>
280
+ <Key>${escapeXml(key)}</Key>
281
+ <ETag>"${etag}"</ETag>
282
+ </PostResponse>`;
283
+ return awsXmlResponse(c, xml, 201);
284
+ }
285
+ return c.body(null, 204);
286
+ };
287
+ const handlePutObject = async (c) => {
288
+ const bucketName = c.req.param("bucket");
289
+ const key = c.req.param("key");
290
+ const bucket = aws().s3Buckets.findOneBy("bucket_name", bucketName);
291
+ if (!bucket) {
292
+ return awsErrorXml(c, "NoSuchBucket", "The specified bucket does not exist.", 404);
293
+ }
294
+ const copySource = c.req.header("x-amz-copy-source");
295
+ if (copySource) {
296
+ const normalized = copySource.startsWith("/") ? copySource.slice(1) : copySource;
297
+ const slashIndex = normalized.indexOf("/");
298
+ if (slashIndex < 0) {
299
+ return awsErrorXml(c, "InvalidArgument", "Invalid copy source.", 400);
300
+ }
301
+ const srcBucket = normalized.slice(0, slashIndex);
302
+ const srcKey = normalized.slice(slashIndex + 1);
303
+ const srcObj = aws().s3Objects.findBy("bucket_name", srcBucket).find((o) => o.key === srcKey);
304
+ if (!srcObj) {
305
+ return awsErrorXml(c, "NoSuchKey", "The specified source key does not exist.", 404);
306
+ }
307
+ const etag2 = srcObj.etag;
308
+ const now = (/* @__PURE__ */ new Date()).toISOString();
309
+ const existing2 = aws().s3Objects.findBy("bucket_name", bucketName).find((o) => o.key === key);
310
+ if (existing2) {
311
+ aws().s3Objects.update(existing2.id, {
312
+ body: srcObj.body,
313
+ content_type: srcObj.content_type,
314
+ content_length: srcObj.content_length,
315
+ etag: etag2,
316
+ last_modified: now,
317
+ metadata: { ...srcObj.metadata }
318
+ });
319
+ } else {
320
+ aws().s3Objects.insert({
321
+ bucket_name: bucketName,
322
+ key,
323
+ body: srcObj.body,
324
+ content_type: srcObj.content_type,
325
+ content_length: srcObj.content_length,
326
+ etag: etag2,
327
+ last_modified: now,
328
+ metadata: { ...srcObj.metadata }
329
+ });
330
+ }
331
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
332
+ <CopyObjectResult>
333
+ <ETag>"${etag2}"</ETag>
334
+ <LastModified>${now}</LastModified>
335
+ </CopyObjectResult>`;
336
+ return c.text(xml, 200, {
337
+ "Content-Type": "application/xml",
338
+ "Last-Modified": new Date(now).toUTCString()
339
+ });
340
+ }
341
+ const body = await c.req.text();
342
+ const contentType = c.req.header("Content-Type") ?? "application/octet-stream";
343
+ const etag = md5(body);
344
+ const metadata = {};
345
+ for (const [headerName, headerValue] of Object.entries(c.req.header())) {
346
+ if (typeof headerValue === "string" && headerName.toLowerCase().startsWith("x-amz-meta-")) {
347
+ metadata[headerName.slice("x-amz-meta-".length)] = headerValue;
348
+ }
349
+ }
350
+ const existing = aws().s3Objects.findBy("bucket_name", bucketName).find((o) => o.key === key);
351
+ if (existing) {
352
+ aws().s3Objects.update(existing.id, {
353
+ body,
354
+ content_type: contentType,
355
+ content_length: new TextEncoder().encode(body).byteLength,
356
+ etag,
357
+ last_modified: (/* @__PURE__ */ new Date()).toISOString(),
358
+ metadata
359
+ });
360
+ } else {
361
+ aws().s3Objects.insert({
362
+ bucket_name: bucketName,
363
+ key,
364
+ body,
365
+ content_type: contentType,
366
+ content_length: new TextEncoder().encode(body).byteLength,
367
+ etag,
368
+ last_modified: (/* @__PURE__ */ new Date()).toISOString(),
369
+ metadata
370
+ });
371
+ }
372
+ return c.text("", 200, { ETag: `"${etag}"` });
373
+ };
374
+ const handleGetObject = (c) => {
375
+ const bucketName = c.req.param("bucket");
376
+ const key = c.req.param("key");
377
+ const bucket = aws().s3Buckets.findOneBy("bucket_name", bucketName);
378
+ if (!bucket) {
379
+ return awsErrorXml(c, "NoSuchBucket", "The specified bucket does not exist.", 404);
380
+ }
381
+ const obj = aws().s3Objects.findBy("bucket_name", bucketName).find((o) => o.key === key);
382
+ if (!obj) {
383
+ return awsErrorXml(c, "NoSuchKey", "The specified key does not exist.", 404);
384
+ }
385
+ const headers = {
386
+ "Content-Type": obj.content_type,
387
+ "Content-Length": String(obj.content_length),
388
+ ETag: `"${obj.etag}"`,
389
+ "Last-Modified": new Date(obj.last_modified).toUTCString()
390
+ };
391
+ for (const [k, v] of Object.entries(obj.metadata)) {
392
+ headers[`x-amz-meta-${k}`] = v;
393
+ }
394
+ return c.text(obj.body, 200, headers);
395
+ };
396
+ const handleHeadObject = (c) => {
397
+ const bucketName = c.req.param("bucket");
398
+ const key = c.req.param("key");
399
+ const obj = aws().s3Objects.findBy("bucket_name", bucketName).find((o) => o.key === key);
400
+ if (!obj) {
401
+ return c.text("", 404);
402
+ }
403
+ return c.text("", 200, {
404
+ "Content-Type": obj.content_type,
405
+ "Content-Length": String(obj.content_length),
406
+ ETag: `"${obj.etag}"`,
407
+ "Last-Modified": new Date(obj.last_modified).toUTCString()
408
+ });
409
+ };
410
+ const handleDeleteObject = (c) => {
411
+ const bucketName = c.req.param("bucket");
412
+ const key = c.req.param("key");
413
+ const obj = aws().s3Objects.findBy("bucket_name", bucketName).find((o) => o.key === key);
414
+ if (obj) {
415
+ aws().s3Objects.delete(obj.id);
416
+ }
417
+ return c.body(null, 204);
418
+ };
419
+ app.get("/s3/", handleListBuckets);
420
+ app.put("/s3/:bucket", handleCreateBucket);
421
+ app.delete("/s3/:bucket", handleDeleteBucket);
422
+ app.on("HEAD", "/s3/:bucket", handleHeadBucket);
423
+ app.get("/s3/:bucket", handleListObjects);
424
+ app.post("/s3/:bucket", handlePresignedPost);
425
+ app.put("/s3/:bucket/:key{.+}", handlePutObject);
426
+ app.get("/s3/:bucket/:key{.+}", handleGetObject);
427
+ app.on("HEAD", "/s3/:bucket/:key{.+}", handleHeadObject);
428
+ app.delete("/s3/:bucket/:key{.+}", handleDeleteObject);
429
+ app.get("/", handleListBuckets);
430
+ app.put("/:bucket", handleCreateBucket);
431
+ app.put("/:bucket/", handleCreateBucket);
432
+ app.delete("/:bucket", handleDeleteBucket);
433
+ app.delete("/:bucket/", handleDeleteBucket);
434
+ app.on("HEAD", "/:bucket", handleHeadBucket);
435
+ app.on("HEAD", "/:bucket/", handleHeadBucket);
436
+ app.get("/:bucket", handleListObjects);
437
+ app.get("/:bucket/", handleListObjects);
438
+ app.post("/:bucket", handlePresignedPost);
439
+ app.post("/:bucket/", handlePresignedPost);
440
+ app.put("/:bucket/:key{.+}", handlePutObject);
441
+ app.get("/:bucket/:key{.+}", handleGetObject);
442
+ app.on("HEAD", "/:bucket/:key{.+}", handleHeadObject);
443
+ app.delete("/:bucket/:key{.+}", handleDeleteObject);
444
+ }
445
+ function sqsRoutes(ctx) {
446
+ const { app, store, baseUrl } = ctx;
447
+ const aws = () => getAwsStore(store);
448
+ const accountId = getAccountId();
449
+ app.post("/sqs/", async (c) => {
450
+ const body = await c.req.text();
451
+ const params = parseQueryString(body);
452
+ const action = params["Action"] ?? c.req.query("Action") ?? "";
453
+ switch (action) {
454
+ case "CreateQueue":
455
+ return createQueue(c, params);
456
+ case "DeleteQueue":
457
+ return deleteQueue(c, params);
458
+ case "ListQueues":
459
+ return listQueues(c, params);
460
+ case "GetQueueUrl":
461
+ return getQueueUrl(c, params);
462
+ case "GetQueueAttributes":
463
+ return getQueueAttributes(c, params);
464
+ case "SendMessage":
465
+ return sendMessage(c, params);
466
+ case "ReceiveMessage":
467
+ return receiveMessage(c, params);
468
+ case "DeleteMessage":
469
+ return deleteMessage(c, params);
470
+ case "PurgeQueue":
471
+ return purgeQueue(c, params);
472
+ default:
473
+ return awsErrorXml(c, "InvalidAction", `The action ${action} is not valid for this endpoint.`, 400);
474
+ }
475
+ });
476
+ function createQueue(c, params) {
477
+ const queueName = params["QueueName"] ?? "";
478
+ if (!queueName) {
479
+ return awsErrorXml(c, "MissingParameter", "The request must contain the parameter QueueName.", 400);
480
+ }
481
+ const existing = aws().sqsQueues.findOneBy("queue_name", queueName);
482
+ if (existing) {
483
+ const xml2 = `<?xml version="1.0" encoding="UTF-8"?>
484
+ <CreateQueueResponse>
485
+ <CreateQueueResult>
486
+ <QueueUrl>${escapeXml(existing.queue_url)}</QueueUrl>
487
+ </CreateQueueResult>
488
+ <ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
489
+ </CreateQueueResponse>`;
490
+ return awsXmlResponse(c, xml2);
491
+ }
492
+ const fifo = queueName.endsWith(".fifo");
493
+ const queueUrl = `${baseUrl}/sqs/${accountId}/${queueName}`;
494
+ const arn = `arn:aws:sqs:us-east-1:${accountId}:${queueName}`;
495
+ const attrs = {};
496
+ for (let i = 1; params[`Attribute.${i}.Name`]; i++) {
497
+ attrs[params[`Attribute.${i}.Name`]] = params[`Attribute.${i}.Value`] ?? "";
498
+ }
499
+ aws().sqsQueues.insert({
500
+ queue_name: queueName,
501
+ queue_url: queueUrl,
502
+ arn,
503
+ visibility_timeout: parseInt(attrs["VisibilityTimeout"] ?? "30", 10),
504
+ delay_seconds: parseInt(attrs["DelaySeconds"] ?? "0", 10),
505
+ max_message_size: parseInt(attrs["MaximumMessageSize"] ?? "262144", 10),
506
+ message_retention_period: parseInt(attrs["MessageRetentionPeriod"] ?? "345600", 10),
507
+ receive_message_wait_time: parseInt(attrs["ReceiveMessageWaitTimeSeconds"] ?? "0", 10),
508
+ fifo
509
+ });
510
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
511
+ <CreateQueueResponse>
512
+ <CreateQueueResult>
513
+ <QueueUrl>${escapeXml(queueUrl)}</QueueUrl>
514
+ </CreateQueueResult>
515
+ <ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
516
+ </CreateQueueResponse>`;
517
+ return awsXmlResponse(c, xml);
518
+ }
519
+ function deleteQueue(c, params) {
520
+ const queueUrl = params["QueueUrl"] ?? "";
521
+ const queue = aws().sqsQueues.findOneBy("queue_url", queueUrl);
522
+ if (!queue) {
523
+ return awsErrorXml(c, "AWS.SimpleQueueService.NonExistentQueue", "The specified queue does not exist.", 400);
524
+ }
525
+ const messages = aws().sqsMessages.findBy("queue_name", queue.queue_name);
526
+ for (const msg of messages) {
527
+ aws().sqsMessages.delete(msg.id);
528
+ }
529
+ aws().sqsQueues.delete(queue.id);
530
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
531
+ <DeleteQueueResponse>
532
+ <ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
533
+ </DeleteQueueResponse>`;
534
+ return awsXmlResponse(c, xml);
535
+ }
536
+ function listQueues(c, params) {
537
+ const prefix = params["QueueNamePrefix"] ?? "";
538
+ let queues = aws().sqsQueues.all();
539
+ if (prefix) {
540
+ queues = queues.filter((q) => q.queue_name.startsWith(prefix));
541
+ }
542
+ const queueUrlsXml = queues.map((q) => ` <QueueUrl>${escapeXml(q.queue_url)}</QueueUrl>`).join("\n");
543
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
544
+ <ListQueuesResponse>
545
+ <ListQueuesResult>
546
+ ${queueUrlsXml}
547
+ </ListQueuesResult>
548
+ <ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
549
+ </ListQueuesResponse>`;
550
+ return awsXmlResponse(c, xml);
551
+ }
552
+ function getQueueUrl(c, params) {
553
+ const queueName = params["QueueName"] ?? "";
554
+ const queue = aws().sqsQueues.findOneBy("queue_name", queueName);
555
+ if (!queue) {
556
+ return awsErrorXml(c, "AWS.SimpleQueueService.NonExistentQueue", "The specified queue does not exist.", 400);
557
+ }
558
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
559
+ <GetQueueUrlResponse>
560
+ <GetQueueUrlResult>
561
+ <QueueUrl>${escapeXml(queue.queue_url)}</QueueUrl>
562
+ </GetQueueUrlResult>
563
+ <ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
564
+ </GetQueueUrlResponse>`;
565
+ return awsXmlResponse(c, xml);
566
+ }
567
+ function getQueueAttributes(c, params) {
568
+ const queueUrl = params["QueueUrl"] ?? "";
569
+ const queue = aws().sqsQueues.findOneBy("queue_url", queueUrl);
570
+ if (!queue) {
571
+ return awsErrorXml(c, "AWS.SimpleQueueService.NonExistentQueue", "The specified queue does not exist.", 400);
572
+ }
573
+ const messages = aws().sqsMessages.findBy("queue_name", queue.queue_name);
574
+ const now = Date.now();
575
+ const visibleCount = messages.filter((m) => m.visible_after <= now).length;
576
+ const inFlightCount = messages.filter((m) => m.visible_after > now).length;
577
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
578
+ <GetQueueAttributesResponse>
579
+ <GetQueueAttributesResult>
580
+ <Attribute><Name>QueueArn</Name><Value>${queue.arn}</Value></Attribute>
581
+ <Attribute><Name>ApproximateNumberOfMessages</Name><Value>${visibleCount}</Value></Attribute>
582
+ <Attribute><Name>ApproximateNumberOfMessagesNotVisible</Name><Value>${inFlightCount}</Value></Attribute>
583
+ <Attribute><Name>VisibilityTimeout</Name><Value>${queue.visibility_timeout}</Value></Attribute>
584
+ <Attribute><Name>MaximumMessageSize</Name><Value>${queue.max_message_size}</Value></Attribute>
585
+ <Attribute><Name>MessageRetentionPeriod</Name><Value>${queue.message_retention_period}</Value></Attribute>
586
+ <Attribute><Name>DelaySeconds</Name><Value>${queue.delay_seconds}</Value></Attribute>
587
+ <Attribute><Name>ReceiveMessageWaitTimeSeconds</Name><Value>${queue.receive_message_wait_time}</Value></Attribute>
588
+ <Attribute><Name>FifoQueue</Name><Value>${queue.fifo}</Value></Attribute>
589
+ </GetQueueAttributesResult>
590
+ <ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
591
+ </GetQueueAttributesResponse>`;
592
+ return awsXmlResponse(c, xml);
593
+ }
594
+ function sendMessage(c, params) {
595
+ const queueUrl = params["QueueUrl"] ?? "";
596
+ const messageBody = params["MessageBody"] ?? "";
597
+ const queue = aws().sqsQueues.findOneBy("queue_url", queueUrl);
598
+ if (!queue) {
599
+ return awsErrorXml(c, "AWS.SimpleQueueService.NonExistentQueue", "The specified queue does not exist.", 400);
600
+ }
601
+ if (!messageBody) {
602
+ return awsErrorXml(c, "MissingParameter", "The request must contain the parameter MessageBody.", 400);
603
+ }
604
+ const bodyBytes = new TextEncoder().encode(messageBody).byteLength;
605
+ if (bodyBytes > queue.max_message_size) {
606
+ return awsErrorXml(
607
+ c,
608
+ "InvalidParameterValue",
609
+ `One or more parameters are invalid. Reason: Message must be shorter than ${queue.max_message_size} bytes.`,
610
+ 400
611
+ );
612
+ }
613
+ const messageId = generateMessageId();
614
+ const bodyMd5 = md5(messageBody);
615
+ const now = Date.now();
616
+ const messageAttributes = {};
617
+ let attrIndex = 1;
618
+ while (params[`MessageAttribute.${attrIndex}.Name`]) {
619
+ const name = params[`MessageAttribute.${attrIndex}.Name`];
620
+ const dataType = params[`MessageAttribute.${attrIndex}.Value.DataType`] ?? "String";
621
+ const stringValue = params[`MessageAttribute.${attrIndex}.Value.StringValue`];
622
+ messageAttributes[name] = { DataType: dataType, StringValue: stringValue };
623
+ attrIndex++;
624
+ }
625
+ aws().sqsMessages.insert({
626
+ queue_name: queue.queue_name,
627
+ message_id: messageId,
628
+ receipt_handle: generateReceiptHandle(),
629
+ body: messageBody,
630
+ md5_of_body: bodyMd5,
631
+ attributes: {
632
+ SentTimestamp: String(now),
633
+ ApproximateReceiveCount: "0",
634
+ ApproximateFirstReceiveTimestamp: "",
635
+ SenderId: getAccountId()
636
+ },
637
+ message_attributes: messageAttributes,
638
+ visible_after: now + queue.delay_seconds * 1e3,
639
+ sent_timestamp: now,
640
+ receive_count: 0
641
+ });
642
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
643
+ <SendMessageResponse>
644
+ <SendMessageResult>
645
+ <MessageId>${messageId}</MessageId>
646
+ <MD5OfMessageBody>${bodyMd5}</MD5OfMessageBody>
647
+ </SendMessageResult>
648
+ <ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
649
+ </SendMessageResponse>`;
650
+ return awsXmlResponse(c, xml);
651
+ }
652
+ function receiveMessage(c, params) {
653
+ const queueUrl = params["QueueUrl"] ?? "";
654
+ const maxMessages = Math.min(parseInt(params["MaxNumberOfMessages"] ?? "1", 10), 10);
655
+ const visibilityTimeout = parseInt(params["VisibilityTimeout"] ?? "", 10);
656
+ const queue = aws().sqsQueues.findOneBy("queue_url", queueUrl);
657
+ if (!queue) {
658
+ return awsErrorXml(c, "AWS.SimpleQueueService.NonExistentQueue", "The specified queue does not exist.", 400);
659
+ }
660
+ const now = Date.now();
661
+ const timeout = isNaN(visibilityTimeout) ? queue.visibility_timeout : visibilityTimeout;
662
+ const allMessages = aws().sqsMessages.findBy("queue_name", queue.queue_name);
663
+ const visible = allMessages.filter((m) => m.visible_after <= now);
664
+ const batch = visible.slice(0, maxMessages);
665
+ for (const msg of batch) {
666
+ const newReceiptHandle = generateReceiptHandle();
667
+ aws().sqsMessages.update(msg.id, {
668
+ receipt_handle: newReceiptHandle,
669
+ visible_after: now + timeout * 1e3,
670
+ receive_count: msg.receive_count + 1
671
+ });
672
+ msg.receipt_handle = newReceiptHandle;
673
+ msg.receive_count += 1;
674
+ }
675
+ const messagesXml = batch.map(
676
+ (m) => ` <Message>
677
+ <MessageId>${m.message_id}</MessageId>
678
+ <ReceiptHandle>${m.receipt_handle}</ReceiptHandle>
679
+ <MD5OfBody>${m.md5_of_body}</MD5OfBody>
680
+ <Body>${escapeXml(m.body)}</Body>
681
+ <Attribute><Name>SentTimestamp</Name><Value>${m.sent_timestamp}</Value></Attribute>
682
+ <Attribute><Name>ApproximateReceiveCount</Name><Value>${m.receive_count}</Value></Attribute>
683
+ <Attribute><Name>ApproximateFirstReceiveTimestamp</Name><Value>${m.sent_timestamp}</Value></Attribute>
684
+ </Message>`
685
+ ).join("\n");
686
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
687
+ <ReceiveMessageResponse>
688
+ <ReceiveMessageResult>
689
+ ${messagesXml}
690
+ </ReceiveMessageResult>
691
+ <ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
692
+ </ReceiveMessageResponse>`;
693
+ return awsXmlResponse(c, xml);
694
+ }
695
+ function deleteMessage(c, params) {
696
+ const queueUrl = params["QueueUrl"] ?? "";
697
+ const receiptHandle = params["ReceiptHandle"] ?? "";
698
+ const queue = aws().sqsQueues.findOneBy("queue_url", queueUrl);
699
+ if (!queue) {
700
+ return awsErrorXml(c, "AWS.SimpleQueueService.NonExistentQueue", "The specified queue does not exist.", 400);
701
+ }
702
+ const messages = aws().sqsMessages.findBy("queue_name", queue.queue_name);
703
+ const msg = messages.find((m) => m.receipt_handle === receiptHandle);
704
+ if (msg) {
705
+ aws().sqsMessages.delete(msg.id);
706
+ }
707
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
708
+ <DeleteMessageResponse>
709
+ <ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
710
+ </DeleteMessageResponse>`;
711
+ return awsXmlResponse(c, xml);
712
+ }
713
+ function purgeQueue(c, params) {
714
+ const queueUrl = params["QueueUrl"] ?? "";
715
+ const queue = aws().sqsQueues.findOneBy("queue_url", queueUrl);
716
+ if (!queue) {
717
+ return awsErrorXml(c, "AWS.SimpleQueueService.NonExistentQueue", "The specified queue does not exist.", 400);
718
+ }
719
+ const messages = aws().sqsMessages.findBy("queue_name", queue.queue_name);
720
+ for (const msg of messages) {
721
+ aws().sqsMessages.delete(msg.id);
722
+ }
723
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
724
+ <PurgeQueueResponse>
725
+ <ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
726
+ </PurgeQueueResponse>`;
727
+ return awsXmlResponse(c, xml);
728
+ }
729
+ }
730
+ function iamRoutes(ctx) {
731
+ const { app, store } = ctx;
732
+ const aws = () => getAwsStore(store);
733
+ const accountId = getAccountId();
734
+ app.post("/iam/", async (c) => {
735
+ const body = await c.req.text();
736
+ const params = parseQueryString(body);
737
+ const action = params["Action"] ?? c.req.query("Action") ?? "";
738
+ switch (action) {
739
+ case "CreateUser":
740
+ return createUser(c, params);
741
+ case "GetUser":
742
+ return getUser(c, params);
743
+ case "DeleteUser":
744
+ return deleteUser(c, params);
745
+ case "ListUsers":
746
+ return listUsers(c);
747
+ case "CreateAccessKey":
748
+ return createAccessKey(c, params);
749
+ case "ListAccessKeys":
750
+ return listAccessKeys(c, params);
751
+ case "DeleteAccessKey":
752
+ return deleteAccessKey(c, params);
753
+ case "CreateRole":
754
+ return createRole(c, params);
755
+ case "GetRole":
756
+ return getRole(c, params);
757
+ case "DeleteRole":
758
+ return deleteRole(c, params);
759
+ case "ListRoles":
760
+ return listRoles(c);
761
+ default:
762
+ return awsErrorXml(c, "InvalidAction", `The action ${action} is not valid for this endpoint.`, 400);
763
+ }
764
+ });
765
+ app.post("/sts/", async (c) => {
766
+ const body = await c.req.text();
767
+ const params = parseQueryString(body);
768
+ const action = params["Action"] ?? c.req.query("Action") ?? "";
769
+ switch (action) {
770
+ case "GetCallerIdentity":
771
+ return getCallerIdentity(c);
772
+ case "AssumeRole":
773
+ return assumeRole(c, params);
774
+ default:
775
+ return awsErrorXml(c, "InvalidAction", `The action ${action} is not valid for this endpoint.`, 400);
776
+ }
777
+ });
778
+ function createUser(c, params) {
779
+ const userName = params["UserName"] ?? "";
780
+ if (!userName) {
781
+ return awsErrorXml(c, "ValidationError", "The request must contain the parameter UserName.", 400);
782
+ }
783
+ const existing = aws().iamUsers.findOneBy("user_name", userName);
784
+ if (existing) {
785
+ return awsErrorXml(c, "EntityAlreadyExists", `User with name ${escapeXml(userName)} already exists.`, 409);
786
+ }
787
+ const userId = generateAwsId("AIDA");
788
+ const path = params["Path"] ?? "/";
789
+ const arn = `arn:aws:iam::${accountId}:user${path}${userName}`;
790
+ aws().iamUsers.insert({
791
+ user_name: userName,
792
+ user_id: userId,
793
+ arn,
794
+ path,
795
+ access_keys: []
796
+ });
797
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
798
+ <CreateUserResponse>
799
+ <CreateUserResult>
800
+ <User>
801
+ <Path>${escapeXml(path)}</Path>
802
+ <UserName>${escapeXml(userName)}</UserName>
803
+ <UserId>${userId}</UserId>
804
+ <Arn>${escapeXml(arn)}</Arn>
805
+ <CreateDate>${(/* @__PURE__ */ new Date()).toISOString()}</CreateDate>
806
+ </User>
807
+ </CreateUserResult>
808
+ <ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
809
+ </CreateUserResponse>`;
810
+ return awsXmlResponse(c, xml);
811
+ }
812
+ function getUser(c, params) {
813
+ const userName = params["UserName"] ?? "";
814
+ const user = aws().iamUsers.findOneBy("user_name", userName);
815
+ if (!user) {
816
+ return awsErrorXml(c, "NoSuchEntity", `The user with name ${escapeXml(userName)} cannot be found.`, 404);
817
+ }
818
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
819
+ <GetUserResponse>
820
+ <GetUserResult>
821
+ <User>
822
+ <Path>${escapeXml(user.path)}</Path>
823
+ <UserName>${escapeXml(user.user_name)}</UserName>
824
+ <UserId>${user.user_id}</UserId>
825
+ <Arn>${escapeXml(user.arn)}</Arn>
826
+ <CreateDate>${user.created_at}</CreateDate>
827
+ </User>
828
+ </GetUserResult>
829
+ <ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
830
+ </GetUserResponse>`;
831
+ return awsXmlResponse(c, xml);
832
+ }
833
+ function deleteUser(c, params) {
834
+ const userName = params["UserName"] ?? "";
835
+ const user = aws().iamUsers.findOneBy("user_name", userName);
836
+ if (!user) {
837
+ return awsErrorXml(c, "NoSuchEntity", `The user with name ${escapeXml(userName)} cannot be found.`, 404);
838
+ }
839
+ aws().iamUsers.delete(user.id);
840
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
841
+ <DeleteUserResponse>
842
+ <ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
843
+ </DeleteUserResponse>`;
844
+ return awsXmlResponse(c, xml);
845
+ }
846
+ function listUsers(c) {
847
+ const users = aws().iamUsers.all();
848
+ const usersXml = users.map(
849
+ (u) => ` <member>
850
+ <Path>${escapeXml(u.path)}</Path>
851
+ <UserName>${escapeXml(u.user_name)}</UserName>
852
+ <UserId>${u.user_id}</UserId>
853
+ <Arn>${escapeXml(u.arn)}</Arn>
854
+ <CreateDate>${u.created_at}</CreateDate>
855
+ </member>`
856
+ ).join("\n");
857
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
858
+ <ListUsersResponse>
859
+ <ListUsersResult>
860
+ <IsTruncated>false</IsTruncated>
861
+ <Users>
862
+ ${usersXml}
863
+ </Users>
864
+ </ListUsersResult>
865
+ <ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
866
+ </ListUsersResponse>`;
867
+ return awsXmlResponse(c, xml);
868
+ }
869
+ function createAccessKey(c, params) {
870
+ const userName = params["UserName"] ?? "";
871
+ const user = aws().iamUsers.findOneBy("user_name", userName);
872
+ if (!user) {
873
+ return awsErrorXml(c, "NoSuchEntity", `The user with name ${escapeXml(userName)} cannot be found.`, 404);
874
+ }
875
+ const accessKeyId = "AKIA" + randomBytes2(8).toString("hex").toUpperCase();
876
+ const secretAccessKey = randomBytes2(30).toString("base64");
877
+ const keys = [
878
+ ...user.access_keys,
879
+ { access_key_id: accessKeyId, secret_access_key: secretAccessKey, status: "Active" }
880
+ ];
881
+ aws().iamUsers.update(user.id, { access_keys: keys });
882
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
883
+ <CreateAccessKeyResponse>
884
+ <CreateAccessKeyResult>
885
+ <AccessKey>
886
+ <UserName>${escapeXml(userName)}</UserName>
887
+ <AccessKeyId>${accessKeyId}</AccessKeyId>
888
+ <Status>Active</Status>
889
+ <SecretAccessKey>${secretAccessKey}</SecretAccessKey>
890
+ <CreateDate>${(/* @__PURE__ */ new Date()).toISOString()}</CreateDate>
891
+ </AccessKey>
892
+ </CreateAccessKeyResult>
893
+ <ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
894
+ </CreateAccessKeyResponse>`;
895
+ return awsXmlResponse(c, xml);
896
+ }
897
+ function listAccessKeys(c, params) {
898
+ const userName = params["UserName"] ?? "";
899
+ const user = aws().iamUsers.findOneBy("user_name", userName);
900
+ if (!user) {
901
+ return awsErrorXml(c, "NoSuchEntity", `The user with name ${escapeXml(userName)} cannot be found.`, 404);
902
+ }
903
+ const keysXml = user.access_keys.map(
904
+ (k) => ` <member>
905
+ <UserName>${escapeXml(userName)}</UserName>
906
+ <AccessKeyId>${k.access_key_id}</AccessKeyId>
907
+ <Status>${k.status}</Status>
908
+ </member>`
909
+ ).join("\n");
910
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
911
+ <ListAccessKeysResponse>
912
+ <ListAccessKeysResult>
913
+ <IsTruncated>false</IsTruncated>
914
+ <AccessKeyMetadata>
915
+ ${keysXml}
916
+ </AccessKeyMetadata>
917
+ </ListAccessKeysResult>
918
+ <ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
919
+ </ListAccessKeysResponse>`;
920
+ return awsXmlResponse(c, xml);
921
+ }
922
+ function deleteAccessKey(c, params) {
923
+ const userName = params["UserName"] ?? "";
924
+ const accessKeyId = params["AccessKeyId"] ?? "";
925
+ const user = aws().iamUsers.findOneBy("user_name", userName);
926
+ if (!user) {
927
+ return awsErrorXml(c, "NoSuchEntity", `The user with name ${escapeXml(userName)} cannot be found.`, 404);
928
+ }
929
+ const keys = user.access_keys.filter((k) => k.access_key_id !== accessKeyId);
930
+ aws().iamUsers.update(user.id, { access_keys: keys });
931
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
932
+ <DeleteAccessKeyResponse>
933
+ <ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
934
+ </DeleteAccessKeyResponse>`;
935
+ return awsXmlResponse(c, xml);
936
+ }
937
+ function createRole(c, params) {
938
+ const roleName = params["RoleName"] ?? "";
939
+ if (!roleName) {
940
+ return awsErrorXml(c, "ValidationError", "The request must contain the parameter RoleName.", 400);
941
+ }
942
+ const existing = aws().iamRoles.findOneBy("role_name", roleName);
943
+ if (existing) {
944
+ return awsErrorXml(c, "EntityAlreadyExists", `Role with name ${escapeXml(roleName)} already exists.`, 409);
945
+ }
946
+ const roleId = generateAwsId("AROA");
947
+ const path = params["Path"] ?? "/";
948
+ const arn = `arn:aws:iam::${accountId}:role${path}${roleName}`;
949
+ const assumeRolePolicy = params["AssumeRolePolicyDocument"] ?? "{}";
950
+ const description = params["Description"] ?? "";
951
+ aws().iamRoles.insert({
952
+ role_name: roleName,
953
+ role_id: roleId,
954
+ arn,
955
+ path,
956
+ assume_role_policy_document: assumeRolePolicy,
957
+ description
958
+ });
959
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
960
+ <CreateRoleResponse>
961
+ <CreateRoleResult>
962
+ <Role>
963
+ <Path>${escapeXml(path)}</Path>
964
+ <RoleName>${escapeXml(roleName)}</RoleName>
965
+ <RoleId>${roleId}</RoleId>
966
+ <Arn>${escapeXml(arn)}</Arn>
967
+ <CreateDate>${(/* @__PURE__ */ new Date()).toISOString()}</CreateDate>
968
+ <AssumeRolePolicyDocument>${encodeURIComponent(assumeRolePolicy)}</AssumeRolePolicyDocument>
969
+ <Description>${escapeXml(description)}</Description>
970
+ </Role>
971
+ </CreateRoleResult>
972
+ <ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
973
+ </CreateRoleResponse>`;
974
+ return awsXmlResponse(c, xml);
975
+ }
976
+ function getRole(c, params) {
977
+ const roleName = params["RoleName"] ?? "";
978
+ const role = aws().iamRoles.findOneBy("role_name", roleName);
979
+ if (!role) {
980
+ return awsErrorXml(c, "NoSuchEntity", `The role with name ${escapeXml(roleName)} cannot be found.`, 404);
981
+ }
982
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
983
+ <GetRoleResponse>
984
+ <GetRoleResult>
985
+ <Role>
986
+ <Path>${escapeXml(role.path)}</Path>
987
+ <RoleName>${escapeXml(role.role_name)}</RoleName>
988
+ <RoleId>${role.role_id}</RoleId>
989
+ <Arn>${escapeXml(role.arn)}</Arn>
990
+ <CreateDate>${role.created_at}</CreateDate>
991
+ <AssumeRolePolicyDocument>${encodeURIComponent(role.assume_role_policy_document)}</AssumeRolePolicyDocument>
992
+ <Description>${escapeXml(role.description)}</Description>
993
+ </Role>
994
+ </GetRoleResult>
995
+ <ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
996
+ </GetRoleResponse>`;
997
+ return awsXmlResponse(c, xml);
998
+ }
999
+ function deleteRole(c, params) {
1000
+ const roleName = params["RoleName"] ?? "";
1001
+ const role = aws().iamRoles.findOneBy("role_name", roleName);
1002
+ if (!role) {
1003
+ return awsErrorXml(c, "NoSuchEntity", `The role with name ${escapeXml(roleName)} cannot be found.`, 404);
1004
+ }
1005
+ aws().iamRoles.delete(role.id);
1006
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
1007
+ <DeleteRoleResponse>
1008
+ <ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
1009
+ </DeleteRoleResponse>`;
1010
+ return awsXmlResponse(c, xml);
1011
+ }
1012
+ function listRoles(c) {
1013
+ const roles = aws().iamRoles.all();
1014
+ const rolesXml = roles.map(
1015
+ (r) => ` <member>
1016
+ <Path>${escapeXml(r.path)}</Path>
1017
+ <RoleName>${escapeXml(r.role_name)}</RoleName>
1018
+ <RoleId>${r.role_id}</RoleId>
1019
+ <Arn>${escapeXml(r.arn)}</Arn>
1020
+ <CreateDate>${r.created_at}</CreateDate>
1021
+ <Description>${escapeXml(r.description)}</Description>
1022
+ </member>`
1023
+ ).join("\n");
1024
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
1025
+ <ListRolesResponse>
1026
+ <ListRolesResult>
1027
+ <IsTruncated>false</IsTruncated>
1028
+ <Roles>
1029
+ ${rolesXml}
1030
+ </Roles>
1031
+ </ListRolesResult>
1032
+ <ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
1033
+ </ListRolesResponse>`;
1034
+ return awsXmlResponse(c, xml);
1035
+ }
1036
+ function getCallerIdentity(c) {
1037
+ const authUser = c.get("authUser");
1038
+ const userName = authUser?.login ?? "admin";
1039
+ const arn = `arn:aws:iam::${accountId}:user/${userName}`;
1040
+ const user = aws().iamUsers.findOneBy("user_name", userName);
1041
+ const userId = user?.user_id ?? generateAwsId("AIDA");
1042
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
1043
+ <GetCallerIdentityResponse>
1044
+ <GetCallerIdentityResult>
1045
+ <Arn>${escapeXml(arn)}</Arn>
1046
+ <UserId>${userId}</UserId>
1047
+ <Account>${accountId}</Account>
1048
+ </GetCallerIdentityResult>
1049
+ <ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
1050
+ </GetCallerIdentityResponse>`;
1051
+ return awsXmlResponse(c, xml);
1052
+ }
1053
+ function assumeRole(c, params) {
1054
+ const roleArn = params["RoleArn"] ?? "";
1055
+ const sessionName = params["RoleSessionName"] ?? "session";
1056
+ const role = aws().iamRoles.all().find((r) => r.arn === roleArn);
1057
+ if (!role) {
1058
+ return awsErrorXml(c, "NoSuchEntity", `The role specified cannot be found.`, 404);
1059
+ }
1060
+ const accessKeyId = "ASIA" + randomBytes2(8).toString("hex").toUpperCase();
1061
+ const secretAccessKey = randomBytes2(30).toString("base64");
1062
+ const sessionToken = randomBytes2(64).toString("base64");
1063
+ const expiration = new Date(Date.now() + 3600 * 1e3).toISOString();
1064
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
1065
+ <AssumeRoleResponse>
1066
+ <AssumeRoleResult>
1067
+ <Credentials>
1068
+ <AccessKeyId>${accessKeyId}</AccessKeyId>
1069
+ <SecretAccessKey>${secretAccessKey}</SecretAccessKey>
1070
+ <SessionToken>${sessionToken}</SessionToken>
1071
+ <Expiration>${expiration}</Expiration>
1072
+ </Credentials>
1073
+ <AssumedRoleUser>
1074
+ <Arn>${roleArn}/${sessionName}</Arn>
1075
+ <AssumedRoleId>${role.role_id}:${sessionName}</AssumedRoleId>
1076
+ </AssumedRoleUser>
1077
+ </AssumeRoleResult>
1078
+ <ResponseMetadata><RequestId>${generateMessageId()}</RequestId></ResponseMetadata>
1079
+ </AssumeRoleResponse>`;
1080
+ return awsXmlResponse(c, xml);
1081
+ }
1082
+ }
1083
+ function createErrorHandler(documentationUrl) {
1084
+ return async (c, next) => {
1085
+ if (documentationUrl) {
1086
+ c.set("docsUrl", documentationUrl);
1087
+ }
1088
+ await next();
1089
+ };
1090
+ }
1091
+ var errorHandler = createErrorHandler();
1092
+ var isDebug = typeof process !== "undefined" && (process.env.DEBUG === "1" || process.env.DEBUG === "true" || process.env.EMULATE_DEBUG === "1");
1093
+ function escapeHtml(s) {
1094
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1095
+ }
1096
+ function escapeAttr(s) {
1097
+ return escapeHtml(s).replace(/'/g, "&#39;");
1098
+ }
1099
+ var CSS = `
1100
+ @font-face{
1101
+ font-family:'Geist';font-style:normal;font-weight:100 900;font-display:swap;
1102
+ src:url('/_emulate/fonts/geist-sans.woff2') format('woff2');
1103
+ }
1104
+ @font-face{
1105
+ font-family:'Geist Pixel';font-style:normal;font-weight:400;font-display:swap;
1106
+ src:url('/_emulate/fonts/GeistPixel-Square.woff2') format('woff2');
1107
+ }
1108
+ *{box-sizing:border-box;margin:0;padding:0}
1109
+ body{
1110
+ font-family:'Geist',-apple-system,BlinkMacSystemFont,sans-serif;
1111
+ background:#000;color:#33ff00;min-height:100vh;
1112
+ -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
1113
+ }
1114
+ .emu-bar{
1115
+ border-bottom:1px solid #0a3300;padding:10px 20px;
1116
+ display:flex;align-items:center;gap:10px;font-size:.8125rem;color:#1a8c00;
1117
+ }
1118
+ .emu-bar-title{font-weight:600;color:#33ff00;font-family:'Geist Pixel',monospace;}
1119
+ .emu-bar-links{margin-left:auto;display:flex;gap:16px;}
1120
+ .emu-bar-links a{
1121
+ color:#1a8c00;font-size:.75rem;text-decoration:none;transition:color .15s;
1122
+ }
1123
+ .emu-bar-links a:hover{color:#33ff00;}
1124
+ .emu-bar-links a .full{display:inline;}
1125
+ .emu-bar-links a .short{display:none;}
1126
+ @media(max-width:600px){
1127
+ .emu-bar-links a .full{display:none;}
1128
+ .emu-bar-links a .short{display:inline;}
1129
+ }
1130
+
1131
+ .content{
1132
+ display:flex;align-items:center;justify-content:center;
1133
+ min-height:calc(100vh - 42px);padding:24px 16px;
1134
+ }
1135
+ .content-inner{width:100%;max-width:420px;}
1136
+ .card-title{
1137
+ font-family:'Geist Pixel',monospace;
1138
+ font-size:1.125rem;font-weight:600;margin-bottom:4px;color:#33ff00;
1139
+ }
1140
+ .card-subtitle{color:#1a8c00;font-size:.8125rem;margin-bottom:18px;line-height:1.45;}
1141
+ .powered-by{
1142
+ position:fixed;bottom:0;left:0;right:0;
1143
+ text-align:center;padding:12px;font-size:.6875rem;color:#0a3300;
1144
+ font-family:'Geist Pixel',monospace;
1145
+ }
1146
+ .powered-by a{color:#1a8c00;text-decoration:none;transition:color .15s;}
1147
+ .powered-by a:hover{color:#33ff00;}
1148
+
1149
+ .error-title{
1150
+ font-family:'Geist Pixel',monospace;
1151
+ color:#ff4444;font-size:1.125rem;font-weight:600;margin-bottom:8px;
1152
+ }
1153
+ .error-msg{color:#1a8c00;font-size:.875rem;line-height:1.5;}
1154
+ .error-card{text-align:center;}
1155
+
1156
+ .user-form{margin-bottom:8px;}
1157
+ .user-form:last-of-type{margin-bottom:0;}
1158
+ .user-btn{
1159
+ width:100%;display:flex;align-items:center;gap:12px;
1160
+ padding:10px 12px;border:1px solid #0a3300;border-radius:8px;
1161
+ background:#000;color:inherit;cursor:pointer;text-align:left;
1162
+ font:inherit;transition:border-color .15s;
1163
+ }
1164
+ .user-btn:hover{border-color:#33ff00;}
1165
+ .avatar{
1166
+ width:36px;height:36px;border-radius:50%;
1167
+ background:#0a3300;color:#33ff00;font-weight:600;font-size:.875rem;
1168
+ display:flex;align-items:center;justify-content:center;flex-shrink:0;
1169
+ font-family:'Geist Pixel',monospace;
1170
+ }
1171
+ .user-text{min-width:0;}
1172
+ .user-login{font-weight:600;font-size:.875rem;display:block;color:#33ff00;}
1173
+ .user-meta{color:#1a8c00;font-size:.75rem;margin-top:1px;}
1174
+ .user-email{font-size:.6875rem;color:#116600;word-break:break-all;margin-top:1px;}
1175
+
1176
+ .settings-layout{
1177
+ max-width:920px;margin:0 auto;padding:28px 20px;
1178
+ display:flex;gap:28px;
1179
+ }
1180
+ .settings-sidebar{width:200px;flex-shrink:0;}
1181
+ .settings-sidebar a{
1182
+ display:block;padding:6px 10px;border-radius:6px;color:#1a8c00;
1183
+ text-decoration:none;font-size:.8125rem;transition:color .15s;
1184
+ }
1185
+ .settings-sidebar a:hover{color:#33ff00;}
1186
+ .settings-sidebar a.active{color:#33ff00;font-weight:600;}
1187
+ .settings-main{flex:1;min-width:0;}
1188
+
1189
+ .s-card{
1190
+ padding:18px 0;margin-bottom:14px;border-bottom:1px solid #0a3300;
1191
+ }
1192
+ .s-card:last-child{border-bottom:none;}
1193
+ .s-card-header{display:flex;align-items:center;gap:14px;margin-bottom:14px;}
1194
+ .s-icon{
1195
+ width:42px;height:42px;border-radius:8px;
1196
+ background:#0a3300;display:flex;align-items:center;justify-content:center;
1197
+ font-size:1.125rem;font-weight:700;color:#116600;flex-shrink:0;
1198
+ font-family:'Geist Pixel',monospace;
1199
+ }
1200
+ .s-title{
1201
+ font-family:'Geist Pixel',monospace;
1202
+ font-size:1.25rem;font-weight:600;color:#33ff00;
1203
+ }
1204
+ .s-subtitle{font-size:.75rem;color:#1a8c00;margin-top:2px;}
1205
+ .section-heading{
1206
+ font-size:.9375rem;font-weight:600;margin-bottom:10px;color:#33ff00;
1207
+ display:flex;align-items:center;justify-content:space-between;
1208
+ }
1209
+ .perm-list{list-style:none;}
1210
+ .perm-list li{padding:5px 0;font-size:.8125rem;display:flex;align-items:center;gap:6px;color:#1a8c00;}
1211
+ .check{color:#33ff00;}
1212
+ .org-row{
1213
+ display:flex;align-items:center;gap:8px;padding:7px 0;
1214
+ border-bottom:1px solid #0a3300;font-size:.8125rem;
1215
+ }
1216
+ .org-row:last-child{border-bottom:none;}
1217
+ .org-icon{
1218
+ width:22px;height:22px;border-radius:4px;background:#0a3300;
1219
+ display:flex;align-items:center;justify-content:center;
1220
+ font-size:.625rem;font-weight:700;color:#116600;flex-shrink:0;
1221
+ font-family:'Geist Pixel',monospace;
1222
+ }
1223
+ .org-name{font-weight:600;color:#33ff00;}
1224
+ .badge{font-size:.6875rem;padding:1px 7px;border-radius:999px;font-weight:500;}
1225
+ .badge-granted{background:#0a3300;color:#33ff00;}
1226
+ .badge-denied{background:#1a0a0a;color:#ff4444;}
1227
+ .badge-requested{background:#0a3300;color:#1a8c00;}
1228
+ .btn-revoke{
1229
+ display:inline-block;padding:5px 14px;border-radius:6px;
1230
+ border:1px solid #0a3300;background:transparent;color:#ff4444;
1231
+ font-size:.75rem;font-weight:600;cursor:pointer;transition:border-color .15s;
1232
+ }
1233
+ .btn-revoke:hover{border-color:#ff4444;}
1234
+ .info-text{color:#1a8c00;font-size:.75rem;line-height:1.5;margin-top:10px;}
1235
+ .info-text a,.section-heading a{color:#1a8c00;text-decoration:none;transition:color .15s;}
1236
+ .info-text a:hover,.section-heading a:hover{color:#33ff00;}
1237
+ code{font-family:'Geist Mono','SF Mono',ui-monospace,monospace;font-size:.8125rem;color:#33ff00;word-break:break-all;}
1238
+ .code-block{
1239
+ background:#020;border:1px solid #0a3300;border-radius:6px;padding:10px 12px;
1240
+ margin:8px 0 12px;overflow-x:auto;
1241
+ }
1242
+ .code-block code{white-space:pre;word-break:normal;display:block;line-height:1.5;}
1243
+ .app-link{
1244
+ display:flex;align-items:center;gap:12px;padding:12px;
1245
+ border:1px solid #0a3300;border-radius:8px;background:#000;
1246
+ text-decoration:none;color:inherit;margin-bottom:8px;transition:border-color .15s;
1247
+ }
1248
+ .app-link:hover{border-color:#33ff00;}
1249
+ .app-link-name{font-weight:600;font-size:.875rem;color:#33ff00;}
1250
+ .app-link-scopes{font-size:.6875rem;color:#1a8c00;margin-top:1px;}
1251
+ .empty{color:#1a8c00;text-align:center;padding:28px 0;font-size:.875rem;}
1252
+
1253
+ .inspector-layout{max-width:960px;margin:0 auto;padding:28px 20px;}
1254
+ .inspector-tabs{display:flex;gap:4px;margin-bottom:20px;}
1255
+ .inspector-tabs a{
1256
+ padding:7px 16px;border-radius:6px;text-decoration:none;
1257
+ font-size:.8125rem;color:#1a8c00;border:1px solid transparent;
1258
+ transition:color .15s,border-color .15s;
1259
+ }
1260
+ .inspector-tabs a:hover{color:#33ff00;}
1261
+ .inspector-tabs a.active{color:#33ff00;font-weight:600;border-color:#0a3300;background:#0a3300;}
1262
+ .inspector-section{margin-bottom:24px;}
1263
+ .inspector-section h2{
1264
+ font-family:'Geist Pixel',monospace;
1265
+ font-size:1rem;font-weight:600;color:#33ff00;margin-bottom:10px;
1266
+ }
1267
+ .inspector-section h3{
1268
+ font-family:'Geist Pixel',monospace;
1269
+ font-size:.875rem;font-weight:600;color:#1a8c00;margin:16px 0 8px;
1270
+ }
1271
+ .inspector-table{width:100%;border-collapse:collapse;margin-bottom:12px;}
1272
+ .inspector-table th,.inspector-table td{
1273
+ text-align:left;padding:8px 12px;border-bottom:1px solid #0a3300;
1274
+ font-size:.8125rem;
1275
+ }
1276
+ .inspector-table th{color:#1a8c00;font-weight:600;font-size:.75rem;text-transform:uppercase;letter-spacing:.04em;}
1277
+ .inspector-table td{color:#33ff00;}
1278
+ .inspector-table tbody tr{transition:background .1s;}
1279
+ .inspector-table tbody tr:hover{background:#0a3300;}
1280
+ .inspector-empty{color:#1a8c00;text-align:center;padding:20px 0;font-size:.8125rem;}
1281
+
1282
+ .checkout-layout{
1283
+ display:flex;min-height:calc(100vh - 42px);
1284
+ }
1285
+ .checkout-summary{
1286
+ flex:1;background:#020;padding:48px 40px 48px 10%;
1287
+ display:flex;flex-direction:column;justify-content:center;
1288
+ border-right:1px solid #0a3300;
1289
+ }
1290
+ .checkout-form-side{
1291
+ flex:1;background:#000;padding:48px 10% 48px 40px;
1292
+ display:flex;flex-direction:column;justify-content:center;
1293
+ }
1294
+ .checkout-merchant{
1295
+ display:flex;align-items:center;gap:10px;margin-bottom:6px;
1296
+ }
1297
+ .checkout-merchant-name{
1298
+ font-family:'Geist Pixel',monospace;
1299
+ font-size:.9375rem;font-weight:600;color:#33ff00;
1300
+ }
1301
+ .checkout-test-badge{
1302
+ font-size:.625rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
1303
+ background:#0a3300;color:#1a8c00;padding:2px 8px;border-radius:4px;
1304
+ }
1305
+ .checkout-total{
1306
+ font-family:'Geist Pixel',monospace;
1307
+ font-size:2rem;font-weight:700;color:#33ff00;margin:8px 0 28px;
1308
+ }
1309
+ .checkout-line-item{
1310
+ display:flex;align-items:center;gap:14px;padding:14px 0;
1311
+ border-bottom:1px solid #0a3300;
1312
+ }
1313
+ .checkout-line-item:first-child{border-top:1px solid #0a3300;}
1314
+ .checkout-item-icon{
1315
+ width:42px;height:42px;border-radius:6px;background:#0a3300;
1316
+ display:flex;align-items:center;justify-content:center;flex-shrink:0;
1317
+ font-family:'Geist Pixel',monospace;font-size:.875rem;font-weight:700;color:#116600;
1318
+ }
1319
+ .checkout-item-details{flex:1;min-width:0;}
1320
+ .checkout-item-name{font-size:.875rem;font-weight:600;color:#33ff00;}
1321
+ .checkout-item-qty{font-size:.75rem;color:#1a8c00;margin-top:2px;}
1322
+ .checkout-item-price{
1323
+ font-size:.875rem;font-weight:600;color:#33ff00;text-align:right;white-space:nowrap;
1324
+ }
1325
+ .checkout-item-unit{font-size:.6875rem;color:#1a8c00;text-align:right;margin-top:2px;}
1326
+ .checkout-totals{margin-top:20px;}
1327
+ .checkout-totals-row{
1328
+ display:flex;justify-content:space-between;padding:6px 0;
1329
+ font-size:.8125rem;color:#1a8c00;
1330
+ }
1331
+ .checkout-totals-row.total{
1332
+ border-top:1px solid #0a3300;margin-top:8px;padding-top:14px;
1333
+ font-size:.9375rem;font-weight:600;color:#33ff00;
1334
+ }
1335
+ .checkout-form-section{margin-bottom:24px;}
1336
+ .checkout-form-label{
1337
+ font-size:.8125rem;font-weight:600;color:#33ff00;margin-bottom:8px;display:block;
1338
+ }
1339
+ .checkout-input{
1340
+ width:100%;padding:10px 12px;border:1px solid #0a3300;border-radius:6px;
1341
+ background:#020;color:#33ff00;font:inherit;font-size:.875rem;
1342
+ transition:border-color .15s;outline:none;
1343
+ }
1344
+ .checkout-input:focus{border-color:#33ff00;}
1345
+ .checkout-input::placeholder{color:#116600;}
1346
+ .checkout-card-box{
1347
+ border:1px solid #0a3300;border-radius:6px;padding:14px;
1348
+ background:#020;
1349
+ }
1350
+ .checkout-card-row{
1351
+ display:flex;gap:12px;margin-top:10px;
1352
+ }
1353
+ .checkout-card-row .checkout-input{flex:1;}
1354
+ .checkout-sim-note{
1355
+ font-size:.6875rem;color:#1a8c00;margin-top:10px;text-align:center;
1356
+ font-style:italic;
1357
+ }
1358
+ .checkout-pay-btn{
1359
+ width:100%;padding:14px;border:none;border-radius:8px;
1360
+ background:#33ff00;color:#000;font:inherit;font-size:.9375rem;font-weight:700;
1361
+ cursor:pointer;transition:background .15s;
1362
+ font-family:'Geist Pixel',monospace;
1363
+ }
1364
+ .checkout-pay-btn:hover{background:#44ff22;}
1365
+ .checkout-cancel{
1366
+ text-align:center;margin-top:14px;
1367
+ }
1368
+ .checkout-cancel a{
1369
+ color:#1a8c00;text-decoration:none;font-size:.8125rem;
1370
+ transition:color .15s;
1371
+ }
1372
+ .checkout-cancel a:hover{color:#33ff00;}
1373
+ @media(max-width:768px){
1374
+ .checkout-layout{flex-direction:column;}
1375
+ .checkout-summary{padding:32px 20px;border-right:none;border-bottom:1px solid #0a3300;}
1376
+ .checkout-form-side{padding:32px 20px;}
1377
+ }
1378
+ `;
1379
+ var POWERED_BY = `<div class="powered-by">Powered by <a href="https://emulate.dev" target="_blank" rel="noopener">emulate</a></div>`;
1380
+ function emuBar(service) {
1381
+ const title = service ? `${escapeHtml(service)} Emulator` : "Emulator";
1382
+ return `<div class="emu-bar">
1383
+ <span class="emu-bar-title">${title}</span>
1384
+ <nav class="emu-bar-links">
1385
+ <a href="https://github.com/vercel-labs/emulate/issues" target="_blank" rel="noopener"><span class="full">Report Issue</span><span class="short">Report</span></a>
1386
+ <a href="https://github.com/vercel-labs/emulate" target="_blank" rel="noopener"><span class="full">Source Code</span><span class="short">Source</span></a>
1387
+ <a href="https://emulate.dev" target="_blank" rel="noopener"><span class="full">Learn More</span><span class="short">Learn</span></a>
1388
+ </nav>
1389
+ </div>`;
1390
+ }
1391
+ function head(title) {
1392
+ return `<!DOCTYPE html>
1393
+ <html lang="en">
1394
+ <head>
1395
+ <meta charset="utf-8"/>
1396
+ <meta name="viewport" content="width=device-width,initial-scale=1"/>
1397
+ <link rel="icon" href="/_emulate/favicon.ico"/>
1398
+ <title>${escapeHtml(title)} | emulate</title>
1399
+ <style>${CSS}</style>
1400
+ </head>`;
1401
+ }
1402
+ function renderInspectorPage(title, tabs, activeTab, body, service) {
1403
+ const tabLinks = tabs.map(
1404
+ (t) => `<a href="${escapeAttr(t.href)}" class="${t.id === activeTab ? "active" : ""}">${escapeHtml(t.label)}</a>`
1405
+ ).join("");
1406
+ return `${head(title)}
1407
+ <body>
1408
+ ${emuBar(service)}
1409
+ <div class="inspector-layout">
1410
+ <nav class="inspector-tabs">${tabLinks}</nav>
1411
+ ${body}
1412
+ </div>
1413
+ ${POWERED_BY}
1414
+ </body></html>`;
1415
+ }
1416
+ var SERVICE_LABEL = "AWS";
1417
+ var TABS = [
1418
+ { id: "s3", label: "S3", href: "/_inspector?tab=s3" },
1419
+ { id: "sqs", label: "SQS", href: "/_inspector?tab=sqs" },
1420
+ { id: "iam", label: "IAM", href: "/_inspector?tab=iam" }
1421
+ ];
1422
+ function inspectorRoutes(ctx) {
1423
+ const { app, store } = ctx;
1424
+ const aws = () => getAwsStore(store);
1425
+ app.get("/_inspector", (c) => {
1426
+ const tab = c.req.query("tab") ?? "s3";
1427
+ const s3Store = aws();
1428
+ const buckets = s3Store.s3Buckets.all();
1429
+ const queues = s3Store.sqsQueues.all();
1430
+ const users = s3Store.iamUsers.all();
1431
+ const roles = s3Store.iamRoles.all();
1432
+ let contentHtml = "";
1433
+ if (tab === "s3") {
1434
+ const rows = buckets.map((b) => {
1435
+ const objects = s3Store.s3Objects.findBy("bucket_name", b.bucket_name);
1436
+ return `<tr>
1437
+ <td>${escapeXml(b.bucket_name)}</td>
1438
+ <td>${objects.length}</td>
1439
+ <td>${escapeXml(b.region)}</td>
1440
+ <td>${escapeXml(b.creation_date)}</td>
1441
+ </tr>`;
1442
+ }).join("\n");
1443
+ contentHtml = `
1444
+ <div class="inspector-section">
1445
+ <h2>S3 Buckets (${buckets.length})</h2>
1446
+ <table class="inspector-table">
1447
+ <thead><tr><th>Bucket</th><th>Objects</th><th>Region</th><th>Created</th></tr></thead>
1448
+ <tbody>${rows || `<tr><td colspan="4"><div class="inspector-empty">No buckets</div></td></tr>`}</tbody>
1449
+ </table>
1450
+ </div>`;
1451
+ for (const bucket of buckets) {
1452
+ const objects = s3Store.s3Objects.findBy("bucket_name", bucket.bucket_name);
1453
+ if (objects.length > 0) {
1454
+ const objRows = objects.map(
1455
+ (o) => `<tr>
1456
+ <td>${escapeXml(o.key)}</td>
1457
+ <td>${o.content_length}</td>
1458
+ <td>${escapeXml(o.content_type)}</td>
1459
+ <td>${escapeXml(o.last_modified)}</td>
1460
+ </tr>`
1461
+ ).join("\n");
1462
+ contentHtml += `
1463
+ <div class="inspector-section">
1464
+ <h3>${escapeXml(bucket.bucket_name)} objects</h3>
1465
+ <table class="inspector-table">
1466
+ <thead><tr><th>Key</th><th>Size</th><th>Type</th><th>Last Modified</th></tr></thead>
1467
+ <tbody>${objRows}</tbody>
1468
+ </table>
1469
+ </div>`;
1470
+ }
1471
+ }
1472
+ } else if (tab === "sqs") {
1473
+ const rows = queues.map((q) => {
1474
+ const messages = s3Store.sqsMessages.findBy("queue_name", q.queue_name);
1475
+ return `<tr>
1476
+ <td>${escapeXml(q.queue_name)}</td>
1477
+ <td>${messages.length}</td>
1478
+ <td>${q.fifo ? "Yes" : "No"}</td>
1479
+ <td>${q.visibility_timeout}s</td>
1480
+ </tr>`;
1481
+ }).join("\n");
1482
+ contentHtml = `
1483
+ <div class="inspector-section">
1484
+ <h2>SQS Queues (${queues.length})</h2>
1485
+ <table class="inspector-table">
1486
+ <thead><tr><th>Queue</th><th>Messages</th><th>FIFO</th><th>Visibility Timeout</th></tr></thead>
1487
+ <tbody>${rows || `<tr><td colspan="4"><div class="inspector-empty">No queues</div></td></tr>`}</tbody>
1488
+ </table>
1489
+ </div>`;
1490
+ } else if (tab === "iam") {
1491
+ const userRows = users.map(
1492
+ (u) => `<tr>
1493
+ <td>${escapeXml(u.user_name)}</td>
1494
+ <td>${escapeXml(u.user_id)}</td>
1495
+ <td>${u.access_keys.length}</td>
1496
+ <td>${escapeXml(u.arn)}</td>
1497
+ </tr>`
1498
+ ).join("\n");
1499
+ const roleRows = roles.map(
1500
+ (r) => `<tr>
1501
+ <td>${escapeXml(r.role_name)}</td>
1502
+ <td>${escapeXml(r.role_id)}</td>
1503
+ <td>${escapeXml(r.description)}</td>
1504
+ <td>${escapeXml(r.arn)}</td>
1505
+ </tr>`
1506
+ ).join("\n");
1507
+ contentHtml = `
1508
+ <div class="inspector-section">
1509
+ <h2>IAM Users (${users.length})</h2>
1510
+ <table class="inspector-table">
1511
+ <thead><tr><th>User</th><th>User ID</th><th>Access Keys</th><th>ARN</th></tr></thead>
1512
+ <tbody>${userRows || `<tr><td colspan="4"><div class="inspector-empty">No users</div></td></tr>`}</tbody>
1513
+ </table>
1514
+ </div>
1515
+ <div class="inspector-section">
1516
+ <h2>IAM Roles (${roles.length})</h2>
1517
+ <table class="inspector-table">
1518
+ <thead><tr><th>Role</th><th>Role ID</th><th>Description</th><th>ARN</th></tr></thead>
1519
+ <tbody>${roleRows || `<tr><td colspan="4"><div class="inspector-empty">No roles</div></td></tr>`}</tbody>
1520
+ </table>
1521
+ </div>`;
1522
+ }
1523
+ return c.html(renderInspectorPage("Inspector", TABS, tab, contentHtml, SERVICE_LABEL));
1524
+ });
1525
+ }
1526
+ var manifest = {
1527
+ id: "aws",
1528
+ name: "AWS",
1529
+ description: "Stateful AWS emulator for S3, SQS, IAM, and STS using AWS SDK-compatible request flows.",
1530
+ docsUrl: "https://docs.emulators.dev/aws",
1531
+ surfaces: [
1532
+ { id: "s3", kind: "provider-specific", title: "S3-compatible API", status: "partial", basePath: "/" },
1533
+ { id: "sqs", kind: "provider-specific", title: "SQS Query API", status: "partial", basePath: "/" },
1534
+ { id: "iam", kind: "provider-specific", title: "IAM Query API", status: "partial", basePath: "/iam" },
1535
+ { id: "sts", kind: "provider-specific", title: "STS Query API", status: "partial", basePath: "/sts" },
1536
+ { id: "inspector", kind: "ui", title: "Inspector UI", status: "supported", basePath: "/_inspector" }
1537
+ ],
1538
+ auth: [
1539
+ {
1540
+ id: "aws-credentials",
1541
+ title: "AWS SDK credentials",
1542
+ type: "provider-specific",
1543
+ status: "partial",
1544
+ notes: "The emulator accepts AWS SDK-style requests and seeded IAM access keys, but does not fully validate SigV4."
1545
+ }
1546
+ ],
1547
+ specs: [
1548
+ {
1549
+ kind: "manual",
1550
+ title: "AWS S3, SQS, IAM, and STS subset",
1551
+ coverage: "hand-authored",
1552
+ notes: "AWS APIs use XML and Query protocols rather than OpenAPI. Operations are keyed by AWS action name.",
1553
+ operations: [
1554
+ // S3 (REST verbs)
1555
+ { operationId: "s3:ListBuckets", method: "GET", path: "/", status: "hand-authored" },
1556
+ { operationId: "s3:CreateBucket", method: "PUT", path: "/:bucket", status: "hand-authored" },
1557
+ { operationId: "s3:DeleteBucket", method: "DELETE", path: "/:bucket", status: "hand-authored" },
1558
+ { operationId: "s3:HeadBucket", method: "HEAD", path: "/:bucket", status: "hand-authored" },
1559
+ { operationId: "s3:ListObjectsV2", method: "GET", path: "/:bucket", status: "hand-authored" },
1560
+ { operationId: "s3:CreatePresignedPost", method: "POST", path: "/:bucket", status: "partial" },
1561
+ { operationId: "s3:PutObject", method: "PUT", path: "/:bucket/:key", status: "hand-authored" },
1562
+ { operationId: "s3:GetObject", method: "GET", path: "/:bucket/:key", status: "hand-authored" },
1563
+ { operationId: "s3:HeadObject", method: "HEAD", path: "/:bucket/:key", status: "hand-authored" },
1564
+ { operationId: "s3:DeleteObject", method: "DELETE", path: "/:bucket/:key", status: "hand-authored" },
1565
+ // SQS (Query API)
1566
+ { operationId: "sqs:CreateQueue", method: "POST", path: "/", status: "hand-authored" },
1567
+ { operationId: "sqs:DeleteQueue", method: "POST", path: "/", status: "hand-authored" },
1568
+ { operationId: "sqs:ListQueues", method: "POST", path: "/", status: "hand-authored" },
1569
+ { operationId: "sqs:GetQueueUrl", method: "POST", path: "/", status: "hand-authored" },
1570
+ { operationId: "sqs:GetQueueAttributes", method: "POST", path: "/", status: "hand-authored" },
1571
+ { operationId: "sqs:SendMessage", method: "POST", path: "/", status: "hand-authored" },
1572
+ { operationId: "sqs:ReceiveMessage", method: "POST", path: "/", status: "hand-authored" },
1573
+ { operationId: "sqs:DeleteMessage", method: "POST", path: "/", status: "hand-authored" },
1574
+ { operationId: "sqs:PurgeQueue", method: "POST", path: "/", status: "hand-authored" },
1575
+ // IAM (Query API)
1576
+ { operationId: "iam:CreateUser", method: "POST", path: "/iam", status: "hand-authored" },
1577
+ { operationId: "iam:GetUser", method: "POST", path: "/iam", status: "hand-authored" },
1578
+ { operationId: "iam:DeleteUser", method: "POST", path: "/iam", status: "hand-authored" },
1579
+ { operationId: "iam:ListUsers", method: "POST", path: "/iam", status: "hand-authored" },
1580
+ { operationId: "iam:CreateAccessKey", method: "POST", path: "/iam", status: "hand-authored" },
1581
+ { operationId: "iam:ListAccessKeys", method: "POST", path: "/iam", status: "hand-authored" },
1582
+ { operationId: "iam:DeleteAccessKey", method: "POST", path: "/iam", status: "hand-authored" },
1583
+ { operationId: "iam:CreateRole", method: "POST", path: "/iam", status: "hand-authored" },
1584
+ { operationId: "iam:GetRole", method: "POST", path: "/iam", status: "hand-authored" },
1585
+ { operationId: "iam:DeleteRole", method: "POST", path: "/iam", status: "hand-authored" },
1586
+ { operationId: "iam:ListRoles", method: "POST", path: "/iam", status: "hand-authored" },
1587
+ // STS (Query API)
1588
+ { operationId: "sts:GetCallerIdentity", method: "POST", path: "/sts", status: "hand-authored" },
1589
+ { operationId: "sts:AssumeRole", method: "POST", path: "/sts", status: "partial" }
1590
+ ]
1591
+ }
1592
+ ],
1593
+ seedSchema: {
1594
+ description: "Seed S3 buckets, SQS queues, and IAM users and roles, plus the account region.",
1595
+ fields: [
1596
+ {
1597
+ key: "region",
1598
+ title: "Default region",
1599
+ description: "Region applied to seeded resources.",
1600
+ example: "us-east-1"
1601
+ },
1602
+ { key: "account_id", title: "Account id", description: "AWS account id used in ARNs." },
1603
+ {
1604
+ key: "s3",
1605
+ title: "S3 buckets",
1606
+ description: "Buckets to create under the instance.",
1607
+ example: { buckets: [{ name: "my-app-bucket" }] }
1608
+ },
1609
+ {
1610
+ key: "sqs",
1611
+ title: "SQS queues",
1612
+ description: "Queues to create. FIFO is inferred from a .fifo suffix unless set.",
1613
+ example: { queues: [{ name: "my-app-events" }] }
1614
+ },
1615
+ {
1616
+ key: "iam",
1617
+ title: "IAM users and roles",
1618
+ description: "Users (optionally with an access key) and roles to provision.",
1619
+ example: {
1620
+ users: [{ user_name: "developer", create_access_key: true }],
1621
+ roles: [{ role_name: "lambda-execution-role", description: "Role for Lambda function execution" }]
1622
+ }
1623
+ }
1624
+ ],
1625
+ example: {
1626
+ region: "us-east-1",
1627
+ s3: { buckets: [{ name: "my-app-bucket" }, { name: "my-app-uploads" }] },
1628
+ sqs: { queues: [{ name: "my-app-events" }, { name: "my-app-dlq" }] },
1629
+ iam: {
1630
+ users: [{ user_name: "developer", create_access_key: true }],
1631
+ roles: [{ role_name: "lambda-execution-role", description: "Role for Lambda function execution" }]
1632
+ }
1633
+ }
1634
+ },
1635
+ stateModel: {
1636
+ description: "Entities mutated by AWS provider calls.",
1637
+ collections: [
1638
+ { name: "aws.s3_buckets" },
1639
+ { name: "aws.s3_objects" },
1640
+ { name: "aws.sqs_queues" },
1641
+ { name: "aws.sqs_messages" },
1642
+ { name: "aws.iam_users" },
1643
+ { name: "aws.iam_roles" }
1644
+ ]
1645
+ },
1646
+ connections: [
1647
+ {
1648
+ id: "s3-client",
1649
+ title: "AWS SDK v3 S3Client (TypeScript)",
1650
+ kind: "sdk",
1651
+ language: "typescript",
1652
+ description: "Point the S3 client at the emulator. forcePathStyle keeps bucket names in the path.",
1653
+ template: 'import { S3Client } from "@aws-sdk/client-s3";\n\nconst s3 = new S3Client({\n endpoint: "{{baseUrl}}",\n region: "us-east-1",\n forcePathStyle: true,\n credentials: {\n accessKeyId: "{{clientId}}",\n secretAccessKey: "{{clientSecret}}",\n },\n});'
1654
+ },
1655
+ {
1656
+ id: "sqs-client",
1657
+ title: "AWS SDK v3 SQSClient (TypeScript)",
1658
+ kind: "sdk",
1659
+ language: "typescript",
1660
+ description: "Point the SQS client at the emulator.",
1661
+ template: 'import { SQSClient } from "@aws-sdk/client-sqs";\n\nconst sqs = new SQSClient({\n endpoint: "{{baseUrl}}",\n region: "us-east-1",\n credentials: {\n accessKeyId: "{{clientId}}",\n secretAccessKey: "{{clientSecret}}",\n },\n});'
1662
+ },
1663
+ {
1664
+ id: "aws-env",
1665
+ title: "AWS endpoint and credentials (env)",
1666
+ kind: "env",
1667
+ language: "bash",
1668
+ description: "The AWS SDK and CLI honor AWS_ENDPOINT_URL and the standard credential variables.",
1669
+ template: "AWS_ENDPOINT_URL={{baseUrl}}\nAWS_REGION=us-east-1\nAWS_ACCESS_KEY_ID={{clientId}}\nAWS_SECRET_ACCESS_KEY={{clientSecret}}"
1670
+ },
1671
+ {
1672
+ id: "curl",
1673
+ title: "curl",
1674
+ kind: "curl",
1675
+ language: "bash",
1676
+ description: "List S3 buckets directly against the emulator.",
1677
+ template: "curl -s {{baseUrl}}/"
1678
+ }
1679
+ ]
1680
+ };
1681
+ function seedDefaults(store, baseUrl) {
1682
+ const aws = getAwsStore(store);
1683
+ const accountId = getAccountId();
1684
+ const region = getDefaultRegion();
1685
+ aws.s3Buckets.insert({
1686
+ bucket_name: "emulate-default",
1687
+ region,
1688
+ creation_date: (/* @__PURE__ */ new Date()).toISOString(),
1689
+ acl: "private",
1690
+ versioning_enabled: false
1691
+ });
1692
+ const queueName = "emulate-default-queue";
1693
+ aws.sqsQueues.insert({
1694
+ queue_name: queueName,
1695
+ queue_url: `${baseUrl}/sqs/${accountId}/${queueName}`,
1696
+ arn: `arn:aws:sqs:${region}:${accountId}:${queueName}`,
1697
+ visibility_timeout: 30,
1698
+ delay_seconds: 0,
1699
+ max_message_size: 262144,
1700
+ message_retention_period: 345600,
1701
+ receive_message_wait_time: 0,
1702
+ fifo: false
1703
+ });
1704
+ const userId = generateAwsId("AIDA");
1705
+ aws.iamUsers.insert({
1706
+ user_name: "admin",
1707
+ user_id: userId,
1708
+ arn: `arn:aws:iam::${accountId}:user/admin`,
1709
+ path: "/",
1710
+ access_keys: [
1711
+ {
1712
+ access_key_id: "AKIAIOSFODNN7EXAMPLE",
1713
+ secret_access_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
1714
+ status: "Active"
1715
+ }
1716
+ ]
1717
+ });
1718
+ }
1719
+ function seedFromConfig(store, baseUrl, config) {
1720
+ const aws = getAwsStore(store);
1721
+ const accountId = getAccountId();
1722
+ const region = config.region ?? getDefaultRegion();
1723
+ if (config.s3?.buckets) {
1724
+ for (const b of config.s3.buckets) {
1725
+ const existing = aws.s3Buckets.findOneBy("bucket_name", b.name);
1726
+ if (existing) continue;
1727
+ aws.s3Buckets.insert({
1728
+ bucket_name: b.name,
1729
+ region: b.region ?? region,
1730
+ creation_date: (/* @__PURE__ */ new Date()).toISOString(),
1731
+ acl: "private",
1732
+ versioning_enabled: false
1733
+ });
1734
+ }
1735
+ }
1736
+ if (config.sqs?.queues) {
1737
+ for (const q of config.sqs.queues) {
1738
+ const existing = aws.sqsQueues.findOneBy("queue_name", q.name);
1739
+ if (existing) continue;
1740
+ const fifo = q.fifo ?? q.name.endsWith(".fifo");
1741
+ aws.sqsQueues.insert({
1742
+ queue_name: q.name,
1743
+ queue_url: `${baseUrl}/sqs/${accountId}/${q.name}`,
1744
+ arn: `arn:aws:sqs:${region}:${accountId}:${q.name}`,
1745
+ visibility_timeout: q.visibility_timeout ?? 30,
1746
+ delay_seconds: 0,
1747
+ max_message_size: 262144,
1748
+ message_retention_period: 345600,
1749
+ receive_message_wait_time: 0,
1750
+ fifo
1751
+ });
1752
+ }
1753
+ }
1754
+ if (config.iam?.users) {
1755
+ for (const u of config.iam.users) {
1756
+ const existing = aws.iamUsers.findOneBy("user_name", u.user_name);
1757
+ if (existing) continue;
1758
+ const userId = generateAwsId("AIDA");
1759
+ const path = u.path ?? "/";
1760
+ const accessKeys = u.create_access_key ? [
1761
+ {
1762
+ access_key_id: "AKIA" + generateAwsId("").slice(0, 16),
1763
+ secret_access_key: generateAwsId("") + generateAwsId(""),
1764
+ status: "Active"
1765
+ }
1766
+ ] : [];
1767
+ aws.iamUsers.insert({
1768
+ user_name: u.user_name,
1769
+ user_id: userId,
1770
+ arn: `arn:aws:iam::${accountId}:user${path}${u.user_name}`,
1771
+ path,
1772
+ access_keys: accessKeys
1773
+ });
1774
+ }
1775
+ }
1776
+ if (config.iam?.roles) {
1777
+ for (const r of config.iam.roles) {
1778
+ const existing = aws.iamRoles.findOneBy("role_name", r.role_name);
1779
+ if (existing) continue;
1780
+ const roleId = generateAwsId("AROA");
1781
+ const path = r.path ?? "/";
1782
+ aws.iamRoles.insert({
1783
+ role_name: r.role_name,
1784
+ role_id: roleId,
1785
+ arn: `arn:aws:iam::${accountId}:role${path}${r.role_name}`,
1786
+ path,
1787
+ assume_role_policy_document: r.assume_role_policy ?? "{}",
1788
+ description: r.description ?? ""
1789
+ });
1790
+ }
1791
+ }
1792
+ }
1793
+ var awsPlugin = {
1794
+ name: "aws",
1795
+ register(app, store, webhooks, baseUrl, tokenMap) {
1796
+ const ctx = { app, store, webhooks, baseUrl, tokenMap };
1797
+ inspectorRoutes(ctx);
1798
+ sqsRoutes(ctx);
1799
+ iamRoutes(ctx);
1800
+ s3Routes(ctx);
1801
+ },
1802
+ seed(store, baseUrl) {
1803
+ seedDefaults(store, baseUrl);
1804
+ }
1805
+ };
1806
+ var index_default = awsPlugin;
1807
+ export {
1808
+ awsPlugin,
1809
+ index_default as default,
1810
+ getAwsStore,
1811
+ manifest,
1812
+ seedFromConfig
1813
+ };
1814
+ //# sourceMappingURL=dist-7N4COJHK.js.map