@farukada/aws-langgraph-dynamodb-ts 0.9.0 → 1.0.0-rc.2

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 (512) hide show
  1. package/README.md +1720 -154
  2. package/dist/backfill/backfill.d.ts +168 -0
  3. package/dist/backfill/backfill.js +393 -0
  4. package/dist/checkpointer/actions/delete-thread.d.ts +47 -6
  5. package/dist/checkpointer/actions/delete-thread.js +58 -21
  6. package/dist/checkpointer/actions/get-tuple.d.ts +29 -4
  7. package/dist/checkpointer/actions/get-tuple.js +44 -10
  8. package/dist/checkpointer/actions/list.d.ts +46 -4
  9. package/dist/checkpointer/actions/list.js +121 -66
  10. package/dist/checkpointer/actions/put-writes.d.ts +41 -9
  11. package/dist/checkpointer/actions/put-writes.js +62 -77
  12. package/dist/checkpointer/actions/put.d.ts +83 -4
  13. package/dist/checkpointer/actions/put.js +177 -25
  14. package/dist/checkpointer/internal/delta-history.d.ts +112 -0
  15. package/dist/checkpointer/internal/delta-history.js +252 -0
  16. package/dist/checkpointer/internal/listing.d.ts +149 -0
  17. package/dist/checkpointer/internal/listing.js +245 -0
  18. package/dist/checkpointer/internal/parse.d.ts +262 -0
  19. package/dist/checkpointer/internal/parse.js +372 -0
  20. package/dist/checkpointer/internal/pending-writes.d.ts +275 -0
  21. package/dist/checkpointer/internal/pending-writes.js +588 -0
  22. package/dist/checkpointer/internal/read.d.ts +130 -0
  23. package/dist/checkpointer/internal/read.js +264 -0
  24. package/dist/checkpointer/internal/rows.d.ts +571 -0
  25. package/dist/checkpointer/internal/rows.js +834 -0
  26. package/dist/checkpointer/internal/setup.d.ts +42 -19
  27. package/dist/checkpointer/internal/setup.js +65 -29
  28. package/dist/checkpointer/saver.d.ts +256 -16
  29. package/dist/checkpointer/saver.js +275 -29
  30. package/dist/checkpointer/types.d.ts +39 -39
  31. package/dist/checkpointer/types.js +10 -1
  32. package/dist/factory/factory.d.ts +134 -28
  33. package/dist/factory/factory.js +240 -21
  34. package/dist/factory/types.d.ts +76 -0
  35. package/dist/factory/types.js +10 -0
  36. package/dist/history/actions/add-messages.d.ts +31 -4
  37. package/dist/history/actions/add-messages.js +38 -58
  38. package/dist/history/actions/clear.d.ts +49 -6
  39. package/dist/history/actions/clear.js +66 -14
  40. package/dist/history/actions/get-messages.d.ts +54 -6
  41. package/dist/history/actions/get-messages.js +126 -43
  42. package/dist/history/actions/list-sessions.d.ts +52 -10
  43. package/dist/history/actions/list-sessions.js +139 -40
  44. package/dist/history/actions/reconcile-count.d.ts +42 -10
  45. package/dist/history/actions/reconcile-count.js +45 -45
  46. package/dist/history/chat-message-history.d.ts +220 -33
  47. package/dist/history/chat-message-history.js +240 -43
  48. package/dist/history/internal/append.d.ts +212 -0
  49. package/dist/history/internal/append.js +500 -0
  50. package/dist/history/internal/message-read.d.ts +84 -0
  51. package/dist/history/internal/message-read.js +204 -0
  52. package/dist/history/internal/parse.d.ts +153 -0
  53. package/dist/history/internal/parse.js +252 -0
  54. package/dist/history/internal/rows.d.ts +195 -0
  55. package/dist/history/internal/rows.js +250 -0
  56. package/dist/history/internal/session.d.ts +331 -0
  57. package/dist/history/internal/session.js +628 -0
  58. package/dist/history/internal/setup.d.ts +52 -17
  59. package/dist/history/internal/setup.js +92 -21
  60. package/dist/history/session-adapter.d.ts +102 -7
  61. package/dist/history/session-adapter.js +103 -9
  62. package/dist/history/types.d.ts +80 -29
  63. package/dist/history/types.js +10 -1
  64. package/dist/index.d.ts +42 -11
  65. package/dist/index.js +33 -12
  66. package/dist/shared/adapter.d.ts +135 -0
  67. package/dist/shared/adapter.js +143 -0
  68. package/dist/shared/clock.d.ts +51 -2
  69. package/dist/shared/clock.js +57 -2
  70. package/dist/shared/codec/codec.d.ts +288 -13
  71. package/dist/shared/codec/codec.js +416 -19
  72. package/dist/shared/codec/compression.d.ts +43 -7
  73. package/dist/shared/codec/compression.js +53 -13
  74. package/dist/shared/codec/json-serde.d.ts +76 -4
  75. package/dist/shared/codec/json-serde.js +181 -8
  76. package/dist/shared/codec/s3/client-types.d.ts +53 -0
  77. package/dist/shared/codec/s3/client-types.js +26 -0
  78. package/dist/shared/codec/s3/client.d.ts +43 -10
  79. package/dist/shared/codec/s3/client.js +82 -9
  80. package/dist/shared/codec/s3/config.d.ts +242 -11
  81. package/dist/shared/codec/s3/config.js +293 -11
  82. package/dist/shared/codec/s3/lifecycle.d.ts +164 -6
  83. package/dist/shared/codec/s3/lifecycle.js +335 -27
  84. package/dist/shared/codec/s3/offloader.d.ts +393 -18
  85. package/dist/shared/codec/s3/offloader.js +595 -37
  86. package/dist/shared/concurrency.d.ts +43 -0
  87. package/dist/shared/concurrency.js +78 -0
  88. package/dist/shared/dynamodb/abort.d.ts +47 -0
  89. package/dist/shared/dynamodb/abort.js +59 -0
  90. package/dist/shared/dynamodb/batch-write.d.ts +77 -14
  91. package/dist/shared/dynamodb/batch-write.js +146 -27
  92. package/dist/shared/dynamodb/cancellation.d.ts +121 -4
  93. package/dist/shared/dynamodb/cancellation.js +147 -3
  94. package/dist/shared/dynamodb/client.d.ts +162 -8
  95. package/dist/shared/dynamodb/client.js +153 -5
  96. package/dist/shared/dynamodb/idempotent-write.d.ts +551 -0
  97. package/dist/shared/dynamodb/idempotent-write.js +593 -0
  98. package/dist/shared/dynamodb/paginate.d.ts +105 -9
  99. package/dist/shared/dynamodb/paginate.js +175 -7
  100. package/dist/shared/dynamodb/partition-delete.d.ts +185 -14
  101. package/dist/shared/dynamodb/partition-delete.js +314 -44
  102. package/dist/shared/dynamodb/recency-index.d.ts +231 -0
  103. package/dist/shared/dynamodb/recency-index.js +377 -0
  104. package/dist/shared/dynamodb/retry.d.ts +276 -8
  105. package/dist/shared/dynamodb/retry.js +433 -23
  106. package/dist/shared/dynamodb/table-schema.d.ts +190 -0
  107. package/dist/shared/dynamodb/table-schema.js +209 -0
  108. package/dist/shared/errors/base-error.d.ts +184 -10
  109. package/dist/shared/errors/base-error.js +160 -14
  110. package/dist/shared/errors/boundary.d.ts +71 -0
  111. package/dist/shared/errors/boundary.js +143 -0
  112. package/dist/shared/errors/classify.d.ts +97 -0
  113. package/dist/shared/errors/classify.js +257 -0
  114. package/dist/shared/errors/error-code.d.ts +77 -2
  115. package/dist/shared/errors/error-code.js +83 -1
  116. package/dist/shared/errors/errors.d.ts +158 -59
  117. package/dist/shared/errors/errors.js +219 -92
  118. package/dist/shared/logging/logger.d.ts +69 -3
  119. package/dist/shared/logging/logger.js +97 -3
  120. package/dist/shared/logging/redaction.d.ts +92 -8
  121. package/dist/shared/logging/redaction.js +273 -17
  122. package/dist/shared/logging/secret-patterns.d.ts +149 -19
  123. package/dist/shared/logging/secret-patterns.js +188 -27
  124. package/dist/shared/logging/truncate.d.ts +197 -0
  125. package/dist/shared/logging/truncate.js +231 -0
  126. package/dist/shared/options.d.ts +59 -7
  127. package/dist/shared/options.js +9 -1
  128. package/dist/shared/ulid.d.ts +77 -7
  129. package/dist/shared/ulid.js +103 -8
  130. package/dist/shared/validation/collaborators.d.ts +141 -0
  131. package/dist/shared/validation/collaborators.js +188 -0
  132. package/dist/shared/validation/option-shape.d.ts +89 -0
  133. package/dist/shared/validation/option-shape.js +113 -0
  134. package/dist/shared/validation/options.d.ts +145 -0
  135. package/dist/shared/validation/options.js +328 -0
  136. package/dist/shared/validation/primitives.d.ts +288 -21
  137. package/dist/shared/validation/primitives.js +353 -50
  138. package/dist/shared/validation/ttl.d.ts +66 -10
  139. package/dist/shared/validation/ttl.js +113 -15
  140. package/dist/store/actions/list-namespaces.d.ts +76 -6
  141. package/dist/store/actions/list-namespaces.js +166 -24
  142. package/dist/store/actions/put.d.ts +33 -8
  143. package/dist/store/actions/put.js +53 -60
  144. package/dist/store/actions/reconcile-vector-index.d.ts +31 -10
  145. package/dist/store/actions/reconcile-vector-index.js +34 -15
  146. package/dist/store/actions/search.d.ts +34 -6
  147. package/dist/store/actions/search.js +56 -51
  148. package/dist/store/internal/batch-plan.d.ts +26 -0
  149. package/dist/store/internal/batch-plan.js +109 -0
  150. package/dist/store/internal/filter.d.ts +36 -3
  151. package/dist/store/internal/filter.js +66 -15
  152. package/dist/store/internal/get-item.d.ts +45 -0
  153. package/dist/store/internal/get-item.js +115 -0
  154. package/dist/store/internal/item-write.d.ts +230 -0
  155. package/dist/store/internal/item-write.js +463 -0
  156. package/dist/store/internal/parse.d.ts +225 -0
  157. package/dist/store/internal/parse.js +350 -0
  158. package/dist/store/internal/rows.d.ts +355 -0
  159. package/dist/store/internal/rows.js +447 -0
  160. package/dist/store/internal/semantic-search.d.ts +161 -6
  161. package/dist/store/internal/semantic-search.js +360 -18
  162. package/dist/store/internal/setup.d.ts +77 -20
  163. package/dist/store/internal/setup.js +178 -47
  164. package/dist/store/internal/table-search.d.ts +100 -0
  165. package/dist/store/internal/table-search.js +213 -0
  166. package/dist/store/internal/vector-index.d.ts +247 -0
  167. package/dist/store/internal/vector-index.js +546 -0
  168. package/dist/store/store.d.ts +270 -17
  169. package/dist/store/store.js +329 -38
  170. package/dist/store/types.d.ts +76 -26
  171. package/dist/store/types.js +13 -1
  172. package/dist/store/vector-backend.d.ts +64 -4
  173. package/dist/store/vector-backend.js +15 -1
  174. package/package.json +58 -36
  175. package/dist/checkpointer/actions/delete-thread.d.ts.map +0 -1
  176. package/dist/checkpointer/actions/delete-thread.js.map +0 -1
  177. package/dist/checkpointer/actions/get-tuple.d.ts.map +0 -1
  178. package/dist/checkpointer/actions/get-tuple.js.map +0 -1
  179. package/dist/checkpointer/actions/list.d.ts.map +0 -1
  180. package/dist/checkpointer/actions/list.js.map +0 -1
  181. package/dist/checkpointer/actions/put-writes.d.ts.map +0 -1
  182. package/dist/checkpointer/actions/put-writes.js.map +0 -1
  183. package/dist/checkpointer/actions/put.d.ts.map +0 -1
  184. package/dist/checkpointer/actions/put.js.map +0 -1
  185. package/dist/checkpointer/internal/assemble.d.ts +0 -10
  186. package/dist/checkpointer/internal/assemble.d.ts.map +0 -1
  187. package/dist/checkpointer/internal/assemble.js +0 -37
  188. package/dist/checkpointer/internal/assemble.js.map +0 -1
  189. package/dist/checkpointer/internal/configurable.d.ts +0 -13
  190. package/dist/checkpointer/internal/configurable.d.ts.map +0 -1
  191. package/dist/checkpointer/internal/configurable.js +0 -23
  192. package/dist/checkpointer/internal/configurable.js.map +0 -1
  193. package/dist/checkpointer/internal/fetch.d.ts +0 -10
  194. package/dist/checkpointer/internal/fetch.d.ts.map +0 -1
  195. package/dist/checkpointer/internal/fetch.js +0 -46
  196. package/dist/checkpointer/internal/fetch.js.map +0 -1
  197. package/dist/checkpointer/internal/filter-match.d.ts +0 -12
  198. package/dist/checkpointer/internal/filter-match.d.ts.map +0 -1
  199. package/dist/checkpointer/internal/filter-match.js +0 -14
  200. package/dist/checkpointer/internal/filter-match.js.map +0 -1
  201. package/dist/checkpointer/internal/item-reader.d.ts +0 -55
  202. package/dist/checkpointer/internal/item-reader.d.ts.map +0 -1
  203. package/dist/checkpointer/internal/item-reader.js +0 -88
  204. package/dist/checkpointer/internal/item-reader.js.map +0 -1
  205. package/dist/checkpointer/internal/item-writer.d.ts +0 -26
  206. package/dist/checkpointer/internal/item-writer.d.ts.map +0 -1
  207. package/dist/checkpointer/internal/item-writer.js +0 -92
  208. package/dist/checkpointer/internal/item-writer.js.map +0 -1
  209. package/dist/checkpointer/internal/keys.d.ts +0 -31
  210. package/dist/checkpointer/internal/keys.d.ts.map +0 -1
  211. package/dist/checkpointer/internal/keys.js +0 -87
  212. package/dist/checkpointer/internal/keys.js.map +0 -1
  213. package/dist/checkpointer/internal/query.d.ts +0 -20
  214. package/dist/checkpointer/internal/query.d.ts.map +0 -1
  215. package/dist/checkpointer/internal/query.js +0 -36
  216. package/dist/checkpointer/internal/query.js.map +0 -1
  217. package/dist/checkpointer/internal/setup.d.ts.map +0 -1
  218. package/dist/checkpointer/internal/setup.js.map +0 -1
  219. package/dist/checkpointer/internal/special-write-cas.d.ts +0 -30
  220. package/dist/checkpointer/internal/special-write-cas.d.ts.map +0 -1
  221. package/dist/checkpointer/internal/special-write-cas.js +0 -104
  222. package/dist/checkpointer/internal/special-write-cas.js.map +0 -1
  223. package/dist/checkpointer/internal/special-write-cleanup.d.ts +0 -24
  224. package/dist/checkpointer/internal/special-write-cleanup.d.ts.map +0 -1
  225. package/dist/checkpointer/internal/special-write-cleanup.js +0 -47
  226. package/dist/checkpointer/internal/special-write-cleanup.js.map +0 -1
  227. package/dist/checkpointer/internal/special-write-verify.d.ts +0 -54
  228. package/dist/checkpointer/internal/special-write-verify.d.ts.map +0 -1
  229. package/dist/checkpointer/internal/special-write-verify.js +0 -65
  230. package/dist/checkpointer/internal/special-write-verify.js.map +0 -1
  231. package/dist/checkpointer/internal/validation.d.ts +0 -13
  232. package/dist/checkpointer/internal/validation.d.ts.map +0 -1
  233. package/dist/checkpointer/internal/validation.js +0 -30
  234. package/dist/checkpointer/internal/validation.js.map +0 -1
  235. package/dist/checkpointer/internal/write-guard.d.ts +0 -13
  236. package/dist/checkpointer/internal/write-guard.d.ts.map +0 -1
  237. package/dist/checkpointer/internal/write-guard.js +0 -39
  238. package/dist/checkpointer/internal/write-guard.js.map +0 -1
  239. package/dist/checkpointer/internal/write-index.d.ts +0 -37
  240. package/dist/checkpointer/internal/write-index.d.ts.map +0 -1
  241. package/dist/checkpointer/internal/write-index.js +0 -42
  242. package/dist/checkpointer/internal/write-index.js.map +0 -1
  243. package/dist/checkpointer/saver.d.ts.map +0 -1
  244. package/dist/checkpointer/saver.js.map +0 -1
  245. package/dist/checkpointer/types.d.ts.map +0 -1
  246. package/dist/checkpointer/types.js.map +0 -1
  247. package/dist/factory/factory.d.ts.map +0 -1
  248. package/dist/factory/factory.js.map +0 -1
  249. package/dist/history/actions/add-messages.d.ts.map +0 -1
  250. package/dist/history/actions/add-messages.js.map +0 -1
  251. package/dist/history/actions/clear.d.ts.map +0 -1
  252. package/dist/history/actions/clear.js.map +0 -1
  253. package/dist/history/actions/get-messages.d.ts.map +0 -1
  254. package/dist/history/actions/get-messages.js.map +0 -1
  255. package/dist/history/actions/list-sessions.d.ts.map +0 -1
  256. package/dist/history/actions/list-sessions.js.map +0 -1
  257. package/dist/history/actions/reconcile-count.d.ts.map +0 -1
  258. package/dist/history/actions/reconcile-count.js.map +0 -1
  259. package/dist/history/chat-message-history.d.ts.map +0 -1
  260. package/dist/history/chat-message-history.js.map +0 -1
  261. package/dist/history/internal/append-saga.d.ts +0 -20
  262. package/dist/history/internal/append-saga.d.ts.map +0 -1
  263. package/dist/history/internal/append-saga.js +0 -35
  264. package/dist/history/internal/append-saga.js.map +0 -1
  265. package/dist/history/internal/compensation.d.ts +0 -21
  266. package/dist/history/internal/compensation.d.ts.map +0 -1
  267. package/dist/history/internal/compensation.js +0 -84
  268. package/dist/history/internal/compensation.js.map +0 -1
  269. package/dist/history/internal/item-mapper.d.ts +0 -12
  270. package/dist/history/internal/item-mapper.d.ts.map +0 -1
  271. package/dist/history/internal/item-mapper.js +0 -33
  272. package/dist/history/internal/item-mapper.js.map +0 -1
  273. package/dist/history/internal/keys.d.ts +0 -17
  274. package/dist/history/internal/keys.d.ts.map +0 -1
  275. package/dist/history/internal/keys.js +0 -49
  276. package/dist/history/internal/keys.js.map +0 -1
  277. package/dist/history/internal/message-chunker.d.ts +0 -14
  278. package/dist/history/internal/message-chunker.d.ts.map +0 -1
  279. package/dist/history/internal/message-chunker.js +0 -68
  280. package/dist/history/internal/message-chunker.js.map +0 -1
  281. package/dist/history/internal/message-transaction.d.ts +0 -26
  282. package/dist/history/internal/message-transaction.d.ts.map +0 -1
  283. package/dist/history/internal/message-transaction.js +0 -60
  284. package/dist/history/internal/message-transaction.js.map +0 -1
  285. package/dist/history/internal/query.d.ts +0 -10
  286. package/dist/history/internal/query.d.ts.map +0 -1
  287. package/dist/history/internal/query.js +0 -31
  288. package/dist/history/internal/query.js.map +0 -1
  289. package/dist/history/internal/session-count.d.ts +0 -41
  290. package/dist/history/internal/session-count.d.ts.map +0 -1
  291. package/dist/history/internal/session-count.js +0 -109
  292. package/dist/history/internal/session-count.js.map +0 -1
  293. package/dist/history/internal/session-title.d.ts +0 -20
  294. package/dist/history/internal/session-title.d.ts.map +0 -1
  295. package/dist/history/internal/session-title.js +0 -44
  296. package/dist/history/internal/session-title.js.map +0 -1
  297. package/dist/history/internal/session-update.d.ts +0 -28
  298. package/dist/history/internal/session-update.d.ts.map +0 -1
  299. package/dist/history/internal/session-update.js +0 -70
  300. package/dist/history/internal/session-update.js.map +0 -1
  301. package/dist/history/internal/setup.d.ts.map +0 -1
  302. package/dist/history/internal/setup.js.map +0 -1
  303. package/dist/history/internal/title-generator.d.ts +0 -13
  304. package/dist/history/internal/title-generator.d.ts.map +0 -1
  305. package/dist/history/internal/title-generator.js +0 -25
  306. package/dist/history/internal/title-generator.js.map +0 -1
  307. package/dist/history/internal/ttl-anchor.d.ts +0 -25
  308. package/dist/history/internal/ttl-anchor.d.ts.map +0 -1
  309. package/dist/history/internal/ttl-anchor.js +0 -38
  310. package/dist/history/internal/ttl-anchor.js.map +0 -1
  311. package/dist/history/internal/validation.d.ts +0 -9
  312. package/dist/history/internal/validation.d.ts.map +0 -1
  313. package/dist/history/internal/validation.js +0 -16
  314. package/dist/history/internal/validation.js.map +0 -1
  315. package/dist/history/session-adapter.d.ts.map +0 -1
  316. package/dist/history/session-adapter.js.map +0 -1
  317. package/dist/history/types.d.ts.map +0 -1
  318. package/dist/history/types.js.map +0 -1
  319. package/dist/index.d.ts.map +0 -1
  320. package/dist/index.js.map +0 -1
  321. package/dist/shared/clock.d.ts.map +0 -1
  322. package/dist/shared/clock.js.map +0 -1
  323. package/dist/shared/codec/codec.d.ts.map +0 -1
  324. package/dist/shared/codec/codec.js.map +0 -1
  325. package/dist/shared/codec/compression.d.ts.map +0 -1
  326. package/dist/shared/codec/compression.js.map +0 -1
  327. package/dist/shared/codec/descriptor-keys.d.ts +0 -4
  328. package/dist/shared/codec/descriptor-keys.d.ts.map +0 -1
  329. package/dist/shared/codec/descriptor-keys.js +0 -14
  330. package/dist/shared/codec/descriptor-keys.js.map +0 -1
  331. package/dist/shared/codec/json-serde.d.ts.map +0 -1
  332. package/dist/shared/codec/json-serde.js.map +0 -1
  333. package/dist/shared/codec/s3/client.d.ts.map +0 -1
  334. package/dist/shared/codec/s3/client.js.map +0 -1
  335. package/dist/shared/codec/s3/config.d.ts.map +0 -1
  336. package/dist/shared/codec/s3/config.js.map +0 -1
  337. package/dist/shared/codec/s3/delete.d.ts +0 -8
  338. package/dist/shared/codec/s3/delete.d.ts.map +0 -1
  339. package/dist/shared/codec/s3/delete.js +0 -29
  340. package/dist/shared/codec/s3/delete.js.map +0 -1
  341. package/dist/shared/codec/s3/lifecycle.d.ts.map +0 -1
  342. package/dist/shared/codec/s3/lifecycle.js.map +0 -1
  343. package/dist/shared/codec/s3/offloader.d.ts.map +0 -1
  344. package/dist/shared/codec/s3/offloader.js.map +0 -1
  345. package/dist/shared/codec/s3/orphans.d.ts +0 -18
  346. package/dist/shared/codec/s3/orphans.d.ts.map +0 -1
  347. package/dist/shared/codec/s3/orphans.js +0 -58
  348. package/dist/shared/codec/s3/orphans.js.map +0 -1
  349. package/dist/shared/codec/s3/read-write.d.ts +0 -14
  350. package/dist/shared/codec/s3/read-write.d.ts.map +0 -1
  351. package/dist/shared/codec/s3/read-write.js +0 -43
  352. package/dist/shared/codec/s3/read-write.js.map +0 -1
  353. package/dist/shared/codec/s3/retry.d.ts +0 -5
  354. package/dist/shared/codec/s3/retry.d.ts.map +0 -1
  355. package/dist/shared/codec/s3/retry.js +0 -25
  356. package/dist/shared/codec/s3/retry.js.map +0 -1
  357. package/dist/shared/constants.d.ts +0 -64
  358. package/dist/shared/constants.d.ts.map +0 -1
  359. package/dist/shared/constants.js +0 -67
  360. package/dist/shared/constants.js.map +0 -1
  361. package/dist/shared/dynamodb/backoff.d.ts +0 -15
  362. package/dist/shared/dynamodb/backoff.d.ts.map +0 -1
  363. package/dist/shared/dynamodb/backoff.js +0 -48
  364. package/dist/shared/dynamodb/backoff.js.map +0 -1
  365. package/dist/shared/dynamodb/batch-write.d.ts.map +0 -1
  366. package/dist/shared/dynamodb/batch-write.js.map +0 -1
  367. package/dist/shared/dynamodb/cancellation.d.ts.map +0 -1
  368. package/dist/shared/dynamodb/cancellation.js.map +0 -1
  369. package/dist/shared/dynamodb/client.d.ts.map +0 -1
  370. package/dist/shared/dynamodb/client.js.map +0 -1
  371. package/dist/shared/dynamodb/conditional-put.d.ts +0 -51
  372. package/dist/shared/dynamodb/conditional-put.d.ts.map +0 -1
  373. package/dist/shared/dynamodb/conditional-put.js +0 -59
  374. package/dist/shared/dynamodb/conditional-put.js.map +0 -1
  375. package/dist/shared/dynamodb/drain-unprocessed.d.ts +0 -19
  376. package/dist/shared/dynamodb/drain-unprocessed.d.ts.map +0 -1
  377. package/dist/shared/dynamodb/drain-unprocessed.js +0 -44
  378. package/dist/shared/dynamodb/drain-unprocessed.js.map +0 -1
  379. package/dist/shared/dynamodb/paginate-core.d.ts +0 -22
  380. package/dist/shared/dynamodb/paginate-core.d.ts.map +0 -1
  381. package/dist/shared/dynamodb/paginate-core.js +0 -52
  382. package/dist/shared/dynamodb/paginate-core.js.map +0 -1
  383. package/dist/shared/dynamodb/paginate.d.ts.map +0 -1
  384. package/dist/shared/dynamodb/paginate.js.map +0 -1
  385. package/dist/shared/dynamodb/partition-delete.d.ts.map +0 -1
  386. package/dist/shared/dynamodb/partition-delete.js.map +0 -1
  387. package/dist/shared/dynamodb/retry-classifier.d.ts +0 -9
  388. package/dist/shared/dynamodb/retry-classifier.d.ts.map +0 -1
  389. package/dist/shared/dynamodb/retry-classifier.js +0 -87
  390. package/dist/shared/dynamodb/retry-classifier.js.map +0 -1
  391. package/dist/shared/dynamodb/retry.d.ts.map +0 -1
  392. package/dist/shared/dynamodb/retry.js.map +0 -1
  393. package/dist/shared/dynamodb/scan.d.ts +0 -15
  394. package/dist/shared/dynamodb/scan.d.ts.map +0 -1
  395. package/dist/shared/dynamodb/scan.js +0 -20
  396. package/dist/shared/dynamodb/scan.js.map +0 -1
  397. package/dist/shared/dynamodb/types.d.ts +0 -24
  398. package/dist/shared/dynamodb/types.d.ts.map +0 -1
  399. package/dist/shared/dynamodb/types.js +0 -3
  400. package/dist/shared/dynamodb/types.js.map +0 -1
  401. package/dist/shared/errors/base-error.d.ts.map +0 -1
  402. package/dist/shared/errors/base-error.js.map +0 -1
  403. package/dist/shared/errors/error-code.d.ts.map +0 -1
  404. package/dist/shared/errors/error-code.js.map +0 -1
  405. package/dist/shared/errors/errors.d.ts.map +0 -1
  406. package/dist/shared/errors/errors.js.map +0 -1
  407. package/dist/shared/errors/wrap-error.d.ts +0 -16
  408. package/dist/shared/errors/wrap-error.d.ts.map +0 -1
  409. package/dist/shared/errors/wrap-error.js +0 -30
  410. package/dist/shared/errors/wrap-error.js.map +0 -1
  411. package/dist/shared/logging/logger.d.ts.map +0 -1
  412. package/dist/shared/logging/logger.js.map +0 -1
  413. package/dist/shared/logging/redaction-walk.d.ts +0 -23
  414. package/dist/shared/logging/redaction-walk.d.ts.map +0 -1
  415. package/dist/shared/logging/redaction-walk.js +0 -92
  416. package/dist/shared/logging/redaction-walk.js.map +0 -1
  417. package/dist/shared/logging/redaction.d.ts.map +0 -1
  418. package/dist/shared/logging/redaction.js.map +0 -1
  419. package/dist/shared/logging/secret-patterns.d.ts.map +0 -1
  420. package/dist/shared/logging/secret-patterns.js.map +0 -1
  421. package/dist/shared/options.d.ts.map +0 -1
  422. package/dist/shared/options.js.map +0 -1
  423. package/dist/shared/ulid.d.ts.map +0 -1
  424. package/dist/shared/ulid.js.map +0 -1
  425. package/dist/shared/validation/primitives.d.ts.map +0 -1
  426. package/dist/shared/validation/primitives.js.map +0 -1
  427. package/dist/shared/validation/ttl.d.ts.map +0 -1
  428. package/dist/shared/validation/ttl.js.map +0 -1
  429. package/dist/store/actions/get.d.ts +0 -5
  430. package/dist/store/actions/get.d.ts.map +0 -1
  431. package/dist/store/actions/get.js +0 -35
  432. package/dist/store/actions/get.js.map +0 -1
  433. package/dist/store/actions/list-namespaces.d.ts.map +0 -1
  434. package/dist/store/actions/list-namespaces.js.map +0 -1
  435. package/dist/store/actions/put.d.ts.map +0 -1
  436. package/dist/store/actions/put.js.map +0 -1
  437. package/dist/store/actions/reconcile-vector-index.d.ts.map +0 -1
  438. package/dist/store/actions/reconcile-vector-index.js.map +0 -1
  439. package/dist/store/actions/search.d.ts.map +0 -1
  440. package/dist/store/actions/search.js.map +0 -1
  441. package/dist/store/internal/backend-search.d.ts +0 -5
  442. package/dist/store/internal/backend-search.d.ts.map +0 -1
  443. package/dist/store/internal/backend-search.js +0 -68
  444. package/dist/store/internal/backend-search.js.map +0 -1
  445. package/dist/store/internal/filter.d.ts.map +0 -1
  446. package/dist/store/internal/filter.js.map +0 -1
  447. package/dist/store/internal/index-reconcile.d.ts +0 -22
  448. package/dist/store/internal/index-reconcile.d.ts.map +0 -1
  449. package/dist/store/internal/index-reconcile.js +0 -105
  450. package/dist/store/internal/index-reconcile.js.map +0 -1
  451. package/dist/store/internal/index-sync.d.ts +0 -11
  452. package/dist/store/internal/index-sync.d.ts.map +0 -1
  453. package/dist/store/internal/index-sync.js +0 -26
  454. package/dist/store/internal/index-sync.js.map +0 -1
  455. package/dist/store/internal/item-mapper.d.ts +0 -25
  456. package/dist/store/internal/item-mapper.d.ts.map +0 -1
  457. package/dist/store/internal/item-mapper.js +0 -53
  458. package/dist/store/internal/item-mapper.js.map +0 -1
  459. package/dist/store/internal/keys.d.ts +0 -18
  460. package/dist/store/internal/keys.d.ts.map +0 -1
  461. package/dist/store/internal/keys.js +0 -42
  462. package/dist/store/internal/keys.js.map +0 -1
  463. package/dist/store/internal/namespace-match.d.ts +0 -12
  464. package/dist/store/internal/namespace-match.d.ts.map +0 -1
  465. package/dist/store/internal/namespace-match.js +0 -41
  466. package/dist/store/internal/namespace-match.js.map +0 -1
  467. package/dist/store/internal/overwrite-swap.d.ts +0 -33
  468. package/dist/store/internal/overwrite-swap.d.ts.map +0 -1
  469. package/dist/store/internal/overwrite-swap.js +0 -62
  470. package/dist/store/internal/overwrite-swap.js.map +0 -1
  471. package/dist/store/internal/persist.d.ts +0 -27
  472. package/dist/store/internal/persist.d.ts.map +0 -1
  473. package/dist/store/internal/persist.js +0 -59
  474. package/dist/store/internal/persist.js.map +0 -1
  475. package/dist/store/internal/query.d.ts +0 -6
  476. package/dist/store/internal/query.d.ts.map +0 -1
  477. package/dist/store/internal/query.js +0 -32
  478. package/dist/store/internal/query.js.map +0 -1
  479. package/dist/store/internal/ranker.d.ts +0 -13
  480. package/dist/store/internal/ranker.d.ts.map +0 -1
  481. package/dist/store/internal/ranker.js +0 -31
  482. package/dist/store/internal/ranker.js.map +0 -1
  483. package/dist/store/internal/read-existing.d.ts +0 -19
  484. package/dist/store/internal/read-existing.d.ts.map +0 -1
  485. package/dist/store/internal/read-existing.js +0 -29
  486. package/dist/store/internal/read-existing.js.map +0 -1
  487. package/dist/store/internal/score-direction.d.ts +0 -32
  488. package/dist/store/internal/score-direction.d.ts.map +0 -1
  489. package/dist/store/internal/score-direction.js +0 -39
  490. package/dist/store/internal/score-direction.js.map +0 -1
  491. package/dist/store/internal/search-filter.d.ts +0 -4
  492. package/dist/store/internal/search-filter.d.ts.map +0 -1
  493. package/dist/store/internal/search-filter.js +0 -11
  494. package/dist/store/internal/search-filter.js.map +0 -1
  495. package/dist/store/internal/semantic-search.d.ts.map +0 -1
  496. package/dist/store/internal/semantic-search.js.map +0 -1
  497. package/dist/store/internal/setup.d.ts.map +0 -1
  498. package/dist/store/internal/setup.js.map +0 -1
  499. package/dist/store/internal/validation.d.ts +0 -13
  500. package/dist/store/internal/validation.d.ts.map +0 -1
  501. package/dist/store/internal/validation.js +0 -35
  502. package/dist/store/internal/validation.js.map +0 -1
  503. package/dist/store/internal/write-verify.d.ts +0 -37
  504. package/dist/store/internal/write-verify.d.ts.map +0 -1
  505. package/dist/store/internal/write-verify.js +0 -68
  506. package/dist/store/internal/write-verify.js.map +0 -1
  507. package/dist/store/store.d.ts.map +0 -1
  508. package/dist/store/store.js.map +0 -1
  509. package/dist/store/types.d.ts.map +0 -1
  510. package/dist/store/types.js.map +0 -1
  511. package/dist/store/vector-backend.d.ts.map +0 -1
  512. package/dist/store/vector-backend.js.map +0 -1
@@ -0,0 +1,628 @@
1
+ "use strict";
2
+ /**
3
+ * Hides the SESSION row: what it carries and how each field changes.
4
+ *
5
+ * A session's metadata — `messageCount`, `title`, `createdAt`, `updatedAt`,
6
+ * `ttl`, the `writeId` of the last append, and the recency-index keys — lives
7
+ * on one row, and `messageCount` is a copy of a fact the message rows hold.
8
+ * Keeping the copy in step is this module's job and nobody else's: the append
9
+ * adds to it in the same transaction that writes the messages, a rolled-back
10
+ * append subtracts exactly what it added (and never from a later incarnation of
11
+ * the session), a repair recomputes it under a compare-and-swap, and a listing
12
+ * reads it back only from a row this release can summarise. The recency-index
13
+ * keys are the one field this module does not write alone: `sessionIndexTarget`
14
+ * below tells `src/backfill/backfill.ts` which row is a SESSION row, and
15
+ * backfill writes the keys onto it directly, for a row that predates the index.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.revertSessionCount = revertSessionCount;
19
+ exports.revertSessionCreation = revertSessionCreation;
20
+ exports.buildSessionUpdate = buildSessionUpdate;
21
+ exports.removeRolledBackTitle = removeRolledBackTitle;
22
+ exports.resolveTtlAnchor = resolveTtlAnchor;
23
+ exports.deriveTitle = deriveTitle;
24
+ exports.repairMessageCount = repairMessageCount;
25
+ exports.summariseSession = summariseSession;
26
+ exports.sessionIndexTarget = sessionIndexTarget;
27
+ const clock_1 = require("../../shared/clock");
28
+ const cancellation_1 = require("../../shared/dynamodb/cancellation");
29
+ const idempotent_write_1 = require("../../shared/dynamodb/idempotent-write");
30
+ const recency_index_1 = require("../../shared/dynamodb/recency-index");
31
+ const retry_1 = require("../../shared/dynamodb/retry");
32
+ const table_schema_1 = require("../../shared/dynamodb/table-schema");
33
+ const classify_1 = require("../../shared/errors/classify");
34
+ const error_code_1 = require("../../shared/errors/error-code");
35
+ const errors_1 = require("../../shared/errors/errors");
36
+ const message_read_1 = require("./message-read");
37
+ const rows_1 = require("./rows");
38
+ /**
39
+ * True when the transaction's first item — the SESSION row update or delete,
40
+ * since both callers below send a single-item transaction — failed its
41
+ * ConditionExpression. Named distinctly from
42
+ * shared/dynamodb/idempotent-write.ts's isConditionalCheckFailed, which
43
+ * classifies a rejection (a PutItem exception or a cancelled transaction's
44
+ * reasons alike) without asking which item caused it — sharing that name
45
+ * would be a real trap for whoever read one assuming it was the other.
46
+ */
47
+ function isCancelledByCondition(error) {
48
+ return (0, cancellation_1.conditionFailedAt)(error, 0);
49
+ }
50
+ /**
51
+ * Subtract a count this append already added from the session, leaving it consistent.
52
+ * Guarded so a concurrently-deleted SESSION row is never resurrected as a
53
+ * permanent, ttl-less junk row: if the row is already gone there is nothing
54
+ * to revert, so that specific condition failure is swallowed rather than
55
+ * surfaced — this runs only from an already-in-progress rollback, where a
56
+ * spurious error for a no-op would misrepresent what happened.
57
+ *
58
+ * The same condition also pins the *incarnation*: `createdAt <= createdBefore`
59
+ * (this call's own append timestamp). A session `clear()`-ed and re-created by
60
+ * another caller between this call's commit and its rollback carries a later
61
+ * `createdAt`; decrementing it would corrupt the new incarnation's count, and
62
+ * its rows were never this call's to revert. That rejection is swallowed too,
63
+ * for the same reason as a vanished row.
64
+ *
65
+ * Deliberately does not revert a `forceTtlRefresh`-driven ttl SET from an
66
+ * earlier committed chunk: the healed anchor is never shorter than what
67
+ * was there before, so leaving it in place after a rollback only means the
68
+ * session's metadata row outlives its content a bit longer than ideal —
69
+ * self-healing (the next successful append, or DynamoDB's own TTL sweep,
70
+ * resolves it), unlike reverting, which would need to re-check for a
71
+ * concurrent legitimate extension to avoid regressing it. See README.md's
72
+ * "TTL expiry" section.
73
+ *
74
+ * Accepts: `delta` — how many messages to subtract; `0` is a no-op and spends
75
+ * no write. `createdBefore` — this call's own append timestamp, which pins the
76
+ * incarnation.
77
+ *
78
+ * Returns: nothing, whether the decrement applied or the guard correctly
79
+ * refused it.
80
+ *
81
+ * Throws: whatever the write throws other than its own condition failure. A
82
+ * vanished row and a newer incarnation are both "nothing of mine to revert",
83
+ * not errors — this runs from an in-progress rollback, where a spurious error
84
+ * for a no-op would misrepresent what happened.
85
+ *
86
+ * Guarantees: the decrement is applied at most once, however often the request
87
+ * is re-sent. `ADD #count :neg` is one of the two writes in this package that
88
+ * are not naturally idempotent — the append's own `ADD #count :n` is the
89
+ * other — and applied twice it subtracts twice, with nothing reading the row
90
+ * back afterwards to notice, so a re-sent attempt must be answered from
91
+ * DynamoDB's idempotency cache rather than re-evaluated. Two things hold
92
+ * that together and only together: the `ClientRequestToken`, which makes a
93
+ * re-send a no-op, and the deadline of `MAX_WRITE_LIFETIME_MS`, which
94
+ * stops the retrying while that token is still honoured. Nothing in the token
95
+ * enforces that window — the service honours it for its own ten minutes
96
+ * whatever a caller's retry policy says — so without the deadline a long
97
+ * policy could still be retrying after the window closed, and the re-send
98
+ * would then land as a second subtraction, leaving `messageCount` quietly
99
+ * wrong. `reconcileMessageCount` is the repair if that happens anyway: it
100
+ * recounts the live messages and writes the true total back.
101
+ *
102
+ * The guard is no second line of defence for it. An attempt the condition
103
+ * turns away commits nothing, so DynamoDB caches no result for that attempt's
104
+ * token and a retry is a fresh evaluation rather than a deduplicated one — the
105
+ * exactly-once promise holds for an attempt that **committed**, which is also
106
+ * the only attempt whose re-send could subtract twice.
107
+ */
108
+ async function revertSessionCount(context, sessionId, delta, createdBefore) {
109
+ if (delta === 0)
110
+ return;
111
+ const update = {
112
+ TableName: context.tableName,
113
+ Key: (0, rows_1.sessionRowKey)(sessionId),
114
+ UpdateExpression: 'ADD #count :neg',
115
+ ConditionExpression: `attribute_exists(${table_schema_1.PARTITION_KEY_ATTRIBUTE}) AND #c <= :now`,
116
+ ExpressionAttributeNames: { '#count': 'messageCount', '#c': 'createdAt' },
117
+ ExpressionAttributeValues: { ':neg': -delta, ':now': createdBefore },
118
+ };
119
+ try {
120
+ await (0, idempotent_write_1.transactIdempotently)(context, [{ Update: update }]);
121
+ }
122
+ catch (error) {
123
+ if (isCancelledByCondition(error))
124
+ return;
125
+ throw error;
126
+ }
127
+ }
128
+ /**
129
+ * Undo a rolled-back append's effect on the session row.
130
+ *
131
+ * When this call is the one that created the row — `createdAt` still equals
132
+ * this call's timestamp, and the only messages counted on it are the ones
133
+ * being reverted — the whole row is deleted. Without that, a failed first
134
+ * append left a "ghost session": `title`, `createdAt` and `sessionId` are all
135
+ * written via `if_not_exists`, so they were never reverted and never set
136
+ * again, leaving `listSessions()` reporting a session with `messageCount: 0`
137
+ * whose title still held up to 80 characters of a message the caller was told
138
+ * had not persisted, with no API to clear it.
139
+ *
140
+ * Both conditions are load-bearing. `createdAt = :now` establishes that this
141
+ * call created the row; `messageCount = :total` establishes that nothing else
142
+ * has added to it since. A concurrent append to the same brand-new session
143
+ * fails the count check, because deleting the row would destroy that caller's
144
+ * committed messages — so it falls through to the plain decrement, and then
145
+ * strips just the title this call contributed, which is the only part of the
146
+ * row still carrying rolled-back message content.
147
+ *
148
+ * Accepts: `created` — what this call contributed to the row. `created.total`
149
+ * — every message this call counted onto the row; `0` is a no-op.
150
+ * `created.createdAt` — this call's timestamp, which is what "I created this
151
+ * row" means here. `created.title` — the title this call may have contributed.
152
+ *
153
+ * Returns: nothing. The row is deleted, or decremented and stripped of this
154
+ * call's title; both are a complete undo of what this call contributed.
155
+ *
156
+ * Throws: whatever the writes throw other than their own condition failures.
157
+ *
158
+ * Guarantees: the delete is applied at most once, and inside the window its
159
+ * token is honoured for, a re-send of an attempt that **committed** is
160
+ * answered from DynamoDB's idempotency cache rather than re-evaluated. The
161
+ * condition would already stop such a re-send from removing anything it should
162
+ * not; what the token adds is that it comes back as the success it was.
163
+ * Re-evaluated instead, it finds the row gone, fails both equalities, and the
164
+ * cancellation is read below as "a concurrent append has added to this row" —
165
+ * sending a rollback that already completed down the decrement-and-strip path
166
+ * meant for the case where the row survived. Both writes on that path are
167
+ * themselves guarded, and the decrement's incarnation pin refuses a session
168
+ * re-created in the meantime, so the price is two spurious conditional writes
169
+ * rather than a wrong count; the token is what keeps them from being spent.
170
+ *
171
+ * A rejection carries no idempotency forward — a cancelled attempt commits
172
+ * nothing, so nothing is cached for its token — and here that is exactly the
173
+ * wanted behaviour, since the fall-through below is a fresh decision about
174
+ * what to do instead. The deadline drawn beside the token is what keeps the
175
+ * retrying inside that window; the token enforces no window of its own.
176
+ */
177
+ async function revertSessionCreation(context, sessionId, created) {
178
+ if (created.total === 0)
179
+ return;
180
+ try {
181
+ await (0, idempotent_write_1.transactIdempotently)(context, [
182
+ {
183
+ Delete: {
184
+ TableName: context.tableName,
185
+ Key: (0, rows_1.sessionRowKey)(sessionId),
186
+ ConditionExpression: '#count = :total AND #c = :now',
187
+ ExpressionAttributeNames: { '#count': 'messageCount', '#c': 'createdAt' },
188
+ ExpressionAttributeValues: { ':total': created.total, ':now': created.createdAt },
189
+ },
190
+ },
191
+ ]);
192
+ return;
193
+ }
194
+ catch (error) {
195
+ if (!isCancelledByCondition(error))
196
+ throw error;
197
+ }
198
+ await revertSessionCount(context, sessionId, created.total, created.createdAt);
199
+ if (created.title !== undefined) {
200
+ await removeRolledBackTitle(context, sessionId, created.createdAt, created.title);
201
+ }
202
+ }
203
+ /**
204
+ * Build the metadata `Update` transact-item: `ADD` the message count and `SET`
205
+ * `updatedAt` and the appending write's id every time, while `createdAt`,
206
+ * `sessionId`, `title`, and the `ttl` anchor are written once via
207
+ * `if_not_exists`. Folding the `ttl` anchor in here means the first append
208
+ * fixes one shared expiry atomically with the count, with no separate
209
+ * pre-write that could orphan a metadata-only row. When
210
+ * `forceTtlRefresh` is set (because {@link resolveTtlAnchor} found the persisted
211
+ * anchor missing or already expired), the `ttl` clause instead does a plain
212
+ * `SET`, so the SESSION row's own stale attribute actually heals instead of
213
+ * being permanently blocked by `if_not_exists`. When forceTtlRefresh is set,
214
+ * the SET is additionally guarded by a ConditionExpression so a concurrent
215
+ * caller's already-healed anchor can never be regressed backward — see
216
+ * append.ts for how a lost race is retried without forcing.
217
+ *
218
+ * Accepts: `count` — how many messages this append adds, which `ADD` applies to
219
+ * whatever the row holds, so two concurrent appends both count. `title` —
220
+ * written once and never overwritten, so a session keeps the title its first
221
+ * turn produced. `writeId` — the appending write's own id, rewritten on every
222
+ * append so the row always names the write that last added to it.
223
+ * `ttlTimestamp` — absent leaves the row without an expiry. `forceTtlRefresh` —
224
+ * see above.
225
+ *
226
+ * Returns: the `Update` transact-item. It creates the row when there is none:
227
+ * every once-only field is an `if_not_exists`, so the first append and the
228
+ * thousandth build the same item.
229
+ *
230
+ * Throws: nothing. The condition it carries is evaluated by DynamoDB, and a
231
+ * failed condition surfaces from the transaction, not from here.
232
+ */
233
+ function buildSessionUpdate(tableName, fields) {
234
+ const index = (0, recency_index_1.indexKeys)('SESS', fields.sessionId, fields.now, fields.indexShards ?? recency_index_1.DEFAULT_INDEX_SHARDS);
235
+ const names = {
236
+ '#count': 'messageCount',
237
+ '#u': 'updatedAt',
238
+ '#c': 'createdAt',
239
+ '#sid': 'sessionId',
240
+ '#wid': 'writeId',
241
+ '#v': 'v',
242
+ '#gpk': 'gsi1pk',
243
+ '#gsk': 'gsi1sk',
244
+ };
245
+ const values = {
246
+ ':n': fields.count,
247
+ ':u': fields.now,
248
+ ':c': fields.now,
249
+ ':sid': fields.sessionId,
250
+ ':wid': fields.writeId,
251
+ ':v': table_schema_1.ROW_FORMAT_VERSION,
252
+ ':gpk': index.gsi1pk,
253
+ ':gsk': index.gsi1sk,
254
+ };
255
+ // The row's format version is rewritten on every update, not only on
256
+ // creation: an append by this version leaves a row this version wrote, and a
257
+ // reader must be told that rather than infer it from which attributes happen
258
+ // to be present.
259
+ const sets = [
260
+ '#u = :u',
261
+ '#c = if_not_exists(#c, :c)',
262
+ '#sid = if_not_exists(#sid, :sid)',
263
+ // An unconditional `SET`, deliberately unlike the three `if_not_exists`
264
+ // clauses around it. This is not a once-only field: its whole content is
265
+ // that it moves. Written in their style it would stamp the id when the
266
+ // session was created and never again, and a delete pinning on the id it
267
+ // observed would be present, well formed, and always pass — which is the
268
+ // failure it exists to prevent.
269
+ '#wid = :wid',
270
+ '#v = :v',
271
+ // The session row is listed by recency across partitions, so it carries the
272
+ // index keys — rewritten on every append, which is what keeps "most
273
+ // recently updated first" true without an in-memory sort.
274
+ '#gpk = :gpk',
275
+ '#gsk = :gsk',
276
+ ];
277
+ if (fields.title !== undefined) {
278
+ names['#title'] = 'title';
279
+ values[':title'] = fields.title;
280
+ sets.push('#title = if_not_exists(#title, :title)');
281
+ }
282
+ let conditionExpression;
283
+ if (fields.ttlTimestamp !== undefined) {
284
+ names['#ttl'] = 'ttl';
285
+ values[':ttl'] = fields.ttlTimestamp;
286
+ if (fields.forceTtlRefresh) {
287
+ sets.push('#ttl = :ttl');
288
+ // Guards the force-overwrite so a concurrent caller's already-healed,
289
+ // equal-or-later anchor can never be regressed backward by this one.
290
+ // `<=` (not `<`): two concurrent healers of the same stale anchor
291
+ // typically compute the identical target timestamp, and `<=` lets
292
+ // the second one succeed by re-applying the same value instead of
293
+ // failing the condition and paying a full transaction retry for a
294
+ // write that was never actually a regression.
295
+ conditionExpression = 'attribute_not_exists(#ttl) OR #ttl <= :ttl';
296
+ }
297
+ else {
298
+ sets.push('#ttl = if_not_exists(#ttl, :ttl)');
299
+ }
300
+ }
301
+ return {
302
+ Update: {
303
+ TableName: tableName,
304
+ Key: (0, rows_1.sessionRowKey)(fields.sessionId),
305
+ UpdateExpression: `ADD #count :n SET ${sets.join(', ')}`,
306
+ ...(conditionExpression ? { ConditionExpression: conditionExpression } : {}),
307
+ ExpressionAttributeNames: names,
308
+ ExpressionAttributeValues: values,
309
+ },
310
+ };
311
+ }
312
+ /** True when a plain UpdateItem was turned away by its ConditionExpression. */
313
+ function isConditionRejected(error) {
314
+ return (0, classify_1.classifyAwsError)(error) === error_code_1.ErrorCode.CONDITION_CONFLICT;
315
+ }
316
+ /**
317
+ * Strip a title this call contributed to a session row it created, when the
318
+ * row itself could not be deleted because a concurrent append has since added
319
+ * messages to it.
320
+ *
321
+ * Without this, that narrow window reopens exactly the leak C4 closes: the
322
+ * title is derived from the first human message of an append the caller was
323
+ * told had failed, and `if_not_exists` means nothing ever overwrites it — so
324
+ * a row that now belongs to a different caller keeps up to 80 characters of
325
+ * rolled-back message content.
326
+ *
327
+ * Both guards are load-bearing. `createdAt = :now` establishes that this call
328
+ * created the row, and `title = :title` that the title on it is still the one
329
+ * this call wrote — so a pre-existing title, or one a concurrent caller won
330
+ * the `if_not_exists` race for, is never removed. A condition rejection means
331
+ * exactly that and is not an error; anything else is.
332
+ *
333
+ * Accepts: `createdAt` — this call's own timestamp. `title` — the exact string
334
+ * this call wrote, compared as a value so nothing else's title is removed.
335
+ *
336
+ * Returns: nothing, whether the title was removed or the guards correctly
337
+ * refused.
338
+ *
339
+ * Throws: whatever the update throws other than its own condition failure.
340
+ */
341
+ async function removeRolledBackTitle(context, sessionId, createdAt, title) {
342
+ try {
343
+ await (0, retry_1.withDynamoDBRetry)((request) => context.client.update({
344
+ TableName: context.tableName,
345
+ Key: (0, rows_1.sessionRowKey)(sessionId),
346
+ UpdateExpression: 'REMOVE #title',
347
+ ConditionExpression: '#c = :now AND #title = :title',
348
+ ExpressionAttributeNames: { '#title': 'title', '#c': 'createdAt' },
349
+ ExpressionAttributeValues: { ':now': createdAt, ':title': title },
350
+ }, request), context.retry);
351
+ }
352
+ catch (error) {
353
+ if (isConditionRejected(error))
354
+ return;
355
+ throw error;
356
+ }
357
+ }
358
+ /**
359
+ * Resolve the session's creation-anchored TTL: the value already stored on the
360
+ * SESSION item, if one exists AND is still in the future; otherwise the
361
+ * supplied `candidate`, with `refresh: true` so the caller force-overwrites
362
+ * the stale/missing persisted anchor instead of leaving it stuck (DynamoDB's
363
+ * `if_not_exists` would otherwise never correct an already-expired anchor).
364
+ * This is a strongly-consistent read, never a write, so it cannot leave a
365
+ * metadata-only orphan row when the following append transaction fails.
366
+ *
367
+ * When a stale anchor is healed, only the persisted SESSION row's `ttl` is
368
+ * force-refreshed — the message rows already written under the expired
369
+ * anchor keep their own (already-expired) `ttl` and get swept independently
370
+ * by DynamoDB's TTL sweep. Until that sweep runs (and until
371
+ * `reconcileMessageCount` repairs the count), `messageCount` can therefore be
372
+ * temporarily overstated relative to what `getMessages` actually returns.
373
+ * This is expected, not a bug.
374
+ *
375
+ * Accepts: `candidate` — the anchor this append would use if the session has
376
+ * none, already computed from the configured ttl.
377
+ *
378
+ * Returns: the anchor to stamp on this append's messages, and whether the
379
+ * SESSION row's own `ttl` must be force-overwritten rather than left to
380
+ * `if_not_exists`.
381
+ *
382
+ * Throws: whatever the read throws after retries.
383
+ *
384
+ * Guarantees: a read, never a write — so a failure of the append that follows
385
+ * cannot leave a metadata-only orphan row behind. Strongly consistent, so an
386
+ * anchor an earlier append committed is always seen. Two appends that start
387
+ * together on a session that has none each propose their own candidate; the
388
+ * append transaction's own condition is what settles which persists, so this
389
+ * read never has to be the arbiter (see {@link buildSessionUpdate}).
390
+ */
391
+ async function resolveTtlAnchor(context, sessionId, candidate, signal) {
392
+ const result = await (0, retry_1.withDynamoDBRetry)((request) => context.client.get({
393
+ TableName: context.tableName,
394
+ Key: (0, rows_1.sessionRowKey)(sessionId),
395
+ ConsistentRead: true,
396
+ ProjectionExpression: '#ttl',
397
+ ExpressionAttributeNames: { '#ttl': 'ttl' },
398
+ }, request), (0, retry_1.retryFor)(context, signal));
399
+ const ttl = result.Item?.ttl;
400
+ if (typeof ttl === 'number' && ttl > (0, clock_1.nowSeconds)()) {
401
+ return { ttlTimestamp: ttl, refresh: false };
402
+ }
403
+ return { ttlTimestamp: candidate, refresh: true };
404
+ }
405
+ const MAX_TITLE_LENGTH = 80;
406
+ function isTextBlock(block) {
407
+ const candidate = block;
408
+ return candidate.type === 'text' && typeof candidate.text === 'string';
409
+ }
410
+ /**
411
+ * The human-readable text of a message's `content`: the string itself, or the
412
+ * first `text` block of a content-block array (a multimodal message carries
413
+ * image and text blocks side by side). Undefined when neither yields text.
414
+ */
415
+ function textOf(content) {
416
+ if (typeof content === 'string')
417
+ return content;
418
+ if (!Array.isArray(content))
419
+ return undefined;
420
+ const block = content.find((entry) => typeof entry === 'object' && entry !== null && isTextBlock(entry));
421
+ return block?.text;
422
+ }
423
+ /**
424
+ * Derive a session title from the first human message's text content, at most
425
+ * {@link MAX_TITLE_LENGTH} characters including the ellipsis. Returns undefined
426
+ * when there is no usable text.
427
+ *
428
+ * Truncation counts *code points*, not UTF-16 code units: slicing by index
429
+ * could cut a surrogate pair in half, leaving a lone surrogate that no longer
430
+ * round-trips through UTF-8. The ellipsis is also counted against the maximum
431
+ * rather than appended past it.
432
+ *
433
+ * Accepts: `messages` — one append's messages, in order. The first `human` one
434
+ * is the title's source; an append of only AI or tool messages has none, and an
435
+ * append to an existing session produces a title that is then discarded by
436
+ * `if_not_exists`.
437
+ *
438
+ * Returns: the title, or undefined when there is no usable text — no human
439
+ * message, empty content, or a multimodal message carrying no text block.
440
+ * Undefined means "do not write a title", not "write an empty one".
441
+ *
442
+ * Throws: nothing. A title is a convenience; nothing about it may fail an
443
+ * append.
444
+ */
445
+ function deriveTitle(messages) {
446
+ const firstHuman = messages.find((message) => message.type === 'human');
447
+ const content = firstHuman === undefined ? undefined : textOf(firstHuman.data.content);
448
+ if (content === undefined || content.length === 0)
449
+ return undefined;
450
+ const codePoints = [...content];
451
+ if (codePoints.length <= MAX_TITLE_LENGTH)
452
+ return content;
453
+ return `${codePoints.slice(0, MAX_TITLE_LENGTH - 1).join('')}…`;
454
+ }
455
+ /** Read the count this repair is about to replace, strongly consistently. */
456
+ async function observeCount(context, sessionId, signal) {
457
+ const result = await (0, retry_1.withDynamoDBRetry)((request) => context.client.get({
458
+ TableName: context.tableName,
459
+ Key: (0, rows_1.sessionRowKey)(sessionId),
460
+ ConsistentRead: true,
461
+ ProjectionExpression: '#count',
462
+ ExpressionAttributeNames: { '#count': 'messageCount' },
463
+ }, request), (0, retry_1.retryFor)(context, signal));
464
+ if (!result.Item)
465
+ return { exists: false };
466
+ const count = result.Item.messageCount;
467
+ return typeof count === 'number' ? { exists: true, count } : { exists: true };
468
+ }
469
+ /**
470
+ * The condition admitting the repair only while the row still holds the count
471
+ * it was computed against. A row written before the attribute existed carries
472
+ * none, and pinning its *absence* is what makes the guard correct there too.
473
+ */
474
+ function countGuard(observed) {
475
+ if (observed.count === undefined) {
476
+ return {
477
+ ConditionExpression: `attribute_exists(${table_schema_1.PARTITION_KEY_ATTRIBUTE}) AND attribute_not_exists(#count)`,
478
+ };
479
+ }
480
+ return {
481
+ ConditionExpression: `attribute_exists(${table_schema_1.PARTITION_KEY_ATTRIBUTE}) AND #count = :expected`,
482
+ ExpressionAttributeValues: { ':expected': observed.count },
483
+ };
484
+ }
485
+ /** Write the recomputed count, pinned to what the row held when it was computed. */
486
+ async function writeCount(context, sessionId, repair, signal) {
487
+ const guard = countGuard(repair.observed);
488
+ await (0, retry_1.withDynamoDBRetry)((request) => context.client.update({
489
+ TableName: context.tableName,
490
+ Key: (0, rows_1.sessionRowKey)(sessionId),
491
+ UpdateExpression: 'SET #count = :count',
492
+ ExpressionAttributeNames: { '#count': 'messageCount' },
493
+ ExpressionAttributeValues: { ':count': repair.count, ...guard.ExpressionAttributeValues },
494
+ ConditionExpression: guard.ConditionExpression,
495
+ }, request), (0, retry_1.retryFor)(context, signal));
496
+ }
497
+ /**
498
+ * Recompute `messageCount` from the stored message rows and write it, pinned to
499
+ * the count the row held when the recount began.
500
+ *
501
+ * Accepts: `sessionId` — parsed. `signal` — cancels the reads, the recount and
502
+ * the write.
503
+ *
504
+ * Returns: the count written.
505
+ *
506
+ * Throws: `CONDITION_CONFLICT` when the session does not exist, or when it
507
+ * changed during every one of the compare-and-swap's attempts;
508
+ * `FORMAT_UNSUPPORTED` for a message row a newer release wrote; `VALIDATION`
509
+ * naming `message` for a row in the message key space this package did not
510
+ * write; whatever the reads and the write throw.
511
+ */
512
+ async function repairMessageCount(context, sessionId, signal) {
513
+ for (let attempt = 1; attempt <= idempotent_write_1.OVERWRITE_CAS_MAX_ATTEMPTS; attempt++) {
514
+ const observed = await observeCount(context, sessionId, signal);
515
+ if (!observed.exists) {
516
+ throw (0, errors_1.conflictError)(`Cannot reconcile messageCount: session "${sessionId}" does not exist`);
517
+ }
518
+ const count = await (0, message_read_1.countLiveMessages)(context, sessionId, signal);
519
+ try {
520
+ await writeCount(context, sessionId, { count, observed }, signal);
521
+ return count;
522
+ }
523
+ catch (error) {
524
+ if ((0, classify_1.classifyAwsError)(error) !== error_code_1.ErrorCode.CONDITION_CONFLICT)
525
+ throw error;
526
+ }
527
+ }
528
+ throw (0, errors_1.conflictError)(`Cannot reconcile messageCount: session "${sessionId}" changed during every one of ` +
529
+ `${idempotent_write_1.OVERWRITE_CAS_MAX_ATTEMPTS} attempts; retry when it is quieter`);
530
+ }
531
+ /**
532
+ * Whether a row's `ttl` is an instant a session listing can both judge and
533
+ * render.
534
+ *
535
+ * Neither of the two things done with it refuses a value it cannot use.
536
+ * {@link isExpiredRow} compares it against the clock, and a non-number compares
537
+ * `false` against every clock, so an unreadable ttl reads as *live* rather than
538
+ * being filtered out. `expiresAt` then renders it, and `NaN`, `Infinity` and
539
+ * anything past the ±8.64e12 seconds a `Date` spans are all numbers whose
540
+ * `toISOString` throws `RangeError` — which failed the whole listing.
541
+ */
542
+ function hasReadableTtl(ttl) {
543
+ if (ttl === undefined)
544
+ return true;
545
+ return typeof ttl === 'number' && Number.isFinite(new Date(ttl * 1000).getTime());
546
+ }
547
+ /**
548
+ * Whether every attribute {@link summariseSession} hands back is the type this package
549
+ * writes there.
550
+ *
551
+ * The identity test {@link summariseSession} makes first proves a row is a
552
+ * session row; this proves its own attributes are usable. They are returned
553
+ * under declared types, so a row that disagrees answers the caller with a lie
554
+ * — `messageCount: 'many'` handed back as a number — or, for the ttl, with a
555
+ * `RangeError`. A row this release cannot speak for is dropped the way a
556
+ * foreign row is, never at the cost of the rest of the page.
557
+ */
558
+ function isSummarisable(raw) {
559
+ return (typeof raw.messageCount === 'number' &&
560
+ typeof raw.createdAt === 'string' &&
561
+ typeof raw.updatedAt === 'string' &&
562
+ (raw.title === undefined || typeof raw.title === 'string') &&
563
+ hasReadableTtl(raw.ttl));
564
+ }
565
+ /**
566
+ * The public summary of a SESSION row: the session a row describes, or
567
+ * undefined for a foreign, malformed or expired row.
568
+ *
569
+ * The `sessionId` is bound to the partition the row was found in, as
570
+ * `parseMetaRow`, `parseStoreRow` and `parseMessageRow` bind theirs.
571
+ * Both reads that reach here select rows by something other than the partition
572
+ * — a table scan filtered on the sort key, and a recency-index query — so
573
+ * without the binding a row planted anywhere in the table under this adapter's
574
+ * SESSION sort key was summarised under whatever `sessionId` it claimed, and a
575
+ * caller taking that id to `getMessages` read a partition the row never lived
576
+ * in.
577
+ *
578
+ * Accepts: `raw` — a row a listing read. `atSeconds` — the listing's clock,
579
+ * against which an expired row is absent.
580
+ *
581
+ * Returns: the summary, or `undefined` for a foreign, malformed or expired row.
582
+ *
583
+ * Throws: `FORMAT_UNSUPPORTED` for a row a newer release wrote. It is not a
584
+ * foreign row to skip, and summarising it under this release's rules could
585
+ * return its attributes with a meaning they no longer have. Checked before the
586
+ * shape, the binding and the ttl — as every other read of this package's rows
587
+ * checks it — so a newer row is refused rather than judged against attribute
588
+ * names it may no longer use, and the answer does not depend on the reading
589
+ * machine's clock.
590
+ */
591
+ function summariseSession(raw, atSeconds) {
592
+ const item = raw;
593
+ (0, table_schema_1.assertReadableRow)(item, 'session');
594
+ if (item.SK !== rows_1.SESSION_SORT_KEY || typeof item.sessionId !== 'string')
595
+ return undefined;
596
+ if (item.PK !== (0, rows_1.sessionPartition)(item.sessionId))
597
+ return undefined;
598
+ if (!isSummarisable(raw) || (0, table_schema_1.isExpiredRow)(item, atSeconds))
599
+ return undefined;
600
+ return {
601
+ sessionId: item.sessionId,
602
+ title: item.title,
603
+ messageCount: item.messageCount,
604
+ createdAt: item.createdAt,
605
+ updatedAt: item.updatedAt,
606
+ expiresAt: item.ttl === undefined ? undefined : new Date(item.ttl * 1000).toISOString(),
607
+ };
608
+ }
609
+ /**
610
+ * Where a history row sits in the recency index, for a row written before the
611
+ * index existed.
612
+ *
613
+ * Accepts: `row` — any row of the table.
614
+ *
615
+ * Returns: a SESSION row's identity — its session id, at its own `updatedAt` —
616
+ * or `undefined` for a message row or a row of another adapter.
617
+ *
618
+ * Throws: nothing.
619
+ */
620
+ function sessionIndexTarget(row) {
621
+ const pk = typeof row.PK === 'string' ? row.PK : '';
622
+ const sk = typeof row.SK === 'string' ? row.SK : '';
623
+ if (!pk.startsWith((0, rows_1.historyPartitionPrefix)()))
624
+ return undefined;
625
+ return sk.endsWith('SESSION') && typeof row.sessionId === 'string'
626
+ ? { tag: 'SESS', id: row.sessionId, at: (0, recency_index_1.backfilledAt)(row.updatedAt) }
627
+ : undefined;
628
+ }