swiftfetch-elsolya
v0.1.0
Published
Framework-agnostic HTTP client with caching, dedupe, retry, abort - TypeScript first
Maintainers
Readme
SwiftFetch-elsolya (swiftfetch-elsolya)
مكتبة Framework-agnostic لجلب الداتا (HTTP client) بتشتغل مع أي Framework:
React / Next.js / Vue / Nuxt / Angular / Node … إلخ.
تركّز على:
- API بسيطة:
get / post / put / patch / delete - TypeScript first (Generics)
- Query params
- Headers و auth
- Abort / cancel
- Retry (اختياري)
- Caching + Deduplication (اختياريين)
ملاحظة مهمة: اسم حزمة npm لازم يكون lowercase، لذلك النشر يكون باسم:
swiftfetch-elsolya
التثبيت
npm i swiftfetch-elsolya
# أو
yarn add swiftfetch-elsolya
# أو
pnpm add swiftfetch-elsolyaلو بتستخدم Node أقل من 18، لازم توفر
fetch(مثلاً عبر undici).
Node 18+ و أغلب المتصفحات فيهاfetchجاهز.
Quick Start (Vanilla) — ينفع في أي مكان
1) إنشاء Client
import { createClient } from "swiftfetch-elsolya";
export const api = createClient({
baseURL: "https://api.example.com",
defaultHeaders: {
"Content-Type": "application/json",
},
// Retry (اختياري)
retry: {
retries: 2,
retryDelay: (attempt) => attempt * 500, // ms
retryOn: (err) => err.status >= 500, // جرّب تاني بس في أخطاء السيرفر
},
// Cache + Deduplication (اختياري)
cache: {
enabled: true,
staleTime: 30_000, // الداتا تعتبر fresh لمدة 30 ثانية
cacheTime: 300_000, // تفضل مخزنة 5 دقايق
},
});استخدام الـ HTTP Methods
كل الميثودز بتقبل Generics للـ TypeScript:
api.get<User[]>("/users")
GET
type User = { id: number; name: string };
const users = await api.get<User[]>("/users");GET مع Query Params
const users = await api.get<User[]>("/users", {
query: { page: 1, limit: 20, search: "ali" },
});POST
type CreateUserDto = { name: string };
const created = await api.post<User>("/users", {
body: { name: "Ali" } satisfies CreateUserDto,
});PUT
const updated = await api.put<User>("/users/1", {
body: { name: "Ali Updated" },
});PATCH
const patched = await api.patch<User>("/users/1", {
body: { name: "Ali Patched" },
});DELETE
await api.delete<void>("/users/1");Request عام (لو عايز method بأي قيمة)
const data = await api.request<User>({
method: "GET",
url: "/users/1",
});Headers و Auth
إضافة Authorization لكل الطلبات
export const api = createClient({
baseURL: "https://api.example.com",
defaultHeaders: () => ({
Authorization: `Bearer ${localStorage.getItem("token") ?? ""}`,
"Content-Type": "application/json",
}),
});أو إضافة Headers لطلب واحد
const users = await api.get<User[]>("/users", {
headers: { "X-Trace-Id": "abc-123" },
});Abort / Cancel Request (مهم جدًا في UI)
الطريقة القياسية (AbortController)
const ac = new AbortController();
const p = api.get<User[]>("/users", { signal: ac.signal });
// بعدين لو احتجت:
ac.abort();
await p;في React/Vue تقدر تعمل abort داخل cleanup لما الـ component يتشال.
أخطاء الطلبات (Errors)
المكتبة بترمي Error موحّد (Normalized) بالشكل ده تقريبًا:
try {
await api.get("/boom");
} catch (err) {
// err.message
// err.status
// err.data (لو السيرفر رجّع body للخطأ)
}استخدام SwiftFetch-elsolya في كل Framework
✅ React (CRA/Vite/React 18)
مثال GET + Abort + State
import { useEffect, useState } from "react";
import { api } from "./api";
type User = { id: number; name: string };
export function Users() {
const [users, setUsers] = useState<User[] | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const ac = new AbortController();
api.get<User[]>("/users", { signal: ac.signal })
.then(setUsers)
.catch((e) => {
if (e?.name !== "AbortError") setError(e.message ?? "Unknown error");
});
return () => ac.abort();
}, []);
if (error) return <div>Error: {error}</div>;
if (!users) return <div>Loading...</div>;
return (
<ul>
{users.map((u) => (
<li key={u.id}>{u.name}</li>
))}
</ul>
);
}مثال POST
await api.post("/users", { body: { name: "New User" } });✅ Next.js (App Router / Pages Router)
1) Server Component (App Router) — GET على السيرفر
// app/users/page.tsx
import { createClient } from "swiftfetch-elsolya";
type User = { id: number; name: string };
const api = createClient({
baseURL: process.env.API_URL!,
// في السيرفر غالبًا مفيش localStorage، خلي auth من cookies أو env
});
export default async function Page() {
// لو عايز تمنع كاش Next.js للطلب:
const users = await api.get<User[]>("/users", { next: { revalidate: 0 } });
return (
<div>
<h1>Users</h1>
<pre>{JSON.stringify(users, null, 2)}</pre>
</div>
);
}
nextهنا خيار اختياري لو أنت في Next.js وعايز تمرر تلميحات للكاش (زيrevalidate).
لو مش محتاجه، تجاهله.
2) Client Component (زي React عادي)
"use client";
import { useEffect, useState } from "react";
import { api } from "@/lib/api";
export default function UsersClient() {
const [data, setData] = useState<any>(null);
useEffect(() => {
api.get("/users").then(setData);
}, []);
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}3) Route Handler (API) — POST/GET على السيرفر
// app/api/users/route.ts
import { NextResponse } from "next/server";
import { createClient } from "swiftfetch-elsolya";
const api = createClient({ baseURL: process.env.API_URL! });
export async function GET() {
const users = await api.get("/users");
return NextResponse.json(users);
}
export async function POST(req: Request) {
const body = await req.json();
const created = await api.post("/users", { body });
return NextResponse.json(created);
}✅ Vue 3 (Vite) — Composable بسيط
// composables/useUsers.ts
import { ref, onMounted, onBeforeUnmount } from "vue";
import { api } from "../api";
type User = { id: number; name: string };
export function useUsers() {
const data = ref<User[] | null>(null);
const error = ref<string | null>(null);
const loading = ref(false);
const ac = new AbortController();
onMounted(async () => {
loading.value = true;
try {
data.value = await api.get<User[]>("/users", { signal: ac.signal });
} catch (e: any) {
if (e?.name !== "AbortError") error.value = e?.message ?? "Unknown error";
} finally {
loading.value = false;
}
});
onBeforeUnmount(() => ac.abort());
return { data, error, loading };
}✅ Nuxt 3 — Plugin + Injection
1) Plugin
// plugins/swiftfetch.ts
import { createClient } from "swiftfetch-elsolya";
export default defineNuxtPlugin(() => {
const config = useRuntimeConfig();
const api = createClient({
baseURL: config.public.apiBase,
defaultHeaders: () => ({
"Content-Type": "application/json",
// مثال: Authorization من cookie/ pinia / أي مصدر
}),
});
return {
provide: { api },
};
});في
nuxt.config.ts:
export default defineNuxtConfig({
runtimeConfig: {
public: {
apiBase: "https://api.example.com",
},
},
});2) استخدامه داخل أي Component/Composable
const { $api } = useNuxtApp();
const users = await $api.get("/users");POST/PUT/PATCH/DELETE
await $api.post("/users", { body: { name: "Ali" } });
await $api.put("/users/1", { body: { name: "Ali Updated" } });
await $api.patch("/users/1", { body: { name: "Ali Patched" } });
await $api.delete("/users/1");✅ Angular — Service (مع Observables)
1) Service
// api.service.ts
import { Injectable } from "@angular/core";
import { createClient } from "swiftfetch-elsolya";
import { from, Observable } from "rxjs";
import { environment } from "../environments/environment";
type User = { id: number; name: string };
@Injectable({ providedIn: "root" })
export class ApiService {
private api = createClient({ baseURL: environment.apiBase });
getUsers(): Observable<User[]> {
return from(this.api.get<User[]>("/users"));
}
createUser(dto: { name: string }): Observable<User> {
return from(this.api.post<User>("/users", { body: dto }));
}
deleteUser(id: number): Observable<void> {
return from(this.api.delete<void>(`/users/${id}`));
}
}2) استخدامه في Component
constructor(private api: ApiService) {}
ngOnInit() {
this.api.getUsers().subscribe(users => console.log(users));
}خيارات متقدمة
Timeout (لو حابب)
await api.get("/users", { timeoutMs: 10_000 });Cache per request (لو عايز تتحكم لطلب واحد)
await api.get("/users", {
cache: { enabled: true, staleTime: 5_000 },
});Force refetch (تجاهل الكاش)
await api.get("/users", { cache: { enabled: false } });API Reference (مختصر)
createClient(config)
client.get<T>(url, options?)
client.post<T>(url, options?)
client.put<T>(url, options?)
client.patch<T>(url, options?)
client.delete<T>(url, options?)
client.request<T>({ method, url, ...options })options غالبًا تشمل:
query?: Record<string, any>body?: anyheaders?: Record<string, string>signal?: AbortSignaltimeoutMs?: numberretry?: { retries?: number; retryDelay?: (n:number)=>number; retryOn?: (err)=>boolean }cache?: { enabled?: boolean; staleTime?: number; cacheTime?: number }- (Next.js فقط اختياري)
next?: { revalidate?: number }
License
Ahmed Alsolya
