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.js
CHANGED
|
@@ -75,7 +75,7 @@ var CustomError = class extends Error {
|
|
|
75
75
|
}
|
|
76
76
|
};
|
|
77
77
|
var isAspiError = (error) => {
|
|
78
|
-
return error instanceof AspiError;
|
|
78
|
+
return error instanceof AspiError && error.tag === "aspiError";
|
|
79
79
|
};
|
|
80
80
|
var isCustomError = (error) => {
|
|
81
81
|
return error instanceof CustomError;
|
|
@@ -287,19 +287,17 @@ var Request = class {
|
|
|
287
287
|
#shouldBeResult = false;
|
|
288
288
|
#bodySchemaIssues = [];
|
|
289
289
|
#throwOnError = false;
|
|
290
|
-
constructor(method, path, {
|
|
291
|
-
requestConfig,
|
|
292
|
-
retryConfig,
|
|
293
|
-
middlewares,
|
|
294
|
-
errorCbs,
|
|
295
|
-
throwOnError
|
|
296
|
-
}) {
|
|
290
|
+
constructor(method, path, requestOptions) {
|
|
297
291
|
this.#path = path;
|
|
298
|
-
this.#middlewares = middlewares || [];
|
|
299
|
-
this.#localRequestInit = {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
292
|
+
this.#middlewares = requestOptions.middlewares || [];
|
|
293
|
+
this.#localRequestInit = {
|
|
294
|
+
...requestOptions.requestConfig,
|
|
295
|
+
method
|
|
296
|
+
};
|
|
297
|
+
this.#retryConfig = { ...requestOptions?.retryConfig || {} };
|
|
298
|
+
this.#customErrorCbs = { ...requestOptions?.errorCbs || {} };
|
|
299
|
+
this.#throwOnError = requestOptions.throwOnError || false;
|
|
300
|
+
this.#shouldBeResult = requestOptions.shouldBeResult || false;
|
|
303
301
|
}
|
|
304
302
|
/**
|
|
305
303
|
* Sets the base URL for the request.
|
|
@@ -334,18 +332,25 @@ var Request = class {
|
|
|
334
332
|
return this;
|
|
335
333
|
}
|
|
336
334
|
/**
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
* @
|
|
335
|
+
* Merges the provided headers into the request configuration.
|
|
336
|
+
*
|
|
337
|
+
* @param {HeadersInit} headers - An object or iterable containing header name/value pairs.
|
|
338
|
+
* Existing headers are retained unless a key in this object overwrites them.
|
|
339
|
+
* @returns {this} The current {@link Request} instance for method chaining.
|
|
340
|
+
*
|
|
340
341
|
* @example
|
|
342
|
+
* // Set common JSON headers
|
|
341
343
|
* const request = new Request('/users', config);
|
|
342
344
|
* request.setHeaders({
|
|
343
345
|
* 'Content-Type': 'application/json',
|
|
344
|
-
* 'Accept': 'application/json'
|
|
346
|
+
* 'Accept': 'application/json',
|
|
345
347
|
* });
|
|
346
348
|
*/
|
|
347
349
|
setHeaders(headers) {
|
|
348
|
-
this.#localRequestInit.headers =
|
|
350
|
+
this.#localRequestInit.headers = {
|
|
351
|
+
...this.#localRequestInit.headers ?? {},
|
|
352
|
+
...headers
|
|
353
|
+
};
|
|
349
354
|
return this;
|
|
350
355
|
}
|
|
351
356
|
/**
|
|
@@ -359,7 +364,7 @@ var Request = class {
|
|
|
359
364
|
*/
|
|
360
365
|
setHeader(key, value) {
|
|
361
366
|
this.#localRequestInit.headers = {
|
|
362
|
-
...this.#localRequestInit.headers,
|
|
367
|
+
...this.#localRequestInit.headers ?? {},
|
|
363
368
|
[key]: value
|
|
364
369
|
};
|
|
365
370
|
return this;
|
|
@@ -557,29 +562,54 @@ var Request = class {
|
|
|
557
562
|
return this.error("internalServerError", "INTERNAL_SERVER_ERROR", cb);
|
|
558
563
|
}
|
|
559
564
|
/**
|
|
560
|
-
*
|
|
561
|
-
*
|
|
562
|
-
*
|
|
563
|
-
*
|
|
564
|
-
*
|
|
565
|
+
* Register a custom error handler for a specific HTTP status code.
|
|
566
|
+
*
|
|
567
|
+
* When the response matches the provided `status`, the supplied callback `cb`
|
|
568
|
+
* is invoked and its return value is wrapped in a {@link CustomError} with the
|
|
569
|
+
* given `tag`. The method also augments the request's generic `Opts['error']`
|
|
570
|
+
* type so that the custom error is reflected in the resulting `Result`
|
|
571
|
+
* union.
|
|
572
|
+
*
|
|
573
|
+
* @template Tag - A string literal used as the error tag.
|
|
574
|
+
* @template A - The shape of the data returned by the callback.
|
|
575
|
+
*
|
|
576
|
+
* @param {Tag} tag
|
|
577
|
+
* A unique identifier for the custom error. This value becomes the `tag`
|
|
578
|
+
* property of the {@link CustomError} produced by the handler.
|
|
579
|
+
*
|
|
580
|
+
* @param {HttpErrorStatus} status
|
|
581
|
+
* The HTTP status code (e.g. `'BAD_REQUEST'`, `'NOT_FOUND'`) that should
|
|
582
|
+
* trigger the custom handler.
|
|
583
|
+
*
|
|
584
|
+
* @param {CustomErrorCb<TRequest, A>} cb
|
|
585
|
+
* A callback that receives the failing request and response objects and
|
|
586
|
+
* returns an object describing the error payload.
|
|
587
|
+
*
|
|
588
|
+
* @returns {Request<Method, TRequest, Merge<Omit<Opts, 'error'>, { error: { [K in Tag | keyof Opts['error']]: K extends Tag ? CustomError<Tag, A> : Opts['error'][K]; } }>>}
|
|
589
|
+
* The same {@link Request} instance, now typed with the newly added error
|
|
590
|
+
* variant, allowing method‑chaining.
|
|
591
|
+
*
|
|
565
592
|
* @example
|
|
593
|
+
* ```ts
|
|
566
594
|
* const request = new Request('/users', config);
|
|
595
|
+
*
|
|
596
|
+
* // Attach a custom handler for a 400 Bad Request response
|
|
567
597
|
* request
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
*
|
|
571
|
-
*
|
|
572
|
-
*
|
|
573
|
-
*
|
|
574
|
-
*
|
|
575
|
-
*
|
|
598
|
+
* withResult()
|
|
599
|
+
* .error('customError', 'BAD_REQUEST', (ctx) => {
|
|
600
|
+
* console.log('Bad request error:', ctx);
|
|
601
|
+
* return {
|
|
602
|
+
* message: 'Invalid input',
|
|
603
|
+
* details: ctx.response.responseData,
|
|
604
|
+
* };
|
|
605
|
+
* });
|
|
576
606
|
*
|
|
577
|
-
* // Later when
|
|
607
|
+
* // Later, when executing the request:
|
|
578
608
|
* const result = await request.json();
|
|
579
|
-
* if (Result.isErr(result)) {
|
|
580
|
-
* if(result.tag === 'customError') {
|
|
609
|
+
* if (Result.isErr(result) && result.tag === 'customError') {
|
|
581
610
|
* console.log(result.error.data.message); // 'Invalid input'
|
|
582
611
|
* }
|
|
612
|
+
* ```
|
|
583
613
|
*/
|
|
584
614
|
error(tag, status, cb) {
|
|
585
615
|
this.#customErrorCbs[httpErrors[status]] = {
|
|
@@ -589,58 +619,128 @@ var Request = class {
|
|
|
589
619
|
return this;
|
|
590
620
|
}
|
|
591
621
|
/**
|
|
592
|
-
* Sets query parameters for the request URL.
|
|
593
|
-
*
|
|
594
|
-
*
|
|
622
|
+
* Sets the query parameters for the request URL.
|
|
623
|
+
*
|
|
624
|
+
* Accepts any value that can be passed to the `URLSearchParams` constructor:
|
|
625
|
+
* - an object mapping keys to string values,
|
|
626
|
+
* - an iterable of `[key, value]` tuples,
|
|
627
|
+
* - a raw query string, or
|
|
628
|
+
* - an existing {@link URLSearchParams} instance.
|
|
629
|
+
*
|
|
630
|
+
* The supplied parameters replace any previously defined query parameters.
|
|
631
|
+
*
|
|
632
|
+
* @template T - The concrete type of the supplied parameters.
|
|
633
|
+
* @param {T} params - The query parameters to apply.
|
|
634
|
+
* @returns {this} The request instance for method‑chaining.
|
|
635
|
+
*
|
|
595
636
|
* @example
|
|
596
637
|
* const request = new Request('/users', config);
|
|
597
638
|
* request.setQueryParams({
|
|
598
639
|
* page: '1',
|
|
599
640
|
* limit: '10',
|
|
600
|
-
* sort: 'desc'
|
|
641
|
+
* sort: 'desc',
|
|
601
642
|
* });
|
|
643
|
+
*
|
|
644
|
+
* // Using a raw query string
|
|
645
|
+
* request.setQueryParams('page=1&limit=10');
|
|
646
|
+
*
|
|
647
|
+
* // Using an array of entries
|
|
648
|
+
* request.setQueryParams([['page', '1'], ['limit', '10']]);
|
|
649
|
+
*
|
|
650
|
+
* // Using an existing URLSearchParams instance
|
|
651
|
+
* const qp = new URLSearchParams({ page: '1' });
|
|
652
|
+
* request.setQueryParams(qp);
|
|
602
653
|
*/
|
|
603
654
|
setQueryParams(params) {
|
|
604
|
-
|
|
655
|
+
let qp;
|
|
656
|
+
if (params instanceof URLSearchParams) {
|
|
657
|
+
qp = new URLSearchParams(params);
|
|
658
|
+
} else if (typeof params === "string") {
|
|
659
|
+
qp = new URLSearchParams(params);
|
|
660
|
+
} else if (Array.isArray(params)) {
|
|
661
|
+
qp = new URLSearchParams();
|
|
662
|
+
for (const entry of params) {
|
|
663
|
+
if (Array.isArray(entry) && entry.length === 2) {
|
|
664
|
+
qp.append(String(entry[0]), String(entry[1]));
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
} else if (typeof params === "object" && params !== null) {
|
|
668
|
+
qp = new URLSearchParams();
|
|
669
|
+
for (const [key, value] of Object.entries(
|
|
670
|
+
params
|
|
671
|
+
)) {
|
|
672
|
+
qp.append(key, String(value));
|
|
673
|
+
}
|
|
674
|
+
} else {
|
|
675
|
+
qp = new URLSearchParams();
|
|
676
|
+
}
|
|
677
|
+
this.#queryParams = qp;
|
|
605
678
|
return this;
|
|
606
679
|
}
|
|
607
680
|
/**
|
|
608
|
-
* Sets
|
|
609
|
-
*
|
|
610
|
-
* @
|
|
681
|
+
* Sets a validation schema for the response data.
|
|
682
|
+
*
|
|
683
|
+
* The provided {@link StandardSchemaV1} schema will be used to validate the
|
|
684
|
+
* response payload when the request is executed. If validation fails, a
|
|
685
|
+
* `parseError` is added to the request's error type.
|
|
686
|
+
*
|
|
687
|
+
* @template TSchema - A type extending {@link StandardSchemaV1}
|
|
688
|
+
* @param schema - The schema used to validate the response data
|
|
689
|
+
* @returns The request instance for chaining with an updated generic type that
|
|
690
|
+
* includes the schema and a possible `parseError` in the error union
|
|
691
|
+
*
|
|
611
692
|
* @example
|
|
693
|
+
* ```ts
|
|
612
694
|
* import { z } from 'zod';
|
|
613
695
|
*
|
|
614
696
|
* const userSchema = z.object({
|
|
615
697
|
* id: z.number(),
|
|
616
698
|
* name: z.string(),
|
|
617
|
-
* email: z.string().email()
|
|
699
|
+
* email: z.string().email(),
|
|
618
700
|
* });
|
|
619
701
|
*
|
|
620
702
|
* const request = new Request('/users', config);
|
|
621
703
|
* const result = await request
|
|
622
704
|
* .withResult()
|
|
623
|
-
* .
|
|
705
|
+
* .schema(userSchema)
|
|
624
706
|
* .json();
|
|
625
707
|
*
|
|
626
708
|
* if (Result.isOk(result)) {
|
|
627
709
|
* const user = result.value; // Typed and validated user data
|
|
628
710
|
* }
|
|
711
|
+
* ```
|
|
629
712
|
*/
|
|
630
713
|
schema(schema) {
|
|
631
714
|
this.#schema = schema;
|
|
632
715
|
return this;
|
|
633
716
|
}
|
|
634
717
|
/**
|
|
635
|
-
*
|
|
636
|
-
*
|
|
718
|
+
* Configures the request to **throw** an exception when the response status
|
|
719
|
+
* indicates a failure (i.e., `!response.ok`). This disables the “Result”
|
|
720
|
+
* mode (`withResult`) and enables “throwable” mode, causing
|
|
721
|
+
* `await request.json()` (or other response helpers) to either resolve with
|
|
722
|
+
* the successful payload **or** reject with an `AspiError`/`CustomError`.
|
|
723
|
+
*
|
|
724
|
+
* Use this when you prefer traditional `try / catch` error handling over
|
|
725
|
+
* the explicit `Result` type returned by {@link withResult}.
|
|
726
|
+
*
|
|
727
|
+
* @returns This {@link Request} instance, now typed with `throwable: true` and
|
|
728
|
+
* `withResult: false` for proper chaining.
|
|
729
|
+
*
|
|
637
730
|
* @example
|
|
731
|
+
* ```ts
|
|
638
732
|
* const request = new Request('/users', config);
|
|
639
|
-
* const result = await request
|
|
640
|
-
* .withResult()
|
|
641
|
-
* .throwable()
|
|
642
|
-
* .json();
|
|
643
733
|
*
|
|
734
|
+
* try {
|
|
735
|
+
* const user = await request
|
|
736
|
+
* .throwable() // Enable throwing on HTTP errors
|
|
737
|
+
* .json<User>(); // Will throw if the response is not ok
|
|
738
|
+
* console.log(user);
|
|
739
|
+
* } catch (err) {
|
|
740
|
+
* // err is either AspiError or a CustomError returned by a custom handler
|
|
741
|
+
* console.error('Request failed:', err);
|
|
742
|
+
* }
|
|
743
|
+
* ```
|
|
644
744
|
*/
|
|
645
745
|
throwable() {
|
|
646
746
|
this.#shouldBeResult = false;
|
|
@@ -648,43 +748,100 @@ var Request = class {
|
|
|
648
748
|
return this;
|
|
649
749
|
}
|
|
650
750
|
/**
|
|
651
|
-
*
|
|
652
|
-
*
|
|
751
|
+
* Sends the request and parses the response body as JSON.
|
|
752
|
+
*
|
|
753
|
+
* The resolved value of the returned promise varies based on the request mode:
|
|
754
|
+
*
|
|
755
|
+
* - **Result mode** (`withResult()`): resolves to a {@link Result.Result} that
|
|
756
|
+
* contains either an {@link AspiResultOk} with the parsed payload or a union
|
|
757
|
+
* of possible error types (HTTP errors, custom errors, JSON‑parse errors,
|
|
758
|
+
* schema‑validation errors, etc.).
|
|
759
|
+
*
|
|
760
|
+
* - **Throwable mode** (`throwable()`): resolves directly to the successful
|
|
761
|
+
* payload (`AspiPlainResponse`) and throws a {@link AspiError} or
|
|
762
|
+
* {@link CustomError} on failure.
|
|
763
|
+
*
|
|
764
|
+
* - **Default mode** (no explicit mode): resolves to a tuple
|
|
765
|
+
* `[value, error]` where exactly one element is non‑null.
|
|
766
|
+
*
|
|
767
|
+
* @template T - The inferred output type of the response schema (if a schema
|
|
768
|
+
* was supplied via {@link schema}). When no schema is provided `T` defaults
|
|
769
|
+
* to `any`.
|
|
770
|
+
*
|
|
771
|
+
* @returns A promise whose shape depends on the selected mode (see description).
|
|
772
|
+
* In Result mode it is `Result<ResultOk<…>, …>`, in throwable mode it is
|
|
773
|
+
* `AspiPlainResponse<…>`, and otherwise a tuple
|
|
774
|
+
* `[AspiResultOk<…> | null, Error | null]`.
|
|
775
|
+
*
|
|
653
776
|
* @example
|
|
777
|
+
* // Using the Result API
|
|
654
778
|
* const request = new Request('/users', config);
|
|
655
779
|
* const result = await request
|
|
656
780
|
* .setQueryParams({ id: '123' })
|
|
657
|
-
* .
|
|
658
|
-
|
|
659
|
-
* .notFound((error) => ({ message: 'User not found' }))
|
|
781
|
+
* .withResult()
|
|
782
|
+
* .notFound(() => ({ message: 'User not found' }))
|
|
660
783
|
* .json<User>();
|
|
661
784
|
*
|
|
662
785
|
* if (Result.isOk(result)) {
|
|
663
|
-
*
|
|
786
|
+
* // `result.value` has type `User`
|
|
787
|
+
* console.log(result.value);
|
|
664
788
|
* } else {
|
|
665
|
-
* console.error(result.error);
|
|
789
|
+
* console.error(result.error);
|
|
666
790
|
* }
|
|
667
791
|
*/
|
|
668
792
|
async json() {
|
|
669
|
-
const output = await this.#makeRequest(
|
|
670
|
-
|
|
793
|
+
const output = await this.#makeRequest(async (response) => {
|
|
794
|
+
if (response.status === 204 || response.status >= 300 && response.status < 400) {
|
|
795
|
+
return null;
|
|
796
|
+
}
|
|
797
|
+
return response.json().catch(
|
|
671
798
|
(e) => new CustomError("jsonParseError", {
|
|
672
799
|
message: e instanceof Error ? e.message : "Failed to parse JSON"
|
|
673
800
|
})
|
|
674
|
-
)
|
|
675
|
-
|
|
676
|
-
);
|
|
801
|
+
);
|
|
802
|
+
}, true);
|
|
677
803
|
return this.#mapResponse(output);
|
|
678
804
|
}
|
|
679
805
|
/**
|
|
680
|
-
* Executes the request and returns the response as plain text.
|
|
681
|
-
*
|
|
806
|
+
* Executes the request and returns the response body as plain text.
|
|
807
|
+
*
|
|
808
|
+
* The method respects the request mode:
|
|
809
|
+
*
|
|
810
|
+
* - **Result mode** (`withResult()`): resolves to a {@link Result.Result}
|
|
811
|
+
* containing either an {@link AspiResultOk} with the text payload or an
|
|
812
|
+
* error variant.
|
|
813
|
+
* - **Throwable mode** (`throwable()`): resolves directly to the text string
|
|
814
|
+
* and throws on error.
|
|
815
|
+
* - **Default mode**: resolves to a tuple `[value, error]` where exactly one
|
|
816
|
+
* element is `null`.
|
|
817
|
+
*
|
|
818
|
+
* @returns {Promise<
|
|
819
|
+
* Opts['withResult'] extends true
|
|
820
|
+
* ? Result.Result<
|
|
821
|
+
* AspiResultOk<TRequest, string>,
|
|
822
|
+
* AspiError<TRequest> |
|
|
823
|
+
* (Opts extends { error: any } ? Opts['error'][keyof Opts['error']] : never)
|
|
824
|
+
* >
|
|
825
|
+
* : Opts['throwable'] extends true
|
|
826
|
+
* ? AspiPlainResponse<TRequest, string>
|
|
827
|
+
* : [
|
|
828
|
+
* AspiResultOk<TRequest, string> | null,
|
|
829
|
+
* (
|
|
830
|
+
* | AspiError<TRequest>
|
|
831
|
+
* | (Opts extends { error: any } ? Opts['error'][keyof Opts['error']] : never)
|
|
832
|
+
* | null
|
|
833
|
+
* )
|
|
834
|
+
* ]
|
|
835
|
+
* }>
|
|
836
|
+
* A promise that resolves according to the selected request mode.
|
|
837
|
+
*
|
|
682
838
|
* @example
|
|
839
|
+
* ```ts
|
|
683
840
|
* const request = new Request('/data.txt', config);
|
|
684
841
|
* const result = await request
|
|
685
842
|
* .setQueryParams({ version: '1' })
|
|
686
843
|
* .withResult()
|
|
687
|
-
* .notFound((
|
|
844
|
+
* .notFound(() => ({ message: 'Text file not found' }))
|
|
688
845
|
* .text();
|
|
689
846
|
*
|
|
690
847
|
* if (Result.isOk(result)) {
|
|
@@ -692,22 +849,58 @@ var Request = class {
|
|
|
692
849
|
* } else {
|
|
693
850
|
* console.error(result.error); // Error handling
|
|
694
851
|
* }
|
|
852
|
+
* ```
|
|
695
853
|
*/
|
|
696
854
|
async text() {
|
|
697
|
-
const output = await this.#makeRequest(
|
|
698
|
-
|
|
699
|
-
);
|
|
855
|
+
const output = await this.#makeRequest((response) => {
|
|
856
|
+
return response.text();
|
|
857
|
+
});
|
|
700
858
|
return this.#mapResponse(output);
|
|
701
859
|
}
|
|
702
860
|
/**
|
|
703
|
-
* Executes the request and returns the response as a Blob.
|
|
704
|
-
*
|
|
861
|
+
* Executes the request and returns the response body as a {@link Blob}.
|
|
862
|
+
*
|
|
863
|
+
* The shape of the returned {@link Promise} depends on the request mode:
|
|
864
|
+
*
|
|
865
|
+
* - **Result mode** (`withResult()`): resolves to a {@link Result.Result} containing
|
|
866
|
+
* either an {@link AspiResultOk} with `Blob` data or an error variant.
|
|
867
|
+
* - **Throwable mode** (`throwable()`): resolves directly to a {@link Blob}
|
|
868
|
+
* (wrapped in {@link AspiPlainResponse}) and throws on failure.
|
|
869
|
+
* - **Default mode**: resolves to a tuple `[value, error]` where exactly one element
|
|
870
|
+
* is `null`.
|
|
871
|
+
*
|
|
872
|
+
* @returns {Promise<
|
|
873
|
+
* Opts['withResult'] extends true
|
|
874
|
+
* ? Result.Result<
|
|
875
|
+
* AspiResultOk<TRequest, Blob>,
|
|
876
|
+
* | AspiError<TRequest>
|
|
877
|
+
* | (Opts extends { error: any }
|
|
878
|
+
* ? Opts['error'][keyof Opts['error']]
|
|
879
|
+
* : never)
|
|
880
|
+
* >
|
|
881
|
+
* : Opts['throwable'] extends true
|
|
882
|
+
* ? AspiPlainResponse<TRequest, Blob>
|
|
883
|
+
* : [
|
|
884
|
+
* AspiResultOk<TRequest, Blob> | null,
|
|
885
|
+
* (
|
|
886
|
+
* | (
|
|
887
|
+
* | AspiError<TRequest>
|
|
888
|
+
* | (Opts extends { error: any }
|
|
889
|
+
* ? Opts['error'][keyof Opts['error']]
|
|
890
|
+
* : never)
|
|
891
|
+
* )
|
|
892
|
+
* | null
|
|
893
|
+
* ),
|
|
894
|
+
* ]
|
|
895
|
+
* }>
|
|
896
|
+
*
|
|
705
897
|
* @example
|
|
898
|
+
* ```ts
|
|
706
899
|
* const request = new Request('/image.jpg', config);
|
|
707
900
|
* const result = await request
|
|
708
901
|
* .setQueryParams({ size: 'large' })
|
|
709
902
|
* .withResult()
|
|
710
|
-
* .notFound((
|
|
903
|
+
* .notFound(() => ({ message: 'Image not found' }))
|
|
711
904
|
* .blob();
|
|
712
905
|
*
|
|
713
906
|
* if (Result.isOk(result)) {
|
|
@@ -715,43 +908,107 @@ var Request = class {
|
|
|
715
908
|
* } else {
|
|
716
909
|
* console.error(result.error); // Error handling
|
|
717
910
|
* }
|
|
911
|
+
* ```
|
|
718
912
|
*/
|
|
719
913
|
async blob() {
|
|
720
914
|
const output = await this.#makeRequest((response) => response.blob());
|
|
721
915
|
return this.#mapResponse(output);
|
|
722
916
|
}
|
|
723
917
|
#url() {
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
918
|
+
if (this.#path.startsWith("http://") || this.#path.startsWith("https://")) {
|
|
919
|
+
const absolute = new URL(this.#path);
|
|
920
|
+
if (this.#queryParams) {
|
|
921
|
+
for (const [k, v] of this.#queryParams.entries()) {
|
|
922
|
+
absolute.searchParams.append(k, v);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
return absolute.toString();
|
|
926
|
+
}
|
|
927
|
+
const passedBaseUrl = typeof this.#localRequestInit.baseUrl === "string" ? this.#localRequestInit.baseUrl : this.#localRequestInit.baseUrl.toString();
|
|
928
|
+
const base = passedBaseUrl.replace(/\/+$/, "");
|
|
929
|
+
const [rawPathAndQuery, fragment] = this.#path.split("#", 2);
|
|
930
|
+
const [rawPath, existingQuery] = rawPathAndQuery.split("?", 2);
|
|
931
|
+
let path = rawPath || "";
|
|
932
|
+
path = path.replace(/^\/+/, "");
|
|
933
|
+
path = path.replace(/\/{2,}/g, "/");
|
|
934
|
+
path = path.replace(/\/+$/, "");
|
|
935
|
+
if (path) {
|
|
936
|
+
path = "/" + path.replace(/^\/+/, "");
|
|
937
|
+
}
|
|
938
|
+
const qs = new URLSearchParams(existingQuery ?? "");
|
|
939
|
+
if (this.#queryParams) {
|
|
940
|
+
for (const [k, v] of this.#queryParams.entries()) {
|
|
941
|
+
qs.append(k, v);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
const queryString = qs.toString();
|
|
945
|
+
let url = base + path;
|
|
946
|
+
if (queryString) {
|
|
947
|
+
url += `?${queryString}`;
|
|
948
|
+
}
|
|
949
|
+
if (fragment) {
|
|
950
|
+
url += `#${fragment}`;
|
|
951
|
+
}
|
|
952
|
+
url = url.replace(/\/+$/, "");
|
|
728
953
|
return url;
|
|
729
954
|
}
|
|
730
955
|
/**
|
|
731
|
-
* Returns the
|
|
732
|
-
*
|
|
956
|
+
* Returns the fully‑qualified URL that will be used for the request.
|
|
957
|
+
*
|
|
958
|
+
* The URL is constructed from the configured base URL, the request path,
|
|
959
|
+
* and any query parameters added via {@link setQueryParams}.
|
|
960
|
+
*
|
|
961
|
+
* @returns {string} The complete request URL.
|
|
962
|
+
*
|
|
733
963
|
* @example
|
|
964
|
+
* ```ts
|
|
734
965
|
* const request = new Request('/users', config);
|
|
735
966
|
* request.setBaseUrl('https://api.example.com');
|
|
736
967
|
* request.setQueryParams({ id: '123' });
|
|
737
|
-
*
|
|
968
|
+
*
|
|
969
|
+
* console.log(request.url());
|
|
970
|
+
* // => 'https://api.example.com/users?id=123'
|
|
971
|
+
* ```
|
|
738
972
|
*/
|
|
739
973
|
url() {
|
|
740
974
|
return this.#url();
|
|
741
975
|
}
|
|
742
976
|
/**
|
|
743
|
-
*
|
|
744
|
-
*
|
|
977
|
+
* Switches the request into **Result** mode.
|
|
978
|
+
*
|
|
979
|
+
* In Result mode the response helpers (`json`, `text`, `blob`, …) resolve to a
|
|
980
|
+
* {@link Result.Result} instance instead of the default tuple
|
|
981
|
+
* `[value, error]`. This allows callers to use pattern matching
|
|
982
|
+
* (`Result.isOk`, `Result.isErr`) to handle success and failure.
|
|
983
|
+
*
|
|
984
|
+
* Calling `withResult` disables the “throwable” behaviour (see {@link throwable}).
|
|
985
|
+
*
|
|
986
|
+
* @returns {Request<
|
|
987
|
+
* Method,
|
|
988
|
+
* TRequest,
|
|
989
|
+
* Merge<
|
|
990
|
+
* Omit<Opts, 'withResult' | 'throwable'>,
|
|
991
|
+
* {
|
|
992
|
+
* withResult: true;
|
|
993
|
+
* throwable: false;
|
|
994
|
+
* }
|
|
995
|
+
* >
|
|
996
|
+
* >} The same {@link Request} instance, now typed with `withResult: true` and
|
|
997
|
+
* `throwable: false` for fluent chaining.
|
|
998
|
+
*
|
|
745
999
|
* @example
|
|
1000
|
+
* ```ts
|
|
746
1001
|
* const request = new Request('/users', config);
|
|
1002
|
+
*
|
|
747
1003
|
* const result = await request
|
|
748
|
-
* .withResult()
|
|
1004
|
+
* .withResult() // enable Result mode
|
|
749
1005
|
* .json<User>();
|
|
750
1006
|
*
|
|
751
|
-
* // Returns Result type instead of tuple
|
|
752
1007
|
* if (Result.isOk(result)) {
|
|
753
|
-
*
|
|
1008
|
+
* // `result.value` is of type `User`
|
|
1009
|
+
* console.log(result.value);
|
|
754
1010
|
* }
|
|
1011
|
+
* ```
|
|
755
1012
|
*/
|
|
756
1013
|
withResult() {
|
|
757
1014
|
this.#throwOnError = false;
|
|
@@ -771,6 +1028,9 @@ var Request = class {
|
|
|
771
1028
|
return [null, getErrorOrNull(value)];
|
|
772
1029
|
}
|
|
773
1030
|
}
|
|
1031
|
+
#isSuccessResponse(response) {
|
|
1032
|
+
return response.ok || response.status >= 300 && response.status < 400;
|
|
1033
|
+
}
|
|
774
1034
|
async #makeRequest(responseParser, isJson = false) {
|
|
775
1035
|
if (this.#bodySchemaIssues.length) {
|
|
776
1036
|
return err(new CustomError("parseError", this.#bodySchemaIssues));
|
|
@@ -781,20 +1041,23 @@ var Request = class {
|
|
|
781
1041
|
const requestInit = request.requestInit;
|
|
782
1042
|
const url = this.#url();
|
|
783
1043
|
let attempts = 1;
|
|
784
|
-
let response
|
|
785
|
-
|
|
1044
|
+
let response = new Response(null, {
|
|
1045
|
+
status: 500,
|
|
1046
|
+
statusText: "Internal Server Error"
|
|
1047
|
+
});
|
|
1048
|
+
let responseData = null;
|
|
786
1049
|
while (attempts <= retries) {
|
|
787
1050
|
try {
|
|
788
1051
|
response = await fetch(url, requestInit);
|
|
789
1052
|
responseData = await responseParser(response);
|
|
790
|
-
if (responseData instanceof
|
|
1053
|
+
if (responseData instanceof Error) {
|
|
791
1054
|
return err(responseData);
|
|
792
1055
|
}
|
|
793
1056
|
const retryWhileCondition = retryWhile ? await retryWhile(
|
|
794
1057
|
request,
|
|
795
1058
|
this.#makeResponse(response, responseData)
|
|
796
1059
|
) : false;
|
|
797
|
-
if (response
|
|
1060
|
+
if (this.#isSuccessResponse(response) || !retryOn.includes(response.status) && !retryWhileCondition) {
|
|
798
1061
|
break;
|
|
799
1062
|
}
|
|
800
1063
|
if (response.status in this.#customErrorCbs && attempts === retries) {
|
|
@@ -817,9 +1080,31 @@ var Request = class {
|
|
|
817
1080
|
request,
|
|
818
1081
|
this.#makeResponse(response, responseData)
|
|
819
1082
|
) : retryDelay;
|
|
820
|
-
await
|
|
1083
|
+
await this.#abortDelay(delay, request);
|
|
821
1084
|
}
|
|
822
1085
|
} catch (e) {
|
|
1086
|
+
if (e instanceof Error && e.name === "AbortError") {
|
|
1087
|
+
if (500 in this.#customErrorCbs) {
|
|
1088
|
+
const result = this.#customErrorCbs[response.status].cb({
|
|
1089
|
+
request,
|
|
1090
|
+
response: this.#makeResponse(response, responseData)
|
|
1091
|
+
});
|
|
1092
|
+
return err(
|
|
1093
|
+
new CustomError(
|
|
1094
|
+
// @ts-ignore
|
|
1095
|
+
this.#customErrorCbs[response.status].tag,
|
|
1096
|
+
result
|
|
1097
|
+
)
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
return err(
|
|
1101
|
+
new AspiError(
|
|
1102
|
+
e.message,
|
|
1103
|
+
this.#request(),
|
|
1104
|
+
this.#makeResponse(response, responseData)
|
|
1105
|
+
)
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
823
1108
|
if (attempts === retries) throw e;
|
|
824
1109
|
const delay = typeof retryDelay === "function" ? await retryDelay(
|
|
825
1110
|
retries - attempts - 1,
|
|
@@ -827,14 +1112,14 @@ var Request = class {
|
|
|
827
1112
|
request,
|
|
828
1113
|
this.#makeResponse(response, responseData)
|
|
829
1114
|
) : retryDelay;
|
|
830
|
-
await
|
|
1115
|
+
await this.#abortDelay(delay, request);
|
|
831
1116
|
}
|
|
832
1117
|
if (onRetry) {
|
|
833
1118
|
onRetry(request, this.#makeResponse(response, responseData));
|
|
834
1119
|
}
|
|
835
1120
|
attempts++;
|
|
836
1121
|
}
|
|
837
|
-
if (!response
|
|
1122
|
+
if (!this.#isSuccessResponse(response)) {
|
|
838
1123
|
if (response.status in this.#customErrorCbs) {
|
|
839
1124
|
const result = this.#customErrorCbs[response.status].cb({
|
|
840
1125
|
request,
|
|
@@ -904,17 +1189,38 @@ var Request = class {
|
|
|
904
1189
|
);
|
|
905
1190
|
}
|
|
906
1191
|
}
|
|
1192
|
+
#abortDelay(ms, request) {
|
|
1193
|
+
return new Promise((resolve, reject) => {
|
|
1194
|
+
const timer = setTimeout(resolve, ms);
|
|
1195
|
+
const signal = request.requestInit.signal;
|
|
1196
|
+
if (signal) {
|
|
1197
|
+
if (signal.aborted) {
|
|
1198
|
+
clearTimeout(timer);
|
|
1199
|
+
reject(new DOMException("The user aborted a request.", "AbortError"));
|
|
1200
|
+
} else {
|
|
1201
|
+
const abortHandler = () => {
|
|
1202
|
+
clearTimeout(timer);
|
|
1203
|
+
reject(
|
|
1204
|
+
new DOMException("The user aborted a request.", "AbortError")
|
|
1205
|
+
);
|
|
1206
|
+
};
|
|
1207
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
907
1212
|
#request() {
|
|
908
1213
|
let requestInit = this.#localRequestInit;
|
|
909
1214
|
for (const middleware of this.#middlewares) {
|
|
910
|
-
requestInit = middleware(
|
|
1215
|
+
requestInit = middleware(requestInit);
|
|
911
1216
|
}
|
|
912
1217
|
return {
|
|
913
|
-
requestInit
|
|
914
|
-
|
|
1218
|
+
requestInit: {
|
|
1219
|
+
...requestInit,
|
|
1220
|
+
retryConfig: this.#sanitisedRetryConfig()
|
|
1221
|
+
},
|
|
915
1222
|
path: this.#path,
|
|
916
|
-
queryParams: this.#queryParams || null
|
|
917
|
-
retryConfig: this.#sanitisedRetryConfig()
|
|
1223
|
+
queryParams: this.#queryParams || null
|
|
918
1224
|
};
|
|
919
1225
|
}
|
|
920
1226
|
#sanitisedRetryConfig() {
|
|
@@ -935,28 +1241,96 @@ var Request = class {
|
|
|
935
1241
|
return {
|
|
936
1242
|
response,
|
|
937
1243
|
status: response.status,
|
|
938
|
-
|
|
939
|
-
responseData
|
|
1244
|
+
statusLabel: getHttpErrorStatus(response.status),
|
|
1245
|
+
responseData,
|
|
1246
|
+
statusText: response.statusText
|
|
940
1247
|
};
|
|
941
1248
|
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Returns the underlying {@link AspiRequest} object that will be used for the fetch call.
|
|
1251
|
+
*
|
|
1252
|
+
* This method does not perform any network activity; it simply builds and returns the
|
|
1253
|
+
* request configuration, including any applied middlewares, query parameters, etc.
|
|
1254
|
+
*
|
|
1255
|
+
* @returns {AspiRequest<TRequest>} The constructed request object.
|
|
1256
|
+
*/
|
|
1257
|
+
getRequest() {
|
|
1258
|
+
return this.#request();
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Retrieves the registry of custom error callbacks that have been
|
|
1262
|
+
* registered via {@link error}. The returned object maps HTTP status
|
|
1263
|
+
* codes to their corresponding callback functions and tags.
|
|
1264
|
+
*
|
|
1265
|
+
* @returns {ErrorCallbacks} A shallow copy of the internal error callback registry.
|
|
1266
|
+
*/
|
|
1267
|
+
getErrorCallbackRegistry() {
|
|
1268
|
+
return { ...this.#customErrorCbs };
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Returns whether the request is configured to return a {@link Result.Result}
|
|
1272
|
+
* instead of the default tuple or throwing.
|
|
1273
|
+
*
|
|
1274
|
+
* @returns {boolean} `true` when {@link withResult} has been called.
|
|
1275
|
+
*/
|
|
1276
|
+
isResult() {
|
|
1277
|
+
return this.#shouldBeResult;
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Returns whether the request is configured to throw on HTTP errors.
|
|
1281
|
+
*
|
|
1282
|
+
* @returns {boolean} `true` when {@link throwable} has been called.
|
|
1283
|
+
*/
|
|
1284
|
+
isThrowable() {
|
|
1285
|
+
return this.#throwOnError;
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Returns the effective retry configuration for this request, including defaulted values.
|
|
1289
|
+
*
|
|
1290
|
+
* The returned object contains:
|
|
1291
|
+
* - `retries` – number of retry attempts (default 1)
|
|
1292
|
+
* - `retryDelay` – delay between attempts in milliseconds or a function that returns a delay
|
|
1293
|
+
* - `retryOn` – array of HTTP status codes that should trigger a retry
|
|
1294
|
+
* - `retryWhile` – optional custom predicate executed after each response
|
|
1295
|
+
* - `onRetry` – optional callback invoked after a retry attempt
|
|
1296
|
+
*
|
|
1297
|
+
* A shallow copy is returned to avoid accidental mutation of the internal state.
|
|
1298
|
+
*
|
|
1299
|
+
* @returns {{
|
|
1300
|
+
* retries: number;
|
|
1301
|
+
* retryDelay: number | ((attempt: number, maxAttempts: number, request: AspiRequest<TRequest>, response: AspiResponse<any, true>) => number);
|
|
1302
|
+
* retryOn: number[];
|
|
1303
|
+
* retryWhile?: (request: AspiRequest<TRequest>, response: AspiResponse<any, true>) => boolean | Promise<boolean>;
|
|
1304
|
+
* onRetry?: (request: AspiRequest<TRequest>, response: AspiResponse<any, true>) => void;
|
|
1305
|
+
* }}
|
|
1306
|
+
*/
|
|
1307
|
+
getRetryConfig() {
|
|
1308
|
+
const cfg = this.#sanitisedRetryConfig();
|
|
1309
|
+
return { ...cfg };
|
|
1310
|
+
}
|
|
942
1311
|
};
|
|
943
1312
|
|
|
944
1313
|
// src/aspi.ts
|
|
945
|
-
var
|
|
1314
|
+
var Aspi = class {
|
|
946
1315
|
#globalRequestInit;
|
|
947
1316
|
#middlewares = [];
|
|
948
1317
|
#retryConfig;
|
|
949
1318
|
#customErrorCbs = {};
|
|
950
1319
|
#throwOnError = false;
|
|
1320
|
+
#shouldBeResult = false;
|
|
951
1321
|
constructor(config) {
|
|
952
|
-
const { retryConfig, ...
|
|
953
|
-
this.#globalRequestInit =
|
|
1322
|
+
const { retryConfig, ...requestInit } = config;
|
|
1323
|
+
this.#globalRequestInit = requestInit;
|
|
954
1324
|
this.#retryConfig = retryConfig;
|
|
955
1325
|
}
|
|
956
1326
|
/**
|
|
957
|
-
* Sets the base URL for all API requests
|
|
958
|
-
*
|
|
959
|
-
*
|
|
1327
|
+
* Sets or overrides the base URL used for all subsequent API requests.
|
|
1328
|
+
*
|
|
1329
|
+
* Accepts either a string or a `URL` instance. If a `URL` object is provided,
|
|
1330
|
+
* it is converted to its string representation.
|
|
1331
|
+
*
|
|
1332
|
+
* @param url - The new base URL.
|
|
1333
|
+
* @returns The current {@link Aspi} instance for chaining.
|
|
960
1334
|
* @example
|
|
961
1335
|
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
962
1336
|
* api.setBaseUrl('https://api.newdomain.com');
|
|
@@ -985,17 +1359,17 @@ var Aspi2 = class {
|
|
|
985
1359
|
};
|
|
986
1360
|
return this;
|
|
987
1361
|
}
|
|
988
|
-
#createRequest(method, path
|
|
1362
|
+
#createRequest(method, path) {
|
|
989
1363
|
return new Request(method, path, {
|
|
990
1364
|
requestConfig: {
|
|
991
1365
|
...this.#globalRequestInit,
|
|
992
|
-
method
|
|
993
|
-
body
|
|
1366
|
+
method
|
|
994
1367
|
},
|
|
995
|
-
retryConfig: this.#retryConfig,
|
|
996
1368
|
middlewares: this.#middlewares,
|
|
997
1369
|
errorCbs: this.#customErrorCbs,
|
|
998
|
-
throwOnError: this.#throwOnError
|
|
1370
|
+
throwOnError: this.#throwOnError,
|
|
1371
|
+
shouldBeResult: this.#shouldBeResult,
|
|
1372
|
+
retryConfig: this.#retryConfig
|
|
999
1373
|
});
|
|
1000
1374
|
}
|
|
1001
1375
|
/**
|
|
@@ -1010,40 +1384,39 @@ var Aspi2 = class {
|
|
|
1010
1384
|
return this.#createRequest("GET", path);
|
|
1011
1385
|
}
|
|
1012
1386
|
/**
|
|
1013
|
-
* Makes a POST request to the specified path
|
|
1014
|
-
*
|
|
1015
|
-
* @param {
|
|
1016
|
-
* @returns {Request} A Request instance for chaining
|
|
1387
|
+
* Makes a POST request to the specified path.
|
|
1388
|
+
*
|
|
1389
|
+
* @param {string} path - The path to make the request to.
|
|
1390
|
+
* @returns {Request} A Request instance for chaining.
|
|
1391
|
+
*
|
|
1017
1392
|
* @example
|
|
1018
1393
|
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
1019
|
-
* const response = await api.post('/users'
|
|
1394
|
+
* const response = await api.post('/users').json();
|
|
1020
1395
|
*/
|
|
1021
|
-
post(path
|
|
1022
|
-
return this.#createRequest("POST", path
|
|
1396
|
+
post(path) {
|
|
1397
|
+
return this.#createRequest("POST", path);
|
|
1023
1398
|
}
|
|
1024
1399
|
/**
|
|
1025
|
-
* Makes a PUT request to the specified path
|
|
1026
|
-
* @param {string} path - The path to make the request to
|
|
1027
|
-
* @
|
|
1028
|
-
* @returns {Request} A Request instance for chaining
|
|
1400
|
+
* Makes a PUT request to the specified path.
|
|
1401
|
+
* @param {string} path - The path to make the request to.
|
|
1402
|
+
* @returns {Request} A Request instance for chaining.
|
|
1029
1403
|
* @example
|
|
1030
1404
|
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
1031
|
-
* const response = await api.put('/users/1'
|
|
1405
|
+
* const response = await api.put('/users/1').json();
|
|
1032
1406
|
*/
|
|
1033
|
-
put(path
|
|
1034
|
-
return this.#createRequest("PUT", path
|
|
1407
|
+
put(path) {
|
|
1408
|
+
return this.#createRequest("PUT", path);
|
|
1035
1409
|
}
|
|
1036
1410
|
/**
|
|
1037
|
-
* Makes a PATCH request to the specified path
|
|
1038
|
-
* @param {string} path - The path to make the request to
|
|
1039
|
-
* @
|
|
1040
|
-
* @returns {Request} A Request instance for chaining
|
|
1411
|
+
* Makes a PATCH request to the specified path.
|
|
1412
|
+
* @param {string} path - The path to make the request to.
|
|
1413
|
+
* @returns {Request} A Request instance for chaining.
|
|
1041
1414
|
* @example
|
|
1042
1415
|
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
1043
|
-
* const response = await api.patch('/users/1'
|
|
1416
|
+
* const response = await api.patch('/users/1').json();
|
|
1044
1417
|
*/
|
|
1045
|
-
patch(path
|
|
1046
|
-
return this.#createRequest("PATCH", path
|
|
1418
|
+
patch(path) {
|
|
1419
|
+
return this.#createRequest("PATCH", path);
|
|
1047
1420
|
}
|
|
1048
1421
|
/**
|
|
1049
1422
|
* Makes a DELETE request to the specified path
|
|
@@ -1079,8 +1452,9 @@ var Aspi2 = class {
|
|
|
1079
1452
|
return this.#createRequest("OPTIONS", path);
|
|
1080
1453
|
}
|
|
1081
1454
|
/**
|
|
1082
|
-
* Sets multiple headers for all API requests
|
|
1083
|
-
*
|
|
1455
|
+
* Sets multiple headers for all API requests. Existing headers are preserved
|
|
1456
|
+
* and new ones are merged, overriding any duplicate keys.
|
|
1457
|
+
* @param {HeadersInit} headers - An object containing header key-value pairs
|
|
1084
1458
|
* @returns {Aspi} The Aspi instance for chaining
|
|
1085
1459
|
* @example
|
|
1086
1460
|
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
@@ -1090,21 +1464,26 @@ var Aspi2 = class {
|
|
|
1090
1464
|
* });
|
|
1091
1465
|
*/
|
|
1092
1466
|
setHeaders(headers) {
|
|
1093
|
-
this.#globalRequestInit.headers =
|
|
1467
|
+
this.#globalRequestInit.headers = {
|
|
1468
|
+
...this.#globalRequestInit.headers ?? {},
|
|
1469
|
+
...headers
|
|
1470
|
+
};
|
|
1094
1471
|
return this;
|
|
1095
1472
|
}
|
|
1096
1473
|
/**
|
|
1097
|
-
* Sets a single header for all API requests
|
|
1098
|
-
*
|
|
1099
|
-
* @param
|
|
1100
|
-
* @
|
|
1474
|
+
* Sets a single header for all API requests.
|
|
1475
|
+
*
|
|
1476
|
+
* @param key - The header name.
|
|
1477
|
+
* @param value - The header value.
|
|
1478
|
+
* @returns This {@link Aspi} instance for chaining.
|
|
1479
|
+
*
|
|
1101
1480
|
* @example
|
|
1102
1481
|
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
1103
1482
|
* api.setHeader('Content-Type', 'application/json');
|
|
1104
1483
|
*/
|
|
1105
1484
|
setHeader(key, value) {
|
|
1106
1485
|
this.#globalRequestInit.headers = {
|
|
1107
|
-
...this.#globalRequestInit.headers,
|
|
1486
|
+
...this.#globalRequestInit.headers ?? {},
|
|
1108
1487
|
[key]: value
|
|
1109
1488
|
};
|
|
1110
1489
|
return this;
|
|
@@ -1121,18 +1500,28 @@ var Aspi2 = class {
|
|
|
1121
1500
|
return this.setHeader("Authorization", `Bearer ${token}`);
|
|
1122
1501
|
}
|
|
1123
1502
|
/**
|
|
1124
|
-
*
|
|
1125
|
-
*
|
|
1126
|
-
*
|
|
1503
|
+
* Register a request‑transformer middleware.
|
|
1504
|
+
*
|
|
1505
|
+
* The supplied function receives the current request initialization object
|
|
1506
|
+
* (`T`) and must return a request initialization of type `U`. The middleware
|
|
1507
|
+
* is added to the internal middleware chain and will be applied to every
|
|
1508
|
+
* request created by this {@link Aspi} instance.
|
|
1509
|
+
*
|
|
1510
|
+
* @template T - The input request type, extending the current {@link Aspi} request init type.
|
|
1511
|
+
* @template U - The output request type after transformation.
|
|
1512
|
+
* @param {RequestTransformer<T, U>} fn - The middleware function that transforms a request configuration.
|
|
1513
|
+
* @returns {Aspi<U>} A new {@link Aspi} instance typed with the transformed request configuration.
|
|
1127
1514
|
* @example
|
|
1128
1515
|
* const api = new Aspi({ baseUrl: 'https://api.example.com' });
|
|
1129
|
-
* api.use((req) => {
|
|
1130
|
-
* // Add custom headers
|
|
1131
|
-
*
|
|
1132
|
-
* ...req
|
|
1133
|
-
*
|
|
1516
|
+
* const apiWithHeaders = api.use((req) => {
|
|
1517
|
+
* // Add custom headers to every request
|
|
1518
|
+
* return {
|
|
1519
|
+
* ...req,
|
|
1520
|
+
* headers: {
|
|
1521
|
+
* ...req.headers,
|
|
1522
|
+
* 'x-custom-header': 'custom-value',
|
|
1523
|
+
* },
|
|
1134
1524
|
* };
|
|
1135
|
-
* return req;
|
|
1136
1525
|
* });
|
|
1137
1526
|
*/
|
|
1138
1527
|
use(fn) {
|
|
@@ -1249,7 +1638,7 @@ var Aspi2 = class {
|
|
|
1249
1638
|
* api.internalServerError((req, res) => ({ message: 'Server error occurred' }));
|
|
1250
1639
|
*/
|
|
1251
1640
|
internalServerError(cb) {
|
|
1252
|
-
return this.error("
|
|
1641
|
+
return this.error("internalServerError", "INTERNAL_SERVER_ERROR", cb);
|
|
1253
1642
|
}
|
|
1254
1643
|
/**
|
|
1255
1644
|
* Sets the aspi to throw an error if the response status is not successful.
|
|
@@ -1264,11 +1653,21 @@ var Aspi2 = class {
|
|
|
1264
1653
|
*/
|
|
1265
1654
|
throwable() {
|
|
1266
1655
|
this.#throwOnError = true;
|
|
1656
|
+
this.#shouldBeResult = false;
|
|
1657
|
+
return this;
|
|
1658
|
+
}
|
|
1659
|
+
/**
|
|
1660
|
+
* Configures the request to return a Result object instead of just the response body.
|
|
1661
|
+
* @returns The Aspi instance with result handling enabled.
|
|
1662
|
+
*/
|
|
1663
|
+
withResult() {
|
|
1664
|
+
this.#shouldBeResult = true;
|
|
1665
|
+
this.#throwOnError = false;
|
|
1267
1666
|
return this;
|
|
1268
1667
|
}
|
|
1269
1668
|
};
|
|
1270
1669
|
export {
|
|
1271
|
-
|
|
1670
|
+
Aspi,
|
|
1272
1671
|
AspiError,
|
|
1273
1672
|
CustomError,
|
|
1274
1673
|
Request,
|