@mittwald/api-client-commons 3.1.0 → 4.0.2

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.
Files changed (37) hide show
  1. package/dist/cjs/core/ApiClientBase.js +29 -3
  2. package/dist/cjs/core/Request.d.ts +6 -6
  3. package/dist/cjs/core/Request.js +18 -15
  4. package/dist/cjs/core/Request.test.js +6 -3
  5. package/dist/cjs/react/ApiCallAsyncResourceFactory.d.ts +12 -0
  6. package/dist/cjs/react/ApiCallAsyncResourceFactory.js +52 -0
  7. package/dist/cjs/react/ApiCallAsyncResourceFactory.test-types.d.ts +1 -0
  8. package/dist/cjs/react/ApiCallAsyncResourceFactory.test-types.js +38 -0
  9. package/dist/cjs/react/ApiCallAsyncResourceFactory.test.d.ts +1 -0
  10. package/dist/cjs/react/ApiCallAsyncResourceFactory.test.js +57 -0
  11. package/dist/cjs/react/index.d.ts +1 -0
  12. package/dist/cjs/react/index.js +17 -0
  13. package/dist/cjs/react/types.d.ts +3 -0
  14. package/dist/cjs/react/types.js +2 -0
  15. package/dist/cjs/types/OpenAPIOperation.d.ts +7 -1
  16. package/dist/cjs/types/RequestFunction.d.ts +7 -4
  17. package/dist/cjs/types/RequestFunction.test-types.js +8 -0
  18. package/dist/cjs/types/assertStatus.js +4 -1
  19. package/dist/cjs/types/assertStatus.test-types.js +4 -1
  20. package/dist/esm/core/ApiClientBase.js +1 -1
  21. package/dist/esm/core/Request.d.ts +6 -6
  22. package/dist/esm/core/Request.js +16 -16
  23. package/dist/esm/core/Request.test.js +2 -2
  24. package/dist/esm/react/ApiCallAsyncResourceFactory.d.ts +12 -0
  25. package/dist/esm/react/ApiCallAsyncResourceFactory.js +35 -0
  26. package/dist/esm/react/ApiCallAsyncResourceFactory.test-types.d.ts +1 -0
  27. package/dist/esm/react/ApiCallAsyncResourceFactory.test-types.js +36 -0
  28. package/dist/esm/react/ApiCallAsyncResourceFactory.test.d.ts +1 -0
  29. package/dist/esm/react/ApiCallAsyncResourceFactory.test.js +46 -0
  30. package/dist/esm/react/index.d.ts +1 -0
  31. package/dist/esm/react/index.js +1 -0
  32. package/dist/esm/react/types.d.ts +3 -0
  33. package/dist/esm/react/types.js +1 -0
  34. package/dist/esm/types/OpenAPIOperation.d.ts +7 -1
  35. package/dist/esm/types/RequestFunction.d.ts +7 -4
  36. package/dist/esm/types/RequestFunction.test-types.js +8 -0
  37. package/package.json +21 -1
@@ -1,15 +1,41 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
2
28
  Object.defineProperty(exports, "__esModule", { value: true });
3
29
  exports.ApiClientBase = void 0;
4
- const axios_1 = require("axios");
5
- const Request_js_1 = require("./Request.js");
30
+ const axios_1 = __importStar(require("axios"));
31
+ const Request_js_1 = __importDefault(require("./Request.js"));
6
32
  class ApiClientBase {
7
33
  constructor(axiosConfig = axios_1.default) {
8
34
  this.axios =
9
35
  axiosConfig instanceof axios_1.Axios ? axiosConfig : axios_1.default.create(axiosConfig);
10
36
  }
11
37
  requestFunctionFactory(operation) {
12
- return (conf) => new Request_js_1.default(this.axios, operation, conf).execute();
38
+ return (conf) => new Request_js_1.default(operation, conf).execute(this.axios);
13
39
  }
14
40
  }
15
41
  exports.ApiClientBase = ApiClientBase;
@@ -1,11 +1,11 @@
1
- import { OpenAPIOperation, RequestConfig, ResponsePromise } from "../types/index.js";
2
- import { AxiosInstance } from "axios";
1
+ import { OpenAPIOperation, RequestObject, ResponsePromise } from "../types/index.js";
2
+ import { AxiosInstance, AxiosRequestConfig } from "axios";
3
3
  export declare class Request<TOp extends OpenAPIOperation> {
4
- private readonly axios;
5
4
  private readonly operationDescriptor;
6
- private readonly config?;
7
- constructor(axiosInstance: AxiosInstance, operationDescriptor: TOp, config?: RequestConfig<TOp>);
8
- execute(): ResponsePromise<TOp>;
5
+ private readonly requestObject?;
6
+ readonly requestConfig: AxiosRequestConfig;
7
+ constructor(operationDescriptor: TOp, requestObject?: RequestObject<TOp>);
8
+ execute(axios: AxiosInstance): ResponsePromise<TOp>;
9
9
  private buildAxiosConfig;
10
10
  private makeAxiosHeaders;
11
11
  private convertQueryToUrlSearchParams;
@@ -1,32 +1,35 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.Request = void 0;
4
- const OpenAPIPath_js_1 = require("./OpenAPIPath.js");
7
+ const OpenAPIPath_js_1 = __importDefault(require("./OpenAPIPath.js"));
5
8
  class Request {
6
- constructor(axiosInstance, operationDescriptor, config) {
7
- this.axios = axiosInstance;
9
+ constructor(operationDescriptor, requestObject) {
8
10
  this.operationDescriptor = operationDescriptor;
9
- this.config = config;
11
+ this.requestObject = requestObject;
12
+ this.requestConfig = Object.freeze(this.buildAxiosConfig());
10
13
  }
11
- execute() {
12
- return this.axios.request(this.buildAxiosConfig());
14
+ execute(axios) {
15
+ return axios.request(this.requestConfig);
13
16
  }
14
17
  buildAxiosConfig() {
15
18
  const { method, path } = this.operationDescriptor;
16
- const pathParameters = this.config &&
17
- "pathParameters" in this.config &&
18
- this.config.pathParameters !== null
19
- ? this.config.pathParameters
20
- : undefined;
19
+ const pathParameters = this.requestObject;
21
20
  const openApiPath = new OpenAPIPath_js_1.default(path, pathParameters);
22
21
  const url = openApiPath.buildUrl();
23
- const data = this.config && "data" in this.config ? this.config.data : undefined;
24
- const headersConfig = this.config && "headers" in this.config ? this.config.headers : undefined;
22
+ const data = this.requestObject && "data" in this.requestObject
23
+ ? this.requestObject.data
24
+ : undefined;
25
+ const headersConfig = this.requestObject && "headers" in this.requestObject
26
+ ? this.requestObject.headers
27
+ : undefined;
25
28
  const headers = headersConfig
26
29
  ? this.makeAxiosHeaders(headersConfig)
27
30
  : undefined;
28
- const queryParametersConfig = this.config && "queryParameters" in this.config
29
- ? this.config.queryParameters
31
+ const queryParametersConfig = this.requestObject && "queryParameters" in this.requestObject
32
+ ? this.requestObject.queryParameters
30
33
  : undefined;
31
34
  const params = this.convertQueryToUrlSearchParams(queryParametersConfig);
32
35
  return {
@@ -1,6 +1,9 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const Request_js_1 = require("./Request.js");
6
+ const Request_js_1 = __importDefault(require("./Request.js"));
4
7
  const globals_1 = require("@jest/globals");
5
8
  const requestFn = globals_1.jest.fn();
6
9
  const mockedAxios = {
@@ -16,8 +19,8 @@ describe("query parameters", () => {
16
19
  method: "GET",
17
20
  };
18
21
  const executeRequest = (query) => {
19
- const request = new Request_js_1.default(mockedAxios, op, { queryParameters: query });
20
- request.execute();
22
+ const request = new Request_js_1.default(op, { queryParameters: query });
23
+ request.execute(mockedAxios);
21
24
  const requestConfig = requestFn.mock.calls[0][0];
22
25
  return requestConfig.params.toString();
23
26
  };
@@ -0,0 +1,12 @@
1
+ import { OpenAPIOperation, RequestFunction } from "../types/index.js";
2
+ import { GetApiResourceFn } from "./types.js";
3
+ export declare class ApiCallAsyncResourceFactory<TOp extends OpenAPIOperation> {
4
+ private static namespace;
5
+ private readonly operation;
6
+ private readonly requestFn;
7
+ constructor(operation: TOp, requestFn: RequestFunction<TOp>);
8
+ private getAsyncResourceId;
9
+ private getAsyncResourceTags;
10
+ private executeRequest;
11
+ getApiResource: GetApiResourceFn<TOp>;
12
+ }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.ApiCallAsyncResourceFactory = void 0;
16
+ const react_use_promise_1 = require("@mittwald/react-use-promise");
17
+ const index_js_1 = require("../types/index.js");
18
+ const Request_js_1 = __importDefault(require("../core/Request.js"));
19
+ class ApiCallAsyncResourceFactory {
20
+ constructor(operation, requestFn) {
21
+ this.getApiResource = ((requestObj) => {
22
+ const request = new Request_js_1.default(this.operation, requestObj);
23
+ return (0, react_use_promise_1.getAsyncResource)((requestObj) => this.executeRequest(requestObj), [requestObj], {
24
+ tags: this.getAsyncResourceTags(request),
25
+ loaderId: this.getAsyncResourceId(),
26
+ });
27
+ });
28
+ this.operation = operation;
29
+ this.requestFn = requestFn;
30
+ }
31
+ getAsyncResourceId() {
32
+ return `${ApiCallAsyncResourceFactory.namespace}/${this.operation.operationId}`;
33
+ }
34
+ getAsyncResourceTags(request) {
35
+ var _a;
36
+ const url = (_a = request.requestConfig.url) !== null && _a !== void 0 ? _a : "";
37
+ return [
38
+ this.getAsyncResourceId(),
39
+ `${ApiCallAsyncResourceFactory.namespace}/${this.operation.method}`,
40
+ `${ApiCallAsyncResourceFactory.namespace}/${url}`,
41
+ ];
42
+ }
43
+ executeRequest(requestObj) {
44
+ return __awaiter(this, void 0, void 0, function* () {
45
+ const response = yield this.requestFn(requestObj);
46
+ (0, index_js_1.assertStatus)(response, 200);
47
+ return response.data;
48
+ });
49
+ }
50
+ }
51
+ exports.ApiCallAsyncResourceFactory = ApiCallAsyncResourceFactory;
52
+ ApiCallAsyncResourceFactory.namespace = "@mittwald/api-client";
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const ApiCallAsyncResourceFactory_js_1 = require("./ApiCallAsyncResourceFactory.js");
4
+ const getStuff = new ApiCallAsyncResourceFactory_js_1.ApiCallAsyncResourceFactory({
5
+ operationId: "getStuff",
6
+ path: "/stuff",
7
+ method: "GET",
8
+ }, {});
9
+ function ignoredCheckRequestType() {
10
+ getStuff.getApiResource({
11
+ data: {
12
+ // @ts-expect-error Not matching request type
13
+ foo: "",
14
+ },
15
+ });
16
+ getStuff.getApiResource({
17
+ data: {
18
+ requestString: "",
19
+ },
20
+ });
21
+ }
22
+ function ignoredCheckResponseType() {
23
+ const stuff = getStuff
24
+ .getApiResource({
25
+ data: {
26
+ requestString: "",
27
+ },
28
+ })
29
+ .watch();
30
+ // @ts-expect-error Accessing unknown prop
31
+ stuff.foo;
32
+ (function (ignored) {
33
+ // @ts-expect-error is a number
34
+ })(stuff.responseData);
35
+ (function (ignored) {
36
+ // is number
37
+ })(stuff.responseData);
38
+ }
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const globals_1 = require("@jest/globals");
13
+ const ApiCallAsyncResourceFactory_js_1 = require("./ApiCallAsyncResourceFactory.js");
14
+ const react_use_promise_1 = require("@mittwald/react-use-promise");
15
+ (0, globals_1.beforeEach)(() => {
16
+ (0, react_use_promise_1.refresh)();
17
+ globals_1.jest.resetAllMocks();
18
+ });
19
+ const requestMock = globals_1.jest.fn();
20
+ const getStuff = new ApiCallAsyncResourceFactory_js_1.ApiCallAsyncResourceFactory({
21
+ operationId: "getStuff",
22
+ path: "/stuff",
23
+ method: "GET",
24
+ }, requestMock);
25
+ const testRequest1 = {
26
+ data: {
27
+ foo: "bar",
28
+ },
29
+ };
30
+ const testRequest2 = {
31
+ data: {
32
+ foo: "baz",
33
+ },
34
+ };
35
+ test("Resource loader executes request", () => __awaiter(void 0, void 0, void 0, function* () {
36
+ yield getStuff.getApiResource(testRequest1).load();
37
+ (0, globals_1.expect)(requestMock).toHaveBeenCalledTimes(1);
38
+ const firstRequestParams = requestMock.mock.calls[0][0];
39
+ (0, globals_1.expect)(firstRequestParams).toMatchObject(testRequest1);
40
+ }));
41
+ test("Resource is cached under URL", () => __awaiter(void 0, void 0, void 0, function* () {
42
+ yield getStuff.getApiResource(testRequest1).load();
43
+ (0, globals_1.expect)(requestMock).toHaveBeenCalledTimes(1);
44
+ yield getStuff.getApiResource(testRequest1).load();
45
+ (0, globals_1.expect)(requestMock).toHaveBeenCalledTimes(1);
46
+ (0, react_use_promise_1.refresh)({
47
+ tag: "@mittwald/api-client/stuff",
48
+ });
49
+ yield getStuff.getApiResource(testRequest1).load();
50
+ (0, globals_1.expect)(requestMock).toHaveBeenCalledTimes(2);
51
+ }));
52
+ test("Resources are different when request object changes", () => __awaiter(void 0, void 0, void 0, function* () {
53
+ yield getStuff.getApiResource(testRequest1).load();
54
+ (0, globals_1.expect)(requestMock).toHaveBeenCalledTimes(1);
55
+ yield getStuff.getApiResource(testRequest2).load();
56
+ (0, globals_1.expect)(requestMock).toHaveBeenCalledTimes(2);
57
+ }));
@@ -0,0 +1 @@
1
+ export * from "./ApiCallAsyncResourceFactory.js";
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./ApiCallAsyncResourceFactory.js"), exports);
@@ -0,0 +1,3 @@
1
+ import { OpenAPIOperation, RequestObject, ResponseData } from "../types/index.js";
2
+ import { AsyncResource } from "@mittwald/react-use-promise";
3
+ export type GetApiResourceFn<TOp extends OpenAPIOperation> = null extends RequestObject<TOp> ? (conf?: RequestObject<TOp>) => AsyncResource<ResponseData<TOp>> : (conf: RequestObject<TOp>) => AsyncResource<ResponseData<TOp>>;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,6 +1,6 @@
1
1
  import { AnyResponse, Response } from "./Response.js";
2
2
  import { AnyRequest, RequestType } from "./RequestType.js";
3
- import { HttpMethod } from "./http.js";
3
+ import { HttpMethod, HttpStatus } from "./http.js";
4
4
  export interface OpenAPIOperation<TIgnoredRequest extends AnyRequest = RequestType, IgnoredResponse extends AnyResponse = Response> {
5
5
  operationId: string;
6
6
  path: string;
@@ -8,3 +8,9 @@ export interface OpenAPIOperation<TIgnoredRequest extends AnyRequest = RequestTy
8
8
  }
9
9
  export type InferredRequestType<TOp> = TOp extends OpenAPIOperation<infer TReq> ? TReq : never;
10
10
  export type InferredResponseType<TOp> = TOp extends OpenAPIOperation<RequestType, infer TRes> ? TRes : never;
11
+ export type ResponseData<TOp, TStatus extends HttpStatus = 200> = Extract<InferredResponseType<TOp>, {
12
+ status: TStatus;
13
+ }>["data"];
14
+ export type RequestData<TOp> = TOp extends OpenAPIOperation ? InferredRequestType<TOp> extends {
15
+ data: infer TData;
16
+ } ? TData : never : never;
@@ -1,8 +1,11 @@
1
1
  import { InferredRequestType, InferredResponseType, OpenAPIOperation } from "./OpenAPIOperation.js";
2
2
  import { NullableOnNoRequiredKeysDeep } from "./NullableOnNoRequiredKeysDeep.js";
3
- export type RequestConfig<TOp extends OpenAPIOperation> = NullableOnNoRequiredKeysDeep<InferredRequestType<TOp>>;
3
+ type UnboxPathParameters<T> = T extends {
4
+ pathParameters: infer TPath;
5
+ } ? Omit<T, "pathParameters"> & TPath : T;
6
+ export type RequestObject<TOp extends OpenAPIOperation> = NullableOnNoRequiredKeysDeep<UnboxPathParameters<InferredRequestType<TOp>>>;
4
7
  export type ResponsePromise<TOp extends OpenAPIOperation> = Promise<InferredResponseType<TOp>>;
5
- type RequestFunctionWithOptionalRequest<TOp extends OpenAPIOperation> = (request?: RequestConfig<TOp>) => ResponsePromise<TOp>;
6
- type RequestFunctionWithRequiredRequest<TOp extends OpenAPIOperation> = (request: RequestConfig<TOp>) => ResponsePromise<TOp>;
7
- export type RequestFunction<TOp extends OpenAPIOperation> = null extends RequestConfig<TOp> ? RequestFunctionWithOptionalRequest<TOp> : RequestFunctionWithRequiredRequest<TOp>;
8
+ type RequestFunctionWithOptionalRequest<TOp extends OpenAPIOperation> = (request?: RequestObject<TOp>) => ResponsePromise<TOp>;
9
+ type RequestFunctionWithRequiredRequest<TOp extends OpenAPIOperation> = (request: RequestObject<TOp>) => ResponsePromise<TOp>;
10
+ export type RequestFunction<TOp extends OpenAPIOperation> = null extends RequestObject<TOp> ? RequestFunctionWithOptionalRequest<TOp> : RequestFunctionWithRequiredRequest<TOp>;
8
11
  export {};
@@ -20,3 +20,11 @@ function ignoredTestOptionalHeadersRequestTypes() {
20
20
  headers: { extra: true },
21
21
  });
22
22
  }
23
+ function ignoredTestPathParametersAreInRootOfRequestConfig() {
24
+ const f = {};
25
+ void f({
26
+ foo: "",
27
+ });
28
+ // @ts-expect-error Missing parameter
29
+ void f({});
30
+ }
@@ -1,7 +1,10 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.assertStatus = void 0;
4
- const ApiClientError_js_1 = require("../core/ApiClientError.js");
7
+ const ApiClientError_js_1 = __importDefault(require("../core/ApiClientError.js"));
5
8
  function assertStatus(response, expectedStatus) {
6
9
  if (response.status !== expectedStatus) {
7
10
  throw ApiClientError_js_1.default.fromResponse(`Unexpected response status (expected ${expectedStatus}, got: ${response.status})`, response);
@@ -1,7 +1,10 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  const tsd_1 = require("tsd");
4
- const assertStatus_js_1 = require("./assertStatus.js");
7
+ const assertStatus_js_1 = __importDefault(require("./assertStatus.js"));
5
8
  function ignoredTestAssertStatusAssertsAlsoTheCorrectResponseType() {
6
9
  (0, assertStatus_js_1.default)(someResponse, 200);
7
10
  (0, tsd_1.expectAssignable)(someResponse);
@@ -7,7 +7,7 @@ export class ApiClientBase {
7
7
  axiosConfig instanceof Axios ? axiosConfig : axios.create(axiosConfig);
8
8
  }
9
9
  requestFunctionFactory(operation) {
10
- return (conf) => new Request(this.axios, operation, conf).execute();
10
+ return (conf) => new Request(operation, conf).execute(this.axios);
11
11
  }
12
12
  }
13
13
  export default ApiClientBase;
@@ -1,11 +1,11 @@
1
- import { OpenAPIOperation, RequestConfig, ResponsePromise } from "../types/index.js";
2
- import { AxiosInstance } from "axios";
1
+ import { OpenAPIOperation, RequestObject, ResponsePromise } from "../types/index.js";
2
+ import { AxiosInstance, AxiosRequestConfig } from "axios";
3
3
  export declare class Request<TOp extends OpenAPIOperation> {
4
- private readonly axios;
5
4
  private readonly operationDescriptor;
6
- private readonly config?;
7
- constructor(axiosInstance: AxiosInstance, operationDescriptor: TOp, config?: RequestConfig<TOp>);
8
- execute(): ResponsePromise<TOp>;
5
+ private readonly requestObject?;
6
+ readonly requestConfig: AxiosRequestConfig;
7
+ constructor(operationDescriptor: TOp, requestObject?: RequestObject<TOp>);
8
+ execute(axios: AxiosInstance): ResponsePromise<TOp>;
9
9
  private buildAxiosConfig;
10
10
  private makeAxiosHeaders;
11
11
  private convertQueryToUrlSearchParams;
@@ -1,32 +1,32 @@
1
1
  import OpenAPIPath from "./OpenAPIPath.js";
2
2
  export class Request {
3
- axios;
4
3
  operationDescriptor;
5
- config;
6
- constructor(axiosInstance, operationDescriptor, config) {
7
- this.axios = axiosInstance;
4
+ requestObject;
5
+ requestConfig;
6
+ constructor(operationDescriptor, requestObject) {
8
7
  this.operationDescriptor = operationDescriptor;
9
- this.config = config;
8
+ this.requestObject = requestObject;
9
+ this.requestConfig = Object.freeze(this.buildAxiosConfig());
10
10
  }
11
- execute() {
12
- return this.axios.request(this.buildAxiosConfig());
11
+ execute(axios) {
12
+ return axios.request(this.requestConfig);
13
13
  }
14
14
  buildAxiosConfig() {
15
15
  const { method, path } = this.operationDescriptor;
16
- const pathParameters = this.config &&
17
- "pathParameters" in this.config &&
18
- this.config.pathParameters !== null
19
- ? this.config.pathParameters
20
- : undefined;
16
+ const pathParameters = this.requestObject;
21
17
  const openApiPath = new OpenAPIPath(path, pathParameters);
22
18
  const url = openApiPath.buildUrl();
23
- const data = this.config && "data" in this.config ? this.config.data : undefined;
24
- const headersConfig = this.config && "headers" in this.config ? this.config.headers : undefined;
19
+ const data = this.requestObject && "data" in this.requestObject
20
+ ? this.requestObject.data
21
+ : undefined;
22
+ const headersConfig = this.requestObject && "headers" in this.requestObject
23
+ ? this.requestObject.headers
24
+ : undefined;
25
25
  const headers = headersConfig
26
26
  ? this.makeAxiosHeaders(headersConfig)
27
27
  : undefined;
28
- const queryParametersConfig = this.config && "queryParameters" in this.config
29
- ? this.config.queryParameters
28
+ const queryParametersConfig = this.requestObject && "queryParameters" in this.requestObject
29
+ ? this.requestObject.queryParameters
30
30
  : undefined;
31
31
  const params = this.convertQueryToUrlSearchParams(queryParametersConfig);
32
32
  return {
@@ -14,8 +14,8 @@ describe("query parameters", () => {
14
14
  method: "GET",
15
15
  };
16
16
  const executeRequest = (query) => {
17
- const request = new Request(mockedAxios, op, { queryParameters: query });
18
- request.execute();
17
+ const request = new Request(op, { queryParameters: query });
18
+ request.execute(mockedAxios);
19
19
  const requestConfig = requestFn.mock.calls[0][0];
20
20
  return requestConfig.params.toString();
21
21
  };
@@ -0,0 +1,12 @@
1
+ import { OpenAPIOperation, RequestFunction } from "../types/index.js";
2
+ import { GetApiResourceFn } from "./types.js";
3
+ export declare class ApiCallAsyncResourceFactory<TOp extends OpenAPIOperation> {
4
+ private static namespace;
5
+ private readonly operation;
6
+ private readonly requestFn;
7
+ constructor(operation: TOp, requestFn: RequestFunction<TOp>);
8
+ private getAsyncResourceId;
9
+ private getAsyncResourceTags;
10
+ private executeRequest;
11
+ getApiResource: GetApiResourceFn<TOp>;
12
+ }
@@ -0,0 +1,35 @@
1
+ import { getAsyncResource } from "@mittwald/react-use-promise";
2
+ import { assertStatus, } from "../types/index.js";
3
+ import Request from "../core/Request.js";
4
+ export class ApiCallAsyncResourceFactory {
5
+ static namespace = "@mittwald/api-client";
6
+ operation;
7
+ requestFn;
8
+ constructor(operation, requestFn) {
9
+ this.operation = operation;
10
+ this.requestFn = requestFn;
11
+ }
12
+ getAsyncResourceId() {
13
+ return `${ApiCallAsyncResourceFactory.namespace}/${this.operation.operationId}`;
14
+ }
15
+ getAsyncResourceTags(request) {
16
+ const url = request.requestConfig.url ?? "";
17
+ return [
18
+ this.getAsyncResourceId(),
19
+ `${ApiCallAsyncResourceFactory.namespace}/${this.operation.method}`,
20
+ `${ApiCallAsyncResourceFactory.namespace}/${url}`,
21
+ ];
22
+ }
23
+ async executeRequest(requestObj) {
24
+ const response = await this.requestFn(requestObj);
25
+ assertStatus(response, 200);
26
+ return response.data;
27
+ }
28
+ getApiResource = ((requestObj) => {
29
+ const request = new Request(this.operation, requestObj);
30
+ return getAsyncResource((requestObj) => this.executeRequest(requestObj), [requestObj], {
31
+ tags: this.getAsyncResourceTags(request),
32
+ loaderId: this.getAsyncResourceId(),
33
+ });
34
+ });
35
+ }
@@ -0,0 +1,36 @@
1
+ import { ApiCallAsyncResourceFactory } from "./ApiCallAsyncResourceFactory.js";
2
+ const getStuff = new ApiCallAsyncResourceFactory({
3
+ operationId: "getStuff",
4
+ path: "/stuff",
5
+ method: "GET",
6
+ }, {});
7
+ function ignoredCheckRequestType() {
8
+ getStuff.getApiResource({
9
+ data: {
10
+ // @ts-expect-error Not matching request type
11
+ foo: "",
12
+ },
13
+ });
14
+ getStuff.getApiResource({
15
+ data: {
16
+ requestString: "",
17
+ },
18
+ });
19
+ }
20
+ function ignoredCheckResponseType() {
21
+ const stuff = getStuff
22
+ .getApiResource({
23
+ data: {
24
+ requestString: "",
25
+ },
26
+ })
27
+ .watch();
28
+ // @ts-expect-error Accessing unknown prop
29
+ stuff.foo;
30
+ (function (ignored) {
31
+ // @ts-expect-error is a number
32
+ })(stuff.responseData);
33
+ (function (ignored) {
34
+ // is number
35
+ })(stuff.responseData);
36
+ }
@@ -0,0 +1,46 @@
1
+ import { beforeEach, expect, jest } from "@jest/globals";
2
+ import { ApiCallAsyncResourceFactory } from "./ApiCallAsyncResourceFactory.js";
3
+ import { refresh } from "@mittwald/react-use-promise";
4
+ beforeEach(() => {
5
+ refresh();
6
+ jest.resetAllMocks();
7
+ });
8
+ const requestMock = jest.fn();
9
+ const getStuff = new ApiCallAsyncResourceFactory({
10
+ operationId: "getStuff",
11
+ path: "/stuff",
12
+ method: "GET",
13
+ }, requestMock);
14
+ const testRequest1 = {
15
+ data: {
16
+ foo: "bar",
17
+ },
18
+ };
19
+ const testRequest2 = {
20
+ data: {
21
+ foo: "baz",
22
+ },
23
+ };
24
+ test("Resource loader executes request", async () => {
25
+ await getStuff.getApiResource(testRequest1).load();
26
+ expect(requestMock).toHaveBeenCalledTimes(1);
27
+ const firstRequestParams = requestMock.mock.calls[0][0];
28
+ expect(firstRequestParams).toMatchObject(testRequest1);
29
+ });
30
+ test("Resource is cached under URL", async () => {
31
+ await getStuff.getApiResource(testRequest1).load();
32
+ expect(requestMock).toHaveBeenCalledTimes(1);
33
+ await getStuff.getApiResource(testRequest1).load();
34
+ expect(requestMock).toHaveBeenCalledTimes(1);
35
+ refresh({
36
+ tag: "@mittwald/api-client/stuff",
37
+ });
38
+ await getStuff.getApiResource(testRequest1).load();
39
+ expect(requestMock).toHaveBeenCalledTimes(2);
40
+ });
41
+ test("Resources are different when request object changes", async () => {
42
+ await getStuff.getApiResource(testRequest1).load();
43
+ expect(requestMock).toHaveBeenCalledTimes(1);
44
+ await getStuff.getApiResource(testRequest2).load();
45
+ expect(requestMock).toHaveBeenCalledTimes(2);
46
+ });
@@ -0,0 +1 @@
1
+ export * from "./ApiCallAsyncResourceFactory.js";
@@ -0,0 +1 @@
1
+ export * from "./ApiCallAsyncResourceFactory.js";
@@ -0,0 +1,3 @@
1
+ import { OpenAPIOperation, RequestObject, ResponseData } from "../types/index.js";
2
+ import { AsyncResource } from "@mittwald/react-use-promise";
3
+ export type GetApiResourceFn<TOp extends OpenAPIOperation> = null extends RequestObject<TOp> ? (conf?: RequestObject<TOp>) => AsyncResource<ResponseData<TOp>> : (conf: RequestObject<TOp>) => AsyncResource<ResponseData<TOp>>;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,6 @@
1
1
  import { AnyResponse, Response } from "./Response.js";
2
2
  import { AnyRequest, RequestType } from "./RequestType.js";
3
- import { HttpMethod } from "./http.js";
3
+ import { HttpMethod, HttpStatus } from "./http.js";
4
4
  export interface OpenAPIOperation<TIgnoredRequest extends AnyRequest = RequestType, IgnoredResponse extends AnyResponse = Response> {
5
5
  operationId: string;
6
6
  path: string;
@@ -8,3 +8,9 @@ export interface OpenAPIOperation<TIgnoredRequest extends AnyRequest = RequestTy
8
8
  }
9
9
  export type InferredRequestType<TOp> = TOp extends OpenAPIOperation<infer TReq> ? TReq : never;
10
10
  export type InferredResponseType<TOp> = TOp extends OpenAPIOperation<RequestType, infer TRes> ? TRes : never;
11
+ export type ResponseData<TOp, TStatus extends HttpStatus = 200> = Extract<InferredResponseType<TOp>, {
12
+ status: TStatus;
13
+ }>["data"];
14
+ export type RequestData<TOp> = TOp extends OpenAPIOperation ? InferredRequestType<TOp> extends {
15
+ data: infer TData;
16
+ } ? TData : never : never;
@@ -1,8 +1,11 @@
1
1
  import { InferredRequestType, InferredResponseType, OpenAPIOperation } from "./OpenAPIOperation.js";
2
2
  import { NullableOnNoRequiredKeysDeep } from "./NullableOnNoRequiredKeysDeep.js";
3
- export type RequestConfig<TOp extends OpenAPIOperation> = NullableOnNoRequiredKeysDeep<InferredRequestType<TOp>>;
3
+ type UnboxPathParameters<T> = T extends {
4
+ pathParameters: infer TPath;
5
+ } ? Omit<T, "pathParameters"> & TPath : T;
6
+ export type RequestObject<TOp extends OpenAPIOperation> = NullableOnNoRequiredKeysDeep<UnboxPathParameters<InferredRequestType<TOp>>>;
4
7
  export type ResponsePromise<TOp extends OpenAPIOperation> = Promise<InferredResponseType<TOp>>;
5
- type RequestFunctionWithOptionalRequest<TOp extends OpenAPIOperation> = (request?: RequestConfig<TOp>) => ResponsePromise<TOp>;
6
- type RequestFunctionWithRequiredRequest<TOp extends OpenAPIOperation> = (request: RequestConfig<TOp>) => ResponsePromise<TOp>;
7
- export type RequestFunction<TOp extends OpenAPIOperation> = null extends RequestConfig<TOp> ? RequestFunctionWithOptionalRequest<TOp> : RequestFunctionWithRequiredRequest<TOp>;
8
+ type RequestFunctionWithOptionalRequest<TOp extends OpenAPIOperation> = (request?: RequestObject<TOp>) => ResponsePromise<TOp>;
9
+ type RequestFunctionWithRequiredRequest<TOp extends OpenAPIOperation> = (request: RequestObject<TOp>) => ResponsePromise<TOp>;
10
+ export type RequestFunction<TOp extends OpenAPIOperation> = null extends RequestObject<TOp> ? RequestFunctionWithOptionalRequest<TOp> : RequestFunctionWithRequiredRequest<TOp>;
8
11
  export {};
@@ -18,4 +18,12 @@ function ignoredTestOptionalHeadersRequestTypes() {
18
18
  headers: { extra: true },
19
19
  });
20
20
  }
21
+ function ignoredTestPathParametersAreInRootOfRequestConfig() {
22
+ const f = {};
23
+ void f({
24
+ foo: "",
25
+ });
26
+ // @ts-expect-error Missing parameter
27
+ void f({});
28
+ }
21
29
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mittwald/api-client-commons",
3
- "version": "3.1.0",
3
+ "version": "4.0.2",
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",
@@ -22,6 +22,16 @@
22
22
  "require": "./dist/cjs/index.js",
23
23
  "default": "./dist/esm/index.js"
24
24
  }
25
+ },
26
+ "./react": {
27
+ "types": {
28
+ "require": "./dist/cjs/react/index.d.ts",
29
+ "default": "./dist/esm/react/index.d.ts"
30
+ },
31
+ "default": {
32
+ "require": "./dist/cjs/react/index.js",
33
+ "default": "./dist/esm/react/index.js"
34
+ }
25
35
  }
26
36
  },
27
37
  "scripts": {
@@ -37,6 +47,14 @@
37
47
  "sdk",
38
48
  "rest"
39
49
  ],
50
+ "peerDependencies": {
51
+ "@mittwald/react-use-promise": "^1.3.2"
52
+ },
53
+ "peerDependenciesMeta": {
54
+ "@mittwald/react-use-promise": {
55
+ "optional": true
56
+ }
57
+ },
40
58
  "dependencies": {
41
59
  "@types/parse-path": "^7.0.0",
42
60
  "axios": "^1.4.0",
@@ -46,9 +64,11 @@
46
64
  },
47
65
  "devDependencies": {
48
66
  "@jest/globals": "^29.6.0",
67
+ "@mittwald/react-use-promise": "^1.3.2",
49
68
  "@types/jest": "^29.5.2",
50
69
  "@yarnpkg/pnpify": "^4.0.0-rc.48",
51
70
  "jest": "^29.6.1",
71
+ "react": "^18.2.0",
52
72
  "ts-jest": "^29.1.1",
53
73
  "tsd": "^0.28.1"
54
74
  },