@farukada/aws-langgraph-dynamodb-ts 0.7.0 → 0.9.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 (208) hide show
  1. package/README.md +52 -9
  2. package/dist/checkpointer/actions/delete-thread.d.ts +3 -4
  3. package/dist/checkpointer/actions/delete-thread.d.ts.map +1 -1
  4. package/dist/checkpointer/actions/delete-thread.js +21 -41
  5. package/dist/checkpointer/actions/delete-thread.js.map +1 -1
  6. package/dist/checkpointer/actions/list.d.ts.map +1 -1
  7. package/dist/checkpointer/actions/list.js +38 -4
  8. package/dist/checkpointer/actions/list.js.map +1 -1
  9. package/dist/checkpointer/actions/put-writes.d.ts.map +1 -1
  10. package/dist/checkpointer/actions/put-writes.js +15 -24
  11. package/dist/checkpointer/actions/put-writes.js.map +1 -1
  12. package/dist/checkpointer/internal/item-reader.d.ts +45 -0
  13. package/dist/checkpointer/internal/item-reader.d.ts.map +1 -1
  14. package/dist/checkpointer/internal/item-reader.js +63 -1
  15. package/dist/checkpointer/internal/item-reader.js.map +1 -1
  16. package/dist/checkpointer/internal/item-writer.d.ts +0 -8
  17. package/dist/checkpointer/internal/item-writer.d.ts.map +1 -1
  18. package/dist/checkpointer/internal/item-writer.js +16 -21
  19. package/dist/checkpointer/internal/item-writer.js.map +1 -1
  20. package/dist/checkpointer/internal/keys.d.ts +19 -3
  21. package/dist/checkpointer/internal/keys.d.ts.map +1 -1
  22. package/dist/checkpointer/internal/keys.js +41 -6
  23. package/dist/checkpointer/internal/keys.js.map +1 -1
  24. package/dist/checkpointer/internal/special-write-cas.d.ts +30 -0
  25. package/dist/checkpointer/internal/special-write-cas.d.ts.map +1 -0
  26. package/dist/checkpointer/internal/special-write-cas.js +104 -0
  27. package/dist/checkpointer/internal/special-write-cas.js.map +1 -0
  28. package/dist/checkpointer/internal/special-write-cleanup.d.ts +18 -12
  29. package/dist/checkpointer/internal/special-write-cleanup.d.ts.map +1 -1
  30. package/dist/checkpointer/internal/special-write-cleanup.js +31 -86
  31. package/dist/checkpointer/internal/special-write-cleanup.js.map +1 -1
  32. package/dist/checkpointer/internal/special-write-verify.d.ts +54 -0
  33. package/dist/checkpointer/internal/special-write-verify.d.ts.map +1 -0
  34. package/dist/checkpointer/internal/special-write-verify.js +65 -0
  35. package/dist/checkpointer/internal/special-write-verify.js.map +1 -0
  36. package/dist/checkpointer/internal/validation.d.ts +8 -4
  37. package/dist/checkpointer/internal/validation.d.ts.map +1 -1
  38. package/dist/checkpointer/internal/validation.js +12 -10
  39. package/dist/checkpointer/internal/validation.js.map +1 -1
  40. package/dist/checkpointer/internal/write-guard.d.ts +13 -0
  41. package/dist/checkpointer/internal/write-guard.d.ts.map +1 -0
  42. package/dist/checkpointer/internal/write-guard.js +39 -0
  43. package/dist/checkpointer/internal/write-guard.js.map +1 -0
  44. package/dist/checkpointer/internal/write-index.d.ts +37 -0
  45. package/dist/checkpointer/internal/write-index.d.ts.map +1 -0
  46. package/dist/checkpointer/internal/write-index.js +42 -0
  47. package/dist/checkpointer/internal/write-index.js.map +1 -0
  48. package/dist/checkpointer/types.d.ts +8 -0
  49. package/dist/checkpointer/types.d.ts.map +1 -1
  50. package/dist/history/actions/add-messages.d.ts.map +1 -1
  51. package/dist/history/actions/add-messages.js +24 -4
  52. package/dist/history/actions/add-messages.js.map +1 -1
  53. package/dist/history/actions/clear.d.ts +4 -6
  54. package/dist/history/actions/clear.d.ts.map +1 -1
  55. package/dist/history/actions/clear.js +18 -33
  56. package/dist/history/actions/clear.js.map +1 -1
  57. package/dist/history/actions/get-messages.d.ts +2 -1
  58. package/dist/history/actions/get-messages.d.ts.map +1 -1
  59. package/dist/history/actions/get-messages.js +28 -2
  60. package/dist/history/actions/get-messages.js.map +1 -1
  61. package/dist/history/actions/list-sessions.d.ts +2 -1
  62. package/dist/history/actions/list-sessions.d.ts.map +1 -1
  63. package/dist/history/actions/list-sessions.js +21 -2
  64. package/dist/history/actions/list-sessions.js.map +1 -1
  65. package/dist/history/actions/reconcile-count.d.ts.map +1 -1
  66. package/dist/history/actions/reconcile-count.js +2 -2
  67. package/dist/history/actions/reconcile-count.js.map +1 -1
  68. package/dist/history/internal/append-saga.d.ts.map +1 -1
  69. package/dist/history/internal/append-saga.js +2 -78
  70. package/dist/history/internal/append-saga.js.map +1 -1
  71. package/dist/history/internal/compensation.d.ts +21 -0
  72. package/dist/history/internal/compensation.d.ts.map +1 -0
  73. package/dist/history/internal/compensation.js +84 -0
  74. package/dist/history/internal/compensation.js.map +1 -0
  75. package/dist/history/internal/keys.d.ts +9 -1
  76. package/dist/history/internal/keys.d.ts.map +1 -1
  77. package/dist/history/internal/keys.js +21 -4
  78. package/dist/history/internal/keys.js.map +1 -1
  79. package/dist/history/internal/message-chunker.d.ts.map +1 -1
  80. package/dist/history/internal/message-chunker.js +13 -5
  81. package/dist/history/internal/message-chunker.js.map +1 -1
  82. package/dist/history/internal/session-count.d.ts +21 -0
  83. package/dist/history/internal/session-count.d.ts.map +1 -1
  84. package/dist/history/internal/session-count.js +51 -0
  85. package/dist/history/internal/session-count.js.map +1 -1
  86. package/dist/history/internal/session-title.d.ts +20 -0
  87. package/dist/history/internal/session-title.d.ts.map +1 -0
  88. package/dist/history/internal/session-title.js +44 -0
  89. package/dist/history/internal/session-title.js.map +1 -0
  90. package/dist/history/internal/setup.d.ts +2 -1
  91. package/dist/history/internal/setup.d.ts.map +1 -1
  92. package/dist/history/internal/setup.js +1 -0
  93. package/dist/history/internal/setup.js.map +1 -1
  94. package/dist/history/internal/title-generator.d.ts +8 -2
  95. package/dist/history/internal/title-generator.d.ts.map +1 -1
  96. package/dist/history/internal/title-generator.js +12 -3
  97. package/dist/history/internal/title-generator.js.map +1 -1
  98. package/dist/history/internal/validation.d.ts +9 -0
  99. package/dist/history/internal/validation.d.ts.map +1 -0
  100. package/dist/history/internal/validation.js +16 -0
  101. package/dist/history/internal/validation.js.map +1 -0
  102. package/dist/history/types.d.ts +12 -0
  103. package/dist/history/types.d.ts.map +1 -1
  104. package/dist/index.d.ts +1 -1
  105. package/dist/index.d.ts.map +1 -1
  106. package/dist/index.js.map +1 -1
  107. package/dist/shared/codec/s3/offloader.d.ts +7 -1
  108. package/dist/shared/codec/s3/offloader.d.ts.map +1 -1
  109. package/dist/shared/codec/s3/offloader.js +14 -1
  110. package/dist/shared/codec/s3/offloader.js.map +1 -1
  111. package/dist/shared/constants.d.ts +15 -0
  112. package/dist/shared/constants.d.ts.map +1 -1
  113. package/dist/shared/constants.js +16 -1
  114. package/dist/shared/constants.js.map +1 -1
  115. package/dist/shared/dynamodb/conditional-put.d.ts +51 -0
  116. package/dist/shared/dynamodb/conditional-put.d.ts.map +1 -0
  117. package/dist/shared/dynamodb/conditional-put.js +59 -0
  118. package/dist/shared/dynamodb/conditional-put.js.map +1 -0
  119. package/dist/shared/dynamodb/partition-delete.d.ts +33 -0
  120. package/dist/shared/dynamodb/partition-delete.d.ts.map +1 -0
  121. package/dist/shared/dynamodb/partition-delete.js +70 -0
  122. package/dist/shared/dynamodb/partition-delete.js.map +1 -0
  123. package/dist/shared/dynamodb/retry-classifier.d.ts.map +1 -1
  124. package/dist/shared/dynamodb/retry-classifier.js +9 -1
  125. package/dist/shared/dynamodb/retry-classifier.js.map +1 -1
  126. package/dist/shared/logging/redaction-walk.d.ts +23 -0
  127. package/dist/shared/logging/redaction-walk.d.ts.map +1 -0
  128. package/dist/shared/logging/redaction-walk.js +92 -0
  129. package/dist/shared/logging/redaction-walk.js.map +1 -0
  130. package/dist/shared/logging/redaction.d.ts +26 -17
  131. package/dist/shared/logging/redaction.d.ts.map +1 -1
  132. package/dist/shared/logging/redaction.js +24 -48
  133. package/dist/shared/logging/redaction.js.map +1 -1
  134. package/dist/shared/logging/secret-patterns.d.ts +74 -0
  135. package/dist/shared/logging/secret-patterns.d.ts.map +1 -0
  136. package/dist/shared/logging/secret-patterns.js +107 -0
  137. package/dist/shared/logging/secret-patterns.js.map +1 -0
  138. package/dist/shared/validation/primitives.d.ts +9 -1
  139. package/dist/shared/validation/primitives.d.ts.map +1 -1
  140. package/dist/shared/validation/primitives.js +13 -0
  141. package/dist/shared/validation/primitives.js.map +1 -1
  142. package/dist/store/actions/get.d.ts.map +1 -1
  143. package/dist/store/actions/get.js +15 -1
  144. package/dist/store/actions/get.js.map +1 -1
  145. package/dist/store/actions/list-namespaces.d.ts.map +1 -1
  146. package/dist/store/actions/list-namespaces.js +1 -0
  147. package/dist/store/actions/list-namespaces.js.map +1 -1
  148. package/dist/store/actions/put.d.ts.map +1 -1
  149. package/dist/store/actions/put.js +24 -18
  150. package/dist/store/actions/put.js.map +1 -1
  151. package/dist/store/actions/search.d.ts.map +1 -1
  152. package/dist/store/actions/search.js +4 -35
  153. package/dist/store/actions/search.js.map +1 -1
  154. package/dist/store/internal/backend-search.d.ts +5 -0
  155. package/dist/store/internal/backend-search.d.ts.map +1 -0
  156. package/dist/store/internal/backend-search.js +68 -0
  157. package/dist/store/internal/backend-search.js.map +1 -0
  158. package/dist/store/internal/filter.d.ts.map +1 -1
  159. package/dist/store/internal/filter.js +46 -5
  160. package/dist/store/internal/filter.js.map +1 -1
  161. package/dist/store/internal/index-reconcile.d.ts.map +1 -1
  162. package/dist/store/internal/index-reconcile.js +41 -5
  163. package/dist/store/internal/index-reconcile.js.map +1 -1
  164. package/dist/store/internal/item-mapper.d.ts +1 -0
  165. package/dist/store/internal/item-mapper.d.ts.map +1 -1
  166. package/dist/store/internal/item-mapper.js +2 -0
  167. package/dist/store/internal/item-mapper.js.map +1 -1
  168. package/dist/store/internal/keys.d.ts +1 -1
  169. package/dist/store/internal/keys.d.ts.map +1 -1
  170. package/dist/store/internal/keys.js +8 -2
  171. package/dist/store/internal/keys.js.map +1 -1
  172. package/dist/store/internal/overwrite-swap.d.ts +33 -0
  173. package/dist/store/internal/overwrite-swap.d.ts.map +1 -0
  174. package/dist/store/internal/overwrite-swap.js +62 -0
  175. package/dist/store/internal/overwrite-swap.js.map +1 -0
  176. package/dist/store/internal/persist.d.ts +22 -6
  177. package/dist/store/internal/persist.d.ts.map +1 -1
  178. package/dist/store/internal/persist.js +41 -20
  179. package/dist/store/internal/persist.js.map +1 -1
  180. package/dist/store/internal/read-existing.d.ts +19 -0
  181. package/dist/store/internal/read-existing.d.ts.map +1 -0
  182. package/dist/store/internal/read-existing.js +29 -0
  183. package/dist/store/internal/read-existing.js.map +1 -0
  184. package/dist/store/internal/score-direction.d.ts +32 -0
  185. package/dist/store/internal/score-direction.d.ts.map +1 -0
  186. package/dist/store/internal/score-direction.js +39 -0
  187. package/dist/store/internal/score-direction.js.map +1 -0
  188. package/dist/store/internal/search-filter.d.ts +4 -0
  189. package/dist/store/internal/search-filter.d.ts.map +1 -0
  190. package/dist/store/internal/search-filter.js +11 -0
  191. package/dist/store/internal/search-filter.js.map +1 -0
  192. package/dist/store/internal/setup.d.ts +13 -1
  193. package/dist/store/internal/setup.d.ts.map +1 -1
  194. package/dist/store/internal/setup.js +57 -1
  195. package/dist/store/internal/setup.js.map +1 -1
  196. package/dist/store/internal/validation.d.ts +8 -2
  197. package/dist/store/internal/validation.d.ts.map +1 -1
  198. package/dist/store/internal/validation.js +15 -6
  199. package/dist/store/internal/validation.js.map +1 -1
  200. package/dist/store/internal/write-verify.d.ts +31 -6
  201. package/dist/store/internal/write-verify.d.ts.map +1 -1
  202. package/dist/store/internal/write-verify.js +48 -8
  203. package/dist/store/internal/write-verify.js.map +1 -1
  204. package/dist/store/types.d.ts +16 -0
  205. package/dist/store/types.d.ts.map +1 -1
  206. package/dist/store/vector-backend.d.ts +17 -0
  207. package/dist/store/vector-backend.d.ts.map +1 -1
  208. package/package.json +1 -1
@@ -3,13 +3,24 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.listSessions = listSessions;
4
4
  const scan_1 = require("../../shared/dynamodb/scan");
5
5
  const keys_1 = require("../internal/keys");
6
+ /**
7
+ * True when a session row is past its TTL. DynamoDB's background sweep can lag
8
+ * up to 48h, so an expired session would otherwise keep appearing in listings
9
+ * after `getMessages` had already stopped returning its messages — the same
10
+ * filter that read path applies.
11
+ */
12
+ function isExpired(item, nowSeconds) {
13
+ return item.ttl !== undefined && item.ttl <= nowSeconds;
14
+ }
6
15
  /**
7
16
  * List all sessions as metadata summaries, newest-updated first. The scan is
8
17
  * filtered to session items so the adapter works on a table shared with the
9
- * checkpointer/store; foreign rows are also skipped defensively.
18
+ * checkpointer/store; foreign rows are also skipped defensively, and rows past
19
+ * their TTL are filtered out exactly as `getMessages` filters expired messages.
10
20
  */
11
21
  async function listSessions(context, options) {
12
22
  const sessions = [];
23
+ const nowSeconds = Math.floor(Date.now() / 1000);
13
24
  for await (const raw of (0, scan_1.paginateScan)({
14
25
  client: context.client,
15
26
  params: {
@@ -24,6 +35,8 @@ async function listSessions(context, options) {
24
35
  const item = raw;
25
36
  if (item.SK !== keys_1.SESSION_SORT_KEY || typeof item.sessionId !== 'string')
26
37
  continue;
38
+ if (isExpired(item, nowSeconds))
39
+ continue;
27
40
  sessions.push({
28
41
  sessionId: item.sessionId,
29
42
  title: item.title,
@@ -32,7 +45,13 @@ async function listSessions(context, options) {
32
45
  updatedAt: item.updatedAt,
33
46
  });
34
47
  }
35
- sessions.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
48
+ /**
49
+ * Ordinal, not `localeCompare`: these are ISO-8601 timestamps, whose
50
+ * byte order already is their chronological order. Locale-aware collation
51
+ * applies rules (case folding, punctuation weighting) that have no meaning
52
+ * here and are not guaranteed to agree with it in every locale.
53
+ */
54
+ sessions.sort((a, b) => (a.updatedAt < b.updatedAt ? 1 : a.updatedAt > b.updatedAt ? -1 : 0));
36
55
  return sessions;
37
56
  }
38
57
  //# sourceMappingURL=list-sessions.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"list-sessions.js","sourceRoot":"","sources":["../../../src/history/actions/list-sessions.ts"],"names":[],"mappings":";;;AAAA,qDAA0D;AAC1D,2CAAoD;AAIpD;;;;GAIG;AACI,KAAK,uBACV,OAAuB,EACvB,OAAuD;IAEvD,MAAM,QAAQ,GAAsB,EAAE,CAAC;IACvC,IAAI,KAAK,EAAE,MAAM,GAAG,IAAI,IAAA,mBAAY,EAAC;QACnC,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM,EAAE;YACN,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,gBAAgB,EAAE,gBAAgB;YAClC,wBAAwB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE;YACzC,yBAAyB,EAAE,EAAE,UAAU,EAAE,uBAAgB,EAAE;SAC5D;QACD,aAAa,EAAE,OAAO,EAAE,aAAa;QACrC,QAAQ,EAAE,OAAO,EAAE,QAAQ;KAC5B,CAAC,EAAE,CAAC;QACH,MAAM,IAAI,GAAG,GAAsB,CAAC;QACpC,IAAI,IAAI,CAAC,EAAE,KAAK,uBAAgB,IAAI,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ;YAAE,SAAS;QACjF,QAAQ,CAAC,IAAI,CAAC;YACZ,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC,CAAC;IACL,CAAC;IACD,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAChE,OAAO,QAAQ,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"list-sessions.js","sourceRoot":"","sources":["../../../src/history/actions/list-sessions.ts"],"names":[],"mappings":";;;AAAA,qDAA0D;AAC1D,2CAAoD;AAIpD;;;;;GAKG;AACH,SAAS,SAAS,CAAC,IAAqB,EAAE,UAAkB;IAC1D,OAAO,IAAI,CAAC,GAAG,KAAK,SAAS,IAAI,IAAI,CAAC,GAAG,IAAI,UAAU,CAAC;AAC1D,CAAC;AAED;;;;;GAKG;AACI,KAAK,uBACV,OAAuB,EACvB,OAAuD;IAEvD,MAAM,QAAQ,GAAsB,EAAE,CAAC;IACvC,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IACjD,IAAI,KAAK,EAAE,MAAM,GAAG,IAAI,IAAA,mBAAY,EAAC;QACnC,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM,EAAE;YACN,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,gBAAgB,EAAE,gBAAgB;YAClC,wBAAwB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE;YACzC,yBAAyB,EAAE,EAAE,UAAU,EAAE,uBAAgB,EAAE;SAC5D;QACD,aAAa,EAAE,OAAO,EAAE,aAAa;QACrC,QAAQ,EAAE,OAAO,EAAE,QAAQ;KAC5B,CAAC,EAAE,CAAC;QACH,MAAM,IAAI,GAAG,GAAsB,CAAC;QACpC,IAAI,IAAI,CAAC,EAAE,KAAK,uBAAgB,IAAI,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ;YAAE,SAAS;QACjF,IAAI,SAAS,CAAC,IAAI,EAAE,UAAU,CAAC;YAAE,SAAS;QAC1C,QAAQ,CAAC,IAAI,CAAC;YACZ,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC,CAAC;IACL,CAAC;IACD;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9F,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"reconcile-count.d.ts","sourceRoot":"","sources":["../../../src/history/actions/reconcile-count.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAgBxD;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,CAwBjB"}
1
+ {"version":3,"file":"reconcile-count.d.ts","sourceRoot":"","sources":["../../../src/history/actions/reconcile-count.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAiBxD;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,CAwBjB"}
@@ -3,9 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.reconcileMessageCount = reconcileMessageCount;
4
4
  const retry_1 = require("../../shared/dynamodb/retry");
5
5
  const errors_1 = require("../../shared/errors/errors");
6
- const primitives_1 = require("../../shared/validation/primitives");
7
6
  const keys_1 = require("../internal/keys");
8
7
  const query_1 = require("../internal/query");
8
+ const validation_1 = require("../internal/validation");
9
9
  async function countMessages(context, sessionId) {
10
10
  const base = (0, query_1.messageQuery)(context.tableName, sessionId);
11
11
  let total = 0;
@@ -28,7 +28,7 @@ async function countMessages(context, sessionId) {
28
28
  * reconciled count.
29
29
  */
30
30
  async function reconcileMessageCount(context, sessionId) {
31
- (0, primitives_1.validateNonEmptyString)(sessionId, 'sessionId');
31
+ (0, validation_1.validateSessionId)(sessionId);
32
32
  const count = await countMessages(context, sessionId);
33
33
  try {
34
34
  await (0, retry_1.withDynamoDBRetry)(() => context.client.update({
@@ -1 +1 @@
1
- {"version":3,"file":"reconcile-count.js","sourceRoot":"","sources":["../../../src/history/actions/reconcile-count.ts"],"names":[],"mappings":";;;AAEA,uDAAgE;AAChE,uDAA2D;AAC3D,mEAA4E;AAC5E,2CAAsE;AACtE,6CAAiD;AAGjD,KAAK,UAAU,aAAa,CAAC,OAAuB,EAAE,SAAiB;IACrE,MAAM,IAAI,GAAG,IAAA,oBAAY,EAAC,OAAO,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IACxD,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,QAA0D,CAAC;IAC/D,GAAG,CAAC;QACF,MAAM,IAAI,GAAG,MAAM,IAAA,yBAAiB,EAAC,GAAG,EAAE,CACxC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,CAAC,CAChF,CAAC;QACF,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;QACzB,QAAQ,GAAG,IAAI,CAAC,gBAAgB,CAAC;IACnC,CAAC,QAAQ,QAAQ,EAAE;IACnB,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;GASG;AACI,KAAK,gCACV,OAAuB,EACvB,SAAiB;IAEjB,IAAA,mCAAsB,EAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IAC/C,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACtD,IAAI,CAAC;QACH,MAAM,IAAA,yBAAiB,EAAC,GAAG,EAAE,CAC3B,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC;YACpB,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,GAAG,EAAE,EAAE,EAAE,EAAE,IAAA,uBAAgB,EAAC,SAAS,CAAC,EAAE,EAAE,EAAE,uBAAgB,EAAE;YAC9D,gBAAgB,EAAE,qBAAqB;YACvC,wBAAwB,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE;YACtD,yBAAyB,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;YAC9C,mBAAmB,EAAE,sBAAsB;SAC5C,CAAC,CACH,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA2B,CAAC,IAAI,KAAK,iCAAiC,EAAE,CAAC;YAC5E,MAAM,IAAI,sBAAa,CACrB,2CAA2C,SAAS,kBAAkB,EACtE,KAAc,CACf,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"reconcile-count.js","sourceRoot":"","sources":["../../../src/history/actions/reconcile-count.ts"],"names":[],"mappings":";;;AAEA,uDAAgE;AAChE,uDAA2D;AAC3D,2CAAsE;AACtE,6CAAiD;AAEjD,uDAA2D;AAE3D,KAAK,UAAU,aAAa,CAAC,OAAuB,EAAE,SAAiB;IACrE,MAAM,IAAI,GAAG,IAAA,oBAAY,EAAC,OAAO,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IACxD,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,QAA0D,CAAC;IAC/D,GAAG,CAAC;QACF,MAAM,IAAI,GAAG,MAAM,IAAA,yBAAiB,EAAC,GAAG,EAAE,CACxC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,CAAC,CAChF,CAAC;QACF,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;QACzB,QAAQ,GAAG,IAAI,CAAC,gBAAgB,CAAC;IACnC,CAAC,QAAQ,QAAQ,EAAE;IACnB,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;GASG;AACI,KAAK,gCACV,OAAuB,EACvB,SAAiB;IAEjB,IAAA,8BAAiB,EAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACtD,IAAI,CAAC;QACH,MAAM,IAAA,yBAAiB,EAAC,GAAG,EAAE,CAC3B,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC;YACpB,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,GAAG,EAAE,EAAE,EAAE,EAAE,IAAA,uBAAgB,EAAC,SAAS,CAAC,EAAE,EAAE,EAAE,uBAAgB,EAAE;YAC9D,gBAAgB,EAAE,qBAAqB;YACvC,wBAAwB,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE;YACtD,yBAAyB,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;YAC9C,mBAAmB,EAAE,sBAAsB;SAC5C,CAAC,CACH,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA2B,CAAC,IAAI,KAAK,iCAAiC,EAAE,CAAC;YAC5E,MAAM,IAAI,sBAAa,CACrB,2CAA2C,SAAS,kBAAkB,EACtE,KAAc,CACf,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"append-saga.d.ts","sourceRoot":"","sources":["../../../src/history/internal/append-saga.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAGhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,0EAA0E;AAC1E,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAmGD;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,eAAe,EAAE,EAAE,EAC3B,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,IAAI,CAAC,CAiBf"}
1
+ {"version":3,"file":"append-saga.d.ts","sourceRoot":"","sources":["../../../src/history/internal/append-saga.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAGhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,0EAA0E;AAC1E,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,eAAe,EAAE,EAAE,EAC3B,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,IAAI,CAAC,CAyBf"}
@@ -1,85 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.appendChunks = appendChunks;
4
- const descriptor_keys_1 = require("../../shared/codec/descriptor-keys");
5
- const orphans_1 = require("../../shared/codec/s3/orphans");
6
- const batch_write_1 = require("../../shared/dynamodb/batch-write");
7
- const errors_1 = require("../../shared/errors/errors");
8
4
  const wrap_error_1 = require("../../shared/errors/wrap-error");
5
+ const compensation_1 = require("./compensation");
9
6
  const message_transaction_1 = require("./message-transaction");
10
- const session_count_1 = require("./session-count");
11
- /**
12
- * Best-effort delete the offloaded S3 objects of the `chunks` slice given.
13
- * {@link compensate} calls it once per commit status, never for the whole
14
- * batch, so a committed chunk's objects arrive only once its rows are gone.
15
- */
16
- async function cleanBatchS3(context, chunks) {
17
- if (!context.offloader)
18
- return;
19
- const descriptors = chunks.flat().map((item) => item.message);
20
- await (0, orphans_1.cleanUpS3Orphans)(context.offloader, (0, descriptor_keys_1.collectS3Keys)(descriptors), 'history.addMessages', context.logger);
21
- }
22
- /**
23
- * Delete every committed chunk's items, then revert the counted total by
24
- * however many rows the delete actually persisted before failing (not
25
- * necessarily every committed row — see the catch branch below).
26
- */
27
- async function rollbackCommitted(context, sessionId, committed) {
28
- const keys = committed.flatMap((chunk) => chunk.keys);
29
- const total = committed.reduce((sum, chunk) => sum + chunk.count, 0);
30
- if (keys.length === 0) {
31
- await (0, session_count_1.revertSessionCount)(context, sessionId, total);
32
- return;
33
- }
34
- try {
35
- await (0, batch_write_1.batchWriteAll)(context.client, context.tableName, keys.map((Key) => ({ DeleteRequest: { Key } })));
36
- }
37
- catch (error) {
38
- /**
39
- * batchWriteAll has exactly one throw site and it is always a
40
- * BatchWriteAllIncompleteError (see Task 9) — asserted, not instanceof-
41
- * checked, since the false case is unreachable and this project
42
- * enforces 100% branch coverage with no exceptions.
43
- */
44
- const deleted = error.succeededCount;
45
- await (0, session_count_1.revertSessionCount)(context, sessionId, deleted);
46
- throw error;
47
- }
48
- await (0, session_count_1.revertSessionCount)(context, sessionId, total);
49
- }
50
- /**
51
- * Undo a failed batch. Always throws. S3 cleanup is split by commit status so
52
- * no live row is ever left pointing at a deleted object: the never-committed
53
- * suffix is cleaned immediately, the committed prefix only after its rows are
54
- * confirmed deleted. If the rollback itself fails, the committed chunks' S3
55
- * objects are deliberately left in place (their rows may survive) and it
56
- * raises {@link CompensationFailedError} carrying both the trigger and the
57
- * rollback error; otherwise it rethrows the trigger.
58
- */
59
- async function compensate(context, sessionId, chunks, committed, trigger) {
60
- if (committed.length > 0) {
61
- context.logger.warn('history.addMessages compensating committed chunks after a chunk failed', {
62
- sessionId,
63
- committedChunks: committed.length,
64
- });
65
- }
66
- /** The failed/never-attempted suffix never had a DynamoDB row, so it's safe to clean now. */
67
- await cleanBatchS3(context, chunks.slice(committed.length));
68
- try {
69
- await rollbackCommitted(context, sessionId, committed);
70
- }
71
- catch (rollbackError) {
72
- context.logger.error('history.addMessages rollback failed; messageCount may have drifted', {
73
- sessionId,
74
- committedChunks: committed.length,
75
- });
76
- /** Skip S3 cleanup here: rollback may have failed, so committed rows might still reference these objects. */
77
- throw new errors_1.CompensationFailedError(trigger, (0, wrap_error_1.toError)(rollbackError));
78
- }
79
- /** Only now that committed rows are confirmed deleted is it safe to delete their S3 objects. */
80
- await cleanBatchS3(context, chunks.slice(0, committed.length));
81
- throw trigger;
82
- }
83
7
  /**
84
8
  * Append message chunks with caller-observed atomicity. Each chunk commits its
85
9
  * messages and count in one transaction; if a later chunk fails, every
@@ -104,7 +28,7 @@ async function appendChunks(context, sessionId, chunks, fields) {
104
28
  });
105
29
  }
106
30
  catch (error) {
107
- await compensate(context, sessionId, chunks, committed, (0, wrap_error_1.toError)(error));
31
+ await (0, compensation_1.compensate)(context, sessionId, chunks, committed, (0, wrap_error_1.toError)(error), fields.now, fields.title);
108
32
  }
109
33
  }
110
34
  }
@@ -1 +1 @@
1
- {"version":3,"file":"append-saga.js","sourceRoot":"","sources":["../../../src/history/internal/append-saga.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"append-saga.js","sourceRoot":"","sources":["../../../src/history/internal/append-saga.ts"],"names":[],"mappings":";;;AAAA,+DAAyD;AAEzD,iDAAiE;AACjE,+DAA0D;AAW1D;;;;;;;;GAQG;AACI,KAAK,uBACV,OAAuB,EACvB,SAAiB,EACjB,MAA2B,EAC3B,MAAoB;IAEpB,MAAM,SAAS,GAAqB,EAAE,CAAC;IACvC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,IAAA,uCAAiB,EAAC,OAAO,EAAE,KAAK,EAAE;gBACtC,GAAG,MAAM;gBACT,SAAS;gBACT,KAAK,EAAE,KAAK,CAAC,MAAM;aACpB,CAAC,CAAC;YACH,SAAS,CAAC,IAAI,CAAC;gBACb,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;gBACzD,KAAK,EAAE,KAAK,CAAC,MAAM;aACpB,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAA,yBAAU,EACd,OAAO,EACP,SAAS,EACT,MAAM,EACN,SAAS,EACT,IAAA,oBAAO,EAAC,KAAc,CAAC,EACvB,MAAM,CAAC,GAAG,EACV,MAAM,CAAC,KAAK,CACb,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { ChatMessageItem } from '../types';
2
+ import type { HistoryContext } from './setup';
3
+ /** A chunk that committed, retained so it can be rolled back on a later failure. */
4
+ export interface CommittedChunk {
5
+ keys: {
6
+ PK: string;
7
+ SK: string;
8
+ }[];
9
+ count: number;
10
+ }
11
+ /**
12
+ * Undo a failed batch. Always throws. S3 cleanup is split by commit status so
13
+ * no live row is ever left pointing at a deleted object: the never-committed
14
+ * suffix is cleaned immediately, the committed prefix only after its rows are
15
+ * confirmed deleted. If the rollback itself fails, the committed chunks' S3
16
+ * objects are deliberately left in place (their rows may survive) and it
17
+ * raises {@link CompensationFailedError} carrying both the trigger and the
18
+ * rollback error; otherwise it rethrows the trigger.
19
+ */
20
+ export declare function compensate(context: HistoryContext, sessionId: string, chunks: ChatMessageItem[][], committed: CommittedChunk[], trigger: Error, now: string, title: string | undefined): Promise<never>;
21
+ //# sourceMappingURL=compensation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compensation.d.ts","sourceRoot":"","sources":["../../../src/history/internal/compensation.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,oFAAoF;AACpF,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC;CACf;AA0DD;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,eAAe,EAAE,EAAE,EAC3B,SAAS,EAAE,cAAc,EAAE,EAC3B,OAAO,EAAE,KAAK,EACd,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,OAAO,CAAC,KAAK,CAAC,CAsBhB"}
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compensate = compensate;
4
+ const descriptor_keys_1 = require("../../shared/codec/descriptor-keys");
5
+ const orphans_1 = require("../../shared/codec/s3/orphans");
6
+ const batch_write_1 = require("../../shared/dynamodb/batch-write");
7
+ const errors_1 = require("../../shared/errors/errors");
8
+ const wrap_error_1 = require("../../shared/errors/wrap-error");
9
+ const session_count_1 = require("./session-count");
10
+ /**
11
+ * Best-effort delete the offloaded S3 objects of the `chunks` slice given.
12
+ * {@link compensate} calls it once per commit status, never for the whole
13
+ * batch, so a committed chunk's objects arrive only once its rows are gone.
14
+ */
15
+ async function cleanBatchS3(context, chunks) {
16
+ if (!context.offloader)
17
+ return;
18
+ const descriptors = chunks.flat().map((item) => item.message);
19
+ await (0, orphans_1.cleanUpS3Orphans)(context.offloader, (0, descriptor_keys_1.collectS3Keys)(descriptors), 'history.addMessages', context.logger);
20
+ }
21
+ /**
22
+ * Delete every committed chunk's items, then undo their effect on the session
23
+ * row — deleting it outright when this call created it (see
24
+ * {@link revertSessionCreation}), so a failed first append leaves no ghost
25
+ * session holding the rolled-back message's title. A *partial* delete is not a
26
+ * clean creation to undo, so that branch reverts only the count.
27
+ */
28
+ async function rollbackCommitted(context, sessionId, committed, now, title) {
29
+ const keys = committed.flatMap((chunk) => chunk.keys);
30
+ const total = committed.reduce((sum, chunk) => sum + chunk.count, 0);
31
+ if (keys.length === 0) {
32
+ await (0, session_count_1.revertSessionCreation)(context, sessionId, total, now, title);
33
+ return;
34
+ }
35
+ try {
36
+ await (0, batch_write_1.batchWriteAll)(context.client, context.tableName, keys.map((Key) => ({ DeleteRequest: { Key } })));
37
+ }
38
+ catch (error) {
39
+ /**
40
+ * batchWriteAll has exactly one throw site and it is always a
41
+ * BatchWriteAllIncompleteError (see Task 9) — asserted, not instanceof-
42
+ * checked, since the false case is unreachable and this project
43
+ * enforces 100% branch coverage with no exceptions.
44
+ */
45
+ const deleted = error.succeededCount;
46
+ await (0, session_count_1.revertSessionCount)(context, sessionId, deleted);
47
+ throw error;
48
+ }
49
+ await (0, session_count_1.revertSessionCreation)(context, sessionId, total, now, title);
50
+ }
51
+ /**
52
+ * Undo a failed batch. Always throws. S3 cleanup is split by commit status so
53
+ * no live row is ever left pointing at a deleted object: the never-committed
54
+ * suffix is cleaned immediately, the committed prefix only after its rows are
55
+ * confirmed deleted. If the rollback itself fails, the committed chunks' S3
56
+ * objects are deliberately left in place (their rows may survive) and it
57
+ * raises {@link CompensationFailedError} carrying both the trigger and the
58
+ * rollback error; otherwise it rethrows the trigger.
59
+ */
60
+ async function compensate(context, sessionId, chunks, committed, trigger, now, title) {
61
+ if (committed.length > 0) {
62
+ context.logger.warn('history.addMessages compensating committed chunks after a chunk failed', {
63
+ sessionId,
64
+ committedChunks: committed.length,
65
+ });
66
+ }
67
+ /** The failed/never-attempted suffix never had a DynamoDB row, so it's safe to clean now. */
68
+ await cleanBatchS3(context, chunks.slice(committed.length));
69
+ try {
70
+ await rollbackCommitted(context, sessionId, committed, now, title);
71
+ }
72
+ catch (rollbackError) {
73
+ context.logger.error('history.addMessages rollback failed; messageCount may have drifted', {
74
+ sessionId,
75
+ committedChunks: committed.length,
76
+ });
77
+ /** Skip S3 cleanup here: rollback may have failed, so committed rows might still reference these objects. */
78
+ throw new errors_1.CompensationFailedError(trigger, (0, wrap_error_1.toError)(rollbackError));
79
+ }
80
+ /** Only now that committed rows are confirmed deleted is it safe to delete their S3 objects. */
81
+ await cleanBatchS3(context, chunks.slice(0, committed.length));
82
+ throw trigger;
83
+ }
84
+ //# sourceMappingURL=compensation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compensation.js","sourceRoot":"","sources":["../../../src/history/internal/compensation.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,17 @@
1
+ /** Reserved separator joining sort-key segments; forbidden inside a session id. */
2
+ export declare const SORT_KEY_SEPARATOR = "#";
1
3
  /** Fixed sort key for the per-session metadata item. */
2
4
  export declare const SESSION_SORT_KEY = "HISTORY#SESSION";
3
- /** Partition key for a chat session: the session id itself. */
5
+ /** Partition key for a chat session: the adapter tag plus the session id. */
4
6
  export declare function sessionPartition(sessionId: string): string;
5
7
  /** Sort key for a single message item, ordered by its monotonic ULID. */
6
8
  export declare function messageSortKey(ulid: string): string;
9
+ /**
10
+ * True when `sortKey` is one this adapter writes. A partition query carries no
11
+ * sort-key condition, so a partition-wide delete uses this to leave any row it
12
+ * does not own in place rather than deleting the whole partition blindly.
13
+ */
14
+ export declare function isHistorySortKey(sortKey: string): boolean;
7
15
  /** `begins_with` prefix selecting every message item in a session. */
8
16
  export declare function messageSortKeyPrefix(): string;
9
17
  //# sourceMappingURL=keys.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../../src/history/internal/keys.ts"],"names":[],"mappings":"AAWA,wDAAwD;AACxD,eAAO,MAAM,gBAAgB,oBAA6B,CAAC;AAI3D,+DAA+D;AAC/D,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,yEAAyE;AACzE,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,sEAAsE;AACtE,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C"}
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../../src/history/internal/keys.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAatC,wDAAwD;AACxD,eAAO,MAAM,gBAAgB,oBAA6B,CAAC;AAW3D,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,yEAAyE;AACzE,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,sEAAsE;AACtE,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C"}
@@ -1,9 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SESSION_SORT_KEY = void 0;
3
+ exports.SESSION_SORT_KEY = exports.SORT_KEY_SEPARATOR = void 0;
4
4
  exports.sessionPartition = sessionPartition;
5
5
  exports.messageSortKey = messageSortKey;
6
+ exports.isHistorySortKey = isHistorySortKey;
6
7
  exports.messageSortKeyPrefix = messageSortKeyPrefix;
8
+ /** Reserved separator joining sort-key segments; forbidden inside a session id. */
9
+ exports.SORT_KEY_SEPARATOR = '#';
7
10
  /**
8
11
  * Item-kind tag distinguishing this adapter's sort keys from another
9
12
  * adapter's on a table shared via `DynamoDBFactory.createAll()` — matches the
@@ -13,18 +16,32 @@ exports.messageSortKeyPrefix = messageSortKeyPrefix;
13
16
  * `store.put([sessionId], 'SESSION', ...)`, since `sortKey` collapses a
14
17
  * single-element namespace down to just the key).
15
18
  */
16
- const ADAPTER_PREFIX = 'HISTORY#';
19
+ const ADAPTER_PREFIX = `HISTORY${exports.SORT_KEY_SEPARATOR}`;
17
20
  /** Fixed sort key for the per-session metadata item. */
18
21
  exports.SESSION_SORT_KEY = `${ADAPTER_PREFIX}SESSION`;
19
22
  const MESSAGE_PREFIX = `${ADAPTER_PREFIX}MSG#`;
20
- /** Partition key for a chat session: the session id itself. */
23
+ /**
24
+ * Adapter tag prefixed to every chat-history partition key — see the
25
+ * equivalent in checkpointer/internal/keys.ts for why the three adapters'
26
+ * partitions must not overlap on a shared table.
27
+ */
28
+ const ADAPTER_PARTITION_PREFIX = `HIST${exports.SORT_KEY_SEPARATOR}`;
29
+ /** Partition key for a chat session: the adapter tag plus the session id. */
21
30
  function sessionPartition(sessionId) {
22
- return sessionId;
31
+ return `${ADAPTER_PARTITION_PREFIX}${sessionId}`;
23
32
  }
24
33
  /** Sort key for a single message item, ordered by its monotonic ULID. */
25
34
  function messageSortKey(ulid) {
26
35
  return `${MESSAGE_PREFIX}${ulid}`;
27
36
  }
37
+ /**
38
+ * True when `sortKey` is one this adapter writes. A partition query carries no
39
+ * sort-key condition, so a partition-wide delete uses this to leave any row it
40
+ * does not own in place rather than deleting the whole partition blindly.
41
+ */
42
+ function isHistorySortKey(sortKey) {
43
+ return sortKey.startsWith(ADAPTER_PREFIX);
44
+ }
28
45
  /** `begins_with` prefix selecting every message item in a session. */
29
46
  function messageSortKeyPrefix() {
30
47
  return MESSAGE_PREFIX;
@@ -1 +1 @@
1
- {"version":3,"file":"keys.js","sourceRoot":"","sources":["../../../src/history/internal/keys.ts"],"names":[],"mappings":";;;;;;AAAA;;;;;;;;GAQG;AACH,MAAM,cAAc,GAAG,UAAU,CAAC;AAElC,wDAAwD;AAC3C,QAAA,gBAAgB,GAAG,GAAG,cAAc,SAAS,CAAC;AAE3D,MAAM,cAAc,GAAG,GAAG,cAAc,MAAM,CAAC;AAE/C,+DAA+D;AAC/D,0BAAiC,SAAiB;IAChD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,yEAAyE;AACzE,wBAA+B,IAAY;IACzC,OAAO,GAAG,cAAc,GAAG,IAAI,EAAE,CAAC;AACpC,CAAC;AAED,sEAAsE;AACtE;IACE,OAAO,cAAc,CAAC;AACxB,CAAC"}
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../../../src/history/internal/keys.ts"],"names":[],"mappings":";;;;;;;AAAA,mFAAmF;AACtE,QAAA,kBAAkB,GAAG,GAAG,CAAC;AAEtC;;;;;;;;GAQG;AACH,MAAM,cAAc,GAAG,UAAU,QAAA,kBAAkB,EAAE,CAAC;AAEtD,wDAAwD;AAC3C,QAAA,gBAAgB,GAAG,GAAG,cAAc,SAAS,CAAC;AAE3D,MAAM,cAAc,GAAG,GAAG,cAAc,MAAM,CAAC;AAE/C;;;;GAIG;AACH,MAAM,wBAAwB,GAAG,OAAO,QAAA,kBAAkB,EAAE,CAAC;AAE7D,6EAA6E;AAC7E,0BAAiC,SAAiB;IAChD,OAAO,GAAG,wBAAwB,GAAG,SAAS,EAAE,CAAC;AACnD,CAAC;AAED,yEAAyE;AACzE,wBAA+B,IAAY;IACzC,OAAO,GAAG,cAAc,GAAG,IAAI,EAAE,CAAC;AACpC,CAAC;AAED;;;;GAIG;AACH,0BAAiC,OAAe;IAC9C,OAAO,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AAC5C,CAAC;AAED,sEAAsE;AACtE;IACE,OAAO,cAAc,CAAC;AACxB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"message-chunker.d.ts","sourceRoot":"","sources":["../../../src/history/internal/message-chunker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAmBhD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAQ/D;AAaD;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,eAAe,EAAE,EACxB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,eAAe,EAAE,EAAE,CAgBrB"}
1
+ {"version":3,"file":"message-chunker.d.ts","sourceRoot":"","sources":["../../../src/history/internal/message-chunker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AA4BhD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAQ/D;AAaD;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,eAAe,EAAE,EACxB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,eAAe,EAAE,EAAE,CAgBrB"}
@@ -11,11 +11,19 @@ const codec_1 = require("../../shared/codec/codec");
11
11
  * overshoot the transaction byte limit.
12
12
  */
13
13
  const ITEM_OVERHEAD_BYTES = 256;
14
+ /**
15
+ * Byte length of a string as DynamoDB stores it. `String.length` counts UTF-16
16
+ * code units, which understates every non-ASCII character — the wrong
17
+ * direction for an estimate documented to sit at or above the real size.
18
+ */
19
+ function utf8Bytes(value) {
20
+ return Buffer.byteLength(value, 'utf8');
21
+ }
14
22
  function descriptorBytes(descriptor) {
15
23
  const body = descriptor.location === codec_1.PayloadLocation.INLINE
16
24
  ? descriptor.bytes.length
17
- : descriptor.s3Key.length;
18
- return body + descriptor.serdeType.length;
25
+ : utf8Bytes(descriptor.s3Key);
26
+ return body + utf8Bytes(descriptor.serdeType);
19
27
  }
20
28
  /**
21
29
  * Conservatively estimate a message item's stored size, so chunks can stay under
@@ -23,9 +31,9 @@ function descriptorBytes(descriptor) {
23
31
  * on the safe side of the real marshalled size.
24
32
  */
25
33
  function estimateItemBytes(item) {
26
- return (item.PK.length +
27
- item.SK.length +
28
- item.sessionId.length +
34
+ return (utf8Bytes(item.PK) +
35
+ utf8Bytes(item.SK) +
36
+ utf8Bytes(item.sessionId) +
29
37
  descriptorBytes(item.message) +
30
38
  ITEM_OVERHEAD_BYTES);
31
39
  }
@@ -1 +1 @@
1
- {"version":3,"file":"message-chunker.js","sourceRoot":"","sources":["../../../src/history/internal/message-chunker.ts"],"names":[],"mappings":";;;;AAAA,oDAAmF;AAGnF;;;;;;GAMG;AACH,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAEhC,SAAS,eAAe,CAAC,UAA6B;IACpD,MAAM,IAAI,GACR,UAAU,CAAC,QAAQ,KAAK,uBAAe,CAAC,MAAM;QAC5C,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM;QACzB,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC;IAC9B,OAAO,IAAI,GAAG,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC;AAC5C,CAAC;AAED;;;;GAIG;AACH,2BAAkC,IAAqB;IACrD,OAAO,CACL,IAAI,CAAC,EAAE,CAAC,MAAM;QACd,IAAI,CAAC,EAAE,CAAC,MAAM;QACd,IAAI,CAAC,SAAS,CAAC,MAAM;QACrB,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC;QAC7B,mBAAmB,CACpB,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAClB,KAAa,EACb,KAAa,EACb,IAAY,EACZ,GAAW,EACX,QAAgB;IAEhB,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9B,OAAO,KAAK,IAAI,GAAG,IAAI,KAAK,GAAG,IAAI,GAAG,QAAQ,CAAC;AACjD,CAAC;AAED;;;;GAIG;AACH,qBACE,KAAwB,EACxB,QAAgB,EAChB,QAAgB;IAEhB,MAAM,MAAM,GAAwB,EAAE,CAAC;IACvC,IAAI,OAAO,GAAsB,EAAE,CAAC;IACpC,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,WAAW,CAAC,OAAO,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC;YACxE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACrB,OAAO,GAAG,EAAE,CAAC;YACb,YAAY,GAAG,CAAC,CAAC;QACnB,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,YAAY,IAAI,IAAI,CAAC;IACvB,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC7C,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"message-chunker.js","sourceRoot":"","sources":["../../../src/history/internal/message-chunker.ts"],"names":[],"mappings":";;;;AAAA,oDAAmF;AAGnF;;;;;;GAMG;AACH,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAEhC;;;;GAIG;AACH,SAAS,SAAS,CAAC,KAAa;IAC9B,OAAO,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;AAC1C,CAAC;AAED,SAAS,eAAe,CAAC,UAA6B;IACpD,MAAM,IAAI,GACR,UAAU,CAAC,QAAQ,KAAK,uBAAe,CAAC,MAAM;QAC5C,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM;QACzB,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IAClC,OAAO,IAAI,GAAG,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;AAChD,CAAC;AAED;;;;GAIG;AACH,2BAAkC,IAAqB;IACrD,OAAO,CACL,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;QAClB,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;QAClB,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC;QACzB,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC;QAC7B,mBAAmB,CACpB,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAClB,KAAa,EACb,KAAa,EACb,IAAY,EACZ,GAAW,EACX,QAAgB;IAEhB,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9B,OAAO,KAAK,IAAI,GAAG,IAAI,KAAK,GAAG,IAAI,GAAG,QAAQ,CAAC;AACjD,CAAC;AAED;;;;GAIG;AACH,qBACE,KAAwB,EACxB,QAAgB,EAChB,QAAgB;IAEhB,MAAM,MAAM,GAAwB,EAAE,CAAC;IACvC,IAAI,OAAO,GAAsB,EAAE,CAAC;IACpC,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,WAAW,CAAC,OAAO,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC;YACxE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACrB,OAAO,GAAG,EAAE,CAAC;YACb,YAAY,GAAG,CAAC,CAAC;QACnB,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,YAAY,IAAI,IAAI,CAAC;IACvB,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC7C,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -17,4 +17,25 @@ import type { HistoryContext } from './setup';
17
17
  * "TTL expiry" section.
18
18
  */
19
19
  export declare function revertSessionCount(context: HistoryContext, sessionId: string, delta: number): Promise<void>;
20
+ /**
21
+ * Undo a rolled-back append's effect on the session row.
22
+ *
23
+ * When this call is the one that created the row — `createdAt` still equals
24
+ * this call's timestamp, and the only messages counted on it are the ones
25
+ * being reverted — the whole row is deleted. Without that, a failed first
26
+ * append left a "ghost session": `title`, `createdAt` and `sessionId` are all
27
+ * written via `if_not_exists`, so they were never reverted and never set
28
+ * again, leaving `listSessions()` reporting a session with `messageCount: 0`
29
+ * whose title still held up to 80 characters of a message the caller was told
30
+ * had not persisted, with no API to clear it.
31
+ *
32
+ * Both conditions are load-bearing. `createdAt = :now` establishes that this
33
+ * call created the row; `messageCount = :total` establishes that nothing else
34
+ * has added to it since. A concurrent append to the same brand-new session
35
+ * fails the count check, because deleting the row would destroy that caller's
36
+ * committed messages — so it falls through to the plain decrement, and then
37
+ * strips just the title this call contributed, which is the only part of the
38
+ * row still carrying rolled-back message content.
39
+ */
40
+ export declare function revertSessionCreation(context: HistoryContext, sessionId: string, total: number, createdAt: string, title?: string): Promise<void>;
20
41
  //# sourceMappingURL=session-count.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-count.d.ts","sourceRoot":"","sources":["../../../src/history/internal/session-count.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAe9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAiBf"}
1
+ {"version":3,"file":"session-count.d.ts","sourceRoot":"","sources":["../../../src/history/internal/session-count.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAe9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAwBf"}
@@ -1,10 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.revertSessionCount = revertSessionCount;
4
+ exports.revertSessionCreation = revertSessionCreation;
4
5
  const node_crypto_1 = require("node:crypto");
5
6
  const cancellation_1 = require("../../shared/dynamodb/cancellation");
6
7
  const retry_1 = require("../../shared/dynamodb/retry");
7
8
  const keys_1 = require("./keys");
9
+ const session_title_1 = require("./session-title");
8
10
  /**
9
11
  * True when a TransactWriteItems cancellation was caused by a
10
12
  * ConditionalCheckFailed reason. Named distinctly from
@@ -55,4 +57,53 @@ async function revertSessionCount(context, sessionId, delta) {
55
57
  throw error;
56
58
  }
57
59
  }
60
+ /**
61
+ * Undo a rolled-back append's effect on the session row.
62
+ *
63
+ * When this call is the one that created the row — `createdAt` still equals
64
+ * this call's timestamp, and the only messages counted on it are the ones
65
+ * being reverted — the whole row is deleted. Without that, a failed first
66
+ * append left a "ghost session": `title`, `createdAt` and `sessionId` are all
67
+ * written via `if_not_exists`, so they were never reverted and never set
68
+ * again, leaving `listSessions()` reporting a session with `messageCount: 0`
69
+ * whose title still held up to 80 characters of a message the caller was told
70
+ * had not persisted, with no API to clear it.
71
+ *
72
+ * Both conditions are load-bearing. `createdAt = :now` establishes that this
73
+ * call created the row; `messageCount = :total` establishes that nothing else
74
+ * has added to it since. A concurrent append to the same brand-new session
75
+ * fails the count check, because deleting the row would destroy that caller's
76
+ * committed messages — so it falls through to the plain decrement, and then
77
+ * strips just the title this call contributed, which is the only part of the
78
+ * row still carrying rolled-back message content.
79
+ */
80
+ async function revertSessionCreation(context, sessionId, total, createdAt, title) {
81
+ if (total === 0)
82
+ return;
83
+ const input = {
84
+ TransactItems: [
85
+ {
86
+ Delete: {
87
+ TableName: context.tableName,
88
+ Key: { PK: (0, keys_1.sessionPartition)(sessionId), SK: keys_1.SESSION_SORT_KEY },
89
+ ConditionExpression: '#count = :total AND #c = :now',
90
+ ExpressionAttributeNames: { '#count': 'messageCount', '#c': 'createdAt' },
91
+ ExpressionAttributeValues: { ':total': total, ':now': createdAt },
92
+ },
93
+ },
94
+ ],
95
+ ClientRequestToken: (0, node_crypto_1.randomUUID)(),
96
+ };
97
+ try {
98
+ await (0, retry_1.withDynamoDBRetry)(() => context.client.transactWrite(input));
99
+ return;
100
+ }
101
+ catch (error) {
102
+ if (!isCancelledByCondition(error))
103
+ throw error;
104
+ }
105
+ await revertSessionCount(context, sessionId, total);
106
+ if (title !== undefined)
107
+ await (0, session_title_1.removeRolledBackTitle)(context, sessionId, createdAt, title);
108
+ }
58
109
  //# sourceMappingURL=session-count.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-count.js","sourceRoot":"","sources":["../../../src/history/internal/session-count.ts"],"names":[],"mappings":";;;AAAA,6CAAyC;AAEzC,qEAA4E;AAC5E,uDAAgE;AAChE,iCAA4D;AAG5D;;;;;;;GAOG;AACH,SAAS,sBAAsB,CAAC,KAAY;IAC1C,MAAM,OAAO,GAAG,IAAA,qCAAsB,EAAC,KAAK,CAAC,CAAC;IAC9C,OAAO,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,KAAK,wBAAwB,CAAC;AACzD,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACI,KAAK,6BACV,OAAuB,EACvB,SAAiB,EACjB,KAAa;IAEb,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO;IACxB,MAAM,MAAM,GAAG;QACb,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,GAAG,EAAE,EAAE,EAAE,EAAE,IAAA,uBAAgB,EAAC,SAAS,CAAC,EAAE,EAAE,EAAE,uBAAgB,EAAE;QAC9D,gBAAgB,EAAE,iBAAiB;QACnC,mBAAmB,EAAE,sBAAsB;QAC3C,wBAAwB,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE;QACtD,yBAAyB,EAAE,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE;KAC9C,CAAC;IACF,MAAM,KAAK,GAAG,EAAE,aAAa,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,kBAAkB,EAAE,IAAA,wBAAU,GAAE,EAAE,CAAC;IACxF,IAAI,CAAC;QACH,MAAM,IAAA,yBAAiB,EAAC,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;IACrE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,sBAAsB,CAAC,KAAc,CAAC;YAAE,OAAO;QACnD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"session-count.js","sourceRoot":"","sources":["../../../src/history/internal/session-count.ts"],"names":[],"mappings":";;;;AAAA,6CAAyC;AAEzC,qEAA4E;AAC5E,uDAAgE;AAChE,iCAA4D;AAC5D,mDAAwD;AAGxD;;;;;;;GAOG;AACH,SAAS,sBAAsB,CAAC,KAAY;IAC1C,MAAM,OAAO,GAAG,IAAA,qCAAsB,EAAC,KAAK,CAAC,CAAC;IAC9C,OAAO,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,KAAK,wBAAwB,CAAC;AACzD,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACI,KAAK,6BACV,OAAuB,EACvB,SAAiB,EACjB,KAAa;IAEb,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO;IACxB,MAAM,MAAM,GAAG;QACb,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,GAAG,EAAE,EAAE,EAAE,EAAE,IAAA,uBAAgB,EAAC,SAAS,CAAC,EAAE,EAAE,EAAE,uBAAgB,EAAE;QAC9D,gBAAgB,EAAE,iBAAiB;QACnC,mBAAmB,EAAE,sBAAsB;QAC3C,wBAAwB,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE;QACtD,yBAAyB,EAAE,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE;KAC9C,CAAC;IACF,MAAM,KAAK,GAAG,EAAE,aAAa,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,kBAAkB,EAAE,IAAA,wBAAU,GAAE,EAAE,CAAC;IACxF,IAAI,CAAC;QACH,MAAM,IAAA,yBAAiB,EAAC,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;IACrE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,sBAAsB,CAAC,KAAc,CAAC;YAAE,OAAO;QACnD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACI,KAAK,gCACV,OAAuB,EACvB,SAAiB,EACjB,KAAa,EACb,SAAiB,EACjB,KAAc;IAEd,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO;IACxB,MAAM,KAAK,GAAG;QACZ,aAAa,EAAE;YACb;gBACE,MAAM,EAAE;oBACN,SAAS,EAAE,OAAO,CAAC,SAAS;oBAC5B,GAAG,EAAE,EAAE,EAAE,EAAE,IAAA,uBAAgB,EAAC,SAAS,CAAC,EAAE,EAAE,EAAE,uBAAgB,EAAE;oBAC9D,mBAAmB,EAAE,+BAA+B;oBACpD,wBAAwB,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,WAAW,EAAE;oBACzE,yBAAyB,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE;iBAClE;aACF;SACF;QACD,kBAAkB,EAAE,IAAA,wBAAU,GAAE;KACjC,CAAC;IACF,IAAI,CAAC;QACH,MAAM,IAAA,yBAAiB,EAAC,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;QACnE,OAAO;IACT,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,sBAAsB,CAAC,KAAc,CAAC;YAAE,MAAM,KAAK,CAAC;IAC3D,CAAC;IACD,MAAM,kBAAkB,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IACpD,IAAI,KAAK,KAAK,SAAS;QAAE,MAAM,IAAA,qCAAqB,EAAC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;AAC7F,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { HistoryContext } from './setup';
2
+ /**
3
+ * Strip a title this call contributed to a session row it created, when the
4
+ * row itself could not be deleted because a concurrent append has since added
5
+ * messages to it.
6
+ *
7
+ * Without this, that narrow window reopens exactly the leak C4 closes: the
8
+ * title is derived from the first human message of an append the caller was
9
+ * told had failed, and `if_not_exists` means nothing ever overwrites it — so
10
+ * a row that now belongs to a different caller keeps up to 80 characters of
11
+ * rolled-back message content.
12
+ *
13
+ * Both guards are load-bearing. `createdAt = :now` establishes that this call
14
+ * created the row, and `title = :title` that the title on it is still the one
15
+ * this call wrote — so a pre-existing title, or one a concurrent caller won
16
+ * the `if_not_exists` race for, is never removed. A condition rejection means
17
+ * exactly that and is not an error; anything else is.
18
+ */
19
+ export declare function removeRolledBackTitle(context: HistoryContext, sessionId: string, createdAt: string, title: string): Promise<void>;
20
+ //# sourceMappingURL=session-title.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-title.d.ts","sourceRoot":"","sources":["../../../src/history/internal/session-title.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAO9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAgBf"}