diff --git a/bin/configs/typescript-fetch-allOf-readonly.yaml b/bin/configs/typescript-fetch-allOf-readonly.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f872c77267e9b02845980251204b616995c91333
--- /dev/null
+++ b/bin/configs/typescript-fetch-allOf-readonly.yaml
@@ -0,0 +1,4 @@
+generatorName: typescript-fetch
+outputDir: samples/client/petstore/typescript-fetch/builds/allOf-readonly
+inputSpec: modules/openapi-generator/src/test/resources/3_0/allOf-readonly.yaml
+templateDir: modules/openapi-generator/src/main/resources/typescript-fetch
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/InlineModelResolver.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/InlineModelResolver.java
index ea896fad5097e7fc2355b66c05c343939c51a950..ef684023d350713a792e9fd5cb52c3600643aa04 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/InlineModelResolver.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/InlineModelResolver.java
@@ -189,6 +189,17 @@ public class InlineModelResolver {
         if (schema instanceof ComposedSchema) {
             // allOf, anyOf, oneOf
             ComposedSchema m = (ComposedSchema) schema;
+
+            if (m.getAllOf() != null && m.getAllOf().size() == 1 && m.getReadOnly() != null && m.getReadOnly()) {
+                // Check if this composed schema only contains an allOf and a readOnly.
+                ComposedSchema c = new ComposedSchema();
+                c.setAllOf(m.getAllOf());
+                c.setReadOnly(true);
+                if (m.equals(c)) {
+                    return isModelNeeded(m.getAllOf().get(0), visitedSchemas);
+                }
+            }
+
             if (m.getAllOf() != null && !m.getAllOf().isEmpty()) {
                 // check to ensure at least of the allOf item is model
                 for (Schema inner : m.getAllOf()) {
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchModelTest.java
index e73a0988bbb3ed870065dd1326ca16eed468f13c..259b7f55c9179cad3958b953f256e9ce67cdaad0 100644
--- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchModelTest.java
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchModelTest.java
@@ -36,9 +36,11 @@ import java.time.LocalDateTime;
 import java.time.OffsetDateTime;
 import java.time.ZoneOffset;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Locale;
+import java.util.Map;
 
 /*
 import static io.swagger.codegen.CodegenConstants.IS_ENUM_EXT_NAME;
@@ -458,4 +460,14 @@ public class TypeScriptFetchModelTest {
 
         Assert.assertEquals(codegen.getTypeDeclaration(model), "{ [key: string]: string; }");
     }
+
+    @Test(description = "Don't generate new schemas for readonly references")
+    public void testNestedReadonlySchemas() {
+        final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/allOf-readonly.yaml");
+        final DefaultCodegen codegen = new TypeScriptFetchClientCodegen();
+        codegen.processOpts();
+        codegen.setOpenAPI(openAPI);
+        final Map<String, Schema> schemaBefore = openAPI.getComponents().getSchemas();
+        Assert.assertEquals(schemaBefore.keySet(), Sets.newHashSet("club", "owner"));
+    }
 }
diff --git a/modules/openapi-generator/src/test/resources/3_0/allOf-readonly.yaml b/modules/openapi-generator/src/test/resources/3_0/allOf-readonly.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..643803b6fd6737a057c8ff446253c723bd53c325
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/allOf-readonly.yaml
@@ -0,0 +1,40 @@
+openapi: 3.0.1
+info:
+  version: 1.0.0
+  title: Example
+  license:
+    name: MIT
+servers:
+  - url: http://api.example.xyz/v1
+paths:
+  /person/display/{personId}:
+    get:
+      parameters:
+        - name: personId
+          in: path
+          required: true
+          description: The id of the person to retrieve
+          schema:
+            type: string
+      operationId: list
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/club"
+components:
+  schemas:
+    club:
+      properties:
+        owner:
+          allOf:
+            - $ref: '#/components/schemas/owner'
+          readOnly: true
+
+    owner:
+      properties:
+        name:
+          type: string
+          maxLength: 255
\ No newline at end of file
diff --git a/samples/client/petstore/typescript-fetch/builds/allOf-readonly/.openapi-generator-ignore b/samples/client/petstore/typescript-fetch/builds/allOf-readonly/.openapi-generator-ignore
new file mode 100644
index 0000000000000000000000000000000000000000..7484ee590a3894506cf063799b885428f95a71be
--- /dev/null
+++ b/samples/client/petstore/typescript-fetch/builds/allOf-readonly/.openapi-generator-ignore
@@ -0,0 +1,23 @@
+# OpenAPI Generator Ignore
+# Generated by openapi-generator https://github.com/openapitools/openapi-generator
+
+# Use this file to prevent files from being overwritten by the generator.
+# The patterns follow closely to .gitignore or .dockerignore.
+
+# As an example, the C# client generator defines ApiClient.cs.
+# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
+#ApiClient.cs
+
+# You can match any string of characters against a directory, file or extension with a single asterisk (*):
+#foo/*/qux
+# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
+
+# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
+#foo/**/qux
+# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
+
+# You can also negate patterns with an exclamation (!).
+# For example, you can ignore all files in a docs folder with the file extension .md:
+#docs/*.md
+# Then explicitly reverse the ignore rule for a single file:
+#!docs/README.md
diff --git a/samples/client/petstore/typescript-fetch/builds/allOf-readonly/.openapi-generator/FILES b/samples/client/petstore/typescript-fetch/builds/allOf-readonly/.openapi-generator/FILES
new file mode 100644
index 0000000000000000000000000000000000000000..4036fef51863ee78895be3a4ce5a48206d0f2cf4
--- /dev/null
+++ b/samples/client/petstore/typescript-fetch/builds/allOf-readonly/.openapi-generator/FILES
@@ -0,0 +1,7 @@
+apis/DefaultApi.ts
+apis/index.ts
+index.ts
+models/Club.ts
+models/Owner.ts
+models/index.ts
+runtime.ts
diff --git a/samples/client/petstore/typescript-fetch/builds/allOf-readonly/.openapi-generator/VERSION b/samples/client/petstore/typescript-fetch/builds/allOf-readonly/.openapi-generator/VERSION
new file mode 100644
index 0000000000000000000000000000000000000000..66672d4e9d31378b298e49f1b38a028b2afe48ac
--- /dev/null
+++ b/samples/client/petstore/typescript-fetch/builds/allOf-readonly/.openapi-generator/VERSION
@@ -0,0 +1 @@
+6.1.0-SNAPSHOT
\ No newline at end of file
diff --git a/samples/client/petstore/typescript-fetch/builds/allOf-readonly/apis/DefaultApi.ts b/samples/client/petstore/typescript-fetch/builds/allOf-readonly/apis/DefaultApi.ts
new file mode 100644
index 0000000000000000000000000000000000000000..416fd54366a89f3044e8209ee898608555ddf355
--- /dev/null
+++ b/samples/client/petstore/typescript-fetch/builds/allOf-readonly/apis/DefaultApi.ts
@@ -0,0 +1,62 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Example
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 1.0.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+import * as runtime from '../runtime';
+import type {
+  Club,
+} from '../models';
+import {
+    ClubFromJSON,
+    ClubToJSON,
+} from '../models';
+
+export interface ListRequest {
+    personId: string;
+}
+
+/**
+ * 
+ */
+export class DefaultApi extends runtime.BaseAPI {
+
+    /**
+     */
+    async listRaw(requestParameters: ListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Club>> {
+        if (requestParameters.personId === null || requestParameters.personId === undefined) {
+            throw new runtime.RequiredError('personId','Required parameter requestParameters.personId was null or undefined when calling list.');
+        }
+
+        const queryParameters: any = {};
+
+        const headerParameters: runtime.HTTPHeaders = {};
+
+        const response = await this.request({
+            path: `/person/display/{personId}`.replace(`{${"personId"}}`, encodeURIComponent(String(requestParameters.personId))),
+            method: 'GET',
+            headers: headerParameters,
+            query: queryParameters,
+        }, initOverrides);
+
+        return new runtime.JSONApiResponse(response, (jsonValue) => ClubFromJSON(jsonValue));
+    }
+
+    /**
+     */
+    async list(requestParameters: ListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Club> {
+        const response = await this.listRaw(requestParameters, initOverrides);
+        return await response.value();
+    }
+
+}
diff --git a/samples/client/petstore/typescript-fetch/builds/allOf-readonly/apis/index.ts b/samples/client/petstore/typescript-fetch/builds/allOf-readonly/apis/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..69c44c00fa0d9c0179f4f17e6994e1f500793cee
--- /dev/null
+++ b/samples/client/petstore/typescript-fetch/builds/allOf-readonly/apis/index.ts
@@ -0,0 +1,3 @@
+/* tslint:disable */
+/* eslint-disable */
+export * from './DefaultApi';
diff --git a/samples/client/petstore/typescript-fetch/builds/allOf-readonly/index.ts b/samples/client/petstore/typescript-fetch/builds/allOf-readonly/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..be9d1edeefeb76e94f11ce622252908b7bc04b99
--- /dev/null
+++ b/samples/client/petstore/typescript-fetch/builds/allOf-readonly/index.ts
@@ -0,0 +1,5 @@
+/* tslint:disable */
+/* eslint-disable */
+export * from './runtime';
+export * from './apis';
+export * from './models';
diff --git a/samples/client/petstore/typescript-fetch/builds/allOf-readonly/models/Club.ts b/samples/client/petstore/typescript-fetch/builds/allOf-readonly/models/Club.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d2e7090090003f3222f1f9692423f06f9aec1916
--- /dev/null
+++ b/samples/client/petstore/typescript-fetch/builds/allOf-readonly/models/Club.ts
@@ -0,0 +1,71 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Example
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 1.0.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+import { exists, mapValues } from '../runtime';
+import type { Owner } from './Owner';
+import {
+    OwnerFromJSON,
+    OwnerFromJSONTyped,
+    OwnerToJSON,
+} from './Owner';
+
+/**
+ * 
+ * @export
+ * @interface Club
+ */
+export interface Club {
+    /**
+     * 
+     * @type {Owner}
+     * @memberof Club
+     */
+    readonly owner?: Owner;
+}
+
+/**
+ * Check if a given object implements the Club interface.
+ */
+export function instanceOfClub(value: object): boolean {
+    let isInstance = true;
+
+    return isInstance;
+}
+
+export function ClubFromJSON(json: any): Club {
+    return ClubFromJSONTyped(json, false);
+}
+
+export function ClubFromJSONTyped(json: any, ignoreDiscriminator: boolean): Club {
+    if ((json === undefined) || (json === null)) {
+        return json;
+    }
+    return {
+        
+        'owner': !exists(json, 'owner') ? undefined : OwnerFromJSON(json['owner']),
+    };
+}
+
+export function ClubToJSON(value?: Club | null): any {
+    if (value === undefined) {
+        return undefined;
+    }
+    if (value === null) {
+        return null;
+    }
+    return {
+        
+    };
+}
+
diff --git a/samples/client/petstore/typescript-fetch/builds/allOf-readonly/models/Owner.ts b/samples/client/petstore/typescript-fetch/builds/allOf-readonly/models/Owner.ts
new file mode 100644
index 0000000000000000000000000000000000000000..70542125af998882d8fc6f391ac8f2ff87377971
--- /dev/null
+++ b/samples/client/petstore/typescript-fetch/builds/allOf-readonly/models/Owner.ts
@@ -0,0 +1,65 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Example
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 1.0.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+import { exists, mapValues } from '../runtime';
+/**
+ * 
+ * @export
+ * @interface Owner
+ */
+export interface Owner {
+    /**
+     * 
+     * @type {string}
+     * @memberof Owner
+     */
+    name?: string;
+}
+
+/**
+ * Check if a given object implements the Owner interface.
+ */
+export function instanceOfOwner(value: object): boolean {
+    let isInstance = true;
+
+    return isInstance;
+}
+
+export function OwnerFromJSON(json: any): Owner {
+    return OwnerFromJSONTyped(json, false);
+}
+
+export function OwnerFromJSONTyped(json: any, ignoreDiscriminator: boolean): Owner {
+    if ((json === undefined) || (json === null)) {
+        return json;
+    }
+    return {
+        
+        'name': !exists(json, 'name') ? undefined : json['name'],
+    };
+}
+
+export function OwnerToJSON(value?: Owner | null): any {
+    if (value === undefined) {
+        return undefined;
+    }
+    if (value === null) {
+        return null;
+    }
+    return {
+        
+        'name': value.name,
+    };
+}
+
diff --git a/samples/client/petstore/typescript-fetch/builds/allOf-readonly/models/index.ts b/samples/client/petstore/typescript-fetch/builds/allOf-readonly/models/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c5f449053c2c09eff0e0dc7b2939b6824c44a0f6
--- /dev/null
+++ b/samples/client/petstore/typescript-fetch/builds/allOf-readonly/models/index.ts
@@ -0,0 +1,4 @@
+/* tslint:disable */
+/* eslint-disable */
+export * from './Club';
+export * from './Owner';
diff --git a/samples/client/petstore/typescript-fetch/builds/allOf-readonly/runtime.ts b/samples/client/petstore/typescript-fetch/builds/allOf-readonly/runtime.ts
new file mode 100644
index 0000000000000000000000000000000000000000..22b76e32c8d65730e9b92a673162d047304e63f1
--- /dev/null
+++ b/samples/client/petstore/typescript-fetch/builds/allOf-readonly/runtime.ts
@@ -0,0 +1,407 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * Example
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 1.0.0
+ * 
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+
+export const BASE_PATH = "http://api.example.xyz/v1".replace(/\/+$/, "");
+
+export interface ConfigurationParameters {
+    basePath?: string; // override base path
+    fetchApi?: FetchAPI; // override for fetch implementation
+    middleware?: Middleware[]; // middleware to apply before/after fetch requests
+    queryParamsStringify?: (params: HTTPQuery) => string; // stringify function for query strings
+    username?: string; // parameter for basic security
+    password?: string; // parameter for basic security
+    apiKey?: string | ((name: string) => string); // parameter for apiKey security
+    accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string | Promise<string>); // parameter for oauth2 security
+    headers?: HTTPHeaders; //header params we want to use on every request
+    credentials?: RequestCredentials; //value for the credentials param we want to use on each request
+}
+
+export class Configuration {
+    constructor(private configuration: ConfigurationParameters = {}) {}
+
+    set config(configuration: Configuration) {
+        this.configuration = configuration;
+    }
+
+    get basePath(): string {
+        return this.configuration.basePath != null ? this.configuration.basePath : BASE_PATH;
+    }
+
+    get fetchApi(): FetchAPI | undefined {
+        return this.configuration.fetchApi;
+    }
+
+    get middleware(): Middleware[] {
+        return this.configuration.middleware || [];
+    }
+
+    get queryParamsStringify(): (params: HTTPQuery) => string {
+        return this.configuration.queryParamsStringify || querystring;
+    }
+
+    get username(): string | undefined {
+        return this.configuration.username;
+    }
+
+    get password(): string | undefined {
+        return this.configuration.password;
+    }
+
+    get apiKey(): ((name: string) => string) | undefined {
+        const apiKey = this.configuration.apiKey;
+        if (apiKey) {
+            return typeof apiKey === 'function' ? apiKey : () => apiKey;
+        }
+        return undefined;
+    }
+
+    get accessToken(): ((name?: string, scopes?: string[]) => string | Promise<string>) | undefined {
+        const accessToken = this.configuration.accessToken;
+        if (accessToken) {
+            return typeof accessToken === 'function' ? accessToken : async () => accessToken;
+        }
+        return undefined;
+    }
+
+    get headers(): HTTPHeaders | undefined {
+        return this.configuration.headers;
+    }
+
+    get credentials(): RequestCredentials | undefined {
+        return this.configuration.credentials;
+    }
+}
+
+export const DefaultConfig = new Configuration();
+
+/**
+ * This is the base class for all generated API classes.
+ */
+export class BaseAPI {
+
+    private middleware: Middleware[];
+
+    constructor(protected configuration = DefaultConfig) {
+        this.middleware = configuration.middleware;
+    }
+
+    withMiddleware<T extends BaseAPI>(this: T, ...middlewares: Middleware[]) {
+        const next = this.clone<T>();
+        next.middleware = next.middleware.concat(...middlewares);
+        return next;
+    }
+
+    withPreMiddleware<T extends BaseAPI>(this: T, ...preMiddlewares: Array<Middleware['pre']>) {
+        const middlewares = preMiddlewares.map((pre) => ({ pre }));
+        return this.withMiddleware<T>(...middlewares);
+    }
+
+    withPostMiddleware<T extends BaseAPI>(this: T, ...postMiddlewares: Array<Middleware['post']>) {
+        const middlewares = postMiddlewares.map((post) => ({ post }));
+        return this.withMiddleware<T>(...middlewares);
+    }
+
+    protected async request(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction): Promise<Response> {
+        const { url, init } = await this.createFetchParams(context, initOverrides);
+        const response = await this.fetchApi(url, init);
+        if (response.status >= 200 && response.status < 300) {
+            return response;
+        }
+        throw new ResponseError(response, 'Response returned an error code');
+    }
+
+    private async createFetchParams(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction) {
+        let url = this.configuration.basePath + context.path;
+        if (context.query !== undefined && Object.keys(context.query).length !== 0) {
+            // only add the querystring to the URL if there are query parameters.
+            // this is done to avoid urls ending with a "?" character which buggy webservers
+            // do not handle correctly sometimes.
+            url += '?' + this.configuration.queryParamsStringify(context.query);
+        }
+
+        const headers = Object.assign({}, this.configuration.headers, context.headers);
+        Object.keys(headers).forEach(key => headers[key] === undefined ? delete headers[key] : {});
+
+        const initOverrideFn =
+            typeof initOverrides === "function"
+                ? initOverrides
+                : async () => initOverrides;
+
+        const initParams = {
+            method: context.method,
+            headers,
+            body: context.body,
+            credentials: this.configuration.credentials,
+        };
+
+        const overridedInit: RequestInit = {
+            ...initParams,
+            ...(await initOverrideFn({
+                init: initParams,
+                context,
+            }))
+        }
+
+        const init: RequestInit = {
+            ...overridedInit,
+            body:
+                isFormData(overridedInit.body) ||
+                overridedInit.body instanceof URLSearchParams ||
+                isBlob(overridedInit.body)
+                    ? overridedInit.body
+                    : JSON.stringify(overridedInit.body),
+        };
+
+        return { url, init };
+    }
+
+    private fetchApi = async (url: string, init: RequestInit) => {
+        let fetchParams = { url, init };
+        for (const middleware of this.middleware) {
+            if (middleware.pre) {
+                fetchParams = await middleware.pre({
+                    fetch: this.fetchApi,
+                    ...fetchParams,
+                }) || fetchParams;
+            }
+        }
+        let response = undefined;
+        try {
+            response = await (this.configuration.fetchApi || fetch)(fetchParams.url, fetchParams.init);
+        } catch (e) {
+            for (const middleware of this.middleware) {
+                if (middleware.onError) {
+                    response = await middleware.onError({
+                        fetch: this.fetchApi,
+                        url: fetchParams.url,
+                        init: fetchParams.init,
+                        error: e,
+                        response: response ? response.clone() : undefined,
+                    }) || response;
+                }
+            }
+            if (response === undefined) {
+              if (e instanceof Error) {
+                throw new FetchError(e, 'The request failed and the interceptors did not return an alternative response');
+              } else {
+                throw e;
+              }
+            }
+        }
+        for (const middleware of this.middleware) {
+            if (middleware.post) {
+                response = await middleware.post({
+                    fetch: this.fetchApi,
+                    url: fetchParams.url,
+                    init: fetchParams.init,
+                    response: response.clone(),
+                }) || response;
+            }
+        }
+        return response;
+    }
+
+    /**
+     * Create a shallow clone of `this` by constructing a new instance
+     * and then shallow cloning data members.
+     */
+    private clone<T extends BaseAPI>(this: T): T {
+        const constructor = this.constructor as any;
+        const next = new constructor(this.configuration);
+        next.middleware = this.middleware.slice();
+        return next;
+    }
+};
+
+function isBlob(value: any): value is Blob {
+    return typeof Blob !== 'undefined' && value instanceof Blob
+}
+
+function isFormData(value: any): value is FormData {
+    return typeof FormData !== "undefined" && value instanceof FormData
+}
+
+export class ResponseError extends Error {
+    name: "ResponseError" = "ResponseError";
+    constructor(public response: Response, msg?: string) {
+        super(msg);
+    }
+}
+
+export class FetchError extends Error {
+    name: "FetchError" = "FetchError";
+    constructor(public cause: Error, msg?: string) {
+        super(msg);
+    }
+}
+
+export class RequiredError extends Error {
+    name: "RequiredError" = "RequiredError";
+    constructor(public field: string, msg?: string) {
+        super(msg);
+    }
+}
+
+export const COLLECTION_FORMATS = {
+    csv: ",",
+    ssv: " ",
+    tsv: "\t",
+    pipes: "|",
+};
+
+export type FetchAPI = WindowOrWorkerGlobalScope['fetch'];
+
+export type Json = any;
+export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD';
+export type HTTPHeaders = { [key: string]: string };
+export type HTTPQuery = { [key: string]: string | number | null | boolean | Array<string | number | null | boolean> | Set<string | number | null | boolean> | HTTPQuery };
+export type HTTPBody = Json | FormData | URLSearchParams;
+export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody }
+export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original';
+
+export type InitOverrideFunction = (requestContext: { init: HTTPRequestInit, context: RequestOpts }) => Promise<RequestInit>
+
+export interface FetchParams {
+    url: string;
+    init: RequestInit;
+}
+
+export interface RequestOpts {
+    path: string;
+    method: HTTPMethod;
+    headers: HTTPHeaders;
+    query?: HTTPQuery;
+    body?: HTTPBody;
+}
+
+export function exists(json: any, key: string) {
+    const value = json[key];
+    return value !== null && value !== undefined;
+}
+
+export function querystring(params: HTTPQuery, prefix: string = ''): string {
+    return Object.keys(params)
+        .map(key => querystringSingleKey(key, params[key], prefix))
+        .filter(part => part.length > 0)
+        .join('&');
+}
+
+function querystringSingleKey(key: string, value: string | number | null | undefined | boolean | Array<string | number | null | boolean> | Set<string | number | null | boolean> | HTTPQuery, keyPrefix: string = ''): string {
+    const fullKey = keyPrefix + (keyPrefix.length ? `[${key}]` : key);
+    if (value instanceof Array) {
+        const multiValue = value.map(singleValue => encodeURIComponent(String(singleValue)))
+            .join(`&${encodeURIComponent(fullKey)}=`);
+        return `${encodeURIComponent(fullKey)}=${multiValue}`;
+    }
+    if (value instanceof Set) {
+        const valueAsArray = Array.from(value);
+        return querystringSingleKey(key, valueAsArray, keyPrefix);
+    }
+    if (value instanceof Date) {
+        return `${encodeURIComponent(fullKey)}=${encodeURIComponent(value.toISOString())}`;
+    }
+    if (value instanceof Object) {
+        return querystring(value as HTTPQuery, fullKey);
+    }
+    return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`;
+}
+
+export function mapValues(data: any, fn: (item: any) => any) {
+  return Object.keys(data).reduce(
+    (acc, key) => ({ ...acc, [key]: fn(data[key]) }),
+    {}
+  );
+}
+
+export function canConsumeForm(consumes: Consume[]): boolean {
+    for (const consume of consumes) {
+        if ('multipart/form-data' === consume.contentType) {
+            return true;
+        }
+    }
+    return false;
+}
+
+export interface Consume {
+    contentType: string
+}
+
+export interface RequestContext {
+    fetch: FetchAPI;
+    url: string;
+    init: RequestInit;
+}
+
+export interface ResponseContext {
+    fetch: FetchAPI;
+    url: string;
+    init: RequestInit;
+    response: Response;
+}
+
+export interface ErrorContext {
+    fetch: FetchAPI;
+    url: string;
+    init: RequestInit;
+    error: unknown;
+    response?: Response;
+}
+
+export interface Middleware {
+    pre?(context: RequestContext): Promise<FetchParams | void>;
+    post?(context: ResponseContext): Promise<Response | void>;
+    onError?(context: ErrorContext): Promise<Response | void>;
+}
+
+export interface ApiResponse<T> {
+    raw: Response;
+    value(): Promise<T>;
+}
+
+export interface ResponseTransformer<T> {
+    (json: any): T;
+}
+
+export class JSONApiResponse<T> {
+    constructor(public raw: Response, private transformer: ResponseTransformer<T> = (jsonValue: any) => jsonValue) {}
+
+    async value(): Promise<T> {
+        return this.transformer(await this.raw.json());
+    }
+}
+
+export class VoidApiResponse {
+    constructor(public raw: Response) {}
+
+    async value(): Promise<void> {
+        return undefined;
+    }
+}
+
+export class BlobApiResponse {
+    constructor(public raw: Response) {}
+
+    async value(): Promise<Blob> {
+        return await this.raw.blob();
+    };
+}
+
+export class TextApiResponse {
+    constructor(public raw: Response) {}
+
+    async value(): Promise<string> {
+        return await this.raw.text();
+    };
+}