@shipfox/api-integration-core 5.0.0 → 7.0.1

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 (73) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/CHANGELOG.md +83 -0
  3. package/dist/core/entities/provider.d.ts +7 -2
  4. package/dist/core/entities/provider.d.ts.map +1 -1
  5. package/dist/core/entities/provider.js.map +1 -1
  6. package/dist/core/errors.d.ts +7 -0
  7. package/dist/core/errors.d.ts.map +1 -1
  8. package/dist/core/errors.js +8 -2
  9. package/dist/core/errors.js.map +1 -1
  10. package/dist/index.d.ts +17 -4
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +50 -5
  13. package/dist/index.js.map +1 -1
  14. package/dist/presentation/inter-module.d.ts +9 -0
  15. package/dist/presentation/inter-module.d.ts.map +1 -0
  16. package/dist/presentation/inter-module.js +121 -0
  17. package/dist/presentation/inter-module.js.map +1 -0
  18. package/dist/presentation/routes/agent-tools-gateway/index.d.ts +1 -0
  19. package/dist/presentation/routes/agent-tools-gateway/index.d.ts.map +1 -1
  20. package/dist/presentation/routes/agent-tools-gateway/index.js +1 -0
  21. package/dist/presentation/routes/agent-tools-gateway/index.js.map +1 -1
  22. package/dist/presentation/routes/agent-tools-gateway/resolve-authorized-tools.d.ts +5 -2
  23. package/dist/presentation/routes/agent-tools-gateway/resolve-authorized-tools.d.ts.map +1 -1
  24. package/dist/presentation/routes/agent-tools-gateway/resolve-authorized-tools.js +53 -17
  25. package/dist/presentation/routes/agent-tools-gateway/resolve-authorized-tools.js.map +1 -1
  26. package/dist/presentation/routes/index.d.ts +2 -3
  27. package/dist/presentation/routes/index.d.ts.map +1 -1
  28. package/dist/presentation/routes/index.js +2 -2
  29. package/dist/presentation/routes/index.js.map +1 -1
  30. package/dist/providers/gitea.d.ts.map +1 -1
  31. package/dist/providers/gitea.js +10 -8
  32. package/dist/providers/gitea.js.map +1 -1
  33. package/dist/providers/github.d.ts.map +1 -1
  34. package/dist/providers/github.js +18 -13
  35. package/dist/providers/github.js.map +1 -1
  36. package/dist/providers/jira.d.ts.map +1 -1
  37. package/dist/providers/jira.js +4 -1
  38. package/dist/providers/jira.js.map +1 -1
  39. package/dist/providers/linear.d.ts.map +1 -1
  40. package/dist/providers/linear.js +34 -29
  41. package/dist/providers/linear.js.map +1 -1
  42. package/dist/providers/sentry.d.ts.map +1 -1
  43. package/dist/providers/sentry.js +13 -11
  44. package/dist/providers/sentry.js.map +1 -1
  45. package/dist/providers/slack.d.ts.map +1 -1
  46. package/dist/providers/slack.js +34 -29
  47. package/dist/providers/slack.js.map +1 -1
  48. package/dist/providers/types.d.ts +12 -0
  49. package/dist/providers/types.d.ts.map +1 -1
  50. package/dist/providers/types.js.map +1 -1
  51. package/dist/providers/webhook.d.ts.map +1 -1
  52. package/dist/providers/webhook.js +11 -9
  53. package/dist/providers/webhook.js.map +1 -1
  54. package/dist/tsconfig.test.tsbuildinfo +1 -1
  55. package/package.json +22 -17
  56. package/src/core/entities/provider.ts +7 -1
  57. package/src/core/errors.ts +12 -2
  58. package/src/index.test.ts +112 -0
  59. package/src/index.ts +102 -9
  60. package/src/presentation/inter-module.ts +155 -0
  61. package/src/presentation/routes/agent-tools-gateway/index.ts +1 -0
  62. package/src/presentation/routes/agent-tools-gateway/resolve-authorized-tools.test.ts +36 -0
  63. package/src/presentation/routes/agent-tools-gateway/resolve-authorized-tools.ts +64 -22
  64. package/src/presentation/routes/index.ts +4 -5
  65. package/src/providers/gitea.ts +11 -8
  66. package/src/providers/github.ts +17 -11
  67. package/src/providers/jira.ts +3 -0
  68. package/src/providers/linear.ts +30 -24
  69. package/src/providers/sentry.ts +15 -12
  70. package/src/providers/slack.ts +31 -25
  71. package/src/providers/types.ts +15 -0
  72. package/src/providers/webhook.ts +11 -9
  73. package/tsconfig.build.tsbuildinfo +1 -1
@@ -84,37 +84,42 @@ async function loadLinearModuleParts(options = {}) {
84
84
  resolveConnection: async (connectionId)=>getIntegrationConnectionById(connectionId),
85
85
  secrets
86
86
  });
87
- return {
88
- provider: createLinearIntegrationProvider({
89
- agentTools: {
90
- tokenStore,
91
- endpoint: linearConfig.LINEAR_MCP_ENDPOINT
92
- },
93
- cleanup: {
94
- deleteConnectionRecords: async (connection, { tx })=>{
95
- await deleteLinearInstallationByConnectionId(connection.id, {
96
- tx
97
- });
98
- },
99
- deleteConnectionSecrets: async (connection)=>{
100
- // Scoped secrets accept the provider-local suffix, after this helper validates its prefix.
101
- await (options.secrets?.linear?.deleteSecrets({
102
- workspaceId: connection.workspaceId,
103
- namespace: linearNamespaceSuffix(linearSecretsNamespace(connection.id))
104
- }) ?? Promise.resolve());
105
- }
87
+ const integrationProvider = createLinearIntegrationProvider({
88
+ agentTools: {
89
+ tokenStore,
90
+ endpoint: linearConfig.LINEAR_MCP_ENDPOINT
91
+ },
92
+ cleanup: {
93
+ deleteConnectionRecords: async (connection, { tx })=>{
94
+ await deleteLinearInstallationByConnectionId(connection.id, {
95
+ tx
96
+ });
106
97
  },
107
- routes: {
108
- tokenStore,
109
- getExistingLinearConnection,
110
- connectLinearInstallation,
111
- disconnectLinearInstallation,
112
- publishIntegrationEventReceived,
113
- recordDeliveryOnly,
114
- getIntegrationConnectionById,
115
- coreDb: db
98
+ deleteConnectionSecrets: async (connection)=>{
99
+ // Scoped secrets accept the provider-local suffix, after this helper validates its prefix.
100
+ await (options.secrets?.linear?.deleteSecrets({
101
+ workspaceId: connection.workspaceId,
102
+ namespace: linearNamespaceSuffix(linearSecretsNamespace(connection.id))
103
+ }) ?? Promise.resolve());
116
104
  }
117
- }),
105
+ },
106
+ routes: {
107
+ tokenStore,
108
+ getExistingLinearConnection,
109
+ connectLinearInstallation,
110
+ disconnectLinearInstallation,
111
+ publishIntegrationEventReceived,
112
+ recordDeliveryOnly,
113
+ getIntegrationConnectionById,
114
+ coreDb: db,
115
+ ...options.requireActiveWorkspaceMembership ? {
116
+ requireActiveWorkspaceMembership: options.requireActiveWorkspaceMembership
117
+ } : {}
118
+ }
119
+ });
120
+ return {
121
+ provider: integrationProvider,
122
+ webhookProcessors: integrationProvider.webhookProcessors,
118
123
  e2eRoutes: [
119
124
  createLinearE2eRoutes({
120
125
  tokenStore,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/providers/linear.ts"],"sourcesContent":["import {\n type IntegrationConnection as CoreIntegrationConnection,\n slugifyConnectionSlug,\n} from '@shipfox/api-integration-core-dto';\nimport type {\n ConnectLinearInstallationInput,\n LinearSecretsStore,\n} from '@shipfox/api-integration-linear';\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} from '#providers/connection-slug.js';\nimport type {IntegrationModuleParts, IntegrationProviderModule} from '#providers/types.js';\n\nconst LINEAR_MIGRATIONS_TABLE = '__drizzle_migrations_integrations_linear';\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 return {\n provider: 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 },\n }),\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 migrationsTableName: LINEAR_MIGRATIONS_TABLE,\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":["slugifyConnectionSlug","config","deleteIntegrationConnection","getIntegrationConnectionById","resolveUniqueConnectionSlug","upsertIntegrationConnection","db","publishIntegrationEventReceived","recordDeliveryOnly","retryConnectionSlugCollision","LINEAR_MIGRATIONS_TABLE","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","agentTools","endpoint","LINEAR_MCP_ENDPOINT","cleanup","deleteConnectionRecords","deleteConnectionSecrets","routes","coreDb","e2eRoutes","connectionCapabilities","database","migrationsTableName","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 {\n type IntegrationConnection as CoreIntegrationConnection,\n slugifyConnectionSlug,\n} from '@shipfox/api-integration-core-dto';\nimport type {\n ConnectLinearInstallationInput,\n LinearSecretsStore,\n} from '@shipfox/api-integration-linear';\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} from '#providers/connection-slug.js';\nimport type {IntegrationModuleParts, IntegrationProviderModule} from '#providers/types.js';\n\nconst LINEAR_MIGRATIONS_TABLE = '__drizzle_migrations_integrations_linear';\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 migrationsTableName: LINEAR_MIGRATIONS_TABLE,\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":["slugifyConnectionSlug","config","deleteIntegrationConnection","getIntegrationConnectionById","resolveUniqueConnectionSlug","upsertIntegrationConnection","db","publishIntegrationEventReceived","recordDeliveryOnly","retryConnectionSlugCollision","LINEAR_MIGRATIONS_TABLE","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","migrationsTableName","linearProviderModule","enabled","INTEGRATIONS_ENABLE_LINEAR_PROVIDER","load","startsWith","slice","length"],"mappings":"AAAA,SAEEA,qBAAqB,QAChB,oCAAoC;AAK3C,SAAQC,MAAM,QAAO,aAAa;AAClC,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,QAAO,gCAAgC;AAG3E,MAAMC,0BAA0B;AAChC,MAAMC,kCAAkC;AAKxC,eAAeC,sBACbC,UAA4D,CAAC,CAAC;IAE9D,MAAM,EACJC,sBAAsB,EACtBC,qBAAqB,EACrBC,+BAA+B,EAC/Bf,QAAQgB,YAAY,EACpBC,sCAAsC,EACtCC,8BAA8BC,mCAAmC,EACjEC,qCAAqC,EACrCf,IAAIgB,QAAQ,EACZC,sBAAsB,EACtBC,gBAAgBC,oBAAoB,EACpCC,wBAAwB,EACzB,GAAG,MAAM,MAAM,CAAC;IAEjB,eAAeC,4BAA4BC,KAE1C;QACC,MAAMC,eAAe,MAAMR,sCAAsCO,MAAME,cAAc;QACrF,IAAI,CAACD,cAAc,OAAOE;QAC1B,MAAMC,aAAa,MAAM7B,6BAA6B0B,aAAaI,YAAY;QAC/E,IAAI,CAACD,YAAY,OAAOD;QACxB,OAAOC;IACT;IAEA,eAAeE,0BACbN,KAAqC;QAErC,OAAO,MAAMnB,6BAA6B,IACxCH,KAAK6B,WAAW,CAAC,OAAOC;gBACtB,MAAMC,WAAWrC,sBAAsB,CAAC,OAAO,EAAE4B,MAAMU,kBAAkB,EAAE,EAAE;oBAC3EC,UAAU;gBACZ;gBACA,MAAMC,OAAO,MAAMpC,4BACjB;oBACEqC,aAAab,MAAMa,WAAW;oBAC9BC,UAAU;oBACVC,mBAAmBf,MAAME,cAAc;oBACvCO;gBACF,GACA;oBAACD;gBAAE;gBAEL,MAAMJ,aAAa,MAAM3B,4BACvB;oBACEoC,aAAab,MAAMa,WAAW;oBAC9BC,UAAU;oBACVC,mBAAmBf,MAAME,cAAc;oBACvCU;oBACAI,aAAahB,MAAMgB,WAAW;oBAC9BC,iBAAiB;gBACnB,GACA;oBAACT;gBAAE;gBAGL,MAAMV,yBACJ;oBACEO,cAAcD,WAAWc,EAAE;oBAC3BhB,gBAAgBF,MAAME,cAAc;oBACpCQ,oBAAoBV,MAAMU,kBAAkB;oBAC5CS,WAAWnB,MAAMmB,SAAS;oBAC1BC,QAAQpB,MAAMoB,MAAM;oBACpBC,gBAAgBrB,MAAMqB,cAAc;oBACpCC,QAAQ;gBACV,GACA;oBAACd;gBAAE;gBAGL,OAAOJ;YACT;IAEJ;IAEA,eAAeb,6BAA6BS,KAA6B;QACvE,MAAMR,oCAAmD;YACvDa,cAAcL,MAAMK,YAAY;YAChCkB,eAAehD;YACfiD,eAAe,CAACC,SACdxC,QAAQyC,OAAO,EAAEC,QAAQH,cAAc;oBACrC,GAAGC,MAAM;oBACTG,WAAWC,sBAAsBJ,OAAOG,SAAS;gBACnD,MAAME,QAAQC,OAAO,CAAC;YACxBxB,aAAa,CAACyB,KAAOtD,KAAK6B,WAAW,CAAC,CAACC,KAAOwB,GAAGxB;YACjDyB,kBAAkB,CAACR,QAAQxC,UACzBX,4BAA4B;oBAAC4C,IAAIO,OAAOpB,YAAY;gBAAA,GAAGpB;QAC3D;IACF;IAEA,MAAMiD,kBAAsC;QAC1CC,WAAW,IAAML,QAAQC,OAAO,CAAC;QACjCK,YAAY,IAAMN,QAAQO,MAAM,CAAC,IAAIC,MAAM;IAC7C;IACA,MAAMZ,UAA8BzC,QAAQyC,OAAO,EAAEC,SACjD;QACEQ,WAAW,CAACV,SACVxC,QAAQyC,OAAO,EAAEC,QAAQQ,UAAU;gBACjC,GAAGV,MAAM;gBACTG,WAAWC,sBAAsBJ,OAAOG,SAAS;YACnD,MAAME,QAAQC,OAAO,CAAC;QACxBK,YAAY,CAACX,SACXxC,QAAQyC,OAAO,EAAEC,QAAQS,WAAW;gBAClC,GAAGX,MAAM;gBACTG,WAAWC,sBAAsBJ,OAAOG,SAAS;YACnD,MAAME,QAAQC,OAAO;IACzB,IACAG;IACJ,MAAMK,aAAarD,uBAAuB;QACxCsD,mBAAmB,OAAOnC,eAAiB9B,6BAA6B8B;QACxEqB;IACF;IAEA,MAAMe,sBAAsBrD,gCAAgC;QAC1DsD,YAAY;YAACH;YAAYI,UAAUtD,aAAauD,mBAAmB;QAAA;QACnEC,SAAS;YACPC,yBAAyB,OAAO1C,YAAY,EAACI,EAAE,EAAC;gBAC9C,MAAMlB,uCAAuCc,WAAWc,EAAE,EAAE;oBAACV;gBAAE;YACjE;YACAuC,yBAAyB,OAAO3C;gBAC9B,2FAA2F;gBAC3F,MAAOnB,CAAAA,QAAQyC,OAAO,EAAEC,QAAQH,cAAc;oBAC5CX,aAAaT,WAAWS,WAAW;oBACnCe,WAAWC,sBAAsBlC,uBAAuBS,WAAWc,EAAE;gBACvE,MAAMY,QAAQC,OAAO,EAAC;YACxB;QACF;QACAiB,QAAQ;YACNT;YACAxC;YACAO;YACAf;YACAZ;YACAC;YACAL;YACA0E,QAAQvE;YACR,GAAIO,QAAQiE,gCAAgC,GACxC;gBAACA,kCAAkCjE,QAAQiE,gCAAgC;YAAA,IAC3E,CAAC,CAAC;QACR;IACF;IAEA,OAAO;QACLpC,UAAU2B;QACVU,mBAAmBV,oBAAoBU,iBAAiB;QACxDC,WAAW;YACTjE,sBAAsB;gBACpBoD;gBACAxC;gBACAO;gBACAf;gBACA8D,wBAAwB;oBAAC;iBAAc;YACzC;SACD;QACDC,UAAU;YACR5E,IAAIgB;YACJE,gBAAgBC;YAChB0D,qBAAqBzE;QACvB;IACF;AACF;AAEA,OAAO,MAAM0E,uBAAkD;IAC7DtC,IAAI;IACJuC,SAASpF,OAAOqF,mCAAmC;IACnDC,MAAM3E;AACR,EAAE;AAEF,SAAS6C,sBAAsBD,SAAiB;IAC9C,IAAI,CAACA,UAAUgC,UAAU,CAAC7E,kCAAkC;QAC1D,MAAM,IAAIuD,MAAM;IAClB;IACA,OAAOV,UAAUiC,KAAK,CAAC9E,gCAAgC+E,MAAM;AAC/D"}
@@ -1 +1 @@
1
- {"version":3,"file":"sentry.d.ts","sourceRoot":"","sources":["../../src/providers/sentry.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAyB,yBAAyB,EAAC,MAAM,qBAAqB,CAAC;AA6F3F,eAAO,MAAM,oBAAoB,EAAE,yBAIlC,CAAC"}
1
+ {"version":3,"file":"sentry.d.ts","sourceRoot":"","sources":["../../src/providers/sentry.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAyB,yBAAyB,EAAC,MAAM,qBAAqB,CAAC;AAgG3F,eAAO,MAAM,oBAAoB,EAAE,yBAIlC,CAAC"}
@@ -52,18 +52,20 @@ async function loadSentryModuleParts() {
52
52
  return connection;
53
53
  }));
54
54
  }
55
+ const integrationProvider = createSentryIntegrationProvider({
56
+ getSentryInstallation: ({ installationUuid })=>getSentryInstallationByInstallationUuid(installationUuid),
57
+ getConnectionById,
58
+ connectSentryInstallation,
59
+ persistVerifiedUnclaimedInstallation,
60
+ coreDb: db,
61
+ publishIntegrationEventReceived,
62
+ recordDeliveryOnly,
63
+ getIntegrationConnectionById,
64
+ updateConnectionLifecycleStatus: updateIntegrationConnectionLifecycleStatus
65
+ });
55
66
  return {
56
- provider: createSentryIntegrationProvider({
57
- getSentryInstallation: ({ installationUuid })=>getSentryInstallationByInstallationUuid(installationUuid),
58
- getConnectionById,
59
- connectSentryInstallation,
60
- persistVerifiedUnclaimedInstallation,
61
- coreDb: db,
62
- publishIntegrationEventReceived,
63
- recordDeliveryOnly,
64
- getIntegrationConnectionById,
65
- updateConnectionLifecycleStatus: updateIntegrationConnectionLifecycleStatus
66
- }),
67
+ provider: integrationProvider,
68
+ webhookProcessors: integrationProvider.webhookProcessors,
67
69
  database: {
68
70
  db: sentryDb,
69
71
  migrationsPath: sentryMigrationsPath,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/providers/sentry.ts"],"sourcesContent":["import {\n type IntegrationConnection as CoreIntegrationConnection,\n slugifyConnectionSlug,\n} from '@shipfox/api-integration-core-dto';\nimport type {ConnectSentryInstallationInput} from '@shipfox/api-integration-sentry';\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} from '#providers/connection-slug.js';\nimport type {IntegrationModuleParts, IntegrationProviderModule} from '#providers/types.js';\n\n// Stable migration-tracking table name for the Sentry provider database. This\n// must NOT depend on the provider's position in the module `database` array. A\n// positional name would shift if a provider is flag-disabled and silently\n// re-run migrations against existing tables.\nconst SENTRY_MIGRATIONS_TABLE = '__drizzle_migrations_integrations_sentry';\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 return {\n provider: 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 database: {\n db: sentryDb,\n migrationsPath: sentryMigrationsPath,\n migrationsTableName: SENTRY_MIGRATIONS_TABLE,\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":["slugifyConnectionSlug","config","getIntegrationConnectionById","resolveUniqueConnectionSlug","updateIntegrationConnectionLifecycleStatus","upsertIntegrationConnection","db","publishIntegrationEventReceived","recordDeliveryOnly","retryConnectionSlugCollision","SENTRY_MIGRATIONS_TABLE","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","getSentryInstallation","coreDb","updateConnectionLifecycleStatus","database","migrationsTableName","workers","sentryProviderModule","enabled","INTEGRATIONS_ENABLE_SENTRY_PROVIDER","load"],"mappings":"AAAA,SAEEA,qBAAqB,QAChB,oCAAoC;AAE3C,SAAQC,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,QAAO,gCAAgC;AAG3E,8EAA8E;AAC9E,+EAA+E;AAC/E,0EAA0E;AAC1E,6CAA6C;AAC7C,MAAMC,0BAA0B;AAEhC,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,WAAW5B,sBAAsB,CAAC,OAAO,EAAEyB,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,OAAO;QACLW,UAAUrB,gCAAgC;YACxC8B,uBAAuB,CAAC,EAACP,gBAAgB,EAAC,GACxCrB,wCAAwCqB;YAC1Cf;YACAI;YACAT;YACA4B,QAAQrC;YACRC;YACAC;YACAN;YACA0C,iCAAiCxC;QACnC;QACAyC,UAAU;YACRvC,IAAIW;YACJC,gBAAgBC;YAChB2B,qBAAqBpC;QACvB;QACAqC,SAAS;YAAClC;SAAgC;IAC5C;AACF;AAEA,OAAO,MAAMmC,uBAAkD;IAC7D3B,IAAI;IACJ4B,SAAShD,OAAOiD,mCAAmC;IACnDC,MAAMxC;AACR,EAAE"}
1
+ {"version":3,"sources":["../../src/providers/sentry.ts"],"sourcesContent":["import {\n type IntegrationConnection as CoreIntegrationConnection,\n slugifyConnectionSlug,\n} from '@shipfox/api-integration-core-dto';\nimport type {ConnectSentryInstallationInput} from '@shipfox/api-integration-sentry';\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} from '#providers/connection-slug.js';\nimport type {IntegrationModuleParts, IntegrationProviderModule} from '#providers/types.js';\n\n// Stable migration-tracking table name for the Sentry provider database. This\n// must NOT depend on the provider's position in the module `database` array. A\n// positional name would shift if a provider is flag-disabled and silently\n// re-run migrations against existing tables.\nconst SENTRY_MIGRATIONS_TABLE = '__drizzle_migrations_integrations_sentry';\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 migrationsTableName: SENTRY_MIGRATIONS_TABLE,\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":["slugifyConnectionSlug","config","getIntegrationConnectionById","resolveUniqueConnectionSlug","updateIntegrationConnectionLifecycleStatus","upsertIntegrationConnection","db","publishIntegrationEventReceived","recordDeliveryOnly","retryConnectionSlugCollision","SENTRY_MIGRATIONS_TABLE","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","migrationsTableName","workers","sentryProviderModule","enabled","INTEGRATIONS_ENABLE_SENTRY_PROVIDER","load"],"mappings":"AAAA,SAEEA,qBAAqB,QAChB,oCAAoC;AAE3C,SAAQC,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,QAAO,gCAAgC;AAG3E,8EAA8E;AAC9E,+EAA+E;AAC/E,0EAA0E;AAC1E,6CAA6C;AAC7C,MAAMC,0BAA0B;AAEhC,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,WAAW5B,sBAAsB,CAAC,OAAO,EAAEyB,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,qBAAqBtC;QACvB;QACAuC,SAAS;YAACpC;SAAgC;IAC5C;AACF;AAEA,OAAO,MAAMqC,uBAAkD;IAC7D7B,IAAI;IACJ8B,SAASlD,OAAOmD,mCAAmC;IACnDC,MAAM1C;AACR,EAAE"}
@@ -1 +1 @@
1
- {"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../src/providers/slack.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAyB,yBAAyB,EAAC,MAAM,qBAAqB,CAAC;AAkK3F,eAAO,MAAM,mBAAmB,EAAE,yBAIjC,CAAC"}
1
+ {"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../src/providers/slack.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAyB,yBAAyB,EAAC,MAAM,qBAAqB,CAAC;AAwK3F,eAAO,MAAM,mBAAmB,EAAE,yBAIjC,CAAC"}
@@ -85,37 +85,42 @@ async function loadSlackModuleParts(options = {}) {
85
85
  resolveConnection: async (connectionId)=>getIntegrationConnectionById(connectionId),
86
86
  secrets
87
87
  });
88
- return {
89
- provider: createSlackIntegrationProvider({
90
- agentTools: {
91
- tokenStore
92
- },
93
- cleanup: {
94
- deleteConnectionRecords: async (connection, { tx })=>{
95
- await deleteSlackInstallationByConnectionId(connection.id, {
96
- tx
97
- });
98
- },
99
- deleteConnectionSecrets: async (connection)=>{
100
- // Scoped secrets accept the provider-local suffix, after this helper validates its prefix.
101
- await (options.secrets?.slack?.deleteSecrets({
102
- workspaceId: connection.workspaceId,
103
- namespace: slackNamespaceSuffix(slackSecretsNamespace(connection.id))
104
- }) ?? Promise.resolve());
105
- }
88
+ const integrationProvider = createSlackIntegrationProvider({
89
+ agentTools: {
90
+ tokenStore
91
+ },
92
+ cleanup: {
93
+ deleteConnectionRecords: async (connection, { tx })=>{
94
+ await deleteSlackInstallationByConnectionId(connection.id, {
95
+ tx
96
+ });
106
97
  },
107
- routes: {
108
- tokenStore,
109
- getExistingSlackConnection,
110
- connectSlackInstallation,
111
- disconnectSlackInstallation,
112
- coreDb: db,
113
- claimWebhookDelivery,
114
- publishIntegrationEventReceived,
115
- recordDeliveryOnly,
116
- getIntegrationConnectionById
98
+ deleteConnectionSecrets: async (connection)=>{
99
+ // Scoped secrets accept the provider-local suffix, after this helper validates its prefix.
100
+ await (options.secrets?.slack?.deleteSecrets({
101
+ workspaceId: connection.workspaceId,
102
+ namespace: slackNamespaceSuffix(slackSecretsNamespace(connection.id))
103
+ }) ?? Promise.resolve());
117
104
  }
118
- }),
105
+ },
106
+ routes: {
107
+ tokenStore,
108
+ getExistingSlackConnection,
109
+ connectSlackInstallation,
110
+ disconnectSlackInstallation,
111
+ coreDb: db,
112
+ claimWebhookDelivery,
113
+ publishIntegrationEventReceived,
114
+ recordDeliveryOnly,
115
+ getIntegrationConnectionById,
116
+ ...options.requireActiveWorkspaceMembership ? {
117
+ requireActiveWorkspaceMembership: options.requireActiveWorkspaceMembership
118
+ } : {}
119
+ }
120
+ });
121
+ return {
122
+ provider: integrationProvider,
123
+ webhookProcessors: integrationProvider.webhookProcessors,
119
124
  e2eRoutes: [
120
125
  createSlackE2eRoutes({
121
126
  tokenStore,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/providers/slack.ts"],"sourcesContent":["import {\n type IntegrationConnection as CoreIntegrationConnection,\n slugifyConnectionSlug,\n} from '@shipfox/api-integration-core-dto';\nimport type {\n ConnectSlackInstallationInput,\n SlackSecretsStore,\n} from '@shipfox/api-integration-slack';\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} from '#providers/connection-slug.js';\nimport type {IntegrationModuleParts, IntegrationProviderModule} from '#providers/types.js';\n\nconst SLACK_MIGRATIONS_TABLE = '__drizzle_migrations_integrations_slack';\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 return {\n provider: 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 },\n }),\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 migrationsTableName: SLACK_MIGRATIONS_TABLE,\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":["slugifyConnectionSlug","config","deleteIntegrationConnection","getIntegrationConnectionById","resolveUniqueConnectionSlug","upsertIntegrationConnection","db","claimWebhookDelivery","publishIntegrationEventReceived","recordDeliveryOnly","retryConnectionSlugCollision","SLACK_MIGRATIONS_TABLE","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","agentTools","cleanup","deleteConnectionRecords","deleteConnectionSecrets","routes","coreDb","e2eRoutes","connectionCapabilities","database","migrationsTableName","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 {\n type IntegrationConnection as CoreIntegrationConnection,\n slugifyConnectionSlug,\n} from '@shipfox/api-integration-core-dto';\nimport type {\n ConnectSlackInstallationInput,\n SlackSecretsStore,\n} from '@shipfox/api-integration-slack';\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} from '#providers/connection-slug.js';\nimport type {IntegrationModuleParts, IntegrationProviderModule} from '#providers/types.js';\n\nconst SLACK_MIGRATIONS_TABLE = '__drizzle_migrations_integrations_slack';\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 migrationsTableName: SLACK_MIGRATIONS_TABLE,\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":["slugifyConnectionSlug","config","deleteIntegrationConnection","getIntegrationConnectionById","resolveUniqueConnectionSlug","upsertIntegrationConnection","db","claimWebhookDelivery","publishIntegrationEventReceived","recordDeliveryOnly","retryConnectionSlugCollision","SLACK_MIGRATIONS_TABLE","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","migrationsTableName","slackProviderModule","enabled","INTEGRATIONS_ENABLE_SLACK_PROVIDER","load","startsWith","slice","length"],"mappings":"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"}
@@ -1,3 +1,5 @@
1
+ import type { UserContextMembership } from '@shipfox/api-auth-context';
2
+ import type { WebhookRequestProcessor, WebhookRouteId } from '@shipfox/api-integration-core-dto';
1
3
  import type { RouteExport } from '@shipfox/node-fastify';
2
4
  import type { ModuleDatabase, ModuleWorker } from '@shipfox/node-module';
3
5
  import type { IntegrationProvider } from '#core/entities/provider.js';
@@ -16,6 +18,11 @@ export interface IntegrationModuleParts {
16
18
  e2eRoutes?: RouteExport[] | undefined;
17
19
  workers?: ModuleWorker[] | undefined;
18
20
  startupTasks?: Array<() => Promise<void>> | undefined;
21
+ webhookProcessors?: WebhookProcessorRegistration[] | undefined;
22
+ }
23
+ export interface WebhookProcessorRegistration {
24
+ routeIds: readonly WebhookRouteId[];
25
+ processor: WebhookRequestProcessor;
19
26
  }
20
27
  /**
21
28
  * A config-gated integration, registered once in `providerModules`. `load` is
@@ -29,6 +36,11 @@ export interface IntegrationProviderModule {
29
36
  }
30
37
  export interface IntegrationProviderModuleLoadOptions {
31
38
  secrets?: IntegrationProviderSecrets | undefined;
39
+ requireActiveWorkspaceMembership?: ((input: {
40
+ workspaceId: string;
41
+ userId: string;
42
+ memberships: ReadonlyArray<UserContextMembership>;
43
+ }) => Promise<unknown>) | undefined;
32
44
  }
33
45
  export interface IntegrationProviderSecrets {
34
46
  github?: IntegrationProviderScopedSecrets | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAC,cAAc,EAAE,YAAY,EAAC,MAAM,sBAAsB,CAAC;AACvE,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,4BAA4B,CAAC;AAEpE;;;;;;;;GAQG;AACH,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,QAAQ,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IACtC,SAAS,CAAC,EAAE,WAAW,EAAE,GAAG,SAAS,CAAC;IACtC,OAAO,CAAC,EAAE,YAAY,EAAE,GAAG,SAAS,CAAC;IACrC,YAAY,CAAC,EAAE,KAAK,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC;CACvD;AAED;;;;GAIG;AACH,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,OAAO,CAAC,EAAE,oCAAoC,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;CACvF;AAED,MAAM,WAAW,oCAAoC;IACnD,OAAO,CAAC,EAAE,0BAA0B,GAAG,SAAS,CAAC;CAClD;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,CAAC,EAAE,gCAAgC,GAAG,SAAS,CAAC;IACtD,IAAI,CAAC,EAAE,gCAAgC,GAAG,SAAS,CAAC;IACpD,MAAM,CAAC,EAAE,gCAAgC,GAAG,SAAS,CAAC;IACtD,KAAK,CAAC,EAAE,gCAAgC,GAAG,SAAS,CAAC;IACrD,aAAa,CAAC,MAAM,EAAE;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAClF;AAED,MAAM,WAAW,gCAAgC;IAC/C,SAAS,CAAC,MAAM,EAAE;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACjG,UAAU,CAAC,MAAM,EAAE;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;KACtC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClB,aAAa,CAAC,MAAM,EAAE;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAClF"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,2BAA2B,CAAC;AACrE,OAAO,KAAK,EAAC,uBAAuB,EAAE,cAAc,EAAC,MAAM,mCAAmC,CAAC;AAC/F,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAC,cAAc,EAAE,YAAY,EAAC,MAAM,sBAAsB,CAAC;AACvE,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,4BAA4B,CAAC;AAEpE;;;;;;;;GAQG;AACH,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,QAAQ,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IACtC,SAAS,CAAC,EAAE,WAAW,EAAE,GAAG,SAAS,CAAC;IACtC,OAAO,CAAC,EAAE,YAAY,EAAE,GAAG,SAAS,CAAC;IACrC,YAAY,CAAC,EAAE,KAAK,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC;IACtD,iBAAiB,CAAC,EAAE,4BAA4B,EAAE,GAAG,SAAS,CAAC;CAChE;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAC;IACpC,SAAS,EAAE,uBAAuB,CAAC;CACpC;AAED;;;;GAIG;AACH,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,OAAO,CAAC,EAAE,oCAAoC,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;CACvF;AAED,MAAM,WAAW,oCAAoC;IACnD,OAAO,CAAC,EAAE,0BAA0B,GAAG,SAAS,CAAC;IACjD,gCAAgC,CAAC,EAC7B,CAAC,CAAC,KAAK,EAAE;QACP,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;KACnD,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,GACvB,SAAS,CAAC;CACf;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,CAAC,EAAE,gCAAgC,GAAG,SAAS,CAAC;IACtD,IAAI,CAAC,EAAE,gCAAgC,GAAG,SAAS,CAAC;IACpD,MAAM,CAAC,EAAE,gCAAgC,GAAG,SAAS,CAAC;IACtD,KAAK,CAAC,EAAE,gCAAgC,GAAG,SAAS,CAAC;IACrD,aAAa,CAAC,MAAM,EAAE;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAClF;AAED,MAAM,WAAW,gCAAgC;IAC/C,SAAS,CAAC,MAAM,EAAE;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACjG,UAAU,CAAC,MAAM,EAAE;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;KACtC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClB,aAAa,CAAC,MAAM,EAAE;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAClF"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/providers/types.ts"],"sourcesContent":["import type {RouteExport} from '@shipfox/node-fastify';\nimport type {ModuleDatabase, ModuleWorker} from '@shipfox/node-module';\nimport type {IntegrationProvider} from '#core/entities/provider.js';\n\n/**\n * Everything one integration contributes to the composed integrations module:\n * a registry provider, plus an optional dedicated database, background workers,\n * and one-shot boot-time tasks. Providers that own none of these simply omit them.\n *\n * A startup task is run once after modules are initialized (migrations done). The\n * provider owns its own wiring — core runs each task generically and isolates\n * failures so a task can never gate API boot.\n */\nexport interface IntegrationModuleParts {\n provider: IntegrationProvider;\n database?: ModuleDatabase | undefined;\n e2eRoutes?: RouteExport[] | undefined;\n workers?: ModuleWorker[] | undefined;\n startupTasks?: Array<() => Promise<void>> | undefined;\n}\n\n/**\n * A config-gated integration, registered once in `providerModules`. `load` is\n * called lazily and only when `enabled`, so a disabled provider never imports\n * its (potentially heavy) implementation package.\n */\nexport interface IntegrationProviderModule {\n id: string;\n enabled: boolean;\n load(options?: IntegrationProviderModuleLoadOptions): Promise<IntegrationModuleParts>;\n}\n\nexport interface IntegrationProviderModuleLoadOptions {\n secrets?: IntegrationProviderSecrets | undefined;\n}\n\nexport interface IntegrationProviderSecrets {\n github?: IntegrationProviderScopedSecrets | undefined;\n jira?: IntegrationProviderScopedSecrets | undefined;\n linear?: IntegrationProviderScopedSecrets | undefined;\n slack?: IntegrationProviderScopedSecrets | undefined;\n deleteSecrets(params: {workspaceId: string; namespace: string}): Promise<number>;\n}\n\nexport interface IntegrationProviderScopedSecrets {\n getSecret(params: {workspaceId: string; namespace: string; key: string}): Promise<string | null>;\n setSecrets(params: {\n workspaceId: string;\n namespace: string;\n values: Record<string, string>;\n editedBy?: string | null | undefined;\n }): Promise<void>;\n deleteSecrets(params: {workspaceId: string; namespace: string}): Promise<number>;\n}\n"],"names":[],"mappings":"AA4CA,WASC"}
1
+ {"version":3,"sources":["../../src/providers/types.ts"],"sourcesContent":["import type {UserContextMembership} from '@shipfox/api-auth-context';\nimport type {WebhookRequestProcessor, WebhookRouteId} from '@shipfox/api-integration-core-dto';\nimport type {RouteExport} from '@shipfox/node-fastify';\nimport type {ModuleDatabase, ModuleWorker} from '@shipfox/node-module';\nimport type {IntegrationProvider} from '#core/entities/provider.js';\n\n/**\n * Everything one integration contributes to the composed integrations module:\n * a registry provider, plus an optional dedicated database, background workers,\n * and one-shot boot-time tasks. Providers that own none of these simply omit them.\n *\n * A startup task is run once after modules are initialized (migrations done). The\n * provider owns its own wiring — core runs each task generically and isolates\n * failures so a task can never gate API boot.\n */\nexport interface IntegrationModuleParts {\n provider: IntegrationProvider;\n database?: ModuleDatabase | undefined;\n e2eRoutes?: RouteExport[] | undefined;\n workers?: ModuleWorker[] | undefined;\n startupTasks?: Array<() => Promise<void>> | undefined;\n webhookProcessors?: WebhookProcessorRegistration[] | undefined;\n}\n\nexport interface WebhookProcessorRegistration {\n routeIds: readonly WebhookRouteId[];\n processor: WebhookRequestProcessor;\n}\n\n/**\n * A config-gated integration, registered once in `providerModules`. `load` is\n * called lazily and only when `enabled`, so a disabled provider never imports\n * its (potentially heavy) implementation package.\n */\nexport interface IntegrationProviderModule {\n id: string;\n enabled: boolean;\n load(options?: IntegrationProviderModuleLoadOptions): Promise<IntegrationModuleParts>;\n}\n\nexport interface IntegrationProviderModuleLoadOptions {\n secrets?: IntegrationProviderSecrets | undefined;\n requireActiveWorkspaceMembership?:\n | ((input: {\n workspaceId: string;\n userId: string;\n memberships: ReadonlyArray<UserContextMembership>;\n }) => Promise<unknown>)\n | undefined;\n}\n\nexport interface IntegrationProviderSecrets {\n github?: IntegrationProviderScopedSecrets | undefined;\n jira?: IntegrationProviderScopedSecrets | undefined;\n linear?: IntegrationProviderScopedSecrets | undefined;\n slack?: IntegrationProviderScopedSecrets | undefined;\n deleteSecrets(params: {workspaceId: string; namespace: string}): Promise<number>;\n}\n\nexport interface IntegrationProviderScopedSecrets {\n getSecret(params: {workspaceId: string; namespace: string; key: string}): Promise<string | null>;\n setSecrets(params: {\n workspaceId: string;\n namespace: string;\n values: Record<string, string>;\n editedBy?: string | null | undefined;\n }): Promise<void>;\n deleteSecrets(params: {workspaceId: string; namespace: string}): Promise<number>;\n}\n"],"names":[],"mappings":"AA2DA,WASC"}
@@ -1 +1 @@
1
- {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/providers/webhook.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,qBAAqB,CAAC;AAEnE,eAAO,MAAM,qBAAqB,EAAE,yBAiBnC,CAAC"}
1
+ {"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/providers/webhook.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAC,yBAAyB,EAAC,MAAM,qBAAqB,CAAC;AAEnE,eAAO,MAAM,qBAAqB,EAAE,yBAmBnC,CAAC"}
@@ -7,16 +7,18 @@ export const webhookProviderModule = {
7
7
  enabled: config.INTEGRATIONS_ENABLE_WEBHOOK_PROVIDER,
8
8
  load: async ()=>{
9
9
  const { createWebhookIntegrationProvider } = await import('@shipfox/api-integration-webhook');
10
+ const integrationProvider = createWebhookIntegrationProvider({
11
+ coreDb: db,
12
+ createIntegrationConnection,
13
+ listIntegrationConnections,
14
+ getIntegrationConnectionById,
15
+ updateIntegrationConnectionLifecycleStatus,
16
+ deleteIntegrationConnection,
17
+ publishIntegrationEventReceived
18
+ });
10
19
  return {
11
- provider: createWebhookIntegrationProvider({
12
- coreDb: db,
13
- createIntegrationConnection,
14
- listIntegrationConnections,
15
- getIntegrationConnectionById,
16
- updateIntegrationConnectionLifecycleStatus,
17
- deleteIntegrationConnection,
18
- publishIntegrationEventReceived
19
- })
20
+ provider: integrationProvider,
21
+ webhookProcessors: integrationProvider.webhookProcessors
20
22
  };
21
23
  }
22
24
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/providers/webhook.ts"],"sourcesContent":["import {config} from '#config.js';\nimport {\n createIntegrationConnection,\n deleteIntegrationConnection,\n getIntegrationConnectionById,\n listIntegrationConnections,\n updateIntegrationConnectionLifecycleStatus,\n} from '#db/connections.js';\nimport {db} from '#db/db.js';\nimport {publishIntegrationEventReceived} from '#db/webhook-deliveries.js';\nimport type {IntegrationProviderModule} from '#providers/types.js';\n\nexport const webhookProviderModule: IntegrationProviderModule = {\n id: 'webhook',\n enabled: config.INTEGRATIONS_ENABLE_WEBHOOK_PROVIDER,\n load: async () => {\n const {createWebhookIntegrationProvider} = await import('@shipfox/api-integration-webhook');\n return {\n provider: createWebhookIntegrationProvider({\n coreDb: db,\n createIntegrationConnection,\n listIntegrationConnections,\n getIntegrationConnectionById,\n updateIntegrationConnectionLifecycleStatus,\n deleteIntegrationConnection,\n publishIntegrationEventReceived,\n }),\n };\n },\n};\n"],"names":["config","createIntegrationConnection","deleteIntegrationConnection","getIntegrationConnectionById","listIntegrationConnections","updateIntegrationConnectionLifecycleStatus","db","publishIntegrationEventReceived","webhookProviderModule","id","enabled","INTEGRATIONS_ENABLE_WEBHOOK_PROVIDER","load","createWebhookIntegrationProvider","provider","coreDb"],"mappings":"AAAA,SAAQA,MAAM,QAAO,aAAa;AAClC,SACEC,2BAA2B,EAC3BC,2BAA2B,EAC3BC,4BAA4B,EAC5BC,0BAA0B,EAC1BC,0CAA0C,QACrC,qBAAqB;AAC5B,SAAQC,EAAE,QAAO,YAAY;AAC7B,SAAQC,+BAA+B,QAAO,4BAA4B;AAG1E,OAAO,MAAMC,wBAAmD;IAC9DC,IAAI;IACJC,SAASV,OAAOW,oCAAoC;IACpDC,MAAM;QACJ,MAAM,EAACC,gCAAgC,EAAC,GAAG,MAAM,MAAM,CAAC;QACxD,OAAO;YACLC,UAAUD,iCAAiC;gBACzCE,QAAQT;gBACRL;gBACAG;gBACAD;gBACAE;gBACAH;gBACAK;YACF;QACF;IACF;AACF,EAAE"}
1
+ {"version":3,"sources":["../../src/providers/webhook.ts"],"sourcesContent":["import {config} from '#config.js';\nimport {\n createIntegrationConnection,\n deleteIntegrationConnection,\n getIntegrationConnectionById,\n listIntegrationConnections,\n updateIntegrationConnectionLifecycleStatus,\n} from '#db/connections.js';\nimport {db} from '#db/db.js';\nimport {publishIntegrationEventReceived} from '#db/webhook-deliveries.js';\nimport type {IntegrationProviderModule} from '#providers/types.js';\n\nexport const webhookProviderModule: IntegrationProviderModule = {\n id: 'webhook',\n enabled: config.INTEGRATIONS_ENABLE_WEBHOOK_PROVIDER,\n load: async () => {\n const {createWebhookIntegrationProvider} = await import('@shipfox/api-integration-webhook');\n const integrationProvider = createWebhookIntegrationProvider({\n coreDb: db,\n createIntegrationConnection,\n listIntegrationConnections,\n getIntegrationConnectionById,\n updateIntegrationConnectionLifecycleStatus,\n deleteIntegrationConnection,\n publishIntegrationEventReceived,\n });\n return {\n provider: integrationProvider,\n webhookProcessors: integrationProvider.webhookProcessors,\n };\n },\n};\n"],"names":["config","createIntegrationConnection","deleteIntegrationConnection","getIntegrationConnectionById","listIntegrationConnections","updateIntegrationConnectionLifecycleStatus","db","publishIntegrationEventReceived","webhookProviderModule","id","enabled","INTEGRATIONS_ENABLE_WEBHOOK_PROVIDER","load","createWebhookIntegrationProvider","integrationProvider","coreDb","provider","webhookProcessors"],"mappings":"AAAA,SAAQA,MAAM,QAAO,aAAa;AAClC,SACEC,2BAA2B,EAC3BC,2BAA2B,EAC3BC,4BAA4B,EAC5BC,0BAA0B,EAC1BC,0CAA0C,QACrC,qBAAqB;AAC5B,SAAQC,EAAE,QAAO,YAAY;AAC7B,SAAQC,+BAA+B,QAAO,4BAA4B;AAG1E,OAAO,MAAMC,wBAAmD;IAC9DC,IAAI;IACJC,SAASV,OAAOW,oCAAoC;IACpDC,MAAM;QACJ,MAAM,EAACC,gCAAgC,EAAC,GAAG,MAAM,MAAM,CAAC;QACxD,MAAMC,sBAAsBD,iCAAiC;YAC3DE,QAAQT;YACRL;YACAG;YACAD;YACAE;YACAH;YACAK;QACF;QACA,OAAO;YACLS,UAAUF;YACVG,mBAAmBH,oBAAoBG,iBAAiB;QAC1D;IACF;AACF,EAAE"}