@adtrackify/at-service-common 4.0.4 → 4.0.6

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 (779) 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 +354 -354
  5. package/dist/cjs/__tests__/clients/dynamodb-client.spec.d.ts +1 -1
  6. package/dist/cjs/__tests__/clients/dynamodb-client.spec.js +194 -194
  7. package/dist/cjs/__tests__/clients/sqs-bundled-client.spec.d.ts +1 -1
  8. package/dist/cjs/__tests__/clients/sqs-bundled-client.spec.js +931 -931
  9. package/dist/cjs/__tests__/clients/sqs-bundling-contracts.spec.d.ts +1 -1
  10. package/dist/cjs/__tests__/clients/sqs-bundling-contracts.spec.js +563 -563
  11. package/dist/cjs/__tests__/clients/sqs-client.spec.d.ts +1 -1
  12. package/dist/cjs/__tests__/clients/sqs-client.spec.js +191 -191
  13. package/dist/cjs/__tests__/clients/sqs-unbundle.spec.d.ts +1 -1
  14. package/dist/cjs/__tests__/clients/sqs-unbundle.spec.js +1357 -1357
  15. package/dist/cjs/__tests__/db/contact-enrichments-db-service.spec.d.ts +1 -1
  16. package/dist/cjs/__tests__/db/contact-enrichments-db-service.spec.js +68 -68
  17. package/dist/cjs/__tests__/db/destinations-db-service.spec.d.ts +1 -1
  18. package/dist/cjs/__tests__/db/destinations-db-service.spec.js +125 -125
  19. package/dist/cjs/__tests__/db/products-db-service.spec.d.ts +1 -1
  20. package/dist/cjs/__tests__/db/products-db-service.spec.js +89 -89
  21. package/dist/cjs/__tests__/db/shared-read-db-services.spec.d.ts +1 -1
  22. package/dist/cjs/__tests__/db/shared-read-db-services.spec.js +89 -89
  23. package/dist/cjs/__tests__/db/shopify-app-installs-db-service.spec.d.ts +1 -1
  24. package/dist/cjs/__tests__/db/shopify-app-installs-db-service.spec.js +104 -104
  25. package/dist/cjs/__tests__/db/subscriptions-db-service.spec.d.ts +1 -1
  26. package/dist/cjs/__tests__/db/subscriptions-db-service.spec.js +95 -95
  27. package/dist/cjs/__tests__/db/user-accounts-db-service.spec.d.ts +1 -1
  28. package/dist/cjs/__tests__/db/user-accounts-db-service.spec.js +76 -76
  29. package/dist/cjs/__tests__/helpers/account-users-helper.spec.d.ts +1 -1
  30. package/dist/cjs/__tests__/helpers/account-users-helper.spec.js +220 -220
  31. package/dist/cjs/__tests__/helpers/acuity-helper.spec.d.ts +1 -1
  32. package/dist/cjs/__tests__/helpers/acuity-helper.spec.js +69 -69
  33. package/dist/cjs/__tests__/helpers/api-key-auth-helper.spec.d.ts +1 -1
  34. package/dist/cjs/__tests__/helpers/api-key-auth-helper.spec.js +82 -82
  35. package/dist/cjs/__tests__/identity-cache/identity-cache-db-service.spec.d.ts +1 -1
  36. package/dist/cjs/__tests__/identity-cache/identity-cache-db-service.spec.js +676 -676
  37. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.d.ts +1 -0
  38. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.js +646 -0
  39. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.js.map +1 -0
  40. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.d.ts +1 -0
  41. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.js +327 -0
  42. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.js.map +1 -0
  43. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service.spec.d.ts +1 -1
  44. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js +1282 -1140
  45. package/dist/cjs/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js.map +1 -1
  46. package/dist/cjs/__tests__/identity-cache/identity-cache-tier-routing.spec.d.ts +1 -1
  47. package/dist/cjs/__tests__/identity-cache/identity-cache-tier-routing.spec.js +852 -851
  48. package/dist/cjs/__tests__/identity-cache/identity-cache-tier-routing.spec.js.map +1 -1
  49. package/dist/cjs/__tests__/identity-cache/identity-gateway-service.spec.d.ts +1 -0
  50. package/dist/cjs/__tests__/identity-cache/identity-gateway-service.spec.js +567 -0
  51. package/dist/cjs/__tests__/identity-cache/identity-gateway-service.spec.js.map +1 -0
  52. package/dist/cjs/__tests__/identity-cache/trait-merging-and-staleness.spec.d.ts +1 -1
  53. package/dist/cjs/__tests__/identity-cache/trait-merging-and-staleness.spec.js +1060 -1060
  54. package/dist/cjs/__tests__/identity-cache/volatile-traits-optimization.spec.d.ts +1 -1
  55. package/dist/cjs/__tests__/identity-cache/volatile-traits-optimization.spec.js +818 -818
  56. package/dist/cjs/__tests__/integration/sqs-bundling-roundtrip.spec.d.ts +1 -1
  57. package/dist/cjs/__tests__/integration/sqs-bundling-roundtrip.spec.js +584 -584
  58. package/dist/cjs/__tests__/libs/compress-decompress.spec.d.ts +1 -1
  59. package/dist/cjs/__tests__/libs/compress-decompress.spec.js +16 -16
  60. package/dist/cjs/__tests__/libs/contacts.spec.d.ts +1 -1
  61. package/dist/cjs/__tests__/libs/contacts.spec.js +294 -294
  62. package/dist/cjs/__tests__/libs/currency.spec.d.ts +1 -1
  63. package/dist/cjs/__tests__/libs/currency.spec.js +220 -220
  64. package/dist/cjs/__tests__/libs/dates.spec.d.ts +1 -1
  65. package/dist/cjs/__tests__/libs/dates.spec.js +130 -130
  66. package/dist/cjs/__tests__/libs/domain.spec.d.ts +1 -1
  67. package/dist/cjs/__tests__/libs/domain.spec.js +107 -107
  68. package/dist/cjs/__tests__/libs/numbers.spec.d.ts +1 -1
  69. package/dist/cjs/__tests__/libs/numbers.spec.js +261 -261
  70. package/dist/cjs/__tests__/s3-client/s3-client.spec.d.ts +1 -1
  71. package/dist/cjs/__tests__/s3-client/s3-client.spec.js +33 -33
  72. package/dist/cjs/__tests__/services/acuity-api-service.spec.d.ts +1 -1
  73. package/dist/cjs/__tests__/services/acuity-api-service.spec.js +71 -71
  74. package/dist/cjs/__tests__/services/cost/cost-calculation-types.spec.d.ts +1 -1
  75. package/dist/cjs/__tests__/services/cost/cost-calculation-types.spec.js +23 -23
  76. package/dist/cjs/__tests__/services/cost/cost-calculator-service.spec.d.ts +1 -1
  77. package/dist/cjs/__tests__/services/cost/cost-calculator-service.spec.js +3319 -3319
  78. package/dist/cjs/__tests__/services/cost/cost-currency-service.spec.d.ts +1 -1
  79. package/dist/cjs/__tests__/services/cost/cost-currency-service.spec.js +114 -114
  80. package/dist/cjs/__tests__/services/cost/cost-filter-service.spec.d.ts +1 -1
  81. package/dist/cjs/__tests__/services/cost/cost-filter-service.spec.js +468 -468
  82. package/dist/cjs/__tests__/services/cost/order-cost/order-cost-resolution-service.spec.d.ts +1 -1
  83. package/dist/cjs/__tests__/services/cost/order-cost/order-cost-resolution-service.spec.js +206 -206
  84. package/dist/cjs/__tests__/services/currency-exchange-rate-lookup-service.spec.d.ts +1 -1
  85. package/dist/cjs/__tests__/services/currency-exchange-rate-lookup-service.spec.js +34 -34
  86. package/dist/cjs/__tests__/services/email-verification/contact-email-verification-service.spec.d.ts +1 -1
  87. package/dist/cjs/__tests__/services/email-verification/contact-email-verification-service.spec.js +93 -93
  88. package/dist/cjs/__tests__/services/email-verification/email-verification-service.spec.d.ts +1 -1
  89. package/dist/cjs/__tests__/services/email-verification/email-verification-service.spec.js +57 -57
  90. package/dist/cjs/__tests__/shopify/shopify-graphql-transformer.spec.d.ts +1 -1
  91. package/dist/cjs/__tests__/shopify/shopify-graphql-transformer.spec.js +35 -35
  92. package/dist/cjs/__tests__/unit/libs/api-router/public-api-router.spec.d.ts +1 -1
  93. package/dist/cjs/__tests__/unit/libs/api-router/public-api-router.spec.js +181 -181
  94. package/dist/cjs/__tests__/unit/libs/api-router/route-matcher.spec.d.ts +1 -1
  95. package/dist/cjs/__tests__/unit/libs/api-router/route-matcher.spec.js +69 -69
  96. package/dist/cjs/__tests__/utils/custom-measure-formula-utils.spec.d.ts +1 -1
  97. package/dist/cjs/__tests__/utils/custom-measure-formula-utils.spec.js +139 -139
  98. package/dist/cjs/clients/generic/cognito-client.d.ts +23 -23
  99. package/dist/cjs/clients/generic/cognito-client.js +209 -209
  100. package/dist/cjs/clients/generic/dynamodb-client.d.ts +20 -20
  101. package/dist/cjs/clients/generic/dynamodb-client.js +235 -235
  102. package/dist/cjs/clients/generic/eventbridge-client.d.ts +14 -14
  103. package/dist/cjs/clients/generic/eventbridge-client.js +51 -51
  104. package/dist/cjs/clients/generic/http-client.d.ts +14 -14
  105. package/dist/cjs/clients/generic/http-client.js +61 -61
  106. package/dist/cjs/clients/generic/index.d.ts +13 -13
  107. package/dist/cjs/clients/generic/index.js +29 -29
  108. package/dist/cjs/clients/generic/lambda-invoke-client.d.ts +10 -10
  109. package/dist/cjs/clients/generic/lambda-invoke-client.js +39 -39
  110. package/dist/cjs/clients/generic/location-client.d.ts +8 -8
  111. package/dist/cjs/clients/generic/location-client.js +31 -31
  112. package/dist/cjs/clients/generic/redis-client.d.ts +33 -33
  113. package/dist/cjs/clients/generic/redis-client.js +191 -191
  114. package/dist/cjs/clients/generic/s3-client.d.ts +23 -23
  115. package/dist/cjs/clients/generic/s3-client.js +216 -216
  116. package/dist/cjs/clients/generic/singlestore-db-client.d.ts +14 -14
  117. package/dist/cjs/clients/generic/singlestore-db-client.js +67 -67
  118. package/dist/cjs/clients/generic/sqs-bundled-client.d.ts +15 -15
  119. package/dist/cjs/clients/generic/sqs-bundled-client.js +311 -311
  120. package/dist/cjs/clients/generic/sqs-bundled-client.types.d.ts +53 -53
  121. package/dist/cjs/clients/generic/sqs-bundled-client.types.js +17 -17
  122. package/dist/cjs/clients/generic/sqs-client.d.ts +53 -53
  123. package/dist/cjs/clients/generic/sqs-client.js +285 -285
  124. package/dist/cjs/clients/generic/sqs-unbundle.d.ts +32 -32
  125. package/dist/cjs/clients/generic/sqs-unbundle.js +144 -144
  126. package/dist/cjs/clients/index.d.ts +3 -3
  127. package/dist/cjs/clients/index.js +19 -19
  128. package/dist/cjs/clients/internal-api/accounts-client.d.ts +91 -91
  129. package/dist/cjs/clients/internal-api/accounts-client.js +129 -129
  130. package/dist/cjs/clients/internal-api/cache-lambda-client.d.ts +26 -26
  131. package/dist/cjs/clients/internal-api/cache-lambda-client.js +89 -89
  132. package/dist/cjs/clients/internal-api/db-management-client.d.ts +18 -18
  133. package/dist/cjs/clients/internal-api/db-management-client.js +36 -36
  134. package/dist/cjs/clients/internal-api/destinations-client.d.ts +34 -34
  135. package/dist/cjs/clients/internal-api/destinations-client.js +79 -79
  136. package/dist/cjs/clients/internal-api/event-collector-client.d.ts +20 -20
  137. package/dist/cjs/clients/internal-api/event-collector-client.js +36 -36
  138. package/dist/cjs/clients/internal-api/identity-client.d.ts +31 -31
  139. package/dist/cjs/clients/internal-api/identity-client.js +91 -91
  140. package/dist/cjs/clients/internal-api/index.d.ts +9 -9
  141. package/dist/cjs/clients/internal-api/index.js +25 -25
  142. package/dist/cjs/clients/internal-api/shopify-app-install-client.d.ts +37 -37
  143. package/dist/cjs/clients/internal-api/shopify-app-install-client.js +81 -81
  144. package/dist/cjs/clients/internal-api/subscriptions-client.d.ts +26 -26
  145. package/dist/cjs/clients/internal-api/subscriptions-client.js +77 -77
  146. package/dist/cjs/clients/internal-api/users-auth-client.d.ts +35 -35
  147. package/dist/cjs/clients/internal-api/users-auth-client.js +110 -110
  148. package/dist/cjs/clients/third-party/acuity-client.d.ts +10 -10
  149. package/dist/cjs/clients/third-party/acuity-client.js +40 -40
  150. package/dist/cjs/clients/third-party/emailable-client.d.ts +7 -7
  151. package/dist/cjs/clients/third-party/emailable-client.js +25 -25
  152. package/dist/cjs/clients/third-party/exchange-rate-api-client.d.ts +17 -17
  153. package/dist/cjs/clients/third-party/exchange-rate-api-client.js +19 -19
  154. package/dist/cjs/clients/third-party/index.d.ts +5 -5
  155. package/dist/cjs/clients/third-party/index.js +21 -21
  156. package/dist/cjs/clients/third-party/loops-client.d.ts +10 -10
  157. package/dist/cjs/clients/third-party/loops-client.js +30 -30
  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-client.d.ts +29 -29
  165. package/dist/cjs/clients/third-party/shopify-client.js +146 -146
  166. package/dist/cjs/constants/index.d.ts +1 -1
  167. package/dist/cjs/constants/index.js +17 -17
  168. package/dist/cjs/constants/sqs.d.ts +20 -20
  169. package/dist/cjs/constants/sqs.js +26 -26
  170. package/dist/cjs/helpers/account-users-helper.d.ts +2 -2
  171. package/dist/cjs/helpers/account-users-helper.js +22 -22
  172. package/dist/cjs/helpers/acuity-helper.d.ts +4 -4
  173. package/dist/cjs/helpers/acuity-helper.js +56 -56
  174. package/dist/cjs/helpers/api-key-auth-helper.d.ts +9 -9
  175. package/dist/cjs/helpers/api-key-auth-helper.js +40 -40
  176. package/dist/cjs/helpers/api-key-authorizer-helper.d.ts +36 -36
  177. package/dist/cjs/helpers/api-key-authorizer-helper.js +87 -87
  178. package/dist/cjs/helpers/identity-cache-helper.d.ts +30 -30
  179. package/dist/cjs/helpers/identity-cache-helper.js +253 -253
  180. package/dist/cjs/helpers/index.d.ts +10 -10
  181. package/dist/cjs/helpers/index.js +26 -26
  182. package/dist/cjs/helpers/input-validation-helper.d.ts +3 -3
  183. package/dist/cjs/helpers/input-validation-helper.js +22 -22
  184. package/dist/cjs/helpers/logging-helper.d.ts +16 -16
  185. package/dist/cjs/helpers/logging-helper.js +84 -84
  186. package/dist/cjs/helpers/response-helper.d.ts +18 -18
  187. package/dist/cjs/helpers/response-helper.js +43 -43
  188. package/dist/cjs/helpers/shopify-helper.d.ts +9 -9
  189. package/dist/cjs/helpers/shopify-helper.js +26 -26
  190. package/dist/cjs/helpers/sqs-utils.d.ts +6 -6
  191. package/dist/cjs/helpers/sqs-utils.js +14 -14
  192. package/dist/cjs/index.d.ts +7 -7
  193. package/dist/cjs/index.js +23 -23
  194. package/dist/cjs/libs/api-router/index.d.ts +2 -2
  195. package/dist/cjs/libs/api-router/index.js +18 -18
  196. package/dist/cjs/libs/api-router/public-api-router.d.ts +3 -3
  197. package/dist/cjs/libs/api-router/public-api-router.js +36 -36
  198. package/dist/cjs/libs/api-router/route-matcher.d.ts +21 -21
  199. package/dist/cjs/libs/api-router/route-matcher.js +36 -36
  200. package/dist/cjs/libs/click-id-parser.d.ts +23 -23
  201. package/dist/cjs/libs/click-id-parser.js +49 -49
  202. package/dist/cjs/libs/compression.d.ts +2 -2
  203. package/dist/cjs/libs/compression.js +33 -33
  204. package/dist/cjs/libs/contacts.d.ts +7 -7
  205. package/dist/cjs/libs/contacts.js +152 -152
  206. package/dist/cjs/libs/cookie.d.ts +17 -17
  207. package/dist/cjs/libs/cookie.js +76 -76
  208. package/dist/cjs/libs/crypto.d.ts +4 -4
  209. package/dist/cjs/libs/crypto.js +25 -25
  210. package/dist/cjs/libs/csv.d.ts +2 -2
  211. package/dist/cjs/libs/csv.js +35 -35
  212. package/dist/cjs/libs/currency.d.ts +1 -1
  213. package/dist/cjs/libs/currency.js +29 -29
  214. package/dist/cjs/libs/dates.d.ts +12 -12
  215. package/dist/cjs/libs/dates.js +96 -96
  216. package/dist/cjs/libs/domain.d.ts +2 -2
  217. package/dist/cjs/libs/domain.js +38 -38
  218. package/dist/cjs/libs/emails.d.ts +8 -8
  219. package/dist/cjs/libs/emails.js +154 -154
  220. package/dist/cjs/libs/http-error.d.ts +21 -21
  221. package/dist/cjs/libs/http-error.js +63 -63
  222. package/dist/cjs/libs/http-status-codes.d.ts +58 -58
  223. package/dist/cjs/libs/http-status-codes.js +62 -62
  224. package/dist/cjs/libs/index.d.ts +19 -19
  225. package/dist/cjs/libs/index.js +35 -35
  226. package/dist/cjs/libs/numbers.d.ts +1 -1
  227. package/dist/cjs/libs/numbers.js +15 -15
  228. package/dist/cjs/libs/referrer-parser/index.d.ts +2 -2
  229. package/dist/cjs/libs/referrer-parser/index.js +18 -18
  230. package/dist/cjs/libs/referrer-parser/referrer-data.d.ts +9 -9
  231. package/dist/cjs/libs/referrer-parser/referrer-data.js +3307 -3307
  232. package/dist/cjs/libs/referrer-parser/referrer-parser-util.d.ts +20 -20
  233. package/dist/cjs/libs/referrer-parser/referrer-parser-util.js +131 -131
  234. package/dist/cjs/libs/strings.d.ts +3 -3
  235. package/dist/cjs/libs/strings.js +46 -46
  236. package/dist/cjs/libs/traits.d.ts +6 -6
  237. package/dist/cjs/libs/traits.js +65 -65
  238. package/dist/cjs/libs/url.d.ts +1 -1
  239. package/dist/cjs/libs/url.js +13 -13
  240. package/dist/cjs/services/acuity-api-service.d.ts +9 -9
  241. package/dist/cjs/services/acuity-api-service.js +73 -73
  242. package/dist/cjs/services/cache/generic-cached-object.d.ts +5 -5
  243. package/dist/cjs/services/cache/generic-cached-object.js +2 -2
  244. package/dist/cjs/services/cache/index.d.ts +1 -1
  245. package/dist/cjs/services/cache/index.js +17 -17
  246. package/dist/cjs/services/cache/product-cache-service.d.ts +21 -21
  247. package/dist/cjs/services/cache/product-cache-service.js +76 -76
  248. package/dist/cjs/services/cost/cost-calculation-types.d.ts +69 -69
  249. package/dist/cjs/services/cost/cost-calculation-types.js +19 -19
  250. package/dist/cjs/services/cost/cost-calculator-service.d.ts +24 -24
  251. package/dist/cjs/services/cost/cost-calculator-service.js +456 -456
  252. package/dist/cjs/services/cost/cost-currency-service.d.ts +6 -6
  253. package/dist/cjs/services/cost/cost-currency-service.js +87 -87
  254. package/dist/cjs/services/cost/cost-filter-service.d.ts +10 -10
  255. package/dist/cjs/services/cost/cost-filter-service.js +121 -121
  256. package/dist/cjs/services/cost/index.d.ts +5 -5
  257. package/dist/cjs/services/cost/index.js +21 -21
  258. package/dist/cjs/services/cost/order-cost/index.d.ts +2 -2
  259. package/dist/cjs/services/cost/order-cost/index.js +18 -18
  260. package/dist/cjs/services/cost/order-cost/order-cost-resolution-service.d.ts +23 -23
  261. package/dist/cjs/services/cost/order-cost/order-cost-resolution-service.js +361 -361
  262. package/dist/cjs/services/cost/order-cost/order-cost-resolution-types.d.ts +37 -37
  263. package/dist/cjs/services/cost/order-cost/order-cost-resolution-types.js +2 -2
  264. package/dist/cjs/services/currency-exchange-rate-lookup-service.d.ts +12 -12
  265. package/dist/cjs/services/currency-exchange-rate-lookup-service.js +94 -94
  266. package/dist/cjs/services/db/accounts-db-service.d.ts +9 -9
  267. package/dist/cjs/services/db/accounts-db-service.js +33 -33
  268. package/dist/cjs/services/db/api-keys-db-service.d.ts +10 -10
  269. package/dist/cjs/services/db/api-keys-db-service.js +36 -36
  270. package/dist/cjs/services/db/contact-enrichments-db-service.d.ts +15 -15
  271. package/dist/cjs/services/db/contact-enrichments-db-service.js +94 -94
  272. package/dist/cjs/services/db/currency-exchange-rates-db-service.d.ts +21 -21
  273. package/dist/cjs/services/db/currency-exchange-rates-db-service.js +39 -39
  274. package/dist/cjs/services/db/custom-measures-db-service.d.ts +14 -14
  275. package/dist/cjs/services/db/custom-measures-db-service.js +48 -48
  276. package/dist/cjs/services/db/destinations-db-service.d.ts +13 -13
  277. package/dist/cjs/services/db/destinations-db-service.js +74 -74
  278. package/dist/cjs/services/db/identity-cache-db-service.d.ts +28 -28
  279. package/dist/cjs/services/db/identity-cache-db-service.js +320 -320
  280. package/dist/cjs/services/db/identity-cache-dynamodb-service/constants.d.ts +3 -0
  281. package/dist/cjs/services/db/identity-cache-dynamodb-service/constants.js +7 -0
  282. package/dist/cjs/services/db/identity-cache-dynamodb-service/constants.js.map +1 -0
  283. package/dist/cjs/services/db/identity-cache-dynamodb-service/helpers.d.ts +14 -0
  284. package/dist/cjs/services/db/identity-cache-dynamodb-service/helpers.js +251 -0
  285. package/dist/cjs/services/db/identity-cache-dynamodb-service/helpers.js.map +1 -0
  286. package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.d.ts +24 -0
  287. package/dist/cjs/services/db/{identity-cache-dynamodb-service.js → identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.js} +613 -735
  288. package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.js.map +1 -0
  289. package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-gateway-service.d.ts +22 -0
  290. package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-gateway-service.js +375 -0
  291. package/dist/cjs/services/db/identity-cache-dynamodb-service/identity-gateway-service.js.map +1 -0
  292. package/dist/cjs/services/db/identity-cache-dynamodb-service/index.d.ts +4 -0
  293. package/dist/cjs/services/db/identity-cache-dynamodb-service/index.js +21 -0
  294. package/dist/cjs/services/db/identity-cache-dynamodb-service/index.js.map +1 -0
  295. package/dist/cjs/services/db/identity-cache-dynamodb-service/key-builders.d.ts +12 -0
  296. package/dist/cjs/services/db/identity-cache-dynamodb-service/key-builders.js +32 -0
  297. package/dist/cjs/services/db/identity-cache-dynamodb-service/key-builders.js.map +1 -0
  298. package/dist/cjs/services/db/identity-cache-dynamodb-service/types.d.ts +205 -0
  299. package/dist/cjs/services/db/identity-cache-dynamodb-service/types.js +35 -0
  300. package/dist/cjs/services/db/identity-cache-dynamodb-service/types.js.map +1 -0
  301. package/dist/cjs/services/db/index.d.ts +22 -19
  302. package/dist/cjs/services/db/index.js +38 -35
  303. package/dist/cjs/services/db/index.js.map +1 -1
  304. package/dist/cjs/services/db/log-events-db-service.d.ts +11 -11
  305. package/dist/cjs/services/db/log-events-db-service.js +181 -181
  306. package/dist/cjs/services/db/pixels-db-service.d.ts +8 -8
  307. package/dist/cjs/services/db/pixels-db-service.js +35 -35
  308. package/dist/cjs/services/db/products-db-service-types.d.ts +10 -10
  309. package/dist/cjs/services/db/products-db-service-types.js +2 -2
  310. package/dist/cjs/services/db/products-db-service.d.ts +19 -19
  311. package/dist/cjs/services/db/products-db-service.js +281 -281
  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/purchased-contacts/index.d.ts +2 -2
  315. package/dist/cjs/services/db/purchased-contacts/index.js +18 -18
  316. package/dist/cjs/services/db/purchased-contacts/purchased-contacts-db-service.d.ts +18 -18
  317. package/dist/cjs/services/db/purchased-contacts/purchased-contacts-db-service.js +152 -152
  318. package/dist/cjs/services/db/purchased-contacts/types.d.ts +11 -11
  319. package/dist/cjs/services/db/purchased-contacts/types.js +2 -2
  320. package/dist/cjs/services/db/shopify-app-installs-db-service.d.ts +10 -10
  321. package/dist/cjs/services/db/shopify-app-installs-db-service.js +52 -52
  322. package/dist/cjs/services/db/shopify-products-cache-db-service.d.ts +16 -16
  323. package/dist/cjs/services/db/shopify-products-cache-db-service.js +73 -73
  324. package/dist/cjs/services/db/subscriptions-db-service.d.ts +11 -11
  325. package/dist/cjs/services/db/subscriptions-db-service.js +38 -38
  326. package/dist/cjs/services/db/tracking-events-db-service.d.ts +21 -21
  327. package/dist/cjs/services/db/tracking-events-db-service.js +188 -188
  328. package/dist/cjs/services/db/user-accounts-db-service.d.ts +7 -7
  329. package/dist/cjs/services/db/user-accounts-db-service.js +17 -17
  330. package/dist/cjs/services/email-verification/contact-email-verification-service.d.ts +7 -7
  331. package/dist/cjs/services/email-verification/contact-email-verification-service.js +101 -101
  332. package/dist/cjs/services/email-verification/email-verification-service.d.ts +19 -19
  333. package/dist/cjs/services/email-verification/email-verification-service.js +131 -131
  334. package/dist/cjs/services/email-verification/index.d.ts +2 -2
  335. package/dist/cjs/services/email-verification/index.js +18 -18
  336. package/dist/cjs/services/eventbridge-integration-service.d.ts +9 -9
  337. package/dist/cjs/services/eventbridge-integration-service.js +28 -28
  338. package/dist/cjs/services/events/index.d.ts +3 -3
  339. package/dist/cjs/services/events/index.js +19 -19
  340. package/dist/cjs/services/events/log-event-service.d.ts +19 -19
  341. package/dist/cjs/services/events/log-event-service.js +77 -77
  342. package/dist/cjs/services/events/metric-event-service.d.ts +9 -9
  343. package/dist/cjs/services/events/metric-event-service.js +49 -49
  344. package/dist/cjs/services/events/tracking-event-sqs-service.d.ts +8 -8
  345. package/dist/cjs/services/events/tracking-event-sqs-service.js +34 -34
  346. package/dist/cjs/services/generic-cache-service.d.ts +7 -7
  347. package/dist/cjs/services/generic-cache-service.js +33 -33
  348. package/dist/cjs/services/index.d.ts +11 -11
  349. package/dist/cjs/services/index.js +27 -27
  350. package/dist/cjs/services/ipdata-lookup-service.d.ts +20 -20
  351. package/dist/cjs/services/ipdata-lookup-service.js +112 -112
  352. package/dist/cjs/services/shopify/index.d.ts +2 -2
  353. package/dist/cjs/services/shopify/index.js +18 -18
  354. package/dist/cjs/services/shopify/products/index.d.ts +1 -1
  355. package/dist/cjs/services/shopify/products/index.js +17 -17
  356. package/dist/cjs/services/shopify/products/shopify-products-serviceV2.d.ts +17 -17
  357. package/dist/cjs/services/shopify/products/shopify-products-serviceV2.js +112 -112
  358. package/dist/cjs/services/shopify/shopify-graphql-transformer.d.ts +8 -8
  359. package/dist/cjs/services/shopify/shopify-graphql-transformer.js +141 -141
  360. package/dist/cjs/types/acuity-types.d.ts +74 -74
  361. package/dist/cjs/types/acuity-types.js +2 -2
  362. package/dist/cjs/types/api-response.d.ts +6 -6
  363. package/dist/cjs/types/api-response.js +2 -2
  364. package/dist/cjs/types/index.d.ts +4 -4
  365. package/dist/cjs/types/index.js +33 -33
  366. package/dist/cjs/types/internal-events/event-detail-types.d.ts +20 -20
  367. package/dist/cjs/types/internal-events/event-detail-types.js +27 -27
  368. package/dist/cjs/types/internal-events/index.d.ts +1 -1
  369. package/dist/cjs/types/internal-events/index.js +17 -17
  370. package/dist/cjs/types/shopify-graphql-types/admin.generated.d.ts +123 -123
  371. package/dist/cjs/types/shopify-graphql-types/admin.generated.js +2 -2
  372. package/dist/cjs/types/shopify-graphql-types/admin.types.d.ts +26289 -26289
  373. package/dist/cjs/types/shopify-graphql-types/admin.types.js +5311 -5311
  374. package/dist/cjs/types/shopify-graphql-types/index.d.ts +2 -2
  375. package/dist/cjs/types/shopify-graphql-types/index.js +18 -18
  376. package/dist/cjs/types/shopify-rest-types.d.ts +767 -767
  377. package/dist/cjs/types/shopify-rest-types.js +2 -2
  378. package/dist/cjs/utils/compression.d.ts +36 -36
  379. package/dist/cjs/utils/compression.js +198 -198
  380. package/dist/cjs/utils/custom-measure-formula-utils.d.ts +6 -6
  381. package/dist/cjs/utils/custom-measure-formula-utils.js +209 -209
  382. package/dist/cjs/utils/index.d.ts +4 -4
  383. package/dist/cjs/utils/index.js +20 -20
  384. package/dist/cjs/utils/retry-envelope.d.ts +12 -12
  385. package/dist/cjs/utils/retry-envelope.js +28 -28
  386. package/dist/cjs/utils/size.d.ts +2 -2
  387. package/dist/cjs/utils/size.js +49 -49
  388. package/dist/esm/__tests__/clients/acuity-client.spec.d.ts +1 -1
  389. package/dist/esm/__tests__/clients/acuity-client.spec.js +41 -41
  390. package/dist/esm/__tests__/clients/cross-platform-compression.spec.d.ts +1 -1
  391. package/dist/esm/__tests__/clients/cross-platform-compression.spec.js +329 -329
  392. package/dist/esm/__tests__/clients/dynamodb-client.spec.d.ts +1 -1
  393. package/dist/esm/__tests__/clients/dynamodb-client.spec.js +192 -192
  394. package/dist/esm/__tests__/clients/sqs-bundled-client.spec.d.ts +1 -1
  395. package/dist/esm/__tests__/clients/sqs-bundled-client.spec.js +906 -906
  396. package/dist/esm/__tests__/clients/sqs-bundling-contracts.spec.d.ts +1 -1
  397. package/dist/esm/__tests__/clients/sqs-bundling-contracts.spec.js +538 -538
  398. package/dist/esm/__tests__/clients/sqs-client.spec.d.ts +1 -1
  399. package/dist/esm/__tests__/clients/sqs-client.spec.js +189 -189
  400. package/dist/esm/__tests__/clients/sqs-unbundle.spec.d.ts +1 -1
  401. package/dist/esm/__tests__/clients/sqs-unbundle.spec.js +1355 -1355
  402. package/dist/esm/__tests__/db/contact-enrichments-db-service.spec.d.ts +1 -1
  403. package/dist/esm/__tests__/db/contact-enrichments-db-service.spec.js +66 -66
  404. package/dist/esm/__tests__/db/destinations-db-service.spec.d.ts +1 -1
  405. package/dist/esm/__tests__/db/destinations-db-service.spec.js +123 -123
  406. package/dist/esm/__tests__/db/products-db-service.spec.d.ts +1 -1
  407. package/dist/esm/__tests__/db/products-db-service.spec.js +87 -87
  408. package/dist/esm/__tests__/db/shared-read-db-services.spec.d.ts +1 -1
  409. package/dist/esm/__tests__/db/shared-read-db-services.spec.js +87 -87
  410. package/dist/esm/__tests__/db/shopify-app-installs-db-service.spec.d.ts +1 -1
  411. package/dist/esm/__tests__/db/shopify-app-installs-db-service.spec.js +102 -102
  412. package/dist/esm/__tests__/db/subscriptions-db-service.spec.d.ts +1 -1
  413. package/dist/esm/__tests__/db/subscriptions-db-service.spec.js +93 -93
  414. package/dist/esm/__tests__/db/user-accounts-db-service.spec.d.ts +1 -1
  415. package/dist/esm/__tests__/db/user-accounts-db-service.spec.js +74 -74
  416. package/dist/esm/__tests__/helpers/account-users-helper.spec.d.ts +1 -1
  417. package/dist/esm/__tests__/helpers/account-users-helper.spec.js +218 -218
  418. package/dist/esm/__tests__/helpers/acuity-helper.spec.d.ts +1 -1
  419. package/dist/esm/__tests__/helpers/acuity-helper.spec.js +67 -67
  420. package/dist/esm/__tests__/helpers/api-key-auth-helper.spec.d.ts +1 -1
  421. package/dist/esm/__tests__/helpers/api-key-auth-helper.spec.js +80 -80
  422. package/dist/esm/__tests__/identity-cache/identity-cache-db-service.spec.d.ts +1 -1
  423. package/dist/esm/__tests__/identity-cache/identity-cache-db-service.spec.js +674 -674
  424. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.d.ts +1 -0
  425. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.js +644 -0
  426. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-db-service-v2-comparison.spec.js.map +1 -0
  427. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.d.ts +1 -0
  428. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.js +325 -0
  429. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service-v2-comparison.spec.js.map +1 -0
  430. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service.spec.d.ts +1 -1
  431. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js +1280 -1138
  432. package/dist/esm/__tests__/identity-cache/identity-cache-dynamodb-service.spec.js.map +1 -1
  433. package/dist/esm/__tests__/identity-cache/identity-cache-tier-routing.spec.d.ts +1 -1
  434. package/dist/esm/__tests__/identity-cache/identity-cache-tier-routing.spec.js +850 -849
  435. package/dist/esm/__tests__/identity-cache/identity-cache-tier-routing.spec.js.map +1 -1
  436. package/dist/esm/__tests__/identity-cache/identity-gateway-service.spec.d.ts +1 -0
  437. package/dist/esm/__tests__/identity-cache/identity-gateway-service.spec.js +565 -0
  438. package/dist/esm/__tests__/identity-cache/identity-gateway-service.spec.js.map +1 -0
  439. package/dist/esm/__tests__/identity-cache/trait-merging-and-staleness.spec.d.ts +1 -1
  440. package/dist/esm/__tests__/identity-cache/trait-merging-and-staleness.spec.js +1058 -1058
  441. package/dist/esm/__tests__/identity-cache/volatile-traits-optimization.spec.d.ts +1 -1
  442. package/dist/esm/__tests__/identity-cache/volatile-traits-optimization.spec.js +816 -816
  443. package/dist/esm/__tests__/integration/sqs-bundling-roundtrip.spec.d.ts +1 -1
  444. package/dist/esm/__tests__/integration/sqs-bundling-roundtrip.spec.js +582 -582
  445. package/dist/esm/__tests__/libs/compress-decompress.spec.d.ts +1 -1
  446. package/dist/esm/__tests__/libs/compress-decompress.spec.js +14 -14
  447. package/dist/esm/__tests__/libs/contacts.spec.d.ts +1 -1
  448. package/dist/esm/__tests__/libs/contacts.spec.js +292 -292
  449. package/dist/esm/__tests__/libs/currency.spec.d.ts +1 -1
  450. package/dist/esm/__tests__/libs/currency.spec.js +218 -218
  451. package/dist/esm/__tests__/libs/dates.spec.d.ts +1 -1
  452. package/dist/esm/__tests__/libs/dates.spec.js +128 -128
  453. package/dist/esm/__tests__/libs/domain.spec.d.ts +1 -1
  454. package/dist/esm/__tests__/libs/domain.spec.js +105 -105
  455. package/dist/esm/__tests__/libs/numbers.spec.d.ts +1 -1
  456. package/dist/esm/__tests__/libs/numbers.spec.js +259 -259
  457. package/dist/esm/__tests__/s3-client/s3-client.spec.d.ts +1 -1
  458. package/dist/esm/__tests__/s3-client/s3-client.spec.js +31 -31
  459. package/dist/esm/__tests__/services/acuity-api-service.spec.d.ts +1 -1
  460. package/dist/esm/__tests__/services/acuity-api-service.spec.js +69 -69
  461. package/dist/esm/__tests__/services/cost/cost-calculation-types.spec.d.ts +1 -1
  462. package/dist/esm/__tests__/services/cost/cost-calculation-types.spec.js +21 -21
  463. package/dist/esm/__tests__/services/cost/cost-calculator-service.spec.d.ts +1 -1
  464. package/dist/esm/__tests__/services/cost/cost-calculator-service.spec.js +3317 -3317
  465. package/dist/esm/__tests__/services/cost/cost-currency-service.spec.d.ts +1 -1
  466. package/dist/esm/__tests__/services/cost/cost-currency-service.spec.js +112 -112
  467. package/dist/esm/__tests__/services/cost/cost-filter-service.spec.d.ts +1 -1
  468. package/dist/esm/__tests__/services/cost/cost-filter-service.spec.js +466 -466
  469. package/dist/esm/__tests__/services/cost/order-cost/order-cost-resolution-service.spec.d.ts +1 -1
  470. package/dist/esm/__tests__/services/cost/order-cost/order-cost-resolution-service.spec.js +204 -204
  471. package/dist/esm/__tests__/services/currency-exchange-rate-lookup-service.spec.d.ts +1 -1
  472. package/dist/esm/__tests__/services/currency-exchange-rate-lookup-service.spec.js +32 -32
  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/route-matcher.spec.d.ts +1 -1
  482. package/dist/esm/__tests__/unit/libs/api-router/route-matcher.spec.js +67 -67
  483. package/dist/esm/__tests__/utils/custom-measure-formula-utils.spec.d.ts +1 -1
  484. package/dist/esm/__tests__/utils/custom-measure-formula-utils.spec.js +137 -137
  485. package/dist/esm/clients/generic/cognito-client.d.ts +23 -23
  486. package/dist/esm/clients/generic/cognito-client.js +204 -204
  487. package/dist/esm/clients/generic/dynamodb-client.d.ts +20 -20
  488. package/dist/esm/clients/generic/dynamodb-client.js +231 -231
  489. package/dist/esm/clients/generic/eventbridge-client.d.ts +14 -14
  490. package/dist/esm/clients/generic/eventbridge-client.js +47 -47
  491. package/dist/esm/clients/generic/http-client.d.ts +14 -14
  492. package/dist/esm/clients/generic/http-client.js +53 -53
  493. package/dist/esm/clients/generic/index.d.ts +13 -13
  494. package/dist/esm/clients/generic/index.js +13 -13
  495. package/dist/esm/clients/generic/lambda-invoke-client.d.ts +10 -10
  496. package/dist/esm/clients/generic/lambda-invoke-client.js +35 -35
  497. package/dist/esm/clients/generic/location-client.d.ts +8 -8
  498. package/dist/esm/clients/generic/location-client.js +27 -27
  499. package/dist/esm/clients/generic/redis-client.d.ts +33 -33
  500. package/dist/esm/clients/generic/redis-client.js +184 -184
  501. package/dist/esm/clients/generic/s3-client.d.ts +23 -23
  502. package/dist/esm/clients/generic/s3-client.js +209 -209
  503. package/dist/esm/clients/generic/singlestore-db-client.d.ts +14 -14
  504. package/dist/esm/clients/generic/singlestore-db-client.js +40 -40
  505. package/dist/esm/clients/generic/sqs-bundled-client.d.ts +15 -15
  506. package/dist/esm/clients/generic/sqs-bundled-client.js +307 -307
  507. package/dist/esm/clients/generic/sqs-bundled-client.types.d.ts +53 -53
  508. package/dist/esm/clients/generic/sqs-bundled-client.types.js +14 -14
  509. package/dist/esm/clients/generic/sqs-client.d.ts +53 -53
  510. package/dist/esm/clients/generic/sqs-client.js +281 -281
  511. package/dist/esm/clients/generic/sqs-unbundle.d.ts +32 -32
  512. package/dist/esm/clients/generic/sqs-unbundle.js +137 -137
  513. package/dist/esm/clients/index.d.ts +3 -3
  514. package/dist/esm/clients/index.js +3 -3
  515. package/dist/esm/clients/internal-api/accounts-client.d.ts +91 -91
  516. package/dist/esm/clients/internal-api/accounts-client.js +125 -125
  517. package/dist/esm/clients/internal-api/cache-lambda-client.d.ts +26 -26
  518. package/dist/esm/clients/internal-api/cache-lambda-client.js +85 -85
  519. package/dist/esm/clients/internal-api/db-management-client.d.ts +18 -18
  520. package/dist/esm/clients/internal-api/db-management-client.js +32 -32
  521. package/dist/esm/clients/internal-api/destinations-client.d.ts +34 -34
  522. package/dist/esm/clients/internal-api/destinations-client.js +75 -75
  523. package/dist/esm/clients/internal-api/event-collector-client.d.ts +20 -20
  524. package/dist/esm/clients/internal-api/event-collector-client.js +32 -32
  525. package/dist/esm/clients/internal-api/identity-client.d.ts +31 -31
  526. package/dist/esm/clients/internal-api/identity-client.js +87 -87
  527. package/dist/esm/clients/internal-api/index.d.ts +9 -9
  528. package/dist/esm/clients/internal-api/index.js +9 -9
  529. package/dist/esm/clients/internal-api/shopify-app-install-client.d.ts +37 -37
  530. package/dist/esm/clients/internal-api/shopify-app-install-client.js +77 -77
  531. package/dist/esm/clients/internal-api/subscriptions-client.d.ts +26 -26
  532. package/dist/esm/clients/internal-api/subscriptions-client.js +73 -73
  533. package/dist/esm/clients/internal-api/users-auth-client.d.ts +35 -35
  534. package/dist/esm/clients/internal-api/users-auth-client.js +106 -106
  535. package/dist/esm/clients/third-party/acuity-client.d.ts +10 -10
  536. package/dist/esm/clients/third-party/acuity-client.js +36 -36
  537. package/dist/esm/clients/third-party/emailable-client.d.ts +7 -7
  538. package/dist/esm/clients/third-party/emailable-client.js +21 -21
  539. package/dist/esm/clients/third-party/exchange-rate-api-client.d.ts +17 -17
  540. package/dist/esm/clients/third-party/exchange-rate-api-client.js +15 -15
  541. package/dist/esm/clients/third-party/index.d.ts +5 -5
  542. package/dist/esm/clients/third-party/index.js +5 -5
  543. package/dist/esm/clients/third-party/loops-client.d.ts +10 -10
  544. package/dist/esm/clients/third-party/loops-client.js +26 -26
  545. package/dist/esm/clients/third-party/shopify/graphql-order-queries.d.ts +25 -25
  546. package/dist/esm/clients/third-party/shopify/graphql-order-queries.js +1 -1
  547. package/dist/esm/clients/third-party/shopify/graphql-product-queries.d.ts +2 -2
  548. package/dist/esm/clients/third-party/shopify/graphql-product-queries.js +2 -2
  549. package/dist/esm/clients/third-party/shopify/shopify-graphql-client.d.ts +10 -10
  550. package/dist/esm/clients/third-party/shopify/shopify-graphql-client.js +157 -157
  551. package/dist/esm/clients/third-party/shopify-client.d.ts +29 -29
  552. package/dist/esm/clients/third-party/shopify-client.js +142 -142
  553. package/dist/esm/constants/index.d.ts +1 -1
  554. package/dist/esm/constants/index.js +1 -1
  555. package/dist/esm/constants/sqs.d.ts +20 -20
  556. package/dist/esm/constants/sqs.js +22 -22
  557. package/dist/esm/helpers/account-users-helper.d.ts +2 -2
  558. package/dist/esm/helpers/account-users-helper.js +18 -18
  559. package/dist/esm/helpers/acuity-helper.d.ts +4 -4
  560. package/dist/esm/helpers/acuity-helper.js +51 -51
  561. package/dist/esm/helpers/api-key-auth-helper.d.ts +9 -9
  562. package/dist/esm/helpers/api-key-auth-helper.js +35 -35
  563. package/dist/esm/helpers/api-key-authorizer-helper.d.ts +36 -36
  564. package/dist/esm/helpers/api-key-authorizer-helper.js +83 -83
  565. package/dist/esm/helpers/identity-cache-helper.d.ts +30 -30
  566. package/dist/esm/helpers/identity-cache-helper.js +248 -248
  567. package/dist/esm/helpers/index.d.ts +10 -10
  568. package/dist/esm/helpers/index.js +10 -10
  569. package/dist/esm/helpers/input-validation-helper.d.ts +3 -3
  570. package/dist/esm/helpers/input-validation-helper.js +18 -18
  571. package/dist/esm/helpers/logging-helper.d.ts +16 -16
  572. package/dist/esm/helpers/logging-helper.js +56 -56
  573. package/dist/esm/helpers/response-helper.d.ts +18 -18
  574. package/dist/esm/helpers/response-helper.js +37 -37
  575. package/dist/esm/helpers/shopify-helper.d.ts +9 -9
  576. package/dist/esm/helpers/shopify-helper.js +21 -21
  577. package/dist/esm/helpers/sqs-utils.d.ts +6 -6
  578. package/dist/esm/helpers/sqs-utils.js +9 -9
  579. package/dist/esm/index.d.ts +7 -7
  580. package/dist/esm/index.js +7 -7
  581. package/dist/esm/libs/api-router/index.d.ts +2 -2
  582. package/dist/esm/libs/api-router/index.js +2 -2
  583. package/dist/esm/libs/api-router/public-api-router.d.ts +3 -3
  584. package/dist/esm/libs/api-router/public-api-router.js +32 -32
  585. package/dist/esm/libs/api-router/route-matcher.d.ts +21 -21
  586. package/dist/esm/libs/api-router/route-matcher.js +30 -30
  587. package/dist/esm/libs/click-id-parser.d.ts +23 -23
  588. package/dist/esm/libs/click-id-parser.js +45 -45
  589. package/dist/esm/libs/compression.d.ts +2 -2
  590. package/dist/esm/libs/compression.js +25 -25
  591. package/dist/esm/libs/contacts.d.ts +7 -7
  592. package/dist/esm/libs/contacts.js +143 -143
  593. package/dist/esm/libs/cookie.d.ts +17 -17
  594. package/dist/esm/libs/cookie.js +70 -70
  595. package/dist/esm/libs/crypto.d.ts +4 -4
  596. package/dist/esm/libs/crypto.js +15 -15
  597. package/dist/esm/libs/csv.d.ts +2 -2
  598. package/dist/esm/libs/csv.js +30 -30
  599. package/dist/esm/libs/currency.d.ts +1 -1
  600. package/dist/esm/libs/currency.js +22 -22
  601. package/dist/esm/libs/dates.d.ts +12 -12
  602. package/dist/esm/libs/dates.js +83 -83
  603. package/dist/esm/libs/domain.d.ts +2 -2
  604. package/dist/esm/libs/domain.js +33 -33
  605. package/dist/esm/libs/emails.d.ts +8 -8
  606. package/dist/esm/libs/emails.js +146 -146
  607. package/dist/esm/libs/http-error.d.ts +21 -21
  608. package/dist/esm/libs/http-error.js +59 -59
  609. package/dist/esm/libs/http-status-codes.d.ts +58 -58
  610. package/dist/esm/libs/http-status-codes.js +59 -59
  611. package/dist/esm/libs/index.d.ts +19 -19
  612. package/dist/esm/libs/index.js +19 -19
  613. package/dist/esm/libs/numbers.d.ts +1 -1
  614. package/dist/esm/libs/numbers.js +11 -11
  615. package/dist/esm/libs/referrer-parser/index.d.ts +2 -2
  616. package/dist/esm/libs/referrer-parser/index.js +2 -2
  617. package/dist/esm/libs/referrer-parser/referrer-data.d.ts +9 -9
  618. package/dist/esm/libs/referrer-parser/referrer-data.js +3304 -3304
  619. package/dist/esm/libs/referrer-parser/referrer-parser-util.d.ts +20 -20
  620. package/dist/esm/libs/referrer-parser/referrer-parser-util.js +124 -124
  621. package/dist/esm/libs/strings.d.ts +3 -3
  622. package/dist/esm/libs/strings.js +40 -40
  623. package/dist/esm/libs/traits.d.ts +6 -6
  624. package/dist/esm/libs/traits.js +54 -54
  625. package/dist/esm/libs/url.d.ts +1 -1
  626. package/dist/esm/libs/url.js +9 -9
  627. package/dist/esm/services/acuity-api-service.d.ts +9 -9
  628. package/dist/esm/services/acuity-api-service.js +69 -69
  629. package/dist/esm/services/cache/generic-cached-object.d.ts +5 -5
  630. package/dist/esm/services/cache/generic-cached-object.js +1 -1
  631. package/dist/esm/services/cache/index.d.ts +1 -1
  632. package/dist/esm/services/cache/index.js +1 -1
  633. package/dist/esm/services/cache/product-cache-service.d.ts +21 -21
  634. package/dist/esm/services/cache/product-cache-service.js +68 -68
  635. package/dist/esm/services/cost/cost-calculation-types.d.ts +69 -69
  636. package/dist/esm/services/cost/cost-calculation-types.js +15 -15
  637. package/dist/esm/services/cost/cost-calculator-service.d.ts +24 -24
  638. package/dist/esm/services/cost/cost-calculator-service.js +450 -450
  639. package/dist/esm/services/cost/cost-currency-service.d.ts +6 -6
  640. package/dist/esm/services/cost/cost-currency-service.js +84 -84
  641. package/dist/esm/services/cost/cost-filter-service.d.ts +10 -10
  642. package/dist/esm/services/cost/cost-filter-service.js +118 -118
  643. package/dist/esm/services/cost/index.d.ts +5 -5
  644. package/dist/esm/services/cost/index.js +5 -5
  645. package/dist/esm/services/cost/order-cost/index.d.ts +2 -2
  646. package/dist/esm/services/cost/order-cost/index.js +2 -2
  647. package/dist/esm/services/cost/order-cost/order-cost-resolution-service.d.ts +23 -23
  648. package/dist/esm/services/cost/order-cost/order-cost-resolution-service.js +355 -355
  649. package/dist/esm/services/cost/order-cost/order-cost-resolution-types.d.ts +37 -37
  650. package/dist/esm/services/cost/order-cost/order-cost-resolution-types.js +1 -1
  651. package/dist/esm/services/currency-exchange-rate-lookup-service.d.ts +12 -12
  652. package/dist/esm/services/currency-exchange-rate-lookup-service.js +90 -90
  653. package/dist/esm/services/db/accounts-db-service.d.ts +9 -9
  654. package/dist/esm/services/db/accounts-db-service.js +29 -29
  655. package/dist/esm/services/db/api-keys-db-service.d.ts +10 -10
  656. package/dist/esm/services/db/api-keys-db-service.js +32 -32
  657. package/dist/esm/services/db/contact-enrichments-db-service.d.ts +15 -15
  658. package/dist/esm/services/db/contact-enrichments-db-service.js +90 -90
  659. package/dist/esm/services/db/currency-exchange-rates-db-service.d.ts +21 -21
  660. package/dist/esm/services/db/currency-exchange-rates-db-service.js +35 -35
  661. package/dist/esm/services/db/custom-measures-db-service.d.ts +14 -14
  662. package/dist/esm/services/db/custom-measures-db-service.js +44 -44
  663. package/dist/esm/services/db/destinations-db-service.d.ts +13 -13
  664. package/dist/esm/services/db/destinations-db-service.js +70 -70
  665. package/dist/esm/services/db/identity-cache-db-service.d.ts +28 -28
  666. package/dist/esm/services/db/identity-cache-db-service.js +313 -313
  667. package/dist/esm/services/db/identity-cache-dynamodb-service/constants.d.ts +3 -0
  668. package/dist/esm/services/db/identity-cache-dynamodb-service/constants.js +4 -0
  669. package/dist/esm/services/db/identity-cache-dynamodb-service/constants.js.map +1 -0
  670. package/dist/esm/services/db/identity-cache-dynamodb-service/helpers.d.ts +14 -0
  671. package/dist/esm/services/db/identity-cache-dynamodb-service/helpers.js +234 -0
  672. package/dist/esm/services/db/identity-cache-dynamodb-service/helpers.js.map +1 -0
  673. package/dist/esm/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.d.ts +24 -0
  674. package/dist/esm/services/db/{identity-cache-dynamodb-service.js → identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.js} +606 -728
  675. package/dist/esm/services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.js.map +1 -0
  676. package/dist/esm/services/db/identity-cache-dynamodb-service/identity-gateway-service.d.ts +22 -0
  677. package/dist/esm/services/db/identity-cache-dynamodb-service/identity-gateway-service.js +368 -0
  678. package/dist/esm/services/db/identity-cache-dynamodb-service/identity-gateway-service.js.map +1 -0
  679. package/dist/esm/services/db/identity-cache-dynamodb-service/index.d.ts +4 -0
  680. package/dist/esm/services/db/identity-cache-dynamodb-service/index.js +5 -0
  681. package/dist/esm/services/db/identity-cache-dynamodb-service/index.js.map +1 -0
  682. package/dist/esm/services/db/identity-cache-dynamodb-service/key-builders.d.ts +12 -0
  683. package/dist/esm/services/db/identity-cache-dynamodb-service/key-builders.js +24 -0
  684. package/dist/esm/services/db/identity-cache-dynamodb-service/key-builders.js.map +1 -0
  685. package/dist/esm/services/db/identity-cache-dynamodb-service/types.d.ts +205 -0
  686. package/dist/esm/services/db/identity-cache-dynamodb-service/types.js +32 -0
  687. package/dist/esm/services/db/identity-cache-dynamodb-service/types.js.map +1 -0
  688. package/dist/esm/services/db/index.d.ts +22 -19
  689. package/dist/esm/services/db/index.js +22 -19
  690. package/dist/esm/services/db/index.js.map +1 -1
  691. package/dist/esm/services/db/log-events-db-service.d.ts +11 -11
  692. package/dist/esm/services/db/log-events-db-service.js +177 -177
  693. package/dist/esm/services/db/pixels-db-service.d.ts +8 -8
  694. package/dist/esm/services/db/pixels-db-service.js +31 -31
  695. package/dist/esm/services/db/products-db-service-types.d.ts +10 -10
  696. package/dist/esm/services/db/products-db-service-types.js +1 -1
  697. package/dist/esm/services/db/products-db-service.d.ts +19 -19
  698. package/dist/esm/services/db/products-db-service.js +277 -277
  699. package/dist/esm/services/db/purchasable-contacts-db-service.d.ts +9 -9
  700. package/dist/esm/services/db/purchasable-contacts-db-service.js +39 -39
  701. package/dist/esm/services/db/purchased-contacts/index.d.ts +2 -2
  702. package/dist/esm/services/db/purchased-contacts/index.js +2 -2
  703. package/dist/esm/services/db/purchased-contacts/purchased-contacts-db-service.d.ts +18 -18
  704. package/dist/esm/services/db/purchased-contacts/purchased-contacts-db-service.js +148 -148
  705. package/dist/esm/services/db/purchased-contacts/types.d.ts +11 -11
  706. package/dist/esm/services/db/purchased-contacts/types.js +1 -1
  707. package/dist/esm/services/db/shopify-app-installs-db-service.d.ts +10 -10
  708. package/dist/esm/services/db/shopify-app-installs-db-service.js +48 -48
  709. package/dist/esm/services/db/shopify-products-cache-db-service.d.ts +16 -16
  710. package/dist/esm/services/db/shopify-products-cache-db-service.js +66 -66
  711. package/dist/esm/services/db/subscriptions-db-service.d.ts +11 -11
  712. package/dist/esm/services/db/subscriptions-db-service.js +34 -34
  713. package/dist/esm/services/db/tracking-events-db-service.d.ts +21 -21
  714. package/dist/esm/services/db/tracking-events-db-service.js +184 -184
  715. package/dist/esm/services/db/user-accounts-db-service.d.ts +7 -7
  716. package/dist/esm/services/db/user-accounts-db-service.js +13 -13
  717. package/dist/esm/services/email-verification/contact-email-verification-service.d.ts +7 -7
  718. package/dist/esm/services/email-verification/contact-email-verification-service.js +97 -97
  719. package/dist/esm/services/email-verification/email-verification-service.d.ts +19 -19
  720. package/dist/esm/services/email-verification/email-verification-service.js +127 -127
  721. package/dist/esm/services/email-verification/index.d.ts +2 -2
  722. package/dist/esm/services/email-verification/index.js +2 -2
  723. package/dist/esm/services/eventbridge-integration-service.d.ts +9 -9
  724. package/dist/esm/services/eventbridge-integration-service.js +24 -24
  725. package/dist/esm/services/events/index.d.ts +3 -3
  726. package/dist/esm/services/events/index.js +3 -3
  727. package/dist/esm/services/events/log-event-service.d.ts +19 -19
  728. package/dist/esm/services/events/log-event-service.js +73 -73
  729. package/dist/esm/services/events/metric-event-service.d.ts +9 -9
  730. package/dist/esm/services/events/metric-event-service.js +45 -45
  731. package/dist/esm/services/events/tracking-event-sqs-service.d.ts +8 -8
  732. package/dist/esm/services/events/tracking-event-sqs-service.js +30 -30
  733. package/dist/esm/services/generic-cache-service.d.ts +7 -7
  734. package/dist/esm/services/generic-cache-service.js +29 -29
  735. package/dist/esm/services/index.d.ts +11 -11
  736. package/dist/esm/services/index.js +11 -11
  737. package/dist/esm/services/ipdata-lookup-service.d.ts +20 -20
  738. package/dist/esm/services/ipdata-lookup-service.js +108 -108
  739. package/dist/esm/services/shopify/index.d.ts +2 -2
  740. package/dist/esm/services/shopify/index.js +2 -2
  741. package/dist/esm/services/shopify/products/index.d.ts +1 -1
  742. package/dist/esm/services/shopify/products/index.js +1 -1
  743. package/dist/esm/services/shopify/products/shopify-products-serviceV2.d.ts +17 -17
  744. package/dist/esm/services/shopify/products/shopify-products-serviceV2.js +108 -108
  745. package/dist/esm/services/shopify/shopify-graphql-transformer.d.ts +8 -8
  746. package/dist/esm/services/shopify/shopify-graphql-transformer.js +138 -138
  747. package/dist/esm/types/acuity-types.d.ts +74 -74
  748. package/dist/esm/types/acuity-types.js +1 -1
  749. package/dist/esm/types/api-response.d.ts +6 -6
  750. package/dist/esm/types/api-response.js +1 -1
  751. package/dist/esm/types/index.d.ts +4 -4
  752. package/dist/esm/types/index.js +4 -4
  753. package/dist/esm/types/internal-events/event-detail-types.d.ts +20 -20
  754. package/dist/esm/types/internal-events/event-detail-types.js +24 -24
  755. package/dist/esm/types/internal-events/index.d.ts +1 -1
  756. package/dist/esm/types/internal-events/index.js +1 -1
  757. package/dist/esm/types/shopify-graphql-types/admin.generated.d.ts +123 -123
  758. package/dist/esm/types/shopify-graphql-types/admin.generated.js +1 -1
  759. package/dist/esm/types/shopify-graphql-types/admin.types.d.ts +26289 -26289
  760. package/dist/esm/types/shopify-graphql-types/admin.types.js +5299 -5299
  761. package/dist/esm/types/shopify-graphql-types/index.d.ts +2 -2
  762. package/dist/esm/types/shopify-graphql-types/index.js +2 -2
  763. package/dist/esm/types/shopify-rest-types.d.ts +767 -767
  764. package/dist/esm/types/shopify-rest-types.js +1 -1
  765. package/dist/esm/utils/compression.d.ts +36 -36
  766. package/dist/esm/utils/compression.js +187 -187
  767. package/dist/esm/utils/custom-measure-formula-utils.d.ts +6 -6
  768. package/dist/esm/utils/custom-measure-formula-utils.js +201 -201
  769. package/dist/esm/utils/index.d.ts +4 -4
  770. package/dist/esm/utils/index.js +4 -4
  771. package/dist/esm/utils/retry-envelope.d.ts +12 -12
  772. package/dist/esm/utils/retry-envelope.js +22 -22
  773. package/dist/esm/utils/size.d.ts +2 -2
  774. package/dist/esm/utils/size.js +44 -44
  775. package/package.json +135 -134
  776. package/dist/cjs/services/db/identity-cache-dynamodb-service.d.ts +0 -44
  777. package/dist/cjs/services/db/identity-cache-dynamodb-service.js.map +0 -1
  778. package/dist/esm/services/db/identity-cache-dynamodb-service.d.ts +0 -44
  779. package/dist/esm/services/db/identity-cache-dynamodb-service.js.map +0 -1
@@ -1,1141 +1,1283 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const mockInvokeFunction = jest.fn();
4
- jest.mock('../../clients/index.js', () => ({
5
- DynamoDbClient: {
6
- safeGet: jest.fn(),
7
- safePut: jest.fn(),
8
- safeDelete: jest.fn(),
9
- safeBatchGet: jest.fn(),
10
- safeBatchWrite: jest.fn(),
11
- safeQueryByGSI: jest.fn(),
12
- batchWrite: jest.fn(),
13
- },
14
- LambdaInvokeClient: jest.fn().mockImplementation(() => ({
15
- invokeFunction: mockInvokeFunction,
16
- })),
17
- }));
18
- jest.mock('../../helpers/index.js', () => ({
19
- Logger: {
20
- debug: jest.fn(),
21
- info: jest.fn(),
22
- warn: jest.fn(),
23
- error: jest.fn(),
24
- },
25
- }));
26
- const identity_cache_dynamodb_service_js_1 = require("../../services/db/identity-cache-dynamodb-service.js");
27
- const index_js_1 = require("../../clients/index.js");
28
- const mockedDynamoDbClient = index_js_1.DynamoDbClient;
29
- describe('IdentityCacheDynamoDbService', () => {
30
- beforeEach(() => {
31
- jest.clearAllMocks();
32
- mockedDynamoDbClient.safeGet.mockReset();
33
- mockedDynamoDbClient.safePut.mockReset();
34
- mockedDynamoDbClient.safeDelete.mockReset();
35
- mockedDynamoDbClient.safeBatchGet.mockReset();
36
- mockedDynamoDbClient.safeBatchWrite.mockReset();
37
- mockedDynamoDbClient.safeQueryByGSI.mockReset();
38
- mockedDynamoDbClient.batchWrite.mockReset();
39
- mockInvokeFunction.mockReset();
40
- identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.lambdaInvokeClient = undefined;
41
- });
42
- describe('getIdentityWithCaching', () => {
43
- const pixelId = 'pixel123';
44
- const lambdaArn = 'arn:aws:lambda:us-east-1:123456789:function:identity-private';
45
- describe('cache hit (fresh cache)', () => {
46
- it('should return cached identity without invoking Neptune Lambda', async () => {
47
- const incomingIdentity = {
48
- identityId: 'identity456',
49
- traits: { emails: ['test@email.com'] },
50
- };
51
- const cachedResponse = {
52
- pk: `identity#${pixelId}#identity456`,
53
- pixelId,
54
- identityId: 'identity456',
55
- response: {
56
- identityId: 'identity456',
57
- traits: { emails: ['test@email.com'] },
58
- },
59
- updatedAt: new Date().toISOString(),
60
- };
61
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
62
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
63
- expect(result).toBeDefined();
64
- expect(result?.identityId).toBe('identity456');
65
- expect(mockInvokeFunction).not.toHaveBeenCalled();
66
- });
67
- });
68
- describe('cache miss', () => {
69
- it('should invoke Neptune Lambda and return resolved identity', async () => {
70
- const incomingIdentity = {
71
- identityId: 'identity456',
72
- traits: { emails: ['test@email.com'] },
73
- };
74
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
75
- mockInvokeFunction.mockResolvedValueOnce({
76
- statusCode: 200,
77
- body: JSON.stringify({
78
- identity: {
79
- identityId: 'resolved-identity',
80
- traits: { emails: ['test@email.com', 'resolved@email.com'] },
81
- },
82
- }),
83
- });
84
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
85
- expect(mockInvokeFunction).toHaveBeenCalledWith(lambdaArn, {
86
- pixelId,
87
- context: { identity: incomingIdentity },
88
- });
89
- expect(result?.identityId).toBe('resolved-identity');
90
- expect(mockedDynamoDbClient.safeBatchWrite).toHaveBeenCalled();
91
- });
92
- it('should return incomingIdentity when Neptune Lambda fails to resolve', async () => {
93
- const incomingIdentity = {
94
- identityId: 'identity456',
95
- traits: { emails: ['test@email.com'] },
96
- };
97
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
98
- mockInvokeFunction.mockResolvedValueOnce({
99
- statusCode: 200,
100
- body: JSON.stringify({ identity: undefined }),
101
- });
102
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
103
- expect(result).toEqual(incomingIdentity);
104
- });
105
- });
106
- describe('cache stale', () => {
107
- it('should invoke Neptune Lambda when cache is stale', async () => {
108
- const incomingIdentity = {
109
- identityId: 'identity456',
110
- traits: { emails: ['old@email.com', 'new@email.com'] },
111
- };
112
- const cachedResponse = {
113
- pk: `identity#${pixelId}#identity456`,
114
- pixelId,
115
- identityId: 'identity456',
116
- response: {
117
- identityId: 'identity456',
118
- traits: { emails: ['old@email.com'] },
119
- },
120
- updatedAt: new Date().toISOString(),
121
- };
122
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
123
- mockInvokeFunction.mockResolvedValueOnce({
124
- statusCode: 200,
125
- body: JSON.stringify({
126
- identity: {
127
- identityId: 'identity456',
128
- traits: { emails: ['old@email.com', 'new@email.com'] },
129
- },
130
- }),
131
- });
132
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
133
- expect(mockInvokeFunction).toHaveBeenCalled();
134
- expect(result?.identityId).toBe('identity456');
135
- expect(result?.traits?.emails).toContain('new@email.com');
136
- });
137
- it('should use merged cache identity when Neptune fails', async () => {
138
- const incomingIdentity = {
139
- identityId: 'identity456',
140
- traits: { emails: ['old@email.com', 'new@email.com'] },
141
- };
142
- const cachedResponse = {
143
- pk: `identity#${pixelId}#identity456`,
144
- pixelId,
145
- identityId: 'identity456',
146
- response: {
147
- identityId: 'identity456',
148
- traits: { emails: ['old@email.com'] },
149
- },
150
- updatedAt: new Date().toISOString(),
151
- };
152
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
153
- mockInvokeFunction.mockResolvedValueOnce({
154
- statusCode: 500,
155
- body: 'Internal Server Error',
156
- });
157
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
158
- expect(result).toBeDefined();
159
- expect(result?.identityId).toBe('identity456');
160
- });
161
- });
162
- describe('Neptune Lambda invocation', () => {
163
- it('should write-back Neptune response to cache on success', async () => {
164
- const incomingIdentity = {
165
- traits: { emails: ['test@email.com'] },
166
- };
167
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
168
- mockInvokeFunction.mockResolvedValueOnce({
169
- statusCode: 200,
170
- body: JSON.stringify({
171
- identity: {
172
- identityId: 'neptune-resolved-id',
173
- traits: { emails: ['test@email.com'] },
174
- },
175
- }),
176
- });
177
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
178
- expect(mockedDynamoDbClient.safeBatchWrite).toHaveBeenCalled();
179
- const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
180
- const identityPks = writtenItems.map((item) => item.pk);
181
- expect(identityPks).toContain(`identity#${pixelId}#neptune-resolved-id`);
182
- });
183
- it('should not write to cache when Neptune returns undefined', async () => {
184
- const incomingIdentity = {
185
- traits: { emails: ['test@email.com'] },
186
- };
187
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
188
- mockInvokeFunction.mockResolvedValueOnce({
189
- statusCode: 200,
190
- body: JSON.stringify({ identity: undefined }),
191
- });
192
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
193
- expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
194
- });
195
- });
196
- describe('error handling (fail-open)', () => {
197
- it('should return undefined when pixelId is missing', async () => {
198
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityWithCaching(null, { identityId: 'test-id' }, lambdaArn);
199
- expect(result).toBeUndefined();
200
- expect(mockInvokeFunction).not.toHaveBeenCalled();
201
- });
202
- it('should return undefined when incomingIdentity is missing', async () => {
203
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, null, lambdaArn);
204
- expect(result).toBeUndefined();
205
- expect(mockInvokeFunction).not.toHaveBeenCalled();
206
- });
207
- it('should return undefined when Neptune Lambda throws', async () => {
208
- const incomingIdentity = {
209
- identityId: 'identity456',
210
- traits: { emails: ['test@email.com'] },
211
- };
212
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
213
- mockInvokeFunction.mockRejectedValueOnce(new Error('Lambda invocation failed'));
214
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
215
- expect(result).toEqual(incomingIdentity);
216
- });
217
- it('should not throw when cache write fails after Neptune success', async () => {
218
- const incomingIdentity = {
219
- traits: { emails: ['test@email.com'] },
220
- };
221
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
222
- mockInvokeFunction.mockResolvedValueOnce({
223
- statusCode: 200,
224
- body: JSON.stringify({
225
- identity: {
226
- identityId: 'neptune-resolved-id',
227
- traits: { emails: ['test@email.com'] },
228
- },
229
- }),
230
- });
231
- mockedDynamoDbClient.safeBatchWrite.mockRejectedValueOnce(new Error('DynamoDB write failed'));
232
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
233
- expect(result?.identityId).toBe('neptune-resolved-id');
234
- });
235
- });
236
- });
237
- describe('Key Builders', () => {
238
- describe('buildIdentityPk', () => {
239
- it('should build correct pk for identity lookup', async () => {
240
- const pixelId = 'pixel123';
241
- const identityId = 'identity456';
242
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
243
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, { identityId });
244
- expect(mockedDynamoDbClient.safeGet).toHaveBeenCalledWith(expect.any(String), 'pk', `identity#${pixelId}#${identityId}`);
245
- });
246
- });
247
- describe('buildEmailPk', () => {
248
- it('should build correct pk for email lookup (lowercase)', async () => {
249
- const pixelId = 'pixel123';
250
- const email = 'Test@Email.COM';
251
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
252
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
253
- traits: { emails: [email] },
254
- });
255
- expect(mockedDynamoDbClient.safeGet).toHaveBeenCalledWith(expect.any(String), 'pk', `email#${pixelId}#test@email.com`);
256
- });
257
- });
258
- describe('buildUserIdPk (no longer used for lookup)', () => {
259
- it('should NOT lookup by userId (optimization: email-only secondary lookup)', async () => {
260
- const pixelId = 'pixel123';
261
- const userId = ' user789 ';
262
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
263
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
264
- traits: { userIds: [userId] },
265
- });
266
- expect(mockedDynamoDbClient.safeBatchGet).not.toHaveBeenCalled();
267
- });
268
- });
269
- });
270
- describe('getIdentityFromCache', () => {
271
- const pixelId = 'pixel123';
272
- describe('with identityId (primary lookup)', () => {
273
- it('should return cache hit when identity found', async () => {
274
- const identityId = 'identity456';
275
- const cachedResponse = {
276
- pk: `identity#${pixelId}#${identityId}`,
277
- pixelId,
278
- identityId,
279
- response: {
280
- identityId,
281
- traits: { emails: ['test@email.com'] },
282
- },
283
- updatedAt: new Date().toISOString(),
284
- };
285
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
286
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
287
- identityId,
288
- traits: { emails: ['test@email.com'] },
289
- });
290
- expect(result.resolvedIdentity).toBeDefined();
291
- expect(result.resolvedIdentity?.identityId).toBe(identityId);
292
- expect(result.isCacheStale).toBe(false);
293
- });
294
- it('should return cache miss when identity not found', async () => {
295
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
296
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
297
- identityId: 'unknown-id',
298
- });
299
- expect(result.resolvedIdentity).toBeUndefined();
300
- expect(result.isCacheStale).toBe(true);
301
- });
302
- it('should NOT fallback to secondary keys when identityId lookup misses', async () => {
303
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
304
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
305
- identityId: 'unknown-id',
306
- traits: { emails: ['test@email.com'] },
307
- });
308
- expect(mockedDynamoDbClient.safeBatchGet).not.toHaveBeenCalled();
309
- });
310
- it('should detect stale cache when new traits present', async () => {
311
- const identityId = 'identity456';
312
- const cachedResponse = {
313
- pk: `identity#${pixelId}#${identityId}`,
314
- pixelId,
315
- identityId,
316
- response: {
317
- identityId,
318
- traits: { emails: ['old@email.com'] },
319
- },
320
- updatedAt: new Date().toISOString(),
321
- };
322
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
323
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
324
- identityId,
325
- traits: { emails: ['old@email.com', 'new@email.com'] },
326
- });
327
- expect(result.resolvedIdentity).toBeDefined();
328
- expect(result.isCacheStale).toBe(true);
329
- });
330
- });
331
- describe('without identityId (secondary lookup)', () => {
332
- it('should use single get for email lookup (pointer-based)', async () => {
333
- const pointerRecord = {
334
- pk: `email#${pixelId}#test@email.com`,
335
- pixelId,
336
- identityId: 'resolved-id',
337
- gsi1pk: 'resolved-id',
338
- updatedAt: new Date().toISOString(),
339
- };
340
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(pointerRecord);
341
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
342
- traits: { emails: ['test@email.com'], userIds: ['user123'] },
343
- });
344
- expect(mockedDynamoDbClient.safeGet).toHaveBeenCalledWith(expect.any(String), 'pk', `email#${pixelId}#test@email.com`);
345
- expect(mockedDynamoDbClient.safeBatchGet).not.toHaveBeenCalled();
346
- expect(result.resolvedIdentity).toBeDefined();
347
- expect(result.resolvedIdentity?.traits?.emails).toContain('test@email.com');
348
- expect(result.isCacheStale).toBe(true);
349
- });
350
- it('should return discovered identityId with stale flag for Neptune resolution', async () => {
351
- const pointerRecord = {
352
- pk: `email#${pixelId}#test@email.com`,
353
- pixelId,
354
- identityId: 'discovered-id',
355
- gsi1pk: 'discovered-id',
356
- updatedAt: new Date().toISOString(),
357
- };
358
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(pointerRecord);
359
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
360
- traits: { emails: ['test@email.com'], userIds: ['user123'] },
361
- });
362
- expect(result.resolvedIdentity).toBeDefined();
363
- expect(result.resolvedIdentity?.identityId).toBe('discovered-id');
364
- expect(result.isCacheStale).toBe(true);
365
- });
366
- it('should return cache miss when no secondary keys match', async () => {
367
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
368
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
369
- traits: { emails: ['unknown@email.com'] },
370
- });
371
- expect(result.resolvedIdentity).toBeUndefined();
372
- expect(result.isCacheStale).toBe(true);
373
- });
374
- it('should return cache miss when no emails to lookup', async () => {
375
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
376
- traits: { userIds: ['user123'] },
377
- });
378
- expect(result.resolvedIdentity).toBeUndefined();
379
- expect(result.isCacheStale).toBe(true);
380
- expect(mockedDynamoDbClient.safeGet).not.toHaveBeenCalled();
381
- });
382
- it('should return cache miss when empty traits', async () => {
383
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
384
- traits: {},
385
- });
386
- expect(result.resolvedIdentity).toBeUndefined();
387
- expect(result.isCacheStale).toBe(true);
388
- });
389
- });
390
- describe('error handling (fail-open)', () => {
391
- it('should return cache miss on safeGet error', async () => {
392
- mockedDynamoDbClient.safeGet.mockRejectedValueOnce(new Error('DynamoDB error'));
393
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
394
- identityId: 'test-id',
395
- });
396
- expect(result.resolvedIdentity).toBeUndefined();
397
- expect(result.isCacheStale).toBe(true);
398
- });
399
- it('should return cache miss on safeGet error for email lookup', async () => {
400
- mockedDynamoDbClient.safeGet.mockRejectedValueOnce(new Error('DynamoDB error'));
401
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
402
- traits: { emails: ['test@email.com'] },
403
- });
404
- expect(result.resolvedIdentity).toBeUndefined();
405
- expect(result.isCacheStale).toBe(true);
406
- });
407
- it('should return cache miss for null pixelId', async () => {
408
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(null, {
409
- identityId: 'test-id',
410
- });
411
- expect(result.resolvedIdentity).toBeUndefined();
412
- expect(result.isCacheStale).toBe(true);
413
- });
414
- it('should return cache miss for null incomingIdentity', async () => {
415
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, null);
416
- expect(result.resolvedIdentity).toBeUndefined();
417
- expect(result.isCacheStale).toBe(true);
418
- });
419
- });
420
- });
421
- describe('updateIdentityCache', () => {
422
- const pixelId = 'pixel123';
423
- it('should write identity with email pointer (no full blob on secondary keys)', async () => {
424
- const identity = {
425
- identityId: 'identity456',
426
- traits: {
427
- emails: ['test@email.com', 'other@email.com'],
428
- userIds: ['user1', 'user2'],
429
- },
430
- };
431
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.updateIdentityCache(pixelId, identity);
432
- expect(mockedDynamoDbClient.safeBatchWrite).toHaveBeenCalledTimes(1);
433
- const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
434
- expect(writtenItems).toHaveLength(3);
435
- const pks = writtenItems.map((item) => item.pk);
436
- expect(pks).toContain(`identity#${pixelId}#identity456`);
437
- expect(pks).toContain(`email#${pixelId}#test@email.com`);
438
- expect(pks).toContain(`email#${pixelId}#other@email.com`);
439
- expect(pks).not.toContain(`user_id#${pixelId}#user1`);
440
- expect(pks).not.toContain(`user_id#${pixelId}#user2`);
441
- const identityItem = writtenItems.find((item) => item.pk.startsWith('identity#'));
442
- expect(identityItem).toBeDefined();
443
- expect(identityItem.response).toBeDefined();
444
- expect(identityItem.response.identityId).toBe('identity456');
445
- const emailItem = writtenItems.find((item) => item.pk.startsWith('email#'));
446
- expect(emailItem).toBeDefined();
447
- expect(emailItem.response).toBeUndefined();
448
- expect(emailItem.identityId).toBe('identity456');
449
- });
450
- it('should not throw on safeBatchWrite error (fail-open)', async () => {
451
- mockedDynamoDbClient.safeBatchWrite.mockRejectedValueOnce(new Error('DynamoDB error'));
452
- const identity = {
453
- identityId: 'identity456',
454
- traits: { emails: ['test@email.com'] },
455
- };
456
- await expect(identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.updateIdentityCache(pixelId, identity)).resolves.toBeUndefined();
457
- });
458
- it('should return early for missing identityId', async () => {
459
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.updateIdentityCache(pixelId, {});
460
- expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
461
- });
462
- it('should return early for missing pixelId', async () => {
463
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.updateIdentityCache(null, {
464
- identityId: 'test-id',
465
- });
466
- expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
467
- });
468
- it('should handle identity with no traits', async () => {
469
- const identity = {
470
- identityId: 'identity456',
471
- };
472
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.updateIdentityCache(pixelId, identity);
473
- const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
474
- expect(writtenItems).toHaveLength(1);
475
- });
476
- it('should include gsi1pk for reverse lookups', async () => {
477
- const identity = {
478
- identityId: 'identity456',
479
- traits: { emails: ['test@email.com'] },
480
- };
481
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.updateIdentityCache(pixelId, identity);
482
- const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
483
- for (const item of writtenItems) {
484
- expect(item.gsi1pk).toBe('identity456');
485
- }
486
- });
487
- });
488
- describe('getIdentityMap', () => {
489
- it('should return identity map when found', async () => {
490
- const mockMap = {
491
- pk: 'identity_map#pixel123#identity456',
492
- pixelId: 'pixel123',
493
- identityId: 'identity456',
494
- linkedIdentities: ['identity789'],
495
- updatedAt: new Date().toISOString(),
496
- };
497
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(mockMap);
498
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityMap('pixel123', 'identity456');
499
- expect(result).toEqual(mockMap);
500
- expect(mockedDynamoDbClient.safeGet).toHaveBeenCalledWith(expect.any(String), 'pk', 'identity_map#pixel123#identity456');
501
- });
502
- it('should return undefined when not found', async () => {
503
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
504
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityMap('pixel123', 'identity456');
505
- expect(result).toBeUndefined();
506
- });
507
- it('should return undefined for missing params (fail-open)', async () => {
508
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityMap('', '');
509
- expect(result).toBeUndefined();
510
- expect(mockedDynamoDbClient.safeGet).not.toHaveBeenCalled();
511
- });
512
- it('should return undefined when only pixelId is missing', async () => {
513
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityMap('', 'identity456');
514
- expect(result).toBeUndefined();
515
- expect(mockedDynamoDbClient.safeGet).not.toHaveBeenCalled();
516
- });
517
- it('should return undefined when only identityId is missing', async () => {
518
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityMap('pixel123', '');
519
- expect(result).toBeUndefined();
520
- expect(mockedDynamoDbClient.safeGet).not.toHaveBeenCalled();
521
- });
522
- it('should return undefined on safeGet error (fail-open)', async () => {
523
- mockedDynamoDbClient.safeGet.mockRejectedValueOnce(new Error('DynamoDB error'));
524
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityMap('pixel123', 'identity456');
525
- expect(result).toBeUndefined();
526
- });
527
- });
528
- describe('getForcePurgeFlag', () => {
529
- it('should return true when flag exists', async () => {
530
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce({
531
- pk: 'force_purge#pixel123#identity456',
532
- });
533
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getForcePurgeFlag('pixel123', 'identity456');
534
- expect(result).toBe(true);
535
- });
536
- it('should return false when flag not found', async () => {
537
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
538
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getForcePurgeFlag('pixel123', 'identity456');
539
- expect(result).toBe(false);
540
- });
541
- it('should return false on error (fail-open)', async () => {
542
- mockedDynamoDbClient.safeGet.mockRejectedValueOnce(new Error('DynamoDB error'));
543
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getForcePurgeFlag('pixel123', 'identity456');
544
- expect(result).toBe(false);
545
- });
546
- it('should return false for missing params', async () => {
547
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getForcePurgeFlag('', '');
548
- expect(result).toBe(false);
549
- expect(mockedDynamoDbClient.safeGet).not.toHaveBeenCalled();
550
- });
551
- });
552
- describe('setForcePurgeFlag', () => {
553
- it('should set flag with TTL', async () => {
554
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.setForcePurgeFlag('pixel123', 'identity456');
555
- expect(mockedDynamoDbClient.safePut).toHaveBeenCalledTimes(1);
556
- const putItem = mockedDynamoDbClient.safePut.mock.calls[0][1];
557
- expect(putItem.pk).toBe('force_purge#pixel123#identity456');
558
- expect(putItem.pixelId).toBe('pixel123');
559
- expect(putItem.identityId).toBe('identity456');
560
- expect(putItem.ttl).toBeGreaterThan(0);
561
- });
562
- it('should accept custom TTL', async () => {
563
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.setForcePurgeFlag('pixel123', 'identity456', 3600);
564
- const putItem = mockedDynamoDbClient.safePut.mock.calls[0][1];
565
- expect(putItem.ttl).toBeGreaterThan(0);
566
- });
567
- it('should include createdAt timestamp', async () => {
568
- const before = new Date().toISOString();
569
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.setForcePurgeFlag('pixel123', 'identity456');
570
- const after = new Date().toISOString();
571
- const putItem = mockedDynamoDbClient.safePut.mock.calls[0][1];
572
- expect(putItem.createdAt).toBeDefined();
573
- expect(putItem.createdAt >= before).toBe(true);
574
- expect(putItem.createdAt <= after).toBe(true);
575
- });
576
- it('should return early when pixelId is missing', async () => {
577
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.setForcePurgeFlag('', 'identity456');
578
- expect(mockedDynamoDbClient.safePut).not.toHaveBeenCalled();
579
- });
580
- it('should return early when identityId is missing', async () => {
581
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.setForcePurgeFlag('pixel123', '');
582
- expect(mockedDynamoDbClient.safePut).not.toHaveBeenCalled();
583
- });
584
- it('should not throw on error (fail-open)', async () => {
585
- mockedDynamoDbClient.safePut.mockRejectedValueOnce(new Error('DynamoDB error'));
586
- await expect(identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.setForcePurgeFlag('pixel123', 'identity456')).resolves.toBeUndefined();
587
- });
588
- });
589
- describe('putIdentityMap', () => {
590
- it('should write identity map', async () => {
591
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.putIdentityMap('pixel123', 'identity456', ['linked1', 'linked2']);
592
- expect(mockedDynamoDbClient.safePut).toHaveBeenCalledTimes(1);
593
- const putItem = mockedDynamoDbClient.safePut.mock.calls[0][1];
594
- expect(putItem.pk).toBe('identity_map#pixel123#identity456');
595
- expect(putItem.linkedIdentities).toEqual(['linked1', 'linked2']);
596
- });
597
- it('should return early for missing params', async () => {
598
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.putIdentityMap('', '', []);
599
- expect(mockedDynamoDbClient.safePut).not.toHaveBeenCalled();
600
- });
601
- it('should return early when only pixelId is missing', async () => {
602
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.putIdentityMap('', 'identity456', ['linked1']);
603
- expect(mockedDynamoDbClient.safePut).not.toHaveBeenCalled();
604
- });
605
- it('should return early when only identityId is missing', async () => {
606
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.putIdentityMap('pixel123', '', ['linked1']);
607
- expect(mockedDynamoDbClient.safePut).not.toHaveBeenCalled();
608
- });
609
- it('should include updatedAt timestamp', async () => {
610
- const before = new Date().toISOString();
611
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.putIdentityMap('pixel123', 'identity456', ['linked1']);
612
- const after = new Date().toISOString();
613
- const putItem = mockedDynamoDbClient.safePut.mock.calls[0][1];
614
- expect(putItem.updatedAt).toBeDefined();
615
- expect(putItem.updatedAt >= before).toBe(true);
616
- expect(putItem.updatedAt <= after).toBe(true);
617
- });
618
- it('should not throw on safePut error (fail-open)', async () => {
619
- mockedDynamoDbClient.safePut.mockRejectedValueOnce(new Error('DynamoDB error'));
620
- await expect(identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.putIdentityMap('pixel123', 'identity456', ['linked1'])).resolves.toBeUndefined();
621
- });
622
- });
623
- describe('deleteIdentityMap', () => {
624
- it('should delete identity map', async () => {
625
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.deleteIdentityMap('pixel123', 'identity456');
626
- expect(mockedDynamoDbClient.safeDelete).toHaveBeenCalledWith(expect.any(String), 'pk', 'identity_map#pixel123#identity456');
627
- });
628
- it('should return early for missing params', async () => {
629
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.deleteIdentityMap('', '');
630
- expect(mockedDynamoDbClient.safeDelete).not.toHaveBeenCalled();
631
- });
632
- it('should return early when only pixelId is missing', async () => {
633
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.deleteIdentityMap('', 'identity456');
634
- expect(mockedDynamoDbClient.safeDelete).not.toHaveBeenCalled();
635
- });
636
- it('should return early when only identityId is missing', async () => {
637
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.deleteIdentityMap('pixel123', '');
638
- expect(mockedDynamoDbClient.safeDelete).not.toHaveBeenCalled();
639
- });
640
- it('should not throw on safeDelete error (fail-open)', async () => {
641
- mockedDynamoDbClient.safeDelete.mockRejectedValueOnce(new Error('DynamoDB error'));
642
- await expect(identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.deleteIdentityMap('pixel123', 'identity456')).resolves.toBeUndefined();
643
- });
644
- });
645
- describe('deleteForcePurgeFlag', () => {
646
- it('should delete force purge flag', async () => {
647
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.deleteForcePurgeFlag('pixel123', 'identity456');
648
- expect(mockedDynamoDbClient.safeDelete).toHaveBeenCalledWith(expect.any(String), 'pk', 'force_purge#pixel123#identity456');
649
- });
650
- it('should return early for missing params', async () => {
651
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.deleteForcePurgeFlag('', '');
652
- expect(mockedDynamoDbClient.safeDelete).not.toHaveBeenCalled();
653
- });
654
- it('should return early when only pixelId is missing', async () => {
655
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.deleteForcePurgeFlag('', 'identity456');
656
- expect(mockedDynamoDbClient.safeDelete).not.toHaveBeenCalled();
657
- });
658
- it('should return early when only identityId is missing', async () => {
659
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.deleteForcePurgeFlag('pixel123', '');
660
- expect(mockedDynamoDbClient.safeDelete).not.toHaveBeenCalled();
661
- });
662
- it('should not throw on safeDelete error (fail-open)', async () => {
663
- mockedDynamoDbClient.safeDelete.mockRejectedValueOnce(new Error('DynamoDB error'));
664
- await expect(identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.deleteForcePurgeFlag('pixel123', 'identity456')).resolves.toBeUndefined();
665
- });
666
- });
667
- describe('deleteIdentityCache', () => {
668
- const pixelId = 'pixel123';
669
- it('should delete all related cache items', async () => {
670
- mockedDynamoDbClient.safeQueryByGSI.mockResolvedValue([
671
- { pk: `identity#${pixelId}#identity456`, pixelId },
672
- { pk: `email#${pixelId}#test@email.com`, pixelId },
673
- ]);
674
- mockedDynamoDbClient.batchWrite.mockResolvedValue({ $metadata: {} });
675
- const incomingIdentity = {
676
- identityId: 'identity456',
677
- traits: { emails: ['test@email.com'] },
678
- };
679
- const resolvedIdentity = {
680
- identityId: 'identity456',
681
- traits: { emails: ['test@email.com', 'other@email.com'] },
682
- };
683
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.deleteIdentityCache(pixelId, incomingIdentity, resolvedIdentity);
684
- expect(mockedDynamoDbClient.safeQueryByGSI).toHaveBeenCalled();
685
- expect(mockedDynamoDbClient.batchWrite).toHaveBeenCalled();
686
- });
687
- it('should return early for missing pixelId', async () => {
688
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.deleteIdentityCache(null, {}, {});
689
- expect(mockedDynamoDbClient.safeQueryByGSI).not.toHaveBeenCalled();
690
- });
691
- it('should handle different identityIds in incoming vs resolved', async () => {
692
- mockedDynamoDbClient.safeQueryByGSI.mockResolvedValue([{ pk: `identity#${pixelId}#incoming-id`, pixelId }]);
693
- mockedDynamoDbClient.batchWrite.mockResolvedValue({ $metadata: {} });
694
- const incomingIdentity = {
695
- identityId: 'incoming-id',
696
- traits: { emails: ['test@email.com'] },
697
- };
698
- const resolvedIdentity = {
699
- identityId: 'resolved-id',
700
- traits: { emails: ['test@email.com'] },
701
- };
702
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.deleteIdentityCache(pixelId, incomingIdentity, resolvedIdentity);
703
- expect(mockedDynamoDbClient.safeQueryByGSI).toHaveBeenCalledTimes(2);
704
- });
705
- it('should not throw on batchWrite error (fail-open)', async () => {
706
- mockedDynamoDbClient.safeQueryByGSI.mockResolvedValue([{ pk: `identity#${pixelId}#identity456`, pixelId }]);
707
- mockedDynamoDbClient.batchWrite.mockRejectedValueOnce(new Error('DynamoDB error'));
708
- await expect(identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.deleteIdentityCache(pixelId, { identityId: 'identity456' }, { identityId: 'identity456' })).resolves.toBeUndefined();
709
- });
710
- it('should deduplicate keys when same items returned from GSI query', async () => {
711
- const duplicateItem = { pk: `identity#${pixelId}#identity456`, pixelId };
712
- mockedDynamoDbClient.safeQueryByGSI.mockResolvedValue([duplicateItem, duplicateItem]);
713
- mockedDynamoDbClient.batchWrite.mockResolvedValue({ $metadata: {} });
714
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.deleteIdentityCache(pixelId, { identityId: 'identity456' }, { identityId: 'identity456' });
715
- expect(mockedDynamoDbClient.batchWrite).toHaveBeenCalled();
716
- const batchWriteCall = mockedDynamoDbClient.batchWrite.mock.calls[0];
717
- expect(batchWriteCall).toBeDefined();
718
- });
719
- });
720
- describe('merge and staleness logic', () => {
721
- const pixelId = 'pixel123';
722
- it('should merge incoming traits with cached traits', async () => {
723
- const cachedResponse = {
724
- pk: `identity#${pixelId}#identity456`,
725
- pixelId,
726
- identityId: 'identity456',
727
- response: {
728
- identityId: 'identity456',
729
- traits: {
730
- emails: ['cached@email.com'],
731
- userIds: ['cached-user'],
732
- },
733
- },
734
- updatedAt: new Date().toISOString(),
735
- };
736
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
737
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
738
- identityId: 'identity456',
739
- traits: {
740
- emails: ['incoming@email.com'],
741
- phones: ['+1234567890'],
742
- },
743
- });
744
- expect(result.resolvedIdentity?.traits?.emails).toContain('incoming@email.com');
745
- expect(result.resolvedIdentity?.traits?.emails).toContain('cached@email.com');
746
- expect(result.resolvedIdentity?.traits?.userIds).toContain('cached-user');
747
- expect(result.resolvedIdentity?.traits?.phones).toContain('+1234567890');
748
- });
749
- it('should mark cache stale when new data present', async () => {
750
- const cachedResponse = {
751
- pk: `identity#${pixelId}#identity456`,
752
- pixelId,
753
- identityId: 'identity456',
754
- response: {
755
- identityId: 'identity456',
756
- traits: { emails: ['cached@email.com'] },
757
- },
758
- updatedAt: new Date().toISOString(),
759
- };
760
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
761
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
762
- identityId: 'identity456',
763
- traits: {
764
- emails: ['cached@email.com', 'new@email.com'],
765
- },
766
- });
767
- expect(result.isCacheStale).toBe(true);
768
- });
769
- it('should not mark cache stale when incoming is subset of cached', async () => {
770
- const cachedResponse = {
771
- pk: `identity#${pixelId}#identity456`,
772
- pixelId,
773
- identityId: 'identity456',
774
- response: {
775
- identityId: 'identity456',
776
- traits: {
777
- emails: ['a@email.com', 'b@email.com'],
778
- userIds: ['user1', 'user2'],
779
- },
780
- },
781
- updatedAt: new Date().toISOString(),
782
- };
783
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
784
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
785
- identityId: 'identity456',
786
- traits: {
787
- emails: ['a@email.com'],
788
- },
789
- });
790
- expect(result.isCacheStale).toBe(false);
791
- });
792
- });
793
- describe('multiple emails pointer writes', () => {
794
- const pixelId = 'pixel123';
795
- it('should write ALL emails as pointer records (not just first)', async () => {
796
- const identity = {
797
- identityId: 'identity456',
798
- traits: {
799
- emails: ['first@email.com', 'second@email.com', 'third@email.com', 'fourth@email.com'],
800
- },
801
- };
802
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.updateIdentityCache(pixelId, identity);
803
- const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
804
- expect(writtenItems).toHaveLength(5);
805
- const emailPks = writtenItems
806
- .filter((item) => item.pk.startsWith('email#'))
807
- .map((item) => item.pk);
808
- expect(emailPks).toContain(`email#${pixelId}#first@email.com`);
809
- expect(emailPks).toContain(`email#${pixelId}#second@email.com`);
810
- expect(emailPks).toContain(`email#${pixelId}#third@email.com`);
811
- expect(emailPks).toContain(`email#${pixelId}#fourth@email.com`);
812
- });
813
- it('should skip null/undefined emails in array', async () => {
814
- const identity = {
815
- identityId: 'identity456',
816
- traits: {
817
- emails: ['valid@email.com', null, undefined, '', 'another@email.com'],
818
- },
819
- };
820
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.updateIdentityCache(pixelId, identity);
821
- const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
822
- const emailItems = writtenItems.filter((item) => item.pk.startsWith('email#'));
823
- expect(emailItems).toHaveLength(2);
824
- expect(emailItems.map((e) => e.pk)).toContain(`email#${pixelId}#valid@email.com`);
825
- expect(emailItems.map((e) => e.pk)).toContain(`email#${pixelId}#another@email.com`);
826
- });
827
- it('should handle empty emails array', async () => {
828
- const identity = {
829
- identityId: 'identity456',
830
- traits: { emails: [] },
831
- };
832
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.updateIdentityCache(pixelId, identity);
833
- const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
834
- expect(writtenItems).toHaveLength(1);
835
- expect(writtenItems[0].pk).toBe(`identity#${pixelId}#identity456`);
836
- });
837
- it('should handle email normalization (lowercase)', async () => {
838
- const identity = {
839
- identityId: 'identity456',
840
- traits: {
841
- emails: ['UPPERCASE@EMAIL.COM', 'MixedCase@Email.Com'],
842
- },
843
- };
844
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.updateIdentityCache(pixelId, identity);
845
- const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
846
- const emailPks = writtenItems
847
- .filter((item) => item.pk.startsWith('email#'))
848
- .map((item) => item.pk);
849
- expect(emailPks).toContain(`email#${pixelId}#uppercase@email.com`);
850
- expect(emailPks).toContain(`email#${pixelId}#mixedcase@email.com`);
851
- });
852
- });
853
- describe('null/undefined traits edge cases', () => {
854
- const pixelId = 'pixel123';
855
- it('should handle null traits object', async () => {
856
- const identity = {
857
- identityId: 'identity456',
858
- traits: null,
859
- };
860
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.updateIdentityCache(pixelId, identity);
861
- const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
862
- expect(writtenItems).toHaveLength(1);
863
- });
864
- it('should handle undefined traits object', async () => {
865
- const identity = {
866
- identityId: 'identity456',
867
- traits: undefined,
868
- };
869
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.updateIdentityCache(pixelId, identity);
870
- const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
871
- expect(writtenItems).toHaveLength(1);
872
- });
873
- it('should return cache miss for identity with null traits in lookup', async () => {
874
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
875
- traits: null,
876
- });
877
- expect(result.resolvedIdentity).toBeUndefined();
878
- expect(result.isCacheStale).toBe(true);
879
- expect(mockedDynamoDbClient.safeGet).not.toHaveBeenCalled();
880
- });
881
- it('should handle identity with undefined emails in traits', async () => {
882
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
883
- traits: { userIds: ['user123'] },
884
- });
885
- expect(result.resolvedIdentity).toBeUndefined();
886
- expect(result.isCacheStale).toBe(true);
887
- });
888
- });
889
- describe('backward compatibility (old full-blob format)', () => {
890
- const pixelId = 'pixel123';
891
- it('should read old format email record with full response blob', async () => {
892
- const oldFormatEmailRecord = {
893
- pk: `email#${pixelId}#test@email.com`,
894
- pixelId,
895
- identityId: 'discovered-id',
896
- gsi1pk: 'discovered-id',
897
- response: {
898
- identityId: 'discovered-id',
899
- traits: { emails: ['test@email.com', 'other@email.com'] },
900
- },
901
- updatedAt: new Date().toISOString(),
902
- };
903
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(oldFormatEmailRecord);
904
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
905
- traits: { emails: ['test@email.com'] },
906
- });
907
- expect(result.resolvedIdentity).toBeDefined();
908
- expect(result.resolvedIdentity?.identityId).toBe('discovered-id');
909
- expect(result.isCacheStale).toBe(true);
910
- });
911
- it('should read new format pointer record (no response blob)', async () => {
912
- const newFormatPointerRecord = {
913
- pk: `email#${pixelId}#test@email.com`,
914
- pixelId,
915
- identityId: 'discovered-id',
916
- gsi1pk: 'discovered-id',
917
- updatedAt: new Date().toISOString(),
918
- };
919
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(newFormatPointerRecord);
920
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
921
- traits: { emails: ['test@email.com'] },
922
- });
923
- expect(result.resolvedIdentity).toBeDefined();
924
- expect(result.resolvedIdentity?.identityId).toBe('discovered-id');
925
- expect(result.isCacheStale).toBe(true);
926
- });
927
- it('should trigger Neptune resolution after finding pointer record', async () => {
928
- const lambdaArn = 'arn:aws:lambda:us-east-1:123456789:function:identity-private';
929
- const pointerRecord = {
930
- pk: `email#${pixelId}#test@email.com`,
931
- pixelId,
932
- identityId: 'discovered-id',
933
- gsi1pk: 'discovered-id',
934
- updatedAt: new Date().toISOString(),
935
- };
936
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(pointerRecord);
937
- mockInvokeFunction.mockResolvedValueOnce({
938
- statusCode: 200,
939
- body: JSON.stringify({
940
- identity: {
941
- identityId: 'discovered-id',
942
- traits: { emails: ['test@email.com', 'resolved@email.com'] },
943
- },
944
- }),
945
- });
946
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, { traits: { emails: ['test@email.com'] } }, lambdaArn);
947
- expect(mockInvokeFunction).toHaveBeenCalledWith(lambdaArn, expect.objectContaining({
948
- pixelId,
949
- context: {
950
- identity: expect.objectContaining({
951
- identityId: 'discovered-id',
952
- }),
953
- },
954
- }));
955
- expect(result?.identityId).toBe('discovered-id');
956
- });
957
- });
958
- describe('deleteIdentityCache with multiple email pointers', () => {
959
- const pixelId = 'pixel123';
960
- it('should delete all email pointers from both incoming and resolved identities', async () => {
961
- mockedDynamoDbClient.safeQueryByGSI.mockResolvedValue([
962
- { pk: `identity#${pixelId}#identity456`, pixelId },
963
- ]);
964
- mockedDynamoDbClient.batchWrite.mockResolvedValue({ $metadata: {} });
965
- const incomingIdentity = {
966
- identityId: 'identity456',
967
- traits: { emails: ['incoming1@email.com', 'incoming2@email.com'] },
968
- };
969
- const resolvedIdentity = {
970
- identityId: 'identity456',
971
- traits: { emails: ['resolved1@email.com', 'resolved2@email.com', 'resolved3@email.com'] },
972
- };
973
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.deleteIdentityCache(pixelId, incomingIdentity, resolvedIdentity);
974
- expect(mockedDynamoDbClient.batchWrite).toHaveBeenCalled();
975
- const batchWriteCall = mockedDynamoDbClient.batchWrite.mock.calls[0][0];
976
- const deleteRequests = batchWriteCall.RequestItems[Object.keys(batchWriteCall.RequestItems)[0]];
977
- const deletedPks = deleteRequests.map((req) => req.DeleteRequest.Key.pk);
978
- expect(deletedPks).toContain(`identity#${pixelId}#identity456`);
979
- expect(deletedPks).toContain(`email#${pixelId}#incoming1@email.com`);
980
- expect(deletedPks).toContain(`email#${pixelId}#incoming2@email.com`);
981
- expect(deletedPks).toContain(`email#${pixelId}#resolved1@email.com`);
982
- expect(deletedPks).toContain(`email#${pixelId}#resolved2@email.com`);
983
- expect(deletedPks).toContain(`email#${pixelId}#resolved3@email.com`);
984
- });
985
- it('should handle empty emails in delete', async () => {
986
- mockedDynamoDbClient.safeQueryByGSI.mockResolvedValue([
987
- { pk: `identity#${pixelId}#identity456`, pixelId },
988
- ]);
989
- mockedDynamoDbClient.batchWrite.mockResolvedValue({ $metadata: {} });
990
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.deleteIdentityCache(pixelId, { identityId: 'identity456', traits: { emails: [] } }, { identityId: 'identity456' });
991
- expect(mockedDynamoDbClient.batchWrite).toHaveBeenCalled();
992
- });
993
- it('should handle missing traits in delete', async () => {
994
- mockedDynamoDbClient.safeQueryByGSI.mockResolvedValue([
995
- { pk: `identity#${pixelId}#identity456`, pixelId },
996
- ]);
997
- mockedDynamoDbClient.batchWrite.mockResolvedValue({ $metadata: {} });
998
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.deleteIdentityCache(pixelId, { identityId: 'identity456' }, { identityId: 'identity456' });
999
- expect(mockedDynamoDbClient.batchWrite).toHaveBeenCalled();
1000
- });
1001
- it('should not delete items from other pixelIds', async () => {
1002
- const otherPixelId = 'other-pixel';
1003
- mockedDynamoDbClient.safeQueryByGSI.mockResolvedValue([
1004
- { pk: `identity#${pixelId}#identity456`, pixelId },
1005
- { pk: `identity#${otherPixelId}#identity456`, pixelId: otherPixelId },
1006
- ]);
1007
- mockedDynamoDbClient.batchWrite.mockResolvedValue({ $metadata: {} });
1008
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.deleteIdentityCache(pixelId, { identityId: 'identity456' }, { identityId: 'identity456' });
1009
- const batchWriteCall = mockedDynamoDbClient.batchWrite.mock.calls[0][0];
1010
- const deleteRequests = batchWriteCall.RequestItems[Object.keys(batchWriteCall.RequestItems)[0]];
1011
- const deletedPks = deleteRequests.map((req) => req.DeleteRequest.Key.pk);
1012
- expect(deletedPks).toContain(`identity#${pixelId}#identity456`);
1013
- expect(deletedPks).not.toContain(`identity#${otherPixelId}#identity456`);
1014
- });
1015
- });
1016
- describe('GSI1 query for reverse lookups', () => {
1017
- const pixelId = 'pixel123';
1018
- it('should find all records by identityId via GSI1', async () => {
1019
- const identityId = 'identity456';
1020
- const mockResults = [
1021
- { pk: `identity#${pixelId}#${identityId}`, pixelId, identityId },
1022
- { pk: `email#${pixelId}#email1@test.com`, pixelId, identityId },
1023
- { pk: `email#${pixelId}#email2@test.com`, pixelId, identityId },
1024
- ];
1025
- mockedDynamoDbClient.safeQueryByGSI.mockResolvedValue(mockResults);
1026
- mockedDynamoDbClient.batchWrite.mockResolvedValue({ $metadata: {} });
1027
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.deleteIdentityCache(pixelId, { identityId }, { identityId });
1028
- expect(mockedDynamoDbClient.safeQueryByGSI).toHaveBeenCalledWith(expect.any(String), expect.any(String), 'gsi1pk', identityId);
1029
- });
1030
- it('should handle GSI1 query returning empty results', async () => {
1031
- mockedDynamoDbClient.safeQueryByGSI.mockResolvedValue([]);
1032
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.deleteIdentityCache(pixelId, { identityId: 'unknown-id' }, { identityId: 'unknown-id' });
1033
- expect(mockedDynamoDbClient.batchWrite).not.toHaveBeenCalled();
1034
- });
1035
- it('should handle GSI1 query error gracefully (fail-open)', async () => {
1036
- mockedDynamoDbClient.safeQueryByGSI.mockRejectedValueOnce(new Error('GSI query failed'));
1037
- await expect(identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.deleteIdentityCache(pixelId, { identityId: 'identity456' }, { identityId: 'identity456' })).resolves.toBeUndefined();
1038
- });
1039
- });
1040
- describe('email pointer lookups', () => {
1041
- const pixelId = 'pixel123';
1042
- it('should only lookup first email (optimization)', async () => {
1043
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
1044
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
1045
- traits: { emails: ['first@email.com', 'second@email.com', 'third@email.com'] },
1046
- });
1047
- expect(mockedDynamoDbClient.safeGet).toHaveBeenCalledTimes(1);
1048
- expect(mockedDynamoDbClient.safeGet).toHaveBeenCalledWith(expect.any(String), 'pk', `email#${pixelId}#first@email.com`);
1049
- });
1050
- it('should handle whitespace in email during lookup', async () => {
1051
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
1052
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityFromCache(pixelId, {
1053
- traits: { emails: [' test@email.com '] },
1054
- });
1055
- expect(mockedDynamoDbClient.safeGet).toHaveBeenCalled();
1056
- });
1057
- });
1058
- describe('fail-open behavior comprehensive', () => {
1059
- const pixelId = 'pixel123';
1060
- const lambdaArn = 'arn:aws:lambda:us-east-1:123456789:function:identity-private';
1061
- it('should return incoming identity when Neptune throws (fail-open fallback)', async () => {
1062
- const incomingIdentity = {
1063
- identityId: 'identity456',
1064
- traits: { emails: ['test@email.com'] },
1065
- };
1066
- mockedDynamoDbClient.safeGet.mockRejectedValueOnce(new Error('DynamoDB down'));
1067
- mockInvokeFunction.mockRejectedValueOnce(new Error('Lambda down'));
1068
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
1069
- expect(result).toEqual(incomingIdentity);
1070
- });
1071
- it('should continue when cache write fails after Neptune success', async () => {
1072
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
1073
- mockInvokeFunction.mockResolvedValueOnce({
1074
- statusCode: 200,
1075
- body: JSON.stringify({
1076
- identity: { identityId: 'resolved-id', traits: { emails: ['test@email.com'] } },
1077
- }),
1078
- });
1079
- mockedDynamoDbClient.safeBatchWrite.mockRejectedValueOnce(new Error('Write failed'));
1080
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, { traits: { emails: ['test@email.com'] } }, lambdaArn);
1081
- expect(result?.identityId).toBe('resolved-id');
1082
- });
1083
- it('should return incoming identity when Neptune returns non-200', async () => {
1084
- const incomingIdentity = {
1085
- identityId: 'incoming-id',
1086
- traits: { emails: ['test@email.com'] },
1087
- };
1088
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
1089
- mockInvokeFunction.mockResolvedValueOnce({
1090
- statusCode: 500,
1091
- body: 'Internal Server Error',
1092
- });
1093
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
1094
- expect(result).toEqual(incomingIdentity);
1095
- });
1096
- it('should return incoming identity when Neptune returns malformed JSON (fail-open)', async () => {
1097
- const incomingIdentity = {
1098
- identityId: 'test-id',
1099
- traits: { emails: ['test@email.com'] },
1100
- };
1101
- mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
1102
- mockInvokeFunction.mockResolvedValueOnce({
1103
- statusCode: 200,
1104
- body: 'not valid json',
1105
- });
1106
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
1107
- expect(result).toEqual(incomingIdentity);
1108
- });
1109
- it('should return undefined only when both inputs are invalid', async () => {
1110
- const result = await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.getIdentityWithCaching(null, null, lambdaArn);
1111
- expect(result).toBeUndefined();
1112
- });
1113
- });
1114
- describe('gsi1pk attribute on all records', () => {
1115
- const pixelId = 'pixel123';
1116
- it('should include gsi1pk on identity record', async () => {
1117
- const identity = {
1118
- identityId: 'identity456',
1119
- traits: { emails: ['test@email.com'] },
1120
- };
1121
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.updateIdentityCache(pixelId, identity);
1122
- const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
1123
- const identityItem = writtenItems.find((item) => item.pk.startsWith('identity#'));
1124
- expect(identityItem).toBeDefined();
1125
- expect(identityItem.gsi1pk).toBe('identity456');
1126
- });
1127
- it('should include gsi1pk on all email pointer records', async () => {
1128
- const identity = {
1129
- identityId: 'identity456',
1130
- traits: { emails: ['email1@test.com', 'email2@test.com'] },
1131
- };
1132
- await identity_cache_dynamodb_service_js_1.IdentityCacheDynamoDbService.updateIdentityCache(pixelId, identity);
1133
- const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
1134
- const emailItems = writtenItems.filter((item) => item.pk.startsWith('email#'));
1135
- for (const emailItem of emailItems) {
1136
- expect(emailItem.gsi1pk).toBe('identity456');
1137
- }
1138
- });
1139
- });
1140
- });
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const at_tracking_event_types_1 = require("@adtrackify/at-tracking-event-types");
4
+ const mockInvokeFunction = jest.fn();
5
+ jest.mock('../../clients/index.js', () => ({
6
+ DynamoDbClient: {
7
+ safeGet: jest.fn(),
8
+ safePut: jest.fn(),
9
+ safeDelete: jest.fn(),
10
+ safeBatchGet: jest.fn(),
11
+ safeBatchWrite: jest.fn(),
12
+ safeQueryByGSI: jest.fn(),
13
+ batchWrite: jest.fn(),
14
+ },
15
+ LambdaInvokeClient: jest.fn().mockImplementation(() => ({
16
+ invokeFunction: mockInvokeFunction,
17
+ })),
18
+ }));
19
+ jest.mock('../../helpers/index.js', () => ({
20
+ Logger: {
21
+ debug: jest.fn(),
22
+ info: jest.fn(),
23
+ warn: jest.fn(),
24
+ error: jest.fn(),
25
+ },
26
+ }));
27
+ const key_builders_js_1 = require("../../services/db/identity-cache-dynamodb-service/key-builders.js");
28
+ const identity_gateway_service_js_1 = require("../../services/db/identity-cache-dynamodb-service/identity-gateway-service.js");
29
+ const types_js_1 = require("../../services/db/identity-cache-dynamodb-service/types.js");
30
+ const identity_cache_dynamodb_db_service_js_1 = require("../../services/db/identity-cache-dynamodb-service/identity-cache-dynamodb-db-service.js");
31
+ const index_js_1 = require("../../clients/index.js");
32
+ const mockedDynamoDbClient = index_js_1.DynamoDbClient;
33
+ describe('IdentityGatewayService', () => {
34
+ beforeEach(() => {
35
+ jest.clearAllMocks();
36
+ mockedDynamoDbClient.safeGet.mockReset();
37
+ mockedDynamoDbClient.safePut.mockReset();
38
+ mockedDynamoDbClient.safeDelete.mockReset();
39
+ mockedDynamoDbClient.safeBatchGet.mockReset();
40
+ mockedDynamoDbClient.safeBatchWrite.mockReset();
41
+ mockedDynamoDbClient.safeQueryByGSI.mockReset();
42
+ mockedDynamoDbClient.batchWrite.mockReset();
43
+ mockInvokeFunction.mockReset();
44
+ identity_gateway_service_js_1.IdentityGatewayService.lambdaInvokeClient = undefined;
45
+ });
46
+ describe('identity cache key builders', () => {
47
+ it('should build primary identity cache keys', () => {
48
+ expect((0, key_builders_js_1.buildIdentityPk)('pixel123', 'identity456')).toBe('identity#pixel123#identity456');
49
+ });
50
+ it('should build lowercase email pointer keys', () => {
51
+ expect((0, key_builders_js_1.buildEmailPk)('pixel123', 'Test@Email.COM')).toBe('email#pixel123#test@email.com');
52
+ });
53
+ it('should build trimmed user id pointer keys', () => {
54
+ expect((0, key_builders_js_1.buildUserIdPk)('pixel123', ' user-1 ')).toBe('user_id#pixel123#user-1');
55
+ });
56
+ it('should build identity map keys', () => {
57
+ expect((0, key_builders_js_1.buildIdentityMapPk)('pixel123', 'identity456')).toBe('identity_map#pixel123#identity456');
58
+ });
59
+ it('should build force purge keys', () => {
60
+ expect((0, key_builders_js_1.buildForcePurgePk)('pixel123', 'identity456')).toBe('force_purge#pixel123#identity456');
61
+ });
62
+ it('should match the installed tracking-event-types key builder behavior during migration', () => {
63
+ expect((0, key_builders_js_1.buildIdentityPk)('pixel123', 'identity456')).toBe((0, at_tracking_event_types_1.buildIdentityPk)('pixel123', 'identity456'));
64
+ expect((0, key_builders_js_1.buildEmailPk)('pixel123', 'Test@Email.COM')).toBe((0, at_tracking_event_types_1.buildEmailPk)('pixel123', 'Test@Email.COM'));
65
+ expect((0, key_builders_js_1.buildUserIdPk)('pixel123', ' user-1 ')).toBe((0, at_tracking_event_types_1.buildUserIdPk)('pixel123', ' user-1 '));
66
+ expect((0, key_builders_js_1.buildIdentityMapPk)('pixel123', 'identity456')).toBe((0, at_tracking_event_types_1.buildIdentityMapPk)('pixel123', 'identity456'));
67
+ expect((0, key_builders_js_1.buildForcePurgePk)('pixel123', 'identity456')).toBe((0, at_tracking_event_types_1.buildForcePurgePk)('pixel123', 'identity456'));
68
+ });
69
+ });
70
+ describe('getIdentityWithCaching', () => {
71
+ const pixelId = 'pixel123';
72
+ const lambdaArn = 'arn:aws:lambda:us-east-1:123456789:function:identity-private';
73
+ describe('cache hit (fresh cache)', () => {
74
+ it('should return cached identity without invoking Neptune Lambda', async () => {
75
+ const incomingIdentity = {
76
+ identityId: 'identity456',
77
+ traits: { emails: ['test@email.com'] },
78
+ };
79
+ const cachedResponse = {
80
+ pk: `identity#${pixelId}#identity456`,
81
+ pixelId,
82
+ identityId: 'identity456',
83
+ response: {
84
+ identityId: 'identity456',
85
+ traits: { emails: ['test@email.com'] },
86
+ },
87
+ updatedAt: new Date().toISOString(),
88
+ };
89
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
90
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
91
+ expect(result).toBeDefined();
92
+ expect(result?.identityId).toBe('identity456');
93
+ expect(mockInvokeFunction).not.toHaveBeenCalled();
94
+ });
95
+ });
96
+ describe('cache miss', () => {
97
+ it('should invoke Neptune Lambda and return resolved identity', async () => {
98
+ const incomingIdentity = {
99
+ identityId: 'identity456',
100
+ traits: { emails: ['test@email.com'] },
101
+ };
102
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
103
+ mockInvokeFunction.mockResolvedValueOnce({
104
+ statusCode: 200,
105
+ body: JSON.stringify({
106
+ identity: {
107
+ identityId: 'resolved-identity',
108
+ traits: { emails: ['test@email.com', 'resolved@email.com'] },
109
+ },
110
+ }),
111
+ });
112
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
113
+ expect(mockInvokeFunction).toHaveBeenCalledWith(lambdaArn, {
114
+ pixelId,
115
+ context: { identity: incomingIdentity },
116
+ });
117
+ expect(result?.identityId).toBe('resolved-identity');
118
+ expect(mockedDynamoDbClient.safeBatchWrite).toHaveBeenCalled();
119
+ });
120
+ it('should return incomingIdentity when Neptune Lambda fails to resolve', async () => {
121
+ const incomingIdentity = {
122
+ identityId: 'identity456',
123
+ traits: { emails: ['test@email.com'] },
124
+ };
125
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
126
+ mockInvokeFunction.mockResolvedValueOnce({
127
+ statusCode: 200,
128
+ body: JSON.stringify({ identity: undefined }),
129
+ });
130
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
131
+ expect(result).toEqual(incomingIdentity);
132
+ });
133
+ });
134
+ describe('cache stale', () => {
135
+ it('should invoke Neptune Lambda when cache is stale', async () => {
136
+ const incomingIdentity = {
137
+ identityId: 'identity456',
138
+ traits: { emails: ['old@email.com', 'new@email.com'] },
139
+ };
140
+ const cachedResponse = {
141
+ pk: `identity#${pixelId}#identity456`,
142
+ pixelId,
143
+ identityId: 'identity456',
144
+ response: {
145
+ identityId: 'identity456',
146
+ traits: { emails: ['old@email.com'] },
147
+ },
148
+ updatedAt: new Date().toISOString(),
149
+ };
150
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
151
+ mockInvokeFunction.mockResolvedValueOnce({
152
+ statusCode: 200,
153
+ body: JSON.stringify({
154
+ identity: {
155
+ identityId: 'identity456',
156
+ traits: { emails: ['old@email.com', 'new@email.com'] },
157
+ },
158
+ }),
159
+ });
160
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
161
+ expect(mockInvokeFunction).toHaveBeenCalled();
162
+ expect(result?.identityId).toBe('identity456');
163
+ expect(result?.traits?.emails).toContain('new@email.com');
164
+ });
165
+ it('should use merged cache identity when Neptune fails', async () => {
166
+ const incomingIdentity = {
167
+ identityId: 'identity456',
168
+ traits: { emails: ['old@email.com', 'new@email.com'] },
169
+ };
170
+ const cachedResponse = {
171
+ pk: `identity#${pixelId}#identity456`,
172
+ pixelId,
173
+ identityId: 'identity456',
174
+ response: {
175
+ identityId: 'identity456',
176
+ traits: { emails: ['old@email.com'] },
177
+ },
178
+ updatedAt: new Date().toISOString(),
179
+ };
180
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
181
+ mockInvokeFunction.mockResolvedValueOnce({
182
+ statusCode: 500,
183
+ body: 'Internal Server Error',
184
+ });
185
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
186
+ expect(result).toBeDefined();
187
+ expect(result?.identityId).toBe('identity456');
188
+ });
189
+ });
190
+ describe('Neptune Lambda invocation', () => {
191
+ it('should write-back Neptune response to cache on success', async () => {
192
+ const incomingIdentity = {
193
+ traits: { emails: ['test@email.com'] },
194
+ };
195
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
196
+ mockInvokeFunction.mockResolvedValueOnce({
197
+ statusCode: 200,
198
+ body: JSON.stringify({
199
+ identity: {
200
+ identityId: 'neptune-resolved-id',
201
+ traits: { emails: ['test@email.com'] },
202
+ },
203
+ }),
204
+ });
205
+ await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
206
+ expect(mockedDynamoDbClient.safeBatchWrite).toHaveBeenCalled();
207
+ const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
208
+ const identityPks = writtenItems.map((item) => item.pk);
209
+ expect(identityPks).toContain(`identity#${pixelId}#neptune-resolved-id`);
210
+ });
211
+ it('should not write to cache when Neptune returns undefined', async () => {
212
+ const incomingIdentity = {
213
+ traits: { emails: ['test@email.com'] },
214
+ };
215
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
216
+ mockInvokeFunction.mockResolvedValueOnce({
217
+ statusCode: 200,
218
+ body: JSON.stringify({ identity: undefined }),
219
+ });
220
+ await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
221
+ expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
222
+ });
223
+ });
224
+ describe('error handling (fail-open)', () => {
225
+ it('should return undefined when pixelId is missing', async () => {
226
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCaching(null, { identityId: 'test-id' }, lambdaArn);
227
+ expect(result).toBeUndefined();
228
+ expect(mockInvokeFunction).not.toHaveBeenCalled();
229
+ });
230
+ it('should return undefined when incomingIdentity is missing', async () => {
231
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCaching(pixelId, null, lambdaArn);
232
+ expect(result).toBeUndefined();
233
+ expect(mockInvokeFunction).not.toHaveBeenCalled();
234
+ });
235
+ it('should return undefined when Neptune Lambda throws', async () => {
236
+ const incomingIdentity = {
237
+ identityId: 'identity456',
238
+ traits: { emails: ['test@email.com'] },
239
+ };
240
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
241
+ mockInvokeFunction.mockRejectedValueOnce(new Error('Lambda invocation failed'));
242
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
243
+ expect(result).toEqual(incomingIdentity);
244
+ });
245
+ it('should not throw when cache write fails after Neptune success', async () => {
246
+ const incomingIdentity = {
247
+ traits: { emails: ['test@email.com'] },
248
+ };
249
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
250
+ mockInvokeFunction.mockResolvedValueOnce({
251
+ statusCode: 200,
252
+ body: JSON.stringify({
253
+ identity: {
254
+ identityId: 'neptune-resolved-id',
255
+ traits: { emails: ['test@email.com'] },
256
+ },
257
+ }),
258
+ });
259
+ mockedDynamoDbClient.safeBatchWrite.mockRejectedValueOnce(new Error('DynamoDB write failed'));
260
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
261
+ expect(result?.identityId).toBe('neptune-resolved-id');
262
+ });
263
+ });
264
+ });
265
+ describe('Key Builders', () => {
266
+ describe('buildIdentityPk', () => {
267
+ it('should build correct pk for identity lookup', async () => {
268
+ const pixelId = 'pixel123';
269
+ const identityId = 'identity456';
270
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
271
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, { identityId });
272
+ expect(mockedDynamoDbClient.safeGet).toHaveBeenCalledWith(expect.any(String), 'pk', `identity#${pixelId}#${identityId}`);
273
+ });
274
+ });
275
+ describe('buildEmailPk', () => {
276
+ it('should build correct pk for email lookup (lowercase)', async () => {
277
+ const pixelId = 'pixel123';
278
+ const email = 'Test@Email.COM';
279
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
280
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
281
+ traits: { emails: [email] },
282
+ });
283
+ expect(mockedDynamoDbClient.safeGet).toHaveBeenCalledWith(expect.any(String), 'pk', `email#${pixelId}#test@email.com`);
284
+ });
285
+ });
286
+ describe('buildUserIdPk (no longer used for lookup)', () => {
287
+ it('should NOT lookup by userId (optimization: email-only secondary lookup)', async () => {
288
+ const pixelId = 'pixel123';
289
+ const userId = ' user789 ';
290
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
291
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
292
+ traits: { userIds: [userId] },
293
+ });
294
+ expect(mockedDynamoDbClient.safeBatchGet).not.toHaveBeenCalled();
295
+ });
296
+ });
297
+ });
298
+ describe('getIdentityFromCache', () => {
299
+ const pixelId = 'pixel123';
300
+ describe('with identityId (primary lookup)', () => {
301
+ it('should return cache hit when identity found', async () => {
302
+ const identityId = 'identity456';
303
+ const cachedResponse = {
304
+ pk: `identity#${pixelId}#${identityId}`,
305
+ pixelId,
306
+ identityId,
307
+ response: {
308
+ identityId,
309
+ traits: { emails: ['test@email.com'] },
310
+ },
311
+ updatedAt: new Date().toISOString(),
312
+ };
313
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
314
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
315
+ identityId,
316
+ traits: { emails: ['test@email.com'] },
317
+ });
318
+ expect(result.resolvedIdentity).toBeDefined();
319
+ expect(result.resolvedIdentity?.identityId).toBe(identityId);
320
+ expect(result.isCacheStale).toBe(false);
321
+ });
322
+ it('should return cache miss when identity not found', async () => {
323
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
324
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
325
+ identityId: 'unknown-id',
326
+ });
327
+ expect(result.resolvedIdentity).toBeUndefined();
328
+ expect(result.isCacheStale).toBe(true);
329
+ });
330
+ it('should NOT fallback to secondary keys when identityId lookup misses', async () => {
331
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
332
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
333
+ identityId: 'unknown-id',
334
+ traits: { emails: ['test@email.com'] },
335
+ });
336
+ expect(mockedDynamoDbClient.safeBatchGet).not.toHaveBeenCalled();
337
+ });
338
+ it('should detect stale cache when new traits present', async () => {
339
+ const identityId = 'identity456';
340
+ const cachedResponse = {
341
+ pk: `identity#${pixelId}#${identityId}`,
342
+ pixelId,
343
+ identityId,
344
+ response: {
345
+ identityId,
346
+ traits: { emails: ['old@email.com'] },
347
+ },
348
+ updatedAt: new Date().toISOString(),
349
+ };
350
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
351
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
352
+ identityId,
353
+ traits: { emails: ['old@email.com', 'new@email.com'] },
354
+ });
355
+ expect(result.resolvedIdentity).toBeDefined();
356
+ expect(result.isCacheStale).toBe(true);
357
+ });
358
+ });
359
+ describe('without identityId (secondary lookup)', () => {
360
+ it('should use single get for email lookup (pointer-based)', async () => {
361
+ const pointerRecord = {
362
+ pk: `email#${pixelId}#test@email.com`,
363
+ pixelId,
364
+ identityId: 'resolved-id',
365
+ gsi1pk: 'resolved-id',
366
+ updatedAt: new Date().toISOString(),
367
+ };
368
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(pointerRecord);
369
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
370
+ traits: { emails: ['test@email.com'], userIds: ['user123'] },
371
+ });
372
+ expect(mockedDynamoDbClient.safeGet).toHaveBeenCalledWith(expect.any(String), 'pk', `email#${pixelId}#test@email.com`);
373
+ expect(mockedDynamoDbClient.safeBatchGet).not.toHaveBeenCalled();
374
+ expect(result.resolvedIdentity).toBeDefined();
375
+ expect(result.resolvedIdentity?.traits?.emails).toContain('test@email.com');
376
+ expect(result.isCacheStale).toBe(true);
377
+ });
378
+ it('should return discovered identityId with stale flag for Neptune resolution', async () => {
379
+ const pointerRecord = {
380
+ pk: `email#${pixelId}#test@email.com`,
381
+ pixelId,
382
+ identityId: 'discovered-id',
383
+ gsi1pk: 'discovered-id',
384
+ updatedAt: new Date().toISOString(),
385
+ };
386
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(pointerRecord);
387
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
388
+ traits: { emails: ['test@email.com'], userIds: ['user123'] },
389
+ });
390
+ expect(result.resolvedIdentity).toBeDefined();
391
+ expect(result.resolvedIdentity?.identityId).toBe('discovered-id');
392
+ expect(result.isCacheStale).toBe(true);
393
+ });
394
+ it('should return cache miss when no secondary keys match', async () => {
395
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
396
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
397
+ traits: { emails: ['unknown@email.com'] },
398
+ });
399
+ expect(result.resolvedIdentity).toBeUndefined();
400
+ expect(result.isCacheStale).toBe(true);
401
+ });
402
+ it('should return cache miss when no emails to lookup', async () => {
403
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
404
+ traits: { userIds: ['user123'] },
405
+ });
406
+ expect(result.resolvedIdentity).toBeUndefined();
407
+ expect(result.isCacheStale).toBe(true);
408
+ expect(mockedDynamoDbClient.safeGet).not.toHaveBeenCalled();
409
+ });
410
+ it('should return cache miss when empty traits', async () => {
411
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
412
+ traits: {},
413
+ });
414
+ expect(result.resolvedIdentity).toBeUndefined();
415
+ expect(result.isCacheStale).toBe(true);
416
+ });
417
+ });
418
+ describe('error handling (fail-open)', () => {
419
+ it('should return cache miss on safeGet error', async () => {
420
+ mockedDynamoDbClient.safeGet.mockRejectedValueOnce(new Error('DynamoDB error'));
421
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
422
+ identityId: 'test-id',
423
+ });
424
+ expect(result.resolvedIdentity).toBeUndefined();
425
+ expect(result.isCacheStale).toBe(true);
426
+ });
427
+ it('should return cache miss on safeGet error for email lookup', async () => {
428
+ mockedDynamoDbClient.safeGet.mockRejectedValueOnce(new Error('DynamoDB error'));
429
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
430
+ traits: { emails: ['test@email.com'] },
431
+ });
432
+ expect(result.resolvedIdentity).toBeUndefined();
433
+ expect(result.isCacheStale).toBe(true);
434
+ });
435
+ it('should return cache miss for null pixelId', async () => {
436
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(null, {
437
+ identityId: 'test-id',
438
+ });
439
+ expect(result.resolvedIdentity).toBeUndefined();
440
+ expect(result.isCacheStale).toBe(true);
441
+ });
442
+ it('should return cache miss for null incomingIdentity', async () => {
443
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, null);
444
+ expect(result.resolvedIdentity).toBeUndefined();
445
+ expect(result.isCacheStale).toBe(true);
446
+ });
447
+ });
448
+ });
449
+ describe('updateIdentityCache', () => {
450
+ const pixelId = 'pixel123';
451
+ it('should write identity with email pointer (no full blob on secondary keys)', async () => {
452
+ const identity = {
453
+ identityId: 'identity456',
454
+ traits: {
455
+ emails: ['test@email.com', 'other@email.com'],
456
+ userIds: ['user1', 'user2'],
457
+ },
458
+ };
459
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, identity);
460
+ expect(mockedDynamoDbClient.safeBatchWrite).toHaveBeenCalledTimes(1);
461
+ const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
462
+ expect(writtenItems).toHaveLength(3);
463
+ const pks = writtenItems.map((item) => item.pk);
464
+ expect(pks).toContain(`identity#${pixelId}#identity456`);
465
+ expect(pks).toContain(`email#${pixelId}#test@email.com`);
466
+ expect(pks).toContain(`email#${pixelId}#other@email.com`);
467
+ expect(pks).not.toContain(`user_id#${pixelId}#user1`);
468
+ expect(pks).not.toContain(`user_id#${pixelId}#user2`);
469
+ const identityItem = writtenItems.find((item) => item.pk.startsWith('identity#'));
470
+ expect(identityItem).toBeDefined();
471
+ expect(identityItem?.response).toBeDefined();
472
+ expect(identityItem?.response.identityId).toBe('identity456');
473
+ const emailItem = writtenItems.find((item) => item.pk.startsWith('email#'));
474
+ expect(emailItem).toBeDefined();
475
+ expect(emailItem?.response).toBeUndefined();
476
+ expect(emailItem?.identityId).toBe('identity456');
477
+ });
478
+ it('should not throw on safeBatchWrite error (fail-open)', async () => {
479
+ mockedDynamoDbClient.safeBatchWrite.mockRejectedValueOnce(new Error('DynamoDB error'));
480
+ const identity = {
481
+ identityId: 'identity456',
482
+ traits: { emails: ['test@email.com'] },
483
+ };
484
+ await expect(identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, identity)).resolves.toBeUndefined();
485
+ });
486
+ it('should return early for missing identityId', async () => {
487
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, {});
488
+ expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
489
+ });
490
+ it('should return early for missing pixelId', async () => {
491
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.updateIdentityCache(null, {
492
+ identityId: 'test-id',
493
+ });
494
+ expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
495
+ });
496
+ it('should handle identity with no traits', async () => {
497
+ const identity = {
498
+ identityId: 'identity456',
499
+ };
500
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, identity);
501
+ const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
502
+ expect(writtenItems).toHaveLength(1);
503
+ });
504
+ it('should include gsi1pk for reverse lookups', async () => {
505
+ const identity = {
506
+ identityId: 'identity456',
507
+ traits: { emails: ['test@email.com'] },
508
+ };
509
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, identity);
510
+ const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
511
+ for (const item of writtenItems) {
512
+ expect(item.gsi1pk).toBe('identity456');
513
+ }
514
+ });
515
+ });
516
+ describe('getIdentityMap', () => {
517
+ it('should return identity map when found', async () => {
518
+ const mockMap = {
519
+ pk: 'identity_map#pixel123#identity456',
520
+ pixelId: 'pixel123',
521
+ identityId: 'identity456',
522
+ linkedIdentities: ['identity789'],
523
+ updatedAt: new Date().toISOString(),
524
+ };
525
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(mockMap);
526
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityMap('pixel123', 'identity456');
527
+ expect(result).toEqual(mockMap);
528
+ expect(mockedDynamoDbClient.safeGet).toHaveBeenCalledWith(expect.any(String), 'pk', 'identity_map#pixel123#identity456');
529
+ });
530
+ it('should return undefined when not found', async () => {
531
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
532
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityMap('pixel123', 'identity456');
533
+ expect(result).toBeUndefined();
534
+ });
535
+ it('should return undefined for missing params (fail-open)', async () => {
536
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityMap('', '');
537
+ expect(result).toBeUndefined();
538
+ expect(mockedDynamoDbClient.safeGet).not.toHaveBeenCalled();
539
+ });
540
+ it('should return undefined when only pixelId is missing', async () => {
541
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityMap('', 'identity456');
542
+ expect(result).toBeUndefined();
543
+ expect(mockedDynamoDbClient.safeGet).not.toHaveBeenCalled();
544
+ });
545
+ it('should return undefined when only identityId is missing', async () => {
546
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityMap('pixel123', '');
547
+ expect(result).toBeUndefined();
548
+ expect(mockedDynamoDbClient.safeGet).not.toHaveBeenCalled();
549
+ });
550
+ it('should return undefined on safeGet error (fail-open)', async () => {
551
+ mockedDynamoDbClient.safeGet.mockRejectedValueOnce(new Error('DynamoDB error'));
552
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityMap('pixel123', 'identity456');
553
+ expect(result).toBeUndefined();
554
+ });
555
+ });
556
+ describe('getForcePurgeFlag', () => {
557
+ it('should return true when flag exists', async () => {
558
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce({
559
+ pk: 'force_purge#pixel123#identity456',
560
+ });
561
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getForcePurgeFlag('pixel123', 'identity456');
562
+ expect(result).toBe(true);
563
+ });
564
+ it('should return false when flag not found', async () => {
565
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
566
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getForcePurgeFlag('pixel123', 'identity456');
567
+ expect(result).toBe(false);
568
+ });
569
+ it('should return false on error (fail-open)', async () => {
570
+ mockedDynamoDbClient.safeGet.mockRejectedValueOnce(new Error('DynamoDB error'));
571
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getForcePurgeFlag('pixel123', 'identity456');
572
+ expect(result).toBe(false);
573
+ });
574
+ it('should return false for missing params', async () => {
575
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getForcePurgeFlag('', '');
576
+ expect(result).toBe(false);
577
+ expect(mockedDynamoDbClient.safeGet).not.toHaveBeenCalled();
578
+ });
579
+ });
580
+ describe('setForcePurgeFlag', () => {
581
+ it('should set flag with TTL', async () => {
582
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.setForcePurgeFlag('pixel123', 'identity456');
583
+ expect(mockedDynamoDbClient.safePut).toHaveBeenCalledTimes(1);
584
+ const putItem = mockedDynamoDbClient.safePut.mock.calls[0][1];
585
+ expect(putItem.pk).toBe('force_purge#pixel123#identity456');
586
+ expect(putItem.pixelId).toBe('pixel123');
587
+ expect(putItem.identityId).toBe('identity456');
588
+ expect(putItem.ttl).toBeGreaterThan(0);
589
+ });
590
+ it('should accept custom TTL', async () => {
591
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.setForcePurgeFlag('pixel123', 'identity456', 3600);
592
+ const putItem = mockedDynamoDbClient.safePut.mock.calls[0][1];
593
+ expect(putItem.ttl).toBeGreaterThan(0);
594
+ });
595
+ it('should include createdAt timestamp', async () => {
596
+ const before = new Date().toISOString();
597
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.setForcePurgeFlag('pixel123', 'identity456');
598
+ const after = new Date().toISOString();
599
+ const putItem = mockedDynamoDbClient.safePut.mock.calls[0][1];
600
+ expect(putItem.createdAt).toBeDefined();
601
+ expect(putItem.createdAt >= before).toBe(true);
602
+ expect(putItem.createdAt <= after).toBe(true);
603
+ });
604
+ it('should return early when pixelId is missing', async () => {
605
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.setForcePurgeFlag('', 'identity456');
606
+ expect(mockedDynamoDbClient.safePut).not.toHaveBeenCalled();
607
+ });
608
+ it('should return early when identityId is missing', async () => {
609
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.setForcePurgeFlag('pixel123', '');
610
+ expect(mockedDynamoDbClient.safePut).not.toHaveBeenCalled();
611
+ });
612
+ it('should not throw on error (fail-open)', async () => {
613
+ mockedDynamoDbClient.safePut.mockRejectedValueOnce(new Error('DynamoDB error'));
614
+ await expect(identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.setForcePurgeFlag('pixel123', 'identity456')).resolves.toBeUndefined();
615
+ });
616
+ });
617
+ describe('putIdentityMap', () => {
618
+ it('should write identity map', async () => {
619
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.putIdentityMap('pixel123', 'identity456', ['linked1', 'linked2']);
620
+ expect(mockedDynamoDbClient.safePut).toHaveBeenCalledTimes(1);
621
+ const putItem = mockedDynamoDbClient.safePut.mock.calls[0][1];
622
+ expect(putItem.pk).toBe('identity_map#pixel123#identity456');
623
+ expect(putItem.linkedIdentities).toEqual(['linked1', 'linked2']);
624
+ });
625
+ it('should return early for missing params', async () => {
626
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.putIdentityMap('', '', []);
627
+ expect(mockedDynamoDbClient.safePut).not.toHaveBeenCalled();
628
+ });
629
+ it('should return early when only pixelId is missing', async () => {
630
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.putIdentityMap('', 'identity456', ['linked1']);
631
+ expect(mockedDynamoDbClient.safePut).not.toHaveBeenCalled();
632
+ });
633
+ it('should return early when only identityId is missing', async () => {
634
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.putIdentityMap('pixel123', '', ['linked1']);
635
+ expect(mockedDynamoDbClient.safePut).not.toHaveBeenCalled();
636
+ });
637
+ it('should include updatedAt timestamp', async () => {
638
+ const before = new Date().toISOString();
639
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.putIdentityMap('pixel123', 'identity456', ['linked1']);
640
+ const after = new Date().toISOString();
641
+ const putItem = mockedDynamoDbClient.safePut.mock.calls[0][1];
642
+ expect(putItem.updatedAt).toBeDefined();
643
+ expect(putItem.updatedAt >= before).toBe(true);
644
+ expect(putItem.updatedAt <= after).toBe(true);
645
+ });
646
+ it('should not throw on safePut error (fail-open)', async () => {
647
+ mockedDynamoDbClient.safePut.mockRejectedValueOnce(new Error('DynamoDB error'));
648
+ await expect(identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.putIdentityMap('pixel123', 'identity456', ['linked1'])).resolves.toBeUndefined();
649
+ });
650
+ });
651
+ describe('deleteIdentityMap', () => {
652
+ it('should delete identity map', async () => {
653
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.deleteIdentityMap('pixel123', 'identity456');
654
+ expect(mockedDynamoDbClient.safeDelete).toHaveBeenCalledWith(expect.any(String), 'pk', 'identity_map#pixel123#identity456');
655
+ });
656
+ it('should return early for missing params', async () => {
657
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.deleteIdentityMap('', '');
658
+ expect(mockedDynamoDbClient.safeDelete).not.toHaveBeenCalled();
659
+ });
660
+ it('should return early when only pixelId is missing', async () => {
661
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.deleteIdentityMap('', 'identity456');
662
+ expect(mockedDynamoDbClient.safeDelete).not.toHaveBeenCalled();
663
+ });
664
+ it('should return early when only identityId is missing', async () => {
665
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.deleteIdentityMap('pixel123', '');
666
+ expect(mockedDynamoDbClient.safeDelete).not.toHaveBeenCalled();
667
+ });
668
+ it('should not throw on safeDelete error (fail-open)', async () => {
669
+ mockedDynamoDbClient.safeDelete.mockRejectedValueOnce(new Error('DynamoDB error'));
670
+ await expect(identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.deleteIdentityMap('pixel123', 'identity456')).resolves.toBeUndefined();
671
+ });
672
+ });
673
+ describe('deleteForcePurgeFlag', () => {
674
+ it('should delete force purge flag', async () => {
675
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.deleteForcePurgeFlag('pixel123', 'identity456');
676
+ expect(mockedDynamoDbClient.safeDelete).toHaveBeenCalledWith(expect.any(String), 'pk', 'force_purge#pixel123#identity456');
677
+ });
678
+ it('should return early for missing params', async () => {
679
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.deleteForcePurgeFlag('', '');
680
+ expect(mockedDynamoDbClient.safeDelete).not.toHaveBeenCalled();
681
+ });
682
+ it('should return early when only pixelId is missing', async () => {
683
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.deleteForcePurgeFlag('', 'identity456');
684
+ expect(mockedDynamoDbClient.safeDelete).not.toHaveBeenCalled();
685
+ });
686
+ it('should return early when only identityId is missing', async () => {
687
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.deleteForcePurgeFlag('pixel123', '');
688
+ expect(mockedDynamoDbClient.safeDelete).not.toHaveBeenCalled();
689
+ });
690
+ it('should not throw on safeDelete error (fail-open)', async () => {
691
+ mockedDynamoDbClient.safeDelete.mockRejectedValueOnce(new Error('DynamoDB error'));
692
+ await expect(identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.deleteForcePurgeFlag('pixel123', 'identity456')).resolves.toBeUndefined();
693
+ });
694
+ });
695
+ describe('deleteIdentityCache', () => {
696
+ const pixelId = 'pixel123';
697
+ it('should delete all related cache items', async () => {
698
+ mockedDynamoDbClient.safeQueryByGSI.mockResolvedValue([
699
+ { pk: `identity#${pixelId}#identity456`, pixelId },
700
+ { pk: `email#${pixelId}#test@email.com`, pixelId },
701
+ ]);
702
+ mockedDynamoDbClient.batchWrite.mockResolvedValue({ $metadata: {} });
703
+ const incomingIdentity = {
704
+ identityId: 'identity456',
705
+ traits: { emails: ['test@email.com'] },
706
+ };
707
+ const resolvedIdentity = {
708
+ identityId: 'identity456',
709
+ traits: { emails: ['test@email.com', 'other@email.com'] },
710
+ };
711
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.deleteIdentityCache(pixelId, incomingIdentity, resolvedIdentity);
712
+ expect(mockedDynamoDbClient.safeQueryByGSI).toHaveBeenCalled();
713
+ expect(mockedDynamoDbClient.batchWrite).toHaveBeenCalled();
714
+ });
715
+ it('should return early for missing pixelId', async () => {
716
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.deleteIdentityCache(null, {}, {});
717
+ expect(mockedDynamoDbClient.safeQueryByGSI).not.toHaveBeenCalled();
718
+ });
719
+ it('should handle different identityIds in incoming vs resolved', async () => {
720
+ mockedDynamoDbClient.safeQueryByGSI.mockResolvedValue([{ pk: `identity#${pixelId}#incoming-id`, pixelId }]);
721
+ mockedDynamoDbClient.batchWrite.mockResolvedValue({ $metadata: {} });
722
+ const incomingIdentity = {
723
+ identityId: 'incoming-id',
724
+ traits: { emails: ['test@email.com'] },
725
+ };
726
+ const resolvedIdentity = {
727
+ identityId: 'resolved-id',
728
+ traits: { emails: ['test@email.com'] },
729
+ };
730
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.deleteIdentityCache(pixelId, incomingIdentity, resolvedIdentity);
731
+ expect(mockedDynamoDbClient.safeQueryByGSI).toHaveBeenCalledTimes(2);
732
+ });
733
+ it('should not throw on batchWrite error (fail-open)', async () => {
734
+ mockedDynamoDbClient.safeQueryByGSI.mockResolvedValue([{ pk: `identity#${pixelId}#identity456`, pixelId }]);
735
+ mockedDynamoDbClient.batchWrite.mockRejectedValueOnce(new Error('DynamoDB error'));
736
+ await expect(identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.deleteIdentityCache(pixelId, { identityId: 'identity456' }, { identityId: 'identity456' })).resolves.toBeUndefined();
737
+ });
738
+ it('should deduplicate keys when same items returned from GSI query', async () => {
739
+ const duplicateItem = { pk: `identity#${pixelId}#identity456`, pixelId };
740
+ mockedDynamoDbClient.safeQueryByGSI.mockResolvedValue([duplicateItem, duplicateItem]);
741
+ mockedDynamoDbClient.batchWrite.mockResolvedValue({ $metadata: {} });
742
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.deleteIdentityCache(pixelId, { identityId: 'identity456' }, { identityId: 'identity456' });
743
+ expect(mockedDynamoDbClient.batchWrite).toHaveBeenCalled();
744
+ const batchWriteCall = mockedDynamoDbClient.batchWrite.mock.calls[0];
745
+ expect(batchWriteCall).toBeDefined();
746
+ });
747
+ });
748
+ describe('merge and staleness logic', () => {
749
+ const pixelId = 'pixel123';
750
+ it('should merge incoming traits with cached traits', async () => {
751
+ const cachedResponse = {
752
+ pk: `identity#${pixelId}#identity456`,
753
+ pixelId,
754
+ identityId: 'identity456',
755
+ response: {
756
+ identityId: 'identity456',
757
+ traits: {
758
+ emails: ['cached@email.com'],
759
+ userIds: ['cached-user'],
760
+ },
761
+ },
762
+ updatedAt: new Date().toISOString(),
763
+ };
764
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
765
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
766
+ identityId: 'identity456',
767
+ traits: {
768
+ emails: ['incoming@email.com'],
769
+ phones: ['+1234567890'],
770
+ },
771
+ });
772
+ expect(result.resolvedIdentity?.traits?.emails).toContain('incoming@email.com');
773
+ expect(result.resolvedIdentity?.traits?.emails).toContain('cached@email.com');
774
+ expect(result.resolvedIdentity?.traits?.userIds).toContain('cached-user');
775
+ expect(result.resolvedIdentity?.traits?.phones).toContain('+1234567890');
776
+ });
777
+ it('should mark cache stale when new data present', async () => {
778
+ const cachedResponse = {
779
+ pk: `identity#${pixelId}#identity456`,
780
+ pixelId,
781
+ identityId: 'identity456',
782
+ response: {
783
+ identityId: 'identity456',
784
+ traits: { emails: ['cached@email.com'] },
785
+ },
786
+ updatedAt: new Date().toISOString(),
787
+ };
788
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
789
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
790
+ identityId: 'identity456',
791
+ traits: {
792
+ emails: ['cached@email.com', 'new@email.com'],
793
+ },
794
+ });
795
+ expect(result.isCacheStale).toBe(true);
796
+ });
797
+ it('should not mark cache stale when incoming is subset of cached', async () => {
798
+ const cachedResponse = {
799
+ pk: `identity#${pixelId}#identity456`,
800
+ pixelId,
801
+ identityId: 'identity456',
802
+ response: {
803
+ identityId: 'identity456',
804
+ traits: {
805
+ emails: ['a@email.com', 'b@email.com'],
806
+ userIds: ['user1', 'user2'],
807
+ },
808
+ },
809
+ updatedAt: new Date().toISOString(),
810
+ };
811
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
812
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
813
+ identityId: 'identity456',
814
+ traits: {
815
+ emails: ['a@email.com'],
816
+ },
817
+ });
818
+ expect(result.isCacheStale).toBe(false);
819
+ });
820
+ });
821
+ describe('multiple emails pointer writes', () => {
822
+ const pixelId = 'pixel123';
823
+ it('should write ALL emails as pointer records (not just first)', async () => {
824
+ const identity = {
825
+ identityId: 'identity456',
826
+ traits: {
827
+ emails: ['first@email.com', 'second@email.com', 'third@email.com', 'fourth@email.com'],
828
+ },
829
+ };
830
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, identity);
831
+ const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
832
+ expect(writtenItems).toHaveLength(5);
833
+ const emailPks = writtenItems
834
+ .filter((item) => item.pk.startsWith('email#'))
835
+ .map((item) => item.pk);
836
+ expect(emailPks).toContain(`email#${pixelId}#first@email.com`);
837
+ expect(emailPks).toContain(`email#${pixelId}#second@email.com`);
838
+ expect(emailPks).toContain(`email#${pixelId}#third@email.com`);
839
+ expect(emailPks).toContain(`email#${pixelId}#fourth@email.com`);
840
+ });
841
+ it('should skip null/undefined emails in array', async () => {
842
+ const identity = {
843
+ identityId: 'identity456',
844
+ traits: {
845
+ emails: ['valid@email.com', null, undefined, '', 'another@email.com'],
846
+ },
847
+ };
848
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, identity);
849
+ const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
850
+ const emailItems = writtenItems.filter((item) => item.pk.startsWith('email#'));
851
+ expect(emailItems).toHaveLength(2);
852
+ expect(emailItems.map((e) => e.pk)).toContain(`email#${pixelId}#valid@email.com`);
853
+ expect(emailItems.map((e) => e.pk)).toContain(`email#${pixelId}#another@email.com`);
854
+ });
855
+ it('should handle empty emails array', async () => {
856
+ const identity = {
857
+ identityId: 'identity456',
858
+ traits: { emails: [] },
859
+ };
860
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, identity);
861
+ const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
862
+ expect(writtenItems).toHaveLength(1);
863
+ expect(writtenItems[0].pk).toBe(`identity#${pixelId}#identity456`);
864
+ });
865
+ it('should handle email normalization (lowercase)', async () => {
866
+ const identity = {
867
+ identityId: 'identity456',
868
+ traits: {
869
+ emails: ['UPPERCASE@EMAIL.COM', 'MixedCase@Email.Com'],
870
+ },
871
+ };
872
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, identity);
873
+ const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
874
+ const emailPks = writtenItems
875
+ .filter((item) => item.pk.startsWith('email#'))
876
+ .map((item) => item.pk);
877
+ expect(emailPks).toContain(`email#${pixelId}#uppercase@email.com`);
878
+ expect(emailPks).toContain(`email#${pixelId}#mixedcase@email.com`);
879
+ });
880
+ });
881
+ describe('null/undefined traits edge cases', () => {
882
+ const pixelId = 'pixel123';
883
+ it('should handle null traits object', async () => {
884
+ const identity = {
885
+ identityId: 'identity456',
886
+ traits: null,
887
+ };
888
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, identity);
889
+ const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
890
+ expect(writtenItems).toHaveLength(1);
891
+ });
892
+ it('should handle undefined traits object', async () => {
893
+ const identity = {
894
+ identityId: 'identity456',
895
+ traits: undefined,
896
+ };
897
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, identity);
898
+ const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
899
+ expect(writtenItems).toHaveLength(1);
900
+ });
901
+ it('should return cache miss for identity with null traits in lookup', async () => {
902
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
903
+ traits: null,
904
+ });
905
+ expect(result.resolvedIdentity).toBeUndefined();
906
+ expect(result.isCacheStale).toBe(true);
907
+ expect(mockedDynamoDbClient.safeGet).not.toHaveBeenCalled();
908
+ });
909
+ it('should handle identity with undefined emails in traits', async () => {
910
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
911
+ traits: { userIds: ['user123'] },
912
+ });
913
+ expect(result.resolvedIdentity).toBeUndefined();
914
+ expect(result.isCacheStale).toBe(true);
915
+ });
916
+ });
917
+ describe('backward compatibility (old full-blob format)', () => {
918
+ const pixelId = 'pixel123';
919
+ it('should read old format email record with full response blob', async () => {
920
+ const oldFormatEmailRecord = {
921
+ pk: `email#${pixelId}#test@email.com`,
922
+ pixelId,
923
+ identityId: 'discovered-id',
924
+ gsi1pk: 'discovered-id',
925
+ response: {
926
+ identityId: 'discovered-id',
927
+ traits: { emails: ['test@email.com', 'other@email.com'] },
928
+ },
929
+ updatedAt: new Date().toISOString(),
930
+ };
931
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(oldFormatEmailRecord);
932
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
933
+ traits: { emails: ['test@email.com'] },
934
+ });
935
+ expect(result.resolvedIdentity).toBeDefined();
936
+ expect(result.resolvedIdentity?.identityId).toBe('discovered-id');
937
+ expect(result.isCacheStale).toBe(true);
938
+ });
939
+ it('should read new format pointer record (no response blob)', async () => {
940
+ const newFormatPointerRecord = {
941
+ pk: `email#${pixelId}#test@email.com`,
942
+ pixelId,
943
+ identityId: 'discovered-id',
944
+ gsi1pk: 'discovered-id',
945
+ updatedAt: new Date().toISOString(),
946
+ };
947
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(newFormatPointerRecord);
948
+ const result = await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
949
+ traits: { emails: ['test@email.com'] },
950
+ });
951
+ expect(result.resolvedIdentity).toBeDefined();
952
+ expect(result.resolvedIdentity?.identityId).toBe('discovered-id');
953
+ expect(result.isCacheStale).toBe(true);
954
+ });
955
+ it('should trigger Neptune resolution after finding pointer record', async () => {
956
+ const lambdaArn = 'arn:aws:lambda:us-east-1:123456789:function:identity-private';
957
+ const pointerRecord = {
958
+ pk: `email#${pixelId}#test@email.com`,
959
+ pixelId,
960
+ identityId: 'discovered-id',
961
+ gsi1pk: 'discovered-id',
962
+ updatedAt: new Date().toISOString(),
963
+ };
964
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(pointerRecord);
965
+ mockInvokeFunction.mockResolvedValueOnce({
966
+ statusCode: 200,
967
+ body: JSON.stringify({
968
+ identity: {
969
+ identityId: 'discovered-id',
970
+ traits: { emails: ['test@email.com', 'resolved@email.com'] },
971
+ },
972
+ }),
973
+ });
974
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCaching(pixelId, { traits: { emails: ['test@email.com'] } }, lambdaArn);
975
+ expect(mockInvokeFunction).toHaveBeenCalledWith(lambdaArn, expect.objectContaining({
976
+ pixelId,
977
+ context: {
978
+ identity: expect.objectContaining({
979
+ identityId: 'discovered-id',
980
+ }),
981
+ },
982
+ }));
983
+ expect(result?.identityId).toBe('discovered-id');
984
+ });
985
+ });
986
+ describe('deleteIdentityCache with multiple email pointers', () => {
987
+ const pixelId = 'pixel123';
988
+ it('should delete all email pointers from both incoming and resolved identities', async () => {
989
+ mockedDynamoDbClient.safeQueryByGSI.mockResolvedValue([
990
+ { pk: `identity#${pixelId}#identity456`, pixelId },
991
+ ]);
992
+ mockedDynamoDbClient.batchWrite.mockResolvedValue({ $metadata: {} });
993
+ const incomingIdentity = {
994
+ identityId: 'identity456',
995
+ traits: { emails: ['incoming1@email.com', 'incoming2@email.com'] },
996
+ };
997
+ const resolvedIdentity = {
998
+ identityId: 'identity456',
999
+ traits: { emails: ['resolved1@email.com', 'resolved2@email.com', 'resolved3@email.com'] },
1000
+ };
1001
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.deleteIdentityCache(pixelId, incomingIdentity, resolvedIdentity);
1002
+ expect(mockedDynamoDbClient.batchWrite).toHaveBeenCalled();
1003
+ const batchWriteCall = mockedDynamoDbClient.batchWrite.mock.calls[0][0];
1004
+ const deleteRequests = batchWriteCall.RequestItems[Object.keys(batchWriteCall.RequestItems)[0]];
1005
+ const deletedPks = deleteRequests.map((req) => req.DeleteRequest.Key.pk);
1006
+ expect(deletedPks).toContain(`identity#${pixelId}#identity456`);
1007
+ expect(deletedPks).toContain(`email#${pixelId}#incoming1@email.com`);
1008
+ expect(deletedPks).toContain(`email#${pixelId}#incoming2@email.com`);
1009
+ expect(deletedPks).toContain(`email#${pixelId}#resolved1@email.com`);
1010
+ expect(deletedPks).toContain(`email#${pixelId}#resolved2@email.com`);
1011
+ expect(deletedPks).toContain(`email#${pixelId}#resolved3@email.com`);
1012
+ });
1013
+ it('should handle empty emails in delete', async () => {
1014
+ mockedDynamoDbClient.safeQueryByGSI.mockResolvedValue([
1015
+ { pk: `identity#${pixelId}#identity456`, pixelId },
1016
+ ]);
1017
+ mockedDynamoDbClient.batchWrite.mockResolvedValue({ $metadata: {} });
1018
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.deleteIdentityCache(pixelId, { identityId: 'identity456', traits: { emails: [] } }, { identityId: 'identity456' });
1019
+ expect(mockedDynamoDbClient.batchWrite).toHaveBeenCalled();
1020
+ });
1021
+ it('should handle missing traits in delete', async () => {
1022
+ mockedDynamoDbClient.safeQueryByGSI.mockResolvedValue([
1023
+ { pk: `identity#${pixelId}#identity456`, pixelId },
1024
+ ]);
1025
+ mockedDynamoDbClient.batchWrite.mockResolvedValue({ $metadata: {} });
1026
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.deleteIdentityCache(pixelId, { identityId: 'identity456' }, { identityId: 'identity456' });
1027
+ expect(mockedDynamoDbClient.batchWrite).toHaveBeenCalled();
1028
+ });
1029
+ it('should not delete items from other pixelIds', async () => {
1030
+ const otherPixelId = 'other-pixel';
1031
+ mockedDynamoDbClient.safeQueryByGSI.mockResolvedValue([
1032
+ { pk: `identity#${pixelId}#identity456`, pixelId },
1033
+ { pk: `identity#${otherPixelId}#identity456`, pixelId: otherPixelId },
1034
+ ]);
1035
+ mockedDynamoDbClient.batchWrite.mockResolvedValue({ $metadata: {} });
1036
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.deleteIdentityCache(pixelId, { identityId: 'identity456' }, { identityId: 'identity456' });
1037
+ const batchWriteCall = mockedDynamoDbClient.batchWrite.mock.calls[0][0];
1038
+ const deleteRequests = batchWriteCall.RequestItems[Object.keys(batchWriteCall.RequestItems)[0]];
1039
+ const deletedPks = deleteRequests.map((req) => req.DeleteRequest.Key.pk);
1040
+ expect(deletedPks).toContain(`identity#${pixelId}#identity456`);
1041
+ expect(deletedPks).not.toContain(`identity#${otherPixelId}#identity456`);
1042
+ });
1043
+ });
1044
+ describe('GSI1 query for reverse lookups', () => {
1045
+ const pixelId = 'pixel123';
1046
+ it('should find all records by identityId via GSI1', async () => {
1047
+ const identityId = 'identity456';
1048
+ const mockResults = [
1049
+ { pk: `identity#${pixelId}#${identityId}`, pixelId, identityId },
1050
+ { pk: `email#${pixelId}#email1@test.com`, pixelId, identityId },
1051
+ { pk: `email#${pixelId}#email2@test.com`, pixelId, identityId },
1052
+ ];
1053
+ mockedDynamoDbClient.safeQueryByGSI.mockResolvedValue(mockResults);
1054
+ mockedDynamoDbClient.batchWrite.mockResolvedValue({ $metadata: {} });
1055
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.deleteIdentityCache(pixelId, { identityId }, { identityId });
1056
+ expect(mockedDynamoDbClient.safeQueryByGSI).toHaveBeenCalledWith(expect.any(String), expect.any(String), 'gsi1pk', identityId);
1057
+ });
1058
+ it('should handle GSI1 query returning empty results', async () => {
1059
+ mockedDynamoDbClient.safeQueryByGSI.mockResolvedValue([]);
1060
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.deleteIdentityCache(pixelId, { identityId: 'unknown-id' }, { identityId: 'unknown-id' });
1061
+ expect(mockedDynamoDbClient.batchWrite).not.toHaveBeenCalled();
1062
+ });
1063
+ it('should handle GSI1 query error gracefully (fail-open)', async () => {
1064
+ mockedDynamoDbClient.safeQueryByGSI.mockRejectedValueOnce(new Error('GSI query failed'));
1065
+ await expect(identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.deleteIdentityCache(pixelId, { identityId: 'identity456' }, { identityId: 'identity456' })).resolves.toBeUndefined();
1066
+ });
1067
+ });
1068
+ describe('email pointer lookups', () => {
1069
+ const pixelId = 'pixel123';
1070
+ it('should only lookup first email (optimization)', async () => {
1071
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
1072
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
1073
+ traits: { emails: ['first@email.com', 'second@email.com', 'third@email.com'] },
1074
+ });
1075
+ expect(mockedDynamoDbClient.safeGet).toHaveBeenCalledTimes(1);
1076
+ expect(mockedDynamoDbClient.safeGet).toHaveBeenCalledWith(expect.any(String), 'pk', `email#${pixelId}#first@email.com`);
1077
+ });
1078
+ it('should handle whitespace in email during lookup', async () => {
1079
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
1080
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.getIdentityFromCache(pixelId, {
1081
+ traits: { emails: [' test@email.com '] },
1082
+ });
1083
+ expect(mockedDynamoDbClient.safeGet).toHaveBeenCalled();
1084
+ });
1085
+ });
1086
+ describe('fail-open behavior comprehensive', () => {
1087
+ const pixelId = 'pixel123';
1088
+ const lambdaArn = 'arn:aws:lambda:us-east-1:123456789:function:identity-private';
1089
+ it('should return incoming identity when Neptune throws (fail-open fallback)', async () => {
1090
+ const incomingIdentity = {
1091
+ identityId: 'identity456',
1092
+ traits: { emails: ['test@email.com'] },
1093
+ };
1094
+ mockedDynamoDbClient.safeGet.mockRejectedValueOnce(new Error('DynamoDB down'));
1095
+ mockInvokeFunction.mockRejectedValueOnce(new Error('Lambda down'));
1096
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
1097
+ expect(result).toEqual(incomingIdentity);
1098
+ });
1099
+ it('should continue when cache write fails after Neptune success', async () => {
1100
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
1101
+ mockInvokeFunction.mockResolvedValueOnce({
1102
+ statusCode: 200,
1103
+ body: JSON.stringify({
1104
+ identity: { identityId: 'resolved-id', traits: { emails: ['test@email.com'] } },
1105
+ }),
1106
+ });
1107
+ mockedDynamoDbClient.safeBatchWrite.mockRejectedValueOnce(new Error('Write failed'));
1108
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCaching(pixelId, { traits: { emails: ['test@email.com'] } }, lambdaArn);
1109
+ expect(result?.identityId).toBe('resolved-id');
1110
+ });
1111
+ it('should return incoming identity when Neptune returns non-200', async () => {
1112
+ const incomingIdentity = {
1113
+ identityId: 'incoming-id',
1114
+ traits: { emails: ['test@email.com'] },
1115
+ };
1116
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
1117
+ mockInvokeFunction.mockResolvedValueOnce({
1118
+ statusCode: 500,
1119
+ body: 'Internal Server Error',
1120
+ });
1121
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
1122
+ expect(result).toEqual(incomingIdentity);
1123
+ });
1124
+ it('should return incoming identity when Neptune returns malformed JSON (fail-open)', async () => {
1125
+ const incomingIdentity = {
1126
+ identityId: 'test-id',
1127
+ traits: { emails: ['test@email.com'] },
1128
+ };
1129
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
1130
+ mockInvokeFunction.mockResolvedValueOnce({
1131
+ statusCode: 200,
1132
+ body: 'not valid json',
1133
+ });
1134
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCaching(pixelId, incomingIdentity, lambdaArn);
1135
+ expect(result).toEqual(incomingIdentity);
1136
+ });
1137
+ it('should return undefined only when both inputs are invalid', async () => {
1138
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCaching(null, null, lambdaArn);
1139
+ expect(result).toBeUndefined();
1140
+ });
1141
+ });
1142
+ describe('getIdentityWithCachingV2', () => {
1143
+ const pixelId = 'pixel123';
1144
+ const lambdaArn = 'arn:aws:lambda:us-east-1:123456789:function:identity-private';
1145
+ it('should return invalid input summary without resolving', async () => {
1146
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2('', { identityId: 'identity456' }, lambdaArn);
1147
+ expect(result.identity).toBeUndefined();
1148
+ expect(result.summary).toEqual(expect.objectContaining({
1149
+ outcome: types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.INVALID_INPUT,
1150
+ didReadCache: false,
1151
+ didCallNeptune: false,
1152
+ didWriteCache: false,
1153
+ }));
1154
+ expect(mockInvokeFunction).not.toHaveBeenCalled();
1155
+ expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
1156
+ });
1157
+ it('should return fresh cache hit summary without invoking Neptune', async () => {
1158
+ const incomingIdentity = {
1159
+ identityId: 'identity456',
1160
+ traits: { emails: ['test@email.com'] },
1161
+ };
1162
+ const cachedResponse = {
1163
+ pk: `identity#${pixelId}#identity456`,
1164
+ pixelId,
1165
+ identityId: 'identity456',
1166
+ response: {
1167
+ identityId: 'identity456',
1168
+ traits: { emails: ['test@email.com'] },
1169
+ },
1170
+ updatedAt: new Date().toISOString(),
1171
+ };
1172
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
1173
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
1174
+ expect(result.identity?.identityId).toBe('identity456');
1175
+ expect(result.summary).toEqual(expect.objectContaining({
1176
+ outcome: types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.FRESH_CACHE_HIT,
1177
+ pixelId,
1178
+ identityId: 'identity456',
1179
+ didReadCache: true,
1180
+ didCallNeptune: false,
1181
+ didWriteCache: false,
1182
+ cachedIdentity: cachedResponse.response,
1183
+ incomingIdentity,
1184
+ resolvedIdentity: expect.objectContaining({ identityId: 'identity456' }),
1185
+ }));
1186
+ expect(mockInvokeFunction).not.toHaveBeenCalled();
1187
+ });
1188
+ it('should return Neptune write-back summary on cache miss', async () => {
1189
+ const incomingIdentity = {
1190
+ identityId: 'identity456',
1191
+ traits: { emails: ['test@email.com'] },
1192
+ };
1193
+ const neptuneIdentity = {
1194
+ identityId: 'resolved-identity',
1195
+ traits: { emails: ['test@email.com', 'resolved@email.com'] },
1196
+ };
1197
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(null);
1198
+ mockInvokeFunction.mockResolvedValueOnce({
1199
+ statusCode: 200,
1200
+ body: JSON.stringify({ identity: neptuneIdentity }),
1201
+ });
1202
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
1203
+ expect(result.identity).toEqual(neptuneIdentity);
1204
+ expect(result.summary).toEqual(expect.objectContaining({
1205
+ outcome: types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_WRITE_BACK,
1206
+ pixelId,
1207
+ identityId: 'resolved-identity',
1208
+ didReadCache: true,
1209
+ didCallNeptune: true,
1210
+ didWriteCache: true,
1211
+ incomingIdentity,
1212
+ identityForNeptune: incomingIdentity,
1213
+ neptuneIdentity,
1214
+ traitChanges: { reason: { resolved: 'no cached identity' } },
1215
+ }));
1216
+ expect(mockedDynamoDbClient.safeBatchWrite).toHaveBeenCalled();
1217
+ });
1218
+ it('should return Neptune skip-write summary when Neptune matches cached identity', async () => {
1219
+ const incomingIdentity = {
1220
+ identityId: 'identity456',
1221
+ traits: { emails: ['old@email.com', 'new@email.com'] },
1222
+ };
1223
+ const cachedIdentity = {
1224
+ identityId: 'identity456',
1225
+ traits: { emails: ['old@email.com'] },
1226
+ };
1227
+ const cachedResponse = {
1228
+ pk: `identity#${pixelId}#identity456`,
1229
+ pixelId,
1230
+ identityId: 'identity456',
1231
+ response: cachedIdentity,
1232
+ updatedAt: new Date().toISOString(),
1233
+ };
1234
+ mockedDynamoDbClient.safeGet.mockResolvedValueOnce(cachedResponse);
1235
+ mockInvokeFunction.mockResolvedValueOnce({
1236
+ statusCode: 200,
1237
+ body: JSON.stringify({ identity: cachedIdentity }),
1238
+ });
1239
+ const result = await identity_gateway_service_js_1.IdentityGatewayService.getIdentityWithCachingV2(pixelId, incomingIdentity, lambdaArn);
1240
+ expect(result.identity).toEqual(cachedIdentity);
1241
+ expect(result.summary).toEqual(expect.objectContaining({
1242
+ outcome: types_js_1.IDENTITY_CACHE_WITH_CACHING_OUTCOME.NEPTUNE_SKIP_WRITE,
1243
+ pixelId,
1244
+ identityId: 'identity456',
1245
+ didReadCache: true,
1246
+ didCallNeptune: true,
1247
+ didWriteCache: false,
1248
+ cachedIdentity,
1249
+ incomingIdentity,
1250
+ identityForNeptune: expect.objectContaining({ identityId: 'identity456' }),
1251
+ neptuneIdentity: cachedIdentity,
1252
+ }));
1253
+ expect(mockedDynamoDbClient.safeBatchWrite).not.toHaveBeenCalled();
1254
+ });
1255
+ });
1256
+ describe('gsi1pk attribute on all records', () => {
1257
+ const pixelId = 'pixel123';
1258
+ it('should include gsi1pk on identity record', async () => {
1259
+ const identity = {
1260
+ identityId: 'identity456',
1261
+ traits: { emails: ['test@email.com'] },
1262
+ };
1263
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, identity);
1264
+ const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
1265
+ const identityItem = writtenItems.find((item) => item.pk.startsWith('identity#'));
1266
+ expect(identityItem).toBeDefined();
1267
+ expect(identityItem?.gsi1pk).toBe('identity456');
1268
+ });
1269
+ it('should include gsi1pk on all email pointer records', async () => {
1270
+ const identity = {
1271
+ identityId: 'identity456',
1272
+ traits: { emails: ['email1@test.com', 'email2@test.com'] },
1273
+ };
1274
+ await identity_cache_dynamodb_db_service_js_1.IdentityCacheDynamoDbDbService.updateIdentityCache(pixelId, identity);
1275
+ const writtenItems = mockedDynamoDbClient.safeBatchWrite.mock.calls[0][1];
1276
+ const emailItems = writtenItems.filter((item) => item.pk.startsWith('email#'));
1277
+ for (const emailItem of emailItems) {
1278
+ expect(emailItem.gsi1pk).toBe('identity456');
1279
+ }
1280
+ });
1281
+ });
1282
+ });
1141
1283
  //# sourceMappingURL=identity-cache-dynamodb-service.spec.js.map