devchain-cli 0.22.0 → 0.23.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 (241) hide show
  1. package/dist/drizzle/0077_familiar_morbius.sql +6 -0
  2. package/dist/drizzle/0078_preserve_legacy_connection_ownership.sql +180 -0
  3. package/dist/drizzle/0079_premium_forgotten_one.sql +18 -0
  4. package/dist/drizzle/0080_lethal_thunderbolt_ross.sql +49 -0
  5. package/dist/drizzle/0081_early_kronos.sql +58 -0
  6. package/dist/drizzle/0082_huge_black_queen.sql +14 -0
  7. package/dist/drizzle/meta/0077_snapshot.json +6957 -0
  8. package/dist/drizzle/meta/0078_snapshot.json +6957 -0
  9. package/dist/drizzle/meta/0079_snapshot.json +7093 -0
  10. package/dist/drizzle/meta/0080_snapshot.json +7241 -0
  11. package/dist/drizzle/meta/0081_snapshot.json +7482 -0
  12. package/dist/drizzle/meta/0082_snapshot.json +7571 -0
  13. package/dist/drizzle/meta/_journal.json +42 -0
  14. package/dist/node_modules/@devchain/shared/tsconfig.tsbuildinfo +1 -1
  15. package/dist/server/common/errors/error-types.d.ts +7 -0
  16. package/dist/server/common/errors/error-types.js +7 -1
  17. package/dist/server/common/errors/error-types.js.map +1 -1
  18. package/dist/server/modules/epic-time/controllers/agent-time-buffer.controller.d.ts +8 -0
  19. package/dist/server/modules/epic-time/controllers/agent-time-buffer.controller.js +67 -0
  20. package/dist/server/modules/epic-time/controllers/agent-time-buffer.controller.js.map +1 -0
  21. package/dist/server/modules/epic-time/controllers/external-estimate-log.controller.d.ts +12 -0
  22. package/dist/server/modules/epic-time/controllers/external-estimate-log.controller.js +322 -0
  23. package/dist/server/modules/epic-time/controllers/external-estimate-log.controller.js.map +1 -0
  24. package/dist/server/modules/epic-time/epic-time.module.js +14 -4
  25. package/dist/server/modules/epic-time/epic-time.module.js.map +1 -1
  26. package/dist/server/modules/epic-time/models/epic-time-daily-allocator.d.ts +30 -0
  27. package/dist/server/modules/epic-time/models/epic-time-daily-allocator.js +125 -0
  28. package/dist/server/modules/epic-time/models/epic-time-daily-allocator.js.map +1 -0
  29. package/dist/server/modules/epic-time/models/epic-time-local-day.d.ts +9 -0
  30. package/dist/server/modules/epic-time/models/epic-time-local-day.js +164 -0
  31. package/dist/server/modules/epic-time/models/epic-time-local-day.js.map +1 -0
  32. package/dist/server/modules/epic-time/models/epic-time.models.d.ts +138 -0
  33. package/dist/server/modules/epic-time/models/epic-time.models.js +2 -0
  34. package/dist/server/modules/epic-time/models/epic-time.models.js.map +1 -1
  35. package/dist/server/modules/epic-time/services/agent-time-accounting.service.d.ts +1 -0
  36. package/dist/server/modules/epic-time/services/agent-time-accounting.service.js +31 -8
  37. package/dist/server/modules/epic-time/services/agent-time-accounting.service.js.map +1 -1
  38. package/dist/server/modules/epic-time/services/epic-estimate-logging.service.d.ts +37 -0
  39. package/dist/server/modules/epic-time/services/epic-estimate-logging.service.js +716 -0
  40. package/dist/server/modules/epic-time/services/epic-estimate-logging.service.js.map +1 -0
  41. package/dist/server/modules/epic-time/services/epic-time.service.d.ts +12 -3
  42. package/dist/server/modules/epic-time/services/epic-time.service.js +155 -69
  43. package/dist/server/modules/epic-time/services/epic-time.service.js.map +1 -1
  44. package/dist/server/modules/epic-time/services/epic-time.store.d.ts +29 -3
  45. package/dist/server/modules/epic-time/services/epic-time.store.js +524 -40
  46. package/dist/server/modules/epic-time/services/epic-time.store.js.map +1 -1
  47. package/dist/server/modules/epics/controllers/epics.controller.d.ts +10 -1
  48. package/dist/server/modules/epics/controllers/epics.controller.js +108 -2
  49. package/dist/server/modules/epics/controllers/epics.controller.js.map +1 -1
  50. package/dist/server/modules/epics/epics.module.js +8 -2
  51. package/dist/server/modules/epics/epics.module.js.map +1 -1
  52. package/dist/server/modules/epics/models/epic-relations.models.d.ts +34 -0
  53. package/dist/server/modules/epics/models/epic-relations.models.js +16 -0
  54. package/dist/server/modules/epics/models/epic-relations.models.js.map +1 -0
  55. package/dist/server/modules/epics/services/epic-relation-target-resolver.d.ts +8 -0
  56. package/dist/server/modules/epics/services/epic-relation-target-resolver.js +37 -0
  57. package/dist/server/modules/epics/services/epic-relation-target-resolver.js.map +1 -0
  58. package/dist/server/modules/epics/services/epic-relations.service.d.ts +52 -0
  59. package/dist/server/modules/epics/services/epic-relations.service.js +135 -0
  60. package/dist/server/modules/epics/services/epic-relations.service.js.map +1 -0
  61. package/dist/server/modules/epics/services/epics.service.d.ts +9 -2
  62. package/dist/server/modules/epics/services/epics.service.js +115 -11
  63. package/dist/server/modules/epics/services/epics.service.js.map +1 -1
  64. package/dist/server/modules/events/catalog/broadcast-registry.js +16 -0
  65. package/dist/server/modules/events/catalog/broadcast-registry.js.map +1 -1
  66. package/dist/server/modules/events/catalog/epic.relations.invalidated.d.ts +12 -0
  67. package/dist/server/modules/events/catalog/epic.relations.invalidated.js +13 -0
  68. package/dist/server/modules/events/catalog/epic.relations.invalidated.js.map +1 -0
  69. package/dist/server/modules/events/catalog/epic.time.scope.invalidated.d.ts +12 -0
  70. package/dist/server/modules/events/catalog/epic.time.scope.invalidated.js +13 -0
  71. package/dist/server/modules/events/catalog/epic.time.scope.invalidated.js.map +1 -0
  72. package/dist/server/modules/events/catalog/index.d.ts +27 -4
  73. package/dist/server/modules/events/catalog/index.js +6 -0
  74. package/dist/server/modules/events/catalog/index.js.map +1 -1
  75. package/dist/server/modules/events/catalog/integration.connection.created.d.ts +4 -1
  76. package/dist/server/modules/events/catalog/integration.connection.created.js +5 -2
  77. package/dist/server/modules/events/catalog/integration.connection.created.js.map +1 -1
  78. package/dist/server/modules/events/catalog/integration.connection.deleted.d.ts +4 -1
  79. package/dist/server/modules/events/catalog/integration.connection.deleted.js +5 -2
  80. package/dist/server/modules/events/catalog/integration.connection.deleted.js.map +1 -1
  81. package/dist/server/modules/events/catalog/integration.connection.legacy.d.ts +85 -0
  82. package/dist/server/modules/events/catalog/integration.connection.legacy.js +41 -0
  83. package/dist/server/modules/events/catalog/integration.connection.legacy.js.map +1 -0
  84. package/dist/server/modules/events/catalog/integration.connection.updated.d.ts +4 -1
  85. package/dist/server/modules/events/catalog/integration.connection.updated.js +5 -2
  86. package/dist/server/modules/events/catalog/integration.connection.updated.js.map +1 -1
  87. package/dist/server/modules/events/services/committed-event-replay.d.ts +10 -0
  88. package/dist/server/modules/events/services/committed-event-replay.js +30 -0
  89. package/dist/server/modules/events/services/committed-event-replay.js.map +1 -0
  90. package/dist/server/modules/events/services/committed-event.store.d.ts +1 -0
  91. package/dist/server/modules/events/services/committed-event.store.js +12 -7
  92. package/dist/server/modules/events/services/committed-event.store.js.map +1 -1
  93. package/dist/server/modules/events/services/durable-event-registry.service.d.ts +9 -2
  94. package/dist/server/modules/events/services/durable-event-registry.service.js.map +1 -1
  95. package/dist/server/modules/external-integrations/adapters/clickup-external-task.provider.d.ts +7 -0
  96. package/dist/server/modules/external-integrations/adapters/clickup-external-task.provider.js +158 -50
  97. package/dist/server/modules/external-integrations/adapters/clickup-external-task.provider.js.map +1 -1
  98. package/dist/server/modules/external-integrations/adapters/jira-external-task.provider.d.ts +4 -0
  99. package/dist/server/modules/external-integrations/adapters/jira-external-task.provider.js +73 -33
  100. package/dist/server/modules/external-integrations/adapters/jira-external-task.provider.js.map +1 -1
  101. package/dist/server/modules/external-integrations/connections/integration-connections.controller.d.ts +6 -3
  102. package/dist/server/modules/external-integrations/connections/integration-connections.controller.js +67 -12
  103. package/dist/server/modules/external-integrations/connections/integration-connections.controller.js.map +1 -1
  104. package/dist/server/modules/external-integrations/connections/integration-connections.service.d.ts +35 -3
  105. package/dist/server/modules/external-integrations/connections/integration-connections.service.js +178 -17
  106. package/dist/server/modules/external-integrations/connections/integration-connections.service.js.map +1 -1
  107. package/dist/server/modules/external-integrations/connections/managed-subtask-sync.controller.d.ts +6 -3
  108. package/dist/server/modules/external-integrations/connections/managed-subtask-sync.controller.js +51 -11
  109. package/dist/server/modules/external-integrations/connections/managed-subtask-sync.controller.js.map +1 -1
  110. package/dist/server/modules/external-integrations/external-integrations.module.js +6 -1
  111. package/dist/server/modules/external-integrations/external-integrations.module.js.map +1 -1
  112. package/dist/server/modules/external-integrations/models/external-edit-session.models.d.ts +1 -0
  113. package/dist/server/modules/external-integrations/models/external-provider.models.d.ts +6 -2
  114. package/dist/server/modules/external-integrations/models/external-provider.models.js +2 -3
  115. package/dist/server/modules/external-integrations/models/external-provider.models.js.map +1 -1
  116. package/dist/server/modules/external-integrations/models/external-time-mutation.models.d.ts +33 -2
  117. package/dist/server/modules/external-integrations/models/external-time-mutation.models.js +14 -0
  118. package/dist/server/modules/external-integrations/models/external-time-mutation.models.js.map +1 -1
  119. package/dist/server/modules/external-integrations/my-work/external-edit-session.service.d.ts +10 -9
  120. package/dist/server/modules/external-integrations/my-work/external-edit-session.service.js +81 -46
  121. package/dist/server/modules/external-integrations/my-work/external-edit-session.service.js.map +1 -1
  122. package/dist/server/modules/external-integrations/my-work/external-my-work.controller.d.ts +22 -20
  123. package/dist/server/modules/external-integrations/my-work/external-my-work.controller.js +136 -64
  124. package/dist/server/modules/external-integrations/my-work/external-my-work.controller.js.map +1 -1
  125. package/dist/server/modules/external-integrations/my-work/external-my-work.openapi.d.ts +7 -0
  126. package/dist/server/modules/external-integrations/my-work/external-my-work.openapi.js +289 -4
  127. package/dist/server/modules/external-integrations/my-work/external-my-work.openapi.js.map +1 -1
  128. package/dist/server/modules/external-integrations/my-work/external-my-work.service.d.ts +12 -7
  129. package/dist/server/modules/external-integrations/my-work/external-my-work.service.js +114 -54
  130. package/dist/server/modules/external-integrations/my-work/external-my-work.service.js.map +1 -1
  131. package/dist/server/modules/external-integrations/my-work/external-time-mutation.service.d.ts +19 -6
  132. package/dist/server/modules/external-integrations/my-work/external-time-mutation.service.js +346 -46
  133. package/dist/server/modules/external-integrations/my-work/external-time-mutation.service.js.map +1 -1
  134. package/dist/server/modules/external-integrations/sessions/external-edit-session.store.d.ts +2 -1
  135. package/dist/server/modules/external-integrations/sessions/external-edit-session.store.js +3 -2
  136. package/dist/server/modules/external-integrations/sessions/external-edit-session.store.js.map +1 -1
  137. package/dist/server/modules/external-integrations/sessions/external-time-mutation.store.d.ts +6 -1
  138. package/dist/server/modules/external-integrations/sessions/external-time-mutation.store.js +19 -0
  139. package/dist/server/modules/external-integrations/sessions/external-time-mutation.store.js.map +1 -1
  140. package/dist/server/modules/external-integrations/sessions/provider-operation-gate.d.ts +15 -2
  141. package/dist/server/modules/external-integrations/sessions/provider-operation-gate.js +33 -6
  142. package/dist/server/modules/external-integrations/sessions/provider-operation-gate.js.map +1 -1
  143. package/dist/server/modules/external-integrations/subscribers/external-subtask-sync.subscriber.d.ts +13 -6
  144. package/dist/server/modules/external-integrations/subscribers/external-subtask-sync.subscriber.js +212 -75
  145. package/dist/server/modules/external-integrations/subscribers/external-subtask-sync.subscriber.js.map +1 -1
  146. package/dist/server/modules/external-integrations/subscribers/managed-subtask-content.d.ts +13 -4
  147. package/dist/server/modules/external-integrations/subscribers/managed-subtask-content.js +98 -0
  148. package/dist/server/modules/external-integrations/subscribers/managed-subtask-content.js.map +1 -1
  149. package/dist/server/modules/external-integrations/subscribers/managed-subtask-sync-health.service.d.ts +10 -4
  150. package/dist/server/modules/external-integrations/subscribers/managed-subtask-sync-health.service.js +57 -18
  151. package/dist/server/modules/external-integrations/subscribers/managed-subtask-sync-health.service.js.map +1 -1
  152. package/dist/server/modules/mcp/dtos/mcp.dto.d.ts +139 -0
  153. package/dist/server/modules/mcp/dtos/mcp.dto.js +41 -1
  154. package/dist/server/modules/mcp/dtos/mcp.dto.js.map +1 -1
  155. package/dist/server/modules/mcp/services/handlers/epic-context.d.ts +2 -0
  156. package/dist/server/modules/mcp/services/handlers/epic-tools.d.ts +4 -0
  157. package/dist/server/modules/mcp/services/handlers/epic-tools.js +275 -0
  158. package/dist/server/modules/mcp/services/handlers/epic-tools.js.map +1 -1
  159. package/dist/server/modules/mcp/services/mcp-tool-binding.registry.d.ts +2 -1
  160. package/dist/server/modules/mcp/services/mcp-tool-binding.registry.js +13 -8
  161. package/dist/server/modules/mcp/services/mcp-tool-binding.registry.js.map +1 -1
  162. package/dist/server/modules/mcp/services/testing/mcp-tool-binding-registry.fixture.d.ts +1 -1
  163. package/dist/server/modules/mcp/services/testing/mcp-tool-binding-registry.fixture.js +1 -1
  164. package/dist/server/modules/mcp/services/testing/mcp-tool-binding-registry.fixture.js.map +1 -1
  165. package/dist/server/modules/mcp/tool-descriptors/binding-types.d.ts +2 -0
  166. package/dist/server/modules/mcp/tool-descriptors/binding-types.js.map +1 -1
  167. package/dist/server/modules/mcp/tool-descriptors/epic.bindings.js +6 -0
  168. package/dist/server/modules/mcp/tool-descriptors/epic.bindings.js.map +1 -1
  169. package/dist/server/modules/mcp/tool-descriptors/epic.metadata.js +132 -0
  170. package/dist/server/modules/mcp/tool-descriptors/epic.metadata.js.map +1 -1
  171. package/dist/server/modules/projects/controllers/projects.controller.js +1 -4
  172. package/dist/server/modules/projects/controllers/projects.controller.js.map +1 -1
  173. package/dist/server/modules/projects/projects.module.js +2 -0
  174. package/dist/server/modules/projects/projects.module.js.map +1 -1
  175. package/dist/server/modules/projects/services/projects.service.d.ts +4 -1
  176. package/dist/server/modules/projects/services/projects.service.js +23 -2
  177. package/dist/server/modules/projects/services/projects.service.js.map +1 -1
  178. package/dist/server/modules/providers/adapters/claude.adapter.d.ts +4 -2
  179. package/dist/server/modules/providers/adapters/claude.adapter.js +31 -0
  180. package/dist/server/modules/providers/adapters/claude.adapter.js.map +1 -1
  181. package/dist/server/modules/providers/services/provider-mcp-ensure.service.d.ts +7 -0
  182. package/dist/server/modules/providers/services/provider-mcp-ensure.service.js +69 -4
  183. package/dist/server/modules/providers/services/provider-mcp-ensure.service.js.map +1 -1
  184. package/dist/server/modules/session-reader/data/pricing.json +452 -56
  185. package/dist/server/modules/session-reader/services/session-reader.service.d.ts +2 -0
  186. package/dist/server/modules/session-reader/services/session-reader.service.js +85 -9
  187. package/dist/server/modules/session-reader/services/session-reader.service.js.map +1 -1
  188. package/dist/server/modules/session-reader/services/transcript-path-validator.service.js +8 -2
  189. package/dist/server/modules/session-reader/services/transcript-path-validator.service.js.map +1 -1
  190. package/dist/server/modules/session-reader/services/transcript-watcher.service.js +44 -17
  191. package/dist/server/modules/session-reader/services/transcript-watcher.service.js.map +1 -1
  192. package/dist/server/modules/sessions/services/session-runtime/__test-utils__/pipeline-harness.d.ts +1 -0
  193. package/dist/server/modules/sessions/services/session-runtime/__test-utils__/pipeline-harness.js +1 -0
  194. package/dist/server/modules/sessions/services/session-runtime/__test-utils__/pipeline-harness.js.map +1 -1
  195. package/dist/server/modules/sessions/services/session-runtime/session-launch-pipeline.service.js +11 -1
  196. package/dist/server/modules/sessions/services/session-runtime/session-launch-pipeline.service.js.map +1 -1
  197. package/dist/server/modules/sessions/utils/claude-config.d.ts +4 -1
  198. package/dist/server/modules/sessions/utils/claude-config.js +265 -60
  199. package/dist/server/modules/sessions/utils/claude-config.js.map +1 -1
  200. package/dist/server/modules/storage/db/schema.d.ts +1109 -98
  201. package/dist/server/modules/storage/db/schema.js +141 -3
  202. package/dist/server/modules/storage/db/schema.js.map +1 -1
  203. package/dist/server/modules/storage/db/sqlite-json.d.ts +1 -0
  204. package/dist/server/modules/storage/db/sqlite-json.js +4 -0
  205. package/dist/server/modules/storage/db/sqlite-json.js.map +1 -1
  206. package/dist/server/modules/storage/interfaces/storage.interface.d.ts +58 -10
  207. package/dist/server/modules/storage/interfaces/storage.interface.js.map +1 -1
  208. package/dist/server/modules/storage/local/delegates/epic.delegate.d.ts +30 -5
  209. package/dist/server/modules/storage/local/delegates/epic.delegate.js +718 -14
  210. package/dist/server/modules/storage/local/delegates/epic.delegate.js.map +1 -1
  211. package/dist/server/modules/storage/local/delegates/external-estimate-log.delegate.d.ts +52 -0
  212. package/dist/server/modules/storage/local/delegates/external-estimate-log.delegate.js +659 -0
  213. package/dist/server/modules/storage/local/delegates/external-estimate-log.delegate.js.map +1 -0
  214. package/dist/server/modules/storage/local/delegates/external-managed-subtask.delegate.d.ts +2 -1
  215. package/dist/server/modules/storage/local/delegates/external-managed-subtask.delegate.js +17 -4
  216. package/dist/server/modules/storage/local/delegates/external-managed-subtask.delegate.js.map +1 -1
  217. package/dist/server/modules/storage/local/delegates/integration.delegate.d.ts +23 -8
  218. package/dist/server/modules/storage/local/delegates/integration.delegate.js +238 -52
  219. package/dist/server/modules/storage/local/delegates/integration.delegate.js.map +1 -1
  220. package/dist/server/modules/storage/local/delegates/project.delegate.d.ts +2 -0
  221. package/dist/server/modules/storage/local/delegates/project.delegate.js +180 -130
  222. package/dist/server/modules/storage/local/delegates/project.delegate.js.map +1 -1
  223. package/dist/server/modules/storage/local/local-storage.service.d.ts +45 -10
  224. package/dist/server/modules/storage/local/local-storage.service.js +99 -15
  225. package/dist/server/modules/storage/local/local-storage.service.js.map +1 -1
  226. package/dist/server/modules/storage/models/domain.models.d.ts +170 -0
  227. package/dist/server/modules/storage/models/domain.models.js +9 -0
  228. package/dist/server/modules/storage/models/domain.models.js.map +1 -1
  229. package/dist/server/templates/teams-dev.json +311 -301
  230. package/dist/server/tsconfig.tsbuildinfo +1 -1
  231. package/dist/server/ui/assets/{ExternalRichEditor-DMtnZIMZ.js → ExternalRichEditor-BG9SopK8.js} +1 -1
  232. package/dist/server/ui/assets/{ReviewDetailPage-BmyiM5IR.js → ReviewDetailPage-DNo7Qx3m.js} +1 -1
  233. package/dist/server/ui/assets/{ReviewsPage-CQ87BMwT.js → ReviewsPage-Cay-IObR.js} +1 -1
  234. package/dist/server/ui/assets/index-C1Fdqua-.js +1197 -0
  235. package/dist/server/ui/assets/index-CV8ydm7w.css +32 -0
  236. package/dist/server/ui/assets/{useReviewSubscription-DjbEUzt4.js → useReviewSubscription-sAAfh4k9.js} +1 -1
  237. package/dist/server/ui/index.html +2 -2
  238. package/dist/templates/teams-dev.json +311 -301
  239. package/package.json +19 -2
  240. package/dist/server/ui/assets/index-BX6Yze-A.js +0 -1177
  241. package/dist/server/ui/assets/index-qoQEv_u4.css +0 -32
@@ -0,0 +1,659 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ExternalEstimateLogStorageDelegate = void 0;
4
+ const drizzle_orm_1 = require("drizzle-orm");
5
+ const error_types_1 = require("../../../../common/errors/error-types");
6
+ const schema_1 = require("../../db/schema");
7
+ const base_storage_delegate_1 = require("./base-storage.delegate");
8
+ const MAX_TIME_ENTRY_DURATION_MS = 7 * 24 * 60 * 60 * 1_000;
9
+ const MAX_BATCH_IDENTITIES = 1_000;
10
+ const MAX_DAILY_LEDGER_ROWS = 3_660;
11
+ const OPERATION_ID_PATTERN = /^[A-Za-z0-9:_.-]{1,128}$/;
12
+ const ACTIVITY_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
13
+ const MAX_ZONE_LENGTH = 128;
14
+ const LIST_LOGGED_MINUTES_SQL = `
15
+ WITH input(remote_scope_key, remote_task_id) AS MATERIALIZED (
16
+ SELECT
17
+ json_extract(value, '$.remoteScopeKey'),
18
+ json_extract(value, '$.remoteTaskId')
19
+ FROM json_each(?)
20
+ )
21
+ SELECT
22
+ states.remote_scope_key AS "remoteScopeKey",
23
+ states.remote_task_id AS "remoteTaskId",
24
+ states.logged_minutes AS "loggedMinutes"
25
+ FROM input
26
+ CROSS JOIN external_estimate_log_states AS states
27
+ ON states.provider = ?
28
+ AND states.remote_scope_key = input.remote_scope_key
29
+ AND states.remote_task_id = input.remote_task_id
30
+ ORDER BY states.remote_scope_key, states.remote_task_id`;
31
+ const UPSERT_DAY_SQL = `
32
+ INSERT INTO external_estimate_log_days
33
+ (provider, remote_scope_key, remote_task_id, activity_date,
34
+ logged_minutes, created_at, updated_at)
35
+ VALUES (?, ?, ?, ?, ?, ?, ?)
36
+ ON CONFLICT(provider, remote_scope_key, remote_task_id, activity_date) DO UPDATE SET
37
+ logged_minutes = logged_minutes + excluded.logged_minutes,
38
+ updated_at = excluded.updated_at`;
39
+ const LIST_BY_REMOTE_TASK_SQL = `
40
+ SELECT
41
+ states.provider AS "provider",
42
+ states.remote_scope_key AS "remoteScopeKey",
43
+ states.remote_task_id AS "remoteTaskId",
44
+ states.logged_minutes AS "loggedMinutes",
45
+ states.revision AS "revision",
46
+ states.pending_operation_id AS "pendingOperationId",
47
+ states.pending_delta_minutes AS "pendingDeltaMinutes",
48
+ states.pending_estimate_total_minutes AS "pendingEstimateTotalMinutes",
49
+ states.pending_started_at AS "pendingStartedAt",
50
+ states.pending_connection_id AS "pendingConnectionId",
51
+ states.pending_connection_generation AS "pendingConnectionGeneration",
52
+ states.pending_phase AS "pendingPhase",
53
+ states.pending_resolution AS "pendingResolution",
54
+ states.aggregation_time_zone AS "aggregationTimeZone",
55
+ states.pending_activity_date AS "pendingActivityDate",
56
+ states.created_at AS "createdAt",
57
+ states.updated_at AS "updatedAt",
58
+ COALESCE((
59
+ SELECT SUM(days.logged_minutes)
60
+ FROM external_estimate_log_days AS days
61
+ WHERE days.provider = states.provider
62
+ AND days.remote_scope_key = states.remote_scope_key
63
+ AND days.remote_task_id = states.remote_task_id
64
+ ), 0) AS "daySum"
65
+ FROM external_estimate_log_states AS states
66
+ WHERE states.provider = ? AND states.remote_task_id = ?
67
+ ORDER BY states.remote_scope_key`;
68
+ class ExternalEstimateLogStorageDelegate extends base_storage_delegate_1.BaseStorageDelegate {
69
+ constructor(context) {
70
+ super(context);
71
+ }
72
+ async get(identity) {
73
+ const normalized = this.normalizeIdentity(identity);
74
+ const row = this.getRow(normalized);
75
+ if (!row) {
76
+ return null;
77
+ }
78
+ this.assertDaysWithinScalar(normalized, row.loggedMinutes);
79
+ return this.mapState(row);
80
+ }
81
+ async getDailyCheckpoint(identity) {
82
+ const normalized = this.normalizeIdentity(identity);
83
+ const row = this.getRow(normalized);
84
+ if (!row) {
85
+ return null;
86
+ }
87
+ const days = this.listDayRows(normalized);
88
+ if (days.length > MAX_DAILY_LEDGER_ROWS) {
89
+ throw new error_types_1.StorageError(`External estimate log dated ledger exceeds ${MAX_DAILY_LEDGER_ROWS} rows.`);
90
+ }
91
+ const daySum = days.reduce((total, day) => total + day.loggedMinutes, 0);
92
+ if (daySum > row.loggedMinutes) {
93
+ throw this.ledgerExceedsScalar();
94
+ }
95
+ return {
96
+ state: this.mapState(row),
97
+ days,
98
+ unallocatedLoggedMinutes: row.loggedMinutes - daySum,
99
+ };
100
+ }
101
+ async listByRemoteTask(provider, remoteTaskId) {
102
+ const normalizedProvider = this.requireProvider(provider);
103
+ const normalizedTaskId = this.requireIdentifier(remoteTaskId, 'Remote task');
104
+ const rows = this.rawClient
105
+ .prepare(LIST_BY_REMOTE_TASK_SQL)
106
+ .all(normalizedProvider, normalizedTaskId);
107
+ return rows.map((row) => {
108
+ if (row.daySum > row.loggedMinutes) {
109
+ throw this.ledgerExceedsScalar();
110
+ }
111
+ return this.mapState(row);
112
+ });
113
+ }
114
+ listLoggedMinutes(provider, identities) {
115
+ if (identities.length === 0) {
116
+ return [];
117
+ }
118
+ const normalizedProvider = this.requireProvider(provider);
119
+ const unique = new Map();
120
+ for (const identity of identities) {
121
+ const remoteScopeKey = this.requireIdentifier(identity.remoteScopeKey, 'Remote scope');
122
+ const remoteTaskId = this.requireIdentifier(identity.remoteTaskId, 'Remote task');
123
+ unique.set(`${remoteScopeKey}\u0000${remoteTaskId}`, { remoteScopeKey, remoteTaskId });
124
+ }
125
+ if (unique.size > MAX_BATCH_IDENTITIES) {
126
+ throw new error_types_1.ValidationError(`At most ${MAX_BATCH_IDENTITIES} estimate checkpoint identities may be read at once.`);
127
+ }
128
+ const seed = JSON.stringify([...unique.values()]);
129
+ return this.rawClient
130
+ .prepare(LIST_LOGGED_MINUTES_SQL)
131
+ .all(seed, normalizedProvider);
132
+ }
133
+ async setLoggedMinutes(data) {
134
+ const normalized = this.normalizeIdentity(data);
135
+ this.requireNonnegativeInteger(data.loggedMinutes, 'Logged minutes');
136
+ this.requireExpectedRevision(data.expectedRevision);
137
+ const aggregationTimeZone = data.aggregationTimeZone === null
138
+ ? null
139
+ : this.requireAggregationZone(data.aggregationTimeZone);
140
+ const currentTotals = this.requireDailyTotals(data.currentDailyTotals, 'Current daily totals');
141
+ return this.txRunner.runImmediateQueuedOrJoin(() => {
142
+ this.assertCurrentLink(normalized);
143
+ const existing = this.getRow(normalized);
144
+ this.assertExpectedRevision(existing, data.expectedRevision, normalized);
145
+ if (existing?.pendingOperationId) {
146
+ throw new error_types_1.ConflictError('Estimate log state has a pending operation.', {
147
+ operationId: existing.pendingOperationId,
148
+ });
149
+ }
150
+ this.assertDaysWithinScalar(normalized, existing?.loggedMinutes ?? 0);
151
+ const now = new Date().toISOString();
152
+ const boundZone = aggregationTimeZone ?? existing?.aggregationTimeZone ?? null;
153
+ if (!existing) {
154
+ this.db
155
+ .insert(schema_1.externalEstimateLogStates)
156
+ .values({
157
+ ...normalized,
158
+ loggedMinutes: data.loggedMinutes,
159
+ revision: 1,
160
+ aggregationTimeZone: boundZone,
161
+ createdAt: now,
162
+ updatedAt: now,
163
+ })
164
+ .run();
165
+ }
166
+ else {
167
+ this.updateByRevision(normalized, existing.revision, {
168
+ loggedMinutes: data.loggedMinutes,
169
+ aggregationTimeZone: boundZone,
170
+ revision: existing.revision + 1,
171
+ updatedAt: now,
172
+ });
173
+ }
174
+ this.clearDays(normalized);
175
+ const currentTotal = currentTotals.reduce((total, day) => total + day.minutes, 0);
176
+ const placements = this.computeCreditPlacements(Math.min(data.loggedMinutes, currentTotal), currentTotals, []);
177
+ this.applyDayPlacements(normalized, placements, now);
178
+ return this.requireState(normalized);
179
+ });
180
+ }
181
+ async prepare(data) {
182
+ const normalized = this.normalizeIdentity(data);
183
+ const operationId = this.requireOperationId(data.operationId);
184
+ this.requireExpectedRevision(data.expectedRevision);
185
+ const activityDate = data.activityDate === null ? null : this.requireActivityDate(data.activityDate);
186
+ const aggregationTimeZone = data.aggregationTimeZone === null
187
+ ? null
188
+ : this.requireAggregationZone(data.aggregationTimeZone);
189
+ const capturedTotals = this.requireDailyTotals(data.capturedDailyTotals, 'Captured daily totals');
190
+ return this.txRunner.runImmediateQueuedOrJoin(() => {
191
+ this.requirePositiveDelta(data.deltaMinutes);
192
+ this.requireNonnegativeInteger(data.estimateTotalMinutes, 'Estimate total minutes');
193
+ const startedAt = this.requireTimestamp(data.startedAt);
194
+ const connectionId = this.requireIdentifier(data.connectionId, 'Connection');
195
+ this.requirePositiveInteger(data.connectionGeneration, 'Connection generation');
196
+ this.assertCurrentLink(normalized);
197
+ this.assertConnectionEpoch(normalized.provider, connectionId, data.connectionGeneration);
198
+ const existing = this.getRow(normalized);
199
+ this.assertExpectedRevision(existing, data.expectedRevision, normalized);
200
+ if (existing?.pendingOperationId) {
201
+ throw new error_types_1.ConflictError('Estimate log state has a pending operation.', {
202
+ operationId: existing.pendingOperationId,
203
+ });
204
+ }
205
+ const now = new Date().toISOString();
206
+ const existingDays = this.listDayRows(normalized);
207
+ const daySum = existingDays.reduce((total, day) => total + day.loggedMinutes, 0);
208
+ const creditMinutes = Math.max(0, (existing?.loggedMinutes ?? 0) - daySum);
209
+ const placements = this.computeCreditPlacements(creditMinutes, capturedTotals, existingDays);
210
+ this.applyDayPlacements(normalized, placements, now);
211
+ const boundZone = aggregationTimeZone ?? existing?.aggregationTimeZone ?? null;
212
+ const pending = {
213
+ pendingOperationId: operationId,
214
+ pendingDeltaMinutes: data.deltaMinutes,
215
+ pendingEstimateTotalMinutes: data.estimateTotalMinutes,
216
+ pendingStartedAt: startedAt,
217
+ pendingConnectionId: connectionId,
218
+ pendingConnectionGeneration: data.connectionGeneration,
219
+ pendingPhase: 'prepared',
220
+ pendingResolution: null,
221
+ pendingActivityDate: activityDate,
222
+ };
223
+ if (!existing) {
224
+ this.db
225
+ .insert(schema_1.externalEstimateLogStates)
226
+ .values({
227
+ ...normalized,
228
+ loggedMinutes: 0,
229
+ revision: 1,
230
+ aggregationTimeZone: boundZone,
231
+ ...pending,
232
+ createdAt: now,
233
+ updatedAt: now,
234
+ })
235
+ .run();
236
+ }
237
+ else {
238
+ this.updateByRevision(normalized, existing.revision, {
239
+ ...pending,
240
+ aggregationTimeZone: boundZone,
241
+ revision: existing.revision + 1,
242
+ updatedAt: now,
243
+ });
244
+ }
245
+ return this.requireState(normalized);
246
+ });
247
+ }
248
+ async markOutcomeUnknown(data) {
249
+ const normalized = this.normalizeIdentity(data);
250
+ const operationId = this.requireOperationId(data.operationId);
251
+ this.requireExpectedRevision(data.expectedRevision);
252
+ return this.txRunner.runImmediateQueuedOrJoin(() => {
253
+ this.assertCurrentLink(normalized);
254
+ const state = this.requirePending(normalized, operationId);
255
+ this.assertExpectedRevision(state, data.expectedRevision, normalized);
256
+ if (state.pendingPhase === 'outcome_unknown') {
257
+ return this.mapState(state);
258
+ }
259
+ this.updateByRevision(normalized, state.revision, {
260
+ pendingPhase: 'outcome_unknown',
261
+ revision: state.revision + 1,
262
+ updatedAt: new Date().toISOString(),
263
+ });
264
+ return this.requireState(normalized);
265
+ });
266
+ }
267
+ async confirm(data) {
268
+ return this.settle(data, 'logged');
269
+ }
270
+ async clear(data) {
271
+ return this.settle(data, 'not_logged');
272
+ }
273
+ async storeResolution(data) {
274
+ const normalized = this.normalizeIdentity(data);
275
+ const operationId = this.requireOperationId(data.operationId);
276
+ this.requireExpectedRevision(data.expectedRevision);
277
+ if (data.resolution !== 'logged' && data.resolution !== 'not_logged') {
278
+ throw new error_types_1.ValidationError('Estimate operation resolution is invalid.');
279
+ }
280
+ return this.txRunner.runImmediateQueuedOrJoin(() => {
281
+ this.assertCurrentLink(normalized);
282
+ const state = this.requirePending(normalized, operationId);
283
+ this.assertExpectedRevision(state, data.expectedRevision, normalized);
284
+ if (state.pendingResolution === data.resolution) {
285
+ return this.mapState(state);
286
+ }
287
+ if (state.pendingResolution !== null) {
288
+ throw new error_types_1.ConflictError('Estimate operation already has a different resolution.', {
289
+ operationId,
290
+ });
291
+ }
292
+ this.updateByRevision(normalized, state.revision, {
293
+ pendingResolution: data.resolution,
294
+ revision: state.revision + 1,
295
+ updatedAt: new Date().toISOString(),
296
+ });
297
+ return this.requireState(normalized);
298
+ });
299
+ }
300
+ async applyResolution(data) {
301
+ const normalized = this.normalizeIdentity(data);
302
+ const operationId = this.requireOperationId(data.operationId);
303
+ this.requireExpectedRevision(data.expectedRevision);
304
+ return this.txRunner.runImmediateQueuedOrJoin(() => {
305
+ this.assertCurrentLink(normalized);
306
+ const state = this.requireState(normalized);
307
+ if (state.pendingOperationId === null) {
308
+ return state;
309
+ }
310
+ if (state.pendingOperationId !== operationId) {
311
+ throw new error_types_1.ConflictError('A different estimate operation is pending.', {
312
+ operationId: state.pendingOperationId,
313
+ });
314
+ }
315
+ this.assertExpectedRevision(state, data.expectedRevision, normalized);
316
+ if (state.pendingResolution === null) {
317
+ throw new error_types_1.ConflictError('Estimate operation has no stored resolution.', { operationId });
318
+ }
319
+ return this.applySettlement(normalized, state, state.pendingResolution === 'logged');
320
+ });
321
+ }
322
+ settle(data, resolution) {
323
+ const normalized = this.normalizeIdentity(data);
324
+ const operationId = this.requireOperationId(data.operationId);
325
+ this.requireExpectedRevision(data.expectedRevision);
326
+ return this.txRunner.runImmediateQueuedOrJoin(() => {
327
+ this.assertCurrentLink(normalized);
328
+ const state = this.requireState(normalized);
329
+ if (state.pendingOperationId === null) {
330
+ return state;
331
+ }
332
+ if (state.pendingOperationId !== operationId) {
333
+ throw new error_types_1.ConflictError('A different estimate operation is pending.', {
334
+ operationId: state.pendingOperationId,
335
+ });
336
+ }
337
+ this.assertExpectedRevision(state, data.expectedRevision, normalized);
338
+ return this.applySettlement(normalized, state, resolution === 'logged');
339
+ });
340
+ }
341
+ applySettlement(identity, state, addDelta) {
342
+ const nextLoggedMinutes = addDelta
343
+ ? state.loggedMinutes + state.pendingDeltaMinutes
344
+ : state.loggedMinutes;
345
+ if (!Number.isSafeInteger(nextLoggedMinutes)) {
346
+ throw new error_types_1.ValidationError('Logged minutes exceed the supported numeric range.');
347
+ }
348
+ const now = new Date().toISOString();
349
+ this.updateByRevision(identity, state.revision, {
350
+ loggedMinutes: nextLoggedMinutes,
351
+ revision: state.revision + 1,
352
+ pendingOperationId: null,
353
+ pendingDeltaMinutes: null,
354
+ pendingEstimateTotalMinutes: null,
355
+ pendingStartedAt: null,
356
+ pendingConnectionId: null,
357
+ pendingConnectionGeneration: null,
358
+ pendingPhase: null,
359
+ pendingResolution: null,
360
+ pendingActivityDate: null,
361
+ updatedAt: now,
362
+ });
363
+ if (addDelta && state.pendingActivityDate !== null) {
364
+ this.upsertDayIncrement(identity, state.pendingActivityDate, state.pendingDeltaMinutes, now);
365
+ }
366
+ return this.requireState(identity);
367
+ }
368
+ listDayRows(identity) {
369
+ return this.db
370
+ .select({
371
+ provider: schema_1.externalEstimateLogDays.provider,
372
+ remoteScopeKey: schema_1.externalEstimateLogDays.remoteScopeKey,
373
+ remoteTaskId: schema_1.externalEstimateLogDays.remoteTaskId,
374
+ activityDate: schema_1.externalEstimateLogDays.activityDate,
375
+ loggedMinutes: schema_1.externalEstimateLogDays.loggedMinutes,
376
+ })
377
+ .from(schema_1.externalEstimateLogDays)
378
+ .where(this.dayPredicate(identity))
379
+ .orderBy((0, drizzle_orm_1.asc)(schema_1.externalEstimateLogDays.activityDate))
380
+ .all();
381
+ }
382
+ sumDayMinutes(identity) {
383
+ const row = this.rawClient
384
+ .prepare(`SELECT COALESCE(SUM(logged_minutes), 0) AS total
385
+ FROM external_estimate_log_days
386
+ WHERE provider = ? AND remote_scope_key = ? AND remote_task_id = ?`)
387
+ .get(identity.provider, identity.remoteScopeKey, identity.remoteTaskId);
388
+ return row.total;
389
+ }
390
+ assertDaysWithinScalar(identity, loggedMinutes) {
391
+ if (this.sumDayMinutes(identity) > loggedMinutes) {
392
+ throw this.ledgerExceedsScalar();
393
+ }
394
+ }
395
+ ledgerExceedsScalar() {
396
+ return new error_types_1.StorageError('External estimate log dated ledger exceeds the scalar checkpoint.');
397
+ }
398
+ clearDays(identity) {
399
+ this.db.delete(schema_1.externalEstimateLogDays).where(this.dayPredicate(identity)).run();
400
+ }
401
+ upsertDayIncrement(identity, activityDate, minutes, now) {
402
+ this.rawClient
403
+ .prepare(UPSERT_DAY_SQL)
404
+ .run(identity.provider, identity.remoteScopeKey, identity.remoteTaskId, activityDate, minutes, now, now);
405
+ }
406
+ applyDayPlacements(identity, placements, now) {
407
+ for (const placement of placements) {
408
+ this.upsertDayIncrement(identity, placement.activityDate, placement.minutes, now);
409
+ }
410
+ }
411
+ computeCreditPlacements(creditMinutes, dailyTotals, existingDays) {
412
+ const coveredByDate = new Map(existingDays.map((day) => [day.activityDate, day.loggedMinutes]));
413
+ let remainingMinutes = creditMinutes;
414
+ const placements = [];
415
+ for (const total of dailyTotals) {
416
+ if (remainingMinutes <= 0) {
417
+ break;
418
+ }
419
+ const covered = coveredByDate.get(total.activityDate) ?? 0;
420
+ const uncovered = Math.max(0, total.minutes - covered);
421
+ const placed = Math.min(remainingMinutes, uncovered);
422
+ if (placed > 0) {
423
+ placements.push({ activityDate: total.activityDate, minutes: placed });
424
+ remainingMinutes -= placed;
425
+ }
426
+ }
427
+ return placements;
428
+ }
429
+ dayPredicate(identity) {
430
+ return (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.externalEstimateLogDays.provider, identity.provider), (0, drizzle_orm_1.eq)(schema_1.externalEstimateLogDays.remoteScopeKey, identity.remoteScopeKey), (0, drizzle_orm_1.eq)(schema_1.externalEstimateLogDays.remoteTaskId, identity.remoteTaskId));
431
+ }
432
+ requireDailyTotals(entries, label) {
433
+ const totalsByDate = new Map();
434
+ for (const entry of entries ?? []) {
435
+ const activityDate = this.requireActivityDate(entry?.activityDate, label);
436
+ if (!Number.isSafeInteger(entry?.minutes) || entry.minutes < 0) {
437
+ throw new error_types_1.ValidationError(`${label} minutes must be nonnegative whole numbers.`);
438
+ }
439
+ totalsByDate.set(activityDate, (totalsByDate.get(activityDate) ?? 0) + entry.minutes);
440
+ }
441
+ if (totalsByDate.size > MAX_DAILY_LEDGER_ROWS) {
442
+ throw new error_types_1.ValidationError(`${label} may carry at most ${MAX_DAILY_LEDGER_ROWS} unique activity dates.`);
443
+ }
444
+ return [...totalsByDate.entries()]
445
+ .map(([activityDate, minutes]) => ({ activityDate, minutes }))
446
+ .sort((left, right) => left.activityDate.localeCompare(right.activityDate));
447
+ }
448
+ requireActivityDate(value, label = 'Daily totals') {
449
+ const normalized = value?.trim();
450
+ if (!normalized || !ACTIVITY_DATE_PATTERN.test(normalized)) {
451
+ throw new error_types_1.ValidationError(`${label} activity date is invalid.`);
452
+ }
453
+ const [year, month, day] = normalized.split('-').map(Number);
454
+ const calendar = new Date(Date.UTC(year, month - 1, day));
455
+ if (calendar.getUTCFullYear() !== year ||
456
+ calendar.getUTCMonth() !== month - 1 ||
457
+ calendar.getUTCDate() !== day) {
458
+ throw new error_types_1.ValidationError(`${label} activity date is invalid.`);
459
+ }
460
+ return normalized;
461
+ }
462
+ requireAggregationZone(value) {
463
+ const normalized = value?.trim();
464
+ if (!normalized || normalized.length > MAX_ZONE_LENGTH) {
465
+ throw new error_types_1.ValidationError('Aggregation time zone is invalid.');
466
+ }
467
+ return normalized;
468
+ }
469
+ getRow(identity) {
470
+ return this.db
471
+ .select()
472
+ .from(schema_1.externalEstimateLogStates)
473
+ .where(this.identityPredicate(identity))
474
+ .limit(1)
475
+ .get();
476
+ }
477
+ requireState(identity) {
478
+ const row = this.getRow(identity);
479
+ if (!row) {
480
+ throw new error_types_1.NotFoundError('External estimate log state');
481
+ }
482
+ this.assertDaysWithinScalar(identity, row.loggedMinutes);
483
+ return this.mapState(row);
484
+ }
485
+ requirePending(identity, operationId) {
486
+ const state = this.getRow(identity);
487
+ if (!state) {
488
+ throw new error_types_1.NotFoundError('External estimate log state');
489
+ }
490
+ if (state.pendingOperationId === null) {
491
+ throw new error_types_1.ConflictError('Estimate log state has no pending operation.');
492
+ }
493
+ if (state.pendingOperationId !== operationId) {
494
+ throw new error_types_1.ConflictError('A different estimate operation is pending.', {
495
+ operationId: state.pendingOperationId,
496
+ });
497
+ }
498
+ return state;
499
+ }
500
+ updateByRevision(identity, revision, values) {
501
+ const result = this.db
502
+ .update(schema_1.externalEstimateLogStates)
503
+ .set(values)
504
+ .where((0, drizzle_orm_1.and)(this.identityPredicate(identity), (0, drizzle_orm_1.eq)(schema_1.externalEstimateLogStates.revision, revision)))
505
+ .run();
506
+ if (result.changes !== 1) {
507
+ throw this.optimisticLock(identity, revision);
508
+ }
509
+ }
510
+ assertCurrentLink(identity) {
511
+ const row = this.db
512
+ .select({ remoteTaskId: schema_1.externalTaskLinks.remoteTaskId })
513
+ .from(schema_1.externalTaskLinks)
514
+ .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.externalTaskLinks.provider, identity.provider), (0, drizzle_orm_1.eq)(schema_1.externalTaskLinks.remoteScopeKey, identity.remoteScopeKey), (0, drizzle_orm_1.eq)(schema_1.externalTaskLinks.remoteTaskId, identity.remoteTaskId)))
515
+ .limit(1)
516
+ .get();
517
+ if (!row) {
518
+ throw new error_types_1.NotFoundError('Current external task link');
519
+ }
520
+ }
521
+ assertConnectionEpoch(provider, connectionId, generation) {
522
+ const connection = this.db
523
+ .select({ id: schema_1.integrationConnections.id })
524
+ .from(schema_1.integrationConnections)
525
+ .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.integrationConnections.id, connectionId), (0, drizzle_orm_1.eq)(schema_1.integrationConnections.provider, provider), (0, drizzle_orm_1.eq)(schema_1.integrationConnections.generation, generation)))
526
+ .limit(1)
527
+ .get();
528
+ if (!connection) {
529
+ throw new error_types_1.ConflictError('Integration connection epoch is stale.');
530
+ }
531
+ }
532
+ identityPredicate(identity) {
533
+ return (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.externalEstimateLogStates.provider, identity.provider), (0, drizzle_orm_1.eq)(schema_1.externalEstimateLogStates.remoteScopeKey, identity.remoteScopeKey), (0, drizzle_orm_1.eq)(schema_1.externalEstimateLogStates.remoteTaskId, identity.remoteTaskId));
534
+ }
535
+ assertExpectedRevision(state, expectedRevision, identity) {
536
+ const actualRevision = state?.revision ?? 0;
537
+ if (actualRevision !== expectedRevision) {
538
+ throw this.optimisticLock(identity, expectedRevision, actualRevision);
539
+ }
540
+ }
541
+ optimisticLock(identity, expectedRevision, actualRevision) {
542
+ return new error_types_1.OptimisticLockError('External estimate log state', `${identity.provider}:${identity.remoteScopeKey}:${identity.remoteTaskId}`, { expectedRevision, ...(actualRevision === undefined ? {} : { actualRevision }) });
543
+ }
544
+ normalizeIdentity(identity) {
545
+ const remoteScopeKey = this.requireIdentifier(identity.remoteScopeKey, 'Remote scope');
546
+ const remoteTaskId = this.requireIdentifier(identity.remoteTaskId, 'Remote task');
547
+ const provider = this.requireProvider(identity.provider);
548
+ return { provider, remoteScopeKey, remoteTaskId };
549
+ }
550
+ requireProvider(provider) {
551
+ if (provider !== 'clickup' && provider !== 'jira') {
552
+ throw new error_types_1.ValidationError('Integration provider is invalid.');
553
+ }
554
+ return provider;
555
+ }
556
+ requireIdentifier(value, label) {
557
+ const normalized = value?.trim();
558
+ if (!normalized) {
559
+ throw new error_types_1.ValidationError(`${label} identifier is required.`);
560
+ }
561
+ return normalized;
562
+ }
563
+ requireOperationId(value) {
564
+ const normalized = value?.trim();
565
+ if (!normalized || !OPERATION_ID_PATTERN.test(normalized)) {
566
+ throw new error_types_1.ValidationError('Estimate operation ID is invalid.');
567
+ }
568
+ return normalized;
569
+ }
570
+ requireExpectedRevision(value) {
571
+ this.requireNonnegativeInteger(value, 'Expected revision');
572
+ }
573
+ requireNonnegativeInteger(value, label) {
574
+ if (!Number.isSafeInteger(value) || value < 0) {
575
+ throw new error_types_1.ValidationError(`${label} must be a nonnegative whole number.`);
576
+ }
577
+ }
578
+ requirePositiveInteger(value, label) {
579
+ if (!Number.isSafeInteger(value) || value < 1) {
580
+ throw new error_types_1.ValidationError(`${label} must be a positive whole number.`);
581
+ }
582
+ }
583
+ requirePositiveDelta(deltaMinutes) {
584
+ if (!Number.isSafeInteger(deltaMinutes) || deltaMinutes === 0) {
585
+ throw new error_types_1.ValidationError('Estimate delta must be a nonzero whole number.');
586
+ }
587
+ if (deltaMinutes < 0) {
588
+ throw new error_types_1.ValidationError('Estimate delta must be positive.');
589
+ }
590
+ if (deltaMinutes * 60_000 > MAX_TIME_ENTRY_DURATION_MS) {
591
+ throw new error_types_1.ValidationError('Estimate delta exceeds the maximum time-entry duration.');
592
+ }
593
+ }
594
+ requireTimestamp(value) {
595
+ const normalized = value?.trim();
596
+ if (!normalized || !Number.isFinite(Date.parse(normalized))) {
597
+ throw new error_types_1.ValidationError('Estimate operation start time is invalid.');
598
+ }
599
+ return normalized;
600
+ }
601
+ mapState(row) {
602
+ const common = {
603
+ provider: row.provider,
604
+ remoteScopeKey: row.remoteScopeKey,
605
+ remoteTaskId: row.remoteTaskId,
606
+ loggedMinutes: row.loggedMinutes,
607
+ revision: row.revision,
608
+ aggregationTimeZone: row.aggregationTimeZone,
609
+ createdAt: row.createdAt,
610
+ updatedAt: row.updatedAt,
611
+ };
612
+ if (row.pendingOperationId === null) {
613
+ if (row.pendingDeltaMinutes !== null ||
614
+ row.pendingEstimateTotalMinutes !== null ||
615
+ row.pendingStartedAt !== null ||
616
+ row.pendingConnectionId !== null ||
617
+ row.pendingConnectionGeneration !== null ||
618
+ row.pendingPhase !== null ||
619
+ row.pendingResolution !== null ||
620
+ row.pendingActivityDate !== null) {
621
+ throw new error_types_1.StorageError('External estimate log state has incomplete pending fields.');
622
+ }
623
+ return {
624
+ ...common,
625
+ pendingOperationId: null,
626
+ pendingDeltaMinutes: null,
627
+ pendingEstimateTotalMinutes: null,
628
+ pendingStartedAt: null,
629
+ pendingConnectionId: null,
630
+ pendingConnectionGeneration: null,
631
+ pendingPhase: null,
632
+ pendingResolution: null,
633
+ pendingActivityDate: null,
634
+ };
635
+ }
636
+ if (row.pendingDeltaMinutes === null ||
637
+ row.pendingEstimateTotalMinutes === null ||
638
+ row.pendingStartedAt === null ||
639
+ row.pendingConnectionId === null ||
640
+ row.pendingConnectionGeneration === null ||
641
+ row.pendingPhase === null) {
642
+ throw new error_types_1.StorageError('External estimate log state has incomplete pending fields.');
643
+ }
644
+ return {
645
+ ...common,
646
+ pendingOperationId: row.pendingOperationId,
647
+ pendingDeltaMinutes: row.pendingDeltaMinutes,
648
+ pendingEstimateTotalMinutes: row.pendingEstimateTotalMinutes,
649
+ pendingStartedAt: row.pendingStartedAt,
650
+ pendingConnectionId: row.pendingConnectionId,
651
+ pendingConnectionGeneration: row.pendingConnectionGeneration,
652
+ pendingPhase: row.pendingPhase,
653
+ pendingResolution: row.pendingResolution,
654
+ pendingActivityDate: row.pendingActivityDate,
655
+ };
656
+ }
657
+ }
658
+ exports.ExternalEstimateLogStorageDelegate = ExternalEstimateLogStorageDelegate;
659
+ //# sourceMappingURL=external-estimate-log.delegate.js.map