@shipfox/api-integration-core-dto 9.0.2 → 12.2.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.
@@ -4,14 +4,15 @@ export declare const WEBHOOK_MAX_RAW_BODY_BYTES: number;
4
4
  export declare const WEBHOOK_MAX_SERIALIZED_REQUEST_BYTES: number;
5
5
  export declare const WEBHOOK_MAX_HEADER_BYTES: number;
6
6
  export declare const WEBHOOK_MAX_RAW_QUERY_STRING_LENGTH: number;
7
- export declare const webhookRouteIds: readonly ["github", "gitea", "linear", "sentry", "slack.event", "slack.command", "webhook.connection"];
7
+ export declare const webhookRouteIds: readonly ["github", "gitea", "linear", "sentry", "slack.event", "slack.command", "webhook.connection", "jira"];
8
8
  export declare const webhookRouteIdSchema: z.ZodEnum<{
9
- github: "github";
10
9
  gitea: "gitea";
10
+ github: "github";
11
+ jira: "jira";
11
12
  linear: "linear";
12
13
  sentry: "sentry";
13
- "slack.event": "slack.event";
14
14
  "slack.command": "slack.command";
15
+ "slack.event": "slack.event";
15
16
  "webhook.connection": "webhook.connection";
16
17
  }>;
17
18
  export type WebhookRouteId = z.infer<typeof webhookRouteIdSchema>;
@@ -31,12 +32,12 @@ export declare const storedWebhookRequestSchema: z.ZodDiscriminatedUnion<[z.ZodO
31
32
  data: z.ZodString;
32
33
  }, z.core.$strict>;
33
34
  route_id: z.ZodEnum<{
34
- github: "github";
35
35
  gitea: "gitea";
36
+ github: "github";
36
37
  linear: "linear";
37
38
  sentry: "sentry";
38
- "slack.event": "slack.event";
39
39
  "slack.command": "slack.command";
40
+ "slack.event": "slack.event";
40
41
  }>;
41
42
  path_parameters: z.ZodObject<{}, z.core.$strict>;
42
43
  }, z.core.$strict>, z.ZodObject<{
@@ -54,6 +55,21 @@ export declare const storedWebhookRequestSchema: z.ZodDiscriminatedUnion<[z.ZodO
54
55
  path_parameters: z.ZodObject<{
55
56
  connection_id: z.ZodString;
56
57
  }, z.core.$strict>;
58
+ }, z.core.$strict>, z.ZodObject<{
59
+ schema_version: z.ZodLiteral<1>;
60
+ request_id: z.ZodString;
61
+ received_at: z.ZodString;
62
+ method: z.ZodLiteral<"POST">;
63
+ raw_query_string: z.ZodString;
64
+ headers: z.ZodRecord<z.ZodString, z.ZodString>;
65
+ body: z.ZodObject<{
66
+ encoding: z.ZodLiteral<"base64">;
67
+ data: z.ZodString;
68
+ }, z.core.$strict>;
69
+ route_id: z.ZodLiteral<"jira">;
70
+ path_parameters: z.ZodObject<{
71
+ connection_id: z.ZodString;
72
+ }, z.core.$strict>;
57
73
  }, z.core.$strict>], "route_id">;
58
74
  export type StoredWebhookRequest = z.infer<typeof storedWebhookRequestSchema>;
59
75
  export declare const webhookProcessingResultSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -66,25 +82,32 @@ export declare const webhookProcessingResultSchema: z.ZodDiscriminatedUnion<[z.Z
66
82
  }, z.core.$strict>, z.ZodObject<{
67
83
  outcome: z.ZodLiteral<"discarded">;
68
84
  reason: z.ZodEnum<{
69
- missing_required_input: "missing_required_input";
85
+ connection_unavailable: "connection_unavailable";
70
86
  invalid_signature: "invalid_signature";
71
- stale_at_receipt: "stale_at_receipt";
72
87
  malformed_payload: "malformed_payload";
88
+ missing_required_input: "missing_required_input";
89
+ stale_at_receipt: "stale_at_receipt";
73
90
  unsupported_event: "unsupported_event";
74
- connection_unavailable: "connection_unavailable";
75
91
  }>;
76
92
  deliveryId: z.ZodOptional<z.ZodString>;
77
93
  }, z.core.$strict>], "outcome">;
78
94
  export type WebhookProcessingResult = z.infer<typeof webhookProcessingResultSchema>;
79
- export interface CreateStoredWebhookRequestInput {
95
+ interface CreateStoredWebhookRequestInputBase {
80
96
  requestId: string;
81
97
  routeId: WebhookRouteId;
82
98
  receivedAt: string;
83
99
  rawQueryString: string;
84
100
  headers: Record<string, string>;
85
101
  body: Uint8Array;
86
- connectionId?: string | undefined;
87
102
  }
103
+ type ConnectionScopedWebhookRouteId = 'webhook.connection' | 'jira';
104
+ export type CreateStoredWebhookRequestInput = (CreateStoredWebhookRequestInputBase & {
105
+ routeId: ConnectionScopedWebhookRouteId;
106
+ connectionId: string;
107
+ }) | (CreateStoredWebhookRequestInputBase & {
108
+ routeId: Exclude<WebhookRouteId, ConnectionScopedWebhookRouteId>;
109
+ connectionId?: never;
110
+ });
88
111
  export declare function encodeWebhookBody(body: Uint8Array): string;
89
112
  export declare function decodeWebhookBody(body: z.infer<typeof webhookBodySchema>): Uint8Array;
90
113
  export declare function createStoredWebhookRequest(input: CreateStoredWebhookRequestInput): StoredWebhookRequest;
@@ -1 +1 @@
1
- {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,eAAO,MAAM,8BAA8B,EAAG,CAAU,CAAC;AACzD,eAAO,MAAM,0BAA0B,QAAa,CAAC;AACrD,eAAO,MAAM,oCAAoC,QAAc,CAAC;AAChE,eAAO,MAAM,wBAAwB,QAAY,CAAC;AAClD,eAAO,MAAM,mCAAmC,QAAW,CAAC;AAW5D,eAAO,MAAM,eAAe,wGAA8D,CAAC;AAE3F,eAAO,MAAM,oBAAoB;;;;;;;;EAA0B,CAAC;AAC5D,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AA4BlE,QAAA,MAAM,iBAAiB;;;kBAYpB,CAAC;AA6BJ,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAGrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;+BAuBxC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF,MAAM,WAAW,+BAA+B;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,cAAc,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,UAAU,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CA0B1D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,GAAG,UAAU,CAyBrF;AAED,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,+BAA+B,GACrC,oBAAoB,CAetB"}
1
+ {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,eAAO,MAAM,8BAA8B,EAAG,CAAU,CAAC;AACzD,eAAO,MAAM,0BAA0B,QAAa,CAAC;AACrD,eAAO,MAAM,oCAAoC,QAAc,CAAC;AAChE,eAAO,MAAM,wBAAwB,QAAY,CAAC;AAClD,eAAO,MAAM,mCAAmC,QAAW,CAAC;AAW5D,eAAO,MAAM,eAAe,gHAAsE,CAAC;AAEnG,eAAO,MAAM,oBAAoB;;;;;;;;;EAA0B,CAAC;AAC5D,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AA4BlE,QAAA,MAAM,iBAAiB;;;kBAYpB,CAAC;AAoCJ,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAIrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;+BAuBxC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF,UAAU,mCAAmC;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,cAAc,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,KAAK,8BAA8B,GAAG,oBAAoB,GAAG,MAAM,CAAC;AAEpE,MAAM,MAAM,+BAA+B,GACvC,CAAC,mCAAmC,GAAG;IACrC,OAAO,EAAE,8BAA8B,CAAC;IACxC,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC,GACF,CAAC,mCAAmC,GAAG;IACrC,OAAO,EAAE,OAAO,CAAC,cAAc,EAAE,8BAA8B,CAAC,CAAC;IACjE,YAAY,CAAC,EAAE,KAAK,CAAC;CACtB,CAAC,CAAC;AAEP,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CA0B1D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,GAAG,UAAU,CAyBrF;AAED,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,+BAA+B,GACrC,oBAAoB,CAiBtB"}
package/dist/webhooks.js CHANGED
@@ -14,7 +14,8 @@ const standardWebhookRouteIds = [
14
14
  ];
15
15
  export const webhookRouteIds = [
16
16
  ...standardWebhookRouteIds,
17
- 'webhook.connection'
17
+ 'webhook.connection',
18
+ 'jira'
18
19
  ];
19
20
  export const webhookRouteIdSchema = z.enum(webhookRouteIds);
20
21
  const base64Pattern = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
@@ -53,9 +54,14 @@ const genericWebhookRouteRequestSchema = storedWebhookRequestBaseSchema.extend({
53
54
  route_id: z.literal('webhook.connection'),
54
55
  path_parameters: connectionPathParametersSchema
55
56
  }).strict();
57
+ const jiraWebhookRouteRequestSchema = storedWebhookRequestBaseSchema.extend({
58
+ route_id: z.literal('jira'),
59
+ path_parameters: connectionPathParametersSchema
60
+ }).strict();
56
61
  export const storedWebhookRequestSchema = z.discriminatedUnion('route_id', [
57
62
  standardWebhookRouteRequestSchema,
58
- genericWebhookRouteRequestSchema
63
+ genericWebhookRouteRequestSchema,
64
+ jiraWebhookRouteRequestSchema
59
65
  ]);
60
66
  export const webhookProcessingResultSchema = z.discriminatedUnion('outcome', [
61
67
  z.object({
@@ -112,7 +118,7 @@ export function decodeWebhookBody(body) {
112
118
  return output;
113
119
  }
114
120
  export function createStoredWebhookRequest(input) {
115
- const pathParameters = input.routeId === 'webhook.connection' ? {
121
+ const pathParameters = input.routeId === 'webhook.connection' || input.routeId === 'jira' ? {
116
122
  connection_id: input.connectionId
117
123
  } : {};
118
124
  return storedWebhookRequestSchema.parse({
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/webhooks.ts"],"sourcesContent":["import {z} from 'zod';\n\nexport const WEBHOOK_REQUEST_SCHEMA_VERSION = 1 as const;\nexport const WEBHOOK_MAX_RAW_BODY_BYTES = 512 * 1024;\nexport const WEBHOOK_MAX_SERIALIZED_REQUEST_BYTES = 1024 * 1024;\nexport const WEBHOOK_MAX_HEADER_BYTES = 64 * 1024;\nexport const WEBHOOK_MAX_RAW_QUERY_STRING_LENGTH = 8 * 1024;\n\nconst standardWebhookRouteIds = [\n 'github',\n 'gitea',\n 'linear',\n 'sentry',\n 'slack.event',\n 'slack.command',\n] as const;\n\nexport const webhookRouteIds = [...standardWebhookRouteIds, 'webhook.connection'] as const;\n\nexport const webhookRouteIdSchema = z.enum(webhookRouteIds);\nexport type WebhookRouteId = z.infer<typeof webhookRouteIdSchema>;\n\nconst base64Pattern = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;\n\nfunction decodedBase64Length(value: string): number {\n const paddingLength = value.endsWith('==') ? 2 : value.endsWith('=') ? 1 : 0;\n\n return (value.length / 4) * 3 - paddingLength;\n}\n\nfunction headersByteLength(headers: Record<string, string>): number {\n return new TextEncoder().encode(JSON.stringify(headers)).byteLength;\n}\n\nconst webhookHeadersSchema = z\n .record(z.string(), z.string().max(8 * 1024))\n .refine(\n (headers) =>\n Object.keys(headers).every(\n (name) => name === name.toLowerCase() && name.length > 0 && name.length <= 128,\n ),\n 'Webhook header names must be lowercase and at most 128 characters',\n )\n .refine(\n (headers) => headersByteLength(headers) <= WEBHOOK_MAX_HEADER_BYTES,\n `Webhook headers must serialize to at most ${WEBHOOK_MAX_HEADER_BYTES} bytes`,\n );\n\nconst webhookBodySchema = z\n .object({\n encoding: z.literal('base64'),\n data: z\n .string()\n .max(Math.ceil(WEBHOOK_MAX_RAW_BODY_BYTES / 3) * 4)\n .regex(base64Pattern),\n })\n .strict()\n .refine(\n (body) => decodedBase64Length(body.data) <= WEBHOOK_MAX_RAW_BODY_BYTES,\n `Webhook bodies must decode to at most ${WEBHOOK_MAX_RAW_BODY_BYTES} bytes`,\n );\n\nconst storedWebhookRequestBaseSchema = z.object({\n schema_version: z.literal(WEBHOOK_REQUEST_SCHEMA_VERSION),\n request_id: z.string().uuid(),\n received_at: z.string().datetime({offset: true}),\n method: z.literal('POST'),\n raw_query_string: z.string().max(WEBHOOK_MAX_RAW_QUERY_STRING_LENGTH),\n headers: webhookHeadersSchema,\n body: webhookBodySchema,\n});\n\nconst emptyPathParametersSchema = z.object({}).strict();\nconst connectionPathParametersSchema = z.object({connection_id: z.string().uuid()}).strict();\n\nconst standardWebhookRouteRequestSchema = storedWebhookRequestBaseSchema\n .extend({\n route_id: z.enum(standardWebhookRouteIds),\n path_parameters: emptyPathParametersSchema,\n })\n .strict();\n\nconst genericWebhookRouteRequestSchema = storedWebhookRequestBaseSchema\n .extend({\n route_id: z.literal('webhook.connection'),\n path_parameters: connectionPathParametersSchema,\n })\n .strict();\n\nexport const storedWebhookRequestSchema = z.discriminatedUnion('route_id', [\n standardWebhookRouteRequestSchema,\n genericWebhookRouteRequestSchema,\n]);\nexport type StoredWebhookRequest = z.infer<typeof storedWebhookRequestSchema>;\n\nexport const webhookProcessingResultSchema = z.discriminatedUnion('outcome', [\n z\n .object({\n outcome: z.literal('processed'),\n deliveryId: z.string().min(1).optional(),\n challenge: z.string().min(1).optional(),\n })\n .strict(),\n z.object({outcome: z.literal('duplicate'), deliveryId: z.string().min(1)}).strict(),\n z\n .object({\n outcome: z.literal('discarded'),\n reason: z.enum([\n 'missing_required_input',\n 'invalid_signature',\n 'stale_at_receipt',\n 'malformed_payload',\n 'unsupported_event',\n 'connection_unavailable',\n ]),\n deliveryId: z.string().min(1).optional(),\n })\n .strict(),\n]);\nexport type WebhookProcessingResult = z.infer<typeof webhookProcessingResultSchema>;\n\nexport interface CreateStoredWebhookRequestInput {\n requestId: string;\n routeId: WebhookRouteId;\n receivedAt: string;\n rawQueryString: string;\n headers: Record<string, string>;\n body: Uint8Array;\n connectionId?: string | undefined;\n}\n\nexport function encodeWebhookBody(body: Uint8Array): string {\n let encoded = '';\n\n for (let index = 0; index < body.length; index += 3) {\n const first = body[index] ?? 0;\n const second = body[index + 1];\n const third = body[index + 2];\n const combined = (first << 16) | ((second ?? 0) << 8) | (third ?? 0);\n\n encoded += 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'[\n (combined >> 18) & 63\n ];\n encoded += 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'[\n (combined >> 12) & 63\n ];\n encoded +=\n second === undefined\n ? '='\n : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'[(combined >> 6) & 63];\n encoded +=\n third === undefined\n ? '='\n : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'[combined & 63];\n }\n\n return encoded;\n}\n\nexport function decodeWebhookBody(body: z.infer<typeof webhookBodySchema>): Uint8Array {\n const output = new Uint8Array(decodedBase64Length(body.data));\n let outputIndex = 0;\n\n for (let index = 0; index < body.data.length; index += 4) {\n const chunk = body.data.slice(index, index + 4);\n const values = chunk\n .split('')\n .map((character) =>\n character === '='\n ? 0\n : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.indexOf(character),\n );\n const first = values[0] ?? 0;\n const second = values[1] ?? 0;\n const third = values[2] ?? 0;\n const fourth = values[3] ?? 0;\n const combined = (first << 18) | (second << 12) | (third << 6) | fourth;\n\n output[outputIndex++] = (combined >> 16) & 255;\n if (chunk[2] !== '=') output[outputIndex++] = (combined >> 8) & 255;\n if (chunk[3] !== '=') output[outputIndex++] = combined & 255;\n }\n\n return output;\n}\n\nexport function createStoredWebhookRequest(\n input: CreateStoredWebhookRequestInput,\n): StoredWebhookRequest {\n const pathParameters =\n input.routeId === 'webhook.connection' ? {connection_id: input.connectionId} : {};\n\n return storedWebhookRequestSchema.parse({\n schema_version: WEBHOOK_REQUEST_SCHEMA_VERSION,\n request_id: input.requestId,\n route_id: input.routeId,\n received_at: input.receivedAt,\n method: 'POST',\n path_parameters: pathParameters,\n raw_query_string: input.rawQueryString,\n headers: input.headers,\n body: {encoding: 'base64', data: encodeWebhookBody(input.body)},\n });\n}\n"],"names":["z","WEBHOOK_REQUEST_SCHEMA_VERSION","WEBHOOK_MAX_RAW_BODY_BYTES","WEBHOOK_MAX_SERIALIZED_REQUEST_BYTES","WEBHOOK_MAX_HEADER_BYTES","WEBHOOK_MAX_RAW_QUERY_STRING_LENGTH","standardWebhookRouteIds","webhookRouteIds","webhookRouteIdSchema","enum","base64Pattern","decodedBase64Length","value","paddingLength","endsWith","length","headersByteLength","headers","TextEncoder","encode","JSON","stringify","byteLength","webhookHeadersSchema","record","string","max","refine","Object","keys","every","name","toLowerCase","webhookBodySchema","object","encoding","literal","data","Math","ceil","regex","strict","body","storedWebhookRequestBaseSchema","schema_version","request_id","uuid","received_at","datetime","offset","method","raw_query_string","emptyPathParametersSchema","connectionPathParametersSchema","connection_id","standardWebhookRouteRequestSchema","extend","route_id","path_parameters","genericWebhookRouteRequestSchema","storedWebhookRequestSchema","discriminatedUnion","webhookProcessingResultSchema","outcome","deliveryId","min","optional","challenge","reason","encodeWebhookBody","encoded","index","first","second","third","combined","undefined","decodeWebhookBody","output","Uint8Array","outputIndex","chunk","slice","values","split","map","character","indexOf","fourth","createStoredWebhookRequest","input","pathParameters","routeId","connectionId","parse","requestId","receivedAt","rawQueryString"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/webhooks.ts"],"sourcesContent":["import {z} from 'zod';\n\nexport const WEBHOOK_REQUEST_SCHEMA_VERSION = 1 as const;\nexport const WEBHOOK_MAX_RAW_BODY_BYTES = 512 * 1024;\nexport const WEBHOOK_MAX_SERIALIZED_REQUEST_BYTES = 1024 * 1024;\nexport const WEBHOOK_MAX_HEADER_BYTES = 64 * 1024;\nexport const WEBHOOK_MAX_RAW_QUERY_STRING_LENGTH = 8 * 1024;\n\nconst standardWebhookRouteIds = [\n 'github',\n 'gitea',\n 'linear',\n 'sentry',\n 'slack.event',\n 'slack.command',\n] as const;\n\nexport const webhookRouteIds = [...standardWebhookRouteIds, 'webhook.connection', 'jira'] as const;\n\nexport const webhookRouteIdSchema = z.enum(webhookRouteIds);\nexport type WebhookRouteId = z.infer<typeof webhookRouteIdSchema>;\n\nconst base64Pattern = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;\n\nfunction decodedBase64Length(value: string): number {\n const paddingLength = value.endsWith('==') ? 2 : value.endsWith('=') ? 1 : 0;\n\n return (value.length / 4) * 3 - paddingLength;\n}\n\nfunction headersByteLength(headers: Record<string, string>): number {\n return new TextEncoder().encode(JSON.stringify(headers)).byteLength;\n}\n\nconst webhookHeadersSchema = z\n .record(z.string(), z.string().max(8 * 1024))\n .refine(\n (headers) =>\n Object.keys(headers).every(\n (name) => name === name.toLowerCase() && name.length > 0 && name.length <= 128,\n ),\n 'Webhook header names must be lowercase and at most 128 characters',\n )\n .refine(\n (headers) => headersByteLength(headers) <= WEBHOOK_MAX_HEADER_BYTES,\n `Webhook headers must serialize to at most ${WEBHOOK_MAX_HEADER_BYTES} bytes`,\n );\n\nconst webhookBodySchema = z\n .object({\n encoding: z.literal('base64'),\n data: z\n .string()\n .max(Math.ceil(WEBHOOK_MAX_RAW_BODY_BYTES / 3) * 4)\n .regex(base64Pattern),\n })\n .strict()\n .refine(\n (body) => decodedBase64Length(body.data) <= WEBHOOK_MAX_RAW_BODY_BYTES,\n `Webhook bodies must decode to at most ${WEBHOOK_MAX_RAW_BODY_BYTES} bytes`,\n );\n\nconst storedWebhookRequestBaseSchema = z.object({\n schema_version: z.literal(WEBHOOK_REQUEST_SCHEMA_VERSION),\n request_id: z.string().uuid(),\n received_at: z.string().datetime({offset: true}),\n method: z.literal('POST'),\n raw_query_string: z.string().max(WEBHOOK_MAX_RAW_QUERY_STRING_LENGTH),\n headers: webhookHeadersSchema,\n body: webhookBodySchema,\n});\n\nconst emptyPathParametersSchema = z.object({}).strict();\nconst connectionPathParametersSchema = z.object({connection_id: z.string().uuid()}).strict();\n\nconst standardWebhookRouteRequestSchema = storedWebhookRequestBaseSchema\n .extend({\n route_id: z.enum(standardWebhookRouteIds),\n path_parameters: emptyPathParametersSchema,\n })\n .strict();\n\nconst genericWebhookRouteRequestSchema = storedWebhookRequestBaseSchema\n .extend({\n route_id: z.literal('webhook.connection'),\n path_parameters: connectionPathParametersSchema,\n })\n .strict();\n\nconst jiraWebhookRouteRequestSchema = storedWebhookRequestBaseSchema\n .extend({\n route_id: z.literal('jira'),\n path_parameters: connectionPathParametersSchema,\n })\n .strict();\n\nexport const storedWebhookRequestSchema = z.discriminatedUnion('route_id', [\n standardWebhookRouteRequestSchema,\n genericWebhookRouteRequestSchema,\n jiraWebhookRouteRequestSchema,\n]);\nexport type StoredWebhookRequest = z.infer<typeof storedWebhookRequestSchema>;\n\nexport const webhookProcessingResultSchema = z.discriminatedUnion('outcome', [\n z\n .object({\n outcome: z.literal('processed'),\n deliveryId: z.string().min(1).optional(),\n challenge: z.string().min(1).optional(),\n })\n .strict(),\n z.object({outcome: z.literal('duplicate'), deliveryId: z.string().min(1)}).strict(),\n z\n .object({\n outcome: z.literal('discarded'),\n reason: z.enum([\n 'missing_required_input',\n 'invalid_signature',\n 'stale_at_receipt',\n 'malformed_payload',\n 'unsupported_event',\n 'connection_unavailable',\n ]),\n deliveryId: z.string().min(1).optional(),\n })\n .strict(),\n]);\nexport type WebhookProcessingResult = z.infer<typeof webhookProcessingResultSchema>;\n\ninterface CreateStoredWebhookRequestInputBase {\n requestId: string;\n routeId: WebhookRouteId;\n receivedAt: string;\n rawQueryString: string;\n headers: Record<string, string>;\n body: Uint8Array;\n}\n\ntype ConnectionScopedWebhookRouteId = 'webhook.connection' | 'jira';\n\nexport type CreateStoredWebhookRequestInput =\n | (CreateStoredWebhookRequestInputBase & {\n routeId: ConnectionScopedWebhookRouteId;\n connectionId: string;\n })\n | (CreateStoredWebhookRequestInputBase & {\n routeId: Exclude<WebhookRouteId, ConnectionScopedWebhookRouteId>;\n connectionId?: never;\n });\n\nexport function encodeWebhookBody(body: Uint8Array): string {\n let encoded = '';\n\n for (let index = 0; index < body.length; index += 3) {\n const first = body[index] ?? 0;\n const second = body[index + 1];\n const third = body[index + 2];\n const combined = (first << 16) | ((second ?? 0) << 8) | (third ?? 0);\n\n encoded += 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'[\n (combined >> 18) & 63\n ];\n encoded += 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'[\n (combined >> 12) & 63\n ];\n encoded +=\n second === undefined\n ? '='\n : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'[(combined >> 6) & 63];\n encoded +=\n third === undefined\n ? '='\n : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'[combined & 63];\n }\n\n return encoded;\n}\n\nexport function decodeWebhookBody(body: z.infer<typeof webhookBodySchema>): Uint8Array {\n const output = new Uint8Array(decodedBase64Length(body.data));\n let outputIndex = 0;\n\n for (let index = 0; index < body.data.length; index += 4) {\n const chunk = body.data.slice(index, index + 4);\n const values = chunk\n .split('')\n .map((character) =>\n character === '='\n ? 0\n : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.indexOf(character),\n );\n const first = values[0] ?? 0;\n const second = values[1] ?? 0;\n const third = values[2] ?? 0;\n const fourth = values[3] ?? 0;\n const combined = (first << 18) | (second << 12) | (third << 6) | fourth;\n\n output[outputIndex++] = (combined >> 16) & 255;\n if (chunk[2] !== '=') output[outputIndex++] = (combined >> 8) & 255;\n if (chunk[3] !== '=') output[outputIndex++] = combined & 255;\n }\n\n return output;\n}\n\nexport function createStoredWebhookRequest(\n input: CreateStoredWebhookRequestInput,\n): StoredWebhookRequest {\n const pathParameters =\n input.routeId === 'webhook.connection' || input.routeId === 'jira'\n ? {connection_id: input.connectionId}\n : {};\n\n return storedWebhookRequestSchema.parse({\n schema_version: WEBHOOK_REQUEST_SCHEMA_VERSION,\n request_id: input.requestId,\n route_id: input.routeId,\n received_at: input.receivedAt,\n method: 'POST',\n path_parameters: pathParameters,\n raw_query_string: input.rawQueryString,\n headers: input.headers,\n body: {encoding: 'base64', data: encodeWebhookBody(input.body)},\n });\n}\n"],"names":["z","WEBHOOK_REQUEST_SCHEMA_VERSION","WEBHOOK_MAX_RAW_BODY_BYTES","WEBHOOK_MAX_SERIALIZED_REQUEST_BYTES","WEBHOOK_MAX_HEADER_BYTES","WEBHOOK_MAX_RAW_QUERY_STRING_LENGTH","standardWebhookRouteIds","webhookRouteIds","webhookRouteIdSchema","enum","base64Pattern","decodedBase64Length","value","paddingLength","endsWith","length","headersByteLength","headers","TextEncoder","encode","JSON","stringify","byteLength","webhookHeadersSchema","record","string","max","refine","Object","keys","every","name","toLowerCase","webhookBodySchema","object","encoding","literal","data","Math","ceil","regex","strict","body","storedWebhookRequestBaseSchema","schema_version","request_id","uuid","received_at","datetime","offset","method","raw_query_string","emptyPathParametersSchema","connectionPathParametersSchema","connection_id","standardWebhookRouteRequestSchema","extend","route_id","path_parameters","genericWebhookRouteRequestSchema","jiraWebhookRouteRequestSchema","storedWebhookRequestSchema","discriminatedUnion","webhookProcessingResultSchema","outcome","deliveryId","min","optional","challenge","reason","encodeWebhookBody","encoded","index","first","second","third","combined","undefined","decodeWebhookBody","output","Uint8Array","outputIndex","chunk","slice","values","split","map","character","indexOf","fourth","createStoredWebhookRequest","input","pathParameters","routeId","connectionId","parse","requestId","receivedAt","rawQueryString"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shipfox/api-integration-core-dto",
3
3
  "license": "MIT",
4
- "version": "9.0.2",
4
+ "version": "12.2.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/ShipfoxHQ/shipfox.git",
@@ -23,7 +23,7 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "zod": "^4.4.3",
26
- "@shipfox/inter-module": "0.2.2"
26
+ "@shipfox/inter-module": "0.2.3"
27
27
  },
28
28
  "imports": {
29
29
  "#*": "./dist/*"
@@ -1,11 +1,22 @@
1
1
  import {
2
+ INTEGRATION_CONNECTION_AVAILABLE,
2
3
  INTEGRATION_EVENT_RECEIVED,
3
4
  INTEGRATION_SOURCE_COMMIT_PUSHED,
5
+ INTEGRATION_SOURCE_REPOSITORY_UPDATED,
6
+ integrationConnectionAvailableSchema,
4
7
  integrationEventReceivedSchema,
5
8
  integrationSourceCommitPushedSchema,
9
+ integrationSourceRepositoryUpdatedSchema,
6
10
  integrationsEventSchemas,
7
11
  } from './events.js';
8
12
 
13
+ const validConnectionAvailable = {
14
+ provider: 'linear',
15
+ workspaceId: 'ws-1',
16
+ connectionId: 'conn-1',
17
+ slug: 'linear_shipfox',
18
+ };
19
+
9
20
  const validEventReceived = {
10
21
  provider: 'github',
11
22
  source: 'github',
@@ -33,6 +44,34 @@ const validCommitPushed = {
33
44
  },
34
45
  };
35
46
 
47
+ const validRepositoryUpdated = {
48
+ provider: 'github',
49
+ workspaceId: 'ws-1',
50
+ connectionId: 'conn-1',
51
+ deliveryId: 'delivery-1',
52
+ receivedAt: '2026-06-21T00:00:00.000Z',
53
+ repository: {
54
+ externalRepositoryId: 'github:42',
55
+ owner: 'acme',
56
+ name: 'platform-renamed',
57
+ defaultBranch: 'main',
58
+ },
59
+ };
60
+
61
+ describe('integrationConnectionAvailableSchema', () => {
62
+ it('parses a valid connection-available payload unchanged', () => {
63
+ expect(integrationConnectionAvailableSchema.parse(validConnectionAvailable)).toEqual(
64
+ validConnectionAvailable,
65
+ );
66
+ });
67
+
68
+ it('rejects a payload without a connection slug', () => {
69
+ const {slug: _slug, ...withoutSlug} = validConnectionAvailable;
70
+
71
+ expect(() => integrationConnectionAvailableSchema.parse(withoutSlug)).toThrow();
72
+ });
73
+ });
74
+
36
75
  describe('integrationSourceCommitPushedSchema', () => {
37
76
  it('parses a valid commit-pushed payload unchanged', () => {
38
77
  const result = integrationSourceCommitPushedSchema.parse(validCommitPushed);
@@ -98,12 +137,33 @@ describe('integrationEventReceivedSchema', () => {
98
137
  });
99
138
  });
100
139
 
140
+ describe('integrationSourceRepositoryUpdatedSchema', () => {
141
+ it('parses a valid repository identity update unchanged', () => {
142
+ const result = integrationSourceRepositoryUpdatedSchema.parse(validRepositoryUpdated);
143
+
144
+ expect(result).toEqual(validRepositoryUpdated);
145
+ });
146
+
147
+ it('rejects a payload missing repository identity', () => {
148
+ const {repository: _repository, ...withoutRepository} = validRepositoryUpdated;
149
+
150
+ const parse = () => integrationSourceRepositoryUpdatedSchema.parse(withoutRepository);
151
+
152
+ expect(parse).toThrow();
153
+ });
154
+ });
155
+
101
156
  describe('integrationsEventSchemas', () => {
102
157
  it('registers every integration publisher event type', () => {
103
158
  const registeredTypes = Object.keys(integrationsEventSchemas).sort();
104
159
 
105
160
  expect(registeredTypes).toEqual(
106
- [INTEGRATION_EVENT_RECEIVED, INTEGRATION_SOURCE_COMMIT_PUSHED].sort(),
161
+ [
162
+ INTEGRATION_CONNECTION_AVAILABLE,
163
+ INTEGRATION_EVENT_RECEIVED,
164
+ INTEGRATION_SOURCE_COMMIT_PUSHED,
165
+ INTEGRATION_SOURCE_REPOSITORY_UPDATED,
166
+ ].sort(),
107
167
  );
108
168
  });
109
169
  });
package/src/events.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import {z} from 'zod';
2
2
 
3
3
  export const INTEGRATION_EVENT_RECEIVED = 'integrations.event.received' as const;
4
+ export const INTEGRATION_CONNECTION_AVAILABLE = 'integrations.connection.available' as const;
4
5
 
5
6
  const nonEmptyStringSchema = z.string().nonempty();
6
7
  const isoDateTimeSchema = z.string().datetime();
@@ -22,6 +23,16 @@ export const integrationEventReceivedSchema = z.object({
22
23
  });
23
24
  export type IntegrationEventReceivedEvent = z.infer<typeof integrationEventReceivedSchema>;
24
25
 
26
+ export const integrationConnectionAvailableSchema = z.object({
27
+ provider: nonEmptyStringSchema,
28
+ workspaceId: nonEmptyStringSchema,
29
+ connectionId: nonEmptyStringSchema,
30
+ slug: nonEmptyStringSchema,
31
+ });
32
+ export type IntegrationConnectionAvailableEvent = z.infer<
33
+ typeof integrationConnectionAvailableSchema
34
+ >;
35
+
25
36
  // A source-control push, normalized by the producing provider and carried by
26
37
  // `INTEGRATION_SOURCE_COMMIT_PUSHED` for domain consumers.
27
38
  export const sourcePushSchema = z.object({
@@ -51,6 +62,32 @@ export type IntegrationSourceCommitPushedEvent = z.infer<
51
62
  typeof integrationSourceCommitPushedSchema
52
63
  >;
53
64
 
65
+ export const sourceRepositoryIdentitySchema = z.object({
66
+ externalRepositoryId: nonEmptyStringSchema,
67
+ owner: nonEmptyStringSchema,
68
+ name: nonEmptyStringSchema,
69
+ defaultBranch: nonEmptyStringSchema,
70
+ });
71
+ export type SourceRepositoryIdentity = z.infer<typeof sourceRepositoryIdentitySchema>;
72
+
73
+ export const INTEGRATION_SOURCE_REPOSITORY_UPDATED =
74
+ 'integrations.source_control.repository_updated' as const;
75
+
76
+ // Typed, provider-agnostic source-control event. The producing provider owns the
77
+ // translation from its raw webhook into this shape, so domain consumers never decode
78
+ // provider payloads.
79
+ export const integrationSourceRepositoryUpdatedSchema = z.object({
80
+ provider: nonEmptyStringSchema,
81
+ workspaceId: nonEmptyStringSchema,
82
+ connectionId: nonEmptyStringSchema,
83
+ deliveryId: nonEmptyStringSchema,
84
+ receivedAt: isoDateTimeSchema,
85
+ repository: sourceRepositoryIdentitySchema,
86
+ });
87
+ export type IntegrationSourceRepositoryUpdatedEvent = z.infer<
88
+ typeof integrationSourceRepositoryUpdatedSchema
89
+ >;
90
+
54
91
  export interface SentryIssuePayload {
55
92
  action: SentryIssueAction;
56
93
  issueId: string;
@@ -81,11 +118,15 @@ export const SENTRY_ISSUE_ACTIONS = [
81
118
  export type SentryIssueAction = (typeof SENTRY_ISSUE_ACTIONS)[number];
82
119
 
83
120
  export interface IntegrationsEventMap {
121
+ [INTEGRATION_CONNECTION_AVAILABLE]: IntegrationConnectionAvailableEvent;
84
122
  [INTEGRATION_EVENT_RECEIVED]: IntegrationEventReceivedEvent;
85
123
  [INTEGRATION_SOURCE_COMMIT_PUSHED]: IntegrationSourceCommitPushedEvent;
124
+ [INTEGRATION_SOURCE_REPOSITORY_UPDATED]: IntegrationSourceRepositoryUpdatedEvent;
86
125
  }
87
126
 
88
127
  export const integrationsEventSchemas = {
128
+ [INTEGRATION_CONNECTION_AVAILABLE]: integrationConnectionAvailableSchema,
89
129
  [INTEGRATION_EVENT_RECEIVED]: integrationEventReceivedSchema,
90
130
  [INTEGRATION_SOURCE_COMMIT_PUSHED]: integrationSourceCommitPushedSchema,
131
+ [INTEGRATION_SOURCE_REPOSITORY_UPDATED]: integrationSourceRepositoryUpdatedSchema,
91
132
  } satisfies Record<keyof IntegrationsEventMap, z.ZodType>;
@@ -1,6 +1,32 @@
1
1
  import {integrationsInterModuleContract} from './inter-module.js';
2
2
 
3
3
  describe('integrationsInterModuleContract', () => {
4
+ test('accepts a nullable normalized trigger reference', () => {
5
+ const result = integrationsInterModuleContract.methods.resolveTriggerReference.output.parse({
6
+ externalRepositoryId: 'github:42',
7
+ ref: 'refs/heads/main',
8
+ commit: 'a'.repeat(40),
9
+ actor: 'octocat',
10
+ });
11
+
12
+ expect(result?.externalRepositoryId).toBe('github:42');
13
+ expect(result?.actor).toBe('octocat');
14
+ expect(
15
+ integrationsInterModuleContract.methods.resolveTriggerReference.output.parse(null),
16
+ ).toBeNull();
17
+ });
18
+
19
+ test('accepts a reference from a payload that named no actor', () => {
20
+ const result = integrationsInterModuleContract.methods.resolveTriggerReference.output.parse({
21
+ externalRepositoryId: 'github:42',
22
+ ref: 'refs/heads/main',
23
+ commit: 'a'.repeat(40),
24
+ actor: null,
25
+ });
26
+
27
+ expect(result?.actor).toBeNull();
28
+ });
29
+
4
30
  test('accepts a source repository lookup through the producer contract', () => {
5
31
  const result = integrationsInterModuleContract.methods.resolveSourceRepository.output.parse({
6
32
  connection: {
@@ -23,6 +49,22 @@ describe('integrationsInterModuleContract', () => {
23
49
  expect(result.repository.fullName).toBe('shipfox/project');
24
50
  });
25
51
 
52
+ test('accepts a workspace-scoped connection lookup through the producer contract', () => {
53
+ const input = integrationsInterModuleContract.methods.resolveConnection.input.parse({
54
+ workspaceId: '00000000-0000-4000-8000-000000000001',
55
+ slug: 'github-main',
56
+ });
57
+ const output = integrationsInterModuleContract.methods.resolveConnection.output.parse({
58
+ id: '00000000-0000-4000-8000-000000000002',
59
+ provider: 'github',
60
+ slug: 'github-main',
61
+ });
62
+
63
+ expect(input.slug).toBe('github-main');
64
+ expect(output?.id).toBe('00000000-0000-4000-8000-000000000002');
65
+ expect(integrationsInterModuleContract.methods.resolveConnection.output.parse(null)).toBeNull();
66
+ });
67
+
26
68
  test.each([
27
69
  ['connection-not-found', {connectionId: '00000000-0000-4000-8000-000000000001'}],
28
70
  ['provider-unavailable', {provider: 'github'}],
@@ -4,6 +4,7 @@ import {z} from 'zod';
4
4
  const id = z.string().uuid();
5
5
  const provider = z.string().min(1);
6
6
  const capability = z.enum(['source_control', 'agent_tools']);
7
+ const connection = z.object({id, provider, slug: z.string().min(1)});
7
8
  const repository = z.object({
8
9
  externalRepositoryId: z.string(),
9
10
  owner: z.string(),
@@ -14,6 +15,12 @@ const repository = z.object({
14
15
  cloneUrl: z.string(),
15
16
  htmlUrl: z.string(),
16
17
  });
18
+ const triggerReference = z.object({
19
+ externalRepositoryId: z.string(),
20
+ ref: z.string(),
21
+ commit: z.string(),
22
+ actor: z.string().nullable(),
23
+ });
17
24
  const sourceInput = z.object({workspaceId: id, connectionId: id, externalRepositoryId: z.string()});
18
25
  const providerError = z.object({
19
26
  reason: z.string(),
@@ -35,7 +42,16 @@ export const integrationsInterModuleContract = defineInterModuleContract({
35
42
  methods: {
36
43
  resolveSourceRepository: {
37
44
  input: sourceInput,
38
- output: z.object({connection: z.object({id, provider, slug: z.string()}), repository}),
45
+ output: z.object({connection, repository}),
46
+ errors: sourceErrors,
47
+ },
48
+ resolveConnection: {
49
+ input: z.object({workspaceId: id, slug: z.string().min(1)}),
50
+ output: connection.nullable(),
51
+ },
52
+ resolveTriggerReference: {
53
+ input: z.object({workspaceId: id, connectionId: id, payload: z.unknown()}),
54
+ output: triggerReference.nullable(),
39
55
  errors: sourceErrors,
40
56
  },
41
57
  listSourceFiles: {
@@ -90,6 +90,24 @@ describe('storedWebhookRequestSchema', () => {
90
90
  expect(storedWebhookRequestSchema.safeParse(withoutConnectionId).success).toBe(false);
91
91
  });
92
92
 
93
+ it('requires and round-trips a connection identifier for Jira requests', () => {
94
+ const request = createStoredWebhookRequest({
95
+ requestId,
96
+ routeId: 'jira',
97
+ receivedAt,
98
+ rawQueryString: '',
99
+ headers: {authorization: 'Bearer token'},
100
+ body: new Uint8Array([1, 2, 3]),
101
+ connectionId: 'c0a8012e-0b6d-4d8f-8d5c-6d74102602b0',
102
+ });
103
+
104
+ expect(request.path_parameters.connection_id).toBe('c0a8012e-0b6d-4d8f-8d5c-6d74102602b0');
105
+ expect(storedWebhookRequestSchema.parse(JSON.parse(JSON.stringify(request)))).toEqual(request);
106
+ expect(storedWebhookRequestSchema.safeParse({...request, path_parameters: {}}).success).toBe(
107
+ false,
108
+ );
109
+ });
110
+
93
111
  it('keeps the maximum-size fixture below the SQS message limit', () => {
94
112
  const fixture = createMaximumSizeStoredWebhookRequestFixture();
95
113
  const serializedSize = new TextEncoder().encode(JSON.stringify(fixture)).byteLength;
package/src/webhooks.ts CHANGED
@@ -15,7 +15,7 @@ const standardWebhookRouteIds = [
15
15
  'slack.command',
16
16
  ] as const;
17
17
 
18
- export const webhookRouteIds = [...standardWebhookRouteIds, 'webhook.connection'] as const;
18
+ export const webhookRouteIds = [...standardWebhookRouteIds, 'webhook.connection', 'jira'] as const;
19
19
 
20
20
  export const webhookRouteIdSchema = z.enum(webhookRouteIds);
21
21
  export type WebhookRouteId = z.infer<typeof webhookRouteIdSchema>;
@@ -87,9 +87,17 @@ const genericWebhookRouteRequestSchema = storedWebhookRequestBaseSchema
87
87
  })
88
88
  .strict();
89
89
 
90
+ const jiraWebhookRouteRequestSchema = storedWebhookRequestBaseSchema
91
+ .extend({
92
+ route_id: z.literal('jira'),
93
+ path_parameters: connectionPathParametersSchema,
94
+ })
95
+ .strict();
96
+
90
97
  export const storedWebhookRequestSchema = z.discriminatedUnion('route_id', [
91
98
  standardWebhookRouteRequestSchema,
92
99
  genericWebhookRouteRequestSchema,
100
+ jiraWebhookRouteRequestSchema,
93
101
  ]);
94
102
  export type StoredWebhookRequest = z.infer<typeof storedWebhookRequestSchema>;
95
103
 
@@ -119,16 +127,27 @@ export const webhookProcessingResultSchema = z.discriminatedUnion('outcome', [
119
127
  ]);
120
128
  export type WebhookProcessingResult = z.infer<typeof webhookProcessingResultSchema>;
121
129
 
122
- export interface CreateStoredWebhookRequestInput {
130
+ interface CreateStoredWebhookRequestInputBase {
123
131
  requestId: string;
124
132
  routeId: WebhookRouteId;
125
133
  receivedAt: string;
126
134
  rawQueryString: string;
127
135
  headers: Record<string, string>;
128
136
  body: Uint8Array;
129
- connectionId?: string | undefined;
130
137
  }
131
138
 
139
+ type ConnectionScopedWebhookRouteId = 'webhook.connection' | 'jira';
140
+
141
+ export type CreateStoredWebhookRequestInput =
142
+ | (CreateStoredWebhookRequestInputBase & {
143
+ routeId: ConnectionScopedWebhookRouteId;
144
+ connectionId: string;
145
+ })
146
+ | (CreateStoredWebhookRequestInputBase & {
147
+ routeId: Exclude<WebhookRouteId, ConnectionScopedWebhookRouteId>;
148
+ connectionId?: never;
149
+ });
150
+
132
151
  export function encodeWebhookBody(body: Uint8Array): string {
133
152
  let encoded = '';
134
153
 
@@ -188,7 +207,9 @@ export function createStoredWebhookRequest(
188
207
  input: CreateStoredWebhookRequestInput,
189
208
  ): StoredWebhookRequest {
190
209
  const pathParameters =
191
- input.routeId === 'webhook.connection' ? {connection_id: input.connectionId} : {};
210
+ input.routeId === 'webhook.connection' || input.routeId === 'jira'
211
+ ? {connection_id: input.connectionId}
212
+ : {};
192
213
 
193
214
  return storedWebhookRequestSchema.parse({
194
215
  schema_version: WEBHOOK_REQUEST_SCHEMA_VERSION,