@openstax/ts-utils 1.33.0 → 1.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (503) hide show
  1. package/.cfnlintrc +2 -0
  2. package/.github/CODEOWNERS +1 -0
  3. package/.github/workflows/ci.yml +36 -0
  4. package/.github/workflows/lint.yml +55 -0
  5. package/.nvmrc +1 -0
  6. package/.syncignore +4 -0
  7. package/.syncpackrc +18 -0
  8. package/CONTRIBUTING.md +96 -0
  9. package/LICENSE +661 -0
  10. package/Procfile +1 -0
  11. package/README.md +62 -90
  12. package/app.json +23 -0
  13. package/cspell.json +32 -0
  14. package/deploy/constants.env +21 -0
  15. package/deploy/deploy.bash +157 -0
  16. package/deploy/deployment-alt-region.cfn.yml +70 -0
  17. package/deploy/deployment.cfn.yml +650 -0
  18. package/deploy/destroy-deployment.bash +23 -0
  19. package/deploy/shared.cfn.yml +94 -0
  20. package/docs/lambda-build.md +35 -0
  21. package/package.json +12 -228
  22. package/packages/frontend/README.md +46 -0
  23. package/packages/frontend/package.json +101 -0
  24. package/packages/frontend/public/favicon.ico +0 -0
  25. package/packages/frontend/public/index.html +107 -0
  26. package/packages/frontend/public/maintenance.html +59 -0
  27. package/packages/frontend/public/manifest.json +15 -0
  28. package/packages/frontend/public/robots.txt +3 -0
  29. package/packages/frontend/script/make-certificate.bash +49 -0
  30. package/packages/frontend/script/server/cli.js +11 -0
  31. package/packages/frontend/script/server/index.js +47 -0
  32. package/packages/frontend/script/start.bash +22 -0
  33. package/packages/frontend/script/trust-localhost.bash +7 -0
  34. package/packages/frontend/src/auth/authProvider.ts +10 -0
  35. package/packages/frontend/src/auth/useAuth.ts +33 -0
  36. package/packages/frontend/src/components/Pagination.tsx +26 -0
  37. package/packages/frontend/src/configProvider/index.ts +53 -0
  38. package/packages/frontend/src/configProvider/use.ts +41 -0
  39. package/packages/frontend/src/core/context/services.spec.tsx +39 -0
  40. package/packages/frontend/src/core/context/services.tsx +16 -0
  41. package/packages/frontend/src/core/index.spec.ts +7 -0
  42. package/packages/frontend/src/core/index.ts +20 -0
  43. package/packages/frontend/src/core/services.tsx +14 -0
  44. package/packages/frontend/src/core/types.ts +3 -0
  45. package/packages/frontend/src/example/api.ts +28 -0
  46. package/packages/frontend/src/example/components/Layout.tsx +23 -0
  47. package/packages/frontend/src/example/screens/Home.spec.tsx +68 -0
  48. package/packages/frontend/src/example/screens/Home.tsx +78 -0
  49. package/packages/frontend/src/example/screens/ThingList.spec.tsx +60 -0
  50. package/packages/frontend/src/example/screens/ThingList.tsx +75 -0
  51. package/packages/frontend/src/example/screens/ThingView.spec.tsx +71 -0
  52. package/packages/frontend/src/example/screens/ThingView.tsx +47 -0
  53. package/packages/frontend/src/example/screens/index.ts +9 -0
  54. package/packages/frontend/src/index.css +159 -0
  55. package/packages/frontend/src/index.tsx +67 -0
  56. package/packages/frontend/src/react-app-env.d.ts +1 -0
  57. package/packages/frontend/src/routing/components/RouteLink.spec.tsx +55 -0
  58. package/packages/frontend/src/routing/components/RouteLink.tsx +35 -0
  59. package/packages/frontend/src/routing/middleware.ts +6 -0
  60. package/packages/frontend/src/routing/useQuery.ts +14 -0
  61. package/packages/frontend/src/setupProxy.js +19 -0
  62. package/packages/frontend/src/setupTests.ts +9 -0
  63. package/packages/frontend/src/tests/testServices.tsx +23 -0
  64. package/packages/frontend/tsconfig.json +27 -0
  65. package/packages/lambda/.eslintrc.js +64 -0
  66. package/packages/lambda/jest-global-setup.js +3 -0
  67. package/packages/lambda/jest-setup-after-env.js +1 -0
  68. package/packages/lambda/jest.config.js +31 -0
  69. package/packages/lambda/jest.resolver.js +17 -0
  70. package/packages/lambda/package.json +68 -0
  71. package/packages/lambda/script/build.bash +19 -0
  72. package/packages/lambda/script/bundle-functions.bash +10 -0
  73. package/packages/lambda/script/lambdaLocalProxy.js +16 -0
  74. package/packages/lambda/script/lambdaLocalProxy.spec.ts +147 -0
  75. package/packages/lambda/script/utils/getRouteData.ts +7 -0
  76. package/packages/lambda/script/utils/routeDataLoader.js +8 -0
  77. package/packages/lambda/script/utils/routeDataLoader.spec.ts +8 -0
  78. package/packages/lambda/src/functions/serviceApi/core/index.ts +7 -0
  79. package/packages/lambda/src/functions/serviceApi/core/request.spec.ts +38 -0
  80. package/packages/lambda/src/functions/serviceApi/core/request.ts +42 -0
  81. package/packages/lambda/src/functions/serviceApi/core/routes.spec.ts +7 -0
  82. package/packages/lambda/src/functions/serviceApi/core/routes.ts +10 -0
  83. package/packages/lambda/src/functions/serviceApi/core/services.ts +9 -0
  84. package/packages/lambda/src/functions/serviceApi/core/types.ts +13 -0
  85. package/packages/lambda/src/functions/serviceApi/entry/lambda/https-xray.ts +4 -0
  86. package/packages/lambda/src/functions/serviceApi/entry/lambda/index.spec.ts +48 -0
  87. package/packages/lambda/src/functions/serviceApi/entry/lambda/index.ts +58 -0
  88. package/packages/lambda/src/functions/serviceApi/entry/lambda/services.ts +36 -0
  89. package/packages/lambda/src/functions/serviceApi/entry/local.ts +71 -0
  90. package/packages/lambda/src/functions/serviceApi/versions/v0/example/documentSearchMiddleware.spec.ts +16 -0
  91. package/packages/lambda/src/functions/serviceApi/versions/v0/example/documentSearchMiddleware.ts +41 -0
  92. package/packages/lambda/src/functions/serviceApi/versions/v0/example/documentStoreMiddleware.spec.ts +78 -0
  93. package/packages/lambda/src/functions/serviceApi/versions/v0/example/documentStoreMiddleware.ts +70 -0
  94. package/packages/lambda/src/functions/serviceApi/versions/v0/example/routes.spec.ts +306 -0
  95. package/packages/lambda/src/functions/serviceApi/versions/v0/example/routes.ts +176 -0
  96. package/packages/lambda/src/functions/serviceApi/versions/v0/index.spec.ts +263 -0
  97. package/packages/lambda/src/functions/serviceApi/versions/v0/index.ts +134 -0
  98. package/packages/lambda/src/functions/serviceApi/versions/v0/middleware/authMiddleware.spec.ts +23 -0
  99. package/packages/lambda/src/functions/serviceApi/versions/v0/middleware/authMiddleware.ts +32 -0
  100. package/packages/lambda/src/functions/serviceApi/versions/v0/middleware/configMiddleware.spec.ts +10 -0
  101. package/packages/lambda/src/functions/serviceApi/versions/v0/middleware/configMiddleware.ts +7 -0
  102. package/packages/lambda/src/functions/serviceApi/versions/v0/middleware/frontendFileServerMiddleware.spec.ts +13 -0
  103. package/packages/lambda/src/functions/serviceApi/versions/v0/middleware/frontendFileServerMiddleware.ts +23 -0
  104. package/packages/lambda/src/functions/serviceApi/versions/v0/middleware/paginationMiddleware.spec.ts +9 -0
  105. package/packages/lambda/src/functions/serviceApi/versions/v0/middleware/paginationMiddleware.ts +9 -0
  106. package/packages/lambda/src/functions/serviceApi/versions/v0/middleware/searchMiddleware.spec.ts +12 -0
  107. package/packages/lambda/src/functions/serviceApi/versions/v0/middleware/searchMiddleware.ts +21 -0
  108. package/packages/lambda/src/functions/serviceApi/versions/v0/middleware/userRoleValidatorMiddleware.spec.ts +21 -0
  109. package/packages/lambda/src/functions/serviceApi/versions/v0/middleware/userRoleValidatorMiddleware.ts +18 -0
  110. package/packages/lambda/tsconfig.json +30 -0
  111. package/packages/lambda/webpack.config.js +97 -0
  112. package/packages/utils/.eslintrc.js +64 -0
  113. package/packages/utils/README.md +118 -0
  114. package/packages/utils/jest-global-setup.js +3 -0
  115. package/packages/utils/jest.config.js +25 -0
  116. package/packages/utils/jest.resolver.js +17 -0
  117. package/packages/utils/package.json +238 -0
  118. package/packages/utils/src/assertions/index.spec.ts +126 -0
  119. package/{dist/esm/assertions/index.js → packages/utils/src/assertions/index.ts} +64 -49
  120. package/packages/utils/src/aws/ssmService.ts +7 -0
  121. package/packages/utils/src/config/awsParameterConfig.ts +24 -0
  122. package/packages/utils/src/config/envConfig.ts +58 -0
  123. package/packages/utils/src/config/index.spec.ts +165 -0
  124. package/{dist/esm/config/index.d.ts → packages/utils/src/config/index.ts} +29 -13
  125. package/packages/utils/src/config/lambdaParameterConfig.ts +49 -0
  126. package/{dist/esm/config/replaceConfig.js → packages/utils/src/config/replaceConfig.ts} +16 -6
  127. package/packages/utils/src/config/resolveConfigValue.ts +10 -0
  128. package/packages/utils/src/errors/index.spec.ts +35 -0
  129. package/{dist/esm/errors/index.js → packages/utils/src/errors/index.ts} +57 -41
  130. package/packages/utils/src/fetch/fetchStatusRetry.spec.ts +197 -0
  131. package/packages/utils/src/fetch/fetchStatusRetry.ts +33 -0
  132. package/packages/utils/src/fetch/index.spec.ts +34 -0
  133. package/packages/utils/src/fetch/index.ts +87 -0
  134. package/packages/utils/src/guards/index.spec.ts +58 -0
  135. package/{dist/esm/guards/index.d.ts → packages/utils/src/guards/index.ts} +10 -7
  136. package/packages/utils/src/index.spec.ts +471 -0
  137. package/packages/utils/src/middleware/apiErrorHandler.spec.ts +65 -0
  138. package/packages/utils/src/middleware/apiErrorHandler.ts +67 -0
  139. package/packages/utils/src/middleware/apiSlowResponseMiddleware.spec.ts +184 -0
  140. package/packages/utils/src/middleware/apiSlowResponseMiddleware.ts +71 -0
  141. package/packages/utils/src/middleware/index.spec.ts +99 -0
  142. package/{dist/cjs/middleware/index.d.ts → packages/utils/src/middleware/index.ts} +53 -5
  143. package/packages/utils/src/middleware/lambdaCorsResponseMiddleware.spec.ts +103 -0
  144. package/packages/utils/src/middleware/lambdaCorsResponseMiddleware.ts +52 -0
  145. package/packages/utils/src/middleware/throwNotFoundMiddleware.spec.ts +20 -0
  146. package/packages/utils/src/middleware/throwNotFoundMiddleware.ts +11 -0
  147. package/packages/utils/src/misc/hashValue.ts +18 -0
  148. package/packages/utils/src/misc/helpers.ts +259 -0
  149. package/packages/utils/src/misc/merge.ts +48 -0
  150. package/{dist/esm/misc/partitionSequence.js → packages/utils/src/misc/partitionSequence.ts} +23 -15
  151. package/packages/utils/src/pagination/index.spec.ts +150 -0
  152. package/packages/utils/src/pagination/index.ts +117 -0
  153. package/{dist/esm/routing/helpers.js → packages/utils/src/routing/helpers.ts} +42 -30
  154. package/packages/utils/src/routing/index.spec.ts +553 -0
  155. package/packages/utils/src/routing/index.ts +424 -0
  156. package/packages/utils/src/routing/validators/zod.spec.ts +16 -0
  157. package/packages/utils/src/routing/validators/zod.ts +14 -0
  158. package/packages/utils/src/services/accountsGateway/README.md +3 -0
  159. package/packages/utils/src/services/accountsGateway/index.spec.ts +518 -0
  160. package/packages/utils/src/services/accountsGateway/index.ts +251 -0
  161. package/packages/utils/src/services/apiGateway/README.md +93 -0
  162. package/packages/utils/src/services/apiGateway/index.spec.ts +254 -0
  163. package/packages/utils/src/services/apiGateway/index.ts +189 -0
  164. package/packages/utils/src/services/authProvider/README.md +21 -0
  165. package/packages/utils/src/services/authProvider/browser.spec.ts +391 -0
  166. package/packages/utils/src/services/authProvider/browser.ts +209 -0
  167. package/packages/utils/src/services/authProvider/decryption.spec.ts +337 -0
  168. package/packages/utils/src/services/authProvider/decryption.ts +98 -0
  169. package/packages/utils/src/services/authProvider/index.ts +93 -0
  170. package/packages/utils/src/services/authProvider/stub.spec.ts +29 -0
  171. package/packages/utils/src/services/authProvider/subrequest.spec.ts +105 -0
  172. package/packages/utils/src/services/authProvider/subrequest.ts +68 -0
  173. package/packages/utils/src/services/authProvider/utils/decryptAndVerify.spec.ts +128 -0
  174. package/packages/utils/src/services/authProvider/utils/decryptAndVerify.ts +106 -0
  175. package/packages/utils/src/services/authProvider/utils/embeddedAuthProvider.spec.ts +26 -0
  176. package/packages/utils/src/services/authProvider/utils/embeddedAuthProvider.ts +57 -0
  177. package/packages/utils/src/services/authProvider/utils/userRoleValidator.spec.ts +135 -0
  178. package/packages/utils/src/services/authProvider/utils/userRoleValidator.ts +49 -0
  179. package/packages/utils/src/services/authProvider/utils/userSubrequest.spec.ts +26 -0
  180. package/packages/utils/src/services/authProvider/utils/userSubrequest.ts +10 -0
  181. package/packages/utils/src/services/documentStore/dynamoEncoding.ts +57 -0
  182. package/packages/utils/src/services/documentStore/fileSystemAssert.spec.ts +43 -0
  183. package/packages/utils/src/services/documentStore/fileSystemAssert.ts +10 -0
  184. package/{dist/cjs/services/documentStore/index.d.ts → packages/utils/src/services/documentStore/index.ts} +8 -8
  185. package/packages/utils/src/services/documentStore/unversioned/README.md +13 -0
  186. package/packages/utils/src/services/documentStore/unversioned/dynamodb.spec.ts +859 -0
  187. package/packages/utils/src/services/documentStore/unversioned/dynamodb.ts +243 -0
  188. package/packages/utils/src/services/documentStore/unversioned/file-system.spec.ts +629 -0
  189. package/packages/utils/src/services/documentStore/unversioned/file-system.ts +194 -0
  190. package/{dist/cjs/services/documentStore/unversioned/index.d.ts → packages/utils/src/services/documentStore/unversioned/index.ts} +2 -0
  191. package/packages/utils/src/services/documentStore/versioned/README.md +13 -0
  192. package/packages/utils/src/services/documentStore/versioned/dynamodb.spec.ts +376 -0
  193. package/packages/utils/src/services/documentStore/versioned/dynamodb.ts +167 -0
  194. package/packages/utils/src/services/documentStore/versioned/file-system.spec.ts +262 -0
  195. package/packages/utils/src/services/documentStore/versioned/file-system.ts +90 -0
  196. package/packages/utils/src/services/documentStore/versioned/index.ts +25 -0
  197. package/packages/utils/src/services/exercisesGateway/README.md +5 -0
  198. package/packages/utils/src/services/exercisesGateway/index.spec.ts +326 -0
  199. package/packages/utils/src/services/exercisesGateway/index.ts +163 -0
  200. package/packages/utils/src/services/fileServer/index.spec.ts +88 -0
  201. package/packages/utils/src/services/fileServer/index.ts +43 -0
  202. package/packages/utils/src/services/fileServer/localFileServer.spec.ts +182 -0
  203. package/packages/utils/src/services/fileServer/localFileServer.ts +159 -0
  204. package/packages/utils/src/services/fileServer/s3FileServer.spec.ts +266 -0
  205. package/packages/utils/src/services/fileServer/s3FileServer.ts +155 -0
  206. package/packages/utils/src/services/launchParams/index.spec.ts +366 -0
  207. package/packages/utils/src/services/launchParams/signer.ts +73 -0
  208. package/packages/utils/src/services/launchParams/verifier.ts +120 -0
  209. package/packages/utils/src/services/logger/console.spec.ts +29 -0
  210. package/{dist/esm/services/logger/console.js → packages/utils/src/services/logger/console.ts} +5 -2
  211. package/packages/utils/src/services/logger/index.spec.ts +65 -0
  212. package/{dist/esm/services/logger/index.d.ts → packages/utils/src/services/logger/index.ts} +23 -9
  213. package/packages/utils/src/services/lrsGateway/README.md +5 -0
  214. package/packages/utils/src/services/lrsGateway/addStatementDefaultFields.ts +22 -0
  215. package/packages/utils/src/services/lrsGateway/attempt-utils.spec.ts +847 -0
  216. package/packages/utils/src/services/lrsGateway/attempt-utils.ts +358 -0
  217. package/packages/utils/src/services/lrsGateway/file-system.spec.ts +363 -0
  218. package/packages/utils/src/services/lrsGateway/file-system.ts +165 -0
  219. package/packages/utils/src/services/lrsGateway/index.spec.ts +194 -0
  220. package/packages/utils/src/services/lrsGateway/index.ts +257 -0
  221. package/packages/utils/src/services/lrsGateway/xapiUtils.spec.ts +887 -0
  222. package/packages/utils/src/services/lrsGateway/xapiUtils.ts +262 -0
  223. package/packages/utils/src/services/postgresConnection/index.spec.ts +170 -0
  224. package/packages/utils/src/services/postgresConnection/index.ts +84 -0
  225. package/packages/utils/src/services/searchProvider/README.md +3 -0
  226. package/packages/utils/src/services/searchProvider/index.ts +59 -0
  227. package/packages/utils/src/services/searchProvider/memorySearchTheBadWay.spec.ts +526 -0
  228. package/packages/utils/src/services/searchProvider/memorySearchTheBadWay.ts +223 -0
  229. package/packages/utils/src/services/searchProvider/openSearch.spec.ts +926 -0
  230. package/packages/utils/src/services/searchProvider/openSearch.ts +195 -0
  231. package/{dist/esm/types.d.ts → packages/utils/src/types.ts} +34 -6
  232. package/packages/utils/tsconfig.json +31 -0
  233. package/packages/utils/tsconfig.without-specs.cjs.json +7 -0
  234. package/packages/utils/tsconfig.without-specs.esm.json +7 -0
  235. package/packages/utils/tsconfig.without-specs.json +6 -0
  236. package/scripts/build.bash +24 -0
  237. package/scripts/ci.bash +10 -0
  238. package/scripts/start.bash +29 -0
  239. package/dist/cjs/assertions/index.d.ts +0 -89
  240. package/dist/cjs/assertions/index.js +0 -157
  241. package/dist/cjs/aws/ssmService.d.ts +0 -5
  242. package/dist/cjs/aws/ssmService.js +0 -9
  243. package/dist/cjs/config/awsParameterConfig.d.ts +0 -10
  244. package/dist/cjs/config/awsParameterConfig.js +0 -26
  245. package/dist/cjs/config/envConfig.d.ts +0 -24
  246. package/dist/cjs/config/envConfig.js +0 -57
  247. package/dist/cjs/config/index.d.ts +0 -48
  248. package/dist/cjs/config/index.js +0 -35
  249. package/dist/cjs/config/lambdaParameterConfig.d.ts +0 -12
  250. package/dist/cjs/config/lambdaParameterConfig.js +0 -45
  251. package/dist/cjs/config/replaceConfig.d.ts +0 -14
  252. package/dist/cjs/config/replaceConfig.js +0 -22
  253. package/dist/cjs/config/resolveConfigValue.d.ts +0 -5
  254. package/dist/cjs/config/resolveConfigValue.js +0 -12
  255. package/dist/cjs/errors/index.d.ts +0 -88
  256. package/dist/cjs/errors/index.js +0 -123
  257. package/dist/cjs/fetch/fetchStatusRetry.d.ts +0 -8
  258. package/dist/cjs/fetch/fetchStatusRetry.js +0 -27
  259. package/dist/cjs/fetch/index.d.ts +0 -64
  260. package/dist/cjs/fetch/index.js +0 -55
  261. package/dist/cjs/guards/index.d.ts +0 -38
  262. package/dist/cjs/guards/index.js +0 -44
  263. package/dist/cjs/index.js +0 -20
  264. package/dist/cjs/middleware/apiErrorHandler.d.ts +0 -24
  265. package/dist/cjs/middleware/apiErrorHandler.js +0 -42
  266. package/dist/cjs/middleware/apiSlowResponseMiddleware.d.ts +0 -23
  267. package/dist/cjs/middleware/apiSlowResponseMiddleware.js +0 -54
  268. package/dist/cjs/middleware/index.js +0 -48
  269. package/dist/cjs/middleware/lambdaCorsResponseMiddleware.d.ts +0 -20
  270. package/dist/cjs/middleware/lambdaCorsResponseMiddleware.js +0 -44
  271. package/dist/cjs/middleware/throwNotFoundMiddleware.d.ts +0 -4
  272. package/dist/cjs/middleware/throwNotFoundMiddleware.js +0 -14
  273. package/dist/cjs/misc/hashValue.d.ts +0 -10
  274. package/dist/cjs/misc/hashValue.js +0 -17
  275. package/dist/cjs/misc/helpers.d.ts +0 -124
  276. package/dist/cjs/misc/helpers.js +0 -214
  277. package/dist/cjs/misc/merge.d.ts +0 -21
  278. package/dist/cjs/misc/merge.js +0 -45
  279. package/dist/cjs/misc/partitionSequence.d.ts +0 -35
  280. package/dist/cjs/misc/partitionSequence.js +0 -55
  281. package/dist/cjs/pagination/index.d.ts +0 -91
  282. package/dist/cjs/pagination/index.js +0 -83
  283. package/dist/cjs/routing/helpers.d.ts +0 -57
  284. package/dist/cjs/routing/helpers.js +0 -90
  285. package/dist/cjs/routing/index.d.ts +0 -290
  286. package/dist/cjs/routing/index.js +0 -295
  287. package/dist/cjs/routing/validators/zod.d.ts +0 -4
  288. package/dist/cjs/routing/validators/zod.js +0 -14
  289. package/dist/cjs/services/accountsGateway/index.d.ts +0 -92
  290. package/dist/cjs/services/accountsGateway/index.js +0 -138
  291. package/dist/cjs/services/apiGateway/index.d.ts +0 -68
  292. package/dist/cjs/services/apiGateway/index.js +0 -118
  293. package/dist/cjs/services/authProvider/browser.d.ts +0 -40
  294. package/dist/cjs/services/authProvider/browser.js +0 -155
  295. package/dist/cjs/services/authProvider/decryption.d.ts +0 -19
  296. package/dist/cjs/services/authProvider/decryption.js +0 -73
  297. package/dist/cjs/services/authProvider/index.d.ts +0 -63
  298. package/dist/cjs/services/authProvider/index.js +0 -34
  299. package/dist/cjs/services/authProvider/subrequest.d.ts +0 -13
  300. package/dist/cjs/services/authProvider/subrequest.js +0 -49
  301. package/dist/cjs/services/authProvider/utils/decryptAndVerify.d.ts +0 -28
  302. package/dist/cjs/services/authProvider/utils/decryptAndVerify.js +0 -91
  303. package/dist/cjs/services/authProvider/utils/embeddedAuthProvider.d.ts +0 -26
  304. package/dist/cjs/services/authProvider/utils/embeddedAuthProvider.js +0 -47
  305. package/dist/cjs/services/authProvider/utils/userRoleValidator.d.ts +0 -13
  306. package/dist/cjs/services/authProvider/utils/userRoleValidator.js +0 -37
  307. package/dist/cjs/services/authProvider/utils/userSubrequest.d.ts +0 -3
  308. package/dist/cjs/services/authProvider/utils/userSubrequest.js +0 -13
  309. package/dist/cjs/services/documentStore/dynamoEncoding.d.ts +0 -10
  310. package/dist/cjs/services/documentStore/dynamoEncoding.js +0 -52
  311. package/dist/cjs/services/documentStore/fileSystemAssert.d.ts +0 -1
  312. package/dist/cjs/services/documentStore/fileSystemAssert.js +0 -14
  313. package/dist/cjs/services/documentStore/index.js +0 -2
  314. package/dist/cjs/services/documentStore/unversioned/dynamodb.d.ts +0 -31
  315. package/dist/cjs/services/documentStore/unversioned/dynamodb.js +0 -233
  316. package/dist/cjs/services/documentStore/unversioned/file-system.d.ts +0 -32
  317. package/dist/cjs/services/documentStore/unversioned/file-system.js +0 -214
  318. package/dist/cjs/services/documentStore/unversioned/index.js +0 -2
  319. package/dist/cjs/services/documentStore/versioned/dynamodb.d.ts +0 -25
  320. package/dist/cjs/services/documentStore/versioned/dynamodb.js +0 -143
  321. package/dist/cjs/services/documentStore/versioned/file-system.d.ts +0 -25
  322. package/dist/cjs/services/documentStore/versioned/file-system.js +0 -73
  323. package/dist/cjs/services/documentStore/versioned/index.d.ts +0 -17
  324. package/dist/cjs/services/documentStore/versioned/index.js +0 -2
  325. package/dist/cjs/services/exercisesGateway/index.d.ts +0 -67
  326. package/dist/cjs/services/exercisesGateway/index.js +0 -107
  327. package/dist/cjs/services/fileServer/index.d.ts +0 -30
  328. package/dist/cjs/services/fileServer/index.js +0 -19
  329. package/dist/cjs/services/fileServer/localFileServer.d.ts +0 -13
  330. package/dist/cjs/services/fileServer/localFileServer.js +0 -132
  331. package/dist/cjs/services/fileServer/s3FileServer.d.ts +0 -14
  332. package/dist/cjs/services/fileServer/s3FileServer.js +0 -131
  333. package/dist/cjs/services/launchParams/index.js +0 -7
  334. package/dist/cjs/services/launchParams/signer.d.ts +0 -23
  335. package/dist/cjs/services/launchParams/signer.js +0 -58
  336. package/dist/cjs/services/launchParams/verifier.d.ts +0 -21
  337. package/dist/cjs/services/launchParams/verifier.js +0 -129
  338. package/dist/cjs/services/logger/console.d.ts +0 -4
  339. package/dist/cjs/services/logger/console.js +0 -12
  340. package/dist/cjs/services/logger/index.d.ts +0 -39
  341. package/dist/cjs/services/logger/index.js +0 -31
  342. package/dist/cjs/services/lrsGateway/addStatementDefaultFields.d.ts +0 -5
  343. package/dist/cjs/services/lrsGateway/addStatementDefaultFields.js +0 -21
  344. package/dist/cjs/services/lrsGateway/attempt-utils.d.ts +0 -70
  345. package/dist/cjs/services/lrsGateway/attempt-utils.js +0 -258
  346. package/dist/cjs/services/lrsGateway/file-system.d.ts +0 -15
  347. package/dist/cjs/services/lrsGateway/file-system.js +0 -150
  348. package/dist/cjs/services/lrsGateway/index.d.ts +0 -122
  349. package/dist/cjs/services/lrsGateway/index.js +0 -148
  350. package/dist/cjs/services/lrsGateway/xapiUtils.d.ts +0 -68
  351. package/dist/cjs/services/lrsGateway/xapiUtils.js +0 -109
  352. package/dist/cjs/services/postgresConnection/index.d.ts +0 -28
  353. package/dist/cjs/services/postgresConnection/index.js +0 -65
  354. package/dist/cjs/services/searchProvider/index.d.ts +0 -67
  355. package/dist/cjs/services/searchProvider/index.js +0 -2
  356. package/dist/cjs/services/searchProvider/memorySearchTheBadWay.d.ts +0 -20
  357. package/dist/cjs/services/searchProvider/memorySearchTheBadWay.js +0 -191
  358. package/dist/cjs/services/searchProvider/openSearch.d.ts +0 -28
  359. package/dist/cjs/services/searchProvider/openSearch.js +0 -154
  360. package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +0 -1
  361. package/dist/cjs/types.d.ts +0 -31
  362. package/dist/cjs/types.js +0 -2
  363. package/dist/esm/assertions/index.d.ts +0 -89
  364. package/dist/esm/aws/ssmService.d.ts +0 -5
  365. package/dist/esm/aws/ssmService.js +0 -6
  366. package/dist/esm/config/awsParameterConfig.d.ts +0 -10
  367. package/dist/esm/config/awsParameterConfig.js +0 -22
  368. package/dist/esm/config/envConfig.d.ts +0 -24
  369. package/dist/esm/config/envConfig.js +0 -53
  370. package/dist/esm/config/index.js +0 -17
  371. package/dist/esm/config/lambdaParameterConfig.d.ts +0 -12
  372. package/dist/esm/config/lambdaParameterConfig.js +0 -38
  373. package/dist/esm/config/replaceConfig.d.ts +0 -14
  374. package/dist/esm/config/resolveConfigValue.d.ts +0 -5
  375. package/dist/esm/config/resolveConfigValue.js +0 -8
  376. package/dist/esm/errors/index.d.ts +0 -88
  377. package/dist/esm/fetch/fetchStatusRetry.d.ts +0 -8
  378. package/dist/esm/fetch/fetchStatusRetry.js +0 -23
  379. package/dist/esm/fetch/index.d.ts +0 -64
  380. package/dist/esm/fetch/index.js +0 -46
  381. package/dist/esm/guards/index.js +0 -36
  382. package/dist/esm/index.d.ts +0 -4
  383. package/dist/esm/index.js +0 -4
  384. package/dist/esm/middleware/apiErrorHandler.d.ts +0 -24
  385. package/dist/esm/middleware/apiErrorHandler.js +0 -38
  386. package/dist/esm/middleware/apiSlowResponseMiddleware.d.ts +0 -23
  387. package/dist/esm/middleware/apiSlowResponseMiddleware.js +0 -50
  388. package/dist/esm/middleware/index.d.ts +0 -47
  389. package/dist/esm/middleware/index.js +0 -44
  390. package/dist/esm/middleware/lambdaCorsResponseMiddleware.d.ts +0 -20
  391. package/dist/esm/middleware/lambdaCorsResponseMiddleware.js +0 -40
  392. package/dist/esm/middleware/throwNotFoundMiddleware.d.ts +0 -4
  393. package/dist/esm/middleware/throwNotFoundMiddleware.js +0 -10
  394. package/dist/esm/misc/hashValue.d.ts +0 -10
  395. package/dist/esm/misc/hashValue.js +0 -13
  396. package/dist/esm/misc/helpers.d.ts +0 -124
  397. package/dist/esm/misc/helpers.js +0 -199
  398. package/dist/esm/misc/merge.d.ts +0 -21
  399. package/dist/esm/misc/merge.js +0 -40
  400. package/dist/esm/misc/partitionSequence.d.ts +0 -35
  401. package/dist/esm/pagination/index.d.ts +0 -91
  402. package/dist/esm/pagination/index.js +0 -77
  403. package/dist/esm/routing/helpers.d.ts +0 -57
  404. package/dist/esm/routing/index.d.ts +0 -290
  405. package/dist/esm/routing/index.js +0 -246
  406. package/dist/esm/routing/validators/zod.d.ts +0 -4
  407. package/dist/esm/routing/validators/zod.js +0 -10
  408. package/dist/esm/services/accountsGateway/index.d.ts +0 -92
  409. package/dist/esm/services/accountsGateway/index.js +0 -131
  410. package/dist/esm/services/apiGateway/index.d.ts +0 -68
  411. package/dist/esm/services/apiGateway/index.js +0 -77
  412. package/dist/esm/services/authProvider/browser.d.ts +0 -40
  413. package/dist/esm/services/authProvider/browser.js +0 -151
  414. package/dist/esm/services/authProvider/decryption.d.ts +0 -19
  415. package/dist/esm/services/authProvider/decryption.js +0 -69
  416. package/dist/esm/services/authProvider/index.d.ts +0 -63
  417. package/dist/esm/services/authProvider/index.js +0 -26
  418. package/dist/esm/services/authProvider/subrequest.d.ts +0 -13
  419. package/dist/esm/services/authProvider/subrequest.js +0 -45
  420. package/dist/esm/services/authProvider/utils/decryptAndVerify.d.ts +0 -28
  421. package/dist/esm/services/authProvider/utils/decryptAndVerify.js +0 -85
  422. package/dist/esm/services/authProvider/utils/embeddedAuthProvider.d.ts +0 -26
  423. package/dist/esm/services/authProvider/utils/embeddedAuthProvider.js +0 -40
  424. package/dist/esm/services/authProvider/utils/userRoleValidator.d.ts +0 -13
  425. package/dist/esm/services/authProvider/utils/userRoleValidator.js +0 -33
  426. package/dist/esm/services/authProvider/utils/userSubrequest.d.ts +0 -3
  427. package/dist/esm/services/authProvider/utils/userSubrequest.js +0 -6
  428. package/dist/esm/services/documentStore/dynamoEncoding.d.ts +0 -10
  429. package/dist/esm/services/documentStore/dynamoEncoding.js +0 -45
  430. package/dist/esm/services/documentStore/fileSystemAssert.d.ts +0 -1
  431. package/dist/esm/services/documentStore/fileSystemAssert.js +0 -10
  432. package/dist/esm/services/documentStore/index.d.ts +0 -14
  433. package/dist/esm/services/documentStore/index.js +0 -1
  434. package/dist/esm/services/documentStore/unversioned/dynamodb.d.ts +0 -31
  435. package/dist/esm/services/documentStore/unversioned/dynamodb.js +0 -226
  436. package/dist/esm/services/documentStore/unversioned/file-system.d.ts +0 -32
  437. package/dist/esm/services/documentStore/unversioned/file-system.js +0 -174
  438. package/dist/esm/services/documentStore/unversioned/index.d.ts +0 -2
  439. package/dist/esm/services/documentStore/unversioned/index.js +0 -1
  440. package/dist/esm/services/documentStore/versioned/dynamodb.d.ts +0 -25
  441. package/dist/esm/services/documentStore/versioned/dynamodb.js +0 -139
  442. package/dist/esm/services/documentStore/versioned/file-system.d.ts +0 -25
  443. package/dist/esm/services/documentStore/versioned/file-system.js +0 -69
  444. package/dist/esm/services/documentStore/versioned/index.d.ts +0 -17
  445. package/dist/esm/services/documentStore/versioned/index.js +0 -1
  446. package/dist/esm/services/exercisesGateway/index.d.ts +0 -67
  447. package/dist/esm/services/exercisesGateway/index.js +0 -70
  448. package/dist/esm/services/fileServer/index.d.ts +0 -30
  449. package/dist/esm/services/fileServer/index.js +0 -13
  450. package/dist/esm/services/fileServer/localFileServer.d.ts +0 -13
  451. package/dist/esm/services/fileServer/localFileServer.js +0 -125
  452. package/dist/esm/services/fileServer/s3FileServer.d.ts +0 -14
  453. package/dist/esm/services/fileServer/s3FileServer.js +0 -124
  454. package/dist/esm/services/launchParams/index.d.ts +0 -2
  455. package/dist/esm/services/launchParams/index.js +0 -2
  456. package/dist/esm/services/launchParams/signer.d.ts +0 -23
  457. package/dist/esm/services/launchParams/signer.js +0 -51
  458. package/dist/esm/services/launchParams/verifier.d.ts +0 -21
  459. package/dist/esm/services/launchParams/verifier.js +0 -92
  460. package/dist/esm/services/logger/console.d.ts +0 -4
  461. package/dist/esm/services/logger/index.js +0 -27
  462. package/dist/esm/services/lrsGateway/addStatementDefaultFields.d.ts +0 -5
  463. package/dist/esm/services/lrsGateway/addStatementDefaultFields.js +0 -14
  464. package/dist/esm/services/lrsGateway/attempt-utils.d.ts +0 -70
  465. package/dist/esm/services/lrsGateway/attempt-utils.js +0 -236
  466. package/dist/esm/services/lrsGateway/file-system.d.ts +0 -15
  467. package/dist/esm/services/lrsGateway/file-system.js +0 -110
  468. package/dist/esm/services/lrsGateway/index.d.ts +0 -122
  469. package/dist/esm/services/lrsGateway/index.js +0 -111
  470. package/dist/esm/services/lrsGateway/xapiUtils.d.ts +0 -68
  471. package/dist/esm/services/lrsGateway/xapiUtils.js +0 -99
  472. package/dist/esm/services/postgresConnection/index.d.ts +0 -28
  473. package/dist/esm/services/postgresConnection/index.js +0 -58
  474. package/dist/esm/services/searchProvider/index.d.ts +0 -67
  475. package/dist/esm/services/searchProvider/index.js +0 -1
  476. package/dist/esm/services/searchProvider/memorySearchTheBadWay.d.ts +0 -20
  477. package/dist/esm/services/searchProvider/memorySearchTheBadWay.js +0 -187
  478. package/dist/esm/services/searchProvider/openSearch.d.ts +0 -28
  479. package/dist/esm/services/searchProvider/openSearch.js +0 -150
  480. package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +0 -1
  481. package/dist/esm/types.js +0 -1
  482. /package/{script → packages/utils/script}/bin/copy-from-template.bash +0 -0
  483. /package/{script → packages/utils/script}/bin/delete-stack.bash +0 -0
  484. /package/{script → packages/utils/script}/bin/deploy.bash +0 -0
  485. /package/{script → packages/utils/script}/bin/destroy-deployment.bash +0 -0
  486. /package/{script → packages/utils/script}/bin/empty-bucket.bash +0 -0
  487. /package/{script → packages/utils/script}/bin/get-arg.bash +0 -0
  488. /package/{script → packages/utils/script}/bin/get-deployed-environments.bash +0 -0
  489. /package/{script → packages/utils/script}/bin/get-env-param.bash +0 -0
  490. /package/{script → packages/utils/script}/bin/get-kwarg.bash +0 -0
  491. /package/{script → packages/utils/script}/bin/get-stack-param.bash +0 -0
  492. /package/{script → packages/utils/script}/bin/has-flag.bash +0 -0
  493. /package/{script → packages/utils/script}/bin/init-constants-script.bash +0 -0
  494. /package/{script → packages/utils/script}/bin/init-params-script.bash +0 -0
  495. /package/{script → packages/utils/script}/bin/stack-exists.bash +0 -0
  496. /package/{script → packages/utils/script}/bin/update-utils.bash +0 -0
  497. /package/{script → packages/utils/script}/bin/upload-pager-duty-endpoints.bash +0 -0
  498. /package/{script → packages/utils/script}/bin/upload-params.bash +0 -0
  499. /package/{script → packages/utils/script}/bin/which.bash +0 -0
  500. /package/{script → packages/utils/script}/bin-entry.bash +0 -0
  501. /package/{script → packages/utils/script}/build.bash +0 -0
  502. /package/{dist/cjs/index.d.ts → packages/utils/src/index.ts} +0 -0
  503. /package/{dist/cjs/services/launchParams/index.d.ts → packages/utils/src/services/launchParams/index.ts} +0 -0
@@ -0,0 +1,859 @@
1
+ import * as awsSdk from '@aws-sdk/client-dynamodb';
2
+ import { dynamoUnversionedDocumentStore } from './dynamodb';
3
+
4
+ jest.mock('@aws-sdk/client-dynamodb', () => {
5
+ const sendSpy = jest.fn(() => Promise.resolve()) as any;
6
+ class DynamoDB {
7
+ static sendSpy = sendSpy;
8
+ send(...a: any[]) { return DynamoDB.sendSpy(...a); }
9
+ }
10
+ return {
11
+ ...jest.requireActual('@aws-sdk/client-dynamodb'),
12
+ DynamoDB: DynamoDB
13
+ };
14
+ });
15
+
16
+ type TTestDocument = {
17
+ coolId: string;
18
+ maybe?: null;
19
+ name: string;
20
+ strings?: string[];
21
+ isCool?: boolean;
22
+ counter?: number;
23
+ };
24
+
25
+ describe('dynamoUnversionedDocumentStore', () => {
26
+ const sendSpy: jest.SpyInstance = (awsSdk.DynamoDB as any).sendSpy;
27
+ const config = {dynamodb: {tableName: 'cool-table-name'}};
28
+ const middleware = {};
29
+ let mockDynamoClient: awsSdk.DynamoDB;
30
+ let mockClientSend: jest.SpyInstance;
31
+
32
+ beforeEach(() => {
33
+ mockClientSend = jest.fn();
34
+ mockDynamoClient = {send: mockClientSend} as unknown as awsSdk.DynamoDB;
35
+ });
36
+
37
+ afterEach(() => {
38
+ jest.clearAllMocks();
39
+ });
40
+
41
+ describe('loadAllDocumentsTheBadWay', () => {
42
+ it('loads pages of documents', async() => {
43
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
44
+
45
+ sendSpy.mockReturnValueOnce(Promise.resolve({
46
+ LastEvaluatedKey: {
47
+ coolId: {S: 'my-document-id'},
48
+ timestamp: {N: '123'},
49
+ },
50
+ Items: [
51
+ {
52
+ coolId: {S: 'my-document-id'},
53
+ name: {S: 'my-doc-one'},
54
+ }
55
+ ]
56
+ }));
57
+ sendSpy.mockReturnValueOnce(Promise.resolve({
58
+ Items: [
59
+ {
60
+ coolId: {S: 'my-document-id-two'},
61
+ name: {S: 'my-doc-two'},
62
+ }
63
+ ]
64
+ }));
65
+
66
+ expect(await store.loadAllDocumentsTheBadWay()).toEqual([
67
+ {
68
+ coolId: 'my-document-id',
69
+ name: 'my-doc-one',
70
+ },
71
+ {
72
+ coolId: 'my-document-id-two',
73
+ name: 'my-doc-two',
74
+ }
75
+ ]);
76
+ });
77
+
78
+ it('handles null items', async() => {
79
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
80
+ sendSpy.mockReturnValueOnce(Promise.resolve({
81
+ }));
82
+ expect(await store.loadAllDocumentsTheBadWay()).toEqual([
83
+ ]);
84
+ });
85
+ });
86
+
87
+ describe('batchGetItem', () => {
88
+ it('gets documents', async() => {
89
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
90
+
91
+ sendSpy.mockReturnValue(Promise.resolve({Responses: {
92
+ 'cool-table-name': [
93
+ {
94
+ coolId: {S: 'my-document-id'},
95
+ maybe: {NULL: true},
96
+ strings: {L: [{S: 'string1'}, {S: 'string2'}]},
97
+ name: {S: 'my-doc'},
98
+ isCool: {BOOL: true}
99
+ },
100
+ {
101
+ coolId: {S: 'my-document-id-2'},
102
+ maybe: {NULL: true},
103
+ strings: {L: [{S: 'string3'}, {S: 'string4'}]},
104
+ name: {S: 'my-doc-2'},
105
+ isCool: {BOOL: false}
106
+ }
107
+ ]
108
+ }}));
109
+
110
+ expect(await store.batchGetItem(['my-document-id', 'my-document-id-2'])).toEqual([
111
+ {
112
+ coolId: 'my-document-id',
113
+ maybe: null,
114
+ strings: ['string1', 'string2'],
115
+ name: 'my-doc',
116
+ isCool: true
117
+ },
118
+ {
119
+ coolId: 'my-document-id-2',
120
+ maybe: null,
121
+ strings: ['string3', 'string4'],
122
+ name: 'my-doc-2',
123
+ isCool: false
124
+ },
125
+ ]);
126
+ });
127
+
128
+ it('gets documents in multiple batches', async() => {
129
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
130
+
131
+ sendSpy.mockReturnValueOnce(Promise.resolve({Responses: {
132
+ 'cool-table-name': [
133
+ {
134
+ coolId: {S: 'my-document-id'},
135
+ maybe: {NULL: true},
136
+ strings: {L: [{S: 'string1'}, {S: 'string2'}]},
137
+ name: {S: 'my-doc'},
138
+ isCool: {BOOL: true}
139
+ },
140
+ ]
141
+ }, UnprocessedKeys: {
142
+ 'cool-table-name': { Keys: [ { coolId: { S: 'my-document-id-2' } } ] },
143
+ }})).mockReturnValueOnce(Promise.resolve({Responses: {
144
+ 'cool-table-name': [
145
+ {
146
+ coolId: {S: 'my-document-id-2'},
147
+ maybe: {NULL: true},
148
+ strings: {L: [{S: 'string3'}, {S: 'string4'}]},
149
+ name: {S: 'my-doc-2'},
150
+ isCool: {BOOL: false}
151
+ },
152
+ ]
153
+ }}));
154
+
155
+ expect(await store.batchGetItem(['my-document-id', 'my-document-id-2'])).toEqual([
156
+ {
157
+ coolId: 'my-document-id',
158
+ maybe: null,
159
+ strings: ['string1', 'string2'],
160
+ name: 'my-doc',
161
+ isCool: true
162
+ },
163
+ {
164
+ coolId: 'my-document-id-2',
165
+ maybe: null,
166
+ strings: ['string3', 'string4'],
167
+ name: 'my-doc-2',
168
+ isCool: false
169
+ },
170
+ ]);
171
+ });
172
+
173
+ it('gets empty array for missing documents', async() => {
174
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
175
+ sendSpy.mockReturnValue(Promise.resolve({}));
176
+ expect(await store.batchGetItem(['my-document-id', 'my-document-id-2'])).toEqual([]);
177
+ });
178
+
179
+ it('throws on weirdo data types', async() => {
180
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
181
+ sendSpy.mockReturnValue(Promise.resolve({Responses: { isCool: {NOT_A_REAL_TYPE: true} }}));
182
+
183
+ await expect(async() => store.batchGetItem(['my-document-id', 'my-document-id-2'])).rejects.toThrow();
184
+ });
185
+ });
186
+
187
+ describe('getItem', () => {
188
+ it('gets a document', async() => {
189
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
190
+
191
+ sendSpy.mockReturnValue(Promise.resolve({Item: {
192
+ coolId: {S: 'my-document-id'},
193
+ maybe: {NULL: true},
194
+ strings: {L: [{S: 'string1'}, {S: 'string2'}]},
195
+ name: {S: 'my-doc'},
196
+ isCool: {BOOL: true}
197
+ }}));
198
+
199
+ expect(await store.getItem('my-document-id')).toEqual({
200
+ coolId: 'my-document-id',
201
+ maybe: null,
202
+ strings: ['string1', 'string2'],
203
+ name: 'my-doc',
204
+ isCool: true
205
+ });
206
+ });
207
+
208
+ it('gets undefined for missing document', async() => {
209
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
210
+ sendSpy.mockReturnValue(Promise.resolve({}));
211
+ expect(await store.getItem('my-document-id')).toBeUndefined();
212
+ });
213
+
214
+ it('throws on weirdo data types', async() => {
215
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
216
+ sendSpy.mockReturnValue(Promise.resolve({Item: { isCool: {NOT_A_REAL_TYPE: true} }}));
217
+
218
+ await expect(async() => store.getItem('my-document-id')).rejects.toThrow();
219
+ });
220
+ });
221
+
222
+ describe('incrementItemAttribute', () => {
223
+ it('increments the given attribute, starting from 0 if undefined', async() => {
224
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
225
+ sendSpy.mockReturnValueOnce(Promise.resolve({Attributes: {counter: {N: 1}}}));
226
+ sendSpy.mockReturnValueOnce(Promise.resolve({Attributes: {counter: {N: 2}}}));
227
+
228
+ expect(await store.incrementItemAttribute('my-document-id', 'counter')).toBe(1);
229
+ expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
230
+ input: {
231
+ Key: {coolId: {S: 'my-document-id'}},
232
+ TableName: 'cool-table-name',
233
+ UpdateExpression: 'ADD #f :one',
234
+ ConditionExpression: 'attribute_exists(#k)',
235
+ ExpressionAttributeNames: { '#k': 'coolId', '#f': 'counter' },
236
+ ExpressionAttributeValues: { ':one': { N: '1' } },
237
+ ReturnValues: 'ALL_NEW',
238
+ }
239
+ }));
240
+ expect(await store.incrementItemAttribute('my-document-id', 'counter')).toBe(2);
241
+ expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
242
+ input: {
243
+ Key: {coolId: {S: 'my-document-id'}},
244
+ TableName: 'cool-table-name',
245
+ UpdateExpression: 'ADD #f :one',
246
+ ConditionExpression: 'attribute_exists(#k)',
247
+ ExpressionAttributeNames: { '#k': 'coolId', '#f': 'counter' },
248
+ ExpressionAttributeValues: { ':one': { N: '1' } },
249
+ ReturnValues: 'ALL_NEW',
250
+ }
251
+ }));
252
+ });
253
+
254
+ it('calls afterWrite', async() => {
255
+ const afterWrite = jest.fn();
256
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId', {afterWrite});
257
+ sendSpy.mockReturnValueOnce(Promise.resolve({Attributes: {counter: {N: 1}}}));
258
+
259
+ expect(await store.incrementItemAttribute('my-document-id', 'counter')).toBe(1);
260
+ expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
261
+ input: {
262
+ Key: {coolId: {S: 'my-document-id'}},
263
+ TableName: 'cool-table-name',
264
+ UpdateExpression: 'ADD #f :one',
265
+ ConditionExpression: 'attribute_exists(#k)',
266
+ ExpressionAttributeNames: { '#k': 'coolId', '#f': 'counter' },
267
+ ExpressionAttributeValues: { ':one': { N: '1' } },
268
+ ReturnValues: 'ALL_NEW',
269
+ }
270
+ }));
271
+ expect(afterWrite).toHaveBeenCalledTimes(1);
272
+ expect(afterWrite).toHaveBeenCalledWith(expect.objectContaining({
273
+ counter: 1
274
+ }));
275
+ });
276
+
277
+ it('throws if the document does not exist', async() => {
278
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
279
+
280
+ sendSpy.mockReturnValueOnce(Promise.resolve({}));
281
+ await expect(
282
+ store.incrementItemAttribute('my-document-id', 'counter')
283
+ ).rejects.toThrow('Item with coolId "my-document-id" does not exist');
284
+
285
+ sendSpy.mockReturnValueOnce(Promise.reject({ name: 'ConditionalCheckFailedException' }));
286
+ await expect(
287
+ store.incrementItemAttribute('my-document-id', 'counter')
288
+ ).rejects.toThrow('Item with coolId "my-document-id" does not exist');
289
+ });
290
+
291
+ it('throws if the document does not return the field', async() => {
292
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
293
+
294
+ sendSpy.mockReturnValueOnce(Promise.resolve({Attributes: {}}));
295
+ await expect(
296
+ store.incrementItemAttribute('my-document-id', 'counter')
297
+ ).rejects.toThrow('Item with coolId "my-document-id" did not produce field "counter"');
298
+ });
299
+ });
300
+
301
+ describe('patchItem', () => {
302
+ beforeAll(() => {
303
+ jest.useFakeTimers();
304
+ jest.setSystemTime(new Date(2020, 6, 28));
305
+ });
306
+
307
+ afterAll(() => {
308
+ jest.useRealTimers();
309
+ });
310
+
311
+ it('overwrites only the given attributes and returns updated document', async() => {
312
+ const testDoc = {coolId: 'my-document-id', name: 'my-doc'};
313
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
314
+ sendSpy.mockReturnValueOnce(Promise.resolve({
315
+ Attributes: {coolId: {S: 'my-document-id'}, name: {S: 'my-doc'}, counter: {N: 42}}
316
+ }));
317
+ const result = await store.patchItem(testDoc);
318
+ expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
319
+ input: {
320
+ Key: { coolId: {S: 'my-document-id'} },
321
+ TableName: 'cool-table-name',
322
+ UpdateExpression: 'SET #f0 = :f0',
323
+ ConditionExpression: 'attribute_exists(#k)',
324
+ ExpressionAttributeNames: { '#k': 'coolId', '#f0': 'name' },
325
+ ExpressionAttributeValues: { ':f0': { S: 'my-doc' } },
326
+ ReturnValues: 'ALL_NEW',
327
+ }
328
+ }));
329
+ expect(result).toEqual({...testDoc, counter: 42});
330
+ });
331
+
332
+ it('calls afterWrite', async() => {
333
+ const afterWrite = jest.fn();
334
+ const testDoc = {coolId: 'my-document-id', name: 'my-doc'};
335
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId', {afterWrite});
336
+ sendSpy.mockReturnValueOnce(Promise.resolve({
337
+ Attributes: {coolId: {S: 'my-document-id'}, name: {S: 'my-doc'}, counter: {N: 42}}
338
+ }));
339
+ const result = await store.patchItem(testDoc);
340
+ expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
341
+ input: {
342
+ Key: { coolId: {S: 'my-document-id'} },
343
+ TableName: 'cool-table-name',
344
+ UpdateExpression: 'SET #f0 = :f0',
345
+ ConditionExpression: 'attribute_exists(#k)',
346
+ ExpressionAttributeNames: { '#k': 'coolId', '#f0': 'name' },
347
+ ExpressionAttributeValues: { ':f0': { S: 'my-doc' } },
348
+ ReturnValues: 'ALL_NEW',
349
+ }
350
+ }));
351
+ expect(result).toEqual({...testDoc, counter: 42});
352
+ expect(afterWrite).toHaveBeenCalledWith(expect.objectContaining({
353
+ ...testDoc, counter: 42
354
+ }));
355
+ });
356
+
357
+ it('throws if the key attribute is not provided', async() => {
358
+ const testDoc = {name: 'my-doc'};
359
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
360
+ await expect(store.patchItem(testDoc)).rejects.toThrow('Key attribute "coolId" is required for patchItem');
361
+ });
362
+
363
+ it('throws if there are no updates', async() => {
364
+ const testDoc = {coolId: 'my-document-id'};
365
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
366
+ await expect(store.patchItem(testDoc)).rejects.toThrow('No attributes to update');
367
+ });
368
+
369
+ it('throws on weirdo data types', async() => {
370
+ const testDoc = {coolId: 'my-document-id', name: 'my-doc', foobar: Buffer.from('asdf')};
371
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
372
+ await expect(async() => store.patchItem(testDoc)).rejects.toThrow();
373
+ });
374
+
375
+ it('throws if the document does not exist', async() => {
376
+ const testDoc = {coolId: 'my-document-id', name: 'my-doc'};
377
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
378
+
379
+ sendSpy.mockReturnValueOnce(Promise.resolve({}));
380
+ await expect(store.patchItem(testDoc)).rejects.toThrow('Item with coolId "my-document-id" does not exist');
381
+
382
+ sendSpy.mockReturnValueOnce(Promise.reject({ name: 'ConditionalCheckFailedException' }));
383
+ await expect(store.patchItem(testDoc)).rejects.toThrow('Item with coolId "my-document-id" does not exist');
384
+ });
385
+ });
386
+
387
+ describe('putItem', () => {
388
+ beforeAll(() => {
389
+ jest.useFakeTimers();
390
+ jest.setSystemTime(new Date(2020, 6, 28));
391
+ });
392
+
393
+ afterAll(() => {
394
+ jest.useRealTimers();
395
+ });
396
+
397
+ it('saves and returns new document', async() => {
398
+ const testDoc = {coolId: 'my-document-id', name: 'my-doc', isCool: false, maybe: null, strings: ['string1', 'string2']};
399
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
400
+ const result = await store.putItem(testDoc);
401
+ expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
402
+ input: {
403
+ TableName: 'cool-table-name',
404
+ Item: {
405
+ coolId: {S: 'my-document-id'},
406
+ strings: {L: [{S: 'string1'}, {S: 'string2'}]},
407
+ maybe: {NULL: true},
408
+ name: {S: 'my-doc'},
409
+ isCool: {BOOL: false}
410
+ }
411
+ }
412
+ }));
413
+ expect(result).toEqual(testDoc);
414
+ });
415
+
416
+ it('calls afterWrite', async() => {
417
+ const afterWrite = jest.fn();
418
+ const testDoc = {coolId: 'my-document-id', name: 'my-doc', isCool: false, maybe: null, strings: ['string1', 'string2']};
419
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId', {afterWrite});
420
+ const result = await store.putItem(testDoc);
421
+ expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
422
+ input: {
423
+ TableName: 'cool-table-name',
424
+ Item: {
425
+ coolId: {S: 'my-document-id'},
426
+ strings: {L: [{S: 'string1'}, {S: 'string2'}]},
427
+ maybe: {NULL: true},
428
+ name: {S: 'my-doc'},
429
+ isCool: {BOOL: false}
430
+ }
431
+ }
432
+ }));
433
+ expect(result).toEqual(testDoc);
434
+ expect(afterWrite).toHaveBeenCalledWith(testDoc);
435
+ });
436
+
437
+ it('overwrites and returns updated document', async() => {
438
+ const testDoc = {coolId: 'my-document-id', name: 'my-doc'};
439
+ const testDoc2 = {coolId: 'my-document-id', name: 'my-doc-2'};
440
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
441
+ const result = await store.putItem(testDoc);
442
+ expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
443
+ input: {
444
+ TableName: 'cool-table-name',
445
+ Item: {
446
+ coolId: {S: 'my-document-id'},
447
+ name: {S: 'my-doc'},
448
+ }
449
+ }
450
+ }));
451
+ expect(result).toEqual(testDoc);
452
+ const result2 = await store.putItem(testDoc2);
453
+ expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
454
+ input: {
455
+ TableName: 'cool-table-name',
456
+ Item: {
457
+ coolId: {S: 'my-document-id'},
458
+ name: {S: 'my-doc-2'},
459
+ }
460
+ }
461
+ }));
462
+ expect(result2).toEqual(testDoc2);
463
+ });
464
+
465
+ it('throws on weirdo data types', async() => {
466
+ const testDoc = {coolId: 'my-document-id', name: 'my-doc', foobar: Buffer.from('asdf')};
467
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
468
+ await expect(async() => store.putItem(testDoc)).rejects.toThrow();
469
+ });
470
+ });
471
+
472
+ describe('createItem', () => {
473
+ beforeAll(() => {
474
+ jest.useFakeTimers();
475
+ jest.setSystemTime(new Date(2020, 6, 28));
476
+ });
477
+
478
+ afterAll(() => {
479
+ jest.useRealTimers();
480
+ });
481
+
482
+ it('creates and returns new document', async() => {
483
+ const testDoc = {coolId: 'new-document-id', name: 'new-doc'};
484
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
485
+
486
+ const result = await store.createItem(testDoc);
487
+
488
+ expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
489
+ input: expect.objectContaining({
490
+ TableName: 'cool-table-name',
491
+ Item: expect.objectContaining({
492
+ coolId: {S: 'new-document-id'},
493
+ name: {S: 'new-doc'}
494
+ }),
495
+ ConditionExpression: 'attribute_not_exists(#k)',
496
+ ExpressionAttributeNames: {
497
+ '#k': 'coolId'
498
+ }
499
+ })
500
+ }));
501
+ expect(result).toEqual(testDoc);
502
+ });
503
+
504
+ it('calls afterWrite callback', async() => {
505
+ const afterWrite = jest.fn();
506
+ const testDoc = {coolId: 'new-document-id', name: 'new-doc'};
507
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId', {afterWrite});
508
+
509
+ await store.createItem(testDoc);
510
+ expect(afterWrite).toHaveBeenCalledWith(testDoc);
511
+ });
512
+
513
+ it('throws ConflictError if document already exists', async() => {
514
+ const testDoc = {coolId: 'existing-document-id', name: 'existing-doc'};
515
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
516
+
517
+ sendSpy.mockRejectedValue({ name: 'ConditionalCheckFailedException' });
518
+
519
+ await expect(store.createItem(testDoc)).rejects.toThrow('Item with coolId "existing-document-id" already exists');
520
+ });
521
+
522
+ it('throws other errors unchanged', async() => {
523
+ const testDoc = {coolId: 'new-document-id', name: 'new-doc'};
524
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
525
+
526
+ const error = new Error('DynamoDB service error');
527
+ sendSpy.mockRejectedValue(error);
528
+
529
+ await expect(store.createItem(testDoc)).rejects.toThrow('DynamoDB service error');
530
+ });
531
+
532
+ it('throws on weirdo data types', async() => {
533
+ const testDoc = {coolId: 'my-document-id', name: 'my-doc', foobar: Buffer.from('asdf')};
534
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
535
+ await expect(async() => store.createItem(testDoc)).rejects.toThrow();
536
+ });
537
+ });
538
+
539
+ describe('batchCreateItem', () => {
540
+ beforeAll(() => {
541
+ jest.useFakeTimers();
542
+ jest.setSystemTime(new Date(2020, 6, 28));
543
+ });
544
+
545
+ afterAll(() => {
546
+ jest.useRealTimers();
547
+ });
548
+
549
+ it('creates multiple new documents', async() => {
550
+ const testDocs = [
551
+ {coolId: 'new-document-1', name: 'new-doc-1'},
552
+ {coolId: 'new-document-2', name: 'new-doc-2'},
553
+ {coolId: 'new-document-3', name: 'new-doc-3'}
554
+ ];
555
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
556
+
557
+ sendSpy.mockResolvedValue({}); // Reset mock to success
558
+
559
+ const result = await store.batchCreateItem(testDocs);
560
+
561
+ expect(sendSpy).toHaveBeenCalledTimes(3);
562
+ testDocs.forEach((testDoc) => {
563
+ expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
564
+ input: expect.objectContaining({
565
+ TableName: 'cool-table-name',
566
+ Item: expect.objectContaining({
567
+ coolId: {S: testDoc.coolId},
568
+ name: {S: testDoc.name}
569
+ }),
570
+ ConditionExpression: 'attribute_not_exists(#k)',
571
+ ExpressionAttributeNames: {
572
+ '#k': 'coolId'
573
+ }
574
+ })
575
+ }));
576
+ });
577
+ expect(result).toEqual({ successful: testDocs, failed: [] });
578
+ });
579
+
580
+ it('handles empty array', async() => {
581
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
582
+
583
+ const result = await store.batchCreateItem([]);
584
+
585
+ expect(sendSpy).not.toHaveBeenCalled();
586
+ expect(result).toEqual({ successful: [], failed: [] });
587
+ });
588
+
589
+ it('calls afterWrite callback for each item', async() => {
590
+ const afterWrite = jest.fn();
591
+ const testDocs = [
592
+ {coolId: 'new-document-1', name: 'new-doc-1'},
593
+ {coolId: 'new-document-2', name: 'new-doc-2'}
594
+ ];
595
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId', {afterWrite});
596
+
597
+ sendSpy.mockResolvedValue({}); // Reset mock to success
598
+
599
+ const result = await store.batchCreateItem(testDocs);
600
+
601
+ expect(afterWrite).toHaveBeenCalledTimes(2);
602
+ expect(afterWrite).toHaveBeenCalledWith(testDocs[0]);
603
+ expect(afterWrite).toHaveBeenCalledWith(testDocs[1]);
604
+ expect(result).toEqual({ successful: testDocs, failed: [] });
605
+ });
606
+
607
+ it('returns successful and failed items when some already exist', async() => {
608
+ const testDocs = [
609
+ {coolId: 'new-document-1', name: 'new-doc-1'},
610
+ {coolId: 'existing-document', name: 'existing-doc'},
611
+ {coolId: 'new-document-3', name: 'new-doc-3'}
612
+ ];
613
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
614
+
615
+ sendSpy
616
+ .mockResolvedValueOnce({}) // First item succeeds
617
+ .mockRejectedValueOnce({ name: 'ConditionalCheckFailedException' }) // Second item conflicts
618
+ .mockResolvedValueOnce({}); // Third item succeeds
619
+
620
+ const result = await store.batchCreateItem(testDocs);
621
+
622
+ expect(result.successful).toEqual([testDocs[0], testDocs[2]]);
623
+ expect(result.failed).toHaveLength(1);
624
+ expect(result.failed[0].item).toEqual(testDocs[1]);
625
+ expect(result.failed[0].error.message).toContain('Item with coolId "existing-document" already exists');
626
+ });
627
+
628
+ it('returns other errors in failed array', async() => {
629
+ const testDocs = [{coolId: 'new-document-1', name: 'new-doc-1'}];
630
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
631
+
632
+ const error = new Error('DynamoDB service error');
633
+ sendSpy.mockRejectedValue(error);
634
+
635
+ const result = await store.batchCreateItem(testDocs);
636
+
637
+ expect(result.successful).toEqual([]);
638
+ expect(result.failed).toHaveLength(1);
639
+ expect(result.failed[0].item).toEqual(testDocs[0]);
640
+ expect(result.failed[0].error.message).toBe('DynamoDB service error');
641
+ });
642
+
643
+ it('processes single item', async() => {
644
+ const testDoc = {coolId: 'new-document-1', name: 'new-doc-1'};
645
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
646
+
647
+ sendSpy.mockResolvedValue({}); // Reset mock to success
648
+
649
+ const result = await store.batchCreateItem([testDoc]);
650
+
651
+ expect(sendSpy).toHaveBeenCalledTimes(1);
652
+ expect(result).toEqual({ successful: [testDoc], failed: [] });
653
+ });
654
+
655
+ it('respects custom concurrency parameter', async() => {
656
+ const testDocs = [
657
+ {coolId: 'new-document-1', name: 'new-doc-1'},
658
+ {coolId: 'new-document-2', name: 'new-doc-2'}
659
+ ];
660
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
661
+
662
+ sendSpy.mockResolvedValue({});
663
+
664
+ const result = await store.batchCreateItem(testDocs, 5); // Custom concurrency
665
+
666
+ expect(result).toEqual({ successful: testDocs, failed: [] });
667
+ });
668
+
669
+ it('calls batchAfterWrite instead of individual afterWrite', async() => {
670
+ const afterWrite = jest.fn();
671
+ const batchAfterWrite = jest.fn();
672
+ const testDocs = [
673
+ {coolId: 'new-document-1', name: 'new-doc-1'},
674
+ {coolId: 'new-document-2', name: 'new-doc-2'}
675
+ ];
676
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId', {afterWrite, batchAfterWrite});
677
+
678
+ sendSpy.mockResolvedValue({});
679
+
680
+ const result = await store.batchCreateItem(testDocs);
681
+
682
+ // Should call batchAfterWrite once with all successful items
683
+ expect(batchAfterWrite).toHaveBeenCalledTimes(1);
684
+ expect(batchAfterWrite).toHaveBeenCalledWith(testDocs);
685
+
686
+ // Should NOT call individual afterWrite when batchAfterWrite is defined
687
+ expect(afterWrite).not.toHaveBeenCalled();
688
+
689
+ expect(result).toEqual({ successful: testDocs, failed: [] });
690
+ });
691
+
692
+ it('calls batchAfterWrite only with successful items', async() => {
693
+ const batchAfterWrite = jest.fn();
694
+ const testDocs = [
695
+ {coolId: 'new-document-1', name: 'new-doc-1'},
696
+ {coolId: 'existing-document', name: 'existing-doc'},
697
+ {coolId: 'new-document-3', name: 'new-doc-3'}
698
+ ];
699
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId', {batchAfterWrite});
700
+
701
+ sendSpy
702
+ .mockResolvedValueOnce({}) // First item succeeds
703
+ .mockRejectedValueOnce({ name: 'ConditionalCheckFailedException' }) // Second item conflicts
704
+ .mockResolvedValueOnce({}); // Third item succeeds
705
+
706
+ const result = await store.batchCreateItem(testDocs);
707
+
708
+ // Should call batchAfterWrite with only successful items
709
+ expect(batchAfterWrite).toHaveBeenCalledTimes(1);
710
+ expect(batchAfterWrite).toHaveBeenCalledWith([testDocs[0], testDocs[2]]);
711
+
712
+ expect(result.successful).toEqual([testDocs[0], testDocs[2]]);
713
+ expect(result.failed).toHaveLength(1);
714
+ });
715
+
716
+ it('does not call batchAfterWrite if no items succeed', async() => {
717
+ const batchAfterWrite = jest.fn();
718
+ const testDocs = [{coolId: 'existing-document', name: 'existing-doc'}];
719
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId', {batchAfterWrite});
720
+
721
+ sendSpy.mockRejectedValue({ name: 'ConditionalCheckFailedException' });
722
+
723
+ const result = await store.batchCreateItem(testDocs);
724
+
725
+ // Should NOT call batchAfterWrite when no items succeed
726
+ expect(batchAfterWrite).not.toHaveBeenCalled();
727
+
728
+ expect(result.successful).toEqual([]);
729
+ expect(result.failed).toHaveLength(1);
730
+ });
731
+
732
+ it('falls back to individual afterWrite when batchAfterWrite is not defined', async() => {
733
+ const afterWrite = jest.fn();
734
+ const testDocs = [
735
+ {coolId: 'new-document-1', name: 'new-doc-1'},
736
+ {coolId: 'new-document-2', name: 'new-doc-2'}
737
+ ];
738
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId', {afterWrite});
739
+
740
+ sendSpy.mockResolvedValue({});
741
+
742
+ const result = await store.batchCreateItem(testDocs);
743
+
744
+ // Should call afterWrite for each item when batchAfterWrite is not defined
745
+ expect(afterWrite).toHaveBeenCalledTimes(2);
746
+ expect(afterWrite).toHaveBeenCalledWith(testDocs[0]);
747
+ expect(afterWrite).toHaveBeenCalledWith(testDocs[1]);
748
+
749
+ expect(result).toEqual({ successful: testDocs, failed: [] });
750
+ });
751
+ });
752
+
753
+ describe('custom client', () => {
754
+ it('uses provided dynamodb client', async() => {
755
+ const customSendSpy = jest.fn().mockResolvedValue({Item: {coolId: {S: 'test-id'}}});
756
+ mockDynamoClient.send = customSendSpy;
757
+
758
+ const store = dynamoUnversionedDocumentStore({dynamoClient: mockDynamoClient})<TTestDocument>()(config)(middleware, 'coolId');
759
+ await store.getItem('test-id');
760
+
761
+ expect(customSendSpy).toHaveBeenCalled();
762
+ expect(sendSpy).not.toHaveBeenCalled();
763
+ });
764
+ });
765
+
766
+ describe('getItemsByField', () => {
767
+ it('gets items by field', async() => {
768
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
769
+
770
+ sendSpy.mockReturnValue(Promise.resolve({Items: [{
771
+ coolId: {S: 'my-document-id'},
772
+ maybe: {NULL: true},
773
+ strings: {L: [{S: 'string1'}, {S: 'string2'}]},
774
+ name: {S: 'my-doc'},
775
+ isCool: {BOOL: true}
776
+ }], LastEvaluatedKey: undefined}));
777
+
778
+ expect(await store.getItemsByField('name', 'my-doc')).toEqual({
779
+ items: [{
780
+ coolId: 'my-document-id',
781
+ maybe: null,
782
+ strings: ['string1', 'string2'],
783
+ name: 'my-doc',
784
+ isCool: true
785
+ }],
786
+ nextPageToken: undefined
787
+ });
788
+ });
789
+
790
+ it('processes nextPageToken', async() => {
791
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
792
+
793
+ sendSpy.mockReturnValue(Promise.resolve({
794
+ Items: [{
795
+ coolId: {S: 'my-document-id'},
796
+ maybe: {NULL: true},
797
+ strings: {L: [{S: 'string1'}, {S: 'string2'}]},
798
+ name: {S: 'my-doc'},
799
+ isCool: {BOOL: true}
800
+ }],
801
+ LastEvaluatedKey: { coolId: {S: 'my-document-id' } }
802
+ }));
803
+
804
+ const page1 = await store.getItemsByField('name', 'my-doc');
805
+
806
+ expect(page1).toEqual({
807
+ items: [{
808
+ coolId: 'my-document-id',
809
+ maybe: null,
810
+ strings: ['string1', 'string2'],
811
+ name: 'my-doc',
812
+ isCool: true
813
+ }],
814
+ nextPageToken: 'eyJjb29sSWQiOnsiUyI6Im15LWRvY3VtZW50LWlkIn19'
815
+ });
816
+
817
+ sendSpy.mockReturnValue(Promise.resolve({
818
+ Items: [{
819
+ coolId: {S: 'my-document-id-2'},
820
+ maybe: {NULL: true},
821
+ strings: {L: [{S: 'string3'}, {S: 'string4'}]},
822
+ name: {S: 'my-doc-2'},
823
+ isCool: {BOOL: false}
824
+ }],
825
+ LastEvaluatedKey: undefined
826
+ }));
827
+
828
+ const page2 = await store.getItemsByField('name', 'my-doc', page1.nextPageToken);
829
+
830
+ expect(page2).toEqual({
831
+ items: [{
832
+ coolId: 'my-document-id-2',
833
+ maybe: null,
834
+ strings: ['string3', 'string4'],
835
+ name: 'my-doc-2',
836
+ isCool: false
837
+ }],
838
+ nextPageToken: undefined
839
+ });
840
+
841
+ expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
842
+ input: expect.objectContaining({
843
+ ExclusiveStartKey: { coolId: {S: 'my-document-id' } }
844
+ })
845
+ }));
846
+ });
847
+
848
+ it('handles no items', async() => {
849
+ const store = dynamoUnversionedDocumentStore()<TTestDocument>()(config)(middleware, 'coolId');
850
+
851
+ sendSpy.mockReturnValue(Promise.resolve({Items: undefined, LastEvaluatedKey: undefined}));
852
+
853
+ expect(await store.getItemsByField('name', 'my-doc')).toEqual({
854
+ items: [],
855
+ nextPageToken: undefined
856
+ });
857
+ });
858
+ });
859
+ });