@rustrak/client 0.12.3 → 0.14.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.js CHANGED
@@ -357,6 +357,28 @@ var upsertProjectMemberSchema = z.object({
357
357
  user_id: z.number().int(),
358
358
  role: projectRoleSchema
359
359
  });
360
+ var metricDeltaSchema = z.object({
361
+ current: z.number().int(),
362
+ /**
363
+ * The same window immediately before this one. Null only for all-time
364
+ * requests, where a `0` would render as a misleading "+100%".
365
+ */
366
+ previous: z.number().int().nullable()
367
+ });
368
+ var projectListStatsSchema = z.object({
369
+ /**
370
+ * Distinct issues active in each bucket, oldest first. A shape, not a
371
+ * total: an issue firing all day appears in every bucket it touched.
372
+ */
373
+ trend: z.array(z.number().int()),
374
+ events: metricDeltaSchema,
375
+ /** Issues first seen in the window. Drives the "getting worse" signal. */
376
+ new_issues: metricDeltaSchema,
377
+ /** Unresolved issues right now, independent of the window. */
378
+ open_issues: z.number().int(),
379
+ /** The subset of `open_issues` at `fatal` level. */
380
+ fatal_issues: z.number().int()
381
+ });
360
382
  var projectSchema = z.object({
361
383
  id: z.number().int(),
362
384
  name: z.string(),
@@ -367,15 +389,31 @@ var projectSchema = z.object({
367
389
  digested_event_count: z.number().int(),
368
390
  created_at: dateTimeSchema,
369
391
  updated_at: dateTimeSchema,
370
- platform: z.string().nullable()
392
+ platform: z.string().nullable(),
393
+ stats: projectListStatsSchema.optional()
371
394
  });
372
395
  var createProjectSchema = z.object({
373
396
  name: z.string().min(1),
374
- slug: z.string().optional()
397
+ /**
398
+ * Slugified server-side. Supplying it means the user chose it, so a taken
399
+ * slug is a 409. Omitting it derives one from the name and silently
400
+ * de-duplicates, since nobody chose that value.
401
+ */
402
+ slug: z.string().min(1).optional(),
403
+ /**
404
+ * Validated server-side against SELECTABLE_PLATFORMS. Omitting it leaves the
405
+ * project eligible for platform auto-detection from its first event.
406
+ */
407
+ platform: z.string().min(1).optional()
375
408
  });
376
409
  var updateProjectSchema = z.object({
377
410
  name: z.string().min(1).optional(),
378
- platform: z.string().min(1).optional()
411
+ platform: z.string().min(1).optional(),
412
+ /**
413
+ * Slugified server-side. Unlike on create, a taken slug is a 409 rather than
414
+ * being silently de-duplicated.
415
+ */
416
+ slug: z.string().min(1).optional()
379
417
  });
380
418
  var releaseHealthRowSchema = z.object({
381
419
  release: z.string(),
@@ -413,6 +451,11 @@ var chunkUploadCapabilitySchema = z.object({
413
451
  accept: z.array(z.string()),
414
452
  concurrency: z.number().int().optional()
415
453
  });
454
+ var assembleInputSchema = z.object({
455
+ checksum: z.string().min(1),
456
+ chunks: z.array(z.string().min(1)),
457
+ projects: z.array(z.string().min(1)).min(1)
458
+ });
416
459
  var assembleResponseSchema = z.object({
417
460
  state: z.string(),
418
461
  missingChunks: z.array(z.string()),
@@ -437,15 +480,15 @@ var eventTimeseriesPointSchema = z.object({
437
480
  info: z.number().int()
438
481
  });
439
482
  var eventTimeseriesSchema = z.array(eventTimeseriesPointSchema);
440
- var metricDeltaSchema = z.object({
483
+ var metricDeltaSchema2 = z.object({
441
484
  current: z.number().int(),
442
485
  previous: z.number().int().nullable()
443
486
  });
444
487
  var projectStatsSummarySchema = z.object({
445
488
  /** Resolved window in hours, or `null` for all time. */
446
489
  period_hours: z.number().int().nullable(),
447
- events: metricDeltaSchema,
448
- new_issues: metricDeltaSchema,
490
+ events: metricDeltaSchema2,
491
+ new_issues: metricDeltaSchema2,
449
492
  /** Currently unresolved issues, independent of the window. */
450
493
  open_issues: z.number().int()
451
494
  });
@@ -595,117 +638,330 @@ var registerRequestSchema = z.object({
595
638
  password: z.string().min(1)
596
639
  });
597
640
 
598
- // src/errors/base.ts
599
- var RustrakError = class extends Error {
600
- /**
601
- * Whether this error is safe to retry
602
- */
603
- retryable;
604
- /**
605
- * HTTP status code if applicable
606
- */
607
- statusCode;
608
- /**
609
- * Original error cause
610
- */
611
- cause;
612
- constructor(message, options) {
613
- super(message);
614
- this.name = this.constructor.name;
615
- this.retryable = options?.retryable ?? false;
616
- this.statusCode = options?.statusCode;
617
- this.cause = options?.cause;
618
- if (Error.captureStackTrace) {
619
- Error.captureStackTrace(this, this.constructor);
620
- }
621
- }
622
- };
641
+ // src/errors.ts
642
+ var FIELD_ERROR_CODES = [
643
+ "required",
644
+ "invalid",
645
+ "already_exists",
646
+ "too_short",
647
+ "too_long",
648
+ "custom"
649
+ ];
650
+ var SERVER_ERROR_MESSAGE = "The server failed to handle the request.";
651
+ var NETWORK_ERROR_MESSAGE = "The request could not reach the server.";
652
+ var TIMEOUT_ERROR_MESSAGE = "The request timed out.";
653
+ function isRetryable(error) {
654
+ return error.kind === "network" || error.kind === "server_error" || error.kind === "rate_limited";
655
+ }
623
656
 
624
- // src/errors/http.ts
625
- var NetworkError = class extends RustrakError {
626
- constructor(message, cause) {
627
- super(message, { retryable: true, cause });
657
+ // src/result.ts
658
+ function Ok(data) {
659
+ return { success: true, data };
660
+ }
661
+ function Err(error) {
662
+ return { success: false, error };
663
+ }
664
+ function unwrap(result) {
665
+ if (result.success) {
666
+ return result.data;
628
667
  }
629
- };
630
- var AuthenticationError = class extends RustrakError {
631
- constructor(message = "Authentication failed") {
632
- super(message, { retryable: false, statusCode: 401 });
668
+ throw new Error(
669
+ `unwrap() called on a failed Result: ${describe(result.error)}`
670
+ );
671
+ }
672
+ function unwrapOr(result, fallback) {
673
+ return result.success ? result.data : fallback;
674
+ }
675
+ function mapResult(result, fn) {
676
+ return result.success ? Ok(fn(result.data)) : result;
677
+ }
678
+ function describe(error) {
679
+ if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
680
+ return error.message;
633
681
  }
634
- };
635
- var AuthorizationError = class extends RustrakError {
636
- constructor(message = "Insufficient permissions") {
637
- super(message, { retryable: false, statusCode: 403 });
682
+ return String(error);
683
+ }
684
+ var RustrakTransportFailure = class extends Error {
685
+ rustrakError;
686
+ constructor(error) {
687
+ super(error.message);
688
+ this.name = "RustrakTransportFailure";
689
+ this.rustrakError = error;
638
690
  }
639
691
  };
640
- var NotFoundError = class extends RustrakError {
641
- constructor(resource) {
642
- super(`Resource not found: ${resource}`, {
643
- retryable: false,
644
- statusCode: 404
645
- });
692
+ function isTransportFailure(value) {
693
+ return value instanceof RustrakTransportFailure;
694
+ }
695
+ function readErrorMessage(error, status) {
696
+ const fallback = `HTTP ${status} error`;
697
+ const body = error.data;
698
+ if (body && typeof body === "object") {
699
+ const nested = typeof body.error === "object" ? body.error?.message : body.error;
700
+ return nested || body.message || fallback;
646
701
  }
647
- };
648
- var RateLimitError = class extends RustrakError {
649
- /**
650
- * Number of seconds to wait before retrying (from Retry-After header)
651
- */
652
- retryAfter;
653
- constructor(message = "Rate limit exceeded", retryAfter) {
654
- super(message, { retryable: true, statusCode: 429 });
655
- if (retryAfter !== void 0) {
656
- this.retryAfter = typeof retryAfter === "string" ? parseInt(retryAfter, 10) : retryAfter;
702
+ return fallback;
703
+ }
704
+ var KNOWN_FIELD_ERROR_CODES = new Set(FIELD_ERROR_CODES);
705
+ function isFieldErrorCode(value) {
706
+ return typeof value === "string" && KNOWN_FIELD_ERROR_CODES.has(value);
707
+ }
708
+ var warnedFieldErrorCodes = /* @__PURE__ */ new Set();
709
+ function warnUnknownFieldErrorCode(code, field) {
710
+ if (warnedFieldErrorCodes.has(code)) return;
711
+ warnedFieldErrorCodes.add(code);
712
+ console.warn(
713
+ `[@rustrak/client] Dropped a field error with an unrecognised code "${code}" (field "${field}"). This client build does not know that code, so the affected inputs degrade to a form-level error. The server is most likely newer than @rustrak/client; upgrade the client to match the server version.`
714
+ );
715
+ }
716
+ function readFieldErrors(error) {
717
+ const body = error.data;
718
+ const raw = body && typeof body === "object" ? body.error?.fields : void 0;
719
+ if (!Array.isArray(raw)) {
720
+ return void 0;
721
+ }
722
+ const fields = [];
723
+ for (const entry of raw) {
724
+ if (typeof entry !== "object" || entry === null) continue;
725
+ const { field, code, message } = entry;
726
+ if (typeof field !== "string") continue;
727
+ if (!isFieldErrorCode(code)) {
728
+ if (typeof code === "string") warnUnknownFieldErrorCode(code, field);
729
+ continue;
657
730
  }
731
+ fields.push(
732
+ code === "custom" && typeof message === "string" ? { field, code, message } : { field, code }
733
+ );
658
734
  }
659
- };
660
- var ServerError = class extends RustrakError {
661
- constructor(message, statusCode = 500) {
662
- super(message, { retryable: true, statusCode });
735
+ return fields.length > 0 ? fields : void 0;
736
+ }
737
+ function readRetryAfter(response) {
738
+ const header = response.headers.get("Retry-After")?.trim();
739
+ if (!header) {
740
+ return void 0;
663
741
  }
664
- };
665
- var BadRequestError = class extends RustrakError {
666
- constructor(message) {
667
- super(message, { retryable: false, statusCode: 400 });
742
+ if (/^\d+$/.test(header)) {
743
+ return Number.parseInt(header, 10);
668
744
  }
669
- };
670
-
671
- // src/errors/validation.ts
672
- var ValidationError = class extends RustrakError {
673
- /**
674
- * Zod validation errors
675
- */
676
- validationErrors;
677
- constructor(message, validationErrors) {
678
- super(message, { retryable: false });
679
- this.validationErrors = validationErrors;
745
+ if (/^[+-]\d+$/.test(header)) {
746
+ return void 0;
680
747
  }
681
- /**
682
- * Get a formatted string of validation errors
683
- */
684
- getValidationDetails() {
685
- if (!this.validationErrors) {
686
- return this.message;
748
+ const date = Date.parse(header);
749
+ if (Number.isNaN(date)) {
750
+ return void 0;
751
+ }
752
+ return Math.max(0, Math.ceil((date - Date.now()) / 1e3));
753
+ }
754
+ function annotate(base, fields) {
755
+ return fields === void 0 ? base : { ...base, fields };
756
+ }
757
+ function transformHttpError(error) {
758
+ const { response } = error;
759
+ const status = response.status;
760
+ if (status >= 500) {
761
+ return { kind: "server_error", status, message: SERVER_ERROR_MESSAGE };
762
+ }
763
+ const message = readErrorMessage(error, status);
764
+ const fields = readFieldErrors(error);
765
+ switch (status) {
766
+ case 400:
767
+ return annotate({ kind: "validation", status, message }, fields);
768
+ case 401:
769
+ return annotate({ kind: "unauthenticated", status, message }, fields);
770
+ case 403:
771
+ return annotate({ kind: "forbidden", status, message }, fields);
772
+ case 404:
773
+ return annotate({ kind: "not_found", status, message }, fields);
774
+ case 409:
775
+ return annotate({ kind: "conflict", status, message }, fields);
776
+ case 410:
777
+ return annotate({ kind: "gone", status, message }, fields);
778
+ case 413:
779
+ return annotate({ kind: "payload_too_large", status, message }, fields);
780
+ case 429: {
781
+ const retryAfter = readRetryAfter(response);
782
+ return annotate(
783
+ retryAfter === void 0 ? { kind: "rate_limited", status, message } : { kind: "rate_limited", status, message, retryAfter },
784
+ fields
785
+ );
687
786
  }
688
- const errors = this.validationErrors.issues.map((err) => `${err.path.map(String).join(".")}: ${err.message}`).join(", ");
689
- return `${this.message} - ${errors}`;
787
+ default:
788
+ return annotate({ kind: "client_error", status, message }, fields);
690
789
  }
691
- };
790
+ }
791
+ function createKyInstance(config) {
792
+ const headers = {
793
+ // `Accept` is set here rather than relied on from ky. The `.json()`
794
+ // shortcut sets it before the request is issued, and `BaseResource` no
795
+ // longer uses that shortcut: it awaits the `Response` and parses the body
796
+ // itself, so without this every one of the 86 methods would send `*/*` and
797
+ // any content-negotiating hop could answer with HTML.
798
+ Accept: "application/json",
799
+ "Content-Type": "application/json",
800
+ ...config.headers
801
+ };
802
+ if (config.token) {
803
+ headers.Authorization = `Bearer ${config.token}`;
804
+ }
805
+ return ky.create({
806
+ prefix: config.baseUrl,
807
+ timeout: config.timeout ?? 3e4,
808
+ credentials: "include",
809
+ retry: {
810
+ limit: config.maxRetries ?? 2,
811
+ statusCodes: [408, 500, 502, 503, 504],
812
+ methods: ["get", "post", "put", "patch", "delete"]
813
+ },
814
+ headers,
815
+ hooks: {
816
+ beforeError: [
817
+ ({ error }) => {
818
+ if (error.name === "TimeoutError") {
819
+ throw new RustrakTransportFailure({
820
+ kind: "network",
821
+ message: TIMEOUT_ERROR_MESSAGE,
822
+ reason: "timeout"
823
+ });
824
+ }
825
+ if (isHTTPError(error)) {
826
+ throw new RustrakTransportFailure(transformHttpError(error));
827
+ }
828
+ throw new RustrakTransportFailure({
829
+ kind: "network",
830
+ message: NETWORK_ERROR_MESSAGE,
831
+ reason: "unreachable"
832
+ });
833
+ }
834
+ ]
835
+ }
836
+ });
837
+ }
692
838
 
693
839
  // src/resources/base.ts
840
+ var TRANSPORT_TYPE_ERROR = /terminated|aborted|socket|network|closed|reset|fetch failed/i;
841
+ function isProgrammingError(error) {
842
+ if (!(error instanceof Error)) {
843
+ return true;
844
+ }
845
+ if (error instanceof ReferenceError || error instanceof RangeError) {
846
+ return true;
847
+ }
848
+ if (error instanceof TypeError) {
849
+ return error.cause === void 0 && !TRANSPORT_TYPE_ERROR.test(error.message);
850
+ }
851
+ return false;
852
+ }
853
+ function classifyBodyFailure(error) {
854
+ if (error instanceof SyntaxError) {
855
+ return {
856
+ kind: "invalid_response",
857
+ message: "The server returned a body that is not valid JSON."
858
+ };
859
+ }
860
+ if (isProgrammingError(error)) {
861
+ return "rethrow";
862
+ }
863
+ return {
864
+ kind: "network",
865
+ message: NETWORK_ERROR_MESSAGE,
866
+ reason: error instanceof Error && error.name === "TimeoutError" ? "timeout" : "unreachable"
867
+ };
868
+ }
869
+ function discardBody(response) {
870
+ try {
871
+ void response.body?.cancel().catch(() => {
872
+ });
873
+ } catch {
874
+ }
875
+ }
694
876
  var BaseResource = class {
695
877
  http;
696
878
  constructor(http) {
697
879
  this.http = http;
698
880
  }
699
881
  /**
700
- * Validate API response against Zod schema
701
- * @throws {ValidationError} if validation fails
882
+ * Validate an API response against its Zod schema.
883
+ *
884
+ * Failure is `invalid_response`: "our own response schema did not match",
885
+ * never "the user's input was rejected". No Zod issues are attached: they
886
+ * embed the offending response data.
702
887
  */
703
888
  validate(data, schema) {
704
889
  const result = schema.safeParse(data);
705
890
  if (!result.success) {
706
- throw new ValidationError("API response validation failed", result.error);
891
+ return Err({
892
+ kind: "invalid_response",
893
+ message: "The server response did not match the expected schema."
894
+ });
707
895
  }
708
- return result.data;
896
+ return Ok(result.data);
897
+ }
898
+ /**
899
+ * Validate caller-supplied input before it reaches the network.
900
+ *
901
+ * Failure is `invalid_request` and has no `status`: nothing was sent.
902
+ */
903
+ validateInput(input, schema) {
904
+ const result = schema.safeParse(input);
905
+ if (!result.success) {
906
+ return Err({
907
+ kind: "invalid_request",
908
+ message: "The request input did not match the expected schema."
909
+ });
910
+ }
911
+ return Ok(result.data);
912
+ }
913
+ /**
914
+ * The boundary. Runs `send`, converts a transport failure into an `Err`, and
915
+ * hands the `Response` to `read`.
916
+ *
917
+ * Anything thrown that is not the internal carrier is a programming error and
918
+ * is rethrown.
919
+ */
920
+ async requestResponse(send, read) {
921
+ let response;
922
+ try {
923
+ response = await send();
924
+ } catch (error) {
925
+ if (isTransportFailure(error)) {
926
+ return Err(error.rustrakError);
927
+ }
928
+ throw error;
929
+ }
930
+ return read(response);
931
+ }
932
+ /**
933
+ * Read a JSON body, mapping a failed read onto the right `kind`.
934
+ *
935
+ * Shared with `AuthResource`, which needs the `Set-Cookie` headers off the
936
+ * raw `Response` and so cannot go through {@link BaseResource.request}.
937
+ */
938
+ async readJson(response) {
939
+ try {
940
+ return Ok(await response.json());
941
+ } catch (error) {
942
+ const failure = classifyBodyFailure(error);
943
+ if (failure === "rethrow") {
944
+ throw error;
945
+ }
946
+ return Err(failure);
947
+ }
948
+ }
949
+ /** Send a request and validate its JSON body against `schema`. */
950
+ async request(send, schema) {
951
+ return this.requestResponse(send, async (response) => {
952
+ const body = await this.readJson(response);
953
+ if (!body.success) {
954
+ return body;
955
+ }
956
+ return this.validate(body.data, schema);
957
+ });
958
+ }
959
+ /** Send a request whose response body carries no contract (204, or ignored). */
960
+ async requestVoid(send) {
961
+ return this.requestResponse(send, async (response) => {
962
+ discardBody(response);
963
+ return Ok(void 0);
964
+ });
709
965
  }
710
966
  };
711
967
 
@@ -735,46 +991,56 @@ var AgentsResource = class extends BaseResource {
735
991
  * Time-bucketed count of agent-run spans (`gen_ai.operation.type:agent`).
736
992
  */
737
993
  async getRuns(projectId, options) {
738
- const data = await this.http.get(`api/projects/${projectId}/agents/runs`, {
739
- searchParams: timeseriesSearchParams(options)
740
- }).json();
741
- return this.validate(data, z.array(agentTimeseriesPointSchema));
994
+ return this.request(
995
+ () => this.http.get(`api/projects/${projectId}/agents/runs`, {
996
+ searchParams: timeseriesSearchParams(options)
997
+ }),
998
+ z.array(agentTimeseriesPointSchema)
999
+ );
742
1000
  }
743
1001
  /**
744
1002
  * Time-bucketed avg/p95 duration for `agent`/`ai_client` spans.
745
1003
  */
746
1004
  async getDuration(projectId, options) {
747
- const data = await this.http.get(`api/projects/${projectId}/agents/duration`, {
748
- searchParams: timeseriesSearchParams(options)
749
- }).json();
750
- return this.validate(data, z.array(agentDurationPointSchema));
1005
+ return this.request(
1006
+ () => this.http.get(`api/projects/${projectId}/agents/duration`, {
1007
+ searchParams: timeseriesSearchParams(options)
1008
+ }),
1009
+ z.array(agentDurationPointSchema)
1010
+ );
751
1011
  }
752
1012
  /**
753
1013
  * Top models by LLM call count (`gen_ai.operation.type:ai_client`).
754
1014
  */
755
1015
  async getModelsByCalls(projectId, options) {
756
- const data = await this.http.get(`api/projects/${projectId}/agents/models/calls`, {
757
- searchParams: breakdownSearchParams(options)
758
- }).json();
759
- return this.validate(data, z.array(genAiBreakdownRowSchema));
1016
+ return this.request(
1017
+ () => this.http.get(`api/projects/${projectId}/agents/models/calls`, {
1018
+ searchParams: breakdownSearchParams(options)
1019
+ }),
1020
+ z.array(genAiBreakdownRowSchema)
1021
+ );
760
1022
  }
761
1023
  /**
762
1024
  * Top models by total tokens used (`gen_ai.operation.type:ai_client`).
763
1025
  */
764
1026
  async getModelsByTokens(projectId, options) {
765
- const data = await this.http.get(`api/projects/${projectId}/agents/models/tokens`, {
766
- searchParams: breakdownSearchParams(options)
767
- }).json();
768
- return this.validate(data, z.array(genAiBreakdownRowSchema));
1027
+ return this.request(
1028
+ () => this.http.get(`api/projects/${projectId}/agents/models/tokens`, {
1029
+ searchParams: breakdownSearchParams(options)
1030
+ }),
1031
+ z.array(genAiBreakdownRowSchema)
1032
+ );
769
1033
  }
770
1034
  /**
771
1035
  * Top tools by call count (`gen_ai.operation.type:tool`).
772
1036
  */
773
1037
  async getTools(projectId, options) {
774
- const data = await this.http.get(`api/projects/${projectId}/agents/tools`, {
775
- searchParams: breakdownSearchParams(options)
776
- }).json();
777
- return this.validate(data, z.array(genAiBreakdownRowSchema));
1038
+ return this.request(
1039
+ () => this.http.get(`api/projects/${projectId}/agents/tools`, {
1040
+ searchParams: breakdownSearchParams(options)
1041
+ }),
1042
+ z.array(genAiBreakdownRowSchema)
1043
+ );
778
1044
  }
779
1045
  /**
780
1046
  * Paginated per-trace_id aggregate (duration, tokens, tool usage) across
@@ -788,9 +1054,10 @@ var AgentsResource = class extends BaseResource {
788
1054
  if (options?.per_page) {
789
1055
  searchParams.per_page = String(options.per_page);
790
1056
  }
791
- const data = await this.http.get(`api/projects/${projectId}/agents/traces`, { searchParams }).json();
792
- return this.validate(
793
- data,
1057
+ return this.request(
1058
+ () => this.http.get(`api/projects/${projectId}/agents/traces`, {
1059
+ searchParams
1060
+ }),
794
1061
  offsetPaginatedResponseSchema(agentTraceSummarySchema)
795
1062
  );
796
1063
  }
@@ -800,52 +1067,83 @@ var AlertIntegrationsResource = class extends BaseResource {
800
1067
  * List all alert integrations
801
1068
  */
802
1069
  async list() {
803
- const data = await this.http.get("api/integrations").json();
804
- return this.validate(data, z.array(alertIntegrationSchema));
1070
+ return this.request(
1071
+ () => this.http.get("api/integrations"),
1072
+ z.array(alertIntegrationSchema)
1073
+ );
805
1074
  }
806
1075
  /**
807
1076
  * Get a single alert integration by ID
808
1077
  */
809
1078
  async get(id) {
810
- const data = await this.http.get(`api/integrations/${id}`).json();
811
- return this.validate(data, alertIntegrationSchema);
1079
+ return this.request(
1080
+ () => this.http.get(`api/integrations/${id}`),
1081
+ alertIntegrationSchema
1082
+ );
812
1083
  }
813
1084
  /**
814
1085
  * Create a new alert integration
815
1086
  */
816
1087
  async create(input) {
817
- const validatedInput = this.validate(input, createAlertIntegrationSchema);
818
- const data = await this.http.post("api/integrations", { json: validatedInput }).json();
819
- return this.validate(data, alertIntegrationSchema);
1088
+ const validatedInput = this.validateInput(
1089
+ input,
1090
+ createAlertIntegrationSchema
1091
+ );
1092
+ if (!validatedInput.success) {
1093
+ return validatedInput;
1094
+ }
1095
+ return this.request(
1096
+ () => this.http.post("api/integrations", { json: validatedInput.data }),
1097
+ alertIntegrationSchema
1098
+ );
820
1099
  }
821
1100
  /**
822
1101
  * Update an existing alert integration
823
1102
  */
824
1103
  async update(id, input) {
825
- const validatedInput = this.validate(input, updateAlertIntegrationSchema);
826
- const data = await this.http.patch(`api/integrations/${id}`, { json: validatedInput }).json();
827
- return this.validate(data, alertIntegrationSchema);
1104
+ const validatedInput = this.validateInput(
1105
+ input,
1106
+ updateAlertIntegrationSchema
1107
+ );
1108
+ if (!validatedInput.success) {
1109
+ return validatedInput;
1110
+ }
1111
+ return this.request(
1112
+ () => this.http.patch(`api/integrations/${id}`, {
1113
+ json: validatedInput.data
1114
+ }),
1115
+ alertIntegrationSchema
1116
+ );
828
1117
  }
829
1118
  /**
830
1119
  * Delete an alert integration
831
1120
  */
832
1121
  async delete(id) {
833
- await this.http.delete(`api/integrations/${id}`);
1122
+ return this.requestVoid(() => this.http.delete(`api/integrations/${id}`));
834
1123
  }
835
1124
  /**
836
1125
  * Send a test notification to verify integration configuration.
837
1126
  * For Slack bot_token integrations, routingOverride must include `channel`.
838
1127
  */
839
1128
  async test(id, routingOverride) {
840
- const body = routingOverride !== void 0 ? this.validate(
841
- { routing_override: routingOverride },
842
- testIntegrationBodySchema
843
- ) : void 0;
844
- const data = await this.http.post(
845
- `api/integrations/${id}/test`,
846
- body !== void 0 ? { json: body } : void 0
847
- ).json();
848
- return this.validate(data, testChannelResponseSchema);
1129
+ let body;
1130
+ if (routingOverride !== void 0) {
1131
+ const validatedInput = this.validateInput(
1132
+ { routing_override: routingOverride },
1133
+ testIntegrationBodySchema
1134
+ );
1135
+ if (!validatedInput.success) {
1136
+ return validatedInput;
1137
+ }
1138
+ body = validatedInput.data;
1139
+ }
1140
+ return this.request(
1141
+ () => this.http.post(
1142
+ `api/integrations/${id}/test`,
1143
+ body !== void 0 ? { json: body } : void 0
1144
+ ),
1145
+ testChannelResponseSchema
1146
+ );
849
1147
  }
850
1148
  };
851
1149
  var AlertRulesResource = class extends BaseResource {
@@ -853,39 +1151,57 @@ var AlertRulesResource = class extends BaseResource {
853
1151
  * List all alert rules for a project
854
1152
  */
855
1153
  async list(projectId) {
856
- const data = await this.http.get(`api/projects/${projectId}/alert-rules`).json();
857
- return this.validate(data, z.array(alertRuleSchema));
1154
+ return this.request(
1155
+ () => this.http.get(`api/projects/${projectId}/alert-rules`),
1156
+ z.array(alertRuleSchema)
1157
+ );
858
1158
  }
859
1159
  /**
860
1160
  * Get a single alert rule by ID
861
1161
  */
862
1162
  async get(projectId, ruleId) {
863
- const data = await this.http.get(`api/projects/${projectId}/alert-rules/${ruleId}`).json();
864
- return this.validate(data, alertRuleSchema);
1163
+ return this.request(
1164
+ () => this.http.get(`api/projects/${projectId}/alert-rules/${ruleId}`),
1165
+ alertRuleSchema
1166
+ );
865
1167
  }
866
1168
  /**
867
1169
  * Create a new alert rule for a project
868
1170
  */
869
1171
  async create(projectId, input) {
870
- const validatedInput = this.validate(input, createAlertRuleSchema);
871
- const data = await this.http.post(`api/projects/${projectId}/alert-rules`, { json: validatedInput }).json();
872
- return this.validate(data, alertRuleSchema);
1172
+ const validatedInput = this.validateInput(input, createAlertRuleSchema);
1173
+ if (!validatedInput.success) {
1174
+ return validatedInput;
1175
+ }
1176
+ return this.request(
1177
+ () => this.http.post(`api/projects/${projectId}/alert-rules`, {
1178
+ json: validatedInput.data
1179
+ }),
1180
+ alertRuleSchema
1181
+ );
873
1182
  }
874
1183
  /**
875
1184
  * Update an existing alert rule
876
1185
  */
877
1186
  async update(projectId, ruleId, input) {
878
- const validatedInput = this.validate(input, updateAlertRuleSchema);
879
- const data = await this.http.patch(`api/projects/${projectId}/alert-rules/${ruleId}`, {
880
- json: validatedInput
881
- }).json();
882
- return this.validate(data, alertRuleSchema);
1187
+ const validatedInput = this.validateInput(input, updateAlertRuleSchema);
1188
+ if (!validatedInput.success) {
1189
+ return validatedInput;
1190
+ }
1191
+ return this.request(
1192
+ () => this.http.patch(`api/projects/${projectId}/alert-rules/${ruleId}`, {
1193
+ json: validatedInput.data
1194
+ }),
1195
+ alertRuleSchema
1196
+ );
883
1197
  }
884
1198
  /**
885
1199
  * Delete an alert rule
886
1200
  */
887
1201
  async delete(projectId, ruleId) {
888
- await this.http.delete(`api/projects/${projectId}/alert-rules/${ruleId}`);
1202
+ return this.requestVoid(
1203
+ () => this.http.delete(`api/projects/${projectId}/alert-rules/${ruleId}`)
1204
+ );
889
1205
  }
890
1206
  /**
891
1207
  * List alert history for a project
@@ -897,12 +1213,18 @@ var AlertRulesResource = class extends BaseResource {
897
1213
  }
898
1214
  const query = searchParams.toString();
899
1215
  const url = query ? `api/projects/${projectId}/alert-history?${query}` : `api/projects/${projectId}/alert-history`;
900
- const data = await this.http.get(url).json();
901
- return this.validate(data, z.array(alertHistorySchema));
1216
+ return this.request(() => this.http.get(url), z.array(alertHistorySchema));
902
1217
  }
903
1218
  };
904
1219
 
905
1220
  // src/resources/auth.ts
1221
+ function readSetCookie(response) {
1222
+ if (typeof response.headers.getSetCookie === "function") {
1223
+ return response.headers.getSetCookie();
1224
+ }
1225
+ const folded = response.headers.get("set-cookie");
1226
+ return folded === null ? [] : [folded];
1227
+ }
906
1228
  var AuthResource = class extends BaseResource {
907
1229
  /**
908
1230
  * Register a new user account
@@ -911,17 +1233,17 @@ var AuthResource = class extends BaseResource {
911
1233
  * @returns LoginResult with user information and session cookies
912
1234
  */
913
1235
  async register(credentials) {
914
- const validatedInput = this.validate(credentials, registerRequestSchema);
915
- const response = await this.http.post("auth/register", {
916
- json: validatedInput
917
- });
918
- const cookies = response.headers.getSetCookie();
919
- const data = await response.json();
920
- const authResponse = this.validate(data, authResponseSchema);
921
- return {
922
- user: authResponse.user,
923
- cookies
924
- };
1236
+ const validatedInput = this.validateInput(
1237
+ credentials,
1238
+ registerRequestSchema
1239
+ );
1240
+ if (!validatedInput.success) {
1241
+ return validatedInput;
1242
+ }
1243
+ return this.requestResponse(
1244
+ () => this.http.post("auth/register", { json: validatedInput.data }),
1245
+ (response) => this.readLoginResult(response)
1246
+ );
925
1247
  }
926
1248
  /**
927
1249
  * Login with email and password
@@ -930,17 +1252,14 @@ var AuthResource = class extends BaseResource {
930
1252
  * @returns LoginResult with user information and session cookies
931
1253
  */
932
1254
  async login(credentials) {
933
- const validatedInput = this.validate(credentials, loginRequestSchema);
934
- const response = await this.http.post("auth/login", {
935
- json: validatedInput
936
- });
937
- const cookies = response.headers.getSetCookie();
938
- const data = await response.json();
939
- const authResponse = this.validate(data, authResponseSchema);
940
- return {
941
- user: authResponse.user,
942
- cookies
943
- };
1255
+ const validatedInput = this.validateInput(credentials, loginRequestSchema);
1256
+ if (!validatedInput.success) {
1257
+ return validatedInput;
1258
+ }
1259
+ return this.requestResponse(
1260
+ () => this.http.post("auth/login", { json: validatedInput.data }),
1261
+ (response) => this.readLoginResult(response)
1262
+ );
944
1263
  }
945
1264
  /**
946
1265
  * Logout the current user
@@ -948,17 +1267,28 @@ var AuthResource = class extends BaseResource {
948
1267
  * @returns Array of Set-Cookie headers (typically clearing cookies)
949
1268
  */
950
1269
  async logout() {
951
- const response = await this.http.post("auth/logout");
952
- return response.headers.getSetCookie();
1270
+ return this.requestResponse(
1271
+ () => this.http.post("auth/logout"),
1272
+ async (response) => {
1273
+ const cookies = readSetCookie(response);
1274
+ discardBody(response);
1275
+ return Ok(cookies);
1276
+ }
1277
+ );
953
1278
  }
954
1279
  /**
955
1280
  * Get current authenticated user
956
1281
  * Requires a valid session cookie
957
- * @returns User information
1282
+ *
1283
+ * No session is `{success: false, kind: 'unauthenticated'}`, not a `null`
1284
+ * user: it is the literal reading of the server's 401. Callers must send the
1285
+ * visitor to login on `unauthenticated` **only**. Treating `network` or
1286
+ * `server_error` the same way turns a flaky connection into a login loop.
1287
+ *
1288
+ * @returns The current user, or `unauthenticated` when there is no session
958
1289
  */
959
1290
  async getCurrentUser() {
960
- const data = await this.http.get("auth/me").json();
961
- return this.validate(data, userSchema);
1291
+ return this.request(() => this.http.get("auth/me"), userSchema);
962
1292
  }
963
1293
  /**
964
1294
  * Get the details of a pending invitation by its token (public endpoint)
@@ -967,8 +1297,10 @@ var AuthResource = class extends BaseResource {
967
1297
  * @returns Invitation info (email, role, status, expiry)
968
1298
  */
969
1299
  async getInvitation(token) {
970
- const data = await this.http.get(`auth/invitation/${token}`).json();
971
- return this.validate(data, invitationInfoSchema);
1300
+ return this.request(
1301
+ () => this.http.get(`auth/invitation/${token}`),
1302
+ invitationInfoSchema
1303
+ );
972
1304
  }
973
1305
  /**
974
1306
  * Accept a pending invitation, creating the user account and logging in
@@ -976,17 +1308,33 @@ var AuthResource = class extends BaseResource {
976
1308
  * @returns LoginResult with user information and session cookies
977
1309
  */
978
1310
  async acceptInvitation(input) {
979
- const validatedInput = this.validate(input, acceptInvitationSchema);
980
- const response = await this.http.post("auth/accept-invitation", {
981
- json: validatedInput
982
- });
983
- const cookies = response.headers.getSetCookie();
984
- const data = await response.json();
985
- const authResponse = this.validate(data, authResponseSchema);
986
- return {
987
- user: authResponse.user,
988
- cookies
989
- };
1311
+ const validatedInput = this.validateInput(input, acceptInvitationSchema);
1312
+ if (!validatedInput.success) {
1313
+ return validatedInput;
1314
+ }
1315
+ return this.requestResponse(
1316
+ () => this.http.post("auth/accept-invitation", {
1317
+ json: validatedInput.data
1318
+ }),
1319
+ (response) => this.readLoginResult(response)
1320
+ );
1321
+ }
1322
+ /**
1323
+ * The three session-establishing endpoints share a body and need the
1324
+ * `Set-Cookie` headers off the raw `Response`, which is why they go through
1325
+ * `requestResponse` rather than the plain JSON path.
1326
+ */
1327
+ async readLoginResult(response) {
1328
+ const cookies = readSetCookie(response);
1329
+ const body = await this.readJson(response);
1330
+ if (!body.success) {
1331
+ return body;
1332
+ }
1333
+ const authResponse = this.validate(body.data, authResponseSchema);
1334
+ if (!authResponse.success) {
1335
+ return authResponse;
1336
+ }
1337
+ return Ok({ user: authResponse.data.user, cookies });
990
1338
  }
991
1339
  };
992
1340
 
@@ -1003,17 +1351,23 @@ var EventsResource = class extends BaseResource {
1003
1351
  if (options?.cursor) {
1004
1352
  searchParams.cursor = options.cursor;
1005
1353
  }
1006
- const data = await this.http.get(`api/projects/${projectId}/issues/${issueId}/events`, {
1007
- searchParams
1008
- }).json();
1009
- return this.validate(data, paginatedResponseSchema(eventSchema));
1354
+ return this.request(
1355
+ () => this.http.get(`api/projects/${projectId}/issues/${issueId}/events`, {
1356
+ searchParams
1357
+ }),
1358
+ paginatedResponseSchema(eventSchema)
1359
+ );
1010
1360
  }
1011
1361
  /**
1012
1362
  * Get a single event by ID with full details
1013
1363
  */
1014
1364
  async get(projectId, issueId, eventId) {
1015
- const data = await this.http.get(`api/projects/${projectId}/issues/${issueId}/events/${eventId}`).json();
1016
- return this.validate(data, eventDetailSchema);
1365
+ return this.request(
1366
+ () => this.http.get(
1367
+ `api/projects/${projectId}/issues/${issueId}/events/${eventId}`
1368
+ ),
1369
+ eventDetailSchema
1370
+ );
1017
1371
  }
1018
1372
  };
1019
1373
  var serverVersionSchema = z.object({
@@ -1023,8 +1377,10 @@ var serverVersionSchema = z.object({
1023
1377
  // src/resources/health.ts
1024
1378
  var HealthResource = class extends BaseResource {
1025
1379
  async getVersion() {
1026
- const data = await this.http.get("health/version").json();
1027
- return this.validate(data, serverVersionSchema);
1380
+ return this.request(
1381
+ () => this.http.get("health/version"),
1382
+ serverVersionSchema
1383
+ );
1028
1384
  }
1029
1385
  };
1030
1386
 
@@ -1035,23 +1391,30 @@ var InvitationsResource = class extends BaseResource {
1035
1391
  * @param input - Email and global role for the invited user
1036
1392
  */
1037
1393
  async create(input) {
1038
- const validatedInput = this.validate(input, createInvitationSchema);
1039
- const data = await this.http.post("api/invitations", { json: validatedInput }).json();
1040
- return this.validate(data, invitationSchema);
1394
+ const validatedInput = this.validateInput(input, createInvitationSchema);
1395
+ if (!validatedInput.success) {
1396
+ return validatedInput;
1397
+ }
1398
+ return this.request(
1399
+ () => this.http.post("api/invitations", { json: validatedInput.data }),
1400
+ invitationSchema
1401
+ );
1041
1402
  }
1042
1403
  /**
1043
1404
  * List all invitations
1044
1405
  */
1045
1406
  async list() {
1046
- const data = await this.http.get("api/invitations").json();
1047
- return this.validate(data, invitationSchema.array());
1407
+ return this.request(
1408
+ () => this.http.get("api/invitations"),
1409
+ invitationSchema.array()
1410
+ );
1048
1411
  }
1049
1412
  /**
1050
1413
  * Revoke a pending invitation
1051
1414
  * @param token - Invitation token to revoke
1052
1415
  */
1053
1416
  async revoke(token) {
1054
- await this.http.delete(`api/invitations/${token}`);
1417
+ return this.requestVoid(() => this.http.delete(`api/invitations/${token}`));
1055
1418
  }
1056
1419
  };
1057
1420
  var IssuesResource = class extends BaseResource {
@@ -1078,25 +1441,34 @@ var IssuesResource = class extends BaseResource {
1078
1441
  if (options?.q) {
1079
1442
  searchParams.q = options.q;
1080
1443
  }
1081
- const data = await this.http.get(`api/projects/${projectId}/issues`, { searchParams }).json();
1082
- return this.validate(data, offsetPaginatedResponseSchema(issueSchema));
1444
+ return this.request(
1445
+ () => this.http.get(`api/projects/${projectId}/issues`, { searchParams }),
1446
+ offsetPaginatedResponseSchema(issueSchema)
1447
+ );
1083
1448
  }
1084
1449
  /**
1085
1450
  * Get a single issue by ID
1086
1451
  */
1087
1452
  async get(projectId, issueId) {
1088
- const data = await this.http.get(`api/projects/${projectId}/issues/${issueId}`).json();
1089
- return this.validate(data, issueSchema);
1453
+ return this.request(
1454
+ () => this.http.get(`api/projects/${projectId}/issues/${issueId}`),
1455
+ issueSchema
1456
+ );
1090
1457
  }
1091
1458
  /**
1092
1459
  * Update issue state (status, substatus, priority, assignment, …)
1093
1460
  */
1094
1461
  async updateState(projectId, issueId, input) {
1095
- const validatedInput = this.validate(input, updateIssueStateSchema);
1096
- const data = await this.http.patch(`api/projects/${projectId}/issues/${issueId}`, {
1097
- json: validatedInput
1098
- }).json();
1099
- return this.validate(data, issueSchema);
1462
+ const validatedInput = this.validateInput(input, updateIssueStateSchema);
1463
+ if (!validatedInput.success) {
1464
+ return validatedInput;
1465
+ }
1466
+ return this.request(
1467
+ () => this.http.patch(`api/projects/${projectId}/issues/${issueId}`, {
1468
+ json: validatedInput.data
1469
+ }),
1470
+ issueSchema
1471
+ );
1100
1472
  }
1101
1473
  /**
1102
1474
  * Resolve an issue in the next release (suppresses regression until a deploy).
@@ -1110,115 +1482,163 @@ var IssuesResource = class extends BaseResource {
1110
1482
  * Delete an issue
1111
1483
  */
1112
1484
  async delete(projectId, issueId) {
1113
- await this.http.delete(`api/projects/${projectId}/issues/${issueId}`);
1485
+ return this.requestVoid(
1486
+ () => this.http.delete(`api/projects/${projectId}/issues/${issueId}`)
1487
+ );
1114
1488
  }
1115
1489
  /**
1116
1490
  * Bulk-mutate issues (status and/or priority).
1117
1491
  */
1118
1492
  async bulkUpdate(projectId, input) {
1119
- const validatedInput = this.validate(input, bulkUpdateIssuesSchema);
1120
- const data = await this.http.put(`api/projects/${projectId}/issues`, { json: validatedInput }).json();
1121
- return this.validate(data, z.object({ updated: z.number().int() }));
1493
+ const validatedInput = this.validateInput(input, bulkUpdateIssuesSchema);
1494
+ if (!validatedInput.success) {
1495
+ return validatedInput;
1496
+ }
1497
+ return this.request(
1498
+ () => this.http.put(`api/projects/${projectId}/issues`, {
1499
+ json: validatedInput.data
1500
+ }),
1501
+ z.object({ updated: z.number().int() })
1502
+ );
1122
1503
  }
1123
1504
  /**
1124
1505
  * Bulk-delete issues.
1125
1506
  */
1126
1507
  async bulkDelete(projectId, input) {
1127
- const validatedInput = this.validate(input, bulkDeleteIssuesSchema);
1128
- const data = await this.http.delete(`api/projects/${projectId}/issues`, { json: validatedInput }).json();
1129
- return this.validate(data, z.object({ deleted: z.number().int() }));
1508
+ const validatedInput = this.validateInput(input, bulkDeleteIssuesSchema);
1509
+ if (!validatedInput.success) {
1510
+ return validatedInput;
1511
+ }
1512
+ return this.request(
1513
+ () => this.http.delete(`api/projects/${projectId}/issues`, {
1514
+ json: validatedInput.data
1515
+ }),
1516
+ z.object({ deleted: z.number().int() })
1517
+ );
1130
1518
  }
1131
1519
  /**
1132
1520
  * List the grouping hashes that map to an issue.
1133
1521
  */
1134
1522
  async getHashes(projectId, issueId) {
1135
- const data = await this.http.get(`api/projects/${projectId}/issues/${issueId}/hashes`).json();
1136
- return this.validate(data, z.array(issueHashSchema));
1523
+ return this.request(
1524
+ () => this.http.get(`api/projects/${projectId}/issues/${issueId}/hashes`),
1525
+ z.array(issueHashSchema)
1526
+ );
1137
1527
  }
1138
1528
  /**
1139
1529
  * List the distinct values (with counts) for a tag key across the issue.
1140
1530
  * Returns a bare list, one entry per value (Sentry-compatible shape).
1141
1531
  */
1142
1532
  async getTagValues(projectId, issueId, key) {
1143
- const data = await this.http.get(
1144
- `api/projects/${projectId}/issues/${issueId}/tags/${encodeURIComponent(key)}`
1145
- ).json();
1146
- return this.validate(data, z.array(issueTagValueSchema));
1533
+ return this.request(
1534
+ () => this.http.get(
1535
+ `api/projects/${projectId}/issues/${issueId}/tags/${encodeURIComponent(key)}`
1536
+ ),
1537
+ z.array(issueTagValueSchema)
1538
+ );
1147
1539
  }
1148
1540
  /**
1149
1541
  * Fetch per-issue aggregates (unique user count + top tags).
1150
1542
  */
1151
1543
  async getAggregates(projectId, issueId) {
1152
- const data = await this.http.get(`api/projects/${projectId}/issues/${issueId}/aggregates`).json();
1153
- return this.validate(data, issueAggregatesSchema);
1544
+ return this.request(
1545
+ () => this.http.get(`api/projects/${projectId}/issues/${issueId}/aggregates`),
1546
+ issueAggregatesSchema
1547
+ );
1154
1548
  }
1155
1549
  /**
1156
1550
  * Fetch a zero-filled event-count timeseries (24h or 30d).
1157
1551
  */
1158
1552
  async getStats(projectId, issueId, window = "24h") {
1159
- const data = await this.http.get(`api/projects/${projectId}/issues/${issueId}/stats`, {
1160
- searchParams: { window }
1161
- }).json();
1162
- return this.validate(data, issueStatsSchema);
1553
+ return this.request(
1554
+ () => this.http.get(`api/projects/${projectId}/issues/${issueId}/stats`, {
1555
+ searchParams: { window }
1556
+ }),
1557
+ issueStatsSchema
1558
+ );
1163
1559
  }
1164
1560
  /**
1165
1561
  * List an issue's activity log (status changes, comments/notes, …).
1166
1562
  */
1167
1563
  async getActivity(projectId, issueId) {
1168
- const data = await this.http.get(`api/projects/${projectId}/issues/${issueId}/activity`).json();
1169
- return this.validate(data, z.array(activityEntrySchema));
1564
+ return this.request(
1565
+ () => this.http.get(`api/projects/${projectId}/issues/${issueId}/activity`),
1566
+ z.array(activityEntrySchema)
1567
+ );
1170
1568
  }
1171
1569
  /**
1172
1570
  * Add a comment (note) to an issue.
1173
1571
  */
1174
1572
  async addComment(projectId, issueId, input) {
1175
- const validatedInput = this.validate(input, createCommentSchema);
1176
- const data = await this.http.post(`api/projects/${projectId}/issues/${issueId}/comments`, {
1177
- json: validatedInput
1178
- }).json();
1179
- return this.validate(data, activityEntrySchema);
1573
+ const validatedInput = this.validateInput(input, createCommentSchema);
1574
+ if (!validatedInput.success) {
1575
+ return validatedInput;
1576
+ }
1577
+ return this.request(
1578
+ () => this.http.post(`api/projects/${projectId}/issues/${issueId}/comments`, {
1579
+ json: validatedInput.data
1580
+ }),
1581
+ activityEntrySchema
1582
+ );
1180
1583
  }
1181
1584
  /**
1182
1585
  * Set or clear the current user's bookmark on an issue.
1183
1586
  */
1184
1587
  async setBookmark(projectId, issueId, enabled) {
1185
- const data = await this.http.put(`api/projects/${projectId}/issues/${issueId}/bookmark`, {
1186
- json: { enabled }
1187
- }).json();
1188
- return this.validate(data, z.object({ is_bookmarked: z.boolean() }));
1588
+ return this.request(
1589
+ () => this.http.put(`api/projects/${projectId}/issues/${issueId}/bookmark`, {
1590
+ json: { enabled }
1591
+ }),
1592
+ z.object({ is_bookmarked: z.boolean() })
1593
+ );
1189
1594
  }
1190
1595
  /**
1191
1596
  * Set or clear the current user's subscription to an issue.
1192
1597
  */
1193
1598
  async setSubscription(projectId, issueId, enabled) {
1194
- const data = await this.http.put(`api/projects/${projectId}/issues/${issueId}/subscription`, {
1195
- json: { enabled }
1196
- }).json();
1197
- return this.validate(data, z.object({ is_subscribed: z.boolean() }));
1599
+ return this.request(
1600
+ () => this.http.put(
1601
+ `api/projects/${projectId}/issues/${issueId}/subscription`,
1602
+ { json: { enabled } }
1603
+ ),
1604
+ z.object({ is_subscribed: z.boolean() })
1605
+ );
1198
1606
  }
1199
1607
  /**
1200
1608
  * Mark the issue as seen by the current user.
1201
1609
  */
1202
1610
  async markSeen(projectId, issueId) {
1203
- const data = await this.http.post(`api/projects/${projectId}/issues/${issueId}/seen`).json();
1204
- return this.validate(data, z.object({ has_seen: z.boolean() }));
1611
+ return this.request(
1612
+ () => this.http.post(`api/projects/${projectId}/issues/${issueId}/seen`),
1613
+ z.object({ has_seen: z.boolean() })
1614
+ );
1205
1615
  }
1206
1616
  /**
1207
1617
  * List user feedback reports for an issue.
1208
1618
  */
1209
1619
  async listUserReports(projectId, issueId) {
1210
- const data = await this.http.get(`api/projects/${projectId}/issues/${issueId}/user-reports`).json();
1211
- return this.validate(data, z.array(userReportSchema));
1620
+ return this.request(
1621
+ () => this.http.get(
1622
+ `api/projects/${projectId}/issues/${issueId}/user-reports`
1623
+ ),
1624
+ z.array(userReportSchema)
1625
+ );
1212
1626
  }
1213
1627
  /**
1214
1628
  * Attach a user feedback report to an issue.
1215
1629
  */
1216
1630
  async createUserReport(projectId, issueId, input) {
1217
- const validatedInput = this.validate(input, createUserReportSchema);
1218
- const data = await this.http.post(`api/projects/${projectId}/issues/${issueId}/user-reports`, {
1219
- json: validatedInput
1220
- }).json();
1221
- return this.validate(data, userReportSchema);
1631
+ const validatedInput = this.validateInput(input, createUserReportSchema);
1632
+ if (!validatedInput.success) {
1633
+ return validatedInput;
1634
+ }
1635
+ return this.request(
1636
+ () => this.http.post(
1637
+ `api/projects/${projectId}/issues/${issueId}/user-reports`,
1638
+ { json: validatedInput.data }
1639
+ ),
1640
+ userReportSchema
1641
+ );
1222
1642
  }
1223
1643
  };
1224
1644
 
@@ -1242,8 +1662,10 @@ var LogsResource = class extends BaseResource {
1242
1662
  if (options?.trace_id) {
1243
1663
  searchParams.trace_id = options.trace_id;
1244
1664
  }
1245
- const data = await this.http.get(`api/projects/${projectId}/logs`, { searchParams }).json();
1246
- return this.validate(data, offsetPaginatedResponseSchema(logSchema));
1665
+ return this.request(
1666
+ () => this.http.get(`api/projects/${projectId}/logs`, { searchParams }),
1667
+ offsetPaginatedResponseSchema(logSchema)
1668
+ );
1247
1669
  }
1248
1670
  };
1249
1671
 
@@ -1254,8 +1676,10 @@ var MembersResource = class extends BaseResource {
1254
1676
  * @param projectId - ID of the project
1255
1677
  */
1256
1678
  async list(projectId) {
1257
- const data = await this.http.get(`api/projects/${projectId}/members`).json();
1258
- return this.validate(data, projectMemberSchema.array());
1679
+ return this.request(
1680
+ () => this.http.get(`api/projects/${projectId}/members`),
1681
+ projectMemberSchema.array()
1682
+ );
1259
1683
  }
1260
1684
  /**
1261
1685
  * Add or update a project member
@@ -1263,10 +1687,15 @@ var MembersResource = class extends BaseResource {
1263
1687
  * @param input - User ID and per-project role
1264
1688
  */
1265
1689
  async upsert(projectId, input) {
1266
- const validatedInput = this.validate(input, upsertProjectMemberSchema);
1267
- await this.http.put(`api/projects/${projectId}/members`, {
1268
- json: validatedInput
1269
- });
1690
+ const validatedInput = this.validateInput(input, upsertProjectMemberSchema);
1691
+ if (!validatedInput.success) {
1692
+ return validatedInput;
1693
+ }
1694
+ return this.requestVoid(
1695
+ () => this.http.put(`api/projects/${projectId}/members`, {
1696
+ json: validatedInput.data
1697
+ })
1698
+ );
1270
1699
  }
1271
1700
  /**
1272
1701
  * Remove a member from a project
@@ -1274,7 +1703,9 @@ var MembersResource = class extends BaseResource {
1274
1703
  * @param userId - ID of the user to remove
1275
1704
  */
1276
1705
  async remove(projectId, userId) {
1277
- await this.http.delete(`api/projects/${projectId}/members/${userId}`);
1706
+ return this.requestVoid(
1707
+ () => this.http.delete(`api/projects/${projectId}/members/${userId}`)
1708
+ );
1278
1709
  }
1279
1710
  };
1280
1711
 
@@ -1294,39 +1725,56 @@ var ProjectsResource = class extends BaseResource {
1294
1725
  if (options?.order) {
1295
1726
  searchParams.set("order", options.order);
1296
1727
  }
1728
+ if (options?.stats_period) {
1729
+ searchParams.set("stats_period", options.stats_period);
1730
+ }
1297
1731
  const query = searchParams.toString();
1298
1732
  const url = query ? `api/projects?${query}` : "api/projects";
1299
- const data = await this.http.get(url).json();
1300
- return this.validate(data, offsetPaginatedResponseSchema(projectSchema));
1733
+ return this.request(
1734
+ () => this.http.get(url),
1735
+ offsetPaginatedResponseSchema(projectSchema)
1736
+ );
1301
1737
  }
1302
1738
  /**
1303
1739
  * Get a single project by ID
1304
1740
  */
1305
1741
  async get(id) {
1306
- const data = await this.http.get(`api/projects/${id}`).json();
1307
- return this.validate(data, projectSchema);
1742
+ return this.request(
1743
+ () => this.http.get(`api/projects/${id}`),
1744
+ projectSchema
1745
+ );
1308
1746
  }
1309
1747
  /**
1310
1748
  * Create a new project
1311
1749
  */
1312
1750
  async create(input) {
1313
- const validatedInput = this.validate(input, createProjectSchema);
1314
- const data = await this.http.post("api/projects", { json: validatedInput }).json();
1315
- return this.validate(data, projectSchema);
1751
+ const validatedInput = this.validateInput(input, createProjectSchema);
1752
+ if (!validatedInput.success) {
1753
+ return validatedInput;
1754
+ }
1755
+ return this.request(
1756
+ () => this.http.post("api/projects", { json: validatedInput.data }),
1757
+ projectSchema
1758
+ );
1316
1759
  }
1317
1760
  /**
1318
1761
  * Update an existing project
1319
1762
  */
1320
1763
  async update(id, input) {
1321
- const validatedInput = this.validate(input, updateProjectSchema);
1322
- const data = await this.http.patch(`api/projects/${id}`, { json: validatedInput }).json();
1323
- return this.validate(data, projectSchema);
1764
+ const validatedInput = this.validateInput(input, updateProjectSchema);
1765
+ if (!validatedInput.success) {
1766
+ return validatedInput;
1767
+ }
1768
+ return this.request(
1769
+ () => this.http.patch(`api/projects/${id}`, { json: validatedInput.data }),
1770
+ projectSchema
1771
+ );
1324
1772
  }
1325
1773
  /**
1326
1774
  * Delete a project
1327
1775
  */
1328
1776
  async delete(id) {
1329
- await this.http.delete(`api/projects/${id}`);
1777
+ return this.requestVoid(() => this.http.delete(`api/projects/${id}`));
1330
1778
  }
1331
1779
  };
1332
1780
  var ReleasesResource = class extends BaseResource {
@@ -1342,11 +1790,13 @@ var ReleasesResource = class extends BaseResource {
1342
1790
  if (limit !== void 0) {
1343
1791
  searchParams.limit = limit.toString();
1344
1792
  }
1345
- const data = await this.http.get(
1346
- `api/projects/${projectId}/releases/${encodeURIComponent(release)}/new-issues`,
1347
- { searchParams }
1348
- ).json();
1349
- return this.validate(data, z.array(issueSchema));
1793
+ return this.request(
1794
+ () => this.http.get(
1795
+ `api/projects/${projectId}/releases/${encodeURIComponent(release)}/new-issues`,
1796
+ { searchParams }
1797
+ ),
1798
+ z.array(issueSchema)
1799
+ );
1350
1800
  }
1351
1801
  };
1352
1802
 
@@ -1372,9 +1822,10 @@ var SessionsResource = class extends BaseResource {
1372
1822
  if (options?.per_page) {
1373
1823
  searchParams.per_page = String(options.per_page);
1374
1824
  }
1375
- const data = await this.http.get(`api/projects/${projectId}/sessions/stats`, { searchParams }).json();
1376
- return this.validate(
1377
- data,
1825
+ return this.request(
1826
+ () => this.http.get(`api/projects/${projectId}/sessions/stats`, {
1827
+ searchParams
1828
+ }),
1378
1829
  offsetPaginatedResponseSchema(releaseHealthRowSchema)
1379
1830
  );
1380
1831
  }
@@ -1388,8 +1839,12 @@ var SessionsResource = class extends BaseResource {
1388
1839
  if (period) {
1389
1840
  searchParams.period = period;
1390
1841
  }
1391
- const data = await this.http.get(`api/projects/${projectId}/sessions/summary`, { searchParams }).json();
1392
- return this.validate(data, sessionSummarySchema);
1842
+ return this.request(
1843
+ () => this.http.get(`api/projects/${projectId}/sessions/summary`, {
1844
+ searchParams
1845
+ }),
1846
+ sessionSummarySchema
1847
+ );
1393
1848
  }
1394
1849
  /**
1395
1850
  * Get a time-bucketed session trend for a project, aggregated across all
@@ -1406,8 +1861,12 @@ var SessionsResource = class extends BaseResource {
1406
1861
  if (interval !== void 0) {
1407
1862
  searchParams.interval = interval.toString();
1408
1863
  }
1409
- const data = await this.http.get(`api/projects/${projectId}/sessions/timeseries`, { searchParams }).json();
1410
- return this.validate(data, sessionTimeseriesSchema);
1864
+ return this.request(
1865
+ () => this.http.get(`api/projects/${projectId}/sessions/timeseries`, {
1866
+ searchParams
1867
+ }),
1868
+ sessionTimeseriesSchema
1869
+ );
1411
1870
  }
1412
1871
  };
1413
1872
 
@@ -1418,8 +1877,10 @@ var SourceMapsResource = class extends BaseResource {
1418
1877
  * Returns the upload URL, chunk size limits, and accepted artifact types.
1419
1878
  */
1420
1879
  async getChunkUploadCapability(orgSlug) {
1421
- const data = await this.http.get(`api/0/organizations/${orgSlug}/chunk-upload/`).json();
1422
- return this.validate(data, chunkUploadCapabilitySchema);
1880
+ return this.request(
1881
+ () => this.http.get(`api/0/organizations/${orgSlug}/chunk-upload/`),
1882
+ chunkUploadCapabilitySchema
1883
+ );
1423
1884
  }
1424
1885
  /**
1425
1886
  * Upload chunks as multipart/form-data, batching by `chunksPerRequest` (default 64).
@@ -1428,7 +1889,10 @@ var SourceMapsResource = class extends BaseResource {
1428
1889
  */
1429
1890
  async uploadChunks(orgSlug, chunks, chunksPerRequest = 64) {
1430
1891
  if (!Number.isInteger(chunksPerRequest) || chunksPerRequest <= 0) {
1431
- throw new Error("chunksPerRequest must be a positive integer");
1892
+ return Err({
1893
+ kind: "invalid_request",
1894
+ message: "chunksPerRequest must be a positive integer."
1895
+ });
1432
1896
  }
1433
1897
  for (let i = 0; i < chunks.length; i += chunksPerRequest) {
1434
1898
  const batch = chunks.slice(i, i + chunksPerRequest);
@@ -1436,10 +1900,16 @@ var SourceMapsResource = class extends BaseResource {
1436
1900
  for (const chunk of batch) {
1437
1901
  form.append(chunk.hash, chunk.data);
1438
1902
  }
1439
- await this.http.post(`api/0/organizations/${orgSlug}/chunk-upload/`, {
1440
- body: form
1441
- });
1903
+ const uploaded = await this.requestVoid(
1904
+ () => this.http.post(`api/0/organizations/${orgSlug}/chunk-upload/`, {
1905
+ body: form
1906
+ })
1907
+ );
1908
+ if (!uploaded.success) {
1909
+ return uploaded;
1910
+ }
1442
1911
  }
1912
+ return Ok(void 0);
1443
1913
  }
1444
1914
  /**
1445
1915
  * Trigger assembly of a previously uploaded artifact bundle.
@@ -1449,17 +1919,28 @@ var SourceMapsResource = class extends BaseResource {
1449
1919
  * still needs uploading via `uploadChunks`.
1450
1920
  */
1451
1921
  async assembleBundle(orgSlug, input) {
1452
- const data = await this.http.post(`api/0/organizations/${orgSlug}/artifactbundle/assemble/`, {
1453
- json: input
1454
- }).json();
1455
- return this.validate(data, assembleResponseSchema);
1922
+ const validatedInput = this.validateInput(input, assembleInputSchema);
1923
+ if (!validatedInput.success) {
1924
+ return validatedInput;
1925
+ }
1926
+ return this.request(
1927
+ () => this.http.post(
1928
+ `api/0/organizations/${orgSlug}/artifactbundle/assemble/`,
1929
+ { json: validatedInput.data }
1930
+ ),
1931
+ assembleResponseSchema
1932
+ );
1456
1933
  }
1457
1934
  /**
1458
1935
  * List all source map files uploaded for a project.
1459
1936
  */
1460
1937
  async list(orgSlug, projectSlug) {
1461
- const data = await this.http.get(`api/0/projects/${orgSlug}/${projectSlug}/files/source-maps/`).json();
1462
- return this.validate(data, listSourceMapsResponseSchema);
1938
+ return this.request(
1939
+ () => this.http.get(
1940
+ `api/0/projects/${orgSlug}/${projectSlug}/files/source-maps/`
1941
+ ),
1942
+ listSourceMapsResponseSchema
1943
+ );
1463
1944
  }
1464
1945
  };
1465
1946
 
@@ -1489,8 +1970,10 @@ var SpansResource = class extends BaseResource {
1489
1970
  if (options?.operation_type) {
1490
1971
  searchParams.operation_type = options.operation_type;
1491
1972
  }
1492
- const data = await this.http.get(`api/projects/${projectId}/spans`, { searchParams }).json();
1493
- return this.validate(data, offsetPaginatedResponseSchema(spanSchema));
1973
+ return this.request(
1974
+ () => this.http.get(`api/projects/${projectId}/spans`, { searchParams }),
1975
+ offsetPaginatedResponseSchema(spanSchema)
1976
+ );
1494
1977
  }
1495
1978
  };
1496
1979
 
@@ -1515,8 +1998,12 @@ var StatsResource = class extends BaseResource {
1515
1998
  if (interval !== void 0) {
1516
1999
  searchParams.interval = interval.toString();
1517
2000
  }
1518
- const data = await this.http.get(`api/projects/${projectId}/events/stats`, { searchParams }).json();
1519
- return this.validate(data, eventTimeseriesSchema);
2001
+ return this.request(
2002
+ () => this.http.get(`api/projects/${projectId}/events/stats`, {
2003
+ searchParams
2004
+ }),
2005
+ eventTimeseriesSchema
2006
+ );
1520
2007
  }
1521
2008
  /**
1522
2009
  * Get project-wide counters for a window, each paired with the same counter
@@ -1531,8 +2018,12 @@ var StatsResource = class extends BaseResource {
1531
2018
  if (period) {
1532
2019
  searchParams.period = period;
1533
2020
  }
1534
- const data = await this.http.get(`api/projects/${projectId}/stats/summary`, { searchParams }).json();
1535
- return this.validate(data, projectStatsSummarySchema);
2021
+ return this.request(
2022
+ () => this.http.get(`api/projects/${projectId}/stats/summary`, {
2023
+ searchParams
2024
+ }),
2025
+ projectStatsSummarySchema
2026
+ );
1536
2027
  }
1537
2028
  };
1538
2029
  var StorageResource = class extends BaseResource {
@@ -1541,42 +2032,60 @@ var StorageResource = class extends BaseResource {
1541
2032
  * whole-DB size, and exact source-map weight.
1542
2033
  */
1543
2034
  async getSummary() {
1544
- const data = await this.http.get("api/storage/summary").json();
1545
- return this.validate(data, storageSummarySchema);
2035
+ return this.request(
2036
+ () => this.http.get("api/storage/summary"),
2037
+ storageSummarySchema
2038
+ );
1546
2039
  }
1547
2040
  /**
1548
2041
  * Get the per-project storage breakdown (one row per project, including
1549
2042
  * empty ones).
1550
2043
  */
1551
2044
  async getProjects() {
1552
- const data = await this.http.get("api/storage/projects").json();
1553
- return this.validate(data, z.array(projectStorageSchema));
2045
+ return this.request(
2046
+ () => this.http.get("api/storage/projects"),
2047
+ z.array(projectStorageSchema)
2048
+ );
1554
2049
  }
1555
2050
  /**
1556
2051
  * Dry-run: count the rows a cleanup would remove. Mutates nothing — use it to
1557
2052
  * confirm impact before {@link executeCleanup}.
1558
2053
  */
1559
2054
  async previewCleanup(options) {
1560
- const json = cleanupOptionsSchema.parse(options);
1561
- const data = await this.http.post("api/storage/cleanup/preview", { json }).json();
1562
- return this.validate(data, cleanupCountsSchema);
2055
+ const validatedInput = this.validateInput(options, cleanupOptionsSchema);
2056
+ if (!validatedInput.success) {
2057
+ return validatedInput;
2058
+ }
2059
+ return this.request(
2060
+ () => this.http.post("api/storage/cleanup/preview", {
2061
+ json: validatedInput.data
2062
+ }),
2063
+ cleanupCountsSchema
2064
+ );
1563
2065
  }
1564
2066
  /**
1565
2067
  * Execute a cleanup: delete data older than `older_than_days` (optionally
1566
2068
  * scoped to one project) and remove any issue left with zero events.
1567
2069
  */
1568
2070
  async executeCleanup(options) {
1569
- const json = cleanupOptionsSchema.parse(options);
1570
- const data = await this.http.post("api/storage/cleanup", { json }).json();
1571
- return this.validate(data, cleanupCountsSchema);
2071
+ const validatedInput = this.validateInput(options, cleanupOptionsSchema);
2072
+ if (!validatedInput.success) {
2073
+ return validatedInput;
2074
+ }
2075
+ return this.request(
2076
+ () => this.http.post("api/storage/cleanup", { json: validatedInput.data }),
2077
+ cleanupCountsSchema
2078
+ );
1572
2079
  }
1573
2080
  /**
1574
2081
  * Dry-run for {@link gcSourceMaps}: count the orphaned source-map files and
1575
2082
  * bytes a GC would reclaim. Mutates nothing.
1576
2083
  */
1577
2084
  async previewGcSourceMaps() {
1578
- const data = await this.http.post("api/storage/source-maps/gc/preview").json();
1579
- return this.validate(data, sourceMapGcResultSchema);
2085
+ return this.request(
2086
+ () => this.http.post("api/storage/source-maps/gc/preview"),
2087
+ sourceMapGcResultSchema
2088
+ );
1580
2089
  }
1581
2090
  /**
1582
2091
  * Garbage-collect orphaned source maps: files no longer referenced by any
@@ -1584,8 +2093,10 @@ var StorageResource = class extends BaseResource {
1584
2093
  * referenced files.
1585
2094
  */
1586
2095
  async gcSourceMaps() {
1587
- const data = await this.http.post("api/storage/source-maps/gc").json();
1588
- return this.validate(data, sourceMapGcResultSchema);
2096
+ return this.request(
2097
+ () => this.http.post("api/storage/source-maps/gc"),
2098
+ sourceMapGcResultSchema
2099
+ );
1589
2100
  }
1590
2101
  };
1591
2102
 
@@ -1595,8 +2106,10 @@ var TeamResource = class extends BaseResource {
1595
2106
  * List all team members (users)
1596
2107
  */
1597
2108
  async list() {
1598
- const data = await this.http.get("api/team").json();
1599
- return this.validate(data, teamMemberSchema.array());
2109
+ return this.request(
2110
+ () => this.http.get("api/team"),
2111
+ teamMemberSchema.array()
2112
+ );
1600
2113
  }
1601
2114
  /**
1602
2115
  * Change a user's global role
@@ -1604,15 +2117,20 @@ var TeamResource = class extends BaseResource {
1604
2117
  * @param role - New global role to assign
1605
2118
  */
1606
2119
  async updateRole(userId, role) {
1607
- const body = this.validate({ role }, updateUserRoleSchema);
1608
- await this.http.patch(`api/team/${userId}/role`, { json: body });
2120
+ const body = this.validateInput({ role }, updateUserRoleSchema);
2121
+ if (!body.success) {
2122
+ return body;
2123
+ }
2124
+ return this.requestVoid(
2125
+ () => this.http.patch(`api/team/${userId}/role`, { json: body.data })
2126
+ );
1609
2127
  }
1610
2128
  /**
1611
2129
  * Permanently remove a user from the instance.
1612
2130
  * @param userId - ID of the user to delete
1613
2131
  */
1614
2132
  async remove(userId) {
1615
- await this.http.delete(`api/team/${userId}`);
2133
+ return this.requestVoid(() => this.http.delete(`api/team/${userId}`));
1616
2134
  }
1617
2135
  };
1618
2136
 
@@ -1622,8 +2140,10 @@ var TokensResource = class extends BaseResource {
1622
2140
  * List all auth tokens (masked)
1623
2141
  */
1624
2142
  async list() {
1625
- const data = await this.http.get("api/tokens").json();
1626
- return this.validate(data, authTokenSchema.array());
2143
+ return this.request(
2144
+ () => this.http.get("api/tokens"),
2145
+ authTokenSchema.array()
2146
+ );
1627
2147
  }
1628
2148
  /**
1629
2149
  * Get a single auth token by ID (full token value).
@@ -1632,23 +2152,30 @@ var TokensResource = class extends BaseResource {
1632
2152
  * complete token for GET /api/tokens/{id}.
1633
2153
  */
1634
2154
  async get(id) {
1635
- const data = await this.http.get(`api/tokens/${id}`).json();
1636
- return this.validate(data, authTokenCreatedSchema);
2155
+ return this.request(
2156
+ () => this.http.get(`api/tokens/${id}`),
2157
+ authTokenCreatedSchema
2158
+ );
1637
2159
  }
1638
2160
  /**
1639
2161
  * Create a new auth token
1640
2162
  * Note: The full token is only returned once during creation
1641
2163
  */
1642
2164
  async create(input) {
1643
- const validatedInput = this.validate(input, createAuthTokenSchema);
1644
- const data = await this.http.post("api/tokens", { json: validatedInput }).json();
1645
- return this.validate(data, authTokenCreatedSchema);
2165
+ const validatedInput = this.validateInput(input, createAuthTokenSchema);
2166
+ if (!validatedInput.success) {
2167
+ return validatedInput;
2168
+ }
2169
+ return this.request(
2170
+ () => this.http.post("api/tokens", { json: validatedInput.data }),
2171
+ authTokenCreatedSchema
2172
+ );
1646
2173
  }
1647
2174
  /**
1648
2175
  * Delete an auth token
1649
2176
  */
1650
2177
  async delete(id) {
1651
- await this.http.delete(`api/tokens/${id}`);
2178
+ return this.requestVoid(() => this.http.delete(`api/tokens/${id}`));
1652
2179
  }
1653
2180
  };
1654
2181
  var TransactionsResource = class extends BaseResource {
@@ -1678,11 +2205,10 @@ var TransactionsResource = class extends BaseResource {
1678
2205
  if (options?.release) {
1679
2206
  searchParams.release = options.release;
1680
2207
  }
1681
- const data = await this.http.get(`api/projects/${projectId}/transactions`, {
1682
- searchParams
1683
- }).json();
1684
- return this.validate(
1685
- data,
2208
+ return this.request(
2209
+ () => this.http.get(`api/projects/${projectId}/transactions`, {
2210
+ searchParams
2211
+ }),
1686
2212
  offsetPaginatedResponseSchema(transactionSchema)
1687
2213
  );
1688
2214
  }
@@ -1690,8 +2216,12 @@ var TransactionsResource = class extends BaseResource {
1690
2216
  * Get the indexed spans extracted from a transaction, in waterfall order.
1691
2217
  */
1692
2218
  async getSpans(projectId, transactionId) {
1693
- const data = await this.http.get(`api/projects/${projectId}/transactions/${transactionId}/spans`).json();
1694
- return this.validate(data, z.array(spanSchema));
2219
+ return this.request(
2220
+ () => this.http.get(
2221
+ `api/projects/${projectId}/transactions/${transactionId}/spans`
2222
+ ),
2223
+ z.array(spanSchema)
2224
+ );
1695
2225
  }
1696
2226
  /**
1697
2227
  * Get aggregate performance stats (count, p50/p95/p99 latency, failure rate)
@@ -1706,99 +2236,43 @@ var TransactionsResource = class extends BaseResource {
1706
2236
  if (options?.per_page) {
1707
2237
  searchParams.per_page = String(options.per_page);
1708
2238
  }
1709
- const data = await this.http.get(`api/projects/${projectId}/transactions/stats`, { searchParams }).json();
1710
- return this.validate(
1711
- data,
2239
+ return this.request(
2240
+ () => this.http.get(`api/projects/${projectId}/transactions/stats`, {
2241
+ searchParams
2242
+ }),
1712
2243
  offsetPaginatedResponseSchema(transactionStatsSchema)
1713
2244
  );
1714
2245
  }
1715
2246
  /**
1716
2247
  * Get aggregate stats for a single (transaction name, op) group — a direct
1717
- * lookup that works regardless of how many groups the project has. Throws
1718
- * NotFoundError when the group has no transactions.
2248
+ * lookup that works regardless of how many groups the project has. Reports
2249
+ * `not_found` when the group has no transactions.
1719
2250
  */
1720
2251
  async getStatForGroup(projectId, name, op) {
1721
2252
  const searchParams = { name };
1722
2253
  if (op) {
1723
2254
  searchParams.op = op;
1724
2255
  }
1725
- const data = await this.http.get(`api/projects/${projectId}/transactions/stats/group`, {
1726
- searchParams
1727
- }).json();
1728
- return this.validate(data, transactionStatsSchema);
2256
+ return this.request(
2257
+ () => this.http.get(`api/projects/${projectId}/transactions/stats/group`, {
2258
+ searchParams
2259
+ }),
2260
+ transactionStatsSchema
2261
+ );
1729
2262
  }
1730
2263
  /**
1731
2264
  * Get a single transaction by ID with its full Sentry payload (spans,
1732
2265
  * contexts.trace, measurements, tags) for the performance detail view.
1733
2266
  */
1734
2267
  async get(projectId, transactionId) {
1735
- const data = await this.http.get(`api/projects/${projectId}/transactions/${transactionId}`).json();
1736
- return this.validate(data, transactionDetailSchema);
2268
+ return this.request(
2269
+ () => this.http.get(
2270
+ `api/projects/${projectId}/transactions/${transactionId}`
2271
+ ),
2272
+ transactionDetailSchema
2273
+ );
1737
2274
  }
1738
2275
  };
1739
- function transformHttpError(error) {
1740
- const { response } = error;
1741
- const status = response.status;
1742
- let errorMessage = `HTTP ${status} error`;
1743
- const body = error.data;
1744
- if (body) {
1745
- errorMessage = body.error || body.message || errorMessage;
1746
- }
1747
- switch (status) {
1748
- case 400:
1749
- return new BadRequestError(errorMessage);
1750
- case 401:
1751
- return new AuthenticationError(errorMessage);
1752
- case 403:
1753
- return new AuthorizationError(errorMessage);
1754
- case 404:
1755
- return new NotFoundError(errorMessage);
1756
- case 429: {
1757
- const retryAfter = response.headers.get("Retry-After");
1758
- return new RateLimitError(errorMessage, retryAfter ?? void 0);
1759
- }
1760
- case 500:
1761
- case 502:
1762
- case 503:
1763
- case 504:
1764
- return new ServerError(errorMessage, status);
1765
- default:
1766
- return new RustrakError(errorMessage, { statusCode: status });
1767
- }
1768
- }
1769
- function createKyInstance(config) {
1770
- const headers = {
1771
- "Content-Type": "application/json",
1772
- ...config.headers
1773
- };
1774
- if (config.token) {
1775
- headers.Authorization = `Bearer ${config.token}`;
1776
- }
1777
- return ky.create({
1778
- prefix: config.baseUrl,
1779
- timeout: config.timeout ?? 3e4,
1780
- credentials: "include",
1781
- retry: {
1782
- limit: config.maxRetries ?? 2,
1783
- statusCodes: [408, 500, 502, 503, 504],
1784
- methods: ["get", "post", "put", "patch", "delete"]
1785
- },
1786
- headers,
1787
- hooks: {
1788
- beforeError: [
1789
- ({ error }) => {
1790
- if (error.name === "TimeoutError") {
1791
- throw new NetworkError("Request timed out", error);
1792
- }
1793
- if (isHTTPError(error)) {
1794
- throw transformHttpError(error);
1795
- }
1796
- throw new NetworkError(error.message, error);
1797
- }
1798
- ]
1799
- }
1800
- });
1801
- }
1802
2276
 
1803
2277
  // src/client.ts
1804
2278
  var RustrakClient = class {
@@ -1913,6 +2387,6 @@ var RustrakClient = class {
1913
2387
  }
1914
2388
  };
1915
2389
 
1916
- export { AuthenticationError, AuthorizationError, BadRequestError, NetworkError, NotFoundError, RateLimitError, RustrakClient, RustrakError, ServerError, ValidationError };
2390
+ export { Err, FIELD_ERROR_CODES, NETWORK_ERROR_MESSAGE, Ok, RustrakClient, SERVER_ERROR_MESSAGE, TIMEOUT_ERROR_MESSAGE, isRetryable, mapResult, unwrap, unwrapOr };
1917
2391
  //# sourceMappingURL=index.js.map
1918
2392
  //# sourceMappingURL=index.js.map