@shipfox/api-integration-jira 11.0.0 → 12.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/.turbo/turbo-build.log +17 -2
  2. package/CHANGELOG.md +36 -0
  3. package/README.md +58 -2
  4. package/dist/api/client.d.ts +34 -0
  5. package/dist/api/client.d.ts.map +1 -1
  6. package/dist/api/client.js +131 -0
  7. package/dist/api/client.js.map +1 -1
  8. package/dist/config.d.ts +0 -1
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/config.js +1 -4
  11. package/dist/config.js.map +1 -1
  12. package/dist/core/agent-tools-provider.d.ts +26 -0
  13. package/dist/core/agent-tools-provider.d.ts.map +1 -0
  14. package/dist/core/agent-tools-provider.js +130 -0
  15. package/dist/core/agent-tools-provider.js.map +1 -0
  16. package/dist/core/agent-tools.d.ts +16 -0
  17. package/dist/core/agent-tools.d.ts.map +1 -0
  18. package/dist/core/agent-tools.js +441 -0
  19. package/dist/core/agent-tools.js.map +1 -0
  20. package/dist/core/install.d.ts +22 -0
  21. package/dist/core/install.d.ts.map +1 -1
  22. package/dist/core/install.js +68 -20
  23. package/dist/core/install.js.map +1 -1
  24. package/dist/core/tokens.d.ts +2 -0
  25. package/dist/core/tokens.d.ts.map +1 -1
  26. package/dist/core/tokens.js +129 -37
  27. package/dist/core/tokens.js.map +1 -1
  28. package/dist/core/webhook-processor.d.ts +15 -0
  29. package/dist/core/webhook-processor.d.ts.map +1 -0
  30. package/dist/core/webhook-processor.js +137 -0
  31. package/dist/core/webhook-processor.js.map +1 -0
  32. package/dist/core/webhook-registration.d.ts +26 -0
  33. package/dist/core/webhook-registration.d.ts.map +1 -0
  34. package/dist/core/webhook-registration.js +165 -0
  35. package/dist/core/webhook-registration.js.map +1 -0
  36. package/dist/core/webhook.d.ts +18 -0
  37. package/dist/core/webhook.d.ts.map +1 -0
  38. package/dist/core/webhook.js +38 -0
  39. package/dist/core/webhook.js.map +1 -0
  40. package/dist/db/db.d.ts +68 -0
  41. package/dist/db/db.d.ts.map +1 -1
  42. package/dist/db/installations.d.ts +33 -6
  43. package/dist/db/installations.d.ts.map +1 -1
  44. package/dist/db/installations.js +79 -7
  45. package/dist/db/installations.js.map +1 -1
  46. package/dist/db/schema/installations.d.ts +34 -0
  47. package/dist/db/schema/installations.d.ts.map +1 -1
  48. package/dist/db/schema/installations.js +12 -2
  49. package/dist/db/schema/installations.js.map +1 -1
  50. package/dist/index.d.ts +34 -7
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +76 -8
  53. package/dist/index.js.map +1 -1
  54. package/dist/presentation/routes/install.d.ts +22 -0
  55. package/dist/presentation/routes/install.d.ts.map +1 -1
  56. package/dist/presentation/routes/install.js.map +1 -1
  57. package/dist/presentation/routes/webhooks.d.ts +8 -0
  58. package/dist/presentation/routes/webhooks.d.ts.map +1 -0
  59. package/dist/presentation/routes/webhooks.js +108 -0
  60. package/dist/presentation/routes/webhooks.js.map +1 -0
  61. package/dist/temporal/activities/index.d.ts +2 -0
  62. package/dist/temporal/activities/index.d.ts.map +1 -0
  63. package/dist/temporal/activities/index.js +3 -0
  64. package/dist/temporal/activities/index.js.map +1 -0
  65. package/dist/temporal/activities/refresh-jira-tokens.d.ts +20 -0
  66. package/dist/temporal/activities/refresh-jira-tokens.d.ts.map +1 -0
  67. package/dist/temporal/activities/refresh-jira-tokens.js +58 -0
  68. package/dist/temporal/activities/refresh-jira-tokens.js.map +1 -0
  69. package/dist/temporal/constants.d.ts +6 -0
  70. package/dist/temporal/constants.d.ts.map +1 -0
  71. package/dist/temporal/constants.js +8 -0
  72. package/dist/temporal/constants.js.map +1 -0
  73. package/dist/temporal/worker.d.ts +9 -0
  74. package/dist/temporal/worker.d.ts.map +1 -0
  75. package/dist/temporal/worker.js +26 -0
  76. package/dist/temporal/worker.js.map +1 -0
  77. package/dist/temporal/workflows/index.bundle.js +25225 -0
  78. package/dist/temporal/workflows/index.bundle.meta.json +1 -0
  79. package/dist/temporal/workflows/index.d.ts +2 -0
  80. package/dist/temporal/workflows/index.d.ts.map +1 -0
  81. package/dist/temporal/workflows/index.js +3 -0
  82. package/dist/temporal/workflows/index.js.map +1 -0
  83. package/dist/temporal/workflows/refresh-jira-tokens-cron.d.ts +2 -0
  84. package/dist/temporal/workflows/refresh-jira-tokens-cron.d.ts.map +1 -0
  85. package/dist/temporal/workflows/refresh-jira-tokens-cron.js +20 -0
  86. package/dist/temporal/workflows/refresh-jira-tokens-cron.js.map +1 -0
  87. package/dist/tsconfig.test.tsbuildinfo +1 -1
  88. package/drizzle/0001_jira-token-refresh-state.sql +12 -0
  89. package/drizzle/meta/0001_snapshot.json +245 -0
  90. package/drizzle/meta/_journal.json +7 -0
  91. package/package.json +17 -8
  92. package/src/api/client.test.ts +236 -9
  93. package/src/api/client.ts +192 -0
  94. package/src/config.test.ts +0 -1
  95. package/src/config.ts +1 -4
  96. package/src/core/agent-tools-provider.test.ts +299 -0
  97. package/src/core/agent-tools-provider.ts +175 -0
  98. package/src/core/agent-tools.ts +450 -0
  99. package/src/core/install.test.ts +179 -0
  100. package/src/core/install.ts +85 -19
  101. package/src/core/tokens-refresh.test.ts +223 -4
  102. package/src/core/tokens.test.ts +1 -1
  103. package/src/core/tokens.ts +153 -33
  104. package/src/core/webhook-processor.test.ts +418 -0
  105. package/src/core/webhook-processor.ts +166 -0
  106. package/src/core/webhook-registration.test.ts +375 -0
  107. package/src/core/webhook-registration.ts +216 -0
  108. package/src/core/webhook.ts +69 -0
  109. package/src/db/installations.test.ts +148 -2
  110. package/src/db/installations.ts +133 -7
  111. package/src/db/schema/installations.ts +21 -1
  112. package/src/index.test.ts +42 -0
  113. package/src/index.ts +148 -5
  114. package/src/presentation/routes/install.ts +12 -0
  115. package/src/presentation/routes/webhooks.test.ts +132 -0
  116. package/src/presentation/routes/webhooks.ts +115 -0
  117. package/src/temporal/activities/index.ts +5 -0
  118. package/src/temporal/activities/refresh-jira-tokens.test.ts +156 -0
  119. package/src/temporal/activities/refresh-jira-tokens.ts +88 -0
  120. package/src/temporal/constants.ts +8 -0
  121. package/src/temporal/worker.ts +39 -0
  122. package/src/temporal/workflows/index.ts +1 -0
  123. package/src/temporal/workflows/refresh-jira-tokens-cron.ts +19 -0
  124. package/test/env.ts +0 -1
  125. package/test/factories/jira-installation.ts +4 -0
  126. package/tsconfig.build.tsbuildinfo +1 -1
package/dist/db/db.d.ts CHANGED
@@ -197,6 +197,40 @@ export declare const schema: {
197
197
  identity: undefined;
198
198
  generated: undefined;
199
199
  }, {}, {}>;
200
+ refreshTokenLastUsedAt: import("drizzle-orm/pg-core").PgColumn<{
201
+ name: "refresh_token_last_used_at";
202
+ tableName: "installations";
203
+ dataType: "date";
204
+ columnType: "PgTimestamp";
205
+ data: Date;
206
+ driverParam: string;
207
+ notNull: true;
208
+ hasDefault: true;
209
+ isPrimaryKey: false;
210
+ isAutoincrement: false;
211
+ hasRuntimeDefault: false;
212
+ enumValues: undefined;
213
+ baseColumn: never;
214
+ identity: undefined;
215
+ generated: undefined;
216
+ }, {}, {}>;
217
+ refreshTokenLastAttemptedAt: import("drizzle-orm/pg-core").PgColumn<{
218
+ name: "refresh_token_last_attempted_at";
219
+ tableName: "installations";
220
+ dataType: "date";
221
+ columnType: "PgTimestamp";
222
+ data: Date;
223
+ driverParam: string;
224
+ notNull: true;
225
+ hasDefault: true;
226
+ isPrimaryKey: false;
227
+ isAutoincrement: false;
228
+ hasRuntimeDefault: false;
229
+ enumValues: undefined;
230
+ baseColumn: never;
231
+ identity: undefined;
232
+ generated: undefined;
233
+ }, {}, {}>;
200
234
  createdAt: import("drizzle-orm/pg-core").PgColumn<{
201
235
  name: "created_at";
202
236
  tableName: "installations";
@@ -527,6 +561,40 @@ export declare function db(): NodePgDatabase<{
527
561
  identity: undefined;
528
562
  generated: undefined;
529
563
  }, {}, {}>;
564
+ refreshTokenLastUsedAt: import("drizzle-orm/pg-core").PgColumn<{
565
+ name: "refresh_token_last_used_at";
566
+ tableName: "installations";
567
+ dataType: "date";
568
+ columnType: "PgTimestamp";
569
+ data: Date;
570
+ driverParam: string;
571
+ notNull: true;
572
+ hasDefault: true;
573
+ isPrimaryKey: false;
574
+ isAutoincrement: false;
575
+ hasRuntimeDefault: false;
576
+ enumValues: undefined;
577
+ baseColumn: never;
578
+ identity: undefined;
579
+ generated: undefined;
580
+ }, {}, {}>;
581
+ refreshTokenLastAttemptedAt: import("drizzle-orm/pg-core").PgColumn<{
582
+ name: "refresh_token_last_attempted_at";
583
+ tableName: "installations";
584
+ dataType: "date";
585
+ columnType: "PgTimestamp";
586
+ data: Date;
587
+ driverParam: string;
588
+ notNull: true;
589
+ hasDefault: true;
590
+ isPrimaryKey: false;
591
+ isAutoincrement: false;
592
+ hasRuntimeDefault: false;
593
+ enumValues: undefined;
594
+ baseColumn: never;
595
+ identity: undefined;
596
+ generated: undefined;
597
+ }, {}, {}>;
530
598
  createdAt: import("drizzle-orm/pg-core").PgColumn<{
531
599
  name: "created_at";
532
600
  tableName: "installations";
@@ -1 +1 @@
1
- {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/db/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAKnE,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAA6C,CAAC;AAIjE,wBAAgB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAGjB;AAED,wBAAgB,OAAO,IAAI,IAAI,CAE9B"}
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/db/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAKnE,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAA6C,CAAC;AAIjE,wBAAgB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAGjB;AAED,wBAAgB,OAAO,IAAI,IAAI,CAE9B"}
@@ -1,3 +1,11 @@
1
+ export type JiraInstallationLock = <T>(lockKey: string, fn: () => Promise<T>) => Promise<T>;
2
+ type JiraAdvisoryLockAttempt<T> = {
3
+ acquired: true;
4
+ value: T;
5
+ } | {
6
+ acquired: false;
7
+ };
8
+ export declare function withJiraInstallationLock<T>(lockKey: string, fn: () => Promise<T>): Promise<Awaited<T>>;
1
9
  export type JiraInstallationStatus = 'installed' | 'revoked';
2
10
  export interface JiraInstallation {
3
11
  id: string;
@@ -11,6 +19,8 @@ export interface JiraInstallation {
11
19
  webhookExpiresAt: Date | null;
12
20
  status: JiraInstallationStatus;
13
21
  tokenExpiresAt: Date | null;
22
+ refreshTokenLastUsedAt: Date;
23
+ refreshTokenLastAttemptedAt: Date;
14
24
  createdAt: Date;
15
25
  updatedAt: Date;
16
26
  }
@@ -25,12 +35,19 @@ export interface UpsertJiraInstallationParams {
25
35
  webhookExpiresAt?: Date | null | undefined;
26
36
  status: JiraInstallationStatus;
27
37
  tokenExpiresAt?: Date | null | undefined;
38
+ refreshTokenLastUsedAt?: Date | undefined;
39
+ refreshTokenLastAttemptedAt?: Date | undefined;
28
40
  }
29
41
  export interface UpdateJiraInstallationTokenExpiryParams {
30
42
  connectionId: string;
31
43
  tokenExpiresAt: Date | null;
32
44
  scopes?: string[] | undefined;
33
45
  }
46
+ export interface UpdateJiraInstallationWebhookParams {
47
+ connectionId: string;
48
+ webhookIds: number[];
49
+ webhookExpiresAt: Date | null;
50
+ }
34
51
  export declare function upsertJiraInstallation(params: UpsertJiraInstallationParams, options?: {
35
52
  tx?: unknown;
36
53
  }): Promise<JiraInstallation>;
@@ -40,16 +57,25 @@ export declare function getJiraInstallationByCloudId(cloudId: string, options?:
40
57
  export declare function updateJiraInstallationTokenExpiry(params: UpdateJiraInstallationTokenExpiryParams, options?: {
41
58
  tx?: unknown;
42
59
  }): Promise<JiraInstallation | undefined>;
60
+ export declare function markJiraInstallationTokenRefreshAttempt(connectionId: string, options?: {
61
+ tx?: unknown;
62
+ }): Promise<JiraInstallation | undefined>;
63
+ export declare function listJiraInstallationsDueForTokenRefresh(params: {
64
+ before: Date;
65
+ limit?: number | undefined;
66
+ }, options?: {
67
+ tx?: unknown;
68
+ }): Promise<JiraInstallation[]>;
69
+ export declare function updateJiraInstallationWebhook(params: UpdateJiraInstallationWebhookParams, options?: {
70
+ tx?: unknown;
71
+ }): Promise<JiraInstallation | undefined>;
43
72
  export declare function deleteJiraInstallationByConnectionId(connectionId: string, options?: {
44
73
  tx?: unknown;
45
74
  }): Promise<boolean>;
46
- export type JiraRefreshLockResult<T> = {
47
- acquired: true;
48
- value: T;
49
- } | {
50
- acquired: false;
51
- };
75
+ export type JiraRefreshLockResult<T> = JiraAdvisoryLockAttempt<T>;
52
76
  export declare function withJiraRefreshLock<T>(connectionId: string, fn: () => Promise<T>): Promise<JiraRefreshLockResult<T>>;
77
+ export declare function withJiraRefreshLockAndWait<T>(connectionId: string, fn: () => Promise<T>): Promise<T>;
78
+ export declare function withJiraWebhookRegistrationLock<T>(lockKey: string, fn: (tx?: unknown) => Promise<T>): Promise<T>;
53
79
  export declare function getJiraInstallationByConnectionId(connectionId: string, options?: {
54
80
  tx?: unknown;
55
81
  }): Promise<JiraInstallation | undefined>;
@@ -59,4 +85,5 @@ export declare function getJiraInstallationByWebhookId(webhookId: number, option
59
85
  export declare function markJiraInstallationRevoked(connectionId: string, options?: {
60
86
  tx?: unknown;
61
87
  }): Promise<JiraInstallation | undefined>;
88
+ export {};
62
89
  //# sourceMappingURL=installations.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"installations.d.ts","sourceRoot":"","sources":["../../src/db/installations.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"installations.d.ts","sourceRoot":"","sources":["../../src/db/installations.ts"],"names":[],"mappings":"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"}
@@ -1,13 +1,36 @@
1
1
  import { isUniqueViolation } from '@shipfox/node-drizzle';
2
- import { pgClient } from '@shipfox/node-postgres';
3
- import { eq, sql } from 'drizzle-orm';
2
+ import { pgClient, withPostgresSession } from '@shipfox/node-postgres';
3
+ import { and, asc, eq, lte, sql } from 'drizzle-orm';
4
4
  import { JiraConnectionAlreadyLinkedError, JiraInstallationAlreadyLinkedError, JiraInstallationSiteMismatchError } from '#core/errors.js';
5
5
  import { db } from './db.js';
6
6
  import { jiraInstallations, toJiraInstallation } from './schema/installations.js';
7
+ const JIRA_ADVISORY_LOCK_RETRY_DELAY_MS = 100;
8
+ const JIRA_ADVISORY_LOCK_MAX_RETRY_DELAY_MS = 1_000;
9
+ const JIRA_ADVISORY_LOCK_TIMEOUT_MS = 30_000;
10
+ export function withJiraInstallationLock(lockKey, fn) {
11
+ const advisoryKey = `jira-installation:${lockKey}`;
12
+ return withJiraAdvisoryLockWait(`Jira installation lock: ${lockKey}`, ()=>withPostgresSession(async (client)=>{
13
+ const lock = await client.query('SELECT pg_try_advisory_lock(hashtext($1)) AS acquired', [
14
+ advisoryKey
15
+ ]);
16
+ if (lock.rows[0]?.acquired !== true) return {
17
+ acquired: false
18
+ };
19
+ try {
20
+ return {
21
+ acquired: true,
22
+ value: await fn()
23
+ };
24
+ } finally{
25
+ await client.query('SELECT pg_advisory_unlock(hashtext($1))', [
26
+ advisoryKey
27
+ ]);
28
+ }
29
+ }));
30
+ }
7
31
  export async function upsertJiraInstallation(params, options = {}) {
8
32
  const executor = options.tx ?? db();
9
33
  const now = new Date();
10
- const webhookIds = params.webhookIds ?? [];
11
34
  let row;
12
35
  try {
13
36
  [row] = await executor.insert(jiraInstallations).values({
@@ -17,10 +40,12 @@ export async function upsertJiraInstallation(params, options = {}) {
17
40
  siteName: params.siteName,
18
41
  authorizingAccountId: params.authorizingAccountId,
19
42
  scopes: params.scopes,
20
- webhookIds,
43
+ webhookIds: params.webhookIds ?? [],
21
44
  webhookExpiresAt: params.webhookExpiresAt ?? null,
22
45
  status: params.status,
23
- tokenExpiresAt: params.tokenExpiresAt ?? null
46
+ tokenExpiresAt: params.tokenExpiresAt ?? null,
47
+ refreshTokenLastUsedAt: params.refreshTokenLastUsedAt ?? now,
48
+ refreshTokenLastAttemptedAt: params.refreshTokenLastAttemptedAt ?? now
24
49
  }).onConflictDoUpdate({
25
50
  target: jiraInstallations.connectionId,
26
51
  setWhere: eq(jiraInstallations.cloudId, params.cloudId),
@@ -30,10 +55,16 @@ export async function upsertJiraInstallation(params, options = {}) {
30
55
  siteName: params.siteName,
31
56
  authorizingAccountId: params.authorizingAccountId,
32
57
  scopes: params.scopes,
33
- webhookIds,
34
- webhookExpiresAt: params.webhookExpiresAt ?? null,
58
+ ...params.webhookIds === undefined ? {} : {
59
+ webhookIds: params.webhookIds
60
+ },
61
+ ...params.webhookExpiresAt === undefined ? {} : {
62
+ webhookExpiresAt: params.webhookExpiresAt
63
+ },
35
64
  status: params.status,
36
65
  tokenExpiresAt: params.tokenExpiresAt ?? null,
66
+ refreshTokenLastUsedAt: params.refreshTokenLastUsedAt ?? now,
67
+ refreshTokenLastAttemptedAt: params.refreshTokenLastAttemptedAt ?? now,
37
68
  updatedAt: now
38
69
  }
39
70
  }).returning();
@@ -57,11 +88,35 @@ export async function getJiraInstallationByCloudId(cloudId, options = {}) {
57
88
  }
58
89
  export async function updateJiraInstallationTokenExpiry(params, options = {}) {
59
90
  const executor = options.tx ?? db();
91
+ const now = new Date();
60
92
  const [row] = await executor.update(jiraInstallations).set({
61
93
  tokenExpiresAt: params.tokenExpiresAt,
94
+ refreshTokenLastUsedAt: now,
95
+ refreshTokenLastAttemptedAt: now,
62
96
  ...params.scopes === undefined ? {} : {
63
97
  scopes: params.scopes
64
98
  },
99
+ updatedAt: now
100
+ }).where(eq(jiraInstallations.connectionId, params.connectionId)).returning();
101
+ return row ? toJiraInstallation(row) : undefined;
102
+ }
103
+ export async function markJiraInstallationTokenRefreshAttempt(connectionId, options = {}) {
104
+ const executor = options.tx ?? db();
105
+ const [row] = await executor.update(jiraInstallations).set({
106
+ refreshTokenLastAttemptedAt: new Date()
107
+ }).where(eq(jiraInstallations.connectionId, connectionId)).returning();
108
+ return row ? toJiraInstallation(row) : undefined;
109
+ }
110
+ export async function listJiraInstallationsDueForTokenRefresh(params, options = {}) {
111
+ const executor = options.tx ?? db();
112
+ const rows = await executor.select().from(jiraInstallations).where(and(eq(jiraInstallations.status, 'installed'), lte(jiraInstallations.refreshTokenLastUsedAt, params.before))).orderBy(asc(jiraInstallations.refreshTokenLastAttemptedAt)).limit(params.limit ?? 100);
113
+ return rows.map(toJiraInstallation);
114
+ }
115
+ export async function updateJiraInstallationWebhook(params, options = {}) {
116
+ const executor = options.tx ?? db();
117
+ const [row] = await executor.update(jiraInstallations).set({
118
+ webhookIds: params.webhookIds,
119
+ webhookExpiresAt: params.webhookExpiresAt,
65
120
  updatedAt: new Date()
66
121
  }).where(eq(jiraInstallations.connectionId, params.connectionId)).returning();
67
122
  return row ? toJiraInstallation(row) : undefined;
@@ -74,6 +129,12 @@ export async function deleteJiraInstallationByConnectionId(connectionId, options
74
129
  export function withJiraRefreshLock(connectionId, fn) {
75
130
  return withJiraRefreshLockClient(connectionId, fn);
76
131
  }
132
+ export function withJiraRefreshLockAndWait(connectionId, fn) {
133
+ return withJiraAdvisoryLockWait(`Jira refresh lock: ${connectionId}`, ()=>withJiraRefreshLock(connectionId, fn));
134
+ }
135
+ export function withJiraWebhookRegistrationLock(lockKey, fn) {
136
+ return withJiraInstallationLock(lockKey, ()=>fn());
137
+ }
77
138
  async function withJiraRefreshLockClient(connectionId, fn) {
78
139
  const client = await pgClient().connect();
79
140
  let acquired = false;
@@ -99,6 +160,17 @@ async function withJiraRefreshLockClient(connectionId, fn) {
99
160
  }
100
161
  }
101
162
  }
163
+ async function withJiraAdvisoryLockWait(lockDescription, attemptLock) {
164
+ const deadline = Date.now() + JIRA_ADVISORY_LOCK_TIMEOUT_MS;
165
+ let retryDelayMs = JIRA_ADVISORY_LOCK_RETRY_DELAY_MS;
166
+ while(true){
167
+ const attempt = await attemptLock();
168
+ if (attempt.acquired) return attempt.value;
169
+ if (Date.now() >= deadline) throw new Error(`Timed out waiting for ${lockDescription}`);
170
+ await new Promise((resolve)=>setTimeout(resolve, retryDelayMs));
171
+ retryDelayMs = Math.min(retryDelayMs * 2, JIRA_ADVISORY_LOCK_MAX_RETRY_DELAY_MS);
172
+ }
173
+ }
102
174
  export async function getJiraInstallationByConnectionId(connectionId, options = {}) {
103
175
  const executor = options.tx ?? db();
104
176
  const rows = await executor.select().from(jiraInstallations).where(eq(jiraInstallations.connectionId, connectionId)).limit(1);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/db/installations.ts"],"sourcesContent":["import {isUniqueViolation} from '@shipfox/node-drizzle';\nimport {pgClient} from '@shipfox/node-postgres';\nimport {eq, sql} from 'drizzle-orm';\nimport {\n JiraConnectionAlreadyLinkedError,\n JiraInstallationAlreadyLinkedError,\n JiraInstallationSiteMismatchError,\n} from '#core/errors.js';\nimport {db} from './db.js';\nimport {jiraInstallations, toJiraInstallation} from './schema/installations.js';\n\nexport type JiraInstallationStatus = 'installed' | 'revoked';\n\nexport interface JiraInstallation {\n id: string;\n connectionId: string;\n cloudId: string;\n siteUrl: string;\n siteName: string;\n authorizingAccountId: string;\n scopes: string[];\n webhookIds: number[];\n webhookExpiresAt: Date | null;\n status: JiraInstallationStatus;\n tokenExpiresAt: Date | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface UpsertJiraInstallationParams {\n connectionId: string;\n cloudId: string;\n siteUrl: string;\n siteName: string;\n authorizingAccountId: string;\n scopes: string[];\n webhookIds?: number[] | undefined;\n webhookExpiresAt?: Date | null | undefined;\n status: JiraInstallationStatus;\n tokenExpiresAt?: Date | null | undefined;\n}\n\nexport interface UpdateJiraInstallationTokenExpiryParams {\n connectionId: string;\n tokenExpiresAt: Date | null;\n scopes?: string[] | undefined;\n}\n\ntype JiraDb = ReturnType<typeof db>;\ntype JiraTx = Parameters<Parameters<JiraDb['transaction']>[0]>[0];\n\nexport async function upsertJiraInstallation(\n params: UpsertJiraInstallationParams,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const now = new Date();\n const webhookIds = params.webhookIds ?? [];\n let row: typeof jiraInstallations.$inferSelect | undefined;\n try {\n [row] = await executor\n .insert(jiraInstallations)\n .values({\n connectionId: params.connectionId,\n cloudId: params.cloudId,\n siteUrl: params.siteUrl,\n siteName: params.siteName,\n authorizingAccountId: params.authorizingAccountId,\n scopes: params.scopes,\n webhookIds,\n webhookExpiresAt: params.webhookExpiresAt ?? null,\n status: params.status,\n tokenExpiresAt: params.tokenExpiresAt ?? null,\n })\n .onConflictDoUpdate({\n target: jiraInstallations.connectionId,\n setWhere: eq(jiraInstallations.cloudId, params.cloudId),\n set: {\n cloudId: params.cloudId,\n siteUrl: params.siteUrl,\n siteName: params.siteName,\n authorizingAccountId: params.authorizingAccountId,\n scopes: params.scopes,\n webhookIds,\n webhookExpiresAt: params.webhookExpiresAt ?? null,\n status: params.status,\n tokenExpiresAt: params.tokenExpiresAt ?? null,\n updatedAt: now,\n },\n })\n .returning();\n } catch (error) {\n if (isUniqueViolation(error, 'integrations_jira_installations_connection_unique')) {\n throw new JiraConnectionAlreadyLinkedError(params.connectionId);\n }\n if (isUniqueViolation(error, 'integrations_jira_installations_cloud_id_unique')) {\n throw new JiraInstallationAlreadyLinkedError(params.cloudId);\n }\n throw error;\n }\n\n if (!row) throw new JiraInstallationSiteMismatchError(params.connectionId, params.cloudId);\n return toJiraInstallation(row);\n}\n\nexport async function getJiraInstallationByCloudId(\n cloudId: string,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const rows = await executor\n .select()\n .from(jiraInstallations)\n .where(eq(jiraInstallations.cloudId, cloudId))\n .limit(1);\n const row = rows[0];\n return row ? toJiraInstallation(row) : undefined;\n}\n\nexport async function updateJiraInstallationTokenExpiry(\n params: UpdateJiraInstallationTokenExpiryParams,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const [row] = await executor\n .update(jiraInstallations)\n .set({\n tokenExpiresAt: params.tokenExpiresAt,\n ...(params.scopes === undefined ? {} : {scopes: params.scopes}),\n updatedAt: new Date(),\n })\n .where(eq(jiraInstallations.connectionId, params.connectionId))\n .returning();\n return row ? toJiraInstallation(row) : undefined;\n}\n\nexport async function deleteJiraInstallationByConnectionId(\n connectionId: string,\n options: {tx?: unknown} = {},\n): Promise<boolean> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const result = await executor\n .delete(jiraInstallations)\n .where(eq(jiraInstallations.connectionId, connectionId));\n return (result.rowCount ?? 0) > 0;\n}\n\nexport type JiraRefreshLockResult<T> = {acquired: true; value: T} | {acquired: false};\n\nexport function withJiraRefreshLock<T>(\n connectionId: string,\n fn: () => Promise<T>,\n): Promise<JiraRefreshLockResult<T>> {\n return withJiraRefreshLockClient(connectionId, fn);\n}\n\nasync function withJiraRefreshLockClient<T>(\n connectionId: string,\n fn: () => Promise<T>,\n): Promise<JiraRefreshLockResult<T>> {\n const client = await pgClient().connect();\n let acquired = false;\n try {\n const lock = await client.query<{acquired: boolean}>(\n 'SELECT pg_try_advisory_lock(hashtext($1)) AS acquired',\n [connectionId],\n );\n acquired = lock.rows[0]?.acquired === true;\n if (!acquired) return {acquired: false};\n return {acquired: true, value: await fn()};\n } finally {\n try {\n if (acquired) await client.query('SELECT pg_advisory_unlock(hashtext($1))', [connectionId]);\n } finally {\n client.release();\n }\n }\n}\n\nexport async function getJiraInstallationByConnectionId(\n connectionId: string,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const rows = await executor\n .select()\n .from(jiraInstallations)\n .where(eq(jiraInstallations.connectionId, connectionId))\n .limit(1);\n const row = rows[0];\n if (!row) return undefined;\n return toJiraInstallation(row);\n}\n\nexport async function getJiraInstallationByWebhookId(\n webhookId: number,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const rows = await executor\n .select()\n .from(jiraInstallations)\n .where(sql`${jiraInstallations.webhookIds} @> ${JSON.stringify([webhookId])}::jsonb`)\n .limit(1);\n const row = rows[0];\n if (!row) return undefined;\n return toJiraInstallation(row);\n}\n\nexport async function markJiraInstallationRevoked(\n connectionId: string,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const [row] = await executor\n .update(jiraInstallations)\n .set({status: 'revoked', updatedAt: new Date()})\n .where(eq(jiraInstallations.connectionId, connectionId))\n .returning();\n if (!row) return undefined;\n return toJiraInstallation(row);\n}\n"],"names":["isUniqueViolation","pgClient","eq","sql","JiraConnectionAlreadyLinkedError","JiraInstallationAlreadyLinkedError","JiraInstallationSiteMismatchError","db","jiraInstallations","toJiraInstallation","upsertJiraInstallation","params","options","executor","tx","now","Date","webhookIds","row","insert","values","connectionId","cloudId","siteUrl","siteName","authorizingAccountId","scopes","webhookExpiresAt","status","tokenExpiresAt","onConflictDoUpdate","target","setWhere","set","updatedAt","returning","error","getJiraInstallationByCloudId","rows","select","from","where","limit","undefined","updateJiraInstallationTokenExpiry","update","deleteJiraInstallationByConnectionId","result","delete","rowCount","withJiraRefreshLock","fn","withJiraRefreshLockClient","client","connect","acquired","lock","query","value","release","getJiraInstallationByConnectionId","getJiraInstallationByWebhookId","webhookId","JSON","stringify","markJiraInstallationRevoked"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/db/installations.ts"],"sourcesContent":["import {isUniqueViolation} from '@shipfox/node-drizzle';\nimport {pgClient, withPostgresSession} from '@shipfox/node-postgres';\nimport {and, asc, eq, lte, sql} from 'drizzle-orm';\nimport {\n JiraConnectionAlreadyLinkedError,\n JiraInstallationAlreadyLinkedError,\n JiraInstallationSiteMismatchError,\n} from '#core/errors.js';\nimport {db} from './db.js';\nimport {jiraInstallations, toJiraInstallation} from './schema/installations.js';\n\nexport type JiraInstallationLock = <T>(lockKey: string, fn: () => Promise<T>) => Promise<T>;\n\nconst JIRA_ADVISORY_LOCK_RETRY_DELAY_MS = 100;\nconst JIRA_ADVISORY_LOCK_MAX_RETRY_DELAY_MS = 1_000;\nconst JIRA_ADVISORY_LOCK_TIMEOUT_MS = 30_000;\n\ntype JiraAdvisoryLockAttempt<T> = {acquired: true; value: T} | {acquired: false};\n\nexport function withJiraInstallationLock<T>(lockKey: string, fn: () => Promise<T>) {\n const advisoryKey = `jira-installation:${lockKey}`;\n return withJiraAdvisoryLockWait(`Jira installation lock: ${lockKey}`, () =>\n withPostgresSession(async (client) => {\n const lock = await client.query<{acquired: boolean}>(\n 'SELECT pg_try_advisory_lock(hashtext($1)) AS acquired',\n [advisoryKey],\n );\n if (lock.rows[0]?.acquired !== true) return {acquired: false as const};\n\n try {\n return {acquired: true as const, value: await fn()};\n } finally {\n await client.query('SELECT pg_advisory_unlock(hashtext($1))', [advisoryKey]);\n }\n }),\n );\n}\n\nexport type JiraInstallationStatus = 'installed' | 'revoked';\n\nexport interface JiraInstallation {\n id: string;\n connectionId: string;\n cloudId: string;\n siteUrl: string;\n siteName: string;\n authorizingAccountId: string;\n scopes: string[];\n webhookIds: number[];\n webhookExpiresAt: Date | null;\n status: JiraInstallationStatus;\n tokenExpiresAt: Date | null;\n refreshTokenLastUsedAt: Date;\n refreshTokenLastAttemptedAt: Date;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface UpsertJiraInstallationParams {\n connectionId: string;\n cloudId: string;\n siteUrl: string;\n siteName: string;\n authorizingAccountId: string;\n scopes: string[];\n webhookIds?: number[] | undefined;\n webhookExpiresAt?: Date | null | undefined;\n status: JiraInstallationStatus;\n tokenExpiresAt?: Date | null | undefined;\n refreshTokenLastUsedAt?: Date | undefined;\n refreshTokenLastAttemptedAt?: Date | undefined;\n}\n\nexport interface UpdateJiraInstallationTokenExpiryParams {\n connectionId: string;\n tokenExpiresAt: Date | null;\n scopes?: string[] | undefined;\n}\n\nexport interface UpdateJiraInstallationWebhookParams {\n connectionId: string;\n webhookIds: number[];\n webhookExpiresAt: Date | null;\n}\n\ntype JiraDb = ReturnType<typeof db>;\ntype JiraTx = Parameters<Parameters<JiraDb['transaction']>[0]>[0];\n\nexport async function upsertJiraInstallation(\n params: UpsertJiraInstallationParams,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const now = new Date();\n let row: typeof jiraInstallations.$inferSelect | undefined;\n try {\n [row] = await executor\n .insert(jiraInstallations)\n .values({\n connectionId: params.connectionId,\n cloudId: params.cloudId,\n siteUrl: params.siteUrl,\n siteName: params.siteName,\n authorizingAccountId: params.authorizingAccountId,\n scopes: params.scopes,\n webhookIds: params.webhookIds ?? [],\n webhookExpiresAt: params.webhookExpiresAt ?? null,\n status: params.status,\n tokenExpiresAt: params.tokenExpiresAt ?? null,\n refreshTokenLastUsedAt: params.refreshTokenLastUsedAt ?? now,\n refreshTokenLastAttemptedAt: params.refreshTokenLastAttemptedAt ?? now,\n })\n .onConflictDoUpdate({\n target: jiraInstallations.connectionId,\n setWhere: eq(jiraInstallations.cloudId, params.cloudId),\n set: {\n cloudId: params.cloudId,\n siteUrl: params.siteUrl,\n siteName: params.siteName,\n authorizingAccountId: params.authorizingAccountId,\n scopes: params.scopes,\n ...(params.webhookIds === undefined ? {} : {webhookIds: params.webhookIds}),\n ...(params.webhookExpiresAt === undefined\n ? {}\n : {webhookExpiresAt: params.webhookExpiresAt}),\n status: params.status,\n tokenExpiresAt: params.tokenExpiresAt ?? null,\n refreshTokenLastUsedAt: params.refreshTokenLastUsedAt ?? now,\n refreshTokenLastAttemptedAt: params.refreshTokenLastAttemptedAt ?? now,\n updatedAt: now,\n },\n })\n .returning();\n } catch (error) {\n if (isUniqueViolation(error, 'integrations_jira_installations_connection_unique')) {\n throw new JiraConnectionAlreadyLinkedError(params.connectionId);\n }\n if (isUniqueViolation(error, 'integrations_jira_installations_cloud_id_unique')) {\n throw new JiraInstallationAlreadyLinkedError(params.cloudId);\n }\n throw error;\n }\n\n if (!row) throw new JiraInstallationSiteMismatchError(params.connectionId, params.cloudId);\n return toJiraInstallation(row);\n}\n\nexport async function getJiraInstallationByCloudId(\n cloudId: string,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const rows = await executor\n .select()\n .from(jiraInstallations)\n .where(eq(jiraInstallations.cloudId, cloudId))\n .limit(1);\n const row = rows[0];\n return row ? toJiraInstallation(row) : undefined;\n}\n\nexport async function updateJiraInstallationTokenExpiry(\n params: UpdateJiraInstallationTokenExpiryParams,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const now = new Date();\n const [row] = await executor\n .update(jiraInstallations)\n .set({\n tokenExpiresAt: params.tokenExpiresAt,\n refreshTokenLastUsedAt: now,\n refreshTokenLastAttemptedAt: now,\n ...(params.scopes === undefined ? {} : {scopes: params.scopes}),\n updatedAt: now,\n })\n .where(eq(jiraInstallations.connectionId, params.connectionId))\n .returning();\n return row ? toJiraInstallation(row) : undefined;\n}\n\nexport async function markJiraInstallationTokenRefreshAttempt(\n connectionId: string,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const [row] = await executor\n .update(jiraInstallations)\n .set({refreshTokenLastAttemptedAt: new Date()})\n .where(eq(jiraInstallations.connectionId, connectionId))\n .returning();\n return row ? toJiraInstallation(row) : undefined;\n}\n\nexport async function listJiraInstallationsDueForTokenRefresh(\n params: {before: Date; limit?: number | undefined},\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation[]> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const rows = await executor\n .select()\n .from(jiraInstallations)\n .where(\n and(\n eq(jiraInstallations.status, 'installed'),\n lte(jiraInstallations.refreshTokenLastUsedAt, params.before),\n ),\n )\n .orderBy(asc(jiraInstallations.refreshTokenLastAttemptedAt))\n .limit(params.limit ?? 100);\n return rows.map(toJiraInstallation);\n}\n\nexport async function updateJiraInstallationWebhook(\n params: UpdateJiraInstallationWebhookParams,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const [row] = await executor\n .update(jiraInstallations)\n .set({\n webhookIds: params.webhookIds,\n webhookExpiresAt: params.webhookExpiresAt,\n updatedAt: new Date(),\n })\n .where(eq(jiraInstallations.connectionId, params.connectionId))\n .returning();\n return row ? toJiraInstallation(row) : undefined;\n}\n\nexport async function deleteJiraInstallationByConnectionId(\n connectionId: string,\n options: {tx?: unknown} = {},\n): Promise<boolean> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const result = await executor\n .delete(jiraInstallations)\n .where(eq(jiraInstallations.connectionId, connectionId));\n return (result.rowCount ?? 0) > 0;\n}\n\nexport type JiraRefreshLockResult<T> = JiraAdvisoryLockAttempt<T>;\n\nexport function withJiraRefreshLock<T>(\n connectionId: string,\n fn: () => Promise<T>,\n): Promise<JiraRefreshLockResult<T>> {\n return withJiraRefreshLockClient(connectionId, fn);\n}\n\nexport function withJiraRefreshLockAndWait<T>(\n connectionId: string,\n fn: () => Promise<T>,\n): Promise<T> {\n return withJiraAdvisoryLockWait(`Jira refresh lock: ${connectionId}`, () =>\n withJiraRefreshLock(connectionId, fn),\n );\n}\n\nexport function withJiraWebhookRegistrationLock<T>(\n lockKey: string,\n fn: (tx?: unknown) => Promise<T>,\n): Promise<T> {\n return withJiraInstallationLock(lockKey, () => fn());\n}\n\nasync function withJiraRefreshLockClient<T>(\n connectionId: string,\n fn: () => Promise<T>,\n): Promise<JiraRefreshLockResult<T>> {\n const client = await pgClient().connect();\n let acquired = false;\n try {\n const lock = await client.query<{acquired: boolean}>(\n 'SELECT pg_try_advisory_lock(hashtext($1)) AS acquired',\n [connectionId],\n );\n acquired = lock.rows[0]?.acquired === true;\n if (!acquired) return {acquired: false};\n return {acquired: true, value: await fn()};\n } finally {\n try {\n if (acquired) await client.query('SELECT pg_advisory_unlock(hashtext($1))', [connectionId]);\n } finally {\n client.release();\n }\n }\n}\n\nasync function withJiraAdvisoryLockWait<T>(\n lockDescription: string,\n attemptLock: () => Promise<JiraAdvisoryLockAttempt<T>>,\n): Promise<T> {\n const deadline = Date.now() + JIRA_ADVISORY_LOCK_TIMEOUT_MS;\n let retryDelayMs = JIRA_ADVISORY_LOCK_RETRY_DELAY_MS;\n\n while (true) {\n const attempt = await attemptLock();\n if (attempt.acquired) return attempt.value;\n if (Date.now() >= deadline) throw new Error(`Timed out waiting for ${lockDescription}`);\n await new Promise((resolve) => setTimeout(resolve, retryDelayMs));\n retryDelayMs = Math.min(retryDelayMs * 2, JIRA_ADVISORY_LOCK_MAX_RETRY_DELAY_MS);\n }\n}\n\nexport async function getJiraInstallationByConnectionId(\n connectionId: string,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const rows = await executor\n .select()\n .from(jiraInstallations)\n .where(eq(jiraInstallations.connectionId, connectionId))\n .limit(1);\n const row = rows[0];\n if (!row) return undefined;\n return toJiraInstallation(row);\n}\n\nexport async function getJiraInstallationByWebhookId(\n webhookId: number,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const rows = await executor\n .select()\n .from(jiraInstallations)\n .where(sql`${jiraInstallations.webhookIds} @> ${JSON.stringify([webhookId])}::jsonb`)\n .limit(1);\n const row = rows[0];\n if (!row) return undefined;\n return toJiraInstallation(row);\n}\n\nexport async function markJiraInstallationRevoked(\n connectionId: string,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const [row] = await executor\n .update(jiraInstallations)\n .set({status: 'revoked', updatedAt: new Date()})\n .where(eq(jiraInstallations.connectionId, connectionId))\n .returning();\n if (!row) return undefined;\n return toJiraInstallation(row);\n}\n"],"names":["isUniqueViolation","pgClient","withPostgresSession","and","asc","eq","lte","sql","JiraConnectionAlreadyLinkedError","JiraInstallationAlreadyLinkedError","JiraInstallationSiteMismatchError","db","jiraInstallations","toJiraInstallation","JIRA_ADVISORY_LOCK_RETRY_DELAY_MS","JIRA_ADVISORY_LOCK_MAX_RETRY_DELAY_MS","JIRA_ADVISORY_LOCK_TIMEOUT_MS","withJiraInstallationLock","lockKey","fn","advisoryKey","withJiraAdvisoryLockWait","client","lock","query","rows","acquired","value","upsertJiraInstallation","params","options","executor","tx","now","Date","row","insert","values","connectionId","cloudId","siteUrl","siteName","authorizingAccountId","scopes","webhookIds","webhookExpiresAt","status","tokenExpiresAt","refreshTokenLastUsedAt","refreshTokenLastAttemptedAt","onConflictDoUpdate","target","setWhere","set","undefined","updatedAt","returning","error","getJiraInstallationByCloudId","select","from","where","limit","updateJiraInstallationTokenExpiry","update","markJiraInstallationTokenRefreshAttempt","listJiraInstallationsDueForTokenRefresh","before","orderBy","map","updateJiraInstallationWebhook","deleteJiraInstallationByConnectionId","result","delete","rowCount","withJiraRefreshLock","withJiraRefreshLockClient","withJiraRefreshLockAndWait","withJiraWebhookRegistrationLock","connect","release","lockDescription","attemptLock","deadline","retryDelayMs","attempt","Error","Promise","resolve","setTimeout","Math","min","getJiraInstallationByConnectionId","getJiraInstallationByWebhookId","webhookId","JSON","stringify","markJiraInstallationRevoked"],"mappings":"AAAA,SAAQA,iBAAiB,QAAO,wBAAwB;AACxD,SAAQC,QAAQ,EAAEC,mBAAmB,QAAO,yBAAyB;AACrE,SAAQC,GAAG,EAAEC,GAAG,EAAEC,EAAE,EAAEC,GAAG,EAAEC,GAAG,QAAO,cAAc;AACnD,SACEC,gCAAgC,EAChCC,kCAAkC,EAClCC,iCAAiC,QAC5B,kBAAkB;AACzB,SAAQC,EAAE,QAAO,UAAU;AAC3B,SAAQC,iBAAiB,EAAEC,kBAAkB,QAAO,4BAA4B;AAIhF,MAAMC,oCAAoC;AAC1C,MAAMC,wCAAwC;AAC9C,MAAMC,gCAAgC;AAItC,OAAO,SAASC,yBAA4BC,OAAe,EAAEC,EAAoB;IAC/E,MAAMC,cAAc,CAAC,kBAAkB,EAAEF,SAAS;IAClD,OAAOG,yBAAyB,CAAC,wBAAwB,EAAEH,SAAS,EAAE,IACpEhB,oBAAoB,OAAOoB;YACzB,MAAMC,OAAO,MAAMD,OAAOE,KAAK,CAC7B,yDACA;gBAACJ;aAAY;YAEf,IAAIG,KAAKE,IAAI,CAAC,EAAE,EAAEC,aAAa,MAAM,OAAO;gBAACA,UAAU;YAAc;YAErE,IAAI;gBACF,OAAO;oBAACA,UAAU;oBAAeC,OAAO,MAAMR;gBAAI;YACpD,SAAU;gBACR,MAAMG,OAAOE,KAAK,CAAC,2CAA2C;oBAACJ;iBAAY;YAC7E;QACF;AAEJ;AAoDA,OAAO,eAAeQ,uBACpBC,MAAoC,EACpCC,UAA0B,CAAC,CAAC;IAE5B,MAAMC,WAAYD,QAAQE,EAAE,IAAIrB;IAChC,MAAMsB,MAAM,IAAIC;IAChB,IAAIC;IACJ,IAAI;QACF,CAACA,IAAI,GAAG,MAAMJ,SACXK,MAAM,CAACxB,mBACPyB,MAAM,CAAC;YACNC,cAAcT,OAAOS,YAAY;YACjCC,SAASV,OAAOU,OAAO;YACvBC,SAASX,OAAOW,OAAO;YACvBC,UAAUZ,OAAOY,QAAQ;YACzBC,sBAAsBb,OAAOa,oBAAoB;YACjDC,QAAQd,OAAOc,MAAM;YACrBC,YAAYf,OAAOe,UAAU,IAAI,EAAE;YACnCC,kBAAkBhB,OAAOgB,gBAAgB,IAAI;YAC7CC,QAAQjB,OAAOiB,MAAM;YACrBC,gBAAgBlB,OAAOkB,cAAc,IAAI;YACzCC,wBAAwBnB,OAAOmB,sBAAsB,IAAIf;YACzDgB,6BAA6BpB,OAAOoB,2BAA2B,IAAIhB;QACrE,GACCiB,kBAAkB,CAAC;YAClBC,QAAQvC,kBAAkB0B,YAAY;YACtCc,UAAU/C,GAAGO,kBAAkB2B,OAAO,EAAEV,OAAOU,OAAO;YACtDc,KAAK;gBACHd,SAASV,OAAOU,OAAO;gBACvBC,SAASX,OAAOW,OAAO;gBACvBC,UAAUZ,OAAOY,QAAQ;gBACzBC,sBAAsBb,OAAOa,oBAAoB;gBACjDC,QAAQd,OAAOc,MAAM;gBACrB,GAAId,OAAOe,UAAU,KAAKU,YAAY,CAAC,IAAI;oBAACV,YAAYf,OAAOe,UAAU;gBAAA,CAAC;gBAC1E,GAAIf,OAAOgB,gBAAgB,KAAKS,YAC5B,CAAC,IACD;oBAACT,kBAAkBhB,OAAOgB,gBAAgB;gBAAA,CAAC;gBAC/CC,QAAQjB,OAAOiB,MAAM;gBACrBC,gBAAgBlB,OAAOkB,cAAc,IAAI;gBACzCC,wBAAwBnB,OAAOmB,sBAAsB,IAAIf;gBACzDgB,6BAA6BpB,OAAOoB,2BAA2B,IAAIhB;gBACnEsB,WAAWtB;YACb;QACF,GACCuB,SAAS;IACd,EAAE,OAAOC,OAAO;QACd,IAAIzD,kBAAkByD,OAAO,sDAAsD;YACjF,MAAM,IAAIjD,iCAAiCqB,OAAOS,YAAY;QAChE;QACA,IAAItC,kBAAkByD,OAAO,oDAAoD;YAC/E,MAAM,IAAIhD,mCAAmCoB,OAAOU,OAAO;QAC7D;QACA,MAAMkB;IACR;IAEA,IAAI,CAACtB,KAAK,MAAM,IAAIzB,kCAAkCmB,OAAOS,YAAY,EAAET,OAAOU,OAAO;IACzF,OAAO1B,mBAAmBsB;AAC5B;AAEA,OAAO,eAAeuB,6BACpBnB,OAAe,EACfT,UAA0B,CAAC,CAAC;IAE5B,MAAMC,WAAYD,QAAQE,EAAE,IAAIrB;IAChC,MAAMc,OAAO,MAAMM,SAChB4B,MAAM,GACNC,IAAI,CAAChD,mBACLiD,KAAK,CAACxD,GAAGO,kBAAkB2B,OAAO,EAAEA,UACpCuB,KAAK,CAAC;IACT,MAAM3B,MAAMV,IAAI,CAAC,EAAE;IACnB,OAAOU,MAAMtB,mBAAmBsB,OAAOmB;AACzC;AAEA,OAAO,eAAeS,kCACpBlC,MAA+C,EAC/CC,UAA0B,CAAC,CAAC;IAE5B,MAAMC,WAAYD,QAAQE,EAAE,IAAIrB;IAChC,MAAMsB,MAAM,IAAIC;IAChB,MAAM,CAACC,IAAI,GAAG,MAAMJ,SACjBiC,MAAM,CAACpD,mBACPyC,GAAG,CAAC;QACHN,gBAAgBlB,OAAOkB,cAAc;QACrCC,wBAAwBf;QACxBgB,6BAA6BhB;QAC7B,GAAIJ,OAAOc,MAAM,KAAKW,YAAY,CAAC,IAAI;YAACX,QAAQd,OAAOc,MAAM;QAAA,CAAC;QAC9DY,WAAWtB;IACb,GACC4B,KAAK,CAACxD,GAAGO,kBAAkB0B,YAAY,EAAET,OAAOS,YAAY,GAC5DkB,SAAS;IACZ,OAAOrB,MAAMtB,mBAAmBsB,OAAOmB;AACzC;AAEA,OAAO,eAAeW,wCACpB3B,YAAoB,EACpBR,UAA0B,CAAC,CAAC;IAE5B,MAAMC,WAAYD,QAAQE,EAAE,IAAIrB;IAChC,MAAM,CAACwB,IAAI,GAAG,MAAMJ,SACjBiC,MAAM,CAACpD,mBACPyC,GAAG,CAAC;QAACJ,6BAA6B,IAAIf;IAAM,GAC5C2B,KAAK,CAACxD,GAAGO,kBAAkB0B,YAAY,EAAEA,eACzCkB,SAAS;IACZ,OAAOrB,MAAMtB,mBAAmBsB,OAAOmB;AACzC;AAEA,OAAO,eAAeY,wCACpBrC,MAAkD,EAClDC,UAA0B,CAAC,CAAC;IAE5B,MAAMC,WAAYD,QAAQE,EAAE,IAAIrB;IAChC,MAAMc,OAAO,MAAMM,SAChB4B,MAAM,GACNC,IAAI,CAAChD,mBACLiD,KAAK,CACJ1D,IACEE,GAAGO,kBAAkBkC,MAAM,EAAE,cAC7BxC,IAAIM,kBAAkBoC,sBAAsB,EAAEnB,OAAOsC,MAAM,IAG9DC,OAAO,CAAChE,IAAIQ,kBAAkBqC,2BAA2B,GACzDa,KAAK,CAACjC,OAAOiC,KAAK,IAAI;IACzB,OAAOrC,KAAK4C,GAAG,CAACxD;AAClB;AAEA,OAAO,eAAeyD,8BACpBzC,MAA2C,EAC3CC,UAA0B,CAAC,CAAC;IAE5B,MAAMC,WAAYD,QAAQE,EAAE,IAAIrB;IAChC,MAAM,CAACwB,IAAI,GAAG,MAAMJ,SACjBiC,MAAM,CAACpD,mBACPyC,GAAG,CAAC;QACHT,YAAYf,OAAOe,UAAU;QAC7BC,kBAAkBhB,OAAOgB,gBAAgB;QACzCU,WAAW,IAAIrB;IACjB,GACC2B,KAAK,CAACxD,GAAGO,kBAAkB0B,YAAY,EAAET,OAAOS,YAAY,GAC5DkB,SAAS;IACZ,OAAOrB,MAAMtB,mBAAmBsB,OAAOmB;AACzC;AAEA,OAAO,eAAeiB,qCACpBjC,YAAoB,EACpBR,UAA0B,CAAC,CAAC;IAE5B,MAAMC,WAAYD,QAAQE,EAAE,IAAIrB;IAChC,MAAM6D,SAAS,MAAMzC,SAClB0C,MAAM,CAAC7D,mBACPiD,KAAK,CAACxD,GAAGO,kBAAkB0B,YAAY,EAAEA;IAC5C,OAAO,AAACkC,CAAAA,OAAOE,QAAQ,IAAI,CAAA,IAAK;AAClC;AAIA,OAAO,SAASC,oBACdrC,YAAoB,EACpBnB,EAAoB;IAEpB,OAAOyD,0BAA0BtC,cAAcnB;AACjD;AAEA,OAAO,SAAS0D,2BACdvC,YAAoB,EACpBnB,EAAoB;IAEpB,OAAOE,yBAAyB,CAAC,mBAAmB,EAAEiB,cAAc,EAAE,IACpEqC,oBAAoBrC,cAAcnB;AAEtC;AAEA,OAAO,SAAS2D,gCACd5D,OAAe,EACfC,EAAgC;IAEhC,OAAOF,yBAAyBC,SAAS,IAAMC;AACjD;AAEA,eAAeyD,0BACbtC,YAAoB,EACpBnB,EAAoB;IAEpB,MAAMG,SAAS,MAAMrB,WAAW8E,OAAO;IACvC,IAAIrD,WAAW;IACf,IAAI;QACF,MAAMH,OAAO,MAAMD,OAAOE,KAAK,CAC7B,yDACA;YAACc;SAAa;QAEhBZ,WAAWH,KAAKE,IAAI,CAAC,EAAE,EAAEC,aAAa;QACtC,IAAI,CAACA,UAAU,OAAO;YAACA,UAAU;QAAK;QACtC,OAAO;YAACA,UAAU;YAAMC,OAAO,MAAMR;QAAI;IAC3C,SAAU;QACR,IAAI;YACF,IAAIO,UAAU,MAAMJ,OAAOE,KAAK,CAAC,2CAA2C;gBAACc;aAAa;QAC5F,SAAU;YACRhB,OAAO0D,OAAO;QAChB;IACF;AACF;AAEA,eAAe3D,yBACb4D,eAAuB,EACvBC,WAAsD;IAEtD,MAAMC,WAAWjD,KAAKD,GAAG,KAAKjB;IAC9B,IAAIoE,eAAetE;IAEnB,MAAO,KAAM;QACX,MAAMuE,UAAU,MAAMH;QACtB,IAAIG,QAAQ3D,QAAQ,EAAE,OAAO2D,QAAQ1D,KAAK;QAC1C,IAAIO,KAAKD,GAAG,MAAMkD,UAAU,MAAM,IAAIG,MAAM,CAAC,sBAAsB,EAAEL,iBAAiB;QACtF,MAAM,IAAIM,QAAQ,CAACC,UAAYC,WAAWD,SAASJ;QACnDA,eAAeM,KAAKC,GAAG,CAACP,eAAe,GAAGrE;IAC5C;AACF;AAEA,OAAO,eAAe6E,kCACpBtD,YAAoB,EACpBR,UAA0B,CAAC,CAAC;IAE5B,MAAMC,WAAYD,QAAQE,EAAE,IAAIrB;IAChC,MAAMc,OAAO,MAAMM,SAChB4B,MAAM,GACNC,IAAI,CAAChD,mBACLiD,KAAK,CAACxD,GAAGO,kBAAkB0B,YAAY,EAAEA,eACzCwB,KAAK,CAAC;IACT,MAAM3B,MAAMV,IAAI,CAAC,EAAE;IACnB,IAAI,CAACU,KAAK,OAAOmB;IACjB,OAAOzC,mBAAmBsB;AAC5B;AAEA,OAAO,eAAe0D,+BACpBC,SAAiB,EACjBhE,UAA0B,CAAC,CAAC;IAE5B,MAAMC,WAAYD,QAAQE,EAAE,IAAIrB;IAChC,MAAMc,OAAO,MAAMM,SAChB4B,MAAM,GACNC,IAAI,CAAChD,mBACLiD,KAAK,CAACtD,GAAG,CAAC,EAAEK,kBAAkBgC,UAAU,CAAC,IAAI,EAAEmD,KAAKC,SAAS,CAAC;QAACF;KAAU,EAAE,OAAO,CAAC,EACnFhC,KAAK,CAAC;IACT,MAAM3B,MAAMV,IAAI,CAAC,EAAE;IACnB,IAAI,CAACU,KAAK,OAAOmB;IACjB,OAAOzC,mBAAmBsB;AAC5B;AAEA,OAAO,eAAe8D,4BACpB3D,YAAoB,EACpBR,UAA0B,CAAC,CAAC;IAE5B,MAAMC,WAAYD,QAAQE,EAAE,IAAIrB;IAChC,MAAM,CAACwB,IAAI,GAAG,MAAMJ,SACjBiC,MAAM,CAACpD,mBACPyC,GAAG,CAAC;QAACP,QAAQ;QAAWS,WAAW,IAAIrB;IAAM,GAC7C2B,KAAK,CAACxD,GAAGO,kBAAkB0B,YAAY,EAAEA,eACzCkB,SAAS;IACZ,IAAI,CAACrB,KAAK,OAAOmB;IACjB,OAAOzC,mBAAmBsB;AAC5B"}
@@ -196,6 +196,40 @@ export declare const jiraInstallations: import("drizzle-orm/pg-core").PgTableWit
196
196
  identity: undefined;
197
197
  generated: undefined;
198
198
  }, {}, {}>;
199
+ refreshTokenLastUsedAt: import("drizzle-orm/pg-core").PgColumn<{
200
+ name: "refresh_token_last_used_at";
201
+ tableName: "installations";
202
+ dataType: "date";
203
+ columnType: "PgTimestamp";
204
+ data: Date;
205
+ driverParam: string;
206
+ notNull: true;
207
+ hasDefault: true;
208
+ isPrimaryKey: false;
209
+ isAutoincrement: false;
210
+ hasRuntimeDefault: false;
211
+ enumValues: undefined;
212
+ baseColumn: never;
213
+ identity: undefined;
214
+ generated: undefined;
215
+ }, {}, {}>;
216
+ refreshTokenLastAttemptedAt: import("drizzle-orm/pg-core").PgColumn<{
217
+ name: "refresh_token_last_attempted_at";
218
+ tableName: "installations";
219
+ dataType: "date";
220
+ columnType: "PgTimestamp";
221
+ data: Date;
222
+ driverParam: string;
223
+ notNull: true;
224
+ hasDefault: true;
225
+ isPrimaryKey: false;
226
+ isAutoincrement: false;
227
+ hasRuntimeDefault: false;
228
+ enumValues: undefined;
229
+ baseColumn: never;
230
+ identity: undefined;
231
+ generated: undefined;
232
+ }, {}, {}>;
199
233
  createdAt: import("drizzle-orm/pg-core").PgColumn<{
200
234
  name: "created_at";
201
235
  tableName: "installations";
@@ -1 +1 @@
1
- {"version":3,"file":"installations.d.ts","sourceRoot":"","sources":["../../../src/db/schema/installations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,sBAAsB,CAAC;AAG3D,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqB7B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,OAAO,iBAAiB,CAAC,YAAY,CAAC;AACvE,MAAM,MAAM,wBAAwB,GAAG,OAAO,iBAAiB,CAAC,YAAY,CAAC;AAE7E,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,kBAAkB,GAAG,gBAAgB,CAgB5E"}
1
+ {"version":3,"file":"installations.d.ts","sourceRoot":"","sources":["../../../src/db/schema/installations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,sBAAsB,CAAC;AAG3D,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuC7B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,OAAO,iBAAiB,CAAC,YAAY,CAAC;AACvE,MAAM,MAAM,wBAAwB,GAAG,OAAO,iBAAiB,CAAC,YAAY,CAAC;AAE7E,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,kBAAkB,GAAG,gBAAgB,CAkB5E"}
@@ -1,5 +1,5 @@
1
1
  import { uuidv7PrimaryKey } from '@shipfox/node-drizzle';
2
- import { jsonb, text, timestamp, uniqueIndex, uuid } from 'drizzle-orm/pg-core';
2
+ import { index, jsonb, text, timestamp, uniqueIndex, uuid } from 'drizzle-orm/pg-core';
3
3
  import { pgTable } from './common.js';
4
4
  export const jiraInstallations = pgTable('installations', {
5
5
  id: uuidv7PrimaryKey(),
@@ -17,6 +17,12 @@ export const jiraInstallations = pgTable('installations', {
17
17
  tokenExpiresAt: timestamp('token_expires_at', {
18
18
  withTimezone: true
19
19
  }),
20
+ refreshTokenLastUsedAt: timestamp('refresh_token_last_used_at', {
21
+ withTimezone: true
22
+ }).notNull().defaultNow(),
23
+ refreshTokenLastAttemptedAt: timestamp('refresh_token_last_attempted_at', {
24
+ withTimezone: true
25
+ }).notNull().defaultNow(),
20
26
  createdAt: timestamp('created_at', {
21
27
  withTimezone: true
22
28
  }).notNull().defaultNow(),
@@ -25,7 +31,9 @@ export const jiraInstallations = pgTable('installations', {
25
31
  }).notNull().defaultNow()
26
32
  }, (table)=>[
27
33
  uniqueIndex('integrations_jira_installations_connection_unique').on(table.connectionId),
28
- uniqueIndex('integrations_jira_installations_cloud_id_unique').on(table.cloudId)
34
+ uniqueIndex('integrations_jira_installations_cloud_id_unique').on(table.cloudId),
35
+ index('integrations_jira_installations_token_refresh_due_idx').on(table.status, table.refreshTokenLastUsedAt),
36
+ index('integrations_jira_installations_token_refresh_attempt_idx').on(table.status, table.refreshTokenLastAttemptedAt)
29
37
  ]);
30
38
  export function toJiraInstallation(row) {
31
39
  return {
@@ -40,6 +48,8 @@ export function toJiraInstallation(row) {
40
48
  webhookExpiresAt: row.webhookExpiresAt,
41
49
  status: row.status,
42
50
  tokenExpiresAt: row.tokenExpiresAt,
51
+ refreshTokenLastUsedAt: row.refreshTokenLastUsedAt,
52
+ refreshTokenLastAttemptedAt: row.refreshTokenLastAttemptedAt,
43
53
  createdAt: row.createdAt,
44
54
  updatedAt: row.updatedAt
45
55
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/db/schema/installations.ts"],"sourcesContent":["import {uuidv7PrimaryKey} from '@shipfox/node-drizzle';\nimport {jsonb, text, timestamp, uniqueIndex, uuid} from 'drizzle-orm/pg-core';\nimport type {JiraInstallation} from '#db/installations.js';\nimport {pgTable} from './common.js';\n\nexport const jiraInstallations = pgTable(\n 'installations',\n {\n id: uuidv7PrimaryKey(),\n connectionId: uuid('connection_id').notNull(),\n cloudId: text('cloud_id').notNull(),\n siteUrl: text('site_url').notNull(),\n siteName: text('site_name').notNull(),\n authorizingAccountId: text('authorizing_account_id').notNull(),\n scopes: jsonb('scopes').$type<string[]>().notNull(),\n webhookIds: jsonb('webhook_ids').$type<number[]>().notNull().default([]),\n webhookExpiresAt: timestamp('webhook_expires_at', {withTimezone: true}),\n status: text('status').notNull().$type<JiraInstallation['status']>(),\n tokenExpiresAt: timestamp('token_expires_at', {withTimezone: true}),\n createdAt: timestamp('created_at', {withTimezone: true}).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', {withTimezone: true}).notNull().defaultNow(),\n },\n (table) => [\n uniqueIndex('integrations_jira_installations_connection_unique').on(table.connectionId),\n uniqueIndex('integrations_jira_installations_cloud_id_unique').on(table.cloudId),\n ],\n);\n\nexport type JiraInstallationDb = typeof jiraInstallations.$inferSelect;\nexport type JiraInstallationCreateDb = typeof jiraInstallations.$inferInsert;\n\nexport function toJiraInstallation(row: JiraInstallationDb): JiraInstallation {\n return {\n id: row.id,\n connectionId: row.connectionId,\n cloudId: row.cloudId,\n siteUrl: row.siteUrl,\n siteName: row.siteName,\n authorizingAccountId: row.authorizingAccountId,\n scopes: row.scopes,\n webhookIds: row.webhookIds,\n webhookExpiresAt: row.webhookExpiresAt,\n status: row.status,\n tokenExpiresAt: row.tokenExpiresAt,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n"],"names":["uuidv7PrimaryKey","jsonb","text","timestamp","uniqueIndex","uuid","pgTable","jiraInstallations","id","connectionId","notNull","cloudId","siteUrl","siteName","authorizingAccountId","scopes","$type","webhookIds","default","webhookExpiresAt","withTimezone","status","tokenExpiresAt","createdAt","defaultNow","updatedAt","table","on","toJiraInstallation","row"],"mappings":"AAAA,SAAQA,gBAAgB,QAAO,wBAAwB;AACvD,SAAQC,KAAK,EAAEC,IAAI,EAAEC,SAAS,EAAEC,WAAW,EAAEC,IAAI,QAAO,sBAAsB;AAE9E,SAAQC,OAAO,QAAO,cAAc;AAEpC,OAAO,MAAMC,oBAAoBD,QAC/B,iBACA;IACEE,IAAIR;IACJS,cAAcJ,KAAK,iBAAiBK,OAAO;IAC3CC,SAAST,KAAK,YAAYQ,OAAO;IACjCE,SAASV,KAAK,YAAYQ,OAAO;IACjCG,UAAUX,KAAK,aAAaQ,OAAO;IACnCI,sBAAsBZ,KAAK,0BAA0BQ,OAAO;IAC5DK,QAAQd,MAAM,UAAUe,KAAK,GAAaN,OAAO;IACjDO,YAAYhB,MAAM,eAAee,KAAK,GAAaN,OAAO,GAAGQ,OAAO,CAAC,EAAE;IACvEC,kBAAkBhB,UAAU,sBAAsB;QAACiB,cAAc;IAAI;IACrEC,QAAQnB,KAAK,UAAUQ,OAAO,GAAGM,KAAK;IACtCM,gBAAgBnB,UAAU,oBAAoB;QAACiB,cAAc;IAAI;IACjEG,WAAWpB,UAAU,cAAc;QAACiB,cAAc;IAAI,GAAGV,OAAO,GAAGc,UAAU;IAC7EC,WAAWtB,UAAU,cAAc;QAACiB,cAAc;IAAI,GAAGV,OAAO,GAAGc,UAAU;AAC/E,GACA,CAACE,QAAU;QACTtB,YAAY,qDAAqDuB,EAAE,CAACD,MAAMjB,YAAY;QACtFL,YAAY,mDAAmDuB,EAAE,CAACD,MAAMf,OAAO;KAChF,EACD;AAKF,OAAO,SAASiB,mBAAmBC,GAAuB;IACxD,OAAO;QACLrB,IAAIqB,IAAIrB,EAAE;QACVC,cAAcoB,IAAIpB,YAAY;QAC9BE,SAASkB,IAAIlB,OAAO;QACpBC,SAASiB,IAAIjB,OAAO;QACpBC,UAAUgB,IAAIhB,QAAQ;QACtBC,sBAAsBe,IAAIf,oBAAoB;QAC9CC,QAAQc,IAAId,MAAM;QAClBE,YAAYY,IAAIZ,UAAU;QAC1BE,kBAAkBU,IAAIV,gBAAgB;QACtCE,QAAQQ,IAAIR,MAAM;QAClBC,gBAAgBO,IAAIP,cAAc;QAClCC,WAAWM,IAAIN,SAAS;QACxBE,WAAWI,IAAIJ,SAAS;IAC1B;AACF"}
1
+ {"version":3,"sources":["../../../src/db/schema/installations.ts"],"sourcesContent":["import {uuidv7PrimaryKey} from '@shipfox/node-drizzle';\nimport {index, jsonb, text, timestamp, uniqueIndex, uuid} from 'drizzle-orm/pg-core';\nimport type {JiraInstallation} from '#db/installations.js';\nimport {pgTable} from './common.js';\n\nexport const jiraInstallations = pgTable(\n 'installations',\n {\n id: uuidv7PrimaryKey(),\n connectionId: uuid('connection_id').notNull(),\n cloudId: text('cloud_id').notNull(),\n siteUrl: text('site_url').notNull(),\n siteName: text('site_name').notNull(),\n authorizingAccountId: text('authorizing_account_id').notNull(),\n scopes: jsonb('scopes').$type<string[]>().notNull(),\n webhookIds: jsonb('webhook_ids').$type<number[]>().notNull().default([]),\n webhookExpiresAt: timestamp('webhook_expires_at', {withTimezone: true}),\n status: text('status').notNull().$type<JiraInstallation['status']>(),\n tokenExpiresAt: timestamp('token_expires_at', {withTimezone: true}),\n refreshTokenLastUsedAt: timestamp('refresh_token_last_used_at', {\n withTimezone: true,\n })\n .notNull()\n .defaultNow(),\n refreshTokenLastAttemptedAt: timestamp('refresh_token_last_attempted_at', {\n withTimezone: true,\n })\n .notNull()\n .defaultNow(),\n createdAt: timestamp('created_at', {withTimezone: true}).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', {withTimezone: true}).notNull().defaultNow(),\n },\n (table) => [\n uniqueIndex('integrations_jira_installations_connection_unique').on(table.connectionId),\n uniqueIndex('integrations_jira_installations_cloud_id_unique').on(table.cloudId),\n index('integrations_jira_installations_token_refresh_due_idx').on(\n table.status,\n table.refreshTokenLastUsedAt,\n ),\n index('integrations_jira_installations_token_refresh_attempt_idx').on(\n table.status,\n table.refreshTokenLastAttemptedAt,\n ),\n ],\n);\n\nexport type JiraInstallationDb = typeof jiraInstallations.$inferSelect;\nexport type JiraInstallationCreateDb = typeof jiraInstallations.$inferInsert;\n\nexport function toJiraInstallation(row: JiraInstallationDb): JiraInstallation {\n return {\n id: row.id,\n connectionId: row.connectionId,\n cloudId: row.cloudId,\n siteUrl: row.siteUrl,\n siteName: row.siteName,\n authorizingAccountId: row.authorizingAccountId,\n scopes: row.scopes,\n webhookIds: row.webhookIds,\n webhookExpiresAt: row.webhookExpiresAt,\n status: row.status,\n tokenExpiresAt: row.tokenExpiresAt,\n refreshTokenLastUsedAt: row.refreshTokenLastUsedAt,\n refreshTokenLastAttemptedAt: row.refreshTokenLastAttemptedAt,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n"],"names":["uuidv7PrimaryKey","index","jsonb","text","timestamp","uniqueIndex","uuid","pgTable","jiraInstallations","id","connectionId","notNull","cloudId","siteUrl","siteName","authorizingAccountId","scopes","$type","webhookIds","default","webhookExpiresAt","withTimezone","status","tokenExpiresAt","refreshTokenLastUsedAt","defaultNow","refreshTokenLastAttemptedAt","createdAt","updatedAt","table","on","toJiraInstallation","row"],"mappings":"AAAA,SAAQA,gBAAgB,QAAO,wBAAwB;AACvD,SAAQC,KAAK,EAAEC,KAAK,EAAEC,IAAI,EAAEC,SAAS,EAAEC,WAAW,EAAEC,IAAI,QAAO,sBAAsB;AAErF,SAAQC,OAAO,QAAO,cAAc;AAEpC,OAAO,MAAMC,oBAAoBD,QAC/B,iBACA;IACEE,IAAIT;IACJU,cAAcJ,KAAK,iBAAiBK,OAAO;IAC3CC,SAAST,KAAK,YAAYQ,OAAO;IACjCE,SAASV,KAAK,YAAYQ,OAAO;IACjCG,UAAUX,KAAK,aAAaQ,OAAO;IACnCI,sBAAsBZ,KAAK,0BAA0BQ,OAAO;IAC5DK,QAAQd,MAAM,UAAUe,KAAK,GAAaN,OAAO;IACjDO,YAAYhB,MAAM,eAAee,KAAK,GAAaN,OAAO,GAAGQ,OAAO,CAAC,EAAE;IACvEC,kBAAkBhB,UAAU,sBAAsB;QAACiB,cAAc;IAAI;IACrEC,QAAQnB,KAAK,UAAUQ,OAAO,GAAGM,KAAK;IACtCM,gBAAgBnB,UAAU,oBAAoB;QAACiB,cAAc;IAAI;IACjEG,wBAAwBpB,UAAU,8BAA8B;QAC9DiB,cAAc;IAChB,GACGV,OAAO,GACPc,UAAU;IACbC,6BAA6BtB,UAAU,mCAAmC;QACxEiB,cAAc;IAChB,GACGV,OAAO,GACPc,UAAU;IACbE,WAAWvB,UAAU,cAAc;QAACiB,cAAc;IAAI,GAAGV,OAAO,GAAGc,UAAU;IAC7EG,WAAWxB,UAAU,cAAc;QAACiB,cAAc;IAAI,GAAGV,OAAO,GAAGc,UAAU;AAC/E,GACA,CAACI,QAAU;QACTxB,YAAY,qDAAqDyB,EAAE,CAACD,MAAMnB,YAAY;QACtFL,YAAY,mDAAmDyB,EAAE,CAACD,MAAMjB,OAAO;QAC/EX,MAAM,yDAAyD6B,EAAE,CAC/DD,MAAMP,MAAM,EACZO,MAAML,sBAAsB;QAE9BvB,MAAM,6DAA6D6B,EAAE,CACnED,MAAMP,MAAM,EACZO,MAAMH,2BAA2B;KAEpC,EACD;AAKF,OAAO,SAASK,mBAAmBC,GAAuB;IACxD,OAAO;QACLvB,IAAIuB,IAAIvB,EAAE;QACVC,cAAcsB,IAAItB,YAAY;QAC9BE,SAASoB,IAAIpB,OAAO;QACpBC,SAASmB,IAAInB,OAAO;QACpBC,UAAUkB,IAAIlB,QAAQ;QACtBC,sBAAsBiB,IAAIjB,oBAAoB;QAC9CC,QAAQgB,IAAIhB,MAAM;QAClBE,YAAYc,IAAId,UAAU;QAC1BE,kBAAkBY,IAAIZ,gBAAgB;QACtCE,QAAQU,IAAIV,MAAM;QAClBC,gBAAgBS,IAAIT,cAAc;QAClCC,wBAAwBQ,IAAIR,sBAAsB;QAClDE,6BAA6BM,IAAIN,2BAA2B;QAC5DC,WAAWK,IAAIL,SAAS;QACxBC,WAAWI,IAAIJ,SAAS;IAC1B;AACF"}
package/dist/index.d.ts CHANGED
@@ -1,12 +1,19 @@
1
- import { type JiraApiClient } from '#api/client.js';
1
+ import { type JiraAgentToolsClient, type JiraApiClient } from '#api/client.js';
2
2
  import { config } from '#config.js';
3
+ import { JiraAgentToolsProvider } from '#core/agent-tools-provider.js';
4
+ import type { JiraTokenStore } from '#core/tokens.js';
3
5
  import { closeDb, db } from '#db/db.js';
4
6
  import { getJiraInstallationByConnectionId } from '#db/installations.js';
5
7
  import { migrationsPath } from '#db/migrations.js';
6
8
  import { type CreateJiraIntegrationRoutesOptions } from '#presentation/routes/install.js';
9
+ import { type CreateJiraWebhookRoutesOptions } from '#presentation/routes/webhooks.js';
7
10
  export type { JiraProvider } from '@shipfox/api-integration-jira-dto';
8
- export type { JiraAccessibleResource, JiraApiClient, JiraAuthorization, JiraIdentity, } from '#api/client.js';
9
- export { createJiraApiClient, mapJiraError } from '#api/client.js';
11
+ export type { JiraAccessibleResource, JiraAgentToolHttpMethod, JiraAgentToolQueryValue, JiraAgentToolRequest, JiraAgentToolResponse, JiraAgentToolsClient, JiraApiClient, JiraAuthorization, JiraDynamicWebhookRegistration, JiraIdentity, } from '#api/client.js';
12
+ export { createJiraAgentToolsClient, createJiraApiClient, JIRA_DYNAMIC_WEBHOOK_EVENTS, JIRA_DYNAMIC_WEBHOOK_JQL, mapJiraError, } from '#api/client.js';
13
+ export type { JiraAgentToolCatalogEntry, JiraAgentToolId, JiraAgentToolRequiredScope, } from '#core/agent-tools.js';
14
+ export { jiraAgentToolCatalog, jiraAgentToolSelectionCatalog, jiraPlainTextToAdf, } from '#core/agent-tools.js';
15
+ export type { JiraAgentToolsProviderOptions, JiraToolCallResult, } from '#core/agent-tools-provider.js';
16
+ export { JiraAgentToolsProvider } from '#core/agent-tools-provider.js';
10
17
  export type { DisconnectJiraInstallationParams } from '#core/disconnect.js';
11
18
  export { disconnectJiraInstallation } from '#core/disconnect.js';
12
19
  export { JiraAccessTokenMissingError, JiraAuthorizationScopeMismatchError, JiraConnectionAlreadyLinkedError, JiraConnectionNotFoundError, JiraInstallationAlreadyLinkedError, JiraInstallationSiteMismatchError, JiraInstallStateActorMismatchError, JiraInstallStateError, JiraIntegrationProviderError, JiraOAuthCallbackError, JiraOfflineAccessNotGrantedError, JiraPendingSelectionNotFoundError, JiraSiteSelectionMismatchError, JiraTokenUnrefreshableError, } from '#core/errors.js';
@@ -18,21 +25,41 @@ export { assertJiraAuthorizationScopes, formatJiraOAuthScopes, JIRA_OAUTH_SCOPES
18
25
  export { signJiraInstallState, verifyJiraInstallState } from '#core/state.js';
19
26
  export type { CreateJiraTokenStoreParams, GetJiraAccessTokenParams, JiraConnectionResolverResult, JiraSecretsStore, JiraTokenStore, StoreJiraTokensParams, } from '#core/tokens.js';
20
27
  export { createJiraTokenStore, jiraSecretsNamespace } from '#core/tokens.js';
21
- export type { JiraInstallation, JiraInstallationStatus, UpsertJiraInstallationParams, } from '#db/installations.js';
22
- export { deleteJiraInstallationByConnectionId, getJiraInstallationByCloudId, getJiraInstallationByConnectionId, getJiraInstallationByWebhookId, markJiraInstallationRevoked, updateJiraInstallationTokenExpiry, upsertJiraInstallation, withJiraRefreshLock, } from '#db/installations.js';
28
+ export type { CreateJiraWebhookProcessorOptions, JiraWebhookProcessor, } from '#core/webhook-processor.js';
29
+ export { createJiraWebhookProcessor } from '#core/webhook-processor.js';
30
+ export type { RegisterJiraWebhookParams } from '#core/webhook-registration.js';
31
+ export { JIRA_WEBHOOK_TTL_MS, registerJiraWebhook } from '#core/webhook-registration.js';
32
+ export type { JiraInstallation, JiraInstallationLock, JiraInstallationStatus, UpdateJiraInstallationWebhookParams, UpsertJiraInstallationParams, } from '#db/installations.js';
33
+ export { deleteJiraInstallationByConnectionId, getJiraInstallationByCloudId, getJiraInstallationByConnectionId, getJiraInstallationByWebhookId, listJiraInstallationsDueForTokenRefresh, markJiraInstallationRevoked, updateJiraInstallationTokenExpiry, updateJiraInstallationWebhook, upsertJiraInstallation, withJiraRefreshLock, withJiraWebhookRegistrationLock, } from '#db/installations.js';
34
+ export type { CreateJiraWebhookRoutesOptions } from '#presentation/routes/webhooks.js';
35
+ export { createJiraWebhookRoutes } from '#presentation/routes/webhooks.js';
36
+ export { createJiraMaintenanceWorker } from '#temporal/worker.js';
23
37
  export { closeDb, config, db, migrationsPath };
24
38
  export interface CreateJiraIntegrationProviderOptions {
25
39
  jira?: JiraApiClient | undefined;
40
+ agentTools?: {
41
+ tokenStore: Pick<JiraTokenStore, 'getAccessToken'>;
42
+ jira?: JiraAgentToolsClient | undefined;
43
+ } | undefined;
26
44
  getJiraInstallationByConnectionId?: typeof getJiraInstallationByConnectionId | undefined;
27
- routes?: Omit<CreateJiraIntegrationRoutesOptions, 'jira' | 'connectionCapabilities'> | undefined;
45
+ routes?: JiraIntegrationProviderRoutesOptions | undefined;
28
46
  }
47
+ type JiraIntegrationProviderRoutesOptions = Omit<CreateJiraIntegrationRoutesOptions, 'jira' | 'connectionCapabilities' | 'registerJiraWebhook'> & Omit<CreateJiraWebhookRoutesOptions, 'processor'>;
29
48
  export declare function createJiraIntegrationProvider(options?: CreateJiraIntegrationProviderOptions): {
30
49
  provider: string;
31
50
  displayName: string;
32
- adapters: {};
51
+ adapters: {
52
+ agent_tools: JiraAgentToolsProvider;
53
+ } | {
54
+ agent_tools?: never;
55
+ };
33
56
  connectionExternalUrl(connection: {
34
57
  id: string;
35
58
  }): Promise<string | undefined>;
36
59
  routes: import("@shipfox/node-fastify").RouteGroup[];
60
+ webhookProcessors: {
61
+ routeIds: readonly ["jira"];
62
+ processor: import("#core/webhook-processor.js").JiraWebhookProcessor;
63
+ }[] | undefined;
37
64
  };
38
65
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,KAAK,aAAa,EAAC,MAAM,gBAAgB,CAAC;AACvE,OAAO,EAAC,MAAM,EAAC,MAAM,YAAY,CAAC;AAClC,OAAO,EAAC,OAAO,EAAE,EAAE,EAAC,MAAM,WAAW,CAAC;AACtC,OAAO,EAAC,iCAAiC,EAAC,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAAC,cAAc,EAAC,MAAM,mBAAmB,CAAC;AACjD,OAAO,EACL,KAAK,kCAAkC,EAExC,MAAM,iCAAiC,CAAC;AAEzC,YAAY,EAAC,YAAY,EAAC,MAAM,mCAAmC,CAAC;AACpE,YAAY,EACV,sBAAsB,EACtB,aAAa,EACb,iBAAiB,EACjB,YAAY,GACb,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAC,mBAAmB,EAAE,YAAY,EAAC,MAAM,gBAAgB,CAAC;AACjE,YAAY,EAAC,gCAAgC,EAAC,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAAC,0BAA0B,EAAC,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EACL,2BAA2B,EAC3B,mCAAmC,EACnC,gCAAgC,EAChC,2BAA2B,EAC3B,kCAAkC,EAClC,iCAAiC,EACjC,kCAAkC,EAClC,qBAAqB,EACrB,4BAA4B,EAC5B,sBAAsB,EACtB,gCAAgC,EAChC,iCAAiC,EACjC,8BAA8B,EAC9B,2BAA2B,GAC5B,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAC,4BAA4B,EAAE,wBAAwB,EAAC,MAAM,kBAAkB,CAAC;AAC7F,OAAO,EACL,kBAAkB,EAClB,4BAA4B,EAC5B,uBAAuB,GACxB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAC,gCAAgC,EAAE,yBAAyB,EAAC,MAAM,kBAAkB,CAAC;AAClG,OAAO,EAAC,+BAA+B,EAAE,2BAA2B,EAAC,MAAM,kBAAkB,CAAC;AAC9F,OAAO,EACL,6BAA6B,EAC7B,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAC,oBAAoB,EAAE,sBAAsB,EAAC,MAAM,gBAAgB,CAAC;AAC5E,YAAY,EACV,0BAA0B,EAC1B,wBAAwB,EACxB,4BAA4B,EAC5B,gBAAgB,EAChB,cAAc,EACd,qBAAqB,GACtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAC,oBAAoB,EAAE,oBAAoB,EAAC,MAAM,iBAAiB,CAAC;AAC3E,YAAY,EACV,gBAAgB,EAChB,sBAAsB,EACtB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,oCAAoC,EACpC,4BAA4B,EAC5B,iCAAiC,EACjC,8BAA8B,EAC9B,2BAA2B,EAC3B,iCAAiC,EACjC,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,cAAc,EAAC,CAAC;AAE7C,MAAM,WAAW,oCAAoC;IACnD,IAAI,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IACjC,iCAAiC,CAAC,EAAE,OAAO,iCAAiC,GAAG,SAAS,CAAC;IACzF,MAAM,CAAC,EAAE,IAAI,CAAC,kCAAkC,EAAE,MAAM,GAAG,wBAAwB,CAAC,GAAG,SAAS,CAAC;CAClG;AAED,wBAAgB,6BAA6B,CAAC,OAAO,GAAE,oCAAyC;;;;sCAWpD;QAAC,EAAE,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;;EAKrF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}