aspi 1.3.0 → 2.1.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/README.md +464 -209
- package/dist/index.cjs +548 -149
- package/dist/index.d.cts +583 -138
- package/dist/index.d.ts +583 -138
- package/dist/index.js +548 -149
- package/package.json +13 -14
package/dist/index.cjs
CHANGED
|
@@ -20,7 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
Aspi: () =>
|
|
23
|
+
Aspi: () => Aspi,
|
|
24
24
|
AspiError: () => AspiError,
|
|
25
25
|
CustomError: () => CustomError,
|
|
26
26
|
Request: () => Request,
|
|
@@ -103,7 +103,7 @@ var CustomError = class extends Error {
|
|
|
103
103
|
}
|
|
104
104
|
};
|
|
105
105
|
var isAspiError = (error) => {
|
|
106
|
-
return error instanceof AspiError;
|
|
106
|
+
return error instanceof AspiError && error.tag === "aspiError";
|
|
107
107
|
};
|
|
108
108
|
var isCustomError = (error) => {
|
|
109
109
|
return error instanceof CustomError;
|
|
@@ -315,19 +315,17 @@ var Request = class {
|
|
|
315
315
|
#shouldBeResult = false;
|
|
316
316
|
#bodySchemaIssues = [];
|
|
317
317
|
#throwOnError = false;
|
|
318
|
-
constructor(method, path, {
|
|
319
|
-
requestConfig,
|
|
320
|
-
retryConfig,
|
|
321
|
-
middlewares,
|
|
322
|
-
errorCbs,
|
|
323
|
-
throwOnError
|
|
324
|
-
}) {
|
|
318
|
+
constructor(method, path, requestOptions) {
|
|
325
319
|
this.#path = path;
|
|
326
|
-
this.#middlewares = middlewares || [];
|
|
327
|
-
this.#localRequestInit = {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
320
|
+
this.#middlewares = requestOptions.middlewares || [];
|
|
321
|
+
this.#localRequestInit = {
|
|
322
|
+
...requestOptions.requestConfig,
|
|
323
|
+
method
|
|
324
|
+
};
|
|
325
|
+
this.#retryConfig = { ...requestOptions?.retryConfig || {} };
|
|
326
|
+
this.#customErrorCbs = { ...requestOptions?.errorCbs || {} };
|
|
327
|
+
this.#throwOnError = requestOptions.throwOnError || false;
|
|
328
|
+
this.#shouldBeResult = requestOptions.shouldBeResult || false;
|
|
331
329
|
}
|
|
332
330
|
/**
|
|
333
331
|
* Sets the base URL for the request.
|
|
@@ -362,18 +360,25 @@ var Request = class {
|
|
|
362
360
|
return this;
|
|
363
361
|
}
|
|
364
362
|
/**
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
* @
|
|
363
|
+
* Merges the provided headers into the request configuration.
|
|
364
|
+
*
|
|
365
|
+
* @param {HeadersInit} headers - An object or iterable containing header name/value pairs.
|
|
366
|
+
* Existing headers are retained unless a key in this object overwrites them.
|
|
367
|
+
* @returns {this} The current {@link Request} instance for method chaining.
|
|
368
|
+
*
|
|
368
369
|
* @example
|
|
370
|
+
* // Set common JSON headers
|
|
369
371
|
* const request = new Request('/users', config);
|
|
370
372
|
* request.setHeaders({
|
|
371
373
|
* 'Content-Type': 'application/json',
|
|
372
|
-
* 'Accept': 'application/json'
|
|
374
|
+
* 'Accept': 'application/json',
|
|
373
375
|
* });
|
|
374
376
|
*/
|
|
375
377
|
setHeaders(headers) {
|
|
376
|
-
this.#localRequestInit.headers =
|
|
378
|
+
this.#localRequestInit.headers = {
|
|
379
|
+
...this.#localRequestInit.headers ?? {},
|
|
380
|
+
...headers
|
|
381
|
+
};
|
|
377
382
|
return this;
|
|
378
383
|
}
|
|
379
384
|
/**
|
|
@@ -387,7 +392,7 @@ var Request = class {
|
|
|
387
392
|
*/
|
|
388
393
|
setHeader(key, value) {
|
|
389
394
|
this.#localRequestInit.headers = {
|
|
390
|
-
...this.#localRequestInit.headers,
|
|
395
|
+
...this.#localRequestInit.headers ?? {},
|
|
391
396
|
[key]: value
|
|
392
397
|
};
|
|
393
398
|
return this;
|
|
@@ -585,29 +590,54 @@ var Request = class {
|
|
|
585
590
|
return this.error("internalServerError", "INTERNAL_SERVER_ERROR", cb);
|
|
586
591
|
}
|
|
587
592
|
/**
|
|
588
|
-
*
|
|
589
|
-
*
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
+
* Register a custom error handler for a specific HTTP status code.
|
|
594
|
+
*
|
|
595
|
+
* When the response matches the provided `status`, the supplied callback `cb`
|
|
596
|
+
* is invoked and its return value is wrapped in a {@link CustomError} with the
|
|
597
|
+
* given `tag`. The method also augments the request's generic `Opts['error']`
|
|
598
|
+
* type so that the custom error is reflected in the resulting `Result`
|
|
599
|
+
* union.
|
|
600
|
+
*
|
|
601
|
+
* @template Tag - A string literal used as the error tag.
|
|
602
|
+
* @template A - The shape of the data returned by the callback.
|
|
603
|
+
*
|
|
604
|
+
* @param {Tag} tag
|
|
605
|
+
* A unique identifier for the custom error. This value becomes the `tag`
|
|
606
|
+
* property of the {@link CustomError} produced by the handler.
|
|
607
|
+
*
|
|
608
|
+
* @param {HttpErrorStatus} status
|
|
609
|
+
* The HTTP status code (e.g. `'BAD_REQUEST'`, `'NOT_FOUND'`) that should
|
|
610
|
+
* trigger the custom handler.
|
|
611
|
+
*
|
|
612
|
+
* @param {CustomErrorCb<TRequest, A>} cb
|
|
613
|
+
* A callback that receives the failing request and response objects and
|
|
614
|
+
* returns an object describing the error payload.
|
|
615
|
+
*
|
|
616
|
+
* @returns {Request<Method, TRequest, Merge<Omit<Opts, 'error'>, { error: { [K in Tag | keyof Opts['error']]: K extends Tag ? CustomError<Tag, A> : Opts['error'][K]; } }>>}
|
|
617
|
+
* The same {@link Request} instance, now typed with the newly added error
|
|
618
|
+
* variant, allowing method‑chaining.
|
|
619
|
+
*
|
|
593
620
|
* @example
|
|
621
|
+
* ```ts
|
|
594
622
|
* const request = new Request('/users', config);
|
|
623
|
+
*
|
|
624
|
+
* // Attach a custom handler for a 400 Bad Request response
|
|
595
625
|
* request
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
*
|
|
599
|
-
*
|
|
600
|
-
*
|
|
601
|
-
*
|
|
602
|
-
*
|
|
603
|
-
*
|
|
626
|
+
* withResult()
|
|
627
|
+
* .error('customError', 'BAD_REQUEST', (ctx) => {
|
|
628
|
+
* console.log('Bad request error:', ctx);
|
|
629
|
+
* return {
|
|
630
|
+
* message: 'Invalid input',
|
|
631
|
+
* details: ctx.response.responseData,
|
|
632
|
+
* };
|
|
633
|
+
* });
|
|
604
634
|
*
|
|
605
|
-
* // Later when
|
|
635
|
+
* // Later, when executing the request:
|
|
606
636
|
* const result = await request.json();
|
|
607
|
-
* if (Result.isErr(result)) {
|
|
608
|
-
* if(result.tag === 'customError') {
|
|
637
|
+
* if (Result.isErr(result) && result.tag === 'customError') {
|
|
609
638
|
* console.log(result.error.data.message); // 'Invalid input'
|
|
610
639
|
* }
|
|
640
|
+
* ```
|
|
611
641
|
*/
|
|
612
642
|
error(tag, status, cb) {
|
|
613
643
|
this.#customErrorCbs[httpErrors[status]] = {
|
|
@@ -617,58 +647,128 @@ var Request = class {
|
|
|
617
647
|
return this;
|
|
618
648
|
}
|
|
619
649
|
/**
|
|
620
|
-
* Sets query parameters for the request URL.
|
|
621
|
-
*
|
|
622
|
-
*
|
|
650
|
+
* Sets the query parameters for the request URL.
|
|
651
|
+
*
|
|
652
|
+
* Accepts any value that can be passed to the `URLSearchParams` constructor:
|
|
653
|
+
* - an object mapping keys to string values,
|
|
654
|
+
* - an iterable of `[key, value]` tuples,
|
|
655
|
+
* - a raw query string, or
|
|
656
|
+
* - an existing {@link URLSearchParams} instance.
|
|
657
|
+
*
|
|
658
|
+
* The supplied parameters replace any previously defined query parameters.
|
|
659
|
+
*
|
|
660
|
+
* @template T - The concrete type of the supplied parameters.
|
|
661
|
+
* @param {T} params - The query parameters to apply.
|
|
662
|
+
* @returns {this} The request instance for method‑chaining.
|
|
663
|
+
*
|
|
623
664
|
* @example
|
|
624
665
|
* const request = new Request('/users', config);
|
|
625
666
|
* request.setQueryParams({
|
|
626
667
|
* page: '1',
|
|
627
668
|
* limit: '10',
|
|
628
|
-
* sort: 'desc'
|
|
669
|
+
* sort: 'desc',
|
|
629
670
|
* });
|
|
671
|
+
*
|
|
672
|
+
* // Using a raw query string
|
|
673
|
+
* request.setQueryParams('page=1&limit=10');
|
|
674
|
+
*
|
|
675
|
+
* // Using an array of entries
|
|
676
|
+
* request.setQueryParams([['page', '1'], ['limit', '10']]);
|
|
677
|
+
*
|
|
678
|
+
* // Using an existing URLSearchParams instance
|
|
679
|
+
* const qp = new URLSearchParams({ page: '1' });
|
|
680
|
+
* request.setQueryParams(qp);
|
|
630
681
|
*/
|
|
631
682
|
setQueryParams(params) {
|
|
632
|
-
|
|
683
|
+
let qp;
|
|
684
|
+
if (params instanceof URLSearchParams) {
|
|
685
|
+
qp = new URLSearchParams(params);
|
|
686
|
+
} else if (typeof params === "string") {
|
|
687
|
+
qp = new URLSearchParams(params);
|
|
688
|
+
} else if (Array.isArray(params)) {
|
|
689
|
+
qp = new URLSearchParams();
|
|
690
|
+
for (const entry of params) {
|
|
691
|
+
if (Array.isArray(entry) && entry.length === 2) {
|
|
692
|
+
qp.append(String(entry[0]), String(entry[1]));
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
} else if (typeof params === "object" && params !== null) {
|
|
696
|
+
qp = new URLSearchParams();
|
|
697
|
+
for (const [key, value] of Object.entries(
|
|
698
|
+
params
|
|
699
|
+
)) {
|
|
700
|
+
qp.append(key, String(value));
|
|
701
|
+
}
|
|
702
|
+
} else {
|
|
703
|
+
qp = new URLSearchParams();
|
|
704
|
+
}
|
|
705
|
+
this.#queryParams = qp;
|
|
633
706
|
return this;
|
|
634
707
|
}
|
|
635
708
|
/**
|
|
636
|
-
* Sets
|
|
637
|
-
*
|
|
638
|
-
* @
|
|
709
|
+
* Sets a validation schema for the response data.
|
|
710
|
+
*
|
|
711
|
+
* The provided {@link StandardSchemaV1} schema will be used to validate the
|
|
712
|
+
* response payload when the request is executed. If validation fails, a
|
|
713
|
+
* `parseError` is added to the request's error type.
|
|
714
|
+
*
|
|
715
|
+
* @template TSchema - A type extending {@link StandardSchemaV1}
|
|
716
|
+
* @param schema - The schema used to validate the response data
|
|
717
|
+
* @returns The request instance for chaining with an updated generic type that
|
|
718
|
+
* includes the schema and a possible `parseError` in the error union
|
|
719
|
+
*
|
|
639
720
|
* @example
|
|
721
|
+
* ```ts
|
|
640
722
|
* import { z } from 'zod';
|
|
641
723
|
*
|
|
642
724
|
* const userSchema = z.object({
|
|
643
725
|
* id: z.number(),
|
|
644
726
|
* name: z.string(),
|
|
645
|
-
* email: z.string().email()
|
|
727
|
+
* email: z.string().email(),
|
|
646
728
|
* });
|
|
647
729
|
*
|
|
648
730
|
* const request = new Request('/users', config);
|
|
649
731
|
* const result = await request
|
|
650
732
|
* .withResult()
|
|
651
|
-
* .
|
|
733
|
+
* .schema(userSchema)
|
|
652
734
|
* .json();
|
|
653
735
|
*
|
|
654
736
|
* if (Result.isOk(result)) {
|
|
655
737
|
* const user = result.value; // Typed and validated user data
|
|
656
738
|
* }
|
|
739
|
+
* ```
|
|
657
740
|
*/
|
|
658
741
|
schema(schema) {
|
|
659
742
|
this.#schema = schema;
|
|
660
743
|
return this;
|
|
661
744
|
}
|
|
662
745
|
/**
|
|
663
|
-
*
|
|
664
|
-
*
|
|
746
|
+
* Configures the request to **throw** an exception when the response status
|
|
747
|
+
* indicates a failure (i.e., `!response.ok`). This disables the “Result”
|
|
748
|
+
* mode (`withResult`) and enables “throwable” mode, causing
|
|
749
|
+
* `await request.json()` (or other response helpers) to either resolve with
|
|
750
|
+
* the successful payload **or** reject with an `AspiError`/`CustomError`.
|
|
751
|
+
*
|
|
752
|
+
* Use this when you prefer traditional `try / catch` error handling over
|
|
753
|
+
* the explicit `Result` type returned by {@link withResult}.
|
|
754
|
+
*
|
|
755
|
+
* @returns This {@link Request} instance, now typed with `throwable: true` and
|
|
756
|
+
* `withResult: false` for proper chaining.
|
|
757
|
+
*
|
|
665
758
|
* @example
|
|
759
|
+
* ```ts
|
|
666
760
|
* const request = new Request('/users', config);
|
|
667
|
-
* const result = await request
|
|
668
|
-
* .withResult()
|
|
669
|
-
* .throwable()
|
|
670
|
-
* .json();
|
|
671
761
|
*
|
|
762
|
+
* try {
|
|
763
|
+
* const user = await request
|
|
764
|
+
* .throwable() // Enable throwing on HTTP errors
|
|
765
|
+
* .json<User>(); // Will throw if the response is not ok
|
|
766
|
+
* console.log(user);
|
|
767
|
+
* } catch (err) {
|
|
768
|
+
* // err is either AspiError or a CustomError returned by a custom handler
|
|
769
|
+
* console.error('Request failed:', err);
|
|
770
|
+
* }
|
|
771
|
+
* ```
|
|
672
772
|
*/
|
|
673
773
|
throwable() {
|
|
674
774
|
this.#shouldBeResult = false;
|
|
@@ -676,43 +776,100 @@ var Request = class {
|
|
|
676
776
|
return this;
|
|
677
777
|
}
|
|
678
778
|
/**
|
|
679
|
-
*
|
|
680
|
-
*
|
|
779
|
+
* Sends the request and parses the response body as JSON.
|
|
780
|
+
*
|
|
781
|
+
* The resolved value of the returned promise varies based on the request mode:
|
|
782
|
+
*
|
|
783
|
+
* - **Result mode** (`withResult()`): resolves to a {@link Result.Result} that
|
|
784
|
+
* contains either an {@link AspiResultOk} with the parsed payload or a union
|
|
785
|
+
* of possible error types (HTTP errors, custom errors, JSON‑parse errors,
|
|
786
|
+
* schema‑validation errors, etc.).
|
|
787
|
+
*
|
|
788
|
+
* - **Throwable mode** (`throwable()`): resolves directly to the successful
|
|
789
|
+
* payload (`AspiPlainResponse`) and throws a {@link AspiError} or
|
|
790
|
+
* {@link CustomError} on failure.
|
|
791
|
+
*
|
|
792
|
+
* - **Default mode** (no explicit mode): resolves to a tuple
|
|
793
|
+
* `[value, error]` where exactly one element is non‑null.
|
|
794
|
+
*
|
|
795
|
+
* @template T - The inferred output type of the response schema (if a schema
|
|
796
|
+
* was supplied via {@link schema}). When no schema is provided `T` defaults
|
|
797
|
+
* to `any`.
|
|
798
|
+
*
|
|
799
|
+
* @returns A promise whose shape depends on the selected mode (see description).
|
|
800
|
+
* In Result mode it is `Result<ResultOk<…>, …>`, in throwable mode it is
|
|
801
|
+
* `AspiPlainResponse<…>`, and otherwise a tuple
|
|
802
|
+
* `[AspiResultOk<…> | null, Error | null]`.
|
|
803
|
+
*
|
|
681
804
|
* @example
|
|
805
|
+
* // Using the Result API
|
|
682
806
|
* const request = new Request('/users', config);
|
|
683
807
|
* const result = await request
|
|
684
808
|
* .setQueryParams({ id: '123' })
|
|
685
|
-
* .
|
|
686
|
-
|
|
687
|
-
* .notFound((error) => ({ message: 'User not found' }))
|
|
809
|
+
* .withResult()
|
|
810
|
+
* .notFound(() => ({ message: 'User not found' }))
|
|
688
811
|
* .json<User>();
|
|
689
812
|
*
|
|
690
813
|
* if (Result.isOk(result)) {
|
|
691
|
-
*
|
|
814
|
+
* // `result.value` has type `User`
|
|
815
|
+
* console.log(result.value);
|
|
692
816
|
* } else {
|
|
693
|
-
* console.error(result.error);
|
|
817
|
+
* console.error(result.error);
|
|
694
818
|
* }
|
|
695
819
|
*/
|
|
696
820
|
async json() {
|
|
697
|
-
const output = await this.#makeRequest(
|
|
698
|
-
|
|
821
|
+
const output = await this.#makeRequest(async (response) => {
|
|
822
|
+
if (response.status === 204 || response.status >= 300 && response.status < 400) {
|
|
823
|
+
return null;
|
|
824
|
+
}
|
|
825
|
+
return response.json().catch(
|
|
699
826
|
(e) => new CustomError("jsonParseError", {
|
|
700
827
|
message: e instanceof Error ? e.message : "Failed to parse JSON"
|
|
701
828
|
})
|
|
702
|
-
)
|
|
703
|
-
|
|
704
|
-
);
|
|
829
|
+
);
|
|
830
|
+
}, true);
|
|
705
831
|
return this.#mapResponse(output);
|
|
706
832
|
}
|
|
707
833
|
/**
|
|
708
|
-
* Executes the request and returns the response as plain text.
|
|
709
|
-
*
|
|
834
|
+
* Executes the request and returns the response body as plain text.
|
|
835
|
+
*
|
|
836
|
+
* The method respects the request mode:
|
|
837
|
+
*
|
|
838
|
+
* - **Result mode** (`withResult()`): resolves to a {@link Result.Result}
|
|
839
|
+
* containing either an {@link AspiResultOk} with the text payload or an
|
|
840
|
+
* error variant.
|
|
841
|
+
* - **Throwable mode** (`throwable()`): resolves directly to the text string
|
|
842
|
+
* and throws on error.
|
|
843
|
+
* - **Default mode**: resolves to a tuple `[value, error]` where exactly one
|
|
844
|
+
* element is `null`.
|
|
845
|
+
*
|
|
846
|
+
* @returns {Promise<
|
|
847
|
+
* Opts['withResult'] extends true
|
|
848
|
+
* ? Result.Result<
|
|
849
|
+
* AspiResultOk<TRequest, string>,
|
|
850
|
+
* AspiError<TRequest> |
|
|
851
|
+
* (Opts extends { error: any } ? Opts['error'][keyof Opts['error']] : never)
|
|
852
|
+
* >
|
|
853
|
+
* : Opts['throwable'] extends true
|
|
854
|
+
* ? AspiPlainResponse<TRequest, string>
|
|
855
|
+
* : [
|
|
856
|
+
* AspiResultOk<TRequest, string> | null,
|
|
857
|
+
* (
|
|
858
|
+
* | AspiError<TRequest>
|
|
859
|
+
* | (Opts extends { error: any } ? Opts['error'][keyof Opts['error']] : never)
|
|
860
|
+
* | null
|
|
861
|
+
* )
|
|
862
|
+
* ]
|
|
863
|
+
* }>
|
|
864
|
+
* A promise that resolves according to the selected request mode.
|
|
865
|
+
*
|
|
710
866
|
* @example
|
|
867
|
+
* ```ts
|
|
711
868
|
* const request = new Request('/data.txt', config);
|
|
712
869
|
* const result = await request
|
|
713
870
|
* .setQueryParams({ version: '1' })
|
|
714
871
|
* .withResult()
|
|
715
|
-
* .notFound((
|
|
872
|
+
* .notFound(() => ({ message: 'Text file not found' }))
|
|
716
873
|
* .text();
|
|
717
874
|
*
|
|
718
875
|
* if (Result.isOk(result)) {
|
|
@@ -720,22 +877,58 @@ var Request = class {
|
|
|
720
877
|
* } else {
|
|
721
878
|
* console.error(result.error); // Error handling
|
|
722
879
|
* }
|
|
880
|
+
* ```
|
|
723
881
|
*/
|
|
724
882
|
async text() {
|
|
725
|
-
const output = await this.#makeRequest(
|
|
726
|
-
|
|
727
|
-
);
|
|
883
|
+
const output = await this.#makeRequest((response) => {
|
|
884
|
+
return response.text();
|
|
885
|
+
});
|
|
728
886
|
return this.#mapResponse(output);
|
|
729
887
|
}
|
|
730
888
|
/**
|
|
731
|
-
* Executes the request and returns the response as a Blob.
|
|
732
|
-
*
|
|
889
|
+
* Executes the request and returns the response body as a {@link Blob}.
|
|
890
|
+
*
|
|
891
|
+
* The shape of the returned {@link Promise} depends on the request mode:
|
|
892
|
+
*
|
|
893
|
+
* - **Result mode** (`withResult()`): resolves to a {@link Result.Result} containing
|
|
894
|
+
* either an {@link AspiResultOk} with `Blob` data or an error variant.
|
|
895
|
+
* - **Throwable mode** (`throwable()`): resolves directly to a {@link Blob}
|
|
896
|
+
* (wrapped in {@link AspiPlainResponse}) and throws on failure.
|
|
897
|
+
* - **Default mode**: resolves to a tuple `[value, error]` where exactly one element
|
|
898
|
+
* is `null`.
|
|
899
|
+
*
|
|
900
|
+
* @returns {Promise<
|
|
901
|
+
* Opts['withResult'] extends true
|
|
902
|
+
* ? Result.Result<
|
|
903
|
+
* AspiResultOk<TRequest, Blob>,
|
|
904
|
+
* | AspiError<TRequest>
|
|
905
|
+
* | (Opts extends { error: any }
|
|
906
|
+
* ? Opts['error'][keyof Opts['error']]
|
|
907
|
+
* : never)
|
|
908
|
+
* >
|
|
909
|
+
* : Opts['throwable'] extends true
|
|
910
|
+
* ? AspiPlainResponse<TRequest, Blob>
|
|
911
|
+
* : [
|
|
912
|
+
* AspiResultOk<TRequest, Blob> | null,
|
|
913
|
+
* (
|
|
914
|
+
* | (
|
|
915
|
+
* | AspiError<TRequest>
|
|
916
|
+
* | (Opts extends { error: any }
|
|
917
|
+
* ? Opts['error'][keyof Opts['error']]
|
|
918
|
+
* : never)
|
|
919
|
+
* )
|
|
920
|
+
* | null
|
|
921
|
+
* ),
|
|
922
|
+
* ]
|
|
923
|
+
* }>
|
|
924
|
+
*
|
|
733
925
|
* @example
|
|
926
|
+
* ```ts
|
|
734
927
|
* const request = new Request('/image.jpg', config);
|
|
735
928
|
* const result = await request
|
|
736
929
|
* .setQueryParams({ size: 'large' })
|
|
737
930
|
* .withResult()
|
|
738
|
-
* .notFound((
|
|
931
|
+
* .notFound(() => ({ message: 'Image not found' }))
|
|
739
932
|
* .blob();
|
|
740
933
|
*
|
|
741
934
|
* if (Result.isOk(result)) {
|
|
@@ -743,43 +936,107 @@ var Request = class {
|
|
|
743
936
|
* } else {
|
|
744
937
|
* console.error(result.error); // Error handling
|
|
745
938
|
* }
|
|
939
|
+
* ```
|
|
746
940
|
*/
|
|
747
941
|
async blob() {
|
|
748
942
|
const output = await this.#makeRequest((response) => response.blob());
|
|
749
943
|
return this.#mapResponse(output);
|
|
750
944
|
}
|
|
751
945
|
#url() {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
946
|
+
if (this.#path.startsWith("http://") || this.#path.startsWith("https://")) {
|
|
947
|
+
const absolute = new URL(this.#path);
|
|
948
|
+
if (this.#queryParams) {
|
|
949
|
+
for (const [k, v] of this.#queryParams.entries()) {
|
|
950
|
+
absolute.searchParams.append(k, v);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
return absolute.toString();
|
|
954
|
+
}
|
|
955
|
+
const passedBaseUrl = typeof this.#localRequestInit.baseUrl === "string" ? this.#localRequestInit.baseUrl : this.#localRequestInit.baseUrl.toString();
|
|
956
|
+
const base = passedBaseUrl.replace(/\/+$/, "");
|
|
957
|
+
const [rawPathAndQuery, fragment] = this.#path.split("#", 2);
|
|
958
|
+
const [rawPath, existingQuery] = rawPathAndQuery.split("?", 2);
|
|
959
|
+
let path = rawPath || "";
|
|
960
|
+
path = path.replace(/^\/+/, "");
|
|
961
|
+
path = path.replace(/\/{2,}/g, "/");
|
|
962
|
+
path = path.replace(/\/+$/, "");
|
|
963
|
+
if (path) {
|
|
964
|
+
path = "/" + path.replace(/^\/+/, "");
|
|
965
|
+
}
|
|
966
|
+
const qs = new URLSearchParams(existingQuery ?? "");
|
|
967
|
+
if (this.#queryParams) {
|
|
968
|
+
for (const [k, v] of this.#queryParams.entries()) {
|
|
969
|
+
qs.append(k, v);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
const queryString = qs.toString();
|
|
973
|
+
let url = base + path;
|
|
974
|
+
if (queryString) {
|
|
975
|
+
url += `?${queryString}`;
|
|
976
|
+
}
|
|
977
|
+
if (fragment) {
|
|
978
|
+
url += `#${fragment}`;
|
|
979
|
+
}
|
|
980
|
+
url = url.replace(/\/+$/, "");
|
|
756
981
|
return url;
|
|
757
982
|
}
|
|
758
983
|
/**
|
|
759
|
-
* Returns the
|
|
760
|
-
*
|
|
984
|
+
* Returns the fully‑qualified URL that will be used for the request.
|
|
985
|
+
*
|
|
986
|
+
* The URL is constructed from the configured base URL, the request path,
|
|
987
|
+
* and any query parameters added via {@link setQueryParams}.
|
|
988
|
+
*
|
|
989
|
+
* @returns {string} The complete request URL.
|
|
990
|
+
*
|
|
761
991
|
* @example
|
|
992
|
+
* ```ts
|
|
762
993
|
* const request = new Request('/users', config);
|
|
763
994
|
* request.setBaseUrl('https://api.example.com');
|
|
764
995
|
* request.setQueryParams({ id: '123' });
|
|
765
|
-
*
|
|
996
|
+
*
|
|
997
|
+
* console.log(request.url());
|
|
998
|
+
* // => 'https://api.example.com/users?id=123'
|
|
999
|
+
* ```
|
|
766
1000
|
*/
|
|
767
1001
|
url() {
|
|
768
1002
|
return this.#url();
|
|
769
1003
|
}
|
|
770
1004
|
/**
|
|
771
|
-
*
|
|
772
|
-
*
|
|
1005
|
+
* Switches the request into **Result** mode.
|
|
1006
|
+
*
|
|
1007
|
+
* In Result mode the response helpers (`json`, `text`, `blob`, …) resolve to a
|
|
1008
|
+
* {@link Result.Result} instance instead of the default tuple
|
|
1009
|
+
* `[value, error]`. This allows callers to use pattern matching
|
|
1010
|
+
* (`Result.isOk`, `Result.isErr`) to handle success and failure.
|
|
1011
|
+
*
|
|
1012
|
+
* Calling `withResult` disables the “throwable” behaviour (see {@link throwable}).
|
|
1013
|
+
*
|
|
1014
|
+
* @returns {Request<
|
|
1015
|
+
* Method,
|
|
1016
|
+
* TRequest,
|
|
1017
|
+
* Merge<
|
|
1018
|
+
* Omit<Opts, 'withResult' | 'throwable'>,
|
|
1019
|
+
* {
|
|
1020
|
+
* withResult: true;
|
|
1021
|
+
* throwable: false;
|
|
1022
|
+
* }
|
|
1023
|
+
* >
|
|
1024
|
+
* >} The same {@link Request} instance, now typed with `withResult: true` and
|
|
1025
|
+
* `throwable: false` for fluent chaining.
|
|
1026
|
+
*
|
|
773
1027
|
* @example
|
|
1028
|
+
* ```ts
|
|
774
1029
|
* const request = new Request('/users', config);
|
|
1030
|
+
*
|
|
775
1031
|
* const result = await request
|
|
776
|
-
* .withResult()
|
|
1032
|
+
* .withResult() // enable Result mode
|
|
777
1033
|
* .json<User>();
|
|
778
1034
|
*
|
|
779
|
-
* // Returns Result type instead of tuple
|
|
780
1035
|
* if (Result.isOk(result)) {
|
|
781
|
-
*
|
|
1036
|
+
* // `result.value` is of type `User`
|
|
1037
|
+
* console.log(result.value);
|
|
782
1038
|
* }
|
|
1039
|
+
* ```
|
|
783
1040
|
*/
|
|
784
1041
|
withResult() {
|
|
785
1042
|
this.#throwOnError = false;
|
|
@@ -799,6 +1056,9 @@ var Request = class {
|
|
|
799
1056
|
return [null, getErrorOrNull(value)];
|
|
800
1057
|
}
|
|
801
1058
|
}
|
|
1059
|
+
#isSuccessResponse(response) {
|
|
1060
|
+
return response.ok || response.status >= 300 && response.status < 400;
|
|
1061
|
+
}
|
|
802
1062
|
async #makeRequest(responseParser, isJson = false) {
|
|
803
1063
|
if (this.#bodySchemaIssues.length) {
|
|
804
1064
|
return err(new CustomError("parseError", this.#bodySchemaIssues));
|
|
@@ -809,20 +1069,23 @@ var Request = class {
|
|
|
809
1069
|
const requestInit = request.requestInit;
|
|
810
1070
|
const url = this.#url();
|
|
811
1071
|
let attempts = 1;
|
|
812
|
-
let response
|
|
813
|
-
|
|
1072
|
+
let response = new Response(null, {
|
|
1073
|
+
status: 500,
|
|
1074
|
+
statusText: "Internal Server Error"
|
|
1075
|
+
});
|
|
1076
|
+
let responseData = null;
|
|
814
1077
|
while (attempts <= retries) {
|
|
815
1078
|
try {
|
|
816
1079
|
response = await fetch(url, requestInit);
|
|
817
1080
|
responseData = await responseParser(response);
|
|
818
|
-
if (responseData instanceof
|
|
1081
|
+
if (responseData instanceof Error) {
|
|
819
1082
|
return err(responseData);
|
|
820
1083
|
}
|
|
821
1084
|
const retryWhileCondition = retryWhile ? await retryWhile(
|
|
822
1085
|
request,
|
|
823
1086
|
this.#makeResponse(response, responseData)
|
|
824
1087
|
) : false;
|
|
825
|
-
if (response
|
|
1088
|
+
if (this.#isSuccessResponse(response) || !retryOn.includes(response.status) && !retryWhileCondition) {
|
|
826
1089
|
break;
|
|
827
1090
|
}
|
|
828
1091
|
if (response.status in this.#customErrorCbs && attempts === retries) {
|
|
@@ -845,9 +1108,31 @@ var Request = class {
|
|
|
845
1108
|
request,
|
|
846
1109
|
this.#makeResponse(response, responseData)
|
|
847
1110
|
) : retryDelay;
|
|
848
|
-
await
|
|
1111
|
+
await this.#abortDelay(delay, request);
|
|
849
1112
|
}
|
|
850
1113
|
} catch (e) {
|
|
1114
|
+
if (e instanceof Error && e.name === "AbortError") {
|
|
1115
|
+
if (500 in this.#customErrorCbs) {
|
|
1116
|
+
const result = this.#customErrorCbs[response.status].cb({
|
|
1117
|
+
request,
|
|
1118
|
+
response: this.#makeResponse(response, responseData)
|
|
1119
|
+
});
|
|
1120
|
+
return err(
|
|
1121
|
+
new CustomError(
|
|
1122
|
+
// @ts-ignore
|
|
1123
|
+
this.#customErrorCbs[response.status].tag,
|
|
1124
|
+
result
|
|
1125
|
+
)
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
return err(
|
|
1129
|
+
new AspiError(
|
|
1130
|
+
e.message,
|
|
1131
|
+
this.#request(),
|
|
1132
|
+
this.#makeResponse(response, responseData)
|
|
1133
|
+
)
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
851
1136
|
if (attempts === retries) throw e;
|
|
852
1137
|
const delay = typeof retryDelay === "function" ? await retryDelay(
|
|
853
1138
|
retries - attempts - 1,
|
|
@@ -855,14 +1140,14 @@ var Request = class {
|
|
|
855
1140
|
request,
|
|
856
1141
|
this.#makeResponse(response, responseData)
|
|
857
1142
|
) : retryDelay;
|
|
858
|
-
await
|
|
1143
|
+
await this.#abortDelay(delay, request);
|
|
859
1144
|
}
|
|
860
1145
|
if (onRetry) {
|
|
861
1146
|
onRetry(request, this.#makeResponse(response, responseData));
|
|
862
1147
|
}
|
|
863
1148
|
attempts++;
|
|
864
1149
|
}
|
|
865
|
-
if (!response
|
|
1150
|
+
if (!this.#isSuccessResponse(response)) {
|
|
866
1151
|
if (response.status in this.#customErrorCbs) {
|
|
867
1152
|
const result = this.#customErrorCbs[response.status].cb({
|
|
868
1153
|
request,
|
|
@@ -932,17 +1217,38 @@ var Request = class {
|
|
|
932
1217
|
);
|
|
933
1218
|
}
|
|
934
1219
|
}
|
|
1220
|
+
#abortDelay(ms, request) {
|
|
1221
|
+
return new Promise((resolve, reject) => {
|
|
1222
|
+
const timer = setTimeout(resolve, ms);
|
|
1223
|
+
const signal = request.requestInit.signal;
|
|
1224
|
+
if (signal) {
|
|
1225
|
+
if (signal.aborted) {
|
|
1226
|
+
clearTimeout(timer);
|
|
1227
|
+
reject(new DOMException("The user aborted a request.", "AbortError"));
|
|
1228
|
+
} else {
|
|
1229
|
+
const abortHandler = () => {
|
|
1230
|
+
clearTimeout(timer);
|
|
1231
|
+
reject(
|
|
1232
|
+
new DOMException("The user aborted a request.", "AbortError")
|
|
1233
|
+
);
|
|
1234
|
+
};
|
|
1235
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
935
1240
|
#request() {
|
|
936
1241
|
let requestInit = this.#localRequestInit;
|
|
937
1242
|
for (const middleware of this.#middlewares) {
|
|
938
|
-
requestInit = middleware(
|
|
1243
|
+
requestInit = middleware(requestInit);
|
|
939
1244
|
}
|
|
940
1245
|
return {
|
|
941
|
-
requestInit
|
|
942
|
-
|
|
1246
|
+
requestInit: {
|
|
1247
|
+
...requestInit,
|
|
1248
|
+
retryConfig: this.#sanitisedRetryConfig()
|
|
1249
|
+
},
|
|
943
1250
|
path: this.#path,
|
|
944
|
-
queryParams: this.#queryParams || null
|
|
945
|
-
retryConfig: this.#sanitisedRetryConfig()
|
|
1251
|
+
queryParams: this.#queryParams || null
|
|
946
1252
|
};
|
|
947
1253
|
}
|
|
948
1254
|
#sanitisedRetryConfig() {
|
|
@@ -963,28 +1269,96 @@ var Request = class {
|
|
|
963
1269
|
return {
|
|
964
1270
|
response,
|
|
965
1271
|
status: response.status,
|
|
966
|
-
|
|
967
|
-
responseData
|
|
1272
|
+
statusLabel: getHttpErrorStatus(response.status),
|
|
1273
|
+
responseData,
|
|
1274
|
+
statusText: response.statusText
|
|
968
1275
|
};
|
|
969
1276
|
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Returns the underlying {@link AspiRequest} object that will be used for the fetch call.
|
|
1279
|
+
*
|
|
1280
|
+
* This method does not perform any network activity; it simply builds and returns the
|
|
1281
|
+
* request configuration, including any applied middlewares, query parameters, etc.
|
|
1282
|
+
*
|
|
1283
|
+
* @returns {AspiRequest<TRequest>} The constructed request object.
|
|
1284
|
+
*/
|
|
1285
|
+
getRequest() {
|
|
1286
|
+
return this.#request();
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* Retrieves the registry of custom error callbacks that have been
|
|
1290
|
+
* registered via {@link error}. The returned object maps HTTP status
|
|
1291
|
+
* codes to their corresponding callback functions and tags.
|
|
1292
|
+
*
|
|
1293
|
+
* @returns {ErrorCallbacks} A shallow copy of the internal error callback registry.
|
|
1294
|
+
*/
|
|
1295
|
+
getErrorCallbackRegistry() {
|
|
1296
|
+
return { ...this.#customErrorCbs };
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Returns whether the request is configured to return a {@link Result.Result}
|
|
1300
|
+
* instead of the default tuple or throwing.
|
|
1301
|
+
*
|
|
1302
|
+
* @returns {boolean} `true` when {@link withResult} has been called.
|
|
1303
|
+
*/
|
|
1304
|
+
isResult() {
|
|
1305
|
+
return this.#shouldBeResult;
|
|
1306
|
+
}
|
|
1307
|
+
/**
|
|
1308
|
+
* Returns whether the request is configured to throw on HTTP errors.
|
|
1309
|
+
*
|
|
1310
|
+
* @returns {boolean} `true` when {@link throwable} has been called.
|
|
1311
|
+
*/
|
|
1312
|
+
isThrowable() {
|
|
1313
|
+
return this.#throwOnError;
|
|
1314
|
+
}
|
|
1315
|
+
/**
|
|
1316
|
+
* Returns the effective retry configuration for this request, including defaulted values.
|
|
1317
|
+
*
|
|
1318
|
+
* The returned object contains:
|
|
1319
|
+
* - `retries` – number of retry attempts (default 1)
|
|
1320
|
+
* - `retryDelay` – delay between attempts in milliseconds or a function that returns a delay
|
|
1321
|
+
* - `retryOn` – array of HTTP status codes that should trigger a retry
|
|
1322
|
+
* - `retryWhile` – optional custom predicate executed after each response
|
|
1323
|
+
* - `onRetry` – optional callback invoked after a retry attempt
|
|
1324
|
+
*
|
|
1325
|
+
* A shallow copy is returned to avoid accidental mutation of the internal state.
|
|
1326
|
+
*
|
|
1327
|
+
* @returns {{
|
|
1328
|
+
* retries: number;
|
|
1329
|
+
* retryDelay: number | ((attempt: number, maxAttempts: number, request: AspiRequest<TRequest>, response: AspiResponse<any, true>) => number);
|
|
1330
|
+
* retryOn: number[];
|
|
1331
|
+
* retryWhile?: (request: AspiRequest<TRequest>, response: AspiResponse<any, true>) => boolean | Promise<boolean>;
|
|
1332
|
+
* onRetry?: (request: AspiRequest<TRequest>, response: AspiResponse<any, true>) => void;
|
|
1333
|
+
* }}
|
|
1334
|
+
*/
|
|
1335
|
+
getRetryConfig() {
|
|
1336
|
+
const cfg = this.#sanitisedRetryConfig();
|
|
1337
|
+
return { ...cfg };
|
|
1338
|
+
}
|
|
970
1339
|
};
|
|
971
1340
|
|
|
972
1341
|
// src/aspi.ts
|
|
973
|
-
var
|
|
1342
|
+
var Aspi = class {
|
|
974
1343
|
#globalRequestInit;
|
|
975
1344
|
#middlewares = [];
|
|
976
1345
|
#retryConfig;
|
|
977
1346
|
#customErrorCbs = {};
|
|
978
1347
|
#throwOnError = false;
|
|
1348
|
+
#shouldBeResult = false;
|
|
979
1349
|
constructor(config) {
|
|
980
|
-
const { retryConfig, ...
|
|
981
|
-
this.#globalRequestInit =
|
|
1350
|
+
const { retryConfig, ...requestInit } = config;
|
|
1351
|
+
this.#globalRequestInit = requestInit;
|
|
982
1352
|
this.#retryConfig = retryConfig;
|
|
983
1353
|
}
|
|
984
1354
|
/**
|
|
985
|
-
* Sets the base URL for all API requests
|
|
986
|
-
*
|
|
987
|
-
*
|
|
1355
|
+
* Sets or overrides the base URL used for all subsequent API requests.
|
|
1356
|
+
*
|
|
1357
|
+
* Accepts either a string or a `URL` instance. If a `URL` object is provided,
|
|
1358
|
+
* it is converted to its string representation.
|
|
1359
|
+
*
|
|
1360
|
+
* @param url - The new base URL.
|
|
1361
|
+
* @returns The current {@link Aspi} instance for chaining.
|
|
988
1362
|
* @example
|
|
989
1363
|
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
990
1364
|
* api.setBaseUrl('https://api.newdomain.com');
|
|
@@ -1013,17 +1387,17 @@ var Aspi2 = class {
|
|
|
1013
1387
|
};
|
|
1014
1388
|
return this;
|
|
1015
1389
|
}
|
|
1016
|
-
#createRequest(method, path
|
|
1390
|
+
#createRequest(method, path) {
|
|
1017
1391
|
return new Request(method, path, {
|
|
1018
1392
|
requestConfig: {
|
|
1019
1393
|
...this.#globalRequestInit,
|
|
1020
|
-
method
|
|
1021
|
-
body
|
|
1394
|
+
method
|
|
1022
1395
|
},
|
|
1023
|
-
retryConfig: this.#retryConfig,
|
|
1024
1396
|
middlewares: this.#middlewares,
|
|
1025
1397
|
errorCbs: this.#customErrorCbs,
|
|
1026
|
-
throwOnError: this.#throwOnError
|
|
1398
|
+
throwOnError: this.#throwOnError,
|
|
1399
|
+
shouldBeResult: this.#shouldBeResult,
|
|
1400
|
+
retryConfig: this.#retryConfig
|
|
1027
1401
|
});
|
|
1028
1402
|
}
|
|
1029
1403
|
/**
|
|
@@ -1038,40 +1412,39 @@ var Aspi2 = class {
|
|
|
1038
1412
|
return this.#createRequest("GET", path);
|
|
1039
1413
|
}
|
|
1040
1414
|
/**
|
|
1041
|
-
* Makes a POST request to the specified path
|
|
1042
|
-
*
|
|
1043
|
-
* @param {
|
|
1044
|
-
* @returns {Request} A Request instance for chaining
|
|
1415
|
+
* Makes a POST request to the specified path.
|
|
1416
|
+
*
|
|
1417
|
+
* @param {string} path - The path to make the request to.
|
|
1418
|
+
* @returns {Request} A Request instance for chaining.
|
|
1419
|
+
*
|
|
1045
1420
|
* @example
|
|
1046
1421
|
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
1047
|
-
* const response = await api.post('/users'
|
|
1422
|
+
* const response = await api.post('/users').json();
|
|
1048
1423
|
*/
|
|
1049
|
-
post(path
|
|
1050
|
-
return this.#createRequest("POST", path
|
|
1424
|
+
post(path) {
|
|
1425
|
+
return this.#createRequest("POST", path);
|
|
1051
1426
|
}
|
|
1052
1427
|
/**
|
|
1053
|
-
* Makes a PUT request to the specified path
|
|
1054
|
-
* @param {string} path - The path to make the request to
|
|
1055
|
-
* @
|
|
1056
|
-
* @returns {Request} A Request instance for chaining
|
|
1428
|
+
* Makes a PUT request to the specified path.
|
|
1429
|
+
* @param {string} path - The path to make the request to.
|
|
1430
|
+
* @returns {Request} A Request instance for chaining.
|
|
1057
1431
|
* @example
|
|
1058
1432
|
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
1059
|
-
* const response = await api.put('/users/1'
|
|
1433
|
+
* const response = await api.put('/users/1').json();
|
|
1060
1434
|
*/
|
|
1061
|
-
put(path
|
|
1062
|
-
return this.#createRequest("PUT", path
|
|
1435
|
+
put(path) {
|
|
1436
|
+
return this.#createRequest("PUT", path);
|
|
1063
1437
|
}
|
|
1064
1438
|
/**
|
|
1065
|
-
* Makes a PATCH request to the specified path
|
|
1066
|
-
* @param {string} path - The path to make the request to
|
|
1067
|
-
* @
|
|
1068
|
-
* @returns {Request} A Request instance for chaining
|
|
1439
|
+
* Makes a PATCH request to the specified path.
|
|
1440
|
+
* @param {string} path - The path to make the request to.
|
|
1441
|
+
* @returns {Request} A Request instance for chaining.
|
|
1069
1442
|
* @example
|
|
1070
1443
|
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
1071
|
-
* const response = await api.patch('/users/1'
|
|
1444
|
+
* const response = await api.patch('/users/1').json();
|
|
1072
1445
|
*/
|
|
1073
|
-
patch(path
|
|
1074
|
-
return this.#createRequest("PATCH", path
|
|
1446
|
+
patch(path) {
|
|
1447
|
+
return this.#createRequest("PATCH", path);
|
|
1075
1448
|
}
|
|
1076
1449
|
/**
|
|
1077
1450
|
* Makes a DELETE request to the specified path
|
|
@@ -1107,8 +1480,9 @@ var Aspi2 = class {
|
|
|
1107
1480
|
return this.#createRequest("OPTIONS", path);
|
|
1108
1481
|
}
|
|
1109
1482
|
/**
|
|
1110
|
-
* Sets multiple headers for all API requests
|
|
1111
|
-
*
|
|
1483
|
+
* Sets multiple headers for all API requests. Existing headers are preserved
|
|
1484
|
+
* and new ones are merged, overriding any duplicate keys.
|
|
1485
|
+
* @param {HeadersInit} headers - An object containing header key-value pairs
|
|
1112
1486
|
* @returns {Aspi} The Aspi instance for chaining
|
|
1113
1487
|
* @example
|
|
1114
1488
|
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
@@ -1118,21 +1492,26 @@ var Aspi2 = class {
|
|
|
1118
1492
|
* });
|
|
1119
1493
|
*/
|
|
1120
1494
|
setHeaders(headers) {
|
|
1121
|
-
this.#globalRequestInit.headers =
|
|
1495
|
+
this.#globalRequestInit.headers = {
|
|
1496
|
+
...this.#globalRequestInit.headers ?? {},
|
|
1497
|
+
...headers
|
|
1498
|
+
};
|
|
1122
1499
|
return this;
|
|
1123
1500
|
}
|
|
1124
1501
|
/**
|
|
1125
|
-
* Sets a single header for all API requests
|
|
1126
|
-
*
|
|
1127
|
-
* @param
|
|
1128
|
-
* @
|
|
1502
|
+
* Sets a single header for all API requests.
|
|
1503
|
+
*
|
|
1504
|
+
* @param key - The header name.
|
|
1505
|
+
* @param value - The header value.
|
|
1506
|
+
* @returns This {@link Aspi} instance for chaining.
|
|
1507
|
+
*
|
|
1129
1508
|
* @example
|
|
1130
1509
|
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
1131
1510
|
* api.setHeader('Content-Type', 'application/json');
|
|
1132
1511
|
*/
|
|
1133
1512
|
setHeader(key, value) {
|
|
1134
1513
|
this.#globalRequestInit.headers = {
|
|
1135
|
-
...this.#globalRequestInit.headers,
|
|
1514
|
+
...this.#globalRequestInit.headers ?? {},
|
|
1136
1515
|
[key]: value
|
|
1137
1516
|
};
|
|
1138
1517
|
return this;
|
|
@@ -1149,18 +1528,28 @@ var Aspi2 = class {
|
|
|
1149
1528
|
return this.setHeader("Authorization", `Bearer ${token}`);
|
|
1150
1529
|
}
|
|
1151
1530
|
/**
|
|
1152
|
-
*
|
|
1153
|
-
*
|
|
1154
|
-
*
|
|
1531
|
+
* Register a request‑transformer middleware.
|
|
1532
|
+
*
|
|
1533
|
+
* The supplied function receives the current request initialization object
|
|
1534
|
+
* (`T`) and must return a request initialization of type `U`. The middleware
|
|
1535
|
+
* is added to the internal middleware chain and will be applied to every
|
|
1536
|
+
* request created by this {@link Aspi} instance.
|
|
1537
|
+
*
|
|
1538
|
+
* @template T - The input request type, extending the current {@link Aspi} request init type.
|
|
1539
|
+
* @template U - The output request type after transformation.
|
|
1540
|
+
* @param {RequestTransformer<T, U>} fn - The middleware function that transforms a request configuration.
|
|
1541
|
+
* @returns {Aspi<U>} A new {@link Aspi} instance typed with the transformed request configuration.
|
|
1155
1542
|
* @example
|
|
1156
1543
|
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
1157
|
-
* api.use((req) => {
|
|
1158
|
-
* // Add custom headers
|
|
1159
|
-
*
|
|
1160
|
-
* ...req
|
|
1161
|
-
*
|
|
1544
|
+
* const apiWithHeaders = api.use((req) => {
|
|
1545
|
+
* // Add custom headers to every request
|
|
1546
|
+
* return {
|
|
1547
|
+
* ...req,
|
|
1548
|
+
* headers: {
|
|
1549
|
+
* ...req.headers,
|
|
1550
|
+
* 'x-custom-header': 'custom-value',
|
|
1551
|
+
* },
|
|
1162
1552
|
* };
|
|
1163
|
-
* return req;
|
|
1164
1553
|
* });
|
|
1165
1554
|
*/
|
|
1166
1555
|
use(fn) {
|
|
@@ -1277,7 +1666,7 @@ var Aspi2 = class {
|
|
|
1277
1666
|
* api.internalServerError((req, res) => ({ message: 'Server error occurred' }));
|
|
1278
1667
|
*/
|
|
1279
1668
|
internalServerError(cb) {
|
|
1280
|
-
return this.error("
|
|
1669
|
+
return this.error("internalServerError", "INTERNAL_SERVER_ERROR", cb);
|
|
1281
1670
|
}
|
|
1282
1671
|
/**
|
|
1283
1672
|
* Sets the aspi to throw an error if the response status is not successful.
|
|
@@ -1292,6 +1681,16 @@ var Aspi2 = class {
|
|
|
1292
1681
|
*/
|
|
1293
1682
|
throwable() {
|
|
1294
1683
|
this.#throwOnError = true;
|
|
1684
|
+
this.#shouldBeResult = false;
|
|
1685
|
+
return this;
|
|
1686
|
+
}
|
|
1687
|
+
/**
|
|
1688
|
+
* Configures the request to return a Result object instead of just the response body.
|
|
1689
|
+
* @returns The Aspi instance with result handling enabled.
|
|
1690
|
+
*/
|
|
1691
|
+
withResult() {
|
|
1692
|
+
this.#shouldBeResult = true;
|
|
1693
|
+
this.#throwOnError = false;
|
|
1295
1694
|
return this;
|
|
1296
1695
|
}
|
|
1297
1696
|
};
|