@rawdash/connector-sentry 0.21.1 → 0.22.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.
package/dist/index.d.ts CHANGED
@@ -60,7 +60,38 @@ declare const sentryResources: {
60
60
  userCount: z.ZodNumber;
61
61
  project: z.ZodObject<{
62
62
  slug: z.ZodString;
63
+ id: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
64
+ name: z.ZodOptional<z.ZodString>;
65
+ platform: z.ZodOptional<z.ZodNullable<z.ZodString>>;
63
66
  }, z.core.$strip>;
67
+ annotations: z.ZodOptional<z.ZodUnknown>;
68
+ assignedTo: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
69
+ culprit: z.ZodOptional<z.ZodNullable<z.ZodString>>;
70
+ filtered: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
71
+ hasSeen: z.ZodOptional<z.ZodBoolean>;
72
+ isBookmarked: z.ZodOptional<z.ZodBoolean>;
73
+ isPublic: z.ZodOptional<z.ZodBoolean>;
74
+ isSubscribed: z.ZodOptional<z.ZodBoolean>;
75
+ isUnhandled: z.ZodOptional<z.ZodBoolean>;
76
+ issueCategory: z.ZodOptional<z.ZodString>;
77
+ issueType: z.ZodOptional<z.ZodString>;
78
+ lifetime: z.ZodOptional<z.ZodUnknown>;
79
+ logger: z.ZodOptional<z.ZodNullable<z.ZodString>>;
80
+ metadata: z.ZodOptional<z.ZodUnknown>;
81
+ numComments: z.ZodOptional<z.ZodNumber>;
82
+ permalink: z.ZodOptional<z.ZodString>;
83
+ platform: z.ZodOptional<z.ZodNullable<z.ZodString>>;
84
+ priority: z.ZodOptional<z.ZodString>;
85
+ priorityLockedAt: z.ZodOptional<z.ZodNullable<z.ZodISODateTime>>;
86
+ seerAutofixLastTriggered: z.ZodOptional<z.ZodNullable<z.ZodISODateTime>>;
87
+ seerExplorerAutofixLastTriggered: z.ZodOptional<z.ZodNullable<z.ZodISODateTime>>;
88
+ seerFixabilityScore: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
89
+ shareId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
90
+ stats: z.ZodOptional<z.ZodUnknown>;
91
+ statusDetails: z.ZodOptional<z.ZodUnknown>;
92
+ subscriptionDetails: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
93
+ substatus: z.ZodOptional<z.ZodNullable<z.ZodString>>;
94
+ type: z.ZodOptional<z.ZodString>;
64
95
  }, z.core.$strip>>;
65
96
  };
66
97
  };
@@ -78,6 +109,15 @@ declare const sentryResources: {
78
109
  platform: z.ZodOptional<z.ZodNullable<z.ZodString>>;
79
110
  groupID: z.ZodOptional<z.ZodString>;
80
111
  environment: z.ZodOptional<z.ZodNullable<z.ZodString>>;
112
+ crashFile: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
113
+ culprit: z.ZodOptional<z.ZodString>;
114
+ 'event.type': z.ZodOptional<z.ZodString>;
115
+ location: z.ZodOptional<z.ZodString>;
116
+ metadata: z.ZodOptional<z.ZodUnknown>;
117
+ projectID: z.ZodOptional<z.ZodString>;
118
+ tags: z.ZodOptional<z.ZodUnknown>;
119
+ title: z.ZodOptional<z.ZodString>;
120
+ user: z.ZodOptional<z.ZodUnknown>;
81
121
  }, z.core.$strip>>;
82
122
  };
83
123
  };
@@ -89,11 +129,34 @@ declare const sentryResources: {
89
129
  readonly releases: z.ZodArray<z.ZodObject<{
90
130
  version: z.ZodString;
91
131
  dateCreated: z.ZodISODateTime;
92
- dateReleased: z.ZodNullable<z.ZodISODateTime>;
93
- lastEvent: z.ZodNullable<z.ZodISODateTime>;
132
+ dateReleased: z.ZodOptional<z.ZodNullable<z.ZodISODateTime>>;
133
+ lastEvent: z.ZodOptional<z.ZodNullable<z.ZodISODateTime>>;
94
134
  projects: z.ZodArray<z.ZodObject<{
95
135
  slug: z.ZodString;
136
+ hasHealthData: z.ZodOptional<z.ZodUnknown>;
137
+ id: z.ZodOptional<z.ZodUnknown>;
138
+ name: z.ZodOptional<z.ZodUnknown>;
139
+ newGroups: z.ZodOptional<z.ZodUnknown>;
140
+ platform: z.ZodOptional<z.ZodUnknown>;
141
+ platforms: z.ZodOptional<z.ZodUnknown>;
96
142
  }, z.core.$strip>>;
143
+ authors: z.ZodOptional<z.ZodUnknown>;
144
+ commitCount: z.ZodOptional<z.ZodUnknown>;
145
+ currentProjectMeta: z.ZodOptional<z.ZodUnknown>;
146
+ data: z.ZodOptional<z.ZodUnknown>;
147
+ deployCount: z.ZodOptional<z.ZodUnknown>;
148
+ firstEvent: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
149
+ id: z.ZodOptional<z.ZodUnknown>;
150
+ lastCommit: z.ZodOptional<z.ZodUnknown>;
151
+ lastDeploy: z.ZodOptional<z.ZodUnknown>;
152
+ newGroups: z.ZodOptional<z.ZodUnknown>;
153
+ owner: z.ZodOptional<z.ZodUnknown>;
154
+ ref: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
155
+ shortVersion: z.ZodOptional<z.ZodUnknown>;
156
+ status: z.ZodOptional<z.ZodUnknown>;
157
+ url: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
158
+ userAgent: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
159
+ versionInfo: z.ZodOptional<z.ZodUnknown>;
97
160
  }, z.core.$strip>>;
98
161
  };
99
162
  };
@@ -109,7 +172,7 @@ declare const sentryResources: {
109
172
  }];
110
173
  readonly responses: {
111
174
  readonly error_stats: z.ZodObject<{
112
- intervals: z.ZodArray<z.ZodISODateTime>;
175
+ intervals: z.ZodOptional<z.ZodArray<z.ZodISODateTime>>;
113
176
  groups: z.ZodArray<z.ZodObject<{
114
177
  by: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
115
178
  totals: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
@@ -152,7 +215,38 @@ declare class SentryConnector extends BaseConnector<SentrySettings, SentryCreden
152
215
  userCount: z.ZodNumber;
153
216
  project: z.ZodObject<{
154
217
  slug: z.ZodString;
218
+ id: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
219
+ name: z.ZodOptional<z.ZodString>;
220
+ platform: z.ZodOptional<z.ZodNullable<z.ZodString>>;
155
221
  }, z.core.$strip>;
222
+ annotations: z.ZodOptional<z.ZodUnknown>;
223
+ assignedTo: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
224
+ culprit: z.ZodOptional<z.ZodNullable<z.ZodString>>;
225
+ filtered: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
226
+ hasSeen: z.ZodOptional<z.ZodBoolean>;
227
+ isBookmarked: z.ZodOptional<z.ZodBoolean>;
228
+ isPublic: z.ZodOptional<z.ZodBoolean>;
229
+ isSubscribed: z.ZodOptional<z.ZodBoolean>;
230
+ isUnhandled: z.ZodOptional<z.ZodBoolean>;
231
+ issueCategory: z.ZodOptional<z.ZodString>;
232
+ issueType: z.ZodOptional<z.ZodString>;
233
+ lifetime: z.ZodOptional<z.ZodUnknown>;
234
+ logger: z.ZodOptional<z.ZodNullable<z.ZodString>>;
235
+ metadata: z.ZodOptional<z.ZodUnknown>;
236
+ numComments: z.ZodOptional<z.ZodNumber>;
237
+ permalink: z.ZodOptional<z.ZodString>;
238
+ platform: z.ZodOptional<z.ZodNullable<z.ZodString>>;
239
+ priority: z.ZodOptional<z.ZodString>;
240
+ priorityLockedAt: z.ZodOptional<z.ZodNullable<z.ZodISODateTime>>;
241
+ seerAutofixLastTriggered: z.ZodOptional<z.ZodNullable<z.ZodISODateTime>>;
242
+ seerExplorerAutofixLastTriggered: z.ZodOptional<z.ZodNullable<z.ZodISODateTime>>;
243
+ seerFixabilityScore: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
244
+ shareId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
245
+ stats: z.ZodOptional<z.ZodUnknown>;
246
+ statusDetails: z.ZodOptional<z.ZodUnknown>;
247
+ subscriptionDetails: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
248
+ substatus: z.ZodOptional<z.ZodNullable<z.ZodString>>;
249
+ type: z.ZodOptional<z.ZodString>;
156
250
  }, z.core.$strip>>;
157
251
  };
158
252
  };
@@ -170,6 +264,15 @@ declare class SentryConnector extends BaseConnector<SentrySettings, SentryCreden
170
264
  platform: z.ZodOptional<z.ZodNullable<z.ZodString>>;
171
265
  groupID: z.ZodOptional<z.ZodString>;
172
266
  environment: z.ZodOptional<z.ZodNullable<z.ZodString>>;
267
+ crashFile: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
268
+ culprit: z.ZodOptional<z.ZodString>;
269
+ 'event.type': z.ZodOptional<z.ZodString>;
270
+ location: z.ZodOptional<z.ZodString>;
271
+ metadata: z.ZodOptional<z.ZodUnknown>;
272
+ projectID: z.ZodOptional<z.ZodString>;
273
+ tags: z.ZodOptional<z.ZodUnknown>;
274
+ title: z.ZodOptional<z.ZodString>;
275
+ user: z.ZodOptional<z.ZodUnknown>;
173
276
  }, z.core.$strip>>;
174
277
  };
175
278
  };
@@ -181,11 +284,34 @@ declare class SentryConnector extends BaseConnector<SentrySettings, SentryCreden
181
284
  readonly releases: z.ZodArray<z.ZodObject<{
182
285
  version: z.ZodString;
183
286
  dateCreated: z.ZodISODateTime;
184
- dateReleased: z.ZodNullable<z.ZodISODateTime>;
185
- lastEvent: z.ZodNullable<z.ZodISODateTime>;
287
+ dateReleased: z.ZodOptional<z.ZodNullable<z.ZodISODateTime>>;
288
+ lastEvent: z.ZodOptional<z.ZodNullable<z.ZodISODateTime>>;
186
289
  projects: z.ZodArray<z.ZodObject<{
187
290
  slug: z.ZodString;
291
+ hasHealthData: z.ZodOptional<z.ZodUnknown>;
292
+ id: z.ZodOptional<z.ZodUnknown>;
293
+ name: z.ZodOptional<z.ZodUnknown>;
294
+ newGroups: z.ZodOptional<z.ZodUnknown>;
295
+ platform: z.ZodOptional<z.ZodUnknown>;
296
+ platforms: z.ZodOptional<z.ZodUnknown>;
188
297
  }, z.core.$strip>>;
298
+ authors: z.ZodOptional<z.ZodUnknown>;
299
+ commitCount: z.ZodOptional<z.ZodUnknown>;
300
+ currentProjectMeta: z.ZodOptional<z.ZodUnknown>;
301
+ data: z.ZodOptional<z.ZodUnknown>;
302
+ deployCount: z.ZodOptional<z.ZodUnknown>;
303
+ firstEvent: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
304
+ id: z.ZodOptional<z.ZodUnknown>;
305
+ lastCommit: z.ZodOptional<z.ZodUnknown>;
306
+ lastDeploy: z.ZodOptional<z.ZodUnknown>;
307
+ newGroups: z.ZodOptional<z.ZodUnknown>;
308
+ owner: z.ZodOptional<z.ZodUnknown>;
309
+ ref: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
310
+ shortVersion: z.ZodOptional<z.ZodUnknown>;
311
+ status: z.ZodOptional<z.ZodUnknown>;
312
+ url: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
313
+ userAgent: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
314
+ versionInfo: z.ZodOptional<z.ZodUnknown>;
189
315
  }, z.core.$strip>>;
190
316
  };
191
317
  };
@@ -201,7 +327,7 @@ declare class SentryConnector extends BaseConnector<SentrySettings, SentryCreden
201
327
  }];
202
328
  readonly responses: {
203
329
  readonly error_stats: z.ZodObject<{
204
- intervals: z.ZodArray<z.ZodISODateTime>;
330
+ intervals: z.ZodOptional<z.ZodArray<z.ZodISODateTime>>;
205
331
  groups: z.ZodArray<z.ZodObject<{
206
332
  by: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
207
333
  totals: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
@@ -236,7 +362,38 @@ declare class SentryConnector extends BaseConnector<SentrySettings, SentryCreden
236
362
  userCount: z.ZodNumber;
237
363
  project: z.ZodObject<{
238
364
  slug: z.ZodString;
365
+ id: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
366
+ name: z.ZodOptional<z.ZodString>;
367
+ platform: z.ZodOptional<z.ZodNullable<z.ZodString>>;
239
368
  }, z.core.$strip>;
369
+ annotations: z.ZodOptional<z.ZodUnknown>;
370
+ assignedTo: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
371
+ culprit: z.ZodOptional<z.ZodNullable<z.ZodString>>;
372
+ filtered: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
373
+ hasSeen: z.ZodOptional<z.ZodBoolean>;
374
+ isBookmarked: z.ZodOptional<z.ZodBoolean>;
375
+ isPublic: z.ZodOptional<z.ZodBoolean>;
376
+ isSubscribed: z.ZodOptional<z.ZodBoolean>;
377
+ isUnhandled: z.ZodOptional<z.ZodBoolean>;
378
+ issueCategory: z.ZodOptional<z.ZodString>;
379
+ issueType: z.ZodOptional<z.ZodString>;
380
+ lifetime: z.ZodOptional<z.ZodUnknown>;
381
+ logger: z.ZodOptional<z.ZodNullable<z.ZodString>>;
382
+ metadata: z.ZodOptional<z.ZodUnknown>;
383
+ numComments: z.ZodOptional<z.ZodNumber>;
384
+ permalink: z.ZodOptional<z.ZodString>;
385
+ platform: z.ZodOptional<z.ZodNullable<z.ZodString>>;
386
+ priority: z.ZodOptional<z.ZodString>;
387
+ priorityLockedAt: z.ZodOptional<z.ZodNullable<z.ZodISODateTime>>;
388
+ seerAutofixLastTriggered: z.ZodOptional<z.ZodNullable<z.ZodISODateTime>>;
389
+ seerExplorerAutofixLastTriggered: z.ZodOptional<z.ZodNullable<z.ZodISODateTime>>;
390
+ seerFixabilityScore: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
391
+ shareId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
392
+ stats: z.ZodOptional<z.ZodUnknown>;
393
+ statusDetails: z.ZodOptional<z.ZodUnknown>;
394
+ subscriptionDetails: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
395
+ substatus: z.ZodOptional<z.ZodNullable<z.ZodString>>;
396
+ type: z.ZodOptional<z.ZodString>;
240
397
  }, z.core.$strip>>;
241
398
  } & {
242
399
  readonly issue_events: z.ZodArray<z.ZodObject<{
@@ -247,20 +404,52 @@ declare class SentryConnector extends BaseConnector<SentrySettings, SentryCreden
247
404
  platform: z.ZodOptional<z.ZodNullable<z.ZodString>>;
248
405
  groupID: z.ZodOptional<z.ZodString>;
249
406
  environment: z.ZodOptional<z.ZodNullable<z.ZodString>>;
407
+ crashFile: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
408
+ culprit: z.ZodOptional<z.ZodString>;
409
+ 'event.type': z.ZodOptional<z.ZodString>;
410
+ location: z.ZodOptional<z.ZodString>;
411
+ metadata: z.ZodOptional<z.ZodUnknown>;
412
+ projectID: z.ZodOptional<z.ZodString>;
413
+ tags: z.ZodOptional<z.ZodUnknown>;
414
+ title: z.ZodOptional<z.ZodString>;
415
+ user: z.ZodOptional<z.ZodUnknown>;
250
416
  }, z.core.$strip>>;
251
417
  } & {
252
418
  readonly releases: z.ZodArray<z.ZodObject<{
253
419
  version: z.ZodString;
254
420
  dateCreated: z.ZodISODateTime;
255
- dateReleased: z.ZodNullable<z.ZodISODateTime>;
256
- lastEvent: z.ZodNullable<z.ZodISODateTime>;
421
+ dateReleased: z.ZodOptional<z.ZodNullable<z.ZodISODateTime>>;
422
+ lastEvent: z.ZodOptional<z.ZodNullable<z.ZodISODateTime>>;
257
423
  projects: z.ZodArray<z.ZodObject<{
258
424
  slug: z.ZodString;
425
+ hasHealthData: z.ZodOptional<z.ZodUnknown>;
426
+ id: z.ZodOptional<z.ZodUnknown>;
427
+ name: z.ZodOptional<z.ZodUnknown>;
428
+ newGroups: z.ZodOptional<z.ZodUnknown>;
429
+ platform: z.ZodOptional<z.ZodUnknown>;
430
+ platforms: z.ZodOptional<z.ZodUnknown>;
259
431
  }, z.core.$strip>>;
432
+ authors: z.ZodOptional<z.ZodUnknown>;
433
+ commitCount: z.ZodOptional<z.ZodUnknown>;
434
+ currentProjectMeta: z.ZodOptional<z.ZodUnknown>;
435
+ data: z.ZodOptional<z.ZodUnknown>;
436
+ deployCount: z.ZodOptional<z.ZodUnknown>;
437
+ firstEvent: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
438
+ id: z.ZodOptional<z.ZodUnknown>;
439
+ lastCommit: z.ZodOptional<z.ZodUnknown>;
440
+ lastDeploy: z.ZodOptional<z.ZodUnknown>;
441
+ newGroups: z.ZodOptional<z.ZodUnknown>;
442
+ owner: z.ZodOptional<z.ZodUnknown>;
443
+ ref: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
444
+ shortVersion: z.ZodOptional<z.ZodUnknown>;
445
+ status: z.ZodOptional<z.ZodUnknown>;
446
+ url: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
447
+ userAgent: z.ZodOptional<z.ZodNullable<z.ZodUnknown>>;
448
+ versionInfo: z.ZodOptional<z.ZodUnknown>;
260
449
  }, z.core.$strip>>;
261
450
  } & {
262
451
  readonly error_stats: z.ZodObject<{
263
- intervals: z.ZodArray<z.ZodISODateTime>;
452
+ intervals: z.ZodOptional<z.ZodArray<z.ZodISODateTime>>;
264
453
  groups: z.ZodArray<z.ZodObject<{
265
454
  by: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
266
455
  totals: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
package/dist/index.js CHANGED
@@ -161,6 +161,7 @@ var doc = defineConnectorDoc({
161
161
  tagline: "Sync issues, issue events, releases, and hourly error rates from a Sentry organization.",
162
162
  vendor: {
163
163
  name: "Sentry",
164
+ domain: "sentry.io",
164
165
  apiDocs: "https://docs.sentry.io/api/",
165
166
  website: "https://sentry.io"
166
167
  },
@@ -241,7 +242,40 @@ var issueResponseSchema = z.array(
241
242
  lastSeen: z.iso.datetime(),
242
243
  count: z.union([z.string().regex(/^\d+$/), z.number().int().nonnegative()]),
243
244
  userCount: z.number().int().nonnegative(),
244
- project: z.object({ slug: z.string().min(1) })
245
+ project: z.object({
246
+ slug: z.string().min(1),
247
+ id: z.union([idString, z.number()]).optional(),
248
+ name: z.string().optional(),
249
+ platform: z.string().nullable().optional()
250
+ }),
251
+ annotations: z.unknown().optional(),
252
+ assignedTo: z.unknown().nullable().optional(),
253
+ culprit: z.string().nullable().optional(),
254
+ filtered: z.unknown().nullable().optional(),
255
+ hasSeen: z.boolean().optional(),
256
+ isBookmarked: z.boolean().optional(),
257
+ isPublic: z.boolean().optional(),
258
+ isSubscribed: z.boolean().optional(),
259
+ isUnhandled: z.boolean().optional(),
260
+ issueCategory: z.string().optional(),
261
+ issueType: z.string().optional(),
262
+ lifetime: z.unknown().optional(),
263
+ logger: z.string().nullable().optional(),
264
+ metadata: z.unknown().optional(),
265
+ numComments: z.number().int().nonnegative().optional(),
266
+ permalink: z.string().optional(),
267
+ platform: z.string().nullable().optional(),
268
+ priority: z.string().optional(),
269
+ priorityLockedAt: z.iso.datetime().nullable().optional(),
270
+ seerAutofixLastTriggered: z.iso.datetime().nullable().optional(),
271
+ seerExplorerAutofixLastTriggered: z.iso.datetime().nullable().optional(),
272
+ seerFixabilityScore: z.number().nullable().optional(),
273
+ shareId: z.string().nullable().optional(),
274
+ stats: z.unknown().optional(),
275
+ statusDetails: z.unknown().optional(),
276
+ subscriptionDetails: z.unknown().nullable().optional(),
277
+ substatus: z.string().nullable().optional(),
278
+ type: z.string().optional()
245
279
  })
246
280
  );
247
281
  var issueEventResponseSchema = z.array(
@@ -252,20 +286,56 @@ var issueEventResponseSchema = z.array(
252
286
  message: z.string().nullable().optional(),
253
287
  platform: z.string().nullable().optional(),
254
288
  groupID: z.string().optional(),
255
- environment: z.string().nullable().optional()
289
+ environment: z.string().nullable().optional(),
290
+ crashFile: z.unknown().nullable().optional(),
291
+ culprit: z.string().optional(),
292
+ "event.type": z.string().optional(),
293
+ location: z.string().optional(),
294
+ metadata: z.unknown().optional(),
295
+ projectID: z.string().optional(),
296
+ tags: z.unknown().optional(),
297
+ title: z.string().optional(),
298
+ user: z.unknown().optional()
256
299
  })
257
300
  );
258
301
  var releaseResponseSchema = z.array(
259
302
  z.object({
260
303
  version: idString,
261
304
  dateCreated: z.iso.datetime(),
262
- dateReleased: z.iso.datetime().nullable(),
263
- lastEvent: z.iso.datetime().nullable(),
264
- projects: z.array(z.object({ slug: z.string().min(1) }))
305
+ dateReleased: z.iso.datetime().nullable().optional(),
306
+ lastEvent: z.iso.datetime().nullable().optional(),
307
+ projects: z.array(
308
+ z.object({
309
+ slug: z.string().min(1),
310
+ hasHealthData: z.unknown().optional(),
311
+ id: z.unknown().optional(),
312
+ name: z.unknown().optional(),
313
+ newGroups: z.unknown().optional(),
314
+ platform: z.unknown().optional(),
315
+ platforms: z.unknown().optional()
316
+ })
317
+ ),
318
+ authors: z.unknown().optional(),
319
+ commitCount: z.unknown().optional(),
320
+ currentProjectMeta: z.unknown().optional(),
321
+ data: z.unknown().optional(),
322
+ deployCount: z.unknown().optional(),
323
+ firstEvent: z.unknown().nullable().optional(),
324
+ id: z.unknown().optional(),
325
+ lastCommit: z.unknown().optional(),
326
+ lastDeploy: z.unknown().optional(),
327
+ newGroups: z.unknown().optional(),
328
+ owner: z.unknown().optional(),
329
+ ref: z.unknown().nullable().optional(),
330
+ shortVersion: z.unknown().optional(),
331
+ status: z.unknown().optional(),
332
+ url: z.unknown().nullable().optional(),
333
+ userAgent: z.unknown().nullable().optional(),
334
+ versionInfo: z.unknown().optional()
265
335
  })
266
336
  );
267
337
  var errorStatsResponseSchema = z.object({
268
- intervals: z.array(z.iso.datetime()),
338
+ intervals: z.array(z.iso.datetime()).optional(),
269
339
  groups: z.array(
270
340
  z.object({
271
341
  by: z.record(z.string(), z.union([z.string(), z.number()])),
@@ -583,6 +653,7 @@ var SentryConnector = class _SentryConnector extends BaseConnector {
583
653
  }
584
654
  async writeErrorStats(storage, stats) {
585
655
  const samples = [];
656
+ const intervals = stats.intervals ?? [];
586
657
  for (const group of stats.groups) {
587
658
  const project = group.by["project"];
588
659
  const projectKey = project !== void 0 ? String(project) : "unknown";
@@ -590,8 +661,8 @@ var SentryConnector = class _SentryConnector extends BaseConnector {
590
661
  if (series.length === 0) {
591
662
  continue;
592
663
  }
593
- for (let i = 0; i < stats.intervals.length; i++) {
594
- const intervalIso = stats.intervals[i];
664
+ for (let i = 0; i < intervals.length; i++) {
665
+ const intervalIso = intervals[i];
595
666
  const rawValue = series[i];
596
667
  if (intervalIso === void 0 || rawValue === void 0) {
597
668
  continue;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../connector-shared/src/errors.ts","../../../connector-shared/src/retry.ts","../../../connector-shared/src/version.ts","../../../connector-shared/src/request.ts","../../../connector-shared/src/rate-limit.ts","../../../connector-shared/src/map-concurrent.ts","../../../connector-shared/src/sanitize.ts","../../../connector-shared/src/epoch.ts","../../../connector-shared/src/pagination.ts","../../../connector-shared/src/logger.ts","../src/sentry.ts","../src/index.ts"],"sourcesContent":["import type { HttpResponse } from './types';\n\nexport type HttpErrorKind =\n | 'transient'\n | 'rate_limit'\n | 'auth'\n | 'upstream_bug'\n | 'client_bug';\n\nexport abstract class HttpClientError extends Error {\n abstract readonly kind: HttpErrorKind;\n readonly response?: HttpResponse;\n\n constructor(message: string, response?: HttpResponse) {\n super(message);\n this.name = new.target.name;\n this.response = response;\n }\n}\n\nexport class TransientError extends HttpClientError {\n readonly kind = 'transient' as const;\n}\n\nexport class RateLimitError extends HttpClientError {\n readonly kind = 'rate_limit' as const;\n readonly retryAfter?: Date;\n\n constructor(message: string, response?: HttpResponse, retryAfter?: Date) {\n super(message, response);\n this.retryAfter = retryAfter;\n }\n}\n\nexport class AuthError extends HttpClientError {\n readonly kind = 'auth' as const;\n}\n\nexport class UpstreamBugError extends HttpClientError {\n readonly kind = 'upstream_bug' as const;\n}\n\nexport class ClientBugError extends HttpClientError {\n readonly kind = 'client_bug' as const;\n}\n\nexport function classifyStatus(status: number): HttpErrorKind {\n if (status === 429) {\n return 'rate_limit';\n }\n if (status === 401 || status === 403) {\n return 'auth';\n }\n if (status === 408) {\n return 'transient';\n }\n if (status >= 500) {\n return 'upstream_bug';\n }\n if (status >= 400) {\n return 'client_bug';\n }\n return 'client_bug';\n}\n\nexport function errorForStatus(\n message: string,\n response: HttpResponse,\n retryAfter?: Date,\n): HttpClientError {\n const kind = classifyStatus(response.status);\n switch (kind) {\n case 'rate_limit':\n return new RateLimitError(message, response, retryAfter);\n case 'auth':\n return new AuthError(message, response);\n case 'transient':\n return new TransientError(message, response);\n case 'upstream_bug':\n return new UpstreamBugError(message, response);\n case 'client_bug':\n return new ClientBugError(message, response);\n }\n}\n","import { HttpClientError, RateLimitError, TransientError } from './errors';\n\nexport interface RetryPolicy {\n maxAttempts?: number;\n initialDelayMs?: number;\n maxDelayMs?: number;\n retryOn?: (status: number | null, err?: Error) => boolean;\n}\n\nexport const defaultRetryOn = (status: number | null, err?: Error): boolean => {\n if (err instanceof RateLimitError) {\n return true;\n }\n if (err instanceof TransientError) {\n return true;\n }\n if (status === null) {\n return err instanceof Error && !(err instanceof HttpClientError);\n }\n if (status === 408 || status === 429) {\n return true;\n }\n if (status >= 500) {\n return true;\n }\n return false;\n};\n\nexport function backoffDelayMs(\n attempt: number,\n policy: Required<Pick<RetryPolicy, 'initialDelayMs' | 'maxDelayMs'>>,\n): number {\n const base = policy.initialDelayMs * 2 ** attempt;\n const jitter = base * 0.25 * Math.random();\n return Math.min(base + jitter, policy.maxDelayMs);\n}\n\nexport function parseRetryAfter(\n headerValue: string | null,\n now: Date = new Date(),\n): Date | undefined {\n if (!headerValue) {\n return undefined;\n }\n const trimmed = headerValue.trim();\n if (/^\\d+$/.test(trimmed)) {\n return new Date(now.getTime() + Number(trimmed) * 1000);\n }\n const parsed = Date.parse(trimmed);\n if (Number.isNaN(parsed)) {\n return undefined;\n }\n return new Date(parsed);\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (signal?.aborted) {\n return Promise.reject(signal.reason ?? new Error('Aborted'));\n }\n return new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal!.reason ?? new Error('Aborted'));\n };\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n","export const HTTP_CLIENT_VERSION = '0.0.0';\n\nexport const DEFAULT_USER_AGENT = `rawdash-connector/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;\n\nexport function connectorUserAgent(connectorId: string): string {\n return `rawdash-connector-${connectorId}/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;\n}\n","import {\n AuthError,\n ClientBugError,\n HttpClientError,\n RateLimitError,\n TransientError,\n UpstreamBugError,\n errorForStatus,\n} from './errors';\nimport { defaultRetryOn, parseRetryAfter, sleep } from './retry';\nimport type { FetchLike, HttpMethod, HttpRequest, HttpResponse } from './types';\nimport { DEFAULT_USER_AGENT } from './version';\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_MAX_ATTEMPTS = 3;\nconst DEFAULT_INITIAL_DELAY_MS = 1000;\nconst DEFAULT_MAX_DELAY_MS = 60_000;\nconst OBSERVER_TIMEOUT_MS = 250;\n\nexport interface RequestObservation {\n url: string;\n method: HttpMethod;\n status: number;\n resource: string;\n requestId: string;\n body: unknown;\n}\n\nexport type RequestObserver = (\n event: RequestObservation,\n) => void | Promise<void>;\n\nexport interface RequestOptions {\n fetch?: FetchLike;\n observer?: RequestObserver;\n resource: string;\n requestId?: string;\n}\n\nasync function notifyObserver(\n observer: RequestObserver,\n event: RequestObservation,\n): Promise<void> {\n let result: void | Promise<void>;\n try {\n result = observer(event);\n } catch (err) {\n console.warn('[connector-shared] request observer threw:', err);\n return;\n }\n if (!(result instanceof Promise)) {\n return;\n }\n const guarded = result.catch((err) => {\n console.warn('[connector-shared] request observer rejected:', err);\n });\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<void>((resolve) => {\n timer = setTimeout(resolve, OBSERVER_TIMEOUT_MS);\n });\n try {\n await Promise.race([guarded, timeout]);\n } finally {\n if (timer) {\n clearTimeout(timer);\n }\n }\n}\n\nfunction newRequestId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) {\n return c.randomUUID();\n }\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction mergeHeaders(\n defaults: Record<string, string>,\n overrides: Record<string, string> | undefined,\n): Record<string, string> {\n const merged: Record<string, string> = {};\n for (const [k, v] of Object.entries(defaults)) {\n merged[k.toLowerCase()] = v;\n }\n if (overrides) {\n for (const [k, v] of Object.entries(overrides)) {\n merged[k.toLowerCase()] = v;\n }\n }\n return merged;\n}\n\nfunction linkTimeoutSignal(\n parent: AbortSignal | undefined,\n timeoutMs: number,\n): { signal: AbortSignal; cancel: () => void } {\n const controller = new AbortController();\n const onParentAbort = () => {\n controller.abort(parent?.reason);\n };\n if (parent) {\n if (parent.aborted) {\n controller.abort(parent.reason);\n } else {\n parent.addEventListener('abort', onParentAbort, { once: true });\n }\n }\n const timer = setTimeout(() => {\n controller.abort(new Error(`Request timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n return {\n signal: controller.signal,\n cancel: () => {\n clearTimeout(timer);\n if (parent) {\n parent.removeEventListener('abort', onParentAbort);\n }\n },\n };\n}\n\nasync function readBody(res: Response, parseJson: boolean): Promise<unknown> {\n if (res.status === 204 || res.status === 205) {\n return null;\n }\n const contentType = res.headers.get('content-type') ?? '';\n if (parseJson && contentType.includes('application/json')) {\n const text = await res.text();\n if (text.length === 0) {\n return null;\n }\n return JSON.parse(text);\n }\n return res.text();\n}\n\nexport async function request<T = unknown>(\n req: HttpRequest,\n options: RequestOptions,\n): Promise<HttpResponse<T>> {\n const fetchImpl: FetchLike = options.fetch ?? (globalThis.fetch as FetchLike);\n const retry = req.retry ?? {};\n const maxAttempts = retry.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;\n const initialDelayMs = retry.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;\n const maxDelayMs = retry.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;\n const retryOn = retry.retryOn ?? defaultRetryOn;\n const timeoutMs = req.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const parseJson = req.parseJson ?? true;\n\n const headers = mergeHeaders(\n {\n 'User-Agent': DEFAULT_USER_AGENT,\n Accept: 'application/json',\n },\n req.headers,\n );\n\n let lastErr: Error | undefined;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n req.signal?.throwIfAborted();\n\n const { signal, cancel } = linkTimeoutSignal(req.signal, timeoutMs);\n let res: Response;\n try {\n res = await fetchImpl(req.url, {\n method: req.method ?? 'GET',\n headers,\n body: req.body as RequestInit['body'],\n signal,\n });\n } catch (err) {\n cancel();\n if (req.signal?.aborted) {\n throw req.signal.reason ?? err;\n }\n const error = err instanceof Error ? err : new Error(String(err));\n lastErr = error;\n if (attempt < maxAttempts - 1 && retryOn(null, error)) {\n const delay = computeDelay(attempt, initialDelayMs, maxDelayMs);\n await sleep(delay, req.signal);\n continue;\n }\n throw new TransientError(error.message);\n }\n cancel();\n\n const body = await readBody(res, parseJson);\n const httpResponse: HttpResponse<T> = {\n status: res.status,\n headers: res.headers,\n body: body as T,\n };\n if (req.rateLimit) {\n const state = req.rateLimit.parse(res.headers);\n if (state) {\n httpResponse.rateLimitState = state;\n }\n }\n\n if (options.observer) {\n await notifyObserver(options.observer, {\n url: req.url,\n method: req.method ?? 'GET',\n status: res.status,\n resource: options.resource,\n requestId: options.requestId ?? newRequestId(),\n body,\n });\n }\n\n if (res.ok) {\n return httpResponse;\n }\n\n const retryAfter = parseRetryAfter(res.headers.get('retry-after'));\n const message = `HTTP ${res.status} ${res.statusText} for ${req.method ?? 'GET'} ${req.url}`;\n const err = errorForStatus(message, httpResponse, retryAfter);\n\n if (\n attempt < maxAttempts - 1 &&\n retryOn(res.status, err) &&\n !(err instanceof AuthError) &&\n !(err instanceof ClientBugError)\n ) {\n lastErr = err;\n let delay = computeDelay(attempt, initialDelayMs, maxDelayMs);\n if (err instanceof RateLimitError && retryAfter) {\n const wait = retryAfter.getTime() - Date.now();\n if (wait > 0) {\n delay = Math.min(wait, maxDelayMs);\n }\n }\n await sleep(delay, req.signal);\n continue;\n }\n\n throw err;\n }\n\n throw lastErr ?? new UpstreamBugError('Exhausted retry attempts');\n}\n\nfunction computeDelay(\n attempt: number,\n initialDelayMs: number,\n maxDelayMs: number,\n): number {\n const base = initialDelayMs * 2 ** attempt;\n const jitter = base * 0.25 * Math.random();\n return Math.min(base + jitter, maxDelayMs);\n}\n\nexport { HttpClientError };\n","export interface RateLimitState {\n remaining: number;\n resetAt: Date;\n}\n\nexport interface RateLimitPolicy {\n parse(headers: Headers): RateLimitState | null;\n}\n\nexport interface StandardRateLimitPolicyConfig {\n remainingHeader: string;\n resetHeader: string;\n resetUnit: 's' | 'ms';\n resetFallbackMs?: number;\n}\n\nexport function standardRateLimitPolicy(\n config: StandardRateLimitPolicyConfig,\n): RateLimitPolicy {\n const { remainingHeader, resetHeader, resetUnit, resetFallbackMs } = config;\n const multiplier = resetUnit === 's' ? 1000 : 1;\n return {\n parse(h) {\n const remainingRaw = h.get(remainingHeader);\n if (remainingRaw === null || remainingRaw.trim() === '') {\n return null;\n }\n const remaining = Number(remainingRaw);\n if (!Number.isFinite(remaining)) {\n return null;\n }\n const resetRaw = h.get(resetHeader);\n if (resetRaw === null) {\n if (resetFallbackMs === undefined) {\n return null;\n }\n return {\n remaining,\n resetAt: new Date(Date.now() + resetFallbackMs),\n };\n }\n if (resetRaw.trim() === '') {\n return null;\n }\n const reset = Number(resetRaw);\n if (!Number.isFinite(reset) || reset < 0) {\n return null;\n }\n const resetMs = reset * multiplier;\n if (!Number.isFinite(resetMs)) {\n return null;\n }\n return { remaining, resetAt: new Date(resetMs) };\n },\n };\n}\n","export async function mapWithConcurrency<T, R>(\n items: readonly T[],\n concurrency: number,\n fn: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n const results = new Array<R>(items.length);\n if (items.length === 0) {\n return results;\n }\n const normalized = Number.isFinite(concurrency) ? Math.floor(concurrency) : 1;\n const limit = Math.max(1, Math.min(normalized, items.length));\n let next = 0;\n let failed = false;\n\n async function worker(): Promise<void> {\n while (!failed) {\n const i = next++;\n if (i >= items.length) {\n return;\n }\n try {\n results[i] = await fn(items[i]!, i);\n } catch (err) {\n failed = true;\n throw err;\n }\n }\n }\n\n const workers: Promise<void>[] = [];\n for (let w = 0; w < limit; w++) {\n workers.push(worker());\n }\n await Promise.all(workers);\n return results;\n}\n","export interface SanitizeAllowedUrlOptions {\n url: string | null;\n host: string;\n pathname: string;\n protocol?: 'https:' | 'http:';\n}\n\nexport function sanitizeAllowedUrl(\n options: SanitizeAllowedUrlOptions,\n): string | null {\n const { url, host, pathname, protocol = 'https:' } = options;\n if (url === null) {\n return null;\n }\n try {\n const u = new URL(url);\n if (u.protocol !== protocol || u.host !== host || u.pathname !== pathname) {\n return null;\n }\n return u.toString();\n } catch {\n return null;\n }\n}\n","export type EpochUnit = 'ms' | 's' | 'iso';\n\nexport function parseEpoch(\n value: number | string | null | undefined,\n unit: EpochUnit,\n): number | null {\n if (value === null || value === undefined) {\n return null;\n }\n if (unit === 'iso') {\n if (typeof value !== 'string') {\n return null;\n }\n const ms = new Date(value).getTime();\n return Number.isFinite(ms) ? ms : null;\n }\n if (typeof value === 'string' && value.trim() === '') {\n return null;\n }\n const n = typeof value === 'number' ? value : Number(value);\n if (!Number.isFinite(n)) {\n return null;\n }\n const result = unit === 's' ? n * 1000 : n;\n return Number.isFinite(result) ? result : null;\n}\n","import { request } from './request';\nimport type { HttpRequest } from './types';\n\nexport function parseLinkHeader(header: string | null): Record<string, string> {\n if (!header) {\n return {};\n }\n const result: Record<string, string> = {};\n for (const part of header.split(',')) {\n const match = part.match(/<([^>]+)>\\s*;\\s*rel=\"([^\"]+)\"/);\n if (match) {\n result[match[2]!] = match[1]!;\n }\n }\n return result;\n}\n\nexport async function* paginateLink<T>(\n initial: HttpRequest,\n parse: (body: unknown) => T[],\n options: { resource: string },\n): AsyncIterable<T> {\n let next: string | null = initial.url;\n while (next) {\n const res: Awaited<ReturnType<typeof request>> = await request(\n {\n ...initial,\n url: next,\n },\n { resource: options.resource },\n );\n for (const item of parse(res.body)) {\n yield item;\n }\n const links = parseLinkHeader(res.headers.get('link'));\n next = links['next'] ?? null;\n }\n}\n\nexport async function* paginateCursor<T>(\n initial: HttpRequest,\n parse: (body: unknown) => { items: T[]; nextCursor: string | null },\n buildNext: (req: HttpRequest, cursor: string) => HttpRequest,\n options: { resource: string },\n): AsyncIterable<T> {\n let req: HttpRequest = initial;\n while (true) {\n const res = await request(req, { resource: options.resource });\n const { items, nextCursor } = parse(res.body);\n for (const item of items) {\n yield item;\n }\n if (!nextCursor) {\n return;\n }\n req = buildNext(req, nextCursor);\n }\n}\n\nexport async function* paginatePage<T>(\n initial: HttpRequest,\n parse: (body: unknown) => { items: T[]; hasMore: boolean },\n buildPage: (req: HttpRequest, page: number) => HttpRequest,\n options: { resource: string },\n): AsyncIterable<T> {\n let page = 1;\n while (true) {\n const req = page === 1 ? initial : buildPage(initial, page);\n const res = await request(req, { resource: options.resource });\n const { items, hasMore } = parse(res.body);\n for (const item of items) {\n yield item;\n }\n if (!hasMore || items.length === 0) {\n return;\n }\n page++;\n }\n}\n","export type LogFields = Record<string, unknown>;\n\nexport interface ConnectorLogger {\n info(event: string, fields?: LogFields): void;\n warn(event: string, fields?: LogFields): void;\n}\n\nexport interface ConnectorLoggerOptions {\n scope: string;\n}\n\nconst MAX_VALUE_LEN = 120;\n\nfunction truncate(s: string, max = MAX_VALUE_LEN): string {\n if (s.length <= max) {\n return s;\n }\n return `${s.slice(0, max - 1)}…`;\n}\n\nfunction formatValue(value: unknown): string {\n if (value === null) {\n return 'null';\n }\n if (value === undefined) {\n return '';\n }\n if (typeof value === 'number' || typeof value === 'boolean') {\n return String(value);\n }\n if (typeof value === 'string') {\n const t = truncate(value);\n if (/[\\s\"=]/.test(t)) {\n return JSON.stringify(t);\n }\n return t;\n }\n if (typeof value === 'bigint') {\n return value.toString();\n }\n let json: string | undefined;\n try {\n json = JSON.stringify(value);\n } catch {\n json = undefined;\n }\n return truncate(json ?? String(value));\n}\n\nexport function formatLogFields(fields?: LogFields): string {\n if (!fields) {\n return '';\n }\n const parts: string[] = [];\n for (const [k, v] of Object.entries(fields)) {\n if (v === undefined) {\n continue;\n }\n parts.push(`${k}=${formatValue(v)}`);\n }\n return parts.length > 0 ? ` ${parts.join(' ')}` : '';\n}\n\nexport function formatLogLine(\n scope: string,\n event: string,\n fields?: LogFields,\n): string {\n return `[${scope}] ${event}${formatLogFields(fields)}`;\n}\n\nexport function createDefaultConnectorLogger(\n opts: ConnectorLoggerOptions,\n): ConnectorLogger {\n return {\n info(event, fields) {\n console.info(formatLogLine(opts.scope, event, fields));\n },\n warn(event, fields) {\n console.warn(formatLogLine(opts.scope, event, fields));\n },\n };\n}\n\nconst NOOP_LOGGER: ConnectorLogger = {\n info() {},\n warn() {},\n};\n\nexport function noopConnectorLogger(): ConnectorLogger {\n return NOOP_LOGGER;\n}\n","import {\n type HttpResponse,\n connectorUserAgent,\n mapWithConcurrency,\n parseEpoch,\n sanitizeAllowedUrl,\n standardRateLimitPolicy,\n} from '@rawdash/connector-shared';\nimport {\n BaseConnector,\n type ChunkedSyncCursor,\n type ConnectorContext,\n type ConnectorDoc,\n type CredentialsSchema,\n type StorageHandle,\n type SyncOptions,\n type SyncResult,\n defineConfigFields,\n defineConnectorDoc,\n defineResources,\n makeChunkedCursorGuard,\n paginateChunked,\n schemasFromResources,\n selectActivePhases,\n} from '@rawdash/core';\nimport { z } from 'zod';\n\nexport const configFields = defineConfigFields(\n z.object({\n authToken: z.object({ $secret: z.string() }).meta({\n label: 'Auth Token',\n description:\n 'Sentry Internal Integration token or User Auth Token. Create one at Sentry → Settings → Auth Tokens (or for an org, Settings → Custom Integrations → New Internal Integration).',\n placeholder: 'sntrys_...',\n secret: true,\n }),\n organization: z.string().min(1).meta({\n label: 'Organization slug',\n description: \"Your Sentry organization's slug, as it appears in the URL.\",\n placeholder: 'acme',\n }),\n projects: z.array(z.string().min(1)).nonempty().optional().meta({\n label: 'Projects (optional)',\n description:\n 'Restrict the sync to specific Sentry project slugs (or numeric IDs). Omit to sync every project the token can see.',\n }),\n resources: z\n .array(z.enum(['issues', 'issue_events', 'releases', 'errors_per_hour']))\n .nonempty()\n .optional()\n .meta({\n label: 'Resources',\n description:\n \"Which Sentry resources to sync. Omit to sync all of them. 'issue_events' depends on 'issues' being fetched - enabling it without 'issues' still runs the issues query, but skips writing issue entities.\",\n }),\n eventsPerIssueCap: z.number().int().positive().max(100).optional().meta({\n label: 'Events per issue cap',\n description:\n 'Maximum number of recent events (occurrences) to sample per issue on each sync. Defaults to 100 (the max page size Sentry allows for the issue events endpoint).',\n placeholder: '100',\n }),\n statsLookbackHours: z.number().int().positive().max(168).optional().meta({\n label: 'Stats lookback (hours)',\n description:\n 'How many hours of hourly error-rate data to refresh on each sync. Defaults to 24.',\n placeholder: '24',\n }),\n }),\n);\n\nexport const doc: ConnectorDoc = defineConnectorDoc({\n displayName: 'Sentry',\n category: 'engineering',\n brandColor: '#362D59',\n tagline:\n 'Sync issues, issue events, releases, and hourly error rates from a Sentry organization.',\n vendor: {\n name: 'Sentry',\n apiDocs: 'https://docs.sentry.io/api/',\n website: 'https://sentry.io',\n },\n auth: {\n summary:\n 'A Sentry auth token is required. Use an organization-level Internal Integration token or a User Auth Token with read access to issues, events, and releases.',\n setup: [\n 'Open Sentry → Settings → Custom Integrations → New Internal Integration (or Settings → Auth Tokens for a personal token).',\n 'Grant read access to Issues & Events and Releases.',\n 'Copy the generated token and store it as a secret, referencing it from the connector config as `authToken: secret(\"SENTRY_AUTH_TOKEN\")`.',\n 'Set the `organization` slug as it appears in your Sentry URL.',\n ],\n },\n rateLimit:\n 'Sentry returns X-Sentry-Rate-Limit-Remaining / X-Sentry-Rate-Limit-Reset headers (reset in seconds); list pagination uses the Link header (page size 100).',\n limitations: [\n 'Performance / trace data is out of scope (high cost, low signal for dashboards).',\n 'Self-hosted Sentry on custom hosts is out of scope (pagination URLs are pinned to sentry.io).',\n ],\n});\n\nexport type SentryResource =\n | 'issues'\n | 'issue_events'\n | 'releases'\n | 'errors_per_hour';\n\nexport interface SentrySettings {\n organization: string;\n projects?: readonly string[];\n resources?: readonly SentryResource[];\n eventsPerIssueCap?: number;\n statsLookbackHours?: number;\n}\n\nconst sentryCredentials = {\n authToken: {\n description: 'Sentry auth token',\n auth: 'required' as const,\n },\n} satisfies CredentialsSchema;\n\ntype SentryCredentials = typeof sentryCredentials;\n\nconst sentryRateLimit = standardRateLimitPolicy({\n remainingHeader: 'x-sentry-rate-limit-remaining',\n resetHeader: 'x-sentry-rate-limit-reset',\n resetUnit: 's',\n});\n\nconst PHASE_ORDER = ['issues', 'releases', 'error_stats'] as const;\n\ntype SentryPhase = (typeof PHASE_ORDER)[number];\n\ntype SentrySyncCursor = ChunkedSyncCursor<SentryPhase, string>;\n\nconst isSentrySyncCursor = makeChunkedCursorGuard(PHASE_ORDER);\n\ninterface SentryProjectRef {\n id?: string | number;\n slug: string;\n name?: string;\n}\n\ninterface SentryIssue {\n id: string;\n shortId: string;\n title: string;\n level: string;\n status: string;\n firstSeen: string;\n lastSeen: string;\n count: string | number;\n userCount: number;\n project: SentryProjectRef;\n}\n\ninterface SentryIssueEvent {\n id?: string;\n eventID?: string;\n dateCreated: string;\n message?: string | null;\n platform?: string | null;\n groupID?: string;\n environment?: string | null;\n}\n\ninterface SentryRelease {\n version: string;\n dateCreated: string;\n dateReleased: string | null;\n lastEvent: string | null;\n projects: SentryProjectRef[];\n}\n\ninterface SentryStatsResponse {\n intervals: string[];\n groups: Array<{\n by: Record<string, string | number>;\n totals?: Record<string, number>;\n series?: Record<string, number[]>;\n }>;\n start?: string;\n end?: string;\n}\n\ninterface IssuesPageItem {\n issues: SentryIssue[];\n eventsByIssue: Map<string, SentryIssueEvent[]>;\n}\n\ninterface SentryLink {\n url: string;\n hasResults: boolean;\n}\n\nfunction parseSentryLink(\n header: string | null,\n rel: string,\n): SentryLink | null {\n if (!header) {\n return null;\n }\n for (const part of header.split(',')) {\n const m = part.match(/<([^>]+)>\\s*;\\s*(.+)$/);\n if (!m) {\n continue;\n }\n const url = m[1]!;\n const attrs = m[2]!;\n const relMatch = attrs.match(/rel=\"([^\"]+)\"/);\n if (!relMatch || relMatch[1] !== rel) {\n continue;\n }\n const resultsMatch = attrs.match(/results=\"([^\"]+)\"/);\n const hasResults = resultsMatch ? resultsMatch[1] === 'true' : true;\n return { url, hasResults };\n }\n return null;\n}\n\nconst SENTRY_API_HOST = 'sentry.io';\nconst SENTRY_API_BASE = `https://${SENTRY_API_HOST}/api/0`;\nconst DEFAULT_EVENTS_PER_ISSUE = 100;\nconst DEFAULT_STATS_LOOKBACK_HOURS = 24;\nconst MAX_PAGE_SIZE = 100;\nconst ISSUES_PAGE_SIZE = 100;\nconst RELEASES_PAGE_SIZE = 100;\nconst EVENT_FETCH_CONCURRENCY = 5;\nconst CHUNK_BUDGET_MS = 25_000;\n\nfunction clampPageSize(\n requested: number | undefined,\n fallback: number,\n): number {\n const n = requested ?? fallback;\n if (!Number.isFinite(n) || n < 1) {\n return 1;\n }\n return Math.min(Math.floor(n), MAX_PAGE_SIZE);\n}\n\nconst idString = z.string().min(1);\n\nconst issueResponseSchema = z.array(\n z.object({\n id: idString,\n shortId: z.string(),\n title: z.string(),\n level: z.enum(['debug', 'info', 'warning', 'error', 'fatal']),\n status: z.enum(['resolved', 'unresolved', 'ignored']),\n firstSeen: z.iso.datetime(),\n lastSeen: z.iso.datetime(),\n count: z.union([z.string().regex(/^\\d+$/), z.number().int().nonnegative()]),\n userCount: z.number().int().nonnegative(),\n project: z.object({ slug: z.string().min(1) }),\n }),\n);\n\nconst issueEventResponseSchema = z.array(\n z.object({\n id: z.string().optional(),\n eventID: z.string().optional(),\n dateCreated: z.iso.datetime(),\n message: z.string().nullable().optional(),\n platform: z.string().nullable().optional(),\n groupID: z.string().optional(),\n environment: z.string().nullable().optional(),\n }),\n);\n\nconst releaseResponseSchema = z.array(\n z.object({\n version: idString,\n dateCreated: z.iso.datetime(),\n dateReleased: z.iso.datetime().nullable(),\n lastEvent: z.iso.datetime().nullable(),\n projects: z.array(z.object({ slug: z.string().min(1) })),\n }),\n);\n\nconst errorStatsResponseSchema = z.object({\n intervals: z.array(z.iso.datetime()),\n groups: z.array(\n z.object({\n by: z.record(z.string(), z.union([z.string(), z.number()])),\n totals: z.record(z.string(), z.number()).optional(),\n series: z.record(z.string(), z.array(z.number())).optional(),\n }),\n ),\n start: z.string().optional(),\n end: z.string().optional(),\n});\n\nexport const sentryResources = defineResources({\n sentry_issue: {\n shape: 'entity',\n description:\n 'Sentry issues (error groups) with level, status, occurrence count, affected user count, and first/last seen timestamps.',\n endpoint: 'GET /api/0/organizations/{organization}/issues/',\n responses: { issues: issueResponseSchema },\n },\n sentry_issue_event: {\n shape: 'event',\n description:\n 'Individual event occurrences sampled per issue, with platform, environment, level, and message.',\n endpoint: 'GET /api/0/issues/{issueId}/events/',\n notes:\n 'Events are sampled: at most eventsPerIssueCap recent events per issue per sync (Sentry caps a single events page at 100), so this is a representative sample, not a full audit trail.',\n responses: { issue_events: issueEventResponseSchema },\n },\n sentry_release: {\n shape: 'entity',\n description:\n 'Releases with their versions, associated project slugs, and creation/release/last-event timestamps.',\n endpoint: 'GET /api/0/organizations/{organization}/releases/',\n responses: { releases: releaseResponseSchema },\n },\n sentry_errors_per_hour: {\n shape: 'metric',\n description:\n 'Hourly count of error events, broken down by project, over the configured lookback window.',\n endpoint: 'GET /api/0/organizations/{organization}/stats_v2/',\n unit: 'errors',\n granularity: '1h',\n dimensions: [\n { name: 'project', description: 'Sentry project slug or id.' },\n ],\n responses: { error_stats: errorStatsResponseSchema },\n },\n});\n\nexport const id = 'sentry';\n\nexport class SentryConnector extends BaseConnector<\n SentrySettings,\n SentryCredentials\n> {\n static readonly id = id;\n\n static readonly resources = sentryResources;\n\n static readonly schemas = schemasFromResources(sentryResources);\n\n static create(input: unknown, ctx?: ConnectorContext): SentryConnector {\n const parsed = configFields.parse(input);\n return new SentryConnector(\n {\n organization: parsed.organization,\n projects: parsed.projects,\n resources: parsed.resources,\n eventsPerIssueCap: parsed.eventsPerIssueCap,\n statsLookbackHours: parsed.statsLookbackHours,\n },\n { authToken: parsed.authToken },\n ctx,\n );\n }\n\n readonly id = id;\n override readonly credentials = sentryCredentials;\n\n private buildHeaders(): Record<string, string> {\n return {\n Authorization: `Bearer ${this.creds.authToken}`,\n 'User-Agent': connectorUserAgent('sentry'),\n };\n }\n\n private fetch<T>(\n url: string,\n resource: string,\n signal?: AbortSignal,\n ): Promise<HttpResponse<T>> {\n return this.get<T>(url, {\n resource,\n headers: this.buildHeaders(),\n signal,\n rateLimit: sentryRateLimit,\n });\n }\n\n private activePhases(): SentryPhase[] {\n return selectActivePhases<SentryResource, SentryPhase>(\n (r) => {\n switch (r) {\n case 'issues':\n case 'issue_events':\n return 'issues';\n case 'releases':\n return 'releases';\n case 'errors_per_hour':\n return 'error_stats';\n }\n },\n PHASE_ORDER,\n this.settings.resources,\n );\n }\n\n private allowedPagePath(phase: SentryPhase): string | null {\n const org = this.settings.organization;\n switch (phase) {\n case 'issues':\n return `/api/0/organizations/${org}/issues/`;\n case 'releases':\n return `/api/0/organizations/${org}/releases/`;\n case 'error_stats':\n return null;\n }\n }\n\n private sanitizePageUrl(\n phase: SentryPhase,\n pageUrl: string | null,\n ): string | null {\n const allowedPath = this.allowedPagePath(phase);\n if (allowedPath === null) {\n return null;\n }\n return sanitizeAllowedUrl({\n url: pageUrl,\n host: SENTRY_API_HOST,\n pathname: allowedPath,\n });\n }\n\n private resolveCursor(cursor: unknown): SentrySyncCursor | undefined {\n if (!isSentrySyncCursor(cursor)) {\n return undefined;\n }\n return {\n phase: cursor.phase,\n page: this.sanitizePageUrl(cursor.phase, cursor.page),\n };\n }\n\n private buildInitialIssuesUrl(options: SyncOptions): string {\n const u = new URL(\n `${SENTRY_API_BASE}/organizations/${this.settings.organization}/issues/`,\n );\n u.searchParams.set(\n 'limit',\n String(clampPageSize(options.pageSize, ISSUES_PAGE_SIZE)),\n );\n u.searchParams.set('sort', 'date');\n for (const project of this.settings.projects ?? []) {\n u.searchParams.append('project', project);\n }\n if (options.since) {\n u.searchParams.set('query', `lastSeen:>${options.since}`);\n }\n return u.toString();\n }\n\n private buildInitialReleasesUrl(options: SyncOptions): string {\n const u = new URL(\n `${SENTRY_API_BASE}/organizations/${this.settings.organization}/releases/`,\n );\n u.searchParams.set(\n 'per_page',\n String(clampPageSize(options.pageSize, RELEASES_PAGE_SIZE)),\n );\n for (const project of this.settings.projects ?? []) {\n u.searchParams.append('project', project);\n }\n return u.toString();\n }\n\n private buildStatsUrl(): string {\n const lookback =\n this.settings.statsLookbackHours ?? DEFAULT_STATS_LOOKBACK_HOURS;\n const u = new URL(\n `${SENTRY_API_BASE}/organizations/${this.settings.organization}/stats_v2/`,\n );\n u.searchParams.set('field', 'sum(quantity)');\n u.searchParams.set('category', 'error');\n u.searchParams.set('interval', '1h');\n u.searchParams.set('statsPeriod', `${lookback}h`);\n u.searchParams.append('groupBy', 'project');\n for (const project of this.settings.projects ?? []) {\n u.searchParams.append('project', project);\n }\n return u.toString();\n }\n\n private buildIssueEventsUrl(issueId: string): string {\n const cap = this.settings.eventsPerIssueCap ?? DEFAULT_EVENTS_PER_ISSUE;\n const u = new URL(`${SENTRY_API_BASE}/issues/${issueId}/events/`);\n u.searchParams.set(\n 'limit',\n String(Math.min(cap, DEFAULT_EVENTS_PER_ISSUE)),\n );\n return u.toString();\n }\n\n private async fetchIssuesPage(\n page: string | null,\n options: SyncOptions,\n signal: AbortSignal | undefined,\n ): Promise<{ items: IssuesPageItem[]; next: string | null }> {\n const url = page ?? this.buildInitialIssuesUrl(options);\n const res = await this.fetch<SentryIssue[]>(url, 'issues', signal);\n\n const nextLink = parseSentryLink(res.headers.get('link'), 'next');\n const next =\n nextLink && nextLink.hasResults\n ? this.sanitizePageUrl('issues', nextLink.url)\n : null;\n\n const eventsByIssue = new Map<string, SentryIssueEvent[]>();\n if (this.isResourceEnabled('issue_events')) {\n signal?.throwIfAborted();\n const fetched = await mapWithConcurrency(\n res.body,\n EVENT_FETCH_CONCURRENCY,\n async (issue) => {\n const eventsRes = await this.fetch<SentryIssueEvent[]>(\n this.buildIssueEventsUrl(issue.id),\n 'issue_events',\n signal,\n );\n return [issue.id, eventsRes.body] as const;\n },\n );\n for (const [issueId, events] of fetched) {\n eventsByIssue.set(issueId, events);\n }\n }\n\n return { items: [{ issues: res.body, eventsByIssue }], next };\n }\n\n private async fetchReleasesPage(\n page: string | null,\n options: SyncOptions,\n signal: AbortSignal | undefined,\n ): Promise<{ items: SentryRelease[]; next: string | null }> {\n const url = page ?? this.buildInitialReleasesUrl(options);\n const res = await this.fetch<SentryRelease[]>(url, 'releases', signal);\n const nextLink = parseSentryLink(res.headers.get('link'), 'next');\n const releases = res.body;\n const cutoff = options.since ? new Date(options.since).getTime() : null;\n const filtered =\n cutoff !== null\n ? releases.filter((r) => {\n const ts = new Date(r.dateReleased ?? r.dateCreated).getTime();\n return Number.isFinite(ts) ? ts >= cutoff : true;\n })\n : releases;\n const lastRelease = releases.at(-1);\n const lastTs = lastRelease\n ? new Date(lastRelease.dateReleased ?? lastRelease.dateCreated).getTime()\n : null;\n const cutoffReached =\n cutoff !== null &&\n lastTs !== null &&\n Number.isFinite(lastTs) &&\n lastTs < cutoff;\n const next =\n !cutoffReached && nextLink && nextLink.hasResults\n ? this.sanitizePageUrl('releases', nextLink.url)\n : null;\n return { items: filtered, next };\n }\n\n private async fetchErrorStats(\n signal: AbortSignal | undefined,\n ): Promise<{ items: SentryStatsResponse[]; next: string | null }> {\n const res = await this.fetch<SentryStatsResponse>(\n this.buildStatsUrl(),\n 'error_stats',\n signal,\n );\n return { items: [res.body], next: null };\n }\n\n private async writeIssuesPage(\n storage: StorageHandle,\n item: IssuesPageItem,\n ): Promise<void> {\n const writeEntities = this.isResourceEnabled('issues');\n const writeEvents = this.isResourceEnabled('issue_events');\n\n for (const issue of item.issues) {\n if (writeEntities) {\n const count =\n typeof issue.count === 'string' ? Number(issue.count) : issue.count;\n const firstSeenMs = parseEpoch(issue.firstSeen, 'iso');\n const lastSeenMs = parseEpoch(issue.lastSeen, 'iso');\n if (firstSeenMs === null || lastSeenMs === null) {\n console.warn(\n `[connector-sentry] skipping issue ${issue.id} with unparseable firstSeen/lastSeen`,\n );\n } else {\n await storage.entity({\n type: 'sentry_issue',\n id: issue.id,\n attributes: {\n shortId: issue.shortId,\n title: issue.title,\n level: issue.level,\n status: issue.status,\n firstSeen: firstSeenMs,\n lastSeen: lastSeenMs,\n count: Number.isFinite(count) ? count : 0,\n userCount: issue.userCount,\n projectSlug: issue.project.slug,\n },\n updated_at: lastSeenMs,\n });\n }\n }\n\n if (writeEvents) {\n const events = item.eventsByIssue.get(issue.id) ?? [];\n for (const ev of events) {\n const eventId = ev.eventID ?? ev.id ?? null;\n if (eventId === null) {\n continue;\n }\n const startTs = parseEpoch(ev.dateCreated, 'iso');\n if (startTs === null) {\n continue;\n }\n await storage.event({\n name: 'sentry_issue_event',\n start_ts: startTs,\n end_ts: null,\n attributes: {\n eventId,\n issueId: issue.id,\n issueShortId: issue.shortId,\n projectSlug: issue.project.slug,\n level: issue.level,\n platform: ev.platform ?? null,\n environment: ev.environment ?? null,\n message: ev.message ?? null,\n },\n });\n }\n }\n }\n }\n\n private async writeReleases(\n storage: StorageHandle,\n releases: SentryRelease[],\n ): Promise<void> {\n for (const r of releases) {\n const createdMs = parseEpoch(r.dateCreated, 'iso');\n const releasedMs = parseEpoch(r.dateReleased, 'iso');\n const lastEventMs = parseEpoch(r.lastEvent, 'iso');\n if (createdMs === null) {\n console.warn(\n `[connector-sentry] skipping release ${r.version} with unparseable dateCreated`,\n );\n continue;\n }\n await storage.entity({\n type: 'sentry_release',\n id: r.version,\n attributes: {\n version: r.version,\n projects: r.projects.map((p) => p.slug),\n dateCreated: createdMs,\n dateReleased: releasedMs,\n lastEvent: lastEventMs,\n },\n updated_at: Math.max(createdMs, releasedMs ?? 0, lastEventMs ?? 0),\n });\n }\n }\n\n private async writeErrorStats(\n storage: StorageHandle,\n stats: SentryStatsResponse,\n ): Promise<void> {\n const samples: Array<{\n name: string;\n ts: number;\n value: number;\n attributes: Record<string, string | number>;\n }> = [];\n for (const group of stats.groups) {\n const project = group.by['project'];\n const projectKey = project !== undefined ? String(project) : 'unknown';\n const series = group.series?.['sum(quantity)'] ?? [];\n if (series.length === 0) {\n continue;\n }\n for (let i = 0; i < stats.intervals.length; i++) {\n const intervalIso = stats.intervals[i];\n const rawValue = series[i];\n if (intervalIso === undefined || rawValue === undefined) {\n continue;\n }\n const ts = parseEpoch(intervalIso, 'iso');\n const value = Number(rawValue);\n if (ts === null || !Number.isFinite(value)) {\n continue;\n }\n samples.push({\n name: 'sentry_errors_per_hour',\n ts,\n value,\n attributes: { project: projectKey },\n });\n }\n }\n await storage.metrics(samples, { names: ['sentry_errors_per_hour'] });\n }\n\n async sync(\n options: SyncOptions,\n storage: StorageHandle,\n signal?: AbortSignal,\n ): Promise<SyncResult> {\n const cursor = this.resolveCursor(options.cursor);\n const isFull = options.mode === 'full';\n const phases = this.activePhases();\n\n return paginateChunked<SentryPhase, string>({\n phases,\n cursor,\n signal,\n logger: this.logger,\n pipeline: true,\n maxChunkMs: CHUNK_BUDGET_MS,\n fetchPage: async (phase, page, sig) => {\n switch (phase) {\n case 'issues':\n return this.fetchIssuesPage(page, options, sig);\n case 'releases':\n return this.fetchReleasesPage(page, options, sig);\n case 'error_stats':\n return this.fetchErrorStats(sig);\n }\n },\n writeBatch: async (phase, items, page) => {\n if (isFull && page === null) {\n switch (phase) {\n case 'issues':\n if (this.isResourceEnabled('issues')) {\n await storage.entities([], { types: ['sentry_issue'] });\n }\n if (this.isResourceEnabled('issue_events')) {\n await storage.events([], { names: ['sentry_issue_event'] });\n }\n break;\n case 'releases':\n await storage.entities([], { types: ['sentry_release'] });\n break;\n case 'error_stats':\n break;\n }\n }\n switch (phase) {\n case 'issues':\n for (const item of items as IssuesPageItem[]) {\n await this.writeIssuesPage(storage, item);\n }\n return;\n case 'releases':\n return this.writeReleases(storage, items as SentryRelease[]);\n case 'error_stats':\n for (const stats of items as SentryStatsResponse[]) {\n await this.writeErrorStats(storage, stats);\n }\n return;\n }\n },\n });\n }\n}\n","import { SentryConnector } from './sentry';\n\nexport {\n configFields,\n doc,\n SentryConnector,\n sentryResources as resources,\n id,\n} from './sentry';\nexport type { SentryResource, SentrySettings } from './sentry';\nexport default SentryConnector;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../connector-shared/src/errors.ts","../../../connector-shared/src/retry.ts","../../../connector-shared/src/version.ts","../../../connector-shared/src/request.ts","../../../connector-shared/src/rate-limit.ts","../../../connector-shared/src/map-concurrent.ts","../../../connector-shared/src/sanitize.ts","../../../connector-shared/src/epoch.ts","../../../connector-shared/src/pagination.ts","../../../connector-shared/src/logger.ts","../src/sentry.ts","../src/index.ts"],"sourcesContent":["import type { HttpResponse } from './types';\n\nexport type HttpErrorKind =\n | 'transient'\n | 'rate_limit'\n | 'auth'\n | 'upstream_bug'\n | 'client_bug';\n\nexport abstract class HttpClientError extends Error {\n abstract readonly kind: HttpErrorKind;\n readonly response?: HttpResponse;\n\n constructor(message: string, response?: HttpResponse) {\n super(message);\n this.name = new.target.name;\n this.response = response;\n }\n}\n\nexport class TransientError extends HttpClientError {\n readonly kind = 'transient' as const;\n}\n\nexport class RateLimitError extends HttpClientError {\n readonly kind = 'rate_limit' as const;\n readonly retryAfter?: Date;\n\n constructor(message: string, response?: HttpResponse, retryAfter?: Date) {\n super(message, response);\n this.retryAfter = retryAfter;\n }\n}\n\nexport class AuthError extends HttpClientError {\n readonly kind = 'auth' as const;\n}\n\nexport class UpstreamBugError extends HttpClientError {\n readonly kind = 'upstream_bug' as const;\n}\n\nexport class ClientBugError extends HttpClientError {\n readonly kind = 'client_bug' as const;\n}\n\nexport function classifyStatus(status: number): HttpErrorKind {\n if (status === 429) {\n return 'rate_limit';\n }\n if (status === 401 || status === 403) {\n return 'auth';\n }\n if (status === 408) {\n return 'transient';\n }\n if (status >= 500) {\n return 'upstream_bug';\n }\n if (status >= 400) {\n return 'client_bug';\n }\n return 'client_bug';\n}\n\nexport function errorForStatus(\n message: string,\n response: HttpResponse,\n retryAfter?: Date,\n): HttpClientError {\n const kind = classifyStatus(response.status);\n switch (kind) {\n case 'rate_limit':\n return new RateLimitError(message, response, retryAfter);\n case 'auth':\n return new AuthError(message, response);\n case 'transient':\n return new TransientError(message, response);\n case 'upstream_bug':\n return new UpstreamBugError(message, response);\n case 'client_bug':\n return new ClientBugError(message, response);\n }\n}\n","import { HttpClientError, RateLimitError, TransientError } from './errors';\n\nexport interface RetryPolicy {\n maxAttempts?: number;\n initialDelayMs?: number;\n maxDelayMs?: number;\n retryOn?: (status: number | null, err?: Error) => boolean;\n}\n\nexport const defaultRetryOn = (status: number | null, err?: Error): boolean => {\n if (err instanceof RateLimitError) {\n return true;\n }\n if (err instanceof TransientError) {\n return true;\n }\n if (status === null) {\n return err instanceof Error && !(err instanceof HttpClientError);\n }\n if (status === 408 || status === 429) {\n return true;\n }\n if (status >= 500) {\n return true;\n }\n return false;\n};\n\nexport function backoffDelayMs(\n attempt: number,\n policy: Required<Pick<RetryPolicy, 'initialDelayMs' | 'maxDelayMs'>>,\n): number {\n const base = policy.initialDelayMs * 2 ** attempt;\n const jitter = base * 0.25 * Math.random();\n return Math.min(base + jitter, policy.maxDelayMs);\n}\n\nexport function parseRetryAfter(\n headerValue: string | null,\n now: Date = new Date(),\n): Date | undefined {\n if (!headerValue) {\n return undefined;\n }\n const trimmed = headerValue.trim();\n if (/^\\d+$/.test(trimmed)) {\n return new Date(now.getTime() + Number(trimmed) * 1000);\n }\n const parsed = Date.parse(trimmed);\n if (Number.isNaN(parsed)) {\n return undefined;\n }\n return new Date(parsed);\n}\n\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (signal?.aborted) {\n return Promise.reject(signal.reason ?? new Error('Aborted'));\n }\n return new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal!.reason ?? new Error('Aborted'));\n };\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n","export const HTTP_CLIENT_VERSION = '0.0.0';\n\nexport const DEFAULT_USER_AGENT = `rawdash-connector/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;\n\nexport function connectorUserAgent(connectorId: string): string {\n return `rawdash-connector-${connectorId}/${HTTP_CLIENT_VERSION} (+https://rawdash.dev)`;\n}\n","import {\n AuthError,\n ClientBugError,\n HttpClientError,\n RateLimitError,\n TransientError,\n UpstreamBugError,\n errorForStatus,\n} from './errors';\nimport { defaultRetryOn, parseRetryAfter, sleep } from './retry';\nimport type { FetchLike, HttpMethod, HttpRequest, HttpResponse } from './types';\nimport { DEFAULT_USER_AGENT } from './version';\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_MAX_ATTEMPTS = 3;\nconst DEFAULT_INITIAL_DELAY_MS = 1000;\nconst DEFAULT_MAX_DELAY_MS = 60_000;\nconst OBSERVER_TIMEOUT_MS = 250;\n\nexport interface RequestObservation {\n url: string;\n method: HttpMethod;\n status: number;\n resource: string;\n requestId: string;\n body: unknown;\n}\n\nexport type RequestObserver = (\n event: RequestObservation,\n) => void | Promise<void>;\n\nexport interface RequestOptions {\n fetch?: FetchLike;\n observer?: RequestObserver;\n resource: string;\n requestId?: string;\n}\n\nasync function notifyObserver(\n observer: RequestObserver,\n event: RequestObservation,\n): Promise<void> {\n let result: void | Promise<void>;\n try {\n result = observer(event);\n } catch (err) {\n console.warn('[connector-shared] request observer threw:', err);\n return;\n }\n if (!(result instanceof Promise)) {\n return;\n }\n const guarded = result.catch((err) => {\n console.warn('[connector-shared] request observer rejected:', err);\n });\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<void>((resolve) => {\n timer = setTimeout(resolve, OBSERVER_TIMEOUT_MS);\n });\n try {\n await Promise.race([guarded, timeout]);\n } finally {\n if (timer) {\n clearTimeout(timer);\n }\n }\n}\n\nfunction newRequestId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n if (c?.randomUUID) {\n return c.randomUUID();\n }\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction mergeHeaders(\n defaults: Record<string, string>,\n overrides: Record<string, string> | undefined,\n): Record<string, string> {\n const merged: Record<string, string> = {};\n for (const [k, v] of Object.entries(defaults)) {\n merged[k.toLowerCase()] = v;\n }\n if (overrides) {\n for (const [k, v] of Object.entries(overrides)) {\n merged[k.toLowerCase()] = v;\n }\n }\n return merged;\n}\n\nfunction linkTimeoutSignal(\n parent: AbortSignal | undefined,\n timeoutMs: number,\n): { signal: AbortSignal; cancel: () => void } {\n const controller = new AbortController();\n const onParentAbort = () => {\n controller.abort(parent?.reason);\n };\n if (parent) {\n if (parent.aborted) {\n controller.abort(parent.reason);\n } else {\n parent.addEventListener('abort', onParentAbort, { once: true });\n }\n }\n const timer = setTimeout(() => {\n controller.abort(new Error(`Request timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n return {\n signal: controller.signal,\n cancel: () => {\n clearTimeout(timer);\n if (parent) {\n parent.removeEventListener('abort', onParentAbort);\n }\n },\n };\n}\n\nasync function readBody(res: Response, parseJson: boolean): Promise<unknown> {\n if (res.status === 204 || res.status === 205) {\n return null;\n }\n const contentType = res.headers.get('content-type') ?? '';\n if (parseJson && contentType.includes('application/json')) {\n const text = await res.text();\n if (text.length === 0) {\n return null;\n }\n return JSON.parse(text);\n }\n return res.text();\n}\n\nexport async function request<T = unknown>(\n req: HttpRequest,\n options: RequestOptions,\n): Promise<HttpResponse<T>> {\n const fetchImpl: FetchLike = options.fetch ?? (globalThis.fetch as FetchLike);\n const retry = req.retry ?? {};\n const maxAttempts = retry.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;\n const initialDelayMs = retry.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;\n const maxDelayMs = retry.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;\n const retryOn = retry.retryOn ?? defaultRetryOn;\n const timeoutMs = req.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const parseJson = req.parseJson ?? true;\n\n const headers = mergeHeaders(\n {\n 'User-Agent': DEFAULT_USER_AGENT,\n Accept: 'application/json',\n },\n req.headers,\n );\n\n let lastErr: Error | undefined;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n req.signal?.throwIfAborted();\n\n const { signal, cancel } = linkTimeoutSignal(req.signal, timeoutMs);\n let res: Response;\n try {\n res = await fetchImpl(req.url, {\n method: req.method ?? 'GET',\n headers,\n body: req.body as RequestInit['body'],\n signal,\n });\n } catch (err) {\n cancel();\n if (req.signal?.aborted) {\n throw req.signal.reason ?? err;\n }\n const error = err instanceof Error ? err : new Error(String(err));\n lastErr = error;\n if (attempt < maxAttempts - 1 && retryOn(null, error)) {\n const delay = computeDelay(attempt, initialDelayMs, maxDelayMs);\n await sleep(delay, req.signal);\n continue;\n }\n throw new TransientError(error.message);\n }\n cancel();\n\n const body = await readBody(res, parseJson);\n const httpResponse: HttpResponse<T> = {\n status: res.status,\n headers: res.headers,\n body: body as T,\n };\n if (req.rateLimit) {\n const state = req.rateLimit.parse(res.headers);\n if (state) {\n httpResponse.rateLimitState = state;\n }\n }\n\n if (options.observer) {\n await notifyObserver(options.observer, {\n url: req.url,\n method: req.method ?? 'GET',\n status: res.status,\n resource: options.resource,\n requestId: options.requestId ?? newRequestId(),\n body,\n });\n }\n\n if (res.ok) {\n return httpResponse;\n }\n\n const retryAfter = parseRetryAfter(res.headers.get('retry-after'));\n const message = `HTTP ${res.status} ${res.statusText} for ${req.method ?? 'GET'} ${req.url}`;\n const err = errorForStatus(message, httpResponse, retryAfter);\n\n if (\n attempt < maxAttempts - 1 &&\n retryOn(res.status, err) &&\n !(err instanceof AuthError) &&\n !(err instanceof ClientBugError)\n ) {\n lastErr = err;\n let delay = computeDelay(attempt, initialDelayMs, maxDelayMs);\n if (err instanceof RateLimitError && retryAfter) {\n const wait = retryAfter.getTime() - Date.now();\n if (wait > 0) {\n delay = Math.min(wait, maxDelayMs);\n }\n }\n await sleep(delay, req.signal);\n continue;\n }\n\n throw err;\n }\n\n throw lastErr ?? new UpstreamBugError('Exhausted retry attempts');\n}\n\nfunction computeDelay(\n attempt: number,\n initialDelayMs: number,\n maxDelayMs: number,\n): number {\n const base = initialDelayMs * 2 ** attempt;\n const jitter = base * 0.25 * Math.random();\n return Math.min(base + jitter, maxDelayMs);\n}\n\nexport { HttpClientError };\n","export interface RateLimitState {\n remaining: number;\n resetAt: Date;\n}\n\nexport interface RateLimitPolicy {\n parse(headers: Headers): RateLimitState | null;\n}\n\nexport interface StandardRateLimitPolicyConfig {\n remainingHeader: string;\n resetHeader: string;\n resetUnit: 's' | 'ms';\n resetFallbackMs?: number;\n}\n\nexport function standardRateLimitPolicy(\n config: StandardRateLimitPolicyConfig,\n): RateLimitPolicy {\n const { remainingHeader, resetHeader, resetUnit, resetFallbackMs } = config;\n const multiplier = resetUnit === 's' ? 1000 : 1;\n return {\n parse(h) {\n const remainingRaw = h.get(remainingHeader);\n if (remainingRaw === null || remainingRaw.trim() === '') {\n return null;\n }\n const remaining = Number(remainingRaw);\n if (!Number.isFinite(remaining)) {\n return null;\n }\n const resetRaw = h.get(resetHeader);\n if (resetRaw === null) {\n if (resetFallbackMs === undefined) {\n return null;\n }\n return {\n remaining,\n resetAt: new Date(Date.now() + resetFallbackMs),\n };\n }\n if (resetRaw.trim() === '') {\n return null;\n }\n const reset = Number(resetRaw);\n if (!Number.isFinite(reset) || reset < 0) {\n return null;\n }\n const resetMs = reset * multiplier;\n if (!Number.isFinite(resetMs)) {\n return null;\n }\n return { remaining, resetAt: new Date(resetMs) };\n },\n };\n}\n","export async function mapWithConcurrency<T, R>(\n items: readonly T[],\n concurrency: number,\n fn: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n const results = new Array<R>(items.length);\n if (items.length === 0) {\n return results;\n }\n const normalized = Number.isFinite(concurrency) ? Math.floor(concurrency) : 1;\n const limit = Math.max(1, Math.min(normalized, items.length));\n let next = 0;\n let failed = false;\n\n async function worker(): Promise<void> {\n while (!failed) {\n const i = next++;\n if (i >= items.length) {\n return;\n }\n try {\n results[i] = await fn(items[i]!, i);\n } catch (err) {\n failed = true;\n throw err;\n }\n }\n }\n\n const workers: Promise<void>[] = [];\n for (let w = 0; w < limit; w++) {\n workers.push(worker());\n }\n await Promise.all(workers);\n return results;\n}\n","export interface SanitizeAllowedUrlOptions {\n url: string | null;\n host: string;\n pathname: string;\n protocol?: 'https:' | 'http:';\n}\n\nexport function sanitizeAllowedUrl(\n options: SanitizeAllowedUrlOptions,\n): string | null {\n const { url, host, pathname, protocol = 'https:' } = options;\n if (url === null) {\n return null;\n }\n try {\n const u = new URL(url);\n if (u.protocol !== protocol || u.host !== host || u.pathname !== pathname) {\n return null;\n }\n return u.toString();\n } catch {\n return null;\n }\n}\n","export type EpochUnit = 'ms' | 's' | 'iso';\n\nexport function parseEpoch(\n value: number | string | null | undefined,\n unit: EpochUnit,\n): number | null {\n if (value === null || value === undefined) {\n return null;\n }\n if (unit === 'iso') {\n if (typeof value !== 'string') {\n return null;\n }\n const ms = new Date(value).getTime();\n return Number.isFinite(ms) ? ms : null;\n }\n if (typeof value === 'string' && value.trim() === '') {\n return null;\n }\n const n = typeof value === 'number' ? value : Number(value);\n if (!Number.isFinite(n)) {\n return null;\n }\n const result = unit === 's' ? n * 1000 : n;\n return Number.isFinite(result) ? result : null;\n}\n","import { request } from './request';\nimport type { HttpRequest } from './types';\n\nexport function parseLinkHeader(header: string | null): Record<string, string> {\n if (!header) {\n return {};\n }\n const result: Record<string, string> = {};\n for (const part of header.split(',')) {\n const match = part.match(/<([^>]+)>\\s*;\\s*rel=\"([^\"]+)\"/);\n if (match) {\n result[match[2]!] = match[1]!;\n }\n }\n return result;\n}\n\nexport async function* paginateLink<T>(\n initial: HttpRequest,\n parse: (body: unknown) => T[],\n options: { resource: string },\n): AsyncIterable<T> {\n let next: string | null = initial.url;\n while (next) {\n const res: Awaited<ReturnType<typeof request>> = await request(\n {\n ...initial,\n url: next,\n },\n { resource: options.resource },\n );\n for (const item of parse(res.body)) {\n yield item;\n }\n const links = parseLinkHeader(res.headers.get('link'));\n next = links['next'] ?? null;\n }\n}\n\nexport async function* paginateCursor<T>(\n initial: HttpRequest,\n parse: (body: unknown) => { items: T[]; nextCursor: string | null },\n buildNext: (req: HttpRequest, cursor: string) => HttpRequest,\n options: { resource: string },\n): AsyncIterable<T> {\n let req: HttpRequest = initial;\n while (true) {\n const res = await request(req, { resource: options.resource });\n const { items, nextCursor } = parse(res.body);\n for (const item of items) {\n yield item;\n }\n if (!nextCursor) {\n return;\n }\n req = buildNext(req, nextCursor);\n }\n}\n\nexport async function* paginatePage<T>(\n initial: HttpRequest,\n parse: (body: unknown) => { items: T[]; hasMore: boolean },\n buildPage: (req: HttpRequest, page: number) => HttpRequest,\n options: { resource: string },\n): AsyncIterable<T> {\n let page = 1;\n while (true) {\n const req = page === 1 ? initial : buildPage(initial, page);\n const res = await request(req, { resource: options.resource });\n const { items, hasMore } = parse(res.body);\n for (const item of items) {\n yield item;\n }\n if (!hasMore || items.length === 0) {\n return;\n }\n page++;\n }\n}\n","export type LogFields = Record<string, unknown>;\n\nexport interface ConnectorLogger {\n info(event: string, fields?: LogFields): void;\n warn(event: string, fields?: LogFields): void;\n}\n\nexport interface ConnectorLoggerOptions {\n scope: string;\n}\n\nconst MAX_VALUE_LEN = 120;\n\nfunction truncate(s: string, max = MAX_VALUE_LEN): string {\n if (s.length <= max) {\n return s;\n }\n return `${s.slice(0, max - 1)}…`;\n}\n\nfunction formatValue(value: unknown): string {\n if (value === null) {\n return 'null';\n }\n if (value === undefined) {\n return '';\n }\n if (typeof value === 'number' || typeof value === 'boolean') {\n return String(value);\n }\n if (typeof value === 'string') {\n const t = truncate(value);\n if (/[\\s\"=]/.test(t)) {\n return JSON.stringify(t);\n }\n return t;\n }\n if (typeof value === 'bigint') {\n return value.toString();\n }\n let json: string | undefined;\n try {\n json = JSON.stringify(value);\n } catch {\n json = undefined;\n }\n return truncate(json ?? String(value));\n}\n\nexport function formatLogFields(fields?: LogFields): string {\n if (!fields) {\n return '';\n }\n const parts: string[] = [];\n for (const [k, v] of Object.entries(fields)) {\n if (v === undefined) {\n continue;\n }\n parts.push(`${k}=${formatValue(v)}`);\n }\n return parts.length > 0 ? ` ${parts.join(' ')}` : '';\n}\n\nexport function formatLogLine(\n scope: string,\n event: string,\n fields?: LogFields,\n): string {\n return `[${scope}] ${event}${formatLogFields(fields)}`;\n}\n\nexport function createDefaultConnectorLogger(\n opts: ConnectorLoggerOptions,\n): ConnectorLogger {\n return {\n info(event, fields) {\n console.info(formatLogLine(opts.scope, event, fields));\n },\n warn(event, fields) {\n console.warn(formatLogLine(opts.scope, event, fields));\n },\n };\n}\n\nconst NOOP_LOGGER: ConnectorLogger = {\n info() {},\n warn() {},\n};\n\nexport function noopConnectorLogger(): ConnectorLogger {\n return NOOP_LOGGER;\n}\n","import {\n type HttpResponse,\n connectorUserAgent,\n mapWithConcurrency,\n parseEpoch,\n sanitizeAllowedUrl,\n standardRateLimitPolicy,\n} from '@rawdash/connector-shared';\nimport {\n BaseConnector,\n type ChunkedSyncCursor,\n type ConnectorContext,\n type ConnectorDoc,\n type CredentialsSchema,\n type StorageHandle,\n type SyncOptions,\n type SyncResult,\n defineConfigFields,\n defineConnectorDoc,\n defineResources,\n makeChunkedCursorGuard,\n paginateChunked,\n schemasFromResources,\n selectActivePhases,\n} from '@rawdash/core';\nimport { z } from 'zod';\n\nexport const configFields = defineConfigFields(\n z.object({\n authToken: z.object({ $secret: z.string() }).meta({\n label: 'Auth Token',\n description:\n 'Sentry Internal Integration token or User Auth Token. Create one at Sentry → Settings → Auth Tokens (or for an org, Settings → Custom Integrations → New Internal Integration).',\n placeholder: 'sntrys_...',\n secret: true,\n }),\n organization: z.string().min(1).meta({\n label: 'Organization slug',\n description: \"Your Sentry organization's slug, as it appears in the URL.\",\n placeholder: 'acme',\n }),\n projects: z.array(z.string().min(1)).nonempty().optional().meta({\n label: 'Projects (optional)',\n description:\n 'Restrict the sync to specific Sentry project slugs (or numeric IDs). Omit to sync every project the token can see.',\n }),\n resources: z\n .array(z.enum(['issues', 'issue_events', 'releases', 'errors_per_hour']))\n .nonempty()\n .optional()\n .meta({\n label: 'Resources',\n description:\n \"Which Sentry resources to sync. Omit to sync all of them. 'issue_events' depends on 'issues' being fetched - enabling it without 'issues' still runs the issues query, but skips writing issue entities.\",\n }),\n eventsPerIssueCap: z.number().int().positive().max(100).optional().meta({\n label: 'Events per issue cap',\n description:\n 'Maximum number of recent events (occurrences) to sample per issue on each sync. Defaults to 100 (the max page size Sentry allows for the issue events endpoint).',\n placeholder: '100',\n }),\n statsLookbackHours: z.number().int().positive().max(168).optional().meta({\n label: 'Stats lookback (hours)',\n description:\n 'How many hours of hourly error-rate data to refresh on each sync. Defaults to 24.',\n placeholder: '24',\n }),\n }),\n);\n\nexport const doc: ConnectorDoc = defineConnectorDoc({\n displayName: 'Sentry',\n category: 'engineering',\n brandColor: '#362D59',\n tagline:\n 'Sync issues, issue events, releases, and hourly error rates from a Sentry organization.',\n vendor: {\n name: 'Sentry',\n domain: 'sentry.io',\n apiDocs: 'https://docs.sentry.io/api/',\n website: 'https://sentry.io',\n },\n auth: {\n summary:\n 'A Sentry auth token is required. Use an organization-level Internal Integration token or a User Auth Token with read access to issues, events, and releases.',\n setup: [\n 'Open Sentry → Settings → Custom Integrations → New Internal Integration (or Settings → Auth Tokens for a personal token).',\n 'Grant read access to Issues & Events and Releases.',\n 'Copy the generated token and store it as a secret, referencing it from the connector config as `authToken: secret(\"SENTRY_AUTH_TOKEN\")`.',\n 'Set the `organization` slug as it appears in your Sentry URL.',\n ],\n },\n rateLimit:\n 'Sentry returns X-Sentry-Rate-Limit-Remaining / X-Sentry-Rate-Limit-Reset headers (reset in seconds); list pagination uses the Link header (page size 100).',\n limitations: [\n 'Performance / trace data is out of scope (high cost, low signal for dashboards).',\n 'Self-hosted Sentry on custom hosts is out of scope (pagination URLs are pinned to sentry.io).',\n ],\n});\n\nexport type SentryResource =\n | 'issues'\n | 'issue_events'\n | 'releases'\n | 'errors_per_hour';\n\nexport interface SentrySettings {\n organization: string;\n projects?: readonly string[];\n resources?: readonly SentryResource[];\n eventsPerIssueCap?: number;\n statsLookbackHours?: number;\n}\n\nconst sentryCredentials = {\n authToken: {\n description: 'Sentry auth token',\n auth: 'required' as const,\n },\n} satisfies CredentialsSchema;\n\ntype SentryCredentials = typeof sentryCredentials;\n\nconst sentryRateLimit = standardRateLimitPolicy({\n remainingHeader: 'x-sentry-rate-limit-remaining',\n resetHeader: 'x-sentry-rate-limit-reset',\n resetUnit: 's',\n});\n\nconst PHASE_ORDER = ['issues', 'releases', 'error_stats'] as const;\n\ntype SentryPhase = (typeof PHASE_ORDER)[number];\n\ntype SentrySyncCursor = ChunkedSyncCursor<SentryPhase, string>;\n\nconst isSentrySyncCursor = makeChunkedCursorGuard(PHASE_ORDER);\n\ninterface SentryProjectRef {\n id?: string | number;\n slug: string;\n name?: string;\n platform?: string;\n}\n\ninterface SentryIssue {\n id: string;\n shortId: string;\n title: string;\n level: string;\n status: string;\n firstSeen: string;\n lastSeen: string;\n count: string | number;\n userCount: number;\n project: SentryProjectRef;\n}\n\ninterface SentryIssueEvent {\n id?: string;\n eventID?: string;\n dateCreated: string;\n message?: string | null;\n platform?: string | null;\n groupID?: string;\n environment?: string | null;\n}\n\ninterface SentryRelease {\n version: string;\n dateCreated: string;\n dateReleased?: string | null;\n lastEvent?: string | null;\n projects: SentryProjectRef[];\n}\n\ninterface SentryStatsResponse {\n intervals?: string[];\n groups: Array<{\n by: Record<string, string | number>;\n totals?: Record<string, number>;\n series?: Record<string, number[]>;\n }>;\n start?: string;\n end?: string;\n}\n\ninterface IssuesPageItem {\n issues: SentryIssue[];\n eventsByIssue: Map<string, SentryIssueEvent[]>;\n}\n\ninterface SentryLink {\n url: string;\n hasResults: boolean;\n}\n\nfunction parseSentryLink(\n header: string | null,\n rel: string,\n): SentryLink | null {\n if (!header) {\n return null;\n }\n for (const part of header.split(',')) {\n const m = part.match(/<([^>]+)>\\s*;\\s*(.+)$/);\n if (!m) {\n continue;\n }\n const url = m[1]!;\n const attrs = m[2]!;\n const relMatch = attrs.match(/rel=\"([^\"]+)\"/);\n if (!relMatch || relMatch[1] !== rel) {\n continue;\n }\n const resultsMatch = attrs.match(/results=\"([^\"]+)\"/);\n const hasResults = resultsMatch ? resultsMatch[1] === 'true' : true;\n return { url, hasResults };\n }\n return null;\n}\n\nconst SENTRY_API_HOST = 'sentry.io';\nconst SENTRY_API_BASE = `https://${SENTRY_API_HOST}/api/0`;\nconst DEFAULT_EVENTS_PER_ISSUE = 100;\nconst DEFAULT_STATS_LOOKBACK_HOURS = 24;\nconst MAX_PAGE_SIZE = 100;\nconst ISSUES_PAGE_SIZE = 100;\nconst RELEASES_PAGE_SIZE = 100;\nconst EVENT_FETCH_CONCURRENCY = 5;\nconst CHUNK_BUDGET_MS = 25_000;\n\nfunction clampPageSize(\n requested: number | undefined,\n fallback: number,\n): number {\n const n = requested ?? fallback;\n if (!Number.isFinite(n) || n < 1) {\n return 1;\n }\n return Math.min(Math.floor(n), MAX_PAGE_SIZE);\n}\n\nconst idString = z.string().min(1);\n\nconst issueResponseSchema = z.array(\n z.object({\n id: idString,\n shortId: z.string(),\n title: z.string(),\n level: z.enum(['debug', 'info', 'warning', 'error', 'fatal']),\n status: z.enum(['resolved', 'unresolved', 'ignored']),\n firstSeen: z.iso.datetime(),\n lastSeen: z.iso.datetime(),\n count: z.union([z.string().regex(/^\\d+$/), z.number().int().nonnegative()]),\n userCount: z.number().int().nonnegative(),\n project: z.object({\n slug: z.string().min(1),\n id: z.union([idString, z.number()]).optional(),\n name: z.string().optional(),\n platform: z.string().nullable().optional(),\n }),\n annotations: z.unknown().optional(),\n assignedTo: z.unknown().nullable().optional(),\n culprit: z.string().nullable().optional(),\n filtered: z.unknown().nullable().optional(),\n hasSeen: z.boolean().optional(),\n isBookmarked: z.boolean().optional(),\n isPublic: z.boolean().optional(),\n isSubscribed: z.boolean().optional(),\n isUnhandled: z.boolean().optional(),\n issueCategory: z.string().optional(),\n issueType: z.string().optional(),\n lifetime: z.unknown().optional(),\n logger: z.string().nullable().optional(),\n metadata: z.unknown().optional(),\n numComments: z.number().int().nonnegative().optional(),\n permalink: z.string().optional(),\n platform: z.string().nullable().optional(),\n priority: z.string().optional(),\n priorityLockedAt: z.iso.datetime().nullable().optional(),\n seerAutofixLastTriggered: z.iso.datetime().nullable().optional(),\n seerExplorerAutofixLastTriggered: z.iso.datetime().nullable().optional(),\n seerFixabilityScore: z.number().nullable().optional(),\n shareId: z.string().nullable().optional(),\n stats: z.unknown().optional(),\n statusDetails: z.unknown().optional(),\n subscriptionDetails: z.unknown().nullable().optional(),\n substatus: z.string().nullable().optional(),\n type: z.string().optional(),\n }),\n);\n\nconst issueEventResponseSchema = z.array(\n z.object({\n id: z.string().optional(),\n eventID: z.string().optional(),\n dateCreated: z.iso.datetime(),\n message: z.string().nullable().optional(),\n platform: z.string().nullable().optional(),\n groupID: z.string().optional(),\n environment: z.string().nullable().optional(),\n crashFile: z.unknown().nullable().optional(),\n culprit: z.string().optional(),\n 'event.type': z.string().optional(),\n location: z.string().optional(),\n metadata: z.unknown().optional(),\n projectID: z.string().optional(),\n tags: z.unknown().optional(),\n title: z.string().optional(),\n user: z.unknown().optional(),\n }),\n);\n\nconst releaseResponseSchema = z.array(\n z.object({\n version: idString,\n dateCreated: z.iso.datetime(),\n dateReleased: z.iso.datetime().nullable().optional(),\n lastEvent: z.iso.datetime().nullable().optional(),\n projects: z.array(\n z.object({\n slug: z.string().min(1),\n hasHealthData: z.unknown().optional(),\n id: z.unknown().optional(),\n name: z.unknown().optional(),\n newGroups: z.unknown().optional(),\n platform: z.unknown().optional(),\n platforms: z.unknown().optional(),\n }),\n ),\n authors: z.unknown().optional(),\n commitCount: z.unknown().optional(),\n currentProjectMeta: z.unknown().optional(),\n data: z.unknown().optional(),\n deployCount: z.unknown().optional(),\n firstEvent: z.unknown().nullable().optional(),\n id: z.unknown().optional(),\n lastCommit: z.unknown().optional(),\n lastDeploy: z.unknown().optional(),\n newGroups: z.unknown().optional(),\n owner: z.unknown().optional(),\n ref: z.unknown().nullable().optional(),\n shortVersion: z.unknown().optional(),\n status: z.unknown().optional(),\n url: z.unknown().nullable().optional(),\n userAgent: z.unknown().nullable().optional(),\n versionInfo: z.unknown().optional(),\n }),\n);\n\nconst errorStatsResponseSchema = z.object({\n intervals: z.array(z.iso.datetime()).optional(),\n groups: z.array(\n z.object({\n by: z.record(z.string(), z.union([z.string(), z.number()])),\n totals: z.record(z.string(), z.number()).optional(),\n series: z.record(z.string(), z.array(z.number())).optional(),\n }),\n ),\n start: z.string().optional(),\n end: z.string().optional(),\n});\n\nexport const sentryResources = defineResources({\n sentry_issue: {\n shape: 'entity',\n description:\n 'Sentry issues (error groups) with level, status, occurrence count, affected user count, and first/last seen timestamps.',\n endpoint: 'GET /api/0/organizations/{organization}/issues/',\n responses: { issues: issueResponseSchema },\n },\n sentry_issue_event: {\n shape: 'event',\n description:\n 'Individual event occurrences sampled per issue, with platform, environment, level, and message.',\n endpoint: 'GET /api/0/issues/{issueId}/events/',\n notes:\n 'Events are sampled: at most eventsPerIssueCap recent events per issue per sync (Sentry caps a single events page at 100), so this is a representative sample, not a full audit trail.',\n responses: { issue_events: issueEventResponseSchema },\n },\n sentry_release: {\n shape: 'entity',\n description:\n 'Releases with their versions, associated project slugs, and creation/release/last-event timestamps.',\n endpoint: 'GET /api/0/organizations/{organization}/releases/',\n responses: { releases: releaseResponseSchema },\n },\n sentry_errors_per_hour: {\n shape: 'metric',\n description:\n 'Hourly count of error events, broken down by project, over the configured lookback window.',\n endpoint: 'GET /api/0/organizations/{organization}/stats_v2/',\n unit: 'errors',\n granularity: '1h',\n dimensions: [\n { name: 'project', description: 'Sentry project slug or id.' },\n ],\n responses: { error_stats: errorStatsResponseSchema },\n },\n});\n\nexport const id = 'sentry';\n\nexport class SentryConnector extends BaseConnector<\n SentrySettings,\n SentryCredentials\n> {\n static readonly id = id;\n\n static readonly resources = sentryResources;\n\n static readonly schemas = schemasFromResources(sentryResources);\n\n static create(input: unknown, ctx?: ConnectorContext): SentryConnector {\n const parsed = configFields.parse(input);\n return new SentryConnector(\n {\n organization: parsed.organization,\n projects: parsed.projects,\n resources: parsed.resources,\n eventsPerIssueCap: parsed.eventsPerIssueCap,\n statsLookbackHours: parsed.statsLookbackHours,\n },\n { authToken: parsed.authToken },\n ctx,\n );\n }\n\n readonly id = id;\n override readonly credentials = sentryCredentials;\n\n private buildHeaders(): Record<string, string> {\n return {\n Authorization: `Bearer ${this.creds.authToken}`,\n 'User-Agent': connectorUserAgent('sentry'),\n };\n }\n\n private fetch<T>(\n url: string,\n resource: string,\n signal?: AbortSignal,\n ): Promise<HttpResponse<T>> {\n return this.get<T>(url, {\n resource,\n headers: this.buildHeaders(),\n signal,\n rateLimit: sentryRateLimit,\n });\n }\n\n private activePhases(): SentryPhase[] {\n return selectActivePhases<SentryResource, SentryPhase>(\n (r) => {\n switch (r) {\n case 'issues':\n case 'issue_events':\n return 'issues';\n case 'releases':\n return 'releases';\n case 'errors_per_hour':\n return 'error_stats';\n }\n },\n PHASE_ORDER,\n this.settings.resources,\n );\n }\n\n private allowedPagePath(phase: SentryPhase): string | null {\n const org = this.settings.organization;\n switch (phase) {\n case 'issues':\n return `/api/0/organizations/${org}/issues/`;\n case 'releases':\n return `/api/0/organizations/${org}/releases/`;\n case 'error_stats':\n return null;\n }\n }\n\n private sanitizePageUrl(\n phase: SentryPhase,\n pageUrl: string | null,\n ): string | null {\n const allowedPath = this.allowedPagePath(phase);\n if (allowedPath === null) {\n return null;\n }\n return sanitizeAllowedUrl({\n url: pageUrl,\n host: SENTRY_API_HOST,\n pathname: allowedPath,\n });\n }\n\n private resolveCursor(cursor: unknown): SentrySyncCursor | undefined {\n if (!isSentrySyncCursor(cursor)) {\n return undefined;\n }\n return {\n phase: cursor.phase,\n page: this.sanitizePageUrl(cursor.phase, cursor.page),\n };\n }\n\n private buildInitialIssuesUrl(options: SyncOptions): string {\n const u = new URL(\n `${SENTRY_API_BASE}/organizations/${this.settings.organization}/issues/`,\n );\n u.searchParams.set(\n 'limit',\n String(clampPageSize(options.pageSize, ISSUES_PAGE_SIZE)),\n );\n u.searchParams.set('sort', 'date');\n for (const project of this.settings.projects ?? []) {\n u.searchParams.append('project', project);\n }\n if (options.since) {\n u.searchParams.set('query', `lastSeen:>${options.since}`);\n }\n return u.toString();\n }\n\n private buildInitialReleasesUrl(options: SyncOptions): string {\n const u = new URL(\n `${SENTRY_API_BASE}/organizations/${this.settings.organization}/releases/`,\n );\n u.searchParams.set(\n 'per_page',\n String(clampPageSize(options.pageSize, RELEASES_PAGE_SIZE)),\n );\n for (const project of this.settings.projects ?? []) {\n u.searchParams.append('project', project);\n }\n return u.toString();\n }\n\n private buildStatsUrl(): string {\n const lookback =\n this.settings.statsLookbackHours ?? DEFAULT_STATS_LOOKBACK_HOURS;\n const u = new URL(\n `${SENTRY_API_BASE}/organizations/${this.settings.organization}/stats_v2/`,\n );\n u.searchParams.set('field', 'sum(quantity)');\n u.searchParams.set('category', 'error');\n u.searchParams.set('interval', '1h');\n u.searchParams.set('statsPeriod', `${lookback}h`);\n u.searchParams.append('groupBy', 'project');\n for (const project of this.settings.projects ?? []) {\n u.searchParams.append('project', project);\n }\n return u.toString();\n }\n\n private buildIssueEventsUrl(issueId: string): string {\n const cap = this.settings.eventsPerIssueCap ?? DEFAULT_EVENTS_PER_ISSUE;\n const u = new URL(`${SENTRY_API_BASE}/issues/${issueId}/events/`);\n u.searchParams.set(\n 'limit',\n String(Math.min(cap, DEFAULT_EVENTS_PER_ISSUE)),\n );\n return u.toString();\n }\n\n private async fetchIssuesPage(\n page: string | null,\n options: SyncOptions,\n signal: AbortSignal | undefined,\n ): Promise<{ items: IssuesPageItem[]; next: string | null }> {\n const url = page ?? this.buildInitialIssuesUrl(options);\n const res = await this.fetch<SentryIssue[]>(url, 'issues', signal);\n\n const nextLink = parseSentryLink(res.headers.get('link'), 'next');\n const next =\n nextLink && nextLink.hasResults\n ? this.sanitizePageUrl('issues', nextLink.url)\n : null;\n\n const eventsByIssue = new Map<string, SentryIssueEvent[]>();\n if (this.isResourceEnabled('issue_events')) {\n signal?.throwIfAborted();\n const fetched = await mapWithConcurrency(\n res.body,\n EVENT_FETCH_CONCURRENCY,\n async (issue) => {\n const eventsRes = await this.fetch<SentryIssueEvent[]>(\n this.buildIssueEventsUrl(issue.id),\n 'issue_events',\n signal,\n );\n return [issue.id, eventsRes.body] as const;\n },\n );\n for (const [issueId, events] of fetched) {\n eventsByIssue.set(issueId, events);\n }\n }\n\n return { items: [{ issues: res.body, eventsByIssue }], next };\n }\n\n private async fetchReleasesPage(\n page: string | null,\n options: SyncOptions,\n signal: AbortSignal | undefined,\n ): Promise<{ items: SentryRelease[]; next: string | null }> {\n const url = page ?? this.buildInitialReleasesUrl(options);\n const res = await this.fetch<SentryRelease[]>(url, 'releases', signal);\n const nextLink = parseSentryLink(res.headers.get('link'), 'next');\n const releases = res.body;\n const cutoff = options.since ? new Date(options.since).getTime() : null;\n const filtered =\n cutoff !== null\n ? releases.filter((r) => {\n const ts = new Date(r.dateReleased ?? r.dateCreated).getTime();\n return Number.isFinite(ts) ? ts >= cutoff : true;\n })\n : releases;\n const lastRelease = releases.at(-1);\n const lastTs = lastRelease\n ? new Date(lastRelease.dateReleased ?? lastRelease.dateCreated).getTime()\n : null;\n const cutoffReached =\n cutoff !== null &&\n lastTs !== null &&\n Number.isFinite(lastTs) &&\n lastTs < cutoff;\n const next =\n !cutoffReached && nextLink && nextLink.hasResults\n ? this.sanitizePageUrl('releases', nextLink.url)\n : null;\n return { items: filtered, next };\n }\n\n private async fetchErrorStats(\n signal: AbortSignal | undefined,\n ): Promise<{ items: SentryStatsResponse[]; next: string | null }> {\n const res = await this.fetch<SentryStatsResponse>(\n this.buildStatsUrl(),\n 'error_stats',\n signal,\n );\n return { items: [res.body], next: null };\n }\n\n private async writeIssuesPage(\n storage: StorageHandle,\n item: IssuesPageItem,\n ): Promise<void> {\n const writeEntities = this.isResourceEnabled('issues');\n const writeEvents = this.isResourceEnabled('issue_events');\n\n for (const issue of item.issues) {\n if (writeEntities) {\n const count =\n typeof issue.count === 'string' ? Number(issue.count) : issue.count;\n const firstSeenMs = parseEpoch(issue.firstSeen, 'iso');\n const lastSeenMs = parseEpoch(issue.lastSeen, 'iso');\n if (firstSeenMs === null || lastSeenMs === null) {\n console.warn(\n `[connector-sentry] skipping issue ${issue.id} with unparseable firstSeen/lastSeen`,\n );\n } else {\n await storage.entity({\n type: 'sentry_issue',\n id: issue.id,\n attributes: {\n shortId: issue.shortId,\n title: issue.title,\n level: issue.level,\n status: issue.status,\n firstSeen: firstSeenMs,\n lastSeen: lastSeenMs,\n count: Number.isFinite(count) ? count : 0,\n userCount: issue.userCount,\n projectSlug: issue.project.slug,\n },\n updated_at: lastSeenMs,\n });\n }\n }\n\n if (writeEvents) {\n const events = item.eventsByIssue.get(issue.id) ?? [];\n for (const ev of events) {\n const eventId = ev.eventID ?? ev.id ?? null;\n if (eventId === null) {\n continue;\n }\n const startTs = parseEpoch(ev.dateCreated, 'iso');\n if (startTs === null) {\n continue;\n }\n await storage.event({\n name: 'sentry_issue_event',\n start_ts: startTs,\n end_ts: null,\n attributes: {\n eventId,\n issueId: issue.id,\n issueShortId: issue.shortId,\n projectSlug: issue.project.slug,\n level: issue.level,\n platform: ev.platform ?? null,\n environment: ev.environment ?? null,\n message: ev.message ?? null,\n },\n });\n }\n }\n }\n }\n\n private async writeReleases(\n storage: StorageHandle,\n releases: SentryRelease[],\n ): Promise<void> {\n for (const r of releases) {\n const createdMs = parseEpoch(r.dateCreated, 'iso');\n const releasedMs = parseEpoch(r.dateReleased, 'iso');\n const lastEventMs = parseEpoch(r.lastEvent, 'iso');\n if (createdMs === null) {\n console.warn(\n `[connector-sentry] skipping release ${r.version} with unparseable dateCreated`,\n );\n continue;\n }\n await storage.entity({\n type: 'sentry_release',\n id: r.version,\n attributes: {\n version: r.version,\n projects: r.projects.map((p) => p.slug),\n dateCreated: createdMs,\n dateReleased: releasedMs,\n lastEvent: lastEventMs,\n },\n updated_at: Math.max(createdMs, releasedMs ?? 0, lastEventMs ?? 0),\n });\n }\n }\n\n private async writeErrorStats(\n storage: StorageHandle,\n stats: SentryStatsResponse,\n ): Promise<void> {\n const samples: Array<{\n name: string;\n ts: number;\n value: number;\n attributes: Record<string, string | number>;\n }> = [];\n const intervals = stats.intervals ?? [];\n for (const group of stats.groups) {\n const project = group.by['project'];\n const projectKey = project !== undefined ? String(project) : 'unknown';\n const series = group.series?.['sum(quantity)'] ?? [];\n if (series.length === 0) {\n continue;\n }\n for (let i = 0; i < intervals.length; i++) {\n const intervalIso = intervals[i];\n const rawValue = series[i];\n if (intervalIso === undefined || rawValue === undefined) {\n continue;\n }\n const ts = parseEpoch(intervalIso, 'iso');\n const value = Number(rawValue);\n if (ts === null || !Number.isFinite(value)) {\n continue;\n }\n samples.push({\n name: 'sentry_errors_per_hour',\n ts,\n value,\n attributes: { project: projectKey },\n });\n }\n }\n await storage.metrics(samples, { names: ['sentry_errors_per_hour'] });\n }\n\n async sync(\n options: SyncOptions,\n storage: StorageHandle,\n signal?: AbortSignal,\n ): Promise<SyncResult> {\n const cursor = this.resolveCursor(options.cursor);\n const isFull = options.mode === 'full';\n const phases = this.activePhases();\n\n return paginateChunked<SentryPhase, string>({\n phases,\n cursor,\n signal,\n logger: this.logger,\n pipeline: true,\n maxChunkMs: CHUNK_BUDGET_MS,\n fetchPage: async (phase, page, sig) => {\n switch (phase) {\n case 'issues':\n return this.fetchIssuesPage(page, options, sig);\n case 'releases':\n return this.fetchReleasesPage(page, options, sig);\n case 'error_stats':\n return this.fetchErrorStats(sig);\n }\n },\n writeBatch: async (phase, items, page) => {\n if (isFull && page === null) {\n switch (phase) {\n case 'issues':\n if (this.isResourceEnabled('issues')) {\n await storage.entities([], { types: ['sentry_issue'] });\n }\n if (this.isResourceEnabled('issue_events')) {\n await storage.events([], { names: ['sentry_issue_event'] });\n }\n break;\n case 'releases':\n await storage.entities([], { types: ['sentry_release'] });\n break;\n case 'error_stats':\n break;\n }\n }\n switch (phase) {\n case 'issues':\n for (const item of items as IssuesPageItem[]) {\n await this.writeIssuesPage(storage, item);\n }\n return;\n case 'releases':\n return this.writeReleases(storage, items as SentryRelease[]);\n case 'error_stats':\n for (const stats of items as SentryStatsResponse[]) {\n await this.writeErrorStats(storage, stats);\n }\n return;\n }\n },\n });\n }\n}\n","import { SentryConnector } from './sentry';\n\nexport {\n configFields,\n doc,\n SentryConnector,\n sentryResources as resources,\n id,\n} from './sentry';\nexport type { SentryResource, SentrySettings } from './sentry';\nexport default SentryConnector;\n"],"mappings":";AEAO,IAAM,sBAAsB;AAE5B,IAAM,qBAAqB,qBAAqB,mBAAmB;AAEnE,SAAS,mBAAmB,aAA6B;AAC9D,SAAO,qBAAqB,WAAW,IAAI,mBAAmB;AAChE;AEUO,SAAS,wBACd,QACiB;AACjB,QAAM,EAAE,iBAAiB,aAAa,WAAW,gBAAgB,IAAI;AACrE,QAAM,aAAa,cAAc,MAAM,MAAO;AAC9C,SAAO;IACL,MAAM,GAAG;AACP,YAAM,eAAe,EAAE,IAAI,eAAe;AAC1C,UAAI,iBAAiB,QAAQ,aAAa,KAAK,MAAM,IAAI;AACvD,eAAO;MACT;AACA,YAAM,YAAY,OAAO,YAAY;AACrC,UAAI,CAAC,OAAO,SAAS,SAAS,GAAG;AAC/B,eAAO;MACT;AACA,YAAM,WAAW,EAAE,IAAI,WAAW;AAClC,UAAI,aAAa,MAAM;AACrB,YAAI,oBAAoB,QAAW;AACjC,iBAAO;QACT;AACA,eAAO;UACL;UACA,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,eAAe;QAChD;MACF;AACA,UAAI,SAAS,KAAK,MAAM,IAAI;AAC1B,eAAO;MACT;AACA,YAAM,QAAQ,OAAO,QAAQ;AAC7B,UAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,GAAG;AACxC,eAAO;MACT;AACA,YAAM,UAAU,QAAQ;AACxB,UAAI,CAAC,OAAO,SAAS,OAAO,GAAG;AAC7B,eAAO;MACT;AACA,aAAO,EAAE,WAAW,SAAS,IAAI,KAAK,OAAO,EAAE;IACjD;EACF;AACF;ACvDA,eAAsB,mBACpB,OACA,aACA,IACc;AACd,QAAM,UAAU,IAAI,MAAS,MAAM,MAAM;AACzC,MAAI,MAAM,WAAW,GAAG;AACtB,WAAO;EACT;AACA,QAAM,aAAa,OAAO,SAAS,WAAW,IAAI,KAAK,MAAM,WAAW,IAAI;AAC5E,QAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,YAAY,MAAM,MAAM,CAAC;AAC5D,MAAI,OAAO;AACX,MAAI,SAAS;AAEb,iBAAe,SAAwB;AACrC,WAAO,CAAC,QAAQ;AACd,YAAM,IAAI;AACV,UAAI,KAAK,MAAM,QAAQ;AACrB;MACF;AACA,UAAI;AACF,gBAAQ,CAAC,IAAI,MAAM,GAAG,MAAM,CAAC,GAAI,CAAC;MACpC,SAAS,KAAK;AACZ,iBAAS;AACT,cAAM;MACR;IACF;EACF;AAEA,QAAM,UAA2B,CAAC;AAClC,WAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,YAAQ,KAAK,OAAO,CAAC;EACvB;AACA,QAAM,QAAQ,IAAI,OAAO;AACzB,SAAO;AACT;AC5BO,SAAS,mBACd,SACe;AACf,QAAM,EAAE,KAAK,MAAM,UAAU,WAAW,SAAS,IAAI;AACrD,MAAI,QAAQ,MAAM;AAChB,WAAO;EACT;AACA,MAAI;AACF,UAAM,IAAI,IAAI,IAAI,GAAG;AACrB,QAAI,EAAE,aAAa,YAAY,EAAE,SAAS,QAAQ,EAAE,aAAa,UAAU;AACzE,aAAO;IACT;AACA,WAAO,EAAE,SAAS;EACpB,QAAQ;AACN,WAAO;EACT;AACF;ACrBO,SAAS,WACd,OACA,MACe;AACf,MAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,WAAO;EACT;AACA,MAAI,SAAS,OAAO;AAClB,QAAI,OAAO,UAAU,UAAU;AAC7B,aAAO;IACT;AACA,UAAM,KAAK,IAAI,KAAK,KAAK,EAAE,QAAQ;AACnC,WAAO,OAAO,SAAS,EAAE,IAAI,KAAK;EACpC;AACA,MAAI,OAAO,UAAU,YAAY,MAAM,KAAK,MAAM,IAAI;AACpD,WAAO;EACT;AACA,QAAM,IAAI,OAAO,UAAU,WAAW,QAAQ,OAAO,KAAK;AAC1D,MAAI,CAAC,OAAO,SAAS,CAAC,GAAG;AACvB,WAAO;EACT;AACA,QAAM,SAAS,SAAS,MAAM,IAAI,MAAO;AACzC,SAAO,OAAO,SAAS,MAAM,IAAI,SAAS;AAC5C;;;AGjBA;AAAA,EACE;AAAA,EAQA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,SAAS;AAEX,IAAM,eAAe;AAAA,EAC1B,EAAE,OAAO;AAAA,IACP,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK;AAAA,MAChD,OAAO;AAAA,MACP,aACE;AAAA,MACF,aAAa;AAAA,MACb,QAAQ;AAAA,IACV,CAAC;AAAA,IACD,cAAc,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,KAAK;AAAA,MACnC,OAAO;AAAA,MACP,aAAa;AAAA,MACb,aAAa;AAAA,IACf,CAAC;AAAA,IACD,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK;AAAA,MAC9D,OAAO;AAAA,MACP,aACE;AAAA,IACJ,CAAC;AAAA,IACD,WAAW,EACR,MAAM,EAAE,KAAK,CAAC,UAAU,gBAAgB,YAAY,iBAAiB,CAAC,CAAC,EACvE,SAAS,EACT,SAAS,EACT,KAAK;AAAA,MACJ,OAAO;AAAA,MACP,aACE;AAAA,IACJ,CAAC;AAAA,IACH,mBAAmB,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,GAAG,EAAE,SAAS,EAAE,KAAK;AAAA,MACtE,OAAO;AAAA,MACP,aACE;AAAA,MACF,aAAa;AAAA,IACf,CAAC;AAAA,IACD,oBAAoB,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,GAAG,EAAE,SAAS,EAAE,KAAK;AAAA,MACvE,OAAO;AAAA,MACP,aACE;AAAA,MACF,aAAa;AAAA,IACf,CAAC;AAAA,EACH,CAAC;AACH;AAEO,IAAM,MAAoB,mBAAmB;AAAA,EAClD,aAAa;AAAA,EACb,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,SACE;AAAA,EACF,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,SAAS;AAAA,EACX;AAAA,EACA,MAAM;AAAA,IACJ,SACE;AAAA,IACF,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,WACE;AAAA,EACF,aAAa;AAAA,IACX;AAAA,IACA;AAAA,EACF;AACF,CAAC;AAgBD,IAAM,oBAAoB;AAAA,EACxB,WAAW;AAAA,IACT,aAAa;AAAA,IACb,MAAM;AAAA,EACR;AACF;AAIA,IAAM,kBAAkB,wBAAwB;AAAA,EAC9C,iBAAiB;AAAA,EACjB,aAAa;AAAA,EACb,WAAW;AACb,CAAC;AAED,IAAM,cAAc,CAAC,UAAU,YAAY,aAAa;AAMxD,IAAM,qBAAqB,uBAAuB,WAAW;AA6D7D,SAAS,gBACP,QACA,KACmB;AACnB,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA,EACT;AACA,aAAW,QAAQ,OAAO,MAAM,GAAG,GAAG;AACpC,UAAM,IAAI,KAAK,MAAM,uBAAuB;AAC5C,QAAI,CAAC,GAAG;AACN;AAAA,IACF;AACA,UAAM,MAAM,EAAE,CAAC;AACf,UAAM,QAAQ,EAAE,CAAC;AACjB,UAAM,WAAW,MAAM,MAAM,eAAe;AAC5C,QAAI,CAAC,YAAY,SAAS,CAAC,MAAM,KAAK;AACpC;AAAA,IACF;AACA,UAAM,eAAe,MAAM,MAAM,mBAAmB;AACpD,UAAM,aAAa,eAAe,aAAa,CAAC,MAAM,SAAS;AAC/D,WAAO,EAAE,KAAK,WAAW;AAAA,EAC3B;AACA,SAAO;AACT;AAEA,IAAM,kBAAkB;AACxB,IAAM,kBAAkB,WAAW,eAAe;AAClD,IAAM,2BAA2B;AACjC,IAAM,+BAA+B;AACrC,IAAM,gBAAgB;AACtB,IAAM,mBAAmB;AACzB,IAAM,qBAAqB;AAC3B,IAAM,0BAA0B;AAChC,IAAM,kBAAkB;AAExB,SAAS,cACP,WACA,UACQ;AACR,QAAM,IAAI,aAAa;AACvB,MAAI,CAAC,OAAO,SAAS,CAAC,KAAK,IAAI,GAAG;AAChC,WAAO;AAAA,EACT;AACA,SAAO,KAAK,IAAI,KAAK,MAAM,CAAC,GAAG,aAAa;AAC9C;AAEA,IAAM,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC;AAEjC,IAAM,sBAAsB,EAAE;AAAA,EAC5B,EAAE,OAAO;AAAA,IACP,IAAI;AAAA,IACJ,SAAS,EAAE,OAAO;AAAA,IAClB,OAAO,EAAE,OAAO;AAAA,IAChB,OAAO,EAAE,KAAK,CAAC,SAAS,QAAQ,WAAW,SAAS,OAAO,CAAC;AAAA,IAC5D,QAAQ,EAAE,KAAK,CAAC,YAAY,cAAc,SAAS,CAAC;AAAA,IACpD,WAAW,EAAE,IAAI,SAAS;AAAA,IAC1B,UAAU,EAAE,IAAI,SAAS;AAAA,IACzB,OAAO,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,OAAO,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC;AAAA,IAC1E,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,IACxC,SAAS,EAAE,OAAO;AAAA,MAChB,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,MACtB,IAAI,EAAE,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,EAAE,SAAS;AAAA,MAC7C,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,MAC1B,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC3C,CAAC;AAAA,IACD,aAAa,EAAE,QAAQ,EAAE,SAAS;AAAA,IAClC,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS;AAAA,IAC5C,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACxC,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS;AAAA,IAC1C,SAAS,EAAE,QAAQ,EAAE,SAAS;AAAA,IAC9B,cAAc,EAAE,QAAQ,EAAE,SAAS;AAAA,IACnC,UAAU,EAAE,QAAQ,EAAE,SAAS;AAAA,IAC/B,cAAc,EAAE,QAAQ,EAAE,SAAS;AAAA,IACnC,aAAa,EAAE,QAAQ,EAAE,SAAS;AAAA,IAClC,eAAe,EAAE,OAAO,EAAE,SAAS;AAAA,IACnC,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,IAC/B,UAAU,EAAE,QAAQ,EAAE,SAAS;AAAA,IAC/B,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACvC,UAAU,EAAE,QAAQ,EAAE,SAAS;AAAA,IAC/B,aAAa,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS;AAAA,IACrD,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,IAC/B,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACzC,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA,IAC9B,kBAAkB,EAAE,IAAI,SAAS,EAAE,SAAS,EAAE,SAAS;AAAA,IACvD,0BAA0B,EAAE,IAAI,SAAS,EAAE,SAAS,EAAE,SAAS;AAAA,IAC/D,kCAAkC,EAAE,IAAI,SAAS,EAAE,SAAS,EAAE,SAAS;AAAA,IACvE,qBAAqB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACpD,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACxC,OAAO,EAAE,QAAQ,EAAE,SAAS;AAAA,IAC5B,eAAe,EAAE,QAAQ,EAAE,SAAS;AAAA,IACpC,qBAAqB,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS;AAAA,IACrD,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC1C,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,EAC5B,CAAC;AACH;AAEA,IAAM,2BAA2B,EAAE;AAAA,EACjC,EAAE,OAAO;AAAA,IACP,IAAI,EAAE,OAAO,EAAE,SAAS;AAAA,IACxB,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,IAC7B,aAAa,EAAE,IAAI,SAAS;AAAA,IAC5B,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACxC,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACzC,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,IAC7B,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC5C,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS;AAAA,IAC3C,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,IAC7B,cAAc,EAAE,OAAO,EAAE,SAAS;AAAA,IAClC,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA,IAC9B,UAAU,EAAE,QAAQ,EAAE,SAAS;AAAA,IAC/B,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,IAC/B,MAAM,EAAE,QAAQ,EAAE,SAAS;AAAA,IAC3B,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA,IAC3B,MAAM,EAAE,QAAQ,EAAE,SAAS;AAAA,EAC7B,CAAC;AACH;AAEA,IAAM,wBAAwB,EAAE;AAAA,EAC9B,EAAE,OAAO;AAAA,IACP,SAAS;AAAA,IACT,aAAa,EAAE,IAAI,SAAS;AAAA,IAC5B,cAAc,EAAE,IAAI,SAAS,EAAE,SAAS,EAAE,SAAS;AAAA,IACnD,WAAW,EAAE,IAAI,SAAS,EAAE,SAAS,EAAE,SAAS;AAAA,IAChD,UAAU,EAAE;AAAA,MACV,EAAE,OAAO;AAAA,QACP,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,QACtB,eAAe,EAAE,QAAQ,EAAE,SAAS;AAAA,QACpC,IAAI,EAAE,QAAQ,EAAE,SAAS;AAAA,QACzB,MAAM,EAAE,QAAQ,EAAE,SAAS;AAAA,QAC3B,WAAW,EAAE,QAAQ,EAAE,SAAS;AAAA,QAChC,UAAU,EAAE,QAAQ,EAAE,SAAS;AAAA,QAC/B,WAAW,EAAE,QAAQ,EAAE,SAAS;AAAA,MAClC,CAAC;AAAA,IACH;AAAA,IACA,SAAS,EAAE,QAAQ,EAAE,SAAS;AAAA,IAC9B,aAAa,EAAE,QAAQ,EAAE,SAAS;AAAA,IAClC,oBAAoB,EAAE,QAAQ,EAAE,SAAS;AAAA,IACzC,MAAM,EAAE,QAAQ,EAAE,SAAS;AAAA,IAC3B,aAAa,EAAE,QAAQ,EAAE,SAAS;AAAA,IAClC,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS;AAAA,IAC5C,IAAI,EAAE,QAAQ,EAAE,SAAS;AAAA,IACzB,YAAY,EAAE,QAAQ,EAAE,SAAS;AAAA,IACjC,YAAY,EAAE,QAAQ,EAAE,SAAS;AAAA,IACjC,WAAW,EAAE,QAAQ,EAAE,SAAS;AAAA,IAChC,OAAO,EAAE,QAAQ,EAAE,SAAS;AAAA,IAC5B,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS;AAAA,IACrC,cAAc,EAAE,QAAQ,EAAE,SAAS;AAAA,IACnC,QAAQ,EAAE,QAAQ,EAAE,SAAS;AAAA,IAC7B,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS;AAAA,IACrC,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS;AAAA,IAC3C,aAAa,EAAE,QAAQ,EAAE,SAAS;AAAA,EACpC,CAAC;AACH;AAEA,IAAM,2BAA2B,EAAE,OAAO;AAAA,EACxC,WAAW,EAAE,MAAM,EAAE,IAAI,SAAS,CAAC,EAAE,SAAS;AAAA,EAC9C,QAAQ,EAAE;AAAA,IACR,EAAE,OAAO;AAAA,MACP,IAAI,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;AAAA,MAC1D,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,MAClD,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,SAAS;AAAA,IAC7D,CAAC;AAAA,EACH;AAAA,EACA,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA,EAC3B,KAAK,EAAE,OAAO,EAAE,SAAS;AAC3B,CAAC;AAEM,IAAM,kBAAkB,gBAAgB;AAAA,EAC7C,cAAc;AAAA,IACZ,OAAO;AAAA,IACP,aACE;AAAA,IACF,UAAU;AAAA,IACV,WAAW,EAAE,QAAQ,oBAAoB;AAAA,EAC3C;AAAA,EACA,oBAAoB;AAAA,IAClB,OAAO;AAAA,IACP,aACE;AAAA,IACF,UAAU;AAAA,IACV,OACE;AAAA,IACF,WAAW,EAAE,cAAc,yBAAyB;AAAA,EACtD;AAAA,EACA,gBAAgB;AAAA,IACd,OAAO;AAAA,IACP,aACE;AAAA,IACF,UAAU;AAAA,IACV,WAAW,EAAE,UAAU,sBAAsB;AAAA,EAC/C;AAAA,EACA,wBAAwB;AAAA,IACtB,OAAO;AAAA,IACP,aACE;AAAA,IACF,UAAU;AAAA,IACV,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,MACV,EAAE,MAAM,WAAW,aAAa,6BAA6B;AAAA,IAC/D;AAAA,IACA,WAAW,EAAE,aAAa,yBAAyB;AAAA,EACrD;AACF,CAAC;AAEM,IAAM,KAAK;AAEX,IAAM,kBAAN,MAAM,yBAAwB,cAGnC;AAAA,EACA,OAAgB,KAAK;AAAA,EAErB,OAAgB,YAAY;AAAA,EAE5B,OAAgB,UAAU,qBAAqB,eAAe;AAAA,EAE9D,OAAO,OAAO,OAAgB,KAAyC;AACrE,UAAM,SAAS,aAAa,MAAM,KAAK;AACvC,WAAO,IAAI;AAAA,MACT;AAAA,QACE,cAAc,OAAO;AAAA,QACrB,UAAU,OAAO;AAAA,QACjB,WAAW,OAAO;AAAA,QAClB,mBAAmB,OAAO;AAAA,QAC1B,oBAAoB,OAAO;AAAA,MAC7B;AAAA,MACA,EAAE,WAAW,OAAO,UAAU;AAAA,MAC9B;AAAA,IACF;AAAA,EACF;AAAA,EAES,KAAK;AAAA,EACI,cAAc;AAAA,EAExB,eAAuC;AAC7C,WAAO;AAAA,MACL,eAAe,UAAU,KAAK,MAAM,SAAS;AAAA,MAC7C,cAAc,mBAAmB,QAAQ;AAAA,IAC3C;AAAA,EACF;AAAA,EAEQ,MACN,KACA,UACA,QAC0B;AAC1B,WAAO,KAAK,IAAO,KAAK;AAAA,MACtB;AAAA,MACA,SAAS,KAAK,aAAa;AAAA,MAC3B;AAAA,MACA,WAAW;AAAA,IACb,CAAC;AAAA,EACH;AAAA,EAEQ,eAA8B;AACpC,WAAO;AAAA,MACL,CAAC,MAAM;AACL,gBAAQ,GAAG;AAAA,UACT,KAAK;AAAA,UACL,KAAK;AACH,mBAAO;AAAA,UACT,KAAK;AACH,mBAAO;AAAA,UACT,KAAK;AACH,mBAAO;AAAA,QACX;AAAA,MACF;AAAA,MACA;AAAA,MACA,KAAK,SAAS;AAAA,IAChB;AAAA,EACF;AAAA,EAEQ,gBAAgB,OAAmC;AACzD,UAAM,MAAM,KAAK,SAAS;AAC1B,YAAQ,OAAO;AAAA,MACb,KAAK;AACH,eAAO,wBAAwB,GAAG;AAAA,MACpC,KAAK;AACH,eAAO,wBAAwB,GAAG;AAAA,MACpC,KAAK;AACH,eAAO;AAAA,IACX;AAAA,EACF;AAAA,EAEQ,gBACN,OACA,SACe;AACf,UAAM,cAAc,KAAK,gBAAgB,KAAK;AAC9C,QAAI,gBAAgB,MAAM;AACxB,aAAO;AAAA,IACT;AACA,WAAO,mBAAmB;AAAA,MACxB,KAAK;AAAA,MACL,MAAM;AAAA,MACN,UAAU;AAAA,IACZ,CAAC;AAAA,EACH;AAAA,EAEQ,cAAc,QAA+C;AACnE,QAAI,CAAC,mBAAmB,MAAM,GAAG;AAC/B,aAAO;AAAA,IACT;AACA,WAAO;AAAA,MACL,OAAO,OAAO;AAAA,MACd,MAAM,KAAK,gBAAgB,OAAO,OAAO,OAAO,IAAI;AAAA,IACtD;AAAA,EACF;AAAA,EAEQ,sBAAsB,SAA8B;AAC1D,UAAM,IAAI,IAAI;AAAA,MACZ,GAAG,eAAe,kBAAkB,KAAK,SAAS,YAAY;AAAA,IAChE;AACA,MAAE,aAAa;AAAA,MACb;AAAA,MACA,OAAO,cAAc,QAAQ,UAAU,gBAAgB,CAAC;AAAA,IAC1D;AACA,MAAE,aAAa,IAAI,QAAQ,MAAM;AACjC,eAAW,WAAW,KAAK,SAAS,YAAY,CAAC,GAAG;AAClD,QAAE,aAAa,OAAO,WAAW,OAAO;AAAA,IAC1C;AACA,QAAI,QAAQ,OAAO;AACjB,QAAE,aAAa,IAAI,SAAS,aAAa,QAAQ,KAAK,EAAE;AAAA,IAC1D;AACA,WAAO,EAAE,SAAS;AAAA,EACpB;AAAA,EAEQ,wBAAwB,SAA8B;AAC5D,UAAM,IAAI,IAAI;AAAA,MACZ,GAAG,eAAe,kBAAkB,KAAK,SAAS,YAAY;AAAA,IAChE;AACA,MAAE,aAAa;AAAA,MACb;AAAA,MACA,OAAO,cAAc,QAAQ,UAAU,kBAAkB,CAAC;AAAA,IAC5D;AACA,eAAW,WAAW,KAAK,SAAS,YAAY,CAAC,GAAG;AAClD,QAAE,aAAa,OAAO,WAAW,OAAO;AAAA,IAC1C;AACA,WAAO,EAAE,SAAS;AAAA,EACpB;AAAA,EAEQ,gBAAwB;AAC9B,UAAM,WACJ,KAAK,SAAS,sBAAsB;AACtC,UAAM,IAAI,IAAI;AAAA,MACZ,GAAG,eAAe,kBAAkB,KAAK,SAAS,YAAY;AAAA,IAChE;AACA,MAAE,aAAa,IAAI,SAAS,eAAe;AAC3C,MAAE,aAAa,IAAI,YAAY,OAAO;AACtC,MAAE,aAAa,IAAI,YAAY,IAAI;AACnC,MAAE,aAAa,IAAI,eAAe,GAAG,QAAQ,GAAG;AAChD,MAAE,aAAa,OAAO,WAAW,SAAS;AAC1C,eAAW,WAAW,KAAK,SAAS,YAAY,CAAC,GAAG;AAClD,QAAE,aAAa,OAAO,WAAW,OAAO;AAAA,IAC1C;AACA,WAAO,EAAE,SAAS;AAAA,EACpB;AAAA,EAEQ,oBAAoB,SAAyB;AACnD,UAAM,MAAM,KAAK,SAAS,qBAAqB;AAC/C,UAAM,IAAI,IAAI,IAAI,GAAG,eAAe,WAAW,OAAO,UAAU;AAChE,MAAE,aAAa;AAAA,MACb;AAAA,MACA,OAAO,KAAK,IAAI,KAAK,wBAAwB,CAAC;AAAA,IAChD;AACA,WAAO,EAAE,SAAS;AAAA,EACpB;AAAA,EAEA,MAAc,gBACZ,MACA,SACA,QAC2D;AAC3D,UAAM,MAAM,QAAQ,KAAK,sBAAsB,OAAO;AACtD,UAAM,MAAM,MAAM,KAAK,MAAqB,KAAK,UAAU,MAAM;AAEjE,UAAM,WAAW,gBAAgB,IAAI,QAAQ,IAAI,MAAM,GAAG,MAAM;AAChE,UAAM,OACJ,YAAY,SAAS,aACjB,KAAK,gBAAgB,UAAU,SAAS,GAAG,IAC3C;AAEN,UAAM,gBAAgB,oBAAI,IAAgC;AAC1D,QAAI,KAAK,kBAAkB,cAAc,GAAG;AAC1C,cAAQ,eAAe;AACvB,YAAM,UAAU,MAAM;AAAA,QACpB,IAAI;AAAA,QACJ;AAAA,QACA,OAAO,UAAU;AACf,gBAAM,YAAY,MAAM,KAAK;AAAA,YAC3B,KAAK,oBAAoB,MAAM,EAAE;AAAA,YACjC;AAAA,YACA;AAAA,UACF;AACA,iBAAO,CAAC,MAAM,IAAI,UAAU,IAAI;AAAA,QAClC;AAAA,MACF;AACA,iBAAW,CAAC,SAAS,MAAM,KAAK,SAAS;AACvC,sBAAc,IAAI,SAAS,MAAM;AAAA,MACnC;AAAA,IACF;AAEA,WAAO,EAAE,OAAO,CAAC,EAAE,QAAQ,IAAI,MAAM,cAAc,CAAC,GAAG,KAAK;AAAA,EAC9D;AAAA,EAEA,MAAc,kBACZ,MACA,SACA,QAC0D;AAC1D,UAAM,MAAM,QAAQ,KAAK,wBAAwB,OAAO;AACxD,UAAM,MAAM,MAAM,KAAK,MAAuB,KAAK,YAAY,MAAM;AACrE,UAAM,WAAW,gBAAgB,IAAI,QAAQ,IAAI,MAAM,GAAG,MAAM;AAChE,UAAM,WAAW,IAAI;AACrB,UAAM,SAAS,QAAQ,QAAQ,IAAI,KAAK,QAAQ,KAAK,EAAE,QAAQ,IAAI;AACnE,UAAM,WACJ,WAAW,OACP,SAAS,OAAO,CAAC,MAAM;AACrB,YAAM,KAAK,IAAI,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,QAAQ;AAC7D,aAAO,OAAO,SAAS,EAAE,IAAI,MAAM,SAAS;AAAA,IAC9C,CAAC,IACD;AACN,UAAM,cAAc,SAAS,GAAG,EAAE;AAClC,UAAM,SAAS,cACX,IAAI,KAAK,YAAY,gBAAgB,YAAY,WAAW,EAAE,QAAQ,IACtE;AACJ,UAAM,gBACJ,WAAW,QACX,WAAW,QACX,OAAO,SAAS,MAAM,KACtB,SAAS;AACX,UAAM,OACJ,CAAC,iBAAiB,YAAY,SAAS,aACnC,KAAK,gBAAgB,YAAY,SAAS,GAAG,IAC7C;AACN,WAAO,EAAE,OAAO,UAAU,KAAK;AAAA,EACjC;AAAA,EAEA,MAAc,gBACZ,QACgE;AAChE,UAAM,MAAM,MAAM,KAAK;AAAA,MACrB,KAAK,cAAc;AAAA,MACnB;AAAA,MACA;AAAA,IACF;AACA,WAAO,EAAE,OAAO,CAAC,IAAI,IAAI,GAAG,MAAM,KAAK;AAAA,EACzC;AAAA,EAEA,MAAc,gBACZ,SACA,MACe;AACf,UAAM,gBAAgB,KAAK,kBAAkB,QAAQ;AACrD,UAAM,cAAc,KAAK,kBAAkB,cAAc;AAEzD,eAAW,SAAS,KAAK,QAAQ;AAC/B,UAAI,eAAe;AACjB,cAAM,QACJ,OAAO,MAAM,UAAU,WAAW,OAAO,MAAM,KAAK,IAAI,MAAM;AAChE,cAAM,cAAc,WAAW,MAAM,WAAW,KAAK;AACrD,cAAM,aAAa,WAAW,MAAM,UAAU,KAAK;AACnD,YAAI,gBAAgB,QAAQ,eAAe,MAAM;AAC/C,kBAAQ;AAAA,YACN,qCAAqC,MAAM,EAAE;AAAA,UAC/C;AAAA,QACF,OAAO;AACL,gBAAM,QAAQ,OAAO;AAAA,YACnB,MAAM;AAAA,YACN,IAAI,MAAM;AAAA,YACV,YAAY;AAAA,cACV,SAAS,MAAM;AAAA,cACf,OAAO,MAAM;AAAA,cACb,OAAO,MAAM;AAAA,cACb,QAAQ,MAAM;AAAA,cACd,WAAW;AAAA,cACX,UAAU;AAAA,cACV,OAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAAA,cACxC,WAAW,MAAM;AAAA,cACjB,aAAa,MAAM,QAAQ;AAAA,YAC7B;AAAA,YACA,YAAY;AAAA,UACd,CAAC;AAAA,QACH;AAAA,MACF;AAEA,UAAI,aAAa;AACf,cAAM,SAAS,KAAK,cAAc,IAAI,MAAM,EAAE,KAAK,CAAC;AACpD,mBAAW,MAAM,QAAQ;AACvB,gBAAM,UAAU,GAAG,WAAW,GAAG,MAAM;AACvC,cAAI,YAAY,MAAM;AACpB;AAAA,UACF;AACA,gBAAM,UAAU,WAAW,GAAG,aAAa,KAAK;AAChD,cAAI,YAAY,MAAM;AACpB;AAAA,UACF;AACA,gBAAM,QAAQ,MAAM;AAAA,YAClB,MAAM;AAAA,YACN,UAAU;AAAA,YACV,QAAQ;AAAA,YACR,YAAY;AAAA,cACV;AAAA,cACA,SAAS,MAAM;AAAA,cACf,cAAc,MAAM;AAAA,cACpB,aAAa,MAAM,QAAQ;AAAA,cAC3B,OAAO,MAAM;AAAA,cACb,UAAU,GAAG,YAAY;AAAA,cACzB,aAAa,GAAG,eAAe;AAAA,cAC/B,SAAS,GAAG,WAAW;AAAA,YACzB;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,cACZ,SACA,UACe;AACf,eAAW,KAAK,UAAU;AACxB,YAAM,YAAY,WAAW,EAAE,aAAa,KAAK;AACjD,YAAM,aAAa,WAAW,EAAE,cAAc,KAAK;AACnD,YAAM,cAAc,WAAW,EAAE,WAAW,KAAK;AACjD,UAAI,cAAc,MAAM;AACtB,gBAAQ;AAAA,UACN,uCAAuC,EAAE,OAAO;AAAA,QAClD;AACA;AAAA,MACF;AACA,YAAM,QAAQ,OAAO;AAAA,QACnB,MAAM;AAAA,QACN,IAAI,EAAE;AAAA,QACN,YAAY;AAAA,UACV,SAAS,EAAE;AAAA,UACX,UAAU,EAAE,SAAS,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,UACtC,aAAa;AAAA,UACb,cAAc;AAAA,UACd,WAAW;AAAA,QACb;AAAA,QACA,YAAY,KAAK,IAAI,WAAW,cAAc,GAAG,eAAe,CAAC;AAAA,MACnE,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAc,gBACZ,SACA,OACe;AACf,UAAM,UAKD,CAAC;AACN,UAAM,YAAY,MAAM,aAAa,CAAC;AACtC,eAAW,SAAS,MAAM,QAAQ;AAChC,YAAM,UAAU,MAAM,GAAG,SAAS;AAClC,YAAM,aAAa,YAAY,SAAY,OAAO,OAAO,IAAI;AAC7D,YAAM,SAAS,MAAM,SAAS,eAAe,KAAK,CAAC;AACnD,UAAI,OAAO,WAAW,GAAG;AACvB;AAAA,MACF;AACA,eAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AACzC,cAAM,cAAc,UAAU,CAAC;AAC/B,cAAM,WAAW,OAAO,CAAC;AACzB,YAAI,gBAAgB,UAAa,aAAa,QAAW;AACvD;AAAA,QACF;AACA,cAAM,KAAK,WAAW,aAAa,KAAK;AACxC,cAAM,QAAQ,OAAO,QAAQ;AAC7B,YAAI,OAAO,QAAQ,CAAC,OAAO,SAAS,KAAK,GAAG;AAC1C;AAAA,QACF;AACA,gBAAQ,KAAK;AAAA,UACX,MAAM;AAAA,UACN;AAAA,UACA;AAAA,UACA,YAAY,EAAE,SAAS,WAAW;AAAA,QACpC,CAAC;AAAA,MACH;AAAA,IACF;AACA,UAAM,QAAQ,QAAQ,SAAS,EAAE,OAAO,CAAC,wBAAwB,EAAE,CAAC;AAAA,EACtE;AAAA,EAEA,MAAM,KACJ,SACA,SACA,QACqB;AACrB,UAAM,SAAS,KAAK,cAAc,QAAQ,MAAM;AAChD,UAAM,SAAS,QAAQ,SAAS;AAChC,UAAM,SAAS,KAAK,aAAa;AAEjC,WAAO,gBAAqC;AAAA,MAC1C;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ,KAAK;AAAA,MACb,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,WAAW,OAAO,OAAO,MAAM,QAAQ;AACrC,gBAAQ,OAAO;AAAA,UACb,KAAK;AACH,mBAAO,KAAK,gBAAgB,MAAM,SAAS,GAAG;AAAA,UAChD,KAAK;AACH,mBAAO,KAAK,kBAAkB,MAAM,SAAS,GAAG;AAAA,UAClD,KAAK;AACH,mBAAO,KAAK,gBAAgB,GAAG;AAAA,QACnC;AAAA,MACF;AAAA,MACA,YAAY,OAAO,OAAO,OAAO,SAAS;AACxC,YAAI,UAAU,SAAS,MAAM;AAC3B,kBAAQ,OAAO;AAAA,YACb,KAAK;AACH,kBAAI,KAAK,kBAAkB,QAAQ,GAAG;AACpC,sBAAM,QAAQ,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,cAAc,EAAE,CAAC;AAAA,cACxD;AACA,kBAAI,KAAK,kBAAkB,cAAc,GAAG;AAC1C,sBAAM,QAAQ,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,oBAAoB,EAAE,CAAC;AAAA,cAC5D;AACA;AAAA,YACF,KAAK;AACH,oBAAM,QAAQ,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,gBAAgB,EAAE,CAAC;AACxD;AAAA,YACF,KAAK;AACH;AAAA,UACJ;AAAA,QACF;AACA,gBAAQ,OAAO;AAAA,UACb,KAAK;AACH,uBAAW,QAAQ,OAA2B;AAC5C,oBAAM,KAAK,gBAAgB,SAAS,IAAI;AAAA,YAC1C;AACA;AAAA,UACF,KAAK;AACH,mBAAO,KAAK,cAAc,SAAS,KAAwB;AAAA,UAC7D,KAAK;AACH,uBAAW,SAAS,OAAgC;AAClD,oBAAM,KAAK,gBAAgB,SAAS,KAAK;AAAA,YAC3C;AACA;AAAA,QACJ;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;ACl0BA,IAAO,gBAAQ;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rawdash/connector-sentry",
3
- "version": "0.21.1",
3
+ "version": "0.22.0",
4
4
  "description": "Rawdash connector for Sentry — issues, issue events, releases, and error-rate metrics",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "zod": "^4.4.3",
27
- "@rawdash/core": "0.21.1"
27
+ "@rawdash/core": "0.22.0"
28
28
  },
29
29
  "devDependencies": {
30
30
  "fast-check": "^4.8.0",
@@ -32,7 +32,7 @@
32
32
  "typescript": "^5.7.2",
33
33
  "vitest": "^4.1.4",
34
34
  "@rawdash/connector-shared": "0.3.1",
35
- "@rawdash/connector-test-utils": "0.0.9"
35
+ "@rawdash/connector-test-utils": "0.0.10"
36
36
  },
37
37
  "scripts": {
38
38
  "build": "tsup",