@adtrackify/at-service-common 3.18.11 → 3.18.13

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 (577) hide show
  1. package/dist/cjs/__tests__/clients/acuity-client.spec.d.ts +1 -0
  2. package/dist/cjs/__tests__/clients/acuity-client.spec.js +34 -0
  3. package/dist/cjs/__tests__/clients/acuity-client.spec.js.map +1 -0
  4. package/dist/cjs/__tests__/clients/cross-platform-compression.spec.d.ts +1 -0
  5. package/dist/cjs/__tests__/clients/cross-platform-compression.spec.js +355 -0
  6. package/dist/cjs/__tests__/clients/cross-platform-compression.spec.js.map +1 -0
  7. package/dist/cjs/__tests__/clients/sqs-bundled-client.spec.d.ts +1 -1
  8. package/dist/cjs/__tests__/clients/sqs-bundled-client.spec.js +921 -921
  9. package/dist/cjs/__tests__/clients/sqs-bundling-contracts.spec.d.ts +1 -0
  10. package/dist/cjs/__tests__/clients/sqs-bundling-contracts.spec.js +576 -0
  11. package/dist/cjs/__tests__/clients/sqs-bundling-contracts.spec.js.map +1 -0
  12. package/dist/cjs/__tests__/clients/sqs-client.spec.d.ts +1 -1
  13. package/dist/cjs/__tests__/clients/sqs-client.spec.js +191 -191
  14. package/dist/cjs/__tests__/clients/sqs-unbundle.spec.d.ts +1 -1
  15. package/dist/cjs/__tests__/clients/sqs-unbundle.spec.js +1357 -1228
  16. package/dist/cjs/__tests__/clients/sqs-unbundle.spec.js.map +1 -1
  17. package/dist/cjs/__tests__/db/shared-read-db-services.spec.d.ts +1 -1
  18. package/dist/cjs/__tests__/db/shared-read-db-services.spec.js +89 -89
  19. package/dist/cjs/__tests__/helpers/account-users-helper.spec.d.ts +1 -1
  20. package/dist/cjs/__tests__/helpers/account-users-helper.spec.js +220 -220
  21. package/dist/cjs/__tests__/helpers/acuity-helper.spec.d.ts +1 -0
  22. package/dist/cjs/__tests__/helpers/acuity-helper.spec.js +26 -0
  23. package/dist/cjs/__tests__/helpers/acuity-helper.spec.js.map +1 -0
  24. package/dist/cjs/__tests__/helpers/api-key-auth-helper.spec.d.ts +1 -1
  25. package/dist/cjs/__tests__/helpers/api-key-auth-helper.spec.js +82 -82
  26. package/dist/cjs/__tests__/identity-cache/identity-cache-db-service.spec.d.ts +1 -1
  27. package/dist/cjs/__tests__/identity-cache/identity-cache-db-service.spec.js +674 -674
  28. package/dist/cjs/__tests__/identity-cache/trait-merging-and-staleness.spec.d.ts +1 -1
  29. package/dist/cjs/__tests__/identity-cache/trait-merging-and-staleness.spec.js +588 -588
  30. package/dist/cjs/__tests__/integration/sqs-bundling-roundtrip.spec.d.ts +1 -1
  31. package/dist/cjs/__tests__/integration/sqs-bundling-roundtrip.spec.js +584 -584
  32. package/dist/cjs/__tests__/libs/compress-decompress.spec.d.ts +1 -1
  33. package/dist/cjs/__tests__/libs/compress-decompress.spec.js +16 -16
  34. package/dist/cjs/__tests__/libs/currency.spec.d.ts +1 -1
  35. package/dist/cjs/__tests__/libs/currency.spec.js +220 -220
  36. package/dist/cjs/__tests__/libs/dates.spec.d.ts +1 -1
  37. package/dist/cjs/__tests__/libs/dates.spec.js +130 -130
  38. package/dist/cjs/__tests__/libs/domain.spec.d.ts +1 -1
  39. package/dist/cjs/__tests__/libs/domain.spec.js +107 -107
  40. package/dist/cjs/__tests__/libs/numbers.spec.d.ts +1 -1
  41. package/dist/cjs/__tests__/libs/numbers.spec.js +261 -261
  42. package/dist/cjs/__tests__/s3-client/s3-client.spec.d.ts +1 -1
  43. package/dist/cjs/__tests__/s3-client/s3-client.spec.js +33 -33
  44. package/dist/cjs/__tests__/services/acuity-api-service.spec.d.ts +1 -0
  45. package/dist/cjs/__tests__/services/acuity-api-service.spec.js +52 -0
  46. package/dist/cjs/__tests__/services/acuity-api-service.spec.js.map +1 -0
  47. package/dist/cjs/__tests__/shopify/shopify-graphql-transformer.spec.d.ts +1 -1
  48. package/dist/cjs/__tests__/shopify/shopify-graphql-transformer.spec.js +35 -35
  49. package/dist/cjs/__tests__/unit/libs/api-router/public-api-router.spec.d.ts +1 -1
  50. package/dist/cjs/__tests__/unit/libs/api-router/public-api-router.spec.js +181 -181
  51. package/dist/cjs/__tests__/unit/libs/api-router/route-matcher.spec.d.ts +1 -1
  52. package/dist/cjs/__tests__/unit/libs/api-router/route-matcher.spec.js +69 -69
  53. package/dist/cjs/clients/generic/cognito-client.d.ts +23 -23
  54. package/dist/cjs/clients/generic/cognito-client.js +209 -209
  55. package/dist/cjs/clients/generic/dynamodb-client.d.ts +18 -18
  56. package/dist/cjs/clients/generic/dynamodb-client.js +172 -172
  57. package/dist/cjs/clients/generic/eventbridge-client.d.ts +14 -14
  58. package/dist/cjs/clients/generic/eventbridge-client.js +51 -51
  59. package/dist/cjs/clients/generic/http-client.d.ts +14 -14
  60. package/dist/cjs/clients/generic/http-client.js +61 -61
  61. package/dist/cjs/clients/generic/index.d.ts +13 -13
  62. package/dist/cjs/clients/generic/index.js +29 -29
  63. package/dist/cjs/clients/generic/lambda-invoke-client.d.ts +10 -10
  64. package/dist/cjs/clients/generic/lambda-invoke-client.js +39 -39
  65. package/dist/cjs/clients/generic/location-client.d.ts +8 -8
  66. package/dist/cjs/clients/generic/location-client.js +31 -31
  67. package/dist/cjs/clients/generic/redis-client.d.ts +33 -33
  68. package/dist/cjs/clients/generic/redis-client.js +191 -191
  69. package/dist/cjs/clients/generic/s3-client.d.ts +23 -23
  70. package/dist/cjs/clients/generic/s3-client.js +216 -216
  71. package/dist/cjs/clients/generic/singlestore-db-client.d.ts +14 -14
  72. package/dist/cjs/clients/generic/singlestore-db-client.js +67 -67
  73. package/dist/cjs/clients/generic/sqs-bundled-client.d.ts +15 -15
  74. package/dist/cjs/clients/generic/sqs-bundled-client.js +311 -311
  75. package/dist/cjs/clients/generic/sqs-bundled-client.types.d.ts +53 -53
  76. package/dist/cjs/clients/generic/sqs-bundled-client.types.js +17 -17
  77. package/dist/cjs/clients/generic/sqs-client.d.ts +53 -53
  78. package/dist/cjs/clients/generic/sqs-client.js +285 -285
  79. package/dist/cjs/clients/generic/sqs-unbundle.d.ts +32 -32
  80. package/dist/cjs/clients/generic/sqs-unbundle.js +144 -144
  81. package/dist/cjs/clients/index.d.ts +3 -3
  82. package/dist/cjs/clients/index.js +19 -19
  83. package/dist/cjs/clients/internal-api/accounts-client.d.ts +91 -91
  84. package/dist/cjs/clients/internal-api/accounts-client.js +129 -129
  85. package/dist/cjs/clients/internal-api/cache-lambda-client.d.ts +26 -26
  86. package/dist/cjs/clients/internal-api/cache-lambda-client.js +89 -89
  87. package/dist/cjs/clients/internal-api/db-management-client.d.ts +18 -18
  88. package/dist/cjs/clients/internal-api/db-management-client.js +36 -36
  89. package/dist/cjs/clients/internal-api/destinations-client.d.ts +34 -34
  90. package/dist/cjs/clients/internal-api/destinations-client.js +79 -79
  91. package/dist/cjs/clients/internal-api/event-collector-client.d.ts +20 -20
  92. package/dist/cjs/clients/internal-api/event-collector-client.js +36 -36
  93. package/dist/cjs/clients/internal-api/identity-client.d.ts +31 -31
  94. package/dist/cjs/clients/internal-api/identity-client.js +91 -91
  95. package/dist/cjs/clients/internal-api/index.d.ts +9 -9
  96. package/dist/cjs/clients/internal-api/index.js +25 -25
  97. package/dist/cjs/clients/internal-api/shopify-app-install-client.d.ts +37 -37
  98. package/dist/cjs/clients/internal-api/shopify-app-install-client.js +81 -81
  99. package/dist/cjs/clients/internal-api/subscriptions-client.d.ts +26 -26
  100. package/dist/cjs/clients/internal-api/subscriptions-client.js +77 -77
  101. package/dist/cjs/clients/internal-api/users-auth-client.d.ts +35 -35
  102. package/dist/cjs/clients/internal-api/users-auth-client.js +110 -110
  103. package/dist/cjs/clients/third-party/acuity-client.d.ts +9 -0
  104. package/dist/cjs/clients/third-party/acuity-client.js +35 -0
  105. package/dist/cjs/clients/third-party/acuity-client.js.map +1 -0
  106. package/dist/cjs/clients/third-party/emailable-client.d.ts +7 -7
  107. package/dist/cjs/clients/third-party/emailable-client.js +25 -25
  108. package/dist/cjs/clients/third-party/exchange-rate-api-client.d.ts +17 -17
  109. package/dist/cjs/clients/third-party/exchange-rate-api-client.js +19 -19
  110. package/dist/cjs/clients/third-party/index.d.ts +5 -4
  111. package/dist/cjs/clients/third-party/index.js +21 -20
  112. package/dist/cjs/clients/third-party/index.js.map +1 -1
  113. package/dist/cjs/clients/third-party/loops-client.d.ts +10 -10
  114. package/dist/cjs/clients/third-party/loops-client.js +30 -30
  115. package/dist/cjs/clients/third-party/shopify/graphql-order-queries.d.ts +25 -25
  116. package/dist/cjs/clients/third-party/shopify/graphql-order-queries.js +4 -4
  117. package/dist/cjs/clients/third-party/shopify/graphql-product-queries.d.ts +2 -2
  118. package/dist/cjs/clients/third-party/shopify/graphql-product-queries.js +5 -5
  119. package/dist/cjs/clients/third-party/shopify/shopify-graphql-client.d.ts +10 -10
  120. package/dist/cjs/clients/third-party/shopify/shopify-graphql-client.js +161 -161
  121. package/dist/cjs/clients/third-party/shopify-client.d.ts +29 -29
  122. package/dist/cjs/clients/third-party/shopify-client.js +146 -146
  123. package/dist/cjs/constants/index.d.ts +1 -1
  124. package/dist/cjs/constants/index.js +17 -17
  125. package/dist/cjs/constants/sqs.d.ts +20 -20
  126. package/dist/cjs/constants/sqs.js +26 -26
  127. package/dist/cjs/helpers/account-users-helper.d.ts +2 -2
  128. package/dist/cjs/helpers/account-users-helper.js +22 -22
  129. package/dist/cjs/helpers/acuity-helper.d.ts +2 -0
  130. package/dist/cjs/helpers/acuity-helper.js +23 -0
  131. package/dist/cjs/helpers/acuity-helper.js.map +1 -0
  132. package/dist/cjs/helpers/api-key-auth-helper.d.ts +9 -9
  133. package/dist/cjs/helpers/api-key-auth-helper.js +40 -40
  134. package/dist/cjs/helpers/api-key-authorizer-helper.d.ts +36 -36
  135. package/dist/cjs/helpers/api-key-authorizer-helper.js +87 -87
  136. package/dist/cjs/helpers/identity-cache-helper.d.ts +21 -21
  137. package/dist/cjs/helpers/identity-cache-helper.js +156 -156
  138. package/dist/cjs/helpers/index.d.ts +10 -9
  139. package/dist/cjs/helpers/index.js +26 -25
  140. package/dist/cjs/helpers/index.js.map +1 -1
  141. package/dist/cjs/helpers/input-validation-helper.d.ts +3 -3
  142. package/dist/cjs/helpers/input-validation-helper.js +22 -22
  143. package/dist/cjs/helpers/logging-helper.d.ts +16 -16
  144. package/dist/cjs/helpers/logging-helper.js +84 -84
  145. package/dist/cjs/helpers/response-helper.d.ts +18 -18
  146. package/dist/cjs/helpers/response-helper.js +43 -43
  147. package/dist/cjs/helpers/shopify-helper.d.ts +9 -9
  148. package/dist/cjs/helpers/shopify-helper.js +26 -26
  149. package/dist/cjs/helpers/sqs-utils.d.ts +6 -6
  150. package/dist/cjs/helpers/sqs-utils.js +14 -14
  151. package/dist/cjs/index.d.ts +7 -7
  152. package/dist/cjs/index.js +23 -23
  153. package/dist/cjs/libs/api-router/index.d.ts +2 -2
  154. package/dist/cjs/libs/api-router/index.js +18 -18
  155. package/dist/cjs/libs/api-router/public-api-router.d.ts +3 -3
  156. package/dist/cjs/libs/api-router/public-api-router.js +36 -36
  157. package/dist/cjs/libs/api-router/route-matcher.d.ts +21 -21
  158. package/dist/cjs/libs/api-router/route-matcher.js +36 -36
  159. package/dist/cjs/libs/click-id-parser.d.ts +23 -23
  160. package/dist/cjs/libs/click-id-parser.js +49 -49
  161. package/dist/cjs/libs/compression.d.ts +2 -2
  162. package/dist/cjs/libs/compression.js +33 -33
  163. package/dist/cjs/libs/cookie.d.ts +17 -17
  164. package/dist/cjs/libs/cookie.js +76 -76
  165. package/dist/cjs/libs/crypto.d.ts +4 -4
  166. package/dist/cjs/libs/crypto.js +25 -25
  167. package/dist/cjs/libs/csv.d.ts +2 -2
  168. package/dist/cjs/libs/csv.js +35 -35
  169. package/dist/cjs/libs/currency.d.ts +1 -1
  170. package/dist/cjs/libs/currency.js +29 -29
  171. package/dist/cjs/libs/dates.d.ts +12 -12
  172. package/dist/cjs/libs/dates.js +96 -96
  173. package/dist/cjs/libs/domain.d.ts +2 -2
  174. package/dist/cjs/libs/domain.js +38 -38
  175. package/dist/cjs/libs/emails.d.ts +6 -6
  176. package/dist/cjs/libs/emails.js +122 -122
  177. package/dist/cjs/libs/http-error.d.ts +21 -21
  178. package/dist/cjs/libs/http-error.js +63 -63
  179. package/dist/cjs/libs/http-status-codes.d.ts +58 -58
  180. package/dist/cjs/libs/http-status-codes.js +62 -62
  181. package/dist/cjs/libs/index.d.ts +18 -18
  182. package/dist/cjs/libs/index.js +34 -34
  183. package/dist/cjs/libs/numbers.d.ts +1 -1
  184. package/dist/cjs/libs/numbers.js +15 -15
  185. package/dist/cjs/libs/referrer-parser/index.d.ts +2 -2
  186. package/dist/cjs/libs/referrer-parser/index.js +18 -18
  187. package/dist/cjs/libs/referrer-parser/referrer-data.d.ts +9 -9
  188. package/dist/cjs/libs/referrer-parser/referrer-data.js +3307 -3307
  189. package/dist/cjs/libs/referrer-parser/referrer-parser-util.d.ts +20 -20
  190. package/dist/cjs/libs/referrer-parser/referrer-parser-util.js +131 -131
  191. package/dist/cjs/libs/strings.d.ts +3 -3
  192. package/dist/cjs/libs/strings.js +46 -46
  193. package/dist/cjs/libs/traits.d.ts +6 -6
  194. package/dist/cjs/libs/traits.js +65 -65
  195. package/dist/cjs/libs/url.d.ts +1 -1
  196. package/dist/cjs/libs/url.js +13 -13
  197. package/dist/cjs/services/acuity-api-service.d.ts +8 -0
  198. package/dist/cjs/services/acuity-api-service.js +55 -0
  199. package/dist/cjs/services/acuity-api-service.js.map +1 -0
  200. package/dist/cjs/services/cache/generic-cached-object.d.ts +5 -5
  201. package/dist/cjs/services/cache/generic-cached-object.js +2 -2
  202. package/dist/cjs/services/cache/index.d.ts +1 -1
  203. package/dist/cjs/services/cache/index.js +17 -17
  204. package/dist/cjs/services/cache/product-cache-service.d.ts +21 -21
  205. package/dist/cjs/services/cache/product-cache-service.js +76 -76
  206. package/dist/cjs/services/currency-exchange-rate-lookup-service.d.ts +11 -11
  207. package/dist/cjs/services/currency-exchange-rate-lookup-service.js +66 -66
  208. package/dist/cjs/services/db/accounts-db-service.d.ts +9 -9
  209. package/dist/cjs/services/db/accounts-db-service.js +33 -33
  210. package/dist/cjs/services/db/api-keys-db-service.d.ts +10 -10
  211. package/dist/cjs/services/db/api-keys-db-service.js +36 -36
  212. package/dist/cjs/services/db/currency-exchange-rates-db-service.d.ts +21 -21
  213. package/dist/cjs/services/db/currency-exchange-rates-db-service.js +39 -39
  214. package/dist/cjs/services/db/destinations-db-service.d.ts +12 -12
  215. package/dist/cjs/services/db/destinations-db-service.js +76 -76
  216. package/dist/cjs/services/db/identity-cache-db-service.d.ts +28 -28
  217. package/dist/cjs/services/db/identity-cache-db-service.js +320 -320
  218. package/dist/cjs/services/db/index.d.ts +13 -13
  219. package/dist/cjs/services/db/index.js +29 -29
  220. package/dist/cjs/services/db/log-events-db-service.d.ts +11 -11
  221. package/dist/cjs/services/db/log-events-db-service.js +181 -181
  222. package/dist/cjs/services/db/pixels-db-service.d.ts +8 -8
  223. package/dist/cjs/services/db/pixels-db-service.js +35 -35
  224. package/dist/cjs/services/db/purchasable-contacts-db-service.d.ts +9 -9
  225. package/dist/cjs/services/db/purchasable-contacts-db-service.js +43 -43
  226. package/dist/cjs/services/db/purchased-contacts-db-service.d.ts +17 -17
  227. package/dist/cjs/services/db/purchased-contacts-db-service.js +143 -143
  228. package/dist/cjs/services/db/shopify-app-installs-db-service.d.ts +8 -8
  229. package/dist/cjs/services/db/shopify-app-installs-db-service.js +51 -51
  230. package/dist/cjs/services/db/shopify-products-cache-db-service.d.ts +16 -16
  231. package/dist/cjs/services/db/shopify-products-cache-db-service.js +73 -73
  232. package/dist/cjs/services/db/subscriptions-db-service.d.ts +10 -10
  233. package/dist/cjs/services/db/subscriptions-db-service.js +34 -34
  234. package/dist/cjs/services/db/tracking-events-db-service.d.ts +20 -20
  235. package/dist/cjs/services/db/tracking-events-db-service.js +165 -165
  236. package/dist/cjs/services/eventbridge-integration-service.d.ts +9 -9
  237. package/dist/cjs/services/eventbridge-integration-service.js +28 -28
  238. package/dist/cjs/services/events/index.d.ts +3 -3
  239. package/dist/cjs/services/events/index.js +19 -19
  240. package/dist/cjs/services/events/log-event-service.d.ts +19 -19
  241. package/dist/cjs/services/events/log-event-service.js +77 -77
  242. package/dist/cjs/services/events/metric-event-service.d.ts +9 -9
  243. package/dist/cjs/services/events/metric-event-service.js +49 -49
  244. package/dist/cjs/services/events/tracking-event-sqs-service.d.ts +8 -8
  245. package/dist/cjs/services/events/tracking-event-sqs-service.js +34 -34
  246. package/dist/cjs/services/generic-cache-service.d.ts +7 -7
  247. package/dist/cjs/services/generic-cache-service.js +33 -33
  248. package/dist/cjs/services/index.d.ts +9 -8
  249. package/dist/cjs/services/index.js +25 -24
  250. package/dist/cjs/services/index.js.map +1 -1
  251. package/dist/cjs/services/ipdata-lookup-service.d.ts +20 -20
  252. package/dist/cjs/services/ipdata-lookup-service.js +112 -112
  253. package/dist/cjs/services/shopify/index.d.ts +2 -2
  254. package/dist/cjs/services/shopify/index.js +18 -18
  255. package/dist/cjs/services/shopify/products/index.d.ts +1 -1
  256. package/dist/cjs/services/shopify/products/index.js +17 -17
  257. package/dist/cjs/services/shopify/products/shopify-products-serviceV2.d.ts +17 -17
  258. package/dist/cjs/services/shopify/products/shopify-products-serviceV2.js +112 -112
  259. package/dist/cjs/services/shopify/shopify-graphql-transformer.d.ts +8 -8
  260. package/dist/cjs/services/shopify/shopify-graphql-transformer.js +141 -141
  261. package/dist/cjs/types/acuity-types.d.ts +74 -0
  262. package/dist/cjs/types/acuity-types.js +3 -0
  263. package/dist/cjs/types/acuity-types.js.map +1 -0
  264. package/dist/cjs/types/api-response.d.ts +6 -6
  265. package/dist/cjs/types/api-response.js +2 -2
  266. package/dist/cjs/types/index.d.ts +4 -3
  267. package/dist/cjs/types/index.js +33 -32
  268. package/dist/cjs/types/index.js.map +1 -1
  269. package/dist/cjs/types/internal-events/event-detail-types.d.ts +20 -20
  270. package/dist/cjs/types/internal-events/event-detail-types.js +27 -27
  271. package/dist/cjs/types/internal-events/index.d.ts +1 -1
  272. package/dist/cjs/types/internal-events/index.js +17 -17
  273. package/dist/cjs/types/shopify-graphql-types/admin.generated.d.ts +123 -123
  274. package/dist/cjs/types/shopify-graphql-types/admin.generated.js +2 -2
  275. package/dist/cjs/types/shopify-graphql-types/admin.types.d.ts +26289 -26289
  276. package/dist/cjs/types/shopify-graphql-types/admin.types.js +5311 -5311
  277. package/dist/cjs/types/shopify-graphql-types/index.d.ts +2 -2
  278. package/dist/cjs/types/shopify-graphql-types/index.js +18 -18
  279. package/dist/cjs/types/shopify-rest-types.d.ts +767 -767
  280. package/dist/cjs/types/shopify-rest-types.js +2 -2
  281. package/dist/cjs/utils/compression.d.ts +36 -36
  282. package/dist/cjs/utils/compression.js +198 -198
  283. package/dist/cjs/utils/index.d.ts +3 -3
  284. package/dist/cjs/utils/index.js +19 -19
  285. package/dist/cjs/utils/retry-envelope.d.ts +12 -12
  286. package/dist/cjs/utils/retry-envelope.js +28 -28
  287. package/dist/cjs/utils/size.d.ts +2 -2
  288. package/dist/cjs/utils/size.js +49 -49
  289. package/dist/esm/__tests__/clients/acuity-client.spec.d.ts +1 -0
  290. package/dist/esm/__tests__/clients/acuity-client.spec.js +32 -0
  291. package/dist/esm/__tests__/clients/acuity-client.spec.js.map +1 -0
  292. package/dist/esm/__tests__/clients/cross-platform-compression.spec.d.ts +1 -0
  293. package/dist/esm/__tests__/clients/cross-platform-compression.spec.js +330 -0
  294. package/dist/esm/__tests__/clients/cross-platform-compression.spec.js.map +1 -0
  295. package/dist/esm/__tests__/clients/sqs-bundled-client.spec.d.ts +1 -1
  296. package/dist/esm/__tests__/clients/sqs-bundled-client.spec.js +896 -896
  297. package/dist/esm/__tests__/clients/sqs-bundling-contracts.spec.d.ts +1 -0
  298. package/dist/esm/__tests__/clients/sqs-bundling-contracts.spec.js +551 -0
  299. package/dist/esm/__tests__/clients/sqs-bundling-contracts.spec.js.map +1 -0
  300. package/dist/esm/__tests__/clients/sqs-client.spec.d.ts +1 -1
  301. package/dist/esm/__tests__/clients/sqs-client.spec.js +189 -189
  302. package/dist/esm/__tests__/clients/sqs-unbundle.spec.d.ts +1 -1
  303. package/dist/esm/__tests__/clients/sqs-unbundle.spec.js +1355 -1226
  304. package/dist/esm/__tests__/clients/sqs-unbundle.spec.js.map +1 -1
  305. package/dist/esm/__tests__/db/shared-read-db-services.spec.d.ts +1 -1
  306. package/dist/esm/__tests__/db/shared-read-db-services.spec.js +87 -87
  307. package/dist/esm/__tests__/helpers/account-users-helper.spec.d.ts +1 -1
  308. package/dist/esm/__tests__/helpers/account-users-helper.spec.js +218 -218
  309. package/dist/esm/__tests__/helpers/acuity-helper.spec.d.ts +1 -0
  310. package/dist/esm/__tests__/helpers/acuity-helper.spec.js +24 -0
  311. package/dist/esm/__tests__/helpers/acuity-helper.spec.js.map +1 -0
  312. package/dist/esm/__tests__/helpers/api-key-auth-helper.spec.d.ts +1 -1
  313. package/dist/esm/__tests__/helpers/api-key-auth-helper.spec.js +80 -80
  314. package/dist/esm/__tests__/identity-cache/identity-cache-db-service.spec.d.ts +1 -1
  315. package/dist/esm/__tests__/identity-cache/identity-cache-db-service.spec.js +672 -672
  316. package/dist/esm/__tests__/identity-cache/trait-merging-and-staleness.spec.d.ts +1 -1
  317. package/dist/esm/__tests__/identity-cache/trait-merging-and-staleness.spec.js +586 -586
  318. package/dist/esm/__tests__/integration/sqs-bundling-roundtrip.spec.d.ts +1 -1
  319. package/dist/esm/__tests__/integration/sqs-bundling-roundtrip.spec.js +582 -582
  320. package/dist/esm/__tests__/libs/compress-decompress.spec.d.ts +1 -1
  321. package/dist/esm/__tests__/libs/compress-decompress.spec.js +14 -14
  322. package/dist/esm/__tests__/libs/currency.spec.d.ts +1 -1
  323. package/dist/esm/__tests__/libs/currency.spec.js +218 -218
  324. package/dist/esm/__tests__/libs/dates.spec.d.ts +1 -1
  325. package/dist/esm/__tests__/libs/dates.spec.js +128 -128
  326. package/dist/esm/__tests__/libs/domain.spec.d.ts +1 -1
  327. package/dist/esm/__tests__/libs/domain.spec.js +105 -105
  328. package/dist/esm/__tests__/libs/numbers.spec.d.ts +1 -1
  329. package/dist/esm/__tests__/libs/numbers.spec.js +259 -259
  330. package/dist/esm/__tests__/s3-client/s3-client.spec.d.ts +1 -1
  331. package/dist/esm/__tests__/s3-client/s3-client.spec.js +31 -31
  332. package/dist/esm/__tests__/services/acuity-api-service.spec.d.ts +1 -0
  333. package/dist/esm/__tests__/services/acuity-api-service.spec.js +50 -0
  334. package/dist/esm/__tests__/services/acuity-api-service.spec.js.map +1 -0
  335. package/dist/esm/__tests__/shopify/shopify-graphql-transformer.spec.d.ts +1 -1
  336. package/dist/esm/__tests__/shopify/shopify-graphql-transformer.spec.js +33 -33
  337. package/dist/esm/__tests__/unit/libs/api-router/public-api-router.spec.d.ts +1 -1
  338. package/dist/esm/__tests__/unit/libs/api-router/public-api-router.spec.js +156 -156
  339. package/dist/esm/__tests__/unit/libs/api-router/route-matcher.spec.d.ts +1 -1
  340. package/dist/esm/__tests__/unit/libs/api-router/route-matcher.spec.js +67 -67
  341. package/dist/esm/clients/generic/cognito-client.d.ts +23 -23
  342. package/dist/esm/clients/generic/cognito-client.js +204 -204
  343. package/dist/esm/clients/generic/dynamodb-client.d.ts +18 -18
  344. package/dist/esm/clients/generic/dynamodb-client.js +168 -168
  345. package/dist/esm/clients/generic/eventbridge-client.d.ts +14 -14
  346. package/dist/esm/clients/generic/eventbridge-client.js +47 -47
  347. package/dist/esm/clients/generic/http-client.d.ts +14 -14
  348. package/dist/esm/clients/generic/http-client.js +53 -53
  349. package/dist/esm/clients/generic/index.d.ts +13 -13
  350. package/dist/esm/clients/generic/index.js +13 -13
  351. package/dist/esm/clients/generic/lambda-invoke-client.d.ts +10 -10
  352. package/dist/esm/clients/generic/lambda-invoke-client.js +35 -35
  353. package/dist/esm/clients/generic/location-client.d.ts +8 -8
  354. package/dist/esm/clients/generic/location-client.js +27 -27
  355. package/dist/esm/clients/generic/redis-client.d.ts +33 -33
  356. package/dist/esm/clients/generic/redis-client.js +184 -184
  357. package/dist/esm/clients/generic/s3-client.d.ts +23 -23
  358. package/dist/esm/clients/generic/s3-client.js +209 -209
  359. package/dist/esm/clients/generic/singlestore-db-client.d.ts +14 -14
  360. package/dist/esm/clients/generic/singlestore-db-client.js +40 -40
  361. package/dist/esm/clients/generic/sqs-bundled-client.d.ts +15 -15
  362. package/dist/esm/clients/generic/sqs-bundled-client.js +307 -307
  363. package/dist/esm/clients/generic/sqs-bundled-client.types.d.ts +53 -53
  364. package/dist/esm/clients/generic/sqs-bundled-client.types.js +14 -14
  365. package/dist/esm/clients/generic/sqs-client.d.ts +53 -53
  366. package/dist/esm/clients/generic/sqs-client.js +281 -281
  367. package/dist/esm/clients/generic/sqs-unbundle.d.ts +32 -32
  368. package/dist/esm/clients/generic/sqs-unbundle.js +137 -137
  369. package/dist/esm/clients/index.d.ts +3 -3
  370. package/dist/esm/clients/index.js +3 -3
  371. package/dist/esm/clients/internal-api/accounts-client.d.ts +91 -91
  372. package/dist/esm/clients/internal-api/accounts-client.js +125 -125
  373. package/dist/esm/clients/internal-api/cache-lambda-client.d.ts +26 -26
  374. package/dist/esm/clients/internal-api/cache-lambda-client.js +85 -85
  375. package/dist/esm/clients/internal-api/db-management-client.d.ts +18 -18
  376. package/dist/esm/clients/internal-api/db-management-client.js +32 -32
  377. package/dist/esm/clients/internal-api/destinations-client.d.ts +34 -34
  378. package/dist/esm/clients/internal-api/destinations-client.js +75 -75
  379. package/dist/esm/clients/internal-api/event-collector-client.d.ts +20 -20
  380. package/dist/esm/clients/internal-api/event-collector-client.js +32 -32
  381. package/dist/esm/clients/internal-api/identity-client.d.ts +31 -31
  382. package/dist/esm/clients/internal-api/identity-client.js +87 -87
  383. package/dist/esm/clients/internal-api/index.d.ts +9 -9
  384. package/dist/esm/clients/internal-api/index.js +9 -9
  385. package/dist/esm/clients/internal-api/shopify-app-install-client.d.ts +37 -37
  386. package/dist/esm/clients/internal-api/shopify-app-install-client.js +77 -77
  387. package/dist/esm/clients/internal-api/subscriptions-client.d.ts +26 -26
  388. package/dist/esm/clients/internal-api/subscriptions-client.js +73 -73
  389. package/dist/esm/clients/internal-api/users-auth-client.d.ts +35 -35
  390. package/dist/esm/clients/internal-api/users-auth-client.js +106 -106
  391. package/dist/esm/clients/third-party/acuity-client.d.ts +9 -0
  392. package/dist/esm/clients/third-party/acuity-client.js +31 -0
  393. package/dist/esm/clients/third-party/acuity-client.js.map +1 -0
  394. package/dist/esm/clients/third-party/emailable-client.d.ts +7 -7
  395. package/dist/esm/clients/third-party/emailable-client.js +21 -21
  396. package/dist/esm/clients/third-party/exchange-rate-api-client.d.ts +17 -17
  397. package/dist/esm/clients/third-party/exchange-rate-api-client.js +15 -15
  398. package/dist/esm/clients/third-party/index.d.ts +5 -4
  399. package/dist/esm/clients/third-party/index.js +5 -4
  400. package/dist/esm/clients/third-party/index.js.map +1 -1
  401. package/dist/esm/clients/third-party/loops-client.d.ts +10 -10
  402. package/dist/esm/clients/third-party/loops-client.js +26 -26
  403. package/dist/esm/clients/third-party/shopify/graphql-order-queries.d.ts +25 -25
  404. package/dist/esm/clients/third-party/shopify/graphql-order-queries.js +1 -1
  405. package/dist/esm/clients/third-party/shopify/graphql-product-queries.d.ts +2 -2
  406. package/dist/esm/clients/third-party/shopify/graphql-product-queries.js +2 -2
  407. package/dist/esm/clients/third-party/shopify/shopify-graphql-client.d.ts +10 -10
  408. package/dist/esm/clients/third-party/shopify/shopify-graphql-client.js +157 -157
  409. package/dist/esm/clients/third-party/shopify-client.d.ts +29 -29
  410. package/dist/esm/clients/third-party/shopify-client.js +142 -142
  411. package/dist/esm/constants/index.d.ts +1 -1
  412. package/dist/esm/constants/index.js +1 -1
  413. package/dist/esm/constants/sqs.d.ts +20 -20
  414. package/dist/esm/constants/sqs.js +22 -22
  415. package/dist/esm/helpers/account-users-helper.d.ts +2 -2
  416. package/dist/esm/helpers/account-users-helper.js +18 -18
  417. package/dist/esm/helpers/acuity-helper.d.ts +2 -0
  418. package/dist/esm/helpers/acuity-helper.js +19 -0
  419. package/dist/esm/helpers/acuity-helper.js.map +1 -0
  420. package/dist/esm/helpers/api-key-auth-helper.d.ts +9 -9
  421. package/dist/esm/helpers/api-key-auth-helper.js +35 -35
  422. package/dist/esm/helpers/api-key-authorizer-helper.d.ts +36 -36
  423. package/dist/esm/helpers/api-key-authorizer-helper.js +83 -83
  424. package/dist/esm/helpers/identity-cache-helper.d.ts +21 -21
  425. package/dist/esm/helpers/identity-cache-helper.js +151 -151
  426. package/dist/esm/helpers/index.d.ts +10 -9
  427. package/dist/esm/helpers/index.js +10 -9
  428. package/dist/esm/helpers/index.js.map +1 -1
  429. package/dist/esm/helpers/input-validation-helper.d.ts +3 -3
  430. package/dist/esm/helpers/input-validation-helper.js +18 -18
  431. package/dist/esm/helpers/logging-helper.d.ts +16 -16
  432. package/dist/esm/helpers/logging-helper.js +56 -56
  433. package/dist/esm/helpers/response-helper.d.ts +18 -18
  434. package/dist/esm/helpers/response-helper.js +37 -37
  435. package/dist/esm/helpers/shopify-helper.d.ts +9 -9
  436. package/dist/esm/helpers/shopify-helper.js +21 -21
  437. package/dist/esm/helpers/sqs-utils.d.ts +6 -6
  438. package/dist/esm/helpers/sqs-utils.js +9 -9
  439. package/dist/esm/index.d.ts +7 -7
  440. package/dist/esm/index.js +7 -7
  441. package/dist/esm/libs/api-router/index.d.ts +2 -2
  442. package/dist/esm/libs/api-router/index.js +2 -2
  443. package/dist/esm/libs/api-router/public-api-router.d.ts +3 -3
  444. package/dist/esm/libs/api-router/public-api-router.js +32 -32
  445. package/dist/esm/libs/api-router/route-matcher.d.ts +21 -21
  446. package/dist/esm/libs/api-router/route-matcher.js +30 -30
  447. package/dist/esm/libs/click-id-parser.d.ts +23 -23
  448. package/dist/esm/libs/click-id-parser.js +45 -45
  449. package/dist/esm/libs/compression.d.ts +2 -2
  450. package/dist/esm/libs/compression.js +25 -25
  451. package/dist/esm/libs/cookie.d.ts +17 -17
  452. package/dist/esm/libs/cookie.js +70 -70
  453. package/dist/esm/libs/crypto.d.ts +4 -4
  454. package/dist/esm/libs/crypto.js +15 -15
  455. package/dist/esm/libs/csv.d.ts +2 -2
  456. package/dist/esm/libs/csv.js +30 -30
  457. package/dist/esm/libs/currency.d.ts +1 -1
  458. package/dist/esm/libs/currency.js +22 -22
  459. package/dist/esm/libs/dates.d.ts +12 -12
  460. package/dist/esm/libs/dates.js +83 -83
  461. package/dist/esm/libs/domain.d.ts +2 -2
  462. package/dist/esm/libs/domain.js +33 -33
  463. package/dist/esm/libs/emails.d.ts +6 -6
  464. package/dist/esm/libs/emails.js +115 -115
  465. package/dist/esm/libs/http-error.d.ts +21 -21
  466. package/dist/esm/libs/http-error.js +59 -59
  467. package/dist/esm/libs/http-status-codes.d.ts +58 -58
  468. package/dist/esm/libs/http-status-codes.js +59 -59
  469. package/dist/esm/libs/index.d.ts +18 -18
  470. package/dist/esm/libs/index.js +18 -18
  471. package/dist/esm/libs/numbers.d.ts +1 -1
  472. package/dist/esm/libs/numbers.js +11 -11
  473. package/dist/esm/libs/referrer-parser/index.d.ts +2 -2
  474. package/dist/esm/libs/referrer-parser/index.js +2 -2
  475. package/dist/esm/libs/referrer-parser/referrer-data.d.ts +9 -9
  476. package/dist/esm/libs/referrer-parser/referrer-data.js +3304 -3304
  477. package/dist/esm/libs/referrer-parser/referrer-parser-util.d.ts +20 -20
  478. package/dist/esm/libs/referrer-parser/referrer-parser-util.js +124 -124
  479. package/dist/esm/libs/strings.d.ts +3 -3
  480. package/dist/esm/libs/strings.js +40 -40
  481. package/dist/esm/libs/traits.d.ts +6 -6
  482. package/dist/esm/libs/traits.js +54 -54
  483. package/dist/esm/libs/url.d.ts +1 -1
  484. package/dist/esm/libs/url.js +9 -9
  485. package/dist/esm/services/acuity-api-service.d.ts +8 -0
  486. package/dist/esm/services/acuity-api-service.js +51 -0
  487. package/dist/esm/services/acuity-api-service.js.map +1 -0
  488. package/dist/esm/services/cache/generic-cached-object.d.ts +5 -5
  489. package/dist/esm/services/cache/generic-cached-object.js +1 -1
  490. package/dist/esm/services/cache/index.d.ts +1 -1
  491. package/dist/esm/services/cache/index.js +1 -1
  492. package/dist/esm/services/cache/product-cache-service.d.ts +21 -21
  493. package/dist/esm/services/cache/product-cache-service.js +68 -68
  494. package/dist/esm/services/currency-exchange-rate-lookup-service.d.ts +11 -11
  495. package/dist/esm/services/currency-exchange-rate-lookup-service.js +62 -62
  496. package/dist/esm/services/db/accounts-db-service.d.ts +9 -9
  497. package/dist/esm/services/db/accounts-db-service.js +29 -29
  498. package/dist/esm/services/db/api-keys-db-service.d.ts +10 -10
  499. package/dist/esm/services/db/api-keys-db-service.js +32 -32
  500. package/dist/esm/services/db/currency-exchange-rates-db-service.d.ts +21 -21
  501. package/dist/esm/services/db/currency-exchange-rates-db-service.js +35 -35
  502. package/dist/esm/services/db/destinations-db-service.d.ts +12 -12
  503. package/dist/esm/services/db/destinations-db-service.js +72 -72
  504. package/dist/esm/services/db/identity-cache-db-service.d.ts +28 -28
  505. package/dist/esm/services/db/identity-cache-db-service.js +313 -313
  506. package/dist/esm/services/db/index.d.ts +13 -13
  507. package/dist/esm/services/db/index.js +13 -13
  508. package/dist/esm/services/db/log-events-db-service.d.ts +11 -11
  509. package/dist/esm/services/db/log-events-db-service.js +177 -177
  510. package/dist/esm/services/db/pixels-db-service.d.ts +8 -8
  511. package/dist/esm/services/db/pixels-db-service.js +31 -31
  512. package/dist/esm/services/db/purchasable-contacts-db-service.d.ts +9 -9
  513. package/dist/esm/services/db/purchasable-contacts-db-service.js +39 -39
  514. package/dist/esm/services/db/purchased-contacts-db-service.d.ts +17 -17
  515. package/dist/esm/services/db/purchased-contacts-db-service.js +139 -139
  516. package/dist/esm/services/db/shopify-app-installs-db-service.d.ts +8 -8
  517. package/dist/esm/services/db/shopify-app-installs-db-service.js +47 -47
  518. package/dist/esm/services/db/shopify-products-cache-db-service.d.ts +16 -16
  519. package/dist/esm/services/db/shopify-products-cache-db-service.js +66 -66
  520. package/dist/esm/services/db/subscriptions-db-service.d.ts +10 -10
  521. package/dist/esm/services/db/subscriptions-db-service.js +30 -30
  522. package/dist/esm/services/db/tracking-events-db-service.d.ts +20 -20
  523. package/dist/esm/services/db/tracking-events-db-service.js +161 -161
  524. package/dist/esm/services/eventbridge-integration-service.d.ts +9 -9
  525. package/dist/esm/services/eventbridge-integration-service.js +24 -24
  526. package/dist/esm/services/events/index.d.ts +3 -3
  527. package/dist/esm/services/events/index.js +3 -3
  528. package/dist/esm/services/events/log-event-service.d.ts +19 -19
  529. package/dist/esm/services/events/log-event-service.js +73 -73
  530. package/dist/esm/services/events/metric-event-service.d.ts +9 -9
  531. package/dist/esm/services/events/metric-event-service.js +45 -45
  532. package/dist/esm/services/events/tracking-event-sqs-service.d.ts +8 -8
  533. package/dist/esm/services/events/tracking-event-sqs-service.js +30 -30
  534. package/dist/esm/services/generic-cache-service.d.ts +7 -7
  535. package/dist/esm/services/generic-cache-service.js +29 -29
  536. package/dist/esm/services/index.d.ts +9 -8
  537. package/dist/esm/services/index.js +9 -8
  538. package/dist/esm/services/index.js.map +1 -1
  539. package/dist/esm/services/ipdata-lookup-service.d.ts +20 -20
  540. package/dist/esm/services/ipdata-lookup-service.js +108 -108
  541. package/dist/esm/services/shopify/index.d.ts +2 -2
  542. package/dist/esm/services/shopify/index.js +2 -2
  543. package/dist/esm/services/shopify/products/index.d.ts +1 -1
  544. package/dist/esm/services/shopify/products/index.js +1 -1
  545. package/dist/esm/services/shopify/products/shopify-products-serviceV2.d.ts +17 -17
  546. package/dist/esm/services/shopify/products/shopify-products-serviceV2.js +108 -108
  547. package/dist/esm/services/shopify/shopify-graphql-transformer.d.ts +8 -8
  548. package/dist/esm/services/shopify/shopify-graphql-transformer.js +138 -138
  549. package/dist/esm/types/acuity-types.d.ts +74 -0
  550. package/dist/esm/types/acuity-types.js +2 -0
  551. package/dist/esm/types/acuity-types.js.map +1 -0
  552. package/dist/esm/types/api-response.d.ts +6 -6
  553. package/dist/esm/types/api-response.js +1 -1
  554. package/dist/esm/types/index.d.ts +4 -3
  555. package/dist/esm/types/index.js +4 -3
  556. package/dist/esm/types/index.js.map +1 -1
  557. package/dist/esm/types/internal-events/event-detail-types.d.ts +20 -20
  558. package/dist/esm/types/internal-events/event-detail-types.js +24 -24
  559. package/dist/esm/types/internal-events/index.d.ts +1 -1
  560. package/dist/esm/types/internal-events/index.js +1 -1
  561. package/dist/esm/types/shopify-graphql-types/admin.generated.d.ts +123 -123
  562. package/dist/esm/types/shopify-graphql-types/admin.generated.js +1 -1
  563. package/dist/esm/types/shopify-graphql-types/admin.types.d.ts +26289 -26289
  564. package/dist/esm/types/shopify-graphql-types/admin.types.js +5299 -5299
  565. package/dist/esm/types/shopify-graphql-types/index.d.ts +2 -2
  566. package/dist/esm/types/shopify-graphql-types/index.js +2 -2
  567. package/dist/esm/types/shopify-rest-types.d.ts +767 -767
  568. package/dist/esm/types/shopify-rest-types.js +1 -1
  569. package/dist/esm/utils/compression.d.ts +36 -36
  570. package/dist/esm/utils/compression.js +187 -187
  571. package/dist/esm/utils/index.d.ts +3 -3
  572. package/dist/esm/utils/index.js +3 -3
  573. package/dist/esm/utils/retry-envelope.d.ts +12 -12
  574. package/dist/esm/utils/retry-envelope.js +22 -22
  575. package/dist/esm/utils/size.d.ts +2 -2
  576. package/dist/esm/utils/size.js +44 -44
  577. package/package.json +134 -134
@@ -1,897 +1,897 @@
1
- import { BundledSQSClient } from '../../clients/generic/sqs-bundled-client';
2
- import { CompressionAlgorithm } from '../../clients/generic/sqs-bundled-client.types';
3
- import { createSqsLimits, SQS_1MB } from '../../constants/sqs';
4
- jest.mock('../../helpers/logging-helper', () => ({
5
- Logger: {
6
- debug: jest.fn(),
7
- info: jest.fn(),
8
- warn: jest.fn(),
9
- error: jest.fn(),
10
- },
11
- }));
12
- const actualSizeModule = jest.requireActual('../../utils/size');
13
- const mockSizeInBytes = jest.fn(actualSizeModule.sizeInBytes);
14
- jest.mock('../../utils/size', () => ({
15
- ...jest.requireActual('../../utils/size'),
16
- sizeInBytes: (obj) => mockSizeInBytes(obj),
17
- }));
18
- function makeTestEvent(id, name = 'test', value) {
19
- return { id, name, value };
20
- }
21
- describe('BundledSQSClient', () => {
22
- let mockSqsClient;
23
- let bundledClient;
24
- beforeEach(() => {
25
- mockSqsClient = {
26
- buildAndSendMessagesV2: jest.fn().mockResolvedValue({
27
- successCount: 1,
28
- failedCount: 0,
29
- batchCount: 1,
30
- failedMessages: [],
31
- }),
32
- queueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789/test-queue',
33
- limits: createSqsLimits(SQS_1MB),
34
- };
35
- bundledClient = new BundledSQSClient(mockSqsClient, {
36
- compression: CompressionAlgorithm.ZSTD,
37
- compressionLevel: 3,
38
- });
39
- });
40
- describe('sendBundled', () => {
41
- it('returns empty result for empty items array', async () => {
42
- const result = await bundledClient.sendBundled('test-message', []);
43
- expect(result.totalItems).toBe(0);
44
- expect(result.bundleCount).toBe(0);
45
- expect(result.messageCount).toBe(0);
46
- expect(mockSqsClient.buildAndSendMessagesV2).not.toHaveBeenCalled();
47
- });
48
- it('bundles and sends small items', async () => {
49
- const items = [makeTestEvent('1'), makeTestEvent('2'), makeTestEvent('3')];
50
- const result = await bundledClient.sendBundled('test-message', items);
51
- expect(result.totalItems).toBe(3);
52
- expect(result.bundleCount).toBeGreaterThanOrEqual(1);
53
- expect(mockSqsClient.buildAndSendMessagesV2).toHaveBeenCalledTimes(1);
54
- const sentEnvelopes = mockSqsClient.buildAndSendMessagesV2.mock
55
- .calls[0][1];
56
- expect(sentEnvelopes.length).toBeGreaterThanOrEqual(1);
57
- const envelope = sentEnvelopes[0];
58
- expect(envelope.v).toBe(1);
59
- expect(envelope.c).toBe(CompressionAlgorithm.ZSTD);
60
- expect(envelope.n).toBe(3);
61
- expect(typeof envelope.p).toBe('string');
62
- });
63
- it('tracks compression metrics', async () => {
64
- const items = Array.from({ length: 100 }, (_, i) => makeTestEvent(`event-${i}`, `name-${i}`, i * 100));
65
- const result = await bundledClient.sendBundled('test-message', items);
66
- expect(result.metrics.originalSizeBytes).toBeGreaterThan(0);
67
- expect(result.metrics.finalSizeBytes).toBeGreaterThan(0);
68
- expect(result.metrics.compressionRatio).toBeGreaterThan(1);
69
- expect(result.metrics.compressionTimeMs).toBeGreaterThanOrEqual(0);
70
- });
71
- it('sends items without compression when NONE specified', async () => {
72
- const noCompressionClient = new BundledSQSClient(mockSqsClient, {
73
- compression: CompressionAlgorithm.NONE,
74
- });
75
- const items = [makeTestEvent('1')];
76
- await noCompressionClient.sendBundled('test-message', items);
77
- const sentEnvelopes = mockSqsClient.buildAndSendMessagesV2.mock
78
- .calls[0][1];
79
- const envelope = sentEnvelopes[0];
80
- expect(envelope.c).toBe(CompressionAlgorithm.NONE);
81
- expect(Array.isArray(envelope.p)).toBe(true);
82
- });
83
- it('respects maxItemsPerBundle config', async () => {
84
- const client = new BundledSQSClient(mockSqsClient, {
85
- compression: CompressionAlgorithm.ZSTD,
86
- maxItemsPerBundle: 10,
87
- });
88
- const items = Array.from({ length: 25 }, (_, i) => makeTestEvent(`${i}`));
89
- mockSqsClient.buildAndSendMessagesV2.mockResolvedValue({
90
- successCount: 3,
91
- failedCount: 0,
92
- batchCount: 1,
93
- failedMessages: [],
94
- });
95
- const result = await client.sendBundled('test-message', items);
96
- expect(result.totalItems).toBe(25);
97
- expect(result.bundleCount).toBeGreaterThanOrEqual(2);
98
- const sentEnvelopes = mockSqsClient.buildAndSendMessagesV2.mock
99
- .calls[0][1];
100
- for (const envelope of sentEnvelopes) {
101
- expect(envelope.n).toBeLessThanOrEqual(10);
102
- }
103
- });
104
- it('calculates billable requests based on size', async () => {
105
- const items = Array.from({ length: 50 }, (_, i) => makeTestEvent(`${i}`, 'name', i * 10));
106
- const result = await bundledClient.sendBundled('test-message', items);
107
- expect(result.billableRequests).toBeGreaterThanOrEqual(1);
108
- });
109
- });
110
- describe('retry behavior', () => {
111
- it('retries failed messages', async () => {
112
- mockSqsClient.buildAndSendMessagesV2
113
- .mockResolvedValueOnce({
114
- successCount: 0,
115
- failedCount: 1,
116
- batchCount: 1,
117
- failedMessages: [{ v: 1, c: 'zstd', n: 1, p: 'test' }],
118
- })
119
- .mockResolvedValueOnce({
120
- successCount: 1,
121
- failedCount: 0,
122
- batchCount: 1,
123
- failedMessages: [],
124
- });
125
- const items = [makeTestEvent('1')];
126
- const result = await bundledClient.sendBundled('test-message', items);
127
- expect(mockSqsClient.buildAndSendMessagesV2).toHaveBeenCalledTimes(2);
128
- expect(result.failedBundles).toBe(0);
129
- });
130
- it('stops retrying after max retries', async () => {
131
- mockSqsClient.buildAndSendMessagesV2.mockResolvedValue({
132
- successCount: 0,
133
- failedCount: 1,
134
- batchCount: 1,
135
- failedMessages: [{ v: 1, c: 'zstd', n: 1, p: 'test' }],
136
- });
137
- const items = [makeTestEvent('1')];
138
- const result = await bundledClient.sendBundled('test-message', items);
139
- expect(mockSqsClient.buildAndSendMessagesV2).toHaveBeenCalledTimes(3);
140
- expect(result.failedBundles).toBeGreaterThan(0);
141
- });
142
- });
143
- describe('auto-resize', () => {
144
- it('is enabled by default', () => {
145
- const client = new BundledSQSClient(mockSqsClient);
146
- expect(client.config.autoResize).toBe(true);
147
- });
148
- it('can be configured to be disabled', () => {
149
- const client = new BundledSQSClient(mockSqsClient, {
150
- autoResize: false,
151
- });
152
- expect(client.config.autoResize).toBe(false);
153
- });
154
- });
155
- describe('skippedItems behavior', () => {
156
- afterEach(() => {
157
- mockSizeInBytes.mockImplementation(actualSizeModule.sizeInBytes);
158
- });
159
- it('reports skippedItems as 0 when all items sent successfully', async () => {
160
- const items = [makeTestEvent('1'), makeTestEvent('2')];
161
- const result = await bundledClient.sendBundled('test-message', items);
162
- expect(result.skippedItems).toBe(0);
163
- expect(result.totalItems).toBe(2);
164
- expect(result.failedBundles).toBe(0);
165
- });
166
- it('tracks skippedItems when autoResize disabled and bundle exceeds limit', async () => {
167
- const noResizeClient = new BundledSQSClient(mockSqsClient, {
168
- compression: CompressionAlgorithm.ZSTD,
169
- autoResize: false,
170
- });
171
- const items = [makeTestEvent('1'), makeTestEvent('2'), makeTestEvent('3')];
172
- mockSizeInBytes.mockImplementation((obj) => {
173
- if (typeof obj === 'object' && obj !== null && 'v' in obj) {
174
- return 2000000;
175
- }
176
- return 100;
177
- });
178
- const result = await noResizeClient.sendBundled('test-message', items);
179
- expect(result.skippedItems).toBe(3);
180
- expect(result.totalItems).toBe(3);
181
- expect(mockSqsClient.buildAndSendMessagesV2).not.toHaveBeenCalled();
182
- });
183
- it('tracks skippedItems when single item cannot fit (splitAndRetry terminates)', async () => {
184
- const client = new BundledSQSClient(mockSqsClient, {
185
- compression: CompressionAlgorithm.ZSTD,
186
- autoResize: true,
187
- });
188
- const items = [makeTestEvent('oversized-item')];
189
- mockSizeInBytes.mockImplementation((obj) => {
190
- if (typeof obj === 'object' && obj !== null && 'v' in obj) {
191
- return 2000000;
192
- }
193
- return 100;
194
- });
195
- const result = await client.sendBundled('test-message', items);
196
- expect(result.skippedItems).toBe(1);
197
- expect(result.totalItems).toBe(1);
198
- });
199
- it('reports partial skippedItems when some items are too large', async () => {
200
- const client = new BundledSQSClient(mockSqsClient, {
201
- compression: CompressionAlgorithm.ZSTD,
202
- autoResize: true,
203
- maxItemsPerBundle: 1,
204
- });
205
- const items = [makeTestEvent('small-1'), makeTestEvent('oversized'), makeTestEvent('small-2')];
206
- let envelopeValidationCount = 0;
207
- mockSizeInBytes.mockImplementation((obj) => {
208
- if (typeof obj === 'object' && obj !== null && 'v' in obj) {
209
- envelopeValidationCount++;
210
- if (envelopeValidationCount === 2) {
211
- return 2000000;
212
- }
213
- return 500;
214
- }
215
- return 100;
216
- });
217
- const result = await client.sendBundled('test-message', items);
218
- expect(result.skippedItems).toBe(1);
219
- expect(result.totalItems).toBe(3);
220
- expect(mockSqsClient.buildAndSendMessagesV2).toHaveBeenCalled();
221
- });
222
- it('emptyResult returns skippedItems as 0', async () => {
223
- const result = await bundledClient.sendBundled('test-message', []);
224
- expect(result.skippedItems).toBe(0);
225
- expect(result.totalItems).toBe(0);
226
- });
227
- });
228
- describe('integration: end-to-end bundle flow', () => {
229
- it('produces envelopes that unbundleRecords can process', async () => {
230
- const { unbundleRecords } = await import('../../clients/generic/sqs-unbundle');
231
- const items = [
232
- makeTestEvent('event-1', 'purchase', 99.99),
233
- makeTestEvent('event-2', 'add_to_cart', 29.99),
234
- makeTestEvent('event-3', 'page_view'),
235
- ];
236
- let capturedEnvelope = null;
237
- mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_type, envelopes) => {
238
- capturedEnvelope = envelopes[0];
239
- return {
240
- successCount: envelopes.length,
241
- failedCount: 0,
242
- batchCount: 1,
243
- failedMessages: [],
244
- };
245
- });
246
- await bundledClient.sendBundled('test-message', items);
247
- expect(capturedEnvelope).not.toBeNull();
248
- const sqsRecord = {
249
- messageId: 'msg-1',
250
- body: JSON.stringify({ messageBody: capturedEnvelope }),
251
- };
252
- const { items: unbundledItems, stats } = await unbundleRecords([sqsRecord]);
253
- expect(unbundledItems).toHaveLength(3);
254
- expect(unbundledItems[0]).toEqual({ id: 'event-1', name: 'purchase', value: 99.99 });
255
- expect(unbundledItems[1]).toEqual({ id: 'event-2', name: 'add_to_cart', value: 29.99 });
256
- expect(unbundledItems[2]).toEqual({ id: 'event-3', name: 'page_view' });
257
- expect(stats.bundledSqsRecords).toBe(1);
258
- });
259
- it('maintains compatibility with large bundles', async () => {
260
- const { unbundleRecords } = await import('../../clients/generic/sqs-unbundle');
261
- const items = Array.from({ length: 200 }, (_, i) => makeTestEvent(`event-${i}`, `type-${i % 5}`, i * 1.5));
262
- let capturedEnvelopes = [];
263
- mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_type, envelopes) => {
264
- capturedEnvelopes = envelopes;
265
- return {
266
- successCount: envelopes.length,
267
- failedCount: 0,
268
- batchCount: 1,
269
- failedMessages: [],
270
- };
271
- });
272
- await bundledClient.sendBundled('test-message', items);
273
- const sqsRecords = capturedEnvelopes.map((env, i) => ({
274
- messageId: `msg-${i}`,
275
- body: JSON.stringify({ messageBody: env }),
276
- }));
277
- const { items: unbundledItems } = await unbundleRecords(sqsRecords);
278
- expect(unbundledItems).toHaveLength(200);
279
- expect(unbundledItems[0].id).toBe('event-0');
280
- expect(unbundledItems[199].id).toBe('event-199');
281
- });
282
- });
283
- describe('producer edge cases - real sequences', () => {
284
- it('handles 1000 items with real bundling and verifies envelope structure', async () => {
285
- const { unbundleRecords } = await import('../../clients/generic/sqs-unbundle');
286
- const items = Array.from({ length: 1000 }, (_, i) => ({
287
- id: `item-${i}`,
288
- name: `payload-${i}`,
289
- value: i * 10,
290
- data: `extra-data-${i}`,
291
- }));
292
- const capturedEnvelopes = [];
293
- mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_, envelopes) => {
294
- capturedEnvelopes.push(...envelopes);
295
- return {
296
- successCount: envelopes.length,
297
- failedCount: 0,
298
- batchCount: 1,
299
- failedMessages: [],
300
- };
301
- });
302
- const result = await bundledClient.sendBundled('test', items);
303
- expect(result.totalItems).toBe(1000);
304
- expect(capturedEnvelopes.length).toBeGreaterThan(0);
305
- let totalItemsInEnvelopes = 0;
306
- for (const env of capturedEnvelopes) {
307
- expect(env.v).toBe(1);
308
- expect(env.c).toBe(CompressionAlgorithm.ZSTD);
309
- expect(typeof env.p).toBe('string');
310
- expect(typeof env.n).toBe('number');
311
- expect(env.n).toBeGreaterThan(0);
312
- totalItemsInEnvelopes += env.n;
313
- }
314
- expect(totalItemsInEnvelopes).toBe(1000);
315
- const sqsRecords = capturedEnvelopes.map((env, i) => ({
316
- messageId: `msg-${i}`,
317
- body: JSON.stringify({ messageBody: env }),
318
- }));
319
- const { items: unbundledItems } = await unbundleRecords(sqsRecords);
320
- expect(unbundledItems).toHaveLength(1000);
321
- expect(unbundledItems[0]).toEqual({ id: 'item-0', name: 'payload-0', value: 0, data: 'extra-data-0' });
322
- expect(unbundledItems[999]).toEqual({ id: 'item-999', name: 'payload-999', value: 9990, data: 'extra-data-999' });
323
- });
324
- it('achieves compression effectiveness with repetitive data', async () => {
325
- const { unbundleRecords } = await import('../../clients/generic/sqs-unbundle');
326
- const repetitiveText = 'AAAAAAAAAA'.repeat(100);
327
- const items = Array.from({ length: 100 }, (_, i) => ({
328
- id: `item-${i}`,
329
- name: 'repetitive-test',
330
- value: i,
331
- data: repetitiveText,
332
- }));
333
- const capturedEnvelopes = [];
334
- mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_, envelopes) => {
335
- capturedEnvelopes.push(envelopes[0]);
336
- return {
337
- successCount: envelopes.length,
338
- failedCount: 0,
339
- batchCount: 1,
340
- failedMessages: [],
341
- };
342
- });
343
- const result = await bundledClient.sendBundled('test', items);
344
- expect(result.metrics.compressionRatio).toBeGreaterThan(1);
345
- expect(result.metrics.finalSizeBytes).toBeLessThan(result.metrics.originalSizeBytes);
346
- expect(result.metrics.compressionRatio).toBeGreaterThan(5);
347
- expect(capturedEnvelopes).toHaveLength(1);
348
- const envelope = capturedEnvelopes[0];
349
- expect(typeof envelope.p).toBe('string');
350
- const { items: unbundledItems } = await unbundleRecords([
351
- {
352
- messageId: 'msg-1',
353
- body: JSON.stringify({ messageBody: envelope }),
354
- },
355
- ]);
356
- expect(unbundledItems).toHaveLength(100);
357
- expect(unbundledItems[0].data).toBe(repetitiveText);
358
- expect(unbundledItems[99].data).toBe(repetitiveText);
359
- });
360
- it('handles various item sizes correctly', async () => {
361
- const { unbundleRecords } = await import('../../clients/generic/sqs-unbundle');
362
- const tinyItems = Array.from({ length: 10 }, (_, i) => ({
363
- id: `tiny-${i}`,
364
- name: 't',
365
- }));
366
- const smallItems = Array.from({ length: 10 }, (_, i) => ({
367
- id: `small-${i}`,
368
- name: 'small-item',
369
- value: i,
370
- data: 'x'.repeat(100),
371
- }));
372
- const mediumItems = Array.from({ length: 10 }, (_, i) => ({
373
- id: `medium-${i}`,
374
- name: 'medium-item',
375
- value: i,
376
- data: 'y'.repeat(10000),
377
- }));
378
- const largerItems = Array.from({ length: 5 }, (_, i) => ({
379
- id: `larger-${i}`,
380
- name: 'larger-item',
381
- value: i,
382
- data: 'z'.repeat(100000),
383
- }));
384
- const allItems = [...tinyItems, ...smallItems, ...mediumItems, ...largerItems];
385
- const totalExpectedItems = allItems.length;
386
- const capturedEnvelopes = [];
387
- mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_, envelopes) => {
388
- capturedEnvelopes.push(...envelopes);
389
- return {
390
- successCount: envelopes.length,
391
- failedCount: 0,
392
- batchCount: 1,
393
- failedMessages: [],
394
- };
395
- });
396
- const result = await bundledClient.sendBundled('test', allItems);
397
- expect(result.totalItems).toBe(totalExpectedItems);
398
- expect(result.skippedItems).toBe(0);
399
- const totalInEnvelopes = capturedEnvelopes.reduce((sum, env) => sum + env.n, 0);
400
- expect(totalInEnvelopes).toBe(totalExpectedItems);
401
- const sqsRecords = capturedEnvelopes.map((env, i) => ({
402
- messageId: `msg-${i}`,
403
- body: JSON.stringify({ messageBody: env }),
404
- }));
405
- const { items: unbundledItems } = await unbundleRecords(sqsRecords);
406
- expect(unbundledItems).toHaveLength(totalExpectedItems);
407
- const unbundledTiny = unbundledItems.filter((item) => item.id.startsWith('tiny-'));
408
- const unbundledSmall = unbundledItems.filter((item) => item.id.startsWith('small-'));
409
- const unbundledMedium = unbundledItems.filter((item) => item.id.startsWith('medium-'));
410
- const unbundledLarger = unbundledItems.filter((item) => item.id.startsWith('larger-'));
411
- expect(unbundledTiny).toHaveLength(10);
412
- expect(unbundledSmall).toHaveLength(10);
413
- expect(unbundledMedium).toHaveLength(10);
414
- expect(unbundledLarger).toHaveLength(5);
415
- expect(unbundledSmall[0].data).toBe('x'.repeat(100));
416
- expect(unbundledMedium[0].data).toBe('y'.repeat(10000));
417
- expect(unbundledLarger[0].data).toBe('z'.repeat(100000));
418
- });
419
- it('preserves unicode and special characters through compression', async () => {
420
- const { unbundleRecords } = await import('../../clients/generic/sqs-unbundle');
421
- const unicodeItems = [
422
- {
423
- id: 'emoji',
424
- name: '🎉🚀💯🔥✨',
425
- data: 'Celebration 🎊 with many emojis 🌟⭐💫',
426
- },
427
- {
428
- id: 'chinese',
429
- name: '你好世界',
430
- data: '这是一个测试消息,包含中文字符。北京、上海、广州。',
431
- },
432
- {
433
- id: 'arabic',
434
- name: 'مرحبا بالعالم',
435
- data: 'هذه رسالة اختبار باللغة العربية',
436
- },
437
- {
438
- id: 'japanese',
439
- name: 'こんにちは世界',
440
- data: 'テスト メッセージです。東京、大阪、京都。',
441
- },
442
- {
443
- id: 'korean',
444
- name: '안녕하세요 세계',
445
- data: '테스트 메시지입니다. 서울, 부산, 인천.',
446
- },
447
- {
448
- id: 'special-symbols',
449
- name: '†‡§¶•‰™©®',
450
- data: 'Special: «»‹› ′″ ∞∑∏√∫ ≤≥≠≈ αβγδε',
451
- },
452
- {
453
- id: 'mixed-unicode',
454
- name: 'Mixed: Ñ ü ö ä ß',
455
- data: 'Ümlauts: äöüÄÖÜß — dashes—and–more Ç ñ ¡¿',
456
- },
457
- {
458
- id: 'mathematical',
459
- name: '∀x∈ℝ: x² ≥ 0',
460
- data: 'Math: ∫₀^∞ e^(-x²) dx = √π/2, ∑_{n=1}^{∞} 1/n² = π²/6',
461
- },
462
- {
463
- id: 'currency',
464
- name: '€£¥₹₽¢',
465
- data: 'Prices: $100, €85, £70, ¥10000, ₹8000, ₽7500',
466
- },
467
- {
468
- id: 'newlines-tabs',
469
- name: 'control\tchars',
470
- data: 'Line 1\nLine 2\rLine 3\r\nLine 4\tTabbed',
471
- },
472
- ];
473
- let capturedEnvelope = null;
474
- mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_, envelopes) => {
475
- capturedEnvelope = envelopes[0];
476
- return {
477
- successCount: envelopes.length,
478
- failedCount: 0,
479
- batchCount: 1,
480
- failedMessages: [],
481
- };
482
- });
483
- const result = await bundledClient.sendBundled('test', unicodeItems);
484
- expect(result.totalItems).toBe(unicodeItems.length);
485
- expect(result.skippedItems).toBe(0);
486
- expect(capturedEnvelope).not.toBeNull();
487
- const { items: unbundledItems } = await unbundleRecords([
488
- {
489
- messageId: 'msg-1',
490
- body: JSON.stringify({ messageBody: capturedEnvelope }),
491
- },
492
- ]);
493
- expect(unbundledItems).toHaveLength(unicodeItems.length);
494
- for (let i = 0; i < unicodeItems.length; i++) {
495
- expect(unbundledItems[i]).toEqual(unicodeItems[i]);
496
- }
497
- const emojiItem = unbundledItems.find((item) => item.id === 'emoji');
498
- expect(emojiItem?.name).toBe('🎉🚀💯🔥✨');
499
- const chineseItem = unbundledItems.find((item) => item.id === 'chinese');
500
- expect(chineseItem?.name).toBe('你好世界');
501
- const arabicItem = unbundledItems.find((item) => item.id === 'arabic');
502
- expect(arabicItem?.name).toBe('مرحبا بالعالم');
503
- });
504
- it('preserves deeply nested object structures', async () => {
505
- const { unbundleRecords } = await import('../../clients/generic/sqs-unbundle');
506
- const nestedItems = [
507
- {
508
- id: 'deep-nested',
509
- name: 'deep',
510
- nested: {
511
- level1: {
512
- level2: {
513
- level3: {
514
- level4: {
515
- level5: {
516
- level6: {
517
- value: 'deep-value',
518
- number: 12345,
519
- boolean: true,
520
- },
521
- },
522
- },
523
- },
524
- },
525
- },
526
- },
527
- },
528
- {
529
- id: 'array-of-objects',
530
- name: 'arrays',
531
- items: [
532
- { subId: 'sub-1', data: { nested: { value: 'a' } } },
533
- { subId: 'sub-2', data: { nested: { value: 'b' } } },
534
- { subId: 'sub-3', data: { nested: { value: 'c' } } },
535
- ],
536
- },
537
- {
538
- id: 'mixed-structures',
539
- name: 'mixed',
540
- config: {
541
- settings: {
542
- features: ['feature-1', 'feature-2', 'feature-3'],
543
- metadata: {
544
- tags: {
545
- primary: ['tag-a', 'tag-b'],
546
- secondary: {
547
- group1: ['tag-c', 'tag-d'],
548
- group2: ['tag-e', 'tag-f'],
549
- },
550
- },
551
- },
552
- },
553
- },
554
- },
555
- {
556
- id: 'null-undefined-handling',
557
- name: 'nulls',
558
- value: null,
559
- nested: {
560
- nullField: null,
561
- emptyObject: {},
562
- emptyArray: [],
563
- zeroValue: 0,
564
- falseValue: false,
565
- emptyString: '',
566
- },
567
- },
568
- {
569
- id: 'large-array',
570
- name: 'large-array',
571
- items: Array.from({ length: 100 }, (_, i) => ({
572
- index: i,
573
- data: `item-${i}`,
574
- nested: { level: 1, value: i * 2 },
575
- })),
576
- },
577
- ];
578
- let capturedEnvelope = null;
579
- mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_, envelopes) => {
580
- capturedEnvelope = envelopes[0];
581
- return {
582
- successCount: envelopes.length,
583
- failedCount: 0,
584
- batchCount: 1,
585
- failedMessages: [],
586
- };
587
- });
588
- const genericClient = new BundledSQSClient(mockSqsClient, {
589
- compression: CompressionAlgorithm.ZSTD,
590
- });
591
- const result = await genericClient.sendBundled('test', nestedItems);
592
- expect(result.totalItems).toBe(nestedItems.length);
593
- expect(result.skippedItems).toBe(0);
594
- const { items: unbundledItems } = await unbundleRecords([
595
- {
596
- messageId: 'msg-1',
597
- body: JSON.stringify({ messageBody: capturedEnvelope }),
598
- },
599
- ]);
600
- expect(unbundledItems).toHaveLength(nestedItems.length);
601
- const deepItem = unbundledItems.find((item) => item.id === 'deep-nested');
602
- expect(deepItem?.nested?.level1?.level2?.level3?.level4?.level5?.level6?.value).toBe('deep-value');
603
- expect(deepItem?.nested?.level1?.level2?.level3?.level4?.level5?.level6?.number).toBe(12345);
604
- expect(deepItem?.nested?.level1?.level2?.level3?.level4?.level5?.level6?.boolean).toBe(true);
605
- const arrayItem = unbundledItems.find((item) => item.id === 'array-of-objects');
606
- expect(arrayItem?.items?.length).toBe(3);
607
- expect(arrayItem?.items?.[0]?.data?.nested?.value).toBe('a');
608
- expect(arrayItem?.items?.[2]?.data?.nested?.value).toBe('c');
609
- const mixedItem = unbundledItems.find((item) => item.id === 'mixed-structures');
610
- expect(mixedItem?.config?.settings?.features).toEqual(['feature-1', 'feature-2', 'feature-3']);
611
- expect(mixedItem?.config?.settings?.metadata?.tags?.secondary?.group2).toEqual(['tag-e', 'tag-f']);
612
- const nullItem = unbundledItems.find((item) => item.id === 'null-undefined-handling');
613
- expect(nullItem?.value).toBeNull();
614
- expect(nullItem?.nested?.nullField).toBeNull();
615
- expect(nullItem?.nested?.emptyObject).toEqual({});
616
- expect(nullItem?.nested?.emptyArray).toEqual([]);
617
- expect(nullItem?.nested?.zeroValue).toBe(0);
618
- expect(nullItem?.nested?.falseValue).toBe(false);
619
- expect(nullItem?.nested?.emptyString).toBe('');
620
- const largeArrayItem = unbundledItems.find((item) => item.id === 'large-array');
621
- expect(largeArrayItem?.items).toHaveLength(100);
622
- expect(largeArrayItem?.items?.[0]).toEqual({ index: 0, data: 'item-0', nested: { level: 1, value: 0 } });
623
- expect(largeArrayItem?.items?.[99]).toEqual({ index: 99, data: 'item-99', nested: { level: 1, value: 198 } });
624
- });
625
- it('handles exactly 1 item', async () => {
626
- const { unbundleRecords } = await import('../../clients/generic/sqs-unbundle');
627
- const items = [{ id: 'single', name: 'only-one', value: 42 }];
628
- const capturedEnvelopes = [];
629
- mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_, envelopes) => {
630
- capturedEnvelopes.push(envelopes[0]);
631
- return {
632
- successCount: envelopes.length,
633
- failedCount: 0,
634
- batchCount: 1,
635
- failedMessages: [],
636
- };
637
- });
638
- const result = await bundledClient.sendBundled('test', items);
639
- expect(result.totalItems).toBe(1);
640
- expect(result.bundleCount).toBe(1);
641
- expect(result.messageCount).toBe(1);
642
- expect(capturedEnvelopes).toHaveLength(1);
643
- expect(capturedEnvelopes[0].n).toBe(1);
644
- const { items: unbundledItems } = await unbundleRecords([
645
- {
646
- messageId: 'msg-1',
647
- body: JSON.stringify({ messageBody: capturedEnvelopes[0] }),
648
- },
649
- ]);
650
- expect(unbundledItems).toHaveLength(1);
651
- expect(unbundledItems[0]).toEqual(items[0]);
652
- });
653
- it('handles exactly maxItemsPerBundle items', async () => {
654
- const { unbundleRecords } = await import('../../clients/generic/sqs-unbundle');
655
- const maxItems = 50;
656
- const client = new BundledSQSClient(mockSqsClient, {
657
- compression: CompressionAlgorithm.ZSTD,
658
- maxItemsPerBundle: maxItems,
659
- });
660
- const items = Array.from({ length: maxItems }, (_, i) => ({
661
- id: `item-${i}`,
662
- name: `name-${i}`,
663
- value: i,
664
- }));
665
- const capturedEnvelopes = [];
666
- mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_, envelopes) => {
667
- capturedEnvelopes.push(...envelopes);
668
- return {
669
- successCount: envelopes.length,
670
- failedCount: 0,
671
- batchCount: 1,
672
- failedMessages: [],
673
- };
674
- });
675
- const result = await client.sendBundled('test', items);
676
- expect(result.totalItems).toBe(maxItems);
677
- expect(capturedEnvelopes).toHaveLength(1);
678
- expect(capturedEnvelopes[0].n).toBe(maxItems);
679
- const sqsRecords = capturedEnvelopes.map((env, i) => ({
680
- messageId: `msg-${i}`,
681
- body: JSON.stringify({ messageBody: env }),
682
- }));
683
- const { items: unbundledItems } = await unbundleRecords(sqsRecords);
684
- expect(unbundledItems).toHaveLength(maxItems);
685
- });
686
- it('handles maxItemsPerBundle + 1 items (splits correctly)', async () => {
687
- const { unbundleRecords } = await import('../../clients/generic/sqs-unbundle');
688
- const maxItems = 50;
689
- const client = new BundledSQSClient(mockSqsClient, {
690
- compression: CompressionAlgorithm.ZSTD,
691
- maxItemsPerBundle: maxItems,
692
- });
693
- const items = Array.from({ length: maxItems + 1 }, (_, i) => ({
694
- id: `item-${i}`,
695
- name: `name-${i}`,
696
- value: i,
697
- }));
698
- const capturedEnvelopes = [];
699
- mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_, envelopes) => {
700
- capturedEnvelopes.push(...envelopes);
701
- return {
702
- successCount: envelopes.length,
703
- failedCount: 0,
704
- batchCount: 1,
705
- failedMessages: [],
706
- };
707
- });
708
- const result = await client.sendBundled('test', items);
709
- expect(result.totalItems).toBe(maxItems + 1);
710
- expect(capturedEnvelopes).toHaveLength(2);
711
- expect(capturedEnvelopes[0].n).toBe(maxItems);
712
- expect(capturedEnvelopes[1].n).toBe(1);
713
- const sqsRecords = capturedEnvelopes.map((env, i) => ({
714
- messageId: `msg-${i}`,
715
- body: JSON.stringify({ messageBody: env }),
716
- }));
717
- const { items: unbundledItems } = await unbundleRecords(sqsRecords);
718
- expect(unbundledItems).toHaveLength(maxItems + 1);
719
- });
720
- it('handles uncompressed mode (NONE) with various data', async () => {
721
- const { unbundleRecords } = await import('../../clients/generic/sqs-unbundle');
722
- const noCompressionClient = new BundledSQSClient(mockSqsClient, {
723
- compression: CompressionAlgorithm.NONE,
724
- });
725
- const items = Array.from({ length: 50 }, (_, i) => ({
726
- id: `uncompressed-${i}`,
727
- name: `test-${i}`,
728
- value: i * 100,
729
- data: `Some data for item ${i}`,
730
- }));
731
- const capturedEnvelopes = [];
732
- mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_, envelopes) => {
733
- capturedEnvelopes.push(envelopes[0]);
734
- return {
735
- successCount: envelopes.length,
736
- failedCount: 0,
737
- batchCount: 1,
738
- failedMessages: [],
739
- };
740
- });
741
- const result = await noCompressionClient.sendBundled('test', items);
742
- expect(result.totalItems).toBe(50);
743
- expect(capturedEnvelopes).toHaveLength(1);
744
- const envelope = capturedEnvelopes[0];
745
- expect(envelope.c).toBe(CompressionAlgorithm.NONE);
746
- expect(Array.isArray(envelope.p)).toBe(true);
747
- expect(envelope.p.length).toBe(50);
748
- expect(result.metrics.compressionRatio).toBeCloseTo(1, 0);
749
- const { items: unbundledItems } = await unbundleRecords([
750
- {
751
- messageId: 'msg-1',
752
- body: JSON.stringify({ messageBody: envelope }),
753
- },
754
- ]);
755
- expect(unbundledItems).toHaveLength(50);
756
- expect(unbundledItems[0]).toEqual(items[0]);
757
- expect(unbundledItems[49]).toEqual(items[49]);
758
- });
759
- it('produces correct metrics for a large mixed batch', async () => {
760
- const items = Array.from({ length: 500 }, (_, i) => ({
761
- id: `metric-test-${i}`,
762
- name: `item-${i % 10}`,
763
- value: i,
764
- data: i % 5 === 0 ? 'repeated-data'.repeat(100) : `unique-${i}`,
765
- }));
766
- const capturedEnvelopes = [];
767
- mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_, envelopes) => {
768
- capturedEnvelopes.push(...envelopes);
769
- return {
770
- successCount: envelopes.length,
771
- failedCount: 0,
772
- batchCount: 1,
773
- failedMessages: [],
774
- };
775
- });
776
- const result = await bundledClient.sendBundled('test', items);
777
- expect(result.totalItems).toBe(500);
778
- expect(result.metrics.originalSizeBytes).toBeGreaterThan(0);
779
- expect(result.metrics.finalSizeBytes).toBeGreaterThan(0);
780
- expect(result.metrics.compressionRatio).toBeGreaterThan(1);
781
- expect(result.metrics.compressionTimeMs).toBeGreaterThanOrEqual(0);
782
- expect(result.metrics.avgItemsPerBundle).toBeGreaterThan(0);
783
- const expectedAvg = items.length / capturedEnvelopes.length;
784
- expect(result.metrics.avgItemsPerBundle).toBeCloseTo(expectedAvg, 1);
785
- expect(result.billableRequests).toBeGreaterThanOrEqual(1);
786
- });
787
- it('handles items with special JSON edge cases', async () => {
788
- const { unbundleRecords } = await import('../../clients/generic/sqs-unbundle');
789
- const edgeCaseItems = [
790
- {
791
- id: 'large-numbers',
792
- name: 'numbers',
793
- bigInt: 9007199254740991,
794
- scientific: 1.23e10,
795
- decimal: 0.1 + 0.2,
796
- negative: -999999,
797
- zero: 0,
798
- negativeZero: -0,
799
- },
800
- {
801
- id: 'special-strings',
802
- name: 'strings',
803
- data: JSON.stringify({ nested: 'json' }),
804
- withQuotes: 'He said "hello"',
805
- withBackslash: 'path\\to\\file',
806
- withSlash: 'https://example.com/path',
807
- },
808
- {
809
- id: 'boolean-variants',
810
- name: 'booleans',
811
- trueVal: true,
812
- falseVal: false,
813
- truthy: 1,
814
- falsy: 0,
815
- },
816
- {
817
- id: 'dates-as-strings',
818
- name: 'dates',
819
- isoDate: '2024-01-15T10:30:00.000Z',
820
- timestamp: 1705315800000,
821
- },
822
- ];
823
- let capturedEnvelope = null;
824
- mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_, envelopes) => {
825
- capturedEnvelope = envelopes[0];
826
- return {
827
- successCount: envelopes.length,
828
- failedCount: 0,
829
- batchCount: 1,
830
- failedMessages: [],
831
- };
832
- });
833
- const genericClient = new BundledSQSClient(mockSqsClient, {
834
- compression: CompressionAlgorithm.ZSTD,
835
- });
836
- const result = await genericClient.sendBundled('test', edgeCaseItems);
837
- expect(result.totalItems).toBe(edgeCaseItems.length);
838
- expect(result.skippedItems).toBe(0);
839
- const { items: unbundledItems } = await unbundleRecords([
840
- {
841
- messageId: 'msg-1',
842
- body: JSON.stringify({ messageBody: capturedEnvelope }),
843
- },
844
- ]);
845
- expect(unbundledItems).toHaveLength(edgeCaseItems.length);
846
- const numbersItem = unbundledItems.find((item) => item.id === 'large-numbers');
847
- expect(numbersItem?.bigInt).toBe(9007199254740991);
848
- expect(numbersItem?.scientific).toBe(1.23e10);
849
- expect(numbersItem?.negative).toBe(-999999);
850
- expect(numbersItem?.zero).toBe(0);
851
- const stringsItem = unbundledItems.find((item) => item.id === 'special-strings');
852
- expect(stringsItem?.data).toBe(JSON.stringify({ nested: 'json' }));
853
- expect(stringsItem?.withQuotes).toBe('He said "hello"');
854
- expect(stringsItem?.withBackslash).toBe('path\\to\\file');
855
- const booleansItem = unbundledItems.find((item) => item.id === 'boolean-variants');
856
- expect(booleansItem?.trueVal).toBe(true);
857
- expect(booleansItem?.falseVal).toBe(false);
858
- });
859
- it('handles concurrent sends without interference', async () => {
860
- const batch1 = Array.from({ length: 100 }, (_, i) => ({
861
- id: `batch1-${i}`,
862
- name: 'batch-one',
863
- value: i,
864
- }));
865
- const batch2 = Array.from({ length: 100 }, (_, i) => ({
866
- id: `batch2-${i}`,
867
- name: 'batch-two',
868
- value: i * 2,
869
- }));
870
- const batch3 = Array.from({ length: 100 }, (_, i) => ({
871
- id: `batch3-${i}`,
872
- name: 'batch-three',
873
- value: i * 3,
874
- }));
875
- mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_, envelopes) => {
876
- return {
877
- successCount: envelopes.length,
878
- failedCount: 0,
879
- batchCount: 1,
880
- failedMessages: [],
881
- };
882
- });
883
- const [result1, result2, result3] = await Promise.all([
884
- bundledClient.sendBundled('test', batch1),
885
- bundledClient.sendBundled('test', batch2),
886
- bundledClient.sendBundled('test', batch3),
887
- ]);
888
- expect(result1.totalItems).toBe(100);
889
- expect(result2.totalItems).toBe(100);
890
- expect(result3.totalItems).toBe(100);
891
- expect(result1.skippedItems).toBe(0);
892
- expect(result2.skippedItems).toBe(0);
893
- expect(result3.skippedItems).toBe(0);
894
- });
895
- });
896
- });
1
+ import { BundledSQSClient } from '../../clients/generic/sqs-bundled-client';
2
+ import { CompressionAlgorithm } from '../../clients/generic/sqs-bundled-client.types';
3
+ import { createSqsLimits, SQS_1MB } from '../../constants/sqs';
4
+ jest.mock('../../helpers/logging-helper', () => ({
5
+ Logger: {
6
+ debug: jest.fn(),
7
+ info: jest.fn(),
8
+ warn: jest.fn(),
9
+ error: jest.fn(),
10
+ },
11
+ }));
12
+ const actualSizeModule = jest.requireActual('../../utils/size');
13
+ const mockSizeInBytes = jest.fn(actualSizeModule.sizeInBytes);
14
+ jest.mock('../../utils/size', () => ({
15
+ ...jest.requireActual('../../utils/size'),
16
+ sizeInBytes: (obj) => mockSizeInBytes(obj),
17
+ }));
18
+ function makeTestEvent(id, name = 'test', value) {
19
+ return { id, name, value };
20
+ }
21
+ describe('BundledSQSClient', () => {
22
+ let mockSqsClient;
23
+ let bundledClient;
24
+ beforeEach(() => {
25
+ mockSqsClient = {
26
+ buildAndSendMessagesV2: jest.fn().mockResolvedValue({
27
+ successCount: 1,
28
+ failedCount: 0,
29
+ batchCount: 1,
30
+ failedMessages: [],
31
+ }),
32
+ queueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789/test-queue',
33
+ limits: createSqsLimits(SQS_1MB),
34
+ };
35
+ bundledClient = new BundledSQSClient(mockSqsClient, {
36
+ compression: CompressionAlgorithm.ZSTD,
37
+ compressionLevel: 3,
38
+ });
39
+ });
40
+ describe('sendBundled', () => {
41
+ it('returns empty result for empty items array', async () => {
42
+ const result = await bundledClient.sendBundled('test-message', []);
43
+ expect(result.totalItems).toBe(0);
44
+ expect(result.bundleCount).toBe(0);
45
+ expect(result.messageCount).toBe(0);
46
+ expect(mockSqsClient.buildAndSendMessagesV2).not.toHaveBeenCalled();
47
+ });
48
+ it('bundles and sends small items', async () => {
49
+ const items = [makeTestEvent('1'), makeTestEvent('2'), makeTestEvent('3')];
50
+ const result = await bundledClient.sendBundled('test-message', items);
51
+ expect(result.totalItems).toBe(3);
52
+ expect(result.bundleCount).toBeGreaterThanOrEqual(1);
53
+ expect(mockSqsClient.buildAndSendMessagesV2).toHaveBeenCalledTimes(1);
54
+ const sentEnvelopes = mockSqsClient.buildAndSendMessagesV2.mock
55
+ .calls[0][1];
56
+ expect(sentEnvelopes.length).toBeGreaterThanOrEqual(1);
57
+ const envelope = sentEnvelopes[0];
58
+ expect(envelope.v).toBe(1);
59
+ expect(envelope.c).toBe(CompressionAlgorithm.ZSTD);
60
+ expect(envelope.n).toBe(3);
61
+ expect(typeof envelope.p).toBe('string');
62
+ });
63
+ it('tracks compression metrics', async () => {
64
+ const items = Array.from({ length: 100 }, (_, i) => makeTestEvent(`event-${i}`, `name-${i}`, i * 100));
65
+ const result = await bundledClient.sendBundled('test-message', items);
66
+ expect(result.metrics.originalSizeBytes).toBeGreaterThan(0);
67
+ expect(result.metrics.finalSizeBytes).toBeGreaterThan(0);
68
+ expect(result.metrics.compressionRatio).toBeGreaterThan(1);
69
+ expect(result.metrics.compressionTimeMs).toBeGreaterThanOrEqual(0);
70
+ });
71
+ it('sends items without compression when NONE specified', async () => {
72
+ const noCompressionClient = new BundledSQSClient(mockSqsClient, {
73
+ compression: CompressionAlgorithm.NONE,
74
+ });
75
+ const items = [makeTestEvent('1')];
76
+ await noCompressionClient.sendBundled('test-message', items);
77
+ const sentEnvelopes = mockSqsClient.buildAndSendMessagesV2.mock
78
+ .calls[0][1];
79
+ const envelope = sentEnvelopes[0];
80
+ expect(envelope.c).toBe(CompressionAlgorithm.NONE);
81
+ expect(Array.isArray(envelope.p)).toBe(true);
82
+ });
83
+ it('respects maxItemsPerBundle config', async () => {
84
+ const client = new BundledSQSClient(mockSqsClient, {
85
+ compression: CompressionAlgorithm.ZSTD,
86
+ maxItemsPerBundle: 10,
87
+ });
88
+ const items = Array.from({ length: 25 }, (_, i) => makeTestEvent(`${i}`));
89
+ mockSqsClient.buildAndSendMessagesV2.mockResolvedValue({
90
+ successCount: 3,
91
+ failedCount: 0,
92
+ batchCount: 1,
93
+ failedMessages: [],
94
+ });
95
+ const result = await client.sendBundled('test-message', items);
96
+ expect(result.totalItems).toBe(25);
97
+ expect(result.bundleCount).toBeGreaterThanOrEqual(2);
98
+ const sentEnvelopes = mockSqsClient.buildAndSendMessagesV2.mock
99
+ .calls[0][1];
100
+ for (const envelope of sentEnvelopes) {
101
+ expect(envelope.n).toBeLessThanOrEqual(10);
102
+ }
103
+ });
104
+ it('calculates billable requests based on size', async () => {
105
+ const items = Array.from({ length: 50 }, (_, i) => makeTestEvent(`${i}`, 'name', i * 10));
106
+ const result = await bundledClient.sendBundled('test-message', items);
107
+ expect(result.billableRequests).toBeGreaterThanOrEqual(1);
108
+ });
109
+ });
110
+ describe('retry behavior', () => {
111
+ it('retries failed messages', async () => {
112
+ mockSqsClient.buildAndSendMessagesV2
113
+ .mockResolvedValueOnce({
114
+ successCount: 0,
115
+ failedCount: 1,
116
+ batchCount: 1,
117
+ failedMessages: [{ v: 1, c: 'zstd', n: 1, p: 'test' }],
118
+ })
119
+ .mockResolvedValueOnce({
120
+ successCount: 1,
121
+ failedCount: 0,
122
+ batchCount: 1,
123
+ failedMessages: [],
124
+ });
125
+ const items = [makeTestEvent('1')];
126
+ const result = await bundledClient.sendBundled('test-message', items);
127
+ expect(mockSqsClient.buildAndSendMessagesV2).toHaveBeenCalledTimes(2);
128
+ expect(result.failedBundles).toBe(0);
129
+ });
130
+ it('stops retrying after max retries', async () => {
131
+ mockSqsClient.buildAndSendMessagesV2.mockResolvedValue({
132
+ successCount: 0,
133
+ failedCount: 1,
134
+ batchCount: 1,
135
+ failedMessages: [{ v: 1, c: 'zstd', n: 1, p: 'test' }],
136
+ });
137
+ const items = [makeTestEvent('1')];
138
+ const result = await bundledClient.sendBundled('test-message', items);
139
+ expect(mockSqsClient.buildAndSendMessagesV2).toHaveBeenCalledTimes(3);
140
+ expect(result.failedBundles).toBeGreaterThan(0);
141
+ });
142
+ });
143
+ describe('auto-resize', () => {
144
+ it('is enabled by default', () => {
145
+ const client = new BundledSQSClient(mockSqsClient);
146
+ expect(client.config.autoResize).toBe(true);
147
+ });
148
+ it('can be configured to be disabled', () => {
149
+ const client = new BundledSQSClient(mockSqsClient, {
150
+ autoResize: false,
151
+ });
152
+ expect(client.config.autoResize).toBe(false);
153
+ });
154
+ });
155
+ describe('skippedItems behavior', () => {
156
+ afterEach(() => {
157
+ mockSizeInBytes.mockImplementation(actualSizeModule.sizeInBytes);
158
+ });
159
+ it('reports skippedItems as 0 when all items sent successfully', async () => {
160
+ const items = [makeTestEvent('1'), makeTestEvent('2')];
161
+ const result = await bundledClient.sendBundled('test-message', items);
162
+ expect(result.skippedItems).toBe(0);
163
+ expect(result.totalItems).toBe(2);
164
+ expect(result.failedBundles).toBe(0);
165
+ });
166
+ it('tracks skippedItems when autoResize disabled and bundle exceeds limit', async () => {
167
+ const noResizeClient = new BundledSQSClient(mockSqsClient, {
168
+ compression: CompressionAlgorithm.ZSTD,
169
+ autoResize: false,
170
+ });
171
+ const items = [makeTestEvent('1'), makeTestEvent('2'), makeTestEvent('3')];
172
+ mockSizeInBytes.mockImplementation((obj) => {
173
+ if (typeof obj === 'object' && obj !== null && 'v' in obj) {
174
+ return 2000000;
175
+ }
176
+ return 100;
177
+ });
178
+ const result = await noResizeClient.sendBundled('test-message', items);
179
+ expect(result.skippedItems).toBe(3);
180
+ expect(result.totalItems).toBe(3);
181
+ expect(mockSqsClient.buildAndSendMessagesV2).not.toHaveBeenCalled();
182
+ });
183
+ it('tracks skippedItems when single item cannot fit (splitAndRetry terminates)', async () => {
184
+ const client = new BundledSQSClient(mockSqsClient, {
185
+ compression: CompressionAlgorithm.ZSTD,
186
+ autoResize: true,
187
+ });
188
+ const items = [makeTestEvent('oversized-item')];
189
+ mockSizeInBytes.mockImplementation((obj) => {
190
+ if (typeof obj === 'object' && obj !== null && 'v' in obj) {
191
+ return 2000000;
192
+ }
193
+ return 100;
194
+ });
195
+ const result = await client.sendBundled('test-message', items);
196
+ expect(result.skippedItems).toBe(1);
197
+ expect(result.totalItems).toBe(1);
198
+ });
199
+ it('reports partial skippedItems when some items are too large', async () => {
200
+ const client = new BundledSQSClient(mockSqsClient, {
201
+ compression: CompressionAlgorithm.ZSTD,
202
+ autoResize: true,
203
+ maxItemsPerBundle: 1,
204
+ });
205
+ const items = [makeTestEvent('small-1'), makeTestEvent('oversized'), makeTestEvent('small-2')];
206
+ let envelopeValidationCount = 0;
207
+ mockSizeInBytes.mockImplementation((obj) => {
208
+ if (typeof obj === 'object' && obj !== null && 'v' in obj) {
209
+ envelopeValidationCount++;
210
+ if (envelopeValidationCount === 2) {
211
+ return 2000000;
212
+ }
213
+ return 500;
214
+ }
215
+ return 100;
216
+ });
217
+ const result = await client.sendBundled('test-message', items);
218
+ expect(result.skippedItems).toBe(1);
219
+ expect(result.totalItems).toBe(3);
220
+ expect(mockSqsClient.buildAndSendMessagesV2).toHaveBeenCalled();
221
+ });
222
+ it('emptyResult returns skippedItems as 0', async () => {
223
+ const result = await bundledClient.sendBundled('test-message', []);
224
+ expect(result.skippedItems).toBe(0);
225
+ expect(result.totalItems).toBe(0);
226
+ });
227
+ });
228
+ describe('integration: end-to-end bundle flow', () => {
229
+ it('produces envelopes that unbundleRecords can process', async () => {
230
+ const { unbundleRecords } = await import('../../clients/generic/sqs-unbundle');
231
+ const items = [
232
+ makeTestEvent('event-1', 'purchase', 99.99),
233
+ makeTestEvent('event-2', 'add_to_cart', 29.99),
234
+ makeTestEvent('event-3', 'page_view'),
235
+ ];
236
+ let capturedEnvelope = null;
237
+ mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_type, envelopes) => {
238
+ capturedEnvelope = envelopes[0];
239
+ return {
240
+ successCount: envelopes.length,
241
+ failedCount: 0,
242
+ batchCount: 1,
243
+ failedMessages: [],
244
+ };
245
+ });
246
+ await bundledClient.sendBundled('test-message', items);
247
+ expect(capturedEnvelope).not.toBeNull();
248
+ const sqsRecord = {
249
+ messageId: 'msg-1',
250
+ body: JSON.stringify({ messageBody: capturedEnvelope }),
251
+ };
252
+ const { items: unbundledItems, stats } = await unbundleRecords([sqsRecord]);
253
+ expect(unbundledItems).toHaveLength(3);
254
+ expect(unbundledItems[0]).toEqual({ id: 'event-1', name: 'purchase', value: 99.99 });
255
+ expect(unbundledItems[1]).toEqual({ id: 'event-2', name: 'add_to_cart', value: 29.99 });
256
+ expect(unbundledItems[2]).toEqual({ id: 'event-3', name: 'page_view' });
257
+ expect(stats.bundledSqsRecords).toBe(1);
258
+ });
259
+ it('maintains compatibility with large bundles', async () => {
260
+ const { unbundleRecords } = await import('../../clients/generic/sqs-unbundle');
261
+ const items = Array.from({ length: 200 }, (_, i) => makeTestEvent(`event-${i}`, `type-${i % 5}`, i * 1.5));
262
+ let capturedEnvelopes = [];
263
+ mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_type, envelopes) => {
264
+ capturedEnvelopes = envelopes;
265
+ return {
266
+ successCount: envelopes.length,
267
+ failedCount: 0,
268
+ batchCount: 1,
269
+ failedMessages: [],
270
+ };
271
+ });
272
+ await bundledClient.sendBundled('test-message', items);
273
+ const sqsRecords = capturedEnvelopes.map((env, i) => ({
274
+ messageId: `msg-${i}`,
275
+ body: JSON.stringify({ messageBody: env }),
276
+ }));
277
+ const { items: unbundledItems } = await unbundleRecords(sqsRecords);
278
+ expect(unbundledItems).toHaveLength(200);
279
+ expect(unbundledItems[0].id).toBe('event-0');
280
+ expect(unbundledItems[199].id).toBe('event-199');
281
+ });
282
+ });
283
+ describe('producer edge cases - real sequences', () => {
284
+ it('handles 1000 items with real bundling and verifies envelope structure', async () => {
285
+ const { unbundleRecords } = await import('../../clients/generic/sqs-unbundle');
286
+ const items = Array.from({ length: 1000 }, (_, i) => ({
287
+ id: `item-${i}`,
288
+ name: `payload-${i}`,
289
+ value: i * 10,
290
+ data: `extra-data-${i}`,
291
+ }));
292
+ const capturedEnvelopes = [];
293
+ mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_, envelopes) => {
294
+ capturedEnvelopes.push(...envelopes);
295
+ return {
296
+ successCount: envelopes.length,
297
+ failedCount: 0,
298
+ batchCount: 1,
299
+ failedMessages: [],
300
+ };
301
+ });
302
+ const result = await bundledClient.sendBundled('test', items);
303
+ expect(result.totalItems).toBe(1000);
304
+ expect(capturedEnvelopes.length).toBeGreaterThan(0);
305
+ let totalItemsInEnvelopes = 0;
306
+ for (const env of capturedEnvelopes) {
307
+ expect(env.v).toBe(1);
308
+ expect(env.c).toBe(CompressionAlgorithm.ZSTD);
309
+ expect(typeof env.p).toBe('string');
310
+ expect(typeof env.n).toBe('number');
311
+ expect(env.n).toBeGreaterThan(0);
312
+ totalItemsInEnvelopes += env.n;
313
+ }
314
+ expect(totalItemsInEnvelopes).toBe(1000);
315
+ const sqsRecords = capturedEnvelopes.map((env, i) => ({
316
+ messageId: `msg-${i}`,
317
+ body: JSON.stringify({ messageBody: env }),
318
+ }));
319
+ const { items: unbundledItems } = await unbundleRecords(sqsRecords);
320
+ expect(unbundledItems).toHaveLength(1000);
321
+ expect(unbundledItems[0]).toEqual({ id: 'item-0', name: 'payload-0', value: 0, data: 'extra-data-0' });
322
+ expect(unbundledItems[999]).toEqual({ id: 'item-999', name: 'payload-999', value: 9990, data: 'extra-data-999' });
323
+ });
324
+ it('achieves compression effectiveness with repetitive data', async () => {
325
+ const { unbundleRecords } = await import('../../clients/generic/sqs-unbundle');
326
+ const repetitiveText = 'AAAAAAAAAA'.repeat(100);
327
+ const items = Array.from({ length: 100 }, (_, i) => ({
328
+ id: `item-${i}`,
329
+ name: 'repetitive-test',
330
+ value: i,
331
+ data: repetitiveText,
332
+ }));
333
+ const capturedEnvelopes = [];
334
+ mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_, envelopes) => {
335
+ capturedEnvelopes.push(envelopes[0]);
336
+ return {
337
+ successCount: envelopes.length,
338
+ failedCount: 0,
339
+ batchCount: 1,
340
+ failedMessages: [],
341
+ };
342
+ });
343
+ const result = await bundledClient.sendBundled('test', items);
344
+ expect(result.metrics.compressionRatio).toBeGreaterThan(1);
345
+ expect(result.metrics.finalSizeBytes).toBeLessThan(result.metrics.originalSizeBytes);
346
+ expect(result.metrics.compressionRatio).toBeGreaterThan(5);
347
+ expect(capturedEnvelopes).toHaveLength(1);
348
+ const envelope = capturedEnvelopes[0];
349
+ expect(typeof envelope.p).toBe('string');
350
+ const { items: unbundledItems } = await unbundleRecords([
351
+ {
352
+ messageId: 'msg-1',
353
+ body: JSON.stringify({ messageBody: envelope }),
354
+ },
355
+ ]);
356
+ expect(unbundledItems).toHaveLength(100);
357
+ expect(unbundledItems[0].data).toBe(repetitiveText);
358
+ expect(unbundledItems[99].data).toBe(repetitiveText);
359
+ });
360
+ it('handles various item sizes correctly', async () => {
361
+ const { unbundleRecords } = await import('../../clients/generic/sqs-unbundle');
362
+ const tinyItems = Array.from({ length: 10 }, (_, i) => ({
363
+ id: `tiny-${i}`,
364
+ name: 't',
365
+ }));
366
+ const smallItems = Array.from({ length: 10 }, (_, i) => ({
367
+ id: `small-${i}`,
368
+ name: 'small-item',
369
+ value: i,
370
+ data: 'x'.repeat(100),
371
+ }));
372
+ const mediumItems = Array.from({ length: 10 }, (_, i) => ({
373
+ id: `medium-${i}`,
374
+ name: 'medium-item',
375
+ value: i,
376
+ data: 'y'.repeat(10000),
377
+ }));
378
+ const largerItems = Array.from({ length: 5 }, (_, i) => ({
379
+ id: `larger-${i}`,
380
+ name: 'larger-item',
381
+ value: i,
382
+ data: 'z'.repeat(100000),
383
+ }));
384
+ const allItems = [...tinyItems, ...smallItems, ...mediumItems, ...largerItems];
385
+ const totalExpectedItems = allItems.length;
386
+ const capturedEnvelopes = [];
387
+ mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_, envelopes) => {
388
+ capturedEnvelopes.push(...envelopes);
389
+ return {
390
+ successCount: envelopes.length,
391
+ failedCount: 0,
392
+ batchCount: 1,
393
+ failedMessages: [],
394
+ };
395
+ });
396
+ const result = await bundledClient.sendBundled('test', allItems);
397
+ expect(result.totalItems).toBe(totalExpectedItems);
398
+ expect(result.skippedItems).toBe(0);
399
+ const totalInEnvelopes = capturedEnvelopes.reduce((sum, env) => sum + env.n, 0);
400
+ expect(totalInEnvelopes).toBe(totalExpectedItems);
401
+ const sqsRecords = capturedEnvelopes.map((env, i) => ({
402
+ messageId: `msg-${i}`,
403
+ body: JSON.stringify({ messageBody: env }),
404
+ }));
405
+ const { items: unbundledItems } = await unbundleRecords(sqsRecords);
406
+ expect(unbundledItems).toHaveLength(totalExpectedItems);
407
+ const unbundledTiny = unbundledItems.filter((item) => item.id.startsWith('tiny-'));
408
+ const unbundledSmall = unbundledItems.filter((item) => item.id.startsWith('small-'));
409
+ const unbundledMedium = unbundledItems.filter((item) => item.id.startsWith('medium-'));
410
+ const unbundledLarger = unbundledItems.filter((item) => item.id.startsWith('larger-'));
411
+ expect(unbundledTiny).toHaveLength(10);
412
+ expect(unbundledSmall).toHaveLength(10);
413
+ expect(unbundledMedium).toHaveLength(10);
414
+ expect(unbundledLarger).toHaveLength(5);
415
+ expect(unbundledSmall[0].data).toBe('x'.repeat(100));
416
+ expect(unbundledMedium[0].data).toBe('y'.repeat(10000));
417
+ expect(unbundledLarger[0].data).toBe('z'.repeat(100000));
418
+ });
419
+ it('preserves unicode and special characters through compression', async () => {
420
+ const { unbundleRecords } = await import('../../clients/generic/sqs-unbundle');
421
+ const unicodeItems = [
422
+ {
423
+ id: 'emoji',
424
+ name: '🎉🚀💯🔥✨',
425
+ data: 'Celebration 🎊 with many emojis 🌟⭐💫',
426
+ },
427
+ {
428
+ id: 'chinese',
429
+ name: '你好世界',
430
+ data: '这是一个测试消息,包含中文字符。北京、上海、广州。',
431
+ },
432
+ {
433
+ id: 'arabic',
434
+ name: 'مرحبا بالعالم',
435
+ data: 'هذه رسالة اختبار باللغة العربية',
436
+ },
437
+ {
438
+ id: 'japanese',
439
+ name: 'こんにちは世界',
440
+ data: 'テスト メッセージです。東京、大阪、京都。',
441
+ },
442
+ {
443
+ id: 'korean',
444
+ name: '안녕하세요 세계',
445
+ data: '테스트 메시지입니다. 서울, 부산, 인천.',
446
+ },
447
+ {
448
+ id: 'special-symbols',
449
+ name: '†‡§¶•‰™©®',
450
+ data: 'Special: «»‹› ′″ ∞∑∏√∫ ≤≥≠≈ αβγδε',
451
+ },
452
+ {
453
+ id: 'mixed-unicode',
454
+ name: 'Mixed: Ñ ü ö ä ß',
455
+ data: 'Ümlauts: äöüÄÖÜß — dashes—and–more Ç ñ ¡¿',
456
+ },
457
+ {
458
+ id: 'mathematical',
459
+ name: '∀x∈ℝ: x² ≥ 0',
460
+ data: 'Math: ∫₀^∞ e^(-x²) dx = √π/2, ∑_{n=1}^{∞} 1/n² = π²/6',
461
+ },
462
+ {
463
+ id: 'currency',
464
+ name: '€£¥₹₽¢',
465
+ data: 'Prices: $100, €85, £70, ¥10000, ₹8000, ₽7500',
466
+ },
467
+ {
468
+ id: 'newlines-tabs',
469
+ name: 'control\tchars',
470
+ data: 'Line 1\nLine 2\rLine 3\r\nLine 4\tTabbed',
471
+ },
472
+ ];
473
+ let capturedEnvelope = null;
474
+ mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_, envelopes) => {
475
+ capturedEnvelope = envelopes[0];
476
+ return {
477
+ successCount: envelopes.length,
478
+ failedCount: 0,
479
+ batchCount: 1,
480
+ failedMessages: [],
481
+ };
482
+ });
483
+ const result = await bundledClient.sendBundled('test', unicodeItems);
484
+ expect(result.totalItems).toBe(unicodeItems.length);
485
+ expect(result.skippedItems).toBe(0);
486
+ expect(capturedEnvelope).not.toBeNull();
487
+ const { items: unbundledItems } = await unbundleRecords([
488
+ {
489
+ messageId: 'msg-1',
490
+ body: JSON.stringify({ messageBody: capturedEnvelope }),
491
+ },
492
+ ]);
493
+ expect(unbundledItems).toHaveLength(unicodeItems.length);
494
+ for (let i = 0; i < unicodeItems.length; i++) {
495
+ expect(unbundledItems[i]).toEqual(unicodeItems[i]);
496
+ }
497
+ const emojiItem = unbundledItems.find((item) => item.id === 'emoji');
498
+ expect(emojiItem?.name).toBe('🎉🚀💯🔥✨');
499
+ const chineseItem = unbundledItems.find((item) => item.id === 'chinese');
500
+ expect(chineseItem?.name).toBe('你好世界');
501
+ const arabicItem = unbundledItems.find((item) => item.id === 'arabic');
502
+ expect(arabicItem?.name).toBe('مرحبا بالعالم');
503
+ });
504
+ it('preserves deeply nested object structures', async () => {
505
+ const { unbundleRecords } = await import('../../clients/generic/sqs-unbundle');
506
+ const nestedItems = [
507
+ {
508
+ id: 'deep-nested',
509
+ name: 'deep',
510
+ nested: {
511
+ level1: {
512
+ level2: {
513
+ level3: {
514
+ level4: {
515
+ level5: {
516
+ level6: {
517
+ value: 'deep-value',
518
+ number: 12345,
519
+ boolean: true,
520
+ },
521
+ },
522
+ },
523
+ },
524
+ },
525
+ },
526
+ },
527
+ },
528
+ {
529
+ id: 'array-of-objects',
530
+ name: 'arrays',
531
+ items: [
532
+ { subId: 'sub-1', data: { nested: { value: 'a' } } },
533
+ { subId: 'sub-2', data: { nested: { value: 'b' } } },
534
+ { subId: 'sub-3', data: { nested: { value: 'c' } } },
535
+ ],
536
+ },
537
+ {
538
+ id: 'mixed-structures',
539
+ name: 'mixed',
540
+ config: {
541
+ settings: {
542
+ features: ['feature-1', 'feature-2', 'feature-3'],
543
+ metadata: {
544
+ tags: {
545
+ primary: ['tag-a', 'tag-b'],
546
+ secondary: {
547
+ group1: ['tag-c', 'tag-d'],
548
+ group2: ['tag-e', 'tag-f'],
549
+ },
550
+ },
551
+ },
552
+ },
553
+ },
554
+ },
555
+ {
556
+ id: 'null-undefined-handling',
557
+ name: 'nulls',
558
+ value: null,
559
+ nested: {
560
+ nullField: null,
561
+ emptyObject: {},
562
+ emptyArray: [],
563
+ zeroValue: 0,
564
+ falseValue: false,
565
+ emptyString: '',
566
+ },
567
+ },
568
+ {
569
+ id: 'large-array',
570
+ name: 'large-array',
571
+ items: Array.from({ length: 100 }, (_, i) => ({
572
+ index: i,
573
+ data: `item-${i}`,
574
+ nested: { level: 1, value: i * 2 },
575
+ })),
576
+ },
577
+ ];
578
+ let capturedEnvelope = null;
579
+ mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_, envelopes) => {
580
+ capturedEnvelope = envelopes[0];
581
+ return {
582
+ successCount: envelopes.length,
583
+ failedCount: 0,
584
+ batchCount: 1,
585
+ failedMessages: [],
586
+ };
587
+ });
588
+ const genericClient = new BundledSQSClient(mockSqsClient, {
589
+ compression: CompressionAlgorithm.ZSTD,
590
+ });
591
+ const result = await genericClient.sendBundled('test', nestedItems);
592
+ expect(result.totalItems).toBe(nestedItems.length);
593
+ expect(result.skippedItems).toBe(0);
594
+ const { items: unbundledItems } = await unbundleRecords([
595
+ {
596
+ messageId: 'msg-1',
597
+ body: JSON.stringify({ messageBody: capturedEnvelope }),
598
+ },
599
+ ]);
600
+ expect(unbundledItems).toHaveLength(nestedItems.length);
601
+ const deepItem = unbundledItems.find((item) => item.id === 'deep-nested');
602
+ expect(deepItem?.nested?.level1?.level2?.level3?.level4?.level5?.level6?.value).toBe('deep-value');
603
+ expect(deepItem?.nested?.level1?.level2?.level3?.level4?.level5?.level6?.number).toBe(12345);
604
+ expect(deepItem?.nested?.level1?.level2?.level3?.level4?.level5?.level6?.boolean).toBe(true);
605
+ const arrayItem = unbundledItems.find((item) => item.id === 'array-of-objects');
606
+ expect(arrayItem?.items?.length).toBe(3);
607
+ expect(arrayItem?.items?.[0]?.data?.nested?.value).toBe('a');
608
+ expect(arrayItem?.items?.[2]?.data?.nested?.value).toBe('c');
609
+ const mixedItem = unbundledItems.find((item) => item.id === 'mixed-structures');
610
+ expect(mixedItem?.config?.settings?.features).toEqual(['feature-1', 'feature-2', 'feature-3']);
611
+ expect(mixedItem?.config?.settings?.metadata?.tags?.secondary?.group2).toEqual(['tag-e', 'tag-f']);
612
+ const nullItem = unbundledItems.find((item) => item.id === 'null-undefined-handling');
613
+ expect(nullItem?.value).toBeNull();
614
+ expect(nullItem?.nested?.nullField).toBeNull();
615
+ expect(nullItem?.nested?.emptyObject).toEqual({});
616
+ expect(nullItem?.nested?.emptyArray).toEqual([]);
617
+ expect(nullItem?.nested?.zeroValue).toBe(0);
618
+ expect(nullItem?.nested?.falseValue).toBe(false);
619
+ expect(nullItem?.nested?.emptyString).toBe('');
620
+ const largeArrayItem = unbundledItems.find((item) => item.id === 'large-array');
621
+ expect(largeArrayItem?.items).toHaveLength(100);
622
+ expect(largeArrayItem?.items?.[0]).toEqual({ index: 0, data: 'item-0', nested: { level: 1, value: 0 } });
623
+ expect(largeArrayItem?.items?.[99]).toEqual({ index: 99, data: 'item-99', nested: { level: 1, value: 198 } });
624
+ });
625
+ it('handles exactly 1 item', async () => {
626
+ const { unbundleRecords } = await import('../../clients/generic/sqs-unbundle');
627
+ const items = [{ id: 'single', name: 'only-one', value: 42 }];
628
+ const capturedEnvelopes = [];
629
+ mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_, envelopes) => {
630
+ capturedEnvelopes.push(envelopes[0]);
631
+ return {
632
+ successCount: envelopes.length,
633
+ failedCount: 0,
634
+ batchCount: 1,
635
+ failedMessages: [],
636
+ };
637
+ });
638
+ const result = await bundledClient.sendBundled('test', items);
639
+ expect(result.totalItems).toBe(1);
640
+ expect(result.bundleCount).toBe(1);
641
+ expect(result.messageCount).toBe(1);
642
+ expect(capturedEnvelopes).toHaveLength(1);
643
+ expect(capturedEnvelopes[0].n).toBe(1);
644
+ const { items: unbundledItems } = await unbundleRecords([
645
+ {
646
+ messageId: 'msg-1',
647
+ body: JSON.stringify({ messageBody: capturedEnvelopes[0] }),
648
+ },
649
+ ]);
650
+ expect(unbundledItems).toHaveLength(1);
651
+ expect(unbundledItems[0]).toEqual(items[0]);
652
+ });
653
+ it('handles exactly maxItemsPerBundle items', async () => {
654
+ const { unbundleRecords } = await import('../../clients/generic/sqs-unbundle');
655
+ const maxItems = 50;
656
+ const client = new BundledSQSClient(mockSqsClient, {
657
+ compression: CompressionAlgorithm.ZSTD,
658
+ maxItemsPerBundle: maxItems,
659
+ });
660
+ const items = Array.from({ length: maxItems }, (_, i) => ({
661
+ id: `item-${i}`,
662
+ name: `name-${i}`,
663
+ value: i,
664
+ }));
665
+ const capturedEnvelopes = [];
666
+ mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_, envelopes) => {
667
+ capturedEnvelopes.push(...envelopes);
668
+ return {
669
+ successCount: envelopes.length,
670
+ failedCount: 0,
671
+ batchCount: 1,
672
+ failedMessages: [],
673
+ };
674
+ });
675
+ const result = await client.sendBundled('test', items);
676
+ expect(result.totalItems).toBe(maxItems);
677
+ expect(capturedEnvelopes).toHaveLength(1);
678
+ expect(capturedEnvelopes[0].n).toBe(maxItems);
679
+ const sqsRecords = capturedEnvelopes.map((env, i) => ({
680
+ messageId: `msg-${i}`,
681
+ body: JSON.stringify({ messageBody: env }),
682
+ }));
683
+ const { items: unbundledItems } = await unbundleRecords(sqsRecords);
684
+ expect(unbundledItems).toHaveLength(maxItems);
685
+ });
686
+ it('handles maxItemsPerBundle + 1 items (splits correctly)', async () => {
687
+ const { unbundleRecords } = await import('../../clients/generic/sqs-unbundle');
688
+ const maxItems = 50;
689
+ const client = new BundledSQSClient(mockSqsClient, {
690
+ compression: CompressionAlgorithm.ZSTD,
691
+ maxItemsPerBundle: maxItems,
692
+ });
693
+ const items = Array.from({ length: maxItems + 1 }, (_, i) => ({
694
+ id: `item-${i}`,
695
+ name: `name-${i}`,
696
+ value: i,
697
+ }));
698
+ const capturedEnvelopes = [];
699
+ mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_, envelopes) => {
700
+ capturedEnvelopes.push(...envelopes);
701
+ return {
702
+ successCount: envelopes.length,
703
+ failedCount: 0,
704
+ batchCount: 1,
705
+ failedMessages: [],
706
+ };
707
+ });
708
+ const result = await client.sendBundled('test', items);
709
+ expect(result.totalItems).toBe(maxItems + 1);
710
+ expect(capturedEnvelopes).toHaveLength(2);
711
+ expect(capturedEnvelopes[0].n).toBe(maxItems);
712
+ expect(capturedEnvelopes[1].n).toBe(1);
713
+ const sqsRecords = capturedEnvelopes.map((env, i) => ({
714
+ messageId: `msg-${i}`,
715
+ body: JSON.stringify({ messageBody: env }),
716
+ }));
717
+ const { items: unbundledItems } = await unbundleRecords(sqsRecords);
718
+ expect(unbundledItems).toHaveLength(maxItems + 1);
719
+ });
720
+ it('handles uncompressed mode (NONE) with various data', async () => {
721
+ const { unbundleRecords } = await import('../../clients/generic/sqs-unbundle');
722
+ const noCompressionClient = new BundledSQSClient(mockSqsClient, {
723
+ compression: CompressionAlgorithm.NONE,
724
+ });
725
+ const items = Array.from({ length: 50 }, (_, i) => ({
726
+ id: `uncompressed-${i}`,
727
+ name: `test-${i}`,
728
+ value: i * 100,
729
+ data: `Some data for item ${i}`,
730
+ }));
731
+ const capturedEnvelopes = [];
732
+ mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_, envelopes) => {
733
+ capturedEnvelopes.push(envelopes[0]);
734
+ return {
735
+ successCount: envelopes.length,
736
+ failedCount: 0,
737
+ batchCount: 1,
738
+ failedMessages: [],
739
+ };
740
+ });
741
+ const result = await noCompressionClient.sendBundled('test', items);
742
+ expect(result.totalItems).toBe(50);
743
+ expect(capturedEnvelopes).toHaveLength(1);
744
+ const envelope = capturedEnvelopes[0];
745
+ expect(envelope.c).toBe(CompressionAlgorithm.NONE);
746
+ expect(Array.isArray(envelope.p)).toBe(true);
747
+ expect(envelope.p.length).toBe(50);
748
+ expect(result.metrics.compressionRatio).toBeCloseTo(1, 0);
749
+ const { items: unbundledItems } = await unbundleRecords([
750
+ {
751
+ messageId: 'msg-1',
752
+ body: JSON.stringify({ messageBody: envelope }),
753
+ },
754
+ ]);
755
+ expect(unbundledItems).toHaveLength(50);
756
+ expect(unbundledItems[0]).toEqual(items[0]);
757
+ expect(unbundledItems[49]).toEqual(items[49]);
758
+ });
759
+ it('produces correct metrics for a large mixed batch', async () => {
760
+ const items = Array.from({ length: 500 }, (_, i) => ({
761
+ id: `metric-test-${i}`,
762
+ name: `item-${i % 10}`,
763
+ value: i,
764
+ data: i % 5 === 0 ? 'repeated-data'.repeat(100) : `unique-${i}`,
765
+ }));
766
+ const capturedEnvelopes = [];
767
+ mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_, envelopes) => {
768
+ capturedEnvelopes.push(...envelopes);
769
+ return {
770
+ successCount: envelopes.length,
771
+ failedCount: 0,
772
+ batchCount: 1,
773
+ failedMessages: [],
774
+ };
775
+ });
776
+ const result = await bundledClient.sendBundled('test', items);
777
+ expect(result.totalItems).toBe(500);
778
+ expect(result.metrics.originalSizeBytes).toBeGreaterThan(0);
779
+ expect(result.metrics.finalSizeBytes).toBeGreaterThan(0);
780
+ expect(result.metrics.compressionRatio).toBeGreaterThan(1);
781
+ expect(result.metrics.compressionTimeMs).toBeGreaterThanOrEqual(0);
782
+ expect(result.metrics.avgItemsPerBundle).toBeGreaterThan(0);
783
+ const expectedAvg = items.length / capturedEnvelopes.length;
784
+ expect(result.metrics.avgItemsPerBundle).toBeCloseTo(expectedAvg, 1);
785
+ expect(result.billableRequests).toBeGreaterThanOrEqual(1);
786
+ });
787
+ it('handles items with special JSON edge cases', async () => {
788
+ const { unbundleRecords } = await import('../../clients/generic/sqs-unbundle');
789
+ const edgeCaseItems = [
790
+ {
791
+ id: 'large-numbers',
792
+ name: 'numbers',
793
+ bigInt: 9007199254740991,
794
+ scientific: 1.23e10,
795
+ decimal: 0.1 + 0.2,
796
+ negative: -999999,
797
+ zero: 0,
798
+ negativeZero: -0,
799
+ },
800
+ {
801
+ id: 'special-strings',
802
+ name: 'strings',
803
+ data: JSON.stringify({ nested: 'json' }),
804
+ withQuotes: 'He said "hello"',
805
+ withBackslash: 'path\\to\\file',
806
+ withSlash: 'https://example.com/path',
807
+ },
808
+ {
809
+ id: 'boolean-variants',
810
+ name: 'booleans',
811
+ trueVal: true,
812
+ falseVal: false,
813
+ truthy: 1,
814
+ falsy: 0,
815
+ },
816
+ {
817
+ id: 'dates-as-strings',
818
+ name: 'dates',
819
+ isoDate: '2024-01-15T10:30:00.000Z',
820
+ timestamp: 1705315800000,
821
+ },
822
+ ];
823
+ let capturedEnvelope = null;
824
+ mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_, envelopes) => {
825
+ capturedEnvelope = envelopes[0];
826
+ return {
827
+ successCount: envelopes.length,
828
+ failedCount: 0,
829
+ batchCount: 1,
830
+ failedMessages: [],
831
+ };
832
+ });
833
+ const genericClient = new BundledSQSClient(mockSqsClient, {
834
+ compression: CompressionAlgorithm.ZSTD,
835
+ });
836
+ const result = await genericClient.sendBundled('test', edgeCaseItems);
837
+ expect(result.totalItems).toBe(edgeCaseItems.length);
838
+ expect(result.skippedItems).toBe(0);
839
+ const { items: unbundledItems } = await unbundleRecords([
840
+ {
841
+ messageId: 'msg-1',
842
+ body: JSON.stringify({ messageBody: capturedEnvelope }),
843
+ },
844
+ ]);
845
+ expect(unbundledItems).toHaveLength(edgeCaseItems.length);
846
+ const numbersItem = unbundledItems.find((item) => item.id === 'large-numbers');
847
+ expect(numbersItem?.bigInt).toBe(9007199254740991);
848
+ expect(numbersItem?.scientific).toBe(1.23e10);
849
+ expect(numbersItem?.negative).toBe(-999999);
850
+ expect(numbersItem?.zero).toBe(0);
851
+ const stringsItem = unbundledItems.find((item) => item.id === 'special-strings');
852
+ expect(stringsItem?.data).toBe(JSON.stringify({ nested: 'json' }));
853
+ expect(stringsItem?.withQuotes).toBe('He said "hello"');
854
+ expect(stringsItem?.withBackslash).toBe('path\\to\\file');
855
+ const booleansItem = unbundledItems.find((item) => item.id === 'boolean-variants');
856
+ expect(booleansItem?.trueVal).toBe(true);
857
+ expect(booleansItem?.falseVal).toBe(false);
858
+ });
859
+ it('handles concurrent sends without interference', async () => {
860
+ const batch1 = Array.from({ length: 100 }, (_, i) => ({
861
+ id: `batch1-${i}`,
862
+ name: 'batch-one',
863
+ value: i,
864
+ }));
865
+ const batch2 = Array.from({ length: 100 }, (_, i) => ({
866
+ id: `batch2-${i}`,
867
+ name: 'batch-two',
868
+ value: i * 2,
869
+ }));
870
+ const batch3 = Array.from({ length: 100 }, (_, i) => ({
871
+ id: `batch3-${i}`,
872
+ name: 'batch-three',
873
+ value: i * 3,
874
+ }));
875
+ mockSqsClient.buildAndSendMessagesV2.mockImplementation(async (_, envelopes) => {
876
+ return {
877
+ successCount: envelopes.length,
878
+ failedCount: 0,
879
+ batchCount: 1,
880
+ failedMessages: [],
881
+ };
882
+ });
883
+ const [result1, result2, result3] = await Promise.all([
884
+ bundledClient.sendBundled('test', batch1),
885
+ bundledClient.sendBundled('test', batch2),
886
+ bundledClient.sendBundled('test', batch3),
887
+ ]);
888
+ expect(result1.totalItems).toBe(100);
889
+ expect(result2.totalItems).toBe(100);
890
+ expect(result3.totalItems).toBe(100);
891
+ expect(result1.skippedItems).toBe(0);
892
+ expect(result2.skippedItems).toBe(0);
893
+ expect(result3.skippedItems).toBe(0);
894
+ });
895
+ });
896
+ });
897
897
  //# sourceMappingURL=sqs-bundled-client.spec.js.map