@mittwald/api-client-commons 3.0.8 → 3.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.
@@ -1,6 +1,6 @@
1
1
  import { AxiosInstance, CreateAxiosDefaults } from "axios";
2
- import { RequestFunction } from "../types/RequestFunction.js";
3
- import { OpenAPIOperation } from "../types/OpenAPIOperation.js";
2
+ import { RequestFunction } from "../types/index.js";
3
+ import { OpenAPIOperation } from "../types/index.js";
4
4
  export declare abstract class ApiClientBase {
5
5
  axios: AxiosInstance;
6
6
  constructor(axiosConfig?: AxiosInstance | CreateAxiosDefaults);
@@ -1,5 +1,4 @@
1
- import { OpenAPIOperation } from "../types/OpenAPIOperation.js";
2
- import { RequestConfig, ResponsePromise } from "../types/RequestFunction.js";
1
+ import { OpenAPIOperation, RequestConfig, ResponsePromise } from "../types/index.js";
3
2
  import { AxiosInstance } from "axios";
4
3
  export declare class Request<TOp extends OpenAPIOperation> {
5
4
  private readonly axios;
@@ -9,5 +8,6 @@ export declare class Request<TOp extends OpenAPIOperation> {
9
8
  execute(): ResponsePromise<TOp>;
10
9
  private buildAxiosConfig;
11
10
  private makeAxiosHeaders;
11
+ private convertQueryToUrlSearchParams;
12
12
  }
13
13
  export default Request;
@@ -19,12 +19,22 @@ class Request {
19
19
  ? this.config.pathParameters
20
20
  : undefined;
21
21
  const openApiPath = new OpenAPIPath_js_1.default(path, pathParameters);
22
+ const url = openApiPath.buildUrl();
22
23
  const data = this.config && "data" in this.config ? this.config.data : undefined;
23
- const headers = this.config && "headers" in this.config ? this.config.headers : undefined;
24
+ const headersConfig = this.config && "headers" in this.config ? this.config.headers : undefined;
25
+ const headers = headersConfig
26
+ ? this.makeAxiosHeaders(headersConfig)
27
+ : undefined;
28
+ const queryParametersConfig = this.config && "queryParameters" in this.config
29
+ ? this.config.queryParameters
30
+ : undefined;
31
+ const params = this.convertQueryToUrlSearchParams(queryParametersConfig);
24
32
  return {
25
- url: openApiPath.buildUrl(),
33
+ url,
26
34
  method,
27
- headers: headers ? this.makeAxiosHeaders(headers) : undefined,
35
+ headers,
36
+ // Must be a plain object or an URLSearchParams object
37
+ params,
28
38
  data,
29
39
  validateStatus: () => true,
30
40
  };
@@ -32,6 +42,36 @@ class Request {
32
42
  makeAxiosHeaders(headers) {
33
43
  return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key, value === null || value === void 0 ? void 0 : value.toString()]));
34
44
  }
45
+ convertQueryToUrlSearchParams(query) {
46
+ if (query === undefined || query === null) {
47
+ return undefined;
48
+ }
49
+ if (query instanceof URLSearchParams) {
50
+ return query;
51
+ }
52
+ if (typeof query === "string") {
53
+ return new URLSearchParams(query);
54
+ }
55
+ if (typeof query === "object") {
56
+ const searchParams = new URLSearchParams();
57
+ for (const [key, value] of Object.entries(query)) {
58
+ if (Array.isArray(value)) {
59
+ for (const arrayItem of value) {
60
+ searchParams.append(key, arrayItem);
61
+ }
62
+ }
63
+ else {
64
+ searchParams.append(key, typeof value === "string" ||
65
+ typeof value === "number" ||
66
+ typeof value === "boolean"
67
+ ? value.toString()
68
+ : JSON.stringify(value));
69
+ }
70
+ }
71
+ return searchParams;
72
+ }
73
+ throw new Error(`Unexpected query parameter type (${typeof query})`);
74
+ }
35
75
  }
36
76
  exports.Request = Request;
37
77
  exports.default = Request;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const Request_js_1 = require("./Request.js");
4
+ const globals_1 = require("@jest/globals");
5
+ const requestFn = globals_1.jest.fn();
6
+ const mockedAxios = {
7
+ request: requestFn,
8
+ };
9
+ beforeEach(() => {
10
+ globals_1.jest.resetAllMocks();
11
+ });
12
+ describe("query parameters", () => {
13
+ const op = {
14
+ path: "/",
15
+ operationId: "test",
16
+ method: "GET",
17
+ };
18
+ const executeRequest = (query) => {
19
+ const request = new Request_js_1.default(mockedAxios, op, { queryParameters: query });
20
+ request.execute();
21
+ const requestConfig = requestFn.mock.calls[0][0];
22
+ return requestConfig.params.toString();
23
+ };
24
+ test("Empty query", () => {
25
+ const query = executeRequest({});
26
+ expect(query).toBe("");
27
+ });
28
+ test("Simple parameter", () => {
29
+ const query = executeRequest({
30
+ foo: "bar",
31
+ });
32
+ expect(query).toBe("foo=bar");
33
+ });
34
+ test("Two parameters", () => {
35
+ const query = executeRequest({
36
+ foo: "bar",
37
+ bam: "baz",
38
+ });
39
+ expect(query).toBe("foo=bar&bam=baz");
40
+ });
41
+ test("Array parameters", () => {
42
+ const query = executeRequest({
43
+ foo: ["bar", "bam"],
44
+ });
45
+ expect(query).toBe("foo=bar&foo=bam");
46
+ });
47
+ test("Number, boolean, JSON", () => {
48
+ const query = executeRequest({
49
+ foo: 1,
50
+ bar: true,
51
+ baz: { some: "value" },
52
+ });
53
+ expect(query).toBe("foo=1&bar=true&baz=%7B%22some%22%3A%22value%22%7D");
54
+ });
55
+ });
@@ -1,4 +1,4 @@
1
- import { OpenAPIOperation, InferredRequestType, InferredResponseType } from "./OpenAPIOperation.js";
1
+ import { InferredRequestType, InferredResponseType, OpenAPIOperation } from "./OpenAPIOperation.js";
2
2
  import { NullableOnNoRequiredKeysDeep } from "./NullableOnNoRequiredKeysDeep.js";
3
3
  export type RequestConfig<TOp extends OpenAPIOperation> = NullableOnNoRequiredKeysDeep<InferredRequestType<TOp>>;
4
4
  export type ResponsePromise<TOp extends OpenAPIOperation> = Promise<InferredResponseType<TOp>>;
@@ -1,4 +1,4 @@
1
- import { HttpHeaders, HttpPayload, PathParameters } from "./http.js";
1
+ import { HttpHeaders, HttpPayload, PathParameters, QueryParameters } from "./http.js";
2
2
  type EmptyObject = Record<string, never>;
3
3
  type EmptyRequestComponent = EmptyObject | null;
4
4
  type RequestWithOptionalHeaders = {
@@ -13,6 +13,9 @@ type RequestWithPathParameters<TPathParameters> = TPathParameters extends EmptyR
13
13
  type RequestWithHeaders<THeaders> = THeaders extends EmptyRequestComponent ? RequestWithOptionalHeaders : {
14
14
  headers: THeaders & HttpHeaders;
15
15
  };
16
- export type RequestType<TData extends HttpPayload = EmptyRequestComponent, TPathParameters extends PathParameters | EmptyRequestComponent = EmptyRequestComponent, THeader extends HttpHeaders | EmptyRequestComponent = EmptyRequestComponent> = TData | TPathParameters | THeader extends EmptyRequestComponent ? RequestWithOptionalHeaders : RequestWithData<TData> & RequestWithPathParameters<TPathParameters> & RequestWithHeaders<THeader>;
17
- export type AnyRequest = RequestType<any, any, any>;
16
+ type RequestWithQueryParameters<TQuery> = TQuery extends EmptyRequestComponent ? RequestWithOptionalHeaders : {
17
+ queryParameters: TQuery & HttpHeaders;
18
+ };
19
+ export type RequestType<TData extends HttpPayload = EmptyRequestComponent, TPathParameters extends PathParameters | EmptyRequestComponent = EmptyRequestComponent, TQueryParameters extends QueryParameters | EmptyRequestComponent = EmptyRequestComponent, THeader extends HttpHeaders | EmptyRequestComponent = EmptyRequestComponent> = TData | TPathParameters | THeader | TQueryParameters extends EmptyRequestComponent ? RequestWithOptionalHeaders : RequestWithData<TData> & RequestWithPathParameters<TPathParameters> & RequestWithQueryParameters<TQueryParameters> & RequestWithHeaders<THeader>;
20
+ export type AnyRequest = RequestType<any, any, any, any>;
18
21
  export {};
@@ -84,6 +84,42 @@ function ignoredTestRequestTypesWithHeader() {
84
84
  },
85
85
  });
86
86
  }
87
+ function ignoredTestRequestTypesWithQuery() {
88
+ (0, tsd_1.expectAssignable)({
89
+ data: {
90
+ foo: "",
91
+ },
92
+ pathParameters: { bar: "" },
93
+ headers: { baz: "" },
94
+ queryParameters: {
95
+ whut: "",
96
+ },
97
+ });
98
+ (0, tsd_1.expectAssignable)({
99
+ pathParameters: { bar: "" },
100
+ headers: { baz: "" },
101
+ queryParameters: {
102
+ whut: "",
103
+ },
104
+ });
105
+ (0, tsd_1.expectAssignable)({
106
+ queryParameters: {
107
+ whut: "",
108
+ },
109
+ });
110
+ // @ts-expect-error Not assignable
111
+ (0, tsd_1.expectAssignable)({});
112
+ (0, tsd_1.expectAssignable)({
113
+ queryParameters: {
114
+ // @ts-expect-error Not assignable
115
+ whut: 42,
116
+ },
117
+ });
118
+ (0, tsd_1.expectAssignable)({
119
+ // @ts-expect-error Not assignable
120
+ queryParameters: {},
121
+ });
122
+ }
87
123
  function ignoredTestAdditionalHeadersCanAlwaysBeSet() {
88
124
  (0, tsd_1.expectAssignable)({
89
125
  headers: { extra: true },
@@ -9,4 +9,5 @@ export type HttpHeaders = Partial<{
9
9
  [TKey: string]: HeaderValue | HeaderValue[];
10
10
  }>;
11
11
  export type PathParameters = Record<string, string | number>;
12
+ export type QueryParameters = Record<string, unknown>;
12
13
  export {};
@@ -1,6 +1,6 @@
1
1
  import { AxiosInstance, CreateAxiosDefaults } from "axios";
2
- import { RequestFunction } from "../types/RequestFunction.js";
3
- import { OpenAPIOperation } from "../types/OpenAPIOperation.js";
2
+ import { RequestFunction } from "../types/index.js";
3
+ import { OpenAPIOperation } from "../types/index.js";
4
4
  export declare abstract class ApiClientBase {
5
5
  axios: AxiosInstance;
6
6
  constructor(axiosConfig?: AxiosInstance | CreateAxiosDefaults);
@@ -1,5 +1,4 @@
1
- import { OpenAPIOperation } from "../types/OpenAPIOperation.js";
2
- import { RequestConfig, ResponsePromise } from "../types/RequestFunction.js";
1
+ import { OpenAPIOperation, RequestConfig, ResponsePromise } from "../types/index.js";
3
2
  import { AxiosInstance } from "axios";
4
3
  export declare class Request<TOp extends OpenAPIOperation> {
5
4
  private readonly axios;
@@ -9,5 +8,6 @@ export declare class Request<TOp extends OpenAPIOperation> {
9
8
  execute(): ResponsePromise<TOp>;
10
9
  private buildAxiosConfig;
11
10
  private makeAxiosHeaders;
11
+ private convertQueryToUrlSearchParams;
12
12
  }
13
13
  export default Request;
@@ -19,12 +19,22 @@ export class Request {
19
19
  ? this.config.pathParameters
20
20
  : undefined;
21
21
  const openApiPath = new OpenAPIPath(path, pathParameters);
22
+ const url = openApiPath.buildUrl();
22
23
  const data = this.config && "data" in this.config ? this.config.data : undefined;
23
- const headers = this.config && "headers" in this.config ? this.config.headers : undefined;
24
+ const headersConfig = this.config && "headers" in this.config ? this.config.headers : undefined;
25
+ const headers = headersConfig
26
+ ? this.makeAxiosHeaders(headersConfig)
27
+ : undefined;
28
+ const queryParametersConfig = this.config && "queryParameters" in this.config
29
+ ? this.config.queryParameters
30
+ : undefined;
31
+ const params = this.convertQueryToUrlSearchParams(queryParametersConfig);
24
32
  return {
25
- url: openApiPath.buildUrl(),
33
+ url,
26
34
  method,
27
- headers: headers ? this.makeAxiosHeaders(headers) : undefined,
35
+ headers,
36
+ // Must be a plain object or an URLSearchParams object
37
+ params,
28
38
  data,
29
39
  validateStatus: () => true,
30
40
  };
@@ -32,5 +42,35 @@ export class Request {
32
42
  makeAxiosHeaders(headers) {
33
43
  return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key, value?.toString()]));
34
44
  }
45
+ convertQueryToUrlSearchParams(query) {
46
+ if (query === undefined || query === null) {
47
+ return undefined;
48
+ }
49
+ if (query instanceof URLSearchParams) {
50
+ return query;
51
+ }
52
+ if (typeof query === "string") {
53
+ return new URLSearchParams(query);
54
+ }
55
+ if (typeof query === "object") {
56
+ const searchParams = new URLSearchParams();
57
+ for (const [key, value] of Object.entries(query)) {
58
+ if (Array.isArray(value)) {
59
+ for (const arrayItem of value) {
60
+ searchParams.append(key, arrayItem);
61
+ }
62
+ }
63
+ else {
64
+ searchParams.append(key, typeof value === "string" ||
65
+ typeof value === "number" ||
66
+ typeof value === "boolean"
67
+ ? value.toString()
68
+ : JSON.stringify(value));
69
+ }
70
+ }
71
+ return searchParams;
72
+ }
73
+ throw new Error(`Unexpected query parameter type (${typeof query})`);
74
+ }
35
75
  }
36
76
  export default Request;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,53 @@
1
+ import Request from "./Request.js";
2
+ import { jest } from "@jest/globals";
3
+ const requestFn = jest.fn();
4
+ const mockedAxios = {
5
+ request: requestFn,
6
+ };
7
+ beforeEach(() => {
8
+ jest.resetAllMocks();
9
+ });
10
+ describe("query parameters", () => {
11
+ const op = {
12
+ path: "/",
13
+ operationId: "test",
14
+ method: "GET",
15
+ };
16
+ const executeRequest = (query) => {
17
+ const request = new Request(mockedAxios, op, { queryParameters: query });
18
+ request.execute();
19
+ const requestConfig = requestFn.mock.calls[0][0];
20
+ return requestConfig.params.toString();
21
+ };
22
+ test("Empty query", () => {
23
+ const query = executeRequest({});
24
+ expect(query).toBe("");
25
+ });
26
+ test("Simple parameter", () => {
27
+ const query = executeRequest({
28
+ foo: "bar",
29
+ });
30
+ expect(query).toBe("foo=bar");
31
+ });
32
+ test("Two parameters", () => {
33
+ const query = executeRequest({
34
+ foo: "bar",
35
+ bam: "baz",
36
+ });
37
+ expect(query).toBe("foo=bar&bam=baz");
38
+ });
39
+ test("Array parameters", () => {
40
+ const query = executeRequest({
41
+ foo: ["bar", "bam"],
42
+ });
43
+ expect(query).toBe("foo=bar&foo=bam");
44
+ });
45
+ test("Number, boolean, JSON", () => {
46
+ const query = executeRequest({
47
+ foo: 1,
48
+ bar: true,
49
+ baz: { some: "value" },
50
+ });
51
+ expect(query).toBe("foo=1&bar=true&baz=%7B%22some%22%3A%22value%22%7D");
52
+ });
53
+ });
@@ -1,4 +1,4 @@
1
- import { OpenAPIOperation, InferredRequestType, InferredResponseType } from "./OpenAPIOperation.js";
1
+ import { InferredRequestType, InferredResponseType, OpenAPIOperation } from "./OpenAPIOperation.js";
2
2
  import { NullableOnNoRequiredKeysDeep } from "./NullableOnNoRequiredKeysDeep.js";
3
3
  export type RequestConfig<TOp extends OpenAPIOperation> = NullableOnNoRequiredKeysDeep<InferredRequestType<TOp>>;
4
4
  export type ResponsePromise<TOp extends OpenAPIOperation> = Promise<InferredResponseType<TOp>>;
@@ -1,4 +1,4 @@
1
- import { HttpHeaders, HttpPayload, PathParameters } from "./http.js";
1
+ import { HttpHeaders, HttpPayload, PathParameters, QueryParameters } from "./http.js";
2
2
  type EmptyObject = Record<string, never>;
3
3
  type EmptyRequestComponent = EmptyObject | null;
4
4
  type RequestWithOptionalHeaders = {
@@ -13,6 +13,9 @@ type RequestWithPathParameters<TPathParameters> = TPathParameters extends EmptyR
13
13
  type RequestWithHeaders<THeaders> = THeaders extends EmptyRequestComponent ? RequestWithOptionalHeaders : {
14
14
  headers: THeaders & HttpHeaders;
15
15
  };
16
- export type RequestType<TData extends HttpPayload = EmptyRequestComponent, TPathParameters extends PathParameters | EmptyRequestComponent = EmptyRequestComponent, THeader extends HttpHeaders | EmptyRequestComponent = EmptyRequestComponent> = TData | TPathParameters | THeader extends EmptyRequestComponent ? RequestWithOptionalHeaders : RequestWithData<TData> & RequestWithPathParameters<TPathParameters> & RequestWithHeaders<THeader>;
17
- export type AnyRequest = RequestType<any, any, any>;
16
+ type RequestWithQueryParameters<TQuery> = TQuery extends EmptyRequestComponent ? RequestWithOptionalHeaders : {
17
+ queryParameters: TQuery & HttpHeaders;
18
+ };
19
+ export type RequestType<TData extends HttpPayload = EmptyRequestComponent, TPathParameters extends PathParameters | EmptyRequestComponent = EmptyRequestComponent, TQueryParameters extends QueryParameters | EmptyRequestComponent = EmptyRequestComponent, THeader extends HttpHeaders | EmptyRequestComponent = EmptyRequestComponent> = TData | TPathParameters | THeader | TQueryParameters extends EmptyRequestComponent ? RequestWithOptionalHeaders : RequestWithData<TData> & RequestWithPathParameters<TPathParameters> & RequestWithQueryParameters<TQueryParameters> & RequestWithHeaders<THeader>;
20
+ export type AnyRequest = RequestType<any, any, any, any>;
18
21
  export {};
@@ -82,6 +82,42 @@ function ignoredTestRequestTypesWithHeader() {
82
82
  },
83
83
  });
84
84
  }
85
+ function ignoredTestRequestTypesWithQuery() {
86
+ expectAssignable({
87
+ data: {
88
+ foo: "",
89
+ },
90
+ pathParameters: { bar: "" },
91
+ headers: { baz: "" },
92
+ queryParameters: {
93
+ whut: "",
94
+ },
95
+ });
96
+ expectAssignable({
97
+ pathParameters: { bar: "" },
98
+ headers: { baz: "" },
99
+ queryParameters: {
100
+ whut: "",
101
+ },
102
+ });
103
+ expectAssignable({
104
+ queryParameters: {
105
+ whut: "",
106
+ },
107
+ });
108
+ // @ts-expect-error Not assignable
109
+ expectAssignable({});
110
+ expectAssignable({
111
+ queryParameters: {
112
+ // @ts-expect-error Not assignable
113
+ whut: 42,
114
+ },
115
+ });
116
+ expectAssignable({
117
+ // @ts-expect-error Not assignable
118
+ queryParameters: {},
119
+ });
120
+ }
85
121
  function ignoredTestAdditionalHeadersCanAlwaysBeSet() {
86
122
  expectAssignable({
87
123
  headers: { extra: true },
@@ -9,4 +9,5 @@ export type HttpHeaders = Partial<{
9
9
  [TKey: string]: HeaderValue | HeaderValue[];
10
10
  }>;
11
11
  export type PathParameters = Record<string, string | number>;
12
+ export type QueryParameters = Record<string, unknown>;
12
13
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mittwald/api-client-commons",
3
- "version": "3.0.8",
3
+ "version": "3.1.0",
4
4
  "description": "Common types and utilities for mittwald API clients",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/mittwald/api-client-js.git",
@@ -25,7 +25,7 @@
25
25
  }
26
26
  },
27
27
  "scripts": {
28
- "test": "echo 'No tests available'"
28
+ "test": "node --experimental-vm-modules $(yarn bin jest)"
29
29
  },
30
30
  "files": [
31
31
  "dist/**/*.{js,d.ts}"
@@ -45,7 +45,11 @@
45
45
  "type-fest": "^3.12.0"
46
46
  },
47
47
  "devDependencies": {
48
+ "@jest/globals": "^29.6.0",
49
+ "@types/jest": "^29.5.2",
48
50
  "@yarnpkg/pnpify": "^4.0.0-rc.48",
51
+ "jest": "^29.6.1",
52
+ "ts-jest": "^29.1.1",
49
53
  "tsd": "^0.28.1"
50
54
  },
51
55
  "types": "dist/cjs/index.d.ts"