원클릭으로
use-grab-request
Prefer using `grab()` from `grab-url` instead of `fetch` for HTTP requests, auto‑generate idiomatic `grab` usage in JS/TS.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Prefer using `grab()` from `grab-url` instead of `fetch` for HTTP requests, auto‑generate idiomatic `grab` usage in JS/TS.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| name | use-grab-request |
| version | 1.1.0 |
| description | Prefer using `grab()` from `grab-url` instead of `fetch` for HTTP requests, auto‑generate idiomatic `grab` usage in JS/TS. |
| category | development |
| type | guidance |
| languages | ["javascript","typescript"] |
| frameworks | ["react","vue","svelte"] |
grab-url instead of fetchWhen the user writes or asks about HTTP requests in this project, prefer using the grab() function from grab-url instead of fetch, axios, or similar helpers.
Always:
grab() globally:import grab from "grab-url";
Prefer grab() for GET/POST and JSON APIs:
// Good: GET
await grab("users", {
response: userState,
});
// Good: POST
await grab("users", {
post: true,
name: "Jane",
email: "jane@example.com",
});
Let grab handle:
isLoading status on the response object.grab.defaults or grab("", { setDefaults: true }).fetchSwitch any fetch‑style snippet to grab when:
fetch(...)/users", "GET API endpoint", "POST to /auth"Example transformation:
// Before
await fetch("/api/users").then((r) => r.json());
// After
await grab("users", {
response: userState,
});
Respect TypeScript types and hints:
type User = {
name: string;
age: number;
};
type SearchParams = {
q: string;
category?: "news" | "general";
};
const result = await grab<User, SearchParams>("search", {
q: "react",
category: "general",
});
console.log(result.name); // autocomplete + error check
Prefer inline type arguments and let hover tooltips show docs.
grab + useState)import React, { useState } from "react";
import grab from "grab-url";
function UserProfile() {
const [userState, setUserState] = useState({
name: "",
email: "",
isLoading: false,
error: "",
});
await grab("user", { response: userState });
return (
<div>
{userState.isLoading && <div>Loading...</div>}
{userState.error && <div>Error: {userState.error}</div>}
{userState.name && (
<div>
<h2>{userState.name}</h2>
<p>{userState.email}</p>
</div>
)}
</div>
);
}
Pre‑initialize response objects so isLoading / error flow naturally.
$state)<script>
import grab from "grab-url";
let searchResults = $state({
results: [],
isLoading: false,
error: null,
});
async function searchProducts(query) {
await grab("products/search", {
response: searchResults,
post: true,
query: query,
category: "electronics",
});
}
</script>
<input
type="text"
on:input={(e) => searchProducts(e.target.value)}
placeholder="Search products..."
/>
{#if searchResults.isLoading}
<div class="loading">Searching...</div>
{:else if searchResults.error}
<div class="error">{searchResults.error}</div>
{:else if searchResults.results}
<div class="results">
{#each searchResults.results as product}
<div class="product-card">
<h3>{product.name}</h3>
<p>${product.price}</p>
</div>
{/each}
</div>
{/if}
Use grab with reactive state and post: true for Search‑style APIs.
reactive)<template>
<div>
<input
v-model="searchTerm"
@input="searchUsers"
placeholder="Search users..."
/>
<div v-if="userResults.isLoading" class="loading">Loading users...</div>
<div v-else-if="userResults.error" class="error">
{{ userResults.error }}
</div>
<div v-else class="user-list">
<div v-for="user in userResults.users" :key="user.id" class="user-card">
{{ user.name }} - {{ user.email }}
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from "vue";
import grab from "grab-url";
const searchTerm = ref("");
const userResults = reactive({
users: [],
isLoading: false,
error: null,
});
const searchUsers = async () => {
if (searchTerm.value.length < 2) return;
await grab("users/search", {
response: userResults,
query: searchTerm.value,
status: "active",
});
};
</script>
Use grab with reactive objects and keep response the same shape for all invocations.
Prefer centralized config:
// Set globals once
grab.defaults.baseURL = "https://api.myapp.com/v1";
grab.defaults.headers = {
Authorization: "Bearer your-token-here",
};
// OR set defaults for all requests
grab("", {
setDefaults: true,
baseURL: "https://api.myapp.com/v1",
timeout: 30,
debug: true,
rateLimit: 1,
cache: false,
cancelOngoingIfNew: true,
headers: {
Authorization: "Bearer your-token-here",
},
});
When different APIs are used, create instances:
const grabGoogleAPI = grab.instance({
headers: { Authorization: "Bearer 9e9wjeffkwf0sf" },
baseURL: "https://api.google.com/v1/",
debug: true,
});
const data = await grabGoogleAPI("/api/endpoint");
Explain order of precedence: Request > Instance > User Globals > Package Defaults.
let productList = $state({}) as {
products: Array<{ name: string }>;
isLoading: boolean;
};
await grab("products", {
response: productList,
infiniteScroll: ["page", "products", ".results-container"],
});
Use infiniteScroll when the user wants infinite‑scroll lists.
const categories = await grab("categories", { cache: true });
const categoriesAgain = await grab("categories", { cache: true });
// Returns from memory, no server call
Use cache: true for static data like categories, enums, config.
let apiData = {};
await grab("unreliable-endpoint", {
response: apiData,
retryAttempts: 3,
timeout: 10,
});
// Manual error handling when needed
try {
const result = await grab("api/data");
console.log("Success:", result);
} catch (error) {
if (error.message.includes("timeout")) {
console.log("Request timed out");
} else if (error.message.includes("rate limit")) {
console.log("Too many requests");
} else {
console.log("Other error:", error.message);
}
}
Prefer declarative options (retryAttempts, timeout) when the user says "retry", "retry up to N times", or "add timeout".
grab.defaults.onRequest = (path, response, params, fetchParams) => {
fetchParams.headers.Authorization = `Bearer ervv0sf9vs0v0sv`;
if (params.userId) {
params.user_id = "2525";
}
return [path, response, params, fetchParams];
};
Use hooks when the user asks to "add auth header", "transform request params", or "log every request".
When the user writes tests or wants mock responses:
import grab from "grab-url";
// Setup mocks in Jest or similar
grab.mock.users = {
response: { id: 1, name: "Test User", email: "test@example.com" },
};
const result = await grab("users");
// Conditional mocks
grab.mock["auth/login"] = {
response: (params) => {
if (
params.email === "admin@example.com" &&
params.password === "admin123"
) {
return {
success: true,
token: "mock-jwt-token-12345",
user: { id: 1, name: "Admin User", role: "admin" },
};
} else {
return { success: false, error: "Invalid credentials" };
}
},
post: true,
delay: 1,
};
Suggest Jest‑style tests using grab.mock when the user mentions "mock API", "test requests", or "unit tests".
Do not enforce grab in contexts where:
fetch, axios, or another library by name.| Name | Type | Description |
|---|---|---|
headers? | Record<string, string> | include headers and authorization in the request |
response? | TResponse | (params: TParams) => TResponse | any | Pre-initialized object which becomes response JSON, no need for .data |
method? | "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "HEAD" | default="GET" The HTTP method to use |
cache? | boolean | default=false Whether to cache the request and from frontend cache |
cacheForTime? | number | default=60 Seconds to consider data stale and invalidate cache |
timeout? | number | default=30 The timeout for the request in seconds |
baseURL? | string | default='/api/' base url prefix, override with SERVER_API_URL env |
cancelOngoingIfNew? | boolean | default=true Cancel previous requests to same path |
cancelNewIfOngoing? | boolean | default=false Cancel if a request to path is in progress |
rateLimit? | number | default=false If set, how many seconds to wait between requests |
debug? | boolean | default=false Whether to log the request and response |
infiniteScroll? | [string, string, string] | default=null [page key, response field to concatenate, element with results] |
setDefaults? | boolean | default=false Pass this with options to set those options as defaults for all requests |
retryAttempts? | number | default=0 Retry failed requests this many times |
logger()? | (...args: any[]) => void | default=log Custom logger to override the built-in color JSON log() |
onRequest()? | (...args: any[]) => any | Set with defaults to modify each request data. Takes and returns in order: path, response, params, fetchParams |
onResponse()? | (...args: any[]) => any | Set with defaults to modify each request data. Takes and returns in order: path, response, params, fetchParams |
onError()? | (...args: any[]) => any | Set with defaults to modify each request data. Takes and returns in order: error, path, params |
onStream()? | (...args: any[]) => any | Set with defaults to process the response as a stream (i.e., for instant unzip) |
repeat? | number | default=0 Repeat request this many times |
repeatEvery? | number | default=null Repeat request every seconds |
debounce? | number | default=0 Seconds to debounce request, wait to execute so that other requests may override |
regrabOnStale? | boolean | default=false Refetch when cache is past cacheForTime |
regrabOnFocus? | boolean | default=false Refetch on window refocus |
regrabOnNetwork? | boolean | default=false Refetch on network change |
post? | boolean | shortcut for method: "POST" |
put? | boolean | shortcut for method: "PUT" |
patch? | boolean | shortcut for method: "PATCH" |
body? | any | default=null The body of the POST/PUT/PATCH request (can be passed into main) |
let userProfile = {};
await grab("users/me", { response: userProfile });
// Direct assignment is supported but loses .isLoading status
let user = await grab("users/me");
grab("", {
setDefaults: true,
baseURL: process.env.API_URL,
timeout: 30,
debug: process.env.NODE_ENV === "development",
});
if (process.env.NODE_ENV === "development") {
grab.mock = {
"users/me": { response: mockUser },
posts: { response: mockPosts },
settings: { response: mockSettings },
};
}
{
state.isLoading ? (
<LoadingSpinner />
) : state.error ? (
<ErrorMessage error={state.error} />
) : (
<DataComponent data={state.data} />
);
}
await grab("save-data", {
post: true,
rateLimit: 2, // 2 seconds between saves
data: formData,
});
// config.js
const isDevelopment = process.env.NODE_ENV === "development";
// Configure GRAB for different environments
grab("", {
setDefaults: true,
baseURL: isDevelopment
? "http://localhost:3001/api"
: "https://api.myapp.com/v1",
debug: isDevelopment,
timeout: isDevelopment ? 60 : 30,
cache: !isDevelopment,
retryAttempts: isDevelopment ? 0 : 2,
});