@theholocron/http-client 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +94 -0
- package/dist/index.mjs +91 -0
- package/package.json +56 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
//#region src/errors.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Surfaced from every capability call that hits a vendor API. Wraps
|
|
4
|
+
* the underlying error with `status` (HTTP) and `details` so
|
|
5
|
+
* orchestrators can soft-skip rather than abort.
|
|
6
|
+
*/
|
|
7
|
+
declare class ProviderApiError extends Error {
|
|
8
|
+
readonly status: number | undefined;
|
|
9
|
+
readonly details?: unknown | undefined;
|
|
10
|
+
name: string;
|
|
11
|
+
constructor(message: string, status: number | undefined, details?: unknown | undefined);
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/rest-client.d.ts
|
|
15
|
+
interface RestClientConfig {
|
|
16
|
+
/** Default base URL (e.g. "https://api.github.com"). */
|
|
17
|
+
baseUrl: string;
|
|
18
|
+
/** Auth token. */
|
|
19
|
+
token: string;
|
|
20
|
+
/**
|
|
21
|
+
* How the token is sent.
|
|
22
|
+
* - `"bearer"` (default): `Authorization: Bearer <token>`
|
|
23
|
+
* - `"apikey"`: uses `apiKeyHeader` (e.g. `x-api-key`)
|
|
24
|
+
*/
|
|
25
|
+
tokenScheme?: "bearer" | "apikey";
|
|
26
|
+
/** Header name when `tokenScheme` is `"apikey"`. Defaults to `"x-api-key"`. */
|
|
27
|
+
apiKeyHeader?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Static headers merged into every request. Applied after the auth header
|
|
30
|
+
* so they can override defaults (e.g. GitHub's `accept` override).
|
|
31
|
+
*/
|
|
32
|
+
extraHeaders?: Record<string, string>;
|
|
33
|
+
/** Query params appended to every request URL (e.g. Vercel's `teamId`). */
|
|
34
|
+
defaultQuery?: Record<string, string>;
|
|
35
|
+
/** Vendor label used in error messages (e.g. `"GitHub"`). */
|
|
36
|
+
vendor?: string;
|
|
37
|
+
/** Override `fetch` for tests. Defaults to `globalThis.fetch`. */
|
|
38
|
+
fetch?: typeof fetch;
|
|
39
|
+
}
|
|
40
|
+
interface RequestOptions {
|
|
41
|
+
method?: "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
|
|
42
|
+
body?: unknown;
|
|
43
|
+
/** Per-request query params, merged after `defaultQuery`. */
|
|
44
|
+
query?: Record<string, string>;
|
|
45
|
+
/** Return `undefined` without parsing even on a 200 response. */
|
|
46
|
+
expectNoContent?: boolean;
|
|
47
|
+
}
|
|
48
|
+
interface RestClient {
|
|
49
|
+
readonly baseUrl: string;
|
|
50
|
+
request<T>(path: string, opts?: RequestOptions): Promise<T>;
|
|
51
|
+
}
|
|
52
|
+
declare function createRestClient(config: RestClientConfig): RestClient;
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/auth-resolver.d.ts
|
|
55
|
+
declare class AuthError extends Error {
|
|
56
|
+
name: string;
|
|
57
|
+
}
|
|
58
|
+
interface ResolveTokenInput {
|
|
59
|
+
/** From `--token` CLI flag. */
|
|
60
|
+
cliToken?: string;
|
|
61
|
+
/** Env vars; passed in for testability. Defaults to `process.env`. */
|
|
62
|
+
env?: NodeJS.ProcessEnv;
|
|
63
|
+
/** Keyring lookup fn; passed in for testability. */
|
|
64
|
+
keyring?: (provider: string) => string | null;
|
|
65
|
+
}
|
|
66
|
+
interface ResolveTokenConfig {
|
|
67
|
+
/** HOLOCRON_* env var (e.g. `"HOLOCRON_GH_TOKEN"`). */
|
|
68
|
+
envName: string;
|
|
69
|
+
/** Vendor's own env var (e.g. `"GITHUB_TOKEN"`). */
|
|
70
|
+
vendorEnvName: string;
|
|
71
|
+
/** Keyring service key (e.g. `"github"`). */
|
|
72
|
+
keyringService: string;
|
|
73
|
+
/** Full error message shown when no token is found. */
|
|
74
|
+
errorMessage: string;
|
|
75
|
+
/** Keyring backend — injected by the CLI layer. Defaults to no-op. */
|
|
76
|
+
getKeyringToken?: (provider: string) => string | null;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Returns a `resolveToken(input?)` function wired to the given env vars
|
|
80
|
+
* and keyring service. Plugins call this once at module level:
|
|
81
|
+
*
|
|
82
|
+
* ```ts
|
|
83
|
+
* export const resolveToken = createResolveToken({
|
|
84
|
+
* envName: "HOLOCRON_GITHUB_TOKEN",
|
|
85
|
+
* vendorEnvName: "GITHUB_TOKEN",
|
|
86
|
+
* keyringService: "github",
|
|
87
|
+
* errorMessage: "no GitHub token found ...",
|
|
88
|
+
* getKeyringToken, // injected from @theholocron/cli
|
|
89
|
+
* });
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
declare function createResolveToken(config: ResolveTokenConfig): (input?: ResolveTokenInput) => string;
|
|
93
|
+
//#endregion
|
|
94
|
+
export { AuthError, ProviderApiError, type RequestOptions, type ResolveTokenConfig, type ResolveTokenInput, type RestClient, type RestClientConfig, createResolveToken, createRestClient };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
//#region src/errors.ts
|
|
2
|
+
/**
|
|
3
|
+
* Surfaced from every capability call that hits a vendor API. Wraps
|
|
4
|
+
* the underlying error with `status` (HTTP) and `details` so
|
|
5
|
+
* orchestrators can soft-skip rather than abort.
|
|
6
|
+
*/
|
|
7
|
+
var ProviderApiError = class extends Error {
|
|
8
|
+
status;
|
|
9
|
+
details;
|
|
10
|
+
name = "ProviderApiError";
|
|
11
|
+
constructor(message, status, details) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.status = status;
|
|
14
|
+
this.details = details;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/rest-client.ts
|
|
19
|
+
function createRestClient(config) {
|
|
20
|
+
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
21
|
+
const vendor = config.vendor ?? "";
|
|
22
|
+
let baseUrl = config.baseUrl;
|
|
23
|
+
while (baseUrl.endsWith("/")) baseUrl = baseUrl.slice(0, -1);
|
|
24
|
+
const staticHeaders = { accept: "application/json" };
|
|
25
|
+
if (config.tokenScheme === "apikey") staticHeaders[config.apiKeyHeader ?? "x-api-key"] = config.token;
|
|
26
|
+
else staticHeaders["authorization"] = `Bearer ${config.token}`;
|
|
27
|
+
Object.assign(staticHeaders, config.extraHeaders);
|
|
28
|
+
return {
|
|
29
|
+
baseUrl,
|
|
30
|
+
async request(path, opts = {}) {
|
|
31
|
+
const url = new URL(`${baseUrl}${path.startsWith("/") ? path : "/" + path}`);
|
|
32
|
+
for (const [k, v] of Object.entries(config.defaultQuery ?? {})) url.searchParams.set(k, v);
|
|
33
|
+
for (const [k, v] of Object.entries(opts.query ?? {})) url.searchParams.set(k, v);
|
|
34
|
+
const headers = { ...staticHeaders };
|
|
35
|
+
const init = {
|
|
36
|
+
method: opts.method ?? "GET",
|
|
37
|
+
headers
|
|
38
|
+
};
|
|
39
|
+
if (opts.body !== void 0) {
|
|
40
|
+
headers["content-type"] = "application/json";
|
|
41
|
+
init.body = JSON.stringify(opts.body);
|
|
42
|
+
}
|
|
43
|
+
const tag = vendor ? `${vendor} ${init.method} ${path}` : `${init.method} ${path}`;
|
|
44
|
+
let res;
|
|
45
|
+
try {
|
|
46
|
+
res = await fetchImpl(url.toString(), init);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
throw new ProviderApiError(`${tag} failed: ${err instanceof Error ? `${err.name}: ${err.message}` : String(err)}`, 0, void 0);
|
|
49
|
+
}
|
|
50
|
+
if (!res.ok) {
|
|
51
|
+
const body = await res.text().catch(() => "");
|
|
52
|
+
throw new ProviderApiError(`${tag} → ${res.status}`, res.status, body);
|
|
53
|
+
}
|
|
54
|
+
if (opts.expectNoContent || res.status === 204) return void 0;
|
|
55
|
+
const text = await res.text();
|
|
56
|
+
if (!text) return void 0;
|
|
57
|
+
return JSON.parse(text);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/auth-resolver.ts
|
|
63
|
+
var AuthError = class extends Error {
|
|
64
|
+
name = "AuthError";
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Returns a `resolveToken(input?)` function wired to the given env vars
|
|
68
|
+
* and keyring service. Plugins call this once at module level:
|
|
69
|
+
*
|
|
70
|
+
* ```ts
|
|
71
|
+
* export const resolveToken = createResolveToken({
|
|
72
|
+
* envName: "HOLOCRON_GITHUB_TOKEN",
|
|
73
|
+
* vendorEnvName: "GITHUB_TOKEN",
|
|
74
|
+
* keyringService: "github",
|
|
75
|
+
* errorMessage: "no GitHub token found ...",
|
|
76
|
+
* getKeyringToken, // injected from @theholocron/cli
|
|
77
|
+
* });
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
function createResolveToken(config) {
|
|
81
|
+
const defaultKeyring = config.getKeyringToken ?? (() => null);
|
|
82
|
+
return function resolveToken(input = {}) {
|
|
83
|
+
const env = input.env ?? process.env;
|
|
84
|
+
const keyring = input.keyring ?? defaultKeyring;
|
|
85
|
+
const token = input.cliToken || env[config.envName] || env[config.vendorEnvName] || keyring(config.keyringService);
|
|
86
|
+
if (!token) throw new AuthError(config.errorMessage);
|
|
87
|
+
return token;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
export { AuthError, ProviderApiError, createResolveToken, createRestClient };
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@theholocron/http-client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared HTTP primitives for theholocron \u2014 REST client factory, auth resolver, and error types",
|
|
5
|
+
"homepage": "https://github.com/theholocron/clients/tree/main/packages/http-client#readme",
|
|
6
|
+
"bugs": "https://github.com/theholocron/clients/issues",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/theholocron/clients.git",
|
|
10
|
+
"directory": "packages/http-client"
|
|
11
|
+
},
|
|
12
|
+
"license": "GPL-3.0",
|
|
13
|
+
"author": "Newton Koumantzelis",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/index.mjs",
|
|
16
|
+
"types": "./dist/index.d.mts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.mts",
|
|
20
|
+
"import": "./dist/index.mjs",
|
|
21
|
+
"default": "./dist/index.mjs"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsdown",
|
|
26
|
+
"lint": "eslint .",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"test:watch": "vitest",
|
|
29
|
+
"test:coverage": "vitest run --coverage",
|
|
30
|
+
"typecheck": "tsc --noEmit"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^22.0.0",
|
|
34
|
+
"@theholocron/eslint-config": "catalog:",
|
|
35
|
+
"@theholocron/tsconfig": "catalog:",
|
|
36
|
+
"@theholocron/tsdown-config": "catalog:",
|
|
37
|
+
"@theholocron/vitest-config": "catalog:",
|
|
38
|
+
"@vitest/coverage-v8": "catalog:",
|
|
39
|
+
"@vitest/eslint-plugin": "catalog:",
|
|
40
|
+
"eslint": "catalog:",
|
|
41
|
+
"eslint-plugin-n": "catalog:",
|
|
42
|
+
"globals": "catalog:",
|
|
43
|
+
"tsdown": "catalog:",
|
|
44
|
+
"typescript": "catalog:",
|
|
45
|
+
"vitest": "catalog:"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"files": [
|
|
51
|
+
"dist",
|
|
52
|
+
"README.md"
|
|
53
|
+
],
|
|
54
|
+
"releases": "https://github.com/theholocron/clients/releases",
|
|
55
|
+
"wiki": "https://github.com/theholocron/clients/wiki"
|
|
56
|
+
}
|