@gitkraken/api-tools 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/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/) and this project adheres to [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - 2026-07-09
10
+
11
+ ### Added
12
+
13
+ - Initial release of `@gitkraken/api-tools`: a validation-agnostic HTTP/auth `Transport`, the `HttpError` type, and the `ApiRequest`/`ApiClientConfig` port types, extracted from `@gitkraken/milestones-tools` so they can back the broader v1 API library.
14
+ - Content-type-aware empty-body handling: a `204` (or an empty body with no/JSON content-type) resolves with `undefined` data, while an empty body the server typed as non-JSON (e.g. `text/plain`) resolves with `''`.
package/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Core Tools License
2
+
3
+ Copyright (c) 2025-2026 Axosoft, LLC dba GitKraken ("GitKraken")
4
+
5
+ This software and associated documentation files (the "Software") may be compiled as part of GitKraken software products, including the gitkraken/vscode-gitlens open source project (collectively, the "GitKraken Products"), to the extent the Software is a required component of the GitKraken Products; provided, however, that the Software and its functionality may only be used if you (and any entity that you represent) have agreed to, and are in compliance with, the GitKraken End User License Agreement, available at https://gitkraken.com/eula (the "EULA"), or other agreement governing the use of the Software, as agreed by you and GitKraken, and otherwise have a valid subscription for the correct number of user seats for the applicable version of the Software (e.g., GitLens Free+, GitLens Pro, GitLens Teams, and GitLens Enterprise).
6
+
7
+ The Software is licensed, not sold. This license only gives you some rights to use the Software. GitKraken reserves all other rights. Unless applicable law gives you more rights despite this limitation, you may use the Software only as expressly permitted in this license. In doing so, you must comply with any technical limitations in the Software that only allow you to use it in certain ways. You may not (i) work around any technical limitations in the Software, (ii) reverse engineer, decompile or disassemble the Software, or otherwise attempt to derive the source code for the Software, except and to the extent required by third party licensing terms governing use of certain open source components that may be included with the Software, (iii) remove, minimize, block or modify any notices of GitKraken or its suppliers in the Software, (iv) use the software in any way that is against the law, (v) host, share, publish, rent or lease the Software; (vi) distribute the Software as a stand-alone or integrated offering or combine it with any of your applications for others to use, (vii) use the Software other than in connection with validly licensed GitKraken Products or (viii) use any portion of the Software to create software with the same or similar functionality.
8
+
9
+ You agree that GitKraken and/or its licensors (as applicable) retain all right, title and interest in and to the Software and all modifications and/or patches thereto. You are not granted any other rights beyond what is expressly stated herein. Except as set forth above, it is forbidden to copy, merge, publish, distribute, sublicense, modify and/or sell the Software.
10
+
11
+ If you give feedback about the Software to GitKraken, you give to GitKraken, without charge, the right to use, share and commercialize your feedback in any way and for any purpose. You will not give feedback that is subject to a license that requires GitKraken to license its software or documentation to third parties because we include your feedback in them. These rights survive this License.
12
+
13
+ The full text of this Core Tools License shall be included in all copies or substantial portions of the Software.
14
+
15
+ This License and the EULA, if applicable, are the entire agreement for the Software. You must comply with all domestic and international export laws and regulations that apply to the Software, which include restrictions on destinations, end users and end use. This License is governed and construed by the laws of the State of Arizona, United States without regard to any choice or conflict of law principles or rules.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL GITKRAKEN, THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18
+
19
+ For all third party components incorporated into the Software, those components are licensed under the original license provided by the owner of the applicable component.
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # @gitkraken/api-tools
2
+
3
+ Validation-agnostic HTTP/auth transport for GitKraken client SDKs.
4
+
5
+ `Transport` wraps a host-injected request function (or a default `globalThis.fetch`
6
+ requester), attaches a fresh bearer token per request, unwraps the identity-service
7
+ `{ data, error }` envelope, and surfaces non-2xx (and 3xx redirects) as a typed
8
+ `HttpError`. Callers narrow the unwrapped data by passing an optional `parse` hook;
9
+ the transport itself owns no schema library.
@@ -0,0 +1,45 @@
1
+ //#region src/config.d.ts
2
+ /** A single HTTP request the library asks the host to perform. */
3
+ interface ApiRequestOptions {
4
+ method: string;
5
+ url: string;
6
+ headers?: Record<string, string>;
7
+ body?: unknown;
8
+ signal?: AbortSignal;
9
+ }
10
+ /** The structured response an {@link ApiRequest} resolves to. */
11
+ interface ApiResponse {
12
+ body: unknown;
13
+ status: number;
14
+ statusText?: string;
15
+ headers: Headers;
16
+ }
17
+ /**
18
+ * The request function the host injects so the library never owns the network.
19
+ * It takes a request and resolves a `{ body, status, headers }` response, the
20
+ * same shape GitKraken's other client SDKs use (e.g. `@gitkraken/provider-apis`
21
+ * via `contextFreeMakeRequest`). When omitted, the library uses a default
22
+ * implementation over `globalThis.fetch`.
23
+ */
24
+ type ApiRequest = (options: ApiRequestOptions) => Promise<ApiResponse>;
25
+ /** Optional structured logger. All methods are no-ops when omitted. */
26
+ interface LoggerPort {
27
+ debug(msg: string, meta?: unknown): void;
28
+ warn(msg: string, meta?: unknown): void;
29
+ error(msg: string, meta?: unknown): void;
30
+ }
31
+ /**
32
+ * Configuration for the HTTP transport.
33
+ *
34
+ * `request` is injected so the host owns the network (proxies, on-prem hosts,
35
+ * custom TLS); when omitted the transport uses a default `fetch`-based
36
+ * requester. `getToken` is called per request to supply a fresh bearer token.
37
+ */
38
+ interface ApiClientConfig {
39
+ baseUrl: string;
40
+ getToken: () => Promise<string>;
41
+ request?: ApiRequest;
42
+ logger?: LoggerPort;
43
+ }
44
+ //#endregion
45
+ export { ApiClientConfig, ApiRequest, ApiRequestOptions, ApiResponse, LoggerPort };
package/dist/config.js ADDED
@@ -0,0 +1 @@
1
+ export{};
@@ -0,0 +1,3 @@
1
+ import { ApiClientConfig, ApiRequest, ApiRequestOptions, ApiResponse, LoggerPort } from "./config.js";
2
+ import { ApiResult, HttpError, RequestInput, Transport } from "./transport.js";
3
+ export { type ApiClientConfig, type ApiRequest, type ApiRequestOptions, type ApiResponse, type ApiResult, HttpError, type LoggerPort, type RequestInput, Transport };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{HttpError as e,Transport as t}from"./transport.js";export{e as HttpError,t as Transport};
@@ -0,0 +1,50 @@
1
+ import { ApiClientConfig } from "./config.js";
2
+
3
+ //#region src/transport.d.ts
4
+ /** A non-2xx (or enveloped-error) response surfaced as a typed error carrying the parsed body. */
5
+ declare class HttpError extends Error {
6
+ readonly status: number;
7
+ readonly body: unknown;
8
+ readonly headers: Headers;
9
+ constructor(status: number, body: unknown, headers: Headers);
10
+ }
11
+ interface RequestInput<T> {
12
+ method: string;
13
+ path: string;
14
+ body?: unknown;
15
+ headers?: Record<string, string>;
16
+ signal?: AbortSignal;
17
+ /** Validates and narrows the unwrapped envelope data at the boundary. */
18
+ parse?: (data: unknown) => T;
19
+ }
20
+ /** A successful response: the unwrapped (and optionally parsed) `data` plus the status and response headers. */
21
+ interface ApiResult<T> {
22
+ /** `undefined` when the response carried no body (e.g. `204 No Content`). */
23
+ data: T | undefined;
24
+ status: number;
25
+ headers: Headers;
26
+ }
27
+ /**
28
+ * Thin HTTP layer over the injected request function. Builds the URL, attaches
29
+ * a fresh bearer token and JSON headers, then maps the structured response to an
30
+ * {@link ApiResult} carrying the status and headers alongside the data:
31
+ *
32
+ * - A `204 No Content`, or an empty JSON body, resolves with `undefined` data
33
+ * (it never throws on success), so a caller can distinguish it from a falsy
34
+ * JSON value via the returned `status`/`headers`. An empty non-JSON body (e.g.
35
+ * an empty `text/plain`) resolves with `''` rather than `undefined`.
36
+ * - A non-2xx response (or a 2xx carrying a non-null envelope `error`) becomes
37
+ * an {@link HttpError}.
38
+ * - A JSON body is parsed and the identity-service `{ data, error }` envelope
39
+ * unwrapped to its `data`, which an optional `parse` then validates; a parse
40
+ * failure on a JSON body is surfaced as an {@link HttpError}.
41
+ * - A non-JSON body (per `Content-Type`) is handed back untouched.
42
+ */
43
+ declare class Transport {
44
+ private readonly config;
45
+ private readonly requester;
46
+ constructor(config: ApiClientConfig);
47
+ request<T>(input: RequestInput<T>): Promise<ApiResult<T>>;
48
+ }
49
+ //#endregion
50
+ export { ApiResult, HttpError, RequestInput, Transport };
@@ -0,0 +1 @@
1
+ var e=class extends Error{status;body;headers;constructor(e,t,n){super(`HTTP ${e}`),this.name=`HttpError`,this.status=e,this.body=t,this.headers=n}};const t=async e=>{let t=await globalThis.fetch(e.url,{method:e.method,headers:e.headers,body:e.body===void 0?void 0:JSON.stringify(e.body),signal:e.signal,redirect:`manual`}),n=await t.text();return{body:n.length===0?null:n,status:t.status,statusText:t.statusText,headers:t.headers}};var n=class{config;requester;constructor(e){this.config=e,this.requester=e.request??t}async request(t){let n=await this.config.getToken(),s=await this.requester({method:t.method,url:`${this.config.baseUrl}${t.path}`,headers:{Authorization:`Bearer ${n}`,"Content-Type":`application/json`,...t.headers},body:t.body,signal:t.signal}),{status:c,headers:l}=s,u=c>=200&&c<300,d=l.get(`content-type`),f=i(l,`application/json`);if(c===204||r(s.body)){if(!u)throw new e(c,s.body??null,l);return c!==204&&d!=null&&!f?{data:t.parse?t.parse(``):``,status:c,headers:l}:{data:void 0,status:c,headers:l}}let p=a(s.body,f,c,l);if(!u)throw new e(c,p,l);if(typeof s.body==`string`&&!f)return{data:t.parse?t.parse(p):p,status:c,headers:l};let m=o(p,c,l);return{data:t.parse?t.parse(m):m,status:c,headers:l}}};function r(e){return e==null||typeof e==`string`&&e.length===0}function i(e,t){return(e.get(`content-type`)?.toLowerCase())?.startsWith(t.toLowerCase())??!1}function a(t,n,r,i){if(typeof t!=`string`||!n)return t;try{return JSON.parse(t)}catch{throw new e(r,t,i)}}function o(t,n,r){if(typeof t!=`object`||!t)throw new e(n,t,r);if(`data`in t||`error`in t){let i=t;if(i.error!=null)throw new e(n,t,r);return i.data}return t}export{e as HttpError,n as Transport};
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@gitkraken/api-tools",
3
+ "version": "0.1.0",
4
+ "description": "Validation-agnostic HTTP/auth transport for GitKraken client SDKs (token-per-request, envelope unwrapping).",
5
+ "license": "SEE LICENSE IN LICENSE",
6
+ "author": "Axosoft, LLC dba GitKraken",
7
+ "engines": {
8
+ "node": ">= 22"
9
+ },
10
+ "type": "module",
11
+ "sideEffects": false,
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "CHANGELOG.md"
21
+ ],
22
+ "devDependencies": {
23
+ "@types/node": "^22.0.0",
24
+ "typescript": "^6.0.2",
25
+ "vitest": "^4.1.1"
26
+ },
27
+ "scripts": {
28
+ "build": "tsdown --config-loader=unrun",
29
+ "build:dev": "tsdown --config tsdown.dev.config.ts --config-loader=unrun",
30
+ "typecheck": "tsc --noEmit",
31
+ "test": "vitest run"
32
+ }
33
+ }