tspace-spear
Version:
tspace-spear is a lightweight, high-performance API framework for Node.js that leverages the native HTTP server and supports uWebSockets.js (C++) for maximum speed and efficiency.
163 lines • 4.98 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ApiClient = void 0;
let fetchFn = null;
const getFetch = async () => {
if (fetchFn)
return fetchFn;
// Browser OR modern Node v18+ (preferred)
if (typeof globalThis.fetch === "function") {
fetchFn = globalThis.fetch.bind(globalThis);
return fetchFn;
}
// Legacy Node fallback
const mod = await Promise.resolve().then(() => __importStar(require("node-fetch")));
fetchFn = mod.default;
return fetchFn;
};
const isFormData = (value) => {
return (value != null &&
typeof value === "object" &&
typeof value.append === "function" &&
typeof value.getHeaders === "function");
};
/**
* Type-safe HTTP client built on top of the native Fetch API.
*
* `ApiClient` provides end-to-end type safety for your API routes,
* including:
*
* - `params` typing
* - `query` typing
* - `body` typing
* - typed file uploads
* - fully inferred response types
*
* Route types are inferred from your server route definitions,
* giving you autocomplete and compile-time validation across
* the entire request lifecycle.
*
* @template TRoutes Application route definitions.
*
* @example
* ```ts
* import app from '../server/app';
*
* const client = new ApiClient<typeof app.contract>()
*
* const res = await client.get("/cats", {
* query: {
* id: "1",
* },
* })
*
* // fully typed response
* if(res.ok)
* console.log(res.cats)
* ```
*/
class ApiClient {
baseURL;
constructor(baseURL) {
this.baseURL = baseURL;
}
async request(method, path, input) {
let url = this.baseURL + path;
if (input?.params) {
for (const key in input.params) {
url = url.replace(`:${key}`, encodeURIComponent(input.params[key]));
}
}
if (input?.query) {
const queryString = new URLSearchParams(input.query).toString();
if (queryString) {
url += `?${queryString}`;
}
}
fetchFn = await getFetch();
if (!fetchFn) {
throw new Error("Fetch is not available. Use Node 18+ or polyfill.");
}
let body = input?.body
? JSON.stringify(input.body)
: undefined;
let headers = {
"Content-Type": "application/json",
};
const isFileUpload = isFormData(input?.body);
if (isFileUpload) {
body = input?.body;
headers = undefined;
}
const res = await fetchFn(url, {
method: method,
headers,
body
});
const contentType = res.headers.get("content-type");
const isJson = contentType?.includes("application/json");
const data = isJson
? await res.json()
: await res.text();
return {
ok: res.ok,
headers: res.headers,
status: res.status,
data: data,
};
}
async get(path, ...args) {
const input = args[0];
return this.request("GET", path, input);
}
async post(path, ...args) {
const input = args[0];
return this.request("POST", path, input);
}
async put(path, ...args) {
const input = args[0];
return this.request("PUT", path, input);
}
async patch(path, ...args) {
const input = args[0];
return this.request("PATCH", path, input);
}
async delete(path, ...args) {
const input = args[0];
return this.request("DELETE", path, input);
}
async upload(path, options) {
const { method = "POST", formdata } = options;
return this.request(method, path,
//@ts-ignore
{
body: formdata
});
}
}
exports.ApiClient = ApiClient;
exports.default = ApiClient;
//# sourceMappingURL=index.js.map