@shipfox/api-integration-sentry 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-type$colon$emit.log +1 -0
  3. package/.turbo/turbo-type.log +1 -0
  4. package/CHANGELOG.md +192 -0
  5. package/LICENSE +21 -0
  6. package/README.md +145 -0
  7. package/dist/api/client.d.ts +24 -0
  8. package/dist/api/client.d.ts.map +1 -0
  9. package/dist/api/client.js +101 -0
  10. package/dist/api/client.js.map +1 -0
  11. package/dist/config.d.ts +9 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +32 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/core/errors.d.ts +57 -0
  16. package/dist/core/errors.d.ts.map +1 -0
  17. package/dist/core/errors.js +67 -0
  18. package/dist/core/errors.js.map +1 -0
  19. package/dist/core/install.d.ts +63 -0
  20. package/dist/core/install.d.ts.map +1 -0
  21. package/dist/core/install.js +209 -0
  22. package/dist/core/install.js.map +1 -0
  23. package/dist/core/signature.d.ts +7 -0
  24. package/dist/core/signature.d.ts.map +1 -0
  25. package/dist/core/signature.js +6 -0
  26. package/dist/core/signature.js.map +1 -0
  27. package/dist/core/webhook.d.ts +49 -0
  28. package/dist/core/webhook.d.ts.map +1 -0
  29. package/dist/core/webhook.js +175 -0
  30. package/dist/core/webhook.js.map +1 -0
  31. package/dist/db/db.d.ts +327 -0
  32. package/dist/db/db.d.ts.map +1 -0
  33. package/dist/db/db.js +18 -0
  34. package/dist/db/db.js.map +1 -0
  35. package/dist/db/installations.d.ts +74 -0
  36. package/dist/db/installations.d.ts.map +1 -0
  37. package/dist/db/installations.js +120 -0
  38. package/dist/db/installations.js.map +1 -0
  39. package/dist/db/migrations.d.ts +2 -0
  40. package/dist/db/migrations.d.ts.map +1 -0
  41. package/dist/db/migrations.js +5 -0
  42. package/dist/db/migrations.js.map +1 -0
  43. package/dist/db/schema/common.d.ts +2 -0
  44. package/dist/db/schema/common.d.ts.map +1 -0
  45. package/dist/db/schema/common.js +4 -0
  46. package/dist/db/schema/common.js.map +1 -0
  47. package/dist/db/schema/installations.d.ts +165 -0
  48. package/dist/db/schema/installations.d.ts.map +1 -0
  49. package/dist/db/schema/installations.js +42 -0
  50. package/dist/db/schema/installations.js.map +1 -0
  51. package/dist/index.d.ts +36 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +64 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/presentation/dto/integrations.d.ts +15 -0
  56. package/dist/presentation/dto/integrations.d.ts.map +1 -0
  57. package/dist/presentation/dto/integrations.js +9 -0
  58. package/dist/presentation/dto/integrations.js.map +1 -0
  59. package/dist/presentation/routes/errors.d.ts +2 -0
  60. package/dist/presentation/routes/errors.d.ts.map +1 -0
  61. package/dist/presentation/routes/errors.js +51 -0
  62. package/dist/presentation/routes/errors.js.map +1 -0
  63. package/dist/presentation/routes/install.d.ts +16 -0
  64. package/dist/presentation/routes/install.d.ts.map +1 -0
  65. package/dist/presentation/routes/install.js +76 -0
  66. package/dist/presentation/routes/install.js.map +1 -0
  67. package/dist/presentation/routes/installation-handler.d.ts +9 -0
  68. package/dist/presentation/routes/installation-handler.d.ts.map +1 -0
  69. package/dist/presentation/routes/installation-handler.js +70 -0
  70. package/dist/presentation/routes/installation-handler.js.map +1 -0
  71. package/dist/presentation/routes/issue-handler.d.ts +9 -0
  72. package/dist/presentation/routes/issue-handler.d.ts.map +1 -0
  73. package/dist/presentation/routes/issue-handler.js +50 -0
  74. package/dist/presentation/routes/issue-handler.js.map +1 -0
  75. package/dist/presentation/routes/webhook-context.d.ts +20 -0
  76. package/dist/presentation/routes/webhook-context.d.ts.map +1 -0
  77. package/dist/presentation/routes/webhook-context.js +10 -0
  78. package/dist/presentation/routes/webhook-context.js.map +1 -0
  79. package/dist/presentation/routes/webhook-delivery.d.ts +28 -0
  80. package/dist/presentation/routes/webhook-delivery.d.ts.map +1 -0
  81. package/dist/presentation/routes/webhook-delivery.js +56 -0
  82. package/dist/presentation/routes/webhook-delivery.js.map +1 -0
  83. package/dist/presentation/routes/webhook-request.d.ts +10 -0
  84. package/dist/presentation/routes/webhook-request.d.ts.map +1 -0
  85. package/dist/presentation/routes/webhook-request.js +56 -0
  86. package/dist/presentation/routes/webhook-request.js.map +1 -0
  87. package/dist/presentation/routes/webhooks.d.ts +5 -0
  88. package/dist/presentation/routes/webhooks.d.ts.map +1 -0
  89. package/dist/presentation/routes/webhooks.js +71 -0
  90. package/dist/presentation/routes/webhooks.js.map +1 -0
  91. package/dist/temporal/activities/index.d.ts +5 -0
  92. package/dist/temporal/activities/index.d.ts.map +1 -0
  93. package/dist/temporal/activities/index.js +8 -0
  94. package/dist/temporal/activities/index.js.map +1 -0
  95. package/dist/temporal/activities/prune-unclaimed-installations.d.ts +4 -0
  96. package/dist/temporal/activities/prune-unclaimed-installations.d.ts.map +1 -0
  97. package/dist/temporal/activities/prune-unclaimed-installations.js +11 -0
  98. package/dist/temporal/activities/prune-unclaimed-installations.js.map +1 -0
  99. package/dist/temporal/constants.d.ts +2 -0
  100. package/dist/temporal/constants.d.ts.map +1 -0
  101. package/dist/temporal/constants.js +3 -0
  102. package/dist/temporal/constants.js.map +1 -0
  103. package/dist/temporal/workflows/index.d.ts +2 -0
  104. package/dist/temporal/workflows/index.d.ts.map +1 -0
  105. package/dist/temporal/workflows/index.js +3 -0
  106. package/dist/temporal/workflows/index.js.map +1 -0
  107. package/dist/temporal/workflows/prune-unclaimed-installations-cron.d.ts +2 -0
  108. package/dist/temporal/workflows/prune-unclaimed-installations-cron.d.ts.map +1 -0
  109. package/dist/temporal/workflows/prune-unclaimed-installations-cron.js +14 -0
  110. package/dist/temporal/workflows/prune-unclaimed-installations-cron.js.map +1 -0
  111. package/dist/tsconfig.test.tsbuildinfo +1 -0
  112. package/drizzle/0000_initial.sql +14 -0
  113. package/drizzle/meta/_journal.json +13 -0
  114. package/drizzle.config.ts +7 -0
  115. package/package.json +70 -0
  116. package/src/api/client.test.ts +147 -0
  117. package/src/api/client.ts +147 -0
  118. package/src/config.test.ts +16 -0
  119. package/src/config.ts +35 -0
  120. package/src/connection-external-url.test.ts +79 -0
  121. package/src/core/errors.ts +81 -0
  122. package/src/core/install.test.ts +343 -0
  123. package/src/core/install.ts +305 -0
  124. package/src/core/signature.test.ts +50 -0
  125. package/src/core/signature.ts +11 -0
  126. package/src/core/webhook.test.ts +118 -0
  127. package/src/core/webhook.ts +246 -0
  128. package/src/db/db.ts +18 -0
  129. package/src/db/installations.test.ts +224 -0
  130. package/src/db/installations.ts +207 -0
  131. package/src/db/migrations.ts +4 -0
  132. package/src/db/schema/common.ts +3 -0
  133. package/src/db/schema/installations.ts +46 -0
  134. package/src/index.test.ts +18 -0
  135. package/src/index.ts +127 -0
  136. package/src/presentation/dto/integrations.ts +9 -0
  137. package/src/presentation/routes/errors.ts +48 -0
  138. package/src/presentation/routes/install.test.ts +325 -0
  139. package/src/presentation/routes/install.ts +102 -0
  140. package/src/presentation/routes/installation-handler.ts +76 -0
  141. package/src/presentation/routes/issue-handler.ts +52 -0
  142. package/src/presentation/routes/webhook-context.ts +25 -0
  143. package/src/presentation/routes/webhook-delivery.ts +61 -0
  144. package/src/presentation/routes/webhook-request.ts +66 -0
  145. package/src/presentation/routes/webhooks.test.ts +773 -0
  146. package/src/presentation/routes/webhooks.ts +59 -0
  147. package/src/temporal/activities/index.ts +7 -0
  148. package/src/temporal/activities/prune-unclaimed-installations.test.ts +52 -0
  149. package/src/temporal/activities/prune-unclaimed-installations.ts +11 -0
  150. package/src/temporal/constants.ts +1 -0
  151. package/src/temporal/workflows/index.ts +1 -0
  152. package/src/temporal/workflows/prune-unclaimed-installations-cron.ts +15 -0
  153. package/test/env.ts +11 -0
  154. package/test/factories/index.ts +9 -0
  155. package/test/factories/sentry-installation-webhook.ts +35 -0
  156. package/test/factories/sentry-installation.ts +44 -0
  157. package/test/factories/sentry-issue-webhook.ts +34 -0
  158. package/test/globalSetup.ts +14 -0
  159. package/test/index.ts +9 -0
  160. package/test/setup.ts +17 -0
  161. package/tsconfig.build.json +9 -0
  162. package/tsconfig.build.tsbuildinfo +1 -0
  163. package/tsconfig.json +3 -0
  164. package/tsconfig.test.json +9 -0
  165. package/vitest.config.ts +12 -0
@@ -0,0 +1,59 @@
1
+ import {
2
+ ClientError,
3
+ defineRoute,
4
+ type RouteGroup,
5
+ rawBodyPlugin,
6
+ WEBHOOK_BODY_LIMIT,
7
+ } from '@shipfox/node-fastify';
8
+ import {logger} from '@shipfox/node-opentelemetry';
9
+ import {config} from '#config.js';
10
+ import {verifySentrySignature} from '#core/signature.js';
11
+ import {handleInstallationResource} from './installation-handler.js';
12
+ import {handleIssueResource} from './issue-handler.js';
13
+ import type {SentryWebhookContext} from './webhook-context.js';
14
+ import {recordAndDrop} from './webhook-delivery.js';
15
+ import {parseSentryWebhookRequest} from './webhook-request.js';
16
+
17
+ const ISSUE_RESOURCE = 'issue';
18
+ const INSTALLATION_RESOURCE = 'installation';
19
+
20
+ export type {SentryWebhookContext} from './webhook-context.js';
21
+
22
+ export function createSentryWebhookRoutes(context: SentryWebhookContext): RouteGroup {
23
+ const webhookRoute = defineRoute({
24
+ method: 'POST',
25
+ path: '/',
26
+ auth: [],
27
+ description: 'Sentry integration webhook receiver.',
28
+ options: {bodyLimit: WEBHOOK_BODY_LIMIT},
29
+ handler: (request, reply) => {
30
+ const {deliveryId, resource, signature, signatureHeaderName, rawBody} =
31
+ parseSentryWebhookRequest(request);
32
+
33
+ if (!verifySentrySignature({rawBody, signature, secret: config.SENTRY_APP_CLIENT_SECRET})) {
34
+ throw new ClientError('invalid signature', 'invalid-signature', {status: 401});
35
+ }
36
+ logger().debug(
37
+ {deliveryId, signatureHeader: signatureHeaderName},
38
+ 'sentry webhook: signature verified',
39
+ );
40
+
41
+ if (resource === ISSUE_RESOURCE) {
42
+ return handleIssueResource({context, reply, deliveryId, rawBody});
43
+ }
44
+ if (resource === INSTALLATION_RESOURCE) {
45
+ return handleInstallationResource({context, reply, deliveryId, rawBody});
46
+ }
47
+
48
+ // Unsupported resources are acknowledged so Sentry does not retry or disable the app.
49
+ return recordAndDrop({context, reply, deliveryId});
50
+ },
51
+ });
52
+
53
+ return {
54
+ prefix: '/webhooks/integrations/sentry',
55
+ auth: [],
56
+ plugins: [rawBodyPlugin],
57
+ routes: [webhookRoute],
58
+ };
59
+ }
@@ -0,0 +1,7 @@
1
+ import {pruneUnclaimedSentryInstallationsActivity} from './prune-unclaimed-installations.js';
2
+
3
+ export function createSentryMaintenanceActivities() {
4
+ return {
5
+ pruneUnclaimedSentryInstallationsActivity,
6
+ };
7
+ }
@@ -0,0 +1,52 @@
1
+ import {randomUUID} from 'node:crypto';
2
+ import {db} from '#db/db.js';
3
+ import {getSentryInstallationByInstallationUuid} from '#db/installations.js';
4
+ import {sentryInstallations} from '#db/schema/installations.js';
5
+ import {pruneUnclaimedSentryInstallationsActivity} from './prune-unclaimed-installations.js';
6
+
7
+ const DAY_MS = 24 * 60 * 60 * 1000;
8
+
9
+ // The activity derives its cutoff from SENTRY_UNCLAIMED_INSTALLATION_RETENTION_DAYS
10
+ // (default 7), so rows are seeded with an explicit createdAt to straddle it; the
11
+ // persist helpers always stamp createdAt=now and could not exercise the cutoff.
12
+ describe('pruneUnclaimedSentryInstallationsActivity', () => {
13
+ beforeEach(async () => {
14
+ await db().delete(sentryInstallations);
15
+ });
16
+
17
+ test('tombstones unclaimed installs older than the retention window', async () => {
18
+ const staleUuid = randomUUID();
19
+ await db()
20
+ .insert(sentryInstallations)
21
+ .values({
22
+ connectionId: null,
23
+ installationUuid: staleUuid,
24
+ orgSlug: 'acme',
25
+ status: 'installed',
26
+ createdAt: new Date(Date.now() - 8 * DAY_MS),
27
+ updatedAt: new Date(Date.now() - 8 * DAY_MS),
28
+ });
29
+
30
+ const result = await pruneUnclaimedSentryInstallationsActivity();
31
+
32
+ expect(result.tombstoned).toBe(1);
33
+ expect((await getSentryInstallationByInstallationUuid(staleUuid))?.status).toBe('deleted');
34
+ });
35
+
36
+ test('leaves unclaimed installs within the retention window untouched', async () => {
37
+ const freshUuid = randomUUID();
38
+ await db().insert(sentryInstallations).values({
39
+ connectionId: null,
40
+ installationUuid: freshUuid,
41
+ orgSlug: 'acme',
42
+ status: 'installed',
43
+ createdAt: new Date(),
44
+ updatedAt: new Date(),
45
+ });
46
+
47
+ const result = await pruneUnclaimedSentryInstallationsActivity();
48
+
49
+ expect(result.tombstoned).toBe(0);
50
+ expect((await getSentryInstallationByInstallationUuid(freshUuid))?.status).toBe('installed');
51
+ });
52
+ });
@@ -0,0 +1,11 @@
1
+ import {config} from '#config.js';
2
+ import {pruneUnclaimedSentryInstallations} from '#db/installations.js';
3
+
4
+ const MS_PER_DAY = 24 * 60 * 60 * 1000;
5
+
6
+ export async function pruneUnclaimedSentryInstallationsActivity(): Promise<{tombstoned: number}> {
7
+ const olderThan = new Date(
8
+ Date.now() - config.SENTRY_UNCLAIMED_INSTALLATION_RETENTION_DAYS * MS_PER_DAY,
9
+ );
10
+ return await pruneUnclaimedSentryInstallations({olderThan});
11
+ }
@@ -0,0 +1 @@
1
+ export const SENTRY_MAINTENANCE_TASK_QUEUE = 'integrations-sentry-maintenance';
@@ -0,0 +1 @@
1
+ export {pruneUnclaimedSentryInstallationsCron} from './prune-unclaimed-installations-cron.js';
@@ -0,0 +1,15 @@
1
+ import {log, proxyActivities} from '@temporalio/workflow';
2
+ import type {createSentryMaintenanceActivities} from '../activities/index.js';
3
+
4
+ const {pruneUnclaimedSentryInstallationsActivity} = proxyActivities<
5
+ ReturnType<typeof createSentryMaintenanceActivities>
6
+ >({
7
+ startToCloseTimeout: '5 minutes',
8
+ });
9
+
10
+ export async function pruneUnclaimedSentryInstallationsCron(): Promise<void> {
11
+ const {tombstoned} = await pruneUnclaimedSentryInstallationsActivity();
12
+ if (tombstoned > 0) {
13
+ log.info('Tombstoned stale unclaimed Sentry installations', {tombstoned});
14
+ }
15
+ }
package/test/env.ts ADDED
@@ -0,0 +1,11 @@
1
+ process.env.POSTGRES_HOST ??= 'localhost';
2
+ process.env.POSTGRES_PORT ??= '5432';
3
+ process.env.POSTGRES_USERNAME ??= 'shipfox';
4
+ process.env.POSTGRES_PASSWORD ??= 'password';
5
+ process.env.POSTGRES_DATABASE = 'api_test';
6
+ process.env.POSTGRES_MAX_CONNECTIONS ??= '5';
7
+ process.env.TZ = 'UTC';
8
+ process.env.SENTRY_APP_CLIENT_ID = 'test-client-id';
9
+ process.env.SENTRY_APP_CLIENT_SECRET = 'test-client-secret';
10
+ process.env.SENTRY_APP_SLUG = 'shipfox-test';
11
+ process.env.SENTRY_APP_VERIFY_INSTALL = 'true';
@@ -0,0 +1,9 @@
1
+ export {sentryInstallationFactory} from './sentry-installation.js';
2
+ export {
3
+ type SentryInstallationWebhookPayload,
4
+ sentryInstallationWebhookFactory,
5
+ } from './sentry-installation-webhook.js';
6
+ export {
7
+ type SentryIssueWebhookPayload,
8
+ sentryIssueWebhookFactory,
9
+ } from './sentry-issue-webhook.js';
@@ -0,0 +1,35 @@
1
+ import {Factory} from 'fishery';
2
+
3
+ // Raw Sentry installation webhook payload, shaped exactly as Sentry delivers it
4
+ // over the wire: the lifecycle data nests under `data.installation` and carries
5
+ // the single-use authorization `code`. Tests serialize the built object to form a
6
+ // signed request body; the route parses and validates it with the production Zod
7
+ // schema. Build-only — the payload is never persisted, so there is no onCreate
8
+ // handler.
9
+ export interface SentryInstallationWebhookPayload {
10
+ action: string;
11
+ actor?: {type?: string; id?: string | number; name?: string};
12
+ data: {
13
+ installation: {
14
+ uuid: string;
15
+ status?: string;
16
+ code?: string;
17
+ organization?: {slug: string};
18
+ };
19
+ };
20
+ }
21
+
22
+ export const sentryInstallationWebhookFactory = Factory.define<SentryInstallationWebhookPayload>(
23
+ ({sequence}) => ({
24
+ action: 'created',
25
+ actor: {type: 'user', id: sequence, name: 'Installer'},
26
+ data: {
27
+ installation: {
28
+ uuid: `install-${sequence}`,
29
+ status: 'installed',
30
+ code: `grant-code-${sequence}`,
31
+ organization: {slug: 'acme'},
32
+ },
33
+ },
34
+ }),
35
+ );
@@ -0,0 +1,44 @@
1
+ import {Factory} from 'fishery';
2
+ import {
3
+ persistVerifiedUnclaimedInstallation,
4
+ type SentryInstallation,
5
+ type SentryInstallationStatus,
6
+ upsertSentryInstallation,
7
+ } from '#db/installations.js';
8
+
9
+ // Persists either a claimed install (default: `connectionId` set) or, when
10
+ // `connectionId` is null, a verified-unclaimed install via the dedicated insert
11
+ // so tests can exercise the webhook-authoritative pre-claim state.
12
+ export const sentryInstallationFactory = Factory.define<SentryInstallation>(
13
+ ({sequence, onCreate}) => {
14
+ onCreate((installation) => {
15
+ if (installation.connectionId === null) {
16
+ return persistVerifiedUnclaimedInstallation({
17
+ installationUuid: installation.installationUuid,
18
+ orgSlug: installation.orgSlug,
19
+ codeHash: installation.codeHash ?? 'unclaimed-code-hash',
20
+ });
21
+ }
22
+ return upsertSentryInstallation({
23
+ connectionId: installation.connectionId,
24
+ installationUuid: installation.installationUuid,
25
+ orgSlug: installation.orgSlug,
26
+ status: installation.status as SentryInstallationStatus,
27
+ codeHash: installation.codeHash,
28
+ installerUserId: installation.installerUserId,
29
+ });
30
+ });
31
+
32
+ return {
33
+ id: crypto.randomUUID(),
34
+ connectionId: crypto.randomUUID(),
35
+ installationUuid: `install-${sequence + 1}`,
36
+ orgSlug: 'acme',
37
+ status: 'installed',
38
+ codeHash: null,
39
+ installerUserId: null,
40
+ createdAt: new Date(),
41
+ updatedAt: new Date(),
42
+ };
43
+ },
44
+ );
@@ -0,0 +1,34 @@
1
+ import {Factory} from 'fishery';
2
+
3
+ // Raw Sentry issue webhook payload, shaped exactly as Sentry delivers it over
4
+ // the wire. Tests serialize the built object to form a signed request body; the
5
+ // route parses and validates it with the production Zod schema. Build-only — the
6
+ // payload is never persisted, so there is no onCreate handler.
7
+ export interface SentryIssueWebhookPayload {
8
+ action: string;
9
+ installation: {uuid: string};
10
+ data: {issue: Record<string, unknown>};
11
+ }
12
+
13
+ export const sentryIssueWebhookFactory = Factory.define<SentryIssueWebhookPayload>(
14
+ ({sequence}) => ({
15
+ action: 'created',
16
+ installation: {uuid: `install-${sequence}`},
17
+ data: {
18
+ issue: {
19
+ id: 'issue-123',
20
+ shortId: 'PROJ-1',
21
+ title: 'TypeError: boom',
22
+ culprit: 'app/main',
23
+ level: 'error',
24
+ status: 'unresolved',
25
+ platform: 'javascript',
26
+ web_url: 'https://sentry.io/organizations/acme/issues/123/',
27
+ url: 'https://sentry.io/api/0/issues/123/',
28
+ project_url: 'https://sentry.io/api/0/projects/acme/web/',
29
+ firstSeen: '2026-06-01T00:00:00Z',
30
+ lastSeen: '2026-06-10T00:00:00Z',
31
+ },
32
+ },
33
+ }),
34
+ );
@@ -0,0 +1,14 @@
1
+ import './env.js';
2
+ import {runMigrations} from '@shipfox/node-drizzle';
3
+ import {closePostgresClient, createPostgresClient} from '@shipfox/node-postgres';
4
+ import {closeDb, db} from '#db/db.js';
5
+ import {migrationsPath} from '#db/migrations.js';
6
+
7
+ export async function setup() {
8
+ createPostgresClient();
9
+
10
+ await runMigrations(db(), migrationsPath, '__drizzle_migrations_integrations_sentry');
11
+
12
+ closeDb();
13
+ await closePostgresClient();
14
+ }
package/test/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ export {sentryInstallationFactory} from './factories/sentry-installation.js';
2
+ export {
3
+ type SentryInstallationWebhookPayload,
4
+ sentryInstallationWebhookFactory,
5
+ } from './factories/sentry-installation-webhook.js';
6
+ export {
7
+ type SentryIssueWebhookPayload,
8
+ sentryIssueWebhookFactory,
9
+ } from './factories/sentry-issue-webhook.js';
package/test/setup.ts ADDED
@@ -0,0 +1,17 @@
1
+ import './env.js';
2
+ import {closePostgresClient, createPostgresClient} from '@shipfox/node-postgres';
3
+ import {afterAll, afterEach, beforeAll, vi} from '@shipfox/vitest/vi';
4
+ import {closeDb} from '#db/db.js';
5
+
6
+ beforeAll(() => {
7
+ createPostgresClient();
8
+ });
9
+
10
+ afterEach(() => {
11
+ vi.restoreAllMocks();
12
+ });
13
+
14
+ afterAll(async () => {
15
+ closeDb();
16
+ await closePostgresClient();
17
+ });
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "@shipfox/ts-config/node",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist"
6
+ },
7
+ "include": ["src"],
8
+ "exclude": ["**/*.test.tsx", "**/*.test.ts"]
9
+ }