@adtrackify/at-service-common 3.18.11 → 3.18.12

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 (559) hide show
  1. package/dist/cjs/__tests__/clients/cross-platform-compression.spec.d.ts +1 -0
  2. package/dist/cjs/__tests__/clients/cross-platform-compression.spec.js +355 -0
  3. package/dist/cjs/__tests__/clients/cross-platform-compression.spec.js.map +1 -0
  4. package/dist/cjs/__tests__/clients/sqs-bundled-client.spec.d.ts +1 -1
  5. package/dist/cjs/__tests__/clients/sqs-bundled-client.spec.js +921 -921
  6. package/dist/cjs/__tests__/clients/sqs-bundling-contracts.spec.d.ts +1 -0
  7. package/dist/cjs/__tests__/clients/sqs-bundling-contracts.spec.js +576 -0
  8. package/dist/cjs/__tests__/clients/sqs-bundling-contracts.spec.js.map +1 -0
  9. package/dist/cjs/__tests__/clients/sqs-client.spec.d.ts +1 -1
  10. package/dist/cjs/__tests__/clients/sqs-client.spec.js +191 -191
  11. package/dist/cjs/__tests__/clients/sqs-unbundle.spec.d.ts +1 -1
  12. package/dist/cjs/__tests__/clients/sqs-unbundle.spec.js +1357 -1228
  13. package/dist/cjs/__tests__/clients/sqs-unbundle.spec.js.map +1 -1
  14. package/dist/cjs/__tests__/db/shared-read-db-services.spec.d.ts +1 -1
  15. package/dist/cjs/__tests__/db/shared-read-db-services.spec.js +89 -89
  16. package/dist/cjs/__tests__/helpers/account-users-helper.spec.d.ts +1 -1
  17. package/dist/cjs/__tests__/helpers/account-users-helper.spec.js +220 -220
  18. package/dist/cjs/__tests__/helpers/api-key-auth-helper.spec.d.ts +1 -1
  19. package/dist/cjs/__tests__/helpers/api-key-auth-helper.spec.js +82 -82
  20. package/dist/cjs/__tests__/identity-cache/identity-cache-db-service.spec.d.ts +1 -1
  21. package/dist/cjs/__tests__/identity-cache/identity-cache-db-service.spec.js +674 -674
  22. package/dist/cjs/__tests__/identity-cache/trait-merging-and-staleness.spec.d.ts +1 -1
  23. package/dist/cjs/__tests__/identity-cache/trait-merging-and-staleness.spec.js +588 -588
  24. package/dist/cjs/__tests__/integration/sqs-bundling-roundtrip.spec.d.ts +1 -1
  25. package/dist/cjs/__tests__/integration/sqs-bundling-roundtrip.spec.js +584 -584
  26. package/dist/cjs/__tests__/libs/compress-decompress.spec.d.ts +1 -1
  27. package/dist/cjs/__tests__/libs/compress-decompress.spec.js +16 -16
  28. package/dist/cjs/__tests__/libs/currency.spec.d.ts +1 -1
  29. package/dist/cjs/__tests__/libs/currency.spec.js +220 -220
  30. package/dist/cjs/__tests__/libs/dates.spec.d.ts +1 -1
  31. package/dist/cjs/__tests__/libs/dates.spec.js +130 -130
  32. package/dist/cjs/__tests__/libs/domain.spec.d.ts +1 -1
  33. package/dist/cjs/__tests__/libs/domain.spec.js +107 -107
  34. package/dist/cjs/__tests__/libs/numbers.spec.d.ts +1 -1
  35. package/dist/cjs/__tests__/libs/numbers.spec.js +261 -261
  36. package/dist/cjs/__tests__/s3-client/s3-client.spec.d.ts +1 -1
  37. package/dist/cjs/__tests__/s3-client/s3-client.spec.js +33 -33
  38. package/dist/cjs/__tests__/shopify/shopify-graphql-transformer.spec.d.ts +1 -1
  39. package/dist/cjs/__tests__/shopify/shopify-graphql-transformer.spec.js +35 -35
  40. package/dist/cjs/__tests__/unit/libs/api-router/public-api-router.spec.d.ts +1 -1
  41. package/dist/cjs/__tests__/unit/libs/api-router/public-api-router.spec.js +181 -181
  42. package/dist/cjs/__tests__/unit/libs/api-router/route-matcher.spec.d.ts +1 -1
  43. package/dist/cjs/__tests__/unit/libs/api-router/route-matcher.spec.js +69 -69
  44. package/dist/cjs/clients/generic/cognito-client.d.ts +23 -23
  45. package/dist/cjs/clients/generic/cognito-client.js +209 -209
  46. package/dist/cjs/clients/generic/dynamodb-client.d.ts +18 -18
  47. package/dist/cjs/clients/generic/dynamodb-client.js +172 -172
  48. package/dist/cjs/clients/generic/eventbridge-client.d.ts +14 -14
  49. package/dist/cjs/clients/generic/eventbridge-client.js +51 -51
  50. package/dist/cjs/clients/generic/http-client.d.ts +14 -14
  51. package/dist/cjs/clients/generic/http-client.js +61 -61
  52. package/dist/cjs/clients/generic/index.d.ts +13 -13
  53. package/dist/cjs/clients/generic/index.js +29 -29
  54. package/dist/cjs/clients/generic/lambda-invoke-client.d.ts +10 -10
  55. package/dist/cjs/clients/generic/lambda-invoke-client.js +39 -39
  56. package/dist/cjs/clients/generic/location-client.d.ts +8 -8
  57. package/dist/cjs/clients/generic/location-client.js +31 -31
  58. package/dist/cjs/clients/generic/redis-client.d.ts +33 -33
  59. package/dist/cjs/clients/generic/redis-client.js +191 -191
  60. package/dist/cjs/clients/generic/s3-client.d.ts +23 -23
  61. package/dist/cjs/clients/generic/s3-client.js +216 -216
  62. package/dist/cjs/clients/generic/singlestore-db-client.d.ts +14 -14
  63. package/dist/cjs/clients/generic/singlestore-db-client.js +67 -67
  64. package/dist/cjs/clients/generic/sqs-bundled-client.d.ts +15 -15
  65. package/dist/cjs/clients/generic/sqs-bundled-client.js +311 -311
  66. package/dist/cjs/clients/generic/sqs-bundled-client.types.d.ts +53 -53
  67. package/dist/cjs/clients/generic/sqs-bundled-client.types.js +17 -17
  68. package/dist/cjs/clients/generic/sqs-client.d.ts +53 -53
  69. package/dist/cjs/clients/generic/sqs-client.js +285 -285
  70. package/dist/cjs/clients/generic/sqs-unbundle.d.ts +32 -32
  71. package/dist/cjs/clients/generic/sqs-unbundle.js +144 -144
  72. package/dist/cjs/clients/index.d.ts +3 -3
  73. package/dist/cjs/clients/index.js +19 -19
  74. package/dist/cjs/clients/internal-api/accounts-client.d.ts +91 -91
  75. package/dist/cjs/clients/internal-api/accounts-client.js +129 -129
  76. package/dist/cjs/clients/internal-api/cache-lambda-client.d.ts +26 -26
  77. package/dist/cjs/clients/internal-api/cache-lambda-client.js +89 -89
  78. package/dist/cjs/clients/internal-api/db-management-client.d.ts +18 -18
  79. package/dist/cjs/clients/internal-api/db-management-client.js +36 -36
  80. package/dist/cjs/clients/internal-api/destinations-client.d.ts +34 -34
  81. package/dist/cjs/clients/internal-api/destinations-client.js +79 -79
  82. package/dist/cjs/clients/internal-api/event-collector-client.d.ts +20 -20
  83. package/dist/cjs/clients/internal-api/event-collector-client.js +36 -36
  84. package/dist/cjs/clients/internal-api/identity-client.d.ts +31 -31
  85. package/dist/cjs/clients/internal-api/identity-client.js +91 -91
  86. package/dist/cjs/clients/internal-api/index.d.ts +9 -9
  87. package/dist/cjs/clients/internal-api/index.js +25 -25
  88. package/dist/cjs/clients/internal-api/shopify-app-install-client.d.ts +37 -37
  89. package/dist/cjs/clients/internal-api/shopify-app-install-client.js +81 -81
  90. package/dist/cjs/clients/internal-api/subscriptions-client.d.ts +26 -26
  91. package/dist/cjs/clients/internal-api/subscriptions-client.js +77 -77
  92. package/dist/cjs/clients/internal-api/users-auth-client.d.ts +35 -35
  93. package/dist/cjs/clients/internal-api/users-auth-client.js +110 -110
  94. package/dist/cjs/clients/third-party/audience-acuity/acuity-api-service.d.ts +8 -0
  95. package/dist/cjs/clients/third-party/audience-acuity/acuity-api-service.js +55 -0
  96. package/dist/cjs/clients/third-party/audience-acuity/acuity-api-service.js.map +1 -0
  97. package/dist/cjs/clients/third-party/audience-acuity/acuity-client.d.ts +9 -0
  98. package/dist/cjs/clients/third-party/audience-acuity/acuity-client.js +35 -0
  99. package/dist/cjs/clients/third-party/audience-acuity/acuity-client.js.map +1 -0
  100. package/dist/cjs/clients/third-party/audience-acuity/acuity-helpers.d.ts +2 -0
  101. package/dist/cjs/clients/third-party/audience-acuity/acuity-helpers.js +23 -0
  102. package/dist/cjs/clients/third-party/audience-acuity/acuity-helpers.js.map +1 -0
  103. package/dist/cjs/clients/third-party/audience-acuity/acuity-types.d.ts +74 -0
  104. package/dist/cjs/clients/third-party/audience-acuity/acuity-types.js +3 -0
  105. package/dist/cjs/clients/third-party/audience-acuity/acuity-types.js.map +1 -0
  106. package/dist/cjs/clients/third-party/audience-acuity/index.d.ts +4 -0
  107. package/dist/cjs/clients/third-party/audience-acuity/index.js +21 -0
  108. package/dist/cjs/clients/third-party/audience-acuity/index.js.map +1 -0
  109. package/dist/cjs/clients/third-party/emailable-client.d.ts +7 -7
  110. package/dist/cjs/clients/third-party/emailable-client.js +25 -25
  111. package/dist/cjs/clients/third-party/exchange-rate-api-client.d.ts +17 -17
  112. package/dist/cjs/clients/third-party/exchange-rate-api-client.js +19 -19
  113. package/dist/cjs/clients/third-party/index.d.ts +5 -4
  114. package/dist/cjs/clients/third-party/index.js +21 -20
  115. package/dist/cjs/clients/third-party/index.js.map +1 -1
  116. package/dist/cjs/clients/third-party/loops-client.d.ts +10 -10
  117. package/dist/cjs/clients/third-party/loops-client.js +30 -30
  118. package/dist/cjs/clients/third-party/shopify/graphql-order-queries.d.ts +25 -25
  119. package/dist/cjs/clients/third-party/shopify/graphql-order-queries.js +4 -4
  120. package/dist/cjs/clients/third-party/shopify/graphql-product-queries.d.ts +2 -2
  121. package/dist/cjs/clients/third-party/shopify/graphql-product-queries.js +5 -5
  122. package/dist/cjs/clients/third-party/shopify/shopify-graphql-client.d.ts +10 -10
  123. package/dist/cjs/clients/third-party/shopify/shopify-graphql-client.js +161 -161
  124. package/dist/cjs/clients/third-party/shopify-client.d.ts +29 -29
  125. package/dist/cjs/clients/third-party/shopify-client.js +146 -146
  126. package/dist/cjs/constants/index.d.ts +1 -1
  127. package/dist/cjs/constants/index.js +17 -17
  128. package/dist/cjs/constants/sqs.d.ts +20 -20
  129. package/dist/cjs/constants/sqs.js +26 -26
  130. package/dist/cjs/helpers/account-users-helper.d.ts +2 -2
  131. package/dist/cjs/helpers/account-users-helper.js +22 -22
  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 +9 -9
  139. package/dist/cjs/helpers/index.js +25 -25
  140. package/dist/cjs/helpers/input-validation-helper.d.ts +3 -3
  141. package/dist/cjs/helpers/input-validation-helper.js +22 -22
  142. package/dist/cjs/helpers/logging-helper.d.ts +16 -16
  143. package/dist/cjs/helpers/logging-helper.js +84 -84
  144. package/dist/cjs/helpers/response-helper.d.ts +18 -18
  145. package/dist/cjs/helpers/response-helper.js +43 -43
  146. package/dist/cjs/helpers/shopify-helper.d.ts +9 -9
  147. package/dist/cjs/helpers/shopify-helper.js +26 -26
  148. package/dist/cjs/helpers/sqs-utils.d.ts +6 -6
  149. package/dist/cjs/helpers/sqs-utils.js +14 -14
  150. package/dist/cjs/index.d.ts +7 -7
  151. package/dist/cjs/index.js +23 -23
  152. package/dist/cjs/libs/api-router/index.d.ts +2 -2
  153. package/dist/cjs/libs/api-router/index.js +18 -18
  154. package/dist/cjs/libs/api-router/public-api-router.d.ts +3 -3
  155. package/dist/cjs/libs/api-router/public-api-router.js +36 -36
  156. package/dist/cjs/libs/api-router/route-matcher.d.ts +21 -21
  157. package/dist/cjs/libs/api-router/route-matcher.js +36 -36
  158. package/dist/cjs/libs/click-id-parser.d.ts +23 -23
  159. package/dist/cjs/libs/click-id-parser.js +49 -49
  160. package/dist/cjs/libs/compression.d.ts +2 -2
  161. package/dist/cjs/libs/compression.js +33 -33
  162. package/dist/cjs/libs/cookie.d.ts +17 -17
  163. package/dist/cjs/libs/cookie.js +76 -76
  164. package/dist/cjs/libs/crypto.d.ts +4 -4
  165. package/dist/cjs/libs/crypto.js +25 -25
  166. package/dist/cjs/libs/csv.d.ts +2 -2
  167. package/dist/cjs/libs/csv.js +35 -35
  168. package/dist/cjs/libs/currency.d.ts +1 -1
  169. package/dist/cjs/libs/currency.js +29 -29
  170. package/dist/cjs/libs/dates.d.ts +12 -12
  171. package/dist/cjs/libs/dates.js +96 -96
  172. package/dist/cjs/libs/domain.d.ts +2 -2
  173. package/dist/cjs/libs/domain.js +38 -38
  174. package/dist/cjs/libs/emails.d.ts +6 -6
  175. package/dist/cjs/libs/emails.js +122 -122
  176. package/dist/cjs/libs/http-error.d.ts +21 -21
  177. package/dist/cjs/libs/http-error.js +63 -63
  178. package/dist/cjs/libs/http-status-codes.d.ts +58 -58
  179. package/dist/cjs/libs/http-status-codes.js +62 -62
  180. package/dist/cjs/libs/index.d.ts +18 -18
  181. package/dist/cjs/libs/index.js +34 -34
  182. package/dist/cjs/libs/numbers.d.ts +1 -1
  183. package/dist/cjs/libs/numbers.js +15 -15
  184. package/dist/cjs/libs/referrer-parser/index.d.ts +2 -2
  185. package/dist/cjs/libs/referrer-parser/index.js +18 -18
  186. package/dist/cjs/libs/referrer-parser/referrer-data.d.ts +9 -9
  187. package/dist/cjs/libs/referrer-parser/referrer-data.js +3307 -3307
  188. package/dist/cjs/libs/referrer-parser/referrer-parser-util.d.ts +20 -20
  189. package/dist/cjs/libs/referrer-parser/referrer-parser-util.js +131 -131
  190. package/dist/cjs/libs/strings.d.ts +3 -3
  191. package/dist/cjs/libs/strings.js +46 -46
  192. package/dist/cjs/libs/traits.d.ts +6 -6
  193. package/dist/cjs/libs/traits.js +65 -65
  194. package/dist/cjs/libs/url.d.ts +1 -1
  195. package/dist/cjs/libs/url.js +13 -13
  196. package/dist/cjs/services/cache/generic-cached-object.d.ts +5 -5
  197. package/dist/cjs/services/cache/generic-cached-object.js +2 -2
  198. package/dist/cjs/services/cache/index.d.ts +1 -1
  199. package/dist/cjs/services/cache/index.js +17 -17
  200. package/dist/cjs/services/cache/product-cache-service.d.ts +21 -21
  201. package/dist/cjs/services/cache/product-cache-service.js +76 -76
  202. package/dist/cjs/services/currency-exchange-rate-lookup-service.d.ts +11 -11
  203. package/dist/cjs/services/currency-exchange-rate-lookup-service.js +66 -66
  204. package/dist/cjs/services/db/accounts-db-service.d.ts +9 -9
  205. package/dist/cjs/services/db/accounts-db-service.js +33 -33
  206. package/dist/cjs/services/db/api-keys-db-service.d.ts +10 -10
  207. package/dist/cjs/services/db/api-keys-db-service.js +36 -36
  208. package/dist/cjs/services/db/currency-exchange-rates-db-service.d.ts +21 -21
  209. package/dist/cjs/services/db/currency-exchange-rates-db-service.js +39 -39
  210. package/dist/cjs/services/db/destinations-db-service.d.ts +12 -12
  211. package/dist/cjs/services/db/destinations-db-service.js +76 -76
  212. package/dist/cjs/services/db/identity-cache-db-service.d.ts +28 -28
  213. package/dist/cjs/services/db/identity-cache-db-service.js +320 -320
  214. package/dist/cjs/services/db/index.d.ts +13 -13
  215. package/dist/cjs/services/db/index.js +29 -29
  216. package/dist/cjs/services/db/log-events-db-service.d.ts +11 -11
  217. package/dist/cjs/services/db/log-events-db-service.js +181 -181
  218. package/dist/cjs/services/db/pixels-db-service.d.ts +8 -8
  219. package/dist/cjs/services/db/pixels-db-service.js +35 -35
  220. package/dist/cjs/services/db/purchasable-contacts-db-service.d.ts +9 -9
  221. package/dist/cjs/services/db/purchasable-contacts-db-service.js +43 -43
  222. package/dist/cjs/services/db/purchased-contacts-db-service.d.ts +17 -17
  223. package/dist/cjs/services/db/purchased-contacts-db-service.js +143 -143
  224. package/dist/cjs/services/db/shopify-app-installs-db-service.d.ts +8 -8
  225. package/dist/cjs/services/db/shopify-app-installs-db-service.js +51 -51
  226. package/dist/cjs/services/db/shopify-products-cache-db-service.d.ts +16 -16
  227. package/dist/cjs/services/db/shopify-products-cache-db-service.js +73 -73
  228. package/dist/cjs/services/db/subscriptions-db-service.d.ts +10 -10
  229. package/dist/cjs/services/db/subscriptions-db-service.js +34 -34
  230. package/dist/cjs/services/db/tracking-events-db-service.d.ts +20 -20
  231. package/dist/cjs/services/db/tracking-events-db-service.js +165 -165
  232. package/dist/cjs/services/eventbridge-integration-service.d.ts +9 -9
  233. package/dist/cjs/services/eventbridge-integration-service.js +28 -28
  234. package/dist/cjs/services/events/index.d.ts +3 -3
  235. package/dist/cjs/services/events/index.js +19 -19
  236. package/dist/cjs/services/events/log-event-service.d.ts +19 -19
  237. package/dist/cjs/services/events/log-event-service.js +77 -77
  238. package/dist/cjs/services/events/metric-event-service.d.ts +9 -9
  239. package/dist/cjs/services/events/metric-event-service.js +49 -49
  240. package/dist/cjs/services/events/tracking-event-sqs-service.d.ts +8 -8
  241. package/dist/cjs/services/events/tracking-event-sqs-service.js +34 -34
  242. package/dist/cjs/services/generic-cache-service.d.ts +7 -7
  243. package/dist/cjs/services/generic-cache-service.js +33 -33
  244. package/dist/cjs/services/index.d.ts +8 -8
  245. package/dist/cjs/services/index.js +24 -24
  246. package/dist/cjs/services/ipdata-lookup-service.d.ts +20 -20
  247. package/dist/cjs/services/ipdata-lookup-service.js +112 -112
  248. package/dist/cjs/services/shopify/index.d.ts +2 -2
  249. package/dist/cjs/services/shopify/index.js +18 -18
  250. package/dist/cjs/services/shopify/products/index.d.ts +1 -1
  251. package/dist/cjs/services/shopify/products/index.js +17 -17
  252. package/dist/cjs/services/shopify/products/shopify-products-serviceV2.d.ts +17 -17
  253. package/dist/cjs/services/shopify/products/shopify-products-serviceV2.js +112 -112
  254. package/dist/cjs/services/shopify/shopify-graphql-transformer.d.ts +8 -8
  255. package/dist/cjs/services/shopify/shopify-graphql-transformer.js +141 -141
  256. package/dist/cjs/types/api-response.d.ts +6 -6
  257. package/dist/cjs/types/api-response.js +2 -2
  258. package/dist/cjs/types/index.d.ts +3 -3
  259. package/dist/cjs/types/index.js +32 -32
  260. package/dist/cjs/types/internal-events/event-detail-types.d.ts +20 -20
  261. package/dist/cjs/types/internal-events/event-detail-types.js +27 -27
  262. package/dist/cjs/types/internal-events/index.d.ts +1 -1
  263. package/dist/cjs/types/internal-events/index.js +17 -17
  264. package/dist/cjs/types/shopify-graphql-types/admin.generated.d.ts +123 -123
  265. package/dist/cjs/types/shopify-graphql-types/admin.generated.js +2 -2
  266. package/dist/cjs/types/shopify-graphql-types/admin.types.d.ts +26289 -26289
  267. package/dist/cjs/types/shopify-graphql-types/admin.types.js +5311 -5311
  268. package/dist/cjs/types/shopify-graphql-types/index.d.ts +2 -2
  269. package/dist/cjs/types/shopify-graphql-types/index.js +18 -18
  270. package/dist/cjs/types/shopify-rest-types.d.ts +767 -767
  271. package/dist/cjs/types/shopify-rest-types.js +2 -2
  272. package/dist/cjs/utils/compression.d.ts +36 -36
  273. package/dist/cjs/utils/compression.js +198 -198
  274. package/dist/cjs/utils/index.d.ts +3 -3
  275. package/dist/cjs/utils/index.js +19 -19
  276. package/dist/cjs/utils/retry-envelope.d.ts +12 -12
  277. package/dist/cjs/utils/retry-envelope.js +28 -28
  278. package/dist/cjs/utils/size.d.ts +2 -2
  279. package/dist/cjs/utils/size.js +49 -49
  280. package/dist/esm/__tests__/clients/cross-platform-compression.spec.d.ts +1 -0
  281. package/dist/esm/__tests__/clients/cross-platform-compression.spec.js +330 -0
  282. package/dist/esm/__tests__/clients/cross-platform-compression.spec.js.map +1 -0
  283. package/dist/esm/__tests__/clients/sqs-bundled-client.spec.d.ts +1 -1
  284. package/dist/esm/__tests__/clients/sqs-bundled-client.spec.js +896 -896
  285. package/dist/esm/__tests__/clients/sqs-bundling-contracts.spec.d.ts +1 -0
  286. package/dist/esm/__tests__/clients/sqs-bundling-contracts.spec.js +551 -0
  287. package/dist/esm/__tests__/clients/sqs-bundling-contracts.spec.js.map +1 -0
  288. package/dist/esm/__tests__/clients/sqs-client.spec.d.ts +1 -1
  289. package/dist/esm/__tests__/clients/sqs-client.spec.js +189 -189
  290. package/dist/esm/__tests__/clients/sqs-unbundle.spec.d.ts +1 -1
  291. package/dist/esm/__tests__/clients/sqs-unbundle.spec.js +1355 -1226
  292. package/dist/esm/__tests__/clients/sqs-unbundle.spec.js.map +1 -1
  293. package/dist/esm/__tests__/db/shared-read-db-services.spec.d.ts +1 -1
  294. package/dist/esm/__tests__/db/shared-read-db-services.spec.js +87 -87
  295. package/dist/esm/__tests__/helpers/account-users-helper.spec.d.ts +1 -1
  296. package/dist/esm/__tests__/helpers/account-users-helper.spec.js +218 -218
  297. package/dist/esm/__tests__/helpers/api-key-auth-helper.spec.d.ts +1 -1
  298. package/dist/esm/__tests__/helpers/api-key-auth-helper.spec.js +80 -80
  299. package/dist/esm/__tests__/identity-cache/identity-cache-db-service.spec.d.ts +1 -1
  300. package/dist/esm/__tests__/identity-cache/identity-cache-db-service.spec.js +672 -672
  301. package/dist/esm/__tests__/identity-cache/trait-merging-and-staleness.spec.d.ts +1 -1
  302. package/dist/esm/__tests__/identity-cache/trait-merging-and-staleness.spec.js +586 -586
  303. package/dist/esm/__tests__/integration/sqs-bundling-roundtrip.spec.d.ts +1 -1
  304. package/dist/esm/__tests__/integration/sqs-bundling-roundtrip.spec.js +582 -582
  305. package/dist/esm/__tests__/libs/compress-decompress.spec.d.ts +1 -1
  306. package/dist/esm/__tests__/libs/compress-decompress.spec.js +14 -14
  307. package/dist/esm/__tests__/libs/currency.spec.d.ts +1 -1
  308. package/dist/esm/__tests__/libs/currency.spec.js +218 -218
  309. package/dist/esm/__tests__/libs/dates.spec.d.ts +1 -1
  310. package/dist/esm/__tests__/libs/dates.spec.js +128 -128
  311. package/dist/esm/__tests__/libs/domain.spec.d.ts +1 -1
  312. package/dist/esm/__tests__/libs/domain.spec.js +105 -105
  313. package/dist/esm/__tests__/libs/numbers.spec.d.ts +1 -1
  314. package/dist/esm/__tests__/libs/numbers.spec.js +259 -259
  315. package/dist/esm/__tests__/s3-client/s3-client.spec.d.ts +1 -1
  316. package/dist/esm/__tests__/s3-client/s3-client.spec.js +31 -31
  317. package/dist/esm/__tests__/shopify/shopify-graphql-transformer.spec.d.ts +1 -1
  318. package/dist/esm/__tests__/shopify/shopify-graphql-transformer.spec.js +33 -33
  319. package/dist/esm/__tests__/unit/libs/api-router/public-api-router.spec.d.ts +1 -1
  320. package/dist/esm/__tests__/unit/libs/api-router/public-api-router.spec.js +156 -156
  321. package/dist/esm/__tests__/unit/libs/api-router/route-matcher.spec.d.ts +1 -1
  322. package/dist/esm/__tests__/unit/libs/api-router/route-matcher.spec.js +67 -67
  323. package/dist/esm/clients/generic/cognito-client.d.ts +23 -23
  324. package/dist/esm/clients/generic/cognito-client.js +204 -204
  325. package/dist/esm/clients/generic/dynamodb-client.d.ts +18 -18
  326. package/dist/esm/clients/generic/dynamodb-client.js +168 -168
  327. package/dist/esm/clients/generic/eventbridge-client.d.ts +14 -14
  328. package/dist/esm/clients/generic/eventbridge-client.js +47 -47
  329. package/dist/esm/clients/generic/http-client.d.ts +14 -14
  330. package/dist/esm/clients/generic/http-client.js +53 -53
  331. package/dist/esm/clients/generic/index.d.ts +13 -13
  332. package/dist/esm/clients/generic/index.js +13 -13
  333. package/dist/esm/clients/generic/lambda-invoke-client.d.ts +10 -10
  334. package/dist/esm/clients/generic/lambda-invoke-client.js +35 -35
  335. package/dist/esm/clients/generic/location-client.d.ts +8 -8
  336. package/dist/esm/clients/generic/location-client.js +27 -27
  337. package/dist/esm/clients/generic/redis-client.d.ts +33 -33
  338. package/dist/esm/clients/generic/redis-client.js +184 -184
  339. package/dist/esm/clients/generic/s3-client.d.ts +23 -23
  340. package/dist/esm/clients/generic/s3-client.js +209 -209
  341. package/dist/esm/clients/generic/singlestore-db-client.d.ts +14 -14
  342. package/dist/esm/clients/generic/singlestore-db-client.js +40 -40
  343. package/dist/esm/clients/generic/sqs-bundled-client.d.ts +15 -15
  344. package/dist/esm/clients/generic/sqs-bundled-client.js +307 -307
  345. package/dist/esm/clients/generic/sqs-bundled-client.types.d.ts +53 -53
  346. package/dist/esm/clients/generic/sqs-bundled-client.types.js +14 -14
  347. package/dist/esm/clients/generic/sqs-client.d.ts +53 -53
  348. package/dist/esm/clients/generic/sqs-client.js +281 -281
  349. package/dist/esm/clients/generic/sqs-unbundle.d.ts +32 -32
  350. package/dist/esm/clients/generic/sqs-unbundle.js +137 -137
  351. package/dist/esm/clients/index.d.ts +3 -3
  352. package/dist/esm/clients/index.js +3 -3
  353. package/dist/esm/clients/internal-api/accounts-client.d.ts +91 -91
  354. package/dist/esm/clients/internal-api/accounts-client.js +125 -125
  355. package/dist/esm/clients/internal-api/cache-lambda-client.d.ts +26 -26
  356. package/dist/esm/clients/internal-api/cache-lambda-client.js +85 -85
  357. package/dist/esm/clients/internal-api/db-management-client.d.ts +18 -18
  358. package/dist/esm/clients/internal-api/db-management-client.js +32 -32
  359. package/dist/esm/clients/internal-api/destinations-client.d.ts +34 -34
  360. package/dist/esm/clients/internal-api/destinations-client.js +75 -75
  361. package/dist/esm/clients/internal-api/event-collector-client.d.ts +20 -20
  362. package/dist/esm/clients/internal-api/event-collector-client.js +32 -32
  363. package/dist/esm/clients/internal-api/identity-client.d.ts +31 -31
  364. package/dist/esm/clients/internal-api/identity-client.js +87 -87
  365. package/dist/esm/clients/internal-api/index.d.ts +9 -9
  366. package/dist/esm/clients/internal-api/index.js +9 -9
  367. package/dist/esm/clients/internal-api/shopify-app-install-client.d.ts +37 -37
  368. package/dist/esm/clients/internal-api/shopify-app-install-client.js +77 -77
  369. package/dist/esm/clients/internal-api/subscriptions-client.d.ts +26 -26
  370. package/dist/esm/clients/internal-api/subscriptions-client.js +73 -73
  371. package/dist/esm/clients/internal-api/users-auth-client.d.ts +35 -35
  372. package/dist/esm/clients/internal-api/users-auth-client.js +106 -106
  373. package/dist/esm/clients/third-party/audience-acuity/acuity-api-service.d.ts +8 -0
  374. package/dist/esm/clients/third-party/audience-acuity/acuity-api-service.js +51 -0
  375. package/dist/esm/clients/third-party/audience-acuity/acuity-api-service.js.map +1 -0
  376. package/dist/esm/clients/third-party/audience-acuity/acuity-client.d.ts +9 -0
  377. package/dist/esm/clients/third-party/audience-acuity/acuity-client.js +31 -0
  378. package/dist/esm/clients/third-party/audience-acuity/acuity-client.js.map +1 -0
  379. package/dist/esm/clients/third-party/audience-acuity/acuity-helpers.d.ts +2 -0
  380. package/dist/esm/clients/third-party/audience-acuity/acuity-helpers.js +19 -0
  381. package/dist/esm/clients/third-party/audience-acuity/acuity-helpers.js.map +1 -0
  382. package/dist/esm/clients/third-party/audience-acuity/acuity-types.d.ts +74 -0
  383. package/dist/esm/clients/third-party/audience-acuity/acuity-types.js +2 -0
  384. package/dist/esm/clients/third-party/audience-acuity/acuity-types.js.map +1 -0
  385. package/dist/esm/clients/third-party/audience-acuity/index.d.ts +4 -0
  386. package/dist/esm/clients/third-party/audience-acuity/index.js +5 -0
  387. package/dist/esm/clients/third-party/audience-acuity/index.js.map +1 -0
  388. package/dist/esm/clients/third-party/emailable-client.d.ts +7 -7
  389. package/dist/esm/clients/third-party/emailable-client.js +21 -21
  390. package/dist/esm/clients/third-party/exchange-rate-api-client.d.ts +17 -17
  391. package/dist/esm/clients/third-party/exchange-rate-api-client.js +15 -15
  392. package/dist/esm/clients/third-party/index.d.ts +5 -4
  393. package/dist/esm/clients/third-party/index.js +5 -4
  394. package/dist/esm/clients/third-party/index.js.map +1 -1
  395. package/dist/esm/clients/third-party/loops-client.d.ts +10 -10
  396. package/dist/esm/clients/third-party/loops-client.js +26 -26
  397. package/dist/esm/clients/third-party/shopify/graphql-order-queries.d.ts +25 -25
  398. package/dist/esm/clients/third-party/shopify/graphql-order-queries.js +1 -1
  399. package/dist/esm/clients/third-party/shopify/graphql-product-queries.d.ts +2 -2
  400. package/dist/esm/clients/third-party/shopify/graphql-product-queries.js +2 -2
  401. package/dist/esm/clients/third-party/shopify/shopify-graphql-client.d.ts +10 -10
  402. package/dist/esm/clients/third-party/shopify/shopify-graphql-client.js +157 -157
  403. package/dist/esm/clients/third-party/shopify-client.d.ts +29 -29
  404. package/dist/esm/clients/third-party/shopify-client.js +142 -142
  405. package/dist/esm/constants/index.d.ts +1 -1
  406. package/dist/esm/constants/index.js +1 -1
  407. package/dist/esm/constants/sqs.d.ts +20 -20
  408. package/dist/esm/constants/sqs.js +22 -22
  409. package/dist/esm/helpers/account-users-helper.d.ts +2 -2
  410. package/dist/esm/helpers/account-users-helper.js +18 -18
  411. package/dist/esm/helpers/api-key-auth-helper.d.ts +9 -9
  412. package/dist/esm/helpers/api-key-auth-helper.js +35 -35
  413. package/dist/esm/helpers/api-key-authorizer-helper.d.ts +36 -36
  414. package/dist/esm/helpers/api-key-authorizer-helper.js +83 -83
  415. package/dist/esm/helpers/identity-cache-helper.d.ts +21 -21
  416. package/dist/esm/helpers/identity-cache-helper.js +151 -151
  417. package/dist/esm/helpers/index.d.ts +9 -9
  418. package/dist/esm/helpers/index.js +9 -9
  419. package/dist/esm/helpers/input-validation-helper.d.ts +3 -3
  420. package/dist/esm/helpers/input-validation-helper.js +18 -18
  421. package/dist/esm/helpers/logging-helper.d.ts +16 -16
  422. package/dist/esm/helpers/logging-helper.js +56 -56
  423. package/dist/esm/helpers/response-helper.d.ts +18 -18
  424. package/dist/esm/helpers/response-helper.js +37 -37
  425. package/dist/esm/helpers/shopify-helper.d.ts +9 -9
  426. package/dist/esm/helpers/shopify-helper.js +21 -21
  427. package/dist/esm/helpers/sqs-utils.d.ts +6 -6
  428. package/dist/esm/helpers/sqs-utils.js +9 -9
  429. package/dist/esm/index.d.ts +7 -7
  430. package/dist/esm/index.js +7 -7
  431. package/dist/esm/libs/api-router/index.d.ts +2 -2
  432. package/dist/esm/libs/api-router/index.js +2 -2
  433. package/dist/esm/libs/api-router/public-api-router.d.ts +3 -3
  434. package/dist/esm/libs/api-router/public-api-router.js +32 -32
  435. package/dist/esm/libs/api-router/route-matcher.d.ts +21 -21
  436. package/dist/esm/libs/api-router/route-matcher.js +30 -30
  437. package/dist/esm/libs/click-id-parser.d.ts +23 -23
  438. package/dist/esm/libs/click-id-parser.js +45 -45
  439. package/dist/esm/libs/compression.d.ts +2 -2
  440. package/dist/esm/libs/compression.js +25 -25
  441. package/dist/esm/libs/cookie.d.ts +17 -17
  442. package/dist/esm/libs/cookie.js +70 -70
  443. package/dist/esm/libs/crypto.d.ts +4 -4
  444. package/dist/esm/libs/crypto.js +15 -15
  445. package/dist/esm/libs/csv.d.ts +2 -2
  446. package/dist/esm/libs/csv.js +30 -30
  447. package/dist/esm/libs/currency.d.ts +1 -1
  448. package/dist/esm/libs/currency.js +22 -22
  449. package/dist/esm/libs/dates.d.ts +12 -12
  450. package/dist/esm/libs/dates.js +83 -83
  451. package/dist/esm/libs/domain.d.ts +2 -2
  452. package/dist/esm/libs/domain.js +33 -33
  453. package/dist/esm/libs/emails.d.ts +6 -6
  454. package/dist/esm/libs/emails.js +115 -115
  455. package/dist/esm/libs/http-error.d.ts +21 -21
  456. package/dist/esm/libs/http-error.js +59 -59
  457. package/dist/esm/libs/http-status-codes.d.ts +58 -58
  458. package/dist/esm/libs/http-status-codes.js +59 -59
  459. package/dist/esm/libs/index.d.ts +18 -18
  460. package/dist/esm/libs/index.js +18 -18
  461. package/dist/esm/libs/numbers.d.ts +1 -1
  462. package/dist/esm/libs/numbers.js +11 -11
  463. package/dist/esm/libs/referrer-parser/index.d.ts +2 -2
  464. package/dist/esm/libs/referrer-parser/index.js +2 -2
  465. package/dist/esm/libs/referrer-parser/referrer-data.d.ts +9 -9
  466. package/dist/esm/libs/referrer-parser/referrer-data.js +3304 -3304
  467. package/dist/esm/libs/referrer-parser/referrer-parser-util.d.ts +20 -20
  468. package/dist/esm/libs/referrer-parser/referrer-parser-util.js +124 -124
  469. package/dist/esm/libs/strings.d.ts +3 -3
  470. package/dist/esm/libs/strings.js +40 -40
  471. package/dist/esm/libs/traits.d.ts +6 -6
  472. package/dist/esm/libs/traits.js +54 -54
  473. package/dist/esm/libs/url.d.ts +1 -1
  474. package/dist/esm/libs/url.js +9 -9
  475. package/dist/esm/services/cache/generic-cached-object.d.ts +5 -5
  476. package/dist/esm/services/cache/generic-cached-object.js +1 -1
  477. package/dist/esm/services/cache/index.d.ts +1 -1
  478. package/dist/esm/services/cache/index.js +1 -1
  479. package/dist/esm/services/cache/product-cache-service.d.ts +21 -21
  480. package/dist/esm/services/cache/product-cache-service.js +68 -68
  481. package/dist/esm/services/currency-exchange-rate-lookup-service.d.ts +11 -11
  482. package/dist/esm/services/currency-exchange-rate-lookup-service.js +62 -62
  483. package/dist/esm/services/db/accounts-db-service.d.ts +9 -9
  484. package/dist/esm/services/db/accounts-db-service.js +29 -29
  485. package/dist/esm/services/db/api-keys-db-service.d.ts +10 -10
  486. package/dist/esm/services/db/api-keys-db-service.js +32 -32
  487. package/dist/esm/services/db/currency-exchange-rates-db-service.d.ts +21 -21
  488. package/dist/esm/services/db/currency-exchange-rates-db-service.js +35 -35
  489. package/dist/esm/services/db/destinations-db-service.d.ts +12 -12
  490. package/dist/esm/services/db/destinations-db-service.js +72 -72
  491. package/dist/esm/services/db/identity-cache-db-service.d.ts +28 -28
  492. package/dist/esm/services/db/identity-cache-db-service.js +313 -313
  493. package/dist/esm/services/db/index.d.ts +13 -13
  494. package/dist/esm/services/db/index.js +13 -13
  495. package/dist/esm/services/db/log-events-db-service.d.ts +11 -11
  496. package/dist/esm/services/db/log-events-db-service.js +177 -177
  497. package/dist/esm/services/db/pixels-db-service.d.ts +8 -8
  498. package/dist/esm/services/db/pixels-db-service.js +31 -31
  499. package/dist/esm/services/db/purchasable-contacts-db-service.d.ts +9 -9
  500. package/dist/esm/services/db/purchasable-contacts-db-service.js +39 -39
  501. package/dist/esm/services/db/purchased-contacts-db-service.d.ts +17 -17
  502. package/dist/esm/services/db/purchased-contacts-db-service.js +139 -139
  503. package/dist/esm/services/db/shopify-app-installs-db-service.d.ts +8 -8
  504. package/dist/esm/services/db/shopify-app-installs-db-service.js +47 -47
  505. package/dist/esm/services/db/shopify-products-cache-db-service.d.ts +16 -16
  506. package/dist/esm/services/db/shopify-products-cache-db-service.js +66 -66
  507. package/dist/esm/services/db/subscriptions-db-service.d.ts +10 -10
  508. package/dist/esm/services/db/subscriptions-db-service.js +30 -30
  509. package/dist/esm/services/db/tracking-events-db-service.d.ts +20 -20
  510. package/dist/esm/services/db/tracking-events-db-service.js +161 -161
  511. package/dist/esm/services/eventbridge-integration-service.d.ts +9 -9
  512. package/dist/esm/services/eventbridge-integration-service.js +24 -24
  513. package/dist/esm/services/events/index.d.ts +3 -3
  514. package/dist/esm/services/events/index.js +3 -3
  515. package/dist/esm/services/events/log-event-service.d.ts +19 -19
  516. package/dist/esm/services/events/log-event-service.js +73 -73
  517. package/dist/esm/services/events/metric-event-service.d.ts +9 -9
  518. package/dist/esm/services/events/metric-event-service.js +45 -45
  519. package/dist/esm/services/events/tracking-event-sqs-service.d.ts +8 -8
  520. package/dist/esm/services/events/tracking-event-sqs-service.js +30 -30
  521. package/dist/esm/services/generic-cache-service.d.ts +7 -7
  522. package/dist/esm/services/generic-cache-service.js +29 -29
  523. package/dist/esm/services/index.d.ts +8 -8
  524. package/dist/esm/services/index.js +8 -8
  525. package/dist/esm/services/ipdata-lookup-service.d.ts +20 -20
  526. package/dist/esm/services/ipdata-lookup-service.js +108 -108
  527. package/dist/esm/services/shopify/index.d.ts +2 -2
  528. package/dist/esm/services/shopify/index.js +2 -2
  529. package/dist/esm/services/shopify/products/index.d.ts +1 -1
  530. package/dist/esm/services/shopify/products/index.js +1 -1
  531. package/dist/esm/services/shopify/products/shopify-products-serviceV2.d.ts +17 -17
  532. package/dist/esm/services/shopify/products/shopify-products-serviceV2.js +108 -108
  533. package/dist/esm/services/shopify/shopify-graphql-transformer.d.ts +8 -8
  534. package/dist/esm/services/shopify/shopify-graphql-transformer.js +138 -138
  535. package/dist/esm/types/api-response.d.ts +6 -6
  536. package/dist/esm/types/api-response.js +1 -1
  537. package/dist/esm/types/index.d.ts +3 -3
  538. package/dist/esm/types/index.js +3 -3
  539. package/dist/esm/types/internal-events/event-detail-types.d.ts +20 -20
  540. package/dist/esm/types/internal-events/event-detail-types.js +24 -24
  541. package/dist/esm/types/internal-events/index.d.ts +1 -1
  542. package/dist/esm/types/internal-events/index.js +1 -1
  543. package/dist/esm/types/shopify-graphql-types/admin.generated.d.ts +123 -123
  544. package/dist/esm/types/shopify-graphql-types/admin.generated.js +1 -1
  545. package/dist/esm/types/shopify-graphql-types/admin.types.d.ts +26289 -26289
  546. package/dist/esm/types/shopify-graphql-types/admin.types.js +5299 -5299
  547. package/dist/esm/types/shopify-graphql-types/index.d.ts +2 -2
  548. package/dist/esm/types/shopify-graphql-types/index.js +2 -2
  549. package/dist/esm/types/shopify-rest-types.d.ts +767 -767
  550. package/dist/esm/types/shopify-rest-types.js +1 -1
  551. package/dist/esm/utils/compression.d.ts +36 -36
  552. package/dist/esm/utils/compression.js +187 -187
  553. package/dist/esm/utils/index.d.ts +3 -3
  554. package/dist/esm/utils/index.js +3 -3
  555. package/dist/esm/utils/retry-envelope.d.ts +12 -12
  556. package/dist/esm/utils/retry-envelope.js +22 -22
  557. package/dist/esm/utils/size.d.ts +2 -2
  558. package/dist/esm/utils/size.js +44 -44
  559. 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