@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,2 @@
1
+ export declare const readLogsRoute: import("@shipfox/node-fastify").RouteDefinition;
2
+ //# sourceMappingURL=read-logs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-logs.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/read-logs.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,aAAa,iDA6BxB,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { requireUserContext } from '@shipfox/api-auth-context';
2
+ import { readLogsQuerySchema, readLogsResponseSchema } from '@shipfox/api-logs-dto';
3
+ import { ClientError, defineRoute } from '@shipfox/node-fastify';
4
+ import { z } from 'zod';
5
+ import { buildLogReadResult } from '#core/read-logs.js';
6
+ import { getStreamByStepAttempt } from '#db/streams.js';
7
+ import { toReadLogsDto } from '#presentation/dto/read-logs.js';
8
+ export const readLogsRoute = defineRoute({
9
+ method: 'GET',
10
+ path: '/:stepId/attempts/:attempt/logs',
11
+ description: 'Read a page of logs for a step attempt: inline NDJSON while the stream is hot, a presigned object URL once it is compacted.',
12
+ schema: {
13
+ params: z.object({
14
+ stepId: z.string().uuid(),
15
+ attempt: z.coerce.number().int().min(1)
16
+ }),
17
+ querystring: readLogsQuerySchema,
18
+ response: {
19
+ 200: readLogsResponseSchema
20
+ }
21
+ },
22
+ handler: async (request)=>{
23
+ const user = requireUserContext(request);
24
+ const { stepId, attempt } = request.params;
25
+ const { cursor } = request.query;
26
+ const stream = await getStreamByStepAttempt({
27
+ stepId,
28
+ attempt
29
+ });
30
+ // 404 covers both "no such stream" and "not your workspace" so the endpoint never
31
+ // leaks the existence of another workspace's step.
32
+ if (!stream || !user.canAccess(stream.workspaceId)) {
33
+ throw new ClientError('Logs not found', 'not-found', {
34
+ status: 404
35
+ });
36
+ }
37
+ return toReadLogsDto(await buildLogReadResult(stream, cursor));
38
+ }
39
+ });
40
+
41
+ //# sourceMappingURL=read-logs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/presentation/routes/read-logs.ts"],"sourcesContent":["import {requireUserContext} from '@shipfox/api-auth-context';\nimport {readLogsQuerySchema, readLogsResponseSchema} from '@shipfox/api-logs-dto';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {z} from 'zod';\nimport {buildLogReadResult} from '#core/read-logs.js';\nimport {getStreamByStepAttempt} from '#db/streams.js';\nimport {toReadLogsDto} from '#presentation/dto/read-logs.js';\n\nexport const readLogsRoute = defineRoute({\n method: 'GET',\n path: '/:stepId/attempts/:attempt/logs',\n description:\n 'Read a page of logs for a step attempt: inline NDJSON while the stream is hot, a presigned object URL once it is compacted.',\n schema: {\n params: z.object({\n stepId: z.string().uuid(),\n attempt: z.coerce.number().int().min(1),\n }),\n querystring: readLogsQuerySchema,\n response: {\n 200: readLogsResponseSchema,\n },\n },\n handler: async (request) => {\n const user = requireUserContext(request);\n const {stepId, attempt} = request.params;\n const {cursor} = request.query;\n\n const stream = await getStreamByStepAttempt({stepId, attempt});\n // 404 covers both \"no such stream\" and \"not your workspace\" so the endpoint never\n // leaks the existence of another workspace's step.\n if (!stream || !user.canAccess(stream.workspaceId)) {\n throw new ClientError('Logs not found', 'not-found', {status: 404});\n }\n\n return toReadLogsDto(await buildLogReadResult(stream, cursor));\n },\n});\n"],"names":["requireUserContext","readLogsQuerySchema","readLogsResponseSchema","ClientError","defineRoute","z","buildLogReadResult","getStreamByStepAttempt","toReadLogsDto","readLogsRoute","method","path","description","schema","params","object","stepId","string","uuid","attempt","coerce","number","int","min","querystring","response","handler","request","user","cursor","query","stream","canAccess","workspaceId","status"],"mappings":"AAAA,SAAQA,kBAAkB,QAAO,4BAA4B;AAC7D,SAAQC,mBAAmB,EAAEC,sBAAsB,QAAO,wBAAwB;AAClF,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SAAQC,CAAC,QAAO,MAAM;AACtB,SAAQC,kBAAkB,QAAO,qBAAqB;AACtD,SAAQC,sBAAsB,QAAO,iBAAiB;AACtD,SAAQC,aAAa,QAAO,iCAAiC;AAE7D,OAAO,MAAMC,gBAAgBL,YAAY;IACvCM,QAAQ;IACRC,MAAM;IACNC,aACE;IACFC,QAAQ;QACNC,QAAQT,EAAEU,MAAM,CAAC;YACfC,QAAQX,EAAEY,MAAM,GAAGC,IAAI;YACvBC,SAASd,EAAEe,MAAM,CAACC,MAAM,GAAGC,GAAG,GAAGC,GAAG,CAAC;QACvC;QACAC,aAAavB;QACbwB,UAAU;YACR,KAAKvB;QACP;IACF;IACAwB,SAAS,OAAOC;QACd,MAAMC,OAAO5B,mBAAmB2B;QAChC,MAAM,EAACX,MAAM,EAAEG,OAAO,EAAC,GAAGQ,QAAQb,MAAM;QACxC,MAAM,EAACe,MAAM,EAAC,GAAGF,QAAQG,KAAK;QAE9B,MAAMC,SAAS,MAAMxB,uBAAuB;YAACS;YAAQG;QAAO;QAC5D,kFAAkF;QAClF,mDAAmD;QACnD,IAAI,CAACY,UAAU,CAACH,KAAKI,SAAS,CAACD,OAAOE,WAAW,GAAG;YAClD,MAAM,IAAI9B,YAAY,kBAAkB,aAAa;gBAAC+B,QAAQ;YAAG;QACnE;QAEA,OAAO1B,cAAc,MAAMF,mBAAmByB,QAAQF;IACxD;AACF,GAAG"}
@@ -0,0 +1,8 @@
1
+ import type { WorkflowsJobTerminatedEventDto } from '@shipfox/api-workflows-dto';
2
+ /**
3
+ * A job reached a terminal state (any path: completion, cancellation, lease-expiry,
4
+ * timeout). Arm the grace-then-close workflow for any of its streams the runner never
5
+ * ended itself. Deduped by workflow id, so a redelivered event is a no-op.
6
+ */
7
+ export declare function onJobTerminated(payload: WorkflowsJobTerminatedEventDto): Promise<void>;
8
+ //# sourceMappingURL=on-job-terminated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"on-job-terminated.d.ts","sourceRoot":"","sources":["../../../src/presentation/subscribers/on-job-terminated.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,8BAA8B,EAAC,MAAM,4BAA4B,CAAC;AAM/E;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,8BAA8B,GAAG,OAAO,CAAC,IAAI,CAAC,CAc5F"}
@@ -0,0 +1,32 @@
1
+ import { logger } from '@shipfox/node-opentelemetry';
2
+ import { temporalClient } from '@shipfox/node-temporal';
3
+ import { config } from '#config.js';
4
+ import { LOGS_LIFECYCLE_TASK_QUEUE } from '#temporal/constants.js';
5
+ /**
6
+ * A job reached a terminal state (any path: completion, cancellation, lease-expiry,
7
+ * timeout). Arm the grace-then-close workflow for any of its streams the runner never
8
+ * ended itself. Deduped by workflow id, so a redelivered event is a no-op.
9
+ */ export async function onJobTerminated(payload) {
10
+ try {
11
+ await temporalClient().workflow.start('closeAbandonedStreams', {
12
+ taskQueue: LOGS_LIFECYCLE_TASK_QUEUE,
13
+ workflowId: `logs-close:${payload.jobId}`,
14
+ args: [
15
+ {
16
+ jobId: payload.jobId,
17
+ graceSeconds: config.LOG_STREAM_CLOSE_GRACE_SECONDS
18
+ }
19
+ ]
20
+ });
21
+ } catch (error) {
22
+ if (error instanceof Error && error.name === 'WorkflowExecutionAlreadyStartedError') {
23
+ logger().debug({
24
+ jobId: payload.jobId
25
+ }, 'Close-abandoned-streams workflow already started');
26
+ return;
27
+ }
28
+ throw error;
29
+ }
30
+ }
31
+
32
+ //# sourceMappingURL=on-job-terminated.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/presentation/subscribers/on-job-terminated.ts"],"sourcesContent":["import type {WorkflowsJobTerminatedEventDto} from '@shipfox/api-workflows-dto';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {temporalClient} from '@shipfox/node-temporal';\nimport {config} from '#config.js';\nimport {LOGS_LIFECYCLE_TASK_QUEUE} from '#temporal/constants.js';\n\n/**\n * A job reached a terminal state (any path: completion, cancellation, lease-expiry,\n * timeout). Arm the grace-then-close workflow for any of its streams the runner never\n * ended itself. Deduped by workflow id, so a redelivered event is a no-op.\n */\nexport async function onJobTerminated(payload: WorkflowsJobTerminatedEventDto): Promise<void> {\n try {\n await temporalClient().workflow.start('closeAbandonedStreams', {\n taskQueue: LOGS_LIFECYCLE_TASK_QUEUE,\n workflowId: `logs-close:${payload.jobId}`,\n args: [{jobId: payload.jobId, graceSeconds: config.LOG_STREAM_CLOSE_GRACE_SECONDS}],\n });\n } catch (error) {\n if (error instanceof Error && error.name === 'WorkflowExecutionAlreadyStartedError') {\n logger().debug({jobId: payload.jobId}, 'Close-abandoned-streams workflow already started');\n return;\n }\n throw error;\n }\n}\n"],"names":["logger","temporalClient","config","LOGS_LIFECYCLE_TASK_QUEUE","onJobTerminated","payload","workflow","start","taskQueue","workflowId","jobId","args","graceSeconds","LOG_STREAM_CLOSE_GRACE_SECONDS","error","Error","name","debug"],"mappings":"AACA,SAAQA,MAAM,QAAO,8BAA8B;AACnD,SAAQC,cAAc,QAAO,yBAAyB;AACtD,SAAQC,MAAM,QAAO,aAAa;AAClC,SAAQC,yBAAyB,QAAO,yBAAyB;AAEjE;;;;CAIC,GACD,OAAO,eAAeC,gBAAgBC,OAAuC;IAC3E,IAAI;QACF,MAAMJ,iBAAiBK,QAAQ,CAACC,KAAK,CAAC,yBAAyB;YAC7DC,WAAWL;YACXM,YAAY,CAAC,WAAW,EAAEJ,QAAQK,KAAK,EAAE;YACzCC,MAAM;gBAAC;oBAACD,OAAOL,QAAQK,KAAK;oBAAEE,cAAcV,OAAOW,8BAA8B;gBAAA;aAAE;QACrF;IACF,EAAE,OAAOC,OAAO;QACd,IAAIA,iBAAiBC,SAASD,MAAME,IAAI,KAAK,wCAAwC;YACnFhB,SAASiB,KAAK,CAAC;gBAACP,OAAOL,QAAQK,KAAK;YAAA,GAAG;YACvC;QACF;QACA,MAAMI;IACR;AACF"}
@@ -0,0 +1,9 @@
1
+ import type { LogStreamClosedEvent } from '@shipfox/api-logs-dto';
2
+ /**
3
+ * A stream reached `closed` (declared end or timeout sweep). Start its compaction workflow
4
+ * and return; no S3 work runs in the dispatcher. Deduped by `logs-compact:{streamId}`, so a
5
+ * redelivered event is a no-op. A start that fails for any other reason is left to the
6
+ * reconcile cron, which re-drives any closed-but-uncompacted stream.
7
+ */
8
+ export declare function onLogStreamClosed(payload: LogStreamClosedEvent): Promise<void>;
9
+ //# sourceMappingURL=on-log-stream-closed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"on-log-stream-closed.d.ts","sourceRoot":"","sources":["../../../src/presentation/subscribers/on-log-stream-closed.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,uBAAuB,CAAC;AAKhE;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAcpF"}
@@ -0,0 +1,31 @@
1
+ import { logger } from '@shipfox/node-opentelemetry';
2
+ import { temporalClient } from '@shipfox/node-temporal';
3
+ import { LOGS_COMPACTION_TASK_QUEUE } from '#temporal/constants.js';
4
+ /**
5
+ * A stream reached `closed` (declared end or timeout sweep). Start its compaction workflow
6
+ * and return; no S3 work runs in the dispatcher. Deduped by `logs-compact:{streamId}`, so a
7
+ * redelivered event is a no-op. A start that fails for any other reason is left to the
8
+ * reconcile cron, which re-drives any closed-but-uncompacted stream.
9
+ */ export async function onLogStreamClosed(payload) {
10
+ try {
11
+ await temporalClient().workflow.start('compactStream', {
12
+ taskQueue: LOGS_COMPACTION_TASK_QUEUE,
13
+ workflowId: `logs-compact:${payload.streamId}`,
14
+ args: [
15
+ {
16
+ streamId: payload.streamId
17
+ }
18
+ ]
19
+ });
20
+ } catch (error) {
21
+ if (error instanceof Error && error.name === 'WorkflowExecutionAlreadyStartedError') {
22
+ logger().debug({
23
+ streamId: payload.streamId
24
+ }, 'Compaction workflow already started');
25
+ return;
26
+ }
27
+ throw error;
28
+ }
29
+ }
30
+
31
+ //# sourceMappingURL=on-log-stream-closed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/presentation/subscribers/on-log-stream-closed.ts"],"sourcesContent":["import type {LogStreamClosedEvent} from '@shipfox/api-logs-dto';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {temporalClient} from '@shipfox/node-temporal';\nimport {LOGS_COMPACTION_TASK_QUEUE} from '#temporal/constants.js';\n\n/**\n * A stream reached `closed` (declared end or timeout sweep). Start its compaction workflow\n * and return; no S3 work runs in the dispatcher. Deduped by `logs-compact:{streamId}`, so a\n * redelivered event is a no-op. A start that fails for any other reason is left to the\n * reconcile cron, which re-drives any closed-but-uncompacted stream.\n */\nexport async function onLogStreamClosed(payload: LogStreamClosedEvent): Promise<void> {\n try {\n await temporalClient().workflow.start('compactStream', {\n taskQueue: LOGS_COMPACTION_TASK_QUEUE,\n workflowId: `logs-compact:${payload.streamId}`,\n args: [{streamId: payload.streamId}],\n });\n } catch (error) {\n if (error instanceof Error && error.name === 'WorkflowExecutionAlreadyStartedError') {\n logger().debug({streamId: payload.streamId}, 'Compaction workflow already started');\n return;\n }\n throw error;\n }\n}\n"],"names":["logger","temporalClient","LOGS_COMPACTION_TASK_QUEUE","onLogStreamClosed","payload","workflow","start","taskQueue","workflowId","streamId","args","error","Error","name","debug"],"mappings":"AACA,SAAQA,MAAM,QAAO,8BAA8B;AACnD,SAAQC,cAAc,QAAO,yBAAyB;AACtD,SAAQC,0BAA0B,QAAO,yBAAyB;AAElE;;;;;CAKC,GACD,OAAO,eAAeC,kBAAkBC,OAA6B;IACnE,IAAI;QACF,MAAMH,iBAAiBI,QAAQ,CAACC,KAAK,CAAC,iBAAiB;YACrDC,WAAWL;YACXM,YAAY,CAAC,aAAa,EAAEJ,QAAQK,QAAQ,EAAE;YAC9CC,MAAM;gBAAC;oBAACD,UAAUL,QAAQK,QAAQ;gBAAA;aAAE;QACtC;IACF,EAAE,OAAOE,OAAO;QACd,IAAIA,iBAAiBC,SAASD,MAAME,IAAI,KAAK,wCAAwC;YACnFb,SAASc,KAAK,CAAC;gBAACL,UAAUL,QAAQK,QAAQ;YAAA,GAAG;YAC7C;QACF;QACA,MAAME;IACR;AACF"}
@@ -0,0 +1,3 @@
1
+ import type { WorkflowsStepAttemptTerminatedEventDto } from '@shipfox/api-workflows-dto';
2
+ export declare function onStepAttemptTerminated(payload: WorkflowsStepAttemptTerminatedEventDto): Promise<void>;
3
+ //# sourceMappingURL=on-step-attempt-terminated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"on-step-attempt-terminated.d.ts","sourceRoot":"","sources":["../../../src/presentation/subscribers/on-step-attempt-terminated.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,sCAAsC,EAAC,MAAM,4BAA4B,CAAC;AAGvF,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,sCAAsC,GAC9C,OAAO,CAAC,IAAI,CAAC,CAUf"}
@@ -0,0 +1,14 @@
1
+ import { finalizeAttemptLogStream } from '#core/finalize-attempt-stream.js';
2
+ export async function onStepAttemptTerminated(payload) {
3
+ await finalizeAttemptLogStream({
4
+ jobId: payload.jobId,
5
+ workflowRunAttemptId: payload.workflowRunAttemptId,
6
+ workspaceId: payload.workspaceId,
7
+ projectId: payload.projectId,
8
+ stepId: payload.stepId,
9
+ attempt: payload.attempt,
10
+ logOutcome: payload.logOutcome
11
+ });
12
+ }
13
+
14
+ //# sourceMappingURL=on-step-attempt-terminated.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/presentation/subscribers/on-step-attempt-terminated.ts"],"sourcesContent":["import type {WorkflowsStepAttemptTerminatedEventDto} from '@shipfox/api-workflows-dto';\nimport {finalizeAttemptLogStream} from '#core/finalize-attempt-stream.js';\n\nexport async function onStepAttemptTerminated(\n payload: WorkflowsStepAttemptTerminatedEventDto,\n): Promise<void> {\n await finalizeAttemptLogStream({\n jobId: payload.jobId,\n workflowRunAttemptId: payload.workflowRunAttemptId,\n workspaceId: payload.workspaceId,\n projectId: payload.projectId,\n stepId: payload.stepId,\n attempt: payload.attempt,\n logOutcome: payload.logOutcome,\n });\n}\n"],"names":["finalizeAttemptLogStream","onStepAttemptTerminated","payload","jobId","workflowRunAttemptId","workspaceId","projectId","stepId","attempt","logOutcome"],"mappings":"AACA,SAAQA,wBAAwB,QAAO,mCAAmC;AAE1E,OAAO,eAAeC,wBACpBC,OAA+C;IAE/C,MAAMF,yBAAyB;QAC7BG,OAAOD,QAAQC,KAAK;QACpBC,sBAAsBF,QAAQE,oBAAoB;QAClDC,aAAaH,QAAQG,WAAW;QAChCC,WAAWJ,QAAQI,SAAS;QAC5BC,QAAQL,QAAQK,MAAM;QACtBC,SAASN,QAAQM,OAAO;QACxBC,YAAYP,QAAQO,UAAU;IAChC;AACF"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Job termination does not guarantee the runner flushed an end record: it may have
3
+ * died, been capped, or lost its spool. Each stream closes in its own transaction
4
+ * through the guarded `closeStream`, so a declared-close race is skipped instead of
5
+ * writing a duplicate event or tombstone.
6
+ */
7
+ export declare function closeAbandonedStreamsActivity(params: {
8
+ jobId: string;
9
+ }): Promise<{
10
+ closed: number;
11
+ }>;
12
+ //# sourceMappingURL=close-abandoned-streams.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"close-abandoned-streams.d.ts","sourceRoot":"","sources":["../../../src/temporal/activities/close-abandoned-streams.ts"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAC,CAAC,CAgB5B"}
@@ -0,0 +1,33 @@
1
+ import { closeStream } from '#core/close-stream.js';
2
+ import { db } from '#db/db.js';
3
+ import { listOpenStreamsByJob } from '#db/streams.js';
4
+ import { recordAppendedCount, streamClosedCount } from '#metrics/instance.js';
5
+ /**
6
+ * Job termination does not guarantee the runner flushed an end record: it may have
7
+ * died, been capped, or lost its spool. Each stream closes in its own transaction
8
+ * through the guarded `closeStream`, so a declared-close race is skipped instead of
9
+ * writing a duplicate event or tombstone.
10
+ */ export async function closeAbandonedStreamsActivity(params) {
11
+ const open = await listOpenStreamsByJob(params.jobId);
12
+ let closed = 0;
13
+ for (const stream of open){
14
+ const result = await db().transaction((tx)=>closeStream(tx, {
15
+ streamId: stream.id,
16
+ reason: 'timeout'
17
+ }));
18
+ if (result) {
19
+ closed += 1;
20
+ recordAppendedCount.add(1, {
21
+ kind: 'runner_lost'
22
+ });
23
+ streamClosedCount.add(1, {
24
+ reason: 'timeout'
25
+ });
26
+ }
27
+ }
28
+ return {
29
+ closed
30
+ };
31
+ }
32
+
33
+ //# sourceMappingURL=close-abandoned-streams.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/temporal/activities/close-abandoned-streams.ts"],"sourcesContent":["import {closeStream} from '#core/close-stream.js';\nimport {db} from '#db/db.js';\nimport {listOpenStreamsByJob} from '#db/streams.js';\nimport {recordAppendedCount, streamClosedCount} from '#metrics/instance.js';\n\n/**\n * Job termination does not guarantee the runner flushed an end record: it may have\n * died, been capped, or lost its spool. Each stream closes in its own transaction\n * through the guarded `closeStream`, so a declared-close race is skipped instead of\n * writing a duplicate event or tombstone.\n */\nexport async function closeAbandonedStreamsActivity(params: {\n jobId: string;\n}): Promise<{closed: number}> {\n const open = await listOpenStreamsByJob(params.jobId);\n\n let closed = 0;\n for (const stream of open) {\n const result = await db().transaction((tx) =>\n closeStream(tx, {streamId: stream.id, reason: 'timeout'}),\n );\n if (result) {\n closed += 1;\n recordAppendedCount.add(1, {kind: 'runner_lost'});\n streamClosedCount.add(1, {reason: 'timeout'});\n }\n }\n\n return {closed};\n}\n"],"names":["closeStream","db","listOpenStreamsByJob","recordAppendedCount","streamClosedCount","closeAbandonedStreamsActivity","params","open","jobId","closed","stream","result","transaction","tx","streamId","id","reason","add","kind"],"mappings":"AAAA,SAAQA,WAAW,QAAO,wBAAwB;AAClD,SAAQC,EAAE,QAAO,YAAY;AAC7B,SAAQC,oBAAoB,QAAO,iBAAiB;AACpD,SAAQC,mBAAmB,EAAEC,iBAAiB,QAAO,uBAAuB;AAE5E;;;;;CAKC,GACD,OAAO,eAAeC,8BAA8BC,MAEnD;IACC,MAAMC,OAAO,MAAML,qBAAqBI,OAAOE,KAAK;IAEpD,IAAIC,SAAS;IACb,KAAK,MAAMC,UAAUH,KAAM;QACzB,MAAMI,SAAS,MAAMV,KAAKW,WAAW,CAAC,CAACC,KACrCb,YAAYa,IAAI;gBAACC,UAAUJ,OAAOK,EAAE;gBAAEC,QAAQ;YAAS;QAEzD,IAAIL,QAAQ;YACVF,UAAU;YACVN,oBAAoBc,GAAG,CAAC,GAAG;gBAACC,MAAM;YAAa;YAC/Cd,kBAAkBa,GAAG,CAAC,GAAG;gBAACD,QAAQ;YAAS;QAC7C;IACF;IAEA,OAAO;QAACP;IAAM;AAChB"}
@@ -0,0 +1,33 @@
1
+ import { compactedGzipStream } from '#core/compaction.js';
2
+ import { type Transaction } from '#db/db.js';
3
+ export type CompactStreamResult = {
4
+ outcome: 'gone';
5
+ } | {
6
+ outcome: 'already-compacted';
7
+ } | {
8
+ outcome: 'superseded';
9
+ } | {
10
+ outcome: 'retention-raced';
11
+ } | {
12
+ outcome: 'compacted';
13
+ objectKey: string;
14
+ chunkCount: number;
15
+ uncompressedBytes: number;
16
+ };
17
+ interface CompactStreamDependencies {
18
+ compactedGzipStream: typeof compactedGzipStream;
19
+ setObjectKeyAndDeleteChunks: (tx: Transaction, params: {
20
+ streamId: string;
21
+ objectKey: string;
22
+ }) => Promise<{
23
+ updated: boolean;
24
+ }>;
25
+ }
26
+ export declare function createCompactStreamActivity(dependencies?: CompactStreamDependencies): (params: {
27
+ streamId: string;
28
+ }) => Promise<CompactStreamResult>;
29
+ export declare const compactStreamActivity: (params: {
30
+ streamId: string;
31
+ }) => Promise<CompactStreamResult>;
32
+ export {};
33
+ //# sourceMappingURL=compact-stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compact-stream.d.ts","sourceRoot":"","sources":["../../../src/temporal/activities/compact-stream.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,mBAAmB,EAAC,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAK,KAAK,WAAW,EAAC,MAAM,WAAW,CAAC;AAI/C,MAAM,MAAM,mBAAmB,GAC3B;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,GACjB;IAAC,OAAO,EAAE,mBAAmB,CAAA;CAAC,GAC9B;IAAC,OAAO,EAAE,YAAY,CAAA;CAAC,GACvB;IAAC,OAAO,EAAE,iBAAiB,CAAA;CAAC,GAC5B;IAAC,OAAO,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAA;CAAC,CAAC;AAE7F,UAAU,yBAAyB;IACjC,mBAAmB,EAAE,OAAO,mBAAmB,CAAC;IAChD,2BAA2B,EAAE,CAC3B,EAAE,EAAE,WAAW,EACf,MAAM,EAAE;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,KAC1C,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAC,CAAC,CAAC;CAClC;AAsFD,wBAAgB,2BAA2B,CACzC,YAAY,GAAE,yBAA+C,GAC5D,CAAC,MAAM,EAAE;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAC,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAW9D;AAED,eAAO,MAAM,qBAAqB,WAbtB;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAC,KAAK,OAAO,CAAC,mBAAmB,CAaI,CAAC"}
@@ -0,0 +1,95 @@
1
+ import { Context } from '@temporalio/activity';
2
+ import { compactedObjectKey, deleteObject, putCompactedObject } from '#api/object-storage.js';
3
+ import { compactedGzipStream } from '#core/compaction.js';
4
+ import { chunkStats } from '#db/chunks.js';
5
+ import { db } from '#db/db.js';
6
+ import { getAttemptStreamById, setObjectKeyAndDeleteChunks } from '#db/streams.js';
7
+ import { compactionCount } from '#metrics/instance.js';
8
+ const defaultDependencies = {
9
+ compactedGzipStream,
10
+ setObjectKeyAndDeleteChunks
11
+ };
12
+ /**
13
+ * Compacts one closed stream into a single gzip NDJSON object, then deletes its chunk rows.
14
+ *
15
+ * load stream ──┬─ gone ─────────────► no-op (retention raced)
16
+ * ├─ object_key set ───► no-op (idempotent re-run)
17
+ * └─ else: upload to a per-attempt key ─► gzip ─► multipart (abort-aware, heartbeat)
18
+ * │
19
+ * verify streamed count/maxSeq/bytes == table (mismatch ─► delete upload, throw, retry)
20
+ * │
21
+ * tx: set object_key (state='closed' AND object_key IS NULL) + delete chunks
22
+ * └─ 0 rows ─► delete this attempt's upload, then re-read the row:
23
+ * gone ─► retention raced · keyed ─► superseded by another attempt
24
+ *
25
+ * Each attempt uploads to its own `compactedObjectKey(stream, uuid)`, so a slow or zombie
26
+ * attempt can never overwrite a published object. The single-winner publish (object_key set
27
+ * + chunk delete, atomic) drops chunks only once a complete object is durable; the integrity
28
+ * check (count, maxSeq, and byte total) guards a read bug from publishing a truncated object
29
+ * before the only copy of the source is gone (S3 part checksums cover byte transfer).
30
+ */ async function compactStream(params, dependencies) {
31
+ const stream = await getAttemptStreamById(params.streamId);
32
+ if (!stream) return {
33
+ outcome: 'gone'
34
+ };
35
+ if (stream.objectKey) return {
36
+ outcome: 'already-compacted'
37
+ };
38
+ const ctx = Context.current();
39
+ const uploadKey = compactedObjectKey(stream, crypto.randomUUID());
40
+ const expected = await chunkStats(stream.id);
41
+ const { body, stats } = dependencies.compactedGzipStream({
42
+ streamId: stream.id,
43
+ onPage: ()=>ctx.heartbeat()
44
+ });
45
+ await putCompactedObject({
46
+ key: uploadKey,
47
+ body,
48
+ signal: ctx.cancellationSignal,
49
+ onProgress: ()=>ctx.heartbeat(),
50
+ metadata: {
51
+ stream_id: stream.id,
52
+ chunk_count: String(expected.count),
53
+ uncompressed_bytes: String(expected.uncompressedBytes),
54
+ last_seq: String(expected.maxSeq)
55
+ }
56
+ });
57
+ if (stats.chunkCount !== expected.count || stats.lastSeq !== expected.maxSeq || stats.uncompressedBytes !== expected.uncompressedBytes) {
58
+ await deleteObject(uploadKey).catch(()=>undefined);
59
+ throw new Error(`Compaction integrity check failed for stream ${stream.id}: streamed ${stats.chunkCount} chunks / ${stats.uncompressedBytes} bytes up to seq ${stats.lastSeq}, table holds ${expected.count} / ${expected.uncompressedBytes} bytes up to seq ${expected.maxSeq}`);
60
+ }
61
+ const { updated } = await db().transaction((tx)=>dependencies.setObjectKeyAndDeleteChunks(tx, {
62
+ streamId: stream.id,
63
+ objectKey: uploadKey
64
+ }));
65
+ if (!updated) {
66
+ await deleteObject(uploadKey);
67
+ const current = await getAttemptStreamById(stream.id);
68
+ return {
69
+ outcome: current ? 'superseded' : 'retention-raced'
70
+ };
71
+ }
72
+ return {
73
+ outcome: 'compacted',
74
+ objectKey: uploadKey,
75
+ chunkCount: stats.chunkCount,
76
+ uncompressedBytes: stats.uncompressedBytes
77
+ };
78
+ }
79
+ export function createCompactStreamActivity(dependencies = defaultDependencies) {
80
+ return async (params)=>{
81
+ let outcome = 'failed';
82
+ try {
83
+ const result = await compactStream(params, dependencies);
84
+ outcome = result.outcome;
85
+ return result;
86
+ } finally{
87
+ compactionCount.add(1, {
88
+ outcome
89
+ });
90
+ }
91
+ };
92
+ }
93
+ export const compactStreamActivity = createCompactStreamActivity();
94
+
95
+ //# sourceMappingURL=compact-stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/temporal/activities/compact-stream.ts"],"sourcesContent":["import {Context} from '@temporalio/activity';\nimport {compactedObjectKey, deleteObject, putCompactedObject} from '#api/object-storage.js';\nimport {compactedGzipStream} from '#core/compaction.js';\nimport {chunkStats} from '#db/chunks.js';\nimport {db, type Transaction} from '#db/db.js';\nimport {getAttemptStreamById, setObjectKeyAndDeleteChunks} from '#db/streams.js';\nimport {type CompactionMetricOutcome, compactionCount} from '#metrics/instance.js';\n\nexport type CompactStreamResult =\n | {outcome: 'gone'}\n | {outcome: 'already-compacted'}\n | {outcome: 'superseded'}\n | {outcome: 'retention-raced'}\n | {outcome: 'compacted'; objectKey: string; chunkCount: number; uncompressedBytes: number};\n\ninterface CompactStreamDependencies {\n compactedGzipStream: typeof compactedGzipStream;\n setObjectKeyAndDeleteChunks: (\n tx: Transaction,\n params: {streamId: string; objectKey: string},\n ) => Promise<{updated: boolean}>;\n}\n\nconst defaultDependencies: CompactStreamDependencies = {\n compactedGzipStream,\n setObjectKeyAndDeleteChunks,\n};\n\n/**\n * Compacts one closed stream into a single gzip NDJSON object, then deletes its chunk rows.\n *\n * load stream ──┬─ gone ─────────────► no-op (retention raced)\n * ├─ object_key set ───► no-op (idempotent re-run)\n * └─ else: upload to a per-attempt key ─► gzip ─► multipart (abort-aware, heartbeat)\n * │\n * verify streamed count/maxSeq/bytes == table (mismatch ─► delete upload, throw, retry)\n * │\n * tx: set object_key (state='closed' AND object_key IS NULL) + delete chunks\n * └─ 0 rows ─► delete this attempt's upload, then re-read the row:\n * gone ─► retention raced · keyed ─► superseded by another attempt\n *\n * Each attempt uploads to its own `compactedObjectKey(stream, uuid)`, so a slow or zombie\n * attempt can never overwrite a published object. The single-winner publish (object_key set\n * + chunk delete, atomic) drops chunks only once a complete object is durable; the integrity\n * check (count, maxSeq, and byte total) guards a read bug from publishing a truncated object\n * before the only copy of the source is gone (S3 part checksums cover byte transfer).\n */\nasync function compactStream(\n params: {streamId: string},\n dependencies: CompactStreamDependencies,\n): Promise<CompactStreamResult> {\n const stream = await getAttemptStreamById(params.streamId);\n if (!stream) return {outcome: 'gone'};\n if (stream.objectKey) return {outcome: 'already-compacted'};\n\n const ctx = Context.current();\n const uploadKey = compactedObjectKey(stream, crypto.randomUUID());\n const expected = await chunkStats(stream.id);\n const {body, stats} = dependencies.compactedGzipStream({\n streamId: stream.id,\n onPage: () => ctx.heartbeat(),\n });\n\n await putCompactedObject({\n key: uploadKey,\n body,\n signal: ctx.cancellationSignal,\n onProgress: () => ctx.heartbeat(),\n metadata: {\n stream_id: stream.id,\n chunk_count: String(expected.count),\n uncompressed_bytes: String(expected.uncompressedBytes),\n last_seq: String(expected.maxSeq),\n },\n });\n\n if (\n stats.chunkCount !== expected.count ||\n stats.lastSeq !== expected.maxSeq ||\n stats.uncompressedBytes !== expected.uncompressedBytes\n ) {\n await deleteObject(uploadKey).catch(() => undefined);\n throw new Error(\n `Compaction integrity check failed for stream ${stream.id}: streamed ${stats.chunkCount} chunks / ${stats.uncompressedBytes} bytes up to seq ${stats.lastSeq}, table holds ${expected.count} / ${expected.uncompressedBytes} bytes up to seq ${expected.maxSeq}`,\n );\n }\n\n const {updated} = await db().transaction((tx) =>\n dependencies.setObjectKeyAndDeleteChunks(tx, {\n streamId: stream.id,\n objectKey: uploadKey,\n }),\n );\n if (!updated) {\n await deleteObject(uploadKey);\n const current = await getAttemptStreamById(stream.id);\n return {outcome: current ? 'superseded' : 'retention-raced'};\n }\n\n return {\n outcome: 'compacted',\n objectKey: uploadKey,\n chunkCount: stats.chunkCount,\n uncompressedBytes: stats.uncompressedBytes,\n };\n}\n\nexport function createCompactStreamActivity(\n dependencies: CompactStreamDependencies = defaultDependencies,\n): (params: {streamId: string}) => Promise<CompactStreamResult> {\n return async (params) => {\n let outcome: CompactionMetricOutcome = 'failed';\n try {\n const result = await compactStream(params, dependencies);\n outcome = result.outcome;\n return result;\n } finally {\n compactionCount.add(1, {outcome});\n }\n };\n}\n\nexport const compactStreamActivity = createCompactStreamActivity();\n"],"names":["Context","compactedObjectKey","deleteObject","putCompactedObject","compactedGzipStream","chunkStats","db","getAttemptStreamById","setObjectKeyAndDeleteChunks","compactionCount","defaultDependencies","compactStream","params","dependencies","stream","streamId","outcome","objectKey","ctx","current","uploadKey","crypto","randomUUID","expected","id","body","stats","onPage","heartbeat","key","signal","cancellationSignal","onProgress","metadata","stream_id","chunk_count","String","count","uncompressed_bytes","uncompressedBytes","last_seq","maxSeq","chunkCount","lastSeq","catch","undefined","Error","updated","transaction","tx","createCompactStreamActivity","result","add","compactStreamActivity"],"mappings":"AAAA,SAAQA,OAAO,QAAO,uBAAuB;AAC7C,SAAQC,kBAAkB,EAAEC,YAAY,EAAEC,kBAAkB,QAAO,yBAAyB;AAC5F,SAAQC,mBAAmB,QAAO,sBAAsB;AACxD,SAAQC,UAAU,QAAO,gBAAgB;AACzC,SAAQC,EAAE,QAAyB,YAAY;AAC/C,SAAQC,oBAAoB,EAAEC,2BAA2B,QAAO,iBAAiB;AACjF,SAAsCC,eAAe,QAAO,uBAAuB;AAiBnF,MAAMC,sBAAiD;IACrDN;IACAI;AACF;AAEA;;;;;;;;;;;;;;;;;;CAkBC,GACD,eAAeG,cACbC,MAA0B,EAC1BC,YAAuC;IAEvC,MAAMC,SAAS,MAAMP,qBAAqBK,OAAOG,QAAQ;IACzD,IAAI,CAACD,QAAQ,OAAO;QAACE,SAAS;IAAM;IACpC,IAAIF,OAAOG,SAAS,EAAE,OAAO;QAACD,SAAS;IAAmB;IAE1D,MAAME,MAAMlB,QAAQmB,OAAO;IAC3B,MAAMC,YAAYnB,mBAAmBa,QAAQO,OAAOC,UAAU;IAC9D,MAAMC,WAAW,MAAMlB,WAAWS,OAAOU,EAAE;IAC3C,MAAM,EAACC,IAAI,EAAEC,KAAK,EAAC,GAAGb,aAAaT,mBAAmB,CAAC;QACrDW,UAAUD,OAAOU,EAAE;QACnBG,QAAQ,IAAMT,IAAIU,SAAS;IAC7B;IAEA,MAAMzB,mBAAmB;QACvB0B,KAAKT;QACLK;QACAK,QAAQZ,IAAIa,kBAAkB;QAC9BC,YAAY,IAAMd,IAAIU,SAAS;QAC/BK,UAAU;YACRC,WAAWpB,OAAOU,EAAE;YACpBW,aAAaC,OAAOb,SAASc,KAAK;YAClCC,oBAAoBF,OAAOb,SAASgB,iBAAiB;YACrDC,UAAUJ,OAAOb,SAASkB,MAAM;QAClC;IACF;IAEA,IACEf,MAAMgB,UAAU,KAAKnB,SAASc,KAAK,IACnCX,MAAMiB,OAAO,KAAKpB,SAASkB,MAAM,IACjCf,MAAMa,iBAAiB,KAAKhB,SAASgB,iBAAiB,EACtD;QACA,MAAMrC,aAAakB,WAAWwB,KAAK,CAAC,IAAMC;QAC1C,MAAM,IAAIC,MACR,CAAC,6CAA6C,EAAEhC,OAAOU,EAAE,CAAC,WAAW,EAAEE,MAAMgB,UAAU,CAAC,UAAU,EAAEhB,MAAMa,iBAAiB,CAAC,iBAAiB,EAAEb,MAAMiB,OAAO,CAAC,cAAc,EAAEpB,SAASc,KAAK,CAAC,GAAG,EAAEd,SAASgB,iBAAiB,CAAC,iBAAiB,EAAEhB,SAASkB,MAAM,EAAE;IAEpQ;IAEA,MAAM,EAACM,OAAO,EAAC,GAAG,MAAMzC,KAAK0C,WAAW,CAAC,CAACC,KACxCpC,aAAaL,2BAA2B,CAACyC,IAAI;YAC3ClC,UAAUD,OAAOU,EAAE;YACnBP,WAAWG;QACb;IAEF,IAAI,CAAC2B,SAAS;QACZ,MAAM7C,aAAakB;QACnB,MAAMD,UAAU,MAAMZ,qBAAqBO,OAAOU,EAAE;QACpD,OAAO;YAACR,SAASG,UAAU,eAAe;QAAiB;IAC7D;IAEA,OAAO;QACLH,SAAS;QACTC,WAAWG;QACXsB,YAAYhB,MAAMgB,UAAU;QAC5BH,mBAAmBb,MAAMa,iBAAiB;IAC5C;AACF;AAEA,OAAO,SAASW,4BACdrC,eAA0CH,mBAAmB;IAE7D,OAAO,OAAOE;QACZ,IAAII,UAAmC;QACvC,IAAI;YACF,MAAMmC,SAAS,MAAMxC,cAAcC,QAAQC;YAC3CG,UAAUmC,OAAOnC,OAAO;YACxB,OAAOmC;QACT,SAAU;YACR1C,gBAAgB2C,GAAG,CAAC,GAAG;gBAACpC;YAAO;QACjC;IACF;AACF;AAEA,OAAO,MAAMqC,wBAAwBH,8BAA8B"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Backstop for the event-triggered path: finds closed streams that never got an object
3
+ * key past the stale window and re-starts compaction for each. Starting by the same
4
+ * `logs-compact:{streamId}` workflow id re-drives a stream whose bounded-retry run already
5
+ * failed and closed, while skipping one whose run is still RUNNING (AlreadyStarted). Starts
6
+ * independent workflows, not children, so a re-driven run is not tied to this cron tick.
7
+ *
8
+ * One stream's unexpected start failure is logged and skipped, never thrown: a single poison
9
+ * stream must not abort the batch and leave the rest of the backlog un-re-driven (the next
10
+ * tick retries it). The activity only fails on the initial DB query.
11
+ */
12
+ export declare function compactionReconcileActivity(): Promise<{
13
+ restarted: number;
14
+ failed: number;
15
+ }>;
16
+ //# sourceMappingURL=compaction-reconcile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compaction-reconcile.d.ts","sourceRoot":"","sources":["../../../src/temporal/activities/compaction-reconcile.ts"],"names":[],"mappings":"AASA;;;;;;;;;;GAUG;AACH,wBAAsB,2BAA2B,IAAI,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAC,CAAC,CA2BhG"}
@@ -0,0 +1,52 @@
1
+ import { logger } from '@shipfox/node-opentelemetry';
2
+ import { temporalClient } from '@shipfox/node-temporal';
3
+ import { config } from '#config.js';
4
+ import { listStaleUncompactedStreams } from '#db/streams.js';
5
+ import { LOGS_COMPACTION_TASK_QUEUE } from '#temporal/constants.js';
6
+ // Bounded per tick; remaining stale streams are picked up on the next cron run.
7
+ const RECONCILE_BATCH_LIMIT = 100;
8
+ /**
9
+ * Backstop for the event-triggered path: finds closed streams that never got an object
10
+ * key past the stale window and re-starts compaction for each. Starting by the same
11
+ * `logs-compact:{streamId}` workflow id re-drives a stream whose bounded-retry run already
12
+ * failed and closed, while skipping one whose run is still RUNNING (AlreadyStarted). Starts
13
+ * independent workflows, not children, so a re-driven run is not tied to this cron tick.
14
+ *
15
+ * One stream's unexpected start failure is logged and skipped, never thrown: a single poison
16
+ * stream must not abort the batch and leave the rest of the backlog un-re-driven (the next
17
+ * tick retries it). The activity only fails on the initial DB query.
18
+ */ export async function compactionReconcileActivity() {
19
+ const stale = await listStaleUncompactedStreams({
20
+ olderThanSeconds: config.LOG_COMPACTION_RECONCILE_STALE_SECONDS,
21
+ limit: RECONCILE_BATCH_LIMIT
22
+ });
23
+ let restarted = 0;
24
+ let failed = 0;
25
+ for (const stream of stale){
26
+ try {
27
+ await temporalClient().workflow.start('compactStream', {
28
+ taskQueue: LOGS_COMPACTION_TASK_QUEUE,
29
+ workflowId: `logs-compact:${stream.id}`,
30
+ args: [
31
+ {
32
+ streamId: stream.id
33
+ }
34
+ ]
35
+ });
36
+ restarted += 1;
37
+ } catch (error) {
38
+ if (error instanceof Error && error.name === 'WorkflowExecutionAlreadyStartedError') continue;
39
+ failed += 1;
40
+ logger().error({
41
+ err: error,
42
+ streamId: stream.id
43
+ }, 'Failed to re-drive stale stream compaction');
44
+ }
45
+ }
46
+ return {
47
+ restarted,
48
+ failed
49
+ };
50
+ }
51
+
52
+ //# sourceMappingURL=compaction-reconcile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/temporal/activities/compaction-reconcile.ts"],"sourcesContent":["import {logger} from '@shipfox/node-opentelemetry';\nimport {temporalClient} from '@shipfox/node-temporal';\nimport {config} from '#config.js';\nimport {listStaleUncompactedStreams} from '#db/streams.js';\nimport {LOGS_COMPACTION_TASK_QUEUE} from '#temporal/constants.js';\n\n// Bounded per tick; remaining stale streams are picked up on the next cron run.\nconst RECONCILE_BATCH_LIMIT = 100;\n\n/**\n * Backstop for the event-triggered path: finds closed streams that never got an object\n * key past the stale window and re-starts compaction for each. Starting by the same\n * `logs-compact:{streamId}` workflow id re-drives a stream whose bounded-retry run already\n * failed and closed, while skipping one whose run is still RUNNING (AlreadyStarted). Starts\n * independent workflows, not children, so a re-driven run is not tied to this cron tick.\n *\n * One stream's unexpected start failure is logged and skipped, never thrown: a single poison\n * stream must not abort the batch and leave the rest of the backlog un-re-driven (the next\n * tick retries it). The activity only fails on the initial DB query.\n */\nexport async function compactionReconcileActivity(): Promise<{restarted: number; failed: number}> {\n const stale = await listStaleUncompactedStreams({\n olderThanSeconds: config.LOG_COMPACTION_RECONCILE_STALE_SECONDS,\n limit: RECONCILE_BATCH_LIMIT,\n });\n\n let restarted = 0;\n let failed = 0;\n for (const stream of stale) {\n try {\n await temporalClient().workflow.start('compactStream', {\n taskQueue: LOGS_COMPACTION_TASK_QUEUE,\n workflowId: `logs-compact:${stream.id}`,\n args: [{streamId: stream.id}],\n });\n restarted += 1;\n } catch (error) {\n if (error instanceof Error && error.name === 'WorkflowExecutionAlreadyStartedError') continue;\n failed += 1;\n logger().error(\n {err: error, streamId: stream.id},\n 'Failed to re-drive stale stream compaction',\n );\n }\n }\n\n return {restarted, failed};\n}\n"],"names":["logger","temporalClient","config","listStaleUncompactedStreams","LOGS_COMPACTION_TASK_QUEUE","RECONCILE_BATCH_LIMIT","compactionReconcileActivity","stale","olderThanSeconds","LOG_COMPACTION_RECONCILE_STALE_SECONDS","limit","restarted","failed","stream","workflow","start","taskQueue","workflowId","id","args","streamId","error","Error","name","err"],"mappings":"AAAA,SAAQA,MAAM,QAAO,8BAA8B;AACnD,SAAQC,cAAc,QAAO,yBAAyB;AACtD,SAAQC,MAAM,QAAO,aAAa;AAClC,SAAQC,2BAA2B,QAAO,iBAAiB;AAC3D,SAAQC,0BAA0B,QAAO,yBAAyB;AAElE,gFAAgF;AAChF,MAAMC,wBAAwB;AAE9B;;;;;;;;;;CAUC,GACD,OAAO,eAAeC;IACpB,MAAMC,QAAQ,MAAMJ,4BAA4B;QAC9CK,kBAAkBN,OAAOO,sCAAsC;QAC/DC,OAAOL;IACT;IAEA,IAAIM,YAAY;IAChB,IAAIC,SAAS;IACb,KAAK,MAAMC,UAAUN,MAAO;QAC1B,IAAI;YACF,MAAMN,iBAAiBa,QAAQ,CAACC,KAAK,CAAC,iBAAiB;gBACrDC,WAAWZ;gBACXa,YAAY,CAAC,aAAa,EAAEJ,OAAOK,EAAE,EAAE;gBACvCC,MAAM;oBAAC;wBAACC,UAAUP,OAAOK,EAAE;oBAAA;iBAAE;YAC/B;YACAP,aAAa;QACf,EAAE,OAAOU,OAAO;YACd,IAAIA,iBAAiBC,SAASD,MAAME,IAAI,KAAK,wCAAwC;YACrFX,UAAU;YACVZ,SAASqB,KAAK,CACZ;gBAACG,KAAKH;gBAAOD,UAAUP,OAAOK,EAAE;YAAA,GAChC;QAEJ;IACF;IAEA,OAAO;QAACP;QAAWC;IAAM;AAC3B"}
@@ -0,0 +1,14 @@
1
+ import { closeAbandonedStreamsActivity } from './close-abandoned-streams.js';
2
+ import { compactionReconcileActivity } from './compaction-reconcile.js';
3
+ import { reapStaleOpenStreamsActivity } from './reap-stale-open-streams.js';
4
+ import { retentionSweepActivity } from './retention-sweep.js';
5
+ export declare function createLogsActivities(): {
6
+ closeAbandonedStreamsActivity: typeof closeAbandonedStreamsActivity;
7
+ compactStreamActivity: (params: {
8
+ streamId: string;
9
+ }) => Promise<import("./compact-stream.js").CompactStreamResult>;
10
+ compactionReconcileActivity: typeof compactionReconcileActivity;
11
+ reapStaleOpenStreamsActivity: typeof reapStaleOpenStreamsActivity;
12
+ retentionSweepActivity: typeof retentionSweepActivity;
13
+ };
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/temporal/activities/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,6BAA6B,EAAC,MAAM,8BAA8B,CAAC;AAE3E,OAAO,EAAC,2BAA2B,EAAC,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAC,4BAA4B,EAAC,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAC,sBAAsB,EAAC,MAAM,sBAAsB,CAAC;AAE5D,wBAAgB,oBAAoB;;;;;;;;EAQnC"}
@@ -0,0 +1,16 @@
1
+ import { closeAbandonedStreamsActivity } from './close-abandoned-streams.js';
2
+ import { compactStreamActivity } from './compact-stream.js';
3
+ import { compactionReconcileActivity } from './compaction-reconcile.js';
4
+ import { reapStaleOpenStreamsActivity } from './reap-stale-open-streams.js';
5
+ import { retentionSweepActivity } from './retention-sweep.js';
6
+ export function createLogsActivities() {
7
+ return {
8
+ closeAbandonedStreamsActivity,
9
+ compactStreamActivity,
10
+ compactionReconcileActivity,
11
+ reapStaleOpenStreamsActivity,
12
+ retentionSweepActivity
13
+ };
14
+ }
15
+
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/temporal/activities/index.ts"],"sourcesContent":["import {closeAbandonedStreamsActivity} from './close-abandoned-streams.js';\nimport {compactStreamActivity} from './compact-stream.js';\nimport {compactionReconcileActivity} from './compaction-reconcile.js';\nimport {reapStaleOpenStreamsActivity} from './reap-stale-open-streams.js';\nimport {retentionSweepActivity} from './retention-sweep.js';\n\nexport function createLogsActivities() {\n return {\n closeAbandonedStreamsActivity,\n compactStreamActivity,\n compactionReconcileActivity,\n reapStaleOpenStreamsActivity,\n retentionSweepActivity,\n };\n}\n"],"names":["closeAbandonedStreamsActivity","compactStreamActivity","compactionReconcileActivity","reapStaleOpenStreamsActivity","retentionSweepActivity","createLogsActivities"],"mappings":"AAAA,SAAQA,6BAA6B,QAAO,+BAA+B;AAC3E,SAAQC,qBAAqB,QAAO,sBAAsB;AAC1D,SAAQC,2BAA2B,QAAO,4BAA4B;AACtE,SAAQC,4BAA4B,QAAO,+BAA+B;AAC1E,SAAQC,sBAAsB,QAAO,uBAAuB;AAE5D,OAAO,SAASC;IACd,OAAO;QACLL;QACAC;QACAC;QACAC;QACAC;IACF;AACF"}
@@ -0,0 +1,4 @@
1
+ import { type ReapStaleOpenStreamsResult } from '#core/reap-stale-open-streams.js';
2
+ /** Cron-driven backstop that force-closes open streams the one-shot job-terminal close missed. */
3
+ export declare function reapStaleOpenStreamsActivity(): Promise<ReapStaleOpenStreamsResult>;
4
+ //# sourceMappingURL=reap-stale-open-streams.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reap-stale-open-streams.d.ts","sourceRoot":"","sources":["../../../src/temporal/activities/reap-stale-open-streams.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,0BAA0B,EAEhC,MAAM,kCAAkC,CAAC;AAG1C,kGAAkG;AAClG,wBAAgB,4BAA4B,IAAI,OAAO,CAAC,0BAA0B,CAAC,CAKlF"}
@@ -0,0 +1,11 @@
1
+ import { config } from '#config.js';
2
+ import { reapStaleOpenStreams } from '#core/reap-stale-open-streams.js';
3
+ import { REAP_BATCH_LIMIT } from '#temporal/constants.js';
4
+ /** Cron-driven backstop that force-closes open streams the one-shot job-terminal close missed. */ export function reapStaleOpenStreamsActivity() {
5
+ return reapStaleOpenStreams({
6
+ olderThanSeconds: config.LOG_STREAM_REAP_AFTER_SECONDS,
7
+ batchLimit: REAP_BATCH_LIMIT
8
+ });
9
+ }
10
+
11
+ //# sourceMappingURL=reap-stale-open-streams.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/temporal/activities/reap-stale-open-streams.ts"],"sourcesContent":["import {config} from '#config.js';\nimport {\n type ReapStaleOpenStreamsResult,\n reapStaleOpenStreams,\n} from '#core/reap-stale-open-streams.js';\nimport {REAP_BATCH_LIMIT} from '#temporal/constants.js';\n\n/** Cron-driven backstop that force-closes open streams the one-shot job-terminal close missed. */\nexport function reapStaleOpenStreamsActivity(): Promise<ReapStaleOpenStreamsResult> {\n return reapStaleOpenStreams({\n olderThanSeconds: config.LOG_STREAM_REAP_AFTER_SECONDS,\n batchLimit: REAP_BATCH_LIMIT,\n });\n}\n"],"names":["config","reapStaleOpenStreams","REAP_BATCH_LIMIT","reapStaleOpenStreamsActivity","olderThanSeconds","LOG_STREAM_REAP_AFTER_SECONDS","batchLimit"],"mappings":"AAAA,SAAQA,MAAM,QAAO,aAAa;AAClC,SAEEC,oBAAoB,QACf,mCAAmC;AAC1C,SAAQC,gBAAgB,QAAO,yBAAyB;AAExD,gGAAgG,GAChG,OAAO,SAASC;IACd,OAAOF,qBAAqB;QAC1BG,kBAAkBJ,OAAOK,6BAA6B;QACtDC,YAAYJ;IACd;AACF"}
@@ -0,0 +1,7 @@
1
+ import { type RetentionSweepResult } from '#core/retention.js';
2
+ /**
3
+ * Cron-driven sweep for expired closed log streams. Heartbeats per stream; the core loop owns
4
+ * the wall-clock budget because Temporal timeouts do not stop already-running JS.
5
+ */
6
+ export declare function retentionSweepActivity(): Promise<RetentionSweepResult>;
7
+ //# sourceMappingURL=retention-sweep.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retention-sweep.d.ts","sourceRoot":"","sources":["../../../src/temporal/activities/retention-sweep.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,KAAK,oBAAoB,EAAoB,MAAM,oBAAoB,CAAC;AAOhF;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAStE"}
@@ -0,0 +1,19 @@
1
+ import { Context } from '@temporalio/activity';
2
+ import { config } from '#config.js';
3
+ import { runRetentionSweep } from '#core/retention.js';
4
+ import { RETENTION_BATCH_LIMIT, RETENTION_MAX_ITERATIONS, RETENTION_TIME_BUDGET_MS } from '#temporal/constants.js';
5
+ /**
6
+ * Cron-driven sweep for expired closed log streams. Heartbeats per stream; the core loop owns
7
+ * the wall-clock budget because Temporal timeouts do not stop already-running JS.
8
+ */ export function retentionSweepActivity() {
9
+ const ctx = Context.current();
10
+ return runRetentionSweep({
11
+ retentionDays: config.LOG_RETENTION_DAYS,
12
+ batchLimit: RETENTION_BATCH_LIMIT,
13
+ timeBudgetMs: RETENTION_TIME_BUDGET_MS,
14
+ maxIterations: RETENTION_MAX_ITERATIONS,
15
+ onProgress: ()=>ctx.heartbeat()
16
+ });
17
+ }
18
+
19
+ //# sourceMappingURL=retention-sweep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/temporal/activities/retention-sweep.ts"],"sourcesContent":["import {Context} from '@temporalio/activity';\nimport {config} from '#config.js';\nimport {type RetentionSweepResult, runRetentionSweep} from '#core/retention.js';\nimport {\n RETENTION_BATCH_LIMIT,\n RETENTION_MAX_ITERATIONS,\n RETENTION_TIME_BUDGET_MS,\n} from '#temporal/constants.js';\n\n/**\n * Cron-driven sweep for expired closed log streams. Heartbeats per stream; the core loop owns\n * the wall-clock budget because Temporal timeouts do not stop already-running JS.\n */\nexport function retentionSweepActivity(): Promise<RetentionSweepResult> {\n const ctx = Context.current();\n return runRetentionSweep({\n retentionDays: config.LOG_RETENTION_DAYS,\n batchLimit: RETENTION_BATCH_LIMIT,\n timeBudgetMs: RETENTION_TIME_BUDGET_MS,\n maxIterations: RETENTION_MAX_ITERATIONS,\n onProgress: () => ctx.heartbeat(),\n });\n}\n"],"names":["Context","config","runRetentionSweep","RETENTION_BATCH_LIMIT","RETENTION_MAX_ITERATIONS","RETENTION_TIME_BUDGET_MS","retentionSweepActivity","ctx","current","retentionDays","LOG_RETENTION_DAYS","batchLimit","timeBudgetMs","maxIterations","onProgress","heartbeat"],"mappings":"AAAA,SAAQA,OAAO,QAAO,uBAAuB;AAC7C,SAAQC,MAAM,QAAO,aAAa;AAClC,SAAmCC,iBAAiB,QAAO,qBAAqB;AAChF,SACEC,qBAAqB,EACrBC,wBAAwB,EACxBC,wBAAwB,QACnB,yBAAyB;AAEhC;;;CAGC,GACD,OAAO,SAASC;IACd,MAAMC,MAAMP,QAAQQ,OAAO;IAC3B,OAAON,kBAAkB;QACvBO,eAAeR,OAAOS,kBAAkB;QACxCC,YAAYR;QACZS,cAAcP;QACdQ,eAAeT;QACfU,YAAY,IAAMP,IAAIQ,SAAS;IACjC;AACF"}
@@ -0,0 +1,7 @@
1
+ export declare const LOGS_LIFECYCLE_TASK_QUEUE = "logs-lifecycle";
2
+ export declare const LOGS_COMPACTION_TASK_QUEUE = "logs-compaction";
3
+ export declare const RETENTION_BATCH_LIMIT = 200;
4
+ export declare const RETENTION_TIME_BUDGET_MS: number;
5
+ export declare const RETENTION_MAX_ITERATIONS = 1000;
6
+ export declare const REAP_BATCH_LIMIT = 100;
7
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/temporal/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,yBAAyB,mBAAmB,CAAC;AAI1D,eAAO,MAAM,0BAA0B,oBAAoB,CAAC;AAI5D,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,eAAO,MAAM,wBAAwB,QAAa,CAAC;AACnD,eAAO,MAAM,wBAAwB,OAAQ,CAAC;AAG9C,eAAO,MAAM,gBAAgB,MAAM,CAAC"}