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