@cat-factory/integrations 0.6.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 (242) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.d.ts +68 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +84 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/modules/datadog/DatadogClient.d.ts +54 -0
  7. package/dist/modules/datadog/DatadogClient.d.ts.map +1 -0
  8. package/dist/modules/datadog/DatadogClient.js +132 -0
  9. package/dist/modules/datadog/DatadogClient.js.map +1 -0
  10. package/dist/modules/datadog/DatadogReleaseHealthProvider.d.ts +30 -0
  11. package/dist/modules/datadog/DatadogReleaseHealthProvider.d.ts.map +1 -0
  12. package/dist/modules/datadog/DatadogReleaseHealthProvider.js +101 -0
  13. package/dist/modules/datadog/DatadogReleaseHealthProvider.js.map +1 -0
  14. package/dist/modules/datadog/datadog.logic.d.ts +37 -0
  15. package/dist/modules/datadog/datadog.logic.d.ts.map +1 -0
  16. package/dist/modules/datadog/datadog.logic.js +90 -0
  17. package/dist/modules/datadog/datadog.logic.js.map +1 -0
  18. package/dist/modules/documents/ConfluenceProvider.d.ts +29 -0
  19. package/dist/modules/documents/ConfluenceProvider.d.ts.map +1 -0
  20. package/dist/modules/documents/ConfluenceProvider.js +180 -0
  21. package/dist/modules/documents/ConfluenceProvider.js.map +1 -0
  22. package/dist/modules/documents/DocumentConnectionService.d.ts +30 -0
  23. package/dist/modules/documents/DocumentConnectionService.d.ts.map +1 -0
  24. package/dist/modules/documents/DocumentConnectionService.js +69 -0
  25. package/dist/modules/documents/DocumentConnectionService.js.map +1 -0
  26. package/dist/modules/documents/DocumentImportService.d.ts +34 -0
  27. package/dist/modules/documents/DocumentImportService.d.ts.map +1 -0
  28. package/dist/modules/documents/DocumentImportService.js +83 -0
  29. package/dist/modules/documents/DocumentImportService.js.map +1 -0
  30. package/dist/modules/documents/DocumentLinkService.d.ts +31 -0
  31. package/dist/modules/documents/DocumentLinkService.d.ts.map +1 -0
  32. package/dist/modules/documents/DocumentLinkService.js +75 -0
  33. package/dist/modules/documents/DocumentLinkService.js.map +1 -0
  34. package/dist/modules/documents/DocumentPlannerService.d.ts +23 -0
  35. package/dist/modules/documents/DocumentPlannerService.d.ts.map +1 -0
  36. package/dist/modules/documents/DocumentPlannerService.js +96 -0
  37. package/dist/modules/documents/DocumentPlannerService.js.map +1 -0
  38. package/dist/modules/documents/GitHubDocsProvider.d.ts +42 -0
  39. package/dist/modules/documents/GitHubDocsProvider.d.ts.map +1 -0
  40. package/dist/modules/documents/GitHubDocsProvider.js +86 -0
  41. package/dist/modules/documents/GitHubDocsProvider.js.map +1 -0
  42. package/dist/modules/documents/NotionProvider.d.ts +32 -0
  43. package/dist/modules/documents/NotionProvider.d.ts.map +1 -0
  44. package/dist/modules/documents/NotionProvider.js +221 -0
  45. package/dist/modules/documents/NotionProvider.js.map +1 -0
  46. package/dist/modules/documents/confluence.logic.d.ts +37 -0
  47. package/dist/modules/documents/confluence.logic.d.ts.map +1 -0
  48. package/dist/modules/documents/confluence.logic.js +133 -0
  49. package/dist/modules/documents/confluence.logic.js.map +1 -0
  50. package/dist/modules/documents/documents.logic.d.ts +22 -0
  51. package/dist/modules/documents/documents.logic.d.ts.map +1 -0
  52. package/dist/modules/documents/documents.logic.js +138 -0
  53. package/dist/modules/documents/documents.logic.js.map +1 -0
  54. package/dist/modules/documents/github-docs.logic.d.ts +52 -0
  55. package/dist/modules/documents/github-docs.logic.d.ts.map +1 -0
  56. package/dist/modules/documents/github-docs.logic.js +94 -0
  57. package/dist/modules/documents/github-docs.logic.js.map +1 -0
  58. package/dist/modules/documents/notion.logic.d.ts +31 -0
  59. package/dist/modules/documents/notion.logic.d.ts.map +1 -0
  60. package/dist/modules/documents/notion.logic.js +142 -0
  61. package/dist/modules/documents/notion.logic.js.map +1 -0
  62. package/dist/modules/email/EmailConnectionService.d.ts +34 -0
  63. package/dist/modules/email/EmailConnectionService.d.ts.map +1 -0
  64. package/dist/modules/email/EmailConnectionService.js +82 -0
  65. package/dist/modules/email/EmailConnectionService.js.map +1 -0
  66. package/dist/modules/email/adapters.d.ts +39 -0
  67. package/dist/modules/email/adapters.d.ts.map +1 -0
  68. package/dist/modules/email/adapters.js +79 -0
  69. package/dist/modules/email/adapters.js.map +1 -0
  70. package/dist/modules/environments/EnvironmentConnectionService.d.ts +42 -0
  71. package/dist/modules/environments/EnvironmentConnectionService.d.ts.map +1 -0
  72. package/dist/modules/environments/EnvironmentConnectionService.js +120 -0
  73. package/dist/modules/environments/EnvironmentConnectionService.js.map +1 -0
  74. package/dist/modules/environments/EnvironmentProvisioningService.d.ts +56 -0
  75. package/dist/modules/environments/EnvironmentProvisioningService.d.ts.map +1 -0
  76. package/dist/modules/environments/EnvironmentProvisioningService.js +153 -0
  77. package/dist/modules/environments/EnvironmentProvisioningService.js.map +1 -0
  78. package/dist/modules/environments/EnvironmentTeardownService.d.ts +24 -0
  79. package/dist/modules/environments/EnvironmentTeardownService.d.ts.map +1 -0
  80. package/dist/modules/environments/EnvironmentTeardownService.js +54 -0
  81. package/dist/modules/environments/EnvironmentTeardownService.js.map +1 -0
  82. package/dist/modules/environments/HttpEnvironmentProvider.d.ts +30 -0
  83. package/dist/modules/environments/HttpEnvironmentProvider.d.ts.map +1 -0
  84. package/dist/modules/environments/HttpEnvironmentProvider.js +316 -0
  85. package/dist/modules/environments/HttpEnvironmentProvider.js.map +1 -0
  86. package/dist/modules/environments/environments.logic.d.ts +50 -0
  87. package/dist/modules/environments/environments.logic.d.ts.map +1 -0
  88. package/dist/modules/environments/environments.logic.js +257 -0
  89. package/dist/modules/environments/environments.logic.js.map +1 -0
  90. package/dist/modules/github/GitHubInstallationService.d.ts +66 -0
  91. package/dist/modules/github/GitHubInstallationService.d.ts.map +1 -0
  92. package/dist/modules/github/GitHubInstallationService.js +143 -0
  93. package/dist/modules/github/GitHubInstallationService.js.map +1 -0
  94. package/dist/modules/github/GitHubService.d.ts +29 -0
  95. package/dist/modules/github/GitHubService.d.ts.map +1 -0
  96. package/dist/modules/github/GitHubService.js +61 -0
  97. package/dist/modules/github/GitHubService.js.map +1 -0
  98. package/dist/modules/github/GitHubSyncService.d.ts +97 -0
  99. package/dist/modules/github/GitHubSyncService.d.ts.map +1 -0
  100. package/dist/modules/github/GitHubSyncService.js +241 -0
  101. package/dist/modules/github/GitHubSyncService.js.map +1 -0
  102. package/dist/modules/github/RepoProvisioningService.d.ts +26 -0
  103. package/dist/modules/github/RepoProvisioningService.d.ts.map +1 -0
  104. package/dist/modules/github/RepoProvisioningService.js +36 -0
  105. package/dist/modules/github/RepoProvisioningService.js.map +1 -0
  106. package/dist/modules/github/WebhookService.d.ts +28 -0
  107. package/dist/modules/github/WebhookService.d.ts.map +1 -0
  108. package/dist/modules/github/WebhookService.js +156 -0
  109. package/dist/modules/github/WebhookService.js.map +1 -0
  110. package/dist/modules/github/projection.logic.d.ts +95 -0
  111. package/dist/modules/github/projection.logic.d.ts.map +1 -0
  112. package/dist/modules/github/projection.logic.js +94 -0
  113. package/dist/modules/github/projection.logic.js.map +1 -0
  114. package/dist/modules/github/provisioning.logic.d.ts +11 -0
  115. package/dist/modules/github/provisioning.logic.d.ts.map +1 -0
  116. package/dist/modules/github/provisioning.logic.js +18 -0
  117. package/dist/modules/github/provisioning.logic.js.map +1 -0
  118. package/dist/modules/incident/incident.logic.d.ts +16 -0
  119. package/dist/modules/incident/incident.logic.d.ts.map +1 -0
  120. package/dist/modules/incident/incident.logic.js +23 -0
  121. package/dist/modules/incident/incident.logic.js.map +1 -0
  122. package/dist/modules/incidentio/IncidentIoEnrichmentProvider.d.ts +26 -0
  123. package/dist/modules/incidentio/IncidentIoEnrichmentProvider.d.ts.map +1 -0
  124. package/dist/modules/incidentio/IncidentIoEnrichmentProvider.js +84 -0
  125. package/dist/modules/incidentio/IncidentIoEnrichmentProvider.js.map +1 -0
  126. package/dist/modules/pagerduty/PagerDutyEnrichmentProvider.d.ts +27 -0
  127. package/dist/modules/pagerduty/PagerDutyEnrichmentProvider.d.ts.map +1 -0
  128. package/dist/modules/pagerduty/PagerDutyEnrichmentProvider.js +65 -0
  129. package/dist/modules/pagerduty/PagerDutyEnrichmentProvider.js.map +1 -0
  130. package/dist/modules/providers/ApiKeyService.d.ts +73 -0
  131. package/dist/modules/providers/ApiKeyService.d.ts.map +1 -0
  132. package/dist/modules/providers/ApiKeyService.js +122 -0
  133. package/dist/modules/providers/ApiKeyService.js.map +1 -0
  134. package/dist/modules/providers/LocalModelEndpointService.d.ts +52 -0
  135. package/dist/modules/providers/LocalModelEndpointService.d.ts.map +1 -0
  136. package/dist/modules/providers/LocalModelEndpointService.js +131 -0
  137. package/dist/modules/providers/LocalModelEndpointService.js.map +1 -0
  138. package/dist/modules/providers/PersonalSubscriptionService.d.ts +94 -0
  139. package/dist/modules/providers/PersonalSubscriptionService.d.ts.map +1 -0
  140. package/dist/modules/providers/PersonalSubscriptionService.js +218 -0
  141. package/dist/modules/providers/PersonalSubscriptionService.js.map +1 -0
  142. package/dist/modules/providers/ProviderSubscriptionService.d.ts +75 -0
  143. package/dist/modules/providers/ProviderSubscriptionService.d.ts.map +1 -0
  144. package/dist/modules/providers/ProviderSubscriptionService.js +130 -0
  145. package/dist/modules/providers/ProviderSubscriptionService.js.map +1 -0
  146. package/dist/modules/providers/localModelUrl.d.ts +7 -0
  147. package/dist/modules/providers/localModelUrl.d.ts.map +1 -0
  148. package/dist/modules/providers/localModelUrl.js +67 -0
  149. package/dist/modules/providers/localModelUrl.js.map +1 -0
  150. package/dist/modules/providers/providers.logic.d.ts +23 -0
  151. package/dist/modules/providers/providers.logic.d.ts.map +1 -0
  152. package/dist/modules/providers/providers.logic.js +46 -0
  153. package/dist/modules/providers/providers.logic.js.map +1 -0
  154. package/dist/modules/runners/HttpRunnerPoolProvider.d.ts +51 -0
  155. package/dist/modules/runners/HttpRunnerPoolProvider.d.ts.map +1 -0
  156. package/dist/modules/runners/HttpRunnerPoolProvider.js +304 -0
  157. package/dist/modules/runners/HttpRunnerPoolProvider.js.map +1 -0
  158. package/dist/modules/runners/RunnerPoolConnectionService.d.ts +47 -0
  159. package/dist/modules/runners/RunnerPoolConnectionService.d.ts.map +1 -0
  160. package/dist/modules/runners/RunnerPoolConnectionService.js +98 -0
  161. package/dist/modules/runners/RunnerPoolConnectionService.js.map +1 -0
  162. package/dist/modules/runners/RunnerPoolTransport.d.ts +11 -0
  163. package/dist/modules/runners/RunnerPoolTransport.d.ts.map +1 -0
  164. package/dist/modules/runners/RunnerPoolTransport.js +61 -0
  165. package/dist/modules/runners/RunnerPoolTransport.js.map +1 -0
  166. package/dist/modules/runners/runners.logic.d.ts +16 -0
  167. package/dist/modules/runners/runners.logic.d.ts.map +1 -0
  168. package/dist/modules/runners/runners.logic.js +52 -0
  169. package/dist/modules/runners/runners.logic.js.map +1 -0
  170. package/dist/modules/slack/SlackApiClient.d.ts +67 -0
  171. package/dist/modules/slack/SlackApiClient.d.ts.map +1 -0
  172. package/dist/modules/slack/SlackApiClient.js +132 -0
  173. package/dist/modules/slack/SlackApiClient.js.map +1 -0
  174. package/dist/modules/slack/SlackConnectionService.d.ts +41 -0
  175. package/dist/modules/slack/SlackConnectionService.d.ts.map +1 -0
  176. package/dist/modules/slack/SlackConnectionService.js +136 -0
  177. package/dist/modules/slack/SlackConnectionService.js.map +1 -0
  178. package/dist/modules/slack/SlackMemberMappingService.d.ts +17 -0
  179. package/dist/modules/slack/SlackMemberMappingService.d.ts.map +1 -0
  180. package/dist/modules/slack/SlackMemberMappingService.js +28 -0
  181. package/dist/modules/slack/SlackMemberMappingService.js.map +1 -0
  182. package/dist/modules/slack/SlackNotificationChannel.d.ts +45 -0
  183. package/dist/modules/slack/SlackNotificationChannel.d.ts.map +1 -0
  184. package/dist/modules/slack/SlackNotificationChannel.js +84 -0
  185. package/dist/modules/slack/SlackNotificationChannel.js.map +1 -0
  186. package/dist/modules/slack/SlackSettingsService.d.ts +16 -0
  187. package/dist/modules/slack/SlackSettingsService.d.ts.map +1 -0
  188. package/dist/modules/slack/SlackSettingsService.js +41 -0
  189. package/dist/modules/slack/SlackSettingsService.js.map +1 -0
  190. package/dist/modules/slack/slack.logic.d.ts +55 -0
  191. package/dist/modules/slack/slack.logic.d.ts.map +1 -0
  192. package/dist/modules/slack/slack.logic.js +149 -0
  193. package/dist/modules/slack/slack.logic.js.map +1 -0
  194. package/dist/modules/tasks/GitHubIssuesProvider.d.ts +50 -0
  195. package/dist/modules/tasks/GitHubIssuesProvider.d.ts.map +1 -0
  196. package/dist/modules/tasks/GitHubIssuesProvider.js +92 -0
  197. package/dist/modules/tasks/GitHubIssuesProvider.js.map +1 -0
  198. package/dist/modules/tasks/JiraProvider.d.ts +29 -0
  199. package/dist/modules/tasks/JiraProvider.d.ts.map +1 -0
  200. package/dist/modules/tasks/JiraProvider.js +114 -0
  201. package/dist/modules/tasks/JiraProvider.js.map +1 -0
  202. package/dist/modules/tasks/TaskConnectionService.d.ts +30 -0
  203. package/dist/modules/tasks/TaskConnectionService.d.ts.map +1 -0
  204. package/dist/modules/tasks/TaskConnectionService.js +69 -0
  205. package/dist/modules/tasks/TaskConnectionService.js.map +1 -0
  206. package/dist/modules/tasks/TaskImportService.d.ts +34 -0
  207. package/dist/modules/tasks/TaskImportService.d.ts.map +1 -0
  208. package/dist/modules/tasks/TaskImportService.js +96 -0
  209. package/dist/modules/tasks/TaskImportService.js.map +1 -0
  210. package/dist/modules/tasks/TaskLinkService.d.ts +30 -0
  211. package/dist/modules/tasks/TaskLinkService.d.ts.map +1 -0
  212. package/dist/modules/tasks/TaskLinkService.js +56 -0
  213. package/dist/modules/tasks/TaskLinkService.js.map +1 -0
  214. package/dist/modules/tasks/github-issues.logic.d.ts +35 -0
  215. package/dist/modules/tasks/github-issues.logic.d.ts.map +1 -0
  216. package/dist/modules/tasks/github-issues.logic.js +67 -0
  217. package/dist/modules/tasks/github-issues.logic.js.map +1 -0
  218. package/dist/modules/tasks/jira.logic.d.ts +28 -0
  219. package/dist/modules/tasks/jira.logic.d.ts.map +1 -0
  220. package/dist/modules/tasks/jira.logic.js +151 -0
  221. package/dist/modules/tasks/jira.logic.js.map +1 -0
  222. package/dist/modules/tasks/tasks.logic.d.ts +12 -0
  223. package/dist/modules/tasks/tasks.logic.d.ts.map +1 -0
  224. package/dist/modules/tasks/tasks.logic.js +17 -0
  225. package/dist/modules/tasks/tasks.logic.js.map +1 -0
  226. package/dist/modules/tracker/TicketTrackerService.d.ts +45 -0
  227. package/dist/modules/tracker/TicketTrackerService.d.ts.map +1 -0
  228. package/dist/modules/tracker/TicketTrackerService.js +52 -0
  229. package/dist/modules/tracker/TicketTrackerService.js.map +1 -0
  230. package/dist/modules/tracker/base64.d.ts +2 -0
  231. package/dist/modules/tracker/base64.d.ts.map +1 -0
  232. package/dist/modules/tracker/base64.js +18 -0
  233. package/dist/modules/tracker/base64.js.map +1 -0
  234. package/dist/modules/tracker/github.create.logic.d.ts +16 -0
  235. package/dist/modules/tracker/github.create.logic.d.ts.map +1 -0
  236. package/dist/modules/tracker/github.create.logic.js +25 -0
  237. package/dist/modules/tracker/github.create.logic.js.map +1 -0
  238. package/dist/modules/tracker/jira.create.logic.d.ts +31 -0
  239. package/dist/modules/tracker/jira.create.logic.d.ts.map +1 -0
  240. package/dist/modules/tracker/jira.create.logic.js +59 -0
  241. package/dist/modules/tracker/jira.create.logic.js.map +1 -0
  242. package/package.json +36 -0
@@ -0,0 +1,90 @@
1
+ // Pure helpers for the Datadog post-release-health integration: site validation
2
+ // (anti-SSRF — only real Datadog hosts), base-URL construction, and mapping Datadog's
3
+ // monitor/SLO shapes onto the runtime-neutral `ReleaseSignalState`.
4
+ /** The domain tag used to seal Datadog credentials at rest (HKDF info). */
5
+ export const DATADOG_CIPHER_INFO = 'cat-factory:datadog';
6
+ /** Datadog site host suffixes we allow a connection to point at (anti-SSRF). */
7
+ const ALLOWED_SITE_SUFFIXES = [
8
+ 'datadoghq.com',
9
+ 'datadoghq.eu',
10
+ 'us3.datadoghq.com',
11
+ 'us5.datadoghq.com',
12
+ 'ap1.datadoghq.com',
13
+ 'ddog-gov.com',
14
+ ];
15
+ /**
16
+ * Validate a configured Datadog `site` host and return the normalized host (no
17
+ * scheme/path). Throws when it isn't a recognised Datadog host so a misconfiguration
18
+ * can't turn the server into an SSRF vector. Accepts a bare host or a full URL.
19
+ */
20
+ export function normalizeDatadogSite(site) {
21
+ const trimmed = site
22
+ .trim()
23
+ .replace(/^https?:\/\//i, '')
24
+ .replace(/\/.*$/, '')
25
+ .toLowerCase();
26
+ if (!trimmed)
27
+ throw new DatadogApiError('config', 'Datadog site is empty');
28
+ const ok = ALLOWED_SITE_SUFFIXES.some((s) => trimmed === s || trimmed.endsWith(`.${s}`));
29
+ if (!ok) {
30
+ throw new DatadogApiError('config', `Unrecognised Datadog site host: ${trimmed}`);
31
+ }
32
+ return trimmed;
33
+ }
34
+ /** The API base URL for a (validated) site host. */
35
+ export function datadogApiBase(site) {
36
+ return `https://api.${normalizeDatadogSite(site)}`;
37
+ }
38
+ /**
39
+ * Map Datadog's monitor `overall_state` string onto a release signal state.
40
+ *
41
+ * `attribution` lets the post-release-health gate ignore an alert that PREDATES the
42
+ * release it is watching: a monitor already alerting before `since` (an unrelated /
43
+ * flaky / never-recovered incident) is not attributable to this release, so it is
44
+ * downgraded to `warn` (which does NOT regress the gate) rather than escalating an
45
+ * on-call investigation that blames an innocent PR. When the transition timestamp is
46
+ * unknown (Datadog didn't report `overall_state_modified`) we keep the alert — better
47
+ * to investigate than to silently miss a real regression.
48
+ */
49
+ export function mapMonitorState(overallState, attribution) {
50
+ switch ((overallState ?? '').toLowerCase()) {
51
+ case 'alert':
52
+ case 'alert_recovery':
53
+ // A pre-existing alert (state last changed before the release marker) is not this
54
+ // release's regression — don't escalate on it.
55
+ if (attribution &&
56
+ attribution.stateModifiedMs !== undefined &&
57
+ attribution.stateModifiedMs < attribution.since) {
58
+ return 'warn';
59
+ }
60
+ return 'alert';
61
+ case 'warn':
62
+ case 'warn_recovery':
63
+ return 'warn';
64
+ case 'ok':
65
+ return 'ok';
66
+ default:
67
+ // 'No Data' | 'Unknown' | 'Ignored' | 'Skipped' | undefined
68
+ return 'no_data';
69
+ }
70
+ }
71
+ /**
72
+ * Map an SLO's current SLI value vs its target onto a release signal state:
73
+ * below target → `alert` (error budget breached), at/above → `ok`, missing → `no_data`.
74
+ */
75
+ export function mapSloState(sliValue, target) {
76
+ if (sliValue === null || target === null)
77
+ return 'no_data';
78
+ return sliValue < target ? 'alert' : 'ok';
79
+ }
80
+ export class DatadogApiError extends Error {
81
+ endpoint;
82
+ detail;
83
+ constructor(endpoint, detail) {
84
+ super(`Datadog ${endpoint} failed: ${detail}`);
85
+ this.endpoint = endpoint;
86
+ this.detail = detail;
87
+ this.name = 'DatadogApiError';
88
+ }
89
+ }
90
+ //# sourceMappingURL=datadog.logic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"datadog.logic.js","sourceRoot":"","sources":["../../../src/modules/datadog/datadog.logic.ts"],"names":[],"mappings":"AAEA,gFAAgF;AAChF,sFAAsF;AACtF,oEAAoE;AAEpE,2EAA2E;AAC3E,MAAM,CAAC,MAAM,mBAAmB,GAAG,qBAAqB,CAAA;AAExD,gFAAgF;AAChF,MAAM,qBAAqB,GAAG;IAC5B,eAAe;IACf,cAAc;IACd,mBAAmB;IACnB,mBAAmB;IACnB,mBAAmB;IACnB,cAAc;CACf,CAAA;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,MAAM,OAAO,GAAG,IAAI;SACjB,IAAI,EAAE;SACN,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC;SAC5B,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;SACpB,WAAW,EAAE,CAAA;IAChB,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,eAAe,CAAC,QAAQ,EAAE,uBAAuB,CAAC,CAAA;IAC1E,MAAM,EAAE,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,KAAK,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;IACxF,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,MAAM,IAAI,eAAe,CAAC,QAAQ,EAAE,mCAAmC,OAAO,EAAE,CAAC,CAAA;IACnF,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,eAAe,oBAAoB,CAAC,IAAI,CAAC,EAAE,CAAA;AACpD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAC7B,YAAgC,EAChC,WAAyD;IAEzD,QAAQ,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;QAC3C,KAAK,OAAO,CAAC;QACb,KAAK,gBAAgB;YACnB,kFAAkF;YAClF,+CAA+C;YAC/C,IACE,WAAW;gBACX,WAAW,CAAC,eAAe,KAAK,SAAS;gBACzC,WAAW,CAAC,eAAe,GAAG,WAAW,CAAC,KAAK,EAC/C,CAAC;gBACD,OAAO,MAAM,CAAA;YACf,CAAC;YACD,OAAO,OAAO,CAAA;QAChB,KAAK,MAAM,CAAC;QACZ,KAAK,eAAe;YAClB,OAAO,MAAM,CAAA;QACf,KAAK,IAAI;YACP,OAAO,IAAI,CAAA;QACb;YACE,4DAA4D;YAC5D,OAAO,SAAS,CAAA;IACpB,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,QAAuB,EAAE,MAAqB;IACxE,IAAI,QAAQ,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,SAAS,CAAA;IAC1D,OAAO,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA;AAC3C,CAAC;AAED,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAE7B,QAAQ;IACR,MAAM;IAFjB,YACW,QAAgB,EAChB,MAAc;QAEvB,KAAK,CAAC,WAAW,QAAQ,YAAY,MAAM,EAAE,CAAC,CAAA;wBAHrC,QAAQ;sBACR,MAAM;QAGf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAA;IAC/B,CAAC;CACF"}
@@ -0,0 +1,29 @@
1
+ import { type DocumentContent, type DocumentCredentials, type DocumentSearchResult, type DocumentSourceProvider, type NormalizedConnection } from '@cat-factory/kernel';
2
+ /** Carries the HTTP status so callers can surface a meaningful error. */
3
+ export declare class ConfluenceApiError extends Error {
4
+ readonly status: number;
5
+ constructor(status: number, message: string);
6
+ }
7
+ export declare class ConfluenceProvider implements DocumentSourceProvider {
8
+ readonly kind: 'confluence';
9
+ readonly descriptor: {
10
+ source: "confluence" | "github" | "notion";
11
+ label: string;
12
+ icon: string;
13
+ credentialFields: {
14
+ key: string;
15
+ label: string;
16
+ help?: string | undefined;
17
+ placeholder?: string | undefined;
18
+ secret?: boolean | undefined;
19
+ }[];
20
+ refLabel: string;
21
+ refPlaceholder: string;
22
+ searchable?: boolean | undefined;
23
+ };
24
+ normalizeConnection(input: DocumentCredentials): NormalizedConnection;
25
+ parseRef(input: string): string | null;
26
+ fetchDocument(credentials: DocumentCredentials, externalId: string): Promise<DocumentContent>;
27
+ search(credentials: DocumentCredentials, query: string): Promise<DocumentSearchResult[]>;
28
+ }
29
+ //# sourceMappingURL=ConfluenceProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfluenceProvider.d.ts","sourceRoot":"","sources":["../../../src/modules/documents/ConfluenceProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAA;AAiB5B,yEAAyE;AACzE,qBAAa,kBAAmB,SAAQ,KAAK;IAEzC,QAAQ,CAAC,MAAM,EAAE,MAAM;IADzB,YACW,MAAM,EAAE,MAAM,EACvB,OAAO,EAAE,MAAM,EAIhB;CACF;AA+ED,qBAAa,kBAAmB,YAAW,sBAAsB;IAC/D,QAAQ,CAAC,IAAI,EAAG,YAAY,CAAS;IACrC,QAAQ,CAAC,UAAU;;;;;;;;;;;;;;MAAwB;IAE3C,mBAAmB,CAAC,KAAK,EAAE,mBAAmB,GAAG,oBAAoB,CAepE;IAED,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAErC;IAEK,aAAa,CACjB,WAAW,EAAE,mBAAmB,EAChC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,eAAe,CAAC,CAiD1B;IAEK,MAAM,CAAC,WAAW,EAAE,mBAAmB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAkC7F;CACF"}
@@ -0,0 +1,180 @@
1
+ import { ValidationError, } from '@cat-factory/kernel';
2
+ import { CONFLUENCE_DESCRIPTOR } from './confluence.logic.js';
3
+ import * as confluenceLogic from './confluence.logic.js';
4
+ // ConfluenceProvider: the document-source provider for Confluence Cloud. It
5
+ // authenticates with HTTP Basic (account email + API token), fetches a page in
6
+ // storage format, and converts the body to the Markdown the planner consumes.
7
+ // All Confluence-specific *pure* logic (ref parsing, base-URL SSRF guard,
8
+ // XHTML → Markdown) lives in `@cat-factory/integrations` so it is unit-testable; this
9
+ // class is the thin `fetch` shell around it. No SDK — fetch + `btoa` suffice.
10
+ const USER_AGENT = 'cat-factory';
11
+ /** Bound the redirect chain so a permitted first hop can't walk us anywhere. */
12
+ const MAX_REDIRECTS = 5;
13
+ /** Hard cap on the bytes read off any response body, to protect the isolate. */
14
+ const MAX_RESPONSE_BYTES = 5_000_000;
15
+ /** Carries the HTTP status so callers can surface a meaningful error. */
16
+ export class ConfluenceApiError extends Error {
17
+ status;
18
+ constructor(status, message) {
19
+ super(message);
20
+ this.status = status;
21
+ this.name = 'ConfluenceApiError';
22
+ }
23
+ }
24
+ /**
25
+ * `fetch` with redirects followed by hand so the SSRF guard runs against EVERY
26
+ * hop. With the default `redirect: 'follow'` the permitted site could 302 to an
27
+ * internal target (or downgrade https→http) and the runtime would follow it
28
+ * unchecked, leaking the Basic-auth token. We force `redirect: 'manual'`,
29
+ * re-resolve the `Location` against the current URL, and re-run the same
30
+ * `assertSafe` guard (https-only + host blocklist) before following.
31
+ */
32
+ async function safeFetch(url, init, assertSafe) {
33
+ let current = url;
34
+ for (let hop = 0;; hop++) {
35
+ assertSafe(current);
36
+ const res = await fetch(current, { ...init, redirect: 'manual' });
37
+ if (res.status >= 300 && res.status < 400) {
38
+ const location = res.headers.get('location');
39
+ if (!location)
40
+ return res;
41
+ if (hop >= MAX_REDIRECTS) {
42
+ throw new ConfluenceApiError(502, 'Confluence returned too many redirects');
43
+ }
44
+ current = new URL(location, current).toString();
45
+ continue;
46
+ }
47
+ return res;
48
+ }
49
+ }
50
+ /**
51
+ * Read a response body with a running byte cap so a hostile/huge response can't
52
+ * OOM the isolate. Checks the declared Content-Length first, then enforces the
53
+ * cap while streaming.
54
+ */
55
+ async function readCappedText(res, maxBytes) {
56
+ const declared = res.headers.get('content-length');
57
+ if (declared && Number(declared) > maxBytes) {
58
+ throw new ConfluenceApiError(502, 'Confluence response too large');
59
+ }
60
+ const body = res.body;
61
+ if (!body)
62
+ return '';
63
+ const reader = body.getReader();
64
+ const chunks = [];
65
+ let total = 0;
66
+ try {
67
+ for (;;) {
68
+ const { done, value } = await reader.read();
69
+ if (done)
70
+ break;
71
+ if (!value)
72
+ continue;
73
+ total += value.byteLength;
74
+ if (total > maxBytes) {
75
+ await reader.cancel();
76
+ throw new ConfluenceApiError(502, 'Confluence response too large');
77
+ }
78
+ chunks.push(value);
79
+ }
80
+ }
81
+ finally {
82
+ reader.releaseLock();
83
+ }
84
+ const merged = new Uint8Array(total);
85
+ let offset = 0;
86
+ for (const c of chunks) {
87
+ merged.set(c, offset);
88
+ offset += c.byteLength;
89
+ }
90
+ return new TextDecoder().decode(merged);
91
+ }
92
+ export class ConfluenceProvider {
93
+ kind = 'confluence';
94
+ descriptor = CONFLUENCE_DESCRIPTOR;
95
+ normalizeConnection(input) {
96
+ const baseUrlRaw = input.baseUrl?.trim();
97
+ const accountEmail = input.accountEmail?.trim();
98
+ const apiToken = input.apiToken?.trim();
99
+ if (!baseUrlRaw || !accountEmail || !apiToken) {
100
+ throw new ValidationError('Confluence requires a site URL, account email and API token');
101
+ }
102
+ const baseUrl = confluenceLogic.normalizeBaseUrl(baseUrlRaw);
103
+ // Guard against SSRF: the stored base URL is later fetched with the
104
+ // workspace's credentials, so it must be a public https host.
105
+ confluenceLogic.assertSafeConfluenceBaseUrl(baseUrl);
106
+ return {
107
+ credentials: { baseUrl, accountEmail, apiToken },
108
+ label: baseUrl,
109
+ };
110
+ }
111
+ parseRef(input) {
112
+ return confluenceLogic.parseConfluenceRef(input);
113
+ }
114
+ async fetchDocument(credentials, externalId) {
115
+ const base = credentials.baseUrl.replace(/\/+$/, '');
116
+ const url = `${base}/wiki/rest/api/content/${encodeURIComponent(externalId)}?expand=body.storage,version`;
117
+ const auth = btoa(`${credentials.accountEmail}:${credentials.apiToken}`);
118
+ const res = await safeFetch(url, {
119
+ method: 'GET',
120
+ headers: {
121
+ authorization: `Basic ${auth}`,
122
+ accept: 'application/json',
123
+ 'user-agent': USER_AGENT,
124
+ },
125
+ }, (u) => confluenceLogic.assertSafeConfluenceBaseUrl(u));
126
+ if (!res.ok) {
127
+ const text = await readCappedText(res, MAX_RESPONSE_BYTES).catch(() => '');
128
+ throw new ConfluenceApiError(res.status, `Confluence GET ${url} → ${res.status}: ${text.slice(0, 300)}`);
129
+ }
130
+ const text = await readCappedText(res, MAX_RESPONSE_BYTES);
131
+ const json = (() => {
132
+ try {
133
+ return JSON.parse(text);
134
+ }
135
+ catch {
136
+ return null;
137
+ }
138
+ })();
139
+ if (!json || !json.id) {
140
+ throw new ConfluenceApiError(502, `Confluence returned an unexpected body for page ${externalId}`);
141
+ }
142
+ const linkBase = json._links?.base ?? `${base}/wiki`;
143
+ const webui = json._links?.webui ?? '';
144
+ return {
145
+ externalId: json.id,
146
+ title: json.title ?? '(untitled)',
147
+ url: `${linkBase}${webui}`,
148
+ body: confluenceLogic.confluenceStorageToMarkdown(json.body?.storage?.value ?? ''),
149
+ };
150
+ }
151
+ async search(credentials, query) {
152
+ const base = credentials.baseUrl.replace(/\/+$/, '');
153
+ const cql = encodeURIComponent(confluenceLogic.buildConfluenceSearchCql(query));
154
+ const url = `${base}/wiki/rest/api/content/search?cql=${cql}&limit=20`;
155
+ const auth = btoa(`${credentials.accountEmail}:${credentials.apiToken}`);
156
+ const res = await safeFetch(url, {
157
+ method: 'GET',
158
+ headers: {
159
+ authorization: `Basic ${auth}`,
160
+ accept: 'application/json',
161
+ 'user-agent': USER_AGENT,
162
+ },
163
+ }, (u) => confluenceLogic.assertSafeConfluenceBaseUrl(u));
164
+ if (!res.ok) {
165
+ const text = await readCappedText(res, MAX_RESPONSE_BYTES).catch(() => '');
166
+ throw new ConfluenceApiError(res.status, `Confluence search ${url} → ${res.status}: ${text.slice(0, 300)}`);
167
+ }
168
+ const text = await readCappedText(res, MAX_RESPONSE_BYTES);
169
+ const json = (() => {
170
+ try {
171
+ return JSON.parse(text);
172
+ }
173
+ catch {
174
+ return null;
175
+ }
176
+ })();
177
+ return confluenceLogic.parseConfluenceSearchResults(json, base);
178
+ }
179
+ }
180
+ //# sourceMappingURL=ConfluenceProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfluenceProvider.js","sourceRoot":"","sources":["../../../src/modules/documents/ConfluenceProvider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { Clock } from '@cat-factory/kernel';
2
+ import type { DocumentConnectionRecord, DocumentConnectionRepository } from '@cat-factory/kernel';
3
+ import type { DocumentSourceRegistry } from '@cat-factory/kernel';
4
+ import type { DocumentConnection, DocumentSourceDescriptor, DocumentSourceKind } from '@cat-factory/kernel';
5
+ import type { WorkspaceRepository } from '@cat-factory/kernel';
6
+ export interface DocumentConnectionServiceDependencies {
7
+ documentConnectionRepository: DocumentConnectionRepository;
8
+ registry: DocumentSourceRegistry;
9
+ workspaceRepository: WorkspaceRepository;
10
+ clock: Clock;
11
+ }
12
+ export declare class DocumentConnectionService {
13
+ private readonly deps;
14
+ constructor(deps: DocumentConnectionServiceDependencies);
15
+ /** The descriptors of every configured source (drives the connect UI). */
16
+ listSources(): DocumentSourceDescriptor[];
17
+ /** Resolve a provider for a source or throw if that source isn't configured. */
18
+ private requireProvider;
19
+ /** Connect (or re-connect) a workspace to a document source. */
20
+ connect(workspaceId: string, source: DocumentSourceKind, credentials: Record<string, string>): Promise<DocumentConnection>;
21
+ /** The workspace's current connection for a source, or null if not connected. */
22
+ getConnection(workspaceId: string, source: DocumentSourceKind): Promise<DocumentConnection | null>;
23
+ /** Every live connection the workspace holds, across sources. */
24
+ listConnections(workspaceId: string): Promise<DocumentConnection[]>;
25
+ /** Resolve the live connection (with credentials) or throw if not connected. */
26
+ requireConnection(workspaceId: string, source: DocumentSourceKind): Promise<DocumentConnectionRecord>;
27
+ /** Disconnect a workspace from a source (tombstones the binding). */
28
+ disconnect(workspaceId: string, source: DocumentSourceKind): Promise<void>;
29
+ }
30
+ //# sourceMappingURL=DocumentConnectionService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DocumentConnectionService.d.ts","sourceRoot":"","sources":["../../../src/modules/documents/DocumentConnectionService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,EAAE,wBAAwB,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAA;AACjG,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AACjE,OAAO,KAAK,EACV,kBAAkB,EAClB,wBAAwB,EACxB,kBAAkB,EACnB,MAAM,qBAAqB,CAAA;AAG5B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAQ9D,MAAM,WAAW,qCAAqC;IACpD,4BAA4B,EAAE,4BAA4B,CAAA;IAC1D,QAAQ,EAAE,sBAAsB,CAAA;IAChC,mBAAmB,EAAE,mBAAmB,CAAA;IACxC,KAAK,EAAE,KAAK,CAAA;CACb;AAUD,qBAAa,yBAAyB;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,qCAAqC,EAAI;IAE5E,0EAA0E;IAC1E,WAAW,IAAI,wBAAwB,EAAE,CAExC;IAED,gFAAgF;IAChF,OAAO,CAAC,eAAe;IAMvB,gEAAgE;IAC1D,OAAO,CACX,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,kBAAkB,EAC1B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAClC,OAAO,CAAC,kBAAkB,CAAC,CAkB7B;IAED,iFAAiF;IAC3E,aAAa,CACjB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAGpC;IAED,iEAAiE;IAC3D,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAGxE;IAED,gFAAgF;IAC1E,iBAAiB,CACrB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,wBAAwB,CAAC,CAMnC;IAED,qEAAqE;IAC/D,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ/E;CACF"}
@@ -0,0 +1,69 @@
1
+ import { ConflictError, ValidationError } from '@cat-factory/kernel';
2
+ import { requireWorkspace } from '@cat-factory/kernel';
3
+ function toConnection(record) {
4
+ return {
5
+ source: record.source,
6
+ label: record.label,
7
+ connectedAt: record.createdAt,
8
+ };
9
+ }
10
+ export class DocumentConnectionService {
11
+ deps;
12
+ constructor(deps) {
13
+ this.deps = deps;
14
+ }
15
+ /** The descriptors of every configured source (drives the connect UI). */
16
+ listSources() {
17
+ return this.deps.registry.list().map((p) => p.descriptor);
18
+ }
19
+ /** Resolve a provider for a source or throw if that source isn't configured. */
20
+ requireProvider(source) {
21
+ const provider = this.deps.registry.get(source);
22
+ if (!provider)
23
+ throw new ValidationError(`Unknown or unconfigured document source '${source}'`);
24
+ return provider;
25
+ }
26
+ /** Connect (or re-connect) a workspace to a document source. */
27
+ async connect(workspaceId, source, credentials) {
28
+ await requireWorkspace(this.deps.workspaceRepository, workspaceId);
29
+ const provider = this.requireProvider(source);
30
+ const normalized = provider.normalizeConnection(credentials);
31
+ const existing = await this.deps.documentConnectionRepository.getByWorkspace(workspaceId, source);
32
+ const record = {
33
+ workspaceId,
34
+ source,
35
+ credentials: normalized.credentials,
36
+ label: normalized.label,
37
+ createdAt: existing?.createdAt ?? this.deps.clock.now(),
38
+ deletedAt: null,
39
+ };
40
+ await this.deps.documentConnectionRepository.upsert(record);
41
+ return toConnection(record);
42
+ }
43
+ /** The workspace's current connection for a source, or null if not connected. */
44
+ async getConnection(workspaceId, source) {
45
+ const record = await this.deps.documentConnectionRepository.getByWorkspace(workspaceId, source);
46
+ return record ? toConnection(record) : null;
47
+ }
48
+ /** Every live connection the workspace holds, across sources. */
49
+ async listConnections(workspaceId) {
50
+ const records = await this.deps.documentConnectionRepository.listByWorkspace(workspaceId);
51
+ return records.map(toConnection);
52
+ }
53
+ /** Resolve the live connection (with credentials) or throw if not connected. */
54
+ async requireConnection(workspaceId, source) {
55
+ const record = await this.deps.documentConnectionRepository.getByWorkspace(workspaceId, source);
56
+ if (!record) {
57
+ throw new ConflictError(`Workspace '${workspaceId}' is not connected to ${source}`);
58
+ }
59
+ return record;
60
+ }
61
+ /** Disconnect a workspace from a source (tombstones the binding). */
62
+ async disconnect(workspaceId, source) {
63
+ const record = await this.deps.documentConnectionRepository.getByWorkspace(workspaceId, source);
64
+ if (!record)
65
+ return;
66
+ await this.deps.documentConnectionRepository.softDelete(workspaceId, source, this.deps.clock.now());
67
+ }
68
+ }
69
+ //# sourceMappingURL=DocumentConnectionService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DocumentConnectionService.js","sourceRoot":"","sources":["../../../src/modules/documents/DocumentConnectionService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import type { Clock } from '@cat-factory/kernel';
2
+ import type { DocumentSourceRegistry } from '@cat-factory/kernel';
3
+ import type { DocumentRecord, DocumentRepository } from '@cat-factory/kernel';
4
+ import type { SourceDocument, DocumentSearchResult, DocumentSourceKind } from '@cat-factory/kernel';
5
+ import type { WorkspaceRepository } from '@cat-factory/kernel';
6
+ import type { DocumentConnectionService } from './DocumentConnectionService.js';
7
+ export interface DocumentImportServiceDependencies {
8
+ registry: DocumentSourceRegistry;
9
+ documentRepository: DocumentRepository;
10
+ connectionService: DocumentConnectionService;
11
+ workspaceRepository: WorkspaceRepository;
12
+ clock: Clock;
13
+ }
14
+ /** Project a stored document record onto the wire shape (drops body + tombstone). */
15
+ export declare function toSourceDocument(record: DocumentRecord): SourceDocument;
16
+ export declare class DocumentImportService {
17
+ private readonly deps;
18
+ constructor(deps: DocumentImportServiceDependencies);
19
+ private requireProvider;
20
+ /** Fetch a page (by id or URL) and upsert its projection; returns the document. */
21
+ import(workspaceId: string, source: DocumentSourceKind, ref: string): Promise<SourceDocument>;
22
+ /**
23
+ * Search a source's catalogue by free text, returning lean hits (not yet
24
+ * imported). The provider authenticates with the workspace's stored credentials
25
+ * and builds/parses the source-specific query. Throws if the source can't
26
+ * search (no provider `search`), so the controller can answer cleanly.
27
+ */
28
+ search(workspaceId: string, source: DocumentSourceKind, query: string): Promise<DocumentSearchResult[]>;
29
+ /** Every document imported into the workspace, across sources, as wire shapes. */
30
+ listDocuments(workspaceId: string): Promise<SourceDocument[]>;
31
+ /** Resolve a stored document record (with body) or throw if not imported. */
32
+ requireDocument(workspaceId: string, source: DocumentSourceKind, externalId: string): Promise<DocumentRecord>;
33
+ }
34
+ //# sourceMappingURL=DocumentImportService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DocumentImportService.d.ts","sourceRoot":"","sources":["../../../src/modules/documents/DocumentImportService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAGnG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAC9D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAA;AAS/E,MAAM,WAAW,iCAAiC;IAChD,QAAQ,EAAE,sBAAsB,CAAA;IAChC,kBAAkB,EAAE,kBAAkB,CAAA;IACtC,iBAAiB,EAAE,yBAAyB,CAAA;IAC5C,mBAAmB,EAAE,mBAAmB,CAAA;IACxC,KAAK,EAAE,KAAK,CAAA;CACb;AAED,qFAAqF;AACrF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,CAUvE;AAED,qBAAa,qBAAqB;IACpB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,iCAAiC,EAAI;IAExE,OAAO,CAAC,eAAe;IAMvB,mFAAmF;IAC7E,MAAM,CACV,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,kBAAkB,EAC1B,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC,CA0BzB;IAED;;;;;OAKG;IACG,MAAM,CACV,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAQjC;IAED,kFAAkF;IAC5E,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAGlE;IAED,6EAA6E;IACvE,eAAe,CACnB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,kBAAkB,EAC1B,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,cAAc,CAAC,CAMzB;CACF"}
@@ -0,0 +1,83 @@
1
+ import { ValidationError } from '@cat-factory/kernel';
2
+ import { requireWorkspace } from '@cat-factory/kernel';
3
+ import { buildExcerpt } from './documents.logic.js';
4
+ /** Project a stored document record onto the wire shape (drops body + tombstone). */
5
+ export function toSourceDocument(record) {
6
+ return {
7
+ source: record.source,
8
+ externalId: record.externalId,
9
+ title: record.title,
10
+ url: record.url,
11
+ excerpt: record.excerpt,
12
+ linkedBlockId: record.linkedBlockId,
13
+ syncedAt: record.syncedAt,
14
+ };
15
+ }
16
+ export class DocumentImportService {
17
+ deps;
18
+ constructor(deps) {
19
+ this.deps = deps;
20
+ }
21
+ requireProvider(source) {
22
+ const provider = this.deps.registry.get(source);
23
+ if (!provider)
24
+ throw new ValidationError(`Unknown or unconfigured document source '${source}'`);
25
+ return provider;
26
+ }
27
+ /** Fetch a page (by id or URL) and upsert its projection; returns the document. */
28
+ async import(workspaceId, source, ref) {
29
+ await requireWorkspace(this.deps.workspaceRepository, workspaceId);
30
+ const provider = this.requireProvider(source);
31
+ const externalId = provider.parseRef(ref);
32
+ if (!externalId) {
33
+ throw new ValidationError(`Could not resolve a ${source} page id from '${ref}'`);
34
+ }
35
+ const connection = await this.deps.connectionService.requireConnection(workspaceId, source);
36
+ const content = await provider.fetchDocument(connection.credentials, externalId);
37
+ // Preserve any existing block link across a re-import.
38
+ const existing = await this.deps.documentRepository.get(workspaceId, source, content.externalId);
39
+ const record = {
40
+ workspaceId,
41
+ source,
42
+ externalId: content.externalId,
43
+ title: content.title,
44
+ url: content.url,
45
+ excerpt: buildExcerpt(content.body),
46
+ body: content.body,
47
+ linkedBlockId: existing?.linkedBlockId ?? null,
48
+ syncedAt: this.deps.clock.now(),
49
+ deletedAt: null,
50
+ };
51
+ await this.deps.documentRepository.upsert(record);
52
+ return toSourceDocument(record);
53
+ }
54
+ /**
55
+ * Search a source's catalogue by free text, returning lean hits (not yet
56
+ * imported). The provider authenticates with the workspace's stored credentials
57
+ * and builds/parses the source-specific query. Throws if the source can't
58
+ * search (no provider `search`), so the controller can answer cleanly.
59
+ */
60
+ async search(workspaceId, source, query) {
61
+ await requireWorkspace(this.deps.workspaceRepository, workspaceId);
62
+ const provider = this.requireProvider(source);
63
+ if (!provider.search) {
64
+ throw new ValidationError(`The ${source} source does not support search`);
65
+ }
66
+ const connection = await this.deps.connectionService.requireConnection(workspaceId, source);
67
+ return provider.search(connection.credentials, query, workspaceId);
68
+ }
69
+ /** Every document imported into the workspace, across sources, as wire shapes. */
70
+ async listDocuments(workspaceId) {
71
+ const records = await this.deps.documentRepository.listByWorkspace(workspaceId);
72
+ return records.map(toSourceDocument);
73
+ }
74
+ /** Resolve a stored document record (with body) or throw if not imported. */
75
+ async requireDocument(workspaceId, source, externalId) {
76
+ const record = await this.deps.documentRepository.get(workspaceId, source, externalId);
77
+ if (!record) {
78
+ throw new ValidationError(`${source} page '${externalId}' has not been imported`);
79
+ }
80
+ return record;
81
+ }
82
+ }
83
+ //# sourceMappingURL=DocumentImportService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DocumentImportService.js","sourceRoot":"","sources":["../../../src/modules/documents/DocumentImportService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import type { DocumentBoardPlan, SourceDocument, DocumentSourceKind } from '@cat-factory/kernel';
2
+ import type { BlockRepository } from '@cat-factory/kernel';
3
+ import type { DocumentRepository } from '@cat-factory/kernel';
4
+ import type { BoardWritePort } from '@cat-factory/kernel';
5
+ export interface DocumentLinkServiceDependencies {
6
+ boardService: BoardWritePort;
7
+ blockRepository: BlockRepository;
8
+ documentRepository: DocumentRepository;
9
+ }
10
+ /** Counts of blocks created by a spawn, for the API response. */
11
+ export interface SpawnResult {
12
+ frames: number;
13
+ modules: number;
14
+ tasks: number;
15
+ }
16
+ export declare class DocumentLinkService {
17
+ private readonly deps;
18
+ constructor(deps: DocumentLinkServiceDependencies);
19
+ /**
20
+ * Apply a board plan to a workspace. Without `frameId` each planned frame
21
+ * becomes a new top-level frame; with it, the plan's modules and tasks are
22
+ * added inside that existing frame (the planned frames are flattened into it).
23
+ */
24
+ spawn(workspaceId: string, plan: DocumentBoardPlan, frameId?: string): Promise<SpawnResult>;
25
+ /** Add a planned frame's modules and tasks inside an existing frame. */
26
+ private spawnInto;
27
+ private addTask;
28
+ /** Attach an imported document to a board block as extra agent context. */
29
+ linkToBlock(workspaceId: string, blockId: string, source: DocumentSourceKind, externalId: string): Promise<SourceDocument>;
30
+ }
31
+ //# sourceMappingURL=DocumentLinkService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DocumentLinkService.d.ts","sourceRoot":"","sources":["../../../src/modules/documents/DocumentLinkService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAEnB,MAAM,qBAAqB,CAAA;AAE5B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AASzD,MAAM,WAAW,+BAA+B;IAC9C,YAAY,EAAE,cAAc,CAAA;IAC5B,eAAe,EAAE,eAAe,CAAA;IAChC,kBAAkB,EAAE,kBAAkB,CAAA;CACvC;AAED,iEAAiE;AACjE,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;CACd;AAED,qBAAa,mBAAmB;IAClB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,+BAA+B,EAAI;IAEtE;;;;OAIG;IACG,KAAK,CACT,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,iBAAiB,EACvB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,WAAW,CAAC,CAiCtB;IAED,wEAAwE;YAC1D,SAAS;YAoBT,OAAO;IAiBrB,2EAA2E;IACrE,WAAW,CACf,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,kBAAkB,EAC1B,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,cAAc,CAAC,CAazB;CACF"}