@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.
- package/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +44 -0
- package/dist/core/event-catalogs.d.ts +18 -0
- package/dist/core/event-catalogs.d.ts.map +1 -0
- package/dist/core/event-catalogs.js +27 -0
- package/dist/core/event-catalogs.js.map +1 -0
- package/dist/core/providers/registry.d.ts +1 -0
- package/dist/core/providers/registry.d.ts.map +1 -1
- package/dist/core/providers/registry.js +4 -1
- package/dist/core/providers/registry.js.map +1 -1
- package/dist/core/providers/source-control.d.ts +1 -1
- package/dist/core/providers/source-control.d.ts.map +1 -1
- package/dist/core/providers/source-control.js.map +1 -1
- package/dist/core/source-control-service.d.ts +5 -1
- package/dist/core/source-control-service.d.ts.map +1 -1
- package/dist/core/source-control-service.js +12 -0
- package/dist/core/source-control-service.js.map +1 -1
- package/dist/db/connections.d.ts +4 -1
- package/dist/db/connections.d.ts.map +1 -1
- package/dist/db/connections.js +15 -6
- package/dist/db/connections.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/metrics/instance.d.ts.map +1 -1
- package/dist/metrics/instance.js +2 -0
- package/dist/metrics/instance.js.map +1 -1
- package/dist/presentation/inter-module.d.ts.map +1 -1
- package/dist/presentation/inter-module.js +27 -5
- package/dist/presentation/inter-module.js.map +1 -1
- package/dist/presentation/routes/errors.d.ts.map +1 -1
- package/dist/presentation/routes/errors.js +1 -1
- package/dist/presentation/routes/errors.js.map +1 -1
- package/dist/presentation/routes/manage-connections.d.ts.map +1 -1
- package/dist/presentation/routes/manage-connections.js +7 -3
- package/dist/presentation/routes/manage-connections.js.map +1 -1
- package/dist/providers/gitea.d.ts.map +1 -1
- package/dist/providers/gitea.js +5 -1
- package/dist/providers/gitea.js.map +1 -1
- package/dist/providers/github.d.ts.map +1 -1
- package/dist/providers/github.js +6 -5
- package/dist/providers/github.js.map +1 -1
- package/dist/providers/jira.d.ts.map +1 -1
- package/dist/providers/jira.js +7 -2
- package/dist/providers/jira.js.map +1 -1
- package/dist/providers/linear.d.ts.map +1 -1
- package/dist/providers/linear.js +6 -4
- package/dist/providers/linear.js.map +1 -1
- package/dist/providers/sentry.d.ts.map +1 -1
- package/dist/providers/sentry.js +3 -1
- package/dist/providers/sentry.js.map +1 -1
- package/dist/providers/slack.d.ts.map +1 -1
- package/dist/providers/slack.js +6 -4
- package/dist/providers/slack.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +12 -12
- package/src/core/agent-tool-selection.test.ts +4 -0
- package/src/core/event-catalogs.test.ts +16 -0
- package/src/core/event-catalogs.ts +34 -0
- package/src/core/providers/registry.test.ts +4 -0
- package/src/core/providers/registry.ts +9 -3
- package/src/core/providers/source-control.ts +2 -0
- package/src/core/secret-cleanup.test.ts +1 -0
- package/src/core/source-control-service.test.ts +48 -0
- package/src/core/source-control-service.ts +16 -0
- package/src/db/connections.test.ts +167 -0
- package/src/db/connections.ts +21 -4
- package/src/db/secret-cleanups.test.ts +1 -0
- package/src/index.ts +2 -0
- package/src/metrics/instance.ts +2 -0
- package/src/presentation/inter-module.test.ts +219 -0
- package/src/presentation/inter-module.ts +28 -3
- package/src/presentation/routes/errors.ts +3 -0
- package/src/presentation/routes/list-connections.test.ts +13 -1
- package/src/presentation/routes/list-repositories.test.ts +9 -0
- package/src/presentation/routes/manage-connections.test.ts +52 -0
- package/src/presentation/routes/manage-connections.ts +6 -2
- package/src/providers/gitea.ts +5 -0
- package/src/providers/github.ts +6 -1
- package/src/providers/jira.test.ts +3 -0
- package/src/providers/jira.ts +6 -0
- package/src/providers/linear.test.ts +3 -0
- package/src/providers/linear.ts +6 -1
- package/src/providers/modules.test.ts +64 -1
- package/src/providers/sentry.ts +3 -0
- package/src/providers/slack.test.ts +4 -0
- package/src/providers/slack.ts +6 -1
- package/test/route-utils.ts +4 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/dist/providers/jira.js
CHANGED
|
@@ -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":"
|
|
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"}
|
package/dist/providers/linear.js
CHANGED
|
@@ -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":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sentry.d.ts","sourceRoot":"","sources":["../../src/providers/sentry.ts"],"names":[],"mappings":"
|
|
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"}
|
package/dist/providers/sentry.js
CHANGED
|
@@ -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;
|
|
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":"
|
|
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"}
|
package/dist/providers/slack.js
CHANGED
|
@@ -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":"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"}
|
|
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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"}
|