@r0hitsharma/http-client-core 0.12.0-rohit-fork-ci.1

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 ADDED
@@ -0,0 +1,81 @@
1
+ # @r0hitsharma/http-client-core
2
+
3
+ Typed HTTP client utilities built on OpenAPI and Zod: an `openapi-fetch` client
4
+ factory, the OpenAPI helper types the rest of the toolkit shares, and helpers
5
+ that turn a document's component schemas into zod validators.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @r0hitsharma/http-client-core
11
+ npm install --save-dev openapi-typescript
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ ### Generate types from an OpenAPI document
17
+
18
+ ```bash
19
+ npx uikit-openapi-generate --schema openapi.json --output src/api.types.ts
20
+ ```
21
+
22
+ ### Create a typed client
23
+
24
+ ```typescript
25
+ import { createApiClient } from '@r0hitsharma/http-client-core';
26
+
27
+ import type { paths } from './api.types';
28
+
29
+ const client = createApiClient<paths>('https://api.example.com');
30
+
31
+ // Fully typed request and response
32
+ const { data, error } = await client.GET('/users/{id}', {
33
+ params: { path: { id: '123' } },
34
+ });
35
+ ```
36
+
37
+ The second argument is the rest of the `openapi-fetch` client config (everything
38
+ except `baseUrl`), which is how a test or a mock layer injects its own `fetch`:
39
+
40
+ ```typescript
41
+ const client = createApiClient<paths>('https://api.example.com', {
42
+ fetch: myFetch,
43
+ headers: { 'X-Tenant': tenantId },
44
+ });
45
+ ```
46
+
47
+ ### Build a zod validator from a component schema
48
+
49
+ ```typescript
50
+ import { getComponentSchemaFromOpenApi } from '@r0hitsharma/http-client-core';
51
+
52
+ import openApiDocument from '../openapi.json';
53
+
54
+ const userSchema = getComponentSchemaFromOpenApi(openApiDocument, 'User');
55
+ const result = userSchema.safeParse(await response.json());
56
+ ```
57
+
58
+ `normalizeOpenApiRefs` is exported for documents that need their
59
+ `#/components/schemas/...` refs rewritten to zod's `#/$defs/...` form ahead of
60
+ time; `getComponentSchemaFromOpenApi` applies it internally.
61
+
62
+ For response validation wired into TanStack Query, use
63
+ `createZodResponseMiddleware` from
64
+ [http-client-react](../http-client-react) rather than calling this directly.
65
+
66
+ ### Shared OpenAPI types
67
+
68
+ The `openapi-fetch` and `openapi-typescript-helpers` types that anything built on
69
+ this client needs are re-exported here — `ApiClient`, `ApiClientOptions`,
70
+ `FetchResponse`, `MaybeOptionalInit`, `HttpMethod`, `MediaType`,
71
+ `PathsWithMethod`, `RequiredKeysOf` — so downstream packages type against one
72
+ pinned copy of the OpenAPI toolchain instead of each declaring their own
73
+ dependency on it.
74
+
75
+ ## Peer dependencies
76
+
77
+ - `openapi-typescript`: for generating types from OpenAPI documents
78
+
79
+ ## See also
80
+
81
+ - [http-client-react](../http-client-react) for the TanStack Query layer
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execSync } from 'node:child_process';
4
+ import { existsSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+
8
+ function parseArg(name: string, fallback?: string): string | undefined {
9
+ const prefix = `--${name}=`;
10
+ const inline = process.argv.find((arg) => arg.startsWith(prefix));
11
+ if (inline) {
12
+ return inline.slice(prefix.length);
13
+ }
14
+
15
+ const idx = process.argv.indexOf(`--${name}`);
16
+ if (idx >= 0 && process.argv[idx + 1]) {
17
+ return process.argv[idx + 1];
18
+ }
19
+
20
+ return fallback;
21
+ }
22
+
23
+ const schema = parseArg('schema', process.env.OPENAPI_FILE);
24
+ const output = parseArg('output', 'generated/openapi-types.ts') ?? 'generated/openapi-types.ts';
25
+
26
+ if (!schema) {
27
+ console.error('Missing schema path. Use --schema <path-to-openapi-json>.');
28
+ process.exit(1);
29
+ }
30
+
31
+ const absoluteSchema = path.resolve(process.cwd(), schema);
32
+ const absoluteOutput = path.resolve(process.cwd(), output);
33
+
34
+ if (!existsSync(absoluteSchema)) {
35
+ console.error(`OpenAPI schema file does not exist: ${absoluteSchema}`);
36
+ process.exit(1);
37
+ }
38
+
39
+ mkdirSync(path.dirname(absoluteOutput), { recursive: true });
40
+
41
+ // openapi-typescript builds its output with the classic TypeScript compiler API, which
42
+ // TypeScript 7.0 does not ship (openapi-ts/openapi-typescript#2841). Running from an empty
43
+ // directory makes npx resolve it in an isolated tree alongside a TypeScript that still has that
44
+ // API, instead of picking up the host project's compiler. Exact pins: the
45
+ // isolated tree resolves outside any project .npmrc, so range specifiers
46
+ // would float past consumer supply-chain guards (min-release-age).
47
+ const isolatedCwd = mkdtempSync(path.join(os.tmpdir(), 'openapi-typescript-'));
48
+
49
+ try {
50
+ execSync(
51
+ `npx --yes --package=openapi-typescript@7.13.0 --package=typescript@5.9.3 openapi-typescript "${absoluteSchema}" --output "${absoluteOutput}"`,
52
+ { cwd: isolatedCwd, stdio: 'inherit' },
53
+ );
54
+ } finally {
55
+ rmSync(isolatedCwd, { force: true, recursive: true });
56
+ }
57
+
58
+ console.log(`Generated: ${absoluteOutput}`);
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ import { execSync } from 'node:child_process';
3
+ import { existsSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ function parseArg(name, fallback) {
7
+ const prefix = `--${name}=`;
8
+ const inline = process.argv.find((arg) => arg.startsWith(prefix));
9
+ if (inline) {
10
+ return inline.slice(prefix.length);
11
+ }
12
+ const idx = process.argv.indexOf(`--${name}`);
13
+ if (idx >= 0 && process.argv[idx + 1]) {
14
+ return process.argv[idx + 1];
15
+ }
16
+ return fallback;
17
+ }
18
+ const schema = parseArg('schema', process.env.OPENAPI_FILE);
19
+ const output = parseArg('output', 'generated/openapi-types.ts') ?? 'generated/openapi-types.ts';
20
+ if (!schema) {
21
+ console.error('Missing schema path. Use --schema <path-to-openapi-json>.');
22
+ process.exit(1);
23
+ }
24
+ const absoluteSchema = path.resolve(process.cwd(), schema);
25
+ const absoluteOutput = path.resolve(process.cwd(), output);
26
+ if (!existsSync(absoluteSchema)) {
27
+ console.error(`OpenAPI schema file does not exist: ${absoluteSchema}`);
28
+ process.exit(1);
29
+ }
30
+ mkdirSync(path.dirname(absoluteOutput), { recursive: true });
31
+ // openapi-typescript builds its output with the classic TypeScript compiler API, which
32
+ // TypeScript 7.0 does not ship (openapi-ts/openapi-typescript#2841). Running from an empty
33
+ // directory makes npx resolve it in an isolated tree alongside a TypeScript that still has that
34
+ // API, instead of picking up the host project's compiler. Exact pins: the
35
+ // isolated tree resolves outside any project .npmrc, so range specifiers
36
+ // would float past consumer supply-chain guards (min-release-age).
37
+ const isolatedCwd = mkdtempSync(path.join(os.tmpdir(), 'openapi-typescript-'));
38
+ try {
39
+ execSync(`npx --yes --package=openapi-typescript@7.13.0 --package=typescript@5.9.3 openapi-typescript "${absoluteSchema}" --output "${absoluteOutput}"`, { cwd: isolatedCwd, stdio: 'inherit' });
40
+ }
41
+ finally {
42
+ rmSync(isolatedCwd, { force: true, recursive: true });
43
+ }
44
+ console.log(`Generated: ${absoluteOutput}`);
@@ -0,0 +1,20 @@
1
+ import type { ClientOptions } from 'openapi-fetch';
2
+ import { z } from 'zod';
3
+ /**
4
+ * The `openapi-fetch` / `openapi-typescript` helper types that anything built
5
+ * on this client needs. They are re-exported here so downstream packages
6
+ * (`http-client-react` and `http-client-msw`) type against the same helper
7
+ * versions this package is pinned to, rather than each declaring its own
8
+ * dependency on the OpenAPI toolchain.
9
+ */
10
+ export type { Client as ApiClient, ClientOptions as ApiClientOptions, FetchResponse, MaybeOptionalInit, } from 'openapi-fetch';
11
+ export type { HttpMethod, MediaType, PathsWithMethod, RequiredKeysOf, } from 'openapi-typescript-helpers';
12
+ export type JsonSchema = z.core.JSONSchema.JSONSchema;
13
+ /**
14
+ * `options` is the full `openapi-fetch` client config minus `baseUrl`, which
15
+ * stays the first positional argument for backwards compatibility. It is what
16
+ * lets a test or a mock layer inject its own `fetch`.
17
+ */
18
+ export declare const createApiClient: <TPaths extends {}>(baseUrl?: string, options?: Omit<ClientOptions, 'baseUrl'>) => import("openapi-fetch").Client<TPaths, `${string}/${string}`>;
19
+ export declare function normalizeOpenApiRefs(value: unknown): unknown;
20
+ export declare function getComponentSchemaFromOpenApi(openApi: unknown, name: string): z.ZodType;
@@ -0,0 +1,37 @@
1
+ import createClient from 'openapi-fetch';
2
+ import { z } from 'zod';
3
+ /**
4
+ * `options` is the full `openapi-fetch` client config minus `baseUrl`, which
5
+ * stays the first positional argument for backwards compatibility. It is what
6
+ * lets a test or a mock layer inject its own `fetch`.
7
+ */
8
+ export const createApiClient = (baseUrl = '/', options) => {
9
+ return createClient({ ...options, baseUrl });
10
+ };
11
+ export function normalizeOpenApiRefs(value) {
12
+ if (Array.isArray(value)) {
13
+ return value.map(normalizeOpenApiRefs);
14
+ }
15
+ if (value && typeof value === 'object') {
16
+ const record = value;
17
+ const normalized = {};
18
+ for (const [key, entryValue] of Object.entries(record)) {
19
+ if (key === '$ref' && typeof entryValue === 'string') {
20
+ normalized[key] = entryValue.replace('#/components/schemas/', '#/$defs/');
21
+ }
22
+ else {
23
+ normalized[key] = normalizeOpenApiRefs(entryValue);
24
+ }
25
+ }
26
+ return normalized;
27
+ }
28
+ return value;
29
+ }
30
+ export function getComponentSchemaFromOpenApi(openApi, name) {
31
+ const schema = openApi;
32
+ const defs = normalizeOpenApiRefs(schema.components?.schemas ?? {});
33
+ return z.fromJSONSchema({
34
+ $ref: `#/$defs/${name}`,
35
+ $defs: defs,
36
+ });
37
+ }
@@ -0,0 +1,5 @@
1
+ import formatConfig from '@r0hitsharma/oxfmt-config';
2
+
3
+ export default {
4
+ ...formatConfig,
5
+ };
@@ -0,0 +1,7 @@
1
+ export default {
2
+ plugins: ['typescript'],
3
+ categories: {
4
+ correctness: 'warn',
5
+ suspicious: 'warn',
6
+ },
7
+ };
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@r0hitsharma/http-client-core",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "type": "module",
7
+ "main": "dist/src/index.js",
8
+ "types": "dist/src/index.d.ts",
9
+ "bin": {
10
+ "uikit-openapi-generate": "./dist/bin/generate-openapi.js"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/src/index.d.ts",
15
+ "default": "./dist/src/index.js"
16
+ }
17
+ },
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.build.json",
20
+ "clean": "rm -rf dist",
21
+ "prepare": "npm run build",
22
+ "type:check": "tsc -p tsconfig.json --noEmit",
23
+ "lint": "oxlint -c oxlint.config.ts --max-warnings=0 src",
24
+ "lint:fix": "oxlint -c oxlint.config.ts --fix src",
25
+ "format": "oxfmt -c oxfmt.config.ts --write src",
26
+ "format:check": "oxfmt -c oxfmt.config.ts --check src"
27
+ },
28
+ "dependencies": {
29
+ "openapi-fetch": "0.17.0",
30
+ "openapi-typescript-helpers": "0.1.0",
31
+ "zod": "4.5.4"
32
+ },
33
+ "peerDependencies": {
34
+ "openapi-typescript": "^7.13.0"
35
+ },
36
+ "devDependencies": {
37
+ "@r0hitsharma/oxfmt-config": "*",
38
+ "@r0hitsharma/tsconfig": "*",
39
+ "@types/node": "24.13.3",
40
+ "oxfmt": "0.67.0",
41
+ "oxlint": "1.82.0"
42
+ },
43
+ "version": "0.12.0-rohit-fork-ci.1",
44
+ "repository": {
45
+ "url": "https://github.com/r0hitsharma/uikit"
46
+ }
47
+ }
package/src/index.ts ADDED
@@ -0,0 +1,81 @@
1
+ import createClient from 'openapi-fetch';
2
+ import type { ClientOptions } from 'openapi-fetch';
3
+ import { z } from 'zod';
4
+
5
+ /**
6
+ * The `openapi-fetch` / `openapi-typescript` helper types that anything built
7
+ * on this client needs. They are re-exported here so downstream packages
8
+ * (`http-client-react` and `http-client-msw`) type against the same helper
9
+ * versions this package is pinned to, rather than each declaring its own
10
+ * dependency on the OpenAPI toolchain.
11
+ */
12
+ export type {
13
+ Client as ApiClient,
14
+ ClientOptions as ApiClientOptions,
15
+ FetchResponse,
16
+ MaybeOptionalInit,
17
+ } from 'openapi-fetch';
18
+ export type {
19
+ HttpMethod,
20
+ MediaType,
21
+ PathsWithMethod,
22
+ RequiredKeysOf,
23
+ } from 'openapi-typescript-helpers';
24
+
25
+ export type JsonSchema = z.core.JSONSchema.JSONSchema;
26
+
27
+ /**
28
+ * `options` is the full `openapi-fetch` client config minus `baseUrl`, which
29
+ * stays the first positional argument for backwards compatibility. It is what
30
+ * lets a test or a mock layer inject its own `fetch`.
31
+ */
32
+ export const createApiClient = <TPaths extends {}>(
33
+ baseUrl: string = '/',
34
+ options?: Omit<ClientOptions, 'baseUrl'>,
35
+ ) => {
36
+ return createClient<TPaths>({ ...options, baseUrl });
37
+ };
38
+
39
+ export function normalizeOpenApiRefs(value: unknown): unknown {
40
+ if (Array.isArray(value)) {
41
+ return value.map(normalizeOpenApiRefs);
42
+ }
43
+
44
+ if (value && typeof value === 'object') {
45
+ const record = value as Record<string, unknown>;
46
+ const normalized: Record<string, unknown> = {};
47
+
48
+ for (const [key, entryValue] of Object.entries(record)) {
49
+ if (key === '$ref' && typeof entryValue === 'string') {
50
+ normalized[key] = entryValue.replace(
51
+ '#/components/schemas/',
52
+ '#/$defs/',
53
+ );
54
+ } else {
55
+ normalized[key] = normalizeOpenApiRefs(entryValue);
56
+ }
57
+ }
58
+
59
+ return normalized;
60
+ }
61
+
62
+ return value;
63
+ }
64
+
65
+ export function getComponentSchemaFromOpenApi(
66
+ openApi: unknown,
67
+ name: string,
68
+ ): z.ZodType {
69
+ const schema = openApi as {
70
+ components?: { schemas?: Record<string, unknown> };
71
+ };
72
+ const defs = normalizeOpenApiRefs(schema.components?.schemas ?? {}) as Record<
73
+ string,
74
+ JsonSchema
75
+ >;
76
+
77
+ return z.fromJSONSchema({
78
+ $ref: `#/$defs/${name}`,
79
+ $defs: defs,
80
+ } satisfies JsonSchema);
81
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "@r0hitsharma/tsconfig/node",
3
+ "compilerOptions": {
4
+ "allowImportingTsExtensions": false,
5
+ "types": ["node"],
6
+ "noEmit": false,
7
+ "emitDeclarationOnly": false,
8
+ "declaration": true,
9
+ "declarationMap": false,
10
+ "rootDir": ".",
11
+ "outDir": "dist"
12
+ },
13
+ "include": ["src/**/*.ts", "bin/**/*.ts"]
14
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "@r0hitsharma/tsconfig/node",
3
+ "compilerOptions": {
4
+ "allowImportingTsExtensions": false,
5
+ "types": ["node"]
6
+ },
7
+ "include": [
8
+ "src/**/*.ts",
9
+ "bin/**/*.ts"
10
+ ]
11
+ }