UNPKG

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
"use strict"; 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