@shipfox/api-logs 2.0.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 (342) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-type$colon$emit.log +1 -0
  3. package/.turbo/turbo-type.log +1 -0
  4. package/CHANGELOG.md +179 -0
  5. package/LICENSE +21 -0
  6. package/README.md +151 -0
  7. package/dist/api/object-storage.d.ts +69 -0
  8. package/dist/api/object-storage.d.ts.map +1 -0
  9. package/dist/api/object-storage.js +199 -0
  10. package/dist/api/object-storage.js.map +1 -0
  11. package/dist/config.d.ts +20 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +108 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/core/append-logs.d.ts +28 -0
  16. package/dist/core/append-logs.d.ts.map +1 -0
  17. package/dist/core/append-logs.js +318 -0
  18. package/dist/core/append-logs.js.map +1 -0
  19. package/dist/core/budget.d.ts +12 -0
  20. package/dist/core/budget.d.ts.map +1 -0
  21. package/dist/core/budget.js +9 -0
  22. package/dist/core/budget.js.map +1 -0
  23. package/dist/core/close-stream.d.ts +27 -0
  24. package/dist/core/close-stream.d.ts.map +1 -0
  25. package/dist/core/close-stream.js +59 -0
  26. package/dist/core/close-stream.js.map +1 -0
  27. package/dist/core/compaction.d.ts +28 -0
  28. package/dist/core/compaction.d.ts.map +1 -0
  29. package/dist/core/compaction.js +49 -0
  30. package/dist/core/compaction.js.map +1 -0
  31. package/dist/core/entities/attempt-stream.d.ts +37 -0
  32. package/dist/core/entities/attempt-stream.d.ts.map +1 -0
  33. package/dist/core/entities/attempt-stream.js +15 -0
  34. package/dist/core/entities/attempt-stream.js.map +1 -0
  35. package/dist/core/entities/job-accounting.d.ts +12 -0
  36. package/dist/core/entities/job-accounting.d.ts.map +1 -0
  37. package/dist/core/entities/job-accounting.js +6 -0
  38. package/dist/core/entities/job-accounting.js.map +1 -0
  39. package/dist/core/entities/log-object.d.ts +13 -0
  40. package/dist/core/entities/log-object.d.ts.map +1 -0
  41. package/dist/core/entities/log-object.js +9 -0
  42. package/dist/core/entities/log-object.js.map +1 -0
  43. package/dist/core/errors.d.ts +26 -0
  44. package/dist/core/errors.d.ts.map +1 -0
  45. package/dist/core/errors.js +31 -0
  46. package/dist/core/errors.js.map +1 -0
  47. package/dist/core/finalize-attempt-stream.d.ts +22 -0
  48. package/dist/core/finalize-attempt-stream.d.ts.map +1 -0
  49. package/dist/core/finalize-attempt-stream.js +48 -0
  50. package/dist/core/finalize-attempt-stream.js.map +1 -0
  51. package/dist/core/read-logs.d.ts +33 -0
  52. package/dist/core/read-logs.d.ts.map +1 -0
  53. package/dist/core/read-logs.js +49 -0
  54. package/dist/core/read-logs.js.map +1 -0
  55. package/dist/core/reap-stale-open-streams.d.ts +27 -0
  56. package/dist/core/reap-stale-open-streams.d.ts.map +1 -0
  57. package/dist/core/reap-stale-open-streams.js +50 -0
  58. package/dist/core/reap-stale-open-streams.js.map +1 -0
  59. package/dist/core/retention.d.ts +32 -0
  60. package/dist/core/retention.d.ts.map +1 -0
  61. package/dist/core/retention.js +148 -0
  62. package/dist/core/retention.js.map +1 -0
  63. package/dist/core/session/claude/rows.d.ts +6 -0
  64. package/dist/core/session/claude/rows.d.ts.map +1 -0
  65. package/dist/core/session/claude/rows.js +145 -0
  66. package/dist/core/session/claude/rows.js.map +1 -0
  67. package/dist/core/session/claude-parser.d.ts +4 -0
  68. package/dist/core/session/claude-parser.d.ts.map +1 -0
  69. package/dist/core/session/claude-parser.js +42 -0
  70. package/dist/core/session/claude-parser.js.map +1 -0
  71. package/dist/core/session/entry-schema.d.ts +51 -0
  72. package/dist/core/session/entry-schema.d.ts.map +1 -0
  73. package/dist/core/session/entry-schema.js +61 -0
  74. package/dist/core/session/entry-schema.js.map +1 -0
  75. package/dist/core/session/object.d.ts +12 -0
  76. package/dist/core/session/object.d.ts.map +1 -0
  77. package/dist/core/session/object.js +50 -0
  78. package/dist/core/session/object.js.map +1 -0
  79. package/dist/core/session/parse-session.d.ts +5 -0
  80. package/dist/core/session/parse-session.d.ts.map +1 -0
  81. package/dist/core/session/parse-session.js +18 -0
  82. package/dist/core/session/parse-session.js.map +1 -0
  83. package/dist/core/session/pi/message-rows.d.ts +4 -0
  84. package/dist/core/session/pi/message-rows.d.ts.map +1 -0
  85. package/dist/core/session/pi/message-rows.js +113 -0
  86. package/dist/core/session/pi/message-rows.js.map +1 -0
  87. package/dist/core/session/pi/metadata.d.ts +18 -0
  88. package/dist/core/session/pi/metadata.d.ts.map +1 -0
  89. package/dist/core/session/pi/metadata.js +120 -0
  90. package/dist/core/session/pi/metadata.js.map +1 -0
  91. package/dist/core/session/pi/text.d.ts +12 -0
  92. package/dist/core/session/pi/text.d.ts.map +1 -0
  93. package/dist/core/session/pi/text.js +59 -0
  94. package/dist/core/session/pi/text.js.map +1 -0
  95. package/dist/core/session/pi-parser.d.ts +5 -0
  96. package/dist/core/session/pi-parser.d.ts.map +1 -0
  97. package/dist/core/session/pi-parser.js +73 -0
  98. package/dist/core/session/pi-parser.js.map +1 -0
  99. package/dist/core/session/rows.d.ts +7 -0
  100. package/dist/core/session/rows.d.ts.map +1 -0
  101. package/dist/core/session/rows.js +39 -0
  102. package/dist/core/session/rows.js.map +1 -0
  103. package/dist/core/session/session-record.d.ts +5 -0
  104. package/dist/core/session/session-record.d.ts.map +1 -0
  105. package/dist/core/session/session-record.js +3 -0
  106. package/dist/core/session/session-record.js.map +1 -0
  107. package/dist/db/accounting.d.ts +33 -0
  108. package/dist/db/accounting.d.ts.map +1 -0
  109. package/dist/db/accounting.js +52 -0
  110. package/dist/db/accounting.js.map +1 -0
  111. package/dist/db/chunks.d.ts +62 -0
  112. package/dist/db/chunks.d.ts.map +1 -0
  113. package/dist/db/chunks.js +88 -0
  114. package/dist/db/chunks.js.map +1 -0
  115. package/dist/db/db.d.ts +752 -0
  116. package/dist/db/db.d.ts.map +1 -0
  117. package/dist/db/db.js +24 -0
  118. package/dist/db/db.js.map +1 -0
  119. package/dist/db/index.d.ts +2 -0
  120. package/dist/db/index.d.ts.map +1 -0
  121. package/dist/db/index.js +5 -0
  122. package/dist/db/index.js.map +1 -0
  123. package/dist/db/schema/attempt-streams.d.ts +304 -0
  124. package/dist/db/schema/attempt-streams.d.ts.map +1 -0
  125. package/dist/db/schema/attempt-streams.js +100 -0
  126. package/dist/db/schema/attempt-streams.js.map +1 -0
  127. package/dist/db/schema/chunks.d.ts +166 -0
  128. package/dist/db/schema/chunks.d.ts.map +1 -0
  129. package/dist/db/schema/chunks.js +47 -0
  130. package/dist/db/schema/chunks.js.map +1 -0
  131. package/dist/db/schema/common.d.ts +29 -0
  132. package/dist/db/schema/common.d.ts.map +1 -0
  133. package/dist/db/schema/common.js +11 -0
  134. package/dist/db/schema/common.js.map +1 -0
  135. package/dist/db/schema/job-accounting.d.ts +143 -0
  136. package/dist/db/schema/job-accounting.d.ts.map +1 -0
  137. package/dist/db/schema/job-accounting.js +43 -0
  138. package/dist/db/schema/job-accounting.js.map +1 -0
  139. package/dist/db/schema/outbox.d.ts +195 -0
  140. package/dist/db/schema/outbox.d.ts.map +1 -0
  141. package/dist/db/schema/outbox.js +5 -0
  142. package/dist/db/schema/outbox.js.map +1 -0
  143. package/dist/db/streams.d.ts +141 -0
  144. package/dist/db/streams.d.ts.map +1 -0
  145. package/dist/db/streams.js +197 -0
  146. package/dist/db/streams.js.map +1 -0
  147. package/dist/index.d.ts +4 -0
  148. package/dist/index.d.ts.map +1 -0
  149. package/dist/index.js +81 -0
  150. package/dist/index.js.map +1 -0
  151. package/dist/metrics/index.d.ts +2 -0
  152. package/dist/metrics/index.d.ts.map +1 -0
  153. package/dist/metrics/index.js +3 -0
  154. package/dist/metrics/index.js.map +1 -0
  155. package/dist/metrics/instance.d.ts +14 -0
  156. package/dist/metrics/instance.d.ts.map +1 -0
  157. package/dist/metrics/instance.js +16 -0
  158. package/dist/metrics/instance.js.map +1 -0
  159. package/dist/metrics/service.d.ts +2 -0
  160. package/dist/metrics/service.d.ts.map +1 -0
  161. package/dist/metrics/service.js +20 -0
  162. package/dist/metrics/service.js.map +1 -0
  163. package/dist/presentation/dto/read-logs.d.ts +4 -0
  164. package/dist/presentation/dto/read-logs.d.ts.map +1 -0
  165. package/dist/presentation/dto/read-logs.js +22 -0
  166. package/dist/presentation/dto/read-logs.js.map +1 -0
  167. package/dist/presentation/routes/append-logs.d.ts +2 -0
  168. package/dist/presentation/routes/append-logs.d.ts.map +1 -0
  169. package/dist/presentation/routes/append-logs.js +71 -0
  170. package/dist/presentation/routes/append-logs.js.map +1 -0
  171. package/dist/presentation/routes/index.d.ts +3 -0
  172. package/dist/presentation/routes/index.d.ts.map +1 -0
  173. package/dist/presentation/routes/index.js +32 -0
  174. package/dist/presentation/routes/index.js.map +1 -0
  175. package/dist/presentation/routes/read-logs.d.ts +2 -0
  176. package/dist/presentation/routes/read-logs.d.ts.map +1 -0
  177. package/dist/presentation/routes/read-logs.js +41 -0
  178. package/dist/presentation/routes/read-logs.js.map +1 -0
  179. package/dist/presentation/subscribers/on-job-terminated.d.ts +8 -0
  180. package/dist/presentation/subscribers/on-job-terminated.d.ts.map +1 -0
  181. package/dist/presentation/subscribers/on-job-terminated.js +32 -0
  182. package/dist/presentation/subscribers/on-job-terminated.js.map +1 -0
  183. package/dist/presentation/subscribers/on-log-stream-closed.d.ts +9 -0
  184. package/dist/presentation/subscribers/on-log-stream-closed.d.ts.map +1 -0
  185. package/dist/presentation/subscribers/on-log-stream-closed.js +31 -0
  186. package/dist/presentation/subscribers/on-log-stream-closed.js.map +1 -0
  187. package/dist/presentation/subscribers/on-step-attempt-terminated.d.ts +3 -0
  188. package/dist/presentation/subscribers/on-step-attempt-terminated.d.ts.map +1 -0
  189. package/dist/presentation/subscribers/on-step-attempt-terminated.js +14 -0
  190. package/dist/presentation/subscribers/on-step-attempt-terminated.js.map +1 -0
  191. package/dist/temporal/activities/close-abandoned-streams.d.ts +12 -0
  192. package/dist/temporal/activities/close-abandoned-streams.d.ts.map +1 -0
  193. package/dist/temporal/activities/close-abandoned-streams.js +33 -0
  194. package/dist/temporal/activities/close-abandoned-streams.js.map +1 -0
  195. package/dist/temporal/activities/compact-stream.d.ts +33 -0
  196. package/dist/temporal/activities/compact-stream.d.ts.map +1 -0
  197. package/dist/temporal/activities/compact-stream.js +95 -0
  198. package/dist/temporal/activities/compact-stream.js.map +1 -0
  199. package/dist/temporal/activities/compaction-reconcile.d.ts +16 -0
  200. package/dist/temporal/activities/compaction-reconcile.d.ts.map +1 -0
  201. package/dist/temporal/activities/compaction-reconcile.js +52 -0
  202. package/dist/temporal/activities/compaction-reconcile.js.map +1 -0
  203. package/dist/temporal/activities/index.d.ts +14 -0
  204. package/dist/temporal/activities/index.d.ts.map +1 -0
  205. package/dist/temporal/activities/index.js +16 -0
  206. package/dist/temporal/activities/index.js.map +1 -0
  207. package/dist/temporal/activities/reap-stale-open-streams.d.ts +4 -0
  208. package/dist/temporal/activities/reap-stale-open-streams.d.ts.map +1 -0
  209. package/dist/temporal/activities/reap-stale-open-streams.js +11 -0
  210. package/dist/temporal/activities/reap-stale-open-streams.js.map +1 -0
  211. package/dist/temporal/activities/retention-sweep.d.ts +7 -0
  212. package/dist/temporal/activities/retention-sweep.d.ts.map +1 -0
  213. package/dist/temporal/activities/retention-sweep.js +19 -0
  214. package/dist/temporal/activities/retention-sweep.js.map +1 -0
  215. package/dist/temporal/constants.d.ts +7 -0
  216. package/dist/temporal/constants.d.ts.map +1 -0
  217. package/dist/temporal/constants.js +13 -0
  218. package/dist/temporal/constants.js.map +1 -0
  219. package/dist/temporal/workflows/close-abandoned-streams.d.ts +11 -0
  220. package/dist/temporal/workflows/close-abandoned-streams.d.ts.map +1 -0
  221. package/dist/temporal/workflows/close-abandoned-streams.js +22 -0
  222. package/dist/temporal/workflows/close-abandoned-streams.js.map +1 -0
  223. package/dist/temporal/workflows/compact-stream.d.ts +6 -0
  224. package/dist/temporal/workflows/compact-stream.d.ts.map +1 -0
  225. package/dist/temporal/workflows/compact-stream.js +25 -0
  226. package/dist/temporal/workflows/compact-stream.js.map +1 -0
  227. package/dist/temporal/workflows/compaction-reconcile-cron.d.ts +3 -0
  228. package/dist/temporal/workflows/compaction-reconcile-cron.d.ts.map +1 -0
  229. package/dist/temporal/workflows/compaction-reconcile-cron.js +15 -0
  230. package/dist/temporal/workflows/compaction-reconcile-cron.js.map +1 -0
  231. package/dist/temporal/workflows/index.d.ts +6 -0
  232. package/dist/temporal/workflows/index.d.ts.map +1 -0
  233. package/dist/temporal/workflows/index.js +7 -0
  234. package/dist/temporal/workflows/index.js.map +1 -0
  235. package/dist/temporal/workflows/reap-stale-open-streams-cron.d.ts +2 -0
  236. package/dist/temporal/workflows/reap-stale-open-streams-cron.d.ts.map +1 -0
  237. package/dist/temporal/workflows/reap-stale-open-streams-cron.js +15 -0
  238. package/dist/temporal/workflows/reap-stale-open-streams-cron.js.map +1 -0
  239. package/dist/temporal/workflows/retention-sweep-cron.d.ts +2 -0
  240. package/dist/temporal/workflows/retention-sweep-cron.d.ts.map +1 -0
  241. package/dist/temporal/workflows/retention-sweep-cron.js +23 -0
  242. package/dist/temporal/workflows/retention-sweep-cron.js.map +1 -0
  243. package/dist/tsconfig.test.tsbuildinfo +1 -0
  244. package/drizzle/0000_initial.sql +65 -0
  245. package/drizzle/meta/0000_snapshot.json +529 -0
  246. package/drizzle/meta/_journal.json +13 -0
  247. package/drizzle.config.ts +7 -0
  248. package/package.json +84 -0
  249. package/src/api/object-storage-credentials.test.ts +70 -0
  250. package/src/api/object-storage.test.ts +64 -0
  251. package/src/api/object-storage.ts +226 -0
  252. package/src/config.test.ts +118 -0
  253. package/src/config.ts +130 -0
  254. package/src/core/append-logs.test.ts +563 -0
  255. package/src/core/append-logs.ts +384 -0
  256. package/src/core/budget.test.ts +30 -0
  257. package/src/core/budget.ts +19 -0
  258. package/src/core/close-stream.ts +72 -0
  259. package/src/core/compaction.ts +68 -0
  260. package/src/core/entities/attempt-stream.ts +38 -0
  261. package/src/core/entities/job-accounting.ts +11 -0
  262. package/src/core/entities/log-object.test.ts +25 -0
  263. package/src/core/entities/log-object.ts +18 -0
  264. package/src/core/errors.ts +37 -0
  265. package/src/core/finalize-attempt-stream.test.ts +90 -0
  266. package/src/core/finalize-attempt-stream.ts +59 -0
  267. package/src/core/read-logs.ts +77 -0
  268. package/src/core/reap-stale-open-streams.test.ts +137 -0
  269. package/src/core/reap-stale-open-streams.ts +60 -0
  270. package/src/core/retention.test.ts +388 -0
  271. package/src/core/retention.ts +184 -0
  272. package/src/core/session/claude/rows.ts +223 -0
  273. package/src/core/session/claude-parser.test.ts +199 -0
  274. package/src/core/session/claude-parser.ts +38 -0
  275. package/src/core/session/entry-schema.ts +82 -0
  276. package/src/core/session/object.ts +57 -0
  277. package/src/core/session/parse-session.ts +23 -0
  278. package/src/core/session/pi/message-rows.ts +211 -0
  279. package/src/core/session/pi/metadata.ts +148 -0
  280. package/src/core/session/pi/text.ts +83 -0
  281. package/src/core/session/pi-parser.test.ts +451 -0
  282. package/src/core/session/pi-parser.ts +107 -0
  283. package/src/core/session/rows.ts +38 -0
  284. package/src/core/session/session-record.ts +4 -0
  285. package/src/db/accounting.ts +81 -0
  286. package/src/db/chunks.ts +143 -0
  287. package/src/db/db.ts +22 -0
  288. package/src/db/index.ts +4 -0
  289. package/src/db/schema/attempt-streams.ts +106 -0
  290. package/src/db/schema/chunks.ts +43 -0
  291. package/src/db/schema/common.ts +12 -0
  292. package/src/db/schema/job-accounting.ts +38 -0
  293. package/src/db/schema/outbox.ts +4 -0
  294. package/src/db/streams.test.ts +47 -0
  295. package/src/db/streams.ts +368 -0
  296. package/src/index.ts +77 -0
  297. package/src/metrics/index.ts +1 -0
  298. package/src/metrics/instance.ts +33 -0
  299. package/src/metrics/service.ts +23 -0
  300. package/src/presentation/dto/read-logs.ts +24 -0
  301. package/src/presentation/routes/append-logs.test.ts +279 -0
  302. package/src/presentation/routes/append-logs.ts +66 -0
  303. package/src/presentation/routes/index.ts +27 -0
  304. package/src/presentation/routes/read-logs.test.ts +504 -0
  305. package/src/presentation/routes/read-logs.ts +38 -0
  306. package/src/presentation/subscribers/on-job-terminated.test.ts +64 -0
  307. package/src/presentation/subscribers/on-job-terminated.ts +26 -0
  308. package/src/presentation/subscribers/on-log-stream-closed.test.ts +63 -0
  309. package/src/presentation/subscribers/on-log-stream-closed.ts +26 -0
  310. package/src/presentation/subscribers/on-step-attempt-terminated.ts +16 -0
  311. package/src/temporal/activities/close-abandoned-streams.test.ts +84 -0
  312. package/src/temporal/activities/close-abandoned-streams.ts +30 -0
  313. package/src/temporal/activities/compact-stream.test.ts +221 -0
  314. package/src/temporal/activities/compact-stream.ts +123 -0
  315. package/src/temporal/activities/compaction-reconcile.test.ts +143 -0
  316. package/src/temporal/activities/compaction-reconcile.ts +48 -0
  317. package/src/temporal/activities/index.ts +15 -0
  318. package/src/temporal/activities/reap-stale-open-streams.test.ts +48 -0
  319. package/src/temporal/activities/reap-stale-open-streams.ts +14 -0
  320. package/src/temporal/activities/retention-sweep.test.ts +38 -0
  321. package/src/temporal/activities/retention-sweep.ts +23 -0
  322. package/src/temporal/constants.ts +14 -0
  323. package/src/temporal/workflows/close-abandoned-streams.ts +25 -0
  324. package/src/temporal/workflows/compact-stream.ts +26 -0
  325. package/src/temporal/workflows/compaction-reconcile-cron.ts +14 -0
  326. package/src/temporal/workflows/index.ts +5 -0
  327. package/src/temporal/workflows/reap-stale-open-streams-cron.ts +13 -0
  328. package/src/temporal/workflows/retention-sweep-cron.ts +23 -0
  329. package/test/env.ts +37 -0
  330. package/test/factories/job-accounting.ts +38 -0
  331. package/test/fixtures/closed-stream.ts +53 -0
  332. package/test/fixtures/lease-token.ts +42 -0
  333. package/test/fixtures/ndjson.ts +34 -0
  334. package/test/globalSetup.ts +44 -0
  335. package/test/index.ts +9 -0
  336. package/test/queries.ts +54 -0
  337. package/test/setup.ts +17 -0
  338. package/tsconfig.build.json +9 -0
  339. package/tsconfig.build.tsbuildinfo +1 -0
  340. package/tsconfig.json +3 -0
  341. package/tsconfig.test.json +8 -0
  342. package/vitest.config.ts +13 -0
@@ -0,0 +1,50 @@
1
+ import { logger } from '@shipfox/node-opentelemetry';
2
+ import { db } from '#db/db.js';
3
+ import { listStaleOpenStreams } from '#db/streams.js';
4
+ import { recordAppendedCount, streamClosedCount } from '#metrics/instance.js';
5
+ import { closeStream } from './close-stream.js';
6
+ /**
7
+ * Force-closes open streams the one-shot job-terminal sweep missed: a stream whose first append
8
+ * landed after that sweep's snapshot would otherwise stay `open` forever, invisible to
9
+ * compaction and retention (both partial on `state = 'closed'`). Reusing the guarded
10
+ * `closeStream` re-drives each into the normal closed -> compact -> retention lifecycle.
11
+ *
12
+ * Each row closes in its own transaction, so a tick/retry overlap is idempotent (the guarded
13
+ * `markStreamClosed` lets only one close win) and one failed row is logged and skipped instead
14
+ * of aborting the batch.
15
+ */ export async function reapStaleOpenStreams(params) {
16
+ const stale = await listStaleOpenStreams({
17
+ olderThanSeconds: params.olderThanSeconds,
18
+ limit: params.batchLimit
19
+ });
20
+ const result = {
21
+ reaped: 0,
22
+ failed: 0
23
+ };
24
+ for (const stream of stale){
25
+ try {
26
+ const closed = await db().transaction((tx)=>closeStream(tx, {
27
+ streamId: stream.id,
28
+ reason: 'timeout'
29
+ }));
30
+ if (closed) {
31
+ result.reaped += 1;
32
+ recordAppendedCount.add(1, {
33
+ kind: 'runner_lost'
34
+ });
35
+ streamClosedCount.add(1, {
36
+ reason: 'timeout'
37
+ });
38
+ }
39
+ } catch (error) {
40
+ result.failed += 1;
41
+ logger().error({
42
+ err: error,
43
+ streamId: stream.id
44
+ }, 'Failed to reap stale open log stream');
45
+ }
46
+ }
47
+ return result;
48
+ }
49
+
50
+ //# sourceMappingURL=reap-stale-open-streams.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/reap-stale-open-streams.ts"],"sourcesContent":["import {logger} from '@shipfox/node-opentelemetry';\nimport {db} from '#db/db.js';\nimport {listStaleOpenStreams} from '#db/streams.js';\nimport {recordAppendedCount, streamClosedCount} from '#metrics/instance.js';\nimport {closeStream} from './close-stream.js';\n\nexport interface ReapStaleOpenStreamsResult {\n /** Streams force-closed by this run. */\n reaped: number;\n /** Streams whose guarded close threw; logged, skipped, retried next run. */\n failed: number;\n}\n\nexport interface ReapStaleOpenStreamsParams {\n /**\n * A stream open longer than this since `created_at` can no longer be appended to: its job\n * holds one lease minted at-or-before the stream's creation, so once the lease TTL has\n * elapsed no append can land and the close is safe. Set above `AUTH_JOB_LEASE_TOKEN_EXPIRES_IN`.\n */\n olderThanSeconds: number;\n batchLimit: number;\n}\n\n/**\n * Force-closes open streams the one-shot job-terminal sweep missed: a stream whose first append\n * landed after that sweep's snapshot would otherwise stay `open` forever, invisible to\n * compaction and retention (both partial on `state = 'closed'`). Reusing the guarded\n * `closeStream` re-drives each into the normal closed -> compact -> retention lifecycle.\n *\n * Each row closes in its own transaction, so a tick/retry overlap is idempotent (the guarded\n * `markStreamClosed` lets only one close win) and one failed row is logged and skipped instead\n * of aborting the batch.\n */\nexport async function reapStaleOpenStreams(\n params: ReapStaleOpenStreamsParams,\n): Promise<ReapStaleOpenStreamsResult> {\n const stale = await listStaleOpenStreams({\n olderThanSeconds: params.olderThanSeconds,\n limit: params.batchLimit,\n });\n\n const result: ReapStaleOpenStreamsResult = {reaped: 0, failed: 0};\n for (const stream of stale) {\n try {\n const closed = await db().transaction((tx) =>\n closeStream(tx, {streamId: stream.id, reason: 'timeout'}),\n );\n if (closed) {\n result.reaped += 1;\n recordAppendedCount.add(1, {kind: 'runner_lost'});\n streamClosedCount.add(1, {reason: 'timeout'});\n }\n } catch (error) {\n result.failed += 1;\n logger().error({err: error, streamId: stream.id}, 'Failed to reap stale open log stream');\n }\n }\n\n return result;\n}\n"],"names":["logger","db","listStaleOpenStreams","recordAppendedCount","streamClosedCount","closeStream","reapStaleOpenStreams","params","stale","olderThanSeconds","limit","batchLimit","result","reaped","failed","stream","closed","transaction","tx","streamId","id","reason","add","kind","error","err"],"mappings":"AAAA,SAAQA,MAAM,QAAO,8BAA8B;AACnD,SAAQC,EAAE,QAAO,YAAY;AAC7B,SAAQC,oBAAoB,QAAO,iBAAiB;AACpD,SAAQC,mBAAmB,EAAEC,iBAAiB,QAAO,uBAAuB;AAC5E,SAAQC,WAAW,QAAO,oBAAoB;AAmB9C;;;;;;;;;CASC,GACD,OAAO,eAAeC,qBACpBC,MAAkC;IAElC,MAAMC,QAAQ,MAAMN,qBAAqB;QACvCO,kBAAkBF,OAAOE,gBAAgB;QACzCC,OAAOH,OAAOI,UAAU;IAC1B;IAEA,MAAMC,SAAqC;QAACC,QAAQ;QAAGC,QAAQ;IAAC;IAChE,KAAK,MAAMC,UAAUP,MAAO;QAC1B,IAAI;YACF,MAAMQ,SAAS,MAAMf,KAAKgB,WAAW,CAAC,CAACC,KACrCb,YAAYa,IAAI;oBAACC,UAAUJ,OAAOK,EAAE;oBAAEC,QAAQ;gBAAS;YAEzD,IAAIL,QAAQ;gBACVJ,OAAOC,MAAM,IAAI;gBACjBV,oBAAoBmB,GAAG,CAAC,GAAG;oBAACC,MAAM;gBAAa;gBAC/CnB,kBAAkBkB,GAAG,CAAC,GAAG;oBAACD,QAAQ;gBAAS;YAC7C;QACF,EAAE,OAAOG,OAAO;YACdZ,OAAOE,MAAM,IAAI;YACjBd,SAASwB,KAAK,CAAC;gBAACC,KAAKD;gBAAOL,UAAUJ,OAAOK,EAAE;YAAA,GAAG;QACpD;IACF;IAEA,OAAOR;AACT"}
@@ -0,0 +1,32 @@
1
+ export interface RetentionSweepResult {
2
+ /** Streams whose objects and row were deleted. */
3
+ deleted: number;
4
+ /** Streams skipped because compaction changed `object_key` after we read it (retried next run). */
5
+ raced: number;
6
+ /** Streams whose object cleanup or guarded row delete threw; logged, skipped, and retried next run. */
7
+ failed: number;
8
+ accountingPruned: number;
9
+ iterations: number;
10
+ /** True when the sweep stopped on its wall-clock budget with backlog likely remaining. */
11
+ timedOut: boolean;
12
+ }
13
+ export interface RunRetentionSweepParams {
14
+ retentionDays: number;
15
+ batchLimit: number;
16
+ timeBudgetMs: number;
17
+ maxIterations: number;
18
+ /** Wall clock; injectable so tests can drive the time budget deterministically. */
19
+ now?: () => number;
20
+ /** Liveness signal (e.g. the activity heartbeat); invoked once per processed stream. */
21
+ onProgress?: () => void;
22
+ }
23
+ /**
24
+ * Deletes expired closed streams and prunes accounting for emptied jobs.
25
+ *
26
+ * The loop self-bounds because a Temporal `startToCloseTimeout` marks the activity failed but
27
+ * does not stop already-running JS. Objects are deleted before rows so a cleanup failure leaves
28
+ * the row discoverable for the next sweep; the row delete stays guarded on the observed
29
+ * `object_key`, so a racing compaction publish is re-read before the row is removed.
30
+ */
31
+ export declare function runRetentionSweep(params: RunRetentionSweepParams): Promise<RetentionSweepResult>;
32
+ //# sourceMappingURL=retention.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retention.d.ts","sourceRoot":"","sources":["../../src/core/retention.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,oBAAoB;IACnC,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,mGAAmG;IACnG,KAAK,EAAE,MAAM,CAAC;IACd,uGAAuG;IACvG,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,0FAA0F;IAC1F,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,uBAAuB;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,mFAAmF;IACnF,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,wFAAwF;IACxF,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AA0BD;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAAC,oBAAoB,CAAC,CA+G/B"}
@@ -0,0 +1,148 @@
1
+ import { logger } from '@shipfox/node-opentelemetry';
2
+ import { deleteObjectsByPrefix } from '#api/object-storage.js';
3
+ import { config } from '#config.js';
4
+ import { logObjectKey } from '#core/entities/log-object.js';
5
+ import { deleteJobAccounting } from '#db/accounting.js';
6
+ import { db } from '#db/db.js';
7
+ import { accountingHasNoStreams, deleteExpiredStream, getAttemptStreamById, listExpiredClosedStreams } from '#db/streams.js';
8
+ function deleteExpiredStreamObjects(stream) {
9
+ return deleteObjectsByPrefix(`${logObjectKey(config.LOG_STORAGE_S3_PREFIX, stream)}/`);
10
+ }
11
+ function deleteExpiredStreamRow(params) {
12
+ return db().transaction(async (tx)=>{
13
+ const { deleted, jobId } = await deleteExpiredStream(tx, {
14
+ streamId: params.stream.id,
15
+ observedObjectKey: params.stream.objectKey
16
+ });
17
+ if (deleted && jobId && await accountingHasNoStreams(tx, jobId)) {
18
+ const pruned = await deleteJobAccounting(tx, {
19
+ jobId,
20
+ retentionDays: params.retentionDays
21
+ });
22
+ return {
23
+ deleted,
24
+ prunedAccounting: pruned.deleted
25
+ };
26
+ }
27
+ return {
28
+ deleted,
29
+ prunedAccounting: false
30
+ };
31
+ });
32
+ }
33
+ /**
34
+ * Deletes expired closed streams and prunes accounting for emptied jobs.
35
+ *
36
+ * The loop self-bounds because a Temporal `startToCloseTimeout` marks the activity failed but
37
+ * does not stop already-running JS. Objects are deleted before rows so a cleanup failure leaves
38
+ * the row discoverable for the next sweep; the row delete stays guarded on the observed
39
+ * `object_key`, so a racing compaction publish is re-read before the row is removed.
40
+ */ export async function runRetentionSweep(params) {
41
+ const now = params.now ?? Date.now;
42
+ const deadline = now() + params.timeBudgetMs;
43
+ const result = {
44
+ deleted: 0,
45
+ raced: 0,
46
+ failed: 0,
47
+ accountingPruned: 0,
48
+ iterations: 0,
49
+ timedOut: false
50
+ };
51
+ // Failed or raced rows retry next run; skipping them here keeps the rest of the backlog moving.
52
+ const skip = new Set();
53
+ while(result.iterations < params.maxIterations){
54
+ if (now() >= deadline) {
55
+ result.timedOut = true;
56
+ break;
57
+ }
58
+ const batch = await listExpiredClosedStreams({
59
+ retentionDays: params.retentionDays,
60
+ limit: params.batchLimit,
61
+ excludeIds: skip.size > 0 ? [
62
+ ...skip
63
+ ] : undefined
64
+ });
65
+ if (batch.length === 0) break;
66
+ let timedOutMidBatch = false;
67
+ for (const stream of batch){
68
+ // Per-stream checks keep long batches within the heartbeat and wall-clock budgets.
69
+ params.onProgress?.();
70
+ if (now() >= deadline) {
71
+ result.timedOut = true;
72
+ timedOutMidBatch = true;
73
+ break;
74
+ }
75
+ try {
76
+ await deleteExpiredStreamObjects(stream);
77
+ } catch (error) {
78
+ result.failed += 1;
79
+ skip.add(stream.id);
80
+ logger().error({
81
+ err: error,
82
+ streamId: stream.id
83
+ }, 'Failed to delete expired log stream objects');
84
+ continue;
85
+ }
86
+ let outcome;
87
+ try {
88
+ outcome = await deleteExpiredStreamRow({
89
+ stream,
90
+ retentionDays: params.retentionDays
91
+ });
92
+ } catch (error) {
93
+ result.failed += 1;
94
+ skip.add(stream.id);
95
+ logger().error({
96
+ err: error,
97
+ streamId: stream.id
98
+ }, 'Failed to delete expired log stream row');
99
+ continue;
100
+ }
101
+ if (!outcome.deleted) {
102
+ let current;
103
+ try {
104
+ current = await getAttemptStreamById(stream.id);
105
+ } catch (error) {
106
+ result.failed += 1;
107
+ skip.add(stream.id);
108
+ logger().error({
109
+ err: error,
110
+ streamId: stream.id
111
+ }, 'Failed to reload raced expired log stream');
112
+ continue;
113
+ }
114
+ if (current) {
115
+ try {
116
+ await deleteExpiredStreamObjects(current);
117
+ outcome = await deleteExpiredStreamRow({
118
+ stream: current,
119
+ retentionDays: params.retentionDays
120
+ });
121
+ } catch (error) {
122
+ result.failed += 1;
123
+ skip.add(stream.id);
124
+ logger().error({
125
+ err: error,
126
+ streamId: stream.id
127
+ }, 'Failed to delete raced expired log stream');
128
+ continue;
129
+ }
130
+ }
131
+ if (!current || !outcome.deleted) {
132
+ // `object_key` changed again or the row disappeared; the next sweep will re-read it.
133
+ result.raced += 1;
134
+ skip.add(stream.id);
135
+ continue;
136
+ }
137
+ }
138
+ result.deleted += 1;
139
+ if (outcome.prunedAccounting) result.accountingPruned += 1;
140
+ }
141
+ if (timedOutMidBatch) break;
142
+ result.iterations += 1;
143
+ if (batch.length < params.batchLimit) break;
144
+ }
145
+ return result;
146
+ }
147
+
148
+ //# sourceMappingURL=retention.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/retention.ts"],"sourcesContent":["import {logger} from '@shipfox/node-opentelemetry';\nimport {deleteObjectsByPrefix} from '#api/object-storage.js';\nimport {config} from '#config.js';\nimport type {AttemptStream} from '#core/entities/attempt-stream.js';\nimport {logObjectKey} from '#core/entities/log-object.js';\nimport {deleteJobAccounting} from '#db/accounting.js';\nimport {db} from '#db/db.js';\nimport {\n accountingHasNoStreams,\n deleteExpiredStream,\n getAttemptStreamById,\n listExpiredClosedStreams,\n} from '#db/streams.js';\n\nexport interface RetentionSweepResult {\n /** Streams whose objects and row were deleted. */\n deleted: number;\n /** Streams skipped because compaction changed `object_key` after we read it (retried next run). */\n raced: number;\n /** Streams whose object cleanup or guarded row delete threw; logged, skipped, and retried next run. */\n failed: number;\n accountingPruned: number;\n iterations: number;\n /** True when the sweep stopped on its wall-clock budget with backlog likely remaining. */\n timedOut: boolean;\n}\n\nexport interface RunRetentionSweepParams {\n retentionDays: number;\n batchLimit: number;\n timeBudgetMs: number;\n maxIterations: number;\n /** Wall clock; injectable so tests can drive the time budget deterministically. */\n now?: () => number;\n /** Liveness signal (e.g. the activity heartbeat); invoked once per processed stream. */\n onProgress?: () => void;\n}\n\nfunction deleteExpiredStreamObjects(stream: AttemptStream): Promise<void> {\n return deleteObjectsByPrefix(`${logObjectKey(config.LOG_STORAGE_S3_PREFIX, stream)}/`);\n}\n\nfunction deleteExpiredStreamRow(params: {\n stream: AttemptStream;\n retentionDays: number;\n}): Promise<{deleted: boolean; prunedAccounting: boolean}> {\n return db().transaction(async (tx) => {\n const {deleted, jobId} = await deleteExpiredStream(tx, {\n streamId: params.stream.id,\n observedObjectKey: params.stream.objectKey,\n });\n if (deleted && jobId && (await accountingHasNoStreams(tx, jobId))) {\n const pruned = await deleteJobAccounting(tx, {\n jobId,\n retentionDays: params.retentionDays,\n });\n return {deleted, prunedAccounting: pruned.deleted};\n }\n return {deleted, prunedAccounting: false};\n });\n}\n\n/**\n * Deletes expired closed streams and prunes accounting for emptied jobs.\n *\n * The loop self-bounds because a Temporal `startToCloseTimeout` marks the activity failed but\n * does not stop already-running JS. Objects are deleted before rows so a cleanup failure leaves\n * the row discoverable for the next sweep; the row delete stays guarded on the observed\n * `object_key`, so a racing compaction publish is re-read before the row is removed.\n */\nexport async function runRetentionSweep(\n params: RunRetentionSweepParams,\n): Promise<RetentionSweepResult> {\n const now = params.now ?? Date.now;\n const deadline = now() + params.timeBudgetMs;\n const result: RetentionSweepResult = {\n deleted: 0,\n raced: 0,\n failed: 0,\n accountingPruned: 0,\n iterations: 0,\n timedOut: false,\n };\n\n // Failed or raced rows retry next run; skipping them here keeps the rest of the backlog moving.\n const skip = new Set<string>();\n while (result.iterations < params.maxIterations) {\n if (now() >= deadline) {\n result.timedOut = true;\n break;\n }\n\n const batch = await listExpiredClosedStreams({\n retentionDays: params.retentionDays,\n limit: params.batchLimit,\n excludeIds: skip.size > 0 ? [...skip] : undefined,\n });\n if (batch.length === 0) break;\n\n let timedOutMidBatch = false;\n for (const stream of batch) {\n // Per-stream checks keep long batches within the heartbeat and wall-clock budgets.\n params.onProgress?.();\n if (now() >= deadline) {\n result.timedOut = true;\n timedOutMidBatch = true;\n break;\n }\n\n try {\n await deleteExpiredStreamObjects(stream);\n } catch (error) {\n result.failed += 1;\n skip.add(stream.id);\n logger().error(\n {err: error, streamId: stream.id},\n 'Failed to delete expired log stream objects',\n );\n continue;\n }\n\n let outcome: {deleted: boolean; prunedAccounting: boolean};\n try {\n outcome = await deleteExpiredStreamRow({stream, retentionDays: params.retentionDays});\n } catch (error) {\n result.failed += 1;\n skip.add(stream.id);\n logger().error(\n {err: error, streamId: stream.id},\n 'Failed to delete expired log stream row',\n );\n continue;\n }\n\n if (!outcome.deleted) {\n let current: AttemptStream | null;\n try {\n current = await getAttemptStreamById(stream.id);\n } catch (error) {\n result.failed += 1;\n skip.add(stream.id);\n logger().error(\n {err: error, streamId: stream.id},\n 'Failed to reload raced expired log stream',\n );\n continue;\n }\n if (current) {\n try {\n await deleteExpiredStreamObjects(current);\n outcome = await deleteExpiredStreamRow({\n stream: current,\n retentionDays: params.retentionDays,\n });\n } catch (error) {\n result.failed += 1;\n skip.add(stream.id);\n logger().error(\n {err: error, streamId: stream.id},\n 'Failed to delete raced expired log stream',\n );\n continue;\n }\n }\n if (!current || !outcome.deleted) {\n // `object_key` changed again or the row disappeared; the next sweep will re-read it.\n result.raced += 1;\n skip.add(stream.id);\n continue;\n }\n }\n\n result.deleted += 1;\n if (outcome.prunedAccounting) result.accountingPruned += 1;\n }\n\n if (timedOutMidBatch) break;\n\n result.iterations += 1;\n if (batch.length < params.batchLimit) break;\n }\n\n return result;\n}\n"],"names":["logger","deleteObjectsByPrefix","config","logObjectKey","deleteJobAccounting","db","accountingHasNoStreams","deleteExpiredStream","getAttemptStreamById","listExpiredClosedStreams","deleteExpiredStreamObjects","stream","LOG_STORAGE_S3_PREFIX","deleteExpiredStreamRow","params","transaction","tx","deleted","jobId","streamId","id","observedObjectKey","objectKey","pruned","retentionDays","prunedAccounting","runRetentionSweep","now","Date","deadline","timeBudgetMs","result","raced","failed","accountingPruned","iterations","timedOut","skip","Set","maxIterations","batch","limit","batchLimit","excludeIds","size","undefined","length","timedOutMidBatch","onProgress","error","add","err","outcome","current"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { SessionViewLifecycleRow, SessionViewRow } from '@shipfox/api-logs-dto';
2
+ export declare function systemRow(timestamp: number, message: Record<string, unknown>): SessionViewLifecycleRow;
3
+ export declare function assistantRows(timestamp: number, message: Record<string, unknown>): readonly SessionViewRow[];
4
+ export declare function userRows(timestamp: number, message: Record<string, unknown>): readonly SessionViewRow[];
5
+ export declare function resultRow(timestamp: number, message: Record<string, unknown>): SessionViewLifecycleRow;
6
+ //# sourceMappingURL=rows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rows.d.ts","sourceRoot":"","sources":["../../../../src/core/session/claude/rows.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,cAAc,EAIf,MAAM,uBAAuB,CAAC;AAc/B,wBAAgB,SAAS,CACvB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,uBAAuB,CAczB;AAED,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,SAAS,cAAc,EAAE,CA6C3B;AAED,wBAAgB,QAAQ,CACtB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,SAAS,cAAc,EAAE,CA4B3B;AAED,wBAAgB,SAAS,CACvB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,uBAAuB,CAwBzB"}
@@ -0,0 +1,145 @@
1
+ import { asLooseObject } from '../entry-schema.js';
2
+ import { booleanField, field, formatNumber, isMeta, metaItem, stringField, stringifyValue, toJson } from '../object.js';
3
+ import { lifecycleRow, messageRow, thinkingRow } from '../rows.js';
4
+ export function systemRow(timestamp, message) {
5
+ const subtype = stringField(message, 'subtype');
6
+ const label = subtype === 'init' || message.type === 'init' ? 'Session started' : 'Session event';
7
+ const detail = stringField(message, 'session_id') ?? stringField(message, 'sessionId') ?? null;
8
+ const meta = [
9
+ metaItem('cwd', stringField(message, 'cwd'), false),
10
+ metaItem('model', stringField(message, 'model')),
11
+ metaItem('permission', stringField(message, 'permissionMode') ?? stringField(message, 'permission_mode'))
12
+ ].filter(isMeta);
13
+ return lifecycleRow(timestamp, label, detail, 'default', false, meta);
14
+ }
15
+ export function assistantRows(timestamp, message) {
16
+ const sdkMessage = asLooseObject(message.message) ?? message;
17
+ const rows = [];
18
+ const textParts = [];
19
+ const thinkingParts = [];
20
+ const pushText = ()=>{
21
+ if (textParts.length === 0) return;
22
+ rows.push(messageRow(timestamp, 'assistant', 'assistant', textParts.join('\n\n'), false));
23
+ textParts.length = 0;
24
+ };
25
+ const pushThinking = ()=>{
26
+ if (thinkingParts.length === 0) return;
27
+ rows.push(thinkingRow(timestamp, thinkingParts.join('\n\n')));
28
+ thinkingParts.length = 0;
29
+ };
30
+ for (const block of contentBlocks(sdkMessage)){
31
+ const type = stringField(block, 'type');
32
+ if (type === 'tool_use' || type === 'tool-use' || type === 'toolCall') {
33
+ pushText();
34
+ pushThinking();
35
+ rows.push(toolCallRow(timestamp, block));
36
+ continue;
37
+ }
38
+ if (type === 'thinking' || type === 'reasoning') {
39
+ pushText();
40
+ const text = blockText(block);
41
+ if (text) thinkingParts.push(text);
42
+ continue;
43
+ }
44
+ pushThinking();
45
+ const text = blockText(block);
46
+ if (text) textParts.push(text);
47
+ }
48
+ pushText();
49
+ pushThinking();
50
+ if (rows.length > 0) return rows;
51
+ const text = stringField(sdkMessage, 'content') ?? stringField(message, 'result');
52
+ return [
53
+ messageRow(timestamp, 'assistant', 'assistant', text ?? toJson(message), false)
54
+ ];
55
+ }
56
+ export function userRows(timestamp, message) {
57
+ const sdkMessage = asLooseObject(message.message) ?? message;
58
+ const rows = [];
59
+ const textParts = [];
60
+ const pushText = ()=>{
61
+ if (textParts.length === 0) return;
62
+ rows.push(messageRow(timestamp, 'user', 'user', textParts.join('\n\n'), false));
63
+ textParts.length = 0;
64
+ };
65
+ for (const block of contentBlocks(sdkMessage)){
66
+ const type = stringField(block, 'type');
67
+ if (type === 'tool_result' || type === 'tool-result') {
68
+ pushText();
69
+ rows.push(toolResultRow(timestamp, block));
70
+ continue;
71
+ }
72
+ const text = blockText(block);
73
+ if (text) textParts.push(text);
74
+ }
75
+ pushText();
76
+ if (rows.length > 0) return rows;
77
+ const content = stringField(sdkMessage, 'content');
78
+ return [
79
+ messageRow(timestamp, 'user', 'user', content ?? toJson(message), false)
80
+ ];
81
+ }
82
+ export function resultRow(timestamp, message) {
83
+ const isError = booleanField(message, 'is_error') || booleanField(message, 'isError');
84
+ const subtype = stringField(message, 'subtype');
85
+ const terminalFailure = isError || subtype === 'error';
86
+ const detail = stringField(message, 'result') ?? stringField(message, 'error') ?? stringField(message, 'message') ?? null;
87
+ const meta = [
88
+ numberMeta(message, 'duration_ms', 'duration', 'ms'),
89
+ numberMeta(message, 'duration_api_ms', 'api duration', 'ms'),
90
+ numberMeta(message, 'num_turns', 'turns'),
91
+ costMeta(message)
92
+ ].filter(isMeta);
93
+ return lifecycleRow(timestamp, terminalFailure ? 'Session failed' : 'Session completed', detail, terminalFailure ? 'error' : 'default', terminalFailure, meta);
94
+ }
95
+ function contentBlocks(message) {
96
+ const content = message.content;
97
+ if (!Array.isArray(content)) return [];
98
+ return content.flatMap((block)=>{
99
+ const object = asLooseObject(block);
100
+ return object ? [
101
+ object
102
+ ] : [];
103
+ });
104
+ }
105
+ function toolCallRow(timestamp, block) {
106
+ return {
107
+ kind: 'tool-call',
108
+ timestamp,
109
+ id: stringField(block, 'id') ?? null,
110
+ name: stringField(block, 'name') ?? 'tool',
111
+ input: stringifyValue(field(block, 'input') ?? {})
112
+ };
113
+ }
114
+ function toolResultRow(timestamp, block) {
115
+ return {
116
+ kind: 'tool-result',
117
+ timestamp,
118
+ toolCallId: stringField(block, 'tool_use_id') ?? stringField(block, 'toolUseId') ?? null,
119
+ toolName: stringField(block, 'name') ?? 'tool',
120
+ output: blockText(block) || stringifyValue(field(block, 'content') ?? ''),
121
+ isError: booleanField(block, 'is_error') || booleanField(block, 'isError')
122
+ };
123
+ }
124
+ function blockText(block) {
125
+ const content = field(block, 'content');
126
+ if (Array.isArray(content)) {
127
+ return content.map((item)=>{
128
+ const object = asLooseObject(item);
129
+ return object ? blockText(object) : typeof item === 'string' ? item : '';
130
+ }).filter(Boolean).join('\n\n');
131
+ }
132
+ return stringField(block, 'text') ?? stringField(block, 'thinking') ?? (typeof content === 'string' ? content : '') ?? '';
133
+ }
134
+ function numberMeta(value, key, label, unit) {
135
+ const raw = field(value, key);
136
+ if (typeof raw !== 'number' || !Number.isFinite(raw)) return null;
137
+ return metaItem(label, unit ? `${formatNumber(raw)} ${unit}` : formatNumber(raw));
138
+ }
139
+ function costMeta(message) {
140
+ const value = field(message, 'total_cost_usd') ?? field(message, 'totalCostUsd');
141
+ if (typeof value !== 'number' || !Number.isFinite(value)) return null;
142
+ return metaItem('cost', `$${value.toFixed(value < 0.01 ? 4 : 2)}`);
143
+ }
144
+
145
+ //# sourceMappingURL=rows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/core/session/claude/rows.ts"],"sourcesContent":["import type {\n SessionViewLifecycleRow,\n SessionViewRow,\n SessionViewRowMeta,\n SessionViewToolCallRow,\n SessionViewToolResultRow,\n} from '@shipfox/api-logs-dto';\nimport {asLooseObject} from '../entry-schema.js';\nimport {\n booleanField,\n field,\n formatNumber,\n isMeta,\n metaItem,\n stringField,\n stringifyValue,\n toJson,\n} from '../object.js';\nimport {lifecycleRow, messageRow, thinkingRow} from '../rows.js';\n\nexport function systemRow(\n timestamp: number,\n message: Record<string, unknown>,\n): SessionViewLifecycleRow {\n const subtype = stringField(message, 'subtype');\n const label = subtype === 'init' || message.type === 'init' ? 'Session started' : 'Session event';\n const detail = stringField(message, 'session_id') ?? stringField(message, 'sessionId') ?? null;\n const meta = [\n metaItem('cwd', stringField(message, 'cwd'), false),\n metaItem('model', stringField(message, 'model')),\n metaItem(\n 'permission',\n stringField(message, 'permissionMode') ?? stringField(message, 'permission_mode'),\n ),\n ].filter(isMeta);\n\n return lifecycleRow(timestamp, label, detail, 'default', false, meta);\n}\n\nexport function assistantRows(\n timestamp: number,\n message: Record<string, unknown>,\n): readonly SessionViewRow[] {\n const sdkMessage = asLooseObject(message.message) ?? message;\n const rows: SessionViewRow[] = [];\n const textParts: string[] = [];\n const thinkingParts: string[] = [];\n\n const pushText = () => {\n if (textParts.length === 0) return;\n rows.push(messageRow(timestamp, 'assistant', 'assistant', textParts.join('\\n\\n'), false));\n textParts.length = 0;\n };\n const pushThinking = () => {\n if (thinkingParts.length === 0) return;\n rows.push(thinkingRow(timestamp, thinkingParts.join('\\n\\n')));\n thinkingParts.length = 0;\n };\n\n for (const block of contentBlocks(sdkMessage)) {\n const type = stringField(block, 'type');\n if (type === 'tool_use' || type === 'tool-use' || type === 'toolCall') {\n pushText();\n pushThinking();\n rows.push(toolCallRow(timestamp, block));\n continue;\n }\n\n if (type === 'thinking' || type === 'reasoning') {\n pushText();\n const text = blockText(block);\n if (text) thinkingParts.push(text);\n continue;\n }\n\n pushThinking();\n const text = blockText(block);\n if (text) textParts.push(text);\n }\n\n pushText();\n pushThinking();\n\n if (rows.length > 0) return rows;\n\n const text = stringField(sdkMessage, 'content') ?? stringField(message, 'result');\n return [messageRow(timestamp, 'assistant', 'assistant', text ?? toJson(message), false)];\n}\n\nexport function userRows(\n timestamp: number,\n message: Record<string, unknown>,\n): readonly SessionViewRow[] {\n const sdkMessage = asLooseObject(message.message) ?? message;\n const rows: SessionViewRow[] = [];\n const textParts: string[] = [];\n const pushText = () => {\n if (textParts.length === 0) return;\n rows.push(messageRow(timestamp, 'user', 'user', textParts.join('\\n\\n'), false));\n textParts.length = 0;\n };\n\n for (const block of contentBlocks(sdkMessage)) {\n const type = stringField(block, 'type');\n if (type === 'tool_result' || type === 'tool-result') {\n pushText();\n rows.push(toolResultRow(timestamp, block));\n continue;\n }\n\n const text = blockText(block);\n if (text) textParts.push(text);\n }\n\n pushText();\n\n if (rows.length > 0) return rows;\n\n const content = stringField(sdkMessage, 'content');\n return [messageRow(timestamp, 'user', 'user', content ?? toJson(message), false)];\n}\n\nexport function resultRow(\n timestamp: number,\n message: Record<string, unknown>,\n): SessionViewLifecycleRow {\n const isError = booleanField(message, 'is_error') || booleanField(message, 'isError');\n const subtype = stringField(message, 'subtype');\n const terminalFailure = isError || subtype === 'error';\n const detail =\n stringField(message, 'result') ??\n stringField(message, 'error') ??\n stringField(message, 'message') ??\n null;\n const meta = [\n numberMeta(message, 'duration_ms', 'duration', 'ms'),\n numberMeta(message, 'duration_api_ms', 'api duration', 'ms'),\n numberMeta(message, 'num_turns', 'turns'),\n costMeta(message),\n ].filter(isMeta);\n\n return lifecycleRow(\n timestamp,\n terminalFailure ? 'Session failed' : 'Session completed',\n detail,\n terminalFailure ? 'error' : 'default',\n terminalFailure,\n meta,\n );\n}\n\nfunction contentBlocks(message: Record<string, unknown>): Record<string, unknown>[] {\n const content = message.content;\n if (!Array.isArray(content)) return [];\n\n return content.flatMap((block) => {\n const object = asLooseObject(block);\n return object ? [object] : [];\n });\n}\n\nfunction toolCallRow(timestamp: number, block: Record<string, unknown>): SessionViewToolCallRow {\n return {\n kind: 'tool-call',\n timestamp,\n id: stringField(block, 'id') ?? null,\n name: stringField(block, 'name') ?? 'tool',\n input: stringifyValue(field(block, 'input') ?? {}),\n };\n}\n\nfunction toolResultRow(\n timestamp: number,\n block: Record<string, unknown>,\n): SessionViewToolResultRow {\n return {\n kind: 'tool-result',\n timestamp,\n toolCallId: stringField(block, 'tool_use_id') ?? stringField(block, 'toolUseId') ?? null,\n toolName: stringField(block, 'name') ?? 'tool',\n output: blockText(block) || stringifyValue(field(block, 'content') ?? ''),\n isError: booleanField(block, 'is_error') || booleanField(block, 'isError'),\n };\n}\n\nfunction blockText(block: Record<string, unknown>): string {\n const content = field(block, 'content');\n if (Array.isArray(content)) {\n return content\n .map((item) => {\n const object = asLooseObject(item);\n return object ? blockText(object) : typeof item === 'string' ? item : '';\n })\n .filter(Boolean)\n .join('\\n\\n');\n }\n\n return (\n stringField(block, 'text') ??\n stringField(block, 'thinking') ??\n (typeof content === 'string' ? content : '') ??\n ''\n );\n}\n\nfunction numberMeta(\n value: unknown,\n key: string,\n label: string,\n unit?: string,\n): SessionViewRowMeta | null {\n const raw = field(value, key);\n if (typeof raw !== 'number' || !Number.isFinite(raw)) return null;\n\n return metaItem(label, unit ? `${formatNumber(raw)} ${unit}` : formatNumber(raw));\n}\n\nfunction costMeta(message: Record<string, unknown>): SessionViewRowMeta | null {\n const value = field(message, 'total_cost_usd') ?? field(message, 'totalCostUsd');\n if (typeof value !== 'number' || !Number.isFinite(value)) return null;\n\n return metaItem('cost', `$${value.toFixed(value < 0.01 ? 4 : 2)}`);\n}\n"],"names":["asLooseObject","booleanField","field","formatNumber","isMeta","metaItem","stringField","stringifyValue","toJson","lifecycleRow","messageRow","thinkingRow","systemRow","timestamp","message","subtype","label","type","detail","meta","filter","assistantRows","sdkMessage","rows","textParts","thinkingParts","pushText","length","push","join","pushThinking","block","contentBlocks","toolCallRow","text","blockText","userRows","toolResultRow","content","resultRow","isError","terminalFailure","numberMeta","costMeta","Array","isArray","flatMap","object","kind","id","name","input","toolCallId","toolName","output","map","item","Boolean","value","key","unit","raw","Number","isFinite","toFixed"],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { SessionViewRow } from '@shipfox/api-logs-dto';
2
+ import type { AgentSessionRecord } from './session-record.js';
3
+ export declare function parseClaudeSessionRecord(record: AgentSessionRecord): readonly SessionViewRow[];
4
+ //# sourceMappingURL=claude-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-parser.d.ts","sourceRoot":"","sources":["../../../src/core/session/claude-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAI1D,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,qBAAqB,CAAC;AAQ5D,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,kBAAkB,GAAG,SAAS,cAAc,EAAE,CAyB9F"}
@@ -0,0 +1,42 @@
1
+ import { z } from 'zod';
2
+ import { assistantRows, resultRow, systemRow, userRows } from './claude/rows.js';
3
+ import { rawRecordRow } from './rows.js';
4
+ const claudeMessageSchema = z.object({
5
+ type: z.string().min(1)
6
+ }).catchall(z.unknown());
7
+ export function parseClaudeSessionRecord(record) {
8
+ let json;
9
+ try {
10
+ json = JSON.parse(record.data);
11
+ } catch {
12
+ return [
13
+ rawRecordRow(record, 'Malformed session entry')
14
+ ];
15
+ }
16
+ const parsed = claudeMessageSchema.safeParse(json);
17
+ if (!parsed.success) return [
18
+ rawRecordRow(record, 'Unsupported Claude message')
19
+ ];
20
+ const message = parsed.data;
21
+ switch(message.type){
22
+ case 'system':
23
+ case 'init':
24
+ return [
25
+ systemRow(record.ts, message)
26
+ ];
27
+ case 'assistant':
28
+ return assistantRows(record.ts, message);
29
+ case 'user':
30
+ return userRows(record.ts, message);
31
+ case 'result':
32
+ return [
33
+ resultRow(record.ts, message)
34
+ ];
35
+ default:
36
+ return [
37
+ rawRecordRow(record, `Unknown Claude message: ${message.type}`)
38
+ ];
39
+ }
40
+ }
41
+
42
+ //# sourceMappingURL=claude-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/core/session/claude-parser.ts"],"sourcesContent":["import type {SessionViewRow} from '@shipfox/api-logs-dto';\nimport {z} from 'zod';\nimport {assistantRows, resultRow, systemRow, userRows} from './claude/rows.js';\nimport {rawRecordRow} from './rows.js';\nimport type {AgentSessionRecord} from './session-record.js';\n\nconst claudeMessageSchema = z\n .object({\n type: z.string().min(1),\n })\n .catchall(z.unknown());\n\nexport function parseClaudeSessionRecord(record: AgentSessionRecord): readonly SessionViewRow[] {\n let json: unknown;\n try {\n json = JSON.parse(record.data);\n } catch {\n return [rawRecordRow(record, 'Malformed session entry')];\n }\n\n const parsed = claudeMessageSchema.safeParse(json);\n if (!parsed.success) return [rawRecordRow(record, 'Unsupported Claude message')];\n\n const message = parsed.data;\n switch (message.type) {\n case 'system':\n case 'init':\n return [systemRow(record.ts, message)];\n case 'assistant':\n return assistantRows(record.ts, message);\n case 'user':\n return userRows(record.ts, message);\n case 'result':\n return [resultRow(record.ts, message)];\n default:\n return [rawRecordRow(record, `Unknown Claude message: ${message.type}`)];\n }\n}\n"],"names":["z","assistantRows","resultRow","systemRow","userRows","rawRecordRow","claudeMessageSchema","object","type","string","min","catchall","unknown","parseClaudeSessionRecord","record","json","JSON","parse","data","parsed","safeParse","success","message","ts"],"mappings":"AACA,SAAQA,CAAC,QAAO,MAAM;AACtB,SAAQC,aAAa,EAAEC,SAAS,EAAEC,SAAS,EAAEC,QAAQ,QAAO,mBAAmB;AAC/E,SAAQC,YAAY,QAAO,YAAY;AAGvC,MAAMC,sBAAsBN,EACzBO,MAAM,CAAC;IACNC,MAAMR,EAAES,MAAM,GAAGC,GAAG,CAAC;AACvB,GACCC,QAAQ,CAACX,EAAEY,OAAO;AAErB,OAAO,SAASC,yBAAyBC,MAA0B;IACjE,IAAIC;IACJ,IAAI;QACFA,OAAOC,KAAKC,KAAK,CAACH,OAAOI,IAAI;IAC/B,EAAE,OAAM;QACN,OAAO;YAACb,aAAaS,QAAQ;SAA2B;IAC1D;IAEA,MAAMK,SAASb,oBAAoBc,SAAS,CAACL;IAC7C,IAAI,CAACI,OAAOE,OAAO,EAAE,OAAO;QAAChB,aAAaS,QAAQ;KAA8B;IAEhF,MAAMQ,UAAUH,OAAOD,IAAI;IAC3B,OAAQI,QAAQd,IAAI;QAClB,KAAK;QACL,KAAK;YACH,OAAO;gBAACL,UAAUW,OAAOS,EAAE,EAAED;aAAS;QACxC,KAAK;YACH,OAAOrB,cAAca,OAAOS,EAAE,EAAED;QAClC,KAAK;YACH,OAAOlB,SAASU,OAAOS,EAAE,EAAED;QAC7B,KAAK;YACH,OAAO;gBAACpB,UAAUY,OAAOS,EAAE,EAAED;aAAS;QACxC;YACE,OAAO;gBAACjB,aAAaS,QAAQ,CAAC,wBAAwB,EAAEQ,QAAQd,IAAI,EAAE;aAAE;IAC5E;AACF"}
@@ -0,0 +1,51 @@
1
+ import { z } from 'zod';
2
+ export declare const sessionEntrySchema: z.ZodObject<{
3
+ type: z.ZodString;
4
+ }, z.core.$catchall<z.ZodUnknown>>;
5
+ export declare const agentMessageSchema: z.ZodObject<{
6
+ type: z.ZodOptional<z.ZodString>;
7
+ role: z.ZodOptional<z.ZodString>;
8
+ content: z.ZodOptional<z.ZodUnknown>;
9
+ model: z.ZodOptional<z.ZodString>;
10
+ provider: z.ZodOptional<z.ZodString>;
11
+ stopReason: z.ZodOptional<z.ZodString>;
12
+ errorMessage: z.ZodOptional<z.ZodString>;
13
+ toolCallId: z.ZodOptional<z.ZodString>;
14
+ toolName: z.ZodOptional<z.ZodString>;
15
+ isError: z.ZodOptional<z.ZodBoolean>;
16
+ }, z.core.$catchall<z.ZodUnknown>>;
17
+ export declare const sessionMessageEntrySchema: z.ZodObject<{
18
+ type: z.ZodLiteral<"message">;
19
+ message: z.ZodObject<{
20
+ type: z.ZodOptional<z.ZodString>;
21
+ role: z.ZodOptional<z.ZodString>;
22
+ content: z.ZodOptional<z.ZodUnknown>;
23
+ model: z.ZodOptional<z.ZodString>;
24
+ provider: z.ZodOptional<z.ZodString>;
25
+ stopReason: z.ZodOptional<z.ZodString>;
26
+ errorMessage: z.ZodOptional<z.ZodString>;
27
+ toolCallId: z.ZodOptional<z.ZodString>;
28
+ toolName: z.ZodOptional<z.ZodString>;
29
+ isError: z.ZodOptional<z.ZodBoolean>;
30
+ }, z.core.$catchall<z.ZodUnknown>>;
31
+ }, z.core.$catchall<z.ZodUnknown>>;
32
+ export declare const contentBlockSchema: z.ZodObject<{
33
+ type: z.ZodOptional<z.ZodString>;
34
+ }, z.core.$catchall<z.ZodUnknown>>;
35
+ export type SessionEntry = z.infer<typeof sessionEntrySchema>;
36
+ export type AgentMessage = z.infer<typeof agentMessageSchema>;
37
+ export type ContentBlock = z.infer<typeof contentBlockSchema>;
38
+ export interface ParsedSessionEntry {
39
+ ok: true;
40
+ entry: SessionEntry;
41
+ }
42
+ export interface MalformedSessionEntry {
43
+ ok: false;
44
+ raw: string;
45
+ reason: 'invalid-json' | 'invalid-entry';
46
+ }
47
+ export type SessionEntryParseResult = ParsedSessionEntry | MalformedSessionEntry;
48
+ export declare function parseSessionEntry(data: string): SessionEntryParseResult;
49
+ export declare function asLooseObject(value: unknown): Record<string, unknown> | null;
50
+ export declare function asContentBlocks(value: unknown): ContentBlock[];
51
+ //# sourceMappingURL=entry-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry-schema.d.ts","sourceRoot":"","sources":["../../../src/core/session/entry-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAItB,eAAO,MAAM,kBAAkB;;kCAIP,CAAC;AAEzB,eAAO,MAAM,kBAAkB;;;;;;;;;;;kCAaP,CAAC;AAEzB,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;kCAKd,CAAC;AAEzB,eAAO,MAAM,kBAAkB;;kCAIP,CAAC;AAEzB,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC9D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC9D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,IAAI,CAAC;IACT,KAAK,EAAE,YAAY,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,KAAK,CAAC;IACV,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,cAAc,GAAG,eAAe,CAAC;CAC1C;AAED,MAAM,MAAM,uBAAuB,GAAG,kBAAkB,GAAG,qBAAqB,CAAC;AAEjF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,uBAAuB,CAYvE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAG5E;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,EAAE,CAO9D"}
@@ -0,0 +1,61 @@
1
+ import { z } from 'zod';
2
+ const looseObjectSchema = z.object({}).catchall(z.unknown());
3
+ export const sessionEntrySchema = z.object({
4
+ type: z.string().min(1)
5
+ }).catchall(z.unknown());
6
+ export const agentMessageSchema = z.object({
7
+ type: z.string().optional(),
8
+ role: z.string().optional(),
9
+ content: z.unknown().optional(),
10
+ model: z.string().optional(),
11
+ provider: z.string().optional(),
12
+ stopReason: z.string().optional(),
13
+ errorMessage: z.string().optional(),
14
+ toolCallId: z.string().optional(),
15
+ toolName: z.string().optional(),
16
+ isError: z.boolean().optional()
17
+ }).catchall(z.unknown());
18
+ export const sessionMessageEntrySchema = z.object({
19
+ type: z.literal('message'),
20
+ message: agentMessageSchema
21
+ }).catchall(z.unknown());
22
+ export const contentBlockSchema = z.object({
23
+ type: z.string().optional()
24
+ }).catchall(z.unknown());
25
+ export function parseSessionEntry(data) {
26
+ let json;
27
+ try {
28
+ json = JSON.parse(data);
29
+ } catch {
30
+ return {
31
+ ok: false,
32
+ raw: data,
33
+ reason: 'invalid-json'
34
+ };
35
+ }
36
+ const parsed = sessionEntrySchema.safeParse(json);
37
+ if (!parsed.success) return {
38
+ ok: false,
39
+ raw: data,
40
+ reason: 'invalid-entry'
41
+ };
42
+ return {
43
+ ok: true,
44
+ entry: parsed.data
45
+ };
46
+ }
47
+ export function asLooseObject(value) {
48
+ const parsed = looseObjectSchema.safeParse(value);
49
+ return parsed.success ? parsed.data : null;
50
+ }
51
+ export function asContentBlocks(value) {
52
+ if (!Array.isArray(value)) return [];
53
+ return value.flatMap((item)=>{
54
+ const parsed = contentBlockSchema.safeParse(item);
55
+ return parsed.success ? [
56
+ parsed.data
57
+ ] : [];
58
+ });
59
+ }
60
+
61
+ //# sourceMappingURL=entry-schema.js.map