fauxqs 1.7.0 → 1.9.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.
- package/README.md +1381 -1071
- package/dist/app.d.ts +8 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +21 -0
- package/dist/app.js.map +1 -1
- package/dist/s3/s3Store.d.ts +4 -0
- package/dist/s3/s3Store.d.ts.map +1 -1
- package/dist/s3/s3Store.js +22 -0
- package/dist/s3/s3Store.js.map +1 -1
- package/dist/sqs/sqsStore.d.ts +2 -0
- package/dist/sqs/sqsStore.d.ts.map +1 -1
- package/dist/sqs/sqsStore.js +7 -0
- package/dist/sqs/sqsStore.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,1071 +1,1381 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- [Running
|
|
14
|
-
- [Running in
|
|
15
|
-
|
|
16
|
-
- [
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- [
|
|
21
|
-
- [
|
|
22
|
-
- [
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
- [
|
|
26
|
-
- [
|
|
27
|
-
|
|
28
|
-
- [
|
|
29
|
-
- [
|
|
30
|
-
- [
|
|
31
|
-
- [
|
|
32
|
-
- [
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
- [
|
|
36
|
-
- [
|
|
37
|
-
- [
|
|
38
|
-
- [
|
|
39
|
-
- [
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
```bash
|
|
88
|
-
npx fauxqs
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
services
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
server
|
|
286
|
-
|
|
287
|
-
// Create resources
|
|
288
|
-
server.createQueue("
|
|
289
|
-
server.
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
server.
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
|
373
|
-
|
|
374
|
-
| `
|
|
375
|
-
| `
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
{
|
|
489
|
-
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
```
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
);
|
|
554
|
-
|
|
555
|
-
//
|
|
556
|
-
const msg = await server.spy.
|
|
557
|
-
```
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
//
|
|
583
|
-
await server.spy.
|
|
584
|
-
{ service: "sqs", queueName: "orders" },
|
|
585
|
-
{ status: "
|
|
586
|
-
);
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
)
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
```
|
|
618
|
-
|
|
619
|
-
#####
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
```
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
```
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
|
798
|
-
|
|
799
|
-
|
|
|
800
|
-
|
|
|
801
|
-
|
|
|
802
|
-
|
|
|
803
|
-
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
|
808
|
-
|
|
809
|
-
|
|
|
810
|
-
|
|
|
811
|
-
|
|
|
812
|
-
|
|
|
813
|
-
|
|
|
814
|
-
|
|
|
815
|
-
|
|
|
816
|
-
|
|
|
817
|
-
|
|
|
818
|
-
|
|
|
819
|
-
|
|
|
820
|
-
|
|
|
821
|
-
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
|
826
|
-
|
|
827
|
-
|
|
|
828
|
-
|
|
|
829
|
-
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
|
836
|
-
|
|
837
|
-
|
|
|
838
|
-
|
|
|
839
|
-
|
|
|
840
|
-
|
|
|
841
|
-
|
|
|
842
|
-
|
|
|
843
|
-
|
|
|
844
|
-
|
|
|
845
|
-
|
|
|
846
|
-
|
|
|
847
|
-
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
|
861
|
-
|
|
862
|
-
|
|
|
863
|
-
|
|
|
864
|
-
|
|
|
865
|
-
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
- **Message
|
|
890
|
-
- **
|
|
891
|
-
- **
|
|
892
|
-
- **
|
|
893
|
-
- **
|
|
894
|
-
- **
|
|
895
|
-
- **
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
- **
|
|
900
|
-
- **
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
- **
|
|
905
|
-
- **
|
|
906
|
-
- **
|
|
907
|
-
- **
|
|
908
|
-
- **
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
#
|
|
935
|
-
|
|
936
|
-
```
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
```
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
aws --endpoint-url http://localhost:4566
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
|
1058
|
-
|
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1
|
+
<img src="logo-readme.jpg" alt="fauxqs" width="360" />
|
|
2
|
+
|
|
3
|
+
# fauxqs
|
|
4
|
+
|
|
5
|
+
Local SNS/SQS/S3 emulator for development and testing. Point your `@aws-sdk/client-sqs`, `@aws-sdk/client-sns`, and `@aws-sdk/client-s3` clients at fauxqs instead of real AWS.
|
|
6
|
+
|
|
7
|
+
All state is in-memory. No persistence, no external storage dependencies.
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Installation](#installation)
|
|
12
|
+
- [Usage](#usage)
|
|
13
|
+
- [Running the server](#running-the-server)
|
|
14
|
+
- [Running in the background](#running-in-the-background)
|
|
15
|
+
- [Running with Docker](#running-with-docker)
|
|
16
|
+
- [Running in Docker Compose](#running-in-docker-compose)
|
|
17
|
+
- [Container-to-container S3 virtual-hosted-style](#container-to-container-s3-virtual-hosted-style)
|
|
18
|
+
- [Configuring AWS SDK clients](#configuring-aws-sdk-clients)
|
|
19
|
+
- [Programmatic usage](#programmatic-usage)
|
|
20
|
+
- [Programmatic state setup](#programmatic-state-setup)
|
|
21
|
+
- [Init config file](#init-config-file)
|
|
22
|
+
- [Init config schema reference](#init-config-schema-reference)
|
|
23
|
+
- [Message spy](#message-spy)
|
|
24
|
+
- [Queue inspection](#queue-inspection)
|
|
25
|
+
- [Configurable queue URL host](#configurable-queue-url-host)
|
|
26
|
+
- [Region](#region)
|
|
27
|
+
- [Supported API Actions](#supported-api-actions)
|
|
28
|
+
- [SQS](#sqs)
|
|
29
|
+
- [SNS](#sns)
|
|
30
|
+
- [S3](#s3)
|
|
31
|
+
- [STS](#sts)
|
|
32
|
+
- [SQS Features](#sqs-features)
|
|
33
|
+
- [SNS Features](#sns-features)
|
|
34
|
+
- [S3 Features](#s3-features)
|
|
35
|
+
- [S3 URL styles](#s3-url-styles)
|
|
36
|
+
- [Using with AWS CLI](#using-with-aws-cli)
|
|
37
|
+
- [Testing Strategies](#testing-strategies)
|
|
38
|
+
- [Library mode for tests](#library-mode-for-tests)
|
|
39
|
+
- [Docker mode for local development](#docker-mode-for-local-development)
|
|
40
|
+
- [Recommended combination](#recommended-combination)
|
|
41
|
+
- [Conventions](#conventions)
|
|
42
|
+
- [Limitations](#limitations)
|
|
43
|
+
- [Examples](#examples)
|
|
44
|
+
- [Migrating from LocalStack](#migrating-from-localstack)
|
|
45
|
+
- [SNS/SQS only](#snssqs-only)
|
|
46
|
+
- [SNS/SQS/S3](#snssqss3)
|
|
47
|
+
- [Going hybrid (recommended)](#going-hybrid-recommended)
|
|
48
|
+
- [Benchmarks](#benchmarks)
|
|
49
|
+
- [License](#license)
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
**Docker** (recommended for standalone usage) — [Docker Hub](https://hub.docker.com/r/kibertoad/fauxqs):
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
docker run -p 4566:4566 kibertoad/fauxqs
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**npm** (for embedded library usage or CLI):
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm install fauxqs
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Usage
|
|
66
|
+
|
|
67
|
+
### Running the server
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npx fauxqs
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The server starts on port `4566` and handles SQS, SNS, and S3 on a single endpoint.
|
|
74
|
+
|
|
75
|
+
#### Environment variables
|
|
76
|
+
|
|
77
|
+
| Variable | Description | Default |
|
|
78
|
+
|----------|-------------|---------|
|
|
79
|
+
| `FAUXQS_PORT` | Port to listen on | `4566` |
|
|
80
|
+
| `FAUXQS_HOST` | Host for queue URLs (`sqs.<region>.<host>` format) | `localhost` |
|
|
81
|
+
| `FAUXQS_DEFAULT_REGION` | Fallback region for ARNs and URLs | `us-east-1` |
|
|
82
|
+
| `FAUXQS_LOGGER` | Enable request logging (`true`/`false`) | `true` |
|
|
83
|
+
| `FAUXQS_INIT` | Path to a JSON init config file (see [Init config file](#init-config-file)) | (none) |
|
|
84
|
+
| `FAUXQS_DNS_NAME` | Domain that dnsmasq resolves (including all subdomains) to the container IP. Only needed when the container hostname doesn't match the docker-compose service name — e.g., when using `container_name` or running with plain `docker run`. In docker-compose the hostname is set to the service name automatically, so this is rarely needed. (Docker only) | container hostname |
|
|
85
|
+
| `FAUXQS_DNS_UPSTREAM` | Where dnsmasq forwards non-fauxqs DNS queries (e.g., `registry.npmjs.org`). Change this if you're in a corporate network with an internal DNS server, or if you prefer a different public resolver like `1.1.1.1`. (Docker only) | `8.8.8.8` |
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
FAUXQS_PORT=3000 FAUXQS_INIT=init.json npx fauxqs
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
A health check is available at `GET /health`.
|
|
92
|
+
|
|
93
|
+
### Running in the background
|
|
94
|
+
|
|
95
|
+
To keep fauxqs running while you work on your app or run tests repeatedly, start it as a background process:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npx fauxqs &
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Or in a separate terminal:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
npx fauxqs
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
All state accumulates in memory across requests, so queues, topics, and objects persist until the server is stopped.
|
|
108
|
+
|
|
109
|
+
To stop the server:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# If backgrounded in the same shell
|
|
113
|
+
kill %1
|
|
114
|
+
|
|
115
|
+
# Cross-platform, by port
|
|
116
|
+
npx cross-port-killer 4566
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Running with Docker
|
|
120
|
+
|
|
121
|
+
The official Docker image is available on Docker Hub:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
docker run -p 4566:4566 kibertoad/fauxqs
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
With an init config file:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
docker run -p 4566:4566 \
|
|
131
|
+
-v ./init.json:/app/init.json \
|
|
132
|
+
-e FAUXQS_INIT=/app/init.json \
|
|
133
|
+
kibertoad/fauxqs
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Running in Docker Compose
|
|
137
|
+
|
|
138
|
+
Use the `kibertoad/fauxqs` image and mount a JSON init config to pre-create resources on startup:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
// scripts/fauxqs/init.json
|
|
142
|
+
{
|
|
143
|
+
"queues": [
|
|
144
|
+
{
|
|
145
|
+
"name": "my-queue.fifo",
|
|
146
|
+
"attributes": { "FifoQueue": "true", "ContentBasedDeduplication": "true" }
|
|
147
|
+
},
|
|
148
|
+
{ "name": "my-dlq" }
|
|
149
|
+
],
|
|
150
|
+
"topics": [{ "name": "my-events" }],
|
|
151
|
+
"subscriptions": [{ "topic": "my-events", "queue": "my-dlq" }],
|
|
152
|
+
"buckets": ["my-uploads"]
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
```yaml
|
|
157
|
+
# docker-compose.yml
|
|
158
|
+
services:
|
|
159
|
+
fauxqs:
|
|
160
|
+
image: kibertoad/fauxqs:latest
|
|
161
|
+
ports:
|
|
162
|
+
- "4566:4566"
|
|
163
|
+
environment:
|
|
164
|
+
- FAUXQS_INIT=/app/init.json
|
|
165
|
+
volumes:
|
|
166
|
+
- ./scripts/fauxqs/init.json:/app/init.json
|
|
167
|
+
|
|
168
|
+
app:
|
|
169
|
+
# ...
|
|
170
|
+
depends_on:
|
|
171
|
+
fauxqs:
|
|
172
|
+
condition: service_healthy
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The image has a built-in `HEALTHCHECK`, so `service_healthy` works without extra configuration in your compose file. Other containers reference fauxqs using the Docker service name (`http://fauxqs:4566`). The init config file creates all queues, topics, subscriptions, and buckets before the healthcheck passes, so dependent services start only after resources are ready.
|
|
176
|
+
|
|
177
|
+
#### Container-to-container S3 virtual-hosted-style
|
|
178
|
+
|
|
179
|
+
The Docker image includes a built-in DNS server ([dnsmasq](https://thekelleys.org.uk/dnsmasq/doc.html)) that resolves the container hostname and all its subdomains (e.g., `fauxqs`, `s3.fauxqs`, `my-bucket.s3.fauxqs`) to the container's own IP. This enables virtual-hosted-style S3 from other containers without `forcePathStyle`.
|
|
180
|
+
|
|
181
|
+
To use it, assign fauxqs a static IP and point other containers' DNS to it:
|
|
182
|
+
|
|
183
|
+
```yaml
|
|
184
|
+
# docker-compose.yml
|
|
185
|
+
services:
|
|
186
|
+
fauxqs:
|
|
187
|
+
image: kibertoad/fauxqs:latest
|
|
188
|
+
networks:
|
|
189
|
+
default:
|
|
190
|
+
ipv4_address: 10.0.0.2
|
|
191
|
+
ports:
|
|
192
|
+
- "4566:4566"
|
|
193
|
+
environment:
|
|
194
|
+
- FAUXQS_INIT=/app/init.json
|
|
195
|
+
- FAUXQS_HOST=fauxqs
|
|
196
|
+
volumes:
|
|
197
|
+
- ./scripts/fauxqs/init.json:/app/init.json
|
|
198
|
+
|
|
199
|
+
app:
|
|
200
|
+
dns: 10.0.0.2
|
|
201
|
+
depends_on:
|
|
202
|
+
fauxqs:
|
|
203
|
+
condition: service_healthy
|
|
204
|
+
environment:
|
|
205
|
+
- AWS_ENDPOINT=http://s3.fauxqs:4566
|
|
206
|
+
|
|
207
|
+
networks:
|
|
208
|
+
default:
|
|
209
|
+
ipam:
|
|
210
|
+
config:
|
|
211
|
+
- subnet: 10.0.0.0/24
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
From the `app` container, `my-bucket.s3.fauxqs` resolves to `10.0.0.2` (the fauxqs container), so virtual-hosted-style S3 works:
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
const s3 = new S3Client({
|
|
218
|
+
endpoint: "http://s3.fauxqs:4566",
|
|
219
|
+
region: "us-east-1",
|
|
220
|
+
credentials: { accessKeyId: "test", secretAccessKey: "test" },
|
|
221
|
+
// No forcePathStyle needed!
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
The DNS server is configured automatically using the container hostname (which docker-compose sets to the service name), so in most setups no extra configuration is needed. See the [environment variables table](#environment-variables) for `FAUXQS_DNS_NAME` and `FAUXQS_DNS_UPSTREAM` if you need to override the defaults.
|
|
226
|
+
|
|
227
|
+
### Configuring AWS SDK clients
|
|
228
|
+
|
|
229
|
+
Point your SDK clients at the local server:
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
import { SQSClient } from "@aws-sdk/client-sqs";
|
|
233
|
+
import { SNSClient } from "@aws-sdk/client-sns";
|
|
234
|
+
import { S3Client } from "@aws-sdk/client-s3";
|
|
235
|
+
|
|
236
|
+
const sqsClient = new SQSClient({
|
|
237
|
+
endpoint: "http://localhost:4566",
|
|
238
|
+
region: "us-east-1",
|
|
239
|
+
credentials: { accessKeyId: "test", secretAccessKey: "test" },
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const snsClient = new SNSClient({
|
|
243
|
+
endpoint: "http://localhost:4566",
|
|
244
|
+
region: "us-east-1",
|
|
245
|
+
credentials: { accessKeyId: "test", secretAccessKey: "test" },
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Using fauxqs.dev wildcard DNS — no helpers or forcePathStyle needed
|
|
249
|
+
const s3Client = new S3Client({
|
|
250
|
+
endpoint: "http://s3.localhost.fauxqs.dev:4566",
|
|
251
|
+
region: "us-east-1",
|
|
252
|
+
credentials: { accessKeyId: "test", secretAccessKey: "test" },
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Any credentials are accepted and never validated.
|
|
257
|
+
|
|
258
|
+
> **Note:** The `fauxqs.dev` wildcard DNS (`*.localhost.fauxqs.dev` → `127.0.0.1`) replicates the approach [pioneered by LocalStack](https://hashnode.localstack.cloud/efficient-localstack-s3-endpoint-configuration) with `localhost.localstack.cloud`. A public DNS entry resolves all subdomains to localhost, so virtual-hosted-style S3 requests work without `/etc/hosts` changes, custom request handlers, or `forcePathStyle`. See [S3 URL styles](#s3-url-styles) for alternative approaches.
|
|
259
|
+
|
|
260
|
+
### Programmatic usage
|
|
261
|
+
|
|
262
|
+
You can also embed fauxqs directly in your test suite:
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import { startFauxqs } from "fauxqs";
|
|
266
|
+
|
|
267
|
+
const server = await startFauxqs({ port: 4566, logger: false });
|
|
268
|
+
|
|
269
|
+
console.log(server.address); // "http://127.0.0.1:4566"
|
|
270
|
+
console.log(server.port); // 4566
|
|
271
|
+
|
|
272
|
+
// point your SDK clients at server.address
|
|
273
|
+
|
|
274
|
+
// clean up when done
|
|
275
|
+
await server.stop();
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Pass `port: 0` to let the OS assign a random available port (useful in tests).
|
|
279
|
+
|
|
280
|
+
#### Programmatic state setup
|
|
281
|
+
|
|
282
|
+
The server object exposes methods for pre-creating resources without going through the SDK:
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
const server = await startFauxqs({ port: 0, logger: false });
|
|
286
|
+
|
|
287
|
+
// Create individual resources
|
|
288
|
+
server.createQueue("my-queue");
|
|
289
|
+
server.createQueue("my-dlq", {
|
|
290
|
+
attributes: { VisibilityTimeout: "60" },
|
|
291
|
+
tags: { env: "test" },
|
|
292
|
+
});
|
|
293
|
+
server.createTopic("my-topic");
|
|
294
|
+
server.subscribe({ topic: "my-topic", queue: "my-queue" });
|
|
295
|
+
server.createBucket("my-bucket");
|
|
296
|
+
|
|
297
|
+
// Create resources in a specific region
|
|
298
|
+
server.createQueue("eu-queue", { region: "eu-west-1" });
|
|
299
|
+
server.createTopic("eu-topic", { region: "eu-west-1" });
|
|
300
|
+
server.subscribe({ topic: "eu-topic", queue: "eu-queue", region: "eu-west-1" });
|
|
301
|
+
|
|
302
|
+
// Or create everything at once
|
|
303
|
+
server.setup({
|
|
304
|
+
queues: [
|
|
305
|
+
{ name: "orders" },
|
|
306
|
+
{ name: "notifications", attributes: { DelaySeconds: "5" } },
|
|
307
|
+
{ name: "eu-orders", region: "eu-west-1" },
|
|
308
|
+
],
|
|
309
|
+
topics: [{ name: "events" }],
|
|
310
|
+
subscriptions: [
|
|
311
|
+
{ topic: "events", queue: "orders" },
|
|
312
|
+
{ topic: "events", queue: "notifications" },
|
|
313
|
+
],
|
|
314
|
+
buckets: ["uploads", "exports"],
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// Delete individual resources
|
|
318
|
+
server.deleteQueue("my-queue"); // no-op if queue doesn't exist
|
|
319
|
+
server.deleteTopic("my-topic"); // also removes associated subscriptions
|
|
320
|
+
server.emptyBucket("my-bucket"); // removes all objects, keeps the bucket
|
|
321
|
+
|
|
322
|
+
// Clear all messages and S3 objects between tests (keeps queues, topics, subscriptions, buckets)
|
|
323
|
+
server.reset();
|
|
324
|
+
|
|
325
|
+
// Or nuke everything — removes queues, topics, subscriptions, and buckets too
|
|
326
|
+
server.purgeAll();
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
#### Init config file
|
|
330
|
+
|
|
331
|
+
Create a JSON file to pre-create resources on startup. The file is validated on load — malformed configs produce a clear error instead of silent failures.
|
|
332
|
+
|
|
333
|
+
```json
|
|
334
|
+
{
|
|
335
|
+
"queues": [
|
|
336
|
+
{ "name": "orders" },
|
|
337
|
+
{ "name": "orders-dlq" },
|
|
338
|
+
{ "name": "orders.fifo", "attributes": { "FifoQueue": "true", "ContentBasedDeduplication": "true" } }
|
|
339
|
+
],
|
|
340
|
+
"topics": [
|
|
341
|
+
{ "name": "events" }
|
|
342
|
+
],
|
|
343
|
+
"subscriptions": [
|
|
344
|
+
{ "topic": "events", "queue": "orders" }
|
|
345
|
+
],
|
|
346
|
+
"buckets": ["uploads", "exports"]
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Pass it via the `FAUXQS_INIT` environment variable or the `init` option:
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
FAUXQS_INIT=init.json npx fauxqs
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
const server = await startFauxqs({ init: "init.json" });
|
|
358
|
+
// or inline:
|
|
359
|
+
const server = await startFauxqs({
|
|
360
|
+
init: { queues: [{ name: "my-queue" }], buckets: ["my-bucket"] },
|
|
361
|
+
});
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
#### Init config schema reference
|
|
365
|
+
|
|
366
|
+
All top-level fields are optional. Resources are created in dependency order: queues, topics, subscriptions, buckets.
|
|
367
|
+
|
|
368
|
+
##### `queues`
|
|
369
|
+
|
|
370
|
+
Array of queue objects.
|
|
371
|
+
|
|
372
|
+
| Field | Type | Required | Description |
|
|
373
|
+
|-------|------|----------|-------------|
|
|
374
|
+
| `name` | `string` | Yes | Queue name. Use `.fifo` suffix for FIFO queues. |
|
|
375
|
+
| `region` | `string` | No | Override the default region for this queue. The queue's ARN and URL will use this region. |
|
|
376
|
+
| `attributes` | `Record<string, string>` | No | Queue attributes (see table below). |
|
|
377
|
+
| `tags` | `Record<string, string>` | No | Key-value tags for the queue. |
|
|
378
|
+
|
|
379
|
+
Supported queue attributes:
|
|
380
|
+
|
|
381
|
+
| Attribute | Default | Range / Values |
|
|
382
|
+
|-----------|---------|----------------|
|
|
383
|
+
| `VisibilityTimeout` | `"30"` | `0` – `43200` (seconds) |
|
|
384
|
+
| `DelaySeconds` | `"0"` | `0` – `900` (seconds) |
|
|
385
|
+
| `MaximumMessageSize` | `"1048576"` | `1024` – `1048576` (bytes) |
|
|
386
|
+
| `MessageRetentionPeriod` | `"345600"` | `60` – `1209600` (seconds) |
|
|
387
|
+
| `ReceiveMessageWaitTimeSeconds` | `"0"` | `0` – `20` (seconds) |
|
|
388
|
+
| `RedrivePolicy` | — | JSON string: `{"deadLetterTargetArn": "arn:...", "maxReceiveCount": "5"}` |
|
|
389
|
+
| `Policy` | — | Queue policy JSON string (stored, not enforced) |
|
|
390
|
+
| `KmsMasterKeyId` | — | KMS key ID (stored, no actual encryption) |
|
|
391
|
+
| `KmsDataKeyReusePeriodSeconds` | — | KMS data key reuse period (stored, no actual encryption) |
|
|
392
|
+
| `FifoQueue` | — | `"true"` for FIFO queues (queue name must end with `.fifo`) |
|
|
393
|
+
| `ContentBasedDeduplication` | — | `"true"` or `"false"` (FIFO queues only) |
|
|
394
|
+
|
|
395
|
+
Example:
|
|
396
|
+
|
|
397
|
+
```json
|
|
398
|
+
{
|
|
399
|
+
"queues": [
|
|
400
|
+
{
|
|
401
|
+
"name": "orders",
|
|
402
|
+
"attributes": { "VisibilityTimeout": "60", "DelaySeconds": "5" },
|
|
403
|
+
"tags": { "env": "staging", "team": "platform" }
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
"name": "orders-dlq"
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
"name": "orders.fifo",
|
|
410
|
+
"attributes": {
|
|
411
|
+
"FifoQueue": "true",
|
|
412
|
+
"ContentBasedDeduplication": "true"
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
"name": "retry-queue",
|
|
417
|
+
"attributes": {
|
|
418
|
+
"RedrivePolicy": "{\"deadLetterTargetArn\":\"arn:aws:sqs:us-east-1:000000000000:orders-dlq\",\"maxReceiveCount\":\"3\"}"
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
"name": "eu-orders",
|
|
423
|
+
"region": "eu-west-1"
|
|
424
|
+
}
|
|
425
|
+
]
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
##### `topics`
|
|
430
|
+
|
|
431
|
+
Array of topic objects.
|
|
432
|
+
|
|
433
|
+
| Field | Type | Required | Description |
|
|
434
|
+
|-------|------|----------|-------------|
|
|
435
|
+
| `name` | `string` | Yes | Topic name. Use `.fifo` suffix for FIFO topics. |
|
|
436
|
+
| `region` | `string` | No | Override the default region for this topic. The topic's ARN will use this region. |
|
|
437
|
+
| `attributes` | `Record<string, string>` | No | Topic attributes (e.g., `DisplayName`). |
|
|
438
|
+
| `tags` | `Record<string, string>` | No | Key-value tags for the topic. |
|
|
439
|
+
|
|
440
|
+
Example:
|
|
441
|
+
|
|
442
|
+
```json
|
|
443
|
+
{
|
|
444
|
+
"topics": [
|
|
445
|
+
{
|
|
446
|
+
"name": "events",
|
|
447
|
+
"attributes": { "DisplayName": "Application Events" },
|
|
448
|
+
"tags": { "env": "staging" }
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
"name": "events.fifo",
|
|
452
|
+
"attributes": { "FifoQueue": "true", "ContentBasedDeduplication": "true" }
|
|
453
|
+
}
|
|
454
|
+
]
|
|
455
|
+
}
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
##### `subscriptions`
|
|
459
|
+
|
|
460
|
+
Array of subscription objects. Referenced topics and queues must be defined in the same config (or already exist on the server).
|
|
461
|
+
|
|
462
|
+
| Field | Type | Required | Description |
|
|
463
|
+
|-------|------|----------|-------------|
|
|
464
|
+
| `topic` | `string` | Yes | Topic name (not ARN) to subscribe to. |
|
|
465
|
+
| `queue` | `string` | Yes | Queue name (not ARN) to deliver messages to. |
|
|
466
|
+
| `region` | `string` | No | Override the default region. The topic and queue ARNs will be resolved in this region. |
|
|
467
|
+
| `attributes` | `Record<string, string>` | No | Subscription attributes (see table below). |
|
|
468
|
+
|
|
469
|
+
Supported subscription attributes:
|
|
470
|
+
|
|
471
|
+
| Attribute | Values | Description |
|
|
472
|
+
|-----------|--------|-------------|
|
|
473
|
+
| `RawMessageDelivery` | `"true"` / `"false"` | Deliver the raw message body instead of the SNS envelope JSON. |
|
|
474
|
+
| `FilterPolicy` | JSON string | SNS filter policy for message filtering (e.g., `"{\"color\": [\"blue\"]}"`) |
|
|
475
|
+
| `FilterPolicyScope` | `"MessageAttributes"` / `"MessageBody"` | Whether the filter policy applies to message attributes or body. Defaults to `MessageAttributes`. |
|
|
476
|
+
| `RedrivePolicy` | JSON string | Subscription-level dead-letter queue config. |
|
|
477
|
+
| `DeliveryPolicy` | JSON string | Delivery retry policy (stored, not enforced). |
|
|
478
|
+
| `SubscriptionRoleArn` | ARN string | IAM role ARN for delivery (stored, not enforced). |
|
|
479
|
+
|
|
480
|
+
Example:
|
|
481
|
+
|
|
482
|
+
```json
|
|
483
|
+
{
|
|
484
|
+
"subscriptions": [
|
|
485
|
+
{
|
|
486
|
+
"topic": "events",
|
|
487
|
+
"queue": "orders",
|
|
488
|
+
"attributes": {
|
|
489
|
+
"RawMessageDelivery": "true",
|
|
490
|
+
"FilterPolicy": "{\"eventType\": [\"order.created\", \"order.updated\"]}"
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
"topic": "events",
|
|
495
|
+
"queue": "notifications"
|
|
496
|
+
}
|
|
497
|
+
]
|
|
498
|
+
}
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
##### `buckets`
|
|
502
|
+
|
|
503
|
+
Array of bucket name strings.
|
|
504
|
+
|
|
505
|
+
```json
|
|
506
|
+
{
|
|
507
|
+
"buckets": ["uploads", "exports", "temp"]
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
#### Message spy
|
|
512
|
+
|
|
513
|
+
`MessageSpyReader` lets you await specific events flowing through SQS, SNS, and S3 in your tests — without polling queues yourself. Inspired by `HandlerSpy` from `message-queue-toolkit`.
|
|
514
|
+
|
|
515
|
+
Enable it with the `messageSpies` option:
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
518
|
+
const server = await startFauxqs({ port: 0, logger: false, messageSpies: true });
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
The spy tracks events across all three services using a discriminated union on `service`:
|
|
522
|
+
|
|
523
|
+
**SQS events** (`service: 'sqs'`):
|
|
524
|
+
- **`published`** — message was enqueued (via SendMessage, SendMessageBatch, or SNS fan-out)
|
|
525
|
+
- **`consumed`** — message was deleted (via DeleteMessage / DeleteMessageBatch)
|
|
526
|
+
- **`dlq`** — message exceeded `maxReceiveCount` and was moved to a dead-letter queue
|
|
527
|
+
|
|
528
|
+
**SNS events** (`service: 'sns'`):
|
|
529
|
+
- **`published`** — message was published to a topic (before fan-out to SQS subscriptions)
|
|
530
|
+
|
|
531
|
+
**S3 events** (`service: 's3'`):
|
|
532
|
+
- **`uploaded`** — object was put (PutObject or CompleteMultipartUpload)
|
|
533
|
+
- **`downloaded`** — object was retrieved (GetObject)
|
|
534
|
+
- **`deleted`** — object was deleted (DeleteObject, only when key existed)
|
|
535
|
+
- **`copied`** — object was copied (CopyObject; also emits `uploaded` for the destination)
|
|
536
|
+
|
|
537
|
+
##### Awaiting messages
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
// Wait for a specific SQS message (resolves immediately if already in buffer)
|
|
541
|
+
const msg = await server.spy.waitForMessage(
|
|
542
|
+
(m) => m.service === "sqs" && m.body === "order.created" && m.queueName === "orders",
|
|
543
|
+
"published",
|
|
544
|
+
);
|
|
545
|
+
|
|
546
|
+
// Wait by SQS message ID
|
|
547
|
+
const msg = await server.spy.waitForMessageWithId(messageId, "consumed");
|
|
548
|
+
|
|
549
|
+
// Partial object match (deep-equal on specified fields)
|
|
550
|
+
const msg = await server.spy.waitForMessage({ service: "sqs", queueName: "orders", status: "published" });
|
|
551
|
+
|
|
552
|
+
// Wait for an SNS publish event
|
|
553
|
+
const msg = await server.spy.waitForMessage({ service: "sns", topicName: "my-topic", status: "published" });
|
|
554
|
+
|
|
555
|
+
// Wait for an S3 upload event
|
|
556
|
+
const msg = await server.spy.waitForMessage({ service: "s3", bucket: "my-bucket", key: "file.txt", status: "uploaded" });
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
`waitForMessage` checks the buffer first (retroactive resolution). If no match is found, it returns a Promise that resolves when a matching message arrives.
|
|
560
|
+
|
|
561
|
+
##### Timeout
|
|
562
|
+
|
|
563
|
+
All `waitForMessage` and `waitForMessageWithId` calls accept an optional `timeout` parameter (ms) as the third argument. If no matching message arrives in time, the promise rejects with a timeout error — preventing tests from hanging indefinitely:
|
|
564
|
+
|
|
565
|
+
```typescript
|
|
566
|
+
// Reject after 2 seconds if no match
|
|
567
|
+
const msg = await server.spy.waitForMessage(
|
|
568
|
+
{ service: "sqs", queueName: "orders" },
|
|
569
|
+
"published",
|
|
570
|
+
2000,
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
// Also works with waitForMessageWithId
|
|
574
|
+
const msg = await server.spy.waitForMessageWithId(messageId, "consumed", 5000);
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
##### Waiting for multiple messages
|
|
578
|
+
|
|
579
|
+
`waitForMessages` collects `count` matching messages before resolving. It checks the buffer first, then awaits future arrivals:
|
|
580
|
+
|
|
581
|
+
```typescript
|
|
582
|
+
// Wait for 3 messages on the orders queue
|
|
583
|
+
const msgs = await server.spy.waitForMessages(
|
|
584
|
+
{ service: "sqs", queueName: "orders" },
|
|
585
|
+
{ count: 3, status: "published", timeout: 5000 },
|
|
586
|
+
);
|
|
587
|
+
// msgs.length === 3
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
If the timeout expires before enough messages arrive, the promise rejects with a message showing how many were collected (e.g., `"collected 1/3"`).
|
|
591
|
+
|
|
592
|
+
##### Negative assertions
|
|
593
|
+
|
|
594
|
+
`expectNoMessage` asserts that no matching message appears within a time window. Useful for verifying that filter policies dropped a message or that a side effect did not occur:
|
|
595
|
+
|
|
596
|
+
```typescript
|
|
597
|
+
// Assert no message was delivered to the wrong queue (waits 200ms by default)
|
|
598
|
+
await server.spy.expectNoMessage({ service: "sqs", queueName: "wrong-queue" });
|
|
599
|
+
|
|
600
|
+
// Custom window and status filter
|
|
601
|
+
await server.spy.expectNoMessage(
|
|
602
|
+
{ service: "sqs", queueName: "orders" },
|
|
603
|
+
{ status: "dlq", within: 500 },
|
|
604
|
+
);
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
If a matching message is already in the buffer, `expectNoMessage` rejects immediately. If one arrives during the wait, it rejects with `"matching message arrived during wait"`.
|
|
608
|
+
|
|
609
|
+
##### Synchronous check
|
|
610
|
+
|
|
611
|
+
```typescript
|
|
612
|
+
const msg = server.spy.checkForMessage(
|
|
613
|
+
(m) => m.service === "sqs" && m.queueName === "my-queue",
|
|
614
|
+
"published",
|
|
615
|
+
);
|
|
616
|
+
// returns SpyMessage | undefined
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
##### Buffer management
|
|
620
|
+
|
|
621
|
+
```typescript
|
|
622
|
+
// Get all tracked messages (oldest to newest)
|
|
623
|
+
const all = server.spy.getAllMessages();
|
|
624
|
+
|
|
625
|
+
// Clear buffer and reject pending waiters
|
|
626
|
+
server.spy.clear();
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
The buffer defaults to 100 messages (FIFO eviction). Configure with:
|
|
630
|
+
|
|
631
|
+
```typescript
|
|
632
|
+
const server = await startFauxqs({
|
|
633
|
+
messageSpies: { bufferSize: 500 },
|
|
634
|
+
});
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
##### Types
|
|
638
|
+
|
|
639
|
+
`server.spy` returns a `MessageSpyReader` — a read-only interface that exposes query and await methods but not internal mutation (e.g. recording new events):
|
|
640
|
+
|
|
641
|
+
```typescript
|
|
642
|
+
interface MessageSpyReader {
|
|
643
|
+
waitForMessage(filter: MessageSpyFilter, status?: string, timeout?: number): Promise<SpyMessage>;
|
|
644
|
+
waitForMessageWithId(messageId: string, status?: string, timeout?: number): Promise<SpyMessage>;
|
|
645
|
+
waitForMessages(filter: MessageSpyFilter, options: WaitForMessagesOptions): Promise<SpyMessage[]>;
|
|
646
|
+
expectNoMessage(filter: MessageSpyFilter, options?: ExpectNoMessageOptions): Promise<void>;
|
|
647
|
+
checkForMessage(filter: MessageSpyFilter, status?: string): SpyMessage | undefined;
|
|
648
|
+
getAllMessages(): SpyMessage[];
|
|
649
|
+
clear(): void;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
interface WaitForMessagesOptions {
|
|
653
|
+
count: number;
|
|
654
|
+
status?: string;
|
|
655
|
+
timeout?: number;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
interface ExpectNoMessageOptions {
|
|
659
|
+
status?: string;
|
|
660
|
+
within?: number; // ms, defaults to 200
|
|
661
|
+
}
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
`SpyMessage` is a discriminated union:
|
|
665
|
+
|
|
666
|
+
```typescript
|
|
667
|
+
interface SqsSpyMessage {
|
|
668
|
+
service: "sqs";
|
|
669
|
+
queueName: string;
|
|
670
|
+
messageId: string;
|
|
671
|
+
body: string;
|
|
672
|
+
messageAttributes: Record<string, MessageAttributeValue>;
|
|
673
|
+
status: "published" | "consumed" | "dlq";
|
|
674
|
+
timestamp: number;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
interface SnsSpyMessage {
|
|
678
|
+
service: "sns";
|
|
679
|
+
topicArn: string;
|
|
680
|
+
topicName: string;
|
|
681
|
+
messageId: string;
|
|
682
|
+
body: string;
|
|
683
|
+
messageAttributes: Record<string, MessageAttributeValue>;
|
|
684
|
+
status: "published";
|
|
685
|
+
timestamp: number;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
interface S3SpyEvent {
|
|
689
|
+
service: "s3";
|
|
690
|
+
bucket: string;
|
|
691
|
+
key: string;
|
|
692
|
+
status: "uploaded" | "downloaded" | "deleted" | "copied";
|
|
693
|
+
timestamp: number;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
type SpyMessage = SqsSpyMessage | SnsSpyMessage | S3SpyEvent;
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
##### Spy disabled by default
|
|
700
|
+
|
|
701
|
+
Accessing `server.spy` when `messageSpies` is not set throws an error. There is no overhead on the message flow when spies are disabled.
|
|
702
|
+
|
|
703
|
+
#### Queue inspection
|
|
704
|
+
|
|
705
|
+
Non-destructive inspection of SQS queue state — see all messages (ready, in-flight, and delayed) without consuming them or affecting visibility timeouts.
|
|
706
|
+
|
|
707
|
+
##### Programmatic API
|
|
708
|
+
|
|
709
|
+
```typescript
|
|
710
|
+
const result = server.inspectQueue("my-queue");
|
|
711
|
+
// result is undefined if queue doesn't exist
|
|
712
|
+
if (result) {
|
|
713
|
+
console.log(result.name); // "my-queue"
|
|
714
|
+
console.log(result.url); // "http://sqs.us-east-1.localhost:4566/000000000000/my-queue"
|
|
715
|
+
console.log(result.arn); // "arn:aws:sqs:us-east-1:000000000000:my-queue"
|
|
716
|
+
console.log(result.attributes); // { VisibilityTimeout: "30", ... }
|
|
717
|
+
console.log(result.messages.ready); // messages available for receive
|
|
718
|
+
console.log(result.messages.delayed); // messages waiting for delay to expire
|
|
719
|
+
console.log(result.messages.inflight); // received but not yet deleted
|
|
720
|
+
// Each inflight entry includes: { message, receiptHandle, visibilityDeadline }
|
|
721
|
+
}
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
##### HTTP endpoints
|
|
725
|
+
|
|
726
|
+
```bash
|
|
727
|
+
# List all queues with summary counts
|
|
728
|
+
curl http://localhost:4566/_fauxqs/queues
|
|
729
|
+
# [{ "name": "my-queue", "approximateMessageCount": 5, "approximateInflightCount": 2, "approximateDelayedCount": 0, ... }]
|
|
730
|
+
|
|
731
|
+
# Inspect a specific queue (full state)
|
|
732
|
+
curl http://localhost:4566/_fauxqs/queues/my-queue
|
|
733
|
+
# { "name": "my-queue", "messages": { "ready": [...], "delayed": [...], "inflight": [...] }, ... }
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
Returns 404 for non-existent queues. Inspection never modifies queue state — messages remain exactly where they are.
|
|
737
|
+
|
|
738
|
+
### Configurable queue URL host
|
|
739
|
+
|
|
740
|
+
Queue URLs use the AWS-style `sqs.<region>.<host>` format. The `host` defaults to `localhost`, producing URLs like `http://sqs.us-east-1.localhost:4566/000000000000/myQueue`.
|
|
741
|
+
|
|
742
|
+
To override the host (e.g., for a custom domain):
|
|
743
|
+
|
|
744
|
+
```typescript
|
|
745
|
+
import { startFauxqs } from "fauxqs";
|
|
746
|
+
|
|
747
|
+
const server = await startFauxqs({ port: 4566, host: "myhost.local" });
|
|
748
|
+
// Queue URLs: http://sqs.us-east-1.myhost.local:4566/000000000000/myQueue
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
This also works with `buildApp`:
|
|
752
|
+
|
|
753
|
+
```typescript
|
|
754
|
+
import { buildApp } from "fauxqs";
|
|
755
|
+
|
|
756
|
+
const app = buildApp({ host: "myhost.local" });
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
The configured host ensures queue URLs are consistent across all creation paths (init config, programmatic API, and SDK requests), regardless of the request's `Host` header.
|
|
760
|
+
|
|
761
|
+
### Region
|
|
762
|
+
|
|
763
|
+
Region is part of an entity's identity — a queue named `my-queue` in `us-east-1` is a completely different entity from `my-queue` in `eu-west-1`, just like in real AWS.
|
|
764
|
+
|
|
765
|
+
The region used in ARNs and queue URLs is automatically detected from the SDK client's `Authorization` header (AWS SigV4 credential scope). If your SDK client is configured with `region: "eu-west-1"`, all entities created or looked up through that client will use `eu-west-1` in their ARNs and URLs.
|
|
766
|
+
|
|
767
|
+
```typescript
|
|
768
|
+
const sqsEU = new SQSClient({ region: "eu-west-1", endpoint: "http://localhost:4566", ... });
|
|
769
|
+
const sqsUS = new SQSClient({ region: "us-east-1", endpoint: "http://localhost:4566", ... });
|
|
770
|
+
|
|
771
|
+
// These are two independent queues with different ARNs
|
|
772
|
+
await sqsEU.send(new CreateQueueCommand({ QueueName: "orders" }));
|
|
773
|
+
await sqsUS.send(new CreateQueueCommand({ QueueName: "orders" }));
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
If the region cannot be resolved from request headers (e.g., requests without AWS SigV4 signing), the `defaultRegion` option is used as a fallback (defaults to `"us-east-1"`):
|
|
777
|
+
|
|
778
|
+
```typescript
|
|
779
|
+
const server = await startFauxqs({ defaultRegion: "eu-west-1" });
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
Resources created via init config or programmatic API use the `defaultRegion` unless overridden with an explicit `region` field:
|
|
783
|
+
|
|
784
|
+
```json
|
|
785
|
+
{
|
|
786
|
+
"queues": [
|
|
787
|
+
{ "name": "us-queue" },
|
|
788
|
+
{ "name": "eu-queue", "region": "eu-west-1" }
|
|
789
|
+
]
|
|
790
|
+
}
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
## Supported API Actions
|
|
794
|
+
|
|
795
|
+
### SQS
|
|
796
|
+
|
|
797
|
+
| Action | Supported |
|
|
798
|
+
|--------|-----------|
|
|
799
|
+
| CreateQueue | Yes |
|
|
800
|
+
| DeleteQueue | Yes |
|
|
801
|
+
| GetQueueUrl | Yes |
|
|
802
|
+
| ListQueues | Yes |
|
|
803
|
+
| GetQueueAttributes | Yes |
|
|
804
|
+
| SetQueueAttributes | Yes |
|
|
805
|
+
| PurgeQueue | Yes |
|
|
806
|
+
| SendMessage | Yes |
|
|
807
|
+
| SendMessageBatch | Yes |
|
|
808
|
+
| ReceiveMessage | Yes |
|
|
809
|
+
| DeleteMessage | Yes |
|
|
810
|
+
| DeleteMessageBatch | Yes |
|
|
811
|
+
| ChangeMessageVisibility | Yes |
|
|
812
|
+
| ChangeMessageVisibilityBatch | Yes |
|
|
813
|
+
| TagQueue | Yes |
|
|
814
|
+
| UntagQueue | Yes |
|
|
815
|
+
| ListQueueTags | Yes |
|
|
816
|
+
| AddPermission | No |
|
|
817
|
+
| RemovePermission | No |
|
|
818
|
+
| ListDeadLetterSourceQueues | No |
|
|
819
|
+
| StartMessageMoveTask | No |
|
|
820
|
+
| CancelMessageMoveTask | No |
|
|
821
|
+
| ListMessageMoveTasks | No |
|
|
822
|
+
|
|
823
|
+
### SNS
|
|
824
|
+
|
|
825
|
+
| Action | Supported |
|
|
826
|
+
|--------|-----------|
|
|
827
|
+
| CreateTopic | Yes |
|
|
828
|
+
| DeleteTopic | Yes |
|
|
829
|
+
| ListTopics | Yes |
|
|
830
|
+
| GetTopicAttributes | Yes |
|
|
831
|
+
| SetTopicAttributes | Yes |
|
|
832
|
+
| Subscribe | Yes |
|
|
833
|
+
| Unsubscribe | Yes |
|
|
834
|
+
| ConfirmSubscription | Yes |
|
|
835
|
+
| ListSubscriptions | Yes |
|
|
836
|
+
| ListSubscriptionsByTopic | Yes |
|
|
837
|
+
| GetSubscriptionAttributes | Yes |
|
|
838
|
+
| SetSubscriptionAttributes | Yes |
|
|
839
|
+
| Publish | Yes |
|
|
840
|
+
| PublishBatch | Yes |
|
|
841
|
+
| TagResource | Yes |
|
|
842
|
+
| UntagResource | Yes |
|
|
843
|
+
| ListTagsForResource | Yes |
|
|
844
|
+
| AddPermission | No |
|
|
845
|
+
| RemovePermission | No |
|
|
846
|
+
| GetDataProtectionPolicy | No |
|
|
847
|
+
| PutDataProtectionPolicy | No |
|
|
848
|
+
|
|
849
|
+
Platform application, SMS, and phone number actions are not supported.
|
|
850
|
+
|
|
851
|
+
### S3
|
|
852
|
+
|
|
853
|
+
| Action | Supported |
|
|
854
|
+
|--------|-----------|
|
|
855
|
+
| CreateBucket | Yes |
|
|
856
|
+
| HeadBucket | Yes |
|
|
857
|
+
| ListObjects | Yes |
|
|
858
|
+
| ListObjectsV2 | Yes |
|
|
859
|
+
| CopyObject | Yes |
|
|
860
|
+
| PutObject | Yes |
|
|
861
|
+
| GetObject | Yes |
|
|
862
|
+
| DeleteObject | Yes |
|
|
863
|
+
| HeadObject | Yes |
|
|
864
|
+
| DeleteObjects | Yes |
|
|
865
|
+
| DeleteBucket | Yes |
|
|
866
|
+
| ListBuckets | Yes |
|
|
867
|
+
| CreateMultipartUpload | Yes |
|
|
868
|
+
| UploadPart | Yes |
|
|
869
|
+
| CompleteMultipartUpload | Yes |
|
|
870
|
+
| AbortMultipartUpload | Yes |
|
|
871
|
+
| ListObjectVersions | No |
|
|
872
|
+
| GetBucketLocation | No |
|
|
873
|
+
|
|
874
|
+
Bucket configuration (CORS, lifecycle, encryption, replication, etc.), ACLs, versioning, tagging, and other management actions are not supported.
|
|
875
|
+
|
|
876
|
+
### STS
|
|
877
|
+
|
|
878
|
+
| Action | Supported |
|
|
879
|
+
|--------|-----------|
|
|
880
|
+
| GetCallerIdentity | Yes |
|
|
881
|
+
| AssumeRole | No |
|
|
882
|
+
| GetSessionToken | No |
|
|
883
|
+
| GetFederationToken | No |
|
|
884
|
+
|
|
885
|
+
Returns a mock identity with account `000000000000` and ARN `arn:aws:iam::000000000000:root`. This allows tools like Terraform and the AWS CLI that call `sts:GetCallerIdentity` on startup to work without errors. Other STS actions are not supported.
|
|
886
|
+
|
|
887
|
+
## SQS Features
|
|
888
|
+
|
|
889
|
+
- **Message attributes** with MD5 checksums matching the AWS algorithm
|
|
890
|
+
- **Visibility timeout** — messages become invisible after receive and reappear after timeout
|
|
891
|
+
- **Delay queues** — per-queue default delay and per-message delay overrides
|
|
892
|
+
- **Long polling** — `WaitTimeSeconds` on ReceiveMessage blocks until messages arrive or timeout
|
|
893
|
+
- **Dead letter queues** — messages exceeding `maxReceiveCount` are moved to the configured DLQ
|
|
894
|
+
- **Batch operations** — SendMessageBatch, DeleteMessageBatch, ChangeMessageVisibilityBatch with entry ID validation (`InvalidBatchEntryId`) and total batch size validation (`BatchRequestTooLong`)
|
|
895
|
+
- **Queue attribute range validation** — validates `VisibilityTimeout`, `DelaySeconds`, `ReceiveMessageWaitTimeSeconds`, `MaximumMessageSize`, and `MessageRetentionPeriod` on both CreateQueue and SetQueueAttributes
|
|
896
|
+
- **Message size validation** — rejects messages exceeding 1 MiB (1,048,576 bytes)
|
|
897
|
+
- **Unicode character validation** — rejects messages with characters outside the AWS-allowed set
|
|
898
|
+
- **KMS attributes** — `KmsMasterKeyId` and `KmsDataKeyReusePeriodSeconds` are accepted and stored (no actual encryption)
|
|
899
|
+
- **FIFO queues** — `.fifo` suffix enforcement, `MessageGroupId` ordering, per-group locking (one inflight message per group), `MessageDeduplicationId`, content-based deduplication, sequence numbers, and FIFO-aware DLQ support
|
|
900
|
+
- **Queue tags**
|
|
901
|
+
|
|
902
|
+
## SNS Features
|
|
903
|
+
|
|
904
|
+
- **SNS-to-SQS fan-out** — publish to a topic and messages are delivered to all confirmed SQS subscriptions
|
|
905
|
+
- **Filter policies** — both `MessageAttributes` and `MessageBody` scope, supporting exact match, prefix, suffix, anything-but (including anything-but with suffix), numeric ranges, exists, null conditions, and `$or` top-level grouping. MessageBody scope supports nested key matching
|
|
906
|
+
- **Raw message delivery** — configurable per subscription
|
|
907
|
+
- **Message size validation** — rejects messages exceeding 256 KB (262,144 bytes)
|
|
908
|
+
- **Topic idempotency with conflict detection** — `CreateTopic` returns the existing topic when called with the same name, attributes, and tags, but throws when attributes or tags differ
|
|
909
|
+
- **Subscription idempotency with conflict detection** — `Subscribe` returns the existing subscription when the same (topic, protocol, endpoint) combination is used with matching attributes, but throws when attributes differ
|
|
910
|
+
- **Subscription attribute validation** — `SetSubscriptionAttributes` validates attribute names and rejects unknown or read-only attributes
|
|
911
|
+
- **Topic and subscription tags**
|
|
912
|
+
- **FIFO topics** — `.fifo` suffix enforcement, `MessageGroupId` and `MessageDeduplicationId` passthrough to SQS subscriptions, content-based deduplication
|
|
913
|
+
- **Batch publish**
|
|
914
|
+
|
|
915
|
+
## S3 Features
|
|
916
|
+
|
|
917
|
+
- **Bucket management** — CreateBucket (idempotent), DeleteBucket (rejects non-empty), HeadBucket, ListBuckets, ListObjects (V1 and V2)
|
|
918
|
+
- **Object operations** — PutObject, GetObject, DeleteObject, HeadObject, CopyObject with ETag, Content-Type, and Last-Modified headers
|
|
919
|
+
- **Multipart uploads** — CreateMultipartUpload, UploadPart, CompleteMultipartUpload, AbortMultipartUpload with correct multipart ETag calculation (`MD5-of-part-digests-partCount`), metadata preservation, and part overwrite support
|
|
920
|
+
- **ListObjects V2** — prefix filtering, delimiter-based virtual directories, MaxKeys, continuation tokens, StartAfter
|
|
921
|
+
- **CopyObject** — same-bucket and cross-bucket copy via `x-amz-copy-source` header, with metadata preservation
|
|
922
|
+
- **User metadata** — `x-amz-meta-*` headers are stored and returned on GetObject and HeadObject
|
|
923
|
+
- **Bulk delete** — DeleteObjects for batch key deletion with proper XML entity handling
|
|
924
|
+
- **Keys with slashes** — full support for slash-delimited keys (e.g., `path/to/file.txt`)
|
|
925
|
+
- **Stream uploads** — handles AWS chunked transfer encoding (`Content-Encoding: aws-chunked`) for stream bodies
|
|
926
|
+
- **Path-style and virtual-hosted-style** — both S3 URL styles are supported (see below)
|
|
927
|
+
|
|
928
|
+
### S3 URL styles
|
|
929
|
+
|
|
930
|
+
The AWS SDK sends S3 requests using virtual-hosted-style URLs by default (e.g., `my-bucket.s3.localhost:4566`). This requires `*.localhost` to resolve to `127.0.0.1`. fauxqs supports several approaches.
|
|
931
|
+
|
|
932
|
+
#### Option 1: `fauxqs.dev` wildcard DNS (recommended for Docker image)
|
|
933
|
+
|
|
934
|
+
Works out of the box when running the [official Docker image](#running-with-docker) — nothing to configure. The `fauxqs.dev` domain provides wildcard DNS — `*.localhost.fauxqs.dev` resolves to `127.0.0.1` via a public DNS entry. Just use `s3.localhost.fauxqs.dev` as your endpoint. This replicates the approach [pioneered by LocalStack](https://docs.localstack.cloud/aws/services/s3/) with `localhost.localstack.cloud`: a public DNS record maps all subdomains to localhost, so virtual-hosted-style requests work without `/etc/hosts` changes, custom request handlers, or `forcePathStyle`. Works from any language, `fetch()`, or CLI tool.
|
|
935
|
+
|
|
936
|
+
```typescript
|
|
937
|
+
import { S3Client } from "@aws-sdk/client-s3";
|
|
938
|
+
|
|
939
|
+
const s3 = new S3Client({
|
|
940
|
+
endpoint: "http://s3.localhost.fauxqs.dev:4566",
|
|
941
|
+
region: "us-east-1",
|
|
942
|
+
credentials: { accessKeyId: "test", secretAccessKey: "test" },
|
|
943
|
+
});
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
You can also use raw HTTP requests:
|
|
947
|
+
|
|
948
|
+
```bash
|
|
949
|
+
# Upload
|
|
950
|
+
curl -X PUT --data-binary @file.txt http://my-bucket.s3.localhost.fauxqs.dev:4566/file.txt
|
|
951
|
+
|
|
952
|
+
# Download
|
|
953
|
+
curl http://my-bucket.s3.localhost.fauxqs.dev:4566/file.txt
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
This is the recommended approach for host-to-Docker setups. If you are using fauxqs as an [embedded library](#programmatic-usage) in Node.js tests, prefer Option 2 (`interceptLocalhostDns`) instead — it patches DNS globally so all clients work without modification, and requires no external DNS.
|
|
957
|
+
|
|
958
|
+
For **container-to-container** S3 virtual-hosted-style in docker-compose, use the [built-in DNS server](#container-to-container-s3-virtual-hosted-style) instead — it resolves `*.s3.fauxqs` to the fauxqs container IP so other containers can use virtual-hosted-style S3 without `forcePathStyle`.
|
|
959
|
+
|
|
960
|
+
#### Option 2: `interceptLocalhostDns()` (recommended for embedded library)
|
|
961
|
+
|
|
962
|
+
Patches Node.js `dns.lookup` so that any hostname ending in `.localhost` resolves to `127.0.0.1`. No client changes needed.
|
|
963
|
+
|
|
964
|
+
```typescript
|
|
965
|
+
import { interceptLocalhostDns } from "fauxqs";
|
|
966
|
+
|
|
967
|
+
const restore = interceptLocalhostDns();
|
|
968
|
+
|
|
969
|
+
const s3 = new S3Client({
|
|
970
|
+
endpoint: "http://s3.localhost:4566",
|
|
971
|
+
region: "us-east-1",
|
|
972
|
+
credentials: { accessKeyId: "test", secretAccessKey: "test" },
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
// When done (e.g., in afterAll):
|
|
976
|
+
restore();
|
|
977
|
+
```
|
|
978
|
+
|
|
979
|
+
The suffix is configurable: `interceptLocalhostDns("myhost.test")` matches `*.myhost.test`.
|
|
980
|
+
|
|
981
|
+
**Tradeoffs:** Affects all DNS lookups in the process. Best suited for test suites (`beforeAll` / `afterAll`).
|
|
982
|
+
|
|
983
|
+
#### Option 3: `createLocalhostHandler()` (per-client)
|
|
984
|
+
|
|
985
|
+
Creates an HTTP request handler that resolves all hostnames to `127.0.0.1`. Scoped to a single client instance — no side effects, no external DNS dependency.
|
|
986
|
+
|
|
987
|
+
```typescript
|
|
988
|
+
import { S3Client } from "@aws-sdk/client-s3";
|
|
989
|
+
import { createLocalhostHandler } from "fauxqs";
|
|
990
|
+
|
|
991
|
+
const s3 = new S3Client({
|
|
992
|
+
endpoint: "http://s3.localhost:4566",
|
|
993
|
+
region: "us-east-1",
|
|
994
|
+
credentials: { accessKeyId: "test", secretAccessKey: "test" },
|
|
995
|
+
requestHandler: createLocalhostHandler(),
|
|
996
|
+
});
|
|
997
|
+
```
|
|
998
|
+
|
|
999
|
+
#### Option 4: `forcePathStyle` (simplest fallback)
|
|
1000
|
+
|
|
1001
|
+
Forces the SDK to use path-style URLs (`http://localhost:4566/my-bucket/key`) instead of virtual-hosted-style. No DNS or handler changes needed, but affects how the SDK resolves S3 URLs at runtime.
|
|
1002
|
+
|
|
1003
|
+
```typescript
|
|
1004
|
+
const s3 = new S3Client({
|
|
1005
|
+
endpoint: "http://localhost:4566",
|
|
1006
|
+
forcePathStyle: true,
|
|
1007
|
+
// ...
|
|
1008
|
+
});
|
|
1009
|
+
```
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
### Using with AWS CLI
|
|
1013
|
+
|
|
1014
|
+
fauxqs is wire-compatible with the standard AWS CLI. Point it at the fauxqs endpoint:
|
|
1015
|
+
|
|
1016
|
+
#### SQS
|
|
1017
|
+
|
|
1018
|
+
```bash
|
|
1019
|
+
aws --endpoint-url http://localhost:4566 sqs create-queue --queue-name my-queue
|
|
1020
|
+
aws --endpoint-url http://localhost:4566 sqs create-queue \
|
|
1021
|
+
--queue-name my-queue.fifo \
|
|
1022
|
+
--attributes FifoQueue=true,ContentBasedDeduplication=true
|
|
1023
|
+
aws --endpoint-url http://localhost:4566 sqs send-message \
|
|
1024
|
+
--queue-url http://localhost:4566/000000000000/my-queue \
|
|
1025
|
+
--message-body "hello"
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
#### SNS
|
|
1029
|
+
|
|
1030
|
+
```bash
|
|
1031
|
+
aws --endpoint-url http://localhost:4566 sns create-topic --name my-topic
|
|
1032
|
+
aws --endpoint-url http://localhost:4566 sns subscribe \
|
|
1033
|
+
--topic-arn arn:aws:sns:us-east-1:000000000000:my-topic \
|
|
1034
|
+
--protocol sqs \
|
|
1035
|
+
--notification-endpoint arn:aws:sqs:us-east-1:000000000000:my-queue
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
#### S3
|
|
1039
|
+
|
|
1040
|
+
```bash
|
|
1041
|
+
aws --endpoint-url http://localhost:4566 s3 mb s3://my-bucket
|
|
1042
|
+
aws --endpoint-url http://localhost:4566 s3 cp file.txt s3://my-bucket/file.txt
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
If the AWS CLI uses virtual-hosted-style S3 URLs by default, configure path-style:
|
|
1046
|
+
|
|
1047
|
+
```bash
|
|
1048
|
+
aws configure set default.s3.addressing_style path
|
|
1049
|
+
```
|
|
1050
|
+
|
|
1051
|
+
## Testing Strategies
|
|
1052
|
+
|
|
1053
|
+
fauxqs supports two deployment modes that complement each other for a complete testing workflow:
|
|
1054
|
+
|
|
1055
|
+
| Mode | Best for | Startup | Assertions |
|
|
1056
|
+
|------|----------|---------|------------|
|
|
1057
|
+
| **Library** (embedded) | Unit tests, integration tests, CI | Milliseconds, in-process | Full programmatic API: `spy`, `inspectQueue`, `reset`, `purgeAll` |
|
|
1058
|
+
| **Docker** (standalone) | Local development, acceptance tests, dev environments | Seconds, real HTTP | Init config, HTTP inspection endpoints |
|
|
1059
|
+
|
|
1060
|
+
### Library mode for tests
|
|
1061
|
+
|
|
1062
|
+
Embed fauxqs directly in your test suite. Each test file gets its own server instance on a random port — no Docker dependency, no shared state, no port conflicts:
|
|
1063
|
+
|
|
1064
|
+
```typescript
|
|
1065
|
+
// test/setup.ts
|
|
1066
|
+
import { startFauxqs, type FauxqsServer } from "fauxqs";
|
|
1067
|
+
|
|
1068
|
+
export async function createTestServer(): Promise<FauxqsServer> {
|
|
1069
|
+
const server = await startFauxqs({ port: 0, logger: false, messageSpies: true });
|
|
1070
|
+
|
|
1071
|
+
// Pre-create resources via the programmatic API (no SDK roundtrips)
|
|
1072
|
+
server.createQueue("my-queue");
|
|
1073
|
+
server.createBucket("my-bucket");
|
|
1074
|
+
|
|
1075
|
+
return server;
|
|
1076
|
+
}
|
|
1077
|
+
```
|
|
1078
|
+
|
|
1079
|
+
```typescript
|
|
1080
|
+
// test/app.test.ts
|
|
1081
|
+
import { describe, it, expect, beforeAll, afterAll, beforeEach } from "vitest";
|
|
1082
|
+
|
|
1083
|
+
let server: FauxqsServer;
|
|
1084
|
+
|
|
1085
|
+
beforeAll(async () => { server = await createTestServer(); });
|
|
1086
|
+
afterAll(async () => { await server.stop(); });
|
|
1087
|
+
beforeEach(() => { server.spy.clear(); });
|
|
1088
|
+
|
|
1089
|
+
it("tracks uploads via the spy", async () => {
|
|
1090
|
+
// ... trigger your app logic that uploads to S3 ...
|
|
1091
|
+
|
|
1092
|
+
const event = await server.spy.waitForMessage(
|
|
1093
|
+
{ service: "s3", bucket: "my-bucket", key: "file.txt", status: "uploaded" },
|
|
1094
|
+
undefined,
|
|
1095
|
+
2000, // timeout — prevents tests from hanging
|
|
1096
|
+
);
|
|
1097
|
+
expect(event.status).toBe("uploaded");
|
|
1098
|
+
});
|
|
1099
|
+
```
|
|
1100
|
+
|
|
1101
|
+
Library mode gives you deterministic assertions via the [message spy](#message-spy), non-destructive state inspection via [`inspectQueue()`](#queue-inspection), and instant state reset with `reset()` / `purgeAll()` — none of which are available from outside the process.
|
|
1102
|
+
|
|
1103
|
+
### Docker mode for local development
|
|
1104
|
+
|
|
1105
|
+
Use `docker-compose.yml` with an init config to give your team a consistent local environment:
|
|
1106
|
+
|
|
1107
|
+
```yaml
|
|
1108
|
+
# docker-compose.yml
|
|
1109
|
+
services:
|
|
1110
|
+
fauxqs:
|
|
1111
|
+
image: kibertoad/fauxqs:latest
|
|
1112
|
+
ports: ["4566:4566"]
|
|
1113
|
+
environment:
|
|
1114
|
+
- FAUXQS_INIT=/app/init.json
|
|
1115
|
+
volumes:
|
|
1116
|
+
- ./fauxqs-init.json:/app/init.json
|
|
1117
|
+
|
|
1118
|
+
app:
|
|
1119
|
+
build: .
|
|
1120
|
+
depends_on:
|
|
1121
|
+
fauxqs:
|
|
1122
|
+
condition: service_healthy
|
|
1123
|
+
environment:
|
|
1124
|
+
- AWS_ENDPOINT=http://fauxqs:4566
|
|
1125
|
+
```
|
|
1126
|
+
|
|
1127
|
+
Docker mode validates your real deployment topology — networking, DNS, container-to-container communication — and is language-agnostic (any AWS SDK can connect).
|
|
1128
|
+
|
|
1129
|
+
### Recommended combination
|
|
1130
|
+
|
|
1131
|
+
Use both modes together. Library mode runs in CI on every commit (fast, no Docker required). Docker mode runs locally via `docker compose up` and optionally in a separate CI stage for acceptance testing.
|
|
1132
|
+
|
|
1133
|
+
See the [`examples/recommended/`](examples/recommended/) directory for a complete working example with a Fastify app, library-mode vitest tests, and Docker compose configuration.
|
|
1134
|
+
|
|
1135
|
+
## Conventions
|
|
1136
|
+
|
|
1137
|
+
- Account ID: `000000000000`
|
|
1138
|
+
- Region: auto-detected from SDK `Authorization` header; falls back to `defaultRegion` (defaults to `us-east-1`). Region is part of entity identity — same-name entities in different regions are independent.
|
|
1139
|
+
- Queue URL format: `http://sqs.{region}.{host}:{port}/000000000000/{queueName}` (host defaults to `localhost`)
|
|
1140
|
+
- Queue ARN format: `arn:aws:sqs:{region}:000000000000:{queueName}`
|
|
1141
|
+
- Topic ARN format: `arn:aws:sns:{region}:000000000000:{topicName}`
|
|
1142
|
+
|
|
1143
|
+
## Limitations
|
|
1144
|
+
|
|
1145
|
+
fauxqs is designed for development and testing. It does not support:
|
|
1146
|
+
|
|
1147
|
+
- Non-SQS SNS delivery protocols (HTTP/S, Lambda, email, SMS)
|
|
1148
|
+
- Persistence across restarts
|
|
1149
|
+
- Authentication or authorization
|
|
1150
|
+
- Cross-account operations
|
|
1151
|
+
|
|
1152
|
+
## Examples
|
|
1153
|
+
|
|
1154
|
+
The [`examples/`](examples/) directory contains runnable TypeScript examples covering fauxqs-specific features beyond standard AWS SDK usage:
|
|
1155
|
+
|
|
1156
|
+
| Example | Description |
|
|
1157
|
+
|---------|-------------|
|
|
1158
|
+
| [`alternatives/programmatic/programmatic-api.ts`](examples/alternatives/programmatic/programmatic-api.ts) | Server lifecycle, resource creation, SDK usage, `inspectQueue()`, `reset()`, `purgeAll()`, `setup()` |
|
|
1159
|
+
| [`alternatives/programmatic/message-spy.ts`](examples/alternatives/programmatic/message-spy.ts) | `MessageSpyReader` — all spy methods, partial/predicate filters, discriminated union narrowing, DLQ tracking |
|
|
1160
|
+
| [`alternatives/programmatic/init-config.ts`](examples/alternatives/programmatic/init-config.ts) | File-based and inline init config, DLQ chains, `setup()` idempotency, purge + re-apply pattern |
|
|
1161
|
+
| [`alternatives/programmatic/queue-inspection.ts`](examples/alternatives/programmatic/queue-inspection.ts) | Programmatic `inspectQueue()` and HTTP `/_fauxqs/queues` endpoints |
|
|
1162
|
+
| [`alternatives/docker/standalone/`](examples/alternatives/docker/standalone/standalone-container.ts) | Connecting to a fauxqs Docker container from the host |
|
|
1163
|
+
| [`alternatives/docker/container-to-container/`](examples/alternatives/docker/container-to-container/) | Container-to-container communication via docker-compose |
|
|
1164
|
+
| [`recommended/`](examples/recommended/) | Dual-mode testing: library mode (vitest + spy) for CI, Docker for local dev |
|
|
1165
|
+
|
|
1166
|
+
All examples are type-checked in CI to prevent staleness.
|
|
1167
|
+
|
|
1168
|
+
## Migrating from LocalStack
|
|
1169
|
+
|
|
1170
|
+
If you're currently using LocalStack for local SNS, SQS, and/or S3 emulation, fauxqs is a drop-in replacement for those services. Both listen on port 4566 by default and accept the same AWS SDK calls, so the migration is straightforward.
|
|
1171
|
+
|
|
1172
|
+
There are two approaches: a Docker swap (quickest) and a hybrid setup (recommended for the best integration test experience). Which one makes sense depends on whether you use S3.
|
|
1173
|
+
|
|
1174
|
+
### SNS/SQS only
|
|
1175
|
+
|
|
1176
|
+
If your LocalStack usage is limited to SNS and SQS, the migration is a one-line Docker image swap. No SDK client changes are needed — the endpoint URL, port, and credentials stay the same.
|
|
1177
|
+
|
|
1178
|
+
**Docker swap:**
|
|
1179
|
+
|
|
1180
|
+
```yaml
|
|
1181
|
+
# Before (LocalStack)
|
|
1182
|
+
services:
|
|
1183
|
+
localstack:
|
|
1184
|
+
image: localstack/localstack
|
|
1185
|
+
ports: ["4566:4566"]
|
|
1186
|
+
environment:
|
|
1187
|
+
- SERVICES=sqs,sns
|
|
1188
|
+
|
|
1189
|
+
# After (fauxqs)
|
|
1190
|
+
services:
|
|
1191
|
+
fauxqs:
|
|
1192
|
+
image: kibertoad/fauxqs:latest
|
|
1193
|
+
ports: ["4566:4566"]
|
|
1194
|
+
```
|
|
1195
|
+
|
|
1196
|
+
**Region:** Both LocalStack and fauxqs auto-detect the region from the SDK client's `Authorization` header, so in most setups no configuration is needed. If you have older LocalStack configs that used the now-removed `DEFAULT_REGION` env var (deprecated in v0.12.7, removed in v2.0), the equivalent in fauxqs is `FAUXQS_DEFAULT_REGION` — it serves as a fallback when the region can't be resolved from request headers. Both default to `us-east-1`.
|
|
1197
|
+
|
|
1198
|
+
The main difference is how resources are pre-created. LocalStack uses init hooks (`/etc/localstack/init/ready.d/` shell scripts with `awslocal` CLI calls), while fauxqs uses a declarative JSON config. `awslocal` defaults to `us-east-1` unless you pass `--region`, and fauxqs init config uses `defaultRegion` (`us-east-1`) unless you set an explicit `region` per resource — so both create resources in the same region by default:
|
|
1199
|
+
|
|
1200
|
+
```bash
|
|
1201
|
+
# LocalStack init script (ready.d/init.sh)
|
|
1202
|
+
# awslocal defaults to us-east-1; use --region to override
|
|
1203
|
+
awslocal sqs create-queue --queue-name orders
|
|
1204
|
+
awslocal sqs create-queue --queue-name orders-dlq
|
|
1205
|
+
awslocal sns create-topic --name events
|
|
1206
|
+
awslocal sns subscribe \
|
|
1207
|
+
--topic-arn arn:aws:sns:us-east-1:000000000000:events \
|
|
1208
|
+
--protocol sqs \
|
|
1209
|
+
--notification-endpoint arn:aws:sqs:us-east-1:000000000000:orders
|
|
1210
|
+
```
|
|
1211
|
+
|
|
1212
|
+
```json
|
|
1213
|
+
// fauxqs init.json — uses defaultRegion (us-east-1) unless "region" is set per resource
|
|
1214
|
+
{
|
|
1215
|
+
"queues": [{ "name": "orders" }, { "name": "orders-dlq" }],
|
|
1216
|
+
"topics": [{ "name": "events" }],
|
|
1217
|
+
"subscriptions": [{ "topic": "events", "queue": "orders" }]
|
|
1218
|
+
}
|
|
1219
|
+
```
|
|
1220
|
+
|
|
1221
|
+
```yaml
|
|
1222
|
+
# Before (LocalStack docker-compose.yml)
|
|
1223
|
+
services:
|
|
1224
|
+
localstack:
|
|
1225
|
+
image: localstack/localstack
|
|
1226
|
+
ports: ["4566:4566"]
|
|
1227
|
+
environment:
|
|
1228
|
+
- SERVICES=sqs,sns
|
|
1229
|
+
volumes:
|
|
1230
|
+
- ./ready.d:/etc/localstack/init/ready.d
|
|
1231
|
+
|
|
1232
|
+
# After (fauxqs docker-compose.yml)
|
|
1233
|
+
services:
|
|
1234
|
+
fauxqs:
|
|
1235
|
+
image: kibertoad/fauxqs:latest
|
|
1236
|
+
ports: ["4566:4566"]
|
|
1237
|
+
environment:
|
|
1238
|
+
- FAUXQS_INIT=/app/init.json
|
|
1239
|
+
volumes:
|
|
1240
|
+
- ./init.json:/app/init.json
|
|
1241
|
+
```
|
|
1242
|
+
|
|
1243
|
+
Update your docker-compose service name references (e.g., `http://localstack:4566` to `http://fauxqs:4566`) and you're done.
|
|
1244
|
+
|
|
1245
|
+
### SNS/SQS/S3
|
|
1246
|
+
|
|
1247
|
+
When S3 is involved, the Docker swap is still straightforward — the only additional consideration is S3 URL style. If you were using `forcePathStyle: true` with LocalStack, it works identically with fauxqs. If you were using LocalStack's `localhost.localstack.cloud` wildcard DNS for virtual-hosted-style, switch to `localhost.fauxqs.dev`:
|
|
1248
|
+
|
|
1249
|
+
```typescript
|
|
1250
|
+
// Before (LocalStack)
|
|
1251
|
+
const s3 = new S3Client({
|
|
1252
|
+
endpoint: "http://s3.localhost.localstack.cloud:4566",
|
|
1253
|
+
// ...
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
// After (fauxqs)
|
|
1257
|
+
const s3 = new S3Client({
|
|
1258
|
+
endpoint: "http://s3.localhost.fauxqs.dev:4566",
|
|
1259
|
+
// ...
|
|
1260
|
+
});
|
|
1261
|
+
```
|
|
1262
|
+
|
|
1263
|
+
For container-to-container S3 in docker-compose, fauxqs includes a built-in dnsmasq that resolves `*.s3.fauxqs` to the container IP — see [Container-to-container S3 virtual-hosted-style](#container-to-container-s3-virtual-hosted-style).
|
|
1264
|
+
|
|
1265
|
+
The init config for S3 is the same declarative JSON, with a `buckets` array:
|
|
1266
|
+
|
|
1267
|
+
```json
|
|
1268
|
+
{
|
|
1269
|
+
"queues": [{ "name": "orders" }],
|
|
1270
|
+
"topics": [{ "name": "events" }],
|
|
1271
|
+
"subscriptions": [{ "topic": "events", "queue": "orders" }],
|
|
1272
|
+
"buckets": ["uploads", "exports"]
|
|
1273
|
+
}
|
|
1274
|
+
```
|
|
1275
|
+
|
|
1276
|
+
### Going hybrid (recommended)
|
|
1277
|
+
|
|
1278
|
+
The Docker swap gets you running quickly, but the real win comes from going hybrid: use fauxqs as an **embedded library** in your test suite and keep Docker for local development.
|
|
1279
|
+
|
|
1280
|
+
With LocalStack, integration tests typically look like this:
|
|
1281
|
+
|
|
1282
|
+
1. Start LocalStack container (docker-compose or testcontainers) — takes seconds
|
|
1283
|
+
2. Create resources via `awslocal` or SDK calls — more seconds
|
|
1284
|
+
3. Run your test logic
|
|
1285
|
+
4. Assert by polling SQS queues, checking S3 objects, etc.
|
|
1286
|
+
5. Clean up resources between tests — often fragile or skipped
|
|
1287
|
+
|
|
1288
|
+
With fauxqs in library mode:
|
|
1289
|
+
|
|
1290
|
+
1. `startFauxqs({ port: 0 })` — starts in milliseconds, in-process
|
|
1291
|
+
2. `server.setup({ queues: [...], topics: [...] })` — instant, no network calls
|
|
1292
|
+
3. Run your test logic
|
|
1293
|
+
4. Assert with `server.spy.waitForMessage()` — no polling, no race conditions
|
|
1294
|
+
5. `server.reset()` between tests — clears messages, keeps resources
|
|
1295
|
+
|
|
1296
|
+
**What you gain:**
|
|
1297
|
+
|
|
1298
|
+
| Concern | LocalStack Docker | fauxqs library mode |
|
|
1299
|
+
|---------|-------------------|---------------------|
|
|
1300
|
+
| Test startup | Seconds (container boot + resource creation) | Milliseconds (in-process) |
|
|
1301
|
+
| CI dependency | Docker required | npm only |
|
|
1302
|
+
| Asserting message delivery | Poll SQS queue, hope timing is right | `spy.waitForMessage()` — resolves immediately or waits |
|
|
1303
|
+
| Asserting message *not* delivered | `sleep()` + check | `spy.expectNoMessage()` — deterministic negative assertion |
|
|
1304
|
+
| Filter policy testing | Receive from queue, check absence manually | `expectNoMessage()` on filtered-out queues |
|
|
1305
|
+
| DLQ verification | Receive from DLQ queue via SDK | `spy.waitForMessage({ status: "dlq" })` + `inspectQueue()` |
|
|
1306
|
+
| Queue state inspection | `GetQueueAttributes` (counts only) | `inspectQueue()` — see every message, grouped by state |
|
|
1307
|
+
| State reset between tests | Restart container or re-create resources | `server.reset()` — instant, preserves resource definitions |
|
|
1308
|
+
| S3 event tracking | Check bucket contents via SDK | `spy.waitForMessage({ service: "s3", status: "uploaded" })` |
|
|
1309
|
+
| Parallel test files | Port conflicts or shared state | Each file gets its own server on port 0 |
|
|
1310
|
+
|
|
1311
|
+
**Migration path:**
|
|
1312
|
+
|
|
1313
|
+
1. Install fauxqs as a dev dependency: `npm install -D fauxqs`
|
|
1314
|
+
2. Create a test helper:
|
|
1315
|
+
|
|
1316
|
+
```typescript
|
|
1317
|
+
// test/setup.ts
|
|
1318
|
+
import { startFauxqs, type FauxqsServer } from "fauxqs";
|
|
1319
|
+
|
|
1320
|
+
export async function createTestServer(): Promise<FauxqsServer> {
|
|
1321
|
+
const server = await startFauxqs({ port: 0, logger: false, messageSpies: true });
|
|
1322
|
+
|
|
1323
|
+
server.setup({
|
|
1324
|
+
queues: [{ name: "orders" }, { name: "orders-dlq" }],
|
|
1325
|
+
topics: [{ name: "events" }],
|
|
1326
|
+
subscriptions: [{ topic: "events", queue: "orders" }],
|
|
1327
|
+
buckets: ["uploads"],
|
|
1328
|
+
});
|
|
1329
|
+
|
|
1330
|
+
return server;
|
|
1331
|
+
}
|
|
1332
|
+
```
|
|
1333
|
+
|
|
1334
|
+
3. Replace your LocalStack container setup with the test helper:
|
|
1335
|
+
|
|
1336
|
+
```typescript
|
|
1337
|
+
// Before: LocalStack via testcontainers or docker-compose
|
|
1338
|
+
let endpoint: string;
|
|
1339
|
+
beforeAll(async () => {
|
|
1340
|
+
// start container, wait for health, create resources via SDK...
|
|
1341
|
+
endpoint = "http://localhost:4566";
|
|
1342
|
+
}, 30_000);
|
|
1343
|
+
|
|
1344
|
+
// After: fauxqs library
|
|
1345
|
+
let server: FauxqsServer;
|
|
1346
|
+
beforeAll(async () => {
|
|
1347
|
+
server = await createTestServer();
|
|
1348
|
+
});
|
|
1349
|
+
afterAll(async () => { await server.stop(); });
|
|
1350
|
+
beforeEach(() => { server.reset(); });
|
|
1351
|
+
```
|
|
1352
|
+
|
|
1353
|
+
4. Replace polling-based assertions with spy-based ones:
|
|
1354
|
+
|
|
1355
|
+
```typescript
|
|
1356
|
+
// Before: poll and hope
|
|
1357
|
+
await sqsClient.send(new SendMessageCommand({ QueueUrl: queueUrl, MessageBody: "test" }));
|
|
1358
|
+
const result = await sqsClient.send(new ReceiveMessageCommand({ QueueUrl: queueUrl, WaitTimeSeconds: 5 }));
|
|
1359
|
+
expect(result.Messages?.[0]?.Body).toBe("test");
|
|
1360
|
+
|
|
1361
|
+
// After: spy knows immediately
|
|
1362
|
+
await sqsClient.send(new SendMessageCommand({ QueueUrl: queueUrl, MessageBody: "test" }));
|
|
1363
|
+
const msg = await server.spy.waitForMessage(
|
|
1364
|
+
{ service: "sqs", queueName: "orders", status: "published" },
|
|
1365
|
+
undefined,
|
|
1366
|
+
2000,
|
|
1367
|
+
);
|
|
1368
|
+
expect(msg.body).toBe("test");
|
|
1369
|
+
```
|
|
1370
|
+
|
|
1371
|
+
5. Keep your `docker-compose.yml` with the fauxqs image for local development — `docker compose up` gives your team a running environment without Node.js installed.
|
|
1372
|
+
|
|
1373
|
+
This hybrid setup gives you fast, deterministic tests in CI (no Docker required) and a realistic Docker environment for local development. See the [`examples/recommended/`](examples/recommended/) directory for a complete working example.
|
|
1374
|
+
|
|
1375
|
+
## Benchmarks
|
|
1376
|
+
|
|
1377
|
+
SQS throughput benchmarks are available in the [`benchmarks/`](benchmarks/) directory, comparing fauxqs across different deployment modes (in-process library, official Docker image, lightweight Docker container) and against LocalStack. See [`benchmarks/BENCHMARKING.md`](benchmarks/BENCHMARKING.md) for setup descriptions, instructions, and how to interpret results.
|
|
1378
|
+
|
|
1379
|
+
## License
|
|
1380
|
+
|
|
1381
|
+
MIT
|