@shipfox/api-integration-core 13.0.0 → 14.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/CHANGELOG.md +44 -0
  3. package/dist/core/event-catalogs.d.ts +18 -0
  4. package/dist/core/event-catalogs.d.ts.map +1 -0
  5. package/dist/core/event-catalogs.js +27 -0
  6. package/dist/core/event-catalogs.js.map +1 -0
  7. package/dist/core/providers/registry.d.ts +1 -0
  8. package/dist/core/providers/registry.d.ts.map +1 -1
  9. package/dist/core/providers/registry.js +4 -1
  10. package/dist/core/providers/registry.js.map +1 -1
  11. package/dist/core/providers/source-control.d.ts +1 -1
  12. package/dist/core/providers/source-control.d.ts.map +1 -1
  13. package/dist/core/providers/source-control.js.map +1 -1
  14. package/dist/core/source-control-service.d.ts +5 -1
  15. package/dist/core/source-control-service.d.ts.map +1 -1
  16. package/dist/core/source-control-service.js +12 -0
  17. package/dist/core/source-control-service.js.map +1 -1
  18. package/dist/db/connections.d.ts +4 -1
  19. package/dist/db/connections.d.ts.map +1 -1
  20. package/dist/db/connections.js +15 -6
  21. package/dist/db/connections.js.map +1 -1
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/metrics/instance.d.ts.map +1 -1
  26. package/dist/metrics/instance.js +2 -0
  27. package/dist/metrics/instance.js.map +1 -1
  28. package/dist/presentation/inter-module.d.ts.map +1 -1
  29. package/dist/presentation/inter-module.js +27 -5
  30. package/dist/presentation/inter-module.js.map +1 -1
  31. package/dist/presentation/routes/errors.d.ts.map +1 -1
  32. package/dist/presentation/routes/errors.js +1 -1
  33. package/dist/presentation/routes/errors.js.map +1 -1
  34. package/dist/presentation/routes/manage-connections.d.ts.map +1 -1
  35. package/dist/presentation/routes/manage-connections.js +7 -3
  36. package/dist/presentation/routes/manage-connections.js.map +1 -1
  37. package/dist/providers/gitea.d.ts.map +1 -1
  38. package/dist/providers/gitea.js +5 -1
  39. package/dist/providers/gitea.js.map +1 -1
  40. package/dist/providers/github.d.ts.map +1 -1
  41. package/dist/providers/github.js +6 -5
  42. package/dist/providers/github.js.map +1 -1
  43. package/dist/providers/jira.d.ts.map +1 -1
  44. package/dist/providers/jira.js +7 -2
  45. package/dist/providers/jira.js.map +1 -1
  46. package/dist/providers/linear.d.ts.map +1 -1
  47. package/dist/providers/linear.js +6 -4
  48. package/dist/providers/linear.js.map +1 -1
  49. package/dist/providers/sentry.d.ts.map +1 -1
  50. package/dist/providers/sentry.js +3 -1
  51. package/dist/providers/sentry.js.map +1 -1
  52. package/dist/providers/slack.d.ts.map +1 -1
  53. package/dist/providers/slack.js +6 -4
  54. package/dist/providers/slack.js.map +1 -1
  55. package/dist/tsconfig.test.tsbuildinfo +1 -1
  56. package/package.json +12 -12
  57. package/src/core/agent-tool-selection.test.ts +4 -0
  58. package/src/core/event-catalogs.test.ts +16 -0
  59. package/src/core/event-catalogs.ts +34 -0
  60. package/src/core/providers/registry.test.ts +4 -0
  61. package/src/core/providers/registry.ts +9 -3
  62. package/src/core/providers/source-control.ts +2 -0
  63. package/src/core/secret-cleanup.test.ts +1 -0
  64. package/src/core/source-control-service.test.ts +48 -0
  65. package/src/core/source-control-service.ts +16 -0
  66. package/src/db/connections.test.ts +167 -0
  67. package/src/db/connections.ts +21 -4
  68. package/src/db/secret-cleanups.test.ts +1 -0
  69. package/src/index.ts +2 -0
  70. package/src/metrics/instance.ts +2 -0
  71. package/src/presentation/inter-module.test.ts +219 -0
  72. package/src/presentation/inter-module.ts +28 -3
  73. package/src/presentation/routes/errors.ts +3 -0
  74. package/src/presentation/routes/list-connections.test.ts +13 -1
  75. package/src/presentation/routes/list-repositories.test.ts +9 -0
  76. package/src/presentation/routes/manage-connections.test.ts +52 -0
  77. package/src/presentation/routes/manage-connections.ts +6 -2
  78. package/src/providers/gitea.ts +5 -0
  79. package/src/providers/github.ts +6 -1
  80. package/src/providers/jira.test.ts +3 -0
  81. package/src/providers/jira.ts +6 -0
  82. package/src/providers/linear.test.ts +3 -0
  83. package/src/providers/linear.ts +6 -1
  84. package/src/providers/modules.test.ts +64 -1
  85. package/src/providers/sentry.ts +3 -0
  86. package/src/providers/slack.test.ts +4 -0
  87. package/src/providers/slack.ts +6 -1
  88. package/test/route-utils.ts +4 -0
  89. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,4 +1,5 @@
1
1
  import { config } from '#config.js';
2
+ import { getIntegrationProviderCapabilities } from '#core/providers/registry.js';
2
3
  import { deleteIntegrationConnection, getIntegrationConnectionById, resolveUniqueConnectionSlug, updateIntegrationConnectionLifecycleStatus, upsertIntegrationConnection } from '#db/connections.js';
3
4
  import { db } from '#db/db.js';
4
5
  import { publishIntegrationEventReceived, recordDeliveryOnly } from '#db/webhook-deliveries.js';
@@ -7,6 +8,7 @@ const JIRA_SECRETS_NAMESPACE_PREFIX = 'system/integrations/jira/';
7
8
  async function loadJiraModuleParts(options = {}) {
8
9
  const { createJiraApiClient, createJiraIntegrationProvider, createJiraMaintenanceWorker, createJiraPendingSelectionStore, createJiraTokenStore, db: jiraDb, deregisterJiraWebhooks, deleteJiraInstallationByConnectionId, disconnectJiraInstallation: disconnectJiraInstallationRecords, getJiraInstallationByCloudId, getJiraInstallationByConnectionId, jiraSecretsNamespace, jiraWebhookUrl, migrationsPath, prepareJiraWebhookDeregistration, upsertJiraInstallation, withJiraRefreshLockAndWait, withJiraWebhookRegistrationLock } = await import('@shipfox/api-integration-jira');
9
10
  const jira = createJiraApiClient();
11
+ let providerCapabilities = [];
10
12
  async function getExistingJiraConnection(input) {
11
13
  const installation = await getJiraInstallationByCloudId(input.cloudId);
12
14
  if (!installation) return undefined;
@@ -30,7 +32,8 @@ async function loadJiraModuleParts(options = {}) {
30
32
  externalAccountId: input.cloudId,
31
33
  slug,
32
34
  displayName: input.displayName,
33
- lifecycleStatus: 'active'
35
+ lifecycleStatus: 'active',
36
+ capabilities: providerCapabilities
34
37
  }, {
35
38
  tx
36
39
  });
@@ -162,7 +165,8 @@ async function loadJiraModuleParts(options = {}) {
162
165
  markConnectionActive: async ({ connectionId, tx })=>{
163
166
  await updateIntegrationConnectionLifecycleStatus({
164
167
  id: connectionId,
165
- lifecycleStatus: 'active'
168
+ lifecycleStatus: 'active',
169
+ capabilities: providerCapabilities
166
170
  }, tx === undefined ? {} : {
167
171
  tx: tx
168
172
  });
@@ -180,6 +184,7 @@ async function loadJiraModuleParts(options = {}) {
180
184
  } : {}
181
185
  }
182
186
  });
187
+ providerCapabilities = getIntegrationProviderCapabilities(integrationProvider.adapters);
183
188
  return {
184
189
  provider: integrationProvider,
185
190
  workers: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/providers/jira.ts"],"sourcesContent":["import type {\n ConnectJiraInstallationInput,\n JiraPendingSelectionSecretsStore,\n JiraSecretsStore,\n} from '@shipfox/api-integration-jira';\nimport type {IntegrationConnection as CoreIntegrationConnection} from '@shipfox/api-integration-spi';\nimport {config} from '#config.js';\nimport {\n deleteIntegrationConnection,\n getIntegrationConnectionById,\n resolveUniqueConnectionSlug,\n updateIntegrationConnectionLifecycleStatus,\n upsertIntegrationConnection,\n} from '#db/connections.js';\nimport {db} from '#db/db.js';\nimport {publishIntegrationEventReceived, recordDeliveryOnly} from '#db/webhook-deliveries.js';\nimport {retryConnectionSlugCollision, slugifyConnectionSlug} from '#providers/connection-slug.js';\nimport type {IntegrationModuleParts, IntegrationProviderModule} from '#providers/types.js';\n\nconst JIRA_SECRETS_NAMESPACE_PREFIX = 'system/integrations/jira/';\ntype IntegrationDb = ReturnType<typeof db>;\ntype IntegrationTx = Parameters<Parameters<IntegrationDb['transaction']>[0]>[0];\n\nasync function loadJiraModuleParts(\n options: Parameters<IntegrationProviderModule['load']>[0] = {},\n): Promise<IntegrationModuleParts> {\n const {\n createJiraApiClient,\n createJiraIntegrationProvider,\n createJiraMaintenanceWorker,\n createJiraPendingSelectionStore,\n createJiraTokenStore,\n db: jiraDb,\n deregisterJiraWebhooks,\n deleteJiraInstallationByConnectionId,\n disconnectJiraInstallation: disconnectJiraInstallationRecords,\n getJiraInstallationByCloudId,\n getJiraInstallationByConnectionId,\n jiraSecretsNamespace,\n jiraWebhookUrl,\n migrationsPath,\n prepareJiraWebhookDeregistration,\n upsertJiraInstallation,\n withJiraRefreshLockAndWait,\n withJiraWebhookRegistrationLock,\n } = await import('@shipfox/api-integration-jira');\n const jira = createJiraApiClient();\n\n async function getExistingJiraConnection(input: {\n cloudId: string;\n }): Promise<CoreIntegrationConnection<'jira'> | undefined> {\n const installation = await getJiraInstallationByCloudId(input.cloudId);\n if (!installation) return undefined;\n return (await getIntegrationConnectionById(installation.connectionId)) as\n | CoreIntegrationConnection<'jira'>\n | undefined;\n }\n\n function connectJiraInstallation(\n input: ConnectJiraInstallationInput,\n ): Promise<CoreIntegrationConnection<'jira'>> {\n return retryConnectionSlugCollision(() =>\n db().transaction(async (tx) => {\n const slug = await resolveUniqueConnectionSlug(\n {\n workspaceId: input.workspaceId,\n provider: 'jira',\n externalAccountId: input.cloudId,\n baseSlug: slugifyConnectionSlug(`jira_${input.siteName || input.cloudId}`, {\n fallback: 'jira',\n }),\n },\n {tx},\n );\n const connection = await upsertIntegrationConnection(\n {\n workspaceId: input.workspaceId,\n provider: 'jira',\n externalAccountId: input.cloudId,\n slug,\n displayName: input.displayName,\n lifecycleStatus: 'active',\n },\n {tx},\n );\n await upsertJiraInstallation(\n {\n connectionId: connection.id,\n cloudId: input.cloudId,\n siteUrl: input.siteUrl,\n siteName: input.siteName,\n authorizingAccountId: input.authorizingAccountId,\n scopes: input.scopes,\n status: 'installed',\n tokenExpiresAt: input.tokenExpiresAt,\n },\n {tx},\n );\n return connection as CoreIntegrationConnection<'jira'>;\n }),\n );\n }\n\n async function disconnectJiraInstallation(input: {\n connectionId: string;\n lockAlreadyHeld?: boolean | undefined;\n }): Promise<void> {\n const disconnect = () =>\n disconnectJiraInstallationRecords<IntegrationTx>({\n connectionId: input.connectionId,\n getConnection: getIntegrationConnectionById,\n deleteSecrets: (params) =>\n options.secrets?.jira?.deleteSecrets({\n ...params,\n namespace: jiraNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(0),\n deregisterWebhooks: () =>\n deregisterJiraWebhooks({\n connectionId: input.connectionId,\n getInstallation: getJiraInstallationByConnectionId,\n tokenStore,\n jira,\n }),\n transaction: (fn) => db().transaction((tx) => fn(tx)),\n deleteConnection: (params, transactionOptions) =>\n deleteIntegrationConnection({id: params.connectionId}, transactionOptions),\n });\n if (input.lockAlreadyHeld) {\n await disconnect();\n return;\n }\n const installation = await getJiraInstallationByConnectionId(input.connectionId);\n if (!installation) {\n await disconnect();\n return;\n }\n await withJiraWebhookRegistrationLock(installation.cloudId, disconnect);\n }\n\n const fallbackSecrets: JiraSecretsStore & JiraPendingSelectionSecretsStore = {\n getSecret: () => Promise.resolve(null),\n setSecrets: () => Promise.reject(new Error('Jira token storage is not configured')),\n deleteSecrets: () => Promise.resolve(0),\n };\n const secrets: JiraSecretsStore & JiraPendingSelectionSecretsStore = options.secrets?.jira\n ? {\n getSecret: (params) =>\n options.secrets?.jira?.getSecret({\n ...params,\n namespace: jiraNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(null),\n setSecrets: (params) =>\n options.secrets?.jira?.setSecrets({\n ...params,\n namespace: jiraNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(),\n deleteSecrets: (params) =>\n options.secrets?.jira?.deleteSecrets({\n ...params,\n namespace: jiraNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(0),\n }\n : fallbackSecrets;\n const tokenStore = createJiraTokenStore({\n client: jira,\n resolveConnection: getIntegrationConnectionById,\n secrets,\n markConnectionError: async ({connectionId}) => {\n await updateIntegrationConnectionLifecycleStatus({\n id: connectionId,\n lifecycleStatus: 'error',\n });\n },\n });\n const pendingStore = createJiraPendingSelectionStore({secrets});\n\n const integrationProvider = createJiraIntegrationProvider({\n jira,\n agentTools: {tokenStore},\n cleanup: {\n deleteConnectionRemoteResources: async (connection) => {\n return await prepareJiraWebhookDeregistration({\n connectionId: connection.id,\n getInstallation: getJiraInstallationByConnectionId,\n tokenStore,\n jira,\n });\n },\n withConnectionDeletionLock: async (connection, fn) => {\n const installation = await getJiraInstallationByConnectionId(connection.id);\n if (!installation) {\n await fn();\n return;\n }\n await withJiraWebhookRegistrationLock(installation.cloudId, fn);\n },\n deleteConnectionRecords: async (connection, {tx}) => {\n await deleteJiraInstallationByConnectionId(connection.id, {tx});\n },\n deleteConnectionSecrets: async (connection) => {\n await withJiraRefreshLockAndWait(connection.id, async () => {\n // Scoped secrets accept the provider-local suffix, after this helper validates its prefix.\n await (options.secrets?.jira?.deleteSecrets({\n workspaceId: connection.workspaceId,\n namespace: jiraNamespaceSuffix(jiraSecretsNamespace(connection.id)),\n }) ?? Promise.resolve());\n });\n },\n },\n routes: {\n tokenStore,\n pendingStore,\n getExistingJiraConnection,\n connectJiraInstallation,\n disconnectJiraInstallation,\n coreDb: db,\n publishIntegrationEventReceived,\n recordDeliveryOnly,\n getIntegrationConnectionById,\n markConnectionActive: async ({connectionId, tx}) => {\n await updateIntegrationConnectionLifecycleStatus(\n {\n id: connectionId,\n lifecycleStatus: 'active',\n },\n tx === undefined ? {} : {tx: tx as IntegrationTx},\n );\n },\n markConnectionError: async ({connectionId, tx}) => {\n await updateIntegrationConnectionLifecycleStatus(\n {\n id: connectionId,\n lifecycleStatus: 'error',\n },\n tx === undefined ? {} : {tx: tx as IntegrationTx},\n );\n },\n ...(options.requireActiveWorkspaceMembership\n ? {requireActiveWorkspaceMembership: options.requireActiveWorkspaceMembership}\n : {}),\n },\n });\n\n return {\n provider: integrationProvider,\n workers: [\n createJiraMaintenanceWorker({\n jira,\n tokenStore,\n resolveConnection: getIntegrationConnectionById,\n webhookUrlForConnection: jiraWebhookUrl,\n }),\n ],\n webhookProcessors: integrationProvider.webhookProcessors,\n database: {db: jiraDb, migrationsPath, databaseNamespace: 'integrations_jira'},\n };\n}\n\nexport const jiraProviderModule: IntegrationProviderModule = {\n id: 'jira',\n enabled: config.INTEGRATIONS_ENABLE_JIRA_PROVIDER,\n load: loadJiraModuleParts,\n};\n\nfunction jiraNamespaceSuffix(namespace: string): string {\n if (!namespace.startsWith(JIRA_SECRETS_NAMESPACE_PREFIX)) {\n throw new Error('Jira provider attempted to access an unscoped secret namespace');\n }\n return namespace.slice(JIRA_SECRETS_NAMESPACE_PREFIX.length);\n}\n"],"names":["config","deleteIntegrationConnection","getIntegrationConnectionById","resolveUniqueConnectionSlug","updateIntegrationConnectionLifecycleStatus","upsertIntegrationConnection","db","publishIntegrationEventReceived","recordDeliveryOnly","retryConnectionSlugCollision","slugifyConnectionSlug","JIRA_SECRETS_NAMESPACE_PREFIX","loadJiraModuleParts","options","createJiraApiClient","createJiraIntegrationProvider","createJiraMaintenanceWorker","createJiraPendingSelectionStore","createJiraTokenStore","jiraDb","deregisterJiraWebhooks","deleteJiraInstallationByConnectionId","disconnectJiraInstallation","disconnectJiraInstallationRecords","getJiraInstallationByCloudId","getJiraInstallationByConnectionId","jiraSecretsNamespace","jiraWebhookUrl","migrationsPath","prepareJiraWebhookDeregistration","upsertJiraInstallation","withJiraRefreshLockAndWait","withJiraWebhookRegistrationLock","jira","getExistingJiraConnection","input","installation","cloudId","undefined","connectionId","connectJiraInstallation","transaction","tx","slug","workspaceId","provider","externalAccountId","baseSlug","siteName","fallback","connection","displayName","lifecycleStatus","id","siteUrl","authorizingAccountId","scopes","status","tokenExpiresAt","disconnect","getConnection","deleteSecrets","params","secrets","namespace","jiraNamespaceSuffix","Promise","resolve","deregisterWebhooks","getInstallation","tokenStore","fn","deleteConnection","transactionOptions","lockAlreadyHeld","fallbackSecrets","getSecret","setSecrets","reject","Error","client","resolveConnection","markConnectionError","pendingStore","integrationProvider","agentTools","cleanup","deleteConnectionRemoteResources","withConnectionDeletionLock","deleteConnectionRecords","deleteConnectionSecrets","routes","coreDb","markConnectionActive","requireActiveWorkspaceMembership","workers","webhookUrlForConnection","webhookProcessors","database","databaseNamespace","jiraProviderModule","enabled","INTEGRATIONS_ENABLE_JIRA_PROVIDER","load","startsWith","slice","length"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/providers/jira.ts"],"sourcesContent":["import type {\n ConnectJiraInstallationInput,\n JiraPendingSelectionSecretsStore,\n JiraSecretsStore,\n} from '@shipfox/api-integration-jira';\nimport type {IntegrationConnection as CoreIntegrationConnection} from '@shipfox/api-integration-spi';\nimport {config} from '#config.js';\nimport type {IntegrationCapability} from '#core/entities/provider.js';\nimport {getIntegrationProviderCapabilities} from '#core/providers/registry.js';\nimport {\n deleteIntegrationConnection,\n getIntegrationConnectionById,\n resolveUniqueConnectionSlug,\n updateIntegrationConnectionLifecycleStatus,\n upsertIntegrationConnection,\n} from '#db/connections.js';\nimport {db} from '#db/db.js';\nimport {publishIntegrationEventReceived, recordDeliveryOnly} from '#db/webhook-deliveries.js';\nimport {retryConnectionSlugCollision, slugifyConnectionSlug} from '#providers/connection-slug.js';\nimport type {IntegrationModuleParts, IntegrationProviderModule} from '#providers/types.js';\n\nconst JIRA_SECRETS_NAMESPACE_PREFIX = 'system/integrations/jira/';\ntype IntegrationDb = ReturnType<typeof db>;\ntype IntegrationTx = Parameters<Parameters<IntegrationDb['transaction']>[0]>[0];\n\nasync function loadJiraModuleParts(\n options: Parameters<IntegrationProviderModule['load']>[0] = {},\n): Promise<IntegrationModuleParts> {\n const {\n createJiraApiClient,\n createJiraIntegrationProvider,\n createJiraMaintenanceWorker,\n createJiraPendingSelectionStore,\n createJiraTokenStore,\n db: jiraDb,\n deregisterJiraWebhooks,\n deleteJiraInstallationByConnectionId,\n disconnectJiraInstallation: disconnectJiraInstallationRecords,\n getJiraInstallationByCloudId,\n getJiraInstallationByConnectionId,\n jiraSecretsNamespace,\n jiraWebhookUrl,\n migrationsPath,\n prepareJiraWebhookDeregistration,\n upsertJiraInstallation,\n withJiraRefreshLockAndWait,\n withJiraWebhookRegistrationLock,\n } = await import('@shipfox/api-integration-jira');\n const jira = createJiraApiClient();\n let providerCapabilities: IntegrationCapability[] = [];\n\n async function getExistingJiraConnection(input: {\n cloudId: string;\n }): Promise<CoreIntegrationConnection<'jira'> | undefined> {\n const installation = await getJiraInstallationByCloudId(input.cloudId);\n if (!installation) return undefined;\n return (await getIntegrationConnectionById(installation.connectionId)) as\n | CoreIntegrationConnection<'jira'>\n | undefined;\n }\n\n function connectJiraInstallation(\n input: ConnectJiraInstallationInput,\n ): Promise<CoreIntegrationConnection<'jira'>> {\n return retryConnectionSlugCollision(() =>\n db().transaction(async (tx) => {\n const slug = await resolveUniqueConnectionSlug(\n {\n workspaceId: input.workspaceId,\n provider: 'jira',\n externalAccountId: input.cloudId,\n baseSlug: slugifyConnectionSlug(`jira_${input.siteName || input.cloudId}`, {\n fallback: 'jira',\n }),\n },\n {tx},\n );\n const connection = await upsertIntegrationConnection(\n {\n workspaceId: input.workspaceId,\n provider: 'jira',\n externalAccountId: input.cloudId,\n slug,\n displayName: input.displayName,\n lifecycleStatus: 'active',\n capabilities: providerCapabilities,\n },\n {tx},\n );\n await upsertJiraInstallation(\n {\n connectionId: connection.id,\n cloudId: input.cloudId,\n siteUrl: input.siteUrl,\n siteName: input.siteName,\n authorizingAccountId: input.authorizingAccountId,\n scopes: input.scopes,\n status: 'installed',\n tokenExpiresAt: input.tokenExpiresAt,\n },\n {tx},\n );\n return connection as CoreIntegrationConnection<'jira'>;\n }),\n );\n }\n\n async function disconnectJiraInstallation(input: {\n connectionId: string;\n lockAlreadyHeld?: boolean | undefined;\n }): Promise<void> {\n const disconnect = () =>\n disconnectJiraInstallationRecords<IntegrationTx>({\n connectionId: input.connectionId,\n getConnection: getIntegrationConnectionById,\n deleteSecrets: (params) =>\n options.secrets?.jira?.deleteSecrets({\n ...params,\n namespace: jiraNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(0),\n deregisterWebhooks: () =>\n deregisterJiraWebhooks({\n connectionId: input.connectionId,\n getInstallation: getJiraInstallationByConnectionId,\n tokenStore,\n jira,\n }),\n transaction: (fn) => db().transaction((tx) => fn(tx)),\n deleteConnection: (params, transactionOptions) =>\n deleteIntegrationConnection({id: params.connectionId}, transactionOptions),\n });\n if (input.lockAlreadyHeld) {\n await disconnect();\n return;\n }\n const installation = await getJiraInstallationByConnectionId(input.connectionId);\n if (!installation) {\n await disconnect();\n return;\n }\n await withJiraWebhookRegistrationLock(installation.cloudId, disconnect);\n }\n\n const fallbackSecrets: JiraSecretsStore & JiraPendingSelectionSecretsStore = {\n getSecret: () => Promise.resolve(null),\n setSecrets: () => Promise.reject(new Error('Jira token storage is not configured')),\n deleteSecrets: () => Promise.resolve(0),\n };\n const secrets: JiraSecretsStore & JiraPendingSelectionSecretsStore = options.secrets?.jira\n ? {\n getSecret: (params) =>\n options.secrets?.jira?.getSecret({\n ...params,\n namespace: jiraNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(null),\n setSecrets: (params) =>\n options.secrets?.jira?.setSecrets({\n ...params,\n namespace: jiraNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(),\n deleteSecrets: (params) =>\n options.secrets?.jira?.deleteSecrets({\n ...params,\n namespace: jiraNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(0),\n }\n : fallbackSecrets;\n const tokenStore = createJiraTokenStore({\n client: jira,\n resolveConnection: getIntegrationConnectionById,\n secrets,\n markConnectionError: async ({connectionId}) => {\n await updateIntegrationConnectionLifecycleStatus({\n id: connectionId,\n lifecycleStatus: 'error',\n });\n },\n });\n const pendingStore = createJiraPendingSelectionStore({secrets});\n\n const integrationProvider = createJiraIntegrationProvider({\n jira,\n agentTools: {tokenStore},\n cleanup: {\n deleteConnectionRemoteResources: async (connection) => {\n return await prepareJiraWebhookDeregistration({\n connectionId: connection.id,\n getInstallation: getJiraInstallationByConnectionId,\n tokenStore,\n jira,\n });\n },\n withConnectionDeletionLock: async (connection, fn) => {\n const installation = await getJiraInstallationByConnectionId(connection.id);\n if (!installation) {\n await fn();\n return;\n }\n await withJiraWebhookRegistrationLock(installation.cloudId, fn);\n },\n deleteConnectionRecords: async (connection, {tx}) => {\n await deleteJiraInstallationByConnectionId(connection.id, {tx});\n },\n deleteConnectionSecrets: async (connection) => {\n await withJiraRefreshLockAndWait(connection.id, async () => {\n // Scoped secrets accept the provider-local suffix, after this helper validates its prefix.\n await (options.secrets?.jira?.deleteSecrets({\n workspaceId: connection.workspaceId,\n namespace: jiraNamespaceSuffix(jiraSecretsNamespace(connection.id)),\n }) ?? Promise.resolve());\n });\n },\n },\n routes: {\n tokenStore,\n pendingStore,\n getExistingJiraConnection,\n connectJiraInstallation,\n disconnectJiraInstallation,\n coreDb: db,\n publishIntegrationEventReceived,\n recordDeliveryOnly,\n getIntegrationConnectionById,\n markConnectionActive: async ({connectionId, tx}) => {\n await updateIntegrationConnectionLifecycleStatus(\n {\n id: connectionId,\n lifecycleStatus: 'active',\n capabilities: providerCapabilities,\n },\n tx === undefined ? {} : {tx: tx as IntegrationTx},\n );\n },\n markConnectionError: async ({connectionId, tx}) => {\n await updateIntegrationConnectionLifecycleStatus(\n {\n id: connectionId,\n lifecycleStatus: 'error',\n },\n tx === undefined ? {} : {tx: tx as IntegrationTx},\n );\n },\n ...(options.requireActiveWorkspaceMembership\n ? {requireActiveWorkspaceMembership: options.requireActiveWorkspaceMembership}\n : {}),\n },\n });\n providerCapabilities = getIntegrationProviderCapabilities(integrationProvider.adapters);\n\n return {\n provider: integrationProvider,\n workers: [\n createJiraMaintenanceWorker({\n jira,\n tokenStore,\n resolveConnection: getIntegrationConnectionById,\n webhookUrlForConnection: jiraWebhookUrl,\n }),\n ],\n webhookProcessors: integrationProvider.webhookProcessors,\n database: {db: jiraDb, migrationsPath, databaseNamespace: 'integrations_jira'},\n };\n}\n\nexport const jiraProviderModule: IntegrationProviderModule = {\n id: 'jira',\n enabled: config.INTEGRATIONS_ENABLE_JIRA_PROVIDER,\n load: loadJiraModuleParts,\n};\n\nfunction jiraNamespaceSuffix(namespace: string): string {\n if (!namespace.startsWith(JIRA_SECRETS_NAMESPACE_PREFIX)) {\n throw new Error('Jira provider attempted to access an unscoped secret namespace');\n }\n return namespace.slice(JIRA_SECRETS_NAMESPACE_PREFIX.length);\n}\n"],"names":["config","getIntegrationProviderCapabilities","deleteIntegrationConnection","getIntegrationConnectionById","resolveUniqueConnectionSlug","updateIntegrationConnectionLifecycleStatus","upsertIntegrationConnection","db","publishIntegrationEventReceived","recordDeliveryOnly","retryConnectionSlugCollision","slugifyConnectionSlug","JIRA_SECRETS_NAMESPACE_PREFIX","loadJiraModuleParts","options","createJiraApiClient","createJiraIntegrationProvider","createJiraMaintenanceWorker","createJiraPendingSelectionStore","createJiraTokenStore","jiraDb","deregisterJiraWebhooks","deleteJiraInstallationByConnectionId","disconnectJiraInstallation","disconnectJiraInstallationRecords","getJiraInstallationByCloudId","getJiraInstallationByConnectionId","jiraSecretsNamespace","jiraWebhookUrl","migrationsPath","prepareJiraWebhookDeregistration","upsertJiraInstallation","withJiraRefreshLockAndWait","withJiraWebhookRegistrationLock","jira","providerCapabilities","getExistingJiraConnection","input","installation","cloudId","undefined","connectionId","connectJiraInstallation","transaction","tx","slug","workspaceId","provider","externalAccountId","baseSlug","siteName","fallback","connection","displayName","lifecycleStatus","capabilities","id","siteUrl","authorizingAccountId","scopes","status","tokenExpiresAt","disconnect","getConnection","deleteSecrets","params","secrets","namespace","jiraNamespaceSuffix","Promise","resolve","deregisterWebhooks","getInstallation","tokenStore","fn","deleteConnection","transactionOptions","lockAlreadyHeld","fallbackSecrets","getSecret","setSecrets","reject","Error","client","resolveConnection","markConnectionError","pendingStore","integrationProvider","agentTools","cleanup","deleteConnectionRemoteResources","withConnectionDeletionLock","deleteConnectionRecords","deleteConnectionSecrets","routes","coreDb","markConnectionActive","requireActiveWorkspaceMembership","adapters","workers","webhookUrlForConnection","webhookProcessors","database","databaseNamespace","jiraProviderModule","enabled","INTEGRATIONS_ENABLE_JIRA_PROVIDER","load","startsWith","slice","length"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"linear.d.ts","sourceRoot":"","sources":["../../src/providers/linear.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAyB,yBAAyB,EAAC,MAAM,qBAAqB,CAAC;AAwK3F,eAAO,MAAM,oBAAoB,EAAE,yBAIlC,CAAC"}
1
+ {"version":3,"file":"linear.d.ts","sourceRoot":"","sources":["../../src/providers/linear.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAyB,yBAAyB,EAAC,MAAM,qBAAqB,CAAC;AA2K3F,eAAO,MAAM,oBAAoB,EAAE,yBAIlC,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { config } from '#config.js';
2
+ import { getIntegrationProviderCapabilities } from '#core/providers/registry.js';
2
3
  import { deleteIntegrationConnection, getIntegrationConnectionById, resolveUniqueConnectionSlug, upsertIntegrationConnection } from '#db/connections.js';
3
4
  import { db } from '#db/db.js';
4
5
  import { publishIntegrationEventReceived, recordDeliveryOnly } from '#db/webhook-deliveries.js';
@@ -6,6 +7,7 @@ import { retryConnectionSlugCollision, slugifyConnectionSlug } from '#providers/
6
7
  const LINEAR_SECRETS_NAMESPACE_PREFIX = 'system/integrations/linear/';
7
8
  async function loadLinearModuleParts(options = {}) {
8
9
  const { createLinearTokenStore, createLinearE2eRoutes, createLinearIntegrationProvider, config: linearConfig, deleteLinearInstallationByConnectionId, disconnectLinearInstallation: disconnectLinearInstallationRecords, getLinearInstallationByOrganizationId, db: linearDb, linearSecretsNamespace, migrationsPath: linearMigrationsPath, upsertLinearInstallation } = await import('@shipfox/api-integration-linear');
10
+ let providerCapabilities = [];
9
11
  async function getExistingLinearConnection(input) {
10
12
  const installation = await getLinearInstallationByOrganizationId(input.organizationId);
11
13
  if (!installation) return undefined;
@@ -32,7 +34,8 @@ async function loadLinearModuleParts(options = {}) {
32
34
  externalAccountId: input.organizationId,
33
35
  slug,
34
36
  displayName: input.displayName,
35
- lifecycleStatus: 'active'
37
+ lifecycleStatus: 'active',
38
+ capabilities: providerCapabilities
36
39
  }, {
37
40
  tx
38
41
  });
@@ -115,6 +118,7 @@ async function loadLinearModuleParts(options = {}) {
115
118
  } : {}
116
119
  }
117
120
  });
121
+ providerCapabilities = getIntegrationProviderCapabilities(integrationProvider.adapters);
118
122
  return {
119
123
  provider: integrationProvider,
120
124
  webhookProcessors: integrationProvider.webhookProcessors,
@@ -124,9 +128,7 @@ async function loadLinearModuleParts(options = {}) {
124
128
  getExistingLinearConnection,
125
129
  connectLinearInstallation,
126
130
  disconnectLinearInstallation,
127
- connectionCapabilities: [
128
- 'agent_tools'
129
- ]
131
+ connectionCapabilities: providerCapabilities
130
132
  })
131
133
  ],
132
134
  database: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/providers/linear.ts"],"sourcesContent":["import type {\n ConnectLinearInstallationInput,\n LinearSecretsStore,\n} from '@shipfox/api-integration-linear';\nimport type {IntegrationConnection as CoreIntegrationConnection} from '@shipfox/api-integration-spi';\nimport {config} from '#config.js';\nimport {\n deleteIntegrationConnection,\n getIntegrationConnectionById,\n resolveUniqueConnectionSlug,\n upsertIntegrationConnection,\n} from '#db/connections.js';\nimport {db} from '#db/db.js';\nimport {publishIntegrationEventReceived, recordDeliveryOnly} from '#db/webhook-deliveries.js';\nimport {retryConnectionSlugCollision, slugifyConnectionSlug} from '#providers/connection-slug.js';\nimport type {IntegrationModuleParts, IntegrationProviderModule} from '#providers/types.js';\n\nconst LINEAR_SECRETS_NAMESPACE_PREFIX = 'system/integrations/linear/';\n\ntype IntegrationDb = ReturnType<typeof db>;\ntype IntegrationTx = Parameters<Parameters<IntegrationDb['transaction']>[0]>[0];\n\nasync function loadLinearModuleParts(\n options: Parameters<IntegrationProviderModule['load']>[0] = {},\n): Promise<IntegrationModuleParts> {\n const {\n createLinearTokenStore,\n createLinearE2eRoutes,\n createLinearIntegrationProvider,\n config: linearConfig,\n deleteLinearInstallationByConnectionId,\n disconnectLinearInstallation: disconnectLinearInstallationRecords,\n getLinearInstallationByOrganizationId,\n db: linearDb,\n linearSecretsNamespace,\n migrationsPath: linearMigrationsPath,\n upsertLinearInstallation,\n } = await import('@shipfox/api-integration-linear');\n\n async function getExistingLinearConnection(input: {\n organizationId: string;\n }): Promise<CoreIntegrationConnection<'linear'> | undefined> {\n const installation = await getLinearInstallationByOrganizationId(input.organizationId);\n if (!installation) return undefined;\n const connection = await getIntegrationConnectionById(installation.connectionId);\n if (!connection) return undefined;\n return connection as CoreIntegrationConnection<'linear'>;\n }\n\n async function connectLinearInstallation(\n input: ConnectLinearInstallationInput,\n ): Promise<CoreIntegrationConnection<'linear'>> {\n return await retryConnectionSlugCollision(() =>\n db().transaction(async (tx) => {\n const baseSlug = slugifyConnectionSlug(`linear_${input.organizationUrlKey}`, {\n fallback: 'linear',\n });\n const slug = await resolveUniqueConnectionSlug(\n {\n workspaceId: input.workspaceId,\n provider: 'linear',\n externalAccountId: input.organizationId,\n baseSlug,\n },\n {tx},\n );\n const connection = await upsertIntegrationConnection(\n {\n workspaceId: input.workspaceId,\n provider: 'linear',\n externalAccountId: input.organizationId,\n slug,\n displayName: input.displayName,\n lifecycleStatus: 'active',\n },\n {tx},\n );\n\n await upsertLinearInstallation(\n {\n connectionId: connection.id,\n organizationId: input.organizationId,\n organizationUrlKey: input.organizationUrlKey,\n appUserId: input.appUserId,\n scopes: input.scopes,\n tokenExpiresAt: input.tokenExpiresAt,\n status: 'installed',\n },\n {tx},\n );\n\n return connection as CoreIntegrationConnection<'linear'>;\n }),\n );\n }\n\n async function disconnectLinearInstallation(input: {connectionId: string}): Promise<void> {\n await disconnectLinearInstallationRecords<IntegrationTx>({\n connectionId: input.connectionId,\n getConnection: getIntegrationConnectionById,\n deleteSecrets: (params) =>\n options.secrets?.linear?.deleteSecrets({\n ...params,\n namespace: linearNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(0),\n transaction: (fn) => db().transaction((tx) => fn(tx)),\n deleteConnection: (params, options) =>\n deleteIntegrationConnection({id: params.connectionId}, options),\n });\n }\n\n const fallbackSecrets: LinearSecretsStore = {\n getSecret: () => Promise.resolve(null),\n setSecrets: () => Promise.reject(new Error('Linear token storage is not configured')),\n };\n const secrets: LinearSecretsStore = options.secrets?.linear\n ? {\n getSecret: (params: Parameters<LinearSecretsStore['getSecret']>[0]) =>\n options.secrets?.linear?.getSecret({\n ...params,\n namespace: linearNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(null),\n setSecrets: (params: Parameters<LinearSecretsStore['setSecrets']>[0]) =>\n options.secrets?.linear?.setSecrets({\n ...params,\n namespace: linearNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(),\n }\n : fallbackSecrets;\n const tokenStore = createLinearTokenStore({\n resolveConnection: async (connectionId) => getIntegrationConnectionById(connectionId),\n secrets,\n });\n\n const integrationProvider = createLinearIntegrationProvider({\n agentTools: {tokenStore, endpoint: linearConfig.LINEAR_MCP_ENDPOINT},\n cleanup: {\n deleteConnectionRecords: async (connection, {tx}) => {\n await deleteLinearInstallationByConnectionId(connection.id, {tx});\n },\n deleteConnectionSecrets: async (connection) => {\n // Scoped secrets accept the provider-local suffix, after this helper validates its prefix.\n await (options.secrets?.linear?.deleteSecrets({\n workspaceId: connection.workspaceId,\n namespace: linearNamespaceSuffix(linearSecretsNamespace(connection.id)),\n }) ?? Promise.resolve());\n },\n },\n routes: {\n tokenStore,\n getExistingLinearConnection,\n connectLinearInstallation,\n disconnectLinearInstallation,\n publishIntegrationEventReceived,\n recordDeliveryOnly,\n getIntegrationConnectionById,\n coreDb: db,\n ...(options.requireActiveWorkspaceMembership\n ? {requireActiveWorkspaceMembership: options.requireActiveWorkspaceMembership}\n : {}),\n },\n });\n\n return {\n provider: integrationProvider,\n webhookProcessors: integrationProvider.webhookProcessors,\n e2eRoutes: [\n createLinearE2eRoutes({\n tokenStore,\n getExistingLinearConnection,\n connectLinearInstallation,\n disconnectLinearInstallation,\n connectionCapabilities: ['agent_tools'],\n }),\n ],\n database: {\n db: linearDb,\n migrationsPath: linearMigrationsPath,\n databaseNamespace: 'integrations_linear',\n },\n };\n}\n\nexport const linearProviderModule: IntegrationProviderModule = {\n id: 'linear',\n enabled: config.INTEGRATIONS_ENABLE_LINEAR_PROVIDER,\n load: loadLinearModuleParts,\n};\n\nfunction linearNamespaceSuffix(namespace: string): string {\n if (!namespace.startsWith(LINEAR_SECRETS_NAMESPACE_PREFIX)) {\n throw new Error('Linear provider attempted to access an unscoped secret namespace');\n }\n return namespace.slice(LINEAR_SECRETS_NAMESPACE_PREFIX.length);\n}\n"],"names":["config","deleteIntegrationConnection","getIntegrationConnectionById","resolveUniqueConnectionSlug","upsertIntegrationConnection","db","publishIntegrationEventReceived","recordDeliveryOnly","retryConnectionSlugCollision","slugifyConnectionSlug","LINEAR_SECRETS_NAMESPACE_PREFIX","loadLinearModuleParts","options","createLinearTokenStore","createLinearE2eRoutes","createLinearIntegrationProvider","linearConfig","deleteLinearInstallationByConnectionId","disconnectLinearInstallation","disconnectLinearInstallationRecords","getLinearInstallationByOrganizationId","linearDb","linearSecretsNamespace","migrationsPath","linearMigrationsPath","upsertLinearInstallation","getExistingLinearConnection","input","installation","organizationId","undefined","connection","connectionId","connectLinearInstallation","transaction","tx","baseSlug","organizationUrlKey","fallback","slug","workspaceId","provider","externalAccountId","displayName","lifecycleStatus","id","appUserId","scopes","tokenExpiresAt","status","getConnection","deleteSecrets","params","secrets","linear","namespace","linearNamespaceSuffix","Promise","resolve","fn","deleteConnection","fallbackSecrets","getSecret","setSecrets","reject","Error","tokenStore","resolveConnection","integrationProvider","agentTools","endpoint","LINEAR_MCP_ENDPOINT","cleanup","deleteConnectionRecords","deleteConnectionSecrets","routes","coreDb","requireActiveWorkspaceMembership","webhookProcessors","e2eRoutes","connectionCapabilities","database","databaseNamespace","linearProviderModule","enabled","INTEGRATIONS_ENABLE_LINEAR_PROVIDER","load","startsWith","slice","length"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/providers/linear.ts"],"sourcesContent":["import type {\n ConnectLinearInstallationInput,\n LinearSecretsStore,\n} from '@shipfox/api-integration-linear';\nimport type {IntegrationConnection as CoreIntegrationConnection} from '@shipfox/api-integration-spi';\nimport {config} from '#config.js';\nimport type {IntegrationCapability} from '#core/entities/provider.js';\nimport {getIntegrationProviderCapabilities} from '#core/providers/registry.js';\nimport {\n deleteIntegrationConnection,\n getIntegrationConnectionById,\n resolveUniqueConnectionSlug,\n upsertIntegrationConnection,\n} from '#db/connections.js';\nimport {db} from '#db/db.js';\nimport {publishIntegrationEventReceived, recordDeliveryOnly} from '#db/webhook-deliveries.js';\nimport {retryConnectionSlugCollision, slugifyConnectionSlug} from '#providers/connection-slug.js';\nimport type {IntegrationModuleParts, IntegrationProviderModule} from '#providers/types.js';\n\nconst LINEAR_SECRETS_NAMESPACE_PREFIX = 'system/integrations/linear/';\n\ntype IntegrationDb = ReturnType<typeof db>;\ntype IntegrationTx = Parameters<Parameters<IntegrationDb['transaction']>[0]>[0];\n\nasync function loadLinearModuleParts(\n options: Parameters<IntegrationProviderModule['load']>[0] = {},\n): Promise<IntegrationModuleParts> {\n const {\n createLinearTokenStore,\n createLinearE2eRoutes,\n createLinearIntegrationProvider,\n config: linearConfig,\n deleteLinearInstallationByConnectionId,\n disconnectLinearInstallation: disconnectLinearInstallationRecords,\n getLinearInstallationByOrganizationId,\n db: linearDb,\n linearSecretsNamespace,\n migrationsPath: linearMigrationsPath,\n upsertLinearInstallation,\n } = await import('@shipfox/api-integration-linear');\n let providerCapabilities: IntegrationCapability[] = [];\n\n async function getExistingLinearConnection(input: {\n organizationId: string;\n }): Promise<CoreIntegrationConnection<'linear'> | undefined> {\n const installation = await getLinearInstallationByOrganizationId(input.organizationId);\n if (!installation) return undefined;\n const connection = await getIntegrationConnectionById(installation.connectionId);\n if (!connection) return undefined;\n return connection as CoreIntegrationConnection<'linear'>;\n }\n\n async function connectLinearInstallation(\n input: ConnectLinearInstallationInput,\n ): Promise<CoreIntegrationConnection<'linear'>> {\n return await retryConnectionSlugCollision(() =>\n db().transaction(async (tx) => {\n const baseSlug = slugifyConnectionSlug(`linear_${input.organizationUrlKey}`, {\n fallback: 'linear',\n });\n const slug = await resolveUniqueConnectionSlug(\n {\n workspaceId: input.workspaceId,\n provider: 'linear',\n externalAccountId: input.organizationId,\n baseSlug,\n },\n {tx},\n );\n const connection = await upsertIntegrationConnection(\n {\n workspaceId: input.workspaceId,\n provider: 'linear',\n externalAccountId: input.organizationId,\n slug,\n displayName: input.displayName,\n lifecycleStatus: 'active',\n capabilities: providerCapabilities,\n },\n {tx},\n );\n\n await upsertLinearInstallation(\n {\n connectionId: connection.id,\n organizationId: input.organizationId,\n organizationUrlKey: input.organizationUrlKey,\n appUserId: input.appUserId,\n scopes: input.scopes,\n tokenExpiresAt: input.tokenExpiresAt,\n status: 'installed',\n },\n {tx},\n );\n\n return connection as CoreIntegrationConnection<'linear'>;\n }),\n );\n }\n\n async function disconnectLinearInstallation(input: {connectionId: string}): Promise<void> {\n await disconnectLinearInstallationRecords<IntegrationTx>({\n connectionId: input.connectionId,\n getConnection: getIntegrationConnectionById,\n deleteSecrets: (params) =>\n options.secrets?.linear?.deleteSecrets({\n ...params,\n namespace: linearNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(0),\n transaction: (fn) => db().transaction((tx) => fn(tx)),\n deleteConnection: (params, options) =>\n deleteIntegrationConnection({id: params.connectionId}, options),\n });\n }\n\n const fallbackSecrets: LinearSecretsStore = {\n getSecret: () => Promise.resolve(null),\n setSecrets: () => Promise.reject(new Error('Linear token storage is not configured')),\n };\n const secrets: LinearSecretsStore = options.secrets?.linear\n ? {\n getSecret: (params: Parameters<LinearSecretsStore['getSecret']>[0]) =>\n options.secrets?.linear?.getSecret({\n ...params,\n namespace: linearNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(null),\n setSecrets: (params: Parameters<LinearSecretsStore['setSecrets']>[0]) =>\n options.secrets?.linear?.setSecrets({\n ...params,\n namespace: linearNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(),\n }\n : fallbackSecrets;\n const tokenStore = createLinearTokenStore({\n resolveConnection: async (connectionId) => getIntegrationConnectionById(connectionId),\n secrets,\n });\n\n const integrationProvider = createLinearIntegrationProvider({\n agentTools: {tokenStore, endpoint: linearConfig.LINEAR_MCP_ENDPOINT},\n cleanup: {\n deleteConnectionRecords: async (connection, {tx}) => {\n await deleteLinearInstallationByConnectionId(connection.id, {tx});\n },\n deleteConnectionSecrets: async (connection) => {\n // Scoped secrets accept the provider-local suffix, after this helper validates its prefix.\n await (options.secrets?.linear?.deleteSecrets({\n workspaceId: connection.workspaceId,\n namespace: linearNamespaceSuffix(linearSecretsNamespace(connection.id)),\n }) ?? Promise.resolve());\n },\n },\n routes: {\n tokenStore,\n getExistingLinearConnection,\n connectLinearInstallation,\n disconnectLinearInstallation,\n publishIntegrationEventReceived,\n recordDeliveryOnly,\n getIntegrationConnectionById,\n coreDb: db,\n ...(options.requireActiveWorkspaceMembership\n ? {requireActiveWorkspaceMembership: options.requireActiveWorkspaceMembership}\n : {}),\n },\n });\n providerCapabilities = getIntegrationProviderCapabilities(integrationProvider.adapters);\n\n return {\n provider: integrationProvider,\n webhookProcessors: integrationProvider.webhookProcessors,\n e2eRoutes: [\n createLinearE2eRoutes({\n tokenStore,\n getExistingLinearConnection,\n connectLinearInstallation,\n disconnectLinearInstallation,\n connectionCapabilities: providerCapabilities,\n }),\n ],\n database: {\n db: linearDb,\n migrationsPath: linearMigrationsPath,\n databaseNamespace: 'integrations_linear',\n },\n };\n}\n\nexport const linearProviderModule: IntegrationProviderModule = {\n id: 'linear',\n enabled: config.INTEGRATIONS_ENABLE_LINEAR_PROVIDER,\n load: loadLinearModuleParts,\n};\n\nfunction linearNamespaceSuffix(namespace: string): string {\n if (!namespace.startsWith(LINEAR_SECRETS_NAMESPACE_PREFIX)) {\n throw new Error('Linear provider attempted to access an unscoped secret namespace');\n }\n return namespace.slice(LINEAR_SECRETS_NAMESPACE_PREFIX.length);\n}\n"],"names":["config","getIntegrationProviderCapabilities","deleteIntegrationConnection","getIntegrationConnectionById","resolveUniqueConnectionSlug","upsertIntegrationConnection","db","publishIntegrationEventReceived","recordDeliveryOnly","retryConnectionSlugCollision","slugifyConnectionSlug","LINEAR_SECRETS_NAMESPACE_PREFIX","loadLinearModuleParts","options","createLinearTokenStore","createLinearE2eRoutes","createLinearIntegrationProvider","linearConfig","deleteLinearInstallationByConnectionId","disconnectLinearInstallation","disconnectLinearInstallationRecords","getLinearInstallationByOrganizationId","linearDb","linearSecretsNamespace","migrationsPath","linearMigrationsPath","upsertLinearInstallation","providerCapabilities","getExistingLinearConnection","input","installation","organizationId","undefined","connection","connectionId","connectLinearInstallation","transaction","tx","baseSlug","organizationUrlKey","fallback","slug","workspaceId","provider","externalAccountId","displayName","lifecycleStatus","capabilities","id","appUserId","scopes","tokenExpiresAt","status","getConnection","deleteSecrets","params","secrets","linear","namespace","linearNamespaceSuffix","Promise","resolve","fn","deleteConnection","fallbackSecrets","getSecret","setSecrets","reject","Error","tokenStore","resolveConnection","integrationProvider","agentTools","endpoint","LINEAR_MCP_ENDPOINT","cleanup","deleteConnectionRecords","deleteConnectionSecrets","routes","coreDb","requireActiveWorkspaceMembership","adapters","webhookProcessors","e2eRoutes","connectionCapabilities","database","databaseNamespace","linearProviderModule","enabled","INTEGRATIONS_ENABLE_LINEAR_PROVIDER","load","startsWith","slice","length"],"mappings":"AAKA,SAAQA,MAAM,QAAO,aAAa;AAElC,SAAQC,kCAAkC,QAAO,8BAA8B;AAC/E,SACEC,2BAA2B,EAC3BC,4BAA4B,EAC5BC,2BAA2B,EAC3BC,2BAA2B,QACtB,qBAAqB;AAC5B,SAAQC,EAAE,QAAO,YAAY;AAC7B,SAAQC,+BAA+B,EAAEC,kBAAkB,QAAO,4BAA4B;AAC9F,SAAQC,4BAA4B,EAAEC,qBAAqB,QAAO,gCAAgC;AAGlG,MAAMC,kCAAkC;AAKxC,eAAeC,sBACbC,UAA4D,CAAC,CAAC;IAE9D,MAAM,EACJC,sBAAsB,EACtBC,qBAAqB,EACrBC,+BAA+B,EAC/BhB,QAAQiB,YAAY,EACpBC,sCAAsC,EACtCC,8BAA8BC,mCAAmC,EACjEC,qCAAqC,EACrCf,IAAIgB,QAAQ,EACZC,sBAAsB,EACtBC,gBAAgBC,oBAAoB,EACpCC,wBAAwB,EACzB,GAAG,MAAM,MAAM,CAAC;IACjB,IAAIC,uBAAgD,EAAE;IAEtD,eAAeC,4BAA4BC,KAE1C;QACC,MAAMC,eAAe,MAAMT,sCAAsCQ,MAAME,cAAc;QACrF,IAAI,CAACD,cAAc,OAAOE;QAC1B,MAAMC,aAAa,MAAM9B,6BAA6B2B,aAAaI,YAAY;QAC/E,IAAI,CAACD,YAAY,OAAOD;QACxB,OAAOC;IACT;IAEA,eAAeE,0BACbN,KAAqC;QAErC,OAAO,MAAMpB,6BAA6B,IACxCH,KAAK8B,WAAW,CAAC,OAAOC;gBACtB,MAAMC,WAAW5B,sBAAsB,CAAC,OAAO,EAAEmB,MAAMU,kBAAkB,EAAE,EAAE;oBAC3EC,UAAU;gBACZ;gBACA,MAAMC,OAAO,MAAMrC,4BACjB;oBACEsC,aAAab,MAAMa,WAAW;oBAC9BC,UAAU;oBACVC,mBAAmBf,MAAME,cAAc;oBACvCO;gBACF,GACA;oBAACD;gBAAE;gBAEL,MAAMJ,aAAa,MAAM5B,4BACvB;oBACEqC,aAAab,MAAMa,WAAW;oBAC9BC,UAAU;oBACVC,mBAAmBf,MAAME,cAAc;oBACvCU;oBACAI,aAAahB,MAAMgB,WAAW;oBAC9BC,iBAAiB;oBACjBC,cAAcpB;gBAChB,GACA;oBAACU;gBAAE;gBAGL,MAAMX,yBACJ;oBACEQ,cAAcD,WAAWe,EAAE;oBAC3BjB,gBAAgBF,MAAME,cAAc;oBACpCQ,oBAAoBV,MAAMU,kBAAkB;oBAC5CU,WAAWpB,MAAMoB,SAAS;oBAC1BC,QAAQrB,MAAMqB,MAAM;oBACpBC,gBAAgBtB,MAAMsB,cAAc;oBACpCC,QAAQ;gBACV,GACA;oBAACf;gBAAE;gBAGL,OAAOJ;YACT;IAEJ;IAEA,eAAed,6BAA6BU,KAA6B;QACvE,MAAMT,oCAAmD;YACvDc,cAAcL,MAAMK,YAAY;YAChCmB,eAAelD;YACfmD,eAAe,CAACC,SACd1C,QAAQ2C,OAAO,EAAEC,QAAQH,cAAc;oBACrC,GAAGC,MAAM;oBACTG,WAAWC,sBAAsBJ,OAAOG,SAAS;gBACnD,MAAME,QAAQC,OAAO,CAAC;YACxBzB,aAAa,CAAC0B,KAAOxD,KAAK8B,WAAW,CAAC,CAACC,KAAOyB,GAAGzB;YACjD0B,kBAAkB,CAACR,QAAQ1C,UACzBX,4BAA4B;oBAAC8C,IAAIO,OAAOrB,YAAY;gBAAA,GAAGrB;QAC3D;IACF;IAEA,MAAMmD,kBAAsC;QAC1CC,WAAW,IAAML,QAAQC,OAAO,CAAC;QACjCK,YAAY,IAAMN,QAAQO,MAAM,CAAC,IAAIC,MAAM;IAC7C;IACA,MAAMZ,UAA8B3C,QAAQ2C,OAAO,EAAEC,SACjD;QACEQ,WAAW,CAACV,SACV1C,QAAQ2C,OAAO,EAAEC,QAAQQ,UAAU;gBACjC,GAAGV,MAAM;gBACTG,WAAWC,sBAAsBJ,OAAOG,SAAS;YACnD,MAAME,QAAQC,OAAO,CAAC;QACxBK,YAAY,CAACX,SACX1C,QAAQ2C,OAAO,EAAEC,QAAQS,WAAW;gBAClC,GAAGX,MAAM;gBACTG,WAAWC,sBAAsBJ,OAAOG,SAAS;YACnD,MAAME,QAAQC,OAAO;IACzB,IACAG;IACJ,MAAMK,aAAavD,uBAAuB;QACxCwD,mBAAmB,OAAOpC,eAAiB/B,6BAA6B+B;QACxEsB;IACF;IAEA,MAAMe,sBAAsBvD,gCAAgC;QAC1DwD,YAAY;YAACH;YAAYI,UAAUxD,aAAayD,mBAAmB;QAAA;QACnEC,SAAS;YACPC,yBAAyB,OAAO3C,YAAY,EAACI,EAAE,EAAC;gBAC9C,MAAMnB,uCAAuCe,WAAWe,EAAE,EAAE;oBAACX;gBAAE;YACjE;YACAwC,yBAAyB,OAAO5C;gBAC9B,2FAA2F;gBAC3F,MAAOpB,CAAAA,QAAQ2C,OAAO,EAAEC,QAAQH,cAAc;oBAC5CZ,aAAaT,WAAWS,WAAW;oBACnCgB,WAAWC,sBAAsBpC,uBAAuBU,WAAWe,EAAE;gBACvE,MAAMY,QAAQC,OAAO,EAAC;YACxB;QACF;QACAiB,QAAQ;YACNT;YACAzC;YACAO;YACAhB;YACAZ;YACAC;YACAL;YACA4E,QAAQzE;YACR,GAAIO,QAAQmE,gCAAgC,GACxC;gBAACA,kCAAkCnE,QAAQmE,gCAAgC;YAAA,IAC3E,CAAC,CAAC;QACR;IACF;IACArD,uBAAuB1B,mCAAmCsE,oBAAoBU,QAAQ;IAEtF,OAAO;QACLtC,UAAU4B;QACVW,mBAAmBX,oBAAoBW,iBAAiB;QACxDC,WAAW;YACTpE,sBAAsB;gBACpBsD;gBACAzC;gBACAO;gBACAhB;gBACAiE,wBAAwBzD;YAC1B;SACD;QACD0D,UAAU;YACR/E,IAAIgB;YACJE,gBAAgBC;YAChB6D,mBAAmB;QACrB;IACF;AACF;AAEA,OAAO,MAAMC,uBAAkD;IAC7DvC,IAAI;IACJwC,SAASxF,OAAOyF,mCAAmC;IACnDC,MAAM9E;AACR,EAAE;AAEF,SAAS+C,sBAAsBD,SAAiB;IAC9C,IAAI,CAACA,UAAUiC,UAAU,CAAChF,kCAAkC;QAC1D,MAAM,IAAIyD,MAAM;IAClB;IACA,OAAOV,UAAUkC,KAAK,CAACjF,gCAAgCkF,MAAM;AAC/D"}
@@ -1 +1 @@
1
- {"version":3,"file":"sentry.d.ts","sourceRoot":"","sources":["../../src/providers/sentry.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAyB,yBAAyB,EAAC,MAAM,qBAAqB,CAAC;AA0F3F,eAAO,MAAM,oBAAoB,EAAE,yBAIlC,CAAC"}
1
+ {"version":3,"file":"sentry.d.ts","sourceRoot":"","sources":["../../src/providers/sentry.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAyB,yBAAyB,EAAC,MAAM,qBAAqB,CAAC;AA4F3F,eAAO,MAAM,oBAAoB,EAAE,yBAIlC,CAAC"}
@@ -5,6 +5,7 @@ import { publishIntegrationEventReceived, recordDeliveryOnly } from '#db/webhook
5
5
  import { retryConnectionSlugCollision, slugifyConnectionSlug } from '#providers/connection-slug.js';
6
6
  async function loadSentryModuleParts() {
7
7
  const { createSentryIntegrationProvider, createSentryMaintenanceWorker, getSentryInstallationByInstallationUuid, persistVerifiedUnclaimedInstallation, upsertSentryInstallation, db: sentryDb, migrationsPath: sentryMigrationsPath } = await import('@shipfox/api-integration-sentry');
8
+ const providerCapabilities = [];
8
9
  async function getConnectionById(id) {
9
10
  const connection = await getIntegrationConnectionById(id);
10
11
  if (!connection) return undefined;
@@ -29,7 +30,8 @@ async function loadSentryModuleParts() {
29
30
  externalAccountId: input.installationUuid,
30
31
  slug,
31
32
  displayName: input.displayName,
32
- lifecycleStatus: 'active'
33
+ lifecycleStatus: 'active',
34
+ capabilities: providerCapabilities
33
35
  }, {
34
36
  tx
35
37
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/providers/sentry.ts"],"sourcesContent":["import type {ConnectSentryInstallationInput} from '@shipfox/api-integration-sentry';\nimport type {IntegrationConnection as CoreIntegrationConnection} from '@shipfox/api-integration-spi';\nimport {config} from '#config.js';\nimport {\n getIntegrationConnectionById,\n resolveUniqueConnectionSlug,\n updateIntegrationConnectionLifecycleStatus,\n upsertIntegrationConnection,\n} from '#db/connections.js';\nimport {db} from '#db/db.js';\nimport {publishIntegrationEventReceived, recordDeliveryOnly} from '#db/webhook-deliveries.js';\nimport {retryConnectionSlugCollision, slugifyConnectionSlug} from '#providers/connection-slug.js';\nimport type {IntegrationModuleParts, IntegrationProviderModule} from '#providers/types.js';\n\nasync function loadSentryModuleParts(): Promise<IntegrationModuleParts> {\n const {\n createSentryIntegrationProvider,\n createSentryMaintenanceWorker,\n getSentryInstallationByInstallationUuid,\n persistVerifiedUnclaimedInstallation,\n upsertSentryInstallation,\n db: sentryDb,\n migrationsPath: sentryMigrationsPath,\n } = await import('@shipfox/api-integration-sentry');\n\n async function getConnectionById(\n id: string,\n ): Promise<CoreIntegrationConnection<'sentry'> | undefined> {\n const connection = await getIntegrationConnectionById(id);\n if (!connection) return undefined;\n return connection as CoreIntegrationConnection<'sentry'>;\n }\n\n async function connectSentryInstallation(\n input: ConnectSentryInstallationInput,\n ): Promise<CoreIntegrationConnection<'sentry'>> {\n return await retryConnectionSlugCollision(() =>\n db().transaction(async (tx) => {\n const baseSlug = slugifyConnectionSlug(`sentry_${input.orgSlug}`, {fallback: 'sentry'});\n const slug = await resolveUniqueConnectionSlug(\n {\n workspaceId: input.workspaceId,\n provider: 'sentry',\n externalAccountId: input.installationUuid,\n baseSlug,\n },\n {tx},\n );\n const connection = await upsertIntegrationConnection(\n {\n workspaceId: input.workspaceId,\n provider: 'sentry',\n externalAccountId: input.installationUuid,\n slug,\n displayName: input.displayName,\n lifecycleStatus: 'active',\n },\n {tx},\n );\n\n await upsertSentryInstallation(\n {\n connectionId: connection.id,\n installationUuid: input.installationUuid,\n orgSlug: input.orgSlug,\n status: 'installed',\n codeHash: input.codeHash,\n installerUserId: input.installerUserId,\n },\n {tx},\n );\n\n return connection as CoreIntegrationConnection<'sentry'>;\n }),\n );\n }\n\n const integrationProvider = createSentryIntegrationProvider({\n getSentryInstallation: ({installationUuid}) =>\n getSentryInstallationByInstallationUuid(installationUuid),\n getConnectionById,\n connectSentryInstallation,\n persistVerifiedUnclaimedInstallation,\n coreDb: db,\n publishIntegrationEventReceived,\n recordDeliveryOnly,\n getIntegrationConnectionById,\n updateConnectionLifecycleStatus: updateIntegrationConnectionLifecycleStatus,\n });\n\n return {\n provider: integrationProvider,\n webhookProcessors: integrationProvider.webhookProcessors,\n database: {\n db: sentryDb,\n migrationsPath: sentryMigrationsPath,\n databaseNamespace: 'integrations_sentry',\n },\n workers: [createSentryMaintenanceWorker()],\n };\n}\n\nexport const sentryProviderModule: IntegrationProviderModule = {\n id: 'sentry',\n enabled: config.INTEGRATIONS_ENABLE_SENTRY_PROVIDER,\n load: loadSentryModuleParts,\n};\n"],"names":["config","getIntegrationConnectionById","resolveUniqueConnectionSlug","updateIntegrationConnectionLifecycleStatus","upsertIntegrationConnection","db","publishIntegrationEventReceived","recordDeliveryOnly","retryConnectionSlugCollision","slugifyConnectionSlug","loadSentryModuleParts","createSentryIntegrationProvider","createSentryMaintenanceWorker","getSentryInstallationByInstallationUuid","persistVerifiedUnclaimedInstallation","upsertSentryInstallation","sentryDb","migrationsPath","sentryMigrationsPath","getConnectionById","id","connection","undefined","connectSentryInstallation","input","transaction","tx","baseSlug","orgSlug","fallback","slug","workspaceId","provider","externalAccountId","installationUuid","displayName","lifecycleStatus","connectionId","status","codeHash","installerUserId","integrationProvider","getSentryInstallation","coreDb","updateConnectionLifecycleStatus","webhookProcessors","database","databaseNamespace","workers","sentryProviderModule","enabled","INTEGRATIONS_ENABLE_SENTRY_PROVIDER","load"],"mappings":"AAEA,SAAQA,MAAM,QAAO,aAAa;AAClC,SACEC,4BAA4B,EAC5BC,2BAA2B,EAC3BC,0CAA0C,EAC1CC,2BAA2B,QACtB,qBAAqB;AAC5B,SAAQC,EAAE,QAAO,YAAY;AAC7B,SAAQC,+BAA+B,EAAEC,kBAAkB,QAAO,4BAA4B;AAC9F,SAAQC,4BAA4B,EAAEC,qBAAqB,QAAO,gCAAgC;AAGlG,eAAeC;IACb,MAAM,EACJC,+BAA+B,EAC/BC,6BAA6B,EAC7BC,uCAAuC,EACvCC,oCAAoC,EACpCC,wBAAwB,EACxBV,IAAIW,QAAQ,EACZC,gBAAgBC,oBAAoB,EACrC,GAAG,MAAM,MAAM,CAAC;IAEjB,eAAeC,kBACbC,EAAU;QAEV,MAAMC,aAAa,MAAMpB,6BAA6BmB;QACtD,IAAI,CAACC,YAAY,OAAOC;QACxB,OAAOD;IACT;IAEA,eAAeE,0BACbC,KAAqC;QAErC,OAAO,MAAMhB,6BAA6B,IACxCH,KAAKoB,WAAW,CAAC,OAAOC;gBACtB,MAAMC,WAAWlB,sBAAsB,CAAC,OAAO,EAAEe,MAAMI,OAAO,EAAE,EAAE;oBAACC,UAAU;gBAAQ;gBACrF,MAAMC,OAAO,MAAM5B,4BACjB;oBACE6B,aAAaP,MAAMO,WAAW;oBAC9BC,UAAU;oBACVC,mBAAmBT,MAAMU,gBAAgB;oBACzCP;gBACF,GACA;oBAACD;gBAAE;gBAEL,MAAML,aAAa,MAAMjB,4BACvB;oBACE2B,aAAaP,MAAMO,WAAW;oBAC9BC,UAAU;oBACVC,mBAAmBT,MAAMU,gBAAgB;oBACzCJ;oBACAK,aAAaX,MAAMW,WAAW;oBAC9BC,iBAAiB;gBACnB,GACA;oBAACV;gBAAE;gBAGL,MAAMX,yBACJ;oBACEsB,cAAchB,WAAWD,EAAE;oBAC3Bc,kBAAkBV,MAAMU,gBAAgB;oBACxCN,SAASJ,MAAMI,OAAO;oBACtBU,QAAQ;oBACRC,UAAUf,MAAMe,QAAQ;oBACxBC,iBAAiBhB,MAAMgB,eAAe;gBACxC,GACA;oBAACd;gBAAE;gBAGL,OAAOL;YACT;IAEJ;IAEA,MAAMoB,sBAAsB9B,gCAAgC;QAC1D+B,uBAAuB,CAAC,EAACR,gBAAgB,EAAC,GACxCrB,wCAAwCqB;QAC1Cf;QACAI;QACAT;QACA6B,QAAQtC;QACRC;QACAC;QACAN;QACA2C,iCAAiCzC;IACnC;IAEA,OAAO;QACL6B,UAAUS;QACVI,mBAAmBJ,oBAAoBI,iBAAiB;QACxDC,UAAU;YACRzC,IAAIW;YACJC,gBAAgBC;YAChB6B,mBAAmB;QACrB;QACAC,SAAS;YAACpC;SAAgC;IAC5C;AACF;AAEA,OAAO,MAAMqC,uBAAkD;IAC7D7B,IAAI;IACJ8B,SAASlD,OAAOmD,mCAAmC;IACnDC,MAAM1C;AACR,EAAE"}
1
+ {"version":3,"sources":["../../src/providers/sentry.ts"],"sourcesContent":["import type {ConnectSentryInstallationInput} from '@shipfox/api-integration-sentry';\nimport type {IntegrationConnection as CoreIntegrationConnection} from '@shipfox/api-integration-spi';\nimport {config} from '#config.js';\nimport type {IntegrationCapability} from '#core/entities/provider.js';\nimport {\n getIntegrationConnectionById,\n resolveUniqueConnectionSlug,\n updateIntegrationConnectionLifecycleStatus,\n upsertIntegrationConnection,\n} from '#db/connections.js';\nimport {db} from '#db/db.js';\nimport {publishIntegrationEventReceived, recordDeliveryOnly} from '#db/webhook-deliveries.js';\nimport {retryConnectionSlugCollision, slugifyConnectionSlug} from '#providers/connection-slug.js';\nimport type {IntegrationModuleParts, IntegrationProviderModule} from '#providers/types.js';\n\nasync function loadSentryModuleParts(): Promise<IntegrationModuleParts> {\n const {\n createSentryIntegrationProvider,\n createSentryMaintenanceWorker,\n getSentryInstallationByInstallationUuid,\n persistVerifiedUnclaimedInstallation,\n upsertSentryInstallation,\n db: sentryDb,\n migrationsPath: sentryMigrationsPath,\n } = await import('@shipfox/api-integration-sentry');\n const providerCapabilities: IntegrationCapability[] = [];\n\n async function getConnectionById(\n id: string,\n ): Promise<CoreIntegrationConnection<'sentry'> | undefined> {\n const connection = await getIntegrationConnectionById(id);\n if (!connection) return undefined;\n return connection as CoreIntegrationConnection<'sentry'>;\n }\n\n async function connectSentryInstallation(\n input: ConnectSentryInstallationInput,\n ): Promise<CoreIntegrationConnection<'sentry'>> {\n return await retryConnectionSlugCollision(() =>\n db().transaction(async (tx) => {\n const baseSlug = slugifyConnectionSlug(`sentry_${input.orgSlug}`, {fallback: 'sentry'});\n const slug = await resolveUniqueConnectionSlug(\n {\n workspaceId: input.workspaceId,\n provider: 'sentry',\n externalAccountId: input.installationUuid,\n baseSlug,\n },\n {tx},\n );\n const connection = await upsertIntegrationConnection(\n {\n workspaceId: input.workspaceId,\n provider: 'sentry',\n externalAccountId: input.installationUuid,\n slug,\n displayName: input.displayName,\n lifecycleStatus: 'active',\n capabilities: providerCapabilities,\n },\n {tx},\n );\n\n await upsertSentryInstallation(\n {\n connectionId: connection.id,\n installationUuid: input.installationUuid,\n orgSlug: input.orgSlug,\n status: 'installed',\n codeHash: input.codeHash,\n installerUserId: input.installerUserId,\n },\n {tx},\n );\n\n return connection as CoreIntegrationConnection<'sentry'>;\n }),\n );\n }\n\n const integrationProvider = createSentryIntegrationProvider({\n getSentryInstallation: ({installationUuid}) =>\n getSentryInstallationByInstallationUuid(installationUuid),\n getConnectionById,\n connectSentryInstallation,\n persistVerifiedUnclaimedInstallation,\n coreDb: db,\n publishIntegrationEventReceived,\n recordDeliveryOnly,\n getIntegrationConnectionById,\n updateConnectionLifecycleStatus: updateIntegrationConnectionLifecycleStatus,\n });\n\n return {\n provider: integrationProvider,\n webhookProcessors: integrationProvider.webhookProcessors,\n database: {\n db: sentryDb,\n migrationsPath: sentryMigrationsPath,\n databaseNamespace: 'integrations_sentry',\n },\n workers: [createSentryMaintenanceWorker()],\n };\n}\n\nexport const sentryProviderModule: IntegrationProviderModule = {\n id: 'sentry',\n enabled: config.INTEGRATIONS_ENABLE_SENTRY_PROVIDER,\n load: loadSentryModuleParts,\n};\n"],"names":["config","getIntegrationConnectionById","resolveUniqueConnectionSlug","updateIntegrationConnectionLifecycleStatus","upsertIntegrationConnection","db","publishIntegrationEventReceived","recordDeliveryOnly","retryConnectionSlugCollision","slugifyConnectionSlug","loadSentryModuleParts","createSentryIntegrationProvider","createSentryMaintenanceWorker","getSentryInstallationByInstallationUuid","persistVerifiedUnclaimedInstallation","upsertSentryInstallation","sentryDb","migrationsPath","sentryMigrationsPath","providerCapabilities","getConnectionById","id","connection","undefined","connectSentryInstallation","input","transaction","tx","baseSlug","orgSlug","fallback","slug","workspaceId","provider","externalAccountId","installationUuid","displayName","lifecycleStatus","capabilities","connectionId","status","codeHash","installerUserId","integrationProvider","getSentryInstallation","coreDb","updateConnectionLifecycleStatus","webhookProcessors","database","databaseNamespace","workers","sentryProviderModule","enabled","INTEGRATIONS_ENABLE_SENTRY_PROVIDER","load"],"mappings":"AAEA,SAAQA,MAAM,QAAO,aAAa;AAElC,SACEC,4BAA4B,EAC5BC,2BAA2B,EAC3BC,0CAA0C,EAC1CC,2BAA2B,QACtB,qBAAqB;AAC5B,SAAQC,EAAE,QAAO,YAAY;AAC7B,SAAQC,+BAA+B,EAAEC,kBAAkB,QAAO,4BAA4B;AAC9F,SAAQC,4BAA4B,EAAEC,qBAAqB,QAAO,gCAAgC;AAGlG,eAAeC;IACb,MAAM,EACJC,+BAA+B,EAC/BC,6BAA6B,EAC7BC,uCAAuC,EACvCC,oCAAoC,EACpCC,wBAAwB,EACxBV,IAAIW,QAAQ,EACZC,gBAAgBC,oBAAoB,EACrC,GAAG,MAAM,MAAM,CAAC;IACjB,MAAMC,uBAAgD,EAAE;IAExD,eAAeC,kBACbC,EAAU;QAEV,MAAMC,aAAa,MAAMrB,6BAA6BoB;QACtD,IAAI,CAACC,YAAY,OAAOC;QACxB,OAAOD;IACT;IAEA,eAAeE,0BACbC,KAAqC;QAErC,OAAO,MAAMjB,6BAA6B,IACxCH,KAAKqB,WAAW,CAAC,OAAOC;gBACtB,MAAMC,WAAWnB,sBAAsB,CAAC,OAAO,EAAEgB,MAAMI,OAAO,EAAE,EAAE;oBAACC,UAAU;gBAAQ;gBACrF,MAAMC,OAAO,MAAM7B,4BACjB;oBACE8B,aAAaP,MAAMO,WAAW;oBAC9BC,UAAU;oBACVC,mBAAmBT,MAAMU,gBAAgB;oBACzCP;gBACF,GACA;oBAACD;gBAAE;gBAEL,MAAML,aAAa,MAAMlB,4BACvB;oBACE4B,aAAaP,MAAMO,WAAW;oBAC9BC,UAAU;oBACVC,mBAAmBT,MAAMU,gBAAgB;oBACzCJ;oBACAK,aAAaX,MAAMW,WAAW;oBAC9BC,iBAAiB;oBACjBC,cAAcnB;gBAChB,GACA;oBAACQ;gBAAE;gBAGL,MAAMZ,yBACJ;oBACEwB,cAAcjB,WAAWD,EAAE;oBAC3Bc,kBAAkBV,MAAMU,gBAAgB;oBACxCN,SAASJ,MAAMI,OAAO;oBACtBW,QAAQ;oBACRC,UAAUhB,MAAMgB,QAAQ;oBACxBC,iBAAiBjB,MAAMiB,eAAe;gBACxC,GACA;oBAACf;gBAAE;gBAGL,OAAOL;YACT;IAEJ;IAEA,MAAMqB,sBAAsBhC,gCAAgC;QAC1DiC,uBAAuB,CAAC,EAACT,gBAAgB,EAAC,GACxCtB,wCAAwCsB;QAC1Cf;QACAI;QACAV;QACA+B,QAAQxC;QACRC;QACAC;QACAN;QACA6C,iCAAiC3C;IACnC;IAEA,OAAO;QACL8B,UAAUU;QACVI,mBAAmBJ,oBAAoBI,iBAAiB;QACxDC,UAAU;YACR3C,IAAIW;YACJC,gBAAgBC;YAChB+B,mBAAmB;QACrB;QACAC,SAAS;YAACtC;SAAgC;IAC5C;AACF;AAEA,OAAO,MAAMuC,uBAAkD;IAC7D9B,IAAI;IACJ+B,SAASpD,OAAOqD,mCAAmC;IACnDC,MAAM5C;AACR,EAAE"}
@@ -1 +1 @@
1
- {"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../src/providers/slack.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAyB,yBAAyB,EAAC,MAAM,qBAAqB,CAAC;AAuK3F,eAAO,MAAM,mBAAmB,EAAE,yBAIjC,CAAC"}
1
+ {"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../src/providers/slack.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAyB,yBAAyB,EAAC,MAAM,qBAAqB,CAAC;AA0K3F,eAAO,MAAM,mBAAmB,EAAE,yBAIjC,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { config } from '#config.js';
2
+ import { getIntegrationProviderCapabilities } from '#core/providers/registry.js';
2
3
  import { deleteIntegrationConnection, getIntegrationConnectionById, resolveUniqueConnectionSlug, upsertIntegrationConnection } from '#db/connections.js';
3
4
  import { db } from '#db/db.js';
4
5
  import { claimWebhookDelivery, publishIntegrationEventReceived, recordDeliveryOnly } from '#db/webhook-deliveries.js';
@@ -6,6 +7,7 @@ import { retryConnectionSlugCollision, slugifyConnectionSlug } from '#providers/
6
7
  const SLACK_SECRETS_NAMESPACE_PREFIX = 'system/integrations/slack/';
7
8
  async function loadSlackModuleParts(options = {}) {
8
9
  const { createSlackE2eRoutes, createSlackIntegrationProvider, createSlackTokenStore, db: slackDb, deleteSlackInstallationByConnectionId, disconnectSlackInstallation: disconnectSlackInstallationRecords, getSlackInstallationByTeamId, migrationsPath: slackMigrationsPath, slackSecretsNamespace, upsertSlackInstallation } = await import('@shipfox/api-integration-slack');
10
+ let providerCapabilities = [];
9
11
  async function getExistingSlackConnection(input) {
10
12
  const installation = await getSlackInstallationByTeamId(input.teamId);
11
13
  if (!installation) return undefined;
@@ -32,7 +34,8 @@ async function loadSlackModuleParts(options = {}) {
32
34
  externalAccountId: input.teamId,
33
35
  slug,
34
36
  displayName: input.displayName,
35
- lifecycleStatus: 'active'
37
+ lifecycleStatus: 'active',
38
+ capabilities: providerCapabilities
36
39
  }, {
37
40
  tx
38
41
  });
@@ -116,6 +119,7 @@ async function loadSlackModuleParts(options = {}) {
116
119
  } : {}
117
120
  }
118
121
  });
122
+ providerCapabilities = getIntegrationProviderCapabilities(integrationProvider.adapters);
119
123
  return {
120
124
  provider: integrationProvider,
121
125
  webhookProcessors: integrationProvider.webhookProcessors,
@@ -125,9 +129,7 @@ async function loadSlackModuleParts(options = {}) {
125
129
  getExistingSlackConnection,
126
130
  connectSlackInstallation,
127
131
  disconnectSlackInstallation,
128
- connectionCapabilities: [
129
- 'agent_tools'
130
- ]
132
+ connectionCapabilities: providerCapabilities
131
133
  })
132
134
  ],
133
135
  database: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/providers/slack.ts"],"sourcesContent":["import type {\n ConnectSlackInstallationInput,\n SlackSecretsStore,\n} from '@shipfox/api-integration-slack';\nimport type {IntegrationConnection as CoreIntegrationConnection} from '@shipfox/api-integration-spi';\nimport {config} from '#config.js';\nimport {\n deleteIntegrationConnection,\n getIntegrationConnectionById,\n resolveUniqueConnectionSlug,\n upsertIntegrationConnection,\n} from '#db/connections.js';\nimport {db} from '#db/db.js';\nimport {\n claimWebhookDelivery,\n publishIntegrationEventReceived,\n recordDeliveryOnly,\n} from '#db/webhook-deliveries.js';\nimport {retryConnectionSlugCollision, slugifyConnectionSlug} from '#providers/connection-slug.js';\nimport type {IntegrationModuleParts, IntegrationProviderModule} from '#providers/types.js';\n\nconst SLACK_SECRETS_NAMESPACE_PREFIX = 'system/integrations/slack/';\n\ntype IntegrationDb = ReturnType<typeof db>;\ntype IntegrationTx = Parameters<Parameters<IntegrationDb['transaction']>[0]>[0];\n\nasync function loadSlackModuleParts(\n options: Parameters<IntegrationProviderModule['load']>[0] = {},\n): Promise<IntegrationModuleParts> {\n const {\n createSlackE2eRoutes,\n createSlackIntegrationProvider,\n createSlackTokenStore,\n db: slackDb,\n deleteSlackInstallationByConnectionId,\n disconnectSlackInstallation: disconnectSlackInstallationRecords,\n getSlackInstallationByTeamId,\n migrationsPath: slackMigrationsPath,\n slackSecretsNamespace,\n upsertSlackInstallation,\n } = await import('@shipfox/api-integration-slack');\n\n async function getExistingSlackConnection(input: {\n teamId: string;\n }): Promise<CoreIntegrationConnection<'slack'> | undefined> {\n const installation = await getSlackInstallationByTeamId(input.teamId);\n if (!installation) return undefined;\n const connection = await getIntegrationConnectionById(installation.connectionId);\n if (!connection) return undefined;\n return connection as CoreIntegrationConnection<'slack'>;\n }\n\n async function connectSlackInstallation(\n input: ConnectSlackInstallationInput,\n ): Promise<CoreIntegrationConnection<'slack'>> {\n return await retryConnectionSlugCollision(() =>\n db().transaction(async (tx) => {\n const baseSlug = slugifyConnectionSlug(`slack_${input.teamName || input.teamId}`, {\n fallback: 'slack',\n });\n const slug = await resolveUniqueConnectionSlug(\n {\n workspaceId: input.workspaceId,\n provider: 'slack',\n externalAccountId: input.teamId,\n baseSlug,\n },\n {tx},\n );\n const connection = await upsertIntegrationConnection(\n {\n workspaceId: input.workspaceId,\n provider: 'slack',\n externalAccountId: input.teamId,\n slug,\n displayName: input.displayName,\n lifecycleStatus: 'active',\n },\n {tx},\n );\n await upsertSlackInstallation(\n {\n connectionId: connection.id,\n teamId: input.teamId,\n teamName: input.teamName,\n appId: input.appId,\n botUserId: input.botUserId,\n scopes: input.scopes,\n tokenExpiresAt: input.tokenExpiresAt,\n status: 'installed',\n },\n {tx},\n );\n return connection as CoreIntegrationConnection<'slack'>;\n }),\n );\n }\n\n async function disconnectSlackInstallation(input: {connectionId: string}): Promise<void> {\n await disconnectSlackInstallationRecords<IntegrationTx>({\n connectionId: input.connectionId,\n getConnection: getIntegrationConnectionById,\n deleteSecrets: (params) =>\n options.secrets?.slack?.deleteSecrets({\n ...params,\n namespace: slackNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(0),\n transaction: (fn) => db().transaction((tx) => fn(tx)),\n deleteConnection: (params, options) =>\n deleteIntegrationConnection({id: params.connectionId}, options),\n });\n }\n\n const fallbackSecrets: SlackSecretsStore = {\n getSecret: () => Promise.resolve(null),\n setSecrets: () => Promise.reject(new Error('Slack token storage is not configured')),\n };\n const secrets: SlackSecretsStore = options.secrets?.slack\n ? {\n getSecret: (params: Parameters<SlackSecretsStore['getSecret']>[0]) =>\n options.secrets?.slack?.getSecret({\n ...params,\n namespace: slackNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(null),\n setSecrets: (params: Parameters<SlackSecretsStore['setSecrets']>[0]) =>\n options.secrets?.slack?.setSecrets({\n ...params,\n namespace: slackNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(),\n }\n : fallbackSecrets;\n const tokenStore = createSlackTokenStore({\n resolveConnection: async (connectionId) => getIntegrationConnectionById(connectionId),\n secrets,\n });\n\n const integrationProvider = createSlackIntegrationProvider({\n agentTools: {tokenStore},\n cleanup: {\n deleteConnectionRecords: async (connection, {tx}) => {\n await deleteSlackInstallationByConnectionId(connection.id, {tx});\n },\n deleteConnectionSecrets: async (connection) => {\n // Scoped secrets accept the provider-local suffix, after this helper validates its prefix.\n await (options.secrets?.slack?.deleteSecrets({\n workspaceId: connection.workspaceId,\n namespace: slackNamespaceSuffix(slackSecretsNamespace(connection.id)),\n }) ?? Promise.resolve());\n },\n },\n routes: {\n tokenStore,\n getExistingSlackConnection,\n connectSlackInstallation,\n disconnectSlackInstallation,\n coreDb: db,\n claimWebhookDelivery,\n publishIntegrationEventReceived,\n recordDeliveryOnly,\n getIntegrationConnectionById,\n ...(options.requireActiveWorkspaceMembership\n ? {requireActiveWorkspaceMembership: options.requireActiveWorkspaceMembership}\n : {}),\n },\n });\n\n return {\n provider: integrationProvider,\n webhookProcessors: integrationProvider.webhookProcessors,\n e2eRoutes: [\n createSlackE2eRoutes({\n tokenStore,\n getExistingSlackConnection,\n connectSlackInstallation,\n disconnectSlackInstallation,\n connectionCapabilities: ['agent_tools'],\n }),\n ],\n database: {\n db: slackDb,\n migrationsPath: slackMigrationsPath,\n databaseNamespace: 'integrations_slack',\n },\n };\n}\n\nexport const slackProviderModule: IntegrationProviderModule = {\n id: 'slack',\n enabled: config.INTEGRATIONS_ENABLE_SLACK_PROVIDER,\n load: loadSlackModuleParts,\n};\n\nfunction slackNamespaceSuffix(namespace: string): string {\n if (!namespace.startsWith(SLACK_SECRETS_NAMESPACE_PREFIX)) {\n throw new Error('Slack provider attempted to access an unscoped secret namespace');\n }\n return namespace.slice(SLACK_SECRETS_NAMESPACE_PREFIX.length);\n}\n"],"names":["config","deleteIntegrationConnection","getIntegrationConnectionById","resolveUniqueConnectionSlug","upsertIntegrationConnection","db","claimWebhookDelivery","publishIntegrationEventReceived","recordDeliveryOnly","retryConnectionSlugCollision","slugifyConnectionSlug","SLACK_SECRETS_NAMESPACE_PREFIX","loadSlackModuleParts","options","createSlackE2eRoutes","createSlackIntegrationProvider","createSlackTokenStore","slackDb","deleteSlackInstallationByConnectionId","disconnectSlackInstallation","disconnectSlackInstallationRecords","getSlackInstallationByTeamId","migrationsPath","slackMigrationsPath","slackSecretsNamespace","upsertSlackInstallation","getExistingSlackConnection","input","installation","teamId","undefined","connection","connectionId","connectSlackInstallation","transaction","tx","baseSlug","teamName","fallback","slug","workspaceId","provider","externalAccountId","displayName","lifecycleStatus","id","appId","botUserId","scopes","tokenExpiresAt","status","getConnection","deleteSecrets","params","secrets","slack","namespace","slackNamespaceSuffix","Promise","resolve","fn","deleteConnection","fallbackSecrets","getSecret","setSecrets","reject","Error","tokenStore","resolveConnection","integrationProvider","agentTools","cleanup","deleteConnectionRecords","deleteConnectionSecrets","routes","coreDb","requireActiveWorkspaceMembership","webhookProcessors","e2eRoutes","connectionCapabilities","database","databaseNamespace","slackProviderModule","enabled","INTEGRATIONS_ENABLE_SLACK_PROVIDER","load","startsWith","slice","length"],"mappings":"AAKA,SAAQA,MAAM,QAAO,aAAa;AAClC,SACEC,2BAA2B,EAC3BC,4BAA4B,EAC5BC,2BAA2B,EAC3BC,2BAA2B,QACtB,qBAAqB;AAC5B,SAAQC,EAAE,QAAO,YAAY;AAC7B,SACEC,oBAAoB,EACpBC,+BAA+B,EAC/BC,kBAAkB,QACb,4BAA4B;AACnC,SAAQC,4BAA4B,EAAEC,qBAAqB,QAAO,gCAAgC;AAGlG,MAAMC,iCAAiC;AAKvC,eAAeC,qBACbC,UAA4D,CAAC,CAAC;IAE9D,MAAM,EACJC,oBAAoB,EACpBC,8BAA8B,EAC9BC,qBAAqB,EACrBX,IAAIY,OAAO,EACXC,qCAAqC,EACrCC,6BAA6BC,kCAAkC,EAC/DC,4BAA4B,EAC5BC,gBAAgBC,mBAAmB,EACnCC,qBAAqB,EACrBC,uBAAuB,EACxB,GAAG,MAAM,MAAM,CAAC;IAEjB,eAAeC,2BAA2BC,KAEzC;QACC,MAAMC,eAAe,MAAMP,6BAA6BM,MAAME,MAAM;QACpE,IAAI,CAACD,cAAc,OAAOE;QAC1B,MAAMC,aAAa,MAAM7B,6BAA6B0B,aAAaI,YAAY;QAC/E,IAAI,CAACD,YAAY,OAAOD;QACxB,OAAOC;IACT;IAEA,eAAeE,yBACbN,KAAoC;QAEpC,OAAO,MAAMlB,6BAA6B,IACxCJ,KAAK6B,WAAW,CAAC,OAAOC;gBACtB,MAAMC,WAAW1B,sBAAsB,CAAC,MAAM,EAAEiB,MAAMU,QAAQ,IAAIV,MAAME,MAAM,EAAE,EAAE;oBAChFS,UAAU;gBACZ;gBACA,MAAMC,OAAO,MAAMpC,4BACjB;oBACEqC,aAAab,MAAMa,WAAW;oBAC9BC,UAAU;oBACVC,mBAAmBf,MAAME,MAAM;oBAC/BO;gBACF,GACA;oBAACD;gBAAE;gBAEL,MAAMJ,aAAa,MAAM3B,4BACvB;oBACEoC,aAAab,MAAMa,WAAW;oBAC9BC,UAAU;oBACVC,mBAAmBf,MAAME,MAAM;oBAC/BU;oBACAI,aAAahB,MAAMgB,WAAW;oBAC9BC,iBAAiB;gBACnB,GACA;oBAACT;gBAAE;gBAEL,MAAMV,wBACJ;oBACEO,cAAcD,WAAWc,EAAE;oBAC3BhB,QAAQF,MAAME,MAAM;oBACpBQ,UAAUV,MAAMU,QAAQ;oBACxBS,OAAOnB,MAAMmB,KAAK;oBAClBC,WAAWpB,MAAMoB,SAAS;oBAC1BC,QAAQrB,MAAMqB,MAAM;oBACpBC,gBAAgBtB,MAAMsB,cAAc;oBACpCC,QAAQ;gBACV,GACA;oBAACf;gBAAE;gBAEL,OAAOJ;YACT;IAEJ;IAEA,eAAeZ,4BAA4BQ,KAA6B;QACtE,MAAMP,mCAAkD;YACtDY,cAAcL,MAAMK,YAAY;YAChCmB,eAAejD;YACfkD,eAAe,CAACC,SACdxC,QAAQyC,OAAO,EAAEC,OAAOH,cAAc;oBACpC,GAAGC,MAAM;oBACTG,WAAWC,qBAAqBJ,OAAOG,SAAS;gBAClD,MAAME,QAAQC,OAAO,CAAC;YACxBzB,aAAa,CAAC0B,KAAOvD,KAAK6B,WAAW,CAAC,CAACC,KAAOyB,GAAGzB;YACjD0B,kBAAkB,CAACR,QAAQxC,UACzBZ,4BAA4B;oBAAC4C,IAAIQ,OAAOrB,YAAY;gBAAA,GAAGnB;QAC3D;IACF;IAEA,MAAMiD,kBAAqC;QACzCC,WAAW,IAAML,QAAQC,OAAO,CAAC;QACjCK,YAAY,IAAMN,QAAQO,MAAM,CAAC,IAAIC,MAAM;IAC7C;IACA,MAAMZ,UAA6BzC,QAAQyC,OAAO,EAAEC,QAChD;QACEQ,WAAW,CAACV,SACVxC,QAAQyC,OAAO,EAAEC,OAAOQ,UAAU;gBAChC,GAAGV,MAAM;gBACTG,WAAWC,qBAAqBJ,OAAOG,SAAS;YAClD,MAAME,QAAQC,OAAO,CAAC;QACxBK,YAAY,CAACX,SACXxC,QAAQyC,OAAO,EAAEC,OAAOS,WAAW;gBACjC,GAAGX,MAAM;gBACTG,WAAWC,qBAAqBJ,OAAOG,SAAS;YAClD,MAAME,QAAQC,OAAO;IACzB,IACAG;IACJ,MAAMK,aAAanD,sBAAsB;QACvCoD,mBAAmB,OAAOpC,eAAiB9B,6BAA6B8B;QACxEsB;IACF;IAEA,MAAMe,sBAAsBtD,+BAA+B;QACzDuD,YAAY;YAACH;QAAU;QACvBI,SAAS;YACPC,yBAAyB,OAAOzC,YAAY,EAACI,EAAE,EAAC;gBAC9C,MAAMjB,sCAAsCa,WAAWc,EAAE,EAAE;oBAACV;gBAAE;YAChE;YACAsC,yBAAyB,OAAO1C;gBAC9B,2FAA2F;gBAC3F,MAAOlB,CAAAA,QAAQyC,OAAO,EAAEC,OAAOH,cAAc;oBAC3CZ,aAAaT,WAAWS,WAAW;oBACnCgB,WAAWC,qBAAqBjC,sBAAsBO,WAAWc,EAAE;gBACrE,MAAMa,QAAQC,OAAO,EAAC;YACxB;QACF;QACAe,QAAQ;YACNP;YACAzC;YACAO;YACAd;YACAwD,QAAQtE;YACRC;YACAC;YACAC;YACAN;YACA,GAAIW,QAAQ+D,gCAAgC,GACxC;gBAACA,kCAAkC/D,QAAQ+D,gCAAgC;YAAA,IAC3E,CAAC,CAAC;QACR;IACF;IAEA,OAAO;QACLnC,UAAU4B;QACVQ,mBAAmBR,oBAAoBQ,iBAAiB;QACxDC,WAAW;YACThE,qBAAqB;gBACnBqD;gBACAzC;gBACAO;gBACAd;gBACA4D,wBAAwB;oBAAC;iBAAc;YACzC;SACD;QACDC,UAAU;YACR3E,IAAIY;YACJK,gBAAgBC;YAChB0D,mBAAmB;QACrB;IACF;AACF;AAEA,OAAO,MAAMC,sBAAiD;IAC5DrC,IAAI;IACJsC,SAASnF,OAAOoF,kCAAkC;IAClDC,MAAMzE;AACR,EAAE;AAEF,SAAS6C,qBAAqBD,SAAiB;IAC7C,IAAI,CAACA,UAAU8B,UAAU,CAAC3E,iCAAiC;QACzD,MAAM,IAAIuD,MAAM;IAClB;IACA,OAAOV,UAAU+B,KAAK,CAAC5E,+BAA+B6E,MAAM;AAC9D"}
1
+ {"version":3,"sources":["../../src/providers/slack.ts"],"sourcesContent":["import type {\n ConnectSlackInstallationInput,\n SlackSecretsStore,\n} from '@shipfox/api-integration-slack';\nimport type {IntegrationConnection as CoreIntegrationConnection} from '@shipfox/api-integration-spi';\nimport {config} from '#config.js';\nimport type {IntegrationCapability} from '#core/entities/provider.js';\nimport {getIntegrationProviderCapabilities} from '#core/providers/registry.js';\nimport {\n deleteIntegrationConnection,\n getIntegrationConnectionById,\n resolveUniqueConnectionSlug,\n upsertIntegrationConnection,\n} from '#db/connections.js';\nimport {db} from '#db/db.js';\nimport {\n claimWebhookDelivery,\n publishIntegrationEventReceived,\n recordDeliveryOnly,\n} from '#db/webhook-deliveries.js';\nimport {retryConnectionSlugCollision, slugifyConnectionSlug} from '#providers/connection-slug.js';\nimport type {IntegrationModuleParts, IntegrationProviderModule} from '#providers/types.js';\n\nconst SLACK_SECRETS_NAMESPACE_PREFIX = 'system/integrations/slack/';\n\ntype IntegrationDb = ReturnType<typeof db>;\ntype IntegrationTx = Parameters<Parameters<IntegrationDb['transaction']>[0]>[0];\n\nasync function loadSlackModuleParts(\n options: Parameters<IntegrationProviderModule['load']>[0] = {},\n): Promise<IntegrationModuleParts> {\n const {\n createSlackE2eRoutes,\n createSlackIntegrationProvider,\n createSlackTokenStore,\n db: slackDb,\n deleteSlackInstallationByConnectionId,\n disconnectSlackInstallation: disconnectSlackInstallationRecords,\n getSlackInstallationByTeamId,\n migrationsPath: slackMigrationsPath,\n slackSecretsNamespace,\n upsertSlackInstallation,\n } = await import('@shipfox/api-integration-slack');\n let providerCapabilities: IntegrationCapability[] = [];\n\n async function getExistingSlackConnection(input: {\n teamId: string;\n }): Promise<CoreIntegrationConnection<'slack'> | undefined> {\n const installation = await getSlackInstallationByTeamId(input.teamId);\n if (!installation) return undefined;\n const connection = await getIntegrationConnectionById(installation.connectionId);\n if (!connection) return undefined;\n return connection as CoreIntegrationConnection<'slack'>;\n }\n\n async function connectSlackInstallation(\n input: ConnectSlackInstallationInput,\n ): Promise<CoreIntegrationConnection<'slack'>> {\n return await retryConnectionSlugCollision(() =>\n db().transaction(async (tx) => {\n const baseSlug = slugifyConnectionSlug(`slack_${input.teamName || input.teamId}`, {\n fallback: 'slack',\n });\n const slug = await resolveUniqueConnectionSlug(\n {\n workspaceId: input.workspaceId,\n provider: 'slack',\n externalAccountId: input.teamId,\n baseSlug,\n },\n {tx},\n );\n const connection = await upsertIntegrationConnection(\n {\n workspaceId: input.workspaceId,\n provider: 'slack',\n externalAccountId: input.teamId,\n slug,\n displayName: input.displayName,\n lifecycleStatus: 'active',\n capabilities: providerCapabilities,\n },\n {tx},\n );\n await upsertSlackInstallation(\n {\n connectionId: connection.id,\n teamId: input.teamId,\n teamName: input.teamName,\n appId: input.appId,\n botUserId: input.botUserId,\n scopes: input.scopes,\n tokenExpiresAt: input.tokenExpiresAt,\n status: 'installed',\n },\n {tx},\n );\n return connection as CoreIntegrationConnection<'slack'>;\n }),\n );\n }\n\n async function disconnectSlackInstallation(input: {connectionId: string}): Promise<void> {\n await disconnectSlackInstallationRecords<IntegrationTx>({\n connectionId: input.connectionId,\n getConnection: getIntegrationConnectionById,\n deleteSecrets: (params) =>\n options.secrets?.slack?.deleteSecrets({\n ...params,\n namespace: slackNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(0),\n transaction: (fn) => db().transaction((tx) => fn(tx)),\n deleteConnection: (params, options) =>\n deleteIntegrationConnection({id: params.connectionId}, options),\n });\n }\n\n const fallbackSecrets: SlackSecretsStore = {\n getSecret: () => Promise.resolve(null),\n setSecrets: () => Promise.reject(new Error('Slack token storage is not configured')),\n };\n const secrets: SlackSecretsStore = options.secrets?.slack\n ? {\n getSecret: (params: Parameters<SlackSecretsStore['getSecret']>[0]) =>\n options.secrets?.slack?.getSecret({\n ...params,\n namespace: slackNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(null),\n setSecrets: (params: Parameters<SlackSecretsStore['setSecrets']>[0]) =>\n options.secrets?.slack?.setSecrets({\n ...params,\n namespace: slackNamespaceSuffix(params.namespace),\n }) ?? Promise.resolve(),\n }\n : fallbackSecrets;\n const tokenStore = createSlackTokenStore({\n resolveConnection: async (connectionId) => getIntegrationConnectionById(connectionId),\n secrets,\n });\n\n const integrationProvider = createSlackIntegrationProvider({\n agentTools: {tokenStore},\n cleanup: {\n deleteConnectionRecords: async (connection, {tx}) => {\n await deleteSlackInstallationByConnectionId(connection.id, {tx});\n },\n deleteConnectionSecrets: async (connection) => {\n // Scoped secrets accept the provider-local suffix, after this helper validates its prefix.\n await (options.secrets?.slack?.deleteSecrets({\n workspaceId: connection.workspaceId,\n namespace: slackNamespaceSuffix(slackSecretsNamespace(connection.id)),\n }) ?? Promise.resolve());\n },\n },\n routes: {\n tokenStore,\n getExistingSlackConnection,\n connectSlackInstallation,\n disconnectSlackInstallation,\n coreDb: db,\n claimWebhookDelivery,\n publishIntegrationEventReceived,\n recordDeliveryOnly,\n getIntegrationConnectionById,\n ...(options.requireActiveWorkspaceMembership\n ? {requireActiveWorkspaceMembership: options.requireActiveWorkspaceMembership}\n : {}),\n },\n });\n providerCapabilities = getIntegrationProviderCapabilities(integrationProvider.adapters);\n\n return {\n provider: integrationProvider,\n webhookProcessors: integrationProvider.webhookProcessors,\n e2eRoutes: [\n createSlackE2eRoutes({\n tokenStore,\n getExistingSlackConnection,\n connectSlackInstallation,\n disconnectSlackInstallation,\n connectionCapabilities: providerCapabilities,\n }),\n ],\n database: {\n db: slackDb,\n migrationsPath: slackMigrationsPath,\n databaseNamespace: 'integrations_slack',\n },\n };\n}\n\nexport const slackProviderModule: IntegrationProviderModule = {\n id: 'slack',\n enabled: config.INTEGRATIONS_ENABLE_SLACK_PROVIDER,\n load: loadSlackModuleParts,\n};\n\nfunction slackNamespaceSuffix(namespace: string): string {\n if (!namespace.startsWith(SLACK_SECRETS_NAMESPACE_PREFIX)) {\n throw new Error('Slack provider attempted to access an unscoped secret namespace');\n }\n return namespace.slice(SLACK_SECRETS_NAMESPACE_PREFIX.length);\n}\n"],"names":["config","getIntegrationProviderCapabilities","deleteIntegrationConnection","getIntegrationConnectionById","resolveUniqueConnectionSlug","upsertIntegrationConnection","db","claimWebhookDelivery","publishIntegrationEventReceived","recordDeliveryOnly","retryConnectionSlugCollision","slugifyConnectionSlug","SLACK_SECRETS_NAMESPACE_PREFIX","loadSlackModuleParts","options","createSlackE2eRoutes","createSlackIntegrationProvider","createSlackTokenStore","slackDb","deleteSlackInstallationByConnectionId","disconnectSlackInstallation","disconnectSlackInstallationRecords","getSlackInstallationByTeamId","migrationsPath","slackMigrationsPath","slackSecretsNamespace","upsertSlackInstallation","providerCapabilities","getExistingSlackConnection","input","installation","teamId","undefined","connection","connectionId","connectSlackInstallation","transaction","tx","baseSlug","teamName","fallback","slug","workspaceId","provider","externalAccountId","displayName","lifecycleStatus","capabilities","id","appId","botUserId","scopes","tokenExpiresAt","status","getConnection","deleteSecrets","params","secrets","slack","namespace","slackNamespaceSuffix","Promise","resolve","fn","deleteConnection","fallbackSecrets","getSecret","setSecrets","reject","Error","tokenStore","resolveConnection","integrationProvider","agentTools","cleanup","deleteConnectionRecords","deleteConnectionSecrets","routes","coreDb","requireActiveWorkspaceMembership","adapters","webhookProcessors","e2eRoutes","connectionCapabilities","database","databaseNamespace","slackProviderModule","enabled","INTEGRATIONS_ENABLE_SLACK_PROVIDER","load","startsWith","slice","length"],"mappings":"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"}