open-index/open-npm huggingface dataset:
npm package: @spoons-and-mirrors/skill-convex-docs
Description: OpenCode plugin that provides Convex documentation as a skill
Latest version: 0.2.1

# Convex Skills

An OpenCode plugin that provides two skills for AI assistants working with Convex.

## Skills

### `convex-docs`
A **token-efficient** sitemap of all Convex documentation pages. The skill explains how to construct URLs so LLMs can fetch any doc page as markdown.

- Auto-updates from the Convex sitemap on OpenCode startup
- Only updates when the sitemap has actually changed

### `convex-rules`
Coding rules and patterns for generating correct Convex code. Covers:

- Function definitions with validators
- Database access patterns
- Query and mutation rules
- Actions, HTTP endpoints, crons
- TypeScript best practices

## Installation

Add to your OpenCode config plugin array:

```
"@spoons-and-mirrors/skill-convex-docs@latest"
```
npm package: @spoosh/devtool
Description: Visual debugging plugin for Spoosh - Chrome DevTools panel integration
Latest version: 0.8.0

# @spoosh/devtool

Visual debugging panel for Spoosh. See every request, plugin step, and cached state in your browser's DevTools.

## Installation

### 1. Install the Chrome Extension

Install the [Spoosh DevTools extension](https://chromewebstore.google.com/detail/spoosh-devtools/mcjbgbkoieeebhflnehdbfkidlpkjdhi) from the Chrome Web Store.

### 2. Install the npm package

```bash
npm install @spoosh/devtool
```

## Usage

```typescript
import { Spoosh } from "@spoosh/core";
import { devtool } from "@spoosh/devtool";

const client = new Spoosh("/api").use([
  devtool(),
  // other plugins...
]);
```

Open Chrome DevTools and navigate to the "Spoosh" panel to see your requests.

## Features

| Feature               | Description                                                |
| --------------------- | ---------------------------------------------------------- |
| **Request Timeline**  | See every request with timing, status, and duration        |
| **Plugin Steps**      | Watch middleware execution order with before/after diffs   |
| **State Inspector**   | Browse cache entries, subscriber counts, refetch or delete |
| **Event Log**         | View invalidations, refetch triggers, custom plugin events |
| **Status Badges**     | Pending, success, error, stale, fresh indicators           |
| **Filter & Search**   | Filter by operation type, search by path or query key      |
| **Theme Switching**   | Follows Chrome DevTools theme                              |
| **Sensitive Headers** | Toggle to reveal/hide auth headers with eye icon           |
| **Export**            | Save traces as JSON for analysis                           |
| **Settings**          | Max history size, auto-follow, show/hide passed plugins    |

## Options

```typescript
devtool({
  enabled: true,
  sensitiveHeaders: ["authorization", "cookie", "x-api-key"],
  maxHistory: 50,
  maxMessages: 100,
});
```

| Option             | Type       | Default                                         | Description                                |
| ------------------ | ---------- | ----------------------------------------------- | ------------------------------------------ |
| `enabled`          | `boolean`  | `true`                                          | Enable or disable the devtool              |
| `sensitiveHeaders` | `string[]` | `["authorization", "cookie", "x-api-key", ...]` | Headers to redact in UI and exports        |
| `maxHistory`       | `number`   | `50`                                            | Maximum number of traces to keep in memory |
| `maxMessages`      | `number`   | `100`                                           | Maximum messages per SSE subscription      |

## Production

The plugin does nothing when:

- `enabled: false`
- Running on server (SSR)

```typescript
devtool({ enabled: process.env.NODE_ENV === "development" });
```

## Documentation

See [full documentation](https://spoosh.dev/docs/react/devtool) for detailed usage.
npm package: @spoosh/plugin-cache
Description: Response caching plugin for Spoosh with configurable stale time
Latest version: 0.7.0

# @spoosh/plugin-cache

Response caching plugin for Spoosh with configurable stale time.

**[Documentation](https://spoosh.dev/docs/react/plugins/cache)** · **Requirements:** TypeScript >= 5.0 · **Peer Dependencies:** `@spoosh/core`

## Installation

```bash
npm install @spoosh/plugin-cache
```

## Usage

```typescript
import { Spoosh } from "@spoosh/core";
import { cachePlugin } from "@spoosh/plugin-cache";

const spoosh = new Spoosh<ApiSchema, Error>("/api").use([
  cachePlugin({ staleTime: 5000 }),
]);

// Per-query override
useRead((api) => api("posts").GET(), { staleTime: 10000 });
```

## How It Works

The cache plugin runs with **priority -10**, meaning it executes early in the middleware chain to check the cache before other plugins (like retry, debug) run. This ensures maximum efficiency by short-circuiting requests when cached data is available.

## Options

### Plugin Config

| Option      | Type     | Default | Description                        |
| ----------- | -------- | ------- | ---------------------------------- |
| `staleTime` | `number` | `0`     | Default stale time in milliseconds |

### Per-Request Options

| Option      | Type     | Description                          |
| ----------- | -------- | ------------------------------------ |
| `staleTime` | `number` | Override stale time for this request |

### Write Options

Clear cache after a mutation completes successfully:

```typescript
const { trigger } = useWrite((api) => api("auth/logout").POST());

// Clear cache after logout
await trigger({ clearCache: true });

// Clear cache + trigger all queries to refetch
await trigger({ clearCache: true, invalidate: "*" });
```

| Option       | Type      | Description                                   |
| ------------ | --------- | --------------------------------------------- |
| `clearCache` | `boolean` | Clear all cached data after mutation succeeds |

## Instance API

The plugin exposes a `clearCache` function for manually clearing all cached data:

```typescript
import { create } from "@spoosh/react";

const { useRead, clearCache } = create(spoosh);

// Clear all cached data only (no refetch)
function handleLogout() {
  clearCache();
}

// Clear cache and trigger all queries to refetch
function handleUserSwitch() {
  clearCache({ refetchAll: true });
}
```

| Method                             | Description                                       |
| ---------------------------------- | ------------------------------------------------- |
| `clearCache()`                     | Clears all cached data without triggering refetch |
| `clearCache({ refetchAll: true })` | Clears cache and triggers all queries to refetch  |
npm package: @spoosh/plugin-debounce
Description: Request debouncing plugin for Spoosh - waits for inactivity before fetching
Latest version: 0.4.1

# @spoosh/plugin-debounce

Request debouncing plugin for Spoosh - waits for inactivity before fetching.

**[Documentation](https://spoosh.dev/docs/react/plugins/debounce)** · **Requirements:** TypeScript >= 5.0 · **Peer Dependencies:** `@spoosh/core`

## Installation

```bash
npm install @spoosh/plugin-debounce
```

## Usage

```typescript
import { Spoosh } from "@spoosh/core";
import { debouncePlugin } from "@spoosh/plugin-debounce";

const spoosh = new Spoosh<ApiSchema, Error>("/api").use([debouncePlugin()]);

// Wait 300ms after typing stops before fetching
const { data } = useRead(
  (api) => api("search").GET({ query: { q: searchTerm } }),
  { debounce: 300 }
);

// Conditional debounce - only debounce when search query changes
const { data } = useRead(
  (api) => api("search").GET({ query: { q: searchTerm, page } }),
  { debounce: ({ prevQuery }) => (prevQuery?.q !== searchTerm ? 300 : 0) }
);
```

## Options

### Per-Request Options

| Option     | Type                            | Description                                                          |
| ---------- | ------------------------------- | -------------------------------------------------------------------- |
| `debounce` | `number \| (context) => number` | Milliseconds to wait, or function receiving previous request context |

### Debounce Function Context

When using a function, you receive:

| Property     | Type     | Description               |
| ------------ | -------- | ------------------------- |
| `prevQuery`  | `object` | Previous query parameters |
| `prevParams` | `object` | Previous path parameters  |
npm package: @spoosh/plugin-debug
Description: Debug plugin for Spoosh - logs detailed request lifecycle information
Latest version: 0.2.5
Deprecated: Package deprecated. Please use @spoosh/devtool instead.

# @spoosh/plugin-debug

Debug plugin for Spoosh - logs detailed request lifecycle information.

**[Documentation](https://spoosh.dev/docs/react/plugins/debug)** · **Requirements:** TypeScript >= 5.0 · **Peer Dependencies:** `@spoosh/core`

## Installation

```bash
npm install @spoosh/plugin-debug
```

## Usage

```typescript
import { Spoosh } from "@spoosh/core";
import { debugPlugin } from "@spoosh/plugin-debug";

// Basic usage - logs all phases
const client = new Spoosh<ApiSchema, Error>("/api").use([debugPlugin()]);

// With cache logging
const client = new Spoosh<ApiSchema, Error>("/api").use([
  debugPlugin({ logCache: true }),
]);

// Custom logger with object shape
const client = new Spoosh<ApiSchema, Error>("/api").use([
  debugPlugin({
    logger: (entry) => {
      console.log(entry.phase, entry.path, entry.state.data);
    },
  }),
]);

// Disable in production
const client = new Spoosh<ApiSchema, Error>("/api").use([
  debugPlugin({ enabled: process.env.NODE_ENV === "development" }),
]);
```

## Options

### Plugin Config

| Option     | Type                             | Default | Description                         |
| ---------- | -------------------------------- | ------- | ----------------------------------- |
| `enabled`  | `boolean`                        | `true`  | Enable/disable logging              |
| `logCache` | `boolean`                        | `false` | Include cache entries in log output |
| `logger`   | `(entry: DebugLogEntry) => void` | -       | Custom logger function              |

### DebugLogEntry

| Property           | Type       | Description                                |
| ------------------ | ---------- | ------------------------------------------ |
| `phase`            | `string`   | Current phase (onMount, beforeFetch, etc.) |
| `operationType`    | `string`   | "read", "write", or "infiniteRead"         |
| `method`           | `string`   | HTTP method                                |
| `path`             | `string`   | Request path                               |
| `queryKey`         | `string`   | Unique query key                           |
| `requestTimestamp` | `number`   | Request timestamp                          |
| `tags`             | `string[]` | Cache tags                                 |
| `request`          | `unknown`  | Request options                            |
| `state`            | `object`   | Current state (loading, data, error, etc.) |
| `response`         | `object`   | Response data (if available)               |
| `cacheEntries`     | `array`    | Cache entries (if logCache enabled)        |
npm package: @spoosh/plugin-deduplication
Description: Request deduplication plugin for Spoosh - prevents duplicate in-flight requests
Latest version: 0.4.0

# @spoosh/plugin-deduplication

Request deduplication plugin for Spoosh - prevents duplicate in-flight requests.

**[Documentation](https://spoosh.dev/docs/react/plugins/deduplication)** · **Requirements:** TypeScript >= 5.0 · **Peer Dependencies:** `@spoosh/core`

## Installation

```bash
npm install @spoosh/plugin-deduplication
```

## Usage

```typescript
import { Spoosh } from "@spoosh/core";
import { deduplicationPlugin } from "@spoosh/plugin-deduplication";

const spoosh = new Spoosh<ApiSchema, Error>("/api").use([
  deduplicationPlugin(),
]);

const spoosh = new Spoosh<ApiSchema, Error>("/api").use([
  deduplicationPlugin({ write: "in-flight" }),
]);

useRead((api) => api("posts").GET(), { dedupe: false });
```

## Options

### Plugin Config

| Option  | Type                   | Default       | Description                   |
| ------- | ---------------------- | ------------- | ----------------------------- |
| `read`  | `"in-flight" \| false` | `"in-flight"` | Deduplication mode for reads  |
| `write` | `"in-flight" \| false` | `false`       | Deduplication mode for writes |

### Per-Request Options

| Option   | Type                   | Description                             |
| -------- | ---------------------- | --------------------------------------- |
| `dedupe` | `"in-flight" \| false` | Override deduplication for this request |

### Modes

- `"in-flight"` - Reuse existing in-flight request promise if one exists
- `false` - Always make a new request
npm package: @spoosh/plugin-initial-data
Description: Initial data plugin for Spoosh - show data immediately before fetch completes
Latest version: 0.4.1

# @spoosh/plugin-initial-data

Initial data plugin for Spoosh - show data immediately before fetch completes.

**[Documentation](https://spoosh.dev/docs/react/plugins/initial-data)** · **Requirements:** TypeScript >= 5.0 · **Peer Dependencies:** `@spoosh/core`

## Installation

```bash
npm install @spoosh/plugin-initial-data
```

## Usage

```typescript
import { Spoosh } from "@spoosh/core";
import { initialDataPlugin } from "@spoosh/plugin-initial-data";

const spoosh = new Spoosh<ApiSchema, Error>("/api").use([initialDataPlugin()]);

const { data, isInitialData } = useRead((api) => api("posts").GET(), {
  initialData: prefetchedPosts,
});

const { data } = useRead((api) => api("posts").GET(), {
  initialData: prefetchedPosts,
  refetchOnInitialData: false,
});
```

## Options

### Per-Request Options

| Option                 | Type      | Default | Description                                   |
| ---------------------- | --------- | ------- | --------------------------------------------- |
| `initialData`          | `TData`   | -       | Data to show immediately on first mount       |
| `refetchOnInitialData` | `boolean` | `true`  | Whether to refetch after showing initial data |

### Result

| Property        | Type      | Description                                                |
| --------------- | --------- | ---------------------------------------------------------- |
| `isInitialData` | `boolean` | `true` if currently showing initial data (not yet fetched) |
npm package: @spoosh/plugin-path-case
Description: Path case transformation plugin for Spoosh - enables camelCase API paths with automatic conversion to kebab-case, snake_case, etc.
Latest version: 0.1.1

# @spoosh/plugin-path-case

Path case transformation plugin for Spoosh - write camelCase in TypeScript, send kebab-case (or snake_case) to your API.

**[Documentation](https://spoosh.dev/docs/plugins/path-case)** · **Requirements:** TypeScript >= 5.0 · **Peer Dependencies:** `@spoosh/core`

## The Problem

When your API uses kebab-case or snake_case URLs, you're forced to write ugly bracket notation:

```typescript
// Ugly bracket notation
api["user-profiles"]["profile-settings"].$get();
api["blog-posts"](":postId")["related-articles"].$get();
```

## The Solution

With `pathCasePlugin`, write clean camelCase:

```typescript
// Clean dot notation
api.userProfiles.profileSettings.$get();
api.blogPosts(":postId").relatedArticles.$get();
// HTTP: GET /blog-posts/123/related-articles
```

## Installation

```bash
npm install @spoosh/plugin-path-case
```

## Usage

```typescript
import { Spoosh } from "@spoosh/core";
import { createReactSpoosh } from "@spoosh/react";
import { CamelCaseKeys, pathCasePlugin } from "@spoosh/plugin-path-case";
import type { ApiSchema } from "./generated/api-schema";

// 1. Wrap your schema type with CamelCaseKeys
// 2. Add pathCasePlugin with your target case
const client = new Spoosh<CamelCaseKeys<ApiSchema>, Error>("/api").use([
  pathCasePlugin({ targetCase: "kebab" }),
]);

const { useRead, useWrite } = createReactSpoosh(client);

// Now use camelCase everywhere!
useRead((api) => api.blogPosts(postId).relatedArticles.$get());
```

## Options

### Plugin Config

| Option       | Type                                                    | Default  | Description                              |
| ------------ | ------------------------------------------------------- | -------- | ---------------------------------------- |
| `targetCase` | `"kebab" \| "snake" \| "pascal" \| "camel" \| Function` | Required | Target case for HTTP URLs                |
| `exclude`    | `string[]`                                              | `[]`     | Segments to skip (e.g., `["v1", "api"]`) |

### Per-Request Options

| Option       | Type                  | Description           |
| ------------ | --------------------- | --------------------- |
| `targetCase` | Same as plugin config | Override target case  |
| `exclude`    | `string[]`            | Override exclude list |

## Type Utilities

| Utility          | Description                                     |
| ---------------- | ----------------------------------------------- |
| `CamelCaseKeys`  | Transform schema keys from kebab/snake to camel |
| `KebabCaseKeys`  | Transform schema keys to kebab-case             |
| `SnakeCaseKeys`  | Transform schema keys to snake_case             |
| `PascalCaseKeys` | Transform schema keys to PascalCase             |
npm package: @spoosh/plugin-polling
Description: Automatic polling/refetching plugin for Spoosh
Latest version: 0.6.0

# @spoosh/plugin-polling

Automatic polling/refetching plugin for Spoosh.

**[Documentation](https://spoosh.dev/docs/react/plugins/polling)** · **Requirements:** TypeScript >= 5.0 · **Peer Dependencies:** `@spoosh/core`

## Installation

```bash
npm install @spoosh/plugin-polling
```

## Usage

```typescript
import { Spoosh } from "@spoosh/core";
import { pollingPlugin } from "@spoosh/plugin-polling";

const spoosh = new Spoosh<ApiSchema, Error>("/api").use([pollingPlugin()]);

// Static polling interval (5 seconds)
useRead((api) => api("posts").GET(), { pollingInterval: 5000 });

// Disable polling (Default behavior)
useRead((api) => api("posts").GET(), { pollingInterval: false });

// Dynamic polling interval based on data/error
useRead((api) => api("booking/:id").GET({ params: { id: 123 } }), {
  pollingInterval: ({ data, error }) => {
    if (error) return 10000; // Slower polling on error
    if (data?.status === "pending") return 1000; // Fast polling for pending
    return 5000; // Normal polling
  },
});
```

## Options

### Per-Request Options

| Option            | Type                                                      | Description                                                                               |
| ----------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `pollingInterval` | `number \| false \| ({ data, error }) => number \| false` | Polling interval in milliseconds, `false` to disable, or a function for dynamic intervals |

### Result Properties

| Property           | Type      | Description                                            |
| ------------------ | --------- | ------------------------------------------------------ |
| `isPollingRequest` | `boolean` | `true` when a polling-triggered request is in progress |

## Dynamic Polling

The polling interval can be a function that receives the current data and error, allowing you to adjust the polling rate based on the response:

```typescript
useRead((api) => api("jobs/:id").GET({ params: { id: jobId } }), {
  pollingInterval: ({ data }) => {
    // Stop polling when job is complete
    if (data?.status === "completed") return false;

    // Poll faster while job is running
    if (data?.status === "running") return 1000;

    // Default interval
    return 5000;
  },
});
```
npm package: @spoosh/plugin-progress
Description: Upload and download progress tracking plugin for Spoosh via XHR transport
Latest version: 0.4.1

# @spoosh/plugin-progress

Upload and download progress tracking plugin for Spoosh via XHR transport.

**[Documentation](https://spoosh.dev/docs/react/plugins/progress)** · **Requirements:** TypeScript >= 5.0 · **Peer Dependencies:** `@spoosh/core`

## Installation

```bash
npm install @spoosh/plugin-progress
```

## Usage

```typescript
import { Spoosh } from "@spoosh/core";
import { progressPlugin } from "@spoosh/plugin-progress";

const spoosh = new Spoosh<ApiSchema, Error>("/api").use([progressPlugin()]);

// Enable per-request
useRead((api) => api("files/:id").GET(), { progress: true });

// File upload with progress
const { trigger, meta } = useWrite((api) => api("files").POST(), {
  progress: true,
});
trigger({ body: form({ file: selectedFile }) });
// Access progress via meta.progress
```

## Per-Request Options

| Option     | Type                         | Description                                                 |
| ---------- | ---------------------------- | ----------------------------------------------------------- |
| `progress` | `boolean \| ProgressOptions` | Enable progress tracking. Automatically uses XHR transport. |

### ProgressOptions

| Option        | Type     | Description                                                                  |
| ------------- | -------- | ---------------------------------------------------------------------------- |
| `totalHeader` | `string` | Response header to read total size from when `Content-Length` is unavailable |

## Result

| Field    | Type     | Description                |
| -------- | -------- | -------------------------- |
| `loaded` | `number` | Bytes transferred so far   |
| `total`  | `number` | Total bytes (0 if unknown) |
npm package: @spoosh/plugin-qs
Description: Query string serialization plugin for Spoosh with nested object support
Latest version: 0.5.1

# @spoosh/plugin-qs

Query string serialization plugin for Spoosh with nested object support.

**[Documentation](https://spoosh.dev/docs/react/plugins/qs)** · **Requirements:** TypeScript >= 5.0 · **Peer Dependencies:** `@spoosh/core`

## Installation

```bash
npm install @spoosh/plugin-qs
```

## Usage

```typescript
import { Spoosh } from "@spoosh/core";
import { qsPlugin } from "@spoosh/plugin-qs";

const spoosh = new Spoosh<ApiSchema, Error>("/api").use([
  qsPlugin({ arrayFormat: "brackets" }),
]);

const query = {
  pagination: { limit: 10, offset: 0 },
  filters: { status: "active", tags: ["a", "b"] },
};

useRead((api) => api("items").GET({ query }));
// Result: pagination[limit]=10&pagination[offset]=0&filters[status]=active&filters[tags][]=a,b
```

## Features

- ✅ Nested object serialization with bracket notation
- ✅ Multiple array formats (brackets, indices, repeat, comma)
- ✅ Dot notation support for nested objects
- ✅ Automatic null value skipping
- ✅ Per-request configuration override
- ✅ Powered by battle-tested `qs` package

## Plugin Config

Accepts all [`qs` stringify options](https://github.com/ljharb/qs#stringifying). Common options:

| Option        | Type                                             | Default      | Description                          |
| ------------- | ------------------------------------------------ | ------------ | ------------------------------------ |
| `arrayFormat` | `"brackets" \| "indices" \| "repeat" \| "comma"` | `"brackets"` | How to serialize arrays              |
| `allowDots`   | `boolean`                                        | `false`      | Use dot notation instead of brackets |
| `skipNulls`   | `boolean`                                        | `true`       | Skip null values in serialization    |

## Per-Request Options

Override plugin defaults for specific requests:

```typescript
// Use comma-separated arrays for this request
useRead((api) => api("items").GET({ query }), { qs: { arrayFormat: "comma" } });

// Use dot notation for nested objects
useRead((api) => api("search").GET({ query }), { qs: { allowDots: true } });

// Include null values for this request
useRead((api) => api("data").GET({ query }), { qs: { skipNulls: false } });
```

## Array Formats

### brackets (default)

```typescript
{
  tags: ["a", "b"];
}
// tags[]=a,b
```

### indices

```typescript
{
  tags: ["a", "b"];
}
// tags[0]=a&tags[1]=b
```

### repeat

```typescript
{
  tags: ["a", "b"];
}
// tags=a,b
```

### comma

```typescript
{
  tags: ["a", "b"];
}
// tags=a,b
```

## Dot Notation

```typescript
// allowDots: false (default)
{
  filters: {
    status: "active";
  }
}
// filters[status]=active

// allowDots: true
{
  filters: {
    status: "active";
  }
}
// filters.status=active
```
npm package: @spoosh/plugin-refetch
Description: Auto-refetch plugin for Spoosh - refetch on focus and reconnect
Latest version: 0.4.1

# @spoosh/plugin-refetch

Auto-refetch plugin for Spoosh - refetch on window focus and network reconnect.

**[Documentation](https://spoosh.dev/docs/react/plugins/refetch)** · **Requirements:** TypeScript >= 5.0 · **Peer Dependencies:** `@spoosh/core`

## Installation

```bash
npm install @spoosh/plugin-refetch
```

## Usage

```typescript
import { Spoosh } from "@spoosh/core";
import { refetchPlugin } from "@spoosh/plugin-refetch";

const spoosh = new Spoosh<ApiSchema, Error>("/api").use([
  refetchPlugin({
    refetchOnFocus: true,
    refetchOnReconnect: true,
  }),
]);

// Uses plugin defaults
useRead((api) => api("posts").GET());

// Per-query override
useRead((api) => api("posts").GET(), {
  refetch: {
    onFocus: false, // Disable for this query
  },
});
```

## Options

### Plugin Config

| Option               | Type      | Default | Description                       |
| -------------------- | --------- | ------- | --------------------------------- |
| `refetchOnFocus`     | `boolean` | `false` | Refetch when window regains focus |
| `refetchOnReconnect` | `boolean` | `false` | Refetch when network reconnects   |

### Per-Request Options

Pass options via the `refetch` object:

| Option        | Type      | Description                         |
| ------------- | --------- | ----------------------------------- |
| `onFocus`     | `boolean` | Override focus refetch behavior     |
| `onReconnect` | `boolean` | Override reconnect refetch behavior |
npm package: @spoosh/plugin-throttle
Description: Request throttling plugin for Spoosh - limits request frequency
Latest version: 0.3.1

# @spoosh/plugin-throttle

Request throttling plugin for Spoosh - limits request frequency.

**[Documentation](https://spoosh.dev/docs/react/plugins/throttle)** · **Requirements:** TypeScript >= 5.0 · **Peer Dependencies:** `@spoosh/core`

## Installation

```bash
npm install @spoosh/plugin-throttle
```

## Usage

```typescript
import { Spoosh } from "@spoosh/core";
import { throttlePlugin } from "@spoosh/plugin-throttle";

const spoosh = new Spoosh<ApiSchema, Error>("/api").use([
  // ...otherPlugins,
  throttlePlugin(),
]);

const { data } = useRead((api) => api("expensive").GET(), { throttle: 1000 });
```

## Options

### Per-Request Options

| Option     | Type     | Description                                                          |
| ---------- | -------- | -------------------------------------------------------------------- |
| `throttle` | `number` | Max 1 request per X milliseconds. Extra requests return cached data. |

## Notes

- This plugin runs with priority 100, automatically executing last in the middleware chain to block all requests (including force fetches) that exceed the throttle limit
- Unlike debounce (which delays), throttle immediately returns cached data for extra requests
- Useful for rate-limiting expensive endpoints
npm package: @spoosh/plugin-transform
Description: Data transformation plugin for Spoosh with sync/async support
Latest version: 0.8.2

# @spoosh/plugin-transform

Transform response data with full type inference.

**[Documentation](https://spoosh.dev/docs/react/plugins/transform)** · **Requirements:** TypeScript >= 5.0 · **Peer Dependencies:** `@spoosh/core`

## Installation

```bash
npm install @spoosh/plugin-transform
```

## Usage

### Response Transforms

Response transforms produce a separate `transformedData` field in `meta` while preserving the original `data`:

```typescript
import { Spoosh } from "@spoosh/core";
import { transformPlugin } from "@spoosh/plugin-transform";

const spoosh = new Spoosh<ApiSchema, Error>("/api").use([transformPlugin()]);

const { data, meta } = useRead((api) => api("posts").GET(), {
  transform: (posts) => ({
    count: posts.length,
    hasMore: posts.length >= 10,
    ids: posts.map((p) => p.id),
  }),
});

// data = Post[] (original response, preserved)
// meta.transformedData = { count: number, hasMore: boolean, ids: number[] } | undefined
```

### Async Transforms

Transform functions support async operations:

```typescript
const { data, meta } = useRead((api) => api("posts").GET(), {
  transform: async (posts) => {
    const enriched = await enrichPostsWithMetadata(posts);
    return {
      count: enriched.length,
      titles: enriched.map((p) => p.title),
    };
  },
});
```

## Features

- ✅ Full type inference for transforms
- ✅ Async transform functions supported
- ✅ Per-request transforms (no global config needed)
- ✅ Return `undefined` to remove data entirely

## useWrite with Transform

With the hook-level options pattern, `transform` is now passed as a second argument to `useWrite`, enabling full type inference:

```typescript
type TransformedPost = {
  success: boolean;
  postId: number;
};

const { trigger, meta } = useWrite((api) => api("posts").POST(), {
  transform: (post) => ({
    success: true,
    postId: post.id,
  }),
});

await trigger({ body: { title: "New Post" } });

// meta.transformedData is now properly typed!
const typed = meta.transformedData; // Type: TransformedPost | undefined
```
npm package: @spoosh/test-utils
Description: Test utilities for Spoosh plugins
Latest version: 0.3.0

# @spoosh/test-utils

Test utilities for Spoosh plugins.

## Installation

```bash
npm install -D @spoosh/test-utils
```

## Usage

```typescript
import {
  createMockContext,
  createMockResponse,
  createMockNext,
  createState,
  createStateManager,
  createEventEmitter,
} from "@spoosh/test-utils";

describe("myPlugin", () => {
  it("should work", async () => {
    const plugin = myPlugin();
    const context = createMockContext({
      queryKey: "test-key",
      pluginOptions: { myOption: true },
    });
    const next = createMockNext({ data: { id: 1 } });

    const result = await plugin.middleware!(context, next);

    expect(result.data).toEqual({ id: 1 });
  });
});
```

## API

### `createMockContext(options?)`

Creates a mock `PluginContext` for testing plugins.

### `createMockResponse(overrides?)`

Creates a mock `SpooshResponse`.

### `createMockNext(response?)`

Creates a mock next function that returns a successful response.

### `createMockNextError(error)`

Creates a mock next function that throws an error.

### `createMockNextWithError(error, status?)`

Creates a mock next function that returns an error response.

### `createState(overrides?)`

Creates a default `OperationState` with optional overrides.

### `createStateManager()`

Re-exported from `@spoosh/core` for convenience.

### `createEventEmitter()`

Re-exported from `@spoosh/core` for convenience.
npm package: @spoot/cli
Description: Type-safe CLI argument parsing and command framework for Node.js scripts
Latest version: 1.0.1

# @spoot/cli

Type-safe CLI argument parsing and command framework for Node.js scripts. Provides `Cli`, `Command`, and `Args` abstractions for building structured command-line tools with minimal boilerplate.

## Install

```sh
npm install @spoot/cli
```

## Usage

```ts
import { Cli, Command, Args } from "@spoot/cli";

const deploy = new Command({
  name: "deploy",
  args: Args.object({
    env: Args.string().enum(["staging", "prod"]),
    dryRun: Args.boolean().optional(),
  }),
  run: async ({ env, dryRun }) => {
    console.log(`Deploying to ${env}`, { dryRun });
  },
});

const cli = new Cli({ commands: [deploy] });
await cli.run(process.argv.slice(2));
```

Also exports `loadEnv` for loading `.env` files and `fail` for printing an error and exiting.

## Development

```sh
pnpm typecheck   # type-check
pnpm test        # run Jest tests
pnpm build:lib   # compile to dist/
```
npm package: @spoot/cn
Description: Utility for merging Tailwind CSS class names with clsx and tailwind-merge
Latest version: 1.3.0

# @spoot/cn

Utility for merging Tailwind CSS class names using [`clsx`](https://github.com/lukeed/clsx) and [`tailwind-merge`](https://github.com/dcastil/tailwind-merge).

## Install

```sh
npm install @spoot/cn
```

## Usage

```ts
import { cn } from "@spoot/cn";

<div className={cn("px-4 py-2", isActive && "bg-blue-500", className)} />
```

The `cn` function accepts any combination of strings, arrays, and objects (everything `clsx` supports), then passes the result through `tailwind-merge` to resolve conflicting Tailwind classes.

## Development

```sh
pnpm typecheck   # type-check
pnpm build:lib   # compile to dist/
```
npm package: @spoot/day
Description: Date utilities: type-safe Day, Week, Month, and CalendarWeek abstractions
Latest version: 1.0.1

# @spoot/day

Type-safe date abstractions: `Day`, `Week`, `Month`, `CalendarWeek`, and `DaySelector`. All values are plain-object serializable and free of timezone surprises.

## Install

```sh
npm install @spoot/day
```

## Usage

```ts
import { Day, Week, Month, DaySelector } from "@spoot/day";

const today = Day.today();
const week = Week.containing(today);
const month = Month.containing(today);
```

- **`Day`** – calendar date (YYYY-MM-DD), no time component
- **`Week`** – ISO week with start/end days
- **`Month`** – calendar month with helpers for iterating days
- **`CalendarWeek`** – a week as displayed in a calendar grid (may span two months)
- **`DaySelector`** – predicate for matching days (e.g. every Monday, last day of month)

## Development

```sh
pnpm typecheck   # type-check
pnpm test        # run Jest tests
pnpm build:lib   # compile to dist/
```
npm package: @spoot/gql
Description: GraphQL query execution helper with Zod schema validation
Latest version: 1.0.0

# @spoot/gql

GraphQL query execution helper with [Zod](https://zod.dev) schema validation. Wraps the native `fetch` API to send GraphQL requests and parse/validate the response.

## Install

```sh
npm install @spoot/gql
```

## Usage

```ts
import { gql, fetchGql } from "@spoot/gql";
import { z } from "zod";

const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
    }
  }
`;

const UserSchema = z.object({
  user: z.object({ id: z.string(), name: z.string() }),
});

const data = await fetchGql(
  new URL("https://api.example.com/graphql"),
  { Authorization: "Bearer token" },
  GET_USER,
  UserSchema,
);
```

Throws a descriptive error on HTTP failure or GraphQL `errors` in the response.

## Development

```sh
pnpm typecheck   # type-check
pnpm build:lib   # compile to dist/
```
npm package: @spoot/hostfully-api
Description: TypeScript client for the Hostfully property management GraphQL API
Latest version: 1.0.2

# @spoot/hostfully-api

TypeScript client for the [Hostfully](https://www.hostfully.com) property management GraphQL API. Covers properties, fees, pricing, leads, messages, agencies, orders, and amenities.

## Install

```sh
npm install @spoot/hostfully-api
```

## Usage

```ts
import { HostfullyApi } from "@spoot/hostfully-api";
import "dotenv/config";

const api = new HostfullyApi({
  endpoint: new URL("https://api.hostfully.com/graphql"),
  apiKey: process.env.HOSTFULLY_API_KEY!,
});

const properties = await api.properties.list();
const pricing = await api.pricing.get(propertyUid, checkIn, checkOut);
```

Internally uses `@spoot/gql` for query execution, `@spoot/rx` for streaming results, and `@spoot/schedule` / `@spoot/day` for date handling.

## Development

```sh
pnpm typecheck   # type-check
pnpm build:lib   # compile to dist/
```
npm package: @spoot/log
Description: Simple structured logger for Node.js with newline-safe output
Latest version: 1.0.0

# @spoot/log

Simple structured logger for Node.js. Formats output as single lines (newlines escaped) so log aggregation tools can parse entries reliably.

## Install

```sh
npm install @spoot/log
```

## Usage

```ts
import { logger } from "@spoot/log";

logger.info("Server started on port %d", 3000);
logger.error("Request failed", err);
```

Each call to `logger.info` or `logger.error` emits exactly one line. Multi-line values have their newlines replaced with `\n` so they don't break log parsers.

## Development

```sh
pnpm typecheck   # type-check
pnpm build:lib   # compile to dist/
```
npm package: @spoot/next-session
Description: Encrypted cookie sessions for Next.js using iron-session with Zod validation
Latest version: 1.0.3

# @spoot/next-session

Encrypted, stateless cookie sessions for Next.js route handlers using [iron-session](https://github.com/vvo/iron-session) with [Zod](https://zod.dev) validation.

## Install

```sh
npm install @spoot/next-session
```

Requires `next` as a peer dependency.

## Usage

```ts
import { Session, type SessionConfig } from "@spoot/next-session";
import { type NextRequest } from "next/server";

const config: SessionConfig = {
  cookieName: "my-app-session",
  password: process.env.SESSION_SECRET!,
};

export async function GET(req: NextRequest) {
  const session = await Session.get(req, config);

  if (!session) {
    return Response.json({ error: "Unauthorized" }, { status: 401 });
  }

  return Response.json({ user: session.email });
}
```

Sessions are signed and encrypted with `iron-session`. The session cookie is `HttpOnly`, `SameSite=Lax`, and `Secure` in production.

## Development

```sh
pnpm typecheck   # type-check
pnpm build:lib   # compile to dist/
```
npm package: @spoot/next-url
Description: Next.js URL utilities: current URL detection and redirect target validation
Latest version: 3.0.0

# @spoot/next-url

Next.js URL utilities for route handlers and middleware: read the canonical request URL and validate redirect targets to prevent open-redirect vulnerabilities.

## Install

```sh
npm install @spoot/next-url
```

Requires `next` as a peer dependency.

## Usage

```ts
import { getCurrentUrl, getRedirectTarget } from "@spoot/next-url";
import { type NextRequest, NextResponse } from "next/server";

export function middleware(req: NextRequest) {
  const url = getCurrentUrl(req); // resolves host from headers

  const target = getRedirectTarget(url); // validates ?next= param
  if (target instanceof NextResponse) return target; // 400 on bad redirect

  return NextResponse.redirect(target);
}
```

## API

- **`getCurrentUrl(req)`** – Returns a `URL` with the correct `host` from request headers (handles reverse-proxy scenarios).
- **`getRedirectTarget(url, paramName?)`** – Reads a redirect URL from a query param and validates it is same-origin. Returns a `NextResponse` (400) on invalid input.

## Development

```sh
pnpm typecheck   # type-check
pnpm build:lib   # compile to dist/
```
npm package: @spoot/react-vision-icon
Description: Animated Apple Vision Pro-style icon component for React using RxJS
Latest version: 1.0.4

# @spoot/react-vision-icon

Animated Apple Vision Pro-style icon component for React. Icons respond to hover/focus with a reactive highlight effect powered by RxJS mouse tracking.

## Install

```sh
npm install @spoot/react-vision-icon
```

Requires `react` as a peer dependency.

## Usage

```tsx
import { Icon } from "@spoot/react-vision-icon";

export function MyButton() {
  return (
    <button>
      <Icon src="/icons/star.png" size={48} />
      Favorite
    </button>
  );
}
```

The icon automatically tracks the cursor position within its bounds and applies a radial highlight to simulate the lighting from visionOS app icons.

## Development

```sh
pnpm typecheck   # type-check
pnpm build:lib   # compile to dist/
```
npm package: @spoot/rx
Description: RxJS utilities and namespace re-exports for reactive programming
Latest version: 1.0.0

# @spoot/rx

RxJS utilities and namespace re-exports. Provides a convenience `Rx` namespace that re-exports the most commonly used RxJS operators and types, plus a few extra utilities.

## Install

```sh
npm install @spoot/rx
```

## Usage

```ts
import { Rx, Observable } from "@spoot/rx";

const values$ = new Observable<number>((sub) => {
  sub.next(1);
  sub.complete();
});

values$.pipe(
  Rx.filter((n) => n > 0),
  Rx.map((n) => n * 2),
  Rx.tap((n) => console.log(n)),
).subscribe();
```

Also exports `log$` – a tap operator that logs each value:

```ts
import { log$ } from "@spoot/rx";

values$.pipe(log$("value:")).subscribe();
```

## Development

```sh
pnpm typecheck   # type-check
pnpm build:lib   # compile to dist/
```
npm package: @spoot/schedule
Description: Recurring schedule definitions (daily, weekly, monthly) with Day-based date matching
Latest version: 1.0.2

# @spoot/schedule

Recurring schedule definitions with [`@spoot/day`](../day) date matching. Define daily, weekly, or monthly schedules and check whether a given `Day` falls within them.

## Install

```sh
npm install @spoot/schedule @spoot/day
```

## Usage

```ts
import { Schedule } from "@spoot/schedule";
import { Day } from "@spoot/day";

const everyMonday = Schedule.weekly({ dayOfWeek: 1 });
const firstOfMonth = Schedule.monthly({ dayOfMonth: 1 });

const today = Day.today();

console.log(everyMonday.includes(today));   // true or false
console.log(firstOfMonth.includes(today));  // true or false
```

## Development

```sh
pnpm typecheck   # type-check
pnpm test        # run Jest tests
pnpm build:lib   # compile to dist/
```
npm package: @spootechnologies/leto-cli
Description: Leto CSS Framework CLI
Author: Benjamin Lotterer
Latest version: 0.0.24

# LETO CLI
An easy way to generate a customize version of [leto.css](https://github.com/spootechnologies/leto)

###  Install

```shell
npm i @spootechnologies/leto-cli
``` 

### Getting Started
The cli will start an ui which will guide you throw all possible adjustments of [leto.css](https://github.com/spootechnologies/leto).  
You can for example change colors, adjust the standard media queries and customize the look of leto elements.

### Commands

Starting UI 
```shell
leto-cli generate --ui
```
npm package: @spootechnologies/spooclient
Description: `npm install` Installs all the depencies needed to compile your project
Author: moritz.roessler@gmail.com
Latest version: 0.0.13

# Setup
`npm install`
Installs all the depencies needed to compile your project

## Build 
`npm run build`
 
 Runs `babel src --out-dir lib`

## Test

`npm run test`

Runs jest 

### Worth knowing

If jest throws a ReferenceError `regeneratorRuntime` not found you can `import babel-polyfill` in the failing test.