@shipfox/api-integration-core-dto 6.0.0 → 9.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 (60) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +25 -0
  3. package/dist/event-catalog.d.ts +15 -0
  4. package/dist/event-catalog.d.ts.map +1 -0
  5. package/dist/event-catalog.js +3 -0
  6. package/dist/event-catalog.js.map +1 -0
  7. package/dist/index.d.ts +1 -5
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +1 -5
  10. package/dist/index.js.map +1 -1
  11. package/dist/inter-module.d.ts +1 -1
  12. package/dist/schemas/index.d.ts +1 -1
  13. package/dist/schemas/index.d.ts.map +1 -1
  14. package/dist/schemas/index.js +1 -1
  15. package/dist/schemas/index.js.map +1 -1
  16. package/dist/schemas/integrations.d.ts +2 -2
  17. package/dist/schemas/integrations.d.ts.map +1 -1
  18. package/dist/schemas/integrations.js +2 -2
  19. package/dist/schemas/integrations.js.map +1 -1
  20. package/dist/tsconfig.test.tsbuildinfo +1 -1
  21. package/dist/webhooks.d.ts +0 -5
  22. package/dist/webhooks.d.ts.map +1 -1
  23. package/dist/webhooks.js +0 -17
  24. package/dist/webhooks.js.map +1 -1
  25. package/package.json +8 -13
  26. package/src/event-catalog.ts +16 -0
  27. package/src/index.ts +1 -5
  28. package/src/inter-module.test.ts +38 -0
  29. package/src/schemas/index.ts +1 -0
  30. package/src/schemas/integrations.ts +7 -2
  31. package/src/webhooks.test.ts +20 -1
  32. package/src/webhooks.ts +0 -24
  33. package/tsconfig.build.tsbuildinfo +1 -1
  34. package/dist/contracts/index.d.ts +0 -4
  35. package/dist/contracts/index.d.ts.map +0 -1
  36. package/dist/contracts/index.js +0 -3
  37. package/dist/contracts/index.js.map +0 -1
  38. package/dist/contracts/integrations.d.ts +0 -206
  39. package/dist/contracts/integrations.d.ts.map +0 -1
  40. package/dist/contracts/integrations.js +0 -44
  41. package/dist/contracts/integrations.js.map +0 -1
  42. package/dist/errors.d.ts +0 -4
  43. package/dist/errors.d.ts.map +0 -1
  44. package/dist/errors.js +0 -10
  45. package/dist/errors.js.map +0 -1
  46. package/dist/ports.d.ts +0 -66
  47. package/dist/ports.d.ts.map +0 -1
  48. package/dist/ports.js +0 -3
  49. package/dist/ports.js.map +0 -1
  50. package/dist/slug.d.ts +0 -7
  51. package/dist/slug.d.ts.map +0 -1
  52. package/dist/slug.js +0 -14
  53. package/dist/slug.js.map +0 -1
  54. package/src/contracts/index.ts +0 -48
  55. package/src/contracts/integrations.test.ts +0 -64
  56. package/src/contracts/integrations.ts +0 -325
  57. package/src/errors.ts +0 -6
  58. package/src/ports.ts +0 -79
  59. package/src/slug.test.ts +0 -32
  60. package/src/slug.ts +0 -26
@@ -1,325 +0,0 @@
1
- export type IntegrationProviderKind = string;
2
- export type IntegrationCapability = 'source_control' | 'agent_tools';
3
- export type IntegrationConnectionLifecycleStatus = 'active' | 'disabled' | 'error';
4
-
5
- export interface IntegrationConnection<
6
- ProviderKind extends IntegrationProviderKind = IntegrationProviderKind,
7
- > {
8
- id: string;
9
- workspaceId: string;
10
- provider: ProviderKind;
11
- externalAccountId: string;
12
- slug: string;
13
- displayName: string;
14
- lifecycleStatus: IntegrationConnectionLifecycleStatus;
15
- createdAt: Date;
16
- updatedAt: Date;
17
- }
18
-
19
- export type RepositoryVisibility = 'public' | 'private' | 'internal' | 'unknown';
20
-
21
- export interface RepositorySnapshot {
22
- externalRepositoryId: string;
23
- owner: string;
24
- name: string;
25
- fullName: string;
26
- defaultBranch: string;
27
- visibility: RepositoryVisibility;
28
- cloneUrl: string;
29
- htmlUrl: string;
30
- }
31
-
32
- export interface RepositoryPage {
33
- repositories: RepositorySnapshot[];
34
- nextCursor: string | null;
35
- }
36
-
37
- export interface ListRepositoriesInput<
38
- Connection extends IntegrationConnection = IntegrationConnection,
39
- > {
40
- connection: Connection;
41
- limit: number;
42
- cursor?: string | undefined;
43
- search?: string | undefined;
44
- }
45
-
46
- export interface ResolveRepositoryInput<
47
- Connection extends IntegrationConnection = IntegrationConnection,
48
- > {
49
- connection: Connection;
50
- externalRepositoryId: string;
51
- }
52
-
53
- export interface FileSnapshot {
54
- path: string;
55
- ref: string;
56
- content: string;
57
- }
58
-
59
- export interface FileEntry {
60
- path: string;
61
- type: 'file';
62
- size: number | null;
63
- }
64
-
65
- export interface FilePage {
66
- files: FileEntry[];
67
- nextCursor: string | null;
68
- }
69
-
70
- export interface ListFilesInput<Connection extends IntegrationConnection = IntegrationConnection>
71
- extends ResolveRepositoryInput<Connection> {
72
- ref: string;
73
- prefix: string;
74
- limit: number;
75
- cursor?: string | undefined;
76
- }
77
-
78
- export interface FetchFileInput<Connection extends IntegrationConnection = IntegrationConnection>
79
- extends ResolveRepositoryInput<Connection> {
80
- ref: string;
81
- path: string;
82
- }
83
-
84
- export interface CheckoutCredentials {
85
- username: string;
86
- token: string;
87
- expiresAt: Date;
88
- }
89
-
90
- export interface CheckoutGitAuthor {
91
- name: string;
92
- email: string;
93
- }
94
-
95
- export interface CheckoutPermissions {
96
- contents: 'read' | 'write';
97
- }
98
-
99
- export interface CheckoutSpec {
100
- /**
101
- * Clone URL that must never embed authentication material. Credentials live
102
- * only in `credentials` so a redaction helper can mask them; a provider that
103
- * embeds a token in this URL would bypass redaction and leak it into logs,
104
- * `git remote -v`, and persisted job rows.
105
- */
106
- repositoryUrl: string;
107
- ref: string;
108
- credentials?: CheckoutCredentials | undefined;
109
- gitAuthor?: CheckoutGitAuthor | undefined;
110
- }
111
-
112
- export interface CreateCheckoutSpecInput<
113
- Connection extends IntegrationConnection = IntegrationConnection,
114
- > extends ResolveRepositoryInput<Connection> {
115
- ref?: string | undefined;
116
- permissions?: CheckoutPermissions | undefined;
117
- }
118
-
119
- export interface SourceControlProvider<
120
- Connection extends IntegrationConnection = IntegrationConnection,
121
- > {
122
- listRepositories(input: ListRepositoriesInput<Connection>): Promise<RepositoryPage>;
123
- resolveRepository(input: ResolveRepositoryInput<Connection>): Promise<RepositorySnapshot>;
124
- listFiles(input: ListFilesInput<Connection>): Promise<FilePage>;
125
- fetchFile(input: FetchFileInput<Connection>): Promise<FileSnapshot>;
126
- createCheckoutSpec?(input: CreateCheckoutSpecInput<Connection>): Promise<CheckoutSpec>;
127
- }
128
-
129
- export type AgentToolSensitivity = 'read' | 'write';
130
- export type AgentToolJsonSchema = Record<string, unknown>;
131
-
132
- export interface AgentToolCatalogMethod<RequiredScope = unknown> {
133
- id: string;
134
- description: string;
135
- sensitivity: AgentToolSensitivity;
136
- sensitive: boolean;
137
- requiredScope: RequiredScope;
138
- }
139
-
140
- export interface AgentToolCatalogEntry<RequiredScope = unknown> {
141
- id: string;
142
- description: string;
143
- sensitivity: AgentToolSensitivity;
144
- sensitive: boolean;
145
- requiredScope: RequiredScope;
146
- inputSchema: AgentToolJsonSchema;
147
- outputSchema?: AgentToolJsonSchema | undefined;
148
- methods?: readonly AgentToolCatalogMethod<RequiredScope>[] | undefined;
149
- }
150
-
151
- export type AgentToolSelectorKind = 'family' | 'family_wildcard' | 'method' | 'standalone';
152
-
153
- export interface AgentToolSelector {
154
- readonly token: string;
155
- readonly kind: AgentToolSelectorKind;
156
- readonly sensitivity: AgentToolSensitivity;
157
- readonly sensitive: boolean;
158
- }
159
-
160
- export interface AgentToolSelectionCatalog {
161
- readonly selectors: readonly AgentToolSelector[];
162
- }
163
-
164
- export interface AgentToolCallInput {
165
- toolId: string;
166
- arguments: Record<string, unknown>;
167
- }
168
-
169
- export interface AgentToolSession<CallResult = unknown> {
170
- call(input: AgentToolCallInput): Promise<CallResult>;
171
- close?(): Promise<void>;
172
- }
173
-
174
- export interface OpenAgentToolsSessionInput<
175
- Connection extends IntegrationConnection = IntegrationConnection,
176
- RequiredScope = unknown,
177
- ProviderScope = unknown,
178
- > {
179
- connection: Connection;
180
- tools: readonly AgentToolCatalogEntry<RequiredScope>[];
181
- scope: ProviderScope;
182
- }
183
-
184
- export interface AgentToolsProvider<
185
- Connection extends IntegrationConnection = IntegrationConnection,
186
- RequiredScope = unknown,
187
- ProviderScope = unknown,
188
- CallResult = unknown,
189
- > {
190
- catalog():
191
- | readonly AgentToolCatalogEntry<RequiredScope>[]
192
- | Promise<readonly AgentToolCatalogEntry<RequiredScope>[]>;
193
- selectionCatalog(): AgentToolSelectionCatalog | Promise<AgentToolSelectionCatalog>;
194
- openSession(
195
- input: OpenAgentToolsSessionInput<Connection, RequiredScope, ProviderScope>,
196
- ): Promise<AgentToolSession<CallResult>>;
197
- }
198
-
199
- export interface IntegrationProviderAdapters<
200
- Connection extends IntegrationConnection = IntegrationConnection,
201
- > {
202
- source_control?: SourceControlProvider<Connection> | undefined;
203
- agent_tools?: AgentToolsProvider<Connection> | undefined;
204
- }
205
-
206
- export interface IntegrationProvider<
207
- ProviderKind extends IntegrationProviderKind = IntegrationProviderKind,
208
- Route = unknown,
209
- Connection extends IntegrationConnection<ProviderKind> = IntegrationConnection<ProviderKind>,
210
- > {
211
- provider: ProviderKind;
212
- displayName: string;
213
- adapters?: IntegrationProviderAdapters<Connection> | undefined;
214
- routes?: Route[] | undefined;
215
- /**
216
- * Resolves the provider-side home of a connection (e.g. the Sentry org or the
217
- * GitHub installation settings page). Returns undefined when the connection has
218
- * no external home or the provider-side record is missing.
219
- */
220
- connectionExternalUrl?(connection: Connection): Promise<string | undefined>;
221
- /**
222
- * Deletes provider-owned persistence rows for a connection inside the caller's
223
- * transaction. The transaction belongs to the shared integration database, so
224
- * provider persistence must use it. The core delete route owns the connection row.
225
- * Providers without either cleanup hook remove only that core row.
226
- */
227
- deleteConnectionRecords?(connection: Connection, options: {tx: unknown}): Promise<void>;
228
- /**
229
- * Deletes provider-owned secrets after the connection transaction commits.
230
- * External secret stores cannot participate in the database transaction, so a
231
- * failure is logged after the connection has already been deleted.
232
- */
233
- deleteConnectionSecrets?(connection: Connection): Promise<void>;
234
- }
235
-
236
- export interface RegisteredIntegrationProvider<
237
- ProviderKind extends IntegrationProviderKind = IntegrationProviderKind,
238
- Route = unknown,
239
- Connection extends IntegrationConnection<ProviderKind> = IntegrationConnection<ProviderKind>,
240
- > extends IntegrationProvider<ProviderKind, Route, Connection> {
241
- adapters: IntegrationProviderAdapters<Connection>;
242
- capabilities: IntegrationCapability[];
243
- }
244
-
245
- export type IntegrationProviderErrorReason =
246
- | 'repository-not-found'
247
- | 'installation-not-found'
248
- | 'file-not-found'
249
- | 'access-denied'
250
- | 'rate-limited'
251
- | 'timeout'
252
- | 'provider-unavailable'
253
- | 'malformed-provider-response'
254
- | 'content-too-large'
255
- | 'too-many-files';
256
-
257
- export class IntegrationProviderError extends Error {
258
- constructor(
259
- public readonly reason: IntegrationProviderErrorReason,
260
- message: string,
261
- public readonly retryAfterSeconds?: number | undefined,
262
- ) {
263
- super(message);
264
- this.name = 'IntegrationProviderError';
265
- }
266
- }
267
-
268
- export const MAX_REPOSITORY_FILE_BYTES = 1_000_000;
269
-
270
- export function toIntegrationConnectionDto(
271
- connection: IntegrationConnection,
272
- options: {
273
- capabilities: IntegrationCapability[];
274
- externalUrl?: string | undefined;
275
- },
276
- ) {
277
- return {
278
- id: connection.id,
279
- workspace_id: connection.workspaceId,
280
- provider: connection.provider,
281
- external_account_id: connection.externalAccountId,
282
- slug: connection.slug,
283
- display_name: connection.displayName,
284
- lifecycle_status: connection.lifecycleStatus,
285
- capabilities: options.capabilities,
286
- ...(options.externalUrl ? {external_url: options.externalUrl} : {}),
287
- created_at: connection.createdAt.toISOString(),
288
- updated_at: connection.updatedAt.toISOString(),
289
- };
290
- }
291
-
292
- export function buildProviderRepositoryId(
293
- provider: IntegrationProviderKind,
294
- value: string,
295
- ): string {
296
- return `${provider}:${value}`;
297
- }
298
-
299
- export function parseProviderRepositoryId(
300
- externalRepositoryId: string,
301
- expectedProvider: IntegrationProviderKind,
302
- ): string {
303
- const separatorIndex = externalRepositoryId.indexOf(':');
304
- if (separatorIndex <= 0) {
305
- throw new IntegrationProviderError(
306
- 'repository-not-found',
307
- `External repository id is missing a provider prefix: ${externalRepositoryId}`,
308
- );
309
- }
310
- const prefix = externalRepositoryId.slice(0, separatorIndex);
311
- const value = externalRepositoryId.slice(separatorIndex + 1);
312
- if (prefix !== expectedProvider) {
313
- throw new IntegrationProviderError(
314
- 'repository-not-found',
315
- `External repository id ${externalRepositoryId} is not owned by provider ${expectedProvider}`,
316
- );
317
- }
318
- if (!value) {
319
- throw new IntegrationProviderError(
320
- 'repository-not-found',
321
- `External repository id ${externalRepositoryId} is missing a provider-owned value`,
322
- );
323
- }
324
- return value;
325
- }
package/src/errors.ts DELETED
@@ -1,6 +0,0 @@
1
- export class ConnectionSlugConflictError extends Error {
2
- constructor(cause: unknown) {
3
- super('Could not allocate a unique integration connection slug. Try again.', {cause});
4
- this.name = 'ConnectionSlugConflictError';
5
- }
6
- }
package/src/ports.ts DELETED
@@ -1,79 +0,0 @@
1
- import type {
2
- IntegrationConnection,
3
- IntegrationConnectionLifecycleStatus,
4
- } from '#contracts/index.js';
5
- import type {IntegrationEventReceivedEvent, SourcePushPayload} from '#events.js';
6
-
7
- // The persistence seam between the integrations core and a provider package.
8
- // `@shipfox/api-integration-core` implements these against its own tables and
9
- // injects them into each provider at composition time, so providers depend only
10
- // on this contract — never on core itself, which would create a dependency cycle
11
- // (core composes the providers).
12
-
13
- /**
14
- * Database/transaction handle threaded from core into a provider and back into
15
- * core-owned persistence. Typed loosely so providers don't take a Drizzle
16
- * dependency just to pass a transaction through; core supplies the concrete
17
- * executor and conformance is checked where it wires the providers.
18
- */
19
- // biome-ignore lint/suspicious/noExplicitAny: cross-package tx handle, kept opaque to avoid a cyclic dep on core
20
- export type IntegrationTx = any;
21
-
22
- export type PublishIntegrationEventReceivedFn = (params: {
23
- tx: IntegrationTx;
24
- event: IntegrationEventReceivedEvent;
25
- }) => Promise<{published: boolean}>;
26
-
27
- export type CreateIntegrationConnectionFn = (
28
- params: {
29
- workspaceId: string;
30
- provider: string;
31
- externalAccountId: string;
32
- slug: string;
33
- displayName: string;
34
- lifecycleStatus?: IntegrationConnectionLifecycleStatus | undefined;
35
- },
36
- options?: {tx?: IntegrationTx},
37
- ) => Promise<IntegrationConnection>;
38
-
39
- // Emitted by source-control providers for a single push. The delivery dedup and the outbox
40
- // rows it writes must commit together, so it requires a transaction, never a bare connection.
41
- export type PublishSourcePushFn = (params: {
42
- tx: IntegrationTx;
43
- provider: string;
44
- source: string;
45
- workspaceId: string;
46
- connectionId: string;
47
- connectionName: string;
48
- deliveryId: string;
49
- receivedAt: string;
50
- rawPayload: unknown;
51
- push: SourcePushPayload;
52
- }) => Promise<{published: boolean}>;
53
-
54
- export type RecordDeliveryOnlyFn = (params: {
55
- tx: IntegrationTx;
56
- provider: string;
57
- deliveryId: string;
58
- }) => Promise<void>;
59
-
60
- export type ClaimWebhookDeliveryFn = (params: {
61
- tx: IntegrationTx;
62
- provider: string;
63
- deliveryId: string;
64
- }) => Promise<{claimed: boolean}>;
65
-
66
- export type GetIntegrationConnectionByIdFn = (
67
- id: string,
68
- options?: {tx?: IntegrationTx},
69
- ) => Promise<IntegrationConnection | undefined>;
70
-
71
- export type UpdateIntegrationConnectionLifecycleStatusFn = (
72
- params: {id: string; lifecycleStatus: IntegrationConnectionLifecycleStatus},
73
- options?: {tx?: IntegrationTx},
74
- ) => Promise<IntegrationConnection | undefined>;
75
-
76
- export type DeleteIntegrationConnectionFn = (
77
- params: {id: string},
78
- options?: {tx?: IntegrationTx},
79
- ) => Promise<boolean>;
package/src/slug.test.ts DELETED
@@ -1,32 +0,0 @@
1
- import {connectionSlugSchema, slugifyConnectionSlug} from './slug.js';
2
-
3
- describe('slugifyConnectionSlug', () => {
4
- it.each([
5
- ['GitHub Acme', 'github_acme'],
6
- ['github_acme', 'github_acme'],
7
- ['github-acme', 'github_acme'],
8
- [' acme prod ', 'acme_prod'],
9
- ['___acme___', 'acme'],
10
- ['!!!', 'github'],
11
- ['こんにちは', 'github'],
12
- ])('normalizes "%s"', (input, expected) => {
13
- const result = slugifyConnectionSlug(input, {fallback: 'github'});
14
-
15
- expect(result).toBe(expected);
16
- expect(connectionSlugSchema.safeParse(result).success).toBe(true);
17
- });
18
- });
19
-
20
- describe('connectionSlugSchema', () => {
21
- it.each(['a-b', 'a_b', 'github_my_org'])('accepts %s', (slug) => {
22
- const result = connectionSlugSchema.safeParse(slug);
23
-
24
- expect(result.success).toBe(true);
25
- });
26
-
27
- it.each(['Github', '_x', 'x_', 'a__b', '', 'a'.repeat(101)])('rejects %s', (slug) => {
28
- const result = connectionSlugSchema.safeParse(slug);
29
-
30
- expect(result.success).toBe(false);
31
- });
32
- });
package/src/slug.ts DELETED
@@ -1,26 +0,0 @@
1
- import {z} from 'zod';
2
-
3
- export const CONNECTION_SLUG_MAX_LENGTH = 100;
4
-
5
- export const connectionSlugSchema = z
6
- .string()
7
- .min(1)
8
- .max(CONNECTION_SLUG_MAX_LENGTH)
9
- .regex(/^[a-z0-9]+(?:[_-][a-z0-9]+)*$/);
10
-
11
- export function slugifyConnectionSlug(input: string, options: {fallback: string}): string {
12
- const slug = normalizeSlug(input);
13
- if (slug) return slug;
14
-
15
- const fallback = normalizeSlug(options.fallback);
16
- return fallback || 'connection';
17
- }
18
-
19
- function normalizeSlug(input: string): string {
20
- return input
21
- .toLowerCase()
22
- .replaceAll(/[^a-z0-9]+/g, '_')
23
- .replaceAll(/^_+|_+$/g, '')
24
- .slice(0, CONNECTION_SLUG_MAX_LENGTH)
25
- .replaceAll(/_+$/g, '');
26
- }