@debugbundle/shared-types 0.1.8 → 0.1.9

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.
@@ -0,0 +1,781 @@
1
+ import { z } from "zod";
2
+ const CAPTURE_RULE_EVENT_TYPES = [
3
+ "backend_exception",
4
+ "request_event",
5
+ "log_event",
6
+ "frontend_breadcrumb",
7
+ "frontend_exception",
8
+ "deploy_metadata",
9
+ "error_suppressed",
10
+ "probe_event",
11
+ ];
12
+ const CAPTURE_RULE_RUNTIME_VALUES = [
13
+ "browser",
14
+ "node",
15
+ "python",
16
+ "php",
17
+ "java",
18
+ "go",
19
+ "ruby",
20
+ "unknown",
21
+ ];
22
+ export const CaptureRuleActionValues = ["demote", "sample", "drop"];
23
+ export const CaptureRuleActionSchema = z.enum(CaptureRuleActionValues);
24
+ export const CaptureRuleSampleEventClassValues = ["preserve", "context"];
25
+ export const CaptureRuleSampleEventClassSchema = z.enum(CaptureRuleSampleEventClassValues);
26
+ export const CaptureRuleRuntimeSchema = z.enum(CAPTURE_RULE_RUNTIME_VALUES);
27
+ export const CaptureRuleEventTypeSchema = z.enum(CAPTURE_RULE_EVENT_TYPES);
28
+ export const BrowserEventKindSchema = z.enum(["window_error", "resource_error"]);
29
+ function normalizeOptionalTrimmedString(value) {
30
+ const trimmed = value?.trim();
31
+ return trimmed && trimmed.length > 0 ? trimmed : undefined;
32
+ }
33
+ function normalizeOptionalLowercaseHost(value) {
34
+ const trimmed = normalizeOptionalTrimmedString(value);
35
+ return trimmed?.toLowerCase();
36
+ }
37
+ function normalizeOptionalPath(value) {
38
+ const trimmed = normalizeOptionalTrimmedString(value);
39
+ if (trimmed === undefined) {
40
+ return undefined;
41
+ }
42
+ return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
43
+ }
44
+ function hasValue(value) {
45
+ return value !== undefined && value !== null;
46
+ }
47
+ const UrlMatcherSchema = z
48
+ .object({
49
+ host: z.string().min(1).max(255).optional(),
50
+ host_suffix: z.string().min(1).max(255).optional(),
51
+ path_prefix: z.string().min(1).max(1024).optional(),
52
+ path_equals: z.string().min(1).max(1024).optional(),
53
+ })
54
+ .transform((value) => {
55
+ const normalized = {};
56
+ const host = normalizeOptionalLowercaseHost(value.host);
57
+ const hostSuffix = normalizeOptionalLowercaseHost(value.host_suffix);
58
+ const pathPrefix = normalizeOptionalPath(value.path_prefix);
59
+ const pathEquals = normalizeOptionalPath(value.path_equals);
60
+ if (host !== undefined) {
61
+ normalized.host = host;
62
+ }
63
+ if (hostSuffix !== undefined) {
64
+ normalized.host_suffix = hostSuffix;
65
+ }
66
+ if (pathPrefix !== undefined) {
67
+ normalized.path_prefix = pathPrefix;
68
+ }
69
+ if (pathEquals !== undefined) {
70
+ normalized.path_equals = pathEquals;
71
+ }
72
+ return normalized;
73
+ })
74
+ .refine((value) => hasValue(value.host) || hasValue(value.host_suffix) || hasValue(value.path_prefix) || hasValue(value.path_equals), {
75
+ message: "URL matchers must include at least one host or path constraint.",
76
+ });
77
+ const StatusRangeSchema = z
78
+ .object({
79
+ start: z.number().int().min(100).max(599),
80
+ end: z.number().int().min(100).max(599),
81
+ })
82
+ .refine((value) => value.start <= value.end, {
83
+ message: "Status range start must be less than or equal to end.",
84
+ });
85
+ const CaptureRuleFingerprintSchema = z.object({
86
+ version: z.string().min(1).max(32),
87
+ value: z.string().min(1).max(256),
88
+ });
89
+ function normalizeStringArray(values) {
90
+ if (values === undefined) {
91
+ return undefined;
92
+ }
93
+ return Array.from(new Set(values.map((value) => value.trim()).filter((value) => value.length > 0)));
94
+ }
95
+ function normalizeNumberArray(values) {
96
+ if (values === undefined) {
97
+ return undefined;
98
+ }
99
+ return Array.from(new Set(values)).sort((left, right) => left - right);
100
+ }
101
+ export const CaptureRuleMatcherSchema = z
102
+ .object({
103
+ event_types: z.array(CaptureRuleEventTypeSchema).min(1).optional(),
104
+ services: z.array(z.string().min(1).max(120)).min(1).optional(),
105
+ environments: z.array(z.string().min(1).max(120)).min(1).optional(),
106
+ runtime: z.array(CaptureRuleRuntimeSchema).min(1).optional(),
107
+ first_party: z.boolean().optional(),
108
+ error_name: z.string().min(1).max(120).optional(),
109
+ message_contains: z.string().min(1).max(500).optional(),
110
+ message_equals: z.string().min(1).max(500).optional(),
111
+ browser_event_kind: BrowserEventKindSchema.optional(),
112
+ resource_url: UrlMatcherSchema.optional(),
113
+ request_url: UrlMatcherSchema.optional(),
114
+ status_codes: z.array(z.number().int().min(100).max(599)).min(1).optional(),
115
+ status_ranges: z.array(StatusRangeSchema).min(1).optional(),
116
+ fingerprint: CaptureRuleFingerprintSchema.optional(),
117
+ })
118
+ .transform((value) => {
119
+ const normalized = {};
120
+ const eventTypes = normalizeStringArray(value.event_types);
121
+ const services = normalizeStringArray(value.services);
122
+ const environments = normalizeStringArray(value.environments);
123
+ const runtime = normalizeStringArray(value.runtime);
124
+ const errorName = normalizeOptionalTrimmedString(value.error_name);
125
+ const messageContains = normalizeOptionalTrimmedString(value.message_contains);
126
+ const messageEquals = normalizeOptionalTrimmedString(value.message_equals);
127
+ const statusCodes = normalizeNumberArray(value.status_codes);
128
+ if (eventTypes !== undefined) {
129
+ normalized.event_types = eventTypes;
130
+ }
131
+ if (services !== undefined) {
132
+ normalized.services = services;
133
+ }
134
+ if (environments !== undefined) {
135
+ normalized.environments = environments;
136
+ }
137
+ if (runtime !== undefined) {
138
+ normalized.runtime = runtime;
139
+ }
140
+ if (value.first_party !== undefined) {
141
+ normalized.first_party = value.first_party;
142
+ }
143
+ if (errorName !== undefined) {
144
+ normalized.error_name = errorName;
145
+ }
146
+ if (messageContains !== undefined) {
147
+ normalized.message_contains = messageContains;
148
+ }
149
+ if (messageEquals !== undefined) {
150
+ normalized.message_equals = messageEquals;
151
+ }
152
+ if (value.browser_event_kind !== undefined) {
153
+ normalized.browser_event_kind = value.browser_event_kind;
154
+ }
155
+ if (value.resource_url !== undefined) {
156
+ normalized.resource_url = value.resource_url;
157
+ }
158
+ if (value.request_url !== undefined) {
159
+ normalized.request_url = value.request_url;
160
+ }
161
+ if (statusCodes !== undefined) {
162
+ normalized.status_codes = statusCodes;
163
+ }
164
+ if (value.status_ranges !== undefined) {
165
+ normalized.status_ranges = value.status_ranges;
166
+ }
167
+ if (value.fingerprint !== undefined) {
168
+ normalized.fingerprint = value.fingerprint;
169
+ }
170
+ return normalized;
171
+ })
172
+ .superRefine((value, context) => {
173
+ const narrowingKeys = [
174
+ "services",
175
+ "environments",
176
+ "runtime",
177
+ "first_party",
178
+ "error_name",
179
+ "message_contains",
180
+ "message_equals",
181
+ "browser_event_kind",
182
+ "resource_url",
183
+ "request_url",
184
+ "status_codes",
185
+ "status_ranges",
186
+ "fingerprint",
187
+ ];
188
+ if (!narrowingKeys.some((key) => hasValue(value[key]))) {
189
+ context.addIssue({
190
+ code: z.ZodIssueCode.custom,
191
+ message: "Capture rules must include at least one narrowing field beyond event_types.",
192
+ });
193
+ }
194
+ if (value.browser_event_kind === "resource_error") {
195
+ const hasResourceConstraint = hasValue(value.resource_url) || hasValue(value.fingerprint);
196
+ if (!hasResourceConstraint) {
197
+ context.addIssue({
198
+ code: z.ZodIssueCode.custom,
199
+ message: "Resource-error rules require a resource URL constraint or an exact fingerprint.",
200
+ });
201
+ }
202
+ }
203
+ });
204
+ const CaptureRuleCoreObjectSchema = z
205
+ .object({
206
+ name: z.string().trim().min(1).max(120),
207
+ description: z.string().trim().max(500).nullable(),
208
+ enabled: z.boolean(),
209
+ action: CaptureRuleActionSchema,
210
+ matcher: CaptureRuleMatcherSchema,
211
+ sample_rate: z.number().min(0).max(1).nullable(),
212
+ sample_event_class: CaptureRuleSampleEventClassSchema.nullable(),
213
+ created_by_user_id: z.string().min(1).max(120).nullable(),
214
+ created_from_incident_id: z.string().min(1).max(120).nullable(),
215
+ created_from_event_id: z.string().min(1).max(120).nullable(),
216
+ expires_at: z.string().datetime().nullable(),
217
+ });
218
+ function addCaptureRuleActionValidation(schema) {
219
+ return schema.superRefine((value, context) => {
220
+ if (value["action"] === "sample") {
221
+ if (value["sample_rate"] === null) {
222
+ context.addIssue({
223
+ code: z.ZodIssueCode.custom,
224
+ path: ["sample_rate"],
225
+ message: "Sample rules require sample_rate.",
226
+ });
227
+ }
228
+ if (value["sample_event_class"] === null) {
229
+ context.addIssue({
230
+ code: z.ZodIssueCode.custom,
231
+ path: ["sample_event_class"],
232
+ message: "Sample rules require sample_event_class.",
233
+ });
234
+ }
235
+ return;
236
+ }
237
+ if (value["sample_rate"] !== null) {
238
+ context.addIssue({
239
+ code: z.ZodIssueCode.custom,
240
+ path: ["sample_rate"],
241
+ message: "Only sample rules can set sample_rate.",
242
+ });
243
+ }
244
+ if (value["sample_event_class"] !== null) {
245
+ context.addIssue({
246
+ code: z.ZodIssueCode.custom,
247
+ path: ["sample_event_class"],
248
+ message: "Only sample rules can set sample_event_class.",
249
+ });
250
+ }
251
+ });
252
+ }
253
+ export const CaptureRuleSchema = addCaptureRuleActionValidation(CaptureRuleCoreObjectSchema.extend({
254
+ id: z.string().uuid(),
255
+ project_id: z.string().min(1).max(120),
256
+ hit_count: z.number().int().nonnegative(),
257
+ last_matched_at: z.string().datetime().nullable(),
258
+ created_at: z.string().datetime(),
259
+ updated_at: z.string().datetime(),
260
+ }));
261
+ export const CaptureRuleCreateSchema = z
262
+ .object({
263
+ name: z.string().trim().min(1).max(120),
264
+ description: z.string().trim().max(500).nullable().default(null),
265
+ enabled: z.boolean().default(true),
266
+ action: CaptureRuleActionSchema,
267
+ matcher: CaptureRuleMatcherSchema,
268
+ sample_rate: z.number().min(0).max(1).nullable().optional(),
269
+ sample_event_class: CaptureRuleSampleEventClassSchema.nullable().optional(),
270
+ created_by_user_id: z.string().min(1).max(120).nullable().default(null),
271
+ created_from_incident_id: z.string().min(1).max(120).nullable().default(null),
272
+ created_from_event_id: z.string().min(1).max(120).nullable().default(null),
273
+ expires_at: z.string().datetime().nullable().default(null),
274
+ })
275
+ .superRefine((value, context) => {
276
+ if (value.action === "sample") {
277
+ if (value.sample_rate === undefined || value.sample_rate === null) {
278
+ context.addIssue({
279
+ code: z.ZodIssueCode.custom,
280
+ path: ["sample_rate"],
281
+ message: "Sample rules require sample_rate.",
282
+ });
283
+ }
284
+ return;
285
+ }
286
+ if (value.sample_rate !== undefined && value.sample_rate !== null) {
287
+ context.addIssue({
288
+ code: z.ZodIssueCode.custom,
289
+ path: ["sample_rate"],
290
+ message: "Only sample rules can set sample_rate.",
291
+ });
292
+ }
293
+ if (value.sample_event_class !== undefined && value.sample_event_class !== null) {
294
+ context.addIssue({
295
+ code: z.ZodIssueCode.custom,
296
+ path: ["sample_event_class"],
297
+ message: "Only sample rules can set sample_event_class.",
298
+ });
299
+ }
300
+ })
301
+ .transform((value) => ({
302
+ ...value,
303
+ sample_rate: value.action === "sample" ? value.sample_rate : null,
304
+ sample_event_class: value.action === "sample" ? (value.sample_event_class ?? "preserve") : null,
305
+ }));
306
+ export const CaptureRuleUpdateSchema = z
307
+ .object({
308
+ name: z.string().trim().min(1).max(120).optional(),
309
+ description: z.string().trim().max(500).nullable().optional(),
310
+ enabled: z.boolean().optional(),
311
+ action: CaptureRuleActionSchema.optional(),
312
+ matcher: CaptureRuleMatcherSchema.optional(),
313
+ sample_rate: z.number().min(0).max(1).nullable().optional(),
314
+ sample_event_class: CaptureRuleSampleEventClassSchema.nullable().optional(),
315
+ expires_at: z.string().datetime().nullable().optional(),
316
+ })
317
+ .superRefine((value, context) => {
318
+ if (Object.keys(value).length === 0) {
319
+ context.addIssue({
320
+ code: z.ZodIssueCode.custom,
321
+ message: "At least one capture rule field must be provided.",
322
+ });
323
+ }
324
+ const resolvedAction = value.action;
325
+ if (resolvedAction === "sample") {
326
+ if (!("sample_rate" in value)) {
327
+ context.addIssue({
328
+ code: z.ZodIssueCode.custom,
329
+ path: ["sample_rate"],
330
+ message: "Sample rule updates must include sample_rate when changing action to sample.",
331
+ });
332
+ }
333
+ if (!("sample_event_class" in value)) {
334
+ context.addIssue({
335
+ code: z.ZodIssueCode.custom,
336
+ path: ["sample_event_class"],
337
+ message: "Sample rule updates must include sample_event_class when changing action to sample.",
338
+ });
339
+ }
340
+ return;
341
+ }
342
+ if ("sample_rate" in value) {
343
+ context.addIssue({
344
+ code: z.ZodIssueCode.custom,
345
+ path: ["sample_rate"],
346
+ message: "Sample rule fields can only be updated while setting action to sample.",
347
+ });
348
+ }
349
+ if ("sample_event_class" in value) {
350
+ context.addIssue({
351
+ code: z.ZodIssueCode.custom,
352
+ path: ["sample_event_class"],
353
+ message: "Sample rule fields can only be updated while setting action to sample.",
354
+ });
355
+ }
356
+ });
357
+ export const CaptureRuleResponseSchema = z.object({
358
+ rule: CaptureRuleSchema,
359
+ });
360
+ export const CaptureRulesResponseSchema = z.object({
361
+ access_mode: z.enum(["manage", "preview"]),
362
+ rules: z.array(CaptureRuleSchema),
363
+ });
364
+ export const CaptureRulesFileSchema = z.object({
365
+ version: z.literal(1),
366
+ rules: z.array(CaptureRuleSchema),
367
+ });
368
+ const CaptureRuleEvaluationUrlSchema = z.object({
369
+ host: z.string().min(1).transform((value) => value.toLowerCase()).optional(),
370
+ path: z.string().min(1).transform((value) => (value.startsWith("/") ? value : `/${value}`)),
371
+ });
372
+ export const CaptureRuleEvaluationContextSchema = z.object({
373
+ project_id: z.string().min(1).max(120),
374
+ event_id: z.string().uuid(),
375
+ event_type: CaptureRuleEventTypeSchema,
376
+ service: z.string().min(1).optional(),
377
+ environment: z.string().min(1).optional(),
378
+ runtime: CaptureRuleRuntimeSchema,
379
+ first_party: z.boolean().optional(),
380
+ error_name: z.string().min(1).optional(),
381
+ message: z.string().min(1).optional(),
382
+ browser_event_kind: BrowserEventKindSchema.optional(),
383
+ resource_url: CaptureRuleEvaluationUrlSchema.optional(),
384
+ request_url: CaptureRuleEvaluationUrlSchema.optional(),
385
+ status_code: z.number().int().min(0).max(599).optional(),
386
+ fingerprint: CaptureRuleFingerprintSchema.optional(),
387
+ });
388
+ function normalizeRuntime(value) {
389
+ switch (value?.trim().toLowerCase()) {
390
+ case "browser":
391
+ return "browser";
392
+ case "node":
393
+ case "nodejs":
394
+ return "node";
395
+ case "python":
396
+ return "python";
397
+ case "php":
398
+ return "php";
399
+ case "java":
400
+ return "java";
401
+ case "go":
402
+ case "golang":
403
+ return "go";
404
+ case "ruby":
405
+ return "ruby";
406
+ default:
407
+ return "unknown";
408
+ }
409
+ }
410
+ function normalizeRoutePath(value) {
411
+ const trimmed = value?.trim();
412
+ if (trimmed === undefined || trimmed.length === 0) {
413
+ return undefined;
414
+ }
415
+ const pathWithoutQueryOrFragment = trimmed.split(/[?#]/, 1)[0] ?? "";
416
+ if (pathWithoutQueryOrFragment.length === 0) {
417
+ return "/";
418
+ }
419
+ return pathWithoutQueryOrFragment.startsWith("/") ? pathWithoutQueryOrFragment : `/${pathWithoutQueryOrFragment}`;
420
+ }
421
+ function normalizeEvaluationUrl(value) {
422
+ const trimmed = value?.trim();
423
+ if (trimmed === undefined || trimmed.length === 0) {
424
+ return {};
425
+ }
426
+ const relativePath = normalizeRoutePath(trimmed);
427
+ if (relativePath !== undefined && trimmed.startsWith("/")) {
428
+ return {
429
+ url: { path: relativePath },
430
+ first_party: true,
431
+ };
432
+ }
433
+ try {
434
+ const parsed = new URL(trimmed);
435
+ if (parsed.protocol === "http:" || parsed.protocol === "https:") {
436
+ return {
437
+ url: {
438
+ host: parsed.hostname.length > 0 ? parsed.hostname.toLowerCase() : undefined,
439
+ path: normalizeRoutePath(parsed.pathname) ?? "/",
440
+ },
441
+ first_party: false,
442
+ };
443
+ }
444
+ }
445
+ catch {
446
+ if (relativePath !== undefined) {
447
+ return {
448
+ url: { path: relativePath },
449
+ first_party: true,
450
+ };
451
+ }
452
+ }
453
+ return {};
454
+ }
455
+ function readString(value) {
456
+ return typeof value === "string" && value.trim().length > 0 ? value : undefined;
457
+ }
458
+ function readNumber(value) {
459
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
460
+ }
461
+ function readRecord(value) {
462
+ return typeof value === "object" && value !== null && !Array.isArray(value)
463
+ ? value
464
+ : undefined;
465
+ }
466
+ export function buildCaptureRuleEvaluationContext(input) {
467
+ const context = {
468
+ project_id: input.project_id,
469
+ event_id: input.event.event_id,
470
+ event_type: input.event.event_type,
471
+ service: input.event.service.name,
472
+ environment: input.event.service.environment,
473
+ runtime: normalizeRuntime(input.event.service.runtime),
474
+ ...(input.fingerprint === undefined ? {} : { fingerprint: input.fingerprint }),
475
+ };
476
+ switch (input.event.event_type) {
477
+ case "backend_exception": {
478
+ const request = readRecord(input.event.payload["request"]);
479
+ const response = readRecord(input.event.payload["response"]);
480
+ const requestUrl = normalizeEvaluationUrl(readString(request?.["path"]));
481
+ return CaptureRuleEvaluationContextSchema.parse({
482
+ ...context,
483
+ first_party: true,
484
+ error_name: readString(input.event.payload["name"]),
485
+ message: readString(input.event.payload["message"]),
486
+ request_url: requestUrl.url,
487
+ status_code: readNumber(response?.["status_code"]),
488
+ });
489
+ }
490
+ case "request_event": {
491
+ const requestUrl = normalizeEvaluationUrl(readString(input.event.payload["path"]));
492
+ return CaptureRuleEvaluationContextSchema.parse({
493
+ ...context,
494
+ first_party: requestUrl.first_party ?? true,
495
+ request_url: requestUrl.url,
496
+ status_code: readNumber(input.event.payload["response_status"]),
497
+ });
498
+ }
499
+ case "log_event":
500
+ return CaptureRuleEvaluationContextSchema.parse({
501
+ ...context,
502
+ message: readString(input.event.payload["message"]),
503
+ });
504
+ case "frontend_exception": {
505
+ const browserEvent = readRecord(input.event.payload["browser_event"]);
506
+ const target = readRecord(browserEvent?.["target"]);
507
+ const browserEventKind = browserEvent?.["kind"];
508
+ const resourceSource = typeof target?.["source_url"] === "string"
509
+ ? target["source_url"]
510
+ : typeof browserEvent?.["file_name"] === "string"
511
+ ? browserEvent["file_name"]
512
+ : undefined;
513
+ const resourceUrl = normalizeEvaluationUrl(resourceSource);
514
+ return CaptureRuleEvaluationContextSchema.parse({
515
+ ...context,
516
+ ...(resourceUrl.first_party === undefined ? {} : { first_party: resourceUrl.first_party }),
517
+ error_name: readString(input.event.payload["name"]),
518
+ message: readString(input.event.payload["message"]),
519
+ browser_event_kind: browserEventKind === "window_error" || browserEventKind === "resource_error"
520
+ ? browserEventKind
521
+ : undefined,
522
+ resource_url: resourceUrl.url,
523
+ });
524
+ }
525
+ case "frontend_breadcrumb": {
526
+ if (input.event.payload["breadcrumb_type"] !== "network_request") {
527
+ return CaptureRuleEvaluationContextSchema.parse(context);
528
+ }
529
+ const data = readRecord(input.event.payload["data"]);
530
+ const requestUrl = normalizeEvaluationUrl(readString(data?.["url"]));
531
+ return CaptureRuleEvaluationContextSchema.parse({
532
+ ...context,
533
+ ...(requestUrl.first_party === undefined ? {} : { first_party: requestUrl.first_party }),
534
+ request_url: requestUrl.url,
535
+ status_code: readNumber(data?.["status_code"]),
536
+ });
537
+ }
538
+ default:
539
+ return CaptureRuleEvaluationContextSchema.parse(context);
540
+ }
541
+ }
542
+ export function applyCaptureRuleEventClass(input) {
543
+ if (input.capture_rule === null) {
544
+ return input.event_class;
545
+ }
546
+ if (input.capture_rule.outcome === "demote") {
547
+ return "context_signal";
548
+ }
549
+ if (input.capture_rule.action === "sample" && input.capture_rule.sample_event_class === "context") {
550
+ return "context_signal";
551
+ }
552
+ return input.event_class;
553
+ }
554
+ function matchesUrlMatcher(matcher, value) {
555
+ if (matcher === undefined) {
556
+ return true;
557
+ }
558
+ if (value === undefined) {
559
+ return false;
560
+ }
561
+ if (matcher.host !== undefined && value.host !== matcher.host) {
562
+ return false;
563
+ }
564
+ if (matcher.host_suffix !== undefined && (value.host === undefined || !value.host.endsWith(matcher.host_suffix))) {
565
+ return false;
566
+ }
567
+ if (matcher.path_equals !== undefined && value.path !== matcher.path_equals) {
568
+ return false;
569
+ }
570
+ if (matcher.path_prefix !== undefined && !value.path.startsWith(matcher.path_prefix)) {
571
+ return false;
572
+ }
573
+ return true;
574
+ }
575
+ function matchesStatusRange(statusCode, ranges) {
576
+ if (ranges === undefined) {
577
+ return true;
578
+ }
579
+ if (statusCode === undefined) {
580
+ return false;
581
+ }
582
+ return ranges.some((range) => statusCode >= range.start && statusCode <= range.end);
583
+ }
584
+ function matchesStatusCodes(statusCode, statusCodes) {
585
+ if (statusCodes === undefined) {
586
+ return true;
587
+ }
588
+ if (statusCode === undefined) {
589
+ return false;
590
+ }
591
+ return statusCodes.includes(statusCode);
592
+ }
593
+ export function matchesCaptureRule(rule, contextInput) {
594
+ const context = CaptureRuleEvaluationContextSchema.parse(contextInput);
595
+ const matcher = rule.matcher;
596
+ if (matcher.event_types !== undefined && !matcher.event_types.includes(context.event_type)) {
597
+ return false;
598
+ }
599
+ if (matcher.services !== undefined && (context.service === undefined || !matcher.services.includes(context.service))) {
600
+ return false;
601
+ }
602
+ if (matcher.environments !== undefined &&
603
+ (context.environment === undefined || !matcher.environments.includes(context.environment))) {
604
+ return false;
605
+ }
606
+ if (matcher.runtime !== undefined && !matcher.runtime.includes(context.runtime)) {
607
+ return false;
608
+ }
609
+ if (matcher.first_party !== undefined && context.first_party !== matcher.first_party) {
610
+ return false;
611
+ }
612
+ if (matcher.error_name !== undefined && context.error_name !== matcher.error_name) {
613
+ return false;
614
+ }
615
+ if (matcher.message_equals !== undefined && context.message !== matcher.message_equals) {
616
+ return false;
617
+ }
618
+ if (matcher.message_contains !== undefined) {
619
+ if (context.message === undefined || !context.message.includes(matcher.message_contains)) {
620
+ return false;
621
+ }
622
+ }
623
+ if (matcher.browser_event_kind !== undefined && context.browser_event_kind !== matcher.browser_event_kind) {
624
+ return false;
625
+ }
626
+ if (!matchesUrlMatcher(matcher.resource_url, context.resource_url)) {
627
+ return false;
628
+ }
629
+ if (!matchesUrlMatcher(matcher.request_url, context.request_url)) {
630
+ return false;
631
+ }
632
+ if (!matchesStatusCodes(context.status_code, matcher.status_codes)) {
633
+ return false;
634
+ }
635
+ if (!matchesStatusRange(context.status_code, matcher.status_ranges)) {
636
+ return false;
637
+ }
638
+ if (matcher.fingerprint !== undefined) {
639
+ if (context.fingerprint === undefined) {
640
+ return false;
641
+ }
642
+ if (context.fingerprint.version !== matcher.fingerprint.version ||
643
+ context.fingerprint.value !== matcher.fingerprint.value) {
644
+ return false;
645
+ }
646
+ }
647
+ return true;
648
+ }
649
+ export function isCaptureRuleActive(rule, now) {
650
+ if (!rule.enabled) {
651
+ return false;
652
+ }
653
+ if (rule.expires_at === null) {
654
+ return true;
655
+ }
656
+ return Date.parse(rule.expires_at) > Date.parse(now);
657
+ }
658
+ export function getCaptureRuleSpecificityScore(rule) {
659
+ const matcher = rule.matcher;
660
+ let score = 0;
661
+ if (matcher.fingerprint !== undefined) {
662
+ score += 1000;
663
+ }
664
+ if (matcher.resource_url?.host !== undefined) {
665
+ score += 250;
666
+ }
667
+ if (matcher.request_url?.host !== undefined) {
668
+ score += 250;
669
+ }
670
+ if (matcher.resource_url?.path_equals !== undefined || matcher.request_url?.path_equals !== undefined) {
671
+ score += 200;
672
+ }
673
+ if (matcher.status_codes !== undefined) {
674
+ score += 150;
675
+ }
676
+ if (matcher.browser_event_kind !== undefined) {
677
+ score += 100;
678
+ }
679
+ if (matcher.resource_url?.host_suffix !== undefined || matcher.request_url?.host_suffix !== undefined) {
680
+ score += 90;
681
+ }
682
+ if (matcher.resource_url?.path_prefix !== undefined || matcher.request_url?.path_prefix !== undefined) {
683
+ score += 80;
684
+ }
685
+ if (matcher.error_name !== undefined) {
686
+ score += 70;
687
+ }
688
+ if (matcher.message_equals !== undefined) {
689
+ score += 60;
690
+ }
691
+ if (matcher.message_contains !== undefined) {
692
+ score += 50;
693
+ }
694
+ if (matcher.first_party !== undefined) {
695
+ score += 40;
696
+ }
697
+ if (matcher.services !== undefined) {
698
+ score += 30;
699
+ }
700
+ if (matcher.environments !== undefined) {
701
+ score += 20;
702
+ }
703
+ if (matcher.runtime !== undefined) {
704
+ score += 10;
705
+ }
706
+ if (matcher.event_types !== undefined) {
707
+ score += 5;
708
+ }
709
+ return score;
710
+ }
711
+ function compareCaptureRules(left, right) {
712
+ const specificityDifference = getCaptureRuleSpecificityScore(right) - getCaptureRuleSpecificityScore(left);
713
+ if (specificityDifference !== 0) {
714
+ return specificityDifference;
715
+ }
716
+ const updatedDifference = Date.parse(right.updated_at) - Date.parse(left.updated_at);
717
+ if (updatedDifference !== 0) {
718
+ return updatedDifference;
719
+ }
720
+ return left.id.localeCompare(right.id);
721
+ }
722
+ function stableUnitFloat(seed) {
723
+ let hash = 2166136261;
724
+ for (let index = 0; index < seed.length; index += 1) {
725
+ hash ^= seed.charCodeAt(index);
726
+ hash = Math.imul(hash, 16777619);
727
+ }
728
+ return (hash >>> 0) / 0x100000000;
729
+ }
730
+ export function shouldSampleCaptureRuleEvent(input) {
731
+ if (input.sample_rate <= 0) {
732
+ return false;
733
+ }
734
+ if (input.sample_rate >= 1) {
735
+ return true;
736
+ }
737
+ const seed = `${input.project_id}:${input.rule_id}:${input.event_id}`;
738
+ return stableUnitFloat(seed) < input.sample_rate;
739
+ }
740
+ export function evaluateCaptureRules(rules, contextInput, now) {
741
+ const context = CaptureRuleEvaluationContextSchema.parse(contextInput);
742
+ const activeRules = rules.filter((rule) => isCaptureRuleActive(rule, now)).sort(compareCaptureRules);
743
+ for (const rule of activeRules) {
744
+ if (!matchesCaptureRule(rule, context)) {
745
+ continue;
746
+ }
747
+ if (rule.action === "demote") {
748
+ return {
749
+ rule_id: rule.id,
750
+ action: "demote",
751
+ outcome: "demote",
752
+ sample_rate: null,
753
+ sample_event_class: null,
754
+ };
755
+ }
756
+ if (rule.action === "drop") {
757
+ return {
758
+ rule_id: rule.id,
759
+ action: "drop",
760
+ outcome: "drop",
761
+ sample_rate: null,
762
+ sample_event_class: null,
763
+ };
764
+ }
765
+ const sampledIn = shouldSampleCaptureRuleEvent({
766
+ project_id: context.project_id,
767
+ rule_id: rule.id,
768
+ event_id: context.event_id,
769
+ sample_rate: rule.sample_rate ?? 0,
770
+ });
771
+ return {
772
+ rule_id: rule.id,
773
+ action: "sample",
774
+ outcome: sampledIn ? "sampled_in" : "sampled_out",
775
+ sample_rate: rule.sample_rate,
776
+ sample_event_class: rule.sample_event_class,
777
+ };
778
+ }
779
+ return null;
780
+ }
781
+ //# sourceMappingURL=capture-rules.js.map