@adtrackify/at-service-common 3.19.20 → 3.19.22

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 (810) hide show
  1. package/dist/cjs/__tests__/clients/acuity-client.spec.d.ts +1 -1
  2. package/dist/cjs/__tests__/clients/acuity-client.spec.js +43 -43
  3. package/dist/cjs/__tests__/clients/cross-platform-compression.spec.d.ts +1 -1
  4. package/dist/cjs/__tests__/clients/cross-platform-compression.spec.js +364 -354
  5. package/dist/cjs/__tests__/clients/cross-platform-compression.spec.js.map +1 -1
  6. package/dist/cjs/__tests__/clients/dynamodb-client.spec.d.ts +1 -1
  7. package/dist/cjs/__tests__/clients/dynamodb-client.spec.js +194 -194
  8. package/dist/cjs/__tests__/clients/dynamodb-client.spec.js.map +1 -1
  9. package/dist/cjs/__tests__/clients/sqs-bundled-client.spec.d.ts +1 -1
  10. package/dist/cjs/__tests__/clients/sqs-bundled-client.spec.js +941 -931
  11. package/dist/cjs/__tests__/clients/sqs-bundled-client.spec.js.map +1 -1
  12. package/dist/cjs/__tests__/clients/sqs-bundling-contracts.spec.d.ts +1 -1
  13. package/dist/cjs/__tests__/clients/sqs-bundling-contracts.spec.js +573 -563
  14. package/dist/cjs/__tests__/clients/sqs-bundling-contracts.spec.js.map +1 -1
  15. package/dist/cjs/__tests__/clients/sqs-client.spec.d.ts +1 -1
  16. package/dist/cjs/__tests__/clients/sqs-client.spec.js +191 -191
  17. package/dist/cjs/__tests__/clients/sqs-client.spec.js.map +1 -1
  18. package/dist/cjs/__tests__/clients/sqs-unbundle.spec.d.ts +1 -1
  19. package/dist/cjs/__tests__/clients/sqs-unbundle.spec.js +1357 -1357
  20. package/dist/cjs/__tests__/clients/sqs-unbundle.spec.js.map +1 -1
  21. package/dist/cjs/__tests__/db/contact-enrichments-db-service.spec.d.ts +1 -1
  22. package/dist/cjs/__tests__/db/contact-enrichments-db-service.spec.js +68 -68
  23. package/dist/cjs/__tests__/db/destinations-db-service.spec.d.ts +1 -1
  24. package/dist/cjs/__tests__/db/destinations-db-service.spec.js +125 -125
  25. package/dist/cjs/__tests__/db/shared-read-db-services.spec.d.ts +1 -1
  26. package/dist/cjs/__tests__/db/shared-read-db-services.spec.js +89 -89
  27. package/dist/cjs/__tests__/db/shopify-app-installs-db-service.spec.d.ts +1 -1
  28. package/dist/cjs/__tests__/db/shopify-app-installs-db-service.spec.js +104 -104
  29. package/dist/cjs/__tests__/db/subscriptions-db-service.spec.d.ts +1 -1
  30. package/dist/cjs/__tests__/db/subscriptions-db-service.spec.js +95 -95
  31. package/dist/cjs/__tests__/db/user-accounts-db-service.spec.d.ts +1 -1
  32. package/dist/cjs/__tests__/db/user-accounts-db-service.spec.js +76 -76
  33. package/dist/cjs/__tests__/helpers/account-users-helper.spec.d.ts +1 -1
  34. package/dist/cjs/__tests__/helpers/account-users-helper.spec.js +220 -220
  35. package/dist/cjs/__tests__/helpers/acuity-helper.spec.d.ts +1 -1
  36. package/dist/cjs/__tests__/helpers/acuity-helper.spec.js +69 -69
  37. package/dist/cjs/__tests__/helpers/api-key-auth-helper.spec.d.ts +1 -1
  38. package/dist/cjs/__tests__/helpers/api-key-auth-helper.spec.js +82 -82
  39. package/dist/cjs/__tests__/identity-cache/identity-cache-db-service.spec.d.ts +1 -1
  40. package/dist/cjs/__tests__/identity-cache/identity-cache-db-service.spec.js +674 -674
  41. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service.spec.d.ts +1 -1
  42. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js +1140 -1140
  43. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js.map +1 -1
  44. package/dist/cjs/__tests__/identity-cache/trait-merging-and-staleness.spec.d.ts +1 -1
  45. package/dist/cjs/__tests__/identity-cache/trait-merging-and-staleness.spec.js +588 -588
  46. package/dist/cjs/__tests__/integration/sqs-bundling-roundtrip.spec.d.ts +1 -1
  47. package/dist/cjs/__tests__/integration/sqs-bundling-roundtrip.spec.js +584 -584
  48. package/dist/cjs/__tests__/integration/sqs-bundling-roundtrip.spec.js.map +1 -1
  49. package/dist/cjs/__tests__/libs/compress-decompress.spec.d.ts +1 -1
  50. package/dist/cjs/__tests__/libs/compress-decompress.spec.js +16 -16
  51. package/dist/cjs/__tests__/libs/contacts.spec.d.ts +1 -1
  52. package/dist/cjs/__tests__/libs/contacts.spec.js +294 -294
  53. package/dist/cjs/__tests__/libs/currency.spec.d.ts +1 -1
  54. package/dist/cjs/__tests__/libs/currency.spec.js +220 -220
  55. package/dist/cjs/__tests__/libs/dates.spec.d.ts +1 -1
  56. package/dist/cjs/__tests__/libs/dates.spec.js +130 -130
  57. package/dist/cjs/__tests__/libs/dates.spec.js.map +1 -1
  58. package/dist/cjs/__tests__/libs/domain.spec.d.ts +1 -1
  59. package/dist/cjs/__tests__/libs/domain.spec.js +107 -107
  60. package/dist/cjs/__tests__/libs/numbers.spec.d.ts +1 -1
  61. package/dist/cjs/__tests__/libs/numbers.spec.js +261 -261
  62. package/dist/cjs/__tests__/s3-client/s3-client.spec.d.ts +1 -1
  63. package/dist/cjs/__tests__/s3-client/s3-client.spec.js +33 -33
  64. package/dist/cjs/__tests__/services/acuity-api-service.spec.d.ts +1 -1
  65. package/dist/cjs/__tests__/services/acuity-api-service.spec.js +71 -71
  66. package/dist/cjs/__tests__/services/email-verification/contact-email-verification-service.spec.d.ts +1 -1
  67. package/dist/cjs/__tests__/services/email-verification/contact-email-verification-service.spec.js +93 -93
  68. package/dist/cjs/__tests__/services/email-verification/email-verification-service.spec.d.ts +1 -1
  69. package/dist/cjs/__tests__/services/email-verification/email-verification-service.spec.js +57 -57
  70. package/dist/cjs/__tests__/shopify/shopify-graphql-transformer.spec.d.ts +1 -1
  71. package/dist/cjs/__tests__/shopify/shopify-graphql-transformer.spec.js +35 -35
  72. package/dist/cjs/__tests__/unit/libs/api-router/public-api-router.spec.d.ts +1 -1
  73. package/dist/cjs/__tests__/unit/libs/api-router/public-api-router.spec.js +191 -181
  74. package/dist/cjs/__tests__/unit/libs/api-router/public-api-router.spec.js.map +1 -1
  75. package/dist/cjs/__tests__/unit/libs/api-router/route-matcher.spec.d.ts +1 -1
  76. package/dist/cjs/__tests__/unit/libs/api-router/route-matcher.spec.js +69 -69
  77. package/dist/cjs/__tests__/utils/custom-measure-formula-utils.spec.d.ts +1 -1
  78. package/dist/cjs/__tests__/utils/custom-measure-formula-utils.spec.js +139 -139
  79. package/dist/cjs/clients/generic/cognito-client.d.ts +23 -23
  80. package/dist/cjs/clients/generic/cognito-client.js +209 -209
  81. package/dist/cjs/clients/generic/cognito-client.js.map +1 -1
  82. package/dist/cjs/clients/generic/dynamodb-client.d.ts +20 -20
  83. package/dist/cjs/clients/generic/dynamodb-client.js +235 -229
  84. package/dist/cjs/clients/generic/dynamodb-client.js.map +1 -1
  85. package/dist/cjs/clients/generic/eventbridge-client.d.ts +14 -14
  86. package/dist/cjs/clients/generic/eventbridge-client.js +51 -51
  87. package/dist/cjs/clients/generic/http-client.d.ts +14 -14
  88. package/dist/cjs/clients/generic/http-client.js +61 -61
  89. package/dist/cjs/clients/generic/http-client.js.map +1 -1
  90. package/dist/cjs/clients/generic/index.d.ts +13 -13
  91. package/dist/cjs/clients/generic/index.js +29 -29
  92. package/dist/cjs/clients/generic/lambda-invoke-client.d.ts +10 -10
  93. package/dist/cjs/clients/generic/lambda-invoke-client.js +39 -39
  94. package/dist/cjs/clients/generic/lambda-invoke-client.js.map +1 -1
  95. package/dist/cjs/clients/generic/location-client.d.ts +8 -8
  96. package/dist/cjs/clients/generic/location-client.js +31 -31
  97. package/dist/cjs/clients/generic/location-client.js.map +1 -1
  98. package/dist/cjs/clients/generic/redis-client.d.ts +33 -33
  99. package/dist/cjs/clients/generic/redis-client.js +191 -191
  100. package/dist/cjs/clients/generic/redis-client.js.map +1 -1
  101. package/dist/cjs/clients/generic/s3-client.d.ts +22 -23
  102. package/dist/cjs/clients/generic/s3-client.js +216 -216
  103. package/dist/cjs/clients/generic/s3-client.js.map +1 -1
  104. package/dist/cjs/clients/generic/singlestore-db-client.d.ts +14 -14
  105. package/dist/cjs/clients/generic/singlestore-db-client.js +77 -67
  106. package/dist/cjs/clients/generic/singlestore-db-client.js.map +1 -1
  107. package/dist/cjs/clients/generic/sqs-bundled-client.d.ts +15 -15
  108. package/dist/cjs/clients/generic/sqs-bundled-client.js +311 -311
  109. package/dist/cjs/clients/generic/sqs-bundled-client.js.map +1 -1
  110. package/dist/cjs/clients/generic/sqs-bundled-client.types.d.ts +53 -53
  111. package/dist/cjs/clients/generic/sqs-bundled-client.types.js +17 -17
  112. package/dist/cjs/clients/generic/sqs-bundled-client.types.js.map +1 -1
  113. package/dist/cjs/clients/generic/sqs-client.d.ts +53 -53
  114. package/dist/cjs/clients/generic/sqs-client.js +285 -285
  115. package/dist/cjs/clients/generic/sqs-client.js.map +1 -1
  116. package/dist/cjs/clients/generic/sqs-unbundle.d.ts +32 -32
  117. package/dist/cjs/clients/generic/sqs-unbundle.js +143 -144
  118. package/dist/cjs/clients/generic/sqs-unbundle.js.map +1 -1
  119. package/dist/cjs/clients/index.d.ts +3 -3
  120. package/dist/cjs/clients/index.js +19 -19
  121. package/dist/cjs/clients/internal-api/accounts-client.d.ts +91 -91
  122. package/dist/cjs/clients/internal-api/accounts-client.js +129 -129
  123. package/dist/cjs/clients/internal-api/accounts-client.js.map +1 -1
  124. package/dist/cjs/clients/internal-api/cache-lambda-client.d.ts +26 -26
  125. package/dist/cjs/clients/internal-api/cache-lambda-client.js +89 -89
  126. package/dist/cjs/clients/internal-api/cache-lambda-client.js.map +1 -1
  127. package/dist/cjs/clients/internal-api/db-management-client.d.ts +18 -18
  128. package/dist/cjs/clients/internal-api/db-management-client.js +36 -36
  129. package/dist/cjs/clients/internal-api/destinations-client.d.ts +34 -34
  130. package/dist/cjs/clients/internal-api/destinations-client.js +79 -79
  131. package/dist/cjs/clients/internal-api/destinations-client.js.map +1 -1
  132. package/dist/cjs/clients/internal-api/event-collector-client.d.ts +20 -20
  133. package/dist/cjs/clients/internal-api/event-collector-client.js +36 -36
  134. package/dist/cjs/clients/internal-api/identity-client.d.ts +31 -31
  135. package/dist/cjs/clients/internal-api/identity-client.js +91 -91
  136. package/dist/cjs/clients/internal-api/identity-client.js.map +1 -1
  137. package/dist/cjs/clients/internal-api/index.d.ts +9 -9
  138. package/dist/cjs/clients/internal-api/index.js +25 -25
  139. package/dist/cjs/clients/internal-api/shopify-app-install-client.d.ts +37 -37
  140. package/dist/cjs/clients/internal-api/shopify-app-install-client.js +81 -81
  141. package/dist/cjs/clients/internal-api/subscriptions-client.d.ts +26 -26
  142. package/dist/cjs/clients/internal-api/subscriptions-client.js +77 -77
  143. package/dist/cjs/clients/internal-api/users-auth-client.d.ts +35 -35
  144. package/dist/cjs/clients/internal-api/users-auth-client.js +110 -110
  145. package/dist/cjs/clients/internal-api/users-auth-client.js.map +1 -1
  146. package/dist/cjs/clients/third-party/acuity-client.d.ts +10 -10
  147. package/dist/cjs/clients/third-party/acuity-client.js +40 -40
  148. package/dist/cjs/clients/third-party/emailable-client.d.ts +7 -7
  149. package/dist/cjs/clients/third-party/emailable-client.js +25 -25
  150. package/dist/cjs/clients/third-party/emailable-client.js.map +1 -1
  151. package/dist/cjs/clients/third-party/exchange-rate-api-client.d.ts +17 -17
  152. package/dist/cjs/clients/third-party/exchange-rate-api-client.js +19 -19
  153. package/dist/cjs/clients/third-party/index.d.ts +5 -5
  154. package/dist/cjs/clients/third-party/index.js +21 -21
  155. package/dist/cjs/clients/third-party/loops-client.d.ts +10 -10
  156. package/dist/cjs/clients/third-party/loops-client.js +30 -30
  157. package/dist/cjs/clients/third-party/loops-client.js.map +1 -1
  158. package/dist/cjs/clients/third-party/shopify/graphql-order-queries.d.ts +25 -25
  159. package/dist/cjs/clients/third-party/shopify/graphql-order-queries.js +4 -4
  160. package/dist/cjs/clients/third-party/shopify/graphql-product-queries.d.ts +2 -2
  161. package/dist/cjs/clients/third-party/shopify/graphql-product-queries.js +5 -5
  162. package/dist/cjs/clients/third-party/shopify/shopify-graphql-client.d.ts +10 -10
  163. package/dist/cjs/clients/third-party/shopify/shopify-graphql-client.js +161 -161
  164. package/dist/cjs/clients/third-party/shopify/shopify-graphql-client.js.map +1 -1
  165. package/dist/cjs/clients/third-party/shopify-client.d.ts +29 -29
  166. package/dist/cjs/clients/third-party/shopify-client.js +146 -146
  167. package/dist/cjs/clients/third-party/shopify-client.js.map +1 -1
  168. package/dist/cjs/constants/index.d.ts +1 -1
  169. package/dist/cjs/constants/index.js +17 -17
  170. package/dist/cjs/constants/sqs.d.ts +20 -20
  171. package/dist/cjs/constants/sqs.js +26 -26
  172. package/dist/cjs/constants/sqs.js.map +1 -1
  173. package/dist/cjs/helpers/account-users-helper.d.ts +2 -2
  174. package/dist/cjs/helpers/account-users-helper.js +22 -22
  175. package/dist/cjs/helpers/account-users-helper.js.map +1 -1
  176. package/dist/cjs/helpers/acuity-helper.d.ts +4 -4
  177. package/dist/cjs/helpers/acuity-helper.js +56 -56
  178. package/dist/cjs/helpers/acuity-helper.js.map +1 -1
  179. package/dist/cjs/helpers/api-key-auth-helper.d.ts +10 -9
  180. package/dist/cjs/helpers/api-key-auth-helper.js +40 -40
  181. package/dist/cjs/helpers/api-key-auth-helper.js.map +1 -1
  182. package/dist/cjs/helpers/api-key-authorizer-helper.d.ts +36 -36
  183. package/dist/cjs/helpers/api-key-authorizer-helper.js +87 -87
  184. package/dist/cjs/helpers/api-key-authorizer-helper.js.map +1 -1
  185. package/dist/cjs/helpers/identity-cache-helper.d.ts +21 -21
  186. package/dist/cjs/helpers/identity-cache-helper.js +157 -157
  187. package/dist/cjs/helpers/identity-cache-helper.js.map +1 -1
  188. package/dist/cjs/helpers/index.d.ts +10 -10
  189. package/dist/cjs/helpers/index.js +26 -26
  190. package/dist/cjs/helpers/input-validation-helper.d.ts +3 -3
  191. package/dist/cjs/helpers/input-validation-helper.js +22 -22
  192. package/dist/cjs/helpers/input-validation-helper.js.map +1 -1
  193. package/dist/cjs/helpers/logging-helper.d.ts +16 -16
  194. package/dist/cjs/helpers/logging-helper.js +94 -84
  195. package/dist/cjs/helpers/logging-helper.js.map +1 -1
  196. package/dist/cjs/helpers/response-helper.d.ts +18 -18
  197. package/dist/cjs/helpers/response-helper.js +43 -43
  198. package/dist/cjs/helpers/response-helper.js.map +1 -1
  199. package/dist/cjs/helpers/shopify-helper.d.ts +9 -9
  200. package/dist/cjs/helpers/shopify-helper.js +26 -26
  201. package/dist/cjs/helpers/shopify-helper.js.map +1 -1
  202. package/dist/cjs/helpers/sqs-utils.d.ts +6 -6
  203. package/dist/cjs/helpers/sqs-utils.js +13 -14
  204. package/dist/cjs/helpers/sqs-utils.js.map +1 -1
  205. package/dist/cjs/index.d.ts +7 -7
  206. package/dist/cjs/index.js +23 -23
  207. package/dist/cjs/libs/api-router/index.d.ts +2 -2
  208. package/dist/cjs/libs/api-router/index.js +18 -18
  209. package/dist/cjs/libs/api-router/public-api-router.d.ts +3 -3
  210. package/dist/cjs/libs/api-router/public-api-router.js +36 -36
  211. package/dist/cjs/libs/api-router/public-api-router.js.map +1 -1
  212. package/dist/cjs/libs/api-router/route-matcher.d.ts +21 -21
  213. package/dist/cjs/libs/api-router/route-matcher.js +36 -36
  214. package/dist/cjs/libs/api-router/route-matcher.js.map +1 -1
  215. package/dist/cjs/libs/click-id-parser.d.ts +23 -23
  216. package/dist/cjs/libs/click-id-parser.js +49 -49
  217. package/dist/cjs/libs/click-id-parser.js.map +1 -1
  218. package/dist/cjs/libs/compression.d.ts +2 -2
  219. package/dist/cjs/libs/compression.js +33 -33
  220. package/dist/cjs/libs/compression.js.map +1 -1
  221. package/dist/cjs/libs/contacts.d.ts +7 -7
  222. package/dist/cjs/libs/contacts.js +152 -152
  223. package/dist/cjs/libs/contacts.js.map +1 -1
  224. package/dist/cjs/libs/cookie.d.ts +17 -17
  225. package/dist/cjs/libs/cookie.js +76 -76
  226. package/dist/cjs/libs/cookie.js.map +1 -1
  227. package/dist/cjs/libs/crypto.d.ts +4 -4
  228. package/dist/cjs/libs/crypto.js +25 -25
  229. package/dist/cjs/libs/csv.d.ts +2 -2
  230. package/dist/cjs/libs/csv.js +35 -35
  231. package/dist/cjs/libs/csv.js.map +1 -1
  232. package/dist/cjs/libs/currency.d.ts +1 -1
  233. package/dist/cjs/libs/currency.js +29 -29
  234. package/dist/cjs/libs/currency.js.map +1 -1
  235. package/dist/cjs/libs/dates.d.ts +12 -12
  236. package/dist/cjs/libs/dates.js +96 -96
  237. package/dist/cjs/libs/dates.js.map +1 -1
  238. package/dist/cjs/libs/domain.d.ts +2 -2
  239. package/dist/cjs/libs/domain.js +38 -38
  240. package/dist/cjs/libs/domain.js.map +1 -1
  241. package/dist/cjs/libs/emails.d.ts +8 -8
  242. package/dist/cjs/libs/emails.js +154 -154
  243. package/dist/cjs/libs/emails.js.map +1 -1
  244. package/dist/cjs/libs/http-error.d.ts +21 -21
  245. package/dist/cjs/libs/http-error.js +63 -63
  246. package/dist/cjs/libs/http-error.js.map +1 -1
  247. package/dist/cjs/libs/http-status-codes.d.ts +58 -58
  248. package/dist/cjs/libs/http-status-codes.js +62 -62
  249. package/dist/cjs/libs/http-status-codes.js.map +1 -1
  250. package/dist/cjs/libs/index.d.ts +19 -19
  251. package/dist/cjs/libs/index.js +35 -35
  252. package/dist/cjs/libs/numbers.d.ts +1 -1
  253. package/dist/cjs/libs/numbers.js +15 -15
  254. package/dist/cjs/libs/numbers.js.map +1 -1
  255. package/dist/cjs/libs/referrer-parser/index.d.ts +2 -2
  256. package/dist/cjs/libs/referrer-parser/index.js +18 -18
  257. package/dist/cjs/libs/referrer-parser/referrer-data.d.ts +9 -9
  258. package/dist/cjs/libs/referrer-parser/referrer-data.js +3307 -3307
  259. package/dist/cjs/libs/referrer-parser/referrer-parser-util.d.ts +19 -20
  260. package/dist/cjs/libs/referrer-parser/referrer-parser-util.js +131 -131
  261. package/dist/cjs/libs/referrer-parser/referrer-parser-util.js.map +1 -1
  262. package/dist/cjs/libs/strings.d.ts +3 -3
  263. package/dist/cjs/libs/strings.js +46 -46
  264. package/dist/cjs/libs/strings.js.map +1 -1
  265. package/dist/cjs/libs/traits.d.ts +6 -6
  266. package/dist/cjs/libs/traits.js +65 -65
  267. package/dist/cjs/libs/traits.js.map +1 -1
  268. package/dist/cjs/libs/url.d.ts +1 -1
  269. package/dist/cjs/libs/url.js +13 -13
  270. package/dist/cjs/services/acuity-api-service.d.ts +9 -9
  271. package/dist/cjs/services/acuity-api-service.js +73 -73
  272. package/dist/cjs/services/acuity-api-service.js.map +1 -1
  273. package/dist/cjs/services/cache/generic-cached-object.d.ts +5 -5
  274. package/dist/cjs/services/cache/generic-cached-object.js +2 -2
  275. package/dist/cjs/services/cache/index.d.ts +1 -1
  276. package/dist/cjs/services/cache/index.js +17 -17
  277. package/dist/cjs/services/cache/product-cache-service.d.ts +21 -21
  278. package/dist/cjs/services/cache/product-cache-service.js +76 -76
  279. package/dist/cjs/services/cache/product-cache-service.js.map +1 -1
  280. package/dist/cjs/services/currency-exchange-rate-lookup-service.d.ts +11 -11
  281. package/dist/cjs/services/currency-exchange-rate-lookup-service.js +66 -66
  282. package/dist/cjs/services/currency-exchange-rate-lookup-service.js.map +1 -1
  283. package/dist/cjs/services/db/accounts-db-service.d.ts +9 -9
  284. package/dist/cjs/services/db/accounts-db-service.js +33 -33
  285. package/dist/cjs/services/db/accounts-db-service.js.map +1 -1
  286. package/dist/cjs/services/db/api-keys-db-service.d.ts +10 -10
  287. package/dist/cjs/services/db/api-keys-db-service.js +36 -36
  288. package/dist/cjs/services/db/api-keys-db-service.js.map +1 -1
  289. package/dist/cjs/services/db/contact-enrichments-db-service.d.ts +15 -15
  290. package/dist/cjs/services/db/contact-enrichments-db-service.js +94 -94
  291. package/dist/cjs/services/db/contact-enrichments-db-service.js.map +1 -1
  292. package/dist/cjs/services/db/currency-exchange-rates-db-service.d.ts +21 -21
  293. package/dist/cjs/services/db/currency-exchange-rates-db-service.js +39 -39
  294. package/dist/cjs/services/db/custom-measures-db-service.d.ts +14 -14
  295. package/dist/cjs/services/db/custom-measures-db-service.js +48 -48
  296. package/dist/cjs/services/db/custom-measures-db-service.js.map +1 -1
  297. package/dist/cjs/services/db/destinations-db-service.d.ts +13 -13
  298. package/dist/cjs/services/db/destinations-db-service.js +74 -74
  299. package/dist/cjs/services/db/identity-cache-db-service.d.ts +28 -28
  300. package/dist/cjs/services/db/identity-cache-db-service.js +320 -320
  301. package/dist/cjs/services/db/identity-cache-db-service.js.map +1 -1
  302. package/dist/cjs/services/db/identity-cache-dynamodb-service.d.ts +28 -28
  303. package/dist/cjs/services/db/identity-cache-dynamodb-service.js +377 -377
  304. package/dist/cjs/services/db/identity-cache-dynamodb-service.js.map +1 -1
  305. package/dist/cjs/services/db/index.d.ts +17 -17
  306. package/dist/cjs/services/db/index.js +33 -33
  307. package/dist/cjs/services/db/log-events-db-service.d.ts +11 -11
  308. package/dist/cjs/services/db/log-events-db-service.js +181 -181
  309. package/dist/cjs/services/db/log-events-db-service.js.map +1 -1
  310. package/dist/cjs/services/db/pixels-db-service.d.ts +8 -8
  311. package/dist/cjs/services/db/pixels-db-service.js +35 -35
  312. package/dist/cjs/services/db/purchasable-contacts-db-service.d.ts +9 -9
  313. package/dist/cjs/services/db/purchasable-contacts-db-service.js +43 -43
  314. package/dist/cjs/services/db/purchasable-contacts-db-service.js.map +1 -1
  315. package/dist/cjs/services/db/purchased-contacts/index.d.ts +2 -2
  316. package/dist/cjs/services/db/purchased-contacts/index.js +18 -18
  317. package/dist/cjs/services/db/purchased-contacts/purchased-contacts-db-service.d.ts +18 -18
  318. package/dist/cjs/services/db/purchased-contacts/purchased-contacts-db-service.js +152 -152
  319. package/dist/cjs/services/db/purchased-contacts/purchased-contacts-db-service.js.map +1 -1
  320. package/dist/cjs/services/db/purchased-contacts/types.d.ts +11 -11
  321. package/dist/cjs/services/db/purchased-contacts/types.js +2 -2
  322. package/dist/cjs/services/db/shopify-app-installs-db-service.d.ts +10 -10
  323. package/dist/cjs/services/db/shopify-app-installs-db-service.js +52 -52
  324. package/dist/cjs/services/db/shopify-app-installs-db-service.js.map +1 -1
  325. package/dist/cjs/services/db/shopify-products-cache-db-service.d.ts +16 -16
  326. package/dist/cjs/services/db/shopify-products-cache-db-service.js +73 -73
  327. package/dist/cjs/services/db/shopify-products-cache-db-service.js.map +1 -1
  328. package/dist/cjs/services/db/subscriptions-db-service.d.ts +11 -11
  329. package/dist/cjs/services/db/subscriptions-db-service.js +38 -38
  330. package/dist/cjs/services/db/subscriptions-db-service.js.map +1 -1
  331. package/dist/cjs/services/db/tracking-events-db-service.d.ts +21 -21
  332. package/dist/cjs/services/db/tracking-events-db-service.js +188 -188
  333. package/dist/cjs/services/db/tracking-events-db-service.js.map +1 -1
  334. package/dist/cjs/services/db/user-accounts-db-service.d.ts +7 -7
  335. package/dist/cjs/services/db/user-accounts-db-service.js +17 -17
  336. package/dist/cjs/services/email-verification/contact-email-verification-service.d.ts +7 -7
  337. package/dist/cjs/services/email-verification/contact-email-verification-service.js +101 -101
  338. package/dist/cjs/services/email-verification/contact-email-verification-service.js.map +1 -1
  339. package/dist/cjs/services/email-verification/email-verification-service.d.ts +19 -19
  340. package/dist/cjs/services/email-verification/email-verification-service.js +131 -131
  341. package/dist/cjs/services/email-verification/email-verification-service.js.map +1 -1
  342. package/dist/cjs/services/email-verification/index.d.ts +2 -2
  343. package/dist/cjs/services/email-verification/index.js +18 -18
  344. package/dist/cjs/services/eventbridge-integration-service.d.ts +9 -9
  345. package/dist/cjs/services/eventbridge-integration-service.js +28 -28
  346. package/dist/cjs/services/events/index.d.ts +3 -3
  347. package/dist/cjs/services/events/index.js +19 -19
  348. package/dist/cjs/services/events/log-event-service.d.ts +19 -19
  349. package/dist/cjs/services/events/log-event-service.js +77 -77
  350. package/dist/cjs/services/events/log-event-service.js.map +1 -1
  351. package/dist/cjs/services/events/metric-event-service.d.ts +9 -9
  352. package/dist/cjs/services/events/metric-event-service.js +49 -49
  353. package/dist/cjs/services/events/tracking-event-sqs-service.d.ts +8 -8
  354. package/dist/cjs/services/events/tracking-event-sqs-service.js +34 -34
  355. package/dist/cjs/services/events/tracking-event-sqs-service.js.map +1 -1
  356. package/dist/cjs/services/generic-cache-service.d.ts +7 -7
  357. package/dist/cjs/services/generic-cache-service.js +33 -33
  358. package/dist/cjs/services/generic-cache-service.js.map +1 -1
  359. package/dist/cjs/services/index.d.ts +10 -10
  360. package/dist/cjs/services/index.js +26 -26
  361. package/dist/cjs/services/ipdata-lookup-service.d.ts +20 -20
  362. package/dist/cjs/services/ipdata-lookup-service.js +112 -112
  363. package/dist/cjs/services/ipdata-lookup-service.js.map +1 -1
  364. package/dist/cjs/services/shopify/index.d.ts +2 -2
  365. package/dist/cjs/services/shopify/index.js +18 -18
  366. package/dist/cjs/services/shopify/products/index.d.ts +1 -1
  367. package/dist/cjs/services/shopify/products/index.js +17 -17
  368. package/dist/cjs/services/shopify/products/shopify-products-serviceV2.d.ts +17 -17
  369. package/dist/cjs/services/shopify/products/shopify-products-serviceV2.js +112 -112
  370. package/dist/cjs/services/shopify/products/shopify-products-serviceV2.js.map +1 -1
  371. package/dist/cjs/services/shopify/shopify-graphql-transformer.d.ts +8 -8
  372. package/dist/cjs/services/shopify/shopify-graphql-transformer.js +141 -141
  373. package/dist/cjs/services/shopify/shopify-graphql-transformer.js.map +1 -1
  374. package/dist/cjs/types/acuity-types.d.ts +74 -74
  375. package/dist/cjs/types/acuity-types.js +2 -2
  376. package/dist/cjs/types/api-response.d.ts +6 -6
  377. package/dist/cjs/types/api-response.js +2 -2
  378. package/dist/cjs/types/index.d.ts +4 -4
  379. package/dist/cjs/types/index.js +43 -33
  380. package/dist/cjs/types/index.js.map +1 -1
  381. package/dist/cjs/types/internal-events/event-detail-types.d.ts +20 -20
  382. package/dist/cjs/types/internal-events/event-detail-types.js +27 -27
  383. package/dist/cjs/types/internal-events/event-detail-types.js.map +1 -1
  384. package/dist/cjs/types/internal-events/index.d.ts +1 -1
  385. package/dist/cjs/types/internal-events/index.js +17 -17
  386. package/dist/cjs/types/shopify-graphql-types/admin.generated.d.ts +123 -123
  387. package/dist/cjs/types/shopify-graphql-types/admin.generated.js +2 -2
  388. package/dist/cjs/types/shopify-graphql-types/admin.types.d.ts +26289 -26289
  389. package/dist/cjs/types/shopify-graphql-types/admin.types.js +5311 -5311
  390. package/dist/cjs/types/shopify-graphql-types/admin.types.js.map +1 -1
  391. package/dist/cjs/types/shopify-graphql-types/index.d.ts +2 -2
  392. package/dist/cjs/types/shopify-graphql-types/index.js +18 -18
  393. package/dist/cjs/types/shopify-rest-types.d.ts +767 -767
  394. package/dist/cjs/types/shopify-rest-types.js +2 -2
  395. package/dist/cjs/utils/compression.d.ts +34 -36
  396. package/dist/cjs/utils/compression.js +198 -198
  397. package/dist/cjs/utils/compression.js.map +1 -1
  398. package/dist/cjs/utils/custom-measure-formula-utils.d.ts +6 -6
  399. package/dist/cjs/utils/custom-measure-formula-utils.js +209 -209
  400. package/dist/cjs/utils/custom-measure-formula-utils.js.map +1 -1
  401. package/dist/cjs/utils/index.d.ts +4 -4
  402. package/dist/cjs/utils/index.js +20 -20
  403. package/dist/cjs/utils/retry-envelope.d.ts +12 -12
  404. package/dist/cjs/utils/retry-envelope.js +27 -28
  405. package/dist/cjs/utils/retry-envelope.js.map +1 -1
  406. package/dist/cjs/utils/size.d.ts +2 -2
  407. package/dist/cjs/utils/size.js +48 -49
  408. package/dist/cjs/utils/size.js.map +1 -1
  409. package/dist/esm/__tests__/clients/acuity-client.spec.d.ts +1 -1
  410. package/dist/esm/__tests__/clients/acuity-client.spec.js +41 -41
  411. package/dist/esm/__tests__/clients/cross-platform-compression.spec.d.ts +1 -1
  412. package/dist/esm/__tests__/clients/cross-platform-compression.spec.js +329 -329
  413. package/dist/esm/__tests__/clients/cross-platform-compression.spec.js.map +1 -1
  414. package/dist/esm/__tests__/clients/dynamodb-client.spec.d.ts +1 -1
  415. package/dist/esm/__tests__/clients/dynamodb-client.spec.js +192 -192
  416. package/dist/esm/__tests__/clients/dynamodb-client.spec.js.map +1 -1
  417. package/dist/esm/__tests__/clients/sqs-bundled-client.spec.d.ts +1 -1
  418. package/dist/esm/__tests__/clients/sqs-bundled-client.spec.js +906 -906
  419. package/dist/esm/__tests__/clients/sqs-bundled-client.spec.js.map +1 -1
  420. package/dist/esm/__tests__/clients/sqs-bundling-contracts.spec.d.ts +1 -1
  421. package/dist/esm/__tests__/clients/sqs-bundling-contracts.spec.js +538 -538
  422. package/dist/esm/__tests__/clients/sqs-client.spec.d.ts +1 -1
  423. package/dist/esm/__tests__/clients/sqs-client.spec.js +189 -189
  424. package/dist/esm/__tests__/clients/sqs-client.spec.js.map +1 -1
  425. package/dist/esm/__tests__/clients/sqs-unbundle.spec.d.ts +1 -1
  426. package/dist/esm/__tests__/clients/sqs-unbundle.spec.js +1355 -1355
  427. package/dist/esm/__tests__/clients/sqs-unbundle.spec.js.map +1 -1
  428. package/dist/esm/__tests__/db/contact-enrichments-db-service.spec.d.ts +1 -1
  429. package/dist/esm/__tests__/db/contact-enrichments-db-service.spec.js +66 -66
  430. package/dist/esm/__tests__/db/destinations-db-service.spec.d.ts +1 -1
  431. package/dist/esm/__tests__/db/destinations-db-service.spec.js +123 -123
  432. package/dist/esm/__tests__/db/shared-read-db-services.spec.d.ts +1 -1
  433. package/dist/esm/__tests__/db/shared-read-db-services.spec.js +87 -87
  434. package/dist/esm/__tests__/db/shopify-app-installs-db-service.spec.d.ts +1 -1
  435. package/dist/esm/__tests__/db/shopify-app-installs-db-service.spec.js +102 -102
  436. package/dist/esm/__tests__/db/subscriptions-db-service.spec.d.ts +1 -1
  437. package/dist/esm/__tests__/db/subscriptions-db-service.spec.js +93 -93
  438. package/dist/esm/__tests__/db/user-accounts-db-service.spec.d.ts +1 -1
  439. package/dist/esm/__tests__/db/user-accounts-db-service.spec.js +74 -74
  440. package/dist/esm/__tests__/helpers/account-users-helper.spec.d.ts +1 -1
  441. package/dist/esm/__tests__/helpers/account-users-helper.spec.js +218 -218
  442. package/dist/esm/__tests__/helpers/acuity-helper.spec.d.ts +1 -1
  443. package/dist/esm/__tests__/helpers/acuity-helper.spec.js +67 -67
  444. package/dist/esm/__tests__/helpers/api-key-auth-helper.spec.d.ts +1 -1
  445. package/dist/esm/__tests__/helpers/api-key-auth-helper.spec.js +80 -80
  446. package/dist/esm/__tests__/identity-cache/identity-cache-db-service.spec.d.ts +1 -1
  447. package/dist/esm/__tests__/identity-cache/identity-cache-db-service.spec.js +672 -672
  448. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service.spec.d.ts +1 -1
  449. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js +1138 -1138
  450. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js.map +1 -1
  451. package/dist/esm/__tests__/identity-cache/trait-merging-and-staleness.spec.d.ts +1 -1
  452. package/dist/esm/__tests__/identity-cache/trait-merging-and-staleness.spec.js +586 -586
  453. package/dist/esm/__tests__/integration/sqs-bundling-roundtrip.spec.d.ts +1 -1
  454. package/dist/esm/__tests__/integration/sqs-bundling-roundtrip.spec.js +582 -582
  455. package/dist/esm/__tests__/integration/sqs-bundling-roundtrip.spec.js.map +1 -1
  456. package/dist/esm/__tests__/libs/compress-decompress.spec.d.ts +1 -1
  457. package/dist/esm/__tests__/libs/compress-decompress.spec.js +14 -14
  458. package/dist/esm/__tests__/libs/contacts.spec.d.ts +1 -1
  459. package/dist/esm/__tests__/libs/contacts.spec.js +292 -292
  460. package/dist/esm/__tests__/libs/currency.spec.d.ts +1 -1
  461. package/dist/esm/__tests__/libs/currency.spec.js +218 -218
  462. package/dist/esm/__tests__/libs/dates.spec.d.ts +1 -1
  463. package/dist/esm/__tests__/libs/dates.spec.js +128 -128
  464. package/dist/esm/__tests__/libs/dates.spec.js.map +1 -1
  465. package/dist/esm/__tests__/libs/domain.spec.d.ts +1 -1
  466. package/dist/esm/__tests__/libs/domain.spec.js +105 -105
  467. package/dist/esm/__tests__/libs/numbers.spec.d.ts +1 -1
  468. package/dist/esm/__tests__/libs/numbers.spec.js +259 -259
  469. package/dist/esm/__tests__/s3-client/s3-client.spec.d.ts +1 -1
  470. package/dist/esm/__tests__/s3-client/s3-client.spec.js +31 -31
  471. package/dist/esm/__tests__/services/acuity-api-service.spec.d.ts +1 -1
  472. package/dist/esm/__tests__/services/acuity-api-service.spec.js +69 -69
  473. package/dist/esm/__tests__/services/email-verification/contact-email-verification-service.spec.d.ts +1 -1
  474. package/dist/esm/__tests__/services/email-verification/contact-email-verification-service.spec.js +91 -91
  475. package/dist/esm/__tests__/services/email-verification/email-verification-service.spec.d.ts +1 -1
  476. package/dist/esm/__tests__/services/email-verification/email-verification-service.spec.js +55 -55
  477. package/dist/esm/__tests__/shopify/shopify-graphql-transformer.spec.d.ts +1 -1
  478. package/dist/esm/__tests__/shopify/shopify-graphql-transformer.spec.js +33 -33
  479. package/dist/esm/__tests__/unit/libs/api-router/public-api-router.spec.d.ts +1 -1
  480. package/dist/esm/__tests__/unit/libs/api-router/public-api-router.spec.js +156 -156
  481. package/dist/esm/__tests__/unit/libs/api-router/public-api-router.spec.js.map +1 -1
  482. package/dist/esm/__tests__/unit/libs/api-router/route-matcher.spec.d.ts +1 -1
  483. package/dist/esm/__tests__/unit/libs/api-router/route-matcher.spec.js +67 -67
  484. package/dist/esm/__tests__/utils/custom-measure-formula-utils.spec.d.ts +1 -1
  485. package/dist/esm/__tests__/utils/custom-measure-formula-utils.spec.js +137 -137
  486. package/dist/esm/clients/generic/cognito-client.d.ts +23 -23
  487. package/dist/esm/clients/generic/cognito-client.js +204 -204
  488. package/dist/esm/clients/generic/cognito-client.js.map +1 -1
  489. package/dist/esm/clients/generic/dynamodb-client.d.ts +20 -20
  490. package/dist/esm/clients/generic/dynamodb-client.js +231 -225
  491. package/dist/esm/clients/generic/dynamodb-client.js.map +1 -1
  492. package/dist/esm/clients/generic/eventbridge-client.d.ts +14 -14
  493. package/dist/esm/clients/generic/eventbridge-client.js +47 -47
  494. package/dist/esm/clients/generic/http-client.d.ts +14 -14
  495. package/dist/esm/clients/generic/http-client.js +53 -53
  496. package/dist/esm/clients/generic/http-client.js.map +1 -1
  497. package/dist/esm/clients/generic/index.d.ts +13 -13
  498. package/dist/esm/clients/generic/index.js +13 -13
  499. package/dist/esm/clients/generic/lambda-invoke-client.d.ts +10 -10
  500. package/dist/esm/clients/generic/lambda-invoke-client.js +35 -35
  501. package/dist/esm/clients/generic/lambda-invoke-client.js.map +1 -1
  502. package/dist/esm/clients/generic/location-client.d.ts +8 -8
  503. package/dist/esm/clients/generic/location-client.js +27 -27
  504. package/dist/esm/clients/generic/location-client.js.map +1 -1
  505. package/dist/esm/clients/generic/redis-client.d.ts +33 -33
  506. package/dist/esm/clients/generic/redis-client.js +184 -184
  507. package/dist/esm/clients/generic/redis-client.js.map +1 -1
  508. package/dist/esm/clients/generic/s3-client.d.ts +22 -23
  509. package/dist/esm/clients/generic/s3-client.js +209 -209
  510. package/dist/esm/clients/generic/s3-client.js.map +1 -1
  511. package/dist/esm/clients/generic/singlestore-db-client.d.ts +14 -14
  512. package/dist/esm/clients/generic/singlestore-db-client.js +40 -40
  513. package/dist/esm/clients/generic/singlestore-db-client.js.map +1 -1
  514. package/dist/esm/clients/generic/sqs-bundled-client.d.ts +15 -15
  515. package/dist/esm/clients/generic/sqs-bundled-client.js +307 -307
  516. package/dist/esm/clients/generic/sqs-bundled-client.js.map +1 -1
  517. package/dist/esm/clients/generic/sqs-bundled-client.types.d.ts +53 -53
  518. package/dist/esm/clients/generic/sqs-bundled-client.types.js +14 -14
  519. package/dist/esm/clients/generic/sqs-client.d.ts +53 -53
  520. package/dist/esm/clients/generic/sqs-client.js +281 -281
  521. package/dist/esm/clients/generic/sqs-client.js.map +1 -1
  522. package/dist/esm/clients/generic/sqs-unbundle.d.ts +32 -32
  523. package/dist/esm/clients/generic/sqs-unbundle.js +137 -137
  524. package/dist/esm/clients/generic/sqs-unbundle.js.map +1 -1
  525. package/dist/esm/clients/index.d.ts +3 -3
  526. package/dist/esm/clients/index.js +3 -3
  527. package/dist/esm/clients/internal-api/accounts-client.d.ts +91 -91
  528. package/dist/esm/clients/internal-api/accounts-client.js +125 -125
  529. package/dist/esm/clients/internal-api/accounts-client.js.map +1 -1
  530. package/dist/esm/clients/internal-api/cache-lambda-client.d.ts +26 -26
  531. package/dist/esm/clients/internal-api/cache-lambda-client.js +85 -85
  532. package/dist/esm/clients/internal-api/cache-lambda-client.js.map +1 -1
  533. package/dist/esm/clients/internal-api/db-management-client.d.ts +18 -18
  534. package/dist/esm/clients/internal-api/db-management-client.js +32 -32
  535. package/dist/esm/clients/internal-api/destinations-client.d.ts +34 -34
  536. package/dist/esm/clients/internal-api/destinations-client.js +75 -75
  537. package/dist/esm/clients/internal-api/destinations-client.js.map +1 -1
  538. package/dist/esm/clients/internal-api/event-collector-client.d.ts +20 -20
  539. package/dist/esm/clients/internal-api/event-collector-client.js +32 -32
  540. package/dist/esm/clients/internal-api/identity-client.d.ts +31 -31
  541. package/dist/esm/clients/internal-api/identity-client.js +87 -87
  542. package/dist/esm/clients/internal-api/identity-client.js.map +1 -1
  543. package/dist/esm/clients/internal-api/index.d.ts +9 -9
  544. package/dist/esm/clients/internal-api/index.js +9 -9
  545. package/dist/esm/clients/internal-api/shopify-app-install-client.d.ts +37 -37
  546. package/dist/esm/clients/internal-api/shopify-app-install-client.js +77 -77
  547. package/dist/esm/clients/internal-api/subscriptions-client.d.ts +26 -26
  548. package/dist/esm/clients/internal-api/subscriptions-client.js +73 -73
  549. package/dist/esm/clients/internal-api/users-auth-client.d.ts +35 -35
  550. package/dist/esm/clients/internal-api/users-auth-client.js +106 -106
  551. package/dist/esm/clients/internal-api/users-auth-client.js.map +1 -1
  552. package/dist/esm/clients/third-party/acuity-client.d.ts +10 -10
  553. package/dist/esm/clients/third-party/acuity-client.js +36 -36
  554. package/dist/esm/clients/third-party/emailable-client.d.ts +7 -7
  555. package/dist/esm/clients/third-party/emailable-client.js +21 -21
  556. package/dist/esm/clients/third-party/emailable-client.js.map +1 -1
  557. package/dist/esm/clients/third-party/exchange-rate-api-client.d.ts +17 -17
  558. package/dist/esm/clients/third-party/exchange-rate-api-client.js +15 -15
  559. package/dist/esm/clients/third-party/index.d.ts +5 -5
  560. package/dist/esm/clients/third-party/index.js +5 -5
  561. package/dist/esm/clients/third-party/loops-client.d.ts +10 -10
  562. package/dist/esm/clients/third-party/loops-client.js +26 -26
  563. package/dist/esm/clients/third-party/loops-client.js.map +1 -1
  564. package/dist/esm/clients/third-party/shopify/graphql-order-queries.d.ts +25 -25
  565. package/dist/esm/clients/third-party/shopify/graphql-order-queries.js +1 -1
  566. package/dist/esm/clients/third-party/shopify/graphql-product-queries.d.ts +2 -2
  567. package/dist/esm/clients/third-party/shopify/graphql-product-queries.js +2 -2
  568. package/dist/esm/clients/third-party/shopify/shopify-graphql-client.d.ts +10 -10
  569. package/dist/esm/clients/third-party/shopify/shopify-graphql-client.js +157 -157
  570. package/dist/esm/clients/third-party/shopify/shopify-graphql-client.js.map +1 -1
  571. package/dist/esm/clients/third-party/shopify-client.d.ts +29 -29
  572. package/dist/esm/clients/third-party/shopify-client.js +142 -142
  573. package/dist/esm/clients/third-party/shopify-client.js.map +1 -1
  574. package/dist/esm/constants/index.d.ts +1 -1
  575. package/dist/esm/constants/index.js +1 -1
  576. package/dist/esm/constants/sqs.d.ts +20 -20
  577. package/dist/esm/constants/sqs.js +22 -22
  578. package/dist/esm/constants/sqs.js.map +1 -1
  579. package/dist/esm/helpers/account-users-helper.d.ts +2 -2
  580. package/dist/esm/helpers/account-users-helper.js +18 -18
  581. package/dist/esm/helpers/account-users-helper.js.map +1 -1
  582. package/dist/esm/helpers/acuity-helper.d.ts +4 -4
  583. package/dist/esm/helpers/acuity-helper.js +51 -51
  584. package/dist/esm/helpers/acuity-helper.js.map +1 -1
  585. package/dist/esm/helpers/api-key-auth-helper.d.ts +10 -9
  586. package/dist/esm/helpers/api-key-auth-helper.js +35 -35
  587. package/dist/esm/helpers/api-key-auth-helper.js.map +1 -1
  588. package/dist/esm/helpers/api-key-authorizer-helper.d.ts +36 -36
  589. package/dist/esm/helpers/api-key-authorizer-helper.js +83 -83
  590. package/dist/esm/helpers/api-key-authorizer-helper.js.map +1 -1
  591. package/dist/esm/helpers/identity-cache-helper.d.ts +21 -21
  592. package/dist/esm/helpers/identity-cache-helper.js +152 -152
  593. package/dist/esm/helpers/identity-cache-helper.js.map +1 -1
  594. package/dist/esm/helpers/index.d.ts +10 -10
  595. package/dist/esm/helpers/index.js +10 -10
  596. package/dist/esm/helpers/input-validation-helper.d.ts +3 -3
  597. package/dist/esm/helpers/input-validation-helper.js +18 -18
  598. package/dist/esm/helpers/input-validation-helper.js.map +1 -1
  599. package/dist/esm/helpers/logging-helper.d.ts +16 -16
  600. package/dist/esm/helpers/logging-helper.js +56 -56
  601. package/dist/esm/helpers/logging-helper.js.map +1 -1
  602. package/dist/esm/helpers/response-helper.d.ts +18 -18
  603. package/dist/esm/helpers/response-helper.js +37 -37
  604. package/dist/esm/helpers/response-helper.js.map +1 -1
  605. package/dist/esm/helpers/shopify-helper.d.ts +9 -9
  606. package/dist/esm/helpers/shopify-helper.js +21 -21
  607. package/dist/esm/helpers/shopify-helper.js.map +1 -1
  608. package/dist/esm/helpers/sqs-utils.d.ts +6 -6
  609. package/dist/esm/helpers/sqs-utils.js +9 -9
  610. package/dist/esm/index.d.ts +7 -7
  611. package/dist/esm/index.js +7 -7
  612. package/dist/esm/libs/api-router/index.d.ts +2 -2
  613. package/dist/esm/libs/api-router/index.js +2 -2
  614. package/dist/esm/libs/api-router/public-api-router.d.ts +3 -3
  615. package/dist/esm/libs/api-router/public-api-router.js +32 -32
  616. package/dist/esm/libs/api-router/public-api-router.js.map +1 -1
  617. package/dist/esm/libs/api-router/route-matcher.d.ts +21 -21
  618. package/dist/esm/libs/api-router/route-matcher.js +30 -30
  619. package/dist/esm/libs/api-router/route-matcher.js.map +1 -1
  620. package/dist/esm/libs/click-id-parser.d.ts +23 -23
  621. package/dist/esm/libs/click-id-parser.js +45 -45
  622. package/dist/esm/libs/click-id-parser.js.map +1 -1
  623. package/dist/esm/libs/compression.d.ts +2 -2
  624. package/dist/esm/libs/compression.js +25 -25
  625. package/dist/esm/libs/compression.js.map +1 -1
  626. package/dist/esm/libs/contacts.d.ts +7 -7
  627. package/dist/esm/libs/contacts.js +143 -143
  628. package/dist/esm/libs/contacts.js.map +1 -1
  629. package/dist/esm/libs/cookie.d.ts +17 -17
  630. package/dist/esm/libs/cookie.js +70 -70
  631. package/dist/esm/libs/cookie.js.map +1 -1
  632. package/dist/esm/libs/crypto.d.ts +4 -4
  633. package/dist/esm/libs/crypto.js +15 -15
  634. package/dist/esm/libs/csv.d.ts +2 -2
  635. package/dist/esm/libs/csv.js +30 -30
  636. package/dist/esm/libs/csv.js.map +1 -1
  637. package/dist/esm/libs/currency.d.ts +1 -1
  638. package/dist/esm/libs/currency.js +22 -22
  639. package/dist/esm/libs/currency.js.map +1 -1
  640. package/dist/esm/libs/dates.d.ts +12 -12
  641. package/dist/esm/libs/dates.js +83 -83
  642. package/dist/esm/libs/dates.js.map +1 -1
  643. package/dist/esm/libs/domain.d.ts +2 -2
  644. package/dist/esm/libs/domain.js +33 -33
  645. package/dist/esm/libs/domain.js.map +1 -1
  646. package/dist/esm/libs/emails.d.ts +8 -8
  647. package/dist/esm/libs/emails.js +146 -146
  648. package/dist/esm/libs/emails.js.map +1 -1
  649. package/dist/esm/libs/http-error.d.ts +21 -21
  650. package/dist/esm/libs/http-error.js +59 -59
  651. package/dist/esm/libs/http-error.js.map +1 -1
  652. package/dist/esm/libs/http-status-codes.d.ts +58 -58
  653. package/dist/esm/libs/http-status-codes.js +59 -59
  654. package/dist/esm/libs/index.d.ts +19 -19
  655. package/dist/esm/libs/index.js +19 -19
  656. package/dist/esm/libs/numbers.d.ts +1 -1
  657. package/dist/esm/libs/numbers.js +11 -11
  658. package/dist/esm/libs/numbers.js.map +1 -1
  659. package/dist/esm/libs/referrer-parser/index.d.ts +2 -2
  660. package/dist/esm/libs/referrer-parser/index.js +2 -2
  661. package/dist/esm/libs/referrer-parser/referrer-data.d.ts +9 -9
  662. package/dist/esm/libs/referrer-parser/referrer-data.js +3304 -3304
  663. package/dist/esm/libs/referrer-parser/referrer-parser-util.d.ts +19 -20
  664. package/dist/esm/libs/referrer-parser/referrer-parser-util.js +124 -124
  665. package/dist/esm/libs/referrer-parser/referrer-parser-util.js.map +1 -1
  666. package/dist/esm/libs/strings.d.ts +3 -3
  667. package/dist/esm/libs/strings.js +40 -40
  668. package/dist/esm/libs/strings.js.map +1 -1
  669. package/dist/esm/libs/traits.d.ts +6 -6
  670. package/dist/esm/libs/traits.js +54 -54
  671. package/dist/esm/libs/traits.js.map +1 -1
  672. package/dist/esm/libs/url.d.ts +1 -1
  673. package/dist/esm/libs/url.js +9 -9
  674. package/dist/esm/services/acuity-api-service.d.ts +9 -9
  675. package/dist/esm/services/acuity-api-service.js +69 -69
  676. package/dist/esm/services/acuity-api-service.js.map +1 -1
  677. package/dist/esm/services/cache/generic-cached-object.d.ts +5 -5
  678. package/dist/esm/services/cache/generic-cached-object.js +1 -1
  679. package/dist/esm/services/cache/index.d.ts +1 -1
  680. package/dist/esm/services/cache/index.js +1 -1
  681. package/dist/esm/services/cache/product-cache-service.d.ts +21 -21
  682. package/dist/esm/services/cache/product-cache-service.js +68 -68
  683. package/dist/esm/services/cache/product-cache-service.js.map +1 -1
  684. package/dist/esm/services/currency-exchange-rate-lookup-service.d.ts +11 -11
  685. package/dist/esm/services/currency-exchange-rate-lookup-service.js +62 -62
  686. package/dist/esm/services/currency-exchange-rate-lookup-service.js.map +1 -1
  687. package/dist/esm/services/db/accounts-db-service.d.ts +9 -9
  688. package/dist/esm/services/db/accounts-db-service.js +29 -29
  689. package/dist/esm/services/db/accounts-db-service.js.map +1 -1
  690. package/dist/esm/services/db/api-keys-db-service.d.ts +10 -10
  691. package/dist/esm/services/db/api-keys-db-service.js +32 -32
  692. package/dist/esm/services/db/api-keys-db-service.js.map +1 -1
  693. package/dist/esm/services/db/contact-enrichments-db-service.d.ts +15 -15
  694. package/dist/esm/services/db/contact-enrichments-db-service.js +90 -90
  695. package/dist/esm/services/db/contact-enrichments-db-service.js.map +1 -1
  696. package/dist/esm/services/db/currency-exchange-rates-db-service.d.ts +21 -21
  697. package/dist/esm/services/db/currency-exchange-rates-db-service.js +35 -35
  698. package/dist/esm/services/db/custom-measures-db-service.d.ts +14 -14
  699. package/dist/esm/services/db/custom-measures-db-service.js +44 -44
  700. package/dist/esm/services/db/custom-measures-db-service.js.map +1 -1
  701. package/dist/esm/services/db/destinations-db-service.d.ts +13 -13
  702. package/dist/esm/services/db/destinations-db-service.js +70 -70
  703. package/dist/esm/services/db/identity-cache-db-service.d.ts +28 -28
  704. package/dist/esm/services/db/identity-cache-db-service.js +313 -313
  705. package/dist/esm/services/db/identity-cache-db-service.js.map +1 -1
  706. package/dist/esm/services/db/identity-cache-dynamodb-service.d.ts +28 -28
  707. package/dist/esm/services/db/identity-cache-dynamodb-service.js +370 -370
  708. package/dist/esm/services/db/identity-cache-dynamodb-service.js.map +1 -1
  709. package/dist/esm/services/db/index.d.ts +17 -17
  710. package/dist/esm/services/db/index.js +17 -17
  711. package/dist/esm/services/db/log-events-db-service.d.ts +11 -11
  712. package/dist/esm/services/db/log-events-db-service.js +177 -177
  713. package/dist/esm/services/db/log-events-db-service.js.map +1 -1
  714. package/dist/esm/services/db/pixels-db-service.d.ts +8 -8
  715. package/dist/esm/services/db/pixels-db-service.js +31 -31
  716. package/dist/esm/services/db/purchasable-contacts-db-service.d.ts +9 -9
  717. package/dist/esm/services/db/purchasable-contacts-db-service.js +39 -39
  718. package/dist/esm/services/db/purchasable-contacts-db-service.js.map +1 -1
  719. package/dist/esm/services/db/purchased-contacts/index.d.ts +2 -2
  720. package/dist/esm/services/db/purchased-contacts/index.js +2 -2
  721. package/dist/esm/services/db/purchased-contacts/purchased-contacts-db-service.d.ts +18 -18
  722. package/dist/esm/services/db/purchased-contacts/purchased-contacts-db-service.js +148 -148
  723. package/dist/esm/services/db/purchased-contacts/purchased-contacts-db-service.js.map +1 -1
  724. package/dist/esm/services/db/purchased-contacts/types.d.ts +11 -11
  725. package/dist/esm/services/db/purchased-contacts/types.js +1 -1
  726. package/dist/esm/services/db/shopify-app-installs-db-service.d.ts +10 -10
  727. package/dist/esm/services/db/shopify-app-installs-db-service.js +48 -48
  728. package/dist/esm/services/db/shopify-app-installs-db-service.js.map +1 -1
  729. package/dist/esm/services/db/shopify-products-cache-db-service.d.ts +16 -16
  730. package/dist/esm/services/db/shopify-products-cache-db-service.js +66 -66
  731. package/dist/esm/services/db/shopify-products-cache-db-service.js.map +1 -1
  732. package/dist/esm/services/db/subscriptions-db-service.d.ts +11 -11
  733. package/dist/esm/services/db/subscriptions-db-service.js +34 -34
  734. package/dist/esm/services/db/subscriptions-db-service.js.map +1 -1
  735. package/dist/esm/services/db/tracking-events-db-service.d.ts +21 -21
  736. package/dist/esm/services/db/tracking-events-db-service.js +184 -184
  737. package/dist/esm/services/db/tracking-events-db-service.js.map +1 -1
  738. package/dist/esm/services/db/user-accounts-db-service.d.ts +7 -7
  739. package/dist/esm/services/db/user-accounts-db-service.js +13 -13
  740. package/dist/esm/services/email-verification/contact-email-verification-service.d.ts +7 -7
  741. package/dist/esm/services/email-verification/contact-email-verification-service.js +97 -97
  742. package/dist/esm/services/email-verification/contact-email-verification-service.js.map +1 -1
  743. package/dist/esm/services/email-verification/email-verification-service.d.ts +19 -19
  744. package/dist/esm/services/email-verification/email-verification-service.js +127 -127
  745. package/dist/esm/services/email-verification/email-verification-service.js.map +1 -1
  746. package/dist/esm/services/email-verification/index.d.ts +2 -2
  747. package/dist/esm/services/email-verification/index.js +2 -2
  748. package/dist/esm/services/eventbridge-integration-service.d.ts +9 -9
  749. package/dist/esm/services/eventbridge-integration-service.js +24 -24
  750. package/dist/esm/services/events/index.d.ts +3 -3
  751. package/dist/esm/services/events/index.js +3 -3
  752. package/dist/esm/services/events/log-event-service.d.ts +19 -19
  753. package/dist/esm/services/events/log-event-service.js +73 -73
  754. package/dist/esm/services/events/log-event-service.js.map +1 -1
  755. package/dist/esm/services/events/metric-event-service.d.ts +9 -9
  756. package/dist/esm/services/events/metric-event-service.js +45 -45
  757. package/dist/esm/services/events/tracking-event-sqs-service.d.ts +8 -8
  758. package/dist/esm/services/events/tracking-event-sqs-service.js +30 -30
  759. package/dist/esm/services/events/tracking-event-sqs-service.js.map +1 -1
  760. package/dist/esm/services/generic-cache-service.d.ts +7 -7
  761. package/dist/esm/services/generic-cache-service.js +29 -29
  762. package/dist/esm/services/generic-cache-service.js.map +1 -1
  763. package/dist/esm/services/index.d.ts +10 -10
  764. package/dist/esm/services/index.js +10 -10
  765. package/dist/esm/services/ipdata-lookup-service.d.ts +20 -20
  766. package/dist/esm/services/ipdata-lookup-service.js +108 -108
  767. package/dist/esm/services/ipdata-lookup-service.js.map +1 -1
  768. package/dist/esm/services/shopify/index.d.ts +2 -2
  769. package/dist/esm/services/shopify/index.js +2 -2
  770. package/dist/esm/services/shopify/products/index.d.ts +1 -1
  771. package/dist/esm/services/shopify/products/index.js +1 -1
  772. package/dist/esm/services/shopify/products/shopify-products-serviceV2.d.ts +17 -17
  773. package/dist/esm/services/shopify/products/shopify-products-serviceV2.js +108 -108
  774. package/dist/esm/services/shopify/products/shopify-products-serviceV2.js.map +1 -1
  775. package/dist/esm/services/shopify/shopify-graphql-transformer.d.ts +8 -8
  776. package/dist/esm/services/shopify/shopify-graphql-transformer.js +138 -138
  777. package/dist/esm/services/shopify/shopify-graphql-transformer.js.map +1 -1
  778. package/dist/esm/types/acuity-types.d.ts +74 -74
  779. package/dist/esm/types/acuity-types.js +1 -1
  780. package/dist/esm/types/api-response.d.ts +6 -6
  781. package/dist/esm/types/api-response.js +1 -1
  782. package/dist/esm/types/index.d.ts +4 -4
  783. package/dist/esm/types/index.js +4 -4
  784. package/dist/esm/types/internal-events/event-detail-types.d.ts +20 -20
  785. package/dist/esm/types/internal-events/event-detail-types.js +24 -24
  786. package/dist/esm/types/internal-events/index.d.ts +1 -1
  787. package/dist/esm/types/internal-events/index.js +1 -1
  788. package/dist/esm/types/shopify-graphql-types/admin.generated.d.ts +123 -123
  789. package/dist/esm/types/shopify-graphql-types/admin.generated.js +1 -1
  790. package/dist/esm/types/shopify-graphql-types/admin.types.d.ts +26289 -26289
  791. package/dist/esm/types/shopify-graphql-types/admin.types.js +5299 -5299
  792. package/dist/esm/types/shopify-graphql-types/index.d.ts +2 -2
  793. package/dist/esm/types/shopify-graphql-types/index.js +2 -2
  794. package/dist/esm/types/shopify-rest-types.d.ts +767 -767
  795. package/dist/esm/types/shopify-rest-types.js +1 -1
  796. package/dist/esm/utils/compression.d.ts +34 -36
  797. package/dist/esm/utils/compression.js +187 -187
  798. package/dist/esm/utils/compression.js.map +1 -1
  799. package/dist/esm/utils/custom-measure-formula-utils.d.ts +6 -6
  800. package/dist/esm/utils/custom-measure-formula-utils.js +201 -201
  801. package/dist/esm/utils/custom-measure-formula-utils.js.map +1 -1
  802. package/dist/esm/utils/index.d.ts +4 -4
  803. package/dist/esm/utils/index.js +4 -4
  804. package/dist/esm/utils/retry-envelope.d.ts +12 -12
  805. package/dist/esm/utils/retry-envelope.js +22 -22
  806. package/dist/esm/utils/retry-envelope.js.map +1 -1
  807. package/dist/esm/utils/size.d.ts +2 -2
  808. package/dist/esm/utils/size.js +44 -44
  809. package/dist/esm/utils/size.js.map +1 -1
  810. package/package.json +2 -2
@@ -1,1358 +1,1358 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const sqs_unbundle_1 = require("../../clients/generic/sqs-unbundle");
4
- const sqs_bundled_client_types_1 = require("../../clients/generic/sqs-bundled-client.types");
5
- const compression_1 = require("../../utils/compression");
6
- const node_zlib_1 = require("node:zlib");
7
- const node_util_1 = require("node:util");
8
- const gzipAsync = (0, node_util_1.promisify)(node_zlib_1.gzip);
9
- jest.mock('../../helpers/logging-helper', () => ({
10
- Logger: {
11
- debug: jest.fn(),
12
- info: jest.fn(),
13
- warn: jest.fn(),
14
- error: jest.fn(),
15
- },
16
- }));
17
- function makeTestEvent(id, name = 'test_event', value) {
18
- return { eventId: id, name, value };
19
- }
20
- function makeSqsRecord(body, messageId = 'msg-1') {
21
- return {
22
- messageId,
23
- body: typeof body === 'string' ? body : JSON.stringify(body),
24
- };
25
- }
26
- function makeBundledEnvelope(items) {
27
- return {
28
- v: 1,
29
- c: sqs_bundled_client_types_1.CompressionAlgorithm.NONE,
30
- n: items.length,
31
- s: JSON.stringify(items).length,
32
- p: items,
33
- };
34
- }
35
- async function makeCompressedEnvelope(items, level = 3) {
36
- const json = JSON.stringify(items);
37
- const compressed = await (0, compression_1.compress)(Buffer.from(json, 'utf8'), sqs_bundled_client_types_1.CompressionAlgorithm.ZSTD, level);
38
- const base64 = compressed.toString('base64');
39
- return {
40
- v: 1,
41
- c: sqs_bundled_client_types_1.CompressionAlgorithm.ZSTD,
42
- n: items.length,
43
- s: json.length,
44
- p: base64,
45
- };
46
- }
47
- async function makeGzipCompressedEnvelope(items) {
48
- const json = JSON.stringify(items);
49
- const compressed = await gzipAsync(Buffer.from(json, 'utf8'));
50
- const base64 = compressed.toString('base64');
51
- return {
52
- v: 1,
53
- c: sqs_bundled_client_types_1.CompressionAlgorithm.GZIP,
54
- n: items.length,
55
- s: json.length,
56
- p: base64,
57
- };
58
- }
59
- describe('isBundledEnvelope', () => {
60
- it('returns true for valid bundled envelope with NONE compression', () => {
61
- const envelope = makeBundledEnvelope([makeTestEvent('1')]);
62
- expect((0, sqs_unbundle_1.isBundledEnvelope)(envelope)).toBe(true);
63
- });
64
- it('returns true for valid bundled envelope with ZSTD compression', async () => {
65
- const envelope = await makeCompressedEnvelope([makeTestEvent('1')]);
66
- expect((0, sqs_unbundle_1.isBundledEnvelope)(envelope)).toBe(true);
67
- });
68
- it('returns false for null', () => {
69
- expect((0, sqs_unbundle_1.isBundledEnvelope)(null)).toBe(false);
70
- });
71
- it('returns false for undefined', () => {
72
- expect((0, sqs_unbundle_1.isBundledEnvelope)(undefined)).toBe(false);
73
- });
74
- it('returns false for plain object (legacy message)', () => {
75
- expect((0, sqs_unbundle_1.isBundledEnvelope)(makeTestEvent('1'))).toBe(false);
76
- });
77
- it('returns false for wrong version', () => {
78
- const envelope = { v: 2, c: 'none', n: 1, p: [] };
79
- expect((0, sqs_unbundle_1.isBundledEnvelope)(envelope)).toBe(false);
80
- });
81
- it('returns true for valid bundled envelope with GZIP compression', async () => {
82
- const envelope = await makeGzipCompressedEnvelope([makeTestEvent('1')]);
83
- expect((0, sqs_unbundle_1.isBundledEnvelope)(envelope)).toBe(true);
84
- });
85
- it('returns false for invalid compression algorithm', () => {
86
- const envelope = { v: 1, c: 'lz4', n: 1, p: [] };
87
- expect((0, sqs_unbundle_1.isBundledEnvelope)(envelope)).toBe(false);
88
- });
89
- it('returns false for missing count', () => {
90
- const envelope = { v: 1, c: 'none', p: [] };
91
- expect((0, sqs_unbundle_1.isBundledEnvelope)(envelope)).toBe(false);
92
- });
93
- it('returns false for missing payload', () => {
94
- const envelope = { v: 1, c: 'none', n: 1 };
95
- expect((0, sqs_unbundle_1.isBundledEnvelope)(envelope)).toBe(false);
96
- });
97
- });
98
- describe('unbundleMessage', () => {
99
- it('unbundles NONE compression envelope', async () => {
100
- const items = [makeTestEvent('1'), makeTestEvent('2')];
101
- const envelope = makeBundledEnvelope(items);
102
- const result = await (0, sqs_unbundle_1.unbundleMessage)(envelope);
103
- expect(result).toHaveLength(2);
104
- expect(result[0].eventId).toBe('1');
105
- expect(result[1].eventId).toBe('2');
106
- });
107
- it('unbundles ZSTD compressed envelope', async () => {
108
- const items = [makeTestEvent('1'), makeTestEvent('2'), makeTestEvent('3')];
109
- const envelope = await makeCompressedEnvelope(items);
110
- const result = await (0, sqs_unbundle_1.unbundleMessage)(envelope);
111
- expect(result).toHaveLength(3);
112
- expect(result[0].eventId).toBe('1');
113
- expect(result[1].eventId).toBe('2');
114
- expect(result[2].eventId).toBe('3');
115
- });
116
- it('unbundles large batches with ZSTD compression', async () => {
117
- const items = Array.from({ length: 500 }, (_, i) => makeTestEvent(`event-${i}`, `event_${i}`, i * 10));
118
- const envelope = await makeCompressedEnvelope(items);
119
- const result = await (0, sqs_unbundle_1.unbundleMessage)(envelope);
120
- expect(result).toHaveLength(500);
121
- expect(result[0].eventId).toBe('event-0');
122
- expect(result[499].eventId).toBe('event-499');
123
- });
124
- it('throws on unsupported bundle version', async () => {
125
- const envelope = { v: 99, c: 'none', n: 1, p: [] };
126
- await expect((0, sqs_unbundle_1.unbundleMessage)(envelope)).rejects.toThrow('Unsupported bundle version: 99');
127
- });
128
- it('throws on invalid payload type for NONE compression', async () => {
129
- const envelope = { v: 1, c: 'none', n: 1, p: 'not-an-array' };
130
- await expect((0, sqs_unbundle_1.unbundleMessage)(envelope)).rejects.toThrow('Invalid bundle: NONE compression but payload is not an array');
131
- });
132
- it('throws on invalid payload type for ZSTD compression', async () => {
133
- const envelope = { v: 1, c: 'zstd', n: 1, p: [] };
134
- await expect((0, sqs_unbundle_1.unbundleMessage)(envelope)).rejects.toThrow('Invalid bundle: zstd compression but payload is not a string');
135
- });
136
- it('unbundles GZIP compressed envelope', async () => {
137
- const items = [makeTestEvent('1'), makeTestEvent('2'), makeTestEvent('3')];
138
- const envelope = await makeGzipCompressedEnvelope(items);
139
- const result = await (0, sqs_unbundle_1.unbundleMessage)(envelope);
140
- expect(result).toHaveLength(3);
141
- expect(result[0].eventId).toBe('1');
142
- expect(result[1].eventId).toBe('2');
143
- expect(result[2].eventId).toBe('3');
144
- });
145
- it('unbundles large batches with GZIP compression', async () => {
146
- const items = Array.from({ length: 500 }, (_, i) => makeTestEvent(`event-${i}`, `event_${i}`, i * 10));
147
- const envelope = await makeGzipCompressedEnvelope(items);
148
- const result = await (0, sqs_unbundle_1.unbundleMessage)(envelope);
149
- expect(result).toHaveLength(500);
150
- expect(result[0].eventId).toBe('event-0');
151
- expect(result[499].eventId).toBe('event-499');
152
- });
153
- it('throws on invalid payload type for GZIP compression', async () => {
154
- const envelope = { v: 1, c: 'gzip', n: 1, p: [] };
155
- await expect((0, sqs_unbundle_1.unbundleMessage)(envelope)).rejects.toThrow('Invalid bundle: gzip compression but payload is not a string');
156
- });
157
- });
158
- describe('unbundleRecords', () => {
159
- describe('legacy (non-bundled) messages', () => {
160
- it('parses raw JSON message', async () => {
161
- const event = makeTestEvent('raw-1', 'purchase');
162
- const record = makeSqsRecord(event);
163
- const { items, stats, failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
164
- expect(items).toHaveLength(1);
165
- expect(items[0].eventId).toBe('raw-1');
166
- expect(items[0].name).toBe('purchase');
167
- expect(stats.legacySqsRecords).toBe(1);
168
- expect(stats.bundledSqsRecords).toBe(0);
169
- expect(failedMessageIds).toHaveLength(0);
170
- });
171
- it('parses wrapped messageBody format', async () => {
172
- const event = makeTestEvent('wrapped-1');
173
- const record = makeSqsRecord({ messageBody: event });
174
- const { items, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
175
- expect(items).toHaveLength(1);
176
- expect(items[0].eventId).toBe('wrapped-1');
177
- expect(stats.legacySqsRecords).toBe(1);
178
- });
179
- it('handles multiple legacy records', async () => {
180
- const records = [
181
- makeSqsRecord(makeTestEvent('1'), 'msg-1'),
182
- makeSqsRecord(makeTestEvent('2'), 'msg-2'),
183
- makeSqsRecord(makeTestEvent('3'), 'msg-3'),
184
- ];
185
- const { items, stats, recordMap } = await (0, sqs_unbundle_1.unbundleRecords)(records);
186
- expect(items).toHaveLength(3);
187
- expect(stats.legacySqsRecords).toBe(3);
188
- expect(stats.totalItems).toBe(3);
189
- expect(recordMap.get(items[0])).toBe('msg-1');
190
- expect(recordMap.get(items[1])).toBe('msg-2');
191
- expect(recordMap.get(items[2])).toBe('msg-3');
192
- });
193
- });
194
- describe('bundled messages (NONE compression)', () => {
195
- it('unbundles wrapped messageBody envelope', async () => {
196
- const items = [makeTestEvent('b1'), makeTestEvent('b2')];
197
- const envelope = makeBundledEnvelope(items);
198
- const record = makeSqsRecord({ messageBody: envelope });
199
- const { items: result, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
200
- expect(result).toHaveLength(2);
201
- expect(result[0].eventId).toBe('b1');
202
- expect(result[1].eventId).toBe('b2');
203
- expect(stats.bundledSqsRecords).toBe(1);
204
- expect(stats.legacySqsRecords).toBe(0);
205
- });
206
- it('unbundles raw envelope (not wrapped)', async () => {
207
- const items = [makeTestEvent('r1'), makeTestEvent('r2')];
208
- const envelope = makeBundledEnvelope(items);
209
- const record = makeSqsRecord(envelope);
210
- const { items: result, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
211
- expect(result).toHaveLength(2);
212
- expect(stats.bundledSqsRecords).toBe(1);
213
- });
214
- it('maps all items from bundle to same messageId', async () => {
215
- const items = [makeTestEvent('x1'), makeTestEvent('x2'), makeTestEvent('x3')];
216
- const envelope = makeBundledEnvelope(items);
217
- const record = makeSqsRecord({ messageBody: envelope }, 'bundle-msg-1');
218
- const { items: result, recordMap } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
219
- expect(result).toHaveLength(3);
220
- expect(recordMap.get(result[0])).toBe('bundle-msg-1');
221
- expect(recordMap.get(result[1])).toBe('bundle-msg-1');
222
- expect(recordMap.get(result[2])).toBe('bundle-msg-1');
223
- });
224
- });
225
- describe('bundled messages (ZSTD compression)', () => {
226
- it('unbundles ZSTD compressed envelope', async () => {
227
- const items = [makeTestEvent('z1'), makeTestEvent('z2')];
228
- const envelope = await makeCompressedEnvelope(items);
229
- const record = makeSqsRecord({ messageBody: envelope });
230
- const { items: result, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
231
- expect(result).toHaveLength(2);
232
- expect(result[0].eventId).toBe('z1');
233
- expect(result[1].eventId).toBe('z2');
234
- expect(stats.bundledSqsRecords).toBe(1);
235
- });
236
- it('handles large compressed bundles (500 items)', async () => {
237
- const items = Array.from({ length: 500 }, (_, i) => makeTestEvent(`item-${i}`));
238
- const envelope = await makeCompressedEnvelope(items);
239
- const record = makeSqsRecord({ messageBody: envelope });
240
- const { items: result, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
241
- expect(result).toHaveLength(500);
242
- expect(stats.bundledSqsRecords).toBe(1);
243
- expect(stats.totalItems).toBe(500);
244
- });
245
- });
246
- describe('bundled messages (GZIP compression - CF Worker)', () => {
247
- it('unbundles GZIP compressed envelope', async () => {
248
- const items = [makeTestEvent('g1'), makeTestEvent('g2')];
249
- const envelope = await makeGzipCompressedEnvelope(items);
250
- const record = makeSqsRecord({ messageBody: envelope });
251
- const { items: result, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
252
- expect(result).toHaveLength(2);
253
- expect(result[0].eventId).toBe('g1');
254
- expect(result[1].eventId).toBe('g2');
255
- expect(stats.bundledSqsRecords).toBe(1);
256
- });
257
- it('handles large GZIP compressed bundles (500 items)', async () => {
258
- const items = Array.from({ length: 500 }, (_, i) => makeTestEvent(`item-${i}`));
259
- const envelope = await makeGzipCompressedEnvelope(items);
260
- const record = makeSqsRecord({ messageBody: envelope });
261
- const { items: result, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
262
- expect(result).toHaveLength(500);
263
- expect(stats.bundledSqsRecords).toBe(1);
264
- expect(stats.totalItems).toBe(500);
265
- });
266
- it('maps all items from GZIP bundle to same messageId', async () => {
267
- const items = [makeTestEvent('x1'), makeTestEvent('x2'), makeTestEvent('x3')];
268
- const envelope = await makeGzipCompressedEnvelope(items);
269
- const record = makeSqsRecord({ messageBody: envelope }, 'gzip-bundle-msg-1');
270
- const { items: result, recordMap } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
271
- expect(result).toHaveLength(3);
272
- expect(recordMap.get(result[0])).toBe('gzip-bundle-msg-1');
273
- expect(recordMap.get(result[1])).toBe('gzip-bundle-msg-1');
274
- expect(recordMap.get(result[2])).toBe('gzip-bundle-msg-1');
275
- });
276
- it('unbundles GZIP envelope wrapped in full InternalSQSMessage format (CF Worker format)', async () => {
277
- const items = [makeTestEvent('cf1'), makeTestEvent('cf2'), makeTestEvent('cf3')];
278
- const envelope = await makeGzipCompressedEnvelope(items);
279
- const internalSqsMessage = {
280
- messageId: 'cf-internal-msg-123',
281
- messageType: 'TRACKING_EVENT_COLLECT',
282
- messageTime: '2026-05-29T11:00:10.076Z',
283
- messageBody: envelope,
284
- };
285
- const record = makeSqsRecord(internalSqsMessage, 'sqs-msg-id');
286
- const { items: result, stats, recordMap } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
287
- expect(result).toHaveLength(3);
288
- expect(result[0].eventId).toBe('cf1');
289
- expect(result[1].eventId).toBe('cf2');
290
- expect(result[2].eventId).toBe('cf3');
291
- expect(stats.bundledSqsRecords).toBe(1);
292
- expect(stats.legacySqsRecords).toBe(0);
293
- expect(stats.totalItems).toBe(3);
294
- expect(recordMap.get(result[0])).toBe('sqs-msg-id');
295
- expect(recordMap.get(result[1])).toBe('sqs-msg-id');
296
- expect(recordMap.get(result[2])).toBe('sqs-msg-id');
297
- });
298
- it('unbundles GZIP envelope with extra wrapper fields (messageTime, messageType)', async () => {
299
- const items = [makeTestEvent('w1')];
300
- const envelope = await makeGzipCompressedEnvelope(items);
301
- const wrappedMessage = {
302
- messageTime: new Date().toISOString(),
303
- messageType: 'SESSION_EVENT',
304
- messageBody: envelope,
305
- someOtherField: 'ignored',
306
- };
307
- const record = makeSqsRecord(wrappedMessage);
308
- const { items: result, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
309
- expect(result).toHaveLength(1);
310
- expect(result[0].eventId).toBe('w1');
311
- expect(stats.bundledSqsRecords).toBe(1);
312
- });
313
- it('handles decompression failure gracefully (does not return wrapper)', async () => {
314
- const corruptedEnvelope = {
315
- v: 1,
316
- c: 'gzip',
317
- n: 1,
318
- s: 100,
319
- p: 'H4sIAAAAAAAAA+TRUNCATED',
320
- };
321
- const internalSqsMessage = {
322
- messageTime: '2026-05-29T11:00:10.076Z',
323
- messageType: 'TRACKING_EVENT_COLLECT',
324
- messageBody: corruptedEnvelope,
325
- messageId: 'corrupted-msg-id',
326
- };
327
- const record = makeSqsRecord(internalSqsMessage, 'sqs-record-id');
328
- const { items, stats, failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
329
- expect(failedMessageIds).toHaveLength(1);
330
- expect(stats.failedRecords).toBe(1);
331
- expect(items).toHaveLength(0);
332
- items.forEach((item) => {
333
- expect(item).not.toHaveProperty('messageTime');
334
- expect(item).not.toHaveProperty('messageType');
335
- expect(item).not.toHaveProperty('messageBody');
336
- });
337
- });
338
- it('verifies decompression failure does NOT leak wrapper into items', async () => {
339
- const badEnvelope = {
340
- v: 1,
341
- c: 'gzip',
342
- n: 5,
343
- s: 1000,
344
- p: 'dGhpcyBpcyBub3QgdmFsaWQgZ3ppcA==',
345
- };
346
- const wrapper = {
347
- messageTime: new Date().toISOString(),
348
- messageType: 'TRACKING_EVENT_COLLECT',
349
- messageBody: badEnvelope,
350
- messageId: 'test-internal-msg-id',
351
- };
352
- const record = makeSqsRecord(wrapper, 'sqs-msg-id');
353
- const { items, stats, failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
354
- expect(failedMessageIds).toContain('sqs-msg-id');
355
- expect(stats.failedRecords).toBe(1);
356
- expect(items).toHaveLength(0);
357
- expect(stats.totalItems).toBe(0);
358
- });
359
- it('simulates old version without GZIP - returns envelope as item (BUG)', async () => {
360
- const items = [{ pixelId: 'px123', trackingEvent: { name: 'page_view' } }];
361
- const envelope = await makeGzipCompressedEnvelope(items);
362
- const wrapper = {
363
- messageTime: new Date().toISOString(),
364
- messageType: 'TRACKING_EVENT_COLLECT',
365
- messageBody: envelope,
366
- messageId: 'cf-msg-id',
367
- };
368
- expect((0, sqs_unbundle_1.isBundledEnvelope)(envelope)).toBe(true);
369
- const record = makeSqsRecord(wrapper, 'sqs-msg-id');
370
- const { items: result, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
371
- expect(stats.bundledSqsRecords).toBe(1);
372
- expect(result).toHaveLength(1);
373
- expect(result[0].pixelId).toBe('px123');
374
- expect(result[0]).not.toHaveProperty('v');
375
- expect(result[0]).not.toHaveProperty('c');
376
- expect(result[0]).not.toHaveProperty('n');
377
- expect(result[0]).not.toHaveProperty('p');
378
- expect(result[0]).not.toHaveProperty('s');
379
- });
380
- it('unknown compression algorithm causes legacy fallback (returns messageBody)', async () => {
381
- const unknownEnvelope = {
382
- v: 1,
383
- c: 'lz4',
384
- n: 1,
385
- s: 100,
386
- p: 'some-base64-data',
387
- };
388
- const wrapper = {
389
- messageTime: new Date().toISOString(),
390
- messageType: 'TEST_MESSAGE',
391
- messageBody: unknownEnvelope,
392
- messageId: 'wrapper-msg-id',
393
- };
394
- expect((0, sqs_unbundle_1.isBundledEnvelope)(unknownEnvelope)).toBe(false);
395
- const record = makeSqsRecord(wrapper, 'sqs-msg-id');
396
- const { items, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
397
- expect(stats.legacySqsRecords).toBe(1);
398
- expect(stats.bundledSqsRecords).toBe(0);
399
- expect(items).toHaveLength(1);
400
- expect(items[0]).toHaveProperty('v', 1);
401
- expect(items[0]).toHaveProperty('c', 'lz4');
402
- expect(items[0]).toHaveProperty('n', 1);
403
- expect(items[0]).toHaveProperty('p');
404
- });
405
- });
406
- describe('mixed message formats', () => {
407
- it('handles mix of legacy and bundled messages', async () => {
408
- const legacyEvent = makeTestEvent('legacy-1');
409
- const bundledItems = [makeTestEvent('bundled-1'), makeTestEvent('bundled-2')];
410
- const bundleEnvelope = makeBundledEnvelope(bundledItems);
411
- const records = [
412
- makeSqsRecord(legacyEvent, 'msg-legacy'),
413
- makeSqsRecord({ messageBody: bundleEnvelope }, 'msg-bundle'),
414
- ];
415
- const { items, stats, recordMap } = await (0, sqs_unbundle_1.unbundleRecords)(records);
416
- expect(items).toHaveLength(3);
417
- expect(stats.legacySqsRecords).toBe(1);
418
- expect(stats.bundledSqsRecords).toBe(1);
419
- expect(stats.totalItems).toBe(3);
420
- expect(recordMap.get(items[0])).toBe('msg-legacy');
421
- expect(recordMap.get(items[1])).toBe('msg-bundle');
422
- expect(recordMap.get(items[2])).toBe('msg-bundle');
423
- });
424
- it('handles mix of NONE and ZSTD compressed bundles', async () => {
425
- const noneItems = [makeTestEvent('none-1')];
426
- const zstdItems = [makeTestEvent('zstd-1'), makeTestEvent('zstd-2')];
427
- const noneEnvelope = makeBundledEnvelope(noneItems);
428
- const zstdEnvelope = await makeCompressedEnvelope(zstdItems);
429
- const records = [
430
- makeSqsRecord({ messageBody: noneEnvelope }, 'msg-none'),
431
- makeSqsRecord({ messageBody: zstdEnvelope }, 'msg-zstd'),
432
- ];
433
- const { items, stats } = await (0, sqs_unbundle_1.unbundleRecords)(records);
434
- expect(items).toHaveLength(3);
435
- expect(stats.bundledSqsRecords).toBe(2);
436
- expect(stats.totalItems).toBe(3);
437
- });
438
- it('handles mix of ZSTD, GZIP, and legacy messages', async () => {
439
- const legacyEvent = makeTestEvent('legacy-1');
440
- const zstdItems = [makeTestEvent('zstd-1'), makeTestEvent('zstd-2')];
441
- const gzipItems = [makeTestEvent('gzip-1'), makeTestEvent('gzip-2'), makeTestEvent('gzip-3')];
442
- const zstdEnvelope = await makeCompressedEnvelope(zstdItems);
443
- const gzipEnvelope = await makeGzipCompressedEnvelope(gzipItems);
444
- const records = [
445
- makeSqsRecord(legacyEvent, 'msg-legacy'),
446
- makeSqsRecord({ messageBody: zstdEnvelope }, 'msg-zstd'),
447
- makeSqsRecord({ messageBody: gzipEnvelope }, 'msg-gzip'),
448
- ];
449
- const { items, stats, recordMap } = await (0, sqs_unbundle_1.unbundleRecords)(records);
450
- expect(items).toHaveLength(6);
451
- expect(stats.legacySqsRecords).toBe(1);
452
- expect(stats.bundledSqsRecords).toBe(2);
453
- expect(stats.totalItems).toBe(6);
454
- expect(recordMap.get(items[0])).toBe('msg-legacy');
455
- expect(recordMap.get(items[1])).toBe('msg-zstd');
456
- expect(recordMap.get(items[2])).toBe('msg-zstd');
457
- expect(recordMap.get(items[3])).toBe('msg-gzip');
458
- expect(recordMap.get(items[4])).toBe('msg-gzip');
459
- expect(recordMap.get(items[5])).toBe('msg-gzip');
460
- });
461
- });
462
- describe('error handling', () => {
463
- it('returns failed messageId for invalid JSON', async () => {
464
- const records = [makeSqsRecord('not-valid-json{{{', 'msg-bad'), makeSqsRecord(makeTestEvent('good'), 'msg-good')];
465
- const { items, failedMessageIds, stats } = await (0, sqs_unbundle_1.unbundleRecords)(records);
466
- expect(items).toHaveLength(1);
467
- expect(items[0].eventId).toBe('good');
468
- expect(failedMessageIds).toEqual(['msg-bad']);
469
- expect(stats.failedRecords).toBe(1);
470
- expect(stats.legacySqsRecords).toBe(1);
471
- });
472
- it('returns failed messageId for corrupt ZSTD compressed data', async () => {
473
- const corruptEnvelope = {
474
- v: 1,
475
- c: sqs_bundled_client_types_1.CompressionAlgorithm.ZSTD,
476
- n: 10,
477
- p: 'dGhpcyBpcyBub3QgdmFsaWQgenN0ZA==',
478
- };
479
- const records = [
480
- makeSqsRecord({ messageBody: corruptEnvelope }, 'msg-corrupt'),
481
- makeSqsRecord(makeTestEvent('valid'), 'msg-valid'),
482
- ];
483
- const { items, failedMessageIds, stats } = await (0, sqs_unbundle_1.unbundleRecords)(records);
484
- expect(items).toHaveLength(1);
485
- expect(items[0].eventId).toBe('valid');
486
- expect(failedMessageIds).toContain('msg-corrupt');
487
- expect(stats.failedRecords).toBe(1);
488
- });
489
- it('returns failed messageId for corrupt GZIP compressed data', async () => {
490
- const corruptEnvelope = {
491
- v: 1,
492
- c: sqs_bundled_client_types_1.CompressionAlgorithm.GZIP,
493
- n: 10,
494
- p: 'dGhpcyBpcyBub3QgdmFsaWQgZ3ppcA==',
495
- };
496
- const records = [
497
- makeSqsRecord({ messageBody: corruptEnvelope }, 'msg-corrupt-gzip'),
498
- makeSqsRecord(makeTestEvent('valid'), 'msg-valid'),
499
- ];
500
- const { items, failedMessageIds, stats } = await (0, sqs_unbundle_1.unbundleRecords)(records);
501
- expect(items).toHaveLength(1);
502
- expect(items[0].eventId).toBe('valid');
503
- expect(failedMessageIds).toContain('msg-corrupt-gzip');
504
- expect(stats.failedRecords).toBe(1);
505
- });
506
- it('returns empty result for all failed records', async () => {
507
- const records = [makeSqsRecord('invalid-1', 'msg-1'), makeSqsRecord('invalid-2', 'msg-2')];
508
- const { items, failedMessageIds, stats } = await (0, sqs_unbundle_1.unbundleRecords)(records);
509
- expect(items).toHaveLength(0);
510
- expect(failedMessageIds).toEqual(['msg-1', 'msg-2']);
511
- expect(stats.failedRecords).toBe(2);
512
- expect(stats.totalItems).toBe(0);
513
- });
514
- it('handles empty records array', async () => {
515
- const { items, failedMessageIds, stats } = await (0, sqs_unbundle_1.unbundleRecords)([]);
516
- expect(items).toHaveLength(0);
517
- expect(failedMessageIds).toHaveLength(0);
518
- expect(stats.totalRecords).toBe(0);
519
- expect(stats.totalItems).toBe(0);
520
- });
521
- it('rejects ZSTD payload exceeding max decompressed size', async () => {
522
- const largeItems = Array.from({ length: 1000 }, (_, i) => ({
523
- id: `item-${i}`,
524
- data: 'x'.repeat(1000),
525
- }));
526
- const envelope = await makeCompressedEnvelope(largeItems);
527
- const record = makeSqsRecord({ messageBody: envelope }, 'msg-too-large');
528
- const { failedMessageIds, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record], {
529
- maxDecompressedSizeBytes: 100 * 1024,
530
- });
531
- expect(failedMessageIds).toContain('msg-too-large');
532
- expect(stats.failedRecords).toBe(1);
533
- });
534
- it('rejects GZIP payload exceeding max decompressed size (post-decompress check)', async () => {
535
- const largeItems = Array.from({ length: 1000 }, (_, i) => ({
536
- id: `item-${i}`,
537
- data: 'x'.repeat(1000),
538
- }));
539
- const envelope = await makeGzipCompressedEnvelope(largeItems);
540
- const record = makeSqsRecord({ messageBody: envelope }, 'msg-too-large-gzip');
541
- const { failedMessageIds, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record], {
542
- maxDecompressedSizeBytes: 100 * 1024,
543
- });
544
- expect(failedMessageIds).toContain('msg-too-large-gzip');
545
- expect(stats.failedRecords).toBe(1);
546
- });
547
- it('fails on invalid base64 in compressed payload', async () => {
548
- const badEnvelope = {
549
- v: 1,
550
- c: sqs_bundled_client_types_1.CompressionAlgorithm.ZSTD,
551
- n: 1,
552
- s: 100,
553
- p: '!!!not-valid-base64!!!',
554
- };
555
- const record = makeSqsRecord({ messageBody: badEnvelope }, 'msg-bad-base64');
556
- const { failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
557
- expect(failedMessageIds).toContain('msg-bad-base64');
558
- });
559
- });
560
- describe('bundle format edge cases', () => {
561
- it('successfully unbundles even when n does not match actual item count', async () => {
562
- const envelope = {
563
- v: 1,
564
- c: sqs_bundled_client_types_1.CompressionAlgorithm.NONE,
565
- n: 5,
566
- s: 50,
567
- p: [makeTestEvent('1'), makeTestEvent('2')],
568
- };
569
- const record = makeSqsRecord({ messageBody: envelope });
570
- const { items } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
571
- expect(items).toHaveLength(2);
572
- });
573
- it('treats envelope with unsupported version as legacy message', async () => {
574
- const badEnvelope = { v: 99, c: 'none', n: 1, s: 10, p: [] };
575
- const record = makeSqsRecord({ messageBody: badEnvelope }, 'msg-bad-version');
576
- const { items, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
577
- expect(items).toHaveLength(1);
578
- expect(items[0].v).toBe(99);
579
- expect(stats.legacySqsRecords).toBe(1);
580
- expect(stats.bundledSqsRecords).toBe(0);
581
- });
582
- });
583
- describe('timeout protection', () => {
584
- it('respects timeoutMs option for ZSTD', async () => {
585
- const items = [makeTestEvent('1')];
586
- const envelope = await makeCompressedEnvelope(items);
587
- const record = makeSqsRecord({ messageBody: envelope });
588
- const { items: result, failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record], {
589
- timeoutMs: 5000,
590
- });
591
- expect(result).toHaveLength(1);
592
- expect(failedMessageIds).toHaveLength(0);
593
- });
594
- it('respects timeoutMs option for GZIP', async () => {
595
- const items = [makeTestEvent('1')];
596
- const envelope = await makeGzipCompressedEnvelope(items);
597
- const record = makeSqsRecord({ messageBody: envelope });
598
- const { items: result, failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record], {
599
- timeoutMs: 5000,
600
- });
601
- expect(result).toHaveLength(1);
602
- expect(failedMessageIds).toHaveLength(0);
603
- });
604
- it('passes timeoutMs through to decompression', async () => {
605
- const items = [makeTestEvent('1')];
606
- const envelope = await makeCompressedEnvelope(items);
607
- const record = makeSqsRecord({ messageBody: envelope });
608
- await expect((0, sqs_unbundle_1.unbundleRecords)([record], { timeoutMs: 30000 })).resolves.toBeDefined();
609
- });
610
- it('CompressionTimeoutError is properly exported', () => {
611
- const error = new compression_1.CompressionTimeoutError(compression_1.CompressionOperation.DECOMPRESS, 5000);
612
- expect(error).toBeInstanceOf(Error);
613
- expect(error.name).toBe('CompressionTimeoutError');
614
- expect(error.operation).toBe('decompress');
615
- expect(error.timeoutMs).toBe(5000);
616
- expect(error.message).toContain('timed out after 5000ms');
617
- });
618
- it('includes timeout info in failure logging for ZSTD', async () => {
619
- const corruptEnvelope = {
620
- v: 1,
621
- c: sqs_bundled_client_types_1.CompressionAlgorithm.ZSTD,
622
- n: 1,
623
- s: 100,
624
- p: 'dGhpcyBpcyBub3QgdmFsaWQgenN0ZA==',
625
- };
626
- const record = makeSqsRecord({ messageBody: corruptEnvelope }, 'msg-timeout-test');
627
- const { failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record], {
628
- timeoutMs: 5000,
629
- });
630
- expect(failedMessageIds).toContain('msg-timeout-test');
631
- });
632
- it('includes timeout info in failure logging for GZIP', async () => {
633
- const corruptEnvelope = {
634
- v: 1,
635
- c: sqs_bundled_client_types_1.CompressionAlgorithm.GZIP,
636
- n: 1,
637
- s: 100,
638
- p: 'dGhpcyBpcyBub3QgdmFsaWQgZ3ppcA==',
639
- };
640
- const record = makeSqsRecord({ messageBody: corruptEnvelope }, 'msg-gzip-timeout-test');
641
- const { failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record], {
642
- timeoutMs: 5000,
643
- });
644
- expect(failedMessageIds).toContain('msg-gzip-timeout-test');
645
- });
646
- });
647
- describe('end-to-end: producer bundle → consumer unbundle', () => {
648
- it('simulates full bundle → unbundle flow with compression', async () => {
649
- const producerItems = Array.from({ length: 100 }, (_, i) => makeTestEvent(`event-${i}`, 'page_view', i * 1.5));
650
- const envelope = await makeCompressedEnvelope(producerItems, 3);
651
- const sqsMessageBody = { messageBody: envelope };
652
- const consumerRecord = makeSqsRecord(sqsMessageBody, 'sqs-message-id-123');
653
- const { items, recordMap, stats } = await (0, sqs_unbundle_1.unbundleRecords)([consumerRecord]);
654
- expect(items).toHaveLength(100);
655
- expect(items[0]).toEqual({ eventId: 'event-0', name: 'page_view', value: 0 });
656
- expect(items[99]).toEqual({ eventId: 'event-99', name: 'page_view', value: 148.5 });
657
- expect(stats.bundledSqsRecords).toBe(1);
658
- expect(stats.legacySqsRecords).toBe(0);
659
- expect(stats.totalItems).toBe(100);
660
- for (const item of items) {
661
- expect(recordMap.get(item)).toBe('sqs-message-id-123');
662
- }
663
- });
664
- it('simulates mixed batch with legacy and bundled messages', async () => {
665
- const legacyRecord1 = makeSqsRecord({ messageBody: makeTestEvent('legacy-1', 'purchase', 99.99) }, 'legacy-msg-1');
666
- const legacyRecord2 = makeSqsRecord(makeTestEvent('legacy-2', 'add_to_cart', 29.99), 'legacy-msg-2');
667
- const bundledItems = [
668
- makeTestEvent('bundled-1', 'page_view'),
669
- makeTestEvent('bundled-2', 'session_start'),
670
- makeTestEvent('bundled-3', 'page_end'),
671
- ];
672
- const envelope = await makeCompressedEnvelope(bundledItems);
673
- const bundledRecord = makeSqsRecord({ messageBody: envelope }, 'bundled-msg-1');
674
- const records = [legacyRecord1, bundledRecord, legacyRecord2];
675
- const { items, stats, recordMap } = await (0, sqs_unbundle_1.unbundleRecords)(records);
676
- expect(items).toHaveLength(5);
677
- expect(stats.legacySqsRecords).toBe(2);
678
- expect(stats.bundledSqsRecords).toBe(1);
679
- expect(stats.totalItems).toBe(5);
680
- expect(recordMap.get(items[0])).toBe('legacy-msg-1');
681
- expect(recordMap.get(items[1])).toBe('bundled-msg-1');
682
- expect(recordMap.get(items[2])).toBe('bundled-msg-1');
683
- expect(recordMap.get(items[3])).toBe('bundled-msg-1');
684
- expect(recordMap.get(items[4])).toBe('legacy-msg-2');
685
- });
686
- });
687
- describe('consumer edge cases - real sequences', () => {
688
- it('handles 500 items in single compressed bundle with exact verification', async () => {
689
- const originalItems = Array.from({ length: 500 }, (_, i) => ({
690
- id: `item-${i}`,
691
- name: `Event ${i}`,
692
- data: { nested: { value: i * 1.5 } },
693
- timestamp: Date.now() + i,
694
- }));
695
- const envelope = await makeCompressedEnvelope(originalItems);
696
- const record = makeSqsRecord({ messageBody: envelope }, 'msg-500-items');
697
- const { items, recordMap, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
698
- expect(items).toHaveLength(500);
699
- expect(items[0]).toEqual(originalItems[0]);
700
- expect(items[249]).toEqual(originalItems[249]);
701
- expect(items[499]).toEqual(originalItems[499]);
702
- expect(stats.bundledSqsRecords).toBe(1);
703
- expect(stats.totalItems).toBe(500);
704
- for (let i = 0; i < 500; i++) {
705
- expect(items[i].id).toBe(`item-${i}`);
706
- expect(items[i].data.nested.value).toBe(i * 1.5);
707
- }
708
- for (const item of items) {
709
- expect(recordMap.get(item)).toBe('msg-500-items');
710
- }
711
- });
712
- it('handles multiple bundles in single batch with different item counts', async () => {
713
- const counts = [10, 50, 100, 200, 300];
714
- const records = [];
715
- const expectedTotal = counts.reduce((sum, c) => sum + c, 0);
716
- for (let bundleIdx = 0; bundleIdx < counts.length; bundleIdx++) {
717
- const count = counts[bundleIdx];
718
- const items = Array.from({ length: count }, (_, i) => ({
719
- bundleId: bundleIdx,
720
- itemIdx: i,
721
- payload: `bundle-${bundleIdx}-item-${i}`,
722
- }));
723
- const envelope = await makeCompressedEnvelope(items);
724
- records.push(makeSqsRecord({ messageBody: envelope }, `msg-bundle-${bundleIdx}`));
725
- }
726
- const { items, recordMap, stats } = await (0, sqs_unbundle_1.unbundleRecords)(records);
727
- expect(items).toHaveLength(expectedTotal);
728
- expect(stats.totalItems).toBe(expectedTotal);
729
- expect(stats.bundledSqsRecords).toBe(5);
730
- expect(stats.legacySqsRecords).toBe(0);
731
- expect(stats.failedRecords).toBe(0);
732
- const itemsByBundle = new Map();
733
- for (const item of items) {
734
- const expectedMsgId = `msg-bundle-${item.bundleId}`;
735
- expect(recordMap.get(item)).toBe(expectedMsgId);
736
- itemsByBundle.set(item.bundleId, (itemsByBundle.get(item.bundleId) ?? 0) + 1);
737
- }
738
- expect(itemsByBundle.get(0)).toBe(10);
739
- expect(itemsByBundle.get(1)).toBe(50);
740
- expect(itemsByBundle.get(2)).toBe(100);
741
- expect(itemsByBundle.get(3)).toBe(200);
742
- expect(itemsByBundle.get(4)).toBe(300);
743
- });
744
- it('handles realistic 80% bundled / 20% legacy migration ratio', async () => {
745
- const records = [];
746
- const totalMessages = 100;
747
- const bundledCount = 80;
748
- const legacyCount = 20;
749
- const itemsPerBundle = 25;
750
- for (let i = 0; i < bundledCount; i++) {
751
- const items = Array.from({ length: itemsPerBundle }, (_, j) => ({
752
- type: 'bundled',
753
- bundleIdx: i,
754
- itemIdx: j,
755
- }));
756
- const envelope = await makeCompressedEnvelope(items);
757
- records.push(makeSqsRecord({ messageBody: envelope }, `msg-bundled-${i}`));
758
- }
759
- for (let i = 0; i < legacyCount; i++) {
760
- const legacyEvent = {
761
- type: 'legacy',
762
- legacyIdx: i,
763
- itemIdx: 0,
764
- };
765
- records.push(makeSqsRecord(legacyEvent, `msg-legacy-${i}`));
766
- }
767
- const shuffled = [...records].sort(() => Math.random() - 0.5);
768
- const { items, stats, recordMap } = await (0, sqs_unbundle_1.unbundleRecords)(shuffled);
769
- expect(stats.totalRecords).toBe(totalMessages);
770
- expect(stats.bundledSqsRecords).toBe(bundledCount);
771
- expect(stats.legacySqsRecords).toBe(legacyCount);
772
- expect(stats.failedRecords).toBe(0);
773
- expect(items).toHaveLength(bundledCount * itemsPerBundle + legacyCount);
774
- expect(stats.totalItems).toBe(2020);
775
- const bundledItems = items.filter((i) => i.type === 'bundled');
776
- const legacyItems = items.filter((i) => i.type === 'legacy');
777
- expect(bundledItems).toHaveLength(bundledCount * itemsPerBundle);
778
- expect(legacyItems).toHaveLength(legacyCount);
779
- for (const item of bundledItems) {
780
- expect(recordMap.get(item)).toBe(`msg-bundled-${item.bundleIdx}`);
781
- }
782
- for (const item of legacyItems) {
783
- expect(recordMap.get(item)).toBe(`msg-legacy-${item.legacyIdx}`);
784
- }
785
- });
786
- it('preserves Unicode characters through ZSTD compression', async () => {
787
- const unicodeItems = [
788
- {
789
- id: 'emoji',
790
- content: '🎉🚀💻🌍✨ Party time!',
791
- description: 'Emojis should survive compression',
792
- },
793
- {
794
- id: 'chinese',
795
- content: '中文测试:欢迎使用我们的服务',
796
- description: 'Chinese characters',
797
- },
798
- {
799
- id: 'arabic',
800
- content: 'العربية: مرحبا بكم في خدمتنا',
801
- description: 'Arabic text (RTL)',
802
- },
803
- {
804
- id: 'math',
805
- content: '∑∫∂∇ε → ∞ × ∈ ⊆ ∀∃',
806
- description: 'Mathematical symbols',
807
- },
808
- {
809
- id: 'mixed',
810
- content: 'Hello 世界 🌏 مرحبا ∑ Привет',
811
- description: 'Mixed scripts',
812
- },
813
- {
814
- id: 'special',
815
- content: '️️️️️️️️️️️️️️️️',
816
- description: 'Unicode edge cases',
817
- },
818
- {
819
- id: 'japanese',
820
- content: 'こんにちは世界 ひらがな カタカナ 漢字',
821
- description: 'Japanese hiragana, katakana, kanji',
822
- },
823
- {
824
- id: 'korean',
825
- content: '안녕하세요 세계',
826
- description: 'Korean hangul',
827
- },
828
- {
829
- id: 'cyrillic',
830
- content: 'Привет мир! Это тест.',
831
- description: 'Russian Cyrillic',
832
- },
833
- {
834
- id: 'combining',
835
- content: 'e\u0301 n\u0303 c\u0327',
836
- description: 'Combining diacritical marks',
837
- },
838
- ];
839
- const envelope = await makeCompressedEnvelope(unicodeItems);
840
- const record = makeSqsRecord({ messageBody: envelope }, 'msg-unicode');
841
- const { items } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
842
- expect(items).toHaveLength(unicodeItems.length);
843
- for (let i = 0; i < unicodeItems.length; i++) {
844
- expect(items[i].id).toBe(unicodeItems[i].id);
845
- expect(items[i].content).toBe(unicodeItems[i].content);
846
- expect(items[i].description).toBe(unicodeItems[i].description);
847
- const originalBytes = Buffer.from(unicodeItems[i].content, 'utf8');
848
- const recoveredBytes = Buffer.from(items[i].content, 'utf8');
849
- expect(recoveredBytes.equals(originalBytes)).toBe(true);
850
- }
851
- });
852
- it('preserves deeply nested structures (10 levels)', async () => {
853
- function createDeepStructure(depth, maxDepth = 10) {
854
- const node = {
855
- level: depth,
856
- data: `level-${depth}-data-${Math.random().toString(36).slice(2)}`,
857
- };
858
- if (depth < maxDepth) {
859
- node.child = createDeepStructure(depth + 1, maxDepth);
860
- node.siblings = [
861
- createDeepStructure(depth + 1, Math.min(depth + 3, maxDepth)),
862
- createDeepStructure(depth + 1, Math.min(depth + 2, maxDepth)),
863
- ];
864
- }
865
- return node;
866
- }
867
- const deepItems = Array.from({ length: 10 }, (_, i) => ({
868
- id: `deep-${i}`,
869
- nested: createDeepStructure(0, 10),
870
- arrays: [[[[[['deeply', 'nested', 'array']]], [{ inner: { value: i } }]]]],
871
- }));
872
- const envelope = await makeCompressedEnvelope(deepItems);
873
- const record = makeSqsRecord({ messageBody: envelope }, 'msg-deep');
874
- const { items } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
875
- expect(items).toHaveLength(10);
876
- for (let i = 0; i < 10; i++) {
877
- expect(JSON.stringify(items[i])).toBe(JSON.stringify(deepItems[i]));
878
- expect(items[i].nested.level).toBe(0);
879
- expect(items[i].nested.child?.level).toBe(1);
880
- expect(items[i].nested.child?.child?.child?.level).toBe(3);
881
- expect(items[i].arrays[0][0][0][0][0][0]).toBe('deeply');
882
- }
883
- });
884
- it('handles binary-like string data (base64 encoded)', async () => {
885
- const binaryItems = [
886
- {
887
- id: 'small-binary',
888
- base64Data: Buffer.from('Small binary payload').toString('base64'),
889
- size: 'small',
890
- },
891
- {
892
- id: 'medium-binary',
893
- base64Data: Buffer.from('x'.repeat(1000)).toString('base64'),
894
- size: 'medium',
895
- },
896
- {
897
- id: 'image-simulation',
898
- base64Data: Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0xff, 0xd8, 0xff, 0xe0]).toString('base64'),
899
- size: 'binary-header',
900
- },
901
- {
902
- id: 'random-bytes',
903
- base64Data: Buffer.from(Array.from({ length: 256 }, () => Math.floor(Math.random() * 256))).toString('base64'),
904
- size: 'random',
905
- },
906
- {
907
- id: 'all-byte-values',
908
- base64Data: Buffer.from(Array.from({ length: 256 }, (_, i) => i)).toString('base64'),
909
- size: 'full-range',
910
- },
911
- ];
912
- const envelope = await makeCompressedEnvelope(binaryItems);
913
- const record = makeSqsRecord({ messageBody: envelope }, 'msg-binary');
914
- const { items } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
915
- expect(items).toHaveLength(binaryItems.length);
916
- for (let i = 0; i < binaryItems.length; i++) {
917
- expect(items[i].id).toBe(binaryItems[i].id);
918
- expect(items[i].base64Data).toBe(binaryItems[i].base64Data);
919
- const originalDecoded = Buffer.from(binaryItems[i].base64Data, 'base64');
920
- const recoveredDecoded = Buffer.from(items[i].base64Data, 'base64');
921
- expect(recoveredDecoded.equals(originalDecoded)).toBe(true);
922
- }
923
- });
924
- it('handles concurrent unbundling stress test (100 records in parallel)', async () => {
925
- const recordCount = 100;
926
- const records = [];
927
- for (let i = 0; i < recordCount; i++) {
928
- const itemsInBundle = 10 + (i % 20);
929
- const items = Array.from({ length: itemsInBundle }, (_, j) => ({
930
- recordIdx: i,
931
- itemIdx: j,
932
- uniqueId: `r${i}-i${j}`,
933
- timestamp: Date.now() + i * 1000 + j,
934
- data: `payload-${i}-${j}-${Math.random().toString(36).slice(2)}`,
935
- }));
936
- const envelope = await makeCompressedEnvelope(items);
937
- records.push(makeSqsRecord({ messageBody: envelope }, `stress-msg-${i}`));
938
- }
939
- const startTime = Date.now();
940
- const { items, recordMap, stats, failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)(records);
941
- const duration = Date.now() - startTime;
942
- expect(failedMessageIds).toHaveLength(0);
943
- expect(stats.failedRecords).toBe(0);
944
- expect(stats.bundledSqsRecords).toBe(recordCount);
945
- const seenUniqueIds = new Set();
946
- for (const item of items) {
947
- expect(seenUniqueIds.has(item.uniqueId)).toBe(false);
948
- seenUniqueIds.add(item.uniqueId);
949
- const expectedMsgId = `stress-msg-${item.recordIdx}`;
950
- expect(recordMap.get(item)).toBe(expectedMsgId);
951
- expect(item.itemIdx).toBeGreaterThanOrEqual(0);
952
- expect(item.itemIdx).toBeLessThan(30);
953
- }
954
- const expectedTotal = Array.from({ length: recordCount }, (_, i) => 10 + (i % 20)).reduce((a, b) => a + b, 0);
955
- expect(items).toHaveLength(expectedTotal);
956
- expect(stats.totalItems).toBe(expectedTotal);
957
- console.log(`Unbundled ${recordCount} records (${stats.totalItems} items) in ${duration}ms`);
958
- });
959
- it('handles items with all JSON primitive types', async () => {
960
- const primitiveItems = [
961
- {
962
- id: 'primitives',
963
- stringVal: 'hello',
964
- numberVal: 42,
965
- floatVal: 3.14159265359,
966
- boolTrue: true,
967
- boolFalse: false,
968
- nullVal: null,
969
- emptyString: '',
970
- zero: 0,
971
- negativeNum: -999,
972
- largeNum: Number.MAX_SAFE_INTEGER,
973
- smallNum: Number.MIN_SAFE_INTEGER,
974
- scientificNotation: 1.23e10,
975
- },
976
- {
977
- id: 'arrays',
978
- emptyArray: [],
979
- numberArray: [1, 2, 3],
980
- mixedArray: [1, 'two', true, null],
981
- nestedArray: [[1, 2], [3, 4], [[5]]],
982
- },
983
- {
984
- id: 'objects',
985
- emptyObject: {},
986
- nestedObject: { a: { b: { c: 1 } } },
987
- arrayOfObjects: [{ x: 1 }, { y: 2 }],
988
- },
989
- ];
990
- const envelope = await makeCompressedEnvelope(primitiveItems);
991
- const record = makeSqsRecord({ messageBody: envelope }, 'msg-primitives');
992
- const { items } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
993
- expect(items).toHaveLength(3);
994
- expect(JSON.stringify(items)).toBe(JSON.stringify(primitiveItems));
995
- });
996
- it('maintains item order across multiple bundles', async () => {
997
- const records = [];
998
- let globalIdx = 0;
999
- for (let bundleIdx = 0; bundleIdx < 10; bundleIdx++) {
1000
- const itemCount = 10 + bundleIdx * 5;
1001
- const items = Array.from({ length: itemCount }, () => ({
1002
- globalOrder: globalIdx++,
1003
- bundle: bundleIdx,
1004
- }));
1005
- const envelope = await makeCompressedEnvelope(items);
1006
- records.push(makeSqsRecord({ messageBody: envelope }, `order-msg-${bundleIdx}`));
1007
- }
1008
- const { items } = await (0, sqs_unbundle_1.unbundleRecords)(records);
1009
- const bundleGroups = new Map();
1010
- for (const item of items) {
1011
- if (!bundleGroups.has(item.bundle)) {
1012
- bundleGroups.set(item.bundle, []);
1013
- }
1014
- bundleGroups.get(item.bundle)?.push(item.globalOrder);
1015
- }
1016
- for (const [, orders] of bundleGroups) {
1017
- for (let i = 1; i < orders.length; i++) {
1018
- expect(orders[i]).toBeGreaterThan(orders[i - 1]);
1019
- }
1020
- }
1021
- });
1022
- it('handles edge case: single item bundles', async () => {
1023
- const records = await Promise.all(Array.from({ length: 50 }, async (_, i) => {
1024
- const items = [{ id: `single-${i}`, value: i }];
1025
- const envelope = await makeCompressedEnvelope(items);
1026
- return makeSqsRecord({ messageBody: envelope }, `single-msg-${i}`);
1027
- }));
1028
- const { items, stats, recordMap } = await (0, sqs_unbundle_1.unbundleRecords)(records);
1029
- expect(items).toHaveLength(50);
1030
- expect(stats.bundledSqsRecords).toBe(50);
1031
- expect(stats.totalItems).toBe(50);
1032
- for (let i = 0; i < 50; i++) {
1033
- const item = items.find((x) => x.value === i);
1034
- expect(item).toBeDefined();
1035
- if (!item)
1036
- throw new Error(`Expected item with value ${i} to be found`);
1037
- expect(recordMap.get(item)).toMatch(/single-msg-\d+/);
1038
- }
1039
- });
1040
- it('handles edge case: empty arrays in bundles', async () => {
1041
- const emptyEnvelope = {
1042
- v: 1,
1043
- c: sqs_bundled_client_types_1.CompressionAlgorithm.NONE,
1044
- n: 0,
1045
- p: [],
1046
- };
1047
- const record = makeSqsRecord({ messageBody: emptyEnvelope }, 'msg-empty');
1048
- const { items, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
1049
- expect(items).toHaveLength(0);
1050
- expect(stats.bundledSqsRecords).toBe(1);
1051
- expect(stats.totalItems).toBe(0);
1052
- });
1053
- });
1054
- describe('GZIP comprehensive tests', () => {
1055
- describe('GZIP pre-decompression bomb protection', () => {
1056
- it('rejects GZIP with fake ISIZE claiming huge size BEFORE decompression starts', async () => {
1057
- const gzipHeader = Buffer.from([0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03]);
1058
- const emptyDeflate = Buffer.from([0x03, 0x00]);
1059
- const crc32 = Buffer.from([0x00, 0x00, 0x00, 0x00]);
1060
- const fakeIsize = Buffer.alloc(4);
1061
- fakeIsize.writeUInt32LE(100 * 1024 * 1024, 0);
1062
- const fakeGzip = Buffer.concat([gzipHeader, emptyDeflate, crc32, fakeIsize]);
1063
- const base64 = fakeGzip.toString('base64');
1064
- const bombEnvelope = {
1065
- v: 1,
1066
- c: sqs_bundled_client_types_1.CompressionAlgorithm.GZIP,
1067
- n: 1,
1068
- s: 100,
1069
- p: base64,
1070
- };
1071
- const record = makeSqsRecord({ messageBody: bombEnvelope }, 'msg-gzip-bomb');
1072
- const { failedMessageIds, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record], {
1073
- maxDecompressedSizeBytes: 10 * 1024 * 1024,
1074
- });
1075
- expect(failedMessageIds).toContain('msg-gzip-bomb');
1076
- expect(stats.failedRecords).toBe(1);
1077
- });
1078
- it('allows GZIP with ISIZE within limit', async () => {
1079
- const items = [makeTestEvent('valid-1')];
1080
- const envelope = await makeGzipCompressedEnvelope(items);
1081
- const record = makeSqsRecord({ messageBody: envelope }, 'msg-gzip-valid');
1082
- const { items: result, failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record], {
1083
- maxDecompressedSizeBytes: 10 * 1024 * 1024,
1084
- });
1085
- expect(result).toHaveLength(1);
1086
- expect(failedMessageIds).toHaveLength(0);
1087
- });
1088
- it('pre-check catches bomb even when ISIZE is exactly at limit + 1', async () => {
1089
- const limit = 1000;
1090
- const gzipHeader = Buffer.from([0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03]);
1091
- const emptyDeflate = Buffer.from([0x03, 0x00]);
1092
- const crc32 = Buffer.from([0x00, 0x00, 0x00, 0x00]);
1093
- const fakeIsize = Buffer.alloc(4);
1094
- fakeIsize.writeUInt32LE(limit + 1, 0);
1095
- const fakeGzip = Buffer.concat([gzipHeader, emptyDeflate, crc32, fakeIsize]);
1096
- const base64 = fakeGzip.toString('base64');
1097
- const bombEnvelope = {
1098
- v: 1,
1099
- c: sqs_bundled_client_types_1.CompressionAlgorithm.GZIP,
1100
- n: 1,
1101
- s: 100,
1102
- p: base64,
1103
- };
1104
- const record = makeSqsRecord({ messageBody: bombEnvelope }, 'msg-boundary-bomb');
1105
- const { failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record], {
1106
- maxDecompressedSizeBytes: limit,
1107
- });
1108
- expect(failedMessageIds).toContain('msg-boundary-bomb');
1109
- });
1110
- });
1111
- describe('empty GZIP bundle', () => {
1112
- it('unbundles empty GZIP compressed array', async () => {
1113
- const envelope = await makeGzipCompressedEnvelope([]);
1114
- const record = makeSqsRecord({ messageBody: envelope }, 'msg-empty-gzip');
1115
- const { items, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
1116
- expect(items).toHaveLength(0);
1117
- expect(stats.bundledSqsRecords).toBe(1);
1118
- expect(stats.totalItems).toBe(0);
1119
- });
1120
- });
1121
- describe('Unicode characters in GZIP payload', () => {
1122
- it('preserves emoji through GZIP compression', async () => {
1123
- const items = [
1124
- { eventId: 'emoji-1', content: '🎉🚀💻🌍✨' },
1125
- { eventId: 'emoji-2', content: '👋🏽👨‍👩‍👧‍👦🏳️‍🌈' },
1126
- ];
1127
- const envelope = await makeGzipCompressedEnvelope(items);
1128
- const record = makeSqsRecord({ messageBody: envelope });
1129
- const { items: result } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
1130
- expect(result).toHaveLength(2);
1131
- expect(result[0].content).toBe('🎉🚀💻🌍✨');
1132
- expect(result[1].content).toBe('👋🏽👨‍👩‍👧‍👦🏳️‍🌈');
1133
- });
1134
- it('preserves CJK characters through GZIP compression', async () => {
1135
- const items = [
1136
- { eventId: 'cjk-1', content: '中文测试:欢迎使用' },
1137
- { eventId: 'cjk-2', content: 'こんにちは世界 ひらがな カタカナ' },
1138
- { eventId: 'cjk-3', content: '안녕하세요 세계' },
1139
- ];
1140
- const envelope = await makeGzipCompressedEnvelope(items);
1141
- const record = makeSqsRecord({ messageBody: envelope });
1142
- const { items: result } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
1143
- expect(result).toHaveLength(3);
1144
- expect(result[0].content).toBe('中文测试:欢迎使用');
1145
- expect(result[1].content).toBe('こんにちは世界 ひらがな カタカナ');
1146
- expect(result[2].content).toBe('안녕하세요 세계');
1147
- });
1148
- it('preserves special characters and mixed scripts through GZIP compression', async () => {
1149
- const items = [
1150
- { eventId: 'special-1', content: '∑∫∂∇ε → ∞ × ∈ ⊆ ∀∃' },
1151
- { eventId: 'special-2', content: 'العربية: مرحبا بكم' },
1152
- { eventId: 'mixed', content: 'Hello 世界 🌏 مرحبا Привет' },
1153
- { eventId: 'combining', content: 'e\u0301 n\u0303 c\u0327' },
1154
- ];
1155
- const envelope = await makeGzipCompressedEnvelope(items);
1156
- const record = makeSqsRecord({ messageBody: envelope });
1157
- const { items: result } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
1158
- expect(result).toHaveLength(4);
1159
- for (let i = 0; i < items.length; i++) {
1160
- expect(result[i].content).toBe(items[i].content);
1161
- const originalBytes = Buffer.from(items[i].content, 'utf8');
1162
- const recoveredBytes = Buffer.from(result[i].content, 'utf8');
1163
- expect(recoveredBytes.equals(originalBytes)).toBe(true);
1164
- }
1165
- });
1166
- });
1167
- describe('GZIP with invalid magic bytes', () => {
1168
- it('fails gracefully for buffer with wrong magic bytes', async () => {
1169
- const wrongMagic = Buffer.from([0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03]);
1170
- const filler = Buffer.alloc(10);
1171
- const invalidGzip = Buffer.concat([wrongMagic, filler]);
1172
- const base64 = invalidGzip.toString('base64');
1173
- const badEnvelope = {
1174
- v: 1,
1175
- c: sqs_bundled_client_types_1.CompressionAlgorithm.GZIP,
1176
- n: 1,
1177
- s: 100,
1178
- p: base64,
1179
- };
1180
- const record = makeSqsRecord({ messageBody: badEnvelope }, 'msg-wrong-magic');
1181
- const { failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
1182
- expect(failedMessageIds).toContain('msg-wrong-magic');
1183
- });
1184
- it('fails gracefully for buffer starting with valid magic but corrupted data', async () => {
1185
- const validMagic = Buffer.from([0x1f, 0x8b]);
1186
- const garbage = Buffer.from('this is not valid gzip data after magic');
1187
- const invalidGzip = Buffer.concat([validMagic, garbage]);
1188
- const base64 = invalidGzip.toString('base64');
1189
- const badEnvelope = {
1190
- v: 1,
1191
- c: sqs_bundled_client_types_1.CompressionAlgorithm.GZIP,
1192
- n: 1,
1193
- s: 100,
1194
- p: base64,
1195
- };
1196
- const record = makeSqsRecord({ messageBody: badEnvelope }, 'msg-corrupt-gzip');
1197
- const { failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
1198
- expect(failedMessageIds).toContain('msg-corrupt-gzip');
1199
- });
1200
- });
1201
- describe('GZIP minimum size edge cases', () => {
1202
- it('handles buffer exactly 18 bytes (minimum valid)', async () => {
1203
- const minBuffer = Buffer.alloc(18);
1204
- minBuffer[0] = 0x1f;
1205
- minBuffer[1] = 0x8b;
1206
- const size = (0, compression_1.getGzipUncompressedSize)(minBuffer);
1207
- expect(size).toBe(0);
1208
- });
1209
- it('returns undefined for buffer exactly 17 bytes (too small)', async () => {
1210
- const tooSmall = Buffer.alloc(17);
1211
- tooSmall[0] = 0x1f;
1212
- tooSmall[1] = 0x8b;
1213
- const size = (0, compression_1.getGzipUncompressedSize)(tooSmall);
1214
- expect(size).toBeUndefined();
1215
- });
1216
- it('fails gracefully for 17-byte GZIP envelope', async () => {
1217
- const tooSmall = Buffer.alloc(17);
1218
- tooSmall[0] = 0x1f;
1219
- tooSmall[1] = 0x8b;
1220
- const base64 = tooSmall.toString('base64');
1221
- const badEnvelope = {
1222
- v: 1,
1223
- c: sqs_bundled_client_types_1.CompressionAlgorithm.GZIP,
1224
- n: 1,
1225
- s: 100,
1226
- p: base64,
1227
- };
1228
- const record = makeSqsRecord({ messageBody: badEnvelope }, 'msg-too-small');
1229
- const { failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
1230
- expect(failedMessageIds).toContain('msg-too-small');
1231
- });
1232
- });
1233
- describe('GZIP timeout protection', () => {
1234
- it('CompressionTimeoutError has correct properties', () => {
1235
- const error = new compression_1.CompressionTimeoutError(compression_1.CompressionOperation.DECOMPRESS, 100);
1236
- expect(error).toBeInstanceOf(Error);
1237
- expect(error.name).toBe('CompressionTimeoutError');
1238
- expect(error.operation).toBe('decompress');
1239
- expect(error.timeoutMs).toBe(100);
1240
- expect(error.message).toContain('timed out after 100ms');
1241
- });
1242
- it('GZIP decompression respects timeout option', async () => {
1243
- const items = [makeTestEvent('timeout-test')];
1244
- const envelope = await makeGzipCompressedEnvelope(items);
1245
- const record = makeSqsRecord({ messageBody: envelope });
1246
- const { items: result, failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record], {
1247
- timeoutMs: 10000,
1248
- });
1249
- expect(result).toHaveLength(1);
1250
- expect(failedMessageIds).toHaveLength(0);
1251
- });
1252
- });
1253
- describe('large GZIP near SQS limit', () => {
1254
- it('decompresses large GZIP bundle near 256KB compressed', async () => {
1255
- const items = Array.from({ length: 200 }, (_, i) => ({
1256
- eventId: `large-${i}`,
1257
- name: 'page_view',
1258
- data: `repeated-content-for-compression-${i}-`.repeat(50),
1259
- value: i * 1.5,
1260
- }));
1261
- const envelope = await makeGzipCompressedEnvelope(items);
1262
- const compressedSize = Buffer.byteLength(JSON.stringify(envelope.p), 'utf8');
1263
- console.log(`Large GZIP test: compressed payload ~${Math.round(compressedSize / 1024)}KB`);
1264
- const record = makeSqsRecord({ messageBody: envelope }, 'msg-large-gzip');
1265
- const { items: result, failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
1266
- expect(failedMessageIds).toHaveLength(0);
1267
- expect(result).toHaveLength(200);
1268
- expect(result[0].eventId).toBe('large-0');
1269
- expect(result[199].eventId).toBe('large-199');
1270
- });
1271
- it('handles GZIP with maximum decompressed size exactly at limit', async () => {
1272
- const items = [{ eventId: 'boundary', data: 'x'.repeat(5000) }];
1273
- const envelope = await makeGzipCompressedEnvelope(items);
1274
- const record = makeSqsRecord({ messageBody: envelope });
1275
- const { items: result, failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record], {
1276
- maxDecompressedSizeBytes: 10 * 1024,
1277
- });
1278
- expect(failedMessageIds).toHaveLength(0);
1279
- expect(result).toHaveLength(1);
1280
- });
1281
- });
1282
- });
1283
- });
1284
- describe('getGzipUncompressedSize', () => {
1285
- it('returns correct size for valid GZIP', async () => {
1286
- const originalData = 'Hello, World! This is a test.';
1287
- const compressed = await gzipAsync(Buffer.from(originalData, 'utf8'));
1288
- const size = (0, compression_1.getGzipUncompressedSize)(compressed);
1289
- expect(size).toBe(Buffer.byteLength(originalData, 'utf8'));
1290
- });
1291
- it('returns correct size for larger payload', async () => {
1292
- const originalData = 'x'.repeat(10000);
1293
- const compressed = await gzipAsync(Buffer.from(originalData, 'utf8'));
1294
- const size = (0, compression_1.getGzipUncompressedSize)(compressed);
1295
- expect(size).toBe(10000);
1296
- });
1297
- it('returns undefined for buffer too small', () => {
1298
- const tooSmall = Buffer.alloc(17);
1299
- tooSmall[0] = 0x1f;
1300
- tooSmall[1] = 0x8b;
1301
- expect((0, compression_1.getGzipUncompressedSize)(tooSmall)).toBeUndefined();
1302
- });
1303
- it('returns undefined for wrong magic bytes', () => {
1304
- const wrongMagic = Buffer.alloc(20);
1305
- wrongMagic[0] = 0x00;
1306
- wrongMagic[1] = 0x00;
1307
- expect((0, compression_1.getGzipUncompressedSize)(wrongMagic)).toBeUndefined();
1308
- });
1309
- it('returns undefined for empty buffer', () => {
1310
- expect((0, compression_1.getGzipUncompressedSize)(Buffer.alloc(0))).toBeUndefined();
1311
- });
1312
- it('returns undefined for single byte buffer', () => {
1313
- expect((0, compression_1.getGzipUncompressedSize)(Buffer.from([0x1f]))).toBeUndefined();
1314
- });
1315
- it('returns undefined for buffer with only magic bytes', () => {
1316
- expect((0, compression_1.getGzipUncompressedSize)(Buffer.from([0x1f, 0x8b]))).toBeUndefined();
1317
- });
1318
- it('returns 0 for GZIP with ISIZE = 0', () => {
1319
- const buffer = Buffer.alloc(18);
1320
- buffer[0] = 0x1f;
1321
- buffer[1] = 0x8b;
1322
- const size = (0, compression_1.getGzipUncompressedSize)(buffer);
1323
- expect(size).toBe(0);
1324
- });
1325
- it('reads ISIZE correctly as little-endian', async () => {
1326
- const buffer = Buffer.alloc(18);
1327
- buffer[0] = 0x1f;
1328
- buffer[1] = 0x8b;
1329
- buffer.writeUInt32LE(0x12345678, 14);
1330
- const size = (0, compression_1.getGzipUncompressedSize)(buffer);
1331
- expect(size).toBe(0x12345678);
1332
- });
1333
- it('handles maximum 32-bit ISIZE value', () => {
1334
- const buffer = Buffer.alloc(18);
1335
- buffer[0] = 0x1f;
1336
- buffer[1] = 0x8b;
1337
- buffer.writeUInt32LE(0xffffffff, 14);
1338
- const size = (0, compression_1.getGzipUncompressedSize)(buffer);
1339
- expect(size).toBe(0xffffffff);
1340
- });
1341
- it('correctly extracts size from real GZIP data', async () => {
1342
- const testSizes = [1, 10, 100, 1000, 50000];
1343
- for (const testSize of testSizes) {
1344
- const originalData = 'a'.repeat(testSize);
1345
- const compressed = await gzipAsync(Buffer.from(originalData, 'utf8'));
1346
- const size = (0, compression_1.getGzipUncompressedSize)(compressed);
1347
- expect(size).toBe(testSize);
1348
- }
1349
- });
1350
- it('handles JSON array compression correctly', async () => {
1351
- const items = [{ id: 1 }, { id: 2 }, { id: 3 }];
1352
- const json = JSON.stringify(items);
1353
- const compressed = await gzipAsync(Buffer.from(json, 'utf8'));
1354
- const size = (0, compression_1.getGzipUncompressedSize)(compressed);
1355
- expect(size).toBe(Buffer.byteLength(json, 'utf8'));
1356
- });
1357
- });
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const sqs_unbundle_1 = require("../../clients/generic/sqs-unbundle");
4
+ const sqs_bundled_client_types_1 = require("../../clients/generic/sqs-bundled-client.types");
5
+ const compression_1 = require("../../utils/compression");
6
+ const node_zlib_1 = require("node:zlib");
7
+ const node_util_1 = require("node:util");
8
+ const gzipAsync = (0, node_util_1.promisify)(node_zlib_1.gzip);
9
+ jest.mock('../../helpers/logging-helper', () => ({
10
+ Logger: {
11
+ debug: jest.fn(),
12
+ info: jest.fn(),
13
+ warn: jest.fn(),
14
+ error: jest.fn(),
15
+ },
16
+ }));
17
+ function makeTestEvent(id, name = 'test_event', value) {
18
+ return { eventId: id, name, value };
19
+ }
20
+ function makeSqsRecord(body, messageId = 'msg-1') {
21
+ return {
22
+ messageId,
23
+ body: typeof body === 'string' ? body : JSON.stringify(body),
24
+ };
25
+ }
26
+ function makeBundledEnvelope(items) {
27
+ return {
28
+ v: 1,
29
+ c: sqs_bundled_client_types_1.CompressionAlgorithm.NONE,
30
+ n: items.length,
31
+ s: JSON.stringify(items).length,
32
+ p: items,
33
+ };
34
+ }
35
+ async function makeCompressedEnvelope(items, level = 3) {
36
+ const json = JSON.stringify(items);
37
+ const compressed = await (0, compression_1.compress)(Buffer.from(json, 'utf8'), sqs_bundled_client_types_1.CompressionAlgorithm.ZSTD, level);
38
+ const base64 = compressed.toString('base64');
39
+ return {
40
+ v: 1,
41
+ c: sqs_bundled_client_types_1.CompressionAlgorithm.ZSTD,
42
+ n: items.length,
43
+ s: json.length,
44
+ p: base64,
45
+ };
46
+ }
47
+ async function makeGzipCompressedEnvelope(items) {
48
+ const json = JSON.stringify(items);
49
+ const compressed = await gzipAsync(Buffer.from(json, 'utf8'));
50
+ const base64 = compressed.toString('base64');
51
+ return {
52
+ v: 1,
53
+ c: sqs_bundled_client_types_1.CompressionAlgorithm.GZIP,
54
+ n: items.length,
55
+ s: json.length,
56
+ p: base64,
57
+ };
58
+ }
59
+ describe('isBundledEnvelope', () => {
60
+ it('returns true for valid bundled envelope with NONE compression', () => {
61
+ const envelope = makeBundledEnvelope([makeTestEvent('1')]);
62
+ expect((0, sqs_unbundle_1.isBundledEnvelope)(envelope)).toBe(true);
63
+ });
64
+ it('returns true for valid bundled envelope with ZSTD compression', async () => {
65
+ const envelope = await makeCompressedEnvelope([makeTestEvent('1')]);
66
+ expect((0, sqs_unbundle_1.isBundledEnvelope)(envelope)).toBe(true);
67
+ });
68
+ it('returns false for null', () => {
69
+ expect((0, sqs_unbundle_1.isBundledEnvelope)(null)).toBe(false);
70
+ });
71
+ it('returns false for undefined', () => {
72
+ expect((0, sqs_unbundle_1.isBundledEnvelope)(undefined)).toBe(false);
73
+ });
74
+ it('returns false for plain object (legacy message)', () => {
75
+ expect((0, sqs_unbundle_1.isBundledEnvelope)(makeTestEvent('1'))).toBe(false);
76
+ });
77
+ it('returns false for wrong version', () => {
78
+ const envelope = { v: 2, c: 'none', n: 1, p: [] };
79
+ expect((0, sqs_unbundle_1.isBundledEnvelope)(envelope)).toBe(false);
80
+ });
81
+ it('returns true for valid bundled envelope with GZIP compression', async () => {
82
+ const envelope = await makeGzipCompressedEnvelope([makeTestEvent('1')]);
83
+ expect((0, sqs_unbundle_1.isBundledEnvelope)(envelope)).toBe(true);
84
+ });
85
+ it('returns false for invalid compression algorithm', () => {
86
+ const envelope = { v: 1, c: 'lz4', n: 1, p: [] };
87
+ expect((0, sqs_unbundle_1.isBundledEnvelope)(envelope)).toBe(false);
88
+ });
89
+ it('returns false for missing count', () => {
90
+ const envelope = { v: 1, c: 'none', p: [] };
91
+ expect((0, sqs_unbundle_1.isBundledEnvelope)(envelope)).toBe(false);
92
+ });
93
+ it('returns false for missing payload', () => {
94
+ const envelope = { v: 1, c: 'none', n: 1 };
95
+ expect((0, sqs_unbundle_1.isBundledEnvelope)(envelope)).toBe(false);
96
+ });
97
+ });
98
+ describe('unbundleMessage', () => {
99
+ it('unbundles NONE compression envelope', async () => {
100
+ const items = [makeTestEvent('1'), makeTestEvent('2')];
101
+ const envelope = makeBundledEnvelope(items);
102
+ const result = await (0, sqs_unbundle_1.unbundleMessage)(envelope);
103
+ expect(result).toHaveLength(2);
104
+ expect(result[0].eventId).toBe('1');
105
+ expect(result[1].eventId).toBe('2');
106
+ });
107
+ it('unbundles ZSTD compressed envelope', async () => {
108
+ const items = [makeTestEvent('1'), makeTestEvent('2'), makeTestEvent('3')];
109
+ const envelope = await makeCompressedEnvelope(items);
110
+ const result = await (0, sqs_unbundle_1.unbundleMessage)(envelope);
111
+ expect(result).toHaveLength(3);
112
+ expect(result[0].eventId).toBe('1');
113
+ expect(result[1].eventId).toBe('2');
114
+ expect(result[2].eventId).toBe('3');
115
+ });
116
+ it('unbundles large batches with ZSTD compression', async () => {
117
+ const items = Array.from({ length: 500 }, (_, i) => makeTestEvent(`event-${i}`, `event_${i}`, i * 10));
118
+ const envelope = await makeCompressedEnvelope(items);
119
+ const result = await (0, sqs_unbundle_1.unbundleMessage)(envelope);
120
+ expect(result).toHaveLength(500);
121
+ expect(result[0].eventId).toBe('event-0');
122
+ expect(result[499].eventId).toBe('event-499');
123
+ });
124
+ it('throws on unsupported bundle version', async () => {
125
+ const envelope = { v: 99, c: 'none', n: 1, p: [] };
126
+ await expect((0, sqs_unbundle_1.unbundleMessage)(envelope)).rejects.toThrow('Unsupported bundle version: 99');
127
+ });
128
+ it('throws on invalid payload type for NONE compression', async () => {
129
+ const envelope = { v: 1, c: 'none', n: 1, p: 'not-an-array' };
130
+ await expect((0, sqs_unbundle_1.unbundleMessage)(envelope)).rejects.toThrow('Invalid bundle: NONE compression but payload is not an array');
131
+ });
132
+ it('throws on invalid payload type for ZSTD compression', async () => {
133
+ const envelope = { v: 1, c: 'zstd', n: 1, p: [] };
134
+ await expect((0, sqs_unbundle_1.unbundleMessage)(envelope)).rejects.toThrow('Invalid bundle: zstd compression but payload is not a string');
135
+ });
136
+ it('unbundles GZIP compressed envelope', async () => {
137
+ const items = [makeTestEvent('1'), makeTestEvent('2'), makeTestEvent('3')];
138
+ const envelope = await makeGzipCompressedEnvelope(items);
139
+ const result = await (0, sqs_unbundle_1.unbundleMessage)(envelope);
140
+ expect(result).toHaveLength(3);
141
+ expect(result[0].eventId).toBe('1');
142
+ expect(result[1].eventId).toBe('2');
143
+ expect(result[2].eventId).toBe('3');
144
+ });
145
+ it('unbundles large batches with GZIP compression', async () => {
146
+ const items = Array.from({ length: 500 }, (_, i) => makeTestEvent(`event-${i}`, `event_${i}`, i * 10));
147
+ const envelope = await makeGzipCompressedEnvelope(items);
148
+ const result = await (0, sqs_unbundle_1.unbundleMessage)(envelope);
149
+ expect(result).toHaveLength(500);
150
+ expect(result[0].eventId).toBe('event-0');
151
+ expect(result[499].eventId).toBe('event-499');
152
+ });
153
+ it('throws on invalid payload type for GZIP compression', async () => {
154
+ const envelope = { v: 1, c: 'gzip', n: 1, p: [] };
155
+ await expect((0, sqs_unbundle_1.unbundleMessage)(envelope)).rejects.toThrow('Invalid bundle: gzip compression but payload is not a string');
156
+ });
157
+ });
158
+ describe('unbundleRecords', () => {
159
+ describe('legacy (non-bundled) messages', () => {
160
+ it('parses raw JSON message', async () => {
161
+ const event = makeTestEvent('raw-1', 'purchase');
162
+ const record = makeSqsRecord(event);
163
+ const { items, stats, failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
164
+ expect(items).toHaveLength(1);
165
+ expect(items[0].eventId).toBe('raw-1');
166
+ expect(items[0].name).toBe('purchase');
167
+ expect(stats.legacySqsRecords).toBe(1);
168
+ expect(stats.bundledSqsRecords).toBe(0);
169
+ expect(failedMessageIds).toHaveLength(0);
170
+ });
171
+ it('parses wrapped messageBody format', async () => {
172
+ const event = makeTestEvent('wrapped-1');
173
+ const record = makeSqsRecord({ messageBody: event });
174
+ const { items, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
175
+ expect(items).toHaveLength(1);
176
+ expect(items[0].eventId).toBe('wrapped-1');
177
+ expect(stats.legacySqsRecords).toBe(1);
178
+ });
179
+ it('handles multiple legacy records', async () => {
180
+ const records = [
181
+ makeSqsRecord(makeTestEvent('1'), 'msg-1'),
182
+ makeSqsRecord(makeTestEvent('2'), 'msg-2'),
183
+ makeSqsRecord(makeTestEvent('3'), 'msg-3'),
184
+ ];
185
+ const { items, stats, recordMap } = await (0, sqs_unbundle_1.unbundleRecords)(records);
186
+ expect(items).toHaveLength(3);
187
+ expect(stats.legacySqsRecords).toBe(3);
188
+ expect(stats.totalItems).toBe(3);
189
+ expect(recordMap.get(items[0])).toBe('msg-1');
190
+ expect(recordMap.get(items[1])).toBe('msg-2');
191
+ expect(recordMap.get(items[2])).toBe('msg-3');
192
+ });
193
+ });
194
+ describe('bundled messages (NONE compression)', () => {
195
+ it('unbundles wrapped messageBody envelope', async () => {
196
+ const items = [makeTestEvent('b1'), makeTestEvent('b2')];
197
+ const envelope = makeBundledEnvelope(items);
198
+ const record = makeSqsRecord({ messageBody: envelope });
199
+ const { items: result, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
200
+ expect(result).toHaveLength(2);
201
+ expect(result[0].eventId).toBe('b1');
202
+ expect(result[1].eventId).toBe('b2');
203
+ expect(stats.bundledSqsRecords).toBe(1);
204
+ expect(stats.legacySqsRecords).toBe(0);
205
+ });
206
+ it('unbundles raw envelope (not wrapped)', async () => {
207
+ const items = [makeTestEvent('r1'), makeTestEvent('r2')];
208
+ const envelope = makeBundledEnvelope(items);
209
+ const record = makeSqsRecord(envelope);
210
+ const { items: result, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
211
+ expect(result).toHaveLength(2);
212
+ expect(stats.bundledSqsRecords).toBe(1);
213
+ });
214
+ it('maps all items from bundle to same messageId', async () => {
215
+ const items = [makeTestEvent('x1'), makeTestEvent('x2'), makeTestEvent('x3')];
216
+ const envelope = makeBundledEnvelope(items);
217
+ const record = makeSqsRecord({ messageBody: envelope }, 'bundle-msg-1');
218
+ const { items: result, recordMap } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
219
+ expect(result).toHaveLength(3);
220
+ expect(recordMap.get(result[0])).toBe('bundle-msg-1');
221
+ expect(recordMap.get(result[1])).toBe('bundle-msg-1');
222
+ expect(recordMap.get(result[2])).toBe('bundle-msg-1');
223
+ });
224
+ });
225
+ describe('bundled messages (ZSTD compression)', () => {
226
+ it('unbundles ZSTD compressed envelope', async () => {
227
+ const items = [makeTestEvent('z1'), makeTestEvent('z2')];
228
+ const envelope = await makeCompressedEnvelope(items);
229
+ const record = makeSqsRecord({ messageBody: envelope });
230
+ const { items: result, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
231
+ expect(result).toHaveLength(2);
232
+ expect(result[0].eventId).toBe('z1');
233
+ expect(result[1].eventId).toBe('z2');
234
+ expect(stats.bundledSqsRecords).toBe(1);
235
+ });
236
+ it('handles large compressed bundles (500 items)', async () => {
237
+ const items = Array.from({ length: 500 }, (_, i) => makeTestEvent(`item-${i}`));
238
+ const envelope = await makeCompressedEnvelope(items);
239
+ const record = makeSqsRecord({ messageBody: envelope });
240
+ const { items: result, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
241
+ expect(result).toHaveLength(500);
242
+ expect(stats.bundledSqsRecords).toBe(1);
243
+ expect(stats.totalItems).toBe(500);
244
+ });
245
+ });
246
+ describe('bundled messages (GZIP compression - CF Worker)', () => {
247
+ it('unbundles GZIP compressed envelope', async () => {
248
+ const items = [makeTestEvent('g1'), makeTestEvent('g2')];
249
+ const envelope = await makeGzipCompressedEnvelope(items);
250
+ const record = makeSqsRecord({ messageBody: envelope });
251
+ const { items: result, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
252
+ expect(result).toHaveLength(2);
253
+ expect(result[0].eventId).toBe('g1');
254
+ expect(result[1].eventId).toBe('g2');
255
+ expect(stats.bundledSqsRecords).toBe(1);
256
+ });
257
+ it('handles large GZIP compressed bundles (500 items)', async () => {
258
+ const items = Array.from({ length: 500 }, (_, i) => makeTestEvent(`item-${i}`));
259
+ const envelope = await makeGzipCompressedEnvelope(items);
260
+ const record = makeSqsRecord({ messageBody: envelope });
261
+ const { items: result, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
262
+ expect(result).toHaveLength(500);
263
+ expect(stats.bundledSqsRecords).toBe(1);
264
+ expect(stats.totalItems).toBe(500);
265
+ });
266
+ it('maps all items from GZIP bundle to same messageId', async () => {
267
+ const items = [makeTestEvent('x1'), makeTestEvent('x2'), makeTestEvent('x3')];
268
+ const envelope = await makeGzipCompressedEnvelope(items);
269
+ const record = makeSqsRecord({ messageBody: envelope }, 'gzip-bundle-msg-1');
270
+ const { items: result, recordMap } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
271
+ expect(result).toHaveLength(3);
272
+ expect(recordMap.get(result[0])).toBe('gzip-bundle-msg-1');
273
+ expect(recordMap.get(result[1])).toBe('gzip-bundle-msg-1');
274
+ expect(recordMap.get(result[2])).toBe('gzip-bundle-msg-1');
275
+ });
276
+ it('unbundles GZIP envelope wrapped in full InternalSQSMessage format (CF Worker format)', async () => {
277
+ const items = [makeTestEvent('cf1'), makeTestEvent('cf2'), makeTestEvent('cf3')];
278
+ const envelope = await makeGzipCompressedEnvelope(items);
279
+ const internalSqsMessage = {
280
+ messageId: 'cf-internal-msg-123',
281
+ messageType: 'TRACKING_EVENT_COLLECT',
282
+ messageTime: '2026-05-29T11:00:10.076Z',
283
+ messageBody: envelope,
284
+ };
285
+ const record = makeSqsRecord(internalSqsMessage, 'sqs-msg-id');
286
+ const { items: result, stats, recordMap } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
287
+ expect(result).toHaveLength(3);
288
+ expect(result[0].eventId).toBe('cf1');
289
+ expect(result[1].eventId).toBe('cf2');
290
+ expect(result[2].eventId).toBe('cf3');
291
+ expect(stats.bundledSqsRecords).toBe(1);
292
+ expect(stats.legacySqsRecords).toBe(0);
293
+ expect(stats.totalItems).toBe(3);
294
+ expect(recordMap.get(result[0])).toBe('sqs-msg-id');
295
+ expect(recordMap.get(result[1])).toBe('sqs-msg-id');
296
+ expect(recordMap.get(result[2])).toBe('sqs-msg-id');
297
+ });
298
+ it('unbundles GZIP envelope with extra wrapper fields (messageTime, messageType)', async () => {
299
+ const items = [makeTestEvent('w1')];
300
+ const envelope = await makeGzipCompressedEnvelope(items);
301
+ const wrappedMessage = {
302
+ messageTime: new Date().toISOString(),
303
+ messageType: 'SESSION_EVENT',
304
+ messageBody: envelope,
305
+ someOtherField: 'ignored',
306
+ };
307
+ const record = makeSqsRecord(wrappedMessage);
308
+ const { items: result, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
309
+ expect(result).toHaveLength(1);
310
+ expect(result[0].eventId).toBe('w1');
311
+ expect(stats.bundledSqsRecords).toBe(1);
312
+ });
313
+ it('handles decompression failure gracefully (does not return wrapper)', async () => {
314
+ const corruptedEnvelope = {
315
+ v: 1,
316
+ c: 'gzip',
317
+ n: 1,
318
+ s: 100,
319
+ p: 'H4sIAAAAAAAAA+TRUNCATED',
320
+ };
321
+ const internalSqsMessage = {
322
+ messageTime: '2026-05-29T11:00:10.076Z',
323
+ messageType: 'TRACKING_EVENT_COLLECT',
324
+ messageBody: corruptedEnvelope,
325
+ messageId: 'corrupted-msg-id',
326
+ };
327
+ const record = makeSqsRecord(internalSqsMessage, 'sqs-record-id');
328
+ const { items, stats, failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
329
+ expect(failedMessageIds).toHaveLength(1);
330
+ expect(stats.failedRecords).toBe(1);
331
+ expect(items).toHaveLength(0);
332
+ items.forEach((item) => {
333
+ expect(item).not.toHaveProperty('messageTime');
334
+ expect(item).not.toHaveProperty('messageType');
335
+ expect(item).not.toHaveProperty('messageBody');
336
+ });
337
+ });
338
+ it('verifies decompression failure does NOT leak wrapper into items', async () => {
339
+ const badEnvelope = {
340
+ v: 1,
341
+ c: 'gzip',
342
+ n: 5,
343
+ s: 1000,
344
+ p: 'dGhpcyBpcyBub3QgdmFsaWQgZ3ppcA==',
345
+ };
346
+ const wrapper = {
347
+ messageTime: new Date().toISOString(),
348
+ messageType: 'TRACKING_EVENT_COLLECT',
349
+ messageBody: badEnvelope,
350
+ messageId: 'test-internal-msg-id',
351
+ };
352
+ const record = makeSqsRecord(wrapper, 'sqs-msg-id');
353
+ const { items, stats, failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
354
+ expect(failedMessageIds).toContain('sqs-msg-id');
355
+ expect(stats.failedRecords).toBe(1);
356
+ expect(items).toHaveLength(0);
357
+ expect(stats.totalItems).toBe(0);
358
+ });
359
+ it('simulates old version without GZIP - returns envelope as item (BUG)', async () => {
360
+ const items = [{ pixelId: 'px123', trackingEvent: { name: 'page_view' } }];
361
+ const envelope = await makeGzipCompressedEnvelope(items);
362
+ const wrapper = {
363
+ messageTime: new Date().toISOString(),
364
+ messageType: 'TRACKING_EVENT_COLLECT',
365
+ messageBody: envelope,
366
+ messageId: 'cf-msg-id',
367
+ };
368
+ expect((0, sqs_unbundle_1.isBundledEnvelope)(envelope)).toBe(true);
369
+ const record = makeSqsRecord(wrapper, 'sqs-msg-id');
370
+ const { items: result, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
371
+ expect(stats.bundledSqsRecords).toBe(1);
372
+ expect(result).toHaveLength(1);
373
+ expect(result[0].pixelId).toBe('px123');
374
+ expect(result[0]).not.toHaveProperty('v');
375
+ expect(result[0]).not.toHaveProperty('c');
376
+ expect(result[0]).not.toHaveProperty('n');
377
+ expect(result[0]).not.toHaveProperty('p');
378
+ expect(result[0]).not.toHaveProperty('s');
379
+ });
380
+ it('unknown compression algorithm causes legacy fallback (returns messageBody)', async () => {
381
+ const unknownEnvelope = {
382
+ v: 1,
383
+ c: 'lz4',
384
+ n: 1,
385
+ s: 100,
386
+ p: 'some-base64-data',
387
+ };
388
+ const wrapper = {
389
+ messageTime: new Date().toISOString(),
390
+ messageType: 'TEST_MESSAGE',
391
+ messageBody: unknownEnvelope,
392
+ messageId: 'wrapper-msg-id',
393
+ };
394
+ expect((0, sqs_unbundle_1.isBundledEnvelope)(unknownEnvelope)).toBe(false);
395
+ const record = makeSqsRecord(wrapper, 'sqs-msg-id');
396
+ const { items, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
397
+ expect(stats.legacySqsRecords).toBe(1);
398
+ expect(stats.bundledSqsRecords).toBe(0);
399
+ expect(items).toHaveLength(1);
400
+ expect(items[0]).toHaveProperty('v', 1);
401
+ expect(items[0]).toHaveProperty('c', 'lz4');
402
+ expect(items[0]).toHaveProperty('n', 1);
403
+ expect(items[0]).toHaveProperty('p');
404
+ });
405
+ });
406
+ describe('mixed message formats', () => {
407
+ it('handles mix of legacy and bundled messages', async () => {
408
+ const legacyEvent = makeTestEvent('legacy-1');
409
+ const bundledItems = [makeTestEvent('bundled-1'), makeTestEvent('bundled-2')];
410
+ const bundleEnvelope = makeBundledEnvelope(bundledItems);
411
+ const records = [
412
+ makeSqsRecord(legacyEvent, 'msg-legacy'),
413
+ makeSqsRecord({ messageBody: bundleEnvelope }, 'msg-bundle'),
414
+ ];
415
+ const { items, stats, recordMap } = await (0, sqs_unbundle_1.unbundleRecords)(records);
416
+ expect(items).toHaveLength(3);
417
+ expect(stats.legacySqsRecords).toBe(1);
418
+ expect(stats.bundledSqsRecords).toBe(1);
419
+ expect(stats.totalItems).toBe(3);
420
+ expect(recordMap.get(items[0])).toBe('msg-legacy');
421
+ expect(recordMap.get(items[1])).toBe('msg-bundle');
422
+ expect(recordMap.get(items[2])).toBe('msg-bundle');
423
+ });
424
+ it('handles mix of NONE and ZSTD compressed bundles', async () => {
425
+ const noneItems = [makeTestEvent('none-1')];
426
+ const zstdItems = [makeTestEvent('zstd-1'), makeTestEvent('zstd-2')];
427
+ const noneEnvelope = makeBundledEnvelope(noneItems);
428
+ const zstdEnvelope = await makeCompressedEnvelope(zstdItems);
429
+ const records = [
430
+ makeSqsRecord({ messageBody: noneEnvelope }, 'msg-none'),
431
+ makeSqsRecord({ messageBody: zstdEnvelope }, 'msg-zstd'),
432
+ ];
433
+ const { items, stats } = await (0, sqs_unbundle_1.unbundleRecords)(records);
434
+ expect(items).toHaveLength(3);
435
+ expect(stats.bundledSqsRecords).toBe(2);
436
+ expect(stats.totalItems).toBe(3);
437
+ });
438
+ it('handles mix of ZSTD, GZIP, and legacy messages', async () => {
439
+ const legacyEvent = makeTestEvent('legacy-1');
440
+ const zstdItems = [makeTestEvent('zstd-1'), makeTestEvent('zstd-2')];
441
+ const gzipItems = [makeTestEvent('gzip-1'), makeTestEvent('gzip-2'), makeTestEvent('gzip-3')];
442
+ const zstdEnvelope = await makeCompressedEnvelope(zstdItems);
443
+ const gzipEnvelope = await makeGzipCompressedEnvelope(gzipItems);
444
+ const records = [
445
+ makeSqsRecord(legacyEvent, 'msg-legacy'),
446
+ makeSqsRecord({ messageBody: zstdEnvelope }, 'msg-zstd'),
447
+ makeSqsRecord({ messageBody: gzipEnvelope }, 'msg-gzip'),
448
+ ];
449
+ const { items, stats, recordMap } = await (0, sqs_unbundle_1.unbundleRecords)(records);
450
+ expect(items).toHaveLength(6);
451
+ expect(stats.legacySqsRecords).toBe(1);
452
+ expect(stats.bundledSqsRecords).toBe(2);
453
+ expect(stats.totalItems).toBe(6);
454
+ expect(recordMap.get(items[0])).toBe('msg-legacy');
455
+ expect(recordMap.get(items[1])).toBe('msg-zstd');
456
+ expect(recordMap.get(items[2])).toBe('msg-zstd');
457
+ expect(recordMap.get(items[3])).toBe('msg-gzip');
458
+ expect(recordMap.get(items[4])).toBe('msg-gzip');
459
+ expect(recordMap.get(items[5])).toBe('msg-gzip');
460
+ });
461
+ });
462
+ describe('error handling', () => {
463
+ it('returns failed messageId for invalid JSON', async () => {
464
+ const records = [makeSqsRecord('not-valid-json{{{', 'msg-bad'), makeSqsRecord(makeTestEvent('good'), 'msg-good')];
465
+ const { items, failedMessageIds, stats } = await (0, sqs_unbundle_1.unbundleRecords)(records);
466
+ expect(items).toHaveLength(1);
467
+ expect(items[0].eventId).toBe('good');
468
+ expect(failedMessageIds).toEqual(['msg-bad']);
469
+ expect(stats.failedRecords).toBe(1);
470
+ expect(stats.legacySqsRecords).toBe(1);
471
+ });
472
+ it('returns failed messageId for corrupt ZSTD compressed data', async () => {
473
+ const corruptEnvelope = {
474
+ v: 1,
475
+ c: sqs_bundled_client_types_1.CompressionAlgorithm.ZSTD,
476
+ n: 10,
477
+ p: 'dGhpcyBpcyBub3QgdmFsaWQgenN0ZA==',
478
+ };
479
+ const records = [
480
+ makeSqsRecord({ messageBody: corruptEnvelope }, 'msg-corrupt'),
481
+ makeSqsRecord(makeTestEvent('valid'), 'msg-valid'),
482
+ ];
483
+ const { items, failedMessageIds, stats } = await (0, sqs_unbundle_1.unbundleRecords)(records);
484
+ expect(items).toHaveLength(1);
485
+ expect(items[0].eventId).toBe('valid');
486
+ expect(failedMessageIds).toContain('msg-corrupt');
487
+ expect(stats.failedRecords).toBe(1);
488
+ });
489
+ it('returns failed messageId for corrupt GZIP compressed data', async () => {
490
+ const corruptEnvelope = {
491
+ v: 1,
492
+ c: sqs_bundled_client_types_1.CompressionAlgorithm.GZIP,
493
+ n: 10,
494
+ p: 'dGhpcyBpcyBub3QgdmFsaWQgZ3ppcA==',
495
+ };
496
+ const records = [
497
+ makeSqsRecord({ messageBody: corruptEnvelope }, 'msg-corrupt-gzip'),
498
+ makeSqsRecord(makeTestEvent('valid'), 'msg-valid'),
499
+ ];
500
+ const { items, failedMessageIds, stats } = await (0, sqs_unbundle_1.unbundleRecords)(records);
501
+ expect(items).toHaveLength(1);
502
+ expect(items[0].eventId).toBe('valid');
503
+ expect(failedMessageIds).toContain('msg-corrupt-gzip');
504
+ expect(stats.failedRecords).toBe(1);
505
+ });
506
+ it('returns empty result for all failed records', async () => {
507
+ const records = [makeSqsRecord('invalid-1', 'msg-1'), makeSqsRecord('invalid-2', 'msg-2')];
508
+ const { items, failedMessageIds, stats } = await (0, sqs_unbundle_1.unbundleRecords)(records);
509
+ expect(items).toHaveLength(0);
510
+ expect(failedMessageIds).toEqual(['msg-1', 'msg-2']);
511
+ expect(stats.failedRecords).toBe(2);
512
+ expect(stats.totalItems).toBe(0);
513
+ });
514
+ it('handles empty records array', async () => {
515
+ const { items, failedMessageIds, stats } = await (0, sqs_unbundle_1.unbundleRecords)([]);
516
+ expect(items).toHaveLength(0);
517
+ expect(failedMessageIds).toHaveLength(0);
518
+ expect(stats.totalRecords).toBe(0);
519
+ expect(stats.totalItems).toBe(0);
520
+ });
521
+ it('rejects ZSTD payload exceeding max decompressed size', async () => {
522
+ const largeItems = Array.from({ length: 1000 }, (_, i) => ({
523
+ id: `item-${i}`,
524
+ data: 'x'.repeat(1000),
525
+ }));
526
+ const envelope = await makeCompressedEnvelope(largeItems);
527
+ const record = makeSqsRecord({ messageBody: envelope }, 'msg-too-large');
528
+ const { failedMessageIds, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record], {
529
+ maxDecompressedSizeBytes: 100 * 1024,
530
+ });
531
+ expect(failedMessageIds).toContain('msg-too-large');
532
+ expect(stats.failedRecords).toBe(1);
533
+ });
534
+ it('rejects GZIP payload exceeding max decompressed size (post-decompress check)', async () => {
535
+ const largeItems = Array.from({ length: 1000 }, (_, i) => ({
536
+ id: `item-${i}`,
537
+ data: 'x'.repeat(1000),
538
+ }));
539
+ const envelope = await makeGzipCompressedEnvelope(largeItems);
540
+ const record = makeSqsRecord({ messageBody: envelope }, 'msg-too-large-gzip');
541
+ const { failedMessageIds, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record], {
542
+ maxDecompressedSizeBytes: 100 * 1024,
543
+ });
544
+ expect(failedMessageIds).toContain('msg-too-large-gzip');
545
+ expect(stats.failedRecords).toBe(1);
546
+ });
547
+ it('fails on invalid base64 in compressed payload', async () => {
548
+ const badEnvelope = {
549
+ v: 1,
550
+ c: sqs_bundled_client_types_1.CompressionAlgorithm.ZSTD,
551
+ n: 1,
552
+ s: 100,
553
+ p: '!!!not-valid-base64!!!',
554
+ };
555
+ const record = makeSqsRecord({ messageBody: badEnvelope }, 'msg-bad-base64');
556
+ const { failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
557
+ expect(failedMessageIds).toContain('msg-bad-base64');
558
+ });
559
+ });
560
+ describe('bundle format edge cases', () => {
561
+ it('successfully unbundles even when n does not match actual item count', async () => {
562
+ const envelope = {
563
+ v: 1,
564
+ c: sqs_bundled_client_types_1.CompressionAlgorithm.NONE,
565
+ n: 5,
566
+ s: 50,
567
+ p: [makeTestEvent('1'), makeTestEvent('2')],
568
+ };
569
+ const record = makeSqsRecord({ messageBody: envelope });
570
+ const { items } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
571
+ expect(items).toHaveLength(2);
572
+ });
573
+ it('treats envelope with unsupported version as legacy message', async () => {
574
+ const badEnvelope = { v: 99, c: 'none', n: 1, s: 10, p: [] };
575
+ const record = makeSqsRecord({ messageBody: badEnvelope }, 'msg-bad-version');
576
+ const { items, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
577
+ expect(items).toHaveLength(1);
578
+ expect(items[0].v).toBe(99);
579
+ expect(stats.legacySqsRecords).toBe(1);
580
+ expect(stats.bundledSqsRecords).toBe(0);
581
+ });
582
+ });
583
+ describe('timeout protection', () => {
584
+ it('respects timeoutMs option for ZSTD', async () => {
585
+ const items = [makeTestEvent('1')];
586
+ const envelope = await makeCompressedEnvelope(items);
587
+ const record = makeSqsRecord({ messageBody: envelope });
588
+ const { items: result, failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record], {
589
+ timeoutMs: 5000,
590
+ });
591
+ expect(result).toHaveLength(1);
592
+ expect(failedMessageIds).toHaveLength(0);
593
+ });
594
+ it('respects timeoutMs option for GZIP', async () => {
595
+ const items = [makeTestEvent('1')];
596
+ const envelope = await makeGzipCompressedEnvelope(items);
597
+ const record = makeSqsRecord({ messageBody: envelope });
598
+ const { items: result, failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record], {
599
+ timeoutMs: 5000,
600
+ });
601
+ expect(result).toHaveLength(1);
602
+ expect(failedMessageIds).toHaveLength(0);
603
+ });
604
+ it('passes timeoutMs through to decompression', async () => {
605
+ const items = [makeTestEvent('1')];
606
+ const envelope = await makeCompressedEnvelope(items);
607
+ const record = makeSqsRecord({ messageBody: envelope });
608
+ await expect((0, sqs_unbundle_1.unbundleRecords)([record], { timeoutMs: 30000 })).resolves.toBeDefined();
609
+ });
610
+ it('CompressionTimeoutError is properly exported', () => {
611
+ const error = new compression_1.CompressionTimeoutError(compression_1.CompressionOperation.DECOMPRESS, 5000);
612
+ expect(error).toBeInstanceOf(Error);
613
+ expect(error.name).toBe('CompressionTimeoutError');
614
+ expect(error.operation).toBe('decompress');
615
+ expect(error.timeoutMs).toBe(5000);
616
+ expect(error.message).toContain('timed out after 5000ms');
617
+ });
618
+ it('includes timeout info in failure logging for ZSTD', async () => {
619
+ const corruptEnvelope = {
620
+ v: 1,
621
+ c: sqs_bundled_client_types_1.CompressionAlgorithm.ZSTD,
622
+ n: 1,
623
+ s: 100,
624
+ p: 'dGhpcyBpcyBub3QgdmFsaWQgenN0ZA==',
625
+ };
626
+ const record = makeSqsRecord({ messageBody: corruptEnvelope }, 'msg-timeout-test');
627
+ const { failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record], {
628
+ timeoutMs: 5000,
629
+ });
630
+ expect(failedMessageIds).toContain('msg-timeout-test');
631
+ });
632
+ it('includes timeout info in failure logging for GZIP', async () => {
633
+ const corruptEnvelope = {
634
+ v: 1,
635
+ c: sqs_bundled_client_types_1.CompressionAlgorithm.GZIP,
636
+ n: 1,
637
+ s: 100,
638
+ p: 'dGhpcyBpcyBub3QgdmFsaWQgZ3ppcA==',
639
+ };
640
+ const record = makeSqsRecord({ messageBody: corruptEnvelope }, 'msg-gzip-timeout-test');
641
+ const { failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record], {
642
+ timeoutMs: 5000,
643
+ });
644
+ expect(failedMessageIds).toContain('msg-gzip-timeout-test');
645
+ });
646
+ });
647
+ describe('end-to-end: producer bundle → consumer unbundle', () => {
648
+ it('simulates full bundle → unbundle flow with compression', async () => {
649
+ const producerItems = Array.from({ length: 100 }, (_, i) => makeTestEvent(`event-${i}`, 'page_view', i * 1.5));
650
+ const envelope = await makeCompressedEnvelope(producerItems, 3);
651
+ const sqsMessageBody = { messageBody: envelope };
652
+ const consumerRecord = makeSqsRecord(sqsMessageBody, 'sqs-message-id-123');
653
+ const { items, recordMap, stats } = await (0, sqs_unbundle_1.unbundleRecords)([consumerRecord]);
654
+ expect(items).toHaveLength(100);
655
+ expect(items[0]).toEqual({ eventId: 'event-0', name: 'page_view', value: 0 });
656
+ expect(items[99]).toEqual({ eventId: 'event-99', name: 'page_view', value: 148.5 });
657
+ expect(stats.bundledSqsRecords).toBe(1);
658
+ expect(stats.legacySqsRecords).toBe(0);
659
+ expect(stats.totalItems).toBe(100);
660
+ for (const item of items) {
661
+ expect(recordMap.get(item)).toBe('sqs-message-id-123');
662
+ }
663
+ });
664
+ it('simulates mixed batch with legacy and bundled messages', async () => {
665
+ const legacyRecord1 = makeSqsRecord({ messageBody: makeTestEvent('legacy-1', 'purchase', 99.99) }, 'legacy-msg-1');
666
+ const legacyRecord2 = makeSqsRecord(makeTestEvent('legacy-2', 'add_to_cart', 29.99), 'legacy-msg-2');
667
+ const bundledItems = [
668
+ makeTestEvent('bundled-1', 'page_view'),
669
+ makeTestEvent('bundled-2', 'session_start'),
670
+ makeTestEvent('bundled-3', 'page_end'),
671
+ ];
672
+ const envelope = await makeCompressedEnvelope(bundledItems);
673
+ const bundledRecord = makeSqsRecord({ messageBody: envelope }, 'bundled-msg-1');
674
+ const records = [legacyRecord1, bundledRecord, legacyRecord2];
675
+ const { items, stats, recordMap } = await (0, sqs_unbundle_1.unbundleRecords)(records);
676
+ expect(items).toHaveLength(5);
677
+ expect(stats.legacySqsRecords).toBe(2);
678
+ expect(stats.bundledSqsRecords).toBe(1);
679
+ expect(stats.totalItems).toBe(5);
680
+ expect(recordMap.get(items[0])).toBe('legacy-msg-1');
681
+ expect(recordMap.get(items[1])).toBe('bundled-msg-1');
682
+ expect(recordMap.get(items[2])).toBe('bundled-msg-1');
683
+ expect(recordMap.get(items[3])).toBe('bundled-msg-1');
684
+ expect(recordMap.get(items[4])).toBe('legacy-msg-2');
685
+ });
686
+ });
687
+ describe('consumer edge cases - real sequences', () => {
688
+ it('handles 500 items in single compressed bundle with exact verification', async () => {
689
+ const originalItems = Array.from({ length: 500 }, (_, i) => ({
690
+ id: `item-${i}`,
691
+ name: `Event ${i}`,
692
+ data: { nested: { value: i * 1.5 } },
693
+ timestamp: Date.now() + i,
694
+ }));
695
+ const envelope = await makeCompressedEnvelope(originalItems);
696
+ const record = makeSqsRecord({ messageBody: envelope }, 'msg-500-items');
697
+ const { items, recordMap, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
698
+ expect(items).toHaveLength(500);
699
+ expect(items[0]).toEqual(originalItems[0]);
700
+ expect(items[249]).toEqual(originalItems[249]);
701
+ expect(items[499]).toEqual(originalItems[499]);
702
+ expect(stats.bundledSqsRecords).toBe(1);
703
+ expect(stats.totalItems).toBe(500);
704
+ for (let i = 0; i < 500; i++) {
705
+ expect(items[i].id).toBe(`item-${i}`);
706
+ expect(items[i].data.nested.value).toBe(i * 1.5);
707
+ }
708
+ for (const item of items) {
709
+ expect(recordMap.get(item)).toBe('msg-500-items');
710
+ }
711
+ });
712
+ it('handles multiple bundles in single batch with different item counts', async () => {
713
+ const counts = [10, 50, 100, 200, 300];
714
+ const records = [];
715
+ const expectedTotal = counts.reduce((sum, c) => sum + c, 0);
716
+ for (let bundleIdx = 0; bundleIdx < counts.length; bundleIdx++) {
717
+ const count = counts[bundleIdx];
718
+ const items = Array.from({ length: count }, (_, i) => ({
719
+ bundleId: bundleIdx,
720
+ itemIdx: i,
721
+ payload: `bundle-${bundleIdx}-item-${i}`,
722
+ }));
723
+ const envelope = await makeCompressedEnvelope(items);
724
+ records.push(makeSqsRecord({ messageBody: envelope }, `msg-bundle-${bundleIdx}`));
725
+ }
726
+ const { items, recordMap, stats } = await (0, sqs_unbundle_1.unbundleRecords)(records);
727
+ expect(items).toHaveLength(expectedTotal);
728
+ expect(stats.totalItems).toBe(expectedTotal);
729
+ expect(stats.bundledSqsRecords).toBe(5);
730
+ expect(stats.legacySqsRecords).toBe(0);
731
+ expect(stats.failedRecords).toBe(0);
732
+ const itemsByBundle = new Map();
733
+ for (const item of items) {
734
+ const expectedMsgId = `msg-bundle-${item.bundleId}`;
735
+ expect(recordMap.get(item)).toBe(expectedMsgId);
736
+ itemsByBundle.set(item.bundleId, (itemsByBundle.get(item.bundleId) ?? 0) + 1);
737
+ }
738
+ expect(itemsByBundle.get(0)).toBe(10);
739
+ expect(itemsByBundle.get(1)).toBe(50);
740
+ expect(itemsByBundle.get(2)).toBe(100);
741
+ expect(itemsByBundle.get(3)).toBe(200);
742
+ expect(itemsByBundle.get(4)).toBe(300);
743
+ });
744
+ it('handles realistic 80% bundled / 20% legacy migration ratio', async () => {
745
+ const records = [];
746
+ const totalMessages = 100;
747
+ const bundledCount = 80;
748
+ const legacyCount = 20;
749
+ const itemsPerBundle = 25;
750
+ for (let i = 0; i < bundledCount; i++) {
751
+ const items = Array.from({ length: itemsPerBundle }, (_, j) => ({
752
+ type: 'bundled',
753
+ bundleIdx: i,
754
+ itemIdx: j,
755
+ }));
756
+ const envelope = await makeCompressedEnvelope(items);
757
+ records.push(makeSqsRecord({ messageBody: envelope }, `msg-bundled-${i}`));
758
+ }
759
+ for (let i = 0; i < legacyCount; i++) {
760
+ const legacyEvent = {
761
+ type: 'legacy',
762
+ legacyIdx: i,
763
+ itemIdx: 0,
764
+ };
765
+ records.push(makeSqsRecord(legacyEvent, `msg-legacy-${i}`));
766
+ }
767
+ const shuffled = [...records].sort(() => Math.random() - 0.5);
768
+ const { items, stats, recordMap } = await (0, sqs_unbundle_1.unbundleRecords)(shuffled);
769
+ expect(stats.totalRecords).toBe(totalMessages);
770
+ expect(stats.bundledSqsRecords).toBe(bundledCount);
771
+ expect(stats.legacySqsRecords).toBe(legacyCount);
772
+ expect(stats.failedRecords).toBe(0);
773
+ expect(items).toHaveLength(bundledCount * itemsPerBundle + legacyCount);
774
+ expect(stats.totalItems).toBe(2020);
775
+ const bundledItems = items.filter((i) => i.type === 'bundled');
776
+ const legacyItems = items.filter((i) => i.type === 'legacy');
777
+ expect(bundledItems).toHaveLength(bundledCount * itemsPerBundle);
778
+ expect(legacyItems).toHaveLength(legacyCount);
779
+ for (const item of bundledItems) {
780
+ expect(recordMap.get(item)).toBe(`msg-bundled-${item.bundleIdx}`);
781
+ }
782
+ for (const item of legacyItems) {
783
+ expect(recordMap.get(item)).toBe(`msg-legacy-${item.legacyIdx}`);
784
+ }
785
+ });
786
+ it('preserves Unicode characters through ZSTD compression', async () => {
787
+ const unicodeItems = [
788
+ {
789
+ id: 'emoji',
790
+ content: '🎉🚀💻🌍✨ Party time!',
791
+ description: 'Emojis should survive compression',
792
+ },
793
+ {
794
+ id: 'chinese',
795
+ content: '中文测试:欢迎使用我们的服务',
796
+ description: 'Chinese characters',
797
+ },
798
+ {
799
+ id: 'arabic',
800
+ content: 'العربية: مرحبا بكم في خدمتنا',
801
+ description: 'Arabic text (RTL)',
802
+ },
803
+ {
804
+ id: 'math',
805
+ content: '∑∫∂∇ε → ∞ × ∈ ⊆ ∀∃',
806
+ description: 'Mathematical symbols',
807
+ },
808
+ {
809
+ id: 'mixed',
810
+ content: 'Hello 世界 🌏 مرحبا ∑ Привет',
811
+ description: 'Mixed scripts',
812
+ },
813
+ {
814
+ id: 'special',
815
+ content: '️️️️️️️️️️️️️️️️',
816
+ description: 'Unicode edge cases',
817
+ },
818
+ {
819
+ id: 'japanese',
820
+ content: 'こんにちは世界 ひらがな カタカナ 漢字',
821
+ description: 'Japanese hiragana, katakana, kanji',
822
+ },
823
+ {
824
+ id: 'korean',
825
+ content: '안녕하세요 세계',
826
+ description: 'Korean hangul',
827
+ },
828
+ {
829
+ id: 'cyrillic',
830
+ content: 'Привет мир! Это тест.',
831
+ description: 'Russian Cyrillic',
832
+ },
833
+ {
834
+ id: 'combining',
835
+ content: 'e\u0301 n\u0303 c\u0327',
836
+ description: 'Combining diacritical marks',
837
+ },
838
+ ];
839
+ const envelope = await makeCompressedEnvelope(unicodeItems);
840
+ const record = makeSqsRecord({ messageBody: envelope }, 'msg-unicode');
841
+ const { items } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
842
+ expect(items).toHaveLength(unicodeItems.length);
843
+ for (let i = 0; i < unicodeItems.length; i++) {
844
+ expect(items[i].id).toBe(unicodeItems[i].id);
845
+ expect(items[i].content).toBe(unicodeItems[i].content);
846
+ expect(items[i].description).toBe(unicodeItems[i].description);
847
+ const originalBytes = Buffer.from(unicodeItems[i].content, 'utf8');
848
+ const recoveredBytes = Buffer.from(items[i].content, 'utf8');
849
+ expect(recoveredBytes.equals(originalBytes)).toBe(true);
850
+ }
851
+ });
852
+ it('preserves deeply nested structures (10 levels)', async () => {
853
+ function createDeepStructure(depth, maxDepth = 10) {
854
+ const node = {
855
+ level: depth,
856
+ data: `level-${depth}-data-${Math.random().toString(36).slice(2)}`,
857
+ };
858
+ if (depth < maxDepth) {
859
+ node.child = createDeepStructure(depth + 1, maxDepth);
860
+ node.siblings = [
861
+ createDeepStructure(depth + 1, Math.min(depth + 3, maxDepth)),
862
+ createDeepStructure(depth + 1, Math.min(depth + 2, maxDepth)),
863
+ ];
864
+ }
865
+ return node;
866
+ }
867
+ const deepItems = Array.from({ length: 10 }, (_, i) => ({
868
+ id: `deep-${i}`,
869
+ nested: createDeepStructure(0, 10),
870
+ arrays: [[[[[['deeply', 'nested', 'array']]], [{ inner: { value: i } }]]]],
871
+ }));
872
+ const envelope = await makeCompressedEnvelope(deepItems);
873
+ const record = makeSqsRecord({ messageBody: envelope }, 'msg-deep');
874
+ const { items } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
875
+ expect(items).toHaveLength(10);
876
+ for (let i = 0; i < 10; i++) {
877
+ expect(JSON.stringify(items[i])).toBe(JSON.stringify(deepItems[i]));
878
+ expect(items[i].nested.level).toBe(0);
879
+ expect(items[i].nested.child?.level).toBe(1);
880
+ expect(items[i].nested.child?.child?.child?.level).toBe(3);
881
+ expect(items[i].arrays[0][0][0][0][0][0]).toBe('deeply');
882
+ }
883
+ });
884
+ it('handles binary-like string data (base64 encoded)', async () => {
885
+ const binaryItems = [
886
+ {
887
+ id: 'small-binary',
888
+ base64Data: Buffer.from('Small binary payload').toString('base64'),
889
+ size: 'small',
890
+ },
891
+ {
892
+ id: 'medium-binary',
893
+ base64Data: Buffer.from('x'.repeat(1000)).toString('base64'),
894
+ size: 'medium',
895
+ },
896
+ {
897
+ id: 'image-simulation',
898
+ base64Data: Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0xff, 0xd8, 0xff, 0xe0]).toString('base64'),
899
+ size: 'binary-header',
900
+ },
901
+ {
902
+ id: 'random-bytes',
903
+ base64Data: Buffer.from(Array.from({ length: 256 }, () => Math.floor(Math.random() * 256))).toString('base64'),
904
+ size: 'random',
905
+ },
906
+ {
907
+ id: 'all-byte-values',
908
+ base64Data: Buffer.from(Array.from({ length: 256 }, (_, i) => i)).toString('base64'),
909
+ size: 'full-range',
910
+ },
911
+ ];
912
+ const envelope = await makeCompressedEnvelope(binaryItems);
913
+ const record = makeSqsRecord({ messageBody: envelope }, 'msg-binary');
914
+ const { items } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
915
+ expect(items).toHaveLength(binaryItems.length);
916
+ for (let i = 0; i < binaryItems.length; i++) {
917
+ expect(items[i].id).toBe(binaryItems[i].id);
918
+ expect(items[i].base64Data).toBe(binaryItems[i].base64Data);
919
+ const originalDecoded = Buffer.from(binaryItems[i].base64Data, 'base64');
920
+ const recoveredDecoded = Buffer.from(items[i].base64Data, 'base64');
921
+ expect(recoveredDecoded.equals(originalDecoded)).toBe(true);
922
+ }
923
+ });
924
+ it('handles concurrent unbundling stress test (100 records in parallel)', async () => {
925
+ const recordCount = 100;
926
+ const records = [];
927
+ for (let i = 0; i < recordCount; i++) {
928
+ const itemsInBundle = 10 + (i % 20);
929
+ const items = Array.from({ length: itemsInBundle }, (_, j) => ({
930
+ recordIdx: i,
931
+ itemIdx: j,
932
+ uniqueId: `r${i}-i${j}`,
933
+ timestamp: Date.now() + i * 1000 + j,
934
+ data: `payload-${i}-${j}-${Math.random().toString(36).slice(2)}`,
935
+ }));
936
+ const envelope = await makeCompressedEnvelope(items);
937
+ records.push(makeSqsRecord({ messageBody: envelope }, `stress-msg-${i}`));
938
+ }
939
+ const startTime = Date.now();
940
+ const { items, recordMap, stats, failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)(records);
941
+ const duration = Date.now() - startTime;
942
+ expect(failedMessageIds).toHaveLength(0);
943
+ expect(stats.failedRecords).toBe(0);
944
+ expect(stats.bundledSqsRecords).toBe(recordCount);
945
+ const seenUniqueIds = new Set();
946
+ for (const item of items) {
947
+ expect(seenUniqueIds.has(item.uniqueId)).toBe(false);
948
+ seenUniqueIds.add(item.uniqueId);
949
+ const expectedMsgId = `stress-msg-${item.recordIdx}`;
950
+ expect(recordMap.get(item)).toBe(expectedMsgId);
951
+ expect(item.itemIdx).toBeGreaterThanOrEqual(0);
952
+ expect(item.itemIdx).toBeLessThan(30);
953
+ }
954
+ const expectedTotal = Array.from({ length: recordCount }, (_, i) => 10 + (i % 20)).reduce((a, b) => a + b, 0);
955
+ expect(items).toHaveLength(expectedTotal);
956
+ expect(stats.totalItems).toBe(expectedTotal);
957
+ console.log(`Unbundled ${recordCount} records (${stats.totalItems} items) in ${duration}ms`);
958
+ });
959
+ it('handles items with all JSON primitive types', async () => {
960
+ const primitiveItems = [
961
+ {
962
+ id: 'primitives',
963
+ stringVal: 'hello',
964
+ numberVal: 42,
965
+ floatVal: 3.14159265359,
966
+ boolTrue: true,
967
+ boolFalse: false,
968
+ nullVal: null,
969
+ emptyString: '',
970
+ zero: 0,
971
+ negativeNum: -999,
972
+ largeNum: Number.MAX_SAFE_INTEGER,
973
+ smallNum: Number.MIN_SAFE_INTEGER,
974
+ scientificNotation: 1.23e10,
975
+ },
976
+ {
977
+ id: 'arrays',
978
+ emptyArray: [],
979
+ numberArray: [1, 2, 3],
980
+ mixedArray: [1, 'two', true, null],
981
+ nestedArray: [[1, 2], [3, 4], [[5]]],
982
+ },
983
+ {
984
+ id: 'objects',
985
+ emptyObject: {},
986
+ nestedObject: { a: { b: { c: 1 } } },
987
+ arrayOfObjects: [{ x: 1 }, { y: 2 }],
988
+ },
989
+ ];
990
+ const envelope = await makeCompressedEnvelope(primitiveItems);
991
+ const record = makeSqsRecord({ messageBody: envelope }, 'msg-primitives');
992
+ const { items } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
993
+ expect(items).toHaveLength(3);
994
+ expect(JSON.stringify(items)).toBe(JSON.stringify(primitiveItems));
995
+ });
996
+ it('maintains item order across multiple bundles', async () => {
997
+ const records = [];
998
+ let globalIdx = 0;
999
+ for (let bundleIdx = 0; bundleIdx < 10; bundleIdx++) {
1000
+ const itemCount = 10 + bundleIdx * 5;
1001
+ const items = Array.from({ length: itemCount }, () => ({
1002
+ globalOrder: globalIdx++,
1003
+ bundle: bundleIdx,
1004
+ }));
1005
+ const envelope = await makeCompressedEnvelope(items);
1006
+ records.push(makeSqsRecord({ messageBody: envelope }, `order-msg-${bundleIdx}`));
1007
+ }
1008
+ const { items } = await (0, sqs_unbundle_1.unbundleRecords)(records);
1009
+ const bundleGroups = new Map();
1010
+ for (const item of items) {
1011
+ if (!bundleGroups.has(item.bundle)) {
1012
+ bundleGroups.set(item.bundle, []);
1013
+ }
1014
+ bundleGroups.get(item.bundle)?.push(item.globalOrder);
1015
+ }
1016
+ for (const [, orders] of bundleGroups) {
1017
+ for (let i = 1; i < orders.length; i++) {
1018
+ expect(orders[i]).toBeGreaterThan(orders[i - 1]);
1019
+ }
1020
+ }
1021
+ });
1022
+ it('handles edge case: single item bundles', async () => {
1023
+ const records = await Promise.all(Array.from({ length: 50 }, async (_, i) => {
1024
+ const items = [{ id: `single-${i}`, value: i }];
1025
+ const envelope = await makeCompressedEnvelope(items);
1026
+ return makeSqsRecord({ messageBody: envelope }, `single-msg-${i}`);
1027
+ }));
1028
+ const { items, stats, recordMap } = await (0, sqs_unbundle_1.unbundleRecords)(records);
1029
+ expect(items).toHaveLength(50);
1030
+ expect(stats.bundledSqsRecords).toBe(50);
1031
+ expect(stats.totalItems).toBe(50);
1032
+ for (let i = 0; i < 50; i++) {
1033
+ const item = items.find((x) => x.value === i);
1034
+ expect(item).toBeDefined();
1035
+ if (!item)
1036
+ throw new Error(`Expected item with value ${i} to be found`);
1037
+ expect(recordMap.get(item)).toMatch(/single-msg-\d+/);
1038
+ }
1039
+ });
1040
+ it('handles edge case: empty arrays in bundles', async () => {
1041
+ const emptyEnvelope = {
1042
+ v: 1,
1043
+ c: sqs_bundled_client_types_1.CompressionAlgorithm.NONE,
1044
+ n: 0,
1045
+ p: [],
1046
+ };
1047
+ const record = makeSqsRecord({ messageBody: emptyEnvelope }, 'msg-empty');
1048
+ const { items, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
1049
+ expect(items).toHaveLength(0);
1050
+ expect(stats.bundledSqsRecords).toBe(1);
1051
+ expect(stats.totalItems).toBe(0);
1052
+ });
1053
+ });
1054
+ describe('GZIP comprehensive tests', () => {
1055
+ describe('GZIP pre-decompression bomb protection', () => {
1056
+ it('rejects GZIP with fake ISIZE claiming huge size BEFORE decompression starts', async () => {
1057
+ const gzipHeader = Buffer.from([0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03]);
1058
+ const emptyDeflate = Buffer.from([0x03, 0x00]);
1059
+ const crc32 = Buffer.from([0x00, 0x00, 0x00, 0x00]);
1060
+ const fakeIsize = Buffer.alloc(4);
1061
+ fakeIsize.writeUInt32LE(100 * 1024 * 1024, 0);
1062
+ const fakeGzip = Buffer.concat([gzipHeader, emptyDeflate, crc32, fakeIsize]);
1063
+ const base64 = fakeGzip.toString('base64');
1064
+ const bombEnvelope = {
1065
+ v: 1,
1066
+ c: sqs_bundled_client_types_1.CompressionAlgorithm.GZIP,
1067
+ n: 1,
1068
+ s: 100,
1069
+ p: base64,
1070
+ };
1071
+ const record = makeSqsRecord({ messageBody: bombEnvelope }, 'msg-gzip-bomb');
1072
+ const { failedMessageIds, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record], {
1073
+ maxDecompressedSizeBytes: 10 * 1024 * 1024,
1074
+ });
1075
+ expect(failedMessageIds).toContain('msg-gzip-bomb');
1076
+ expect(stats.failedRecords).toBe(1);
1077
+ });
1078
+ it('allows GZIP with ISIZE within limit', async () => {
1079
+ const items = [makeTestEvent('valid-1')];
1080
+ const envelope = await makeGzipCompressedEnvelope(items);
1081
+ const record = makeSqsRecord({ messageBody: envelope }, 'msg-gzip-valid');
1082
+ const { items: result, failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record], {
1083
+ maxDecompressedSizeBytes: 10 * 1024 * 1024,
1084
+ });
1085
+ expect(result).toHaveLength(1);
1086
+ expect(failedMessageIds).toHaveLength(0);
1087
+ });
1088
+ it('pre-check catches bomb even when ISIZE is exactly at limit + 1', async () => {
1089
+ const limit = 1000;
1090
+ const gzipHeader = Buffer.from([0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03]);
1091
+ const emptyDeflate = Buffer.from([0x03, 0x00]);
1092
+ const crc32 = Buffer.from([0x00, 0x00, 0x00, 0x00]);
1093
+ const fakeIsize = Buffer.alloc(4);
1094
+ fakeIsize.writeUInt32LE(limit + 1, 0);
1095
+ const fakeGzip = Buffer.concat([gzipHeader, emptyDeflate, crc32, fakeIsize]);
1096
+ const base64 = fakeGzip.toString('base64');
1097
+ const bombEnvelope = {
1098
+ v: 1,
1099
+ c: sqs_bundled_client_types_1.CompressionAlgorithm.GZIP,
1100
+ n: 1,
1101
+ s: 100,
1102
+ p: base64,
1103
+ };
1104
+ const record = makeSqsRecord({ messageBody: bombEnvelope }, 'msg-boundary-bomb');
1105
+ const { failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record], {
1106
+ maxDecompressedSizeBytes: limit,
1107
+ });
1108
+ expect(failedMessageIds).toContain('msg-boundary-bomb');
1109
+ });
1110
+ });
1111
+ describe('empty GZIP bundle', () => {
1112
+ it('unbundles empty GZIP compressed array', async () => {
1113
+ const envelope = await makeGzipCompressedEnvelope([]);
1114
+ const record = makeSqsRecord({ messageBody: envelope }, 'msg-empty-gzip');
1115
+ const { items, stats } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
1116
+ expect(items).toHaveLength(0);
1117
+ expect(stats.bundledSqsRecords).toBe(1);
1118
+ expect(stats.totalItems).toBe(0);
1119
+ });
1120
+ });
1121
+ describe('Unicode characters in GZIP payload', () => {
1122
+ it('preserves emoji through GZIP compression', async () => {
1123
+ const items = [
1124
+ { eventId: 'emoji-1', content: '🎉🚀💻🌍✨' },
1125
+ { eventId: 'emoji-2', content: '👋🏽👨‍👩‍👧‍👦🏳️‍🌈' },
1126
+ ];
1127
+ const envelope = await makeGzipCompressedEnvelope(items);
1128
+ const record = makeSqsRecord({ messageBody: envelope });
1129
+ const { items: result } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
1130
+ expect(result).toHaveLength(2);
1131
+ expect(result[0].content).toBe('🎉🚀💻🌍✨');
1132
+ expect(result[1].content).toBe('👋🏽👨‍👩‍👧‍👦🏳️‍🌈');
1133
+ });
1134
+ it('preserves CJK characters through GZIP compression', async () => {
1135
+ const items = [
1136
+ { eventId: 'cjk-1', content: '中文测试:欢迎使用' },
1137
+ { eventId: 'cjk-2', content: 'こんにちは世界 ひらがな カタカナ' },
1138
+ { eventId: 'cjk-3', content: '안녕하세요 세계' },
1139
+ ];
1140
+ const envelope = await makeGzipCompressedEnvelope(items);
1141
+ const record = makeSqsRecord({ messageBody: envelope });
1142
+ const { items: result } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
1143
+ expect(result).toHaveLength(3);
1144
+ expect(result[0].content).toBe('中文测试:欢迎使用');
1145
+ expect(result[1].content).toBe('こんにちは世界 ひらがな カタカナ');
1146
+ expect(result[2].content).toBe('안녕하세요 세계');
1147
+ });
1148
+ it('preserves special characters and mixed scripts through GZIP compression', async () => {
1149
+ const items = [
1150
+ { eventId: 'special-1', content: '∑∫∂∇ε → ∞ × ∈ ⊆ ∀∃' },
1151
+ { eventId: 'special-2', content: 'العربية: مرحبا بكم' },
1152
+ { eventId: 'mixed', content: 'Hello 世界 🌏 مرحبا Привет' },
1153
+ { eventId: 'combining', content: 'e\u0301 n\u0303 c\u0327' },
1154
+ ];
1155
+ const envelope = await makeGzipCompressedEnvelope(items);
1156
+ const record = makeSqsRecord({ messageBody: envelope });
1157
+ const { items: result } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
1158
+ expect(result).toHaveLength(4);
1159
+ for (let i = 0; i < items.length; i++) {
1160
+ expect(result[i].content).toBe(items[i].content);
1161
+ const originalBytes = Buffer.from(items[i].content, 'utf8');
1162
+ const recoveredBytes = Buffer.from(result[i].content, 'utf8');
1163
+ expect(recoveredBytes.equals(originalBytes)).toBe(true);
1164
+ }
1165
+ });
1166
+ });
1167
+ describe('GZIP with invalid magic bytes', () => {
1168
+ it('fails gracefully for buffer with wrong magic bytes', async () => {
1169
+ const wrongMagic = Buffer.from([0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03]);
1170
+ const filler = Buffer.alloc(10);
1171
+ const invalidGzip = Buffer.concat([wrongMagic, filler]);
1172
+ const base64 = invalidGzip.toString('base64');
1173
+ const badEnvelope = {
1174
+ v: 1,
1175
+ c: sqs_bundled_client_types_1.CompressionAlgorithm.GZIP,
1176
+ n: 1,
1177
+ s: 100,
1178
+ p: base64,
1179
+ };
1180
+ const record = makeSqsRecord({ messageBody: badEnvelope }, 'msg-wrong-magic');
1181
+ const { failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
1182
+ expect(failedMessageIds).toContain('msg-wrong-magic');
1183
+ });
1184
+ it('fails gracefully for buffer starting with valid magic but corrupted data', async () => {
1185
+ const validMagic = Buffer.from([0x1f, 0x8b]);
1186
+ const garbage = Buffer.from('this is not valid gzip data after magic');
1187
+ const invalidGzip = Buffer.concat([validMagic, garbage]);
1188
+ const base64 = invalidGzip.toString('base64');
1189
+ const badEnvelope = {
1190
+ v: 1,
1191
+ c: sqs_bundled_client_types_1.CompressionAlgorithm.GZIP,
1192
+ n: 1,
1193
+ s: 100,
1194
+ p: base64,
1195
+ };
1196
+ const record = makeSqsRecord({ messageBody: badEnvelope }, 'msg-corrupt-gzip');
1197
+ const { failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
1198
+ expect(failedMessageIds).toContain('msg-corrupt-gzip');
1199
+ });
1200
+ });
1201
+ describe('GZIP minimum size edge cases', () => {
1202
+ it('handles buffer exactly 18 bytes (minimum valid)', async () => {
1203
+ const minBuffer = Buffer.alloc(18);
1204
+ minBuffer[0] = 0x1f;
1205
+ minBuffer[1] = 0x8b;
1206
+ const size = (0, compression_1.getGzipUncompressedSize)(minBuffer);
1207
+ expect(size).toBe(0);
1208
+ });
1209
+ it('returns undefined for buffer exactly 17 bytes (too small)', async () => {
1210
+ const tooSmall = Buffer.alloc(17);
1211
+ tooSmall[0] = 0x1f;
1212
+ tooSmall[1] = 0x8b;
1213
+ const size = (0, compression_1.getGzipUncompressedSize)(tooSmall);
1214
+ expect(size).toBeUndefined();
1215
+ });
1216
+ it('fails gracefully for 17-byte GZIP envelope', async () => {
1217
+ const tooSmall = Buffer.alloc(17);
1218
+ tooSmall[0] = 0x1f;
1219
+ tooSmall[1] = 0x8b;
1220
+ const base64 = tooSmall.toString('base64');
1221
+ const badEnvelope = {
1222
+ v: 1,
1223
+ c: sqs_bundled_client_types_1.CompressionAlgorithm.GZIP,
1224
+ n: 1,
1225
+ s: 100,
1226
+ p: base64,
1227
+ };
1228
+ const record = makeSqsRecord({ messageBody: badEnvelope }, 'msg-too-small');
1229
+ const { failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
1230
+ expect(failedMessageIds).toContain('msg-too-small');
1231
+ });
1232
+ });
1233
+ describe('GZIP timeout protection', () => {
1234
+ it('CompressionTimeoutError has correct properties', () => {
1235
+ const error = new compression_1.CompressionTimeoutError(compression_1.CompressionOperation.DECOMPRESS, 100);
1236
+ expect(error).toBeInstanceOf(Error);
1237
+ expect(error.name).toBe('CompressionTimeoutError');
1238
+ expect(error.operation).toBe('decompress');
1239
+ expect(error.timeoutMs).toBe(100);
1240
+ expect(error.message).toContain('timed out after 100ms');
1241
+ });
1242
+ it('GZIP decompression respects timeout option', async () => {
1243
+ const items = [makeTestEvent('timeout-test')];
1244
+ const envelope = await makeGzipCompressedEnvelope(items);
1245
+ const record = makeSqsRecord({ messageBody: envelope });
1246
+ const { items: result, failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record], {
1247
+ timeoutMs: 10000,
1248
+ });
1249
+ expect(result).toHaveLength(1);
1250
+ expect(failedMessageIds).toHaveLength(0);
1251
+ });
1252
+ });
1253
+ describe('large GZIP near SQS limit', () => {
1254
+ it('decompresses large GZIP bundle near 256KB compressed', async () => {
1255
+ const items = Array.from({ length: 200 }, (_, i) => ({
1256
+ eventId: `large-${i}`,
1257
+ name: 'page_view',
1258
+ data: `repeated-content-for-compression-${i}-`.repeat(50),
1259
+ value: i * 1.5,
1260
+ }));
1261
+ const envelope = await makeGzipCompressedEnvelope(items);
1262
+ const compressedSize = Buffer.byteLength(JSON.stringify(envelope.p), 'utf8');
1263
+ console.log(`Large GZIP test: compressed payload ~${Math.round(compressedSize / 1024)}KB`);
1264
+ const record = makeSqsRecord({ messageBody: envelope }, 'msg-large-gzip');
1265
+ const { items: result, failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record]);
1266
+ expect(failedMessageIds).toHaveLength(0);
1267
+ expect(result).toHaveLength(200);
1268
+ expect(result[0].eventId).toBe('large-0');
1269
+ expect(result[199].eventId).toBe('large-199');
1270
+ });
1271
+ it('handles GZIP with maximum decompressed size exactly at limit', async () => {
1272
+ const items = [{ eventId: 'boundary', data: 'x'.repeat(5000) }];
1273
+ const envelope = await makeGzipCompressedEnvelope(items);
1274
+ const record = makeSqsRecord({ messageBody: envelope });
1275
+ const { items: result, failedMessageIds } = await (0, sqs_unbundle_1.unbundleRecords)([record], {
1276
+ maxDecompressedSizeBytes: 10 * 1024,
1277
+ });
1278
+ expect(failedMessageIds).toHaveLength(0);
1279
+ expect(result).toHaveLength(1);
1280
+ });
1281
+ });
1282
+ });
1283
+ });
1284
+ describe('getGzipUncompressedSize', () => {
1285
+ it('returns correct size for valid GZIP', async () => {
1286
+ const originalData = 'Hello, World! This is a test.';
1287
+ const compressed = await gzipAsync(Buffer.from(originalData, 'utf8'));
1288
+ const size = (0, compression_1.getGzipUncompressedSize)(compressed);
1289
+ expect(size).toBe(Buffer.byteLength(originalData, 'utf8'));
1290
+ });
1291
+ it('returns correct size for larger payload', async () => {
1292
+ const originalData = 'x'.repeat(10000);
1293
+ const compressed = await gzipAsync(Buffer.from(originalData, 'utf8'));
1294
+ const size = (0, compression_1.getGzipUncompressedSize)(compressed);
1295
+ expect(size).toBe(10000);
1296
+ });
1297
+ it('returns undefined for buffer too small', () => {
1298
+ const tooSmall = Buffer.alloc(17);
1299
+ tooSmall[0] = 0x1f;
1300
+ tooSmall[1] = 0x8b;
1301
+ expect((0, compression_1.getGzipUncompressedSize)(tooSmall)).toBeUndefined();
1302
+ });
1303
+ it('returns undefined for wrong magic bytes', () => {
1304
+ const wrongMagic = Buffer.alloc(20);
1305
+ wrongMagic[0] = 0x00;
1306
+ wrongMagic[1] = 0x00;
1307
+ expect((0, compression_1.getGzipUncompressedSize)(wrongMagic)).toBeUndefined();
1308
+ });
1309
+ it('returns undefined for empty buffer', () => {
1310
+ expect((0, compression_1.getGzipUncompressedSize)(Buffer.alloc(0))).toBeUndefined();
1311
+ });
1312
+ it('returns undefined for single byte buffer', () => {
1313
+ expect((0, compression_1.getGzipUncompressedSize)(Buffer.from([0x1f]))).toBeUndefined();
1314
+ });
1315
+ it('returns undefined for buffer with only magic bytes', () => {
1316
+ expect((0, compression_1.getGzipUncompressedSize)(Buffer.from([0x1f, 0x8b]))).toBeUndefined();
1317
+ });
1318
+ it('returns 0 for GZIP with ISIZE = 0', () => {
1319
+ const buffer = Buffer.alloc(18);
1320
+ buffer[0] = 0x1f;
1321
+ buffer[1] = 0x8b;
1322
+ const size = (0, compression_1.getGzipUncompressedSize)(buffer);
1323
+ expect(size).toBe(0);
1324
+ });
1325
+ it('reads ISIZE correctly as little-endian', async () => {
1326
+ const buffer = Buffer.alloc(18);
1327
+ buffer[0] = 0x1f;
1328
+ buffer[1] = 0x8b;
1329
+ buffer.writeUInt32LE(0x12345678, 14);
1330
+ const size = (0, compression_1.getGzipUncompressedSize)(buffer);
1331
+ expect(size).toBe(0x12345678);
1332
+ });
1333
+ it('handles maximum 32-bit ISIZE value', () => {
1334
+ const buffer = Buffer.alloc(18);
1335
+ buffer[0] = 0x1f;
1336
+ buffer[1] = 0x8b;
1337
+ buffer.writeUInt32LE(0xffffffff, 14);
1338
+ const size = (0, compression_1.getGzipUncompressedSize)(buffer);
1339
+ expect(size).toBe(0xffffffff);
1340
+ });
1341
+ it('correctly extracts size from real GZIP data', async () => {
1342
+ const testSizes = [1, 10, 100, 1000, 50000];
1343
+ for (const testSize of testSizes) {
1344
+ const originalData = 'a'.repeat(testSize);
1345
+ const compressed = await gzipAsync(Buffer.from(originalData, 'utf8'));
1346
+ const size = (0, compression_1.getGzipUncompressedSize)(compressed);
1347
+ expect(size).toBe(testSize);
1348
+ }
1349
+ });
1350
+ it('handles JSON array compression correctly', async () => {
1351
+ const items = [{ id: 1 }, { id: 2 }, { id: 3 }];
1352
+ const json = JSON.stringify(items);
1353
+ const compressed = await gzipAsync(Buffer.from(json, 'utf8'));
1354
+ const size = (0, compression_1.getGzipUncompressedSize)(compressed);
1355
+ expect(size).toBe(Buffer.byteLength(json, 'utf8'));
1356
+ });
1357
+ });
1358
1358
  //# sourceMappingURL=sqs-unbundle.spec.js.map