diff --git a/README.md b/README.md
index 203c292..d386626 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# @pictify/sdk
-Official Node.js SDK for [Pictify](https://pictify.io) - Generate images from HTML templates programmatically.
+Official Node.js SDK for [Pictify](https://pictify.io) — generate images, PDFs, and GIFs from HTML, live URLs, and reusable templates.
## Installation
@@ -18,217 +18,258 @@ pnpm add @pictify/sdk
import { Pictify } from '@pictify/sdk';
const pictify = new Pictify({
- apiKey: process.env.PICTIFY_API_KEY
+ apiKey: process.env.PICTIFY_API_KEY!,
});
-// Generate an image
-const result = await pictify.render({
- templateId: 'your-template-id',
- variables: {
- title: 'Hello World',
- subtitle: 'Generated with Pictify',
- backgroundColor: '#667eea'
- },
- format: 'png',
+// Render raw HTML to a PNG
+const image = await pictify.renderHtml({
+ html: '
Hello World
',
width: 1200,
- height: 630
+ height: 630,
+});
+
+console.log('Image URL:', image.url);
+
+// Render a reusable template
+const result = await pictify.render({
+ templateId: 'your-template-uid',
+ variables: { name: 'Ada', company: 'Pictify' },
});
-console.log('Image URL:', result.imageUrl);
+console.log('Image URL:', result.url);
```
## Features
-- **Full TypeScript support** - Complete type definitions included
-- **Promise-based API** - Modern async/await interface
-- **Automatic retries** - Built-in retry logic for transient failures
-- **Stream support** - Download images as streams for efficient memory usage
-- **Batch rendering** - Generate up to 500 images in a single request
-
-## API Reference
+- **Full TypeScript support** — complete type definitions included
+- **Promise-based API** — modern async/await interface
+- **Automatic retries** — exponential backoff on 5xx and network errors
+- **Typed errors** — `AuthenticationError`, `RateLimitError`, `QuotaExceededError`, and more
+- **Templates, HTML, URLs & GIFs** — one client for every render type
+- **Async batch rendering** — submit large jobs and poll for progress
-### Constructor
+## Constructor
```typescript
const pictify = new Pictify({
- apiKey: string, // Required: Your Pictify API key
+ apiKey: string, // Required: your Pictify API key
baseUrl?: string, // Optional: API base URL (default: https://api.pictify.io)
- timeout?: number, // Optional: Request timeout in ms (default: 30000)
- retries?: number // Optional: Number of retries (default: 3)
+ timeout?: number, // Optional: request timeout in ms (default: 30000)
+ retries?: number, // Optional: number of retries on 5xx/network errors (default: 3)
});
```
-### render(options)
+## API Reference
+
+### `renderHtml(options)`
-Generate a single image from a template.
+Render an image (or PDF) directly from HTML. `POST /image`.
```typescript
-const result = await pictify.render({
- templateId: 'template-id',
- variables: { title: 'My Image' },
- format: 'png', // 'png' | 'jpg' | 'webp' | 'gif' | 'pdf'
- width: 1200,
- height: 630,
- download: false, // Set true to get buffer instead of URL
- deviceScaleFactor: 2, // For retina images
- transparent: false, // PNG only
- quality: 90 // JPEG/WebP quality (1-100)
+const image = await pictify.renderHtml({
+ html: 'Hello
',
+ css: 'div { color: blue; }', // optional — inlined into a Hi
',
+ // or: url: 'https://example.com'
+ // or: templateId: 'template-uid', variables: { name: 'Ada' }
+ width: 400, // optional (default: 800)
+ height: 200, // optional (default: 600)
+ quality: 'medium', // optional: 'low' | 'medium' | 'high' (default: medium)
});
-// Multiple layouts for all batch items
-const batchMulti = await pictify.renderBatch({
- templateId: 'template-id',
- items: [
- { variables: { title: 'Card 1' } },
- { variables: { title: 'Card 2' } }
- ],
- layouts: ['default', 'twitter-post']
+// Result: { url, uid, width, height, animationLength }
+console.log(gif.url);
+```
+
+> The source HTML/URL must contain motion (e.g. a CSS animation). Static content produces no frames and returns an error.
+
+### `renderBatch(options)` + `getBatchResults(batchId)`
+
+Submit an async batch render of a template across many variable sets. `POST /templates/:uid/batch-render` returns immediately with a `batchId`; poll `getBatchResults` to track progress.
+
+```typescript
+const job = await pictify.renderBatch({
+ templateId: 'template-uid',
+ variableSets: [
+ { name: 'Ada', company: 'Pictify' },
+ { name: 'Grace', company: 'Pictify' },
+ ], // max 100 per batch
+ format: 'png', // optional
+ quality: 0.9, // optional, 0.1–1.0
+ concurrency: 5, // optional, 1–10 (default: 5)
+ // layout: 'square' or layouts: ['default', 'square'] — optional
});
-// Access per-item, per-layout results
-for (const item of batchMulti.results) {
- for (const r of item.results) {
- console.log(`Item ${item.index} / ${r.layout}: ${r.url}`);
- }
- for (const e of item.errors) {
- console.log(`Item ${item.index} / ${e.layout} failed: ${e.error}`);
- }
+// { batchId, status, totalItems }
+console.log(job.batchId);
+
+// Poll for progress.
+const status = await pictify.getBatchResults(job.batchId);
+console.log(status.status); // 'pending' | 'processing' | 'completed' | 'partial' | 'failed' | 'cancelled'
+console.log(status.completedItems, 'of', status.totalItems);
+for (const item of status.results) {
+ console.log(`item ${item.index}: success=${item.success}`);
}
```
-### renderStream(options)
+> **Rendered URLs are not returned by the poll endpoint** — `getBatchResults` reports per-item `{ index, success, variables }` (and `error` on failures). Final image URLs are delivered via the `render.completed` webhook. Subscribe to webhooks to collect batch output.
-Get image as a readable stream.
+### `getTemplate(templateId)`
-```typescript
-import fs from 'fs';
+Get a single template by its UID. `GET /templates/:uid`.
-const stream = await pictify.renderStream({
- templateId: 'template-id',
- variables: { title: 'Streamed Image' }
-});
+```typescript
+const template = await pictify.getTemplate('template-uid');
-stream.pipe(fs.createWriteStream('output.png'));
+// { uid, name, html, width, height, engine, outputFormat,
+// variables: string[], variableDefinitions: [...], createdAt, ... }
+console.log(template.uid, template.name);
```
-### getTemplate(templateId)
+### `listTemplates(options)`
-Get template details and variables.
+List templates in your account. `GET /templates`.
```typescript
-const template = await pictify.getTemplate('template-id');
+const { templates, pagination } = await pictify.listTemplates({
+ page: 1, // optional (default: 1)
+ limit: 20, // optional, max 100 (default: 12)
+ sort: 'newest', // optional: 'newest' | 'oldest' | 'name'
+});
-// Result
-{
- id: 'template-id',
- name: 'OG Image Template',
- description: 'Template for social media cards',
- width: 1200,
- height: 630,
- variables: [
- { name: 'title', type: 'string', required: true },
- { name: 'backgroundColor', type: 'color', defaultValue: '#ffffff' }
- ]
-}
+console.log(pagination.total, 'templates');
+for (const t of templates) console.log(t.uid, t.name);
```
-### listTemplates()
+### `createTemplate(options)`
-List all templates in your account.
+Create a template from HTML. `POST /templates`. Variables are auto-discovered from `{{variableName}}` tokens in the HTML body.
```typescript
-const templates = await pictify.listTemplates();
+const template = await pictify.createTemplate({
+ html: 'Hi {{firstName}}
',
+ name: 'Welcome Card', // optional
+ width: 600, // optional
+ height: 200, // optional
+ variableDefinitions: [], // optional — auto-extracted from the HTML when omitted
+ outputFormat: 'image', // optional: 'image' | 'pdf'
+});
+
+console.log(template.uid);
```
## Error Handling
+All API errors throw a typed subclass of `PictifyError`.
+
```typescript
-import { Pictify, PictifyError, RateLimitError } from '@pictify/sdk';
+import {
+ Pictify,
+ PictifyError,
+ AuthenticationError,
+ TemplateNotFoundError,
+ RateLimitError,
+ QuotaExceededError,
+ RenderError,
+} from '@pictify/sdk';
try {
- const result = await pictify.render({ /* ... */ });
+ const result = await pictify.render({ templateId: 'template-uid' });
} catch (error) {
if (error instanceof RateLimitError) {
- console.log('Rate limited, retry at:', error.resetAt);
+ console.log('Rate limited — slow down');
+ } else if (error instanceof QuotaExceededError) {
+ console.log('Quota exceeded — upgrade your plan');
+ } else if (error instanceof RenderError) {
+ console.error('Render/validation failed:', error.message, error.errors);
} else if (error instanceof PictifyError) {
console.error('Pictify error:', error.code, error.message);
}
@@ -237,73 +278,72 @@ try {
### Error Types
-| Error Class | Code | Description |
-|------------|------|-------------|
-| `AuthenticationError` | `INVALID_API_KEY` | Invalid or missing API key |
-| `TemplateNotFoundError` | `TEMPLATE_NOT_FOUND` | Template does not exist |
-| `RateLimitError` | `RATE_LIMIT_EXCEEDED` | Too many requests |
-| `QuotaExceededError` | `QUOTA_EXCEEDED` | Monthly quota exceeded |
-| `RenderError` | `RENDER_FAILED` | Rendering failed |
-| `NetworkError` | `NETWORK_ERROR` | Network request failed |
-| `TimeoutError` | `TIMEOUT` | Request timed out |
+| Error Class | Code | HTTP | Description |
+|------------|------|------|-------------|
+| `AuthenticationError` | `INVALID_API_KEY` | 401 | Invalid or missing API key |
+| `QuotaExceededError` | `QUOTA_EXCEEDED` | 402 / 429 | Render quota exceeded |
+| `TemplateNotFoundError` | `TEMPLATE_NOT_FOUND` | 404 | Template (or batch job) not found |
+| `RenderError` | `RENDER_FAILED` | 422 (and other 4xx) | Render or input validation failed (`error.errors` holds field-level details) |
+| `RateLimitError` | `RATE_LIMIT_EXCEEDED` | 429 | Too many requests |
+| `ServerError` | `SERVER_ERROR` | 5xx | Server-side failure |
+| `NetworkError` | `NETWORK_ERROR` | — | Network request failed |
+| `TimeoutError` | `TIMEOUT` | — | Request timed out |
## CommonJS Support
```javascript
const { Pictify } = require('@pictify/sdk');
-const pictify = new Pictify({
- apiKey: process.env.PICTIFY_API_KEY
-});
+const pictify = new Pictify({ apiKey: process.env.PICTIFY_API_KEY });
```
## Examples
-### Express.js API Route
+### Express.js OG-image route
```typescript
import express from 'express';
import { Pictify } from '@pictify/sdk';
const app = express();
-const pictify = new Pictify({ apiKey: process.env.PICTIFY_API_KEY });
+const pictify = new Pictify({ apiKey: process.env.PICTIFY_API_KEY! });
app.get('/og-image', async (req, res) => {
- const { title, description } = req.query;
+ const { title, description } = req.query as Record;
const result = await pictify.render({
- templateId: 'og-template',
- variables: { title, description }
+ templateId: 'og-template-uid',
+ variables: { title, description },
});
- res.redirect(result.imageUrl);
+ res.redirect(result.url!);
});
```
-### Next.js API Route
+### Next.js route handler
```typescript
-// pages/api/og.ts
+// app/api/og/route.ts
import { Pictify } from '@pictify/sdk';
-import type { NextApiRequest, NextApiResponse } from 'next';
+import { NextRequest, NextResponse } from 'next/server';
-const pictify = new Pictify({ apiKey: process.env.PICTIFY_API_KEY });
+const pictify = new Pictify({ apiKey: process.env.PICTIFY_API_KEY! });
+
+export async function GET(req: NextRequest) {
+ const title = req.nextUrl.searchParams.get('title') ?? '';
-export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const result = await pictify.render({
- templateId: 'og-template',
- variables: req.query,
- download: true
+ templateId: 'og-template-uid',
+ variables: { title },
});
- res.setHeader('Content-Type', 'image/png');
- res.send(result.buffer);
+ return NextResponse.redirect(result.url!);
}
```
## License
-MIT - see [LICENSE](LICENSE) for details.
+MIT — see [LICENSE](LICENSE) for details.
## Links
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..14cdf01
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,3896 @@
+{
+ "name": "@pictify/sdk",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@pictify/sdk",
+ "version": "1.0.0",
+ "license": "MIT",
+ "devDependencies": {
+ "@types/node": "^20.10.0",
+ "eslint": "^8.55.0",
+ "tsup": "^8.0.1",
+ "typescript": "^5.3.0",
+ "vitest": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
+ "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
+ "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
+ "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
+ "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
+ "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
+ "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
+ "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
+ "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
+ "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
+ "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
+ "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
+ "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
+ "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
+ "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
+ "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
+ "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
+ "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
+ "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
+ "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
+ "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
+ "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
+ "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
+ "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
+ "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.6.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
+ "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
+ "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
+ "deprecated": "Use @eslint/config-array instead",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^2.0.3",
+ "debug": "^4.3.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+ "deprecated": "Use @eslint/object-schema instead",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@jest/schemas": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
+ "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.27.8"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
+ "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz",
+ "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
+ "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz",
+ "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz",
+ "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz",
+ "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz",
+ "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz",
+ "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz",
+ "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz",
+ "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz",
+ "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz",
+ "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz",
+ "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz",
+ "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz",
+ "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz",
+ "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz",
+ "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz",
+ "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz",
+ "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz",
+ "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz",
+ "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz",
+ "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz",
+ "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
+ "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz",
+ "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@sinclair/typebox": {
+ "version": "0.27.8",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
+ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "20.19.30",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz",
+ "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@vitest/expect": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz",
+ "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "1.6.1",
+ "@vitest/utils": "1.6.1",
+ "chai": "^4.3.10"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz",
+ "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "1.6.1",
+ "p-limit": "^5.0.0",
+ "pathe": "^1.1.1"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner/node_modules/p-limit": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz",
+ "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@vitest/runner/node_modules/pathe": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
+ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@vitest/runner/node_modules/yocto-queue": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz",
+ "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz",
+ "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "magic-string": "^0.30.5",
+ "pathe": "^1.1.1",
+ "pretty-format": "^29.7.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot/node_modules/pathe": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
+ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@vitest/spy": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz",
+ "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyspy": "^2.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz",
+ "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "diff-sequences": "^29.6.3",
+ "estree-walker": "^3.0.3",
+ "loupe": "^2.3.7",
+ "pretty-format": "^29.7.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
+ "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.11.0"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/assertion-error": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/bundle-require": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz",
+ "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "load-tsconfig": "^0.2.3"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "esbuild": ">=0.18"
+ }
+ },
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/chai": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz",
+ "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assertion-error": "^1.1.0",
+ "check-error": "^1.0.3",
+ "deep-eql": "^4.1.3",
+ "get-func-name": "^2.0.2",
+ "loupe": "^2.3.6",
+ "pathval": "^1.1.1",
+ "type-detect": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/check-error": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz",
+ "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-func-name": "^2.0.2"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14.16.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/confbox": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
+ "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/consola": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
+ "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^14.18.0 || >=16.10.0"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-eql": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz",
+ "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-detect": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/diff-sequences": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
+ "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
+ "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.2",
+ "@esbuild/android-arm": "0.27.2",
+ "@esbuild/android-arm64": "0.27.2",
+ "@esbuild/android-x64": "0.27.2",
+ "@esbuild/darwin-arm64": "0.27.2",
+ "@esbuild/darwin-x64": "0.27.2",
+ "@esbuild/freebsd-arm64": "0.27.2",
+ "@esbuild/freebsd-x64": "0.27.2",
+ "@esbuild/linux-arm": "0.27.2",
+ "@esbuild/linux-arm64": "0.27.2",
+ "@esbuild/linux-ia32": "0.27.2",
+ "@esbuild/linux-loong64": "0.27.2",
+ "@esbuild/linux-mips64el": "0.27.2",
+ "@esbuild/linux-ppc64": "0.27.2",
+ "@esbuild/linux-riscv64": "0.27.2",
+ "@esbuild/linux-s390x": "0.27.2",
+ "@esbuild/linux-x64": "0.27.2",
+ "@esbuild/netbsd-arm64": "0.27.2",
+ "@esbuild/netbsd-x64": "0.27.2",
+ "@esbuild/openbsd-arm64": "0.27.2",
+ "@esbuild/openbsd-x64": "0.27.2",
+ "@esbuild/openharmony-arm64": "0.27.2",
+ "@esbuild/sunos-x64": "0.27.2",
+ "@esbuild/win32-arm64": "0.27.2",
+ "@esbuild/win32-ia32": "0.27.2",
+ "@esbuild/win32-x64": "0.27.2"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
+ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
+ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.57.1",
+ "@humanwhocodes/config-array": "^0.13.0",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "@ungap/structured-clone": "^1.2.0",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3",
+ "strip-ansi": "^6.0.1",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/execa": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
+ "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^8.0.1",
+ "human-signals": "^5.0.0",
+ "is-stream": "^3.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^5.1.0",
+ "onetime": "^6.0.0",
+ "signal-exit": "^4.1.0",
+ "strip-final-newline": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=16.17"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/fix-dts-default-cjs-exports": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz",
+ "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "magic-string": "^0.30.17",
+ "mlly": "^1.7.4",
+ "rollup": "^4.34.8"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
+ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.3",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/get-func-name": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
+ "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
+ "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/human-signals": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
+ "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=16.17.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
+ "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/joycon": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
+ "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
+ "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lilconfig": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/load-tsconfig": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz",
+ "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ }
+ },
+ "node_modules/local-pkg": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz",
+ "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mlly": "^1.7.3",
+ "pkg-types": "^1.2.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/loupe": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
+ "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-func-name": "^2.0.1"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/mimic-fn": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
+ "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/mlly": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz",
+ "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "pathe": "^2.0.3",
+ "pkg-types": "^1.3.1",
+ "ufo": "^1.6.1"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/npm-run-path": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
+ "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npm-run-path/node_modules/path-key": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
+ "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathval": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
+ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/pkg-types": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
+ "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.1.8",
+ "mlly": "^1.7.4",
+ "pathe": "^2.0.1"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
+ "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "lilconfig": "^3.1.1"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "jiti": ">=1.21.0",
+ "postcss": ">=8.0.9",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ },
+ "postcss": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/readdirp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.18.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz",
+ "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.57.1",
+ "@rollup/rollup-android-arm64": "4.57.1",
+ "@rollup/rollup-darwin-arm64": "4.57.1",
+ "@rollup/rollup-darwin-x64": "4.57.1",
+ "@rollup/rollup-freebsd-arm64": "4.57.1",
+ "@rollup/rollup-freebsd-x64": "4.57.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.57.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.57.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.57.1",
+ "@rollup/rollup-linux-arm64-musl": "4.57.1",
+ "@rollup/rollup-linux-loong64-gnu": "4.57.1",
+ "@rollup/rollup-linux-loong64-musl": "4.57.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.57.1",
+ "@rollup/rollup-linux-ppc64-musl": "4.57.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.57.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.57.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.57.1",
+ "@rollup/rollup-linux-x64-gnu": "4.57.1",
+ "@rollup/rollup-linux-x64-musl": "4.57.1",
+ "@rollup/rollup-openbsd-x64": "4.57.1",
+ "@rollup/rollup-openharmony-arm64": "4.57.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.57.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.57.1",
+ "@rollup/rollup-win32-x64-gnu": "4.57.1",
+ "@rollup/rollup-win32-x64-msvc": "4.57.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.7.6",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
+ "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/std-env": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
+ "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strip-literal": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz",
+ "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^9.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/sucrase": {
+ "version": "3.35.1",
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
+ "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "tinyglobby": "^0.2.11",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
+ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinypool": {
+ "version": "0.8.4",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz",
+ "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz",
+ "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tree-kill": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
+ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "tree-kill": "cli.js"
+ }
+ },
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/tsup": {
+ "version": "8.5.1",
+ "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz",
+ "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bundle-require": "^5.1.0",
+ "cac": "^6.7.14",
+ "chokidar": "^4.0.3",
+ "consola": "^3.4.0",
+ "debug": "^4.4.0",
+ "esbuild": "^0.27.0",
+ "fix-dts-default-cjs-exports": "^1.0.0",
+ "joycon": "^3.1.1",
+ "picocolors": "^1.1.1",
+ "postcss-load-config": "^6.0.1",
+ "resolve-from": "^5.0.0",
+ "rollup": "^4.34.8",
+ "source-map": "^0.7.6",
+ "sucrase": "^3.35.0",
+ "tinyexec": "^0.3.2",
+ "tinyglobby": "^0.2.11",
+ "tree-kill": "^1.2.2"
+ },
+ "bin": {
+ "tsup": "dist/cli-default.js",
+ "tsup-node": "dist/cli-node.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@microsoft/api-extractor": "^7.36.0",
+ "@swc/core": "^1",
+ "postcss": "^8.4.12",
+ "typescript": ">=4.5.0"
+ },
+ "peerDependenciesMeta": {
+ "@microsoft/api-extractor": {
+ "optional": true
+ },
+ "@swc/core": {
+ "optional": true
+ },
+ "postcss": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tsup/node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-detect": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz",
+ "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/ufo": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz",
+ "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-node": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz",
+ "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.3.4",
+ "pathe": "^1.1.1",
+ "picocolors": "^1.0.0",
+ "vite": "^5.0.0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/vite-node/node_modules/pathe": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
+ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/vite/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz",
+ "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "1.6.1",
+ "@vitest/runner": "1.6.1",
+ "@vitest/snapshot": "1.6.1",
+ "@vitest/spy": "1.6.1",
+ "@vitest/utils": "1.6.1",
+ "acorn-walk": "^8.3.2",
+ "chai": "^4.3.10",
+ "debug": "^4.3.4",
+ "execa": "^8.0.1",
+ "local-pkg": "^0.5.0",
+ "magic-string": "^0.30.5",
+ "pathe": "^1.1.1",
+ "picocolors": "^1.0.0",
+ "std-env": "^3.5.0",
+ "strip-literal": "^2.0.0",
+ "tinybench": "^2.5.1",
+ "tinypool": "^0.8.3",
+ "vite": "^5.0.0",
+ "vite-node": "1.6.1",
+ "why-is-node-running": "^2.2.2"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "@vitest/browser": "1.6.1",
+ "@vitest/ui": "1.6.1",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest/node_modules/pathe": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
+ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/src/__tests__/client.coverage.test.ts b/src/__tests__/client.coverage.test.ts
new file mode 100644
index 0000000..352d8a4
--- /dev/null
+++ b/src/__tests__/client.coverage.test.ts
@@ -0,0 +1,332 @@
+/**
+ * Supplemental unit tests filling coverage gaps left by client.test.ts.
+ *
+ * Goal: every public method has a happy AND an error path, plus the request-body
+ * shape branches (layout/layouts/concurrency). Fully mocked — never hits the net.
+ */
+import { describe, it, expect, afterEach, vi } from 'vitest';
+import { Pictify } from '../client';
+import {
+ AuthenticationError,
+ TemplateNotFoundError,
+ QuotaExceededError,
+ RateLimitError,
+ RenderError,
+ ServerError,
+ PictifyError,
+} from '../errors';
+import {
+ createMockFetch,
+ mockImageResult,
+ mockRenderResult,
+ mockLayoutsRenderResult,
+ mockGifResponse,
+ mockBatchSubmitResult,
+ mockBatchResults,
+ mockTemplateResponse,
+ mockListTemplatesResult,
+} from './helpers';
+
+afterEach(() => {
+ vi.unstubAllGlobals();
+});
+
+function bodyOf(mockFetch: ReturnType, call = 0): Record {
+ const [, options] = mockFetch.mock.calls[call];
+ return JSON.parse(options.body);
+}
+
+// ---------------------------------------------------------------------------
+// render() / renderLayouts() body branches + multi-layout envelope
+// ---------------------------------------------------------------------------
+
+describe('render() — layout / layouts / quality body branches', () => {
+ it('sends `layout` when provided', async () => {
+ const mockFetch = createMockFetch([{ status: 200, body: mockRenderResult }]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'test-key' });
+ await client.render({ templateId: 't', layout: 'square' });
+ expect(bodyOf(mockFetch).layout).toBe('square');
+ });
+
+ it('sends `layouts` when provided', async () => {
+ const mockFetch = createMockFetch([{ status: 200, body: mockLayoutsRenderResult }]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'test-key' });
+ await client.render({ templateId: 't', layouts: ['default', 'square'] });
+ expect(bodyOf(mockFetch).layouts).toEqual(['default', 'square']);
+ });
+
+ it('omits layout/layouts when not provided', async () => {
+ const mockFetch = createMockFetch([{ status: 200, body: mockRenderResult }]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'test-key' });
+ await client.render({ templateId: 't' });
+ const body = bodyOf(mockFetch);
+ expect(body).not.toHaveProperty('layout');
+ expect(body).not.toHaveProperty('layouts');
+ });
+
+ it('forwards quality, width, height when provided', async () => {
+ const mockFetch = createMockFetch([{ status: 200, body: mockRenderResult }]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'test-key' });
+ await client.render({ templateId: 't', quality: 0.8, width: 800, height: 400 });
+ const body = bodyOf(mockFetch);
+ expect(body.quality).toBe(0.8);
+ expect(body.width).toBe(800);
+ expect(body.height).toBe(400);
+ });
+});
+
+describe('renderLayouts()', () => {
+ it('delegates to render() with the layouts array; surfaces errors[]', async () => {
+ const mockFetch = createMockFetch([{ status: 200, body: mockLayoutsRenderResult }]);
+ vi.stubGlobal('fetch', mockFetch);
+
+ const client = new Pictify({ apiKey: 'test-key' });
+ const result = await client.renderLayouts({
+ templateId: 't',
+ layouts: ['default', 'bogus-layout'],
+ });
+
+ expect(mockFetch).toHaveBeenCalledTimes(1);
+ expect(bodyOf(mockFetch).layouts).toEqual(['default', 'bogus-layout']);
+ expect(result.url).toBe(mockLayoutsRenderResult.results[0].url);
+ expect(result.results).toHaveLength(1);
+ expect(result.errors).toHaveLength(1);
+ expect(result.errors[0].layout).toBe('bogus-layout');
+ });
+
+ it('throws TemplateNotFoundError on 404', async () => {
+ const mockFetch = createMockFetch([
+ { status: 404, body: { message: 'Template not found' } },
+ ]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'test-key' });
+ await expect(
+ client.renderLayouts({ templateId: 'missing', layouts: ['default'] })
+ ).rejects.toThrow(TemplateNotFoundError);
+ });
+});
+
+// ---------------------------------------------------------------------------
+// Per-method error paths
+// ---------------------------------------------------------------------------
+
+describe('renderHtml() — error paths', () => {
+ it('throws AuthenticationError on 401 ({ message })', async () => {
+ const mockFetch = createMockFetch([{ status: 401, body: { message: 'Invalid Request' } }]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'bad-key' });
+ const err = await client.renderHtml({ html: 'x
' }).catch((e) => e);
+ expect(err).toBeInstanceOf(AuthenticationError);
+ expect(err.message).toBe('Invalid Request');
+ });
+
+ it('throws RenderError on 422 and exposes field errors', async () => {
+ const mockFetch = createMockFetch([
+ { status: 422, body: { message: 'Validation failed', errors: [{ field: 'html' }] } },
+ ]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'test-key' });
+ const err = (await client.renderHtml({ html: '' }).catch((e) => e)) as RenderError;
+ expect(err).toBeInstanceOf(RenderError);
+ expect(err.errors).toEqual([{ field: 'html' }]);
+ });
+
+ it('prefers body.error over body.message for the error message', async () => {
+ const mockFetch = createMockFetch([
+ { status: 422, body: { error: 'image boom', message: 'ignored' } },
+ ]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'test-key' });
+ const err = await client.renderHtml({ html: 'x
' }).catch((e) => e);
+ expect(err.message).toBe('image boom');
+ });
+});
+
+describe('renderUrl() — error path', () => {
+ it('throws ServerError on 500 (retries: 0)', async () => {
+ const mockFetch = createMockFetch([{ status: 500, body: { error: 'boom' } }]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'test-key', retries: 0 });
+ await expect(client.renderUrl({ url: 'https://x.com' })).rejects.toThrow(ServerError);
+ });
+});
+
+describe('render() — error path', () => {
+ it('throws ServerError on 500 (retries: 0)', async () => {
+ const mockFetch = createMockFetch([{ status: 500, body: { message: 'boom' } }]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'test-key', retries: 0 });
+ await expect(client.render({ templateId: 't' })).rejects.toThrow(ServerError);
+ });
+});
+
+describe('renderGif() — body branches + error path', () => {
+ it('forwards url source', async () => {
+ const mockFetch = createMockFetch([{ status: 200, body: mockGifResponse }]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'test-key' });
+ await client.renderGif({ url: 'https://x.com', quality: 'high' });
+ const body = bodyOf(mockFetch);
+ expect(body.url).toBe('https://x.com');
+ expect(body.quality).toBe('high');
+ });
+
+ it('throws RenderError on 422 (e.g. no animation frames)', async () => {
+ const mockFetch = createMockFetch([
+ { status: 422, body: { error: 'No frames captured', code: 'NO_FRAMES_CAPTURED' } },
+ ]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'test-key' });
+ const err = await client.renderGif({ html: 'static
' }).catch((e) => e);
+ expect(err).toBeInstanceOf(RenderError);
+ expect(err.message).toBe('No frames captured');
+ });
+});
+
+describe('renderBatch() — body branches + error path', () => {
+ it('sends layout, concurrency, quality', async () => {
+ const mockFetch = createMockFetch([{ status: 202, body: mockBatchSubmitResult }]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'test-key' });
+ await client.renderBatch({
+ templateId: 't',
+ variableSets: [{ name: 'A' }],
+ layout: 'square',
+ concurrency: 3,
+ quality: 0.7,
+ });
+ const body = bodyOf(mockFetch);
+ expect(body.layout).toBe('square');
+ expect(body.concurrency).toBe(3);
+ expect(body.quality).toBe(0.7);
+ });
+
+ it('sends layouts when provided', async () => {
+ const mockFetch = createMockFetch([{ status: 202, body: mockBatchSubmitResult }]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'test-key' });
+ await client.renderBatch({
+ templateId: 't',
+ variableSets: [{ name: 'A' }],
+ layouts: ['default', 'square'],
+ });
+ expect(bodyOf(mockFetch).layouts).toEqual(['default', 'square']);
+ });
+
+ it('throws RateLimitError on 429 without a quota code', async () => {
+ const mockFetch = createMockFetch([{ status: 429, body: { message: 'slow down' } }]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'test-key' });
+ await expect(
+ client.renderBatch({ templateId: 't', variableSets: [{}] })
+ ).rejects.toThrow(RateLimitError);
+ });
+
+ it('throws QuotaExceededError on 429 with code quota_exceeded', async () => {
+ const mockFetch = createMockFetch([
+ { status: 429, body: { message: 'over limit', code: 'quota_exceeded' } },
+ ]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'test-key' });
+ const err = await client
+ .renderBatch({ templateId: 't', variableSets: [{}] })
+ .catch((e) => e);
+ expect(err).toBeInstanceOf(QuotaExceededError);
+ expect(err.statusCode).toBe(429);
+ });
+});
+
+describe('getBatchResults() — error path', () => {
+ it('throws TemplateNotFoundError on 404 (batch not found)', async () => {
+ const mockFetch = createMockFetch([
+ { status: 404, body: { message: 'Batch job not found' } },
+ ]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'test-key' });
+ await expect(client.getBatchResults('nope')).rejects.toThrow(TemplateNotFoundError);
+ });
+});
+
+describe('getTemplate() — error path', () => {
+ it('throws TemplateNotFoundError on 404', async () => {
+ const mockFetch = createMockFetch([
+ { status: 404, body: { message: 'Template not found' } },
+ ]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'test-key' });
+ await expect(client.getTemplate('missing')).rejects.toThrow(TemplateNotFoundError);
+ });
+
+ it('throws AuthenticationError on 401', async () => {
+ const mockFetch = createMockFetch([{ status: 401, body: { message: 'Invalid Request' } }]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'bad-key' });
+ await expect(client.getTemplate('t')).rejects.toThrow(AuthenticationError);
+ });
+});
+
+describe('listTemplates() — error path', () => {
+ it('throws QuotaExceededError on 402', async () => {
+ const mockFetch = createMockFetch([{ status: 402, body: { message: 'over quota' } }]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'test-key' });
+ await expect(client.listTemplates()).rejects.toThrow(QuotaExceededError);
+ });
+
+ it('maps an unexpected 4xx (418) to RenderError', async () => {
+ const mockFetch = createMockFetch([{ status: 418, body: { message: 'teapot' } }]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'test-key' });
+ const err = await client.listTemplates().catch((e) => e);
+ expect(err).toBeInstanceOf(RenderError);
+ expect(err.statusCode).toBe(418);
+ });
+
+ it('returns the result on success with default options', async () => {
+ const mockFetch = createMockFetch([{ status: 200, body: mockListTemplatesResult }]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'test-key' });
+ const result = await client.listTemplates();
+ expect(result.templates).toHaveLength(1);
+ });
+});
+
+describe('createTemplate() — error path', () => {
+ it('throws RenderError on 422 (invalid template HTML)', async () => {
+ const mockFetch = createMockFetch([
+ { status: 422, body: { error: 'Template too large', code: 'TEMPLATE_TOO_LARGE' } },
+ ]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'test-key' });
+ const err = await client.createTemplate({ html: 'x
' }).catch((e) => e);
+ expect(err).toBeInstanceOf(RenderError);
+ expect(err.message).toBe('Template too large');
+ });
+});
+
+describe('error mapping — falls back to statusText when body has no message', () => {
+ it('uses statusText when body is empty', async () => {
+ const mockFetch = createMockFetch([
+ { status: 500, body: {}, statusText: 'Internal Server Error' },
+ ]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'test-key', retries: 0 });
+ const err = await client.renderHtml({ html: 'x
' }).catch((e) => e);
+ expect(err).toBeInstanceOf(ServerError);
+ expect(err.message).toBe('Internal Server Error');
+ });
+
+ it('returns a generic PictifyError shape for a 3xx-ish unexpected code', async () => {
+ // 399 is < 400 -> not ok and not handled by 4xx/5xx branches -> UNKNOWN_ERROR
+ const mockFetch = createMockFetch([{ status: 399, body: { message: 'weird' } }]);
+ vi.stubGlobal('fetch', mockFetch);
+ const client = new Pictify({ apiKey: 'test-key', retries: 0 });
+ const err = await client.renderHtml({ html: 'x
' }).catch((e) => e);
+ expect(err).toBeInstanceOf(PictifyError);
+ expect(err.code).toBe('UNKNOWN_ERROR');
+ });
+});
diff --git a/src/__tests__/client.test.ts b/src/__tests__/client.test.ts
index 910042e..0cde9b3 100644
--- a/src/__tests__/client.test.ts
+++ b/src/__tests__/client.test.ts
@@ -2,27 +2,34 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { Pictify } from '../client';
import {
AuthenticationError,
- QuotaExceededError,
- RateLimitError,
RenderError,
+ ServerError,
NetworkError,
TimeoutError,
- PictifyError,
} from '../errors';
import {
createMockFetch,
- createMockStreamFetch,
+ mockImageResult,
mockRenderResult,
- mockTemplate,
- mockBatchResult,
- mockGifResult,
+ mockGifResponse,
+ mockBatchSubmitResult,
+ mockBatchResults,
+ mockTemplateResponse,
+ mockListTemplatesResult,
} from './helpers';
-describe('Pictify Client', () => {
- beforeEach(() => {
- vi.useFakeTimers();
- });
+/** Parse the JSON body sent on the Nth fetch call (default: first). */
+function bodyOf(mockFetch: ReturnType, call = 0): Record {
+ const [, options] = mockFetch.mock.calls[call];
+ return JSON.parse(options.body);
+}
+
+/** Return the URL string of the Nth fetch call (default: first). */
+function urlOf(mockFetch: ReturnType, call = 0): string {
+ return mockFetch.mock.calls[call][0] as string;
+}
+describe('Pictify Client', () => {
afterEach(() => {
vi.unstubAllGlobals();
vi.useRealTimers();
@@ -45,613 +52,394 @@ describe('Pictify Client', () => {
});
it('uses custom base URL when provided', () => {
- const client = new Pictify({
- apiKey: 'test-key',
- baseUrl: 'https://custom.api.com',
- });
+ const client = new Pictify({ apiKey: 'test-key', baseUrl: 'https://custom.api.com' });
expect(client['baseUrl']).toBe('https://custom.api.com');
});
it('strips trailing slash from base URL', () => {
- const client = new Pictify({
- apiKey: 'test-key',
- baseUrl: 'https://custom.api.com/',
- });
+ const client = new Pictify({ apiKey: 'test-key', baseUrl: 'https://custom.api.com/' });
expect(client['baseUrl']).toBe('https://custom.api.com');
});
- it('uses default timeout (30000ms) when not provided', () => {
+ it('uses default timeout and retries', () => {
const client = new Pictify({ apiKey: 'test-key' });
expect(client['timeout']).toBe(30000);
- });
-
- it('uses custom timeout when provided', () => {
- const client = new Pictify({ apiKey: 'test-key', timeout: 60000 });
- expect(client['timeout']).toBe(60000);
- });
-
- it('uses default retries (3) when not provided', () => {
- const client = new Pictify({ apiKey: 'test-key' });
expect(client['retries']).toBe(3);
});
- it('uses custom retries when provided', () => {
- const client = new Pictify({ apiKey: 'test-key', retries: 5 });
- expect(client['retries']).toBe(5);
- });
-
- it('allows zero retries', () => {
- const client = new Pictify({ apiKey: 'test-key', retries: 0 });
+ it('respects custom timeout and retries (including retries: 0)', () => {
+ const client = new Pictify({ apiKey: 'test-key', timeout: 5000, retries: 0 });
+ expect(client['timeout']).toBe(5000);
expect(client['retries']).toBe(0);
});
});
- describe('render', () => {
- it('renders image with minimal options', async () => {
- const mockFetch = createMockFetch([{ status: 200, body: mockRenderResult }]);
- vi.stubGlobal('fetch', mockFetch);
-
- const client = new Pictify({ apiKey: 'test-key' });
- const result = await client.render({ templateId: 'tmpl_123' });
-
- expect(result).toEqual(mockRenderResult);
- expect(mockFetch).toHaveBeenCalledTimes(1);
- });
-
- it('renders image with all options', async () => {
- const mockFetch = createMockFetch([{ status: 200, body: mockRenderResult }]);
+ describe('renderHtml()', () => {
+ it('POSTs to /image with fileExtension and returns { url, id, createdAt }', async () => {
+ const mockFetch = createMockFetch([{ status: 200, body: mockImageResult }]);
vi.stubGlobal('fetch', mockFetch);
const client = new Pictify({ apiKey: 'test-key' });
- await client.render({
- templateId: 'tmpl_123',
- variables: { title: 'Hello' },
- format: 'jpg',
- width: 1200,
- height: 630,
- deviceScaleFactor: 2,
- transparent: true,
- quality: 85,
- download: true,
+ const result = await client.renderHtml({
+ html: 'hi
',
+ width: 600,
+ height: 300,
+ format: 'png',
});
- const [url, options] = mockFetch.mock.calls[0];
- const body = JSON.parse(options.body);
-
- expect(body.templateId).toBe('tmpl_123');
- expect(body.variables).toEqual({ title: 'Hello' });
- expect(body.format).toBe('jpg');
- expect(body.width).toBe(1200);
- expect(body.height).toBe(630);
- expect(body.deviceScaleFactor).toBe(2);
- expect(body.transparent).toBe(true);
- expect(body.quality).toBe(85);
- expect(body.download).toBe(true);
- });
-
- it('sends correct Authorization header', async () => {
- const mockFetch = createMockFetch([{ status: 200, body: mockRenderResult }]);
- vi.stubGlobal('fetch', mockFetch);
-
- const client = new Pictify({ apiKey: 'my-secret-key' });
- await client.render({ templateId: 'tmpl_123' });
-
- const [, options] = mockFetch.mock.calls[0];
- expect(options.headers.Authorization).toBe('Bearer my-secret-key');
+ expect(urlOf(mockFetch)).toBe('https://api.pictify.io/image');
+ const body = bodyOf(mockFetch);
+ expect(body.html).toBe('hi
');
+ expect(body.width).toBe(600);
+ expect(body.height).toBe(300);
+ expect(body.fileExtension).toBe('png');
+ expect(result).toEqual(mockImageResult);
});
- it('sends correct User-Agent header', async () => {
- const mockFetch = createMockFetch([{ status: 200, body: mockRenderResult }]);
+ it('defaults fileExtension to png', async () => {
+ const mockFetch = createMockFetch([{ status: 200, body: mockImageResult }]);
vi.stubGlobal('fetch', mockFetch);
const client = new Pictify({ apiKey: 'test-key' });
- await client.render({ templateId: 'tmpl_123' });
-
- const [, options] = mockFetch.mock.calls[0];
- expect(options.headers['User-Agent']).toBe('@pictify/sdk/1.0.0');
- });
-
- it('handles different formats', async () => {
- const formats = ['png', 'jpg', 'jpeg', 'webp', 'pdf'] as const;
-
- for (const format of formats) {
- const mockFetch = createMockFetch([
- { status: 200, body: { ...mockRenderResult, format } },
- ]);
- vi.stubGlobal('fetch', mockFetch);
+ await client.renderHtml({ html: 'hi
' });
- const client = new Pictify({ apiKey: 'test-key' });
- const result = await client.render({ templateId: 'tmpl_123', format });
-
- expect(result.format).toBe(format);
- }
+ expect(bodyOf(mockFetch).fileExtension).toBe('png');
});
- });
- describe('renderStream', () => {
- it('returns readable stream', async () => {
- const mockFetch = createMockStreamFetch(200, ['chunk1', 'chunk2']);
+ it('inlines css into the html via a hi
');
});
- it('throws NetworkError when no response body', async () => {
- const mockFetch = vi.fn().mockResolvedValue({
- ok: true,
- status: 200,
- body: null,
- });
+ it('forwards selector when provided', async () => {
+ const mockFetch = createMockFetch([{ status: 200, body: mockImageResult }]);
vi.stubGlobal('fetch', mockFetch);
const client = new Pictify({ apiKey: 'test-key' });
+ await client.renderHtml({ html: 'hi
', selector: '#x' });
- await expect(client.renderStream({ templateId: 'tmpl_123' })).rejects.toThrow(
- NetworkError
- );
+ expect(bodyOf(mockFetch).selector).toBe('#x');
});
});
- describe('renderBatch', () => {
- it('renders batch with single item', async () => {
- const mockFetch = createMockFetch([{ status: 200, body: mockBatchResult }]);
+ describe('renderUrl()', () => {
+ it('POSTs to /image with url and returns { url, id, createdAt }', async () => {
+ const mockFetch = createMockFetch([{ status: 200, body: mockImageResult }]);
vi.stubGlobal('fetch', mockFetch);
const client = new Pictify({ apiKey: 'test-key' });
- const result = await client.renderBatch({
- templateId: 'tmpl_123',
- items: [{ variables: { title: 'Test' } }],
- });
-
- expect(result).toEqual(mockBatchResult);
- });
-
- it('throws PictifyError when batch exceeds 500 items', async () => {
- const client = new Pictify({ apiKey: 'test-key' });
- const items = Array(501)
- .fill(null)
- .map(() => ({ variables: {} }));
+ const result = await client.renderUrl({ url: 'https://example.com', width: 800 });
- await expect(
- client.renderBatch({ templateId: 'tmpl_123', items })
- ).rejects.toThrow(PictifyError);
+ expect(urlOf(mockFetch)).toBe('https://api.pictify.io/image');
+ const body = bodyOf(mockFetch);
+ expect(body.url).toBe('https://example.com');
+ expect(body.width).toBe(800);
+ expect(body.fileExtension).toBe('png');
+ expect(result).toEqual(mockImageResult);
});
+ });
- it('accepts batch with exactly 500 items', async () => {
- const mockFetch = createMockFetch([{ status: 200, body: mockBatchResult }]);
+ describe('render()', () => {
+ it('POSTs to /templates/:uid/render and returns the results envelope', async () => {
+ const mockFetch = createMockFetch([{ status: 200, body: mockRenderResult }]);
vi.stubGlobal('fetch', mockFetch);
const client = new Pictify({ apiKey: 'test-key' });
- const items = Array(500)
- .fill(null)
- .map(() => ({ variables: {} }));
+ const result = await client.render({
+ templateId: 'XL13XACH2V',
+ variables: { name: 'Ada', company: 'Pictify' },
+ });
- await expect(
- client.renderBatch({ templateId: 'tmpl_123', items })
- ).resolves.toBeDefined();
+ expect(urlOf(mockFetch)).toBe('https://api.pictify.io/templates/XL13XACH2V/render');
+ const body = bodyOf(mockFetch);
+ expect(body.variables).toEqual({ name: 'Ada', company: 'Pictify' });
+ expect(body.format).toBe('png');
+ expect(result.results).toHaveLength(1);
+ expect(result.templateUid).toBe('XL13XACH2V');
});
- });
- describe('renderHtml', () => {
- it('renders HTML with minimal options', async () => {
+ it('exposes a convenience `url` getter from results[0]', async () => {
const mockFetch = createMockFetch([{ status: 200, body: mockRenderResult }]);
vi.stubGlobal('fetch', mockFetch);
const client = new Pictify({ apiKey: 'test-key' });
- const result = await client.renderHtml({ html: 'Hello
' });
+ const result = await client.render({ templateId: 'XL13XACH2V' });
- expect(result).toEqual(mockRenderResult);
+ expect(result.url).toBe(mockRenderResult.results[0].url);
});
- it('renders HTML with CSS', async () => {
- const mockFetch = createMockFetch([{ status: 200, body: mockRenderResult }]);
+ it('`url` getter is undefined when results[] is empty', async () => {
+ const empty = {
+ results: [],
+ errors: [],
+ totalLayouts: 0,
+ totalRendered: 0,
+ totalErrors: 0,
+ templateUid: 'XL13XACH2V',
+ };
+ const mockFetch = createMockFetch([{ status: 200, body: empty }]);
vi.stubGlobal('fetch', mockFetch);
const client = new Pictify({ apiKey: 'test-key' });
- await client.renderHtml({
- html: 'Hello
',
- css: 'div { color: red; }',
- });
+ const result = await client.render({ templateId: 'XL13XACH2V' });
- const [, options] = mockFetch.mock.calls[0];
- const body = JSON.parse(options.body);
- expect(body.css).toBe('div { color: red; }');
+ expect(result.url).toBeUndefined();
+ expect(result.results).toEqual([]);
});
- it('uses default dimensions (1200x630)', async () => {
+ it('defaults variables to an empty object', async () => {
const mockFetch = createMockFetch([{ status: 200, body: mockRenderResult }]);
vi.stubGlobal('fetch', mockFetch);
const client = new Pictify({ apiKey: 'test-key' });
- await client.renderHtml({ html: 'Hello
' });
+ await client.render({ templateId: 'XL13XACH2V' });
- const [, options] = mockFetch.mock.calls[0];
- const body = JSON.parse(options.body);
- expect(body.width).toBe(1200);
- expect(body.height).toBe(630);
+ expect(bodyOf(mockFetch).variables).toEqual({});
});
- it('uses custom dimensions when provided', async () => {
+ it('url-encodes the template id in the path', async () => {
const mockFetch = createMockFetch([{ status: 200, body: mockRenderResult }]);
vi.stubGlobal('fetch', mockFetch);
const client = new Pictify({ apiKey: 'test-key' });
- await client.renderHtml({
- html: 'Hello
',
- width: 800,
- height: 400,
- });
+ await client.render({ templateId: 'a b/c' });
- const [, options] = mockFetch.mock.calls[0];
- const body = JSON.parse(options.body);
- expect(body.width).toBe(800);
- expect(body.height).toBe(400);
+ expect(urlOf(mockFetch)).toBe('https://api.pictify.io/templates/a%20b%2Fc/render');
});
});
- describe('renderGif', () => {
- it('renders GIF with template and frames', async () => {
- const mockFetch = createMockFetch([{ status: 200, body: mockGifResult }]);
+ describe('renderGif()', () => {
+ it('POSTs to /gif and flattens the { gif } envelope', async () => {
+ const mockFetch = createMockFetch([{ status: 200, body: mockGifResponse }]);
vi.stubGlobal('fetch', mockFetch);
const client = new Pictify({ apiKey: 'test-key' });
- const result = await client.renderGif({
- templateId: 'tmpl_123',
- frames: [{ variables: { text: 'Frame 1' } }, { variables: { text: 'Frame 2' } }],
- });
+ const result = await client.renderGif({ html: 'x
', width: 400, height: 200 });
- expect(result).toEqual(mockGifResult);
+ expect(urlOf(mockFetch)).toBe('https://api.pictify.io/gif');
+ expect(result).toEqual(mockGifResponse.gif);
+ expect(result.url).toBe(mockGifResponse.gif.url);
+ expect(result.uid).toBe(mockGifResponse.gif.uid);
+ expect(result.animationLength).toBe(2000);
});
- it('renders GIF with HTML and frames', async () => {
- const mockFetch = createMockFetch([{ status: 200, body: mockGifResult }]);
+ it('defaults quality to medium', async () => {
+ const mockFetch = createMockFetch([{ status: 200, body: mockGifResponse }]);
vi.stubGlobal('fetch', mockFetch);
const client = new Pictify({ apiKey: 'test-key' });
- await client.renderGif({
- html: '{{text}}
',
- frames: [{ variables: { text: 'Frame 1' } }],
- });
-
- const [, options] = mockFetch.mock.calls[0];
- const body = JSON.parse(options.body);
- expect(body.html).toBe('{{text}}
');
- });
-
- it('throws PictifyError when no frames', async () => {
- const client = new Pictify({ apiKey: 'test-key' });
+ await client.renderGif({ html: 'x
' });
- await expect(
- client.renderGif({ templateId: 'tmpl_123', frames: [] })
- ).rejects.toThrow(PictifyError);
+ expect(bodyOf(mockFetch).quality).toBe('medium');
});
- it('throws PictifyError when frames exceed 100', async () => {
- const client = new Pictify({ apiKey: 'test-key' });
- const frames = Array(101)
- .fill(null)
- .map(() => ({ variables: {} }));
-
- await expect(
- client.renderGif({ templateId: 'tmpl_123', frames })
- ).rejects.toThrow(PictifyError);
- });
-
- it('uses default delay (100ms)', async () => {
- const mockFetch = createMockFetch([{ status: 200, body: mockGifResult }]);
+ it('maps templateId to `template` and forwards variables', async () => {
+ const mockFetch = createMockFetch([{ status: 200, body: mockGifResponse }]);
vi.stubGlobal('fetch', mockFetch);
const client = new Pictify({ apiKey: 'test-key' });
- await client.renderGif({
- templateId: 'tmpl_123',
- frames: [{ variables: {} }],
- });
+ await client.renderGif({ templateId: 'tmpl_1', variables: { name: 'Ada' } });
- const [, options] = mockFetch.mock.calls[0];
- const body = JSON.parse(options.body);
- expect(body.delay).toBe(100);
+ const body = bodyOf(mockFetch);
+ expect(body.template).toBe('tmpl_1');
+ expect(body.variables).toEqual({ name: 'Ada' });
+ expect(body).not.toHaveProperty('templateId');
});
+ });
- it('uses custom delay per frame', async () => {
- const mockFetch = createMockFetch([{ status: 200, body: mockGifResult }]);
+ describe('renderBatch() + getBatchResults()', () => {
+ it('POSTs variableSets to /batch-render and returns the submit result', async () => {
+ const mockFetch = createMockFetch([{ status: 202, body: mockBatchSubmitResult }]);
vi.stubGlobal('fetch', mockFetch);
const client = new Pictify({ apiKey: 'test-key' });
- await client.renderGif({
- templateId: 'tmpl_123',
- frames: [{ variables: {}, delay: 500 }],
- delay: 200,
+ const result = await client.renderBatch({
+ templateId: 'XL13XACH2V',
+ variableSets: [{ name: 'A' }, { name: 'B' }],
});
- const [, options] = mockFetch.mock.calls[0];
- const body = JSON.parse(options.body);
- expect(body.delay).toBe(200);
- expect(body.frames[0].delay).toBe(500);
+ expect(urlOf(mockFetch)).toBe(
+ 'https://api.pictify.io/templates/XL13XACH2V/batch-render'
+ );
+ const body = bodyOf(mockFetch);
+ expect(body.variableSets).toEqual([{ name: 'A' }, { name: 'B' }]);
+ expect(body.format).toBe('png');
+ expect(result.batchId).toBe('batch_LA7U9OUEYT1I234I');
+ expect(result.status).toBe('pending');
+ expect(result.totalItems).toBe(2);
});
- });
- describe('getTemplate', () => {
- it('returns template details', async () => {
- const mockFetch = createMockFetch([{ status: 200, body: mockTemplate }]);
+ it('GETs /templates/batch/:id/results', async () => {
+ const mockFetch = createMockFetch([{ status: 200, body: mockBatchResults }]);
vi.stubGlobal('fetch', mockFetch);
const client = new Pictify({ apiKey: 'test-key' });
- const result = await client.getTemplate('tmpl_123');
+ const result = await client.getBatchResults('batch_LA7U9OUEYT1I234I');
- expect(result).toEqual(mockTemplate);
+ expect(urlOf(mockFetch)).toBe(
+ 'https://api.pictify.io/templates/batch/batch_LA7U9OUEYT1I234I/results'
+ );
+ expect(mockFetch.mock.calls[0][1].method).toBe('GET');
+ expect(result.status).toBe('completed');
+ expect(result.results).toHaveLength(2);
+ // Per the API contract, items carry no URLs.
+ expect(result.results[0]).not.toHaveProperty('url');
});
+ });
- it('sends GET request to correct URL', async () => {
- const mockFetch = createMockFetch([{ status: 200, body: mockTemplate }]);
+ describe('getTemplate()', () => {
+ it('GETs /templates/:uid and unwraps { template }', async () => {
+ const mockFetch = createMockFetch([{ status: 200, body: mockTemplateResponse }]);
vi.stubGlobal('fetch', mockFetch);
const client = new Pictify({ apiKey: 'test-key' });
- await client.getTemplate('tmpl_123');
+ const template = await client.getTemplate('XL13XACH2V');
- const [url] = mockFetch.mock.calls[0];
- expect(url).toContain('/templates/tmpl_123');
+ expect(urlOf(mockFetch)).toBe('https://api.pictify.io/templates/XL13XACH2V');
+ expect(template.uid).toBe('XL13XACH2V');
+ expect(Array.isArray(template.variableDefinitions)).toBe(true);
});
});
- describe('listTemplates', () => {
- it('returns array of templates', async () => {
- const mockFetch = createMockFetch([
- { status: 200, body: { templates: [mockTemplate] } },
- ]);
+ describe('listTemplates()', () => {
+ it('GETs /templates and returns { templates, pagination }', async () => {
+ const mockFetch = createMockFetch([{ status: 200, body: mockListTemplatesResult }]);
vi.stubGlobal('fetch', mockFetch);
const client = new Pictify({ apiKey: 'test-key' });
const result = await client.listTemplates();
- expect(result).toEqual([mockTemplate]);
+ expect(urlOf(mockFetch)).toBe('https://api.pictify.io/templates');
+ expect(Array.isArray(result.templates)).toBe(true);
+ expect(result.pagination.page).toBe(1);
});
- it('returns empty array when no templates', async () => {
- const mockFetch = createMockFetch([{ status: 200, body: { templates: [] } }]);
+ it('serializes page/limit/sort into the query string', async () => {
+ const mockFetch = createMockFetch([{ status: 200, body: mockListTemplatesResult }]);
vi.stubGlobal('fetch', mockFetch);
const client = new Pictify({ apiKey: 'test-key' });
- const result = await client.listTemplates();
-
- expect(result).toEqual([]);
- });
- });
+ await client.listTemplates({ page: 2, limit: 25, sort: 'name' });
- describe('error handling', () => {
- it('throws AuthenticationError on 401', async () => {
- const mockFetch = createMockFetch([
- { status: 401, body: { message: 'Invalid API key' } },
- ]);
- vi.stubGlobal('fetch', mockFetch);
-
- const client = new Pictify({ apiKey: 'invalid-key' });
-
- await expect(client.render({ templateId: 'tmpl_123' })).rejects.toThrow(
- AuthenticationError
- );
- });
-
- it('throws QuotaExceededError on 402', async () => {
- const mockFetch = createMockFetch([
- { status: 402, body: { message: 'Quota exceeded' } },
- ]);
- vi.stubGlobal('fetch', mockFetch);
-
- const client = new Pictify({ apiKey: 'test-key' });
-
- await expect(client.render({ templateId: 'tmpl_123' })).rejects.toThrow(
- QuotaExceededError
+ expect(urlOf(mockFetch)).toBe(
+ 'https://api.pictify.io/templates?page=2&limit=25&sort=name'
);
});
+ });
- it('throws error with TEMPLATE_NOT_FOUND on 404', async () => {
- const mockFetch = createMockFetch([
- { status: 404, body: { message: 'Template not found' } },
- ]);
+ describe('createTemplate()', () => {
+ it('POSTs to /templates and unwraps { template }', async () => {
+ const mockFetch = createMockFetch([{ status: 200, body: mockTemplateResponse }]);
vi.stubGlobal('fetch', mockFetch);
const client = new Pictify({ apiKey: 'test-key' });
-
- await expect(client.render({ templateId: 'tmpl_123' })).rejects.toMatchObject({
- code: 'TEMPLATE_NOT_FOUND',
+ const template = await client.createTemplate({
+ html: 'Hi {{name}}
',
+ name: 'Test',
+ width: 600,
+ height: 200,
});
- });
-
- it('throws RateLimitError on 429', async () => {
- const mockFetch = createMockFetch([
- { status: 429, body: { message: 'Rate limit exceeded' } },
- ]);
- vi.stubGlobal('fetch', mockFetch);
-
- const client = new Pictify({ apiKey: 'test-key' });
-
- await expect(client.render({ templateId: 'tmpl_123' })).rejects.toThrow(
- RateLimitError
- );
- });
-
- it('throws RenderError on 500', async () => {
- const mockFetch = createMockFetch([
- { status: 500, body: { message: 'Server error' } },
- ]);
- vi.stubGlobal('fetch', mockFetch);
- const client = new Pictify({ apiKey: 'test-key', retries: 0 });
-
- await expect(client.render({ templateId: 'tmpl_123' })).rejects.toThrow(RenderError);
- });
-
- it('throws RenderError on 502/503/504', async () => {
- for (const status of [502, 503, 504]) {
- const mockFetch = createMockFetch([
- { status, body: { message: 'Gateway error' } },
- ]);
- vi.stubGlobal('fetch', mockFetch);
-
- const client = new Pictify({ apiKey: 'test-key', retries: 0 });
-
- await expect(client.render({ templateId: 'tmpl_123' })).rejects.toThrow(RenderError);
- }
+ expect(urlOf(mockFetch)).toBe('https://api.pictify.io/templates');
+ const body = bodyOf(mockFetch);
+ expect(body.html).toBe('Hi {{name}}
');
+ expect(body.name).toBe('Test');
+ expect(template.uid).toBe('XL13XACH2V');
});
+ });
- it('throws NetworkError on network failure', async () => {
- const mockFetch = vi.fn().mockRejectedValue(new Error('Connection refused'));
+ describe('request internals', () => {
+ it('sends Authorization and Content-Type headers', async () => {
+ const mockFetch = createMockFetch([{ status: 200, body: mockImageResult }]);
vi.stubGlobal('fetch', mockFetch);
- const client = new Pictify({ apiKey: 'test-key', retries: 0 });
+ const client = new Pictify({ apiKey: 'secret-key' });
+ await client.renderHtml({ html: 'x
' });
- await expect(client.render({ templateId: 'tmpl_123' })).rejects.toThrow(NetworkError);
+ const headers = mockFetch.mock.calls[0][1].headers as Record;
+ expect(headers.Authorization).toBe('Bearer secret-key');
+ expect(headers['Content-Type']).toBe('application/json');
+ expect(headers['User-Agent']).toContain('@pictify/sdk');
});
- it('throws TimeoutError on timeout', async () => {
- const abortError = new Error('Aborted');
- abortError.name = 'AbortError';
- const mockFetch = vi.fn().mockRejectedValue(abortError);
+ it('strips undefined fields from the request body', async () => {
+ const mockFetch = createMockFetch([{ status: 200, body: mockImageResult }]);
vi.stubGlobal('fetch', mockFetch);
- const client = new Pictify({ apiKey: 'test-key', retries: 0 });
+ const client = new Pictify({ apiKey: 'test-key' });
+ // width/height/selector omitted -> must not appear in the serialized body.
+ await client.renderHtml({ html: 'x
' });
- await expect(client.render({ templateId: 'tmpl_123' })).rejects.toThrow(TimeoutError);
+ const body = bodyOf(mockFetch);
+ expect(body).not.toHaveProperty('width');
+ expect(body).not.toHaveProperty('height');
+ expect(body).not.toHaveProperty('selector');
+ expect(body.html).toBe('x
');
});
});
- describe('retry logic', () => {
- it('retries on 5xx errors up to max retries', async () => {
+ describe('retry + timeout behavior', () => {
+ it('retries on 5xx then succeeds', async () => {
+ vi.useFakeTimers();
const mockFetch = createMockFetch([
- { status: 500, body: { message: 'Error 1' } },
- { status: 500, body: { message: 'Error 2' } },
- { status: 200, body: mockRenderResult },
+ { status: 503, body: { error: 'unavailable' } },
+ { status: 200, body: mockImageResult },
]);
vi.stubGlobal('fetch', mockFetch);
- const client = new Pictify({ apiKey: 'test-key' });
- const promise = client.render({ templateId: 'tmpl_123' });
-
- await vi.advanceTimersByTimeAsync(1000);
- await vi.advanceTimersByTimeAsync(2000);
-
+ const client = new Pictify({ apiKey: 'test-key', retries: 2 });
+ const promise = client.renderHtml({ html: 'x
' });
+ await vi.runAllTimersAsync();
const result = await promise;
- expect(result).toEqual(mockRenderResult);
- expect(mockFetch).toHaveBeenCalledTimes(3);
- });
-
- it('does not retry on 4xx errors', async () => {
- const mockFetch = createMockFetch([
- { status: 400, body: { message: 'Bad request' } },
- ]);
- vi.stubGlobal('fetch', mockFetch);
-
- const client = new Pictify({ apiKey: 'test-key' });
- await expect(client.render({ templateId: 'tmpl_123' })).rejects.toThrow(
- PictifyError
- );
- expect(mockFetch).toHaveBeenCalledTimes(1);
+ expect(mockFetch).toHaveBeenCalledTimes(2);
+ expect(result).toEqual(mockImageResult);
});
- it('uses exponential backoff', async () => {
- const mockFetch = createMockFetch([
- { status: 500, body: { message: 'Error' } },
- { status: 500, body: { message: 'Error' } },
- { status: 500, body: { message: 'Error' } },
- { status: 200, body: mockRenderResult },
- ]);
+ it('throws ServerError after exhausting retries on 5xx', async () => {
+ vi.useFakeTimers();
+ const mockFetch = createMockFetch([{ status: 500, body: { error: 'boom' } }]);
vi.stubGlobal('fetch', mockFetch);
- const client = new Pictify({ apiKey: 'test-key' });
- const promise = client.render({ templateId: 'tmpl_123' });
-
- // First retry after 1 second (2^0 * 1000)
- await vi.advanceTimersByTimeAsync(1000);
- expect(mockFetch).toHaveBeenCalledTimes(2);
-
- // Second retry after 2 seconds (2^1 * 1000)
- await vi.advanceTimersByTimeAsync(2000);
- expect(mockFetch).toHaveBeenCalledTimes(3);
-
- // Third retry after 4 seconds (2^2 * 1000)
- await vi.advanceTimersByTimeAsync(4000);
- expect(mockFetch).toHaveBeenCalledTimes(4);
+ const client = new Pictify({ apiKey: 'test-key', retries: 1 });
+ const promise = client.renderHtml({ html: 'x
' }).catch((e) => e);
+ await vi.runAllTimersAsync();
+ const err = await promise;
- await promise;
+ expect(err).toBeInstanceOf(ServerError);
+ expect(mockFetch).toHaveBeenCalledTimes(2); // initial + 1 retry
});
- it('throws after exhausting retries', async () => {
- const mockFetch = createMockFetch([
- { status: 500, body: { message: 'Error' } },
- { status: 500, body: { message: 'Error' } },
- { status: 500, body: { message: 'Error' } },
- { status: 500, body: { message: 'Error' } },
- ]);
+ it('does NOT retry on 4xx', async () => {
+ const mockFetch = createMockFetch([{ status: 422, body: { error: 'bad' } }]);
vi.stubGlobal('fetch', mockFetch);
- const client = new Pictify({ apiKey: 'test-key' });
- let error: Error | undefined;
-
- // Start the render and capture rejection
- const promise = client.render({ templateId: 'tmpl_123' }).catch((e) => {
- error = e;
- });
-
- // Advance through all retry delays: 1s + 2s + 4s = 7s total
- await vi.advanceTimersByTimeAsync(1000);
- await vi.advanceTimersByTimeAsync(2000);
- await vi.advanceTimersByTimeAsync(4000);
-
- await promise;
-
- expect(error).toBeInstanceOf(RenderError);
- expect(mockFetch).toHaveBeenCalledTimes(4);
+ const client = new Pictify({ apiKey: 'test-key', retries: 3 });
+ await expect(client.renderHtml({ html: 'x
' })).rejects.toThrow(RenderError);
+ expect(mockFetch).toHaveBeenCalledTimes(1);
});
- it('retries on network errors', async () => {
- const mockFetch = vi
- .fn()
- .mockRejectedValueOnce(new Error('Network error'))
- .mockRejectedValueOnce(new Error('Network error'))
- .mockResolvedValueOnce({
- ok: true,
- status: 200,
- json: async () => mockRenderResult,
- });
+ it('throws TimeoutError on AbortError', async () => {
+ const abortErr = new Error('aborted');
+ abortErr.name = 'AbortError';
+ const mockFetch = vi.fn().mockRejectedValue(abortErr);
vi.stubGlobal('fetch', mockFetch);
- const client = new Pictify({ apiKey: 'test-key' });
- const promise = client.render({ templateId: 'tmpl_123' });
-
- await vi.advanceTimersByTimeAsync(1000);
- await vi.advanceTimersByTimeAsync(2000);
+ const client = new Pictify({ apiKey: 'test-key', retries: 0 });
+ await expect(client.renderHtml({ html: 'x
' })).rejects.toThrow(TimeoutError);
+ });
- const result = await promise;
- expect(result).toEqual(mockRenderResult);
- });
-
- it('retries on timeout errors', async () => {
- const abortError = new Error('Aborted');
- abortError.name = 'AbortError';
-
- const mockFetch = vi
- .fn()
- .mockRejectedValueOnce(abortError)
- .mockRejectedValueOnce(abortError)
- .mockResolvedValueOnce({
- ok: true,
- status: 200,
- json: async () => mockRenderResult,
- });
+ it('throws NetworkError on a generic fetch failure', async () => {
+ const mockFetch = vi.fn().mockRejectedValue(new Error('socket hang up'));
vi.stubGlobal('fetch', mockFetch);
- const client = new Pictify({ apiKey: 'test-key' });
- const promise = client.render({ templateId: 'tmpl_123' });
-
- await vi.advanceTimersByTimeAsync(1000);
- await vi.advanceTimersByTimeAsync(2000);
-
- const result = await promise;
- expect(result).toEqual(mockRenderResult);
+ const client = new Pictify({ apiKey: 'test-key', retries: 0 });
+ await expect(client.renderHtml({ html: 'x
' })).rejects.toThrow(NetworkError);
});
});
});
diff --git a/src/__tests__/errors.test.ts b/src/__tests__/errors.test.ts
index 79298a7..7005382 100644
--- a/src/__tests__/errors.test.ts
+++ b/src/__tests__/errors.test.ts
@@ -6,6 +6,7 @@ import {
RateLimitError,
QuotaExceededError,
RenderError,
+ ServerError,
NetworkError,
TimeoutError,
createErrorFromResponse,
@@ -53,13 +54,18 @@ describe('AuthenticationError', () => {
});
describe('TemplateNotFoundError', () => {
- it('creates error with template ID', () => {
- const error = new TemplateNotFoundError('tmpl_123');
- expect(error.message).toBe('Template not found: tmpl_123');
+ it('creates error with default message', () => {
+ const error = new TemplateNotFoundError();
+ expect(error.message).toBe('Template not found');
expect(error.code).toBe('TEMPLATE_NOT_FOUND');
expect(error.statusCode).toBe(404);
expect(error.name).toBe('TemplateNotFoundError');
});
+
+ it('creates error with custom message', () => {
+ const error = new TemplateNotFoundError('Batch job not found');
+ expect(error.message).toBe('Batch job not found');
+ });
});
describe('RateLimitError', () => {
@@ -80,28 +86,48 @@ describe('RateLimitError', () => {
});
describe('QuotaExceededError', () => {
- it('creates error with default message', () => {
+ it('creates error with default message and 402', () => {
const error = new QuotaExceededError();
- expect(error.message).toBe('Monthly render quota exceeded');
+ expect(error.message).toBe('Render quota exceeded');
expect(error.code).toBe('QUOTA_EXCEEDED');
expect(error.statusCode).toBe(402);
expect(error.name).toBe('QuotaExceededError');
});
+
+ it('accepts a custom status code (e.g. 429)', () => {
+ const error = new QuotaExceededError('over team limit', 429);
+ expect(error.statusCode).toBe(429);
+ });
});
describe('RenderError', () => {
- it('creates error with message and details', () => {
+ it('creates error with message and details (default 422)', () => {
const error = new RenderError('Render failed', { templateId: 'tmpl_123' });
expect(error.message).toBe('Render failed');
expect(error.code).toBe('RENDER_FAILED');
- expect(error.statusCode).toBe(500);
+ expect(error.statusCode).toBe(422);
expect(error.details).toEqual({ templateId: 'tmpl_123' });
expect(error.name).toBe('RenderError');
});
+
+ it('exposes field-level `errors` from details', () => {
+ const error = new RenderError('Validation failed', { errors: [{ field: 'name' }] });
+ expect(error.errors).toEqual([{ field: 'name' }]);
+ });
+});
+
+describe('ServerError', () => {
+ it('creates error with message and status', () => {
+ const error = new ServerError('Server error', 503);
+ expect(error.message).toBe('Server error');
+ expect(error.code).toBe('SERVER_ERROR');
+ expect(error.statusCode).toBe(503);
+ expect(error.name).toBe('ServerError');
+ });
});
describe('NetworkError', () => {
- it('creates error with message', () => {
+ it('creates error with message and no status', () => {
const error = new NetworkError('Connection refused');
expect(error.message).toBe('Connection refused');
expect(error.code).toBe('NETWORK_ERROR');
@@ -125,73 +151,81 @@ describe('TimeoutError', () => {
});
describe('createErrorFromResponse', () => {
- it('returns AuthenticationError for 401', () => {
- const error = createErrorFromResponse(401, { message: 'Invalid key' });
+ it('returns AuthenticationError for 401 (reads body.message)', () => {
+ const error = createErrorFromResponse(401, { message: 'Invalid Request' });
expect(error).toBeInstanceOf(AuthenticationError);
- expect(error.message).toBe('Invalid key');
+ expect(error.message).toBe('Invalid Request');
});
it('returns QuotaExceededError for 402', () => {
const error = createErrorFromResponse(402, { message: 'Quota exceeded' });
expect(error).toBeInstanceOf(QuotaExceededError);
- expect(error.message).toBe('Quota exceeded');
+ expect(error.statusCode).toBe(402);
});
- it('returns PictifyError with TEMPLATE_NOT_FOUND for 404', () => {
- const error = createErrorFromResponse(404, { message: 'Not found' });
- expect(error).toBeInstanceOf(PictifyError);
+ it('returns TemplateNotFoundError for 404', () => {
+ const error = createErrorFromResponse(404, { message: 'Template not found' });
+ expect(error).toBeInstanceOf(TemplateNotFoundError);
expect(error.code).toBe('TEMPLATE_NOT_FOUND');
});
- it('returns RateLimitError for 429', () => {
- const error = createErrorFromResponse(429, { message: 'Too many requests' });
- expect(error).toBeInstanceOf(RateLimitError);
- expect(error.message).toBe('Too many requests');
+ it('returns RenderError for 422 with field errors', () => {
+ const error = createErrorFromResponse(422, {
+ message: 'Variable validation failed',
+ errors: [{ name: 'company' }],
+ }) as RenderError;
+ expect(error).toBeInstanceOf(RenderError);
+ expect(error.errors).toEqual([{ name: 'company' }]);
});
- it('returns RenderError for 500', () => {
- const error = createErrorFromResponse(500, { message: 'Server error' });
- expect(error).toBeInstanceOf(RenderError);
- expect(error.message).toBe('Server error');
+ it('returns RateLimitError for 429 without a quota code', () => {
+ const error = createErrorFromResponse(429, { message: 'Too many requests' });
+ expect(error).toBeInstanceOf(RateLimitError);
});
- it('returns RenderError for 502', () => {
- const error = createErrorFromResponse(502, { message: 'Bad gateway' });
- expect(error).toBeInstanceOf(RenderError);
+ it('returns QuotaExceededError for 429 with code quota_exceeded', () => {
+ const error = createErrorFromResponse(429, {
+ message: 'You have exhausted your plan limit',
+ code: 'quota_exceeded',
+ });
+ expect(error).toBeInstanceOf(QuotaExceededError);
+ expect(error.statusCode).toBe(429);
});
- it('returns RenderError for 503', () => {
- const error = createErrorFromResponse(503, { message: 'Unavailable' });
- expect(error).toBeInstanceOf(RenderError);
+ it('returns ServerError for 500/502/503/504', () => {
+ for (const status of [500, 502, 503, 504]) {
+ expect(createErrorFromResponse(status, { error: 'x' })).toBeInstanceOf(ServerError);
+ }
});
- it('returns RenderError for 504', () => {
- const error = createErrorFromResponse(504, { message: 'Gateway timeout' });
- expect(error).toBeInstanceOf(RenderError);
+ it('returns RenderError for an unexpected 4xx (e.g. 400, 418)', () => {
+ expect(createErrorFromResponse(400, { error: 'bad' })).toBeInstanceOf(RenderError);
+ expect(createErrorFromResponse(418, { message: 'teapot' })).toBeInstanceOf(RenderError);
});
- it('returns PictifyError with INVALID_VARIABLES for 400', () => {
- const error = createErrorFromResponse(400, { message: 'Invalid input' });
- expect(error).toBeInstanceOf(PictifyError);
- expect(error.code).toBe('INVALID_VARIABLES');
+ it('prefers body.error over body.message', () => {
+ const error = createErrorFromResponse(422, { error: 'image boom', message: 'ignored' });
+ expect(error.message).toBe('image boom');
});
- it('returns generic PictifyError for unknown status', () => {
- const error = createErrorFromResponse(418, { message: "I'm a teapot" });
- expect(error).toBeInstanceOf(PictifyError);
- expect(error.code).toBe('UNKNOWN_ERROR');
+ it('falls back to statusText when body has neither error nor message', () => {
+ const error = createErrorFromResponse(500, {}, 'Internal Server Error');
+ expect(error.message).toBe('Internal Server Error');
});
- it('uses default message when none provided', () => {
+ it('falls back to a generic message when body and statusText are empty', () => {
const error = createErrorFromResponse(500, {});
expect(error.message).toBe('An unexpected error occurred');
});
- it('passes details to error', () => {
- const error = createErrorFromResponse(400, {
- message: 'Bad request',
- details: { field: 'error' },
- });
- expect(error.details).toEqual({ field: 'error' });
+ it('handles a null body without throwing', () => {
+ const error = createErrorFromResponse(401, null);
+ expect(error).toBeInstanceOf(AuthenticationError);
+ });
+
+ it('returns a generic PictifyError for an unexpected non-4xx/5xx status', () => {
+ const error = createErrorFromResponse(399, { message: 'weird' });
+ expect(error).toBeInstanceOf(PictifyError);
+ expect(error.code).toBe('UNKNOWN_ERROR');
});
});
diff --git a/src/__tests__/helpers.ts b/src/__tests__/helpers.ts
index 51c22c2..53c6975 100644
--- a/src/__tests__/helpers.ts
+++ b/src/__tests__/helpers.ts
@@ -3,9 +3,14 @@ import { vi } from 'vitest';
export interface MockResponse {
status: number;
body: unknown;
+ statusText?: string;
headers?: Record;
}
+/**
+ * Build a `fetch` mock that returns the given responses in order. Once the list
+ * is exhausted, the last response is repeated (handy for retry tests).
+ */
export function createMockFetch(responses: MockResponse[]) {
let callIndex = 0;
return vi.fn().mockImplementation(async () => {
@@ -13,90 +18,130 @@ export function createMockFetch(responses: MockResponse[]) {
return {
ok: response.status >= 200 && response.status < 300,
status: response.status,
+ statusText: response.statusText || '',
json: async () => response.body,
- body: response.body,
headers: new Headers(response.headers || {}),
};
});
}
-export function createMockStreamFetch(status: number, chunks: string[]) {
- return vi.fn().mockImplementation(async () => {
- const stream = new ReadableStream({
- start(controller) {
- chunks.forEach((chunk) => {
- controller.enqueue(new TextEncoder().encode(chunk));
- });
- controller.close();
- },
- });
-
- return {
- ok: status >= 200 && status < 300,
- status,
- body: stream,
- headers: new Headers({}),
- };
- });
-}
+// --- Real-shape response fixtures (live-verified against api.pictify.io) ---
-export const mockRenderResult = {
- imageUrl: 'https://cdn.pictify.io/renders/abc123.png',
- renderId: 'render_abc123',
- width: 1200,
- height: 630,
- size: 45678,
- format: 'png',
- renderTime: 234,
+/** `POST /image` → `{ url, id, createdAt }` */
+export const mockImageResult = {
+ url: 'https://media.pictify.io/abc123-1780877013849.png',
+ id: 'abc123-1780877013849',
+ createdAt: '2026-06-08T00:03:33.951Z',
};
-export const mockTemplate = {
- id: 'tmpl_abc123',
- name: 'OG Image Template',
- description: 'Template for Open Graph images',
- width: 1200,
- height: 630,
- variables: [
- { name: 'title', type: 'string', required: true },
- { name: 'description', type: 'string', required: false },
+/** `POST /templates/:uid/render` → results[] envelope (single layout) */
+export const mockRenderResult = {
+ results: [
+ {
+ layout: 'default',
+ url: 'https://media.pictify.io/template-renders/render123.png',
+ width: 600,
+ height: 200,
+ format: 'png',
+ name: 'Default',
+ id: 'RENDER123',
+ createdAt: '2026-06-08T00:03:21.992Z',
+ },
],
- previewUrl: 'https://cdn.pictify.io/previews/tmpl_abc123.png',
- createdAt: '2024-01-15T10:30:00Z',
- updatedAt: '2024-01-15T10:30:00Z',
+ errors: [],
+ totalLayouts: 1,
+ totalRendered: 1,
+ totalErrors: 0,
+ templateUid: 'XL13XACH2V',
};
-export const mockBatchResult = {
+/** Multi-layout render: one success + one error entry */
+export const mockLayoutsRenderResult = {
results: [
{
- index: 0,
- success: true,
- variables: ['title'],
- results: [
- {
- layout: 'default',
- name: 'Default',
- url: 'https://cdn.pictify.io/renders/abc123.png',
- width: 1200,
- height: 630,
- format: 'png',
- id: 'img_abc123',
- },
- ],
- errors: [],
+ layout: 'default',
+ url: 'https://media.pictify.io/template-renders/default123.png',
+ width: 600,
+ height: 200,
+ format: 'png',
+ name: 'Default',
+ id: 'DEFAULT123',
+ createdAt: '2026-06-08T00:03:21.992Z',
},
],
- totalTime: 500,
- successCount: 1,
- failedCount: 0,
+ errors: [{ layout: 'bogus-layout', error: "Layout 'bogus-layout' not found" }],
+ totalLayouts: 2,
+ totalRendered: 1,
+ totalErrors: 1,
+ templateUid: 'XL13XACH2V',
+};
+
+/** `POST /gif` → `{ gif: {...}, _meta }` */
+export const mockGifResponse = {
+ gif: {
+ url: 'https://media.pictify.io/gif123-1780877036774.gif',
+ uid: 'gif123-1780877036774',
+ width: 400,
+ height: 200,
+ animationLength: 2000,
+ },
+ _meta: { processingTime: 5273 },
+};
+
+/** `POST /templates/:uid/batch-render` → 202 `{ batchId, status, totalItems, message }` */
+export const mockBatchSubmitResult = {
+ batchId: 'batch_LA7U9OUEYT1I234I',
+ status: 'pending',
+ totalItems: 2,
+ message: 'Batch job created successfully',
+};
+
+/** `GET /templates/batch/:batchId/results` → status + per-item records (NO urls) */
+export const mockBatchResults = {
+ batchId: 'batch_LA7U9OUEYT1I234I',
+ status: 'completed',
+ progress: 100,
+ totalItems: 2,
+ completedItems: 2,
+ failedItems: 0,
+ results: [
+ { index: 0, success: true, variables: ['name', 'company'] },
+ { index: 1, success: true, variables: ['name', 'company'] },
+ ],
+ errors: [],
+ createdAt: '2026-06-08T00:03:59.172Z',
+ startedAt: '2026-06-08T00:03:59.854Z',
+ completedAt: '2026-06-08T00:04:02.204Z',
+};
+
+/** `GET /templates/:uid` / `POST /templates` → `{ template }` envelope */
+export const mockTemplateResponse = {
+ template: {
+ uid: 'XL13XACH2V',
+ name: 'SDK Repoint Test Template',
+ html: 'Hello {{name}} from {{company}}
',
+ width: 600,
+ height: 200,
+ engine: 'html',
+ outputFormat: 'image',
+ variables: ['name', 'company'],
+ variableDefinitions: [
+ { name: 'name', type: 'text', defaultValue: '', validation: { required: false } },
+ { name: 'company', type: 'text', defaultValue: '', validation: { required: false } },
+ ],
+ createdAt: '2026-06-07T23:56:43.669Z',
+ },
};
-export const mockGifResult = {
- gifUrl: 'https://cdn.pictify.io/renders/abc123.gif',
- renderId: 'render_abc123',
- width: 800,
- height: 600,
- size: 123456,
- frameCount: 3,
- duration: 300,
- renderTime: 1234,
+/** `GET /templates` → `{ templates, pagination }` */
+export const mockListTemplatesResult = {
+ templates: [mockTemplateResponse.template],
+ pagination: {
+ page: 1,
+ limit: 12,
+ total: 1,
+ totalPages: 1,
+ hasNext: false,
+ hasPrev: false,
+ },
};
diff --git a/src/__tests__/integration/client.integration.test.ts b/src/__tests__/integration/client.integration.test.ts
new file mode 100644
index 0000000..24bf8c6
--- /dev/null
+++ b/src/__tests__/integration/client.integration.test.ts
@@ -0,0 +1,266 @@
+/**
+ * Integration tests — exercise every public method against the REAL Pictify API.
+ *
+ * SKIPPED unless PICTIFY_API_KEY is set, so they're safe in the default test run
+ * (they collect, then skip). These make real network calls and create real
+ * renders, which consumes quota — keep the set tight.
+ *
+ * Run live:
+ *
+ * PICTIFY_API_KEY=sk_xxx PICTIFY_TEMPLATE_ID=XL13XACH2V \
+ * npx vitest run src/__tests__/integration
+ *
+ * Optional overrides:
+ * PICTIFY_BASE_URL — non-prod API base (default: https://api.pictify.io)
+ * PICTIFY_TEMPLATE_ID — a template UID in your account (variables: name, company).
+ * Also accepts the legacy PICTIFY_TEST_TEMPLATE_ID.
+ * When unset, template-dependent cases skip.
+ */
+import { describe, it, expect, beforeAll } from 'vitest';
+import { Pictify } from '../../client';
+import { PictifyError, TemplateNotFoundError } from '../../errors';
+import type {
+ ImageResult,
+ RenderResult,
+ GifRenderResult,
+ BatchRenderResult,
+ BatchResults,
+ Template,
+ ListTemplatesResult,
+} from '../../types';
+
+const API_KEY = process.env.PICTIFY_API_KEY;
+const BASE_URL = process.env.PICTIFY_BASE_URL || 'https://api.pictify.io';
+const TEMPLATE_ID =
+ process.env.PICTIFY_TEMPLATE_ID || process.env.PICTIFY_TEST_TEMPLATE_ID;
+
+// Real renders (especially GIFs) can be slow; give each case headroom.
+const NETWORK_TIMEOUT = 90_000;
+
+const describeIfKey = describe.skipIf(!API_KEY);
+const itIfTemplate = (API_KEY && TEMPLATE_ID ? it : it.skip) as typeof it;
+
+const URL_RE = /^https?:\/\//;
+
+describeIfKey('Pictify integration (live API)', () => {
+ let client: Pictify;
+
+ beforeAll(() => {
+ // Non-null assertion is safe: the suite is skipped when API_KEY is absent.
+ client = new Pictify({ apiKey: API_KEY!, baseUrl: BASE_URL });
+ });
+
+ describe('renderHtml()', () => {
+ it(
+ 'renders a PNG from raw HTML and returns a real URL',
+ async () => {
+ const result: ImageResult = await client.renderHtml({
+ html: 'Integration test
',
+ width: 600,
+ height: 300,
+ });
+ expect(result.url).toMatch(URL_RE);
+ expect(result.id).toBeTruthy();
+ expect(result.createdAt).toBeTruthy();
+ // eslint-disable-next-line no-console
+ console.log('[integration] renderHtml ->', result.url);
+ },
+ NETWORK_TIMEOUT
+ );
+ });
+
+ describe('renderUrl()', () => {
+ it(
+ 'screenshots a live URL and returns a real URL',
+ async () => {
+ const result: ImageResult = await client.renderUrl({
+ url: 'https://example.com',
+ width: 800,
+ height: 600,
+ });
+ expect(result.url).toMatch(URL_RE);
+ // eslint-disable-next-line no-console
+ console.log('[integration] renderUrl ->', result.url);
+ },
+ NETWORK_TIMEOUT
+ );
+ });
+
+ describe('render() — template', () => {
+ itIfTemplate(
+ 'renders a single image from the template with {name, company}',
+ async () => {
+ const result: RenderResult = await client.render({
+ templateId: TEMPLATE_ID!,
+ variables: { name: 'Ada', company: 'Pictify' },
+ format: 'png',
+ });
+ expect(Array.isArray(result.results)).toBe(true);
+ expect(result.results.length).toBeGreaterThan(0);
+ expect(result.url).toMatch(URL_RE);
+ expect(result.templateUid).toBe(TEMPLATE_ID);
+ // eslint-disable-next-line no-console
+ console.log('[integration] render ->', result.url);
+ },
+ NETWORK_TIMEOUT
+ );
+ });
+
+ describe('renderLayouts()', () => {
+ itIfTemplate(
+ 'renders `default` and routes a bogus layout into errors[]',
+ async () => {
+ const result: RenderResult = await client.renderLayouts({
+ templateId: TEMPLATE_ID!,
+ variables: { name: 'Ada', company: 'Pictify' },
+ layouts: ['default', 'definitely-not-a-real-layout'],
+ });
+ expect(Array.isArray(result.results)).toBe(true);
+ // 'default' should render successfully...
+ const defaultItem = result.results.find((r) => r.layout === 'default');
+ expect(defaultItem?.url).toMatch(URL_RE);
+ // ...and the bogus layout should land in errors[].
+ expect(result.errors.length).toBeGreaterThan(0);
+ expect(result.errors.some((e) => e.layout === 'definitely-not-a-real-layout')).toBe(
+ true
+ );
+ // eslint-disable-next-line no-console
+ console.log(
+ '[integration] renderLayouts -> default:',
+ defaultItem?.url,
+ '| errors:',
+ result.errors.map((e) => e.layout)
+ );
+ },
+ NETWORK_TIMEOUT
+ );
+ });
+
+ describe('renderGif()', () => {
+ it(
+ 'renders an animated GIF from raw HTML',
+ async () => {
+ const result: GifRenderResult = await client.renderGif({
+ html:
+ '' +
+ 'gif test
',
+ width: 400,
+ height: 200,
+ quality: 'low',
+ });
+ expect(result.url).toMatch(URL_RE);
+ expect(result.uid).toBeTruthy();
+ expect(result.animationLength).toBeGreaterThan(0);
+ // eslint-disable-next-line no-console
+ console.log('[integration] renderGif ->', result.url);
+ },
+ NETWORK_TIMEOUT
+ );
+ });
+
+ describe('listTemplates()', () => {
+ it(
+ 'returns { templates, pagination }',
+ async () => {
+ const result: ListTemplatesResult = await client.listTemplates({ limit: 5 });
+ expect(Array.isArray(result.templates)).toBe(true);
+ expect(result.pagination).toBeTruthy();
+ expect(typeof result.pagination.total).toBe('number');
+ // eslint-disable-next-line no-console
+ console.log(
+ '[integration] listTemplates -> total:',
+ result.pagination.total,
+ '| page size:',
+ result.templates.length
+ );
+ },
+ NETWORK_TIMEOUT
+ );
+ });
+
+ describe('getTemplate()', () => {
+ itIfTemplate(
+ 'returns the unwrapped template by uid',
+ async () => {
+ const template: Template = await client.getTemplate(TEMPLATE_ID!);
+ expect(template.uid).toBe(TEMPLATE_ID);
+ expect(template).not.toHaveProperty('template'); // already unwrapped
+ // eslint-disable-next-line no-console
+ console.log('[integration] getTemplate ->', template.uid, template.name);
+ },
+ NETWORK_TIMEOUT
+ );
+ });
+
+ describe('createTemplate()', () => {
+ it(
+ 'creates a throwaway html template and returns a uid',
+ async () => {
+ const template: Template = await client.createTemplate({
+ html: 'Hi {{firstName}}
',
+ name: `SDK integration throwaway ${Date.now()}`,
+ width: 400,
+ height: 150,
+ });
+ expect(template.uid).toBeTruthy();
+ // Variables auto-extracted from {{firstName}}.
+ const names = (template.variableDefinitions || []).map((v) => v.name);
+ expect(names).toContain('firstName');
+ // eslint-disable-next-line no-console
+ console.log('[integration] createTemplate -> uid:', template.uid);
+ },
+ NETWORK_TIMEOUT
+ );
+ });
+
+ describe('renderBatch() + getBatchResults()', () => {
+ itIfTemplate(
+ 'submits a batch (returns batchId) and polls results once',
+ async () => {
+ const submit: BatchRenderResult = await client.renderBatch({
+ templateId: TEMPLATE_ID!,
+ variableSets: [
+ { name: 'A', company: 'X' },
+ { name: 'B', company: 'Y' },
+ ],
+ format: 'png',
+ });
+ expect(submit.batchId).toBeTruthy();
+ expect(submit.totalItems).toBe(2);
+ // eslint-disable-next-line no-console
+ console.log('[integration] renderBatch -> batchId:', submit.batchId, submit.status);
+
+ // Poll once. The job may still be pending — assert tolerantly.
+ const results: BatchResults = await client.getBatchResults(submit.batchId);
+ expect(results.batchId).toBe(submit.batchId);
+ expect(results.totalItems).toBe(2);
+ expect(
+ ['pending', 'processing', 'completed', 'partial', 'failed', 'cancelled']
+ ).toContain(results.status);
+ // eslint-disable-next-line no-console
+ console.log(
+ '[integration] getBatchResults -> status:',
+ results.status,
+ '| completed:',
+ results.completedItems
+ );
+ },
+ NETWORK_TIMEOUT
+ );
+ });
+
+ describe('error path (live)', () => {
+ it(
+ 'throws TemplateNotFoundError for a non-existent template',
+ async () => {
+ const err = await client
+ .getTemplate('definitely-not-a-real-template-id-xyz')
+ .catch((e) => e);
+ expect(err).toBeInstanceOf(PictifyError);
+ expect(err).toBeInstanceOf(TemplateNotFoundError);
+ },
+ NETWORK_TIMEOUT
+ );
+ });
+});
diff --git a/src/client.ts b/src/client.ts
index 532d67c..f2142cb 100644
--- a/src/client.ts
+++ b/src/client.ts
@@ -1,13 +1,20 @@
import {
PictifyConfig,
+ RenderHtmlOptions,
+ RenderUrlOptions,
+ ImageResult,
RenderOptions,
+ RenderLayoutsOptions,
RenderResult,
+ GifRenderOptions,
+ GifRenderResult,
BatchRenderOptions,
BatchRenderResult,
+ BatchResults,
Template,
- HTMLRenderOptions,
- GIFRenderOptions,
- GIFRenderResult,
+ ListTemplatesOptions,
+ ListTemplatesResult,
+ CreateTemplateOptions,
} from './types';
import {
PictifyError,
@@ -15,29 +22,38 @@ import {
NetworkError,
TimeoutError,
createErrorFromResponse,
+ ApiErrorBody,
} from './errors';
const DEFAULT_BASE_URL = 'https://api.pictify.io';
const DEFAULT_TIMEOUT = 30000;
const DEFAULT_RETRIES = 3;
+const SDK_VERSION = '1.0.0';
/**
- * Pictify client for generating images from HTML templates
+ * Pictify client — generate images, PDFs, and GIFs from raw HTML, live URLs,
+ * and reusable templates via the Pictify API.
*
* @example
* ```typescript
* import { Pictify } from '@pictify/sdk';
*
- * const pictify = new Pictify({
- * apiKey: process.env.PICTIFY_API_KEY
+ * const pictify = new Pictify({ apiKey: process.env.PICTIFY_API_KEY! });
+ *
+ * // Render raw HTML to a PNG
+ * const image = await pictify.renderHtml({
+ * html: 'Hello World
',
+ * width: 1200,
+ * height: 630,
* });
+ * console.log(image.url);
*
+ * // Render a template
* const result = await pictify.render({
- * templateId: 'your-template-id',
- * variables: { title: 'Hello World' }
+ * templateId: 'your-template-uid',
+ * variables: { name: 'Ada', company: 'Pictify' },
* });
- *
- * console.log(result.imageUrl);
+ * console.log(result.url); // results[0].url
* ```
*/
export class Pictify {
@@ -57,302 +73,314 @@ export class Pictify {
this.retries = config.retries ?? DEFAULT_RETRIES;
}
+ // ---------------------------------------------------------------------------
+ // Image rendering (POST /image)
+ // ---------------------------------------------------------------------------
+
+ /**
+ * Render an image (or PDF) directly from HTML.
+ *
+ * `POST /image` — returns `{ url, id, createdAt }`.
+ *
+ * @example
+ * ```typescript
+ * const image = await pictify.renderHtml({
+ * html: 'Hello
',
+ * css: 'div { color: blue; }',
+ * width: 1200,
+ * height: 630,
+ * format: 'png',
+ * });
+ * console.log(image.url);
+ * ```
+ */
+ async renderHtml(options: RenderHtmlOptions): Promise {
+ const html = options.css
+ ? `${options.html}`
+ : options.html;
+
+ return this.request('/image', {
+ method: 'POST',
+ body: {
+ html,
+ width: options.width,
+ height: options.height,
+ selector: options.selector,
+ fileExtension: options.format || 'png',
+ },
+ });
+ }
+
+ /**
+ * Screenshot a live URL.
+ *
+ * `POST /image` with `url` — returns `{ url, id, createdAt }`.
+ *
+ * @example
+ * ```typescript
+ * const image = await pictify.renderUrl({
+ * url: 'https://example.com',
+ * width: 1280,
+ * height: 720,
+ * });
+ * console.log(image.url);
+ * ```
+ */
+ async renderUrl(options: RenderUrlOptions): Promise {
+ return this.request('/image', {
+ method: 'POST',
+ body: {
+ url: options.url,
+ width: options.width,
+ height: options.height,
+ selector: options.selector,
+ fileExtension: options.format || 'png',
+ },
+ });
+ }
+
+ // ---------------------------------------------------------------------------
+ // Template rendering (POST /templates/:uid/render)
+ // ---------------------------------------------------------------------------
+
/**
- * Render an image from a template
+ * Render a single image (or PDF) from a template.
*
- * @param options - Render options including template ID and variables
- * @returns Render result with image URL or buffer
+ * `POST /templates/:uid/render` — returns the `results[]` envelope with a
+ * convenience `url` getter (`results[0]?.url`).
*
* @example
* ```typescript
* const result = await pictify.render({
* templateId: 'og-image-template',
- * variables: {
- * title: 'My Blog Post',
- * description: 'A great article about something'
- * },
+ * variables: { title: 'My Post', author: 'Ada' },
* format: 'png',
- * width: 1200,
- * height: 630
* });
+ * console.log(result.url);
* ```
*/
async render(options: RenderOptions): Promise {
const body: Record = {
- templateId: options.templateId,
variables: options.variables || {},
format: options.format || 'png',
+ quality: options.quality,
width: options.width,
height: options.height,
- deviceScaleFactor: options.deviceScaleFactor,
- transparent: options.transparent,
- quality: options.quality,
- download: options.download,
};
-
- if (options.layout) {
- body.layout = options.layout;
- }
- if (options.layouts) {
- body.layouts = options.layouts;
- }
-
- const response = await this.request('/render', {
- method: 'POST',
- body,
- });
-
- return this.normalizeRenderResult(response);
+ if (options.layout) body.layout = options.layout;
+ if (options.layouts) body.layouts = options.layouts;
+
+ const response = await this.request>(
+ `/templates/${encodeURIComponent(options.templateId)}/render`,
+ { method: 'POST', body }
+ );
+ return this.withUrlGetter(response);
}
/**
- * Render multiple layout variants of a template in a single call
+ * Render multiple layout variants of a template in a single call.
*
- * @param options - Render options with required `layouts` array
- * @returns Render result containing one entry per layout in `results`
+ * `POST /templates/:uid/render` with `layouts` — returns one `results[]` item
+ * per successful layout; missing/invalid layouts appear in `errors[]`.
*
* @example
* ```typescript
* const result = await pictify.renderLayouts({
* templateId: 'og-image-template',
- * variables: { title: 'Hello World' },
- * layouts: ['landscape', 'square', 'story']
+ * variables: { title: 'Hello' },
+ * layouts: ['default', 'square', 'story'],
* });
- *
- * for (const item of result.results) {
- * console.log(`${item.layout}: ${item.url}`);
- * }
+ * for (const item of result.results) console.log(item.layout, item.url);
* ```
*/
- async renderLayouts(
- options: RenderOptions & { layouts: string[] }
- ): Promise {
+ async renderLayouts(options: RenderLayoutsOptions): Promise {
return this.render(options);
}
+ // ---------------------------------------------------------------------------
+ // GIF rendering (POST /gif)
+ // ---------------------------------------------------------------------------
+
/**
- * Render an image and return it as a readable stream
+ * Render an animated GIF from raw HTML, a live URL, or a template.
+ *
+ * `POST /gif` — the `{ gif: {...} }` envelope is flattened to
+ * `{ url, uid, width, height, animationLength }`. Provide exactly one source:
+ * `html`, `url`, or `templateId`.
*
- * @param options - Render options
- * @returns Readable stream of image data
+ * @example
+ * ```typescript
+ * const gif = await pictify.renderGif({
+ * html: 'Hi
',
+ * width: 400,
+ * height: 200,
+ * quality: 'medium',
+ * });
+ * console.log(gif.url);
+ * ```
*/
- async renderStream(options: Omit): Promise {
- const response = await this.rawRequest('/render/stream', {
+ async renderGif(options: GifRenderOptions): Promise {
+ const body: Record = {
+ width: options.width,
+ height: options.height,
+ quality: options.quality || 'medium',
+ };
+ if (options.html) body.html = options.html;
+ if (options.url) body.url = options.url;
+ if (options.templateId) body.template = options.templateId;
+ if (options.variables) body.variables = options.variables;
+
+ const response = await this.request<{ gif: GifRenderResult }>('/gif', {
method: 'POST',
- body: {
- templateId: options.templateId,
- variables: options.variables || {},
- format: options.format || 'png',
- width: options.width,
- height: options.height,
- deviceScaleFactor: options.deviceScaleFactor,
- transparent: options.transparent,
- quality: options.quality,
- },
+ body,
});
-
- if (!response.body) {
- throw new NetworkError('No response body received');
- }
-
- return response.body as unknown as NodeJS.ReadableStream;
+ return response.gif;
}
+ // ---------------------------------------------------------------------------
+ // Batch rendering (async)
+ // ---------------------------------------------------------------------------
+
/**
- * Render multiple images in a single batch request
+ * Submit an async batch render of a template across many variable sets.
*
- * @param options - Batch render options
- * @returns Batch render results
+ * `POST /templates/:uid/batch-render` — returns `{ batchId, status, totalItems }`
+ * immediately (HTTP 202). Poll {@link Pictify.getBatchResults} to track progress.
+ *
+ * Rendered URLs are NOT returned by the poll endpoint — they are delivered via
+ * the `render.completed` webhook.
*
* @example
* ```typescript
- * const results = await pictify.renderBatch({
+ * const job = await pictify.renderBatch({
* templateId: 'product-card',
- * items: products.map(p => ({
- * variables: { name: p.name, price: p.price }
- * }))
+ * variableSets: products.map((p) => ({ name: p.name, price: p.price })),
* });
+ * const status = await pictify.getBatchResults(job.batchId);
* ```
*/
async renderBatch(options: BatchRenderOptions): Promise {
- if (options.items.length > 500) {
- throw new PictifyError(
- 'Batch size cannot exceed 500 items',
- 'INVALID_VARIABLES',
- 400
- );
- }
-
const body: Record = {
- templateId: options.templateId,
- items: options.items,
+ variableSets: options.variableSets,
format: options.format || 'png',
- width: options.width,
- height: options.height,
+ quality: options.quality,
+ concurrency: options.concurrency,
};
+ if (options.layout) body.layout = options.layout;
+ if (options.layouts) body.layouts = options.layouts;
- if (options.layout) {
- body.layout = options.layout;
- }
- if (options.layouts) {
- body.layouts = options.layouts;
- }
+ return this.request(
+ `/templates/${encodeURIComponent(options.templateId)}/batch-render`,
+ { method: 'POST', body }
+ );
+ }
- const response = await this.request('/render/batch', {
- method: 'POST',
- body,
- });
+ /**
+ * Get the status, progress, and per-item results of a batch job.
+ *
+ * `GET /templates/batch/:batchId/results`. Results carry `{ index, success,
+ * variables }` (and `error` on failures) but NOT rendered URLs.
+ */
+ async getBatchResults(batchId: string): Promise {
+ return this.request(
+ `/templates/batch/${encodeURIComponent(batchId)}/results`,
+ { method: 'GET' }
+ );
+ }
+
+ // ---------------------------------------------------------------------------
+ // Template CRUD
+ // ---------------------------------------------------------------------------
- return response;
+ /**
+ * Get a single template by its UID.
+ *
+ * `GET /templates/:uid` — unwraps the `{ template }` envelope.
+ */
+ async getTemplate(templateId: string): Promise {
+ const response = await this.request<{ template: Template }>(
+ `/templates/${encodeURIComponent(templateId)}`,
+ { method: 'GET' }
+ );
+ return response.template;
}
/**
- * Render an image directly from HTML without using a template
+ * List templates in your account.
*
- * @param options - HTML render options
- * @returns Render result with image URL or buffer
+ * `GET /templates` — returns `{ templates, pagination }`.
*
* @example
* ```typescript
- * const result = await pictify.renderHtml({
- * html: 'Hello World
',
- * css: 'div { color: blue; }',
- * width: 1200,
- * height: 630
- * });
+ * const { templates, pagination } = await pictify.listTemplates({ limit: 20 });
* ```
*/
- async renderHtml(options: HTMLRenderOptions): Promise {
- const response = await this.request('/render/html', {
- method: 'POST',
- body: {
- html: options.html,
- css: options.css,
- format: options.format || 'png',
- width: options.width || 1200,
- height: options.height || 630,
- deviceScaleFactor: options.deviceScaleFactor,
- transparent: options.transparent,
- quality: options.quality,
- download: options.download,
- },
+ async listTemplates(options: ListTemplatesOptions = {}): Promise {
+ const query = new URLSearchParams();
+ if (options.page !== undefined) query.set('page', String(options.page));
+ if (options.limit !== undefined) query.set('limit', String(options.limit));
+ if (options.sort !== undefined) query.set('sort', options.sort);
+ const qs = query.toString();
+
+ return this.request(`/templates${qs ? `?${qs}` : ''}`, {
+ method: 'GET',
});
-
- return response;
}
/**
- * Render an animated GIF from a template or raw HTML
+ * Create a template from HTML. Variables are auto-discovered from
+ * `{{variableName}}` tokens in the HTML body.
*
- * @param options - GIF render options with frames
- * @returns GIF render result with URL or buffer
+ * `POST /templates` — unwraps the `{ template }` envelope.
*
* @example
* ```typescript
- * const result = await pictify.renderGif({
- * templateId: 'animation-template',
- * frames: [
- * { variables: { text: 'Frame 1' } },
- * { variables: { text: 'Frame 2' } },
- * { variables: { text: 'Frame 3' } }
- * ],
- * delay: 500,
- * loop: 0
+ * const template = await pictify.createTemplate({
+ * html: 'Hi {{firstName}}
',
+ * name: 'Welcome Card',
+ * width: 600,
+ * height: 200,
* });
+ * console.log(template.uid);
* ```
*/
- async renderGif(options: GIFRenderOptions): Promise {
- if (options.frames.length === 0) {
- throw new PictifyError(
- 'At least one frame is required',
- 'INVALID_FRAMES',
- 400
- );
- }
-
- if (options.frames.length > 100) {
- throw new PictifyError(
- 'GIF cannot exceed 100 frames',
- 'INVALID_FRAMES',
- 400
- );
- }
-
- const response = await this.request('/render/gif', {
+ async createTemplate(options: CreateTemplateOptions): Promise {
+ const response = await this.request<{ template: Template }>('/templates', {
method: 'POST',
body: {
- templateId: options.templateId,
html: options.html,
- css: options.css,
- frames: options.frames,
+ name: options.name,
width: options.width,
height: options.height,
- delay: options.delay || 100,
- loop: options.loop ?? 0,
- quality: options.quality,
+ variableDefinitions: options.variableDefinitions,
+ outputFormat: options.outputFormat,
},
});
-
- return response;
+ return response.template;
}
- /**
- * Get details about a template
- *
- * @param templateId - The template ID
- * @returns Template details including variables
- */
- async getTemplate(templateId: string): Promise {
- return this.request(`/templates/${templateId}`, {
- method: 'GET',
- });
- }
+ // ---------------------------------------------------------------------------
+ // Internals
+ // ---------------------------------------------------------------------------
/**
- * List all templates in your account
- *
- * @returns Array of templates
+ * Attach a non-enumerable `url` convenience getter (`results[0]?.url`) to a
+ * template render response.
*/
- async listTemplates(): Promise {
- const response = await this.request<{ templates: Template[] }>('/templates', {
- method: 'GET',
+ private withUrlGetter(response: Omit): RenderResult {
+ Object.defineProperty(response, 'url', {
+ get(this: Omit) {
+ return this.results?.[0]?.url;
+ },
+ enumerable: false,
+ configurable: true,
});
- return response.templates;
- }
-
- /**
- * Normalize the API response to provide backward-compatible fields.
- * Sets top-level `url`, `imageUrl`, `width`, `height`, `format`, and `renderId`
- * from the first item in `results[]` so that existing code continues to work.
- */
- private normalizeRenderResult(response: RenderResult): RenderResult {
- // If the response already has `results`, add backward-compat getters
- if (response.results && response.results.length > 0) {
- const first = response.results[0];
- if (!response.url) {
- response.url = first.url;
- }
- if (!response.imageUrl) {
- response.imageUrl = first.url;
- }
- if (!response.renderId) {
- response.renderId = first.id;
- }
- if (!response.width) {
- response.width = first.width;
- }
- if (!response.height) {
- response.height = first.height;
- }
- if (!response.format) {
- response.format = first.format;
- }
- }
- return response;
+ return response as RenderResult;
}
/**
- * Make an authenticated request to the API
+ * Make an authenticated JSON request, parsing the body and mapping HTTP
+ * errors to typed PictifyErrors.
*/
private async request(
endpoint: string,
@@ -362,17 +390,29 @@ export class Pictify {
}
): Promise {
const response = await this.rawRequest(endpoint, options);
- const data = await response.json();
+
+ let data: unknown = null;
+ try {
+ data = await response.json();
+ } catch {
+ // Non-JSON or empty body — leave data null.
+ }
if (!response.ok) {
- throw createErrorFromResponse(response.status, data);
+ throw createErrorFromResponse(
+ response.status,
+ data as ApiErrorBody,
+ response.statusText
+ );
}
return data as T;
}
/**
- * Make a raw request to the API
+ * Make a raw fetch request with auth headers, timeout, and retry on 5xx /
+ * network failures. JSON bodies have `undefined` fields stripped so the API
+ * sees only the fields the caller actually set.
*/
private async rawRequest(
endpoint: string,
@@ -382,13 +422,16 @@ export class Pictify {
}
): Promise {
const url = `${this.baseUrl}${endpoint}`;
-
const headers: Record = {
Authorization: `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
- 'User-Agent': '@pictify/sdk/1.0.0',
+ 'User-Agent': `@pictify/sdk/${SDK_VERSION}`,
};
+ const serializedBody = options.body
+ ? JSON.stringify(this.stripUndefined(options.body))
+ : undefined;
+
let lastError: Error | null = null;
for (let attempt = 0; attempt <= this.retries; attempt++) {
@@ -396,37 +439,39 @@ export class Pictify {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
- const response = await fetch(url, {
- method: options.method,
- headers,
- body: options.body ? JSON.stringify(options.body) : undefined,
- signal: controller.signal,
- });
-
- clearTimeout(timeoutId);
+ let response: Response;
+ try {
+ response = await fetch(url, {
+ method: options.method,
+ headers,
+ body: serializedBody,
+ signal: controller.signal,
+ });
+ } finally {
+ clearTimeout(timeoutId);
+ }
- // Don't retry on client errors (4xx)
+ // Don't retry on client errors (4xx) — return for typed mapping.
if (response.status >= 400 && response.status < 500) {
return response;
}
- // Retry on server errors (5xx)
- if (response.status >= 500) {
+ // Retry on server errors (5xx).
+ if (response.status >= 500 && attempt < this.retries) {
lastError = new Error(`Server error: ${response.status}`);
- if (attempt < this.retries) {
- await this.sleep(Math.pow(2, attempt) * 1000);
- continue;
- }
+ await this.sleep(Math.pow(2, attempt) * 1000);
+ continue;
}
return response;
} catch (error) {
if (error instanceof Error) {
- if (error.name === 'AbortError') {
- lastError = new TimeoutError();
- } else {
- lastError = new NetworkError(error.message);
- }
+ lastError =
+ error.name === 'AbortError'
+ ? new TimeoutError()
+ : new NetworkError(error.message);
+ } else {
+ lastError = new NetworkError('Request failed');
}
if (attempt < this.retries) {
@@ -439,7 +484,18 @@ export class Pictify {
throw lastError || new NetworkError('Request failed after retries');
}
+ private stripUndefined(obj: Record): Record {
+ const out: Record = {};
+ for (const [key, value] of Object.entries(obj)) {
+ if (value !== undefined) out[key] = value;
+ }
+ return out;
+ }
+
private sleep(ms: number): Promise {
return new Promise((resolve) => setTimeout(resolve, ms));
}
}
+
+// Re-export PictifyError so consumers can `import { Pictify, PictifyError }`.
+export { PictifyError };
diff --git a/src/errors.ts b/src/errors.ts
index 85de3f0..ee20dfb 100644
--- a/src/errors.ts
+++ b/src/errors.ts
@@ -1,5 +1,25 @@
import { PictifyErrorCode } from './types';
+/**
+ * Shape of an error response body from the Pictify API.
+ *
+ * There is no unified error envelope: image/GIF endpoints return `{ error, code }`,
+ * while template/CRUD endpoints return `{ message }` or `{ message, errors }`.
+ * The SDK reads both keys.
+ */
+export interface ApiErrorBody {
+ /** Error message (image/GIF endpoints) */
+ error?: string;
+ /** Error message (template/CRUD/auth endpoints) */
+ message?: string;
+ /** Backend error code (e.g. "quota_exceeded") */
+ code?: string;
+ /** Field-level validation errors (422 responses) */
+ errors?: unknown;
+ /** Additional context */
+ [key: string]: unknown;
+}
+
/**
* Base error class for Pictify SDK errors
*/
@@ -31,7 +51,7 @@ export class PictifyError extends Error {
}
/**
- * Error thrown when authentication fails
+ * Error thrown when authentication fails (HTTP 401)
*/
export class AuthenticationError extends PictifyError {
constructor(message: string = 'Invalid or missing API key') {
@@ -41,17 +61,17 @@ export class AuthenticationError extends PictifyError {
}
/**
- * Error thrown when a template is not found
+ * Error thrown when a template is not found (HTTP 404)
*/
export class TemplateNotFoundError extends PictifyError {
- constructor(templateId: string) {
- super(`Template not found: ${templateId}`, 'TEMPLATE_NOT_FOUND', 404);
+ constructor(message: string = 'Template not found') {
+ super(message, 'TEMPLATE_NOT_FOUND', 404);
this.name = 'TemplateNotFoundError';
}
}
/**
- * Error thrown when rate limit is exceeded
+ * Error thrown when the rate limit is exceeded (HTTP 429 without a quota code)
*/
export class RateLimitError extends PictifyError {
/** When the rate limit resets */
@@ -59,7 +79,7 @@ export class RateLimitError extends PictifyError {
/** Number of requests remaining */
public readonly remaining?: number;
- constructor(message: string, resetAt?: Date, remaining?: number) {
+ constructor(message: string = 'Rate limit exceeded', resetAt?: Date, remaining?: number) {
super(message, 'RATE_LIMIT_EXCEEDED', 429);
this.name = 'RateLimitError';
this.resetAt = resetAt;
@@ -68,27 +88,42 @@ export class RateLimitError extends PictifyError {
}
/**
- * Error thrown when quota is exceeded
+ * Error thrown when the render quota is exceeded (HTTP 402, or 429 with a quota code)
*/
export class QuotaExceededError extends PictifyError {
- constructor(message: string = 'Monthly render quota exceeded') {
- super(message, 'QUOTA_EXCEEDED', 402);
+ constructor(message: string = 'Render quota exceeded', statusCode: number = 402) {
+ super(message, 'QUOTA_EXCEEDED', statusCode);
this.name = 'QuotaExceededError';
}
}
/**
- * Error thrown when render fails
+ * Error thrown when a render fails or input validation fails (HTTP 422,
+ * and non-quota 4xx). Carries field-level `errors` from the API when present.
*/
export class RenderError extends PictifyError {
- constructor(message: string, details?: Record) {
- super(message, 'RENDER_FAILED', 500, details);
+ /** Field-level validation errors returned by the API (422 responses) */
+ public readonly errors?: unknown;
+
+ constructor(message: string, details?: Record, statusCode: number = 422) {
+ super(message, 'RENDER_FAILED', statusCode, details);
this.name = 'RenderError';
+ this.errors = details?.errors;
+ }
+}
+
+/**
+ * Error thrown when the server fails (HTTP 5xx)
+ */
+export class ServerError extends PictifyError {
+ constructor(message: string, statusCode: number = 500, details?: Record) {
+ super(message, 'SERVER_ERROR', statusCode, details);
+ this.name = 'ServerError';
}
}
/**
- * Error thrown when network request fails
+ * Error thrown when a network request fails
*/
export class NetworkError extends PictifyError {
constructor(message: string) {
@@ -98,7 +133,7 @@ export class NetworkError extends PictifyError {
}
/**
- * Error thrown when request times out
+ * Error thrown when a request times out
*/
export class TimeoutError extends PictifyError {
constructor(message: string = 'Request timed out') {
@@ -108,31 +143,50 @@ export class TimeoutError extends PictifyError {
}
/**
- * Maps HTTP status codes to error classes
+ * Map an HTTP error response to a typed PictifyError.
+ *
+ * Message resolution: `body.error ?? body.message ?? statusText`.
+ *
+ * Status mapping:
+ * - 401 → AuthenticationError
+ * - 402 → QuotaExceededError
+ * - 404 → TemplateNotFoundError
+ * - 422 → RenderError (validation; includes `body.errors`)
+ * - 429 → QuotaExceededError when `code === 'quota_exceeded'`, else RateLimitError
+ * - other 4xx → RenderError
+ * - 5xx → ServerError
*/
export function createErrorFromResponse(
statusCode: number,
- body: { message?: string; code?: string; details?: Record }
+ body: ApiErrorBody | undefined | null,
+ statusText: string = ''
): PictifyError {
- const message = body.message || 'An unexpected error occurred';
+ const safeBody: ApiErrorBody = body || {};
+ const message =
+ safeBody.error ?? safeBody.message ?? (statusText || 'An unexpected error occurred');
+ const details: Record = { ...safeBody };
switch (statusCode) {
case 401:
return new AuthenticationError(message);
+ case 402:
+ return new QuotaExceededError(message, 402);
case 404:
- return new PictifyError(message, 'TEMPLATE_NOT_FOUND', 404, body.details);
+ return new TemplateNotFoundError(message);
+ case 422:
+ return new RenderError(message, details, 422);
case 429:
+ if (safeBody.code === 'quota_exceeded') {
+ return new QuotaExceededError(message, 429);
+ }
return new RateLimitError(message);
- case 402:
- return new QuotaExceededError(message);
- case 400:
- return new PictifyError(message, 'INVALID_VARIABLES', 400, body.details);
- case 500:
- case 502:
- case 503:
- case 504:
- return new RenderError(message, body.details);
default:
- return new PictifyError(message, 'UNKNOWN_ERROR', statusCode, body.details);
+ if (statusCode >= 500) {
+ return new ServerError(message, statusCode, details);
+ }
+ if (statusCode >= 400) {
+ return new RenderError(message, details, statusCode);
+ }
+ return new PictifyError(message, 'UNKNOWN_ERROR', statusCode, details);
}
}
diff --git a/src/index.ts b/src/index.ts
index 4a3c8b9..9338e07 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,25 +1,29 @@
/**
* Pictify Node.js SDK
*
- * Official SDK for generating images from HTML templates using the Pictify API.
+ * Official SDK for generating images, PDFs, and GIFs from HTML, live URLs, and
+ * reusable templates using the Pictify API.
*
* @example
* ```typescript
* import { Pictify } from '@pictify/sdk';
*
- * const pictify = new Pictify({
- * apiKey: process.env.PICTIFY_API_KEY
+ * const pictify = new Pictify({ apiKey: process.env.PICTIFY_API_KEY! });
+ *
+ * // Raw HTML → image
+ * const image = await pictify.renderHtml({
+ * html: 'Hello World
',
+ * width: 1200,
+ * height: 630,
* });
+ * console.log(image.url);
*
+ * // Template → image
* const result = await pictify.render({
- * templateId: 'your-template-id',
- * variables: {
- * title: 'Hello World',
- * subtitle: 'Generated with Pictify'
- * }
+ * templateId: 'your-template-uid',
+ * variables: { title: 'Hello World' },
* });
- *
- * console.log('Image URL:', result.imageUrl);
+ * console.log(result.url);
* ```
*
* @packageDocumentation
diff --git a/src/types.ts b/src/types.ts
index 88ffc5c..835d8fa 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -13,76 +13,101 @@ export interface PictifyConfig {
}
/**
- * Image output formats supported by Pictify
+ * Image output formats supported by Pictify template renders.
+ * The `/image` endpoint additionally accepts these via `format` (mapped to `fileExtension`).
*/
-export type ImageFormat = 'png' | 'jpg' | 'jpeg' | 'webp' | 'gif' | 'pdf';
+export type ImageFormat = 'png' | 'jpg' | 'jpeg' | 'webp' | 'pdf';
/**
- * Options for rendering an image
+ * GIF quality presets accepted by the `/gif` endpoint.
*/
-export interface RenderOptions {
- /** The ID of the template to use */
- templateId: string;
- /** Variables to inject into the template */
- variables?: Record;
- /** Output format (default: png) */
+export type GifQuality = 'low' | 'medium' | 'high';
+
+/**
+ * Options for rendering an image directly from HTML (`POST /image`).
+ */
+export interface RenderHtmlOptions {
+ /** Raw HTML content to render */
+ html: string;
+ /**
+ * Optional CSS. Injected into the HTML inside a