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